Skip to content

feat(runtime-core): Refactor run agent block, and extract some implementation into the CLI, so that runtime can be reused in vscode extension#342

Open
tkislan wants to merge 37 commits into
mainfrom
tk/agent-vscode-abstract
Open

feat(runtime-core): Refactor run agent block, and extract some implementation into the CLI, so that runtime can be reused in vscode extension#342
tkislan wants to merge 37 commits into
mainfrom
tk/agent-vscode-abstract

Conversation

@tkislan
Copy link
Copy Markdown
Contributor

@tkislan tkislan commented Mar 13, 2026

Summary by CodeRabbit

  • Refactor

    • Reworked agent block execution flow to delegate block creation/execution and simplify returned results.
    • Switched OpenAI auth to a runtime-provided token and made agent event handlers awaitable.
    • Introduced notebook-context serialization and output-attachment helpers.
  • Tests

    • Expanded coverage for agent execution, streamed outputs, markdown/code insertion, and notebook-serialization behavior.
  • Chores

    • Added several new runtime utilities to public exports.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 13, 2026

📝 Walkthrough

Walkthrough

This change moves notebook and kernel mutation out of executeAgentBlock: the execution engine now constructs an AgentBlockContext (including openAiToken, mcpServers, serialized notebookContext, and helper functions addAndExecuteCodeBlock/addMarkdownBlock) and manages inserting/executing blocks and collecting their outputs. executeAgentBlock no longer mutates the notebook or returns added block IDs/outputs — it returns only finalOutput and may stream events (onAgentEvent is awaitable). Helpers for serializing notebook context and attaching collected outputs were added and exported.

Sequence Diagram(s)

sequenceDiagram
    participant ExecEngine as Execution Engine
    participant Agent as AgentHandler
    participant OpenAI as OpenAI API
    participant Kernel as Kernel
    participant Notebook as Notebook Store

    ExecEngine->>ExecEngine: build AgentBlockContext (openAiToken, mcpServers, notebookContext, addAndExecuteCodeBlock, addMarkdownBlock)
    ExecEngine->>Agent: executeAgentBlock(context, prompt)
    Agent->>OpenAI: stream completions (uses context.openAiToken)
    loop streaming
        OpenAI-->>Agent: partial tokens / events
        Agent->>ExecEngine: context.onAgentEvent(streamEvent) (awaited)
        alt request to insert code
            Agent->>ExecEngine: context.addAndExecuteCodeBlock(code)
            ExecEngine->>Notebook: create code block
            ExecEngine->>Kernel: kernel.execute(code)
            Kernel-->>ExecEngine: execution outputs
            ExecEngine->>ExecEngine: collect block outputs
            ExecEngine->>ExecEngine: emit onOutput / onBlockDone
        end
        alt request to insert markdown
            Agent->>ExecEngine: context.addMarkdownBlock(content)
            ExecEngine->>Notebook: create markdown block
            ExecEngine-->>Agent: acknowledge
        end
    end
    Agent-->>ExecEngine: finalOutput
    ExecEngine->>ExecEngine: finalize collected outputs (serialize/attach) and finish
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Updates Docs ⚠️ Warning PR introduces substantial API changes to runtime-core without updating documentation files that track the public API. Update packages/runtime-core/README.md and AGENTS.md to document refactored AgentBlockContext, new helper functions, and async callback support.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title accurately summarizes the main change: refactoring agent block execution and extracting implementation to enable runtime reuse in VS Code extension.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Review rate limit: 3/5 reviews remaining, refill in 21 minutes and 26 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 13, 2026

Codecov Report

❌ Patch coverage is 86.95652% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.80%. Comparing base (6d60842) to head (4c32bb0).

Files with missing lines Patch % Lines
packages/runtime-core/src/agent-handler.ts 68.75% 5 Missing ⚠️
packages/runtime-core/src/execution-engine.ts 96.66% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #342      +/-   ##
==========================================
+ Coverage   82.24%   82.80%   +0.56%     
==========================================
  Files         144      144              
  Lines        5868     5869       +1     
  Branches     1096     1095       -1     
==========================================
+ Hits         4826     4860      +34     
+ Misses       1042     1009      -33     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/runtime-core/src/execution-engine.test.ts (1)

833-847: 🛠️ Refactor suggestion | 🟠 Major

Cover the new agent helper path with one real runtime test.

Because executeAgentBlock is mocked at the module boundary, these cases never exercise AgentBlockContext.addAndExecuteCodeBlock, AgentBlockContext.addMarkdownBlock, or the async onAgentEvent flow added by this refactor. A regression in inserted-block reporting would still pass here.

As per coding guidelines, "Create comprehensive tests for all new features using Vitest" and "Test edge cases, error handling, and special characters in test files".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/runtime-core/src/execution-engine.test.ts` around lines 833 - 847,
Add one real runtime test that does not mock executeAgentBlock at the module
boundary so the new helper path is exercised: undo or avoid the module mock for
executeAgentBlock (remove or restore mockExecuteAgentBlock via
vi.unmock/vi.importActual) and call the actual execution flow with the
AGENT_FIXTURE so AgentBlockContext.addAndExecuteCodeBlock,
AgentBlockContext.addMarkdownBlock, and the async onAgentEvent flow run; then
assert that inserted block reporting (addedBlockIds/blockOutputs/finalOutput and
any events) matches expected values. Ensure the test triggers error/edge cases
and special characters from the fixture so the real helper logic and event
handling are covered rather than returning a mocked result.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/runtime-core/src/agent-handler.ts`:
- Around line 18-26: The AgentBlockContext.onAgentEvent type currently forces
Promise<void> which prevents sync handlers and leaves async errors unobserved;
change the onAgentEvent signature to accept void | Promise<void> in the
AgentBlockContext interface and then update the stream processing loop to await
the result of each call to onAgentEvent for events like "text_delta",
"reasoning_delta", "tool_called", and "tool_output" so both sync and async
handlers are supported and errors are propagated; also update the corresponding
ExecutionOptions type in execution-engine.ts to match the new void |
Promise<void> signature so callers compile.

In `@packages/runtime-core/src/execution-engine.ts`:
- Around line 249-287: addAndExecuteCodeBlock currently only records
outputs/executionCount and loses success/error details; change it to capture and
persist the full execution result from kernel.execute (including success,
outputs, executionCount, and any error or computed outputText) into blockOutputs
and collectedOutputs so that a failing result (result.success === false) is not
treated as success and thrown exceptions are converted to a unified result
object and passed to the same flow; specifically update the logic around
kernel.execute, the construction pushed to blockOutputs and collectedOutputs,
and the returned value so both tool responses and host callbacks (e.g.,
onBlockDone) receive the identical full result object rather than just
outputs/executionCount.

