From a5293b2fa65c9999f48fdb5cf1e56622c574ae60 Mon Sep 17 00:00:00 2001 From: can1357 Date: Wed, 21 Jan 2026 10:20:11 +0100 Subject: [PATCH] feat(coding-agent): added multi-cell Python execution with persistent kernel - Added multi-cell Python execution with sequential processing in persistent kernel. - Changed Python tool API to use cells array instead of single code parameter. - Renamed workdir parameter to cwd across Bash and Python tools for consistency. - Fixed indentation adjustment logic for mixed indentation levels in patch tool. --- packages/coding-agent/CHANGELOG.md | 14 ++ packages/coding-agent/src/core/tools/bash.ts | 12 +- packages/coding-agent/src/core/tools/index.ts | 2 +- .../src/core/tools/patch/index.ts | 2 +- .../src/core/tools/patch/normalize.ts | 9 ++ .../coding-agent/src/core/tools/python.ts | 146 +++++++++++++----- .../src/core/tools/task/executor.ts | 8 +- .../src/core/tools/task/worker.ts | 2 +- .../coding-agent/src/prompts/tools/python.md | 42 ++++- .../test/core/python-prelude.test.ts | 2 +- packages/coding-agent/test/edit-diff.test.ts | 18 +-- .../test/python-tool-settings.test.ts | 4 +- .../test/tools/python-execution.test.ts | 4 +- .../coding-agent/test/tools/python.test.ts | 6 +- packages/react-edit-benchmark/runner.ts | 18 +-- 15 files changed, 203 insertions(+), 86 deletions(-) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 6a45a2b83..665b746c4 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -1,6 +1,20 @@ # Changelog ## [Unreleased] +### Added + +- Added multi-cell Python execution with sequential processing in persistent kernel +- Added cell titles for better Python code organization and debugging + +### Changed + +- Changed Python tool to use `cells` array instead of single `code` parameter +- Renamed `workdir` parameter to `cwd` in Bash and Python tools for consistency +- Updated Python tool to display cell-by-cell output when multiple cells are provided + +### Fixed + +- Fixed indentation adjustment logic to handle edge cases with mixed indentation levels ## [6.9.0] - 2026-01-21 ### Removed diff --git a/packages/coding-agent/src/core/tools/bash.ts b/packages/coding-agent/src/core/tools/bash.ts index cb6d9bd23..8a5e2050d 100644 --- a/packages/coding-agent/src/core/tools/bash.ts +++ b/packages/coding-agent/src/core/tools/bash.ts @@ -20,9 +20,7 @@ export const BASH_DEFAULT_PREVIEW_LINES = 10; const bashSchema = Type.Object({ command: Type.String({ description: "Bash command to execute" }), timeout: Type.Optional(Type.Number({ description: "Timeout in seconds (optional, no default timeout)" })), - workdir: Type.Optional( - Type.String({ description: "Working directory for the command (default: current directory)" }), - ), + cwd: Type.Optional(Type.String({ description: "Working directory for the command (default: current directory)" })), }); export interface BashToolDetails { @@ -53,7 +51,7 @@ export class BashTool implements AgentTool { public async execute( _toolCallId: string, - { command, timeout, workdir }: { command: string; timeout?: number; workdir?: string }, + { command, timeout, cwd }: { command: string; timeout?: number; cwd?: string }, signal?: AbortSignal, onUpdate?: AgentToolUpdateCallback, ctx?: AgentToolContext, @@ -73,7 +71,7 @@ export class BashTool implements AgentTool { } } - const commandCwd = workdir ? resolveToCwd(workdir, this.session.cwd) : this.session.cwd; + const commandCwd = cwd ? resolveToCwd(cwd, this.session.cwd) : this.session.cwd; let cwdStat: Awaited>; try { cwdStat = await Bun.file(commandCwd).stat(); @@ -143,7 +141,7 @@ export class BashTool implements AgentTool { interface BashRenderArgs { command?: string; timeout?: number; - workdir?: string; + cwd?: string; } interface BashRenderContext { @@ -166,7 +164,7 @@ export const bashToolRenderer = { const command = args.command || uiTheme.format.ellipsis; const prompt = uiTheme.fg("accent", "$"); const cwd = process.cwd(); - let displayWorkdir = args.workdir; + let displayWorkdir = args.cwd; if (displayWorkdir) { const resolvedCwd = resolve(cwd); diff --git a/packages/coding-agent/src/core/tools/index.ts b/packages/coding-agent/src/core/tools/index.ts index a3ca334f7..1d3af6c11 100644 --- a/packages/coding-agent/src/core/tools/index.ts +++ b/packages/coding-agent/src/core/tools/index.ts @@ -221,7 +221,7 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P }); } else if (!isTestEnv && getPreludeDocs().length === 0) { const sessionFile = session.getSessionFile?.() ?? undefined; - const warmSessionId = sessionFile ? `session:${sessionFile}:workdir:${session.cwd}` : `cwd:${session.cwd}`; + const warmSessionId = sessionFile ? `session:${sessionFile}:cwd:${session.cwd}` : `cwd:${session.cwd}`; void warmPythonEnvironment(session.cwd, warmSessionId, session.settings?.getPythonSharedGateway?.()).catch( (err) => { logger.warn("Failed to warm Python environment", { diff --git a/packages/coding-agent/src/core/tools/patch/index.ts b/packages/coding-agent/src/core/tools/patch/index.ts index a339ea753..406ee5c74 100644 --- a/packages/coding-agent/src/core/tools/patch/index.ts +++ b/packages/coding-agent/src/core/tools/patch/index.ts @@ -48,7 +48,7 @@ export { DEFAULT_FUZZY_THRESHOLD, findContextLine, findMatch as findEditMatch, f // Normalization export { - adjustIndentation as adjustNewTextIndentation, + adjustIndentation, detectLineEnding, normalizeToLF, restoreLineEndings, diff --git a/packages/coding-agent/src/core/tools/patch/normalize.ts b/packages/coding-agent/src/core/tools/patch/normalize.ts index 9920b4e90..ec5daf0c1 100644 --- a/packages/coding-agent/src/core/tools/patch/normalize.ts +++ b/packages/coding-agent/src/core/tools/patch/normalize.ts @@ -193,12 +193,21 @@ export function normalizeForFuzzy(line: string): string { export function adjustIndentation(oldText: string, actualText: string, newText: string): string { const oldMin = minIndent(oldText); const actualMin = minIndent(actualText); + const newMin = minIndent(newText); const delta = actualMin - oldMin; if (delta === 0) { return newText; } + if (newMin === actualMin) { + return newText; + } + + if (newMin !== oldMin) { + return newText; + } + const indentChar = detectIndentChar(actualText); const lines = newText.split("\n"); diff --git a/packages/coding-agent/src/core/tools/python.ts b/packages/coding-agent/src/core/tools/python.ts index 80e1d7c96..2441352a0 100644 --- a/packages/coding-agent/src/core/tools/python.ts +++ b/packages/coding-agent/src/core/tools/python.ts @@ -39,15 +39,26 @@ function groupPreludeHelpers(helpers: PreludeHelper[]): PreludeCategory[] { } export const pythonSchema = Type.Object({ - code: Type.String({ description: "Python code to execute" }), - timeoutMs: Type.Optional(Type.Number({ description: "Timeout in milliseconds (default: 30000)" })), - workdir: Type.Optional( - Type.String({ description: "Working directory for the command (default: current directory)" }), + cells: Type.Array( + Type.Object({ + code: Type.String({ + description: + "Python code for this cell. Keep it focused (imports, helper, test, use). No narrative text—put explanations in the assistant message or in the cell title.", + }), + title: Type.Optional( + Type.String({ description: "Short label for the cell (e.g., 'imports', 'parse helper')." }), + ), + }), + { + description: + "Python cells to execute sequentially. Each cell runs in the same kernel—imports and variables persist. Keep cells small: one logical step each (import, define, test, use). If a cell fails, fix only that cell; earlier cells' state remains.", + }, ), + timeoutMs: Type.Optional(Type.Number({ description: "Timeout in milliseconds (default: 30000)" })), + cwd: Type.Optional(Type.String({ description: "Working directory for the command (default: current directory)" })), reset: Type.Optional(Type.Boolean({ description: "Restart the kernel before executing this code" })), }); - -export type PythonToolParams = { code: string; timeout?: number; workdir?: string; reset?: boolean }; +export type PythonToolParams = Static; export type PythonToolResult = { content: Array<{ type: "text"; text: string }>; @@ -151,7 +162,7 @@ export class PythonTool implements AgentTool { throw new Error("Python tool requires a session when not using proxy executor"); } - const { code, timeoutMs = 30000, workdir, reset } = params; + const { cells, timeoutMs = 30000, cwd, reset } = params; const controller = new AbortController(); const onAbort = () => controller.abort(); signal?.addEventListener("abort", onAbort, { once: true }); @@ -161,7 +172,7 @@ export class PythonTool implements AgentTool { throw new Error("Aborted"); } - const commandCwd = workdir ? resolveToCwd(workdir, this.session.cwd) : this.session.cwd; + const commandCwd = cwd ? resolveToCwd(cwd, this.session.cwd) : this.session.cwd; let cwdStat: Awaited>; try { cwdStat = await Bun.file(commandCwd).stat(); @@ -179,15 +190,14 @@ export class PythonTool implements AgentTool { const images: ImageContent[] = []; const sessionFile = this.session.getSessionFile?.() ?? undefined; - const sessionId = sessionFile ? `session:${sessionFile}:workdir:${commandCwd}` : `cwd:${commandCwd}`; - const executorOptions: PythonExecutorOptions = { + const sessionId = sessionFile ? `session:${sessionFile}:cwd:${commandCwd}` : `cwd:${commandCwd}`; + const baseExecutorOptions: Omit = { cwd: commandCwd, timeoutMs, signal: controller.signal, sessionId, kernelMode: this.session.settings?.getPythonKernelMode?.() ?? "session", useSharedGateway: this.session.settings?.getPythonSharedGateway?.() ?? true, - reset, onChunk: (chunk) => { const chunkBytes = Buffer.byteLength(chunk, "utf-8"); tailChunks.push({ text: chunk, bytes: chunkBytes }); @@ -209,26 +219,65 @@ export class PythonTool implements AgentTool { }, }; - const result = await executePython(code, executorOptions); - const statusEvents: PythonStatusEvent[] = []; - for (const output of result.displayOutputs) { - if (output.type === "json") { - jsonOutputs.push(output.data); + const cellOutputs: string[] = []; + let lastFullOutputPath: string | undefined; + + for (let i = 0; i < cells.length; i++) { + const cell = cells[i]; + const isFirstCell = i === 0; + const executorOptions: PythonExecutorOptions = { + ...baseExecutorOptions, + reset: isFirstCell ? reset : false, + }; + + const result = await executePython(cell.code, executorOptions); + + for (const output of result.displayOutputs) { + if (output.type === "json") { + jsonOutputs.push(output.data); + } + if (output.type === "image") { + images.push({ type: "image", data: output.data, mimeType: output.mimeType }); + } + if (output.type === "status") { + statusEvents.push(output.event); + } } - if (output.type === "image") { - images.push({ type: "image", data: output.data, mimeType: output.mimeType }); + + if (result.fullOutputPath) { + lastFullOutputPath = result.fullOutputPath; } - if (output.type === "status") { - statusEvents.push(output.event); + + const cellOutput = result.output.trim(); + if (cells.length > 1) { + const cellHeader = `[${i + 1}/${cells.length}]`; + const cellTitle = cell.title ? ` ${cell.title}` : ""; + if (cellOutput) { + cellOutputs.push(`${cellHeader}${cellTitle}\n${cellOutput}`); + } else { + cellOutputs.push(`${cellHeader}${cellTitle} (ok)`); + } + } else if (cellOutput) { + cellOutputs.push(cellOutput); + } + if (result.cancelled) { + const errorMsg = result.output || "Command aborted"; + throw new Error(cells.length > 1 ? `Cell ${i + 1} aborted: ${errorMsg}` : errorMsg); + } + + if (result.exitCode !== 0 && result.exitCode !== undefined) { + const combinedOutput = cellOutputs.join("\n\n"); + throw new Error( + cells.length > 1 + ? `${combinedOutput}\n\nCell ${i + 1} failed (exit code ${result.exitCode}). Earlier cells succeeded—their state persists. Fix only cell ${i + 1}.` + : `${combinedOutput}\n\nCommand exited with code ${result.exitCode}`, + ); } } - if (result.cancelled) { - throw new Error(result.output || "Command aborted"); - } - - const truncation = truncateTail(result.output); + const combinedOutput = cellOutputs.join("\n\n"); + const truncation = truncateTail(combinedOutput); let outputText = truncation.content || (jsonOutputs.length > 0 || images.length > 0 ? "(no text output)" : "(no output)"); let details: PythonToolDetails | undefined; @@ -236,14 +285,14 @@ export class PythonTool implements AgentTool { if (truncation.truncated) { details = { truncation, - fullOutputPath: result.fullOutputPath, + fullOutputPath: lastFullOutputPath, jsonOutputs: jsonOutputs, images, statusEvents: statusEvents.length > 0 ? statusEvents : undefined, }; outputText += formatTailTruncationNotice(truncation, { - fullOutputPath: result.fullOutputPath, - originalContent: result.output, + fullOutputPath: lastFullOutputPath, + originalContent: combinedOutput, }); } @@ -255,11 +304,6 @@ export class PythonTool implements AgentTool { }; } - if (result.exitCode !== 0 && result.exitCode !== undefined) { - outputText += `\n\nCommand exited with code ${result.exitCode}`; - throw new Error(outputText); - } - return { content: [{ type: "text", text: outputText }], details }; } finally { signal?.removeEventListener("abort", onAbort); @@ -268,9 +312,9 @@ export class PythonTool implements AgentTool { } interface PythonRenderArgs { - code?: string; + cells?: Array<{ code: string; title?: string }>; timeout?: number; - workdir?: string; + cwd?: string; } interface PythonRenderContext { @@ -603,10 +647,10 @@ function renderStatusEvents(events: PythonStatusEvent[], theme: Theme, expanded: export const pythonToolRenderer = { renderCall(args: PythonRenderArgs, uiTheme: Theme): Component { const ui = new ToolUIKit(uiTheme); - const code = args.code || uiTheme.format.ellipsis; + const cells = args.cells ?? []; const prompt = uiTheme.fg("accent", ">>>"); const cwd = process.cwd(); - let displayWorkdir = args.workdir; + let displayWorkdir = args.cwd; if (displayWorkdir) { const resolvedCwd = resolve(cwd); @@ -622,11 +666,29 @@ export const pythonToolRenderer = { } } - const cmdText = displayWorkdir - ? `${prompt} ${uiTheme.fg("dim", `cd ${displayWorkdir} &&`)} ${code}` - : `${prompt} ${code}`; - const text = ui.title(cmdText); - return new Text(text, 0, 0); + const workdirPrefix = displayWorkdir ? uiTheme.fg("dim", `cd ${displayWorkdir} && `) : ""; + + if (cells.length === 0) { + const text = ui.title(`${prompt} ${workdirPrefix}${uiTheme.format.ellipsis}`); + return new Text(text, 0, 0); + } + + if (cells.length === 1) { + const cell = cells[0]; + const label = cell.title ? `${cell.title}: ` : ""; + const text = ui.title(`${prompt} ${workdirPrefix}${label}${cell.code}`); + return new Text(text, 0, 0); + } + + // Multiple cells: show each with index + const lines: string[] = []; + for (let i = 0; i < cells.length; i++) { + const cellPrompt = uiTheme.fg("accent", `[${i + 1}]`); + const prefix = i === 0 ? workdirPrefix : ""; + const label = cells[i].title ? `${cells[i].title}: ` : ""; + lines.push(ui.title(`${cellPrompt} ${prefix}${label}${cells[i].code}`)); + } + return new Text(lines.join("\n"), 0, 0); }, renderResult( diff --git a/packages/coding-agent/src/core/tools/task/executor.ts b/packages/coding-agent/src/core/tools/task/executor.ts index b89c46315..31ac6eeab 100644 --- a/packages/coding-agent/src/core/tools/task/executor.ts +++ b/packages/coding-agent/src/core/tools/task/executor.ts @@ -16,7 +16,7 @@ import { checkPythonKernelAvailability } from "../../python-kernel"; import type { ToolSession } from ".."; import { LspTool } from "../lsp/index"; import type { LspParams } from "../lsp/types"; -import { PythonTool } from "../python"; +import { PythonTool, type PythonToolParams } from "../python"; import { subprocessToolRegistry } from "./subprocess-tool-registry"; import { type AgentDefinition, @@ -789,11 +789,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise { } function getPythonCallTimeoutMs(params: PythonToolParams): number | undefined { - const timeout = params.timeout; + const timeout = params.timeoutMs; if (typeof timeout === "number" && Number.isFinite(timeout) && timeout > 0) { return Math.max(1000, Math.round(timeout * 1000) + 1000); } diff --git a/packages/coding-agent/src/prompts/tools/python.md b/packages/coding-agent/src/prompts/tools/python.md index 02b418839..555067059 100644 --- a/packages/coding-agent/src/prompts/tools/python.md +++ b/packages/coding-agent/src/prompts/tools/python.md @@ -1,4 +1,42 @@ -Executes Python code in an IPython kernel (session or per-call) with optional timeout. +Executes Python cells sequentially in a persistent IPython kernel. + +## How to use (REPL discipline) + +The kernel persists between calls and between cells. **Imports, variables, and functions survive.** Use this. + +**Work incrementally:** +- One logical step per cell (imports, define a function, test it, use it) +- Pass multiple small cells in one call—they execute sequentially +- Define small functions you can reuse and debug individually +- Put explanations in the assistant message or cell title, **not** inside code + +**When something fails:** +- The error tells you which cell failed (e.g., "Cell 3 failed") +- Earlier cells already ran—their state persists in the kernel +- Resubmit with only the fixed cell (or the fixed cell + remaining cells) +- Do NOT rewrite working cells or re-import modules + +**Anti-patterns to avoid:** +- Putting everything in one giant cell +- Re-importing modules you already imported +- Rewriting working code when only one part failed +- Large functions that are hard to debug piece by piece + +```python +# BAD: One giant cell +cells: [{ + "title": "all-in-one", + "code": "import json\nfrom pathlib import Path\ndef process_all_files():\n # 50 lines...\n pass\nresult = process_all_files()" +}] + +# GOOD: Multiple small cells +cells: [ + {"title": "imports", "code": "import json\nfrom pathlib import Path"}, + {"title": "parse helper", "code": "def parse_config(path):\n return json.loads(Path(path).read_text())"}, + {"title": "test helper", "code": "parse_config('config.json')"}, + {"title": "use helper", "code": "configs = [parse_config(p) for p in Path('.').glob('*.json')]"} +] +``` ## When to use Python @@ -75,7 +113,7 @@ cols(read("data.tsv"), 0, 2, sep="\t") - Code executes as IPython cells; users see the full cell output (including rendered figures, tables, etc.) - Kernel persists for the session by default; per-call mode uses a fresh kernel each call. Use `reset: true` to clear state when session mode is active -- Use `workdir` parameter instead of `os.chdir()` in tool call +- Use `cwd` parameter instead of `os.chdir()` in tool call - Use `plt.show()` to display figures - Use `display()` from IPython.display for rich output (HTML, Markdown, images, etc.) - Output streams in real time, truncated after 50KB diff --git a/packages/coding-agent/test/core/python-prelude.test.ts b/packages/coding-agent/test/core/python-prelude.test.ts index 18a5bcb6f..b41923a7b 100644 --- a/packages/coding-agent/test/core/python-prelude.test.ts +++ b/packages/coding-agent/test/core/python-prelude.test.ts @@ -114,7 +114,7 @@ describe.skipIf(!shouldRun)("PYTHON_PRELUDE integration", () => { print("MISSING=" + ",".join(missing)) `; - const result = await tool.execute("tool-call-1", { code }); + const result = await tool.execute("tool-call-1", { cells: [{ code }] }); const output = result.content.find((item) => item.type === "text")?.text ?? ""; expect(output).toContain("HELPERS_OK=1"); expect(output).toContain("DOCS_OK=1"); diff --git a/packages/coding-agent/test/edit-diff.test.ts b/packages/coding-agent/test/edit-diff.test.ts index 95d68a358..3d0793bb2 100644 --- a/packages/coding-agent/test/edit-diff.test.ts +++ b/packages/coding-agent/test/edit-diff.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test"; -import { adjustNewTextIndentation, DEFAULT_FUZZY_THRESHOLD, findEditMatch } from "../src/core/tools/patch"; +import { adjustIndentation, DEFAULT_FUZZY_THRESHOLD, findEditMatch } from "../src/core/tools/patch"; describe("findEditMatch", () => { describe("exact matching", () => { @@ -149,12 +149,12 @@ describe("findEditMatch", () => { }); }); -describe("adjustNewTextIndentation", () => { +describe("adjustIndentation", () => { test("adds indentation when actualText is more indented than oldText", () => { const oldText = "foo\nbar"; const actualText = " foo\n bar"; const newText = "foo\nbaz\nbar"; - const result = adjustNewTextIndentation(oldText, actualText, newText); + const result = adjustIndentation(oldText, actualText, newText); expect(result).toBe(" foo\n baz\n bar"); }); @@ -162,7 +162,7 @@ describe("adjustNewTextIndentation", () => { const oldText = " foo\n bar"; const actualText = " foo\n bar"; const newText = " foo\n baz"; - const result = adjustNewTextIndentation(oldText, actualText, newText); + const result = adjustIndentation(oldText, actualText, newText); expect(result).toBe(" foo\n baz"); }); @@ -170,7 +170,7 @@ describe("adjustNewTextIndentation", () => { const oldText = "foo\n\nbar"; const actualText = " foo\n\n bar"; const newText = "foo\n\nbaz"; - const result = adjustNewTextIndentation(oldText, actualText, newText); + const result = adjustIndentation(oldText, actualText, newText); expect(result).toBe(" foo\n\n baz"); }); @@ -178,7 +178,7 @@ describe("adjustNewTextIndentation", () => { const oldText = " foo"; const actualText = " foo"; const newText = " bar"; - const result = adjustNewTextIndentation(oldText, actualText, newText); + const result = adjustIndentation(oldText, actualText, newText); expect(result).toBe(" bar"); }); @@ -186,7 +186,7 @@ describe("adjustNewTextIndentation", () => { const oldText = "foo"; const actualText = "\t\tfoo"; const newText = "bar"; - const result = adjustNewTextIndentation(oldText, actualText, newText); + const result = adjustIndentation(oldText, actualText, newText); expect(result).toBe("\t\tbar"); }); @@ -194,7 +194,7 @@ describe("adjustNewTextIndentation", () => { const oldText = "if (x) {\n return y;\n}"; const actualText = " if (x) {\n return y;\n }"; const newText = "if (x) {\n return z;\n}"; - const result = adjustNewTextIndentation(oldText, actualText, newText); + const result = adjustIndentation(oldText, actualText, newText); expect(result).toBe(" if (x) {\n return z;\n }"); }); @@ -202,7 +202,7 @@ describe("adjustNewTextIndentation", () => { const oldText = " foo"; const actualText = "foo"; const newText = " bar"; - const result = adjustNewTextIndentation(oldText, actualText, newText); + const result = adjustIndentation(oldText, actualText, newText); // Should remove up to 4 chars, but line only has 2, so remove 2 expect(result).toBe("bar"); }); diff --git a/packages/coding-agent/test/python-tool-settings.test.ts b/packages/coding-agent/test/python-tool-settings.test.ts index 5348e65da..12cf6cc94 100644 --- a/packages/coding-agent/test/python-tool-settings.test.ts +++ b/packages/coding-agent/test/python-tool-settings.test.ts @@ -77,13 +77,13 @@ describe("python tool settings", () => { const session = createSession(testDir, { getPythonKernelMode: () => "per-call" }); const pythonTool = new PythonTool(session); - await pythonTool.execute("tool-call", { code: "print(1)" }); + await pythonTool.execute("tool-call", { cells: [{ code: "print(1)" }] }); expect(executeSpy).toHaveBeenCalledWith( "print(1)", expect.objectContaining({ kernelMode: "per-call", - sessionId: `session:session.json:workdir:${testDir}`, + sessionId: `session:session.json:cwd:${testDir}`, }), ); }); diff --git a/packages/coding-agent/test/tools/python-execution.test.ts b/packages/coding-agent/test/tools/python-execution.test.ts index a61cdf72e..e4589b4e1 100644 --- a/packages/coding-agent/test/tools/python-execution.test.ts +++ b/packages/coding-agent/test/tools/python-execution.test.ts @@ -40,7 +40,7 @@ describe("python tool execution", () => { const tool = new PythonTool(createSession(tempDir.path)); const result = await tool.execute( "call-id", - { code: "print('hi')", timeoutMs: 5000, workdir: tempDir.path, reset: true }, + { cells: [{ code: "print('hi')" }], timeoutMs: 5000, cwd: tempDir.path, reset: true }, undefined, undefined, undefined, @@ -51,7 +51,7 @@ describe("python tool execution", () => { expect.objectContaining({ cwd: tempDir.path, timeoutMs: 5000, - sessionId: `session:session-file:workdir:${tempDir.path}`, + sessionId: `session:session-file:cwd:${tempDir.path}`, kernelMode: "per-call", reset: true, }), diff --git a/packages/coding-agent/test/tools/python.test.ts b/packages/coding-agent/test/tools/python.test.ts index 39e24a9ad..9e7ddf3aa 100644 --- a/packages/coding-agent/test/tools/python.test.ts +++ b/packages/coding-agent/test/tools/python.test.ts @@ -56,11 +56,11 @@ describe("python tool schema", () => { }; expect(schema.type).toBe("object"); - expect(schema.properties.code.type).toBe("string"); + expect(schema.properties.cells.type).toBe("array"); expect(schema.properties.timeoutMs.type).toBe("number"); - expect(schema.properties.workdir.type).toBe("string"); + expect(schema.properties.cwd.type).toBe("string"); expect(schema.properties.reset.type).toBe("boolean"); - expect(schema.required).toEqual(["code"]); + expect(schema.required).toEqual(["cells"]); }); }); diff --git a/packages/react-edit-benchmark/runner.ts b/packages/react-edit-benchmark/runner.ts index a8521e72d..09f39edcc 100644 --- a/packages/react-edit-benchmark/runner.ts +++ b/packages/react-edit-benchmark/runner.ts @@ -162,7 +162,7 @@ async function runSingleTask( task: EditTask, runIndex: number, config: BenchmarkConfig, - workDir: string, + cwd: string, expectedDir: string, cliPath: string, ): Promise { @@ -193,7 +193,7 @@ async function runSingleTask( }; try { - await appendFile(logFile, `{"type":"meta","task":"${task.id}","run":${runIndex},"workDir":"${workDir}"}\n`); + await appendFile(logFile, `{"type":"meta","task":"${task.id}","run":${runIndex},"workDir":"${cwd}"}\n`); const env: Record = { OMP_NO_TITLE: "1" }; if (config.editVariant !== undefined) { @@ -209,7 +209,7 @@ async function runSingleTask( client = new RpcClient({ cliPath, - cwd: workDir, + cwd, provider: config.provider, model: config.model, args: ["--tools", "read,edit,write,ls"], @@ -323,9 +323,9 @@ ${ patchApplied = toolStats.edit > 0; - const verification = await verifyExpectedFiles(expectedDir, workDir); + const verification = await verifyExpectedFiles(expectedDir, cwd); if (config.autoFormat) { - await formatDirectory(workDir); + await formatDirectory(cwd); } verificationPassed = verification.success; @@ -382,7 +382,7 @@ ${ async function runBatchedTask( item: TaskRunItem, config: BenchmarkConfig, - workDir: string, + cwd: string, expectedDir: string, client: RpcClient, ): Promise { @@ -416,7 +416,7 @@ async function runBatchedTask( try { await appendFile( logFile, - `{"type":"meta","task":"${task.id}","run":${runIndex},"workDir":"${workDir}","batched":true}\n`, + `{"type":"meta","task":"${task.id}","run":${runIndex},"workDir":"${cwd}","batched":true}\n`, ); const promptWithContext = buildPrompt(task, config); @@ -465,9 +465,9 @@ async function runBatchedTask( patchApplied = toolStats.edit > 0; const filesToVerify = task.files.length > 0 ? task.files : undefined; - const verification = await verifyExpectedFileSubset(expectedDir, workDir, filesToVerify); + const verification = await verifyExpectedFileSubset(expectedDir, cwd, filesToVerify); if (config.autoFormat) { - await formatDirectory(workDir); + await formatDirectory(cwd); } verificationPassed = verification.success;