Skip to content

feat(spike): agentic block poc#335

Closed
jamesbhobbs wants to merge 30 commits intomainfrom
agent-poc
Closed

feat(spike): agentic block poc#335
jamesbhobbs wants to merge 30 commits intomainfrom
agent-poc

Conversation

@jamesbhobbs
Copy link
Contributor

@jamesbhobbs jamesbhobbs commented Mar 4, 2026

Summary by CodeRabbit

  • New Features

    • Agent Blocks: run LLM-powered autonomous tasks inside notebooks with MCP server support and integration awareness.
    • CLI: new --prompt option to run agents standalone or append prompts to notebooks.
    • Streaming UX: real-time agent output, tool-call hints and progressive rendering.
  • Documentation

    • New agent block guides, CLI examples, env var docs, and demo notebook.
  • Tests

    • Expanded test coverage for agent execution and tooling.
  • Chores

    • Updated ignore patterns and spell-check dictionary.

jamesbhobbs and others added 11 commits March 3, 2026 11:50
Add --prompt CLI option to run an LLM agent block ad-hoc:
- `deepnote run --prompt "..."` creates a standalone in-memory notebook
- `deepnote run file.deepnote --prompt "..."` appends an LLM block

Thread database integration metadata through to the LLM agent system
prompt so it can discover and query configured integrations.

Update docs and skill references.

Made-with: Cursor
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 4, 2026

📝 Walkthrough

Walkthrough

This PR introduces Agent Block support: new agent block schemas and MCP server config, CLI --prompt flow (including in-memory prompt runs), runtime integration for agent execution and streaming events, a new agent-handler implementing OpenAI Agents tooling and MCP lifecycle, execution-engine changes to run agent blocks and surface agent events, new utilities for extracting output text, tests, docs, examples (demo agent notebook), and various small infra updates (.gitignore, cspell, deps, types).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLI as CLI (run)
    participant RunCmd as Run Command Handler
    participant ExecEngine as Execution Engine
    participant AgentHdlr as Agent Handler
    participant OpenAI as OpenAI Agents API
    participant MCPServer as MCP Server
    participant Kernel as Kernel Client

    User->>CLI: deepnote run --prompt "analyze data"
    CLI->>RunCmd: createRunAction(path, options)
    RunCmd->>RunCmd: setupProject(path, prompt)
    alt no path, prompt provided
        RunCmd->>RunCmd: createPromptOnlyFile(prompt)
    else path provided
        RunCmd->>RunCmd: appendAgentBlock(prompt)
    end
    RunCmd->>ExecEngine: runDeepnoteProject(file, options)

    loop per executable block
        ExecEngine->>ExecEngine: identify block type
        alt agent block
            ExecEngine->>AgentHdlr: executeAgentBlock(block, context)
            AgentHdlr->>AgentHdlr: resolveEnvVars(), buildSystemPrompt()
            AgentHdlr->>MCPServer: connect MCP servers
            AgentHdlr->>OpenAI: start agent with tools
            OpenAI-->>AgentHdlr: stream text_delta / tool_call
            alt tool_call add_code_block
                AgentHdlr->>Kernel: run code
                Kernel-->>AgentHdlr: outputs
                AgentHdlr->>ExecEngine: onAgentEvent(tool_output)
            else tool_call add_markdown_block
                AgentHdlr->>AgentHdlr: insert markdown block
            end
            AgentHdlr-->>ExecEngine: AgentBlockResult (finalOutput, addedBlockIds, blockOutputs)
            AgentHdlr->>MCPServer: cleanup
        else regular block
            ExecEngine->>Kernel: execute code block
            Kernel-->>ExecEngine: outputs
        end
    end

    ExecEngine-->>RunCmd: aggregated results
    RunCmd->>CLI: render outputs (streaming + final)
    CLI-->>User: display notebook and results
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 24.24% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Updates Docs ❓ Inconclusive Primary documentation in OSS repository comprehensively updated with new Agent Block sections, detailed references, and CLI options; private repository roadmap not verified. Verify that the deepnote/deepnote-internal private repository roadmap was also updated as planned.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title accurately reflects the main change: implementing an agentic/agent block feature as a proof-of-concept spike.

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


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

@codecov
Copy link

codecov bot commented Mar 4, 2026

Codecov Report

❌ Patch coverage is 55.30303% with 118 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.61%. Comparing base (770f211) to head (77b9f14).

Files with missing lines Patch % Lines
packages/runtime-core/src/agent-handler.ts 33.64% 71 Missing ⚠️
packages/cli/src/commands/run.ts 59.59% 39 Missing and 1 partial ⚠️
packages/blocks/src/output-text.ts 87.50% 3 Missing ⚠️
packages/mcp/src/tools/execution.ts 0.00% 2 Missing ⚠️
packages/runtime-core/src/execution-engine.ts 92.59% 2 Missing ⚠️

❌ Your patch status has failed because the patch coverage (55.30%) is below the target coverage (80.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #335      +/-   ##
==========================================
- Coverage   83.49%   82.61%   -0.89%     
==========================================
  Files         124      126       +2     
  Lines        7375     7582     +207     
  Branches     2046     2048       +2     
==========================================
+ Hits         6158     6264     +106     
- Misses       1217     1317     +100     
- Partials        0        1       +1     

☔ 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
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: 9

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

Inline comments:
In @.gitignore:
- Around line 1-38: The .gitignore contains duplicated entries (e.g., repeated
lines like "!packages/**/*.png", ".DS_Store", ".env", ".npmrc", ".pnpm-store",
".qlty/*", "*.png", "*.tsbuildinfo", "coverage", "dist", "examples/report.md",
snapshot patterns, and "node_modules")—remove the duplicate lines so each ignore
pattern appears only once; edit the .gitignore to deduplicate all identical
lines (keeping a single canonical occurrence of each pattern) and ensure negated
patterns (like !packages/**/*.png) remain correctly placed and not duplicated.

In `@cspell.json`:
- Line 73: Move the "agentic" entry in cspell.json to the proper alphabetical
position: remove the duplicate at its current location and insert "agentic"
before "Instantiator" (and therefore before "asname") so the list remains
alphabetized; update the array to contain "agentic" only in its correct spot.

In `@packages/blocks/src/deepnote-file/llm-block-schema.test.ts`:
- Around line 31-45: The test object passed to deepnoteFileSchema.safeParse is
missing required top-level fields "environment" and "execution", causing
spurious failures; update the object in llm-block-schema.test.ts (the one
assigned to result) to include minimal valid "environment" and "execution"
entries that match deepnoteFileSchema (e.g., provide the minimal shapes expected
by the schema such as an environment object and an execution object/array or
their empty/default forms) so the safeParse covers the intended fields without
failing for missing top-level properties.

In `@packages/cli/package.json`:
- Around line 57-58: Update the package.json dependency entry for "marked" from
"^15.0.12" to "^17.0.3" (the dependency key is "marked") and then run your
package manager (npm install or yarn install) to refresh lockfile and
node_modules; verify no breaking changes by running the test suite or a quick
markdown rendering check after installing.

In `@packages/cli/src/commands/run.ts`:
- Around line 998-1004: The call to marked.use(markedTerminal()) is mutating
global marked state on every block completion inside the llmStreamed handling;
move the initialization so it runs once (e.g., at module top-level or inside
runDeepnoteProject before streaming begins) instead of inside the callback that
checks llmStreamed and llmTextBuffer. Locate the marked.use(markedTerminal())
invocation near the llmStreamed/llmTextBuffer block and remove it from the
callback; add a single initialization call for marked.use(markedTerminal()) near
the module initialization or at the start of runDeepnoteProject so that
marked.parse(llmTextBuffer) can be used without repeatedly mutating state.
Ensure markedTerminal is still imported where used.

In `@packages/mcp/src/tools/writing.ts`:
- Around line 413-417: The code computes a boolean `candidate` by checking
`spec.type` against a hardcoded list; this duplicates logic from
`@deepnote/runtime-core`. Replace the inline list and startsWith checks with the
shared exported set `executableBlockTypeSet` (and any helper like
`isExecutableBlockType` if available) so `candidate` is determined by checking
membership in `executableBlockTypeSet` and preserving the `input-` prefix rule
if that isn't covered by the shared symbol; update references to `spec.type`,
`candidate`, and import `executableBlockTypeSet` from `@deepnote/runtime-core` to
ensure consistency.

In `@packages/runtime-core/src/execution-engine.ts`:
- Around line 183-188: The code currently records blockIndex when building
allExecutableBlocks (the push that sets blockIndex) which can become stale after
earlier LLM insertions; instead stop storing a fixed blockIndex and resolve the
current index at execution/insertion time by using
notebook.blocks.indexOf(block) (or compute the target insertion position
relative to the current notebook.blocks array) right before inserting
LLM-generated blocks; update any usage of llmBlockIndex (the variable passed at
line ~224 and the similar code around 213-225) to recompute the index from the
live notebook.blocks array so generated blocks are inserted in the correct,
up-to-date location.

In `@packages/runtime-core/src/llm-handler.ts`:
- Around line 167-169: The current loop that calls mcpServers.forEach(await
server.connect()) runs outside the try/finally so if one connect() throws,
previously connected servers are never cleaned up; fix by either moving the
connect loop into the surrounding try block before the finally cleanup, or
explicitly track successfully connected servers (e.g., push to a
connectedServers array as each server.connect() resolves) and in the
catch/finally iterate that array to call the servers' cleanup method (the
existing close/disconnect code used later). Apply the same pattern to the other
connect sequence referenced around the 303-347 range.
- Around line 192-203: The DeepnoteBlock construction uses an unsafe "as any"
cast; replace it by explicitly typing the code block using Extract to preserve
the discriminated union (e.g., use Extract<DeepnoteBlock, { type: 'code' }> when
creating newBlock) and do the same for the markdown variant at the other site
(use Extract<DeepnoteBlock, { type: 'markdown' }>); update the object literals
(the one assigned to newBlock and the one around line ~281) so their shape
matches the Extract type (fields like id, blockGroup, sortingKey from
generateSortingKey(insertIndex), type, content, metadata, executionCount,
outputs for code blocks; the appropriate fields for markdown) and remove the "as
any" casts.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6b35bb24-264b-479a-b040-9ae7befa5a2f

📥 Commits

Reviewing files that changed from the base of the PR and between b5b4b4c and d2c01a4.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (31)
  • .gitignore
  • cspell.json
  • examples/README.md
  • examples/demo-llm-block.deepnote
  • examples/snapshots/agentic-analysis-demo_demo-llm-agentic_latest.snapshot.deepnote
  • packages/blocks/src/blocks/executable-blocks.ts
  • packages/blocks/src/blocks/llm-blocks.test.ts
  • packages/blocks/src/blocks/llm-blocks.ts
  • packages/blocks/src/deepnote-file/deepnote-file-schema.ts
  • packages/blocks/src/deepnote-file/llm-block-schema.test.ts
  • packages/blocks/src/index.ts
  • packages/blocks/src/python-code.test.ts
  • packages/blocks/src/python-code.ts
  • packages/cli/README.md
  • packages/cli/package.json
  • packages/cli/src/cli.ts
  • packages/cli/src/commands/run.test.ts
  • packages/cli/src/commands/run.ts
  • packages/cli/src/marked-terminal.d.ts
  • packages/mcp/src/tools/writing.ts
  • packages/runtime-core/README.md
  • packages/runtime-core/package.json
  • packages/runtime-core/src/execution-engine.test.ts
  • packages/runtime-core/src/execution-engine.ts
  • packages/runtime-core/src/index.ts
  • packages/runtime-core/src/llm-handler.test.ts
  • packages/runtime-core/src/llm-handler.ts
  • skills/deepnote/references/blocks-llm.md
  • skills/deepnote/references/cli-run.md
  • skills/deepnote/references/schema.ts
  • test-fixtures/llm-block.deepnote

Comment on lines +31 to +45
const result = deepnoteFileSchema.safeParse({
version: '1.0.0',
metadata: { createdAt: '2026-01-01T00:00:00Z' },
project: {
id: 'test',
name: 'Test',
settings: {
mcpServers: [
{ name: 'test-server', command: 'python', args: ['-m', 'server'] },
{ name: 'another', command: 'npx', env: { KEY: 'val' } },
],
},
notebooks: [],
},
})
Copy link
Contributor

@coderabbitai coderabbitai bot Mar 4, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

safeParse test object is missing required top-level schema fields.

Line 31 builds a Deepnote file object without environment and execution, but deepnoteFileSchema requires them. This can make the test fail for the wrong reason.

