@oh-my-pi/pi-coding-agent 14.0.5 → 14.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/CHANGELOG.md +120 -0
  2. package/package.json +8 -8
  3. package/src/async/index.ts +1 -0
  4. package/src/async/job-manager.ts +43 -10
  5. package/src/async/support.ts +5 -0
  6. package/src/cli/list-models.ts +96 -57
  7. package/src/commit/agentic/tools/analyze-file.ts +1 -2
  8. package/src/commit/model-selection.ts +16 -13
  9. package/src/config/mcp-schema.json +1 -1
  10. package/src/config/model-equivalence.ts +675 -0
  11. package/src/config/model-registry.ts +242 -45
  12. package/src/config/model-resolver.ts +282 -65
  13. package/src/config/settings-schema.ts +27 -3
  14. package/src/config/settings.ts +1 -1
  15. package/src/cursor.ts +64 -23
  16. package/src/edit/index.ts +254 -89
  17. package/src/edit/modes/chunk.ts +336 -57
  18. package/src/edit/modes/hashline.ts +51 -26
  19. package/src/edit/modes/patch.ts +16 -10
  20. package/src/edit/modes/replace.ts +15 -7
  21. package/src/edit/renderer.ts +248 -94
  22. package/src/export/html/template.css +82 -0
  23. package/src/export/html/template.generated.ts +1 -1
  24. package/src/export/html/template.js +614 -97
  25. package/src/extensibility/custom-tools/types.ts +0 -3
  26. package/src/extensibility/extensions/loader.ts +16 -0
  27. package/src/extensibility/extensions/runner.ts +2 -7
  28. package/src/extensibility/extensions/types.ts +8 -4
  29. package/src/internal-urls/docs-index.generated.ts +4 -4
  30. package/src/internal-urls/jobs-protocol.ts +2 -1
  31. package/src/ipy/executor.ts +447 -52
  32. package/src/ipy/kernel.ts +39 -13
  33. package/src/lsp/client.ts +55 -1
  34. package/src/lsp/index.ts +8 -0
  35. package/src/lsp/types.ts +6 -0
  36. package/src/main.ts +6 -2
  37. package/src/memories/index.ts +7 -6
  38. package/src/modes/acp/acp-agent.ts +4 -1
  39. package/src/modes/components/bash-execution.ts +16 -4
  40. package/src/modes/components/model-selector.ts +221 -64
  41. package/src/modes/components/status-line/presets.ts +17 -6
  42. package/src/modes/components/status-line/segments.ts +15 -0
  43. package/src/modes/components/status-line-segment-editor.ts +1 -0
  44. package/src/modes/components/status-line.ts +7 -1
  45. package/src/modes/components/tool-execution.ts +145 -75
  46. package/src/modes/controllers/command-controller.ts +42 -1
  47. package/src/modes/controllers/event-controller.ts +4 -1
  48. package/src/modes/controllers/extension-ui-controller.ts +28 -5
  49. package/src/modes/controllers/input-controller.ts +9 -3
  50. package/src/modes/controllers/selector-controller.ts +17 -6
  51. package/src/modes/interactive-mode.ts +19 -3
  52. package/src/modes/print-mode.ts +13 -4
  53. package/src/modes/prompt-action-autocomplete.ts +3 -5
  54. package/src/modes/rpc/rpc-mode.ts +8 -2
  55. package/src/modes/shared.ts +2 -2
  56. package/src/modes/types.ts +1 -0
  57. package/src/modes/utils/ui-helpers.ts +1 -0
  58. package/src/prompts/system/system-prompt.md +5 -1
  59. package/src/prompts/tools/bash.md +16 -1
  60. package/src/prompts/tools/cancel-job.md +1 -1
  61. package/src/prompts/tools/chunk-edit.md +191 -163
  62. package/src/prompts/tools/hashline.md +11 -11
  63. package/src/prompts/tools/patch.md +10 -5
  64. package/src/prompts/tools/{await.md → poll.md} +1 -1
  65. package/src/prompts/tools/read-chunk.md +12 -3
  66. package/src/prompts/tools/read.md +9 -0
  67. package/src/prompts/tools/task.md +2 -2
  68. package/src/prompts/tools/vim.md +98 -0
  69. package/src/prompts/tools/write.md +1 -0
  70. package/src/sdk.ts +758 -725
  71. package/src/session/agent-session.ts +187 -40
  72. package/src/session/session-manager.ts +50 -4
  73. package/src/slash-commands/builtin-registry.ts +17 -0
  74. package/src/task/executor.ts +9 -5
  75. package/src/task/index.ts +3 -5
  76. package/src/task/types.ts +2 -2
  77. package/src/tools/bash.ts +240 -57
  78. package/src/tools/cancel-job.ts +2 -1
  79. package/src/tools/find.ts +5 -2
  80. package/src/tools/grep.ts +77 -8
  81. package/src/tools/index.ts +48 -19
  82. package/src/tools/inspect-image.ts +1 -1
  83. package/src/tools/{await-tool.ts → poll-tool.ts} +38 -31
  84. package/src/tools/python.ts +293 -278
  85. package/src/tools/read.ts +218 -1
  86. package/src/tools/sqlite-reader.ts +623 -0
  87. package/src/tools/submit-result.ts +5 -2
  88. package/src/tools/todo-write.ts +8 -2
  89. package/src/tools/vim.ts +966 -0
  90. package/src/tools/write.ts +187 -1
  91. package/src/utils/commit-message-generator.ts +1 -0
  92. package/src/utils/edit-mode.ts +2 -1
  93. package/src/utils/git.ts +24 -1
  94. package/src/utils/session-color.ts +55 -0
  95. package/src/utils/title-generator.ts +16 -7
  96. package/src/vim/buffer.ts +309 -0
  97. package/src/vim/commands.ts +382 -0
  98. package/src/vim/engine.ts +2426 -0
  99. package/src/vim/parser.ts +151 -0
  100. package/src/vim/render.ts +252 -0
  101. package/src/vim/types.ts +197 -0
