Skip to content

feat(agent-block): Add support for Agent block#358

Draft
tkislan wants to merge 9 commits intotk/deepnote-runtime-corefrom
tk/deepnote-agent-block
Draft

feat(agent-block): Add support for Agent block#358
tkislan wants to merge 9 commits intotk/deepnote-runtime-corefrom
tk/deepnote-agent-block

Conversation

@tkislan
Copy link
Contributor

@tkislan tkislan commented Mar 16, 2026

Summary by CodeRabbit

  • New Features

    • Agent notebook cells execute independently, with a model picker (auto, gpt-4o, sonnet) and configurable iteration limits (1–100).
    • Ephemeral auto-generated cells receive visual treatment (decorations, markdown wrapper) and an "Ephemeral" status badge.
    • OpenAI API key management: set/clear prompts and storage for agent execution.
  • Behavior Changes

    • Ephemeral cells are excluded from notebook serialization (not persisted).
  • Tests

    • Comprehensive unit tests for agent execution, UI providers, converters, ephemeral handling, and serialization.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 16, 2026

📝 Walkthrough

Walkthrough

Adds end-to-end agent cell support for Deepnote notebooks: agent cells are detected and executed immediately via a new agent execution handler that serializes notebook context, streams agent events into cell outputs, and manages ephemeral cells (insert, execute, cleanup). Kernel execution is preserved for non-agent cells and only starts when kernel-targeted cells remain. Introduces agent block conversion, status-bar and decoration providers for agent/ephemeral cells, secret store helpers and commands for an OpenAI API key, excludes ephemeral cells from serialization, extends the renderer for ephemeral markdown, and adds comprehensive unit tests.

Sequence Diagram(s)

sequenceDiagram
    participant Controller as NotebookController
    participant Filter as ExecutionFilter
    participant AgentExec as AgentCellExecutor
    participant Serializer as ContextSerializer
    participant EphemeralMgr as EphemeralManager
    participant Kernel as KernelExecutor

    Controller->>Filter: executeCells(request)
    Filter->>Filter: split cells -> agentCells + kernelCells

    alt agentCells exist
        Filter->>AgentExec: executeAgentCell(agentCell)
        AgentExec->>Serializer: serializeNotebookContext(notebook)
        AgentExec->>AgentExec: clear outputs, start execution
        AgentExec->>EphemeralMgr: removeEphemeralCellsForAgent(agentBlockId)
        AgentExec->>AgentExec: executeAgentBlock (stream events)
        AgentExec->>AgentExec: stream text/tool deltas -> update outputs
        AgentExec->>EphemeralMgr: insertEphemeralCell(code/markdown)
        EphemeralMgr->>Kernel: executeEphemeralCell(ephemeralCell)
        Kernel-->>EphemeralMgr: execution result
        EphemeralMgr-->>AgentExec: propagate outputs
        AgentExec->>AgentExec: end execution (success/error)
    end

    alt kernelCells exist
        Filter->>Kernel: start kernel and execute kernelCells
        Kernel-->>Controller: kernel execution complete
    else no kernelCells
        Filter-->>Controller: return (no kernel started)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Updates Docs ❓ Inconclusive Unable to access repository structure, git history, or file contents to assess documentation updates. Provide repository access or list specific files modified in the PR for documentation assessment.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title 'feat(agent-block): Add support for Agent block' clearly summarizes the main change—introducing agent block support with appropriate conventional commit format.

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

📝 Coding Plan
  • Generate coding plan for human review comments

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

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: 6

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

Inline comments:
In `@src/notebooks/deepnote/agentCellExecutionHandler.ts`:
- Around line 110-113: The code directly reads OPENAI_API_KEY from process.env
in agentCellExecutionHandler.ts (openAiToken = process.env.OPENAI_API_KEY) which
is unsafe for production; replace this direct env access with a secure secret
retrieval call (e.g., a new getOpenAiApiKey() that fetches from your secret
manager/credentials vault or from an injected secure config) and update callers
to inject the key instead of relying on process.env; ensure the secret is never
logged or included in error messages and keep the existing null-check/throw
behavior but reference the secure getter (getOpenAiApiKey) or injected parameter
in place of process.env.OPENAI_API_KEY.
- Around line 274-278: The success check in the return object of
agentCellExecutionHandler is too permissive—replace the current expression
`cell.executionSummary?.success !== false` with an explicit true check like
`cell.executionSummary?.success === true` (so only an explicit success is
reported; undefined/in-progress will not be treated as success); update the
return here (where `success`, `outputs:
cell.outputs.map(translateCellDisplayOutput)`, and `executionCount:
cell.executionSummary?.executionOrder ?? null` are constructed) to use that
strict equality.