Proposed fix
   it('validates project-level mcpServers in settings', () => {
-    const result = deepnoteFileSchema.safeParse({
-      version: '1.0.0',
-      metadata: { createdAt: '2026-01-01T00:00:00Z' },
-      project: {
-        id: 'test',
-        name: 'Test',
-        settings: {
-          mcpServers: [
-            { name: 'test-server', command: 'python', args: ['-m', 'server'] },
-            { name: 'another', command: 'npx', env: { KEY: 'val' } },
-          ],
-        },
-        notebooks: [],
-      },
-    })
+    const fixturePath = path.join(__dirname, '../../../../test-fixtures/llm-block.deepnote')
+    const base = deserializeDeepnoteFile(fs.readFileSync(fixturePath, 'utf-8'))
+    const result = deepnoteFileSchema.safeParse({
+      ...base,
+      project: {
+        ...base.project,
+        settings: {
+          ...base.project.settings,
+          mcpServers: [
+            { name: 'test-server', command: 'python', args: ['-m', 'server'] },
+            { name: 'another', command: 'npx', env: { KEY: 'val' } },
+          ],
+        },
+      },
+    })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/blocks/src/deepnote-file/llm-block-schema.test.ts` around lines 31 -
45, The test object passed to deepnoteFileSchema.safeParse is missing required
top-level fields "environment" and "execution", causing spurious failures;
update the object in llm-block-schema.test.ts (the one assigned to result) to
include minimal valid "environment" and "execution" entries that match
deepnoteFileSchema (e.g., provide the minimal shapes expected by the schema such
as an environment object and an execution object/array or their empty/default
forms) so the safeParse covers the intended fields without failing for missing
top-level properties.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Verified: the test passes as-is because environment and execution are both optional in deepnoteFileSchema (both use .optional()). No change needed.

Copy link
Contributor

Choose a reason for hiding this comment

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

@jamesbhobbs, thanks for verifying — my mistake. Good to know the test is valid as-is.


✏️ Learnings added
Learnt from: jamesbhobbs
Repo: deepnote/deepnote PR: 335
File: packages/blocks/src/deepnote-file/llm-block-schema.test.ts:31-45
Timestamp: 2026-03-04T15:29:44.758Z
Learning: In `packages/blocks/src/deepnote-file/deepnote-file-schema.ts`, both `environment` and `execution` are optional fields (`.optional()`) in `deepnoteFileSchema`. Tests using `deepnoteFileSchema.safeParse` do not need to supply these fields.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: CR
Repo: deepnote/deepnote PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-20T13:16:02.007Z
Learning: Applies to packages/blocks/src/**/*.{ts,tsx} : When making changes to `.deepnote` file format, schema, or block types, update `skills/deepnote/references/blocks-*.md` and `skills/deepnote/references/schema.ts`

Learnt from: jamesbhobbs
Repo: deepnote/deepnote PR: 18401
File: scripts/check-chromatic-enabled/pr-template.test.ts:1-6
Timestamp: 2025-09-14T18:25:57.101Z
Learning: In the deepnote/deepnote repository, colocated tests use Node's built-in test runner (node:test, node:assert/strict) rather than Jest.

Learnt from: saltenasl
Repo: deepnote/deepnote PR: 201
File: packages/blocks/src/deserialize-file/deepnote-file-schema.ts:7-8
Timestamp: 2026-01-08T12:52:57.703Z
Learning: In packages/blocks/src/deserialize-file/deepnote-file-schema.ts, the emptyContent helper uses z.preprocess(() => '', z.literal('').optional()) intentionally. The .optional() allows the TypeScript type to be optional even though the preprocessor always normalizes to ''. This pattern is preferred for maintaining type-level optionality while ensuring runtime normalization.

Learnt from: CR
Repo: deepnote/deepnote PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-20T13:16:02.007Z
Learning: Applies to **/*.test.{ts,tsx} : Follow existing test patterns in the codebase (see packages/blocks/src/blocks/*.test.ts)

Learnt from: CR
Repo: deepnote/deepnote PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-20T13:16:02.007Z
Learning: Applies to **/*.test.{ts,tsx} : Group related tests with `describe()` blocks in test files

Learnt from: dinohamzic
Repo: deepnote/deepnote PR: 227
File: packages/cli/src/commands/validate.ts:52-58
Timestamp: 2026-01-22T14:17:22.234Z
Learning: In the deepnote CLI validate command (packages/cli/src/commands/validate.ts), different JSON output shapes are intentional: `{ valid, path, issues }` for validation errors and `{ success, error }` for file resolution/runtime errors, to distinguish error classes.

Learnt from: CR
Repo: deepnote/deepnote PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-20T13:16:02.007Z
Learning: Applies to **/*.test.{ts,tsx} : For functions that generate code, test the exact output format

Learnt from: CR
Repo: deepnote/deepnote PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-20T13:16:02.007Z
Learning: Applies to **/*.test.{ts,tsx} : Test edge cases, error handling, and special characters in test files

Learnt from: CR
Repo: deepnote/deepnote PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-20T13:16:02.007Z
Learning: Applies to **/*.test.{ts,tsx} : Import test utilities from vitest: `import { describe, expect, it } from 'vitest'`

Learnt from: jankuca
Repo: deepnote/deepnote PR: 18415
File: libs/shared/src/deepnote-ai/ai-chat-schema.test.ts:1-5
Timestamp: 2025-09-16T13:05:15.049Z
Learning: In the deepnote project, while assert import is restricted in libs/shared/.eslintrc.json, jankuca confirms that assert is allowed in test files according to project conventions, overriding the ESLint restriction.

jamesbhobbs and others added 2 commits March 4, 2026 15:15
Addresses review comment by @coderabbitai.

Co-Authored-By: Claude <[email protected]>
- Deduplicate cspell.json "agentic" entry and fix alphabetical order
- Move marked.use(markedTerminal()) to module level in run.ts
- Resolve LLM block index at execution time in execution-engine.ts
- Move MCP server connect() inside try/finally in llm-handler.ts
- Replace `as any` casts with Extract types in llm-handler.ts
- Update marked to ^17.0.3

Co-Authored-By: Claude <[email protected]>
Copy link
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: 5

♻️ Duplicate comments (1)
.gitignore (1)

1-36: ⚠️ Potential issue | 🟡 Minor

Deduplicate .gitignore entries.

Most patterns are repeated twice. Keep one canonical copy of each entry; this looks like a merge artifact.

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

In @.gitignore around lines 1 - 36, The .gitignore contains duplicated lines
(e.g., repeated entries like !packages/**/*.png, .DS_Store, .env, .npmrc,
.pnpm-store, .qlty/* directories, *.png, *.tsbuildinfo, coverage, dist,
examples/report.md, examples/snapshots/*_T*.snapshot.deepnote, node_modules)
likely from a merge; clean up the file by removing duplicate entries so each
pattern appears only once, leaving a single canonical copy of each ignore rule
and preserving the existing semantics/order.
🤖 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/cli/package.json`:
- Around line 57-58: The dependency "marked" currently set to a v17 range
conflicts with "marked-terminal" v7.3.0's peer constraint (requires <16); update
the "marked" entry in package.json to a compatible v15 (or any <16) release to
satisfy the peer dependency, then reinstall/update the lockfile (npm/yarn/pnpm)
and run the test/build to ensure nothing breaks; target the "marked" and
"marked-terminal" dependency entries when making the change.

In `@packages/cli/src/commands/run.ts`:
- Around line 274-279: The current branch silently no-ops when
file.project.notebooks is empty; update the logic in run.ts so when path &&
options.prompt and file.project.notebooks.length === 0 you either emit a clear
warning (e.g., processLogger.warn or console.warn) and then create a new
notebook and push the created LLM block into it, or throw a descriptive error;
use the existing createLlmBlock(...) to build the block and ensure you add a new
notebook object with a blocks array (containing the created block) to
file.project.notebooks instead of silently doing nothing (refer to lastNotebook,
file.project.notebooks, and createLlmBlock in your change).

In `@packages/runtime-core/src/llm-handler.ts`:
- Around line 38-40: The generateSortingKey function returns
lexicographic-unfriendly keys like "a1"/"a10" causing misordering; update
generateSortingKey(index: number) to emit fixed-width numeric keys by
zero-padding the index to a constant width (e.g. pad to 6 or more digits) so
lexicographic sorting preserves numeric order—adjust the padding width to
support your maximum expected inserts.
- Around line 52-84: The notebook serialization in llm-handler.ts currently
inlines every block and full outputs (loop over notebook.blocks,
collectedOutputs.get(block.id), pushing block.content and outputs into lines)
which can exceed token limits; fix by implementing bounded serialization: limit
the number of blocks processed (e.g., stop after MAX_BLOCKS), truncate
block.content to MAX_CHARS_PER_BLOCK with an explicit "[truncated]" marker, cap
output size by only including the first N output entries and first M
lines/characters per output (replace large HTML/image outputs with placeholders
like "[HTML output truncated]" or "[Image omitted]"), and ensure error/metadata
fields are stringified safely; add or use constants (MAX_BLOCKS,
MAX_CHARS_PER_BLOCK, MAX_OUTPUT_ENTRIES, MAX_OUTPUT_CHARS) and apply these
limits inside the for (const block of notebook.blocks) and outputs loop to
prevent unbounded growth.
- Around line 129-142: The code currently throws if OPENAI_API_KEY is missing
even when OPENAI_BASE_URL is set; change the guard to only require apiKey when
OPENAI_BASE_URL is not provided (i.e., if (!apiKey &&
!process.env.OPENAI_BASE_URL) throw ...). When creating the OpenAI client (new
OpenAI), pass a safe dummy apiKey when baseURL is present because the SDK
expects an apiKey parameter (e.g., apiKey || '' or a short placeholder) so the
constructor receives a value but real auth uses the custom base URL; keep
baseURL set from process.env.OPENAI_BASE_URL and leave setTracingDisabled(true)
unchanged.

---

Duplicate comments:
In @.gitignore:
- Around line 1-36: The .gitignore contains duplicated lines (e.g., repeated
entries like !packages/**/*.png, .DS_Store, .env, .npmrc, .pnpm-store, .qlty/*
directories, *.png, *.tsbuildinfo, coverage, dist, examples/report.md,
examples/snapshots/*_T*.snapshot.deepnote, node_modules) likely from a merge;
clean up the file by removing duplicate entries so each pattern appears only
once, leaving a single canonical copy of each ignore rule and preserving the
existing semantics/order.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1073000f-1602-4d9a-adfa-febc424235a5

📥 Commits

Reviewing files that changed from the base of the PR and between d2c01a4 and b3490d7.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (6)
  • .gitignore
  • cspell.json
  • packages/cli/package.json
  • packages/cli/src/commands/run.ts
  • packages/runtime-core/src/execution-engine.ts
  • packages/runtime-core/src/llm-handler.ts

Comment on lines +52 to +84
for (const block of notebook.blocks) {
lines.push(`## Block [${block.type}] (id: ${block.id.slice(0, 8)})`)

if (block.content) {
lines.push('```')
lines.push(block.content)
lines.push('```')
}

const outputs = collectedOutputs.get(block.id)
if (outputs && outputs.outputs.length > 0) {
lines.push('### Output:')
for (const output of outputs.outputs) {
const out = output as Record<string, unknown>
if (out.output_type === 'stream' && typeof out.text === 'string') {
lines.push(out.text)
} else if (out.output_type === 'execute_result' || out.output_type === 'display_data') {
const data = out.data as Record<string, unknown> | undefined
if (data?.['text/plain']) {
lines.push(String(data['text/plain']))
} else if (data?.['text/html']) {
lines.push('[HTML output]')
} else if (data?.['image/png'] || data?.['image/jpeg']) {
lines.push('[Image output]')
}
} else if (out.output_type === 'error') {
lines.push(`Error: ${out.ename}: ${out.evalue}`)
}
}
}

lines.push('')
}
Copy link
Contributor

@coderabbitai coderabbitai bot Mar 4, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Notebook context serialization is unbounded.

This currently appends every block and full outputs. Large notebooks can blow token limits and degrade/abort runs.

Proposed fix
 export function serializeNotebookContext(
   file: DeepnoteFile,
   notebookIndex: number,
   collectedOutputs: Map<string, { outputs: unknown[]; executionCount: number | null }>
 ): string {
+  const MAX_BLOCKS = 50
+  const MAX_OUTPUT_CHARS = 4000
   const notebook = file.project.notebooks[notebookIndex]
   if (!notebook) return 'Empty notebook.'
@@
-  for (const block of notebook.blocks) {
+  for (const block of notebook.blocks.slice(-MAX_BLOCKS)) {
@@
-          lines.push(out.text)
+          lines.push(out.text.slice(0, MAX_OUTPUT_CHARS))
@@
-            lines.push(String(data['text/plain']))
+            lines.push(String(data['text/plain']).slice(0, MAX_OUTPUT_CHARS))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for (const block of notebook.blocks) {
lines.push(`## Block [${block.type}] (id: ${block.id.slice(0, 8)})`)
if (block.content) {
lines.push('```')
lines.push(block.content)
lines.push('```')
}
const outputs = collectedOutputs.get(block.id)
if (outputs && outputs.outputs.length > 0) {
lines.push('### Output:')
for (const output of outputs.outputs) {
const out = output as Record<string, unknown>
if (out.output_type === 'stream' && typeof out.text === 'string') {
lines.push(out.text)
} else if (out.output_type === 'execute_result' || out.output_type === 'display_data') {
const data = out.data as Record<string, unknown> | undefined
if (data?.['text/plain']) {
lines.push(String(data['text/plain']))
} else if (data?.['text/html']) {
lines.push('[HTML output]')
} else if (data?.['image/png'] || data?.['image/jpeg']) {
lines.push('[Image output]')
}
} else if (out.output_type === 'error') {
lines.push(`Error: ${out.ename}: ${out.evalue}`)
}
}
}
lines.push('')
}
for (const block of notebook.blocks.slice(-MAX_BLOCKS)) {
lines.push(`## Block [${block.type}] (id: ${block.id.slice(0, 8)})`)
if (block.content) {
lines.push('
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/runtime-core/src/llm-handler.ts` around lines 52 - 84, The notebook
serialization in llm-handler.ts currently inlines every block and full outputs
(loop over notebook.blocks, collectedOutputs.get(block.id), pushing
block.content and outputs into lines) which can exceed token limits; fix by
implementing bounded serialization: limit the number of blocks processed (e.g.,
stop after MAX_BLOCKS), truncate block.content to MAX_CHARS_PER_BLOCK with an
explicit "[truncated]" marker, cap output size by only including the first N
output entries and first M lines/characters per output (replace large HTML/image
outputs with placeholders like "[HTML output truncated]" or "[Image omitted]"),
and ensure error/metadata fields are stringified safely; add or use constants
(MAX_BLOCKS, MAX_CHARS_PER_BLOCK, MAX_OUTPUT_ENTRIES, MAX_OUTPUT_CHARS) and
apply these limits inside the for (const block of notebook.blocks) and outputs
loop to prevent unbounded growth.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's the user's choice.

Copy link
Contributor

Choose a reason for hiding this comment

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

@jamesbhobbs Understood, acknowledged.


🧠 Learnings used
Learnt from: jankuca
Repo: deepnote/deepnote PR: 18381
File: libs/shared-node/src/blocks/notebooks.ts:818-833
Timestamp: 2025-09-11T09:39:56.743Z
Learning: Do not suggest adding whereNull('deleted_at') filters to the following_block and first_block queries in the getSiblingBlockSortingKeyPair function in libs/shared-node/src/blocks/notebooks.ts, as deleted blocks should be included in these lookups for correct sorting behavior.

Learnt from: CR
Repo: deepnote/deepnote PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-20T13:16:02.007Z
Learning: Applies to packages/blocks/src/**/*.{ts,tsx} : When making changes to `.deepnote` file format, schema, or block types, update `skills/deepnote/references/blocks-*.md` and `skills/deepnote/references/schema.ts`

Copy link
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: 3

🤖 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/cli/src/commands/run.ts`:
- Around line 1003-1017: The current logic skips rendering final block outputs
when llmStreamed is set true for tool events but no text_delta arrives
(variables: llmStreamed, llmTextBuffer, result.outputs, renderOutput). Fix by
ensuring that after the streaming/text buffer handling you also render
result.outputs when they exist and haven't been printed: if llmStreamed is true
but llmTextBuffer is empty (or rendered was not written), iterate over
result.outputs and call renderOutput for each, and emit the trailing blank line
as done in the non-streamed branch; apply the same change to the other
equivalent block handling tool events so final outputs are never suppressed.
- Around line 248-253: The prompt-only run creates an in-memory convertedFile
with wasConverted: false which prevents materialization for --open; update the
convertedFile creation (where createPromptOnlyFile(...) is used and
convertedFile is assigned) to set wasConverted: true (or compute it from the
intent to open, e.g., options.open || true for prompt-only runs) so downstream
logic will write a temp file before upload/--open; ensure this change is applied
adjacent to the convertedFile assignment and consider using
absolutePath/workingDirectory as already computed so materialization succeeds
when isMachineOutput is false.
- Around line 193-208: The createLlmBlock function currently returns a
BlocksDeepnoteBlock and uses an unsafe "as any" cast; import the LlmBlock type
from "@deepnote/blocks", change the function signature to return LlmBlock (or
replace the return type with LlmBlock), remove the "as any" cast, and ensure the
constructed object matches the LlmBlock shape (id, blockGroup, sortingKey, type:
'llm', content, metadata, executionCount, outputs); adjust any property names if
needed so TypeScript accepts the object as LlmBlock without coercion.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: b8d50fd9-babe-4508-a2ee-3492116da4ea

📥 Commits

Reviewing files that changed from the base of the PR and between b3490d7 and 7a61e9e.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • packages/cli/package.json
  • packages/cli/src/commands/run.ts
  • packages/mcp/src/tools/writing.ts
  • packages/runtime-core/src/llm-handler.ts

… output suppression

- Import and use LlmBlock type to eliminate `as any` cast
- Set wasConverted: true for prompt-only runs so --open materializes temp file
- Change `else if (!llmStreamed)` to `else` so tool-only LLM runs still render outputs

Addresses review comments by @coderabbitai.

Co-Authored-By: Claude <[email protected]>
Copy link
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/cli/src/commands/run.ts`:
- Around line 472-474: The debug output currently prints safeOptions but only
masks token, exposing options.prompt; update the creation of safeOptions in
run.ts (where debug is called and safeOptions is built) to also redact any
prompt content by replacing options.prompt (and any similarly named prompt field
if present) with a placeholder like '[redacted]' before JSON.stringify; ensure
you still spread the rest of options so nothing else changes and keep using the
debug(`Options: ${JSON.stringify(safeOptions)}`) call.
- Around line 273-279: The code currently always uses lastNotebook to append the
created LLM block, causing prompts passed with --notebook to be added to the
wrong notebook; update the logic in the run command to pick the target notebook
based on the provided options.notebook (e.g., find the notebook from
file.project.notebooks by id or index matching options.notebook) and then call
createLlmBlock(options.prompt, targetNotebook.blocks.length) and push it into
targetNotebook.blocks; if options.notebook is provided but no matching notebook
is found, throw an explicit error (instead of falling back to the last notebook)
to prevent out-of-scope appends.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4e596c75-19b7-4c38-ba14-8b54a29b56fb

📥 Commits

Reviewing files that changed from the base of the PR and between 7a61e9e and 350b612.

📒 Files selected for processing (1)
  • packages/cli/src/commands/run.ts

Comment on lines +273 to +279
if (path && options.prompt) {
const lastNotebook = file.project.notebooks[file.project.notebooks.length - 1]
if (lastNotebook) {
lastNotebook.blocks.push(createLlmBlock(options.prompt, lastNotebook.blocks.length))
} else {
throw new Error('Cannot append prompt: file contains no notebooks')
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Append prompt to the selected execution notebook, not always the last one.

When --notebook is set, Line 276 still appends the prompt block to the last notebook. That can make the prompt block out-of-scope and effectively ignored.

Proposed fix
 if (path && options.prompt) {
-  const lastNotebook = file.project.notebooks[file.project.notebooks.length - 1]
-  if (lastNotebook) {
-    lastNotebook.blocks.push(createLlmBlock(options.prompt, lastNotebook.blocks.length))
+  const targetNotebook = options.notebook
+    ? file.project.notebooks.find(notebook => notebook.name === options.notebook)
+    : file.project.notebooks[file.project.notebooks.length - 1]
+
+  if (targetNotebook) {
+    targetNotebook.blocks.push(createLlmBlock(options.prompt, targetNotebook.blocks.length))
+  } else if (options.notebook) {
+    throw new Error(`Cannot append prompt: notebook "${options.notebook}" not found`)
   } else {
     throw new Error('Cannot append prompt: file contains no notebooks')
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/commands/run.ts` around lines 273 - 279, The code currently
always uses lastNotebook to append the created LLM block, causing prompts passed
with --notebook to be added to the wrong notebook; update the logic in the run
command to pick the target notebook based on the provided options.notebook
(e.g., find the notebook from file.project.notebooks by id or index matching
options.notebook) and then call createLlmBlock(options.prompt,
targetNotebook.blocks.length) and push it into targetNotebook.blocks; if
options.notebook is provided but no matching notebook is found, throw an
explicit error (instead of falling back to the last notebook) to prevent
out-of-scope appends.

Comment on lines +472 to 474
debug(`Running file: ${path ?? '(prompt-only)'}`)
const safeOptions = { ...options, token: options.token ? '[redacted]' : undefined }
debug(`Options: ${JSON.stringify(safeOptions)}`)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Redact prompt in debug logs.

Line 474 logs safeOptions, but only token is masked. Prompt text can contain sensitive data and is currently exposed in debug output.

Proposed fix
-const safeOptions = { ...options, token: options.token ? '[redacted]' : undefined }
+const safeOptions = {
+  ...options,
+  token: options.token ? '[redacted]' : undefined,
+  prompt: options.prompt ? '[redacted]' : undefined,
+}
 debug(`Options: ${JSON.stringify(safeOptions)}`)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/commands/run.ts` around lines 472 - 474, The debug output
currently prints safeOptions but only masks token, exposing options.prompt;
update the creation of safeOptions in run.ts (where debug is called and
safeOptions is built) to also redact any prompt content by replacing
options.prompt (and any similarly named prompt field if present) with a
placeholder like '[redacted]' before JSON.stringify; ensure you still spread the
rest of options so nothing else changes and keep using the debug(`Options:
${JSON.stringify(safeOptions)}`) call.

Copy link
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: 7

♻️ Duplicate comments (1)
packages/runtime-core/src/agent-handler.ts (1)

129-134: ⚠️ Potential issue | 🟠 Major

OPENAI_BASE_URL path is currently unreachable due to the API key guard.

Line 130 hard-fails without OPENAI_API_KEY, but Lines 132-134 state OPENAI_BASE_URL is an alternative. The contract is inconsistent; please align behavior and message.

💡 Proposed fix
-  const apiKey = process.env.OPENAI_API_KEY
-  if (!apiKey) {
+  const baseURL = process.env.OPENAI_BASE_URL
+  const apiKey = process.env.OPENAI_API_KEY ?? (baseURL ? 'unused' : undefined)
+  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.'
+      'OPENAI_API_KEY environment variable is required for agent blocks when OPENAI_BASE_URL is not set.'
     )
   }
@@
   const client = new OpenAI({
     apiKey,
-    baseURL: process.env.OPENAI_BASE_URL,
+    baseURL,
   })
In the latest openai-node SDK, when a custom `baseURL` is set, is an `apiKey` still required, and is a placeholder key (e.g. "unused") supported for OpenAI-compatible providers?
🤖 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 129 - 134, The
current guard reads process.env.OPENAI_API_KEY into apiKey and throws if
missing, but the error text claims OPENAI_BASE_URL is an alternative; change the
guard to first check process.env.OPENAI_BASE_URL and only require OPENAI_API_KEY
when OPENAI_BASE_URL is not set (or allow a placeholder key when base URL is
provided if your SDK requires any value). Update the throw to reference both
variables clearly and modify the check around the apiKey variable in
agent-handler.ts (the apiKey constant and the thrown Error) so the code path
that uses OPENAI_BASE_URL is reachable and the message accurately reflects the
required configuration.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@examples/demo-agent-block.deepnote`:
- Around line 91-95: Update the npx invocation to pin the MCP server package to
a specific safe release: replace the unpinned package reference
'@modelcontextprotocol/server-filesystem' used in the npx args with a versioned
package specifier such as '@modelcontextprotocol/[email protected]'
(or a newer secure release) so the runtime installs a known, patched version;
locate the invocation that includes npx and the args array containing
'@modelcontextprotocol/server-filesystem' and './examples' and update that
package token accordingly.

In `@packages/blocks/src/blocks/agent-blocks.test.ts`:
- Around line 5-75: Add a negative test that asserts deepnoteBlockSchema rejects
an agent block when MCP server config is malformed: call
deepnoteBlockSchema.safeParse with an agent block where
metadata.deepnote_mcp_servers contains an invalid entry (e.g., missing the
required name field or wrong type for command), then assert
expect(result.success).toBe(false) and check result.error.issues includes the
path to metadata.deepnote_mcp_servers[0].name (or the offending field) to ensure
validation fails for that field; add this as a new it() test alongside the
existing agent tests.

In `@packages/blocks/src/deepnote-file/deepnote-file-schema.ts`:
- Around line 272-274: The deepnote_max_iterations schema currently uses
z.number() which allows negatives and fractions; change the field definition for
deepnote_max_iterations to enforce a positive integer (e.g., use
z.number().int().min(1).default(10)) so only valid iteration counts are accepted
by the schema (update any tests/consumers that assume numeric type if needed).

In `@packages/cli/README.md`:
- Line 149: Update the `--prompt <text>` option description in the CLI README to
explicitly state that invoking this option will append or create an agent block
in the notebook (i.e., it mutates notebook state) rather than just “runs” an
agent block; locate the table entry for `--prompt <text>` in the README and
modify the right-hand description to include wording like “appends/creates an
agent block in the notebook (mutates notebook state); requires `OPENAI_API_KEY`”
so the side effect is clear.
- Around line 184-189: The README examples currently show inline secrets (the
commands containing "OPENAI_API_KEY=sk-... deepnote run my-project.deepnote
--prompt ..." and "OPENAI_API_KEY=sk-... deepnote run --prompt ..."); update
those examples to demonstrate exporting the key once (e.g., use a single-line
export instruction) and then run the deepnote run commands without embedding the
key, and add a brief note warning against pasting API keys into command history.

In `@packages/cli/src/commands/run.test.ts`:
- Around line 2536-2560: Add a new test case that uses setupSuccessfulRun(),
calls action(...) with a prompt containing quotes, newlines and unicode (e.g.
include " and \n and emojis) both for the in-memory path (undefined) and/or with
HELLO_WORLD_FILE, then assert mockRunProject was called once and inspect the
created file from mockRunProject.mock.calls[0] (same pattern as the existing
tests) to verify the last notebook block's type is 'agent' and its content
exactly matches the special-character prompt string; use the same helpers
(action, mockRunProject, setupSuccessfulRun, HELLO_WORLD_FILE) and assertions
style as the two existing tests to catch escaping/parsing regressions.

In `@packages/cli/src/commands/run.ts`:
- Around line 249-251: The synthetic prompt path uses process.cwd() instead of
the resolved execution cwd, so change the creation of absolutePath to use the
already-computed workingDirectory variable; update the expression that builds
absolutePath (used when constructing convertedFile with originalPath) to
join(workingDirectory, 'prompt.deepnote') so that --cwd is respected for
prompt-only synthetic paths (refer to variables absolutePath, workingDirectory,
convertedFile, file, originalPath).

---

Duplicate comments:
In `@packages/runtime-core/src/agent-handler.ts`:
- Around line 129-134: The current guard reads process.env.OPENAI_API_KEY into
apiKey and throws if missing, but the error text claims OPENAI_BASE_URL is an
alternative; change the guard to first check process.env.OPENAI_BASE_URL and
only require OPENAI_API_KEY when OPENAI_BASE_URL is not set (or allow a
placeholder key when base URL is provided if your SDK requires any value).
Update the throw to reference both variables clearly and modify the check around
the apiKey variable in agent-handler.ts (the apiKey constant and the thrown
Error) so the code path that uses OPENAI_BASE_URL is reachable and the message
accurately reflects the required configuration.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 944be19d-2463-487e-89e0-eec961035ee8

📥 Commits

Reviewing files that changed from the base of the PR and between 350b612 and ef0d6fc.

📒 Files selected for processing (24)
  • examples/README.md
  • examples/demo-agent-block.deepnote
  • examples/snapshots/agentic-analysis-demo_demo-agent-agentic_latest.snapshot.deepnote
  • packages/blocks/src/blocks/agent-blocks.test.ts
  • packages/blocks/src/blocks/agent-blocks.ts
  • packages/blocks/src/blocks/executable-blocks.ts
  • packages/blocks/src/deepnote-file/agent-block-schema.test.ts
  • packages/blocks/src/deepnote-file/deepnote-file-schema.ts
  • packages/blocks/src/index.ts
  • packages/blocks/src/python-code.test.ts
  • packages/blocks/src/python-code.ts
  • packages/cli/README.md
  • packages/cli/src/commands/run.test.ts
  • packages/cli/src/commands/run.ts
  • packages/runtime-core/README.md
  • 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
  • packages/runtime-core/src/index.ts
  • skills/deepnote/references/blocks-agent.md
  • skills/deepnote/references/cli-run.md
  • skills/deepnote/references/schema.ts
  • test-fixtures/agent-block.deepnote

Comment on lines +91 to +95
command: npx
args:
- '-y'
- '@modelcontextprotocol/server-filesystem'
- ./examples
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

@modelcontextprotocol/server-filesystem npm latest version security advisories

💡 Result:

  • Latest npm version (tagged “Version” by jsDelivr / mirrored on UNPKG): 2026.1.14. [1], [5]
  • Known security advisories (high severity) for @modelcontextprotocol/server-filesystem:
  • Fix version: advisories recommend upgrading to 2025.7.1 or higher (so 2026.1.14 is beyond the fix line). [4]

Commands

npm view `@modelcontextprotocol/server-filesystem` version
npm audit
npm i `@modelcontextprotocol/server-filesystem`@latest

Sources: jsDelivr package page [1], GitHub security advisories list [2], GitLab advisory for CVE-2025-53110 [3], Vulert/NVD-linked CVE-2025-53109 summary [4], UNPKG listing for 2026.1.14 [5].


Pin the MCP server package to a specific secure version.

npx -y @modelcontextprotocol/server-filesystem`` without a pinned version pulls the latest at runtime, risking outdated or vulnerable versions. Two high-severity path validation CVEs (GHSA-hc55-p739-j48w, GHSA-q66q-fx2p-7w4m) affect versions before 2025.7.1. Pin to 2026.1.14 or newer.

Suggested fix
-                  - '@modelcontextprotocol/server-filesystem'
+                  - '@modelcontextprotocol/[email protected]'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/demo-agent-block.deepnote` around lines 91 - 95, Update the npx
invocation to pin the MCP server package to a specific safe release: replace the
unpinned package reference '@modelcontextprotocol/server-filesystem' used in the
npx args with a versioned package specifier such as
'@modelcontextprotocol/[email protected]' (or a newer secure release)
so the runtime installs a known, patched version; locate the invocation that
includes npx and the args array containing
'@modelcontextprotocol/server-filesystem' and './examples' and update that
package token accordingly.

Comment on lines +5 to +75
describe('agent block schema', () => {
it('parses a minimal agent block', () => {
const result = deepnoteBlockSchema.safeParse({
id: 'abc123',
blockGroup: 'grp123',
sortingKey: 'a0',
type: 'agent',
content: 'Analyze the data',
metadata: {},
executionCount: null,
outputs: [],
})

expect(result.success).toBe(true)
if (result.success) {
expect(result.data.type).toBe('agent')
expect(result.data.content).toBe('Analyze the data')
}
})

it('applies default metadata values', () => {
const result = deepnoteBlockSchema.safeParse({
id: 'abc123',
blockGroup: 'grp123',
sortingKey: 'a0',
type: 'agent',
content: 'test prompt',
executionCount: null,
outputs: [],
})

expect(result.success).toBe(true)
if (result.success && result.data.type === 'agent') {
expect(result.data.metadata.deepnote_model).toBe('auto')
expect(result.data.metadata.deepnote_max_iterations).toBe(10)
}
})

it('parses agent block with MCP servers', () => {
const result = deepnoteBlockSchema.safeParse({
id: 'abc123',
blockGroup: 'grp123',
sortingKey: 'a0',
type: 'agent',
content: 'query the db',
metadata: {
deepnote_model: 'gpt-4o-mini',
deepnote_max_iterations: 5,
deepnote_mcp_servers: [
{
name: 'postgres',
command: 'npx',
args: ['-y', '@modelcontextprotocol/server-postgres'],
// biome-ignore lint/suspicious/noTemplateCurlyInString: testing env var reference syntax
env: { DATABASE_URL: '${DATABASE_URL}' },
},
],
},
executionCount: null,
outputs: [],
})

expect(result.success).toBe(true)
if (result.success && result.data.type === 'agent') {
expect(result.data.metadata.deepnote_model).toBe('gpt-4o-mini')
expect(result.data.metadata.deepnote_max_iterations).toBe(5)
expect(result.data.metadata.deepnote_mcp_servers).toHaveLength(1)
expect(result.data.metadata.deepnote_mcp_servers?.[0]?.name).toBe('postgres')
}
})
})
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add at least one invalid-schema assertion for agent metadata.

Current cases are all success-path. Add a failing MCP config case so schema regressions in validation are caught.

Suggested negative test
 describe('agent block schema', () => {
+  it('rejects invalid MCP server config', () => {
+    const result = deepnoteBlockSchema.safeParse({
+      id: 'abc123',
+      blockGroup: 'grp123',
+      sortingKey: 'a0',
+      type: 'agent',
+      content: 'query the db',
+      metadata: {
+        deepnote_mcp_servers: [{ name: 'postgres' }], // missing required command
+      },
+      executionCount: null,
+      outputs: [],
+    })
+
+    expect(result.success).toBe(false)
+  })
+
   it('parses a minimal agent block', () => {

As per coding guidelines: "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/blocks/src/blocks/agent-blocks.test.ts` around lines 5 - 75, Add a
negative test that asserts deepnoteBlockSchema rejects an agent block when MCP
server config is malformed: call deepnoteBlockSchema.safeParse with an agent
block where metadata.deepnote_mcp_servers contains an invalid entry (e.g.,
missing the required name field or wrong type for command), then assert
expect(result.success).toBe(false) and check result.error.issues includes the
path to metadata.deepnote_mcp_servers[0].name (or the offending field) to ensure
validation fails for that field; add this as a new it() test alongside the
existing agent tests.

Comment on lines +272 to +274
deepnote_model: z.string().default('auto'),
deepnote_max_iterations: z.number().default(10),
deepnote_mcp_servers: z.array(mcpServerSchema).optional(),
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Constrain deepnote_max_iterations to valid iteration semantics.

z.number() accepts negative and fractional values. That can propagate invalid iteration counts into agent execution behavior.

Suggested fix
-      deepnote_max_iterations: z.number().default(10),
+      deepnote_max_iterations: z.number().int().min(1).default(10),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
deepnote_model: z.string().default('auto'),
deepnote_max_iterations: z.number().default(10),
deepnote_mcp_servers: z.array(mcpServerSchema).optional(),
deepnote_model: z.string().default('auto'),
deepnote_max_iterations: z.number().int().min(1).default(10),
deepnote_mcp_servers: z.array(mcpServerSchema).optional(),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/blocks/src/deepnote-file/deepnote-file-schema.ts` around lines 272 -
274, The deepnote_max_iterations schema currently uses z.number() which allows
negatives and fractions; change the field definition for deepnote_max_iterations
to enforce a positive integer (e.g., use z.number().int().min(1).default(10)) so
only valid iteration counts are accepted by the schema (update any
tests/consumers that assume numeric type if needed).

| `--block <id>` | Run only the specified block | all blocks |
| `-i, --input <key=val>` | Set input variable value (can be repeated) | |
| `--list-inputs` | List input variables without running | `false` |
| `--prompt <text>` | Run an LLM agent block with the given prompt (requires `OPENAI_API_KEY`) | |
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Call out file mutation in the option description.

Line 149 should explicitly say this appends/creates an agent block (mutates notebook state), not just “runs” one. That side effect is easy to miss in the table view.

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

In `@packages/cli/README.md` at line 149, Update the `--prompt <text>` option
description in the CLI README to explicitly state that invoking this option will
append or create an agent block in the notebook (i.e., it mutates notebook
state) rather than just “runs” an agent block; locate the table entry for
`--prompt <text>` in the README and modify the right-hand description to include
wording like “appends/creates an agent block in the notebook (mutates notebook
state); requires `OPENAI_API_KEY`” so the side effect is clear.

Comment on lines +184 to 189
# Run an agent with a prompt (appends an agent block to the file)
OPENAI_API_KEY=sk-... deepnote run my-project.deepnote --prompt "Analyze the sales data"

# Run an agent block standalone (no file needed)
OPENAI_API_KEY=sk-... deepnote run --prompt "Write a hello world script"
```
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid inline secret usage in examples.

Lines 185 and 188 normalize pasting API keys into command history. Prefer export OPENAI_API_KEY=... once, then run commands without embedding secrets inline.

Suggested doc tweak
-# Run an agent with a prompt (appends an agent block to the file)
-OPENAI_API_KEY=sk-... deepnote run my-project.deepnote --prompt "Analyze the sales data"
+# Run an agent with a prompt (appends an agent block to the file)
+export OPENAI_API_KEY="sk-..."
+deepnote run my-project.deepnote --prompt "Analyze the sales data"

-# Run an agent block standalone (no file needed)
-OPENAI_API_KEY=sk-... deepnote run --prompt "Write a hello world script"
+# Run an agent block standalone (no file needed)
+deepnote run --prompt "Write a hello world script"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Run an agent with a prompt (appends an agent block to the file)
OPENAI_API_KEY=sk-... deepnote run my-project.deepnote --prompt "Analyze the sales data"
# Run an agent block standalone (no file needed)
OPENAI_API_KEY=sk-... deepnote run --prompt "Write a hello world script"
```
# Run an agent with a prompt (appends an agent block to the file)
export OPENAI_API_KEY="sk-..."
deepnote run my-project.deepnote --prompt "Analyze the sales data"
# Run an agent block standalone (no file needed)
deepnote run --prompt "Write a hello world script"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/README.md` around lines 184 - 189, The README examples currently
show inline secrets (the commands containing "OPENAI_API_KEY=sk-... deepnote run
my-project.deepnote --prompt ..." and "OPENAI_API_KEY=sk-... deepnote run
--prompt ..."); update those examples to demonstrate exporting the key once
(e.g., use a single-line export instruction) and then run the deepnote run
commands without embedding the key, and add a brief note warning against pasting
API keys into command history.

Comment on lines +2536 to +2560
it('creates in-memory file and runs with --prompt and no path', async () => {
setupSuccessfulRun()

await action(undefined, { prompt: 'Say hello' })

expect(mockRunProject).toHaveBeenCalledTimes(1)
const [file] = mockRunProject.mock.calls[0]
expect(file.project.notebooks).toHaveLength(1)
expect(file.project.notebooks[0].blocks).toHaveLength(1)
expect(file.project.notebooks[0].blocks[0].type).toBe('agent')
expect(file.project.notebooks[0].blocks[0].content).toBe('Say hello')
})

it('appends agent block to existing file with --prompt and path', async () => {
setupSuccessfulRun()

await action(HELLO_WORLD_FILE, { prompt: 'Analyze data' })

expect(mockRunProject).toHaveBeenCalledTimes(1)
const [file] = mockRunProject.mock.calls[0]
const blocks = file.project.notebooks[file.project.notebooks.length - 1].blocks
const lastBlock = blocks[blocks.length - 1]
expect(lastBlock.type).toBe('agent')
expect(lastBlock.content).toBe('Analyze data')
})
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add a special-character prompt test for --prompt.

Line 2539 and Line 2552 only exercise simple prompts. Add one case with quotes/newlines/unicode to catch escaping/parsing regressions.

Suggested test addition
   it('appends agent block to existing file with --prompt and path', async () => {
     setupSuccessfulRun()

     await action(HELLO_WORLD_FILE, { prompt: 'Analyze data' })

     expect(mockRunProject).toHaveBeenCalledTimes(1)
     const [file] = mockRunProject.mock.calls[0]
     const blocks = file.project.notebooks[file.project.notebooks.length - 1].blocks
     const lastBlock = blocks[blocks.length - 1]
     expect(lastBlock.type).toBe('agent')
     expect(lastBlock.content).toBe('Analyze data')
   })
+
+  it('preserves special characters in prompt content', async () => {
+    setupSuccessfulRun()
+    const prompt = 'Summarize "Q1"\nLine 2 — café ☕'
+
+    await action(undefined, { prompt })
+
+    const [file] = mockRunProject.mock.calls[0]
+    expect(file.project.notebooks[0].blocks[0].type).toBe('agent')
+    expect(file.project.notebooks[0].blocks[0].content).toBe(prompt)
+  })

As per coding guidelines: "Test edge cases, error handling, and special characters in test files."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('creates in-memory file and runs with --prompt and no path', async () => {
setupSuccessfulRun()
await action(undefined, { prompt: 'Say hello' })
expect(mockRunProject).toHaveBeenCalledTimes(1)
const [file] = mockRunProject.mock.calls[0]
expect(file.project.notebooks).toHaveLength(1)
expect(file.project.notebooks[0].blocks).toHaveLength(1)
expect(file.project.notebooks[0].blocks[0].type).toBe('agent')
expect(file.project.notebooks[0].blocks[0].content).toBe('Say hello')
})
it('appends agent block to existing file with --prompt and path', async () => {
setupSuccessfulRun()
await action(HELLO_WORLD_FILE, { prompt: 'Analyze data' })
expect(mockRunProject).toHaveBeenCalledTimes(1)
const [file] = mockRunProject.mock.calls[0]
const blocks = file.project.notebooks[file.project.notebooks.length - 1].blocks
const lastBlock = blocks[blocks.length - 1]
expect(lastBlock.type).toBe('agent')
expect(lastBlock.content).toBe('Analyze data')
})
it('creates in-memory file and runs with --prompt and no path', async () => {
setupSuccessfulRun()
await action(undefined, { prompt: 'Say hello' })
expect(mockRunProject).toHaveBeenCalledTimes(1)
const [file] = mockRunProject.mock.calls[0]
expect(file.project.notebooks).toHaveLength(1)
expect(file.project.notebooks[0].blocks).toHaveLength(1)
expect(file.project.notebooks[0].blocks[0].type).toBe('agent')
expect(file.project.notebooks[0].blocks[0].content).toBe('Say hello')
})
it('appends agent block to existing file with --prompt and path', async () => {
setupSuccessfulRun()
await action(HELLO_WORLD_FILE, { prompt: 'Analyze data' })
expect(mockRunProject).toHaveBeenCalledTimes(1)
const [file] = mockRunProject.mock.calls[0]
const blocks = file.project.notebooks[file.project.notebooks.length - 1].blocks
const lastBlock = blocks[blocks.length - 1]
expect(lastBlock.type).toBe('agent')
expect(lastBlock.content).toBe('Analyze data')
})
it('preserves special characters in prompt content', async () => {
setupSuccessfulRun()
const prompt = 'Summarize "Q1"\nLine 2 — café ☕'
await action(undefined, { prompt })
const [file] = mockRunProject.mock.calls[0]
expect(file.project.notebooks[0].blocks[0].type).toBe('agent')
expect(file.project.notebooks[0].blocks[0].content).toBe(prompt)
})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/commands/run.test.ts` around lines 2536 - 2560, Add a new
test case that uses setupSuccessfulRun(), calls action(...) with a prompt
containing quotes, newlines and unicode (e.g. include " and \n and emojis) both
for the in-memory path (undefined) and/or with HELLO_WORLD_FILE, then assert
mockRunProject was called once and inspect the created file from
mockRunProject.mock.calls[0] (same pattern as the existing tests) to verify the
last notebook block's type is 'agent' and its content exactly matches the
special-character prompt string; use the same helpers (action, mockRunProject,
setupSuccessfulRun, HELLO_WORLD_FILE) and assertions style as the two existing
tests to catch escaping/parsing regressions.

Comment on lines +249 to +251
absolutePath = join(process.cwd(), 'prompt.deepnote')
workingDirectory = options.cwd ?? process.cwd()
convertedFile = { file, originalPath: absolutePath, format: 'deepnote', wasConverted: true }
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use workingDirectory for prompt-only synthetic path.

Line 249 currently ignores --cwd; snapshot/open paths can resolve to the caller’s CWD instead of the execution CWD.

Suggested fix
-    absolutePath = join(process.cwd(), 'prompt.deepnote')
-    workingDirectory = options.cwd ?? process.cwd()
+    workingDirectory = options.cwd ?? process.cwd()
+    absolutePath = join(workingDirectory, 'prompt.deepnote')
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/commands/run.ts` around lines 249 - 251, The synthetic
prompt path uses process.cwd() instead of the resolved execution cwd, so change
the creation of absolutePath to use the already-computed workingDirectory
variable; update the expression that builds absolutePath (used when constructing
convertedFile with originalPath) to join(workingDirectory, 'prompt.deepnote') so
that --cwd is respected for prompt-only synthetic paths (refer to variables
absolutePath, workingDirectory, convertedFile, file, originalPath).

Copy link
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: 7

Caution

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

⚠️ Outside diff range comments (3)
packages/blocks/src/blocks/executable-blocks.ts (1)

18-28: ⚠️ Potential issue | 🟡 Minor

Remove the duplicate 'agent' entry.

Set will dedupe this at runtime, but the second 'agent' looks accidental and makes it harder to tell whether another block type was meant to be added here. As per coding guidelines, "Keep code clean and readable following Biome guidelines".

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

In `@packages/blocks/src/blocks/executable-blocks.ts` around lines 18 - 28, The
array used to construct the executableBlockTypes Set contains a duplicate
'agent' entry which is accidental and should be removed; edit the initializer
for executableBlockTypes (the Set literal in blocks/executable-blocks.ts) to
delete the second 'agent' string so the list only contains unique block types
(and quickly scan the surrounding entries like 'code', 'sql',
'notebook-function', 'visualization', 'button', 'big-number' and
...INPUT_BLOCK_TYPES to ensure no other duplicates or a missing intended type).
packages/blocks/src/deepnote-file/deepnote-file-schema.ts (1)

475-484: 🧹 Nitpick | 🔵 Trivial

Remove the duplicated AgentBlock union member.

ExecutableBlock already includes AgentBlock earlier in the union, so the extra entry just reads like a merge artifact.

Proposed fix
 export type ExecutableBlock =
   | AgentBlock
   | CodeBlock
   | SqlBlock
   | NotebookFunctionBlock
   | VisualizationBlock
   | ButtonBlock
   | BigNumberBlock
-  | AgentBlock
   | InputBlock
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/blocks/src/deepnote-file/deepnote-file-schema.ts` around lines 475 -
484, The union type ExecutableBlock contains a duplicated member AgentBlock;
edit the ExecutableBlock type declaration to remove the redundant "AgentBlock"
entry so each variant appears only once (locate the ExecutableBlock type and
delete the second AgentBlock union member).
packages/runtime-core/src/execution-engine.ts (1)

18-27: ⚠️ Potential issue | 🟡 Minor

Remove the duplicate 'agent' entry from the exported list.

executableBlockTypeSet masks this internally, but callers of executableBlockTypes will still see 'agent' twice.

Suggested fix
 export const executableBlockTypes: ExecutableBlock['type'][] = [
   'agent',
   'code',
   'sql',
   'notebook-function',
   'visualization',
   'button',
   'big-number',
-  'agent',
   'input-text',
   'input-textarea',
   'input-checkbox',
🤖 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 18 - 27, The
exported array executableBlockTypes contains a duplicated 'agent' entry which
causes callers to see it twice; remove the duplicate string from the array
literal so each ExecutableBlock['type'] appears only once (update the exported
executableBlockTypes definition to contain a single 'agent' element).
♻️ Duplicate comments (3)
packages/cli/src/commands/run.ts (2)

462-473: ⚠️ Potential issue | 🟠 Major

Redact prompt in debug logs.

safeOptions only masks token, so a --prompt value is now serialized verbatim into debug output. Prompts often carry notebook data or credentials.

Suggested fix
-      const safeOptions = { ...options, token: options.token ? '[redacted]' : undefined }
+      const safeOptions = {
+        ...options,
+        token: options.token ? '[redacted]' : undefined,
+        prompt: options.prompt ? '[redacted]' : undefined,
+      }
       debug(`Options: ${JSON.stringify(safeOptions)}`)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/commands/run.ts` around lines 462 - 473, The debug
serialization exposes sensitive prompt content because safeOptions only masks
token; update createRunAction to also redact options.prompt before logging by
creating safeOptions from options with token replaced by '[redacted]' when
present and prompt replaced by '[redacted]' when present (leave other fields
unchanged), then use JSON.stringify(safeOptions) in the debug call so neither
prompt nor token appear in logs.

272-278: ⚠️ Potential issue | 🟠 Major

Append the synthetic agent block to the execution notebook.

When --notebook is set, this still pushes the prompt block into the last notebook. The run can then execute a different notebook and skip the prompt entirely.

Suggested fix
 if (path && options.prompt) {
-  const lastNotebook = file.project.notebooks[file.project.notebooks.length - 1]
-  if (lastNotebook) {
-    lastNotebook.blocks.push(createAgentBlock(options.prompt, lastNotebook.blocks.length))
+  const targetNotebook = options.notebook
+    ? file.project.notebooks.find(notebook => notebook.name === options.notebook)
+    : file.project.notebooks[file.project.notebooks.length - 1]
+
+  if (targetNotebook) {
+    targetNotebook.blocks.push(createAgentBlock(options.prompt, targetNotebook.blocks.length))
+  } else if (options.notebook) {
+    throw new Error(`Cannot append prompt: notebook "${options.notebook}" not found`)
   } else {
     throw new Error('Cannot append prompt: file contains no notebooks')
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/commands/run.ts` around lines 272 - 278, The code appends
the synthetic agent block to the last notebook
(file.project.notebooks[file.project.notebooks.length - 1]) which ignores a
user-selected notebook via the --notebook option; change the logic to compute
the intended execution notebook (use the existing notebook selection variable or
option, e.g., a notebookIndex/notebookName passed in via path/options) and
append the block to that notebook (e.g., resolve targetNotebook =
file.project.notebooks[notebookIndex] or find by name) instead of lastNotebook;
update the push call to
targetNotebook.blocks.push(createAgentBlock(options.prompt,
targetNotebook.blocks.length)) and throw an error if the resolved targetNotebook
is missing.
packages/runtime-core/src/agent-handler.ts (1)

38-40: ⚠️ Potential issue | 🟠 Major

This sorting-key generator is incompatible with the notebook formats already in the repo.

The repository already has blocks keyed as a0/a1 and others keyed as 000004-style strings, while execution later sorts lexicographically by sortingKey. Hard-coding a00000003 can move agent-inserted blocks ahead of legacy a1/a2 blocks or behind numeric-key blocks on the next run.

🤖 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 38 - 40, The
generateSortingKey function currently zero-pads and creates keys like
"a00000003", which breaks compatibility with existing notebook keys ("a0"/"a1"
and numeric "000004" styles) and causes incorrect lexicographic ordering; change
generateSortingKey to produce compact keys that preserve legacy grouping by
returning "a" + index.toString() (no pad) so agent-inserted blocks remain in the
expected "a" bucket and do not leapfrog numeric-only keys when sorted by
sortingKey.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@examples/demo-agent-block.deepnote`:
- Around line 80-94: Step 6 currently tells the agent to save the report as
`examples/report.md` which will be resolved relative to the filesystem MCP root
(already `./examples`) and produce `./examples/examples/report.md`; update the
instruction to write the file path relative to the MCP root (e.g., `report.md`
or `./report.md`) so the filesystem MCP tool saves the report at
`./examples/report.md`; ensure the task text referencing the filesystem MCP
server (metadata.deepnote_mcp_servers entry) and the step that calls the
filesystem MCP tool reflect this corrected relative path.

In `@packages/blocks/src/blocks/agent-blocks.test.ts`:
- Around line 42-70: The test 'parses agent block with MCP servers' currently
only asserts the MCP server name allowing regressions that drop other fields;
update the assertions after deepnoteBlockSchema.safeParse to also verify
deepnote_mcp_servers[0].command equals 'npx', deepnote_mcp_servers[0].args
equals ['-y','@modelcontextprotocol/server-postgres'], and
deepnote_mcp_servers[0].env.DATABASE_URL contains the '${DATABASE_URL}'
placeholder (or exact string), so the parser is validated to preserve command,
args, and env fields.

In `@packages/blocks/src/deepnote-file/agent-block-schema.test.ts`:
- Around line 29-49: The test currently only asserts the length of
project.settings.mcpServers after parsing with deepnoteFileSchema, which can
miss shape regressions; update the test to assert the parsed MCP entries' fields
(for example verify each entry's name, command, args, and env where provided)
against the expected values so the schema actually preserves command/args/env
(use deepnoteFileSchema.safeParse result.data.project.settings?.mcpServers and
assert entries equal or match the expected objects).

In `@packages/blocks/src/deepnote-file/deepnote-file-schema.ts`:
- Around line 253-258: The schema mcpServerSchema currently uses z.string() for
the name and command fields which allows empty or whitespace-only values; update
those fields to trim whitespace and enforce at least one character, e.g. replace
name: z.string() and command: z.string() with name: z.string().trim().min(1) and
command: z.string().trim().min(1) so blank or whitespace-only server
names/commands are rejected at validation time.

In `@packages/blocks/src/python-code.test.ts`:
- Around line 984-1000: Replace the duplicate happy-path test in the "Agent
blocks" suite with an edge-case that verifies createPythonCode handles special
characters; update the it(...) case that currently builds an AgentBlock (id
'123', type 'agent', content 'Analyze the data', metadata.deepnote_agent_model
'gpt-4o') to instead use an AgentBlock whose content contains special characters
(quotes, newlines, non-ASCII, and comment-like sequences) and assert
createPythonCode returns properly escaped/commented Python output; locate the
test inside the describe('Agent blocks') block and modify the AgentBlock fixture
and expected result to validate correct handling by createPythonCode.

In `@packages/runtime-core/src/agent-handler.ts`:
- Around line 12-21: AgentBlockContext currently lacks any working-directory
info so relative MCP roots like "./examples" are forwarded unchanged; add a
workingDirectory (string) field to AgentBlockContext and update the code paths
that forward/consume MCP roots (the handler that forwards roots and the code
referenced around the same area) to resolve relative roots against this
workingDirectory using path.resolve(workingDirectory, root) (leave absolute
paths untouched). Ensure the new workingDirectory is threaded through wherever
AgentBlockContext is constructed so consumers use the resolved absolute paths.
- Around line 190-199: The created code block (newBlock) is never updated with
the agent execution results, so newBlock.outputs and newBlock.executionCount
remain empty while results are stored only in collectedOutputs; update newBlock
right after execution by assigning newBlock.outputs =
cloneDeep(collectedOutputs[newBlock.id] || collectedOutputs[correspondingId] ||
executionResult.outputs) and newBlock.executionCount =
executionResult.executionCount || (Array.isArray(newBlock.outputs) ?
newBlock.outputs.length ? (newBlock.outputs[0].execution_count ?? null) : null :
null); ensure you use the same identifiers used when mirroring results
(collectedOutputs, executionResult) and deep-copy the outputs before assigning
so saved notebooks/snapshots persist the executed outputs; apply the same fix in
the other insertion branch that mirrors results (the block handling the
alternate insert path).

---

Outside diff comments:
In `@packages/blocks/src/blocks/executable-blocks.ts`:
- Around line 18-28: The array used to construct the executableBlockTypes Set
contains a duplicate 'agent' entry which is accidental and should be removed;
edit the initializer for executableBlockTypes (the Set literal in
blocks/executable-blocks.ts) to delete the second 'agent' string so the list
only contains unique block types (and quickly scan the surrounding entries like
'code', 'sql', 'notebook-function', 'visualization', 'button', 'big-number' and
...INPUT_BLOCK_TYPES to ensure no other duplicates or a missing intended type).

In `@packages/blocks/src/deepnote-file/deepnote-file-schema.ts`:
- Around line 475-484: The union type ExecutableBlock contains a duplicated
member AgentBlock; edit the ExecutableBlock type declaration to remove the
redundant "AgentBlock" entry so each variant appears only once (locate the
ExecutableBlock type and delete the second AgentBlock union member).

In `@packages/runtime-core/src/execution-engine.ts`:
- Around line 18-27: The exported array executableBlockTypes contains a
duplicated 'agent' entry which causes callers to see it twice; remove the
duplicate string from the array literal so each ExecutableBlock['type'] appears
only once (update the exported executableBlockTypes definition to contain a
single 'agent' element).

---

Duplicate comments:
In `@packages/cli/src/commands/run.ts`:
- Around line 462-473: The debug serialization exposes sensitive prompt content
because safeOptions only masks token; update createRunAction to also redact
options.prompt before logging by creating safeOptions from options with token
replaced by '[redacted]' when present and prompt replaced by '[redacted]' when
present (leave other fields unchanged), then use JSON.stringify(safeOptions) in
the debug call so neither prompt nor token appear in logs.
- Around line 272-278: The code appends the synthetic agent block to the last
notebook (file.project.notebooks[file.project.notebooks.length - 1]) which
ignores a user-selected notebook via the --notebook option; change the logic to
compute the intended execution notebook (use the existing notebook selection
variable or option, e.g., a notebookIndex/notebookName passed in via
path/options) and append the block to that notebook (e.g., resolve
targetNotebook = file.project.notebooks[notebookIndex] or find by name) instead
of lastNotebook; update the push call to
targetNotebook.blocks.push(createAgentBlock(options.prompt,
targetNotebook.blocks.length)) and throw an error if the resolved targetNotebook
is missing.

In `@packages/runtime-core/src/agent-handler.ts`:
- Around line 38-40: The generateSortingKey function currently zero-pads and
creates keys like "a00000003", which breaks compatibility with existing notebook
keys ("a0"/"a1" and numeric "000004" styles) and causes incorrect lexicographic
ordering; change generateSortingKey to produce compact keys that preserve legacy
grouping by returning "a" + index.toString() (no pad) so agent-inserted blocks
remain in the expected "a" bucket and do not leapfrog numeric-only keys when
sorted by sortingKey.
🪄 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: df087efe-227d-45af-b54e-ed50197e8fcc

📥 Commits

Reviewing files that changed from the base of the PR and between ef0d6fc and b0f1637.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (19)
  • examples/demo-agent-block.deepnote
  • examples/snapshots/agentic-analysis-demo_demo-agent-agentic_latest.snapshot.deepnote
  • packages/blocks/src/blocks/agent-blocks.test.ts
  • packages/blocks/src/blocks/executable-blocks.ts
  • packages/blocks/src/deepnote-file/agent-block-schema.test.ts
  • packages/blocks/src/deepnote-file/deepnote-file-schema.ts
  • packages/blocks/src/python-code.test.ts
  • packages/cli/package.json
  • packages/cli/src/commands/run.ts
  • packages/cli/src/utils/block-label.test.ts
  • packages/convert/src/roundtrip-all-formats.test.ts
  • packages/runtime-core/package.json
  • packages/runtime-core/src/agent-handler.ts
  • packages/runtime-core/src/execution-engine.ts
  • packages/runtime-core/src/index.ts
  • skills/deepnote/references/blocks-agent.md
  • skills/deepnote/references/cli-run.md
  • skills/deepnote/references/schema.ts
  • test-fixtures/agent-block.deepnote

Comment on lines +80 to +94
6. Use the filesystem MCP tool to save a final executive report as `examples/report.md`
7. Add a final markdown block with an executive summary containing 3 key takeaways and a recommendation

Use clean, publication-quality matplotlib charts with proper labels and titles.
Use `plt.tight_layout()` and `plt.savefig()` for each chart.
id: a0000000000000000000000000000004
metadata:
deepnote_agent_model: gpt-4o
deepnote_mcp_servers:
- name: filesystem
command: npx
args:
- '-y'
- '@modelcontextprotocol/server-filesystem'
- ./examples
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Write the report path relative to the MCP root.

The filesystem server is already rooted at ./examples, so asking the agent to save examples/report.md targets a nested path instead of the intended file. This makes step 6 write to ./examples/examples/report.md or fail.

Suggested fix
-            6. Use the filesystem MCP tool to save a final executive report as `examples/report.md`
+            6. Use the filesystem MCP tool to save a final executive report as `report.md`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/demo-agent-block.deepnote` around lines 80 - 94, Step 6 currently
tells the agent to save the report as `examples/report.md` which will be
resolved relative to the filesystem MCP root (already `./examples`) and produce
`./examples/examples/report.md`; update the instruction to write the file path
relative to the MCP root (e.g., `report.md` or `./report.md`) so the filesystem
MCP tool saves the report at `./examples/report.md`; ensure the task text
referencing the filesystem MCP server (metadata.deepnote_mcp_servers entry) and
the step that calls the filesystem MCP tool reflect this corrected relative
path.

Comment on lines +42 to +70
it('parses agent block with MCP servers', () => {
const result = deepnoteBlockSchema.safeParse({
id: 'abc123',
blockGroup: 'grp123',
sortingKey: 'a0',
type: 'agent',
content: 'query the db',
metadata: {
deepnote_agent_model: 'gpt-4o-mini',
deepnote_mcp_servers: [
{
name: 'postgres',
command: 'npx',
args: ['-y', '@modelcontextprotocol/server-postgres'],
// biome-ignore lint/suspicious/noTemplateCurlyInString: testing env var reference syntax
env: { DATABASE_URL: '${DATABASE_URL}' },
},
],
},
executionCount: null,
outputs: [],
})

expect(result.success).toBe(true)
if (result.success && result.data.type === 'agent') {
expect(result.data.metadata.deepnote_agent_model).toBe('gpt-4o-mini')
expect(result.data.metadata.deepnote_mcp_servers).toHaveLength(1)
expect(result.data.metadata.deepnote_mcp_servers?.[0]?.name).toBe('postgres')
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Assert the MCP fields you construct.

This case adds command, args, and a ${DATABASE_URL} placeholder, but only name is asserted. A parser that drops or rewrites the new fields still passes.

Proposed fix
     if (result.success && result.data.type === 'agent') {
       expect(result.data.metadata.deepnote_agent_model).toBe('gpt-4o-mini')
       expect(result.data.metadata.deepnote_mcp_servers).toHaveLength(1)
-      expect(result.data.metadata.deepnote_mcp_servers?.[0]?.name).toBe('postgres')
+      expect(result.data.metadata.deepnote_mcp_servers?.[0]).toMatchObject({
+        name: 'postgres',
+        command: 'npx',
+        args: ['-y', '@modelcontextprotocol/server-postgres'],
+        env: { DATABASE_URL: '${DATABASE_URL}' },
+      })
     }
As per coding guidelines: "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/blocks/src/blocks/agent-blocks.test.ts` around lines 42 - 70, The
test 'parses agent block with MCP servers' currently only asserts the MCP server
name allowing regressions that drop other fields; update the assertions after
deepnoteBlockSchema.safeParse to also verify deepnote_mcp_servers[0].command
equals 'npx', deepnote_mcp_servers[0].args equals
['-y','@modelcontextprotocol/server-postgres'], and
deepnote_mcp_servers[0].env.DATABASE_URL contains the '${DATABASE_URL}'
placeholder (or exact string), so the parser is validated to preserve command,
args, and env fields.

Comment on lines +29 to +49
it('validates project-level mcpServers in settings', () => {
const result = deepnoteFileSchema.safeParse({
version: '1.0.0',
metadata: { createdAt: '2026-01-01T00:00:00Z' },
project: {
id: 'test',
name: 'Test',
settings: {
mcpServers: [
{ name: 'test-server', command: 'python', args: ['-m', 'server'] },
{ name: 'another', command: 'npx', env: { KEY: 'val' } },
],
},
notebooks: [],
},
})

expect(result.success).toBe(true)
if (result.success) {
expect(result.data.project.settings?.mcpServers).toHaveLength(2)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Assert the parsed MCP fields, not just the count.

This only checks length. If parsing strips command or env from project settings, the test still passes and misses the new shape.

Proposed fix
     expect(result.success).toBe(true)
     if (result.success) {
       expect(result.data.project.settings?.mcpServers).toHaveLength(2)
+      expect(result.data.project.settings?.mcpServers?.[0]).toMatchObject({
+        name: 'test-server',
+        command: 'python',
+        args: ['-m', 'server'],
+      })
+      expect(result.data.project.settings?.mcpServers?.[1]).toMatchObject({
+        name: 'another',
+        command: 'npx',
+        env: { KEY: 'val' },
+      })
     }
As per coding guidelines: "Create comprehensive tests for all new features using Vitest."
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('validates project-level mcpServers in settings', () => {
const result = deepnoteFileSchema.safeParse({
version: '1.0.0',
metadata: { createdAt: '2026-01-01T00:00:00Z' },
project: {
id: 'test',
name: 'Test',
settings: {
mcpServers: [
{ name: 'test-server', command: 'python', args: ['-m', 'server'] },
{ name: 'another', command: 'npx', env: { KEY: 'val' } },
],
},
notebooks: [],
},
})
expect(result.success).toBe(true)
if (result.success) {
expect(result.data.project.settings?.mcpServers).toHaveLength(2)
}
it('validates project-level mcpServers in settings', () => {
const result = deepnoteFileSchema.safeParse({
version: '1.0.0',
metadata: { createdAt: '2026-01-01T00:00:00Z' },
project: {
id: 'test',
name: 'Test',
settings: {
mcpServers: [
{ name: 'test-server', command: 'python', args: ['-m', 'server'] },
{ name: 'another', command: 'npx', env: { KEY: 'val' } },
],
},
notebooks: [],
},
})
expect(result.success).toBe(true)
if (result.success) {
expect(result.data.project.settings?.mcpServers).toHaveLength(2)
expect(result.data.project.settings?.mcpServers?.[0]).toMatchObject({
name: 'test-server',
command: 'python',
args: ['-m', 'server'],
})
expect(result.data.project.settings?.mcpServers?.[1]).toMatchObject({
name: 'another',
command: 'npx',
env: { KEY: 'val' },
})
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/blocks/src/deepnote-file/agent-block-schema.test.ts` around lines 29
- 49, The test currently only asserts the length of project.settings.mcpServers
after parsing with deepnoteFileSchema, which can miss shape regressions; update
the test to assert the parsed MCP entries' fields (for example verify each
entry's name, command, args, and env where provided) against the expected values
so the schema actually preserves command/args/env (use
deepnoteFileSchema.safeParse result.data.project.settings?.mcpServers and assert
entries equal or match the expected objects).

Comment on lines +253 to +258
export const mcpServerSchema = z.object({
name: z.string(),
command: z.string(),
args: z.array(z.string()).optional(),
env: z.record(z.string()).optional(),
})
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Reject blank MCP server names and commands.

z.string() still accepts '' and whitespace here, so invalid configs pass schema validation and only fail later when the runtime tries to resolve or spawn the server.

Proposed fix
 export const mcpServerSchema = z.object({
-  name: z.string(),
-  command: z.string(),
+  name: z.string().trim().min(1),
+  command: z.string().trim().min(1),
   args: z.array(z.string()).optional(),
   env: z.record(z.string()).optional(),
 })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const mcpServerSchema = z.object({
name: z.string(),
command: z.string(),
args: z.array(z.string()).optional(),
env: z.record(z.string()).optional(),
})
export const mcpServerSchema = z.object({
name: z.string().trim().min(1),
command: z.string().trim().min(1),
args: z.array(z.string()).optional(),
env: z.record(z.string()).optional(),
})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/blocks/src/deepnote-file/deepnote-file-schema.ts` around lines 253 -
258, The schema mcpServerSchema currently uses z.string() for the name and
command fields which allows empty or whitespace-only values; update those fields
to trim whitespace and enforce at least one character, e.g. replace name:
z.string() and command: z.string() with name: z.string().trim().min(1) and
command: z.string().trim().min(1) so blank or whitespace-only server
names/commands are rejected at validation time.

Comment on lines +984 to +1000
describe('Agent blocks', () => {
it('returns comment-based code for agent block', () => {
const block: AgentBlock = {
id: '123',
type: 'agent',
content: 'Analyze the data',
blockGroup: 'abc',
sortingKey: 'a0',
metadata: {
deepnote_agent_model: 'gpt-4o',
},
}

const result = createPythonCode(block)

expect(result).toEqual('# [agent block] System prompt:\n# Analyze the data')
})
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Replace this duplicate happy-path test with a missing edge case.

This is effectively the same assertion as the earlier agent prompt test, so it adds little coverage. A special-characters case would pull more weight here.

Proposed replacement
   describe('Agent blocks', () => {
-    it('returns comment-based code for agent block', () => {
+    it('creates Python comment for agent block with special characters', () => {
       const block: AgentBlock = {
         id: '123',
         type: 'agent',
-        content: 'Analyze the data',
+        content: 'Analyze "sales"\nPath: C:\\tmp\n#keep-comment',
         blockGroup: 'abc',
         sortingKey: 'a0',
         metadata: {
           deepnote_agent_model: 'gpt-4o',
         },
       }
 
       const result = createPythonCode(block)
 
-      expect(result).toEqual('# [agent block] System prompt:\n# Analyze the data')
+      expect(result).toEqual(
+        dedent`
+          # [agent block] System prompt:
+          # Analyze "sales"
+          # Path: C:\\tmp
+          # `#keep-comment`
+        `.trim(),
+      )
     })
   })
As per coding guidelines, "Test edge cases, error handling, and special characters in test files".
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
describe('Agent blocks', () => {
it('returns comment-based code for agent block', () => {
const block: AgentBlock = {
id: '123',
type: 'agent',
content: 'Analyze the data',
blockGroup: 'abc',
sortingKey: 'a0',
metadata: {
deepnote_agent_model: 'gpt-4o',
},
}
const result = createPythonCode(block)
expect(result).toEqual('# [agent block] System prompt:\n# Analyze the data')
})
describe('Agent blocks', () => {
it('creates Python comment for agent block with special characters', () => {
const block: AgentBlock = {
id: '123',
type: 'agent',
content: 'Analyze "sales"\nPath: C:\\tmp\n#keep-comment',
blockGroup: 'abc',
sortingKey: 'a0',
metadata: {
deepnote_agent_model: 'gpt-4o',
},
}
const result = createPythonCode(block)
expect(result).toEqual(
dedent`
# [agent block] System prompt:
# Analyze "sales"
# Path: C:\\tmp
# `#keep-comment`
`.trim(),
)
})
})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/blocks/src/python-code.test.ts` around lines 984 - 1000, Replace the
duplicate happy-path test in the "Agent blocks" suite with an edge-case that
verifies createPythonCode handles special characters; update the it(...) case
that currently builds an AgentBlock (id '123', type 'agent', content 'Analyze
the data', metadata.deepnote_agent_model 'gpt-4o') to instead use an AgentBlock
whose content contains special characters (quotes, newlines, non-ASCII, and
comment-like sequences) and assert createPythonCode returns properly
escaped/commented Python output; locate the test inside the describe('Agent
blocks') block and modify the AgentBlock fixture and expected result to validate
correct handling by createPythonCode.

Comment on lines +12 to +21
export interface AgentBlockContext {
kernel: KernelClient
file: DeepnoteFile
notebookIndex: number
agentBlockIndex: number
collectedOutputs: Map<string, { outputs: unknown[]; executionCount: number | null }>
onLog?: (message: string) => void
onAgentEvent?: (event: AgentStreamEvent) => void
integrations?: Array<{ id: string; name: string; type: string }>
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Resolve relative MCP paths from the project directory.

The examples and fixtures pass roots like ./examples and ./data, but this handler forwards them unchanged and AgentBlockContext has no working-directory field. The same notebook will read and write different locations depending on the caller's shell CWD or --cwd.

Also applies to: 155-167

🤖 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 12 - 21,
AgentBlockContext currently lacks any working-directory info so relative MCP
roots like "./examples" are forwarded unchanged; add a workingDirectory (string)
field to AgentBlockContext and update the code paths that forward/consume MCP
roots (the handler that forwards roots and the code referenced around the same
area) to resolve relative roots against this workingDirectory using
path.resolve(workingDirectory, root) (leave absolute paths untouched). Ensure
the new workingDirectory is threaded through wherever AgentBlockContext is
constructed so consumers use the resolved absolute paths.

Comment on lines +190 to +199
const newBlock: Extract<DeepnoteBlock, { type: 'code' }> = {
id: randomUUID().replace(/-/g, ''),
blockGroup: randomUUID().replace(/-/g, ''),
sortingKey: generateSortingKey(insertIndex),
type: 'code',
content: code,
metadata: {},
executionCount: null,
outputs: [],
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Persist execution results on the inserted code block.

You mirror results into collectedOutputs, but newBlock.outputs and newBlock.executionCount stay at their initial empty/null values. Saved notebooks and snapshots then show agent-created code cells as never run.

Suggested fix
       try {
         const result = await context.kernel.execute(code)
+        newBlock.outputs = result.outputs
+        newBlock.executionCount = result.executionCount
         const outputTexts: string[] = []
         for (const output of result.outputs) {
           const out = output as Record<string, unknown>

Also applies to: 205-243

🤖 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 190 - 199, The
created code block (newBlock) is never updated with the agent execution results,
so newBlock.outputs and newBlock.executionCount remain empty while results are
stored only in collectedOutputs; update newBlock right after execution by
assigning newBlock.outputs = cloneDeep(collectedOutputs[newBlock.id] ||
collectedOutputs[correspondingId] || executionResult.outputs) and
newBlock.executionCount = executionResult.executionCount ||
(Array.isArray(newBlock.outputs) ? newBlock.outputs.length ?
(newBlock.outputs[0].execution_count ?? null) : null : null); ensure you use the
same identifiers used when mirroring results (collectedOutputs, executionResult)
and deep-copy the outputs before assigning so saved notebooks/snapshots persist
the executed outputs; apply the same fix in the other insertion branch that
mirrors results (the block handling the alternate insert path).

Copy link
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

🤖 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/blocks/src/deepnote-file/deepnote-file-schema.ts`:
- Around line 262-265: The metadata schema allows empty or whitespace-only agent
model identifiers because deepnote_agent_model is defined as
z.string().default('auto') without trimming or emptiness checks; update the
executableBlockMetadataSchema extension for deepnote_agent_model to first trim
input and then enforce non-empty content (e.g., transform to s.trim() and refine
or validate that length > 0) so blank strings or strings with only whitespace
are rejected while keeping the default 'auto'.
🪄 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: b19489a4-a993-45c4-9fff-8ef5f02fc688

📥 Commits

Reviewing files that changed from the base of the PR and between b0f1637 and afca27a.

📒 Files selected for processing (1)
  • packages/blocks/src/deepnote-file/deepnote-file-schema.ts

Comment on lines +262 to +265
metadata: executableBlockMetadataSchema
.extend({
deepnote_agent_model: z.string().default('auto'),
deepnote_mcp_servers: z.array(mcpServerSchema).optional(),
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n packages/blocks/src/deepnote-file/deepnote-file-schema.ts | sed -n '260,270p'

Repository: deepnote/deepnote

Length of output: 486


🏁 Script executed:

# Find similar validation patterns for model/agent fields in the codebase
rg "z\.string\(\)" packages/blocks/src/deepnote-file/deepnote-file-schema.ts -A 2 -B 1 | head -40

Repository: deepnote/deepnote

Length of output: 1508


🏁 Script executed:

# Check mcpServerSchema to see how other fields are validated
rg "mcpServerSchema" packages/blocks/src/deepnote-file/deepnote-file-schema.ts -B 5 -A 5

Repository: deepnote/deepnote

Length of output: 1151


🏁 Script executed:

# Search for usage of deepnote_agent_model to see if there's runtime validation
rg "deepnote_agent_model" packages/blocks/src/ -A 3 -B 1

Repository: deepnote/deepnote

Length of output: 2470


🏁 Script executed:

# Check if there are similar min(1) validations elsewhere for model/identifier fields
rg "\.min\(1\)" packages/blocks/src/deepnote-file/ -B 2 -A 1

Repository: deepnote/deepnote

Length of output: 43


🏁 Script executed:

# Check how deepnote_agent_model is used at runtime
rg "agent_model" packages/blocks/src/ -t ts -t tsx --max-count 20

Repository: deepnote/deepnote

Length of output: 88


🏁 Script executed:

# Look for any validation that happens after schema parsing
rg "deepnote_agent_model|agent.*model" packages/blocks/src/python-code.ts -A 5 -B 5

Repository: deepnote/deepnote

Length of output: 43


🏁 Script executed:

# Check what other string fields in schemas use .min(1) or similar validation
rg "z\.string\(\).*min|z\.string\(\).*trim" packages/blocks/src/ -B 1 -A 1 | head -30

Repository: deepnote/deepnote

Length of output: 43


🏁 Script executed:

# Find any validation or usage of agent model after parsing
rg "agent.*model" packages/blocks/src/ -A 2 -B 2 | head -50

Repository: deepnote/deepnote

Length of output: 2429


🏁 Script executed:

# Check the python-code.ts file to see if there's any validation of the model
find packages/blocks/src -name "python-code.ts" -exec cat -n {} \; | head -100

Repository: deepnote/deepnote

Length of output: 3649


🏁 Script executed:

# Look for what models are valid and if there's a list/enum somewhere
rg "gpt-4|auto|model" packages/blocks/src/deepnote-file/deepnote-file-schema.ts -B 2 -A 2

Repository: deepnote/deepnote

Length of output: 244


🏁 Script executed:

# Find the agent blocks implementation to see how deepnote_agent_model is used
find packages/blocks/src -name "*agent*" -type f | head -10

Repository: deepnote/deepnote

Length of output: 103


🏁 Script executed:

# Look at the agent blocks implementation
find packages/blocks/src -name "*agent-blocks*" -type f -exec cat {} \; | head -150

Repository: deepnote/deepnote

Length of output: 574


🏁 Script executed:

# Check if there's an enum or validation for valid agent models
rg "auto|gpt-4|model.*enum|model.*union" packages/blocks/src/deepnote-file/ -B 2 -A 2

Repository: deepnote/deepnote

Length of output: 534


🏁 Script executed:

# Check if deepnote_agent_model is used elsewhere in validation or type definitions
rg "deepnote_agent_model" packages/blocks/src/ -B 3 -A 3 | grep -v ".test.ts"

Repository: deepnote/deepnote

Length of output: 720


🏁 Script executed:

# Verify that empty strings would indeed pass the current schema
python3 << 'EOF'
# Simulate Zod behavior - z.string().default('auto') accepts any string
test_values = ['', ' ', '  ', 'auto', 'gpt-4o', 'invalid-model']
for val in test_values:
    # Current schema behavior
    print(f"Current schema: '{val}' -> PASS (accepted)")
    # Proposed schema behavior with trim and min(1)
    trimmed = val.strip()
    if len(trimmed) >= 1:
        print(f"Proposed schema: '{val}' -> PASS (accepted as '{trimmed}')")
    else:
        print(f"Proposed schema: '{val}' -> FAIL (rejected)")
    print()
EOF

Repository: deepnote/deepnote

Length of output: 621


Disallow blank agent model identifiers.

The schema currently accepts empty strings and whitespace via z.string().default('auto'), allowing invalid model values to pass validation. This should be rejected at schema level.

Proposed fix
   metadata: executableBlockMetadataSchema
     .extend({
-      deepnote_agent_model: z.string().default('auto'),
+      deepnote_agent_model: z.string().trim().min(1).default('auto'),
       deepnote_mcp_servers: z.array(mcpServerSchema).optional(),
     })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
metadata: executableBlockMetadataSchema
.extend({
deepnote_agent_model: z.string().default('auto'),
deepnote_mcp_servers: z.array(mcpServerSchema).optional(),
metadata: executableBlockMetadataSchema
.extend({
deepnote_agent_model: z.string().trim().min(1).default('auto'),
deepnote_mcp_servers: z.array(mcpServerSchema).optional(),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/blocks/src/deepnote-file/deepnote-file-schema.ts` around lines 262 -
265, The metadata schema allows empty or whitespace-only agent model identifiers
because deepnote_agent_model is defined as z.string().default('auto') without
trimming or emptiness checks; update the executableBlockMetadataSchema extension
for deepnote_agent_model to first trim input and then enforce non-empty content
(e.g., transform to s.trim() and refine or validate that length > 0) so blank
strings or strings with only whitespace are rejected while keeping the default
'auto'.

Copy link
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: 4

🤖 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/blocks/src/index.ts`:
- Line 2: The schema is missing the AgentBlock and McpServerConfig type
definitions referenced by the new agent exports; update schema.ts to include
type definitions for AgentBlock and McpServerConfig (matching the shapes
documented in blocks-agent.md) by either re-running the project's schema
generation script to regenerate schema.ts or by manually adding the two type
declarations to schema.ts so that the exported symbol is covered; look for the
AgentBlock and McpServerConfig identifiers and ensure their fields match the
documented structure and are exported from schema.ts.

In `@packages/blocks/src/output-text.ts`:
- Around line 28-34: The code assumes every element of out.traceback is a string
and calls .replace on each, which can throw if elements are non-strings; update
the includeTraceback handling in output-text.ts (the block using
options?.includeTraceback and out.traceback) to filter or coerce elements to
strings before calling .replace — e.g., iterate over (out.traceback as
unknown[]), use typeof checks or String(...) to ensure each item is a string (or
skip non-strings), then strip ANSI sequences and join; ensure the logic
preserves existing newline/prefix behavior and only processes elements that are
valid strings.

In `@packages/runtime-core/src/agent-handler.ts`:
- Around line 222-223: The execute handler currently unsafely casts input to {
content: string } and uses mdContent without validation; update the execute
function to validate that input is an object with a string content property
(same checks used in the code block tool), return a clear error or throw if
validation fails, and only assign mdContent when content is confirmed to be a
string to avoid runtime errors.
- Around line 168-169: The execute handler currently unsafely casts input to {
code: string } (execute: async (input: unknown) => { const { code } = input as {
code: string } }), which can crash on malformed tool calls; update execute to
validate input before destructuring by checking input is an object, not null,
has a "code" key, and that typeof code === "string", and handle invalid shapes
by throwing a clear error or returning a controlled failure response; ensure all
subsequent uses of code rely on the validated value rather than the raw cast.
🪄 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: e77d4213-20a1-4261-b40e-45b057d519d2

📥 Commits

Reviewing files that changed from the base of the PR and between afca27a and f383524.

📒 Files selected for processing (5)
  • packages/blocks/src/index.ts
  • packages/blocks/src/output-text.ts
  • packages/mcp/src/tools/execution.ts
  • packages/mcp/src/utils.ts
  • packages/runtime-core/src/agent-handler.ts

@@ -1,8 +1,10 @@
export { UnsupportedBlockTypeError } from './blocks'
export { isAgentBlock } from './blocks/agent-blocks'
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if agent block documentation exists in references
rg -l 'agent' skills/deepnote/references/ 2>/dev/null || echo "No agent references found"
fd 'schema.ts' skills/deepnote/references/ --exec cat {} 2>/dev/null | head -50

Repository: deepnote/deepnote

Length of output: 1877


🏁 Script executed:

#!/bin/bash
# Check schema.ts for AgentBlock and McpServerConfig definitions
echo "=== Searching schema.ts for AgentBlock and McpServerConfig ===" 
rg -A 5 'AgentBlock|McpServerConfig' skills/deepnote/references/schema.ts

echo -e "\n=== Checking blocks-agent.md content ===" 
cat skills/deepnote/references/blocks-agent.md 2>/dev/null | head -100

Repository: deepnote/deepnote

Length of output: 3484


Ensure AgentBlock and McpServerConfig types are added to schema.ts.

blocks-agent.md is properly documented. However, schema.ts should include the AgentBlock and McpServerConfig type definitions per coding guidelines. The file is auto-generated, so either run the schema generation script or add these types manually.

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

In `@packages/blocks/src/index.ts` at line 2, The schema is missing the AgentBlock
and McpServerConfig type definitions referenced by the new agent exports; update
schema.ts to include type definitions for AgentBlock and McpServerConfig
(matching the shapes documented in blocks-agent.md) by either re-running the
project's schema generation script to regenerate schema.ts or by manually adding
the two type declarations to schema.ts so that the exported symbol is covered;
look for the AgentBlock and McpServerConfig identifiers and ensure their fields
match the documented structure and are exported from schema.ts.

Comment on lines +28 to +34
if (options?.includeTraceback && Array.isArray(out.traceback)) {
text +=
'\n' +
(out.traceback as string[])
// biome-ignore lint/suspicious/noControlCharactersInRegex: strip ANSI escape sequences from traceback
.map(line => line.replace(/\x1b\[[0-9;]*m/g, ''))
.join('\n')
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Traceback elements assumed to be strings without validation.

Array.isArray(out.traceback) doesn't guarantee string elements. If an element isn't a string, .replace() throws.

🛡️ Proposed fix
     if (options?.includeTraceback && Array.isArray(out.traceback)) {
       text +=
         '\n' +
-        (out.traceback as string[])
+        out.traceback
           // biome-ignore lint/suspicious/noControlCharactersInRegex: strip ANSI escape sequences from traceback
-          .map(line => line.replace(/\x1b\[[0-9;]*m/g, ''))
+          .map(line => (typeof line === 'string' ? line.replace(/\x1b\[[0-9;]*m/g, '') : String(line)))
           .join('\n')
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (options?.includeTraceback && Array.isArray(out.traceback)) {
text +=
'\n' +
(out.traceback as string[])
// biome-ignore lint/suspicious/noControlCharactersInRegex: strip ANSI escape sequences from traceback
.map(line => line.replace(/\x1b\[[0-9;]*m/g, ''))
.join('\n')
if (options?.includeTraceback && Array.isArray(out.traceback)) {
text +=
'\n' +
out.traceback
// biome-ignore lint/suspicious/noControlCharactersInRegex: strip ANSI escape sequences from traceback
.map(line => (typeof line === 'string' ? line.replace(/\x1b\[[0-9;]*m/g, '') : String(line)))
.join('\n')
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/blocks/src/output-text.ts` around lines 28 - 34, The code assumes
every element of out.traceback is a string and calls .replace on each, which can
throw if elements are non-strings; update the includeTraceback handling in
output-text.ts (the block using options?.includeTraceback and out.traceback) to
filter or coerce elements to strings before calling .replace — e.g., iterate
over (out.traceback as unknown[]), use typeof checks or String(...) to ensure
each item is a string (or skip non-strings), then strip ANSI sequences and join;
ensure the logic preserves existing newline/prefix behavior and only processes
elements that are valid strings.

Copy link
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 (10)
packages/cli/src/commands/run.ts (3)

248-249: ⚠️ Potential issue | 🟡 Minor

Order issue: absolutePath computed before workingDirectory.

Line 248 uses process.cwd() but workingDirectory isn't set until line 249. Should swap order.

Fix
-    absolutePath = join(process.cwd(), 'prompt.deepnote')
     workingDirectory = options.cwd ?? process.cwd()
+    absolutePath = join(workingDirectory, 'prompt.deepnote')
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/commands/run.ts` around lines 248 - 249, The code computes
absolutePath using process.cwd() before resolving workingDirectory from
options.cwd; swap the two statements so workingDirectory = options.cwd ??
process.cwd() is set first, then compute absolutePath = join(workingDirectory,
'prompt.deepnote') to ensure absolutePath uses the intended workingDirectory
value (refer to the variables absolutePath and workingDirectory and the use of
options.cwd and join).

472-473: ⚠️ Potential issue | 🟠 Major

Redact prompt in debug logs.

safeOptions masks token but exposes prompt, which may contain sensitive data.

Fix
-const safeOptions = { ...options, token: options.token ? '[redacted]' : undefined }
+const safeOptions = {
+  ...options,
+  token: options.token ? '[redacted]' : undefined,
+  prompt: options.prompt ? '[redacted]' : undefined,
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/commands/run.ts` around lines 472 - 473, The debug log
currently redacts only options.token but leaves options.prompt exposed; update
the creation of safeOptions (used before debug in run.ts) to also mask any
prompt field (e.g., set prompt: options.prompt ? '[redacted]' : undefined) so
JSON.stringify(safeOptions) will not include sensitive prompt text; ensure you
reference and modify the same symbols (options -> safeOptions and the debug(...)
call) so the debug output never prints the raw prompt.

272-279: ⚠️ Potential issue | 🟠 Major

Prompt appends to last notebook, ignoring --notebook.

When --notebook is specified, the agent block should go there instead.

Fix
 if (path && options.prompt) {
-  const lastNotebook = file.project.notebooks[file.project.notebooks.length - 1]
-  if (lastNotebook) {
-    lastNotebook.blocks.push(createAgentBlock(options.prompt, lastNotebook.blocks.length))
+  const targetNotebook = options.notebook
+    ? file.project.notebooks.find(n => n.name === options.notebook)
+    : file.project.notebooks[file.project.notebooks.length - 1]
+
+  if (targetNotebook) {
+    targetNotebook.blocks.push(createAgentBlock(options.prompt, targetNotebook.blocks.length))
+  } else if (options.notebook) {
+    throw new Error(`Cannot append prompt: notebook "${options.notebook}" not found`)
   } else {
     throw new Error('Cannot append prompt: file contains no notebooks')
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/commands/run.ts` around lines 272 - 279, The code always
appends the agent block to the last notebook (`lastNotebook`) when
`options.prompt` is set, ignoring a specified `--notebook`; update the logic in
the block that calls `createAgentBlock` to honor `options.notebook` by locating
the target notebook (by name or index, according to how `options.notebook` is
represented) in `file.project.notebooks` and appending to that notebook's
`blocks` instead of `lastNotebook`; if the requested notebook cannot be found,
throw a clear error (replace the current 'Cannot append prompt: file contains no
notebooks' behavior) and keep using `createAgentBlock(options.prompt,
targetNotebook.blocks.length)` to compute the block index.
examples/demo-agent-block.deepnote (2)

80-80: ⚠️ Potential issue | 🟠 Major

Path double-nested.

MCP root is ./examples, so examples/report.md resolves to ./examples/examples/report.md. Use report.md.

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

In `@examples/demo-agent-block.deepnote` at line 80, The step text in
examples/demo-agent-block.deepnote incorrectly uses a double-nested path
"examples/report.md" (MCP root is ./examples), so change that string to
"report.md" so the filesystem MCP tool saves to ./examples/report.md rather than
./examples/examples/report.md; locate and update the line containing "Use the
filesystem MCP tool to save a final executive report as `examples/report.md`" to
use `report.md` instead.

91-94: ⚠️ Potential issue | 🔴 Critical

Pin MCP server version.

Unpinned package risks pulling vulnerable versions.

Fix
                 args:
                   - '-y'
-                  - '@modelcontextprotocol/server-filesystem'
+                  - '@modelcontextprotocol/[email protected]'
                   - ./examples
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/demo-agent-block.deepnote` around lines 91 - 94, The MCP server
package reference '@modelcontextprotocol/server-filesystem' is unpinned in the
args list; change the argument to a pinned version string (e.g.
'@modelcontextprotocol/server-filesystem@<semver>' or exact commit/hash) so the
args entry becomes the package name with a specific version; update the
occurrence of '@modelcontextprotocol/server-filesystem' in the args array to the
chosen pinned version.
packages/runtime-core/src/agent-handler.ts (4)

150-160: ⚠️ Potential issue | 🟠 Major

Relative MCP paths not resolved.

Paths like ./data are forwarded unchanged. Behavior depends on shell CWD rather than project directory.

AgentBlockContext needs a workingDirectory field to resolve relative paths.

🤖 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 - 160, The MCP
paths are not being resolved against the agent/project directory: add a
workingDirectory string field to AgentBlockContext and update the code that
builds mergedMcpConfig to resolve any relative paths (in s.command, s.args
entries that are paths, and s.env values) against context.workingDirectory
before creating clients; then pass those resolved values into
createMCPClient/Experimental_StdioMCPTransport (keep using resolveEnvVars but
feed it the resolved env) so MCP launches use the intended project directory
rather than the process CWD.

218-218: ⚠️ Potential issue | 🟡 Minor

Same validation needed for content.

Fix
-    execute: async ({ content: mdContent }) => {
+    execute: async (input) => {
+      const mdContent = typeof input === 'object' && input && 'content' in input && typeof input.content === 'string'
+        ? input.content
+        : ''
+      if (!mdContent) return 'Error: content parameter is required'
       context.onLog?.('  [agent] Adding markdown block')
🤖 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` at line 218, The execute handler
currently accepts ({ content: mdContent }) without validating mdContent; add the
same validation used elsewhere to ensure mdContent is a non-empty string (or the
expected type) before proceeding, and handle invalid input by returning/throwing
the same error/response pattern used in this module; update the execute function
parameter handling and early-return logic for mdContent to match the other
validated handlers in this file.

175-184: ⚠️ Potential issue | 🟠 Major

Persist outputs on inserted code block.

newBlock.outputs and newBlock.executionCount stay empty. Saved notebooks show agent-created cells as never run.

Fix
       try {
         const result = await context.kernel.execute(code)
+        newBlock.outputs = result.outputs
+        newBlock.executionCount = result.executionCount

         blockOutputs.push({

Also applies to: 190-202

🤖 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 175 - 184, When
creating the new code block in agent-handler (the newBlock object), populate
newBlock.outputs and newBlock.executionCount from the code-execution result
instead of leaving them empty so saved notebooks reflect that the cell has run;
locate the two newBlock constructions (the one shown and the duplicate around
lines 190-202), take the execution result object produced by the executor (e.g.,
the variable that contains stdout/stderr/outputs and count) and assign its
outputs to newBlock.outputs and its execution count (or a derived number) to
newBlock.executionCount before persisting the notebook.

172-172: ⚠️ Potential issue | 🟡 Minor

Validate tool input.

{ code } destructuring assumes correct shape. Malformed calls cause runtime errors.

Fix
-    execute: async ({ code }) => {
+    execute: async (input) => {
+      const code = typeof input === 'object' && input && 'code' in input && typeof input.code === 'string'
+        ? input.code
+        : ''
+      if (!code) return 'Error: code parameter is required'
       context.onLog?.('  [agent] Adding code block and executing...')
🤖 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` at line 172, The execute handler
currently destructures ({ code }) without validation; add an explicit guard at
the start of the execute: async ({ code }) => { ... } function to verify the
incoming payload exists and that code is a string (e.g., if (!payload || typeof
code !== 'string') { /* log and return/throw a structured error */ }), and
ensure you return or throw a clear, consistent error response instead of letting
a runtime exception bubble up; update any callers or tests expecting error
shapes if needed.
packages/cli/README.md (1)

184-189: ⚠️ Potential issue | 🟡 Minor

Inline secrets in examples.

Showing OPENAI_API_KEY=sk-... inline normalizes pasting keys into shell history. Prefer export once.

Fix
-# Run an agent with a prompt (appends an agent block to the file)
-OPENAI_API_KEY=sk-... deepnote run my-project.deepnote --prompt "Analyze the sales data"
+# Run an agent with a prompt (appends an agent block to the file)
+# First: export OPENAI_API_KEY="sk-..."
+deepnote run my-project.deepnote --prompt "Analyze the sales data"

-# Run an agent block standalone (no file needed)
-OPENAI_API_KEY=sk-... deepnote run --prompt "Write a hello world script"
+# Run an agent block standalone (no file needed)
+deepnote run --prompt "Write a hello world script"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/README.md` around lines 184 - 189, The README examples include
inline secret assignment like `OPENAI_API_KEY=sk-... deepnote run ...`; update
both examples (the "Run an agent with a prompt" and "Run an agent block
standalone" code blocks) to avoid inline secrets by showing a safer pattern:
instruct users to set the key once with `export OPENAI_API_KEY=...` (or
reference using an env file like `--env-file .env`) and replace the inline
`OPENAI_API_KEY=sk-...` occurrences with a note telling users to export the
variable beforehand or use an env-file, keeping the example commands free of
literal keys.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@skills/deepnote/references/blocks-agent.md`:
- Around line 58-64: Replace the inline secret usage in the bash examples so the
API key is exported separately instead of prefixed to the command; update the
two examples that currently show "OPENAI_API_KEY=sk-... deepnote run ..." and
"OPENAI_API_KEY=sk-... deepnote run my-project.deepnote ..." to demonstrate
setting the secret with an export (e.g., export OPENAI_API_KEY=...) on its own
line, then running the corresponding deepnote run commands, and keep a note that
users should unset or use a secure secret mechanism afterwards.

In `@test-fixtures/agent-block.deepnote`:
- Around line 8-14: The MCP server package is unpinned in the mcpServers fixture
(entry with name: filesystem and args containing
"@modelcontextprotocol/server-filesystem"); change that argument to a pinned
release (e.g. "@modelcontextprotocol/server-filesystem@<safe-version>") using
the vetted version from your lockfile or security advisory, so the fixture
explicitly references the approved semver (update any test fixtures or docs that
reference mcpServers/name: filesystem accordingly).

---

Duplicate comments:
In `@examples/demo-agent-block.deepnote`:
- Line 80: The step text in examples/demo-agent-block.deepnote incorrectly uses
a double-nested path "examples/report.md" (MCP root is ./examples), so change
that string to "report.md" so the filesystem MCP tool saves to
./examples/report.md rather than ./examples/examples/report.md; locate and
update the line containing "Use the filesystem MCP tool to save a final
executive report as `examples/report.md`" to use `report.md` instead.
- Around line 91-94: The MCP server package reference
'@modelcontextprotocol/server-filesystem' is unpinned in the args list; change
the argument to a pinned version string (e.g.
'@modelcontextprotocol/server-filesystem@<semver>' or exact commit/hash) so the
args entry becomes the package name with a specific version; update the
occurrence of '@modelcontextprotocol/server-filesystem' in the args array to the
chosen pinned version.

In `@packages/cli/README.md`:
- Around line 184-189: The README examples include inline secret assignment like
`OPENAI_API_KEY=sk-... deepnote run ...`; update both examples (the "Run an
agent with a prompt" and "Run an agent block standalone" code blocks) to avoid
inline secrets by showing a safer pattern: instruct users to set the key once
with `export OPENAI_API_KEY=...` (or reference using an env file like
`--env-file .env`) and replace the inline `OPENAI_API_KEY=sk-...` occurrences
with a note telling users to export the variable beforehand or use an env-file,
keeping the example commands free of literal keys.

In `@packages/cli/src/commands/run.ts`:
- Around line 248-249: The code computes absolutePath using process.cwd() before
resolving workingDirectory from options.cwd; swap the two statements so
workingDirectory = options.cwd ?? process.cwd() is set first, then compute
absolutePath = join(workingDirectory, 'prompt.deepnote') to ensure absolutePath
uses the intended workingDirectory value (refer to the variables absolutePath
and workingDirectory and the use of options.cwd and join).
- Around line 472-473: The debug log currently redacts only options.token but
leaves options.prompt exposed; update the creation of safeOptions (used before
debug in run.ts) to also mask any prompt field (e.g., set prompt: options.prompt
? '[redacted]' : undefined) so JSON.stringify(safeOptions) will not include
sensitive prompt text; ensure you reference and modify the same symbols (options
-> safeOptions and the debug(...) call) so the debug output never prints the raw
prompt.
- Around line 272-279: The code always appends the agent block to the last
notebook (`lastNotebook`) when `options.prompt` is set, ignoring a specified
`--notebook`; update the logic in the block that calls `createAgentBlock` to
honor `options.notebook` by locating the target notebook (by name or index,
according to how `options.notebook` is represented) in `file.project.notebooks`
and appending to that notebook's `blocks` instead of `lastNotebook`; if the
requested notebook cannot be found, throw a clear error (replace the current
'Cannot append prompt: file contains no notebooks' behavior) and keep using
`createAgentBlock(options.prompt, targetNotebook.blocks.length)` to compute the
block index.

In `@packages/runtime-core/src/agent-handler.ts`:
- Around line 150-160: The MCP paths are not being resolved against the
agent/project directory: add a workingDirectory string field to
AgentBlockContext and update the code that builds mergedMcpConfig to resolve any
relative paths (in s.command, s.args entries that are paths, and s.env values)
against context.workingDirectory before creating clients; then pass those
resolved values into createMCPClient/Experimental_StdioMCPTransport (keep using
resolveEnvVars but feed it the resolved env) so MCP launches use the intended
project directory rather than the process CWD.
- Line 218: The execute handler currently accepts ({ content: mdContent })
without validating mdContent; add the same validation used elsewhere to ensure
mdContent is a non-empty string (or the expected type) before proceeding, and
handle invalid input by returning/throwing the same error/response pattern used
in this module; update the execute function parameter handling and early-return
logic for mdContent to match the other validated handlers in this file.
- Around line 175-184: When creating the new code block in agent-handler (the
newBlock object), populate newBlock.outputs and newBlock.executionCount from the
code-execution result instead of leaving them empty so saved notebooks reflect
that the cell has run; locate the two newBlock constructions (the one shown and
the duplicate around lines 190-202), take the execution result object produced
by the executor (e.g., the variable that contains stdout/stderr/outputs and
count) and assign its outputs to newBlock.outputs and its execution count (or a
derived number) to newBlock.executionCount before persisting the notebook.
- Line 172: The execute handler currently destructures ({ code }) without
validation; add an explicit guard at the start of the execute: async ({ code })
=> { ... } function to verify the incoming payload exists and that code is a
string (e.g., if (!payload || typeof code !== 'string') { /* log and
return/throw a structured error */ }), and ensure you return or throw a clear,
consistent error response instead of letting a runtime exception bubble up;
update any callers or tests expecting error shapes if needed.
🪄 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: a574191d-b8d7-4e41-9016-6749d491af52

📥 Commits

Reviewing files that changed from the base of the PR and between f383524 and 77b9f14.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (8)
  • examples/demo-agent-block.deepnote
  • packages/blocks/src/python-code.test.ts
  • packages/cli/README.md
  • packages/cli/src/commands/run.ts
  • packages/runtime-core/package.json
  • packages/runtime-core/src/agent-handler.ts
  • skills/deepnote/references/blocks-agent.md
  • test-fixtures/agent-block.deepnote

Comment on lines +58 to +64
```bash
# Standalone (creates an in-memory notebook with just the agent block)
OPENAI_API_KEY=sk-... deepnote run --prompt "Write a hello world script"

# Appended to an existing notebook (runs all blocks, then the agent)
OPENAI_API_KEY=sk-... deepnote run my-project.deepnote --prompt "Analyze the data"
```
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Inline secrets in docs.

Same as README—prefer showing export OPENAI_API_KEY=... separately.

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

In `@skills/deepnote/references/blocks-agent.md` around lines 58 - 64, Replace the
inline secret usage in the bash examples so the API key is exported separately
instead of prefixed to the command; update the two examples that currently show
"OPENAI_API_KEY=sk-... deepnote run ..." and "OPENAI_API_KEY=sk-... deepnote run
my-project.deepnote ..." to demonstrate setting the secret with an export (e.g.,
export OPENAI_API_KEY=...) on its own line, then running the corresponding
deepnote run commands, and keep a note that users should unset or use a secure
secret mechanism afterwards.

Comment on lines +8 to +14
mcpServers:
- name: filesystem
command: npx
args:
- "-y"
- "@modelcontextprotocol/server-filesystem"
- "./data"
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Pin MCP server package version.

Unpinned @modelcontextprotocol/server-filesystem may pull vulnerable versions. Pin to a known safe release.

Fix
         args:
           - "-y"
-          - "@modelcontextprotocol/server-filesystem"
+          - "@modelcontextprotocol/[email protected]"
           - "./data"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
mcpServers:
- name: filesystem
command: npx
args:
- "-y"
- "@modelcontextprotocol/server-filesystem"
- "./data"
mcpServers:
- name: filesystem
command: npx
args:
- "-y"
- "@modelcontextprotocol/server-filesystem@2026.1.14"
- "./data"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test-fixtures/agent-block.deepnote` around lines 8 - 14, The MCP server
package is unpinned in the mcpServers fixture (entry with name: filesystem and
args containing "@modelcontextprotocol/server-filesystem"); change that argument
to a pinned release (e.g.
"@modelcontextprotocol/server-filesystem@<safe-version>") using the vetted
version from your lockfile or security advisory, so the fixture explicitly
references the approved semver (update any test fixtures or docs that reference
mcpServers/name: filesystem accordingly).

@dinohamzic
Copy link
Contributor

Closing in favor of #341

@dinohamzic dinohamzic closed this Mar 12, 2026
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.

2 participants