@oh-my-pi/pi-coding-agent 15.9.67 → 15.10.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 +136 -0
- package/dist/types/cli/args.d.ts +1 -1
- package/dist/types/cli/dry-balance-cli.d.ts +15 -1
- package/dist/types/cli/gallery-cli.d.ts +43 -0
- package/dist/types/cli/gallery-fixtures/agentic.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/codeintel.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/edit.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/fs.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/index.d.ts +4 -0
- package/dist/types/cli/gallery-fixtures/interaction.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/memory.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/misc.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/search.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/shell.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/types.d.ts +44 -0
- package/dist/types/cli/gallery-fixtures/web.d.ts +2 -0
- package/dist/types/cli/gallery-screenshot.d.ts +35 -0
- package/dist/types/commands/gallery.d.ts +47 -0
- package/dist/types/commit/analysis/conventional.d.ts +2 -2
- package/dist/types/commit/analysis/summary.d.ts +2 -2
- package/dist/types/commit/changelog/generate.d.ts +2 -2
- package/dist/types/commit/changelog/index.d.ts +2 -2
- package/dist/types/commit/map-reduce/index.d.ts +3 -3
- package/dist/types/commit/map-reduce/map-phase.d.ts +2 -2
- package/dist/types/commit/map-reduce/reduce-phase.d.ts +2 -2
- package/dist/types/commit/model-selection.d.ts +10 -4
- package/dist/types/config/api-key-resolver.d.ts +34 -0
- package/dist/types/config/keybindings.d.ts +6 -1
- package/dist/types/config/model-id-affixes.d.ts +2 -0
- package/dist/types/config/model-registry.d.ts +25 -2
- package/dist/types/config/settings-schema.d.ts +41 -6
- package/dist/types/dap/config.d.ts +14 -1
- package/dist/types/dap/types.d.ts +10 -0
- package/dist/types/extensibility/plugins/marketplace-auto-update.d.ts +8 -0
- package/dist/types/lsp/types.d.ts +10 -0
- package/dist/types/lsp/utils.d.ts +3 -2
- package/dist/types/main.d.ts +3 -2
- package/dist/types/memory-backend/index.d.ts +2 -1
- package/dist/types/memory-backend/resolve.d.ts +1 -1
- package/dist/types/memory-backend/types.d.ts +1 -1
- package/dist/types/modes/components/chat-block.d.ts +64 -0
- package/dist/types/modes/components/custom-editor.d.ts +5 -1
- package/dist/types/modes/components/overlay-box.d.ts +17 -0
- package/dist/types/modes/components/plan-review-overlay.d.ts +59 -0
- package/dist/types/modes/components/plan-toc.d.ts +41 -0
- package/dist/types/modes/components/read-tool-group.d.ts +2 -0
- package/dist/types/modes/components/tool-execution.d.ts +18 -0
- package/dist/types/modes/components/transcript-container.d.ts +11 -0
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/controllers/event-controller.d.ts +0 -1
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +0 -1
- package/dist/types/modes/controllers/input-controller.d.ts +1 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
- package/dist/types/modes/controllers/streaming-reveal.d.ts +22 -0
- package/dist/types/modes/controllers/tan-command-controller.d.ts +6 -0
- package/dist/types/modes/index.d.ts +5 -4
- package/dist/types/modes/interactive-mode.d.ts +16 -6
- package/dist/types/modes/setup-version.d.ts +11 -0
- package/dist/types/modes/setup-wizard/index.d.ts +2 -1
- package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +2 -1
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +19 -6
- package/dist/types/modes/utils/copy-targets.d.ts +21 -1
- package/dist/types/plan-mode/approved-plan.d.ts +27 -8
- package/dist/types/plan-mode/plan-protection.d.ts +4 -4
- package/dist/types/sdk.d.ts +3 -1
- package/dist/types/session/agent-session.d.ts +21 -0
- package/dist/types/session/messages.d.ts +12 -0
- package/dist/types/session/session-manager.d.ts +3 -1
- package/dist/types/slash-commands/types.d.ts +4 -6
- package/dist/types/task/executor.d.ts +14 -0
- package/dist/types/task/index.d.ts +1 -0
- package/dist/types/task/render.d.ts +3 -2
- package/dist/types/telemetry-export.d.ts +1 -1
- package/dist/types/tools/archive-reader.d.ts +5 -0
- package/dist/types/tools/ast-edit.d.ts +3 -0
- package/dist/types/tools/ast-grep.d.ts +3 -0
- package/dist/types/tools/bash.d.ts +1 -0
- package/dist/types/tools/eval-render.d.ts +1 -8
- package/dist/types/tools/fetch.d.ts +15 -7
- package/dist/types/tools/find.d.ts +8 -4
- package/dist/types/tools/grouped-file-output.d.ts +95 -12
- package/dist/types/tools/memory-render.d.ts +4 -1
- package/dist/types/tools/plan-mode-guard.d.ts +8 -9
- package/dist/types/tools/render-utils.d.ts +13 -9
- package/dist/types/tools/renderers.d.ts +16 -2
- package/dist/types/tools/search.d.ts +5 -1
- package/dist/types/tools/sqlite-reader.d.ts +1 -0
- package/dist/types/tools/todo.d.ts +3 -2
- package/dist/types/tools/write.d.ts +5 -0
- package/dist/types/tui/output-block.d.ts +16 -4
- package/dist/types/tui/status-line.d.ts +3 -0
- package/dist/types/utils/enhanced-paste.d.ts +20 -0
- package/dist/types/web/scrapers/github.d.ts +22 -0
- package/dist/types/web/search/providers/kimi.d.ts +1 -1
- package/dist/types/web/search/providers/perplexity.d.ts +8 -1
- package/dist/types/web/search/types.d.ts +1 -1
- package/package.json +9 -9
- package/scripts/dev-launch +42 -0
- package/scripts/dev-launch-preload.ts +19 -0
- package/src/auto-thinking/classifier.ts +5 -1
- package/src/cli/args.ts +2 -2
- package/src/cli/dry-balance-cli.ts +52 -17
- package/src/cli/gallery-cli.ts +226 -0
- package/src/cli/gallery-fixtures/agentic.ts +292 -0
- package/src/cli/gallery-fixtures/codeintel.ts +188 -0
- package/src/cli/gallery-fixtures/edit.ts +194 -0
- package/src/cli/gallery-fixtures/fs.ts +153 -0
- package/src/cli/gallery-fixtures/index.ts +40 -0
- package/src/cli/gallery-fixtures/interaction.ts +49 -0
- package/src/cli/gallery-fixtures/memory.ts +81 -0
- package/src/cli/gallery-fixtures/misc.ts +250 -0
- package/src/cli/gallery-fixtures/search.ts +213 -0
- package/src/cli/gallery-fixtures/shell.ts +167 -0
- package/src/cli/gallery-fixtures/types.ts +41 -0
- package/src/cli/gallery-fixtures/web.ts +158 -0
- package/src/cli/gallery-screenshot.ts +279 -0
- package/src/cli-commands.ts +1 -0
- package/src/commands/gallery.ts +52 -0
- package/src/commands/launch.ts +1 -1
- package/src/commit/analysis/conventional.ts +2 -2
- package/src/commit/analysis/summary.ts +2 -2
- package/src/commit/changelog/generate.ts +2 -2
- package/src/commit/changelog/index.ts +2 -2
- package/src/commit/map-reduce/index.ts +3 -3
- package/src/commit/map-reduce/map-phase.ts +2 -2
- package/src/commit/map-reduce/reduce-phase.ts +2 -2
- package/src/commit/model-selection.ts +33 -9
- package/src/commit/pipeline.ts +4 -4
- package/src/config/api-key-resolver.ts +58 -0
- package/src/config/keybindings.ts +15 -6
- package/src/config/model-equivalence.ts +35 -12
- package/src/config/model-id-affixes.ts +39 -22
- package/src/config/model-registry.ts +41 -18
- package/src/config/settings-schema.ts +28 -5
- package/src/config/settings.ts +31 -2
- package/src/dap/client.ts +14 -16
- package/src/dap/config.ts +41 -2
- package/src/dap/defaults.json +1 -0
- package/src/dap/session.ts +1 -0
- package/src/dap/types.ts +10 -0
- package/src/debug/index.ts +40 -54
- package/src/edit/renderer.ts +111 -119
- package/src/eval/__tests__/agent-bridge.test.ts +75 -32
- package/src/eval/__tests__/llm-bridge.test.ts +90 -31
- package/src/eval/agent-bridge.ts +34 -7
- package/src/eval/llm-bridge.ts +8 -3
- package/src/extensibility/extensions/runner.ts +1 -0
- package/src/extensibility/plugins/doctor.ts +0 -1
- package/src/extensibility/plugins/marketplace-auto-update.ts +49 -0
- package/src/goals/tools/goal-tool.ts +37 -27
- package/src/internal-urls/docs-index.generated.ts +10 -10
- package/src/lsp/client.ts +104 -55
- package/src/lsp/types.ts +10 -0
- package/src/lsp/utils.ts +3 -2
- package/src/main.ts +53 -56
- package/src/memories/index.ts +12 -5
- package/src/memory-backend/index.ts +13 -1
- package/src/memory-backend/resolve.ts +3 -5
- package/src/memory-backend/types.ts +1 -1
- package/src/mnemopi/backend.ts +5 -1
- package/src/modes/acp/acp-agent.ts +33 -26
- package/src/modes/components/assistant-message.ts +2 -9
- package/src/modes/components/chat-block.ts +111 -0
- package/src/modes/components/copy-selector.ts +1 -44
- package/src/modes/components/custom-editor.ts +33 -1
- package/src/modes/components/custom-message.ts +1 -3
- package/src/modes/components/execution-shared.ts +1 -2
- package/src/modes/components/hook-message.ts +1 -3
- package/src/modes/components/overlay-box.ts +108 -0
- package/src/modes/components/plan-review-overlay.ts +799 -0
- package/src/modes/components/plan-toc.ts +138 -0
- package/src/modes/components/read-tool-group.ts +20 -4
- package/src/modes/components/skill-message.ts +0 -1
- package/src/modes/components/status-line.ts +3 -5
- package/src/modes/components/tips.txt +1 -0
- package/src/modes/components/todo-reminder.ts +0 -2
- package/src/modes/components/tool-execution.ts +115 -90
- package/src/modes/components/transcript-container.ts +84 -24
- package/src/modes/components/user-message.ts +1 -2
- package/src/modes/controllers/command-controller-shared.ts +7 -6
- package/src/modes/controllers/command-controller.ts +70 -57
- package/src/modes/controllers/event-controller.ts +41 -40
- package/src/modes/controllers/extension-ui-controller.ts +10 -73
- package/src/modes/controllers/input-controller.ts +135 -122
- package/src/modes/controllers/mcp-command-controller.ts +69 -60
- package/src/modes/controllers/selector-controller.ts +25 -27
- package/src/modes/controllers/streaming-reveal.ts +212 -0
- package/src/modes/controllers/tan-command-controller.ts +173 -0
- package/src/modes/index.ts +5 -4
- package/src/modes/interactive-mode.ts +171 -82
- package/src/modes/setup-version.ts +11 -0
- package/src/modes/setup-wizard/index.ts +3 -2
- package/src/modes/setup-wizard/scenes/web-search.ts +3 -2
- package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
- package/src/modes/theme/theme-schema.json +1 -1
- package/src/modes/theme/theme.ts +8 -4
- package/src/modes/types.ts +19 -8
- package/src/modes/utils/context-usage.ts +10 -6
- package/src/modes/utils/copy-targets.ts +133 -27
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +44 -46
- package/src/plan-mode/approved-plan.ts +66 -43
- package/src/plan-mode/plan-protection.ts +4 -4
- package/src/prompts/system/background-tan-dispatch.md +8 -0
- package/src/prompts/system/plan-mode-active.md +67 -58
- package/src/prompts/system/plan-mode-approved.md +1 -1
- package/src/sdk.ts +32 -60
- package/src/session/agent-session.ts +89 -13
- package/src/session/messages.ts +26 -0
- package/src/session/session-manager.ts +13 -5
- package/src/slash-commands/builtin-registry.ts +37 -10
- package/src/slash-commands/helpers/usage-report.ts +2 -0
- package/src/slash-commands/types.ts +4 -6
- package/src/task/executor.ts +25 -4
- package/src/task/index.ts +4 -0
- package/src/task/render.ts +212 -148
- package/src/telemetry-export.ts +25 -7
- package/src/tools/archive-reader.ts +64 -0
- package/src/tools/ask.ts +119 -164
- package/src/tools/ast-edit.ts +98 -71
- package/src/tools/ast-grep.ts +37 -43
- package/src/tools/bash.ts +50 -6
- package/src/tools/debug.ts +20 -8
- package/src/tools/eval-backends.ts +6 -17
- package/src/tools/eval-render.ts +21 -18
- package/src/tools/eval.ts +5 -4
- package/src/tools/fetch.ts +391 -91
- package/src/tools/find.ts +44 -30
- package/src/tools/gh-renderer.ts +81 -42
- package/src/tools/grouped-file-output.ts +272 -48
- package/src/tools/image-gen.ts +150 -103
- package/src/tools/inspect-image-renderer.ts +63 -41
- package/src/tools/inspect-image.ts +8 -1
- package/src/tools/job.ts +3 -4
- package/src/tools/memory-render.ts +4 -1
- package/src/tools/plan-mode-guard.ts +21 -39
- package/src/tools/read.ts +23 -16
- package/src/tools/render-utils.ts +38 -40
- package/src/tools/renderers.ts +16 -1
- package/src/tools/report-tool-issue.ts +1 -1
- package/src/tools/resolve.ts +14 -0
- package/src/tools/search-tool-bm25.ts +36 -23
- package/src/tools/search.ts +189 -95
- package/src/tools/sqlite-reader.ts +9 -12
- package/src/tools/todo.ts +138 -59
- package/src/tools/write.ts +100 -60
- package/src/tui/output-block.ts +60 -13
- package/src/tui/status-line.ts +5 -1
- package/src/utils/commit-message-generator.ts +9 -1
- package/src/utils/enhanced-paste.ts +202 -0
- package/src/utils/title-generator.ts +2 -1
- package/src/web/scrapers/github.ts +255 -3
- package/src/web/scrapers/youtube.ts +3 -2
- package/src/web/search/providers/anthropic.ts +25 -19
- package/src/web/search/providers/exa.ts +11 -3
- package/src/web/search/providers/kimi.ts +28 -17
- package/src/web/search/providers/parallel.ts +35 -24
- package/src/web/search/providers/perplexity.ts +199 -51
- package/src/web/search/providers/synthetic.ts +8 -6
- package/src/web/search/providers/tavily.ts +9 -8
- package/src/web/search/providers/zai.ts +8 -6
- package/src/web/search/render.ts +39 -54
- package/src/web/search/types.ts +5 -1
- package/dist/types/eval/__tests__/shared-executors.test.d.ts +0 -1
- package/src/eval/__tests__/shared-executors.test.ts +0 -609
|
@@ -231,6 +231,57 @@ describe("runEvalAgent", () => {
|
|
|
231
231
|
});
|
|
232
232
|
await expect(runEvalAgent({ prompt: "fail" }, { session: makeSession() })).rejects.toThrow("boom");
|
|
233
233
|
});
|
|
234
|
+
|
|
235
|
+
// Regression: a runtime-limit abort returns exitCode=1, stderr="", error=undefined,
|
|
236
|
+
// aborted=true, abortReason="Subagent runtime limit exceeded (...)". The previous
|
|
237
|
+
// failure-message coalesce stopped at the empty `stderr` (since `??` only skips
|
|
238
|
+
// nullish values) and shipped an empty error through the bridge — Python then
|
|
239
|
+
// surfaced the generic `bridge call '__agent__' failed`. See #2006.
|
|
240
|
+
it("surfaces abortReason for aborts that leave stderr empty", async () => {
|
|
241
|
+
mockAgents();
|
|
242
|
+
const runSpy = vi.spyOn(taskExecutor, "runSubprocess");
|
|
243
|
+
runSpy.mockImplementationOnce(async options =>
|
|
244
|
+
singleResult(options, {
|
|
245
|
+
exitCode: 1,
|
|
246
|
+
output: "",
|
|
247
|
+
stderr: "",
|
|
248
|
+
error: undefined,
|
|
249
|
+
aborted: true,
|
|
250
|
+
abortReason: "Subagent runtime limit exceeded (task.maxRuntimeMs=900000)",
|
|
251
|
+
}),
|
|
252
|
+
);
|
|
253
|
+
runSpy.mockImplementationOnce(async options =>
|
|
254
|
+
singleResult(options, {
|
|
255
|
+
exitCode: 1,
|
|
256
|
+
output: "",
|
|
257
|
+
stderr: " ",
|
|
258
|
+
error: " ",
|
|
259
|
+
aborted: true,
|
|
260
|
+
abortReason: "Cancelled by caller",
|
|
261
|
+
}),
|
|
262
|
+
);
|
|
263
|
+
runSpy.mockImplementationOnce(async options =>
|
|
264
|
+
singleResult(options, {
|
|
265
|
+
exitCode: 1,
|
|
266
|
+
output: "",
|
|
267
|
+
stderr: "",
|
|
268
|
+
error: undefined,
|
|
269
|
+
}),
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
await expect(runEvalAgent({ prompt: "slow" }, { session: makeSession() })).rejects.toThrow(
|
|
273
|
+
"Subagent runtime limit exceeded (task.maxRuntimeMs=900000)",
|
|
274
|
+
);
|
|
275
|
+
// Whitespace-only stderr/error must not mask abortReason either.
|
|
276
|
+
await expect(runEvalAgent({ prompt: "cancelled" }, { session: makeSession() })).rejects.toThrow(
|
|
277
|
+
"Cancelled by caller",
|
|
278
|
+
);
|
|
279
|
+
// Last resort: still produce a non-empty message even when nothing useful is set,
|
|
280
|
+
// so Python never falls back to `bridge call '__agent__' failed`.
|
|
281
|
+
await expect(runEvalAgent({ prompt: "blank" }, { session: makeSession() })).rejects.toThrow(
|
|
282
|
+
"agent() subagent 'task' failed.",
|
|
283
|
+
);
|
|
284
|
+
});
|
|
234
285
|
});
|
|
235
286
|
|
|
236
287
|
describe("agent() through eval runtimes", () => {
|
|
@@ -326,18 +377,6 @@ describe("agent() through eval runtimes", () => {
|
|
|
326
377
|
singleResult(options, { output: "hello from python" }),
|
|
327
378
|
);
|
|
328
379
|
|
|
329
|
-
const probe = await executePython('print("probe")', {
|
|
330
|
-
cwd: tempDir.path(),
|
|
331
|
-
sessionId: `${sessionId}:probe`,
|
|
332
|
-
sessionFile,
|
|
333
|
-
kernelMode: "per-call",
|
|
334
|
-
});
|
|
335
|
-
if (probe.exitCode === undefined && probe.cancelled) {
|
|
336
|
-
expect(probe.output).toBe("");
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
339
|
-
expect(probe.exitCode).toBe(0);
|
|
340
|
-
|
|
341
380
|
const result = await executePython('print(agent("hi"))', {
|
|
342
381
|
cwd: tempDir.path(),
|
|
343
382
|
sessionId,
|
|
@@ -345,6 +384,10 @@ describe("agent() through eval runtimes", () => {
|
|
|
345
384
|
kernelMode: "per-call",
|
|
346
385
|
toolSession: session,
|
|
347
386
|
});
|
|
387
|
+
if (result.exitCode === undefined && result.cancelled) {
|
|
388
|
+
expect(result.output).toBe("");
|
|
389
|
+
return; // kernel unavailable in this environment
|
|
390
|
+
}
|
|
348
391
|
|
|
349
392
|
expect(result.exitCode).toBe(0);
|
|
350
393
|
expect(result.output.trim()).toBe("hello from python");
|
|
@@ -373,22 +416,14 @@ describe("agent() through eval runtimes", () => {
|
|
|
373
416
|
}
|
|
374
417
|
});
|
|
375
418
|
|
|
376
|
-
const probe = await executePython('print("probe")', {
|
|
377
|
-
cwd: tempDir.path(),
|
|
378
|
-
sessionId: `${sessionId}:probe`,
|
|
379
|
-
sessionFile,
|
|
380
|
-
kernelMode: "per-call",
|
|
381
|
-
});
|
|
382
|
-
if (probe.exitCode === undefined && probe.cancelled) {
|
|
383
|
-
expect(probe.output).toBe("");
|
|
384
|
-
return;
|
|
385
|
-
}
|
|
386
|
-
expect(probe.exitCode).toBe(0);
|
|
387
|
-
|
|
388
419
|
const result = await executePython(
|
|
389
420
|
'import json\nprint(json.dumps(parallel([lambda n=n: agent(n) for n in ["a", "b", "c", "d"]])))',
|
|
390
421
|
{ cwd: tempDir.path(), sessionId, sessionFile, kernelMode: "per-call", toolSession: session },
|
|
391
422
|
);
|
|
423
|
+
if (result.exitCode === undefined && result.cancelled) {
|
|
424
|
+
expect(result.output).toBe("");
|
|
425
|
+
return; // kernel unavailable in this environment
|
|
426
|
+
}
|
|
392
427
|
|
|
393
428
|
expect(result.exitCode).toBe(0);
|
|
394
429
|
expect(JSON.parse(result.output.trim())).toEqual(["a", "b", "c", "d"]);
|
|
@@ -412,7 +447,14 @@ describe("agent() through eval runtimes", () => {
|
|
|
412
447
|
// The host must respond the instant the cell aborts so the kernel can
|
|
413
448
|
// unwind via KeyboardInterrupt instead of being hard-killed (which used to
|
|
414
449
|
// surface "[kernel] Python kernel shutdown" and lose all session state).
|
|
450
|
+
let inFlight = 0;
|
|
451
|
+
let markSaturated: (() => void) | undefined;
|
|
452
|
+
const saturated = new Promise<void>(resolve => {
|
|
453
|
+
markSaturated = resolve;
|
|
454
|
+
});
|
|
415
455
|
vi.spyOn(taskExecutor, "runSubprocess").mockImplementation(async options => {
|
|
456
|
+
// task.maxConcurrency=6 → six bridge calls block at once; signal then.
|
|
457
|
+
if (++inFlight >= 6) markSaturated?.();
|
|
416
458
|
await Bun.sleep(9000); // deliberately ignores options.signal
|
|
417
459
|
return singleResult(options, { output: options.assignment ?? "" });
|
|
418
460
|
});
|
|
@@ -432,8 +474,9 @@ describe("agent() through eval runtimes", () => {
|
|
|
432
474
|
expect(seed.exitCode).toBe(0);
|
|
433
475
|
|
|
434
476
|
const ac = new AbortController();
|
|
435
|
-
// Abort
|
|
436
|
-
|
|
477
|
+
// Abort the instant all six worker threads are confirmed blocked in their
|
|
478
|
+
// bridge calls (condition-driven) instead of waiting a fixed wall second.
|
|
479
|
+
void saturated.then(() => ac.abort(new Error("external interrupt")));
|
|
437
480
|
|
|
438
481
|
const start = Date.now();
|
|
439
482
|
const result = await executePython(
|
|
@@ -568,12 +611,12 @@ describe("agent() through eval runtimes", () => {
|
|
|
568
611
|
// of its own. The bridge pause must make that delegated time invisible to
|
|
569
612
|
// the watchdog.
|
|
570
613
|
vi.spyOn(taskExecutor, "runSubprocess").mockImplementation(async options => {
|
|
571
|
-
await Bun.sleep(
|
|
614
|
+
await Bun.sleep(40);
|
|
572
615
|
return singleResult(options, { output: "done" });
|
|
573
616
|
});
|
|
574
617
|
|
|
575
618
|
const ops: string[] = [];
|
|
576
|
-
using idle = new IdleTimeout(
|
|
619
|
+
using idle = new IdleTimeout(20);
|
|
577
620
|
const result = await runEvalAgent(
|
|
578
621
|
{ prompt: "investigate" },
|
|
579
622
|
{
|
|
@@ -591,7 +634,7 @@ describe("agent() through eval runtimes", () => {
|
|
|
591
634
|
expect(ops).toEqual([EVAL_TIMEOUT_PAUSE_OP, EVAL_TIMEOUT_RESUME_OP]);
|
|
592
635
|
expect(idle.signal.aborted).toBe(false);
|
|
593
636
|
|
|
594
|
-
await Bun.sleep(
|
|
637
|
+
await Bun.sleep(60);
|
|
595
638
|
expect(idle.signal.aborted).toBe(true);
|
|
596
639
|
});
|
|
597
640
|
|
|
@@ -604,7 +647,7 @@ describe("agent() through eval runtimes", () => {
|
|
|
604
647
|
// They render as status, but timeout accounting is controlled only by the
|
|
605
648
|
// bridge pause/resume events.
|
|
606
649
|
vi.spyOn(taskExecutor, "runSubprocess").mockImplementation(async options => {
|
|
607
|
-
for (let i = 0; i <
|
|
650
|
+
for (let i = 0; i < 20; i++) {
|
|
608
651
|
options.onProgress?.({
|
|
609
652
|
index: options.index,
|
|
610
653
|
id: options.id,
|
|
@@ -621,13 +664,13 @@ describe("agent() through eval runtimes", () => {
|
|
|
621
664
|
cost: 0,
|
|
622
665
|
durationMs: i * 10,
|
|
623
666
|
});
|
|
624
|
-
await Bun.sleep(
|
|
667
|
+
await Bun.sleep(5);
|
|
625
668
|
}
|
|
626
669
|
return singleResult(options, { output: "done" });
|
|
627
670
|
});
|
|
628
671
|
|
|
629
672
|
const ops: string[] = [];
|
|
630
|
-
using idle = new IdleTimeout(
|
|
673
|
+
using idle = new IdleTimeout(40);
|
|
631
674
|
const result = await runEvalAgent(
|
|
632
675
|
{ prompt: "investigate" },
|
|
633
676
|
{
|
|
@@ -4,6 +4,7 @@ import type { Api, AssistantMessage, Model } from "@oh-my-pi/pi-ai";
|
|
|
4
4
|
import * as ai from "@oh-my-pi/pi-ai";
|
|
5
5
|
import { Effort } from "@oh-my-pi/pi-ai";
|
|
6
6
|
import { TempDir } from "@oh-my-pi/pi-utils";
|
|
7
|
+
import { $ } from "bun";
|
|
7
8
|
import type { ModelRegistry } from "../../config/model-registry";
|
|
8
9
|
import { Settings } from "../../config/settings";
|
|
9
10
|
import type { ToolSession } from "../../tools";
|
|
@@ -13,7 +14,7 @@ import { IdleTimeout } from "../idle-timeout";
|
|
|
13
14
|
import { disposeAllVmContexts } from "../js/context-manager";
|
|
14
15
|
import { executeJs } from "../js/executor";
|
|
15
16
|
import { runEvalLlm } from "../llm-bridge";
|
|
16
|
-
import { disposeAllKernelSessions,
|
|
17
|
+
import { disposeAllKernelSessions, type PythonResult } from "../py/executor";
|
|
17
18
|
|
|
18
19
|
function makeModel(provider: string, id: string, extra: Partial<Model<Api>> = {}): Model<Api> {
|
|
19
20
|
return {
|
|
@@ -57,6 +58,7 @@ function makeSession(opts: SessionOptions = {}): ToolSession {
|
|
|
57
58
|
const modelRegistry = {
|
|
58
59
|
getAvailable: () => opts.available ?? [SMOL, DEFAULT, SLOW],
|
|
59
60
|
getApiKey: async () => (opts.apiKey === undefined ? "test-key" : opts.apiKey),
|
|
61
|
+
resolver: () => async () => (opts.apiKey === undefined ? "test-key" : opts.apiKey),
|
|
60
62
|
} as unknown as ModelRegistry;
|
|
61
63
|
return {
|
|
62
64
|
settings,
|
|
@@ -96,6 +98,77 @@ function assistant(opts: {
|
|
|
96
98
|
};
|
|
97
99
|
}
|
|
98
100
|
|
|
101
|
+
async function runPythonLlmInSubprocess(options: { structured: boolean; tempDir: TempDir }): Promise<PythonResult> {
|
|
102
|
+
const repoRoot = path.resolve(import.meta.dir, "../../../..");
|
|
103
|
+
const scriptPath = path.join(options.tempDir.path(), "run-python-llm.ts");
|
|
104
|
+
const resultPath = path.join(options.tempDir.path(), "python-llm-result.json");
|
|
105
|
+
const aiPath = path.resolve(import.meta.dir, "../../../../ai/src/index.ts");
|
|
106
|
+
const executorPath = path.resolve(import.meta.dir, "../py/executor.ts");
|
|
107
|
+
const settingsPath = path.resolve(import.meta.dir, "../../config/settings.ts");
|
|
108
|
+
const code = options.structured
|
|
109
|
+
? 'import json\nprint(json.dumps(llm("hi", schema={"type": "object"})))'
|
|
110
|
+
: 'print(llm("hi", model="smol"))';
|
|
111
|
+
const responseContent = options.structured
|
|
112
|
+
? '[{ type: "toolCall", id: "tc-1", name: "respond", arguments: { ok: true } }]'
|
|
113
|
+
: '[{ type: "text", text: "hello from python" }]';
|
|
114
|
+
await Bun.write(
|
|
115
|
+
scriptPath,
|
|
116
|
+
`
|
|
117
|
+
import { vi } from "bun:test";
|
|
118
|
+
import * as ai from ${JSON.stringify(aiPath)};
|
|
119
|
+
import { executePython } from ${JSON.stringify(executorPath)};
|
|
120
|
+
import { Settings } from ${JSON.stringify(settingsPath)};
|
|
121
|
+
|
|
122
|
+
const SMOL = {
|
|
123
|
+
id: "smol",
|
|
124
|
+
name: "smol",
|
|
125
|
+
api: "openai-responses",
|
|
126
|
+
provider: "p",
|
|
127
|
+
baseUrl: "https://example.test/v1",
|
|
128
|
+
reasoning: false,
|
|
129
|
+
input: ["text"],
|
|
130
|
+
cost: { input: 1, output: 1, cacheRead: 0, cacheWrite: 1 },
|
|
131
|
+
contextWindow: 128000,
|
|
132
|
+
maxTokens: 4096,
|
|
133
|
+
};
|
|
134
|
+
const settings = Settings.isolated({ "async.enabled": false, "task.isolation.mode": "none" });
|
|
135
|
+
settings.setModelRole("smol", "p/smol");
|
|
136
|
+
settings.setModelRole("slow", "p/slow");
|
|
137
|
+
const session = {
|
|
138
|
+
settings,
|
|
139
|
+
modelRegistry: {
|
|
140
|
+
getAvailable: () => [SMOL],
|
|
141
|
+
getApiKey: async () => "test-key",
|
|
142
|
+
resolver: () => async () => "test-key",
|
|
143
|
+
},
|
|
144
|
+
getActiveModelString: () => "p/smol",
|
|
145
|
+
};
|
|
146
|
+
vi.spyOn(ai, "completeSimple").mockResolvedValue({
|
|
147
|
+
role: "assistant",
|
|
148
|
+
api: "openai-responses",
|
|
149
|
+
provider: "p",
|
|
150
|
+
model: "smol",
|
|
151
|
+
stopReason: "stop",
|
|
152
|
+
content: ${responseContent},
|
|
153
|
+
});
|
|
154
|
+
const result = await executePython(${JSON.stringify(code)}, {
|
|
155
|
+
cwd: ${JSON.stringify(options.tempDir.path())},
|
|
156
|
+
sessionId: ${JSON.stringify(`py-llm:${options.structured ? "struct" : "plain"}`)},
|
|
157
|
+
sessionFile: ${JSON.stringify(path.join(options.tempDir.path(), "session.jsonl"))},
|
|
158
|
+
toolSession: session,
|
|
159
|
+
kernelMode: "per-call",
|
|
160
|
+
});
|
|
161
|
+
await Bun.write(${JSON.stringify(resultPath)}, JSON.stringify(result));
|
|
162
|
+
process.exit(0);
|
|
163
|
+
`,
|
|
164
|
+
);
|
|
165
|
+
const child = await $`bun ${scriptPath}`.cwd(repoRoot).quiet().nothrow();
|
|
166
|
+
const stdout = child.stdout.toString();
|
|
167
|
+
const stderr = child.stderr.toString();
|
|
168
|
+
if (child.exitCode !== 0) throw new Error(stderr || stdout || `Python llm subprocess exited with ${child.exitCode}`);
|
|
169
|
+
return (await Bun.file(resultPath).json()) as PythonResult;
|
|
170
|
+
}
|
|
171
|
+
|
|
99
172
|
describe("runEvalLlm", () => {
|
|
100
173
|
afterEach(() => {
|
|
101
174
|
vi.restoreAllMocks();
|
|
@@ -290,38 +363,24 @@ describe("llm() through eval runtimes", () => {
|
|
|
290
363
|
});
|
|
291
364
|
|
|
292
365
|
it("exposes llm() in the Python runtime", async () => {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
sessionFile,
|
|
302
|
-
toolSession: makeSession(),
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
expect(result.exitCode).toBe(0);
|
|
306
|
-
expect(result.output.trim()).toBe("hello from python");
|
|
366
|
+
const tempDir = TempDir.createSync("@omp-eval-llm-py-");
|
|
367
|
+
try {
|
|
368
|
+
const result = await runPythonLlmInSubprocess({ structured: false, tempDir });
|
|
369
|
+
expect(result.exitCode).toBe(0);
|
|
370
|
+
expect(result.output.trim()).toBe("hello from python");
|
|
371
|
+
} finally {
|
|
372
|
+
tempDir.removeSync();
|
|
373
|
+
}
|
|
307
374
|
});
|
|
308
375
|
|
|
309
376
|
it("parses structured llm() output in the Python runtime", async () => {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
cwd: tempDir.path(),
|
|
319
|
-
sessionId,
|
|
320
|
-
sessionFile,
|
|
321
|
-
toolSession: makeSession(),
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
expect(result.exitCode).toBe(0);
|
|
325
|
-
expect(JSON.parse(result.output.trim())).toEqual({ ok: true });
|
|
377
|
+
const tempDir = TempDir.createSync("@omp-eval-llm-py-struct-");
|
|
378
|
+
try {
|
|
379
|
+
const result = await runPythonLlmInSubprocess({ structured: true, tempDir });
|
|
380
|
+
expect(result.exitCode).toBe(0);
|
|
381
|
+
expect(JSON.parse(result.output.trim())).toEqual({ ok: true });
|
|
382
|
+
} finally {
|
|
383
|
+
tempDir.removeSync();
|
|
384
|
+
}
|
|
326
385
|
});
|
|
327
386
|
});
|
package/src/eval/agent-bridge.ts
CHANGED
|
@@ -13,7 +13,7 @@ import subagentUserPromptTemplate from "../prompts/system/subagent-user-prompt.m
|
|
|
13
13
|
import * as taskDiscovery from "../task/discovery";
|
|
14
14
|
import * as taskExecutor from "../task/executor";
|
|
15
15
|
import { AgentOutputManager } from "../task/output-manager";
|
|
16
|
-
import type { AgentDefinition, AgentProgress } from "../task/types";
|
|
16
|
+
import type { AgentDefinition, AgentProgress, SingleResult } from "../task/types";
|
|
17
17
|
import type { ToolSession } from "../tools";
|
|
18
18
|
import { ToolError } from "../tools/tool-errors";
|
|
19
19
|
import { withBridgeTimeoutPause } from "./bridge-timeout";
|
|
@@ -173,6 +173,26 @@ function emitProgressStatus(emitStatus: ((event: JsStatusEvent) => void) | undef
|
|
|
173
173
|
});
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
+
/**
|
|
177
|
+
* Coalesce a subagent failure into a non-empty, human-meaningful error message.
|
|
178
|
+
*
|
|
179
|
+
* When the executor aborts a subagent (runtime limit, parent cancellation, …)
|
|
180
|
+
* the actionable explanation lives on `abortReason`, while `error`/`stderr`
|
|
181
|
+
* are routinely empty strings. Plain `??` coalescing stops at the empty string
|
|
182
|
+
* and ships an empty error through the bridge — Python then surfaces only the
|
|
183
|
+
* generic `bridge call '__agent__' failed`. See #2006.
|
|
184
|
+
*/
|
|
185
|
+
function buildSubagentFailureMessage(agentName: string, result: SingleResult): string {
|
|
186
|
+
const abortReason = trimToUndefined(result.abortReason);
|
|
187
|
+
if (result.aborted && abortReason) return abortReason;
|
|
188
|
+
return (
|
|
189
|
+
trimToUndefined(result.error) ??
|
|
190
|
+
trimToUndefined(result.stderr) ??
|
|
191
|
+
abortReason ??
|
|
192
|
+
`agent() subagent '${agentName}' failed.`
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
176
196
|
/**
|
|
177
197
|
* Run a single subagent on behalf of an eval cell's `agent()` call.
|
|
178
198
|
*/
|
|
@@ -225,7 +245,6 @@ export async function runEvalAgent(args: unknown, options: EvalAgentBridgeOption
|
|
|
225
245
|
getSessionId: options.session.getSessionId ?? (() => null),
|
|
226
246
|
};
|
|
227
247
|
const parentArtifactManager = options.session.getArtifactManager?.() ?? undefined;
|
|
228
|
-
const parentEvalSessionId = options.session.getEvalSessionId?.() ?? undefined;
|
|
229
248
|
const mcpManager = options.session.mcpManager ?? MCPManager.instance();
|
|
230
249
|
const { sessionFile, artifactsDir, contextFile } = await getArtifacts(options.session);
|
|
231
250
|
const outputManager = getOutputManager(options.session);
|
|
@@ -260,6 +279,12 @@ export async function runEvalAgent(args: unknown, options: EvalAgentBridgeOption
|
|
|
260
279
|
authStorage: options.session.authStorage,
|
|
261
280
|
modelRegistry: options.session.modelRegistry,
|
|
262
281
|
settings: options.session.settings,
|
|
282
|
+
// Eval `agent()` subagents are never wall-clock capped: the parent
|
|
283
|
+
// cell's idle watchdog is suspended for the whole bridge call
|
|
284
|
+
// (withBridgeTimeoutPause), so a long-running phase/recovery workflow
|
|
285
|
+
// must not be killed by `task.maxRuntimeMs`. Force the limit off
|
|
286
|
+
// regardless of the inherited session setting.
|
|
287
|
+
maxRuntimeMs: 0,
|
|
263
288
|
mcpManager,
|
|
264
289
|
contextFiles,
|
|
265
290
|
skills: availableSkills,
|
|
@@ -271,14 +296,16 @@ export async function runEvalAgent(args: unknown, options: EvalAgentBridgeOption
|
|
|
271
296
|
parentHindsightSessionState: options.session.getHindsightSessionState?.(),
|
|
272
297
|
parentMnemopiSessionState: options.session.getMnemopiSessionState?.(),
|
|
273
298
|
parentTelemetry: options.session.getTelemetry?.(),
|
|
274
|
-
parentEvalSessionId
|
|
299
|
+
// Deliberately omit parentEvalSessionId: the parent's Python kernel is
|
|
300
|
+
// blocked on this bridge call, so sharing the eval session would deadlock
|
|
301
|
+
// (subagent queues behind the parent's in-flight execution, parent waits
|
|
302
|
+
// for subagent → circular). Each bridge-spawned subagent gets its own
|
|
303
|
+
// eval session with an independent kernel.
|
|
275
304
|
}),
|
|
276
305
|
);
|
|
277
306
|
|
|
278
|
-
if (result.exitCode !== 0 || result.error) {
|
|
279
|
-
|
|
280
|
-
result.error ?? result.stderr ?? result.abortReason ?? `agent() subagent '${agentName}' failed.`;
|
|
281
|
-
throw new ToolError(failureMessage);
|
|
307
|
+
if (result.exitCode !== 0 || result.error || result.aborted) {
|
|
308
|
+
throw new ToolError(buildSubagentFailureMessage(agentName, result));
|
|
282
309
|
}
|
|
283
310
|
|
|
284
311
|
options.session.recordEvalSubagentUsage?.(result.usage?.output ?? 0);
|
package/src/eval/llm-bridge.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { instrumentedCompleteSimple, resolveTelemetry } from "@oh-my-pi/pi-agent
|
|
|
15
15
|
import { type Api, Effort, getSupportedEfforts, type Model, type Tool } from "@oh-my-pi/pi-ai";
|
|
16
16
|
import * as z from "zod/v4";
|
|
17
17
|
import { extractTextContent, extractToolCall, parseJsonPayload } from "../commit/utils";
|
|
18
|
+
|
|
18
19
|
import { expandRoleAlias, formatModelString, resolveModelFromString } from "../config/model-resolver";
|
|
19
20
|
import type { ToolSession } from "../tools";
|
|
20
21
|
import { ToolError } from "../tools/tool-errors";
|
|
@@ -112,8 +113,9 @@ export async function runEvalLlm(args: unknown, options: EvalLlmBridgeOptions):
|
|
|
112
113
|
);
|
|
113
114
|
}
|
|
114
115
|
|
|
115
|
-
const
|
|
116
|
-
|
|
116
|
+
const registry = options.session.modelRegistry;
|
|
117
|
+
const apiKey = await registry?.getApiKey(model);
|
|
118
|
+
if (!registry || !apiKey) {
|
|
117
119
|
throw new ToolError(
|
|
118
120
|
`llm() has no API key for ${formatModelString(model)}. Configure credentials for this provider or choose another tier.`,
|
|
119
121
|
);
|
|
@@ -143,7 +145,10 @@ export async function runEvalLlm(args: unknown, options: EvalLlmBridgeOptions):
|
|
|
143
145
|
tools,
|
|
144
146
|
},
|
|
145
147
|
{
|
|
146
|
-
apiKey,
|
|
148
|
+
apiKey: registry.resolver(model.provider, {
|
|
149
|
+
sessionId: options.session.getSessionId?.() ?? undefined,
|
|
150
|
+
baseUrl: model.baseUrl,
|
|
151
|
+
}),
|
|
147
152
|
signal: options.signal,
|
|
148
153
|
reasoning: reasoningForTier(tier, model),
|
|
149
154
|
toolChoice: schema ? { type: "tool", name: STRUCTURED_TOOL_NAME } : undefined,
|
|
@@ -25,7 +25,6 @@ export async function runDoctorChecks(): Promise<DoctorCheck[]> {
|
|
|
25
25
|
const apiKeys = [
|
|
26
26
|
{ name: "ANTHROPIC_API_KEY", description: "Anthropic API" },
|
|
27
27
|
{ name: "OPENAI_API_KEY", description: "OpenAI API" },
|
|
28
|
-
{ name: "PERPLEXITY_API_KEY", description: "Perplexity search" },
|
|
29
28
|
{ name: "EXA_API_KEY", description: "Exa search" },
|
|
30
29
|
];
|
|
31
30
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { getProjectDir, logger } from "@oh-my-pi/pi-utils";
|
|
2
|
+
|
|
3
|
+
type MarketplaceAutoUpdateMode = "off" | "notify" | "auto";
|
|
4
|
+
|
|
5
|
+
interface MarketplaceAutoUpdateOptions {
|
|
6
|
+
autoUpdate: MarketplaceAutoUpdateMode;
|
|
7
|
+
resolveActiveProjectRegistryPath: (cwd: string) => Promise<string | null>;
|
|
8
|
+
clearPluginRootsCache: () => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function scheduleMarketplaceAutoUpdate(options: MarketplaceAutoUpdateOptions): void {
|
|
12
|
+
if (options.autoUpdate === "off") {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
void runMarketplaceAutoUpdate(options);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function runMarketplaceAutoUpdate(options: MarketplaceAutoUpdateOptions): Promise<void> {
|
|
20
|
+
try {
|
|
21
|
+
// Startup perf: marketplace manager pulls scraper/fetch/cache code; keep it out of the initial TUI graph.
|
|
22
|
+
const {
|
|
23
|
+
MarketplaceManager,
|
|
24
|
+
getInstalledPluginsRegistryPath,
|
|
25
|
+
getMarketplacesCacheDir,
|
|
26
|
+
getMarketplacesRegistryPath,
|
|
27
|
+
getPluginsCacheDir,
|
|
28
|
+
} = await import("./marketplace");
|
|
29
|
+
const mgr = new MarketplaceManager({
|
|
30
|
+
marketplacesRegistryPath: getMarketplacesRegistryPath(),
|
|
31
|
+
installedRegistryPath: getInstalledPluginsRegistryPath(),
|
|
32
|
+
projectInstalledRegistryPath: (await options.resolveActiveProjectRegistryPath(getProjectDir())) ?? undefined,
|
|
33
|
+
marketplacesCacheDir: getMarketplacesCacheDir(),
|
|
34
|
+
pluginsCacheDir: getPluginsCacheDir(),
|
|
35
|
+
clearPluginRootsCache: options.clearPluginRootsCache,
|
|
36
|
+
});
|
|
37
|
+
await mgr.refreshStaleMarketplaces();
|
|
38
|
+
const updates = await mgr.checkForUpdates();
|
|
39
|
+
if (updates.length === 0) return;
|
|
40
|
+
if (options.autoUpdate === "auto") {
|
|
41
|
+
await mgr.upgradeAllPlugins();
|
|
42
|
+
logger.debug(`Auto-upgraded ${updates.length} marketplace plugin(s)`);
|
|
43
|
+
} else {
|
|
44
|
+
logger.debug(`${updates.length} marketplace plugin update(s) available — /marketplace upgrade`);
|
|
45
|
+
}
|
|
46
|
+
} catch {
|
|
47
|
+
// Silently ignore — network failure, corrupt data, offline.
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -8,9 +8,9 @@ import type { Theme, ThemeColor } from "../../modes/theme/theme";
|
|
|
8
8
|
import goalDescription from "../../prompts/tools/goal.md" with { type: "text" };
|
|
9
9
|
import { formatDuration } from "../../slash-commands/helpers/format";
|
|
10
10
|
import type { ToolSession } from "../../tools";
|
|
11
|
-
import {
|
|
11
|
+
import { formatErrorDetail, TRUNCATE_LENGTHS } from "../../tools/render-utils";
|
|
12
12
|
import { ToolError } from "../../tools/tool-errors";
|
|
13
|
-
import { renderStatusLine, truncateToWidth } from "../../tui";
|
|
13
|
+
import { framedBlock, renderStatusLine, truncateToWidth } from "../../tui";
|
|
14
14
|
import { completionBudgetReport, remainingTokens } from "../runtime";
|
|
15
15
|
import type { Goal, GoalStatus, GoalToolDetails } from "../state";
|
|
16
16
|
|
|
@@ -173,8 +173,7 @@ export const goalToolRenderer = {
|
|
|
173
173
|
if (args.op === "create" && args.token_budget !== undefined) {
|
|
174
174
|
meta.push(`budget ${formatNumber(args.token_budget)}`);
|
|
175
175
|
}
|
|
176
|
-
|
|
177
|
-
return new Text(text, 0, 0);
|
|
176
|
+
return new Text(renderStatusLine({ icon: "pending", title: "Goal", description, meta }, uiTheme), 0, 0);
|
|
178
177
|
},
|
|
179
178
|
|
|
180
179
|
renderResult(
|
|
@@ -190,51 +189,62 @@ export const goalToolRenderer = {
|
|
|
190
189
|
|
|
191
190
|
if (result.isError) {
|
|
192
191
|
const header = renderStatusLine({ icon: "error", title: "Goal", description }, uiTheme);
|
|
193
|
-
|
|
194
|
-
|
|
192
|
+
return framedBlock(uiTheme, width => ({
|
|
193
|
+
header,
|
|
194
|
+
sections: [{ lines: formatErrorDetail(fallbackText || "Goal tool failed", uiTheme).split("\n") }],
|
|
195
|
+
state: "error",
|
|
196
|
+
borderColor: "error",
|
|
197
|
+
width,
|
|
198
|
+
}));
|
|
195
199
|
}
|
|
196
200
|
|
|
197
201
|
const goal = details?.goal ?? null;
|
|
198
202
|
if (!goal) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
203
|
+
return new Text(
|
|
204
|
+
renderStatusLine({ icon: "warning", title: "Goal", description, meta: ["no active goal"] }, uiTheme),
|
|
205
|
+
0,
|
|
206
|
+
0,
|
|
207
|
+
);
|
|
202
208
|
}
|
|
203
209
|
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
},
|
|
213
|
-
uiTheme,
|
|
214
|
-
),
|
|
210
|
+
const header = renderStatusLine(
|
|
211
|
+
{
|
|
212
|
+
icon: "success",
|
|
213
|
+
title: "Goal",
|
|
214
|
+
description,
|
|
215
|
+
badge: { label: goal.status, color: goalBadgeColor(goal.status) },
|
|
216
|
+
},
|
|
217
|
+
uiTheme,
|
|
215
218
|
);
|
|
216
219
|
|
|
220
|
+
const lines: string[] = [];
|
|
217
221
|
const objectiveText = truncateToWidth(goal.objective.trim(), TRUNCATE_LENGTHS.LONG);
|
|
218
|
-
lines.push(
|
|
222
|
+
lines.push(uiTheme.italic(uiTheme.fg("muted", `"${objectiveText}"`)));
|
|
219
223
|
|
|
220
224
|
const used = formatNumber(goal.tokensUsed);
|
|
221
225
|
const tokensLine =
|
|
222
226
|
goal.tokenBudget !== undefined
|
|
223
227
|
? `${used} / ${formatNumber(goal.tokenBudget)} tokens (${formatNumber(Math.max(0, goal.tokenBudget - goal.tokensUsed))} left)`
|
|
224
228
|
: `${used} tokens`;
|
|
225
|
-
|
|
226
|
-
|
|
229
|
+
const metaParts = [tokensLine];
|
|
227
230
|
if (goal.timeUsedSeconds > 0) {
|
|
228
|
-
|
|
231
|
+
metaParts.push(`${formatDuration(goal.timeUsedSeconds * 1000)} elapsed`);
|
|
229
232
|
}
|
|
233
|
+
lines.push(uiTheme.fg("dim", metaParts.join(" · ")));
|
|
230
234
|
|
|
231
235
|
const report = details?.completionBudgetReport;
|
|
236
|
+
const sections: Array<{ label?: string; lines: string[] }> = [{ lines }];
|
|
232
237
|
if (report) {
|
|
233
|
-
|
|
234
|
-
lines.push(uiTheme.italic(uiTheme.fg("muted", report)));
|
|
238
|
+
sections.push({ label: "Report", lines: report.split("\n").map(line => uiTheme.fg("muted", line)) });
|
|
235
239
|
}
|
|
236
240
|
|
|
237
|
-
return
|
|
241
|
+
return framedBlock(uiTheme, width => ({
|
|
242
|
+
header,
|
|
243
|
+
sections,
|
|
244
|
+
state: "success",
|
|
245
|
+
borderColor: "borderMuted",
|
|
246
|
+
width,
|
|
247
|
+
}));
|
|
238
248
|
},
|
|
239
249
|
|
|
240
250
|
mergeCallAndResult: true,
|