@@ -175,6 +175,7 @@ export class PythonTool implements AgentTool<typeof pythonSchema> {
175
175
  if (!this.session) {
176
176
  throw new ToolError("Python tool requires a session when not using proxy executor");
177
177
  }
178
+ const session = this.session;
178
179
 
179
180
  const { cells, timeout: rawTimeout = 30, cwd, reset } = params;
180
181
  // Clamp to reasonable range: 1s - 600s (10 min)
@@ -182,7 +183,10 @@ export class PythonTool implements AgentTool<typeof pythonSchema> {
182
183
  const timeoutMs = timeoutSec * 1000;
183
184
  const deadlineMs = Date.now() + timeoutMs;
184
185
  const timeoutSignal = AbortSignal.timeout(Math.max(0, deadlineMs - Date.now()));
185
- const combinedSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
186
+ const sessionAbortController = new AbortController();
187
+ const combinedSignal = signal
188
+ ? AbortSignal.any([signal, timeoutSignal, sessionAbortController.signal])
189
+ : AbortSignal.any([timeoutSignal, sessionAbortController.signal]);
186
190
  let outputSink: OutputSink | undefined;
187
191
  let outputSummary: OutputSummary | undefined;
188
192
  let outputDumped = false;
@@ -193,311 +197,322 @@ export class PythonTool implements AgentTool<typeof pythonSchema> {
193
197
  return outputSummary;
194
198
  };
195
199
 
196
- try {
197
- if (signal?.aborted) {
198
- throw new ToolAbortError();
199
- }
200
-
201
- const commandCwd = cwd ? resolveToCwd(cwd, this.session.cwd) : this.session.cwd;
202
- let cwdStat: fs.Stats;
200
+ const execution = (async (): Promise<AgentToolResult<PythonToolDetails | undefined>> => {
203
201
  try {
204
- cwdStat = await Bun.file(commandCwd).stat();
205
- } catch {
206
- throw new ToolError(`Working directory does not exist: ${commandCwd}`);
207
- }
208
- if (!cwdStat.isDirectory()) {
209
- throw new ToolError(`Working directory is not a directory: ${commandCwd}`);
210
- }
211
-
212
- const tailBuffer = new TailBuffer(DEFAULT_MAX_BYTES * 2);
213
- const jsonOutputs: unknown[] = [];
214
- const images: ImageContent[] = [];
215
- const statusEvents: PythonStatusEvent[] = [];
216
-
217
- const cellResults: PythonCellResult[] = cells.map((cell, index) => ({
218
- index,
219
- title: cell.title,
220
- code: cell.code,
221
- output: "",
222
- status: "pending",
223
- }));
224
- const cellOutputs: string[] = [];
225
-
226
- const appendTail = (text: string) => {
227
- tailBuffer.append(text);
228
- };
229
-
230
- const buildUpdateDetails = (): PythonToolDetails => {
231
- const details: PythonToolDetails = {
232
- cells: cellResults.map(cell => ({
233
- ...cell,
234
- statusEvents: cell.statusEvents ? [...cell.statusEvents] : undefined,
235
- })),
236
- };
237
- if (jsonOutputs.length > 0) {
238
- details.jsonOutputs = jsonOutputs;
239
- }
240
- if (images.length > 0) {
241
- details.images = images;
202
+ if (signal?.aborted) {
203
+ throw new ToolAbortError();
242
204
  }
243
- if (statusEvents.length > 0) {
244
- details.statusEvents = statusEvents;
245
- }
246
- return details;
247
- };
248
-
249
- const pushUpdate = () => {
250
- if (!onUpdate) return;
251
- const tailText = tailBuffer.text();
252
- onUpdate({
253
- content: [{ type: "text", text: tailText }],
254
- details: buildUpdateDetails(),
255
- });
256
- };
205
+ session.assertPythonExecutionAllowed?.();
257
206
 
258
- const sessionFile = this.session.getSessionFile?.() ?? undefined;
259
- const { path: artifactPath, id: artifactId } = (await this.session.allocateOutputArtifact?.("python")) ?? {};
260
- outputSink = new OutputSink({
261
- artifactPath,
262
- artifactId,
263
- onChunk: chunk => {
264
- appendTail(chunk);
265
- pushUpdate();
266
- },
267
- });
268
- const sessionId = sessionFile ? `session:${sessionFile}:cwd:${commandCwd}` : `cwd:${commandCwd}`;
269
-
270
- if (getPreludeDocs().length === 0) {
271
- const warmup = await warmPythonEnvironment(
272
- commandCwd,
273
- sessionId,
274
- this.session.settings.get("python.sharedGateway"),
275
- sessionFile ?? undefined,
276
- );
277
- if (!warmup.ok) {
278
- throw new ToolError(warmup.reason ?? "Python prelude helpers unavailable");
207
+ const commandCwd = cwd ? resolveToCwd(cwd, session.cwd) : session.cwd;
208
+ let cwdStat: fs.Stats;
209
+ try {
210
+ cwdStat = await Bun.file(commandCwd).stat();
211
+ } catch {
212
+ throw new ToolError(`Working directory does not exist: ${commandCwd}`);
213
+ }
214
+ if (!cwdStat.isDirectory()) {
215
+ throw new ToolError(`Working directory is not a directory: ${commandCwd}`);
279
216
  }
280
- }
281
-
282
- const baseExecutorOptions: Omit<PythonExecutorOptions, "reset"> = {
283
- cwd: commandCwd,
284
- deadlineMs,
285
- signal: combinedSignal,
286
- sessionId,
287
- kernelMode: this.session.settings.get("python.kernelMode"),
288
- useSharedGateway: this.session.settings.get("python.sharedGateway"),
289
- sessionFile: sessionFile ?? undefined,
290
- };
291
217
 
292
- for (let i = 0; i < cells.length; i++) {
293
- const cell = cells[i];
294
- const isFirstCell = i === 0;
295
- const cellResult = cellResults[i];
296
- cellResult.status = "running";
297
- cellResult.output = "";
298
- cellResult.statusEvents = undefined;
299
- cellResult.exitCode = undefined;
300
- cellResult.durationMs = undefined;
301
- pushUpdate();
302
-
303
- const executorOptions: PythonExecutorOptions = {
304
- ...baseExecutorOptions,
305
- reset: isFirstCell ? reset : false,
306
- onChunk: chunk => {
307
- outputSink!.push(chunk);
308
- },
218
+ const tailBuffer = new TailBuffer(DEFAULT_MAX_BYTES * 2);
219
+ const jsonOutputs: unknown[] = [];
220
+ const images: ImageContent[] = [];
221
+ const statusEvents: PythonStatusEvent[] = [];
222
+
223
+ const cellResults: PythonCellResult[] = cells.map((cell, index) => ({
224
+ index,
225
+ title: cell.title,
226
+ code: cell.code,
227
+ output: "",
228
+ status: "pending",
229
+ }));
230
+ const cellOutputs: string[] = [];
231
+
232
+ const appendTail = (text: string) => {
233
+ tailBuffer.append(text);
309
234
  };
310
235
 
311
- const startTime = Date.now();
312
- const result = await executePython(cell.code, executorOptions);
313
- const durationMs = Date.now() - startTime;
314
-
315
- const cellStatusEvents: PythonStatusEvent[] = [];
316
- let cellHasMarkdown = false;
317
- for (const output of result.displayOutputs) {
318
- if (output.type === "json") {
319
- jsonOutputs.push(output.data);
320
- }
321
- if (output.type === "image") {
322
- images.push({ type: "image", data: output.data, mimeType: output.mimeType });
236
+ const buildUpdateDetails = (): PythonToolDetails => {
237
+ const details: PythonToolDetails = {
238
+ cells: cellResults.map(cell => ({
239
+ ...cell,
240
+ statusEvents: cell.statusEvents ? [...cell.statusEvents] : undefined,
241
+ })),
242
+ };
243
+ if (jsonOutputs.length > 0) {
244
+ details.jsonOutputs = jsonOutputs;
323
245
  }
324
- if (output.type === "status") {
325
- statusEvents.push(output.event);
326
- cellStatusEvents.push(output.event);
246
+ if (images.length > 0) {
247
+ details.images = images;
327
248
  }
328
- if (output.type === "markdown") {
329
- cellHasMarkdown = true;
249
+ if (statusEvents.length > 0) {
250
+ details.statusEvents = statusEvents;
330
251
  }
331
- }
252
+ return details;
253
+ };
332
254
 
333
- const cellOutput = result.output.trim();
334
- cellResult.output = cellOutput;
335
- cellResult.exitCode = result.exitCode;
336
- cellResult.durationMs = durationMs;
337
- cellResult.statusEvents = cellStatusEvents.length > 0 ? cellStatusEvents : undefined;
338
- cellResult.hasMarkdown = cellHasMarkdown || undefined;
339
-
340
- let combinedCellOutput = "";
341
- if (cells.length > 1) {
342
- const cellHeader = `[${i + 1}/${cells.length}]`;
343
- const cellTitle = cell.title ? ` ${cell.title}` : "";
344
- if (cellOutput) {
345
- combinedCellOutput = `${cellHeader}${cellTitle}\n${cellOutput}`;
346
- } else {
347
- combinedCellOutput = `${cellHeader}${cellTitle} (ok)`;
255
+ const pushUpdate = () => {
256
+ if (!onUpdate) return;
257
+ const tailText = tailBuffer.text();
258
+ onUpdate({
259
+ content: [{ type: "text", text: tailText }],
260
+ details: buildUpdateDetails(),
261
+ });
262
+ };
263
+
264
+ const sessionFile = session.getSessionFile?.() ?? undefined;
265
+ const kernelOwnerId = session.getPythonKernelOwnerId?.() ?? undefined;
266
+ const { path: artifactPath, id: artifactId } = (await session.allocateOutputArtifact?.("python")) ?? {};
267
+ session.assertPythonExecutionAllowed?.();
268
+ outputSink = new OutputSink({
269
+ artifactPath,
270
+ artifactId,
271
+ onChunk: chunk => {
272
+ appendTail(chunk);
273
+ pushUpdate();
274
+ },
275
+ });
276
+ const sessionId = sessionFile ? `session:${sessionFile}:cwd:${commandCwd}` : `cwd:${commandCwd}`;
277
+
278
+ if (getPreludeDocs().length === 0) {
279
+ const warmup = await warmPythonEnvironment(
280
+ commandCwd,
281
+ sessionId,
282
+ session.settings.get("python.sharedGateway"),
283
+ sessionFile ?? undefined,
284
+ kernelOwnerId,
285
+ combinedSignal,
286
+ );
287
+ if (!warmup.ok) {
288
+ if (combinedSignal.aborted) throw new ToolAbortError();
289
+ throw new ToolError(warmup.reason ?? "Python prelude helpers unavailable");
348
290
  }
349
- cellOutputs.push(combinedCellOutput);
350
- } else if (cellOutput) {
351
- combinedCellOutput = cellOutput;
352
- cellOutputs.push(combinedCellOutput);
291
+ session.assertPythonExecutionAllowed?.();
353
292
  }
354
293
 
355
- if (combinedCellOutput) {
356
- const prefix = cellOutputs.length > 1 ? "\n\n" : "";
357
- appendTail(`${prefix}${combinedCellOutput}`);
358
- }
294
+ const baseExecutorOptions = {
295
+ cwd: commandCwd,
296
+ deadlineMs,
297
+ signal: combinedSignal,
298
+ sessionId,
299
+ kernelMode: session.settings.get("python.kernelMode"),
300
+ useSharedGateway: session.settings.get("python.sharedGateway"),
301
+ sessionFile: sessionFile ?? undefined,
302
+ kernelOwnerId,
303
+ };
359
304
 
360
- if (result.cancelled) {
361
- cellResult.status = "error";
305
+ for (let i = 0; i < cells.length; i++) {
306
+ const cell = cells[i];
307
+ const isFirstCell = i === 0;
308
+ const cellResult = cellResults[i];
309
+ cellResult.status = "running";
310
+ cellResult.output = "";
311
+ cellResult.statusEvents = undefined;
312
+ cellResult.exitCode = undefined;
313
+ cellResult.durationMs = undefined;
362
314
  pushUpdate();
363
- const errorMsg = result.output || "Command aborted";
364
- const combinedOutput = cellOutputs.join("\n\n");
365
- const outputText =
366
- cells.length > 1
367
- ? `${combinedOutput}\n\nCell ${i + 1} aborted: ${errorMsg}`
368
- : combinedOutput || errorMsg;
369
-
370
- const rawSummary = (await finalizeOutput()) ?? {
371
- output: "",
372
- truncated: false,
373
- totalLines: 0,
374
- totalBytes: 0,
375
- outputLines: 0,
376
- outputBytes: 0,
377
- };
378
- const outputLines = combinedOutput.length > 0 ? combinedOutput.split("\n").length : 0;
379
- const outputBytes = Buffer.byteLength(combinedOutput, "utf-8");
380
- const missingLines = Math.max(0, rawSummary.totalLines - rawSummary.outputLines);
381
- const missingBytes = Math.max(0, rawSummary.totalBytes - rawSummary.outputBytes);
382
- const summaryForMeta: OutputSummary = {
383
- output: combinedOutput,
384
- truncated: rawSummary.truncated,
385
- totalLines: outputLines + missingLines,
386
- totalBytes: outputBytes + missingBytes,
387
- outputLines,
388
- outputBytes,
389
- artifactId: rawSummary.artifactId,
390
- };
391
315
 
392
- const details: PythonToolDetails = {
393
- cells: cellResults,
394
- jsonOutputs: jsonOutputs.length > 0 ? jsonOutputs : undefined,
395
- images: images.length > 0 ? images : undefined,
396
- statusEvents: statusEvents.length > 0 ? statusEvents : undefined,
397
- isError: true,
316
+ const executorOptions: PythonExecutorOptions = {
317
+ ...baseExecutorOptions,
318
+ reset: isFirstCell ? reset : false,
319
+ onChunk: chunk => {
320
+ outputSink!.push(chunk);
321
+ },
398
322
  };
399
323
 
400
- return toolResult(details)
401
- .text(outputText)
402
- .truncationFromSummary(summaryForMeta, { direction: "tail" })
403
- .done();
404
- }
324
+ const startTime = Date.now();
325
+ const result = await executePython(cell.code, executorOptions);
326
+ const durationMs = Date.now() - startTime;
405
327
 
406
- if (result.exitCode !== 0 && result.exitCode !== undefined) {
407
- cellResult.status = "error";
408
- pushUpdate();
409
- const combinedOutput = cellOutputs.join("\n\n");
410
- const outputText =
411
- cells.length > 1
412
- ? `${combinedOutput}\n\nCell ${i + 1} failed (exit code ${result.exitCode}). Earlier cells succeeded—their state persists. Fix only cell ${i + 1}.`
413
- : combinedOutput
414
- ? `${combinedOutput}\n\nCommand exited with code ${result.exitCode}`
415
- : `Command exited with code ${result.exitCode}`;
416
-
417
- const rawSummary = (await finalizeOutput()) ?? {
418
- output: "",
419
- truncated: false,
420
- totalLines: 0,
421
- totalBytes: 0,
422
- outputLines: 0,
423
- outputBytes: 0,
424
- };
425
- const outputLines = combinedOutput.length > 0 ? combinedOutput.split("\n").length : 0;
426
- const outputBytes = Buffer.byteLength(combinedOutput, "utf-8");
427
- const missingLines = Math.max(0, rawSummary.totalLines - rawSummary.outputLines);
428
- const missingBytes = Math.max(0, rawSummary.totalBytes - rawSummary.outputBytes);
429
- const summaryForMeta: OutputSummary = {
430
- output: combinedOutput,
431
- truncated: rawSummary.truncated,
432
- totalLines: outputLines + missingLines,
433
- totalBytes: outputBytes + missingBytes,
434
- outputLines,
435
- outputBytes,
436
- artifactId: rawSummary.artifactId,
437
- };
328
+ const cellStatusEvents: PythonStatusEvent[] = [];
329
+ let cellHasMarkdown = false;
330
+ for (const output of result.displayOutputs) {
331
+ if (output.type === "json") {
332
+ jsonOutputs.push(output.data);
333
+ }
334
+ if (output.type === "image") {
335
+ images.push({ type: "image", data: output.data, mimeType: output.mimeType });
336
+ }
337
+ if (output.type === "status") {
338
+ statusEvents.push(output.event);
339
+ cellStatusEvents.push(output.event);
340
+ }
341
+ if (output.type === "markdown") {
342
+ cellHasMarkdown = true;
343
+ }
344
+ }
438
345
 
439
- const details: PythonToolDetails = {
440
- cells: cellResults,
441
- jsonOutputs: jsonOutputs.length > 0 ? jsonOutputs : undefined,
442
- images: images.length > 0 ? images : undefined,
443
- statusEvents: statusEvents.length > 0 ? statusEvents : undefined,
444
- isError: true,
445
- };
346
+ const cellOutput = result.output.trim();
347
+ cellResult.output = cellOutput;
348
+ cellResult.exitCode = result.exitCode;
349
+ cellResult.durationMs = durationMs;
350
+ cellResult.statusEvents = cellStatusEvents.length > 0 ? cellStatusEvents : undefined;
351
+ cellResult.hasMarkdown = cellHasMarkdown || undefined;
352
+
353
+ let combinedCellOutput = "";
354
+ if (cells.length > 1) {
355
+ const cellHeader = `[${i + 1}/${cells.length}]`;
356
+ const cellTitle = cell.title ? ` ${cell.title}` : "";
357
+ if (cellOutput) {
358
+ combinedCellOutput = `${cellHeader}${cellTitle}\n${cellOutput}`;
359
+ } else {
360
+ combinedCellOutput = `${cellHeader}${cellTitle} (ok)`;
361
+ }
362
+ cellOutputs.push(combinedCellOutput);
363
+ } else if (cellOutput) {
364
+ combinedCellOutput = cellOutput;
365
+ cellOutputs.push(combinedCellOutput);
366
+ }
446
367
 
447
- return toolResult(details)
448
- .text(outputText)
449
- .truncationFromSummary(summaryForMeta, { direction: "tail" })
450
- .done();
451
- }
368
+ if (combinedCellOutput) {
369
+ const prefix = cellOutputs.length > 1 ? "\n\n" : "";
370
+ appendTail(`${prefix}${combinedCellOutput}`);
371
+ }
452
372
 
453
- cellResult.status = "complete";
454
- pushUpdate();
455
- }
373
+ if (result.cancelled) {
374
+ cellResult.status = "error";
375
+ pushUpdate();
376
+ const errorMsg = result.output || "Command aborted";
377
+ const combinedOutput = cellOutputs.join("\n\n");
378
+ const outputText =
379
+ cells.length > 1
380
+ ? `${combinedOutput}\n\nCell ${i + 1} aborted: ${errorMsg}`
381
+ : combinedOutput || errorMsg;
382
+
383
+ const rawSummary = (await finalizeOutput()) ?? {
384
+ output: "",
385
+ truncated: false,
386
+ totalLines: 0,
387
+ totalBytes: 0,
388
+ outputLines: 0,
389
+ outputBytes: 0,
390
+ };
391
+ const outputLines = combinedOutput.length > 0 ? combinedOutput.split("\n").length : 0;
392
+ const outputBytes = Buffer.byteLength(combinedOutput, "utf-8");
393
+ const missingLines = Math.max(0, rawSummary.totalLines - rawSummary.outputLines);
394
+ const missingBytes = Math.max(0, rawSummary.totalBytes - rawSummary.outputBytes);
395
+ const summaryForMeta: OutputSummary = {
396
+ output: combinedOutput,
397
+ truncated: rawSummary.truncated,
398
+ totalLines: outputLines + missingLines,
399
+ totalBytes: outputBytes + missingBytes,
400
+ outputLines,
401
+ outputBytes,
402
+ artifactId: rawSummary.artifactId,
403
+ };
404
+
405
+ const details: PythonToolDetails = {
406
+ cells: cellResults,
407
+ jsonOutputs: jsonOutputs.length > 0 ? jsonOutputs : undefined,
408
+ images: images.length > 0 ? images : undefined,
409
+ statusEvents: statusEvents.length > 0 ? statusEvents : undefined,
410
+ isError: true,
411
+ };
412
+
413
+ return toolResult(details)
414
+ .text(outputText)
415
+ .truncationFromSummary(summaryForMeta, { direction: "tail" })
416
+ .done();
417
+ }
456
418
 
457
- const combinedOutput = cellOutputs.join("\n\n");
458
- const outputText =
459
- combinedOutput || (jsonOutputs.length > 0 || images.length > 0 ? "(no text output)" : "(no output)");
460
- const rawSummary = (await finalizeOutput()) ?? {
461
- output: "",
462
- truncated: false,
463
- totalLines: 0,
464
- totalBytes: 0,
465
- outputLines: 0,
466
- outputBytes: 0,
467
- };
468
- const outputLines = combinedOutput.length > 0 ? combinedOutput.split("\n").length : 0;
469
- const outputBytes = Buffer.byteLength(combinedOutput, "utf-8");
470
- const missingLines = Math.max(0, rawSummary.totalLines - rawSummary.outputLines);
471
- const missingBytes = Math.max(0, rawSummary.totalBytes - rawSummary.outputBytes);
472
- const summaryForMeta: OutputSummary = {
473
- output: combinedOutput,
474
- truncated: rawSummary.truncated,
475
- totalLines: outputLines + missingLines,
476
- totalBytes: outputBytes + missingBytes,
477
- outputLines,
478
- outputBytes,
479
- artifactId: rawSummary.artifactId,
480
- };
419
+ if (result.exitCode !== 0 && result.exitCode !== undefined) {
420
+ cellResult.status = "error";
421
+ pushUpdate();
422
+ const combinedOutput = cellOutputs.join("\n\n");
423
+ const outputText =
424
+ cells.length > 1
425
+ ? `${combinedOutput}\n\nCell ${i + 1} failed (exit code ${result.exitCode}). Earlier cells succeeded—their state persists. Fix only cell ${i + 1}.`
426
+ : combinedOutput
427
+ ? `${combinedOutput}\n\nCommand exited with code ${result.exitCode}`
428
+ : `Command exited with code ${result.exitCode}`;
429
+
430
+ const rawSummary = (await finalizeOutput()) ?? {
431
+ output: "",
432
+ truncated: false,
433
+ totalLines: 0,
434
+ totalBytes: 0,
435
+ outputLines: 0,
436
+ outputBytes: 0,
437
+ };
438
+ const outputLines = combinedOutput.length > 0 ? combinedOutput.split("\n").length : 0;
439
+ const outputBytes = Buffer.byteLength(combinedOutput, "utf-8");
440
+ const missingLines = Math.max(0, rawSummary.totalLines - rawSummary.outputLines);
441
+ const missingBytes = Math.max(0, rawSummary.totalBytes - rawSummary.outputBytes);
442
+ const summaryForMeta: OutputSummary = {
443
+ output: combinedOutput,
444
+ truncated: rawSummary.truncated,
445
+ totalLines: outputLines + missingLines,
446
+ totalBytes: outputBytes + missingBytes,
447
+ outputLines,
448
+ outputBytes,
449
+ artifactId: rawSummary.artifactId,
450
+ };
451
+
452
+ const details: PythonToolDetails = {
453
+ cells: cellResults,
454
+ jsonOutputs: jsonOutputs.length > 0 ? jsonOutputs : undefined,
455
+ images: images.length > 0 ? images : undefined,
456
+ statusEvents: statusEvents.length > 0 ? statusEvents : undefined,
457
+ isError: true,
458
+ };
459
+
460
+ return toolResult(details)
461
+ .text(outputText)
462
+ .truncationFromSummary(summaryForMeta, { direction: "tail" })
463
+ .done();
464
+ }
481
465
 
482
- const details: PythonToolDetails = {
483
- cells: cellResults,
484
- jsonOutputs: jsonOutputs.length > 0 ? jsonOutputs : undefined,
485
- images: images.length > 0 ? images : undefined,
486
- statusEvents: statusEvents.length > 0 ? statusEvents : undefined,
487
- };
466
+ cellResult.status = "complete";
467
+ pushUpdate();
468
+ }
488
469
 
489
- const resultBuilder = toolResult(details)
490
- .text(outputText)
491
- .truncationFromSummary(summaryForMeta, { direction: "tail" });
470
+ const combinedOutput = cellOutputs.join("\n\n");
471
+ const outputText =
472
+ combinedOutput || (jsonOutputs.length > 0 || images.length > 0 ? "(no text output)" : "(no output)");
473
+ const rawSummary = (await finalizeOutput()) ?? {
474
+ output: "",
475
+ truncated: false,
476
+ totalLines: 0,
477
+ totalBytes: 0,
478
+ outputLines: 0,
479
+ outputBytes: 0,
480
+ };
481
+ const outputLines = combinedOutput.length > 0 ? combinedOutput.split("\n").length : 0;
482
+ const outputBytes = Buffer.byteLength(combinedOutput, "utf-8");
483
+ const missingLines = Math.max(0, rawSummary.totalLines - rawSummary.outputLines);
484
+ const missingBytes = Math.max(0, rawSummary.totalBytes - rawSummary.outputBytes);
485
+ const summaryForMeta: OutputSummary = {
486
+ output: combinedOutput,
487
+ truncated: rawSummary.truncated,
488
+ totalLines: outputLines + missingLines,
489
+ totalBytes: outputBytes + missingBytes,
490
+ outputLines,
491
+ outputBytes,
492
+ artifactId: rawSummary.artifactId,
493
+ };
492
494
 
493
- return resultBuilder.done();
494
- } finally {
495
- if (!outputDumped) {
496
- try {
497
- await finalizeOutput();
498
- } catch {}
495
+ const details: PythonToolDetails = {
496
+ cells: cellResults,
497
+ jsonOutputs: jsonOutputs.length > 0 ? jsonOutputs : undefined,
498
+ images: images.length > 0 ? images : undefined,
499
+ statusEvents: statusEvents.length > 0 ? statusEvents : undefined,
500
+ };
501
+
502
+ const resultBuilder = toolResult(details)
503
+ .text(outputText)
504
+ .truncationFromSummary(summaryForMeta, { direction: "tail" });
505
+
506
+ return resultBuilder.done();
507
+ } finally {
508
+ if (!outputDumped) {
509
+ try {
510
+ await finalizeOutput();
511
+ } catch {}
512
+ }
499
513
  }
500
- }
514
+ })();
515
+ return await (session.trackPythonExecution?.(execution, sessionAbortController) ?? execution);
501
516
  }
502
517
  }
503
518