In `@src/notebooks/deepnote/agentCellStatusBarProvider.ts`:
- Around line 69-71: The dispose() method currently uses an expression-bodied
arrow in this.disposables.forEach((d) => d.dispose()) which triggers the Biome
callback-return lint; change the callback to a block body or replace the forEach
with a for...of loop so the disposables are disposed without returning a
value—e.g., update dispose() to iterate over the disposables array and call
dispose() inside a statement block (reference: dispose method and disposables
property).
- Around line 142-149: getMaxIterations currently only enforces a lower bound;
add an upper-bound check so the returned value is an integer between
MIN_ITERATIONS and MAX_ITERATIONS (e.g., require value <= MAX_ITERATIONS). In
setMaxIterations replace permissive parseInt usage with strict integer
validation (use a full-match regex like /^\d+$/) and then parse with Number() so
inputs like "5.5" or "10abc" are rejected; after parsing ensure the numeric
value is an integer and within MIN_ITERATIONS..MAX_ITERATIONS before accepting
or falling back to DEFAULT_MAX_ITERATIONS. Update both occurrences in
setMaxIterations that currently call parseInt to use this strict validation and
range check, and reference the getMaxIterations and setMaxIterations functions
when making the change.

In `@src/notebooks/deepnote/converters/agentBlockConverter.unit.test.ts`:
- Around line 1-5: Reorder the imports so third-party modules are grouped
together and local imports come after: move the dedent import to be alongside
the other external imports (DeepnoteBlock, chai's assert, and vscode's
NotebookCellData/NotebookCellKind) and place the local AgentBlockConverter
import ('./agentBlockConverter') after that group; ensure the symbols
DeepnoteBlock, assert, NotebookCellData, NotebookCellKind, and dedent remain
imported and only the order changes to comply with the "third-party then local"
guideline.
🪄 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: 45a324a0-84a7-463d-903d-d15c32e2b30d

📥 Commits

Reviewing files that changed from the base of the PR and between d5f67f6 and 46f9a4c.

📒 Files selected for processing (16)
  • src/notebooks/controllers/vscodeNotebookController.ts
  • src/notebooks/deepnote/agentCellExecutionHandler.ts
  • src/notebooks/deepnote/agentCellExecutionHandler.unit.test.ts
  • src/notebooks/deepnote/agentCellStatusBarProvider.ts
  • src/notebooks/deepnote/agentCellStatusBarProvider.unit.test.ts
  • src/notebooks/deepnote/converters/agentBlockConverter.ts
  • src/notebooks/deepnote/converters/agentBlockConverter.unit.test.ts
  • src/notebooks/deepnote/deepnoteDataConverter.ts
  • src/notebooks/deepnote/deepnoteKernelAutoSelector.node.ts
  • src/notebooks/deepnote/deepnoteTestHelpers.ts
  • src/notebooks/deepnote/ephemeralCellDecorationProvider.ts
  • src/notebooks/deepnote/ephemeralCellStatusBarProvider.ts
  • src/notebooks/deepnote/ephemeralCellStatusBarProvider.unit.test.ts
  • src/notebooks/serviceRegistry.node.ts
  • src/notebooks/serviceRegistry.web.ts
  • src/renderers/client/markdown.ts

tkislan added 2 commits March 18, 2026 06:58
- Introduced `getOpenAiApiKey` function to retrieve the OpenAI API key from configuration, improving error handling when the key is not set.
- Updated `executeAgentCell` and `executeEphemeralCell` functions to utilize the new API key retrieval method and handle cancellation tokens.
- Enhanced `AgentCellStatusBarProvider` to validate max iterations using Zod schema, ensuring robust input handling and defaulting to safe values.
- Added unit tests for new functionality and edge cases in both execution handling and status bar provider.
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

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