---

Outside diff comments:
In `@packages/runtime-core/src/execution-engine.test.ts`:
- Around line 833-847: Add one real runtime test that does not mock
executeAgentBlock at the module boundary so the new helper path is exercised:
undo or avoid the module mock for executeAgentBlock (remove or restore
mockExecuteAgentBlock via vi.unmock/vi.importActual) and call the actual
execution flow with the AGENT_FIXTURE so
AgentBlockContext.addAndExecuteCodeBlock, AgentBlockContext.addMarkdownBlock,
and the async onAgentEvent flow run; then assert that inserted block reporting
(addedBlockIds/blockOutputs/finalOutput and any events) matches expected values.
Ensure the test triggers error/edge cases and special characters from the
fixture so the real helper logic and event handling are covered rather than
returning a mocked result.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5a7edfc5-3620-40b0-95cd-66d1fc17f014

📥 Commits

Reviewing files that changed from the base of the PR and between 23d8b03 and dca6b0c.

📒 Files selected for processing (4)
  • packages/cli/src/commands/run.ts
  • packages/runtime-core/src/agent-handler.ts
  • packages/runtime-core/src/execution-engine.test.ts
  • packages/runtime-core/src/execution-engine.ts

Comment thread packages/runtime-core/src/execution-engine.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/runtime-core/src/agent-handler.ts (1)

237-239: 🧹 Nitpick | 🔵 Trivial

Consider explicit undefined handling.

JSON.stringify(undefined) returns undefined. Current nullish coalescing works, but explicit check is clearer.

💡 Optional clarity improvement
-        const outputStr = typeof toolOutput === 'string' ? toolOutput : (JSON.stringify(toolOutput) ?? '')
+        const outputStr = toolOutput === undefined ? '' : typeof toolOutput === 'string' ? toolOutput : JSON.stringify(toolOutput)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/runtime-core/src/agent-handler.ts` around lines 237 - 239, The code
that builds outputStr in agent-handler.ts should explicitly handle undefined
instead of relying on JSON.stringify(undefined) returning undefined; update the
logic in the block around toolOutput / outputStr (variables toolOutput and
outputStr, and the context.onAgentEvent call) so that if toolOutput is undefined
you set outputStr to '' (empty string), otherwise if it's a string keep it, and
otherwise set it to JSON.stringify(toolOutput); ensure the resulting outputStr
is always a string before calling context.onAgentEvent with type 'tool_output'.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/runtime-core/src/agent-handler.ts`:
- Line 104: Remove the leftover commented-out line "const outputs =
collectedOutputs.get(block.id)" in the agent handler; locate the commented
statement near the logic that references collectedOutputs and block.id (in the
function handling agent blocks) and delete it so no dead commented code remains,
ensuring there are no other unused commented variables left after the refactor.
- Around line 69-76: The map callback in blocks.map is shadowing and redundantly
re-reading collectedOutputs.get(block.id): remove the inner const outputs =
collectedOutputs.get(block.id) and the nested if, use the single outputs
variable obtained once (const outputs = collectedOutputs.get(block.id)) and,
when outputs is non-null and outputs.outputs.length > 0, return { ...block,
outputs: outputs.outputs }; otherwise return the original block — update the
callback in agent-handler.ts accordingly to eliminate the unreachable shadowed
declaration.

---

Outside diff comments:
In `@packages/runtime-core/src/agent-handler.ts`:
- Around line 237-239: The code that builds outputStr in agent-handler.ts should
explicitly handle undefined instead of relying on JSON.stringify(undefined)
returning undefined; update the logic in the block around toolOutput / outputStr
(variables toolOutput and outputStr, and the context.onAgentEvent call) so that
if toolOutput is undefined you set outputStr to '' (empty string), otherwise if
it's a string keep it, and otherwise set it to JSON.stringify(toolOutput);
ensure the resulting outputStr is always a string before calling
context.onAgentEvent with type 'tool_output'.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7e67686a-1083-41b3-bf9a-a0ba5b6b0e4e

📥 Commits

Reviewing files that changed from the base of the PR and between dca6b0c and f3c19fb.

📒 Files selected for processing (2)
  • packages/runtime-core/src/agent-handler.ts
  • packages/runtime-core/src/index.ts

Comment thread packages/runtime-core/src/agent-handler.ts Outdated
…ution and ensure proper handling of event callbacks
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/runtime-core/src/agent-handler.ts (1)

171-203: ⚠️ Potential issue | 🟠 Major

Cover MCP startup with the same cleanup path.

createMCPClient(...) and client.tools() still run before the current try/finally. If one server fails after earlier clients started, those stdio transports stay open. Track clients incrementally inside the cleanup scope.

Also applies to: 222-252

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/runtime-core/src/agent-handler.ts` around lines 171 - 203, The MCP
clients and their tool enumeration are created outside the try/finally, so a
failure partway leaves open stdio transports; move the whole client startup and
tools collection into the protected scope and track started clients
incrementally (e.g., push each created client from
mergedMcpConfig.map/createMCPClient into a local startedClients array as you
create them), then in the finally iterate startedClients and call the client's
shutdown method (client.close() or client.dispose() as available) to ensure
Experimental_StdioMCPTransport stdio pipes are cleaned up; also ensure
addCodeBlockTool and addMarkdownBlockTool registration
(context.addAndExecuteCodeBlock / context.addMarkdownBlock) happens after
clients/tools are successfully created so partial startups are cleaned up
consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/runtime-core/src/agent-handler.ts`:
- Around line 69-73: The mapping currently only overrides a block when
collectedOutputs.get(block.id) exists and has outputs.length > 0, which allows
stale block.outputs to persist when an entry exists with outputs: []; change the
condition so any presence of an entry in collectedOutputs for block.id (i.e.,
outputs != null / outputs !== undefined) causes the block to be returned with
outputs set to outputs.outputs (including the empty array); leave the original
block untouched only when there is no map entry for that id (refer to the
blocks.map callback, collectedOutputs.get(block.id), and the outputs.outputs
property).
- Around line 15-23: Update the callback result types so successful and failed
tool returns are serializable and include the execution output: replace
AddAndExecuteCodeBlockResult with a discriminated union where success: true
includes a required executionText (string) carrying stdout/stderr/results, and
success: false includes a SerializableError type (e.g., { message: string;
name?: string; stack?: string; code?: string }) instead of raw Error; do the
same for AddMarkdownBlockResult (e.g., success true includes renderedText or
content string). Then update the AgentBlockContext signatures for
addAndExecuteCodeBlock and addMarkdownBlock to return the new result types so
the agent always receives serializable output and meaningful error information.

---

Outside diff comments:
In `@packages/runtime-core/src/agent-handler.ts`:
- Around line 171-203: The MCP clients and their tool enumeration are created
outside the try/finally, so a failure partway leaves open stdio transports; move
the whole client startup and tools collection into the protected scope and track
started clients incrementally (e.g., push each created client from
mergedMcpConfig.map/createMCPClient into a local startedClients array as you
create them), then in the finally iterate startedClients and call the client's
shutdown method (client.close() or client.dispose() as available) to ensure
Experimental_StdioMCPTransport stdio pipes are cleaned up; also ensure
addCodeBlockTool and addMarkdownBlockTool registration
(context.addAndExecuteCodeBlock / context.addMarkdownBlock) happens after
clients/tools are successfully created so partial startups are cleaned up
consistently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 84e86e00-c5ff-4e1a-8d96-c6677c2b1b9e

📥 Commits

Reviewing files that changed from the base of the PR and between f3c19fb and f70ded4.

📒 Files selected for processing (1)
  • packages/runtime-core/src/agent-handler.ts

Comment thread packages/runtime-core/src/agent-handler.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/runtime-core/src/agent-handler.ts (2)

173-184: 🧹 Nitpick | 🔵 Trivial

MCP transport failures will reject the entire agent run.

If any MCP server fails to connect (bad command, missing binary), Promise.all rejects and no cleanup runs. Consider graceful degradation or wrapping each client creation.

♻️ Possible approach
-  const mcpClients = await Promise.all(
-    mergedMcpConfig.map(s =>
+  const mcpClientResults = await Promise.allSettled(
+    mergedMcpConfig.map(s =>
       createMCPClient({
         transport: new Experimental_StdioMCPTransport({
           command: s.command,
           args: s.args,
           env: resolveEnvVars(s.env),
           stderr: 'pipe',
         }),
       })
     )
   )
+  const mcpClients = mcpClientResults
+    .filter((r): r is PromiseFulfilledResult<Awaited<ReturnType<typeof createMCPClient>>> => r.status === 'fulfilled')
+    .map(r => r.value)
+  const failedCount = mcpClientResults.filter(r => r.status === 'rejected').length
+  if (failedCount > 0) {
+    context.onLog?.(`[agent] ${failedCount} MCP server(s) failed to connect`)
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/runtime-core/src/agent-handler.ts` around lines 173 - 184, The
current use of Promise.all over mergedMcpConfig to createMCPClient instances
means a single Experimental_StdioMCPTransport failure will reject the entire
agent run and skip cleanup; change the creation to handle per-client failures
(e.g., map mergedMcpConfig to individual try/catch or Promise.allSettled,
logging errors for failed createMCPClient calls and only collecting successful
clients into mcpClients) so that createMCPClient/Experimental_StdioMCPTransport
failures degrade gracefully and allow normal cleanup and operation with
remaining clients.

158-161: ⚠️ Potential issue | 🔴 Critical

Replace gpt-5 with an available model name.

The model 'gpt-5' does not exist. As of March 2025, the latest available OpenAI model is GPT-4.5. Update the fallback to use an actual model identifier.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/runtime-core/src/agent-handler.ts` around lines 158 - 161, The
fallback model string is invalid: update the model selection logic that sets the
modelName (using block.metadata.deepnote_agent_model and
process.env.OPENAI_MODEL) to use a real OpenAI model identifier instead of
"gpt-5" — replace that fallback with the current available model (e.g.,
"gpt-4.5") so modelName resolves to a valid model when deepnote_agent_model ===
'auto' and OPENAI_MODEL is unset.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@packages/runtime-core/src/agent-handler.ts`:
- Around line 173-184: The current use of Promise.all over mergedMcpConfig to
createMCPClient instances means a single Experimental_StdioMCPTransport failure
will reject the entire agent run and skip cleanup; change the creation to handle
per-client failures (e.g., map mergedMcpConfig to individual try/catch or
Promise.allSettled, logging errors for failed createMCPClient calls and only
collecting successful clients into mcpClients) so that
createMCPClient/Experimental_StdioMCPTransport failures degrade gracefully and
allow normal cleanup and operation with remaining clients.
- Around line 158-161: The fallback model string is invalid: update the model
selection logic that sets the modelName (using
block.metadata.deepnote_agent_model and process.env.OPENAI_MODEL) to use a real
OpenAI model identifier instead of "gpt-5" — replace that fallback with the
current available model (e.g., "gpt-4.5") so modelName resolves to a valid model
when deepnote_agent_model === 'auto' and OPENAI_MODEL is unset.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8a0a03fc-f6af-401c-820d-23b7f0abaa6f

📥 Commits

Reviewing files that changed from the base of the PR and between f70ded4 and c85033d.

📒 Files selected for processing (1)
  • packages/runtime-core/src/agent-handler.ts

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/runtime-core/src/agent-handler.ts (1)

171-205: ⚠️ Potential issue | 🟠 Major

Clean up MCP clients if setup fails.

Cleanup only starts at Line 244. If one createMCPClient() or client.tools() call rejects here, any earlier stdio transports stay alive because they were created before the try/finally scope. Move MCP setup into the cleanup scope, or close the clients accumulated so far before rethrowing.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/runtime-core/src/agent-handler.ts` around lines 171 - 205, The MCP
client setup can leak processes if createMCPClient() or client.tools() rejects;
ensure partial clients are closed on failure by wrapping the creation and
tools-fetching in a try/catch/finally and, on any error, iterate the
already-created mcpClients and call their shutdown/close/dispose method (e.g.,
client.close() or client.shutdown(), using the actual API) to stop underlying
Experimental_StdioMCPTransport processes before rethrowing; similarly, ensure
any created clients are closed in the existing cleanup/finally block so
mergedMcpConfig, createMCPClient, mcpClients, mcpToolSets and client.tools()
failures do not leave stray stdio transports running.
♻️ Duplicate comments (1)
packages/runtime-core/src/execution-engine.ts (1)

252-286: ⚠️ Potential issue | 🟠 Major

Keep the full result for agent-inserted code blocks.

Line 283 collapses successful executions to { success: true }, the failure path still returns raw Error objects, and Lines 332-339 emit every inserted block as success: true anyway. Thrown executions also never reach onBlockDone because nothing is recorded in blockOutputs. Keep one serializable execution result and reuse it for both the tool response and host callbacks.

Also applies to: 323-339

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/runtime-core/src/execution-engine.ts` around lines 252 - 286, The
addAndExecuteCodeBlock function currently discards successful execution details
and only returns {success: true} while errors return raw Error objects and
failed runs aren't recorded in blockOutputs/collectedOutputs, so change
addAndExecuteCodeBlock to capture the full, serializable execution result from
kernel.execute (including outputs and executionCount and a serializable
error/message on failure), store that same result into blockOutputs and
collectedOutputs for every run (success or failure), and return that single
unified result structure so the tool response and any host callbacks (e.g.,
onBlockDone consumers) consume the exact same serializable execution result.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/runtime-core/src/execution-engine.ts`:
- Around line 236-242: Replace direct reads of process.env for agent provider
settings with the runtime-provided environment in RuntimeConfig.env: use the
existing runtimeConfig.env (the env passed into the engine at RuntimeConfig.env)
to obtain OPENAI_API_KEY and related overrides instead of process.env (affecting
the apiKey check that currently reads process.env.OPENAI_API_KEY and the
forwarded context created around lines 310-315). Ensure the error message
reflects the actual env source and mentions available overrides (e.g.,
OPENAI_API_KEY, OPENAI_BASE_URL, model override keys) when missing, and update
the logic that builds the forwarded context to copy relevant keys from
runtimeConfig.env so embedders can supply base URL and model overrides without
mutating process state.

---

Outside diff comments:
In `@packages/runtime-core/src/agent-handler.ts`:
- Around line 171-205: The MCP client setup can leak processes if
createMCPClient() or client.tools() rejects; ensure partial clients are closed
on failure by wrapping the creation and tools-fetching in a try/catch/finally
and, on any error, iterate the already-created mcpClients and call their
shutdown/close/dispose method (e.g., client.close() or client.shutdown(), using
the actual API) to stop underlying Experimental_StdioMCPTransport processes
before rethrowing; similarly, ensure any created clients are closed in the
existing cleanup/finally block so mergedMcpConfig, createMCPClient, mcpClients,
mcpToolSets and client.tools() failures do not leave stray stdio transports
running.

---

Duplicate comments:
In `@packages/runtime-core/src/execution-engine.ts`:
- Around line 252-286: The addAndExecuteCodeBlock function currently discards
successful execution details and only returns {success: true} while errors
return raw Error objects and failed runs aren't recorded in
blockOutputs/collectedOutputs, so change addAndExecuteCodeBlock to capture the
full, serializable execution result from kernel.execute (including outputs and
executionCount and a serializable error/message on failure), store that same
result into blockOutputs and collectedOutputs for every run (success or
failure), and return that single unified result structure so the tool response
and any host callbacks (e.g., onBlockDone consumers) consume the exact same
serializable execution result.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 41513acb-7b98-4c35-b5d0-ecd19248eb98

📥 Commits

Reviewing files that changed from the base of the PR and between c85033d and 17ad3e5.

📒 Files selected for processing (3)
  • packages/cli/src/commands/run.ts
  • packages/runtime-core/src/agent-handler.ts
  • packages/runtime-core/src/execution-engine.ts

Comment thread packages/runtime-core/src/execution-engine.ts
Base automatically changed from agent-poc-2 to main March 19, 2026 09:11
coderabbitai[bot]
coderabbitai Bot previously approved these changes Mar 23, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
packages/runtime-core/src/execution-engine.test.ts (1)

839-843: ⚠️ Potential issue | 🟠 Major

Use imported types for agent context and mock results.

Mock executeAgentBlock still returns deprecated addedBlockIds and blockOutputs fields (found at lines 841–842, 1030, 1099, 1129, 1157, 1197, 1227, 1255, 1282, 1303, 1321, 1340, 1373). Also, helper contexts use Promise<unknown> instead of AddAndExecuteCodeBlockResult and AddMarkdownBlockResult (lines 1090, 1095, 1126, 1154, 1194, 1278, 1300, 1318, 1337, 1366–1367). The types are already imported—use them.

Example fixes
-      mockExecuteAgentBlock.mockResolvedValue({
-        finalOutput: 'Analysis complete.',
-        addedBlockIds: [],
-        blockOutputs: [],
-      })
+      mockExecuteAgentBlock.mockResolvedValue({
+        finalOutput: 'Analysis complete.',
+      })

-        let capturedContext: { addAndExecuteCodeBlock: (args: { code: string }) => Promise<unknown> } | null = null
+        let capturedContext: Pick<AgentBlockContext, 'addAndExecuteCodeBlock'> | null = null
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/runtime-core/src/execution-engine.test.ts` around lines 839 - 843,
Mocks and helper contexts are using deprecated fields and generic
Promise<unknown>; update mockExecuteAgentBlock.mockResolvedValue calls to return
objects matching the imported
AddAndExecuteCodeBlockResult/AddMarkdownBlockResult shapes (remove deprecated
addedBlockIds and blockOutputs fields) and annotate helper context variables to
use the imported AddAndExecuteCodeBlockResult and AddMarkdownBlockResult types
instead of Promise<unknown>; ensure executeAgentBlock-related mocks and any
helper functions (search for mockExecuteAgentBlock, executeAgentBlock, and the
helper context variables around lines where Promise<unknown> was used) use the
correct imported types so the test types align with current interfaces.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/runtime-core/src/execution-engine.test.ts`:
- Around line 923-943: The test currently relies on hard-coded fixture indexes
(analysisNotebook.blocks[3], [4] and toHaveLength(5)) which is brittle; update
the test to capture the initial block count (e.g. const initialCount =
analysisNotebook.blocks.length), assert the count increased by 2
(expect(analysisNotebook.blocks).toHaveLength(initialCount + 2)), and locate the
inserted blocks by their content or ids rather than by fixed indexes (use
Array.prototype.find to locate blocks where content === helperCode and content
=== helperMarkdown or match type + content), then assert those found blocks
equal the expected objects and that mockKernelClient.execute was called with
helperCode. Ensure any references to addedCodeBlock/addedMarkdownBlock are
replaced with the found blocks and keep assertions for addCodeResult and
addMarkdownResult.
- Around line 1165-1341: The test span around the
describe('context.addMarkdownBlock') block is not formatted per Biome and CI
will fail; run the Biome formatter (e.g., biome format from the repo root) to
reformat the changed test block involving mockExecuteAgentBlock, engine.start,
and engine.runProject according to biome.json, then re-stage the resulting edits
so the CI rediff passes.

---

Duplicate comments:
In `@packages/runtime-core/src/execution-engine.test.ts`:
- Around line 839-843: Mocks and helper contexts are using deprecated fields and
generic Promise<unknown>; update mockExecuteAgentBlock.mockResolvedValue calls
to return objects matching the imported
AddAndExecuteCodeBlockResult/AddMarkdownBlockResult shapes (remove deprecated
addedBlockIds and blockOutputs fields) and annotate helper context variables to
use the imported AddAndExecuteCodeBlockResult and AddMarkdownBlockResult types
instead of Promise<unknown>; ensure executeAgentBlock-related mocks and any
helper functions (search for mockExecuteAgentBlock, executeAgentBlock, and the
helper context variables around lines where Promise<unknown> was used) use the
correct imported types so the test types align with current interfaces.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9f68c9c8-bc40-4ea1-8248-bdebf47ddb80

📥 Commits

Reviewing files that changed from the base of the PR and between da7b1d0 and e35c26d.

📒 Files selected for processing (1)
  • packages/runtime-core/src/execution-engine.test.ts

Comment thread packages/runtime-core/src/execution-engine.test.ts
Comment thread packages/runtime-core/src/execution-engine.test.ts
tkislan and others added 4 commits April 27, 2026 13:07
…ete mock fields

Remove the unread `addedBlockIds: string[]` array in `runProject`'s agent-block
branch — `AgentBlockResult` no longer carries it and nothing else reads the
local. Also drop the matching `addedBlockIds: []` and `blockOutputs: []` from
every mock return in `execution-engine.test.ts` so they reflect the current
`AgentBlockResult` shape.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The agent-handler refactor (dca6b0c) was intended as a mechanical extraction
to make `addAndExecuteCodeBlock` and `addMarkdownBlock` injectable on
`AgentBlockContext`, so the runtime can be reused in a VS Code extension.
That extraction also (unintentionally) changed the tool-execute return shape
from formatted strings to a structured `{ success, error }` object, which:

- Hid the code's stdout/stderr from the LLM. The agent could no longer see
  its own output and could not iterate, because `Error` instances do not
  survive `JSON.stringify` and the success branch carried no output text.
- Broke the CLI's `startsWith('Execution failed')` status check in run.ts:
  `event.output` was now JSON like `{"success":false,"error":{}}` instead of
  the formatted string it was designed to parse, so failures rendered as
  green `[ok]` with raw JSON in the preview.

Drop the invented `AddAndExecuteCodeBlockResult` / `AddMarkdownBlockResult`
types and re-type the context callbacks as `Promise<string>`. The helper
bodies in `runProject` return the same strings the pre-PR `tool({execute})`
callbacks did (`Output:\n…`, `Execution failed:\n…`, `Execution error: …`,
`Markdown block added.`). CLI is unchanged from main and works again; the
LLM sees its own output again.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (5)
packages/runtime-core/src/execution-engine.ts (3)

72-72: ⚠️ Potential issue | 🟠 Major

Keep onAgentEvent compatible with sync handlers.

Line 72 narrows to Promise<void> only, but AgentBlockContext accepts void | Promise<void>. This is an avoidable breaking API mismatch.

#!/bin/bash
# Verify callback type mismatch between execution options and agent context
rg -n "onAgentEvent\\?: \\(event: AgentStreamEvent\\) =>" \
  packages/runtime-core/src/execution-engine.ts \
  packages/runtime-core/src/agent-handler.ts
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/runtime-core/src/execution-engine.ts` at line 72, The onAgentEvent
type in execution-engine.ts is too narrow (returns Promise<void>) and must match
AgentBlockContext's handler type to accept both sync and async handlers; update
the onAgentEvent signature to (event: AgentStreamEvent) => void | Promise<void>
(or reference the same type alias used by AgentBlockContext) so callers and
implementations can return either void or Promise<void>, and adjust any call
sites to handle non-Promise returns if necessary.

258-292: ⚠️ Potential issue | 🟠 Major

Don’t force added code blocks to success.

The helper drops success/error state, then Line 336 reports success: true for every inserted code block. Failures become misreported or invisible in onBlockDone.

Suggested patch
-          const blockOutputs: Array<{ blockId: string; outputs: unknown[]; executionCount: number | null }> = []
+          const blockOutputs: Array<{
+            blockId: string
+            success: boolean
+            outputs: unknown[]
+            executionCount: number | null
+            error?: Error
+          }> = []

...
-              blockOutputs.push({
+              blockOutputs.push({
                 blockId: newBlock.id,
+                success: result.success,
                 outputs: result.outputs,
                 executionCount: result.executionCount,
               })
...
             } catch (error) {
               const message = error instanceof Error ? error.message : String(error)
+              blockOutputs.push({
+                blockId: newBlock.id,
+                success: false,
+                outputs: [createErrorOutput(new Error(message))],
+                executionCount: null,
+                error: error instanceof Error ? error : new Error(message),
+              })
               return `Execution error: ${message}`
             }

...
-                success: true,
+                success: bo.success,
                 outputs: bo.outputs as IOutput[],
                 executionCount: bo.executionCount,
                 durationMs: 0,
+                ...(bo.error ? { error: bo.error } : {}),

Also applies to: 324-340

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/runtime-core/src/execution-engine.ts` around lines 258 - 292, The
addAndExecuteCodeBlock helper currently drops the execution success flag and
only returns text, causing inserted code blocks to be marked as successful
regardless of kernel result; update addAndExecuteCodeBlock so it propagates
result.success into all places that record execution state (specifically include
success in the objects pushed to blockOutputs and collectedOutputs and ensure
any code that later reads collectedOutputs or blockOutputs (e.g., onBlockDone)
uses that success flag), and keep the returned string message separate from the
boolean success so failures are not misreported as success.

243-249: ⚠️ Potential issue | 🟠 Major

Read agent auth/config from runtime config, not globals.

This path still hard-reads process.env, which breaks embedders that pass env via RuntimeConfig.env and avoid mutating process state.

Suggested patch
-          const apiKey = process.env.OPENAI_API_KEY
+          const apiKey = this.config.env?.OPENAI_API_KEY ?? process.env.OPENAI_API_KEY
           if (!apiKey) {
             throw new Error(
               'OPENAI_API_KEY environment variable is required for agent blocks.\n' +
                 'Set it to your OpenAI API key, or set OPENAI_BASE_URL for compatible providers.'
             )
           }

Also applies to: 311-314

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/runtime-core/src/execution-engine.ts` around lines 243 - 249, The
code is directly reading process.env.OPENAI_API_KEY/OPENAI_BASE_URL in the agent
auth path; update it to read from the runtime config's env instead (e.g.,
RuntimeConfig.env or the ExecutionEngine/runtime instance env object) so
embedders can supply env without mutating process.env. Replace uses of
process.env.OPENAI_API_KEY and process.env.OPENAI_BASE_URL with the
runtime-provided env lookup (and keep the same error message/throw behavior),
and apply the same change to the other occurrence mentioned (around the 311-314
block).
packages/runtime-core/src/agent-handler.ts (1)

150-166: ⚠️ Potential issue | 🟠 Major

Stop reading model/baseURL overrides from global env here.

OPENAI_BASE_URL and OPENAI_MODEL still come from process.env, so embedders can’t configure this cleanly through runtime context.

Suggested patch
 export interface AgentBlockContext {
   openAiToken: string
+  openAiBaseUrl?: string
+  openAiModel?: string
   mcpServers: McpServerConfig[]
   notebookContext: string
   addAndExecuteCodeBlock: (args: { code: string }) => Promise<string>
   addMarkdownBlock: (args: { content: string }) => Promise<string>
...
 }

 const openai = createOpenAI({
   apiKey: context.openAiToken,
-  baseURL: process.env.OPENAI_BASE_URL,
+  baseURL: context.openAiBaseUrl ?? process.env.OPENAI_BASE_URL,
 })

 const modelName =
   block.metadata.deepnote_agent_model !== 'auto'
     ? block.metadata.deepnote_agent_model
-    : (process.env.OPENAI_MODEL ?? 'gpt-5')
+    : (context.openAiModel ?? process.env.OPENAI_MODEL ?? 'gpt-5')
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/runtime-core/src/agent-handler.ts` around lines 150 - 166, The code
currently reads OPENAI_BASE_URL and OPENAI_MODEL from process.env which prevents
embedders from configuring these via the runtime context; update the logic in
the createOpenAI / model selection block to source baseURL and model name from
the runtime context instead of global env: replace process.env.OPENAI_BASE_URL
with context.openAiBaseUrl when setting baseURL and use context.openAiModel as
the fallback for modelName (keep the block.metadata.deepnote_agent_model
override and default 'gpt-5' only if context.openAiModel is undefined), and
ensure the branching that chooses between openai.chat(modelName) and
openai(modelName) uses the context-derived baseURL.
packages/runtime-core/src/execution-engine.test.ts (1)

927-941: ⚠️ Potential issue | 🟡 Minor

Avoid hard-coded fixture indexes in helper tests.

blocks[3] / blocks[4] and fixed lengths are still fixture-coupled and brittle. Assert via initial count + content/id lookup instead.

Suggested patch
-      const addedCodeBlock = analysisNotebook.blocks[3]
-      const addedMarkdownBlock = analysisNotebook.blocks[4]
+      const initialCount = AGENT_FIXTURE.project.notebooks
+        .find(notebook => notebook.name === 'Analysis')?.blocks.length ?? 0
+      const addedCodeBlock = analysisNotebook.blocks.find(
+        block => block.type === 'code' && block.content === helperCode
+      )
+      const addedMarkdownBlock = analysisNotebook.blocks.find(
+        block => block.type === 'markdown' && block.content === helperMarkdown
+      )
...
-      expect(analysisNotebook.blocks).toHaveLength(5)
+      expect(analysisNotebook.blocks).toHaveLength(initialCount + 2)

Also applies to: 998-1007

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/runtime-core/src/execution-engine.test.ts` around lines 927 - 941,
Replace brittle hard-coded fixture indexes and fixed length assertions by
recording the initial block count from analysisNotebook.blocks, performing a
content-based lookup for the new blocks (match content === helperCode and
content === helperMarkdown) and asserting the block count increased by 2 and the
found blocks are present; update assertions that reference
addedCodeBlock/addedMarkdownBlock to use the located blocks, keep existing
checks for summary.failedBlocks, agentContext?.notebookContext, addCodeResult
and addMarkdownResult, and retain the mockKernelClient.execute call assertion
(adjust its ordinal if needed relative to initial calls) — target symbols:
analysisNotebook.blocks, helperCode, helperMarkdown, addCodeResult,
addMarkdownResult, summary.failedBlocks, agentContext, and
mockKernelClient.execute.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@packages/runtime-core/src/agent-handler.ts`:
- Around line 150-166: The code currently reads OPENAI_BASE_URL and OPENAI_MODEL
from process.env which prevents embedders from configuring these via the runtime
context; update the logic in the createOpenAI / model selection block to source
baseURL and model name from the runtime context instead of global env: replace
process.env.OPENAI_BASE_URL with context.openAiBaseUrl when setting baseURL and
use context.openAiModel as the fallback for modelName (keep the
block.metadata.deepnote_agent_model override and default 'gpt-5' only if
context.openAiModel is undefined), and ensure the branching that chooses between
openai.chat(modelName) and openai(modelName) uses the context-derived baseURL.

In `@packages/runtime-core/src/execution-engine.test.ts`:
- Around line 927-941: Replace brittle hard-coded fixture indexes and fixed
length assertions by recording the initial block count from
analysisNotebook.blocks, performing a content-based lookup for the new blocks
(match content === helperCode and content === helperMarkdown) and asserting the
block count increased by 2 and the found blocks are present; update assertions
that reference addedCodeBlock/addedMarkdownBlock to use the located blocks, keep
existing checks for summary.failedBlocks, agentContext?.notebookContext,
addCodeResult and addMarkdownResult, and retain the mockKernelClient.execute
call assertion (adjust its ordinal if needed relative to initial calls) — target
symbols: analysisNotebook.blocks, helperCode, helperMarkdown, addCodeResult,
addMarkdownResult, summary.failedBlocks, agentContext, and
mockKernelClient.execute.

In `@packages/runtime-core/src/execution-engine.ts`:
- Line 72: The onAgentEvent type in execution-engine.ts is too narrow (returns
Promise<void>) and must match AgentBlockContext's handler type to accept both
sync and async handlers; update the onAgentEvent signature to (event:
AgentStreamEvent) => void | Promise<void> (or reference the same type alias used
by AgentBlockContext) so callers and implementations can return either void or
Promise<void>, and adjust any call sites to handle non-Promise returns if
necessary.
- Around line 258-292: The addAndExecuteCodeBlock helper currently drops the
execution success flag and only returns text, causing inserted code blocks to be
marked as successful regardless of kernel result; update addAndExecuteCodeBlock
so it propagates result.success into all places that record execution state
(specifically include success in the objects pushed to blockOutputs and
collectedOutputs and ensure any code that later reads collectedOutputs or
blockOutputs (e.g., onBlockDone) uses that success flag), and keep the returned
string message separate from the boolean success so failures are not misreported
as success.
- Around line 243-249: The code is directly reading
process.env.OPENAI_API_KEY/OPENAI_BASE_URL in the agent auth path; update it to
read from the runtime config's env instead (e.g., RuntimeConfig.env or the
ExecutionEngine/runtime instance env object) so embedders can supply env without
mutating process.env. Replace uses of process.env.OPENAI_API_KEY and
process.env.OPENAI_BASE_URL with the runtime-provided env lookup (and keep the
same error message/throw behavior), and apply the same change to the other
occurrence mentioned (around the 311-314 block).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8fba9ec3-d8d0-450a-9a67-9c796fbbdbbd

📥 Commits

Reviewing files that changed from the base of the PR and between e35c26d and f86a60d.

📒 Files selected for processing (3)
  • packages/runtime-core/src/agent-handler.ts
  • packages/runtime-core/src/execution-engine.test.ts
  • packages/runtime-core/src/execution-engine.ts

tkislan added 3 commits April 28, 2026 09:10
… handling

Changed the type of `onAgentEvent` in both `run.ts` and `execution-engine.ts` to support synchronous execution, improving the flexibility of event handling in the CLI and runtime core.
…eNotebookContextFromBlocks

Updated the type of notebookName in the serializeNotebookContextFromBlocks function to enforce it as a non-nullable string, simplifying the serialization logic by removing conditional checks for null values.
Added new serialization functions to improve handling of block outputs in notebooks. Introduced `serializeNotebookContextFromBlocks` and `createBlocksWithAttachedOutputsFromCollectedOutputs` to manage saved and collected outputs more effectively. Updated tests to cover various scenarios, ensuring only relevant outputs are serialized based on collected data.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (3)
packages/runtime-core/src/execution-engine.ts (2)

254-340: ⚠️ Potential issue | 🟠 Major

Added code block outcomes are reported incorrectly.

Line 336 always emits success: true for agent-inserted code blocks. If kernel.execute returns success: false, this reports the wrong status; if execute throws, no per-added-block completion is emitted.

Suggested diff
-          const blockOutputs: Array<{ blockId: string; outputs: unknown[]; executionCount: number | null }> = []
+          const blockOutputs: Array<{
+            blockId: string
+            success: boolean
+            outputs: IOutput[]
+            executionCount: number | null
+            error?: Error
+          }> = []
@@
-              blockOutputs.push({
+              blockOutputs.push({
                 blockId: newBlock.id,
+                success: result.success,
-                outputs: result.outputs,
+                outputs: result.outputs as IOutput[],
                 executionCount: result.executionCount,
               })
@@
             } catch (error) {
               const message = error instanceof Error ? error.message : String(error)
+              const executionError = error instanceof Error ? error : new Error(message)
+              blockOutputs.push({
+                blockId: newBlock.id,
+                success: false,
+                outputs: [createErrorOutput(executionError)],
+                executionCount: null,
+                error: executionError,
+              })
               return `Execution error: ${message}`
             }
@@
-                success: true,
-                outputs: bo.outputs as IOutput[],
+                success: bo.success,
+                outputs: bo.outputs,
                 executionCount: bo.executionCount,
                 durationMs: 0,
+                ...(bo.error ? { error: bo.error } : {}),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/runtime-core/src/execution-engine.ts` around lines 254 - 340, The
agent-added code blocks are always reported as successful because onBlockDone is
called with success: true and thrown executions aren't reported; update
addAndExecuteCodeBlock and the post-agent reporting to record the actual
execution result and failures: when kernel.execute resolves, push blockOutputs
with the real result.success and executionCount (and include outputs), and when
kernel.execute throws, push a failure entry to blockOutputs (include a traceback
or error message in outputs and executionCount=null); then when emitting
onBlockDone for each bo use the stored success flag (not hardcoded true) and
ensure thrown errors produce a corresponding onBlockDone call with success:
false so every added block gets a completion event.

243-249: ⚠️ Potential issue | 🟠 Major

Read agent API key from runtime config env, not only process globals.

Line 243 bypasses RuntimeConfig.env, which makes embedding/runtime reuse harder and forces host-level env mutation.

Suggested diff
-          const apiKey = process.env.OPENAI_API_KEY
+          const apiKey = this.config.env?.OPENAI_API_KEY ?? process.env.OPENAI_API_KEY
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/runtime-core/src/execution-engine.ts` around lines 243 - 249, The
code currently reads OPENAI_API_KEY from process.env (apiKey) which bypasses the
runtime config; update the lookup to first read from RuntimeConfig.env (e.g.,
runtimeConfig.env.OPENAI_API_KEY) and fall back to process.env.OPENAI_API_KEY so
embedded runtimes can supply keys without host env mutation; apply the same
pattern for OPENAI_BASE_URL if used, and update the error/throw logic that
references apiKey accordingly (look for the apiKey variable and the error
message block to change the source).
packages/runtime-core/src/execution-engine.test.ts (1)

927-941: 🧹 Nitpick | 🔵 Trivial

Avoid fixture-index assertions in this helper-flow test.

Line 927 and Line 928 (blocks[3], blocks[4]) plus Line 939 (toHaveLength(5)) make this test fragile to fixture edits. Assert relative growth and find inserted blocks by content/id instead.

Suggested diff
-      const addedCodeBlock = analysisNotebook.blocks[3]
-      const addedMarkdownBlock = analysisNotebook.blocks[4]
+      const initialCount = AGENT_FIXTURE.project.notebooks
+        .find(notebook => notebook.name === 'Analysis')
+        ?.blocks.length ?? 0
+      const addedCodeBlock = analysisNotebook.blocks.find(
+        block => block.type === 'code' && block.content === helperCode
+      )
+      const addedMarkdownBlock = analysisNotebook.blocks.find(
+        block => block.type === 'markdown' && block.content === helperMarkdown
+      )
@@
-      expect(analysisNotebook.blocks).toHaveLength(5)
+      expect(analysisNotebook.blocks).toHaveLength(initialCount + 2)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/runtime-core/src/execution-engine.test.ts` around lines 927 - 941,
The test currently relies on fixture-indexed assertions (blocks[3], blocks[4],
and toHaveLength(5)); change it to assert relative growth and locate inserted
blocks by content or id: record const beforeCount =
analysisNotebook.blocks.length before the helper flow, assert
analysisNotebook.blocks.length === beforeCount + 2 after, find the new code
block via analysisNotebook.blocks.find(b => b.type === 'code' && b.content ===
helperCode) and the new markdown block via .find(b => b.type === 'markdown' &&
b.content === helperMarkdown) (or match by inserted block id if available), then
assert those found blocks exist and contain the expected properties (replacing
addedCodeBlock/addedMarkdownBlock index usage), keep existing assertions for
summary.failedBlocks, agentContext?.notebookContext,
addCodeResult/addMarkdownResult, and mockKernelClient.execute expectations but
remove the fragile toHaveLength(5) and direct index accesses.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/runtime-core/src/agent-handler.ts`:
- Around line 15-23: The handler still reads provider config from process.env;
update AgentBlockContext to accept provider overrides (e.g., add optional fields
like openAiBaseUrl, openAiModel, openAiApiKey or a generic providerConfig
object) and modify the code paths that build provider clients (the logic around
where process.env is read at the block handler — references to openAi
token/URL/model usage) to prefer these AgentBlockContext values before falling
back to process.env; ensure functions that construct clients or call OpenAI
(identify the client creation/usage code that currently reads process.env at
lines around 150–163) pull from ctx.openAiBaseUrl/ctx.openAiModel or
ctx.integrations/providerConfig if present.

---

Duplicate comments:
In `@packages/runtime-core/src/execution-engine.test.ts`:
- Around line 927-941: The test currently relies on fixture-indexed assertions
(blocks[3], blocks[4], and toHaveLength(5)); change it to assert relative growth
and locate inserted blocks by content or id: record const beforeCount =
analysisNotebook.blocks.length before the helper flow, assert
analysisNotebook.blocks.length === beforeCount + 2 after, find the new code
block via analysisNotebook.blocks.find(b => b.type === 'code' && b.content ===
helperCode) and the new markdown block via .find(b => b.type === 'markdown' &&
b.content === helperMarkdown) (or match by inserted block id if available), then
assert those found blocks exist and contain the expected properties (replacing
addedCodeBlock/addedMarkdownBlock index usage), keep existing assertions for
summary.failedBlocks, agentContext?.notebookContext,
addCodeResult/addMarkdownResult, and mockKernelClient.execute expectations but
remove the fragile toHaveLength(5) and direct index accesses.

In `@packages/runtime-core/src/execution-engine.ts`:
- Around line 254-340: The agent-added code blocks are always reported as
successful because onBlockDone is called with success: true and thrown
executions aren't reported; update addAndExecuteCodeBlock and the post-agent
reporting to record the actual execution result and failures: when
kernel.execute resolves, push blockOutputs with the real result.success and
executionCount (and include outputs), and when kernel.execute throws, push a
failure entry to blockOutputs (include a traceback or error message in outputs
and executionCount=null); then when emitting onBlockDone for each bo use the
stored success flag (not hardcoded true) and ensure thrown errors produce a
corresponding onBlockDone call with success: false so every added block gets a
completion event.
- Around line 243-249: The code currently reads OPENAI_API_KEY from process.env
(apiKey) which bypasses the runtime config; update the lookup to first read from
RuntimeConfig.env (e.g., runtimeConfig.env.OPENAI_API_KEY) and fall back to
process.env.OPENAI_API_KEY so embedded runtimes can supply keys without host env
mutation; apply the same pattern for OPENAI_BASE_URL if used, and update the
error/throw logic that references apiKey accordingly (look for the apiKey
variable and the error message block to change the source).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 14bdc844-9080-42f0-9f10-3175fdc3baa3

📥 Commits

Reviewing files that changed from the base of the PR and between f86a60d and 0411383.

📒 Files selected for processing (4)
  • packages/runtime-core/src/agent-handler.test.ts
  • packages/runtime-core/src/agent-handler.ts
  • packages/runtime-core/src/execution-engine.test.ts
  • packages/runtime-core/src/execution-engine.ts

Comment thread packages/runtime-core/src/agent-handler.ts
@tkislan tkislan marked this pull request as ready for review April 28, 2026 10:00
@tkislan tkislan requested a review from a team as a code owner April 28, 2026 10:00
coderabbitai[bot]
coderabbitai Bot previously approved these changes Apr 28, 2026
jamesbhobbs and others added 2 commits April 30, 2026 14:43
Track success on blockOutputs entries and push on kernel.execute
rejection so blocks the agent inserted always get an onBlockDone event
matching their actual outcome. Wrap the agent call in try/finally so
added blocks are drained even when executeAgentBlock throws.

Also tighten add_code_block tool description and drop the redundant
post-loop collectedOutputs.set / unknown[] casts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@jamesbhobbs jamesbhobbs left a comment

Choose a reason for hiding this comment

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

@tkislan i suggest these minor improvements #369, happy to approve if you merge this in, feel free to edit the PR if you disagree esp the first file changed.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/runtime-core/src/agent-handler.ts`:
- Around line 66-73: The current map over blocks always resets any existing
block.outputs to [] unless there's a collectedOutputs entry; change this so only
blocks with a collectedOutputs entry are modified. In the blocks.map callback
that uses collectedOutputs.get(block.id), keep the branch that returns
{...block, outputs: collected.outputs} when collected is non-null, and otherwise
return the original block unchanged (remove the branch that forces outputs: []).
Update the logic around collectedOutputs and the blocks.map so persisted outputs
are preserved unless an override exists.

In `@packages/runtime-core/src/execution-engine.ts`:
- Around line 279-309: The current addAndExecuteCodeBlock() buffers outputs into
blockOutputs/collectedOutputs and only calls onOutput/onBlockDone after
kernel.execute returns, causing helper-block events to be delayed; change the
call to kernel.execute(...) to pass a per-output callback that forwards each
output immediately to the existing onOutput handler (including mapping
executionCount and blockId), and ensure you call onBlockDone (with success and
executionCount) as soon as that execute promise resolves or in the catch path
for errors (instead of waiting to push to blockOutputs only); update the same
pattern in the similar helper at the other location so both places wire
kernel.execute output streaming through onOutput and emit onBlockDone when the
helper finishes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2e32a20f-da8a-4051-b44e-b6bc8d4a2c2c

📥 Commits

Reviewing files that changed from the base of the PR and between 0411383 and 4c32bb0.

📒 Files selected for processing (3)
  • packages/runtime-core/src/agent-handler.ts
  • packages/runtime-core/src/execution-engine.test.ts
  • packages/runtime-core/src/execution-engine.ts

Comment thread packages/runtime-core/src/agent-handler.ts
Comment thread packages/runtime-core/src/execution-engine.ts
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.

3 participants