@oh-my-pi/pi-coding-agent 14.1.0 → 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.
- package/CHANGELOG.md +79 -0
- package/package.json +8 -8
- package/src/async/job-manager.ts +43 -10
- package/src/commit/agentic/tools/analyze-file.ts +1 -2
- package/src/config/mcp-schema.json +1 -1
- package/src/config/model-equivalence.ts +1 -0
- package/src/config/model-registry.ts +63 -34
- package/src/config/model-resolver.ts +111 -15
- package/src/config/settings-schema.ts +4 -3
- package/src/config/settings.ts +1 -1
- package/src/cursor.ts +64 -23
- package/src/edit/index.ts +254 -89
- package/src/edit/modes/chunk.ts +336 -57
- package/src/edit/modes/hashline.ts +51 -26
- package/src/edit/modes/patch.ts +16 -10
- package/src/edit/modes/replace.ts +15 -7
- package/src/edit/renderer.ts +248 -94
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +6 -4
- package/src/extensibility/custom-tools/types.ts +0 -3
- package/src/extensibility/extensions/loader.ts +16 -0
- package/src/extensibility/extensions/runner.ts +2 -7
- package/src/extensibility/extensions/types.ts +8 -4
- package/src/internal-urls/docs-index.generated.ts +3 -3
- package/src/ipy/executor.ts +447 -52
- package/src/ipy/kernel.ts +39 -13
- package/src/lsp/client.ts +54 -0
- package/src/lsp/index.ts +8 -0
- package/src/lsp/types.ts +6 -0
- package/src/main.ts +0 -1
- package/src/modes/acp/acp-agent.ts +4 -1
- package/src/modes/components/bash-execution.ts +16 -4
- package/src/modes/components/status-line/presets.ts +17 -6
- package/src/modes/components/status-line/segments.ts +15 -0
- package/src/modes/components/status-line-segment-editor.ts +1 -0
- package/src/modes/components/status-line.ts +7 -1
- package/src/modes/components/tool-execution.ts +145 -75
- package/src/modes/controllers/command-controller.ts +24 -1
- package/src/modes/controllers/event-controller.ts +4 -1
- package/src/modes/controllers/extension-ui-controller.ts +28 -5
- package/src/modes/controllers/input-controller.ts +9 -3
- package/src/modes/controllers/selector-controller.ts +4 -1
- package/src/modes/interactive-mode.ts +19 -3
- package/src/modes/print-mode.ts +13 -4
- package/src/modes/prompt-action-autocomplete.ts +3 -5
- package/src/modes/rpc/rpc-mode.ts +8 -2
- package/src/modes/shared.ts +2 -2
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +1 -0
- package/src/prompts/tools/bash.md +2 -2
- package/src/prompts/tools/chunk-edit.md +191 -163
- package/src/prompts/tools/hashline.md +11 -11
- package/src/prompts/tools/patch.md +10 -5
- package/src/prompts/tools/{await.md → poll.md} +1 -1
- package/src/prompts/tools/read-chunk.md +3 -3
- package/src/prompts/tools/task.md +2 -2
- package/src/prompts/tools/vim.md +98 -0
- package/src/sdk.ts +754 -724
- package/src/session/agent-session.ts +164 -34
- package/src/session/session-manager.ts +50 -4
- package/src/slash-commands/builtin-registry.ts +17 -0
- package/src/task/executor.ts +4 -4
- package/src/task/index.ts +3 -5
- package/src/task/types.ts +2 -2
- package/src/tools/bash.ts +26 -8
- package/src/tools/find.ts +5 -2
- package/src/tools/grep.ts +77 -8
- package/src/tools/index.ts +48 -19
- package/src/tools/{await-tool.ts → poll-tool.ts} +36 -30
- package/src/tools/python.ts +293 -278
- package/src/tools/submit-result.ts +5 -2
- package/src/tools/todo-write.ts +8 -2
- package/src/tools/vim.ts +966 -0
- package/src/utils/edit-mode.ts +2 -1
- package/src/utils/session-color.ts +55 -0
- package/src/utils/title-generator.ts +15 -6
- package/src/vim/buffer.ts +309 -0
- package/src/vim/commands.ts +382 -0
- package/src/vim/engine.ts +2426 -0
- package/src/vim/parser.ts +151 -0
- package/src/vim/render.ts +252 -0
- package/src/vim/types.ts +197 -0
package/src/tools/python.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
205
|
-
|
|
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
|
-
|
|
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
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
|
|
293
|
-
const
|
|
294
|
-
const
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
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
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
if (
|
|
319
|
-
jsonOutputs
|
|
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 (
|
|
325
|
-
|
|
326
|
-
cellStatusEvents.push(output.event);
|
|
246
|
+
if (images.length > 0) {
|
|
247
|
+
details.images = images;
|
|
327
248
|
}
|
|
328
|
-
if (
|
|
329
|
-
|
|
249
|
+
if (statusEvents.length > 0) {
|
|
250
|
+
details.statusEvents = statusEvents;
|
|
330
251
|
}
|
|
331
|
-
|
|
252
|
+
return details;
|
|
253
|
+
};
|
|
332
254
|
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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
|
-
|
|
350
|
-
} else if (cellOutput) {
|
|
351
|
-
combinedCellOutput = cellOutput;
|
|
352
|
-
cellOutputs.push(combinedCellOutput);
|
|
291
|
+
session.assertPythonExecutionAllowed?.();
|
|
353
292
|
}
|
|
354
293
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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
|
-
|
|
361
|
-
|
|
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
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
316
|
+
const executorOptions: PythonExecutorOptions = {
|
|
317
|
+
...baseExecutorOptions,
|
|
318
|
+
reset: isFirstCell ? reset : false,
|
|
319
|
+
onChunk: chunk => {
|
|
320
|
+
outputSink!.push(chunk);
|
|
321
|
+
},
|
|
398
322
|
};
|
|
399
323
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
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
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
:
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
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
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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
|
-
|
|
448
|
-
.
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
}
|
|
368
|
+
if (combinedCellOutput) {
|
|
369
|
+
const prefix = cellOutputs.length > 1 ? "\n\n" : "";
|
|
370
|
+
appendTail(`${prefix}${combinedCellOutput}`);
|
|
371
|
+
}
|
|
452
372
|
|
|
453
|
-
|
|
454
|
-
|
|
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
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
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
|
-
|
|
483
|
-
|
|
484
|
-
|
|
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
|
-
|
|
490
|
-
|
|
491
|
-
|
|
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
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
}
|
|
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
|
|
|
@@ -57,7 +57,8 @@ export class SubmitResultTool implements AgentTool<TSchema, SubmitResultDetails>
|
|
|
57
57
|
readonly label = "Submit Result";
|
|
58
58
|
readonly description =
|
|
59
59
|
"Finish the task with structured JSON output. Call exactly once at the end of the task.\n\n" +
|
|
60
|
-
|
|
60
|
+
'Pass `result: { data: <your output> }` for success, or `result: { error: "message" }` for failure.\n' +
|
|
61
|
+
"The `data`/`error` wrapper is required — do not put your output directly in `result`.";
|
|
61
62
|
readonly parameters: TSchema;
|
|
62
63
|
strict = true;
|
|
63
64
|
lenientArgValidation = true;
|
|
@@ -171,7 +172,9 @@ export class SubmitResultTool implements AgentTool<TSchema, SubmitResultDetails>
|
|
|
171
172
|
throw new Error("result cannot contain both data and error");
|
|
172
173
|
}
|
|
173
174
|
if (errorMessage === undefined && data === undefined) {
|
|
174
|
-
throw new Error(
|
|
175
|
+
throw new Error(
|
|
176
|
+
'result must contain either `data` or `error`. Use `{result: {data: <your output>}}` for success or `{result: {error: "message"}}` for failure.',
|
|
177
|
+
);
|
|
175
178
|
}
|
|
176
179
|
|
|
177
180
|
const status = errorMessage !== undefined ? "aborted" : "success";
|
package/src/tools/todo-write.ts
CHANGED
|
@@ -251,7 +251,9 @@ function applyOps(file: TodoFile, ops: TodoWriteParams["ops"]): { file: TodoFile
|
|
|
251
251
|
case "update": {
|
|
252
252
|
const task = findTask(file.phases, op.id);
|
|
253
253
|
if (!task) {
|
|
254
|
-
|
|
254
|
+
const totalTasks = file.phases.reduce((sum, p) => sum + p.tasks.length, 0);
|
|
255
|
+
const hint = totalTasks === 0 ? " (todo list is empty — was it replaced or not yet created?)" : "";
|
|
256
|
+
errors.push(`Task "${op.id}" not found${hint}`);
|
|
255
257
|
break;
|
|
256
258
|
}
|
|
257
259
|
if (op.status !== undefined) task.status = op.status;
|
|
@@ -271,7 +273,11 @@ function applyOps(file: TodoFile, ops: TodoWriteParams["ops"]): { file: TodoFile
|
|
|
271
273
|
break;
|
|
272
274
|
}
|
|
273
275
|
}
|
|
274
|
-
if (!removed)
|
|
276
|
+
if (!removed) {
|
|
277
|
+
const totalTasks = file.phases.reduce((sum, p) => sum + p.tasks.length, 0);
|
|
278
|
+
const hint = totalTasks === 0 ? " (todo list is empty)" : "";
|
|
279
|
+
errors.push(`Task "${op.id}" not found${hint}`);
|
|
280
|
+
}
|
|
275
281
|
break;
|
|
276
282
|
}
|
|
277
283
|
}
|