Inline comments:
In `@src/notebooks/deepnote/agentCellExecutionHandler.ts`:
- Around line 142-147: The onLog callback in agentCellExecutionHandler.ts
contains commented-out accumulation code and a TODO; either remove the dead code
or implement it: add an accumulated string variable in the enclosing scope, make
onLog async (or forward logs to an async helper), append incoming message to
accumulated, then call
execution.replaceOutputItems(NotebookCellOutputItem.text(accumulated), output)
to update the cell output; if you choose to drop it, delete the commented lines
and the TODO and keep only logger.info('Agent log', message). Reference: onLog
callback, accumulated variable, execution.replaceOutputItems,
NotebookCellOutputItem.text, and output.
- Around line 41-64: serializeNotebookContext instantiates a new
DeepnoteDataConverter on every call which is wasteful if called frequently;
modify serializeNotebookContext to use a shared or injected converter instance
instead of creating one per invocation (e.g., accept a DeepnoteDataConverter
parameter or read from a module-scoped singleton), and update callers to pass or
rely on the shared converter so convertCellToBlock usage inside
serializeNotebookContext reuses the same converter.

In `@src/notebooks/deepnote/agentCellExecutionHandler.unit.test.ts`:
- Around line 310-324: The test creates a CancellationTokenSource named
tokenSource and cancels it but never disposes it; update the test for 'returns
success false immediately when token is pre-cancelled' to ensure
tokenSource.dispose() is called after use (e.g., in a finally block or via
afterEach cleanup) so the CancellationTokenSource is properly disposed; locate
the tokenSource variable in this test and add the dispose call around
executeEphemeralCell(tokenSource.token) to clean up resources.

In `@src/notebooks/deepnote/agentCellStatusBarProvider.ts`:
- Line 27: MaxIterationsSchema currently only enforces a minimum via
MIN_ITERATIONS so values >100 slip through; update MaxIterationsSchema to also
enforce an upper bound (e.g., .max(100)) or reference a new constant like
MAX_ITERATIONS = 100 if you prefer a named limit, ensuring you use
z.coerce.number().int().min(MIN_ITERATIONS).max(MAX_ITERATIONS) (or .max(100))
to validate both ends; modify the schema definition where MaxIterationsSchema is
declared and add the MAX_ITERATIONS constant if not already present.

In `@src/notebooks/deepnote/ephemeralCellDecorationProvider.ts`:
- Around line 69-71: The dispose method on EphemeralCellDecorationProvider
currently iterates disposables with this.disposables.forEach((d) =>
d.dispose());—replace the forEach with a for...of loop to align with the pattern
used in AgentCellStatusBarProvider and to ensure proper synchronous disposal and
error handling: iterate over this.disposables using for (const d of
this.disposables) and call d.dispose() inside the loop (referencing the dispose
method and the disposables array to locate the change).
🪄 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: 5e9fa43e-8179-4408-8722-29b13fbca570

📥 Commits

Reviewing files that changed from the base of the PR and between 46f9a4c and 75d0220.

📒 Files selected for processing (10)
  • build/esbuild/build.ts
  • src/notebooks/deepnote/agentCellExecutionHandler.ts
  • src/notebooks/deepnote/agentCellExecutionHandler.unit.test.ts
  • src/notebooks/deepnote/agentCellStatusBarProvider.ts
  • src/notebooks/deepnote/agentCellStatusBarProvider.unit.test.ts
  • src/notebooks/deepnote/dataConversionUtils.ts
  • src/notebooks/deepnote/deepnoteSerializer.ts
  • src/notebooks/deepnote/deepnoteSerializer.unit.test.ts
  • src/notebooks/deepnote/ephemeralCellDecorationProvider.ts
  • src/notebooks/deepnote/ephemeralCellStatusBarProvider.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: 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 `@package.json`:
- Around line 1641-1646: The package.json setting "deepnote.agent.openAiApiKey"
stores the API key in plain settings; remove that configuration entry and
instead read/write the key via VS Code SecretStorage (use
context.secrets.get/set) like the existing apiAccess.ts usage; update the code
that previously read configuration for deepnote.agent.openAiApiKey to check
context.secrets.get("openAiApiKey") and, if missing, prompt the user with an
input dialog (and offer a command to set/clear the secret), and reuse the helper
functions or patterns from apiAccess.ts to centralize secret handling and
prompting.
🪄 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: 740fd51e-0220-41ef-8f67-78053eba4d0f

📥 Commits

Reviewing files that changed from the base of the PR and between 75d0220 and f7bec65.

📒 Files selected for processing (1)
  • package.json

- Added commands to set and clear the OpenAI API key, enhancing user interaction.
- Introduced a new `deepnoteSecretStore` module for managing secrets, including functions to get, set, and clear the OpenAI API key.
- Updated `agentCellExecutionHandler` to utilize the new secret management functions, improving error handling when the API key is not set.
- Enhanced unit tests to cover the new secret management functionality and ensure robust error handling.
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)
src/notebooks/deepnote/agentCellStatusBarProvider.ts (1)

207-224: 🛠️ Refactor suggestion | 🟠 Major

Reuse MaxIterationsSchema for consistent validation.

parseInt is lenient: "5.5" becomes 5, "10abc" becomes 10. The existing Zod schema handles this properly and is already used in getMaxIterations.

,

♻️ Suggested fix
             validateInput: (value) => {
-                const num = parseInt(value, 10);
-                if (isNaN(num) || !Number.isInteger(num)) {
-                    return l10n.t('Please enter a whole number');
-                }
-                if (num < MIN_ITERATIONS || num > MAX_ITERATIONS) {
+                const result = MaxIterationsSchema.safeParse(value);
+                if (!result.success) {
                     return l10n.t('Value must be between {0} and {1}', MIN_ITERATIONS, MAX_ITERATIONS);
                 }

                 return undefined;
             }
-        const newValue = parseInt(input, 10);
+        const newValue = MaxIterationsSchema.parse(input);
         if (newValue === currentValue) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/notebooks/deepnote/agentCellStatusBarProvider.ts` around lines 207 - 224,
The validateInput logic should reuse the existing MaxIterationsSchema instead of
using parseInt; replace the parseInt/isNaN checks in validateInput with
MaxIterationsSchema.safeParse(input) (or parse and catch) and return l10n.t(...)
on failure, ensuring the schema enforces integer-only and range constraints
consistent with MIN_ITERATIONS and MAX_ITERATIONS; after the prompt returns, set
newValue from the validated schema result (the parsed numeric value) rather than
calling parseInt again; refer to validateInput, MaxIterationsSchema,
getMaxIterations, MIN_ITERATIONS, MAX_ITERATIONS, and newValue when making these
changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/notebooks/deepnote/agentCellExecutionHandler.ts`:
- Around line 240-245: The code currently assumes workspace.applyEdit(edit)
succeeded and returns insertIndex blindly; change it to check the boolean result
of await workspace.applyEdit(edit) and verify the notebook now contains the
inserted cell (e.g. notebook.cellCount > insertIndex or try
notebook.cellAt(insertIndex) exists). If applyEdit returns false or the
verification fails, throw an Error (or return a sentinel/failure value as per
project convention) instead of returning insertIndex so callers won't operate on
an invalid index; use the same local symbols edit, insertIndex, notebook,
WorkspaceEdit, NotebookEdit.insertCells and workspace.applyEdit to locate and
implement the checks.
- Around line 136-138: The handler onAgentEvent currently logs the full
serialized AgentStreamEvent (logger.info('Agent event', JSON.stringify(event)))
which can leak user/tool content and bloat logs; change this to log only minimal
metadata such as event.type, any safe IDs or timestamps, and the transition
detected using lastAgentEventType (e.g., logger.info('Agent event', { type:
event.type, prevType: lastAgentEventType, timestamp: ... })) and remove
JSON.stringify(event) so no full payload is written to logs.
- Around line 264-283: The code rejects completionDeferred when
token.isCancellationRequested but still proceeds to run
commands.executeCommand('notebook.cell.execute'), allowing work after
cancellation; update the handler (around token, completionDeferred,
CancellationError and before commands.executeCommand) to short-circuit: if token
&& token.isCancellationRequested (or if completionDeferred has already been
rejected/settled) then clear the timeout, dispose any disposables, and
return/throw so commands.executeCommand is not invoked; ensure the same
early-exit path is taken when token.onCancellationRequested fires so cancelled
executions never call notebook.cell.execute.

In `@src/notebooks/deepnote/agentCellExecutionHandler.unit.test.ts`:
- Around line 366-384: The test for executeEphemeralCell should also assert that
no execution request was sent when the token is pre-cancelled: after calling
executeEphemeralCell with the pre-cancelled CancellationTokenSource, add an
assertion that notebook.cell.execute was never invoked (i.e., verify/expect the
mocked notebook cell execution method did not get called), and keep the existing
assertion on the returned result; refer to executeEphemeralCell,
mockedVSCodeNamespaces.commands.executeCommand and the notebook.cell.execute
mock when adding this check.

In `@src/notebooks/deepnote/ephemeralCellDecorationProvider.ts`:
- Around line 117-123: The current loop in ephemeralCellDecorationProvider
builds a Range per line (lineRanges) and calls
editor.setDecorations(this.ephemeralDecorationType, lineRanges), which is
wasteful; replace it by creating a single full-cell Range spanning from the
start of the first line to the end of the last line (use
editor.document.lineAt(0).range.start and
editor.document.lineAt(editor.document.lineCount - 1).range.end) and pass an
array with that single Range to
editor.setDecorations(this.ephemeralDecorationType, [fullRange]) so you avoid
allocating per-line Range objects while preserving the same decoration coverage.

---

Duplicate comments:
In `@src/notebooks/deepnote/agentCellStatusBarProvider.ts`:
- Around line 207-224: The validateInput logic should reuse the existing
MaxIterationsSchema instead of using parseInt; replace the parseInt/isNaN checks
in validateInput with MaxIterationsSchema.safeParse(input) (or parse and catch)
and return l10n.t(...) on failure, ensuring the schema enforces integer-only and
range constraints consistent with MIN_ITERATIONS and MAX_ITERATIONS; after the
prompt returns, set newValue from the validated schema result (the parsed
numeric value) rather than calling parseInt again; refer to validateInput,
MaxIterationsSchema, getMaxIterations, MIN_ITERATIONS, MAX_ITERATIONS, and
newValue when making these changes.
🪄 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: e1298466-ae5e-4a9e-aaf3-1c4f03b06f10

📥 Commits

Reviewing files that changed from the base of the PR and between f7bec65 and ea715e7.

📒 Files selected for processing (8)
  • package.json
  • package.nls.json
  • src/notebooks/deepnote/agentCellExecutionHandler.ts
  • src/notebooks/deepnote/agentCellExecutionHandler.unit.test.ts
  • src/notebooks/deepnote/agentCellStatusBarProvider.ts
  • src/notebooks/deepnote/deepnoteSecretStore.ts
  • src/notebooks/deepnote/deepnoteSecretStore.unit.test.ts
  • src/notebooks/deepnote/ephemeralCellDecorationProvider.ts

Comment on lines +136 to +138
onAgentEvent: async (event: AgentStreamEvent) => {
logger.info('Agent event', JSON.stringify(event));
if (lastAgentEventType != null && lastAgentEventType !== event.type) {
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 logging full streamed agent events.

Line 137 logs full serialized payloads; this can expose user content/tool outputs in logs and increase log volume.

💡 Proposed fix
-                logger.info('Agent event', JSON.stringify(event));
+                logger.info('Agent event', {
+                    type: event.type,
+                    toolName: 'toolName' in event ? event.toolName : undefined,
+                    textLength: 'text' in event ? event.text.length : undefined,
+                    outputLength: 'output' in event ? event.output?.length : undefined
+                });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/notebooks/deepnote/agentCellExecutionHandler.ts` around lines 136 - 138,
The handler onAgentEvent currently logs the full serialized AgentStreamEvent
(logger.info('Agent event', JSON.stringify(event))) which can leak user/tool
content and bloat logs; change this to log only minimal metadata such as
event.type, any safe IDs or timestamps, and the transition detected using
lastAgentEventType (e.g., logger.info('Agent event', { type: event.type,
prevType: lastAgentEventType, timestamp: ... })) and remove
JSON.stringify(event) so no full payload is written to logs.

Comment on lines +240 to +245
const edit = new WorkspaceEdit();
edit.set(notebook.uri, [NotebookEdit.insertCells(insertIndex, [cellData])]);
await workspace.applyEdit(edit);

return insertIndex;
}
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

Insertion path assumes edit success and returns a possibly invalid index.

If the edit is not applied, caller logic can read/execute the wrong cell at the returned index.

💡 Proposed fix
     const edit = new WorkspaceEdit();
     edit.set(notebook.uri, [NotebookEdit.insertCells(insertIndex, [cellData])]);
-    await workspace.applyEdit(edit);
+    const applied = await workspace.applyEdit(edit);
+    if (!applied) {
+        throw new Error('Failed to insert ephemeral cell');
+    }

     return insertIndex;
 }

As per coding guidelines "Verify state after async setup operations - methods can return early without throwing, so check expected state was created".

📝 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
const edit = new WorkspaceEdit();
edit.set(notebook.uri, [NotebookEdit.insertCells(insertIndex, [cellData])]);
await workspace.applyEdit(edit);
return insertIndex;
}
const edit = new WorkspaceEdit();
edit.set(notebook.uri, [NotebookEdit.insertCells(insertIndex, [cellData])]);
const applied = await workspace.applyEdit(edit);
if (!applied) {
throw new Error('Failed to insert ephemeral cell');
}
return insertIndex;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/notebooks/deepnote/agentCellExecutionHandler.ts` around lines 240 - 245,
The code currently assumes workspace.applyEdit(edit) succeeded and returns
insertIndex blindly; change it to check the boolean result of await
workspace.applyEdit(edit) and verify the notebook now contains the inserted cell
(e.g. notebook.cellCount > insertIndex or try notebook.cellAt(insertIndex)
exists). If applyEdit returns false or the verification fails, throw an Error
(or return a sentinel/failure value as per project convention) instead of
returning insertIndex so callers won't operate on an invalid index; use the same
local symbols edit, insertIndex, notebook, WorkspaceEdit,
NotebookEdit.insertCells and workspace.applyEdit to locate and implement the
checks.

Comment on lines +264 to +283
if (token) {
if (token.isCancellationRequested) {
completionDeferred.reject(new CancellationError());
} else {
disposables.push(token.onCancellationRequested(() => completionDeferred.reject(new CancellationError())));
}
}

const timeout = setTimeout(() => {
completionDeferred.reject(new Error('Ephemeral cell execution timed out'));
}, EPHEMERAL_CELL_EXECUTION_TIMEOUT_MS);

try {
const cellIndex = cell.index;

await commands.executeCommand('notebook.cell.execute', {
ranges: [{ start: cellIndex, end: cellIndex + 1 }],
document: cell.notebook.uri
});

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

Pre-cancelled executions still run the cell command.

Line 265 rejects the deferred, but Line 279 still executes notebook.cell.execute. That can run code after cancellation.

💡 Proposed fix
 export async function executeEphemeralCell(
     cell: NotebookCell,
     token?: CancellationToken
 ): Promise<{ success: boolean; outputs: unknown[]; executionCount: number | null }> {
+    if (token?.isCancellationRequested) {
+        return {
+            success: false,
+            outputs: [],
+            executionCount: null
+        };
+    }
+
     const completionDeferred = createDeferred<void>();
     const disposables: IDisposable[] = [];
@@
-    if (token) {
-        if (token.isCancellationRequested) {
-            completionDeferred.reject(new CancellationError());
-        } else {
-            disposables.push(token.onCancellationRequested(() => completionDeferred.reject(new CancellationError())));
-        }
+    if (token) {
+        disposables.push(token.onCancellationRequested(() => completionDeferred.reject(new CancellationError())));
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/notebooks/deepnote/agentCellExecutionHandler.ts` around lines 264 - 283,
The code rejects completionDeferred when token.isCancellationRequested but still
proceeds to run commands.executeCommand('notebook.cell.execute'), allowing work
after cancellation; update the handler (around token, completionDeferred,
CancellationError and before commands.executeCommand) to short-circuit: if token
&& token.isCancellationRequested (or if completionDeferred has already been
rejected/settled) then clear the timeout, dispose any disposables, and
return/throw so commands.executeCommand is not invoked; ensure the same
early-exit path is taken when token.onCancellationRequested fires so cancelled
executions never call notebook.cell.execute.

Comment on lines +366 to +384
test('returns success false immediately when token is pre-cancelled', async () => {
const cell = createMockCell({ index: 0 });
const tokenSource = new CancellationTokenSource();
tokenSource.cancel();

when(mockedVSCodeNamespaces.commands.executeCommand(anything(), anything())).thenResolve();

try {
const result = await executeEphemeralCell(cell, tokenSource.token);

expect(result).to.deep.equal({
success: false,
outputs: [],
executionCount: null
});
} finally {
tokenSource.dispose();
}
});
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Strengthen cancellation test by asserting no execution command is sent.

The current assertion checks return value only. It should also verify notebook.cell.execute is not invoked for a pre-cancelled token.

💡 Proposed test addition
 import { anything, capture, instance, mock, reset, when } from 'ts-mockito';
+import { verify } from 'ts-mockito';
@@
         test('returns success false immediately when token is pre-cancelled', async () => {
@@
             try {
                 const result = await executeEphemeralCell(cell, tokenSource.token);

                 expect(result).to.deep.equal({
                     success: false,
                     outputs: [],
                     executionCount: null
                 });
+                verify(mockedVSCodeNamespaces.commands.executeCommand(anything(), anything())).never();
             } finally {
                 tokenSource.dispose();
             }
         });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/notebooks/deepnote/agentCellExecutionHandler.unit.test.ts` around lines
366 - 384, The test for executeEphemeralCell should also assert that no
execution request was sent when the token is pre-cancelled: after calling
executeEphemeralCell with the pre-cancelled CancellationTokenSource, add an
assertion that notebook.cell.execute was never invoked (i.e., verify/expect the
mocked notebook cell execution method did not get called), and keep the existing
assertion on the returned result; refer to executeEphemeralCell,
mockedVSCodeNamespaces.commands.executeCommand and the notebook.cell.execute
mock when adding this check.

Comment on lines +117 to +123
const lineRanges: Range[] = [];
for (let i = 0; i < editor.document.lineCount; i++) {
const line = editor.document.lineAt(i);
lineRanges.push(line.range);
}

editor.setDecorations(this.ephemeralDecorationType, lineRanges);
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

Use a single full-cell range instead of per-line ranges.

This loop allocates one Range per line on every refresh. For long cells, that is avoidable overhead.

💡 Proposed refactor
-            const lineRanges: Range[] = [];
-            for (let i = 0; i < editor.document.lineCount; i++) {
-                const line = editor.document.lineAt(i);
-                lineRanges.push(line.range);
-            }
-
-            editor.setDecorations(this.ephemeralDecorationType, lineRanges);
+            if (editor.document.lineCount === 0) {
+                editor.setDecorations(this.ephemeralDecorationType, []);
+                continue;
+            }
+
+            const first = editor.document.lineAt(0).range.start;
+            const last = editor.document.lineAt(editor.document.lineCount - 1).range.end;
+            editor.setDecorations(this.ephemeralDecorationType, [new Range(first, last)]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/notebooks/deepnote/ephemeralCellDecorationProvider.ts` around lines 117 -
123, The current loop in ephemeralCellDecorationProvider builds a Range per line
(lineRanges) and calls editor.setDecorations(this.ephemeralDecorationType,
lineRanges), which is wasteful; replace it by creating a single full-cell Range
spanning from the start of the first line to the end of the last line (use
editor.document.lineAt(0).range.start and
editor.document.lineAt(editor.document.lineCount - 1).range.end) and pass an
array with that single Range to
editor.setDecorations(this.ephemeralDecorationType, [fullRange]) so you avoid
allocating per-line Range objects while preserving the same decoration coverage.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant