Skip to content

🤖 fix: render kernel-launched workflow runs as live cards in chat - #4001

Merged
ibetitsmike merged 4 commits into
mainfrom
mike/kernel-workflow-cards
Aug 29, 2026
Merged

🤖 fix: render kernel-launched workflow runs as live cards in chat#4001
ibetitsmike merged 4 commits into
mainfrom
mike/kernel-workflow-cards

Conversation

@ibetitsmike

Copy link
Copy Markdown
Contributor

Summary

Workflow runs launched inside code_execution (the persistent kernel) now render as live workflow timeline cards in the chat transcript, instead of falling back to a generic raw-JSON tool card inside the Code Execution card.

Background

Follow-up to #3987, which added live workflow cards for top-level workflow_run tool calls. Kernel-launched runs still rendered as raw JSON because three independent things broke down for nested calls:

  1. Tool call ID mismatch. ToolBridge generated its own synthetic ID for bridged execute() calls, while the QuickJS runtime recorded the nested transcript call under a different callId. workflow-run-attached (and other tool-emitted UI events such as task-created and live bash output) therefore targeted an ID no rendered card carried.
  2. Kernel bounding markers. Oversized nested args/results are replaced by a { __kernelBounded: true, bytes, preview } marker. The marker fails the canonical workflow_run args schema (so the call bounced to the generic renderer) and stripped the runId/status needed to re-fetch the durable run after reload.
  3. Top-level-only plumbing. StreamManager only attached workflow runs to top-level dynamic-tool parts, and WorkspaceStore's stale live-state sweep pruned nested call IDs mid-run.

Implementation

  • QuickJSRuntime hands the nested record's callId to ToolBridge through a synchronous clear-on-read window (IJSRuntime.takeActiveHostCallId); bridged execute() uses it as toolCallId, so tool-emitted UI events land on the transcript's nested record.
  • Kernel bounding now retains validated identity fields on markers: script_path on bounded workflow_run args, and runId/status on bounded workflow_run/workflow_resume results (via the new captureResultRetained hook). Workflow results stay bounded; nothing is exempted from the caps, and marker fields win on key collisions.
  • StreamManager.attachWorkflowRunToToolCall falls through to nested calls and persists the attachment on the nested record (flushed immediately), so a kernel-launched run's identity survives interruption/reload even when both args and result are markers.
  • WorkspaceStore.cleanupStaleLiveToolState also collects nested call IDs, so nested workflow/bash/advisor live state is no longer swept mid-run.
  • Rendering is marker-aware: getToolComponent routes bounded workflow_run args to WorkflowRunToolCall (render-only union; the canonical tool schema is unchanged), and the card recovers the durable run from the persisted nested attachment or the retained result identity.

Validation

  • New unit coverage at each layer: retained-field capture (ptc/types), bridged-ID handoff end to end through the kernel (code_execution), nested attachment persistence (streamManager), marker routing (getToolComponent), and marker-args + attachment/bounded-result rendering and recovery (WorkflowRunToolCall).
  • make static-check green on this head; targeted node + browser suites all pass on top of current main.

Risks

  • Touches the kernel record bounding path shared by all nested tool calls; a regression would show up as malformed nested tool records. Mitigated by keeping the marker shape authoritative (retained fields can never override __kernelBounded/bytes/preview) and by the success-bit preservation tests.
  • The synchronous call-ID handoff relies on the bridge consuming the ID before any await; the contract is documented on IJSRuntime.takeActiveHostCallId and regression-tested with sequential distinct-ID assertions.

Generated with xum • Model: anthropic:claude-fable-5 • Thinking: xhigh • Cost: $129.20

@ibetitsmike

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 14e2691a58

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/streamManager.ts Outdated
Comment thread src/node/services/streamManager.ts
Comment thread src/node/services/streamManager.ts Outdated
@chatgpt-codex-connector

This comment has been minimized.

@ibetitsmike

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

This comment has been minimized.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 916ee813df

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/streamManager.ts Outdated
Comment thread src/node/services/streamManager.ts
@ibetitsmike

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Breezy!

Reviewed commit: f4283b2c14

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector

This comment has been minimized.

…cards

# Conflicts:
#	src/browser/features/Tools/Shared/getToolComponent.test.ts
#	src/browser/features/Tools/Shared/getToolComponent.ts
#	src/node/services/streamManager.test.ts
@chatgpt-codex-connector

This comment has been minimized.

@ibetitsmike
ibetitsmike enabled auto-merge August 29, 2026 19:20
@ibetitsmike
ibetitsmike added this pull request to the merge queue Aug 29, 2026
Merged via the queue into main with commit c058353 Aug 29, 2026
36 of 39 checks passed
@ibetitsmike
ibetitsmike deleted the mike/kernel-workflow-cards branch August 29, 2026 19:58
mux-bot Bot added a commit that referenced this pull request Aug 29, 2026
…rker

#4001 added a shared isKernelBoundedMarker type guard in
src/common/utils/tools/kernelBoundedMarker.ts and wired it into the
renderer, but left the producer side spelling the same predicate by
hand: applyRetainedResultFields (added in that same commit) and the
pre-existing preserveSuccessBit each re-inlined the typeof/null/
__kernelBounded === true chain with its own cast. Both now call the
guard, making it the single definition of the marker test in
quickjsRuntime.ts and removing the file's last two marker casts.
mux-bot Bot added a commit that referenced this pull request Aug 29, 2026
…rker

#4001 added a shared isKernelBoundedMarker type guard in
src/common/utils/tools/kernelBoundedMarker.ts and wired it into the
renderer, but left the producer side spelling the same predicate by
hand: applyRetainedResultFields (added in that same commit) and the
pre-existing preserveSuccessBit each re-inlined the typeof/null/
__kernelBounded === true chain with its own cast. Both now call the
guard, making it the single definition of the marker test in
quickjsRuntime.ts and removing the file's last two marker casts.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant