@oh-my-pi/pi-coding-agent 0.1.0
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 +1629 -0
- package/README.md +1041 -0
- package/docs/compaction.md +403 -0
- package/docs/config-usage.md +113 -0
- package/docs/custom-tools.md +541 -0
- package/docs/extension-loading.md +1004 -0
- package/docs/hooks.md +867 -0
- package/docs/rpc.md +1040 -0
- package/docs/sdk.md +994 -0
- package/docs/session-tree-plan.md +441 -0
- package/docs/session.md +240 -0
- package/docs/skills.md +290 -0
- package/docs/theme.md +670 -0
- package/docs/tree.md +197 -0
- package/docs/tui.md +341 -0
- package/examples/README.md +21 -0
- package/examples/custom-tools/README.md +124 -0
- package/examples/custom-tools/hello/index.ts +20 -0
- package/examples/custom-tools/question/index.ts +84 -0
- package/examples/custom-tools/subagent/README.md +172 -0
- package/examples/custom-tools/subagent/agents/planner.md +37 -0
- package/examples/custom-tools/subagent/agents/scout.md +50 -0
- package/examples/custom-tools/subagent/agents/worker.md +24 -0
- package/examples/custom-tools/subagent/agents.ts +156 -0
- package/examples/custom-tools/subagent/commands/implement-and-review.md +10 -0
- package/examples/custom-tools/subagent/commands/implement.md +10 -0
- package/examples/custom-tools/subagent/commands/scout-and-plan.md +9 -0
- package/examples/custom-tools/subagent/index.ts +1002 -0
- package/examples/custom-tools/todo/index.ts +212 -0
- package/examples/hooks/README.md +56 -0
- package/examples/hooks/auto-commit-on-exit.ts +49 -0
- package/examples/hooks/confirm-destructive.ts +59 -0
- package/examples/hooks/custom-compaction.ts +116 -0
- package/examples/hooks/dirty-repo-guard.ts +52 -0
- package/examples/hooks/file-trigger.ts +41 -0
- package/examples/hooks/git-checkpoint.ts +53 -0
- package/examples/hooks/handoff.ts +150 -0
- package/examples/hooks/permission-gate.ts +34 -0
- package/examples/hooks/protected-paths.ts +30 -0
- package/examples/hooks/qna.ts +119 -0
- package/examples/hooks/snake.ts +343 -0
- package/examples/hooks/status-line.ts +40 -0
- package/examples/sdk/01-minimal.ts +22 -0
- package/examples/sdk/02-custom-model.ts +49 -0
- package/examples/sdk/03-custom-prompt.ts +44 -0
- package/examples/sdk/04-skills.ts +44 -0
- package/examples/sdk/05-tools.ts +90 -0
- package/examples/sdk/06-hooks.ts +61 -0
- package/examples/sdk/07-context-files.ts +36 -0
- package/examples/sdk/08-slash-commands.ts +42 -0
- package/examples/sdk/09-api-keys-and-oauth.ts +55 -0
- package/examples/sdk/10-settings.ts +38 -0
- package/examples/sdk/11-sessions.ts +48 -0
- package/examples/sdk/12-full-control.ts +95 -0
- package/examples/sdk/README.md +154 -0
- package/package.json +89 -0
- package/src/bun-imports.d.ts +16 -0
- package/src/capability/context-file.ts +40 -0
- package/src/capability/extension.ts +48 -0
- package/src/capability/hook.ts +40 -0
- package/src/capability/index.ts +616 -0
- package/src/capability/instruction.ts +37 -0
- package/src/capability/mcp.ts +52 -0
- package/src/capability/prompt.ts +35 -0
- package/src/capability/rule.ts +56 -0
- package/src/capability/settings.ts +35 -0
- package/src/capability/skill.ts +49 -0
- package/src/capability/slash-command.ts +40 -0
- package/src/capability/system-prompt.ts +35 -0
- package/src/capability/tool.ts +38 -0
- package/src/capability/types.ts +166 -0
- package/src/cli/args.ts +259 -0
- package/src/cli/file-processor.ts +121 -0
- package/src/cli/list-models.ts +104 -0
- package/src/cli/plugin-cli.ts +661 -0
- package/src/cli/session-picker.ts +41 -0
- package/src/cli/update-cli.ts +274 -0
- package/src/cli.ts +10 -0
- package/src/config.ts +391 -0
- package/src/core/agent-session.ts +2178 -0
- package/src/core/auth-storage.ts +258 -0
- package/src/core/bash-executor.ts +197 -0
- package/src/core/compaction/branch-summarization.ts +315 -0
- package/src/core/compaction/compaction.ts +664 -0
- package/src/core/compaction/index.ts +7 -0
- package/src/core/compaction/utils.ts +153 -0
- package/src/core/custom-commands/bundled/review/index.ts +156 -0
- package/src/core/custom-commands/index.ts +15 -0
- package/src/core/custom-commands/loader.ts +226 -0
- package/src/core/custom-commands/types.ts +112 -0
- package/src/core/custom-tools/index.ts +22 -0
- package/src/core/custom-tools/loader.ts +248 -0
- package/src/core/custom-tools/types.ts +185 -0
- package/src/core/custom-tools/wrapper.ts +29 -0
- package/src/core/exec.ts +139 -0
- package/src/core/export-html/index.ts +159 -0
- package/src/core/export-html/template.css +774 -0
- package/src/core/export-html/template.generated.ts +2 -0
- package/src/core/export-html/template.html +45 -0
- package/src/core/export-html/template.js +1185 -0
- package/src/core/export-html/template.macro.ts +24 -0
- package/src/core/file-mentions.ts +54 -0
- package/src/core/hooks/index.ts +16 -0
- package/src/core/hooks/loader.ts +288 -0
- package/src/core/hooks/runner.ts +434 -0
- package/src/core/hooks/tool-wrapper.ts +98 -0
- package/src/core/hooks/types.ts +770 -0
- package/src/core/index.ts +53 -0
- package/src/core/logger.ts +112 -0
- package/src/core/mcp/client.ts +185 -0
- package/src/core/mcp/config.ts +248 -0
- package/src/core/mcp/index.ts +45 -0
- package/src/core/mcp/loader.ts +99 -0
- package/src/core/mcp/manager.ts +235 -0
- package/src/core/mcp/tool-bridge.ts +156 -0
- package/src/core/mcp/transports/http.ts +316 -0
- package/src/core/mcp/transports/index.ts +6 -0
- package/src/core/mcp/transports/stdio.ts +252 -0
- package/src/core/mcp/types.ts +228 -0
- package/src/core/messages.ts +211 -0
- package/src/core/model-registry.ts +334 -0
- package/src/core/model-resolver.ts +494 -0
- package/src/core/plugins/doctor.ts +67 -0
- package/src/core/plugins/index.ts +38 -0
- package/src/core/plugins/installer.ts +189 -0
- package/src/core/plugins/loader.ts +339 -0
- package/src/core/plugins/manager.ts +672 -0
- package/src/core/plugins/parser.ts +105 -0
- package/src/core/plugins/paths.ts +37 -0
- package/src/core/plugins/types.ts +190 -0
- package/src/core/sdk.ts +900 -0
- package/src/core/session-manager.ts +1837 -0
- package/src/core/settings-manager.ts +860 -0
- package/src/core/skills.ts +352 -0
- package/src/core/slash-commands.ts +132 -0
- package/src/core/system-prompt.ts +442 -0
- package/src/core/timings.ts +25 -0
- package/src/core/title-generator.ts +110 -0
- package/src/core/tools/ask.ts +193 -0
- package/src/core/tools/bash-interceptor.ts +120 -0
- package/src/core/tools/bash.ts +91 -0
- package/src/core/tools/context.ts +32 -0
- package/src/core/tools/edit-diff.ts +487 -0
- package/src/core/tools/edit.ts +140 -0
- package/src/core/tools/exa/company.ts +59 -0
- package/src/core/tools/exa/index.ts +63 -0
- package/src/core/tools/exa/linkedin.ts +59 -0
- package/src/core/tools/exa/mcp-client.ts +368 -0
- package/src/core/tools/exa/render.ts +200 -0
- package/src/core/tools/exa/researcher.ts +90 -0
- package/src/core/tools/exa/search.ts +338 -0
- package/src/core/tools/exa/types.ts +167 -0
- package/src/core/tools/exa/websets.ts +248 -0
- package/src/core/tools/find.ts +244 -0
- package/src/core/tools/grep.ts +584 -0
- package/src/core/tools/index.ts +283 -0
- package/src/core/tools/ls.ts +142 -0
- package/src/core/tools/lsp/client.ts +767 -0
- package/src/core/tools/lsp/clients/biome-client.ts +207 -0
- package/src/core/tools/lsp/clients/index.ts +49 -0
- package/src/core/tools/lsp/clients/lsp-linter-client.ts +98 -0
- package/src/core/tools/lsp/config.ts +845 -0
- package/src/core/tools/lsp/edits.ts +110 -0
- package/src/core/tools/lsp/index.ts +1364 -0
- package/src/core/tools/lsp/render.ts +560 -0
- package/src/core/tools/lsp/rust-analyzer.ts +145 -0
- package/src/core/tools/lsp/types.ts +495 -0
- package/src/core/tools/lsp/utils.ts +526 -0
- package/src/core/tools/notebook.ts +182 -0
- package/src/core/tools/output.ts +198 -0
- package/src/core/tools/path-utils.ts +61 -0
- package/src/core/tools/read.ts +507 -0
- package/src/core/tools/renderers.ts +820 -0
- package/src/core/tools/review.ts +275 -0
- package/src/core/tools/rulebook.ts +124 -0
- package/src/core/tools/task/agents.ts +158 -0
- package/src/core/tools/task/artifacts.ts +114 -0
- package/src/core/tools/task/commands.ts +157 -0
- package/src/core/tools/task/discovery.ts +217 -0
- package/src/core/tools/task/executor.ts +531 -0
- package/src/core/tools/task/index.ts +548 -0
- package/src/core/tools/task/model-resolver.ts +176 -0
- package/src/core/tools/task/parallel.ts +38 -0
- package/src/core/tools/task/render.ts +502 -0
- package/src/core/tools/task/subprocess-tool-registry.ts +89 -0
- package/src/core/tools/task/types.ts +142 -0
- package/src/core/tools/truncate.ts +265 -0
- package/src/core/tools/web-fetch.ts +2511 -0
- package/src/core/tools/web-search/auth.ts +199 -0
- package/src/core/tools/web-search/index.ts +583 -0
- package/src/core/tools/web-search/providers/anthropic.ts +198 -0
- package/src/core/tools/web-search/providers/exa.ts +196 -0
- package/src/core/tools/web-search/providers/perplexity.ts +195 -0
- package/src/core/tools/web-search/render.ts +372 -0
- package/src/core/tools/web-search/types.ts +180 -0
- package/src/core/tools/write.ts +63 -0
- package/src/core/ttsr.ts +211 -0
- package/src/core/utils.ts +187 -0
- package/src/discovery/agents-md.ts +75 -0
- package/src/discovery/builtin.ts +647 -0
- package/src/discovery/claude.ts +623 -0
- package/src/discovery/cline.ts +104 -0
- package/src/discovery/codex.ts +571 -0
- package/src/discovery/cursor.ts +266 -0
- package/src/discovery/gemini.ts +368 -0
- package/src/discovery/github.ts +120 -0
- package/src/discovery/helpers.test.ts +127 -0
- package/src/discovery/helpers.ts +249 -0
- package/src/discovery/index.ts +84 -0
- package/src/discovery/mcp-json.ts +127 -0
- package/src/discovery/vscode.ts +99 -0
- package/src/discovery/windsurf.ts +219 -0
- package/src/index.ts +192 -0
- package/src/main.ts +507 -0
- package/src/migrations.ts +156 -0
- package/src/modes/cleanup.ts +23 -0
- package/src/modes/index.ts +48 -0
- package/src/modes/interactive/components/armin.ts +382 -0
- package/src/modes/interactive/components/assistant-message.ts +86 -0
- package/src/modes/interactive/components/bash-execution.ts +199 -0
- package/src/modes/interactive/components/bordered-loader.ts +41 -0
- package/src/modes/interactive/components/branch-summary-message.ts +42 -0
- package/src/modes/interactive/components/compaction-summary-message.ts +45 -0
- package/src/modes/interactive/components/custom-editor.ts +122 -0
- package/src/modes/interactive/components/diff.ts +147 -0
- package/src/modes/interactive/components/dynamic-border.ts +25 -0
- package/src/modes/interactive/components/extensions/extension-dashboard.ts +296 -0
- package/src/modes/interactive/components/extensions/extension-list.ts +479 -0
- package/src/modes/interactive/components/extensions/index.ts +9 -0
- package/src/modes/interactive/components/extensions/inspector-panel.ts +313 -0
- package/src/modes/interactive/components/extensions/state-manager.ts +558 -0
- package/src/modes/interactive/components/extensions/types.ts +191 -0
- package/src/modes/interactive/components/hook-editor.ts +117 -0
- package/src/modes/interactive/components/hook-input.ts +64 -0
- package/src/modes/interactive/components/hook-message.ts +96 -0
- package/src/modes/interactive/components/hook-selector.ts +91 -0
- package/src/modes/interactive/components/model-selector.ts +560 -0
- package/src/modes/interactive/components/oauth-selector.ts +136 -0
- package/src/modes/interactive/components/plugin-settings.ts +481 -0
- package/src/modes/interactive/components/queue-mode-selector.ts +56 -0
- package/src/modes/interactive/components/session-selector.ts +220 -0
- package/src/modes/interactive/components/settings-defs.ts +597 -0
- package/src/modes/interactive/components/settings-selector.ts +545 -0
- package/src/modes/interactive/components/show-images-selector.ts +45 -0
- package/src/modes/interactive/components/status-line/index.ts +4 -0
- package/src/modes/interactive/components/status-line/presets.ts +94 -0
- package/src/modes/interactive/components/status-line/segments.ts +350 -0
- package/src/modes/interactive/components/status-line/separators.ts +55 -0
- package/src/modes/interactive/components/status-line/types.ts +81 -0
- package/src/modes/interactive/components/status-line-segment-editor.ts +357 -0
- package/src/modes/interactive/components/status-line.ts +384 -0
- package/src/modes/interactive/components/theme-selector.ts +62 -0
- package/src/modes/interactive/components/thinking-selector.ts +64 -0
- package/src/modes/interactive/components/tool-execution.ts +946 -0
- package/src/modes/interactive/components/tree-selector.ts +877 -0
- package/src/modes/interactive/components/ttsr-notification.ts +82 -0
- package/src/modes/interactive/components/user-message-selector.ts +159 -0
- package/src/modes/interactive/components/user-message.ts +18 -0
- package/src/modes/interactive/components/visual-truncate.ts +50 -0
- package/src/modes/interactive/components/welcome.ts +228 -0
- package/src/modes/interactive/interactive-mode.ts +2669 -0
- package/src/modes/interactive/theme/dark.json +102 -0
- package/src/modes/interactive/theme/defaults/dark-arctic.json +111 -0
- package/src/modes/interactive/theme/defaults/dark-catppuccin.json +106 -0
- package/src/modes/interactive/theme/defaults/dark-cyberpunk.json +109 -0
- package/src/modes/interactive/theme/defaults/dark-dracula.json +105 -0
- package/src/modes/interactive/theme/defaults/dark-forest.json +103 -0
- package/src/modes/interactive/theme/defaults/dark-github.json +112 -0
- package/src/modes/interactive/theme/defaults/dark-gruvbox.json +119 -0
- package/src/modes/interactive/theme/defaults/dark-monochrome.json +101 -0
- package/src/modes/interactive/theme/defaults/dark-monokai.json +105 -0
- package/src/modes/interactive/theme/defaults/dark-nord.json +104 -0
- package/src/modes/interactive/theme/defaults/dark-ocean.json +108 -0
- package/src/modes/interactive/theme/defaults/dark-one.json +107 -0
- package/src/modes/interactive/theme/defaults/dark-retro.json +99 -0
- package/src/modes/interactive/theme/defaults/dark-rose-pine.json +95 -0
- package/src/modes/interactive/theme/defaults/dark-solarized.json +96 -0
- package/src/modes/interactive/theme/defaults/dark-sunset.json +106 -0
- package/src/modes/interactive/theme/defaults/dark-synthwave.json +102 -0
- package/src/modes/interactive/theme/defaults/dark-tokyo-night.json +108 -0
- package/src/modes/interactive/theme/defaults/index.ts +67 -0
- package/src/modes/interactive/theme/defaults/light-arctic.json +106 -0
- package/src/modes/interactive/theme/defaults/light-catppuccin.json +105 -0
- package/src/modes/interactive/theme/defaults/light-cyberpunk.json +103 -0
- package/src/modes/interactive/theme/defaults/light-forest.json +107 -0
- package/src/modes/interactive/theme/defaults/light-github.json +114 -0
- package/src/modes/interactive/theme/defaults/light-gruvbox.json +115 -0
- package/src/modes/interactive/theme/defaults/light-monochrome.json +100 -0
- package/src/modes/interactive/theme/defaults/light-ocean.json +106 -0
- package/src/modes/interactive/theme/defaults/light-one.json +105 -0
- package/src/modes/interactive/theme/defaults/light-retro.json +105 -0
- package/src/modes/interactive/theme/defaults/light-solarized.json +101 -0
- package/src/modes/interactive/theme/defaults/light-sunset.json +106 -0
- package/src/modes/interactive/theme/defaults/light-synthwave.json +105 -0
- package/src/modes/interactive/theme/defaults/light-tokyo-night.json +118 -0
- package/src/modes/interactive/theme/light.json +99 -0
- package/src/modes/interactive/theme/theme-schema.json +424 -0
- package/src/modes/interactive/theme/theme.ts +2211 -0
- package/src/modes/print-mode.ts +163 -0
- package/src/modes/rpc/rpc-client.ts +527 -0
- package/src/modes/rpc/rpc-mode.ts +494 -0
- package/src/modes/rpc/rpc-types.ts +203 -0
- package/src/prompts/architect-plan.md +10 -0
- package/src/prompts/branch-summary-preamble.md +3 -0
- package/src/prompts/branch-summary.md +28 -0
- package/src/prompts/browser.md +71 -0
- package/src/prompts/compaction-summary.md +34 -0
- package/src/prompts/compaction-turn-prefix.md +16 -0
- package/src/prompts/compaction-update-summary.md +41 -0
- package/src/prompts/explore.md +82 -0
- package/src/prompts/implement-with-critic.md +11 -0
- package/src/prompts/implement.md +11 -0
- package/src/prompts/init.md +30 -0
- package/src/prompts/plan.md +54 -0
- package/src/prompts/reviewer.md +81 -0
- package/src/prompts/summarization-system.md +3 -0
- package/src/prompts/system-prompt.md +27 -0
- package/src/prompts/task.md +56 -0
- package/src/prompts/title-system.md +8 -0
- package/src/prompts/tools/ask.md +24 -0
- package/src/prompts/tools/bash.md +23 -0
- package/src/prompts/tools/edit.md +9 -0
- package/src/prompts/tools/find.md +6 -0
- package/src/prompts/tools/grep.md +12 -0
- package/src/prompts/tools/lsp.md +14 -0
- package/src/prompts/tools/output.md +23 -0
- package/src/prompts/tools/read.md +25 -0
- package/src/prompts/tools/web-fetch.md +8 -0
- package/src/prompts/tools/web-search.md +10 -0
- package/src/prompts/tools/write.md +10 -0
- package/src/utils/changelog.ts +99 -0
- package/src/utils/clipboard.ts +265 -0
- package/src/utils/fuzzy.ts +108 -0
- package/src/utils/mime.ts +30 -0
- package/src/utils/shell-snapshot.ts +218 -0
- package/src/utils/shell.ts +364 -0
- package/src/utils/tools-manager.ts +265 -0
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TUI rendering for task tool.
|
|
3
|
+
*
|
|
4
|
+
* Provides renderCall and renderResult functions for displaying
|
|
5
|
+
* task execution in the terminal UI.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import type { Component } from "@oh-my-pi/pi-tui";
|
|
10
|
+
import { Container, Text } from "@oh-my-pi/pi-tui";
|
|
11
|
+
import type { Theme } from "../../../modes/interactive/theme/theme";
|
|
12
|
+
import type { RenderResultOptions } from "../../custom-tools/types";
|
|
13
|
+
import type { ReportFindingDetails, SubmitReviewDetails } from "../review";
|
|
14
|
+
import { subprocessToolRegistry } from "./subprocess-tool-registry";
|
|
15
|
+
import type { AgentProgress, SingleResult, TaskParams, TaskToolDetails } from "./types";
|
|
16
|
+
|
|
17
|
+
/** Priority labels for review findings */
|
|
18
|
+
const PRIORITY_LABELS: Record<number, string> = {
|
|
19
|
+
0: "P0",
|
|
20
|
+
1: "P1",
|
|
21
|
+
2: "P2",
|
|
22
|
+
3: "P3",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Format token count for display (e.g., 1.5k, 25k).
|
|
27
|
+
*/
|
|
28
|
+
function formatTokens(tokens: number): string {
|
|
29
|
+
if (tokens >= 1000) {
|
|
30
|
+
return `${(tokens / 1000).toFixed(1)}k`;
|
|
31
|
+
}
|
|
32
|
+
return String(tokens);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Format duration for display.
|
|
37
|
+
*/
|
|
38
|
+
export function formatDuration(ms: number): string {
|
|
39
|
+
if (ms < 1000) return `${ms}ms`;
|
|
40
|
+
if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
|
|
41
|
+
return `${(ms / 60000).toFixed(1)}m`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Truncate text to max length with ellipsis.
|
|
46
|
+
*/
|
|
47
|
+
function truncate(text: string, maxLen: number, ellipsis: string): string {
|
|
48
|
+
if (text.length <= maxLen) return text;
|
|
49
|
+
const sliceLen = Math.max(0, maxLen - ellipsis.length);
|
|
50
|
+
return `${text.slice(0, sliceLen)}${ellipsis}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get status icon for agent state.
|
|
55
|
+
* For running status, uses animated spinner if spinnerFrame is provided.
|
|
56
|
+
*/
|
|
57
|
+
function getStatusIcon(status: AgentProgress["status"], theme: Theme, spinnerFrame?: number): string {
|
|
58
|
+
switch (status) {
|
|
59
|
+
case "pending":
|
|
60
|
+
return theme.status.pending;
|
|
61
|
+
case "running": {
|
|
62
|
+
// Use animated spinner if frame is provided, otherwise static icon
|
|
63
|
+
if (spinnerFrame === undefined) return theme.status.running;
|
|
64
|
+
const frames = theme.spinnerFrames;
|
|
65
|
+
return frames[spinnerFrame % frames.length];
|
|
66
|
+
}
|
|
67
|
+
case "completed":
|
|
68
|
+
return theme.status.success;
|
|
69
|
+
case "failed":
|
|
70
|
+
return theme.status.error;
|
|
71
|
+
case "aborted":
|
|
72
|
+
return theme.status.aborted;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function formatBadge(label: string, color: "success" | "error" | "warning" | "accent" | "muted", theme: Theme): string {
|
|
77
|
+
const left = theme.format.bracketLeft;
|
|
78
|
+
const right = theme.format.bracketRight;
|
|
79
|
+
return theme.fg(color, `${left}${label}${right}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function formatFindingSummary(findings: ReportFindingDetails[], theme: Theme): string {
|
|
83
|
+
if (findings.length === 0) return theme.fg("dim", "Findings: none");
|
|
84
|
+
|
|
85
|
+
const counts = new Map<number, number>();
|
|
86
|
+
for (const finding of findings) {
|
|
87
|
+
counts.set(finding.priority, (counts.get(finding.priority) ?? 0) + 1);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const parts: string[] = [];
|
|
91
|
+
for (const priority of [0, 1, 2, 3]) {
|
|
92
|
+
const label = PRIORITY_LABELS[priority] ?? "P?";
|
|
93
|
+
const color = priority === 0 ? "error" : priority === 1 ? "warning" : "muted";
|
|
94
|
+
const count = counts.get(priority) ?? 0;
|
|
95
|
+
parts.push(theme.fg(color, `${label}:${count}`));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return `${theme.fg("dim", "Findings:")} ${parts.join(theme.sep.dot)}`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function renderOutputSection(
|
|
102
|
+
output: string,
|
|
103
|
+
continuePrefix: string,
|
|
104
|
+
expanded: boolean,
|
|
105
|
+
theme: Theme,
|
|
106
|
+
maxCollapsed = 3,
|
|
107
|
+
maxExpanded = 10,
|
|
108
|
+
): string[] {
|
|
109
|
+
const lines: string[] = [];
|
|
110
|
+
const outputLines = output.split("\n").filter((line) => line.trim());
|
|
111
|
+
if (outputLines.length === 0) return lines;
|
|
112
|
+
|
|
113
|
+
lines.push(`${continuePrefix}${theme.fg("dim", "Output")}`);
|
|
114
|
+
|
|
115
|
+
const previewCount = expanded ? maxExpanded : maxCollapsed;
|
|
116
|
+
for (const line of outputLines.slice(0, previewCount)) {
|
|
117
|
+
lines.push(`${continuePrefix} ${theme.fg("dim", truncate(line, 70, theme.format.ellipsis))}`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (outputLines.length > previewCount) {
|
|
121
|
+
lines.push(
|
|
122
|
+
`${continuePrefix} ${theme.fg(
|
|
123
|
+
"dim",
|
|
124
|
+
`${theme.format.ellipsis} ${outputLines.length - previewCount} more lines`,
|
|
125
|
+
)}`,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return lines;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Render the tool call arguments.
|
|
134
|
+
*/
|
|
135
|
+
export function renderCall(args: TaskParams, theme: Theme): Component {
|
|
136
|
+
const label = theme.fg("toolTitle", theme.bold("task"));
|
|
137
|
+
|
|
138
|
+
if (args.tasks.length === 1) {
|
|
139
|
+
// Single task - show agent and task preview
|
|
140
|
+
const task = args.tasks[0];
|
|
141
|
+
const taskPreview = truncate(task.task, 60, theme.format.ellipsis);
|
|
142
|
+
return new Text(`${label} ${theme.fg("accent", task.agent)}: ${theme.fg("muted", taskPreview)}`, 0, 0);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Multiple tasks - show count and agent names
|
|
146
|
+
const agents = args.tasks.map((t) => t.agent).join(", ");
|
|
147
|
+
return new Text(
|
|
148
|
+
`${label} ${theme.fg("muted", `${args.tasks.length} agents: ${truncate(agents, 50, theme.format.ellipsis)}`)}`,
|
|
149
|
+
0,
|
|
150
|
+
0,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Render streaming progress for a single agent.
|
|
156
|
+
*/
|
|
157
|
+
function renderAgentProgress(
|
|
158
|
+
progress: AgentProgress,
|
|
159
|
+
isLast: boolean,
|
|
160
|
+
expanded: boolean,
|
|
161
|
+
theme: Theme,
|
|
162
|
+
spinnerFrame?: number,
|
|
163
|
+
): string[] {
|
|
164
|
+
const lines: string[] = [];
|
|
165
|
+
const prefix = isLast
|
|
166
|
+
? `${theme.boxSharp.bottomLeft}${theme.boxSharp.horizontal}`
|
|
167
|
+
: `${theme.boxSharp.teeRight}${theme.boxSharp.horizontal}`;
|
|
168
|
+
const continuePrefix = isLast ? " " : `${theme.boxSharp.vertical} `;
|
|
169
|
+
|
|
170
|
+
const icon = getStatusIcon(progress.status, theme, spinnerFrame);
|
|
171
|
+
const iconColor =
|
|
172
|
+
progress.status === "completed"
|
|
173
|
+
? "success"
|
|
174
|
+
: progress.status === "failed" || progress.status === "aborted"
|
|
175
|
+
? "error"
|
|
176
|
+
: "accent";
|
|
177
|
+
|
|
178
|
+
// Main status line - include index for Output tool ID derivation
|
|
179
|
+
const agentId = `${progress.agent}(${progress.index})`;
|
|
180
|
+
let statusLine = `${prefix} ${theme.fg(iconColor, icon)} ${theme.fg("accent", agentId)}`;
|
|
181
|
+
|
|
182
|
+
// Only show badge for non-running states (spinner already indicates running)
|
|
183
|
+
if (progress.status !== "running") {
|
|
184
|
+
const statusLabel =
|
|
185
|
+
progress.status === "completed"
|
|
186
|
+
? "done"
|
|
187
|
+
: progress.status === "failed"
|
|
188
|
+
? "failed"
|
|
189
|
+
: progress.status === "aborted"
|
|
190
|
+
? "aborted"
|
|
191
|
+
: "pending";
|
|
192
|
+
statusLine += ` ${formatBadge(statusLabel, iconColor, theme)}`;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (progress.status === "running") {
|
|
196
|
+
const taskPreview = truncate(progress.task, 40, theme.format.ellipsis);
|
|
197
|
+
statusLine += ` ${theme.fg("muted", taskPreview)}`;
|
|
198
|
+
statusLine += `${theme.sep.dot}${theme.fg("dim", `${progress.toolCount} tools`)}`;
|
|
199
|
+
if (progress.tokens > 0) {
|
|
200
|
+
statusLine += `${theme.sep.dot}${theme.fg("dim", `${formatTokens(progress.tokens)} tokens`)}`;
|
|
201
|
+
}
|
|
202
|
+
} else if (progress.status === "completed") {
|
|
203
|
+
statusLine += `${theme.sep.dot}${theme.fg("dim", `${progress.toolCount} tools`)}`;
|
|
204
|
+
statusLine += `${theme.sep.dot}${theme.fg("dim", `${formatTokens(progress.tokens)} tokens`)}`;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
lines.push(statusLine);
|
|
208
|
+
|
|
209
|
+
// Current tool (if running)
|
|
210
|
+
if (progress.status === "running" && progress.currentTool) {
|
|
211
|
+
let toolLine = `${continuePrefix}${theme.tree.hook} ${theme.fg("muted", progress.currentTool)}`;
|
|
212
|
+
if (progress.currentToolArgs) {
|
|
213
|
+
toolLine += `: ${theme.fg("dim", truncate(progress.currentToolArgs, 40, theme.format.ellipsis))}`;
|
|
214
|
+
}
|
|
215
|
+
if (progress.currentToolStartMs) {
|
|
216
|
+
const elapsed = Date.now() - progress.currentToolStartMs;
|
|
217
|
+
if (elapsed > 5000) {
|
|
218
|
+
toolLine += `${theme.sep.dot}${theme.fg("warning", formatDuration(elapsed))}`;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
lines.push(toolLine);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Render extracted tool data inline (e.g., review findings)
|
|
225
|
+
if (progress.extractedToolData) {
|
|
226
|
+
for (const [toolName, dataArray] of Object.entries(progress.extractedToolData)) {
|
|
227
|
+
const handler = subprocessToolRegistry.getHandler(toolName);
|
|
228
|
+
if (handler?.renderInline) {
|
|
229
|
+
// Show last few items inline
|
|
230
|
+
const recentData = (dataArray as unknown[]).slice(-3);
|
|
231
|
+
for (const data of recentData) {
|
|
232
|
+
const component = handler.renderInline(data, theme);
|
|
233
|
+
if (component instanceof Text) {
|
|
234
|
+
lines.push(`${continuePrefix}${component.getText()}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (dataArray.length > 3) {
|
|
238
|
+
lines.push(
|
|
239
|
+
`${continuePrefix}${theme.fg("dim", `${theme.format.ellipsis} ${dataArray.length - 3} more`)}`,
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Expanded view: recent output and tools
|
|
247
|
+
if (expanded && progress.status === "running") {
|
|
248
|
+
const output = progress.recentOutput.join("\n");
|
|
249
|
+
lines.push(...renderOutputSection(output, continuePrefix, true, theme, 2, 6));
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return lines;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Render review result with combined verdict + findings in tree structure.
|
|
257
|
+
*/
|
|
258
|
+
function renderReviewResult(
|
|
259
|
+
summary: SubmitReviewDetails,
|
|
260
|
+
findings: ReportFindingDetails[],
|
|
261
|
+
continuePrefix: string,
|
|
262
|
+
expanded: boolean,
|
|
263
|
+
theme: Theme,
|
|
264
|
+
): string[] {
|
|
265
|
+
const lines: string[] = [];
|
|
266
|
+
|
|
267
|
+
// Verdict line
|
|
268
|
+
const verdictColor = summary.overall_correctness === "correct" ? "success" : "error";
|
|
269
|
+
const verdictIcon = summary.overall_correctness === "correct" ? theme.status.success : theme.status.error;
|
|
270
|
+
lines.push(
|
|
271
|
+
`${continuePrefix}${theme.fg(verdictColor, verdictIcon)} Patch is ${theme.fg(verdictColor, summary.overall_correctness)} ${theme.fg("dim", `(${(summary.confidence * 100).toFixed(0)}% confidence)`)}`,
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
// Explanation preview (first ~80 chars when collapsed, full when expanded)
|
|
275
|
+
if (summary.explanation) {
|
|
276
|
+
if (expanded) {
|
|
277
|
+
lines.push(`${continuePrefix}${theme.fg("dim", "Summary")}`);
|
|
278
|
+
const explanationLines = summary.explanation.split("\n");
|
|
279
|
+
for (const line of explanationLines) {
|
|
280
|
+
lines.push(`${continuePrefix} ${theme.fg("dim", line)}`);
|
|
281
|
+
}
|
|
282
|
+
} else {
|
|
283
|
+
// Preview: first sentence or ~100 chars
|
|
284
|
+
const preview = truncate(`${summary.explanation.split(/[.!?]/)[0]}.`, 100, theme.format.ellipsis);
|
|
285
|
+
lines.push(`${continuePrefix}${theme.fg("dim", `Summary: ${preview}`)}`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Findings summary + list
|
|
290
|
+
lines.push(`${continuePrefix}${formatFindingSummary(findings, theme)}`);
|
|
291
|
+
|
|
292
|
+
if (findings.length > 0) {
|
|
293
|
+
lines.push(`${continuePrefix}`); // Spacing
|
|
294
|
+
lines.push(...renderFindings(findings, continuePrefix, expanded, theme));
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return lines;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Render review findings list (used with and without submit_review).
|
|
302
|
+
*/
|
|
303
|
+
function renderFindings(
|
|
304
|
+
findings: ReportFindingDetails[],
|
|
305
|
+
continuePrefix: string,
|
|
306
|
+
expanded: boolean,
|
|
307
|
+
theme: Theme,
|
|
308
|
+
): string[] {
|
|
309
|
+
const lines: string[] = [];
|
|
310
|
+
const displayCount = expanded ? findings.length : Math.min(3, findings.length);
|
|
311
|
+
|
|
312
|
+
for (let i = 0; i < displayCount; i++) {
|
|
313
|
+
const finding = findings[i];
|
|
314
|
+
const isLastFinding = i === displayCount - 1 && (expanded || findings.length <= 3);
|
|
315
|
+
const findingPrefix = isLastFinding
|
|
316
|
+
? `${theme.boxSharp.bottomLeft}${theme.boxSharp.horizontal}`
|
|
317
|
+
: `${theme.boxSharp.teeRight}${theme.boxSharp.horizontal}`;
|
|
318
|
+
const findingContinue = isLastFinding ? " " : `${theme.boxSharp.vertical} `;
|
|
319
|
+
|
|
320
|
+
const priority = PRIORITY_LABELS[finding.priority] ?? "P?";
|
|
321
|
+
const color = finding.priority === 0 ? "error" : finding.priority === 1 ? "warning" : "muted";
|
|
322
|
+
const titleText = finding.title.replace(/^\[P\d\]\s*/, "");
|
|
323
|
+
const loc = `${path.basename(finding.file_path)}:${finding.line_start}`;
|
|
324
|
+
|
|
325
|
+
lines.push(
|
|
326
|
+
`${continuePrefix}${findingPrefix} ${theme.fg(color, `[${priority}]`)} ${titleText} ${theme.fg("dim", loc)}`,
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
// Show body when expanded
|
|
330
|
+
if (expanded && finding.body) {
|
|
331
|
+
// Wrap body text
|
|
332
|
+
const bodyLines = finding.body.split("\n");
|
|
333
|
+
for (const bodyLine of bodyLines) {
|
|
334
|
+
lines.push(`${continuePrefix}${findingContinue}${theme.fg("dim", bodyLine)}`);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (!expanded && findings.length > 3) {
|
|
340
|
+
lines.push(
|
|
341
|
+
`${continuePrefix}${theme.fg("dim", `${theme.format.ellipsis} ${findings.length - 3} more findings`)}`,
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return lines;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Render final result for a single agent.
|
|
350
|
+
*/
|
|
351
|
+
function renderAgentResult(result: SingleResult, isLast: boolean, expanded: boolean, theme: Theme): string[] {
|
|
352
|
+
const lines: string[] = [];
|
|
353
|
+
const prefix = isLast
|
|
354
|
+
? `${theme.boxSharp.bottomLeft}${theme.boxSharp.horizontal}`
|
|
355
|
+
: `${theme.boxSharp.teeRight}${theme.boxSharp.horizontal}`;
|
|
356
|
+
const continuePrefix = isLast ? " " : `${theme.boxSharp.vertical} `;
|
|
357
|
+
|
|
358
|
+
const aborted = result.aborted ?? false;
|
|
359
|
+
const success = !aborted && result.exitCode === 0;
|
|
360
|
+
const icon = aborted ? theme.status.aborted : success ? theme.status.success : theme.status.error;
|
|
361
|
+
const iconColor = success ? "success" : "error";
|
|
362
|
+
const statusText = aborted ? "aborted" : success ? "done" : "failed";
|
|
363
|
+
|
|
364
|
+
// Main status line - include index for Output tool ID derivation
|
|
365
|
+
const agentId = `${result.agent}(${result.index})`;
|
|
366
|
+
let statusLine = `${prefix} ${theme.fg(iconColor, icon)} ${theme.fg("accent", agentId)} ${formatBadge(statusText, iconColor, theme)}`;
|
|
367
|
+
if (result.tokens > 0) {
|
|
368
|
+
statusLine += `${theme.sep.dot}${theme.fg("dim", `${formatTokens(result.tokens)} tokens`)}`;
|
|
369
|
+
}
|
|
370
|
+
statusLine += `${theme.sep.dot}${theme.fg("dim", formatDuration(result.durationMs))}`;
|
|
371
|
+
|
|
372
|
+
if (result.truncated) {
|
|
373
|
+
statusLine += ` ${theme.fg("warning", "[truncated]")}`;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
lines.push(statusLine);
|
|
377
|
+
|
|
378
|
+
// Check for review result (submit_review + report_finding)
|
|
379
|
+
const submitReviewData = result.extractedToolData?.submit_review as SubmitReviewDetails[] | undefined;
|
|
380
|
+
const reportFindingData = result.extractedToolData?.report_finding as ReportFindingDetails[] | undefined;
|
|
381
|
+
|
|
382
|
+
if (submitReviewData && submitReviewData.length > 0) {
|
|
383
|
+
// Use combined review renderer
|
|
384
|
+
const summary = submitReviewData[submitReviewData.length - 1];
|
|
385
|
+
const findings = reportFindingData ?? [];
|
|
386
|
+
lines.push(...renderReviewResult(summary, findings, continuePrefix, expanded, theme));
|
|
387
|
+
return lines;
|
|
388
|
+
}
|
|
389
|
+
if (reportFindingData && reportFindingData.length > 0) {
|
|
390
|
+
lines.push(
|
|
391
|
+
`${continuePrefix}${theme.fg("warning", theme.status.warning)} ${theme.fg("dim", "Review summary missing (submit_review not called)")}`,
|
|
392
|
+
);
|
|
393
|
+
lines.push(`${continuePrefix}${formatFindingSummary(reportFindingData, theme)}`);
|
|
394
|
+
lines.push(`${continuePrefix}`); // Spacing
|
|
395
|
+
lines.push(...renderFindings(reportFindingData, continuePrefix, expanded, theme));
|
|
396
|
+
return lines;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Check for extracted tool data with custom renderers (skip review tools)
|
|
400
|
+
let hasCustomRendering = false;
|
|
401
|
+
if (result.extractedToolData) {
|
|
402
|
+
for (const [toolName, dataArray] of Object.entries(result.extractedToolData)) {
|
|
403
|
+
// Skip review tools - handled above
|
|
404
|
+
if (toolName === "submit_review" || toolName === "report_finding") continue;
|
|
405
|
+
|
|
406
|
+
const handler = subprocessToolRegistry.getHandler(toolName);
|
|
407
|
+
if (handler?.renderFinal && (dataArray as unknown[]).length > 0) {
|
|
408
|
+
hasCustomRendering = true;
|
|
409
|
+
const component = handler.renderFinal(dataArray as unknown[], theme, expanded);
|
|
410
|
+
if (component instanceof Text) {
|
|
411
|
+
// Prefix each line with continuePrefix
|
|
412
|
+
const text = component.getText();
|
|
413
|
+
for (const line of text.split("\n")) {
|
|
414
|
+
if (line.trim()) {
|
|
415
|
+
lines.push(`${continuePrefix}${line}`);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
} else if (component instanceof Container) {
|
|
419
|
+
// For containers, render each child
|
|
420
|
+
for (const child of (component as Container).children) {
|
|
421
|
+
if (child instanceof Text) {
|
|
422
|
+
lines.push(`${continuePrefix}${child.getText()}`);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Fallback to output preview if no custom rendering
|
|
431
|
+
if (!hasCustomRendering) {
|
|
432
|
+
lines.push(...renderOutputSection(result.output, continuePrefix, expanded, theme, 3, 12));
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Error message
|
|
436
|
+
if (result.error && !success) {
|
|
437
|
+
lines.push(`${continuePrefix}${theme.fg("error", truncate(result.error, 70, theme.format.ellipsis))}`);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return lines;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Render the tool result.
|
|
445
|
+
*/
|
|
446
|
+
export function renderResult(
|
|
447
|
+
result: { content: Array<{ type: string; text?: string }>; details?: TaskToolDetails },
|
|
448
|
+
options: RenderResultOptions,
|
|
449
|
+
theme: Theme,
|
|
450
|
+
): Component {
|
|
451
|
+
const { expanded, isPartial, spinnerFrame } = options;
|
|
452
|
+
const details = result.details;
|
|
453
|
+
|
|
454
|
+
if (!details) {
|
|
455
|
+
// Fallback to simple text
|
|
456
|
+
const text = result.content.find((c) => c.type === "text")?.text || "";
|
|
457
|
+
return new Text(theme.fg("dim", truncate(text, 100, theme.format.ellipsis)), 0, 0);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const lines: string[] = [];
|
|
461
|
+
|
|
462
|
+
if (isPartial && details.progress) {
|
|
463
|
+
// Streaming progress view
|
|
464
|
+
details.progress.forEach((progress, i) => {
|
|
465
|
+
const isLast = i === details.progress!.length - 1;
|
|
466
|
+
lines.push(...renderAgentProgress(progress, isLast, expanded, theme, spinnerFrame));
|
|
467
|
+
});
|
|
468
|
+
} else if (details.results.length > 0) {
|
|
469
|
+
// Final results view
|
|
470
|
+
details.results.forEach((res, i) => {
|
|
471
|
+
const isLast = i === details.results.length - 1;
|
|
472
|
+
lines.push(...renderAgentResult(res, isLast, expanded, theme));
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
// Summary line
|
|
476
|
+
const abortedCount = details.results.filter((r) => r.aborted).length;
|
|
477
|
+
const successCount = details.results.filter((r) => !r.aborted && r.exitCode === 0).length;
|
|
478
|
+
const failCount = details.results.length - successCount - abortedCount;
|
|
479
|
+
let summary = `\n${theme.fg("dim", "Total:")} `;
|
|
480
|
+
if (abortedCount > 0) {
|
|
481
|
+
summary += theme.fg("error", `${abortedCount} aborted`);
|
|
482
|
+
if (successCount > 0 || failCount > 0) summary += ", ";
|
|
483
|
+
}
|
|
484
|
+
if (successCount > 0) {
|
|
485
|
+
summary += theme.fg("success", `${successCount} succeeded`);
|
|
486
|
+
if (failCount > 0) summary += ", ";
|
|
487
|
+
}
|
|
488
|
+
if (failCount > 0) {
|
|
489
|
+
summary += theme.fg("error", `${failCount} failed`);
|
|
490
|
+
}
|
|
491
|
+
summary += `${theme.sep.dot}${theme.fg("dim", formatDuration(details.totalDurationMs))}`;
|
|
492
|
+
lines.push(summary);
|
|
493
|
+
|
|
494
|
+
// Artifacts suppressed from user view - available via session file
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if (lines.length === 0) {
|
|
498
|
+
return new Text(theme.fg("dim", "No results"), 0, 0);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
return new Text(lines.join("\n"), 0, 0);
|
|
502
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry for handling tool events from subprocess agents.
|
|
3
|
+
*
|
|
4
|
+
* Tools can register handlers to:
|
|
5
|
+
* - Extract structured data from their execution results
|
|
6
|
+
* - Trigger subprocess termination on completion
|
|
7
|
+
* - Provide custom rendering for realtime/final display
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Component } from "@oh-my-pi/pi-tui";
|
|
11
|
+
import type { Theme } from "../../../modes/interactive/theme/theme";
|
|
12
|
+
|
|
13
|
+
/** Event from subprocess tool execution (parsed from JSONL) */
|
|
14
|
+
export interface SubprocessToolEvent {
|
|
15
|
+
toolName: string;
|
|
16
|
+
toolCallId: string;
|
|
17
|
+
args?: Record<string, unknown>;
|
|
18
|
+
result?: {
|
|
19
|
+
content: Array<{ type: string; text?: string }>;
|
|
20
|
+
details?: unknown;
|
|
21
|
+
};
|
|
22
|
+
isError?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Handler for subprocess tool events */
|
|
26
|
+
export interface SubprocessToolHandler<TData = unknown> {
|
|
27
|
+
/**
|
|
28
|
+
* Extract structured data from tool result.
|
|
29
|
+
* Extracted data is accumulated in progress.extractedToolData[toolName][].
|
|
30
|
+
*/
|
|
31
|
+
extractData?: (event: SubprocessToolEvent) => TData | undefined;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Whether this tool's completion should terminate the subprocess.
|
|
35
|
+
* Return true to send SIGTERM after the tool completes.
|
|
36
|
+
*/
|
|
37
|
+
shouldTerminate?: (event: SubprocessToolEvent) => boolean;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Render a single data item inline during streaming progress.
|
|
41
|
+
* Called for each tool execution end event.
|
|
42
|
+
*/
|
|
43
|
+
renderInline?: (data: TData, theme: Theme) => Component;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Render accumulated data in the final result view.
|
|
47
|
+
* Called once with all accumulated data for this tool.
|
|
48
|
+
*/
|
|
49
|
+
renderFinal?: (allData: TData[], theme: Theme, expanded: boolean) => Component;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Registry for subprocess tool handlers */
|
|
53
|
+
class SubprocessToolRegistryImpl {
|
|
54
|
+
private handlers = new Map<string, SubprocessToolHandler>();
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Register a handler for a tool's subprocess events.
|
|
58
|
+
*/
|
|
59
|
+
register<T>(toolName: string, handler: SubprocessToolHandler<T>): void {
|
|
60
|
+
this.handlers.set(toolName, handler as SubprocessToolHandler);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get the handler for a tool, if registered.
|
|
65
|
+
*/
|
|
66
|
+
getHandler(toolName: string): SubprocessToolHandler | undefined {
|
|
67
|
+
return this.handlers.get(toolName);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Check if a tool has a registered handler.
|
|
72
|
+
*/
|
|
73
|
+
hasHandler(toolName: string): boolean {
|
|
74
|
+
return this.handlers.has(toolName);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get all registered tool names.
|
|
79
|
+
*/
|
|
80
|
+
getRegisteredTools(): string[] {
|
|
81
|
+
return Array.from(this.handlers.keys());
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Singleton registry instance */
|
|
86
|
+
export const subprocessToolRegistry = new SubprocessToolRegistryImpl();
|
|
87
|
+
|
|
88
|
+
/** Type helper for extracted tool data in progress/result */
|
|
89
|
+
export type ExtractedToolData = Record<string, unknown[]>;
|