@oh-my-pi/pi-coding-agent 1.341.0 → 2.1.1337
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 +86 -0
- package/README.md +1 -1
- package/examples/custom-tools/subagent/index.ts +1 -1
- package/package.json +10 -9
- package/src/bun-imports.d.ts +16 -0
- package/src/cli/args.ts +5 -6
- package/src/cli/file-processor.ts +3 -3
- package/src/cli/list-models.ts +2 -2
- package/src/cli/plugin-cli.ts +1 -1
- package/src/cli/session-picker.ts +2 -2
- package/src/cli/update-cli.ts +273 -0
- package/src/cli.ts +1 -1
- package/src/config.ts +23 -75
- package/src/core/agent-session.ts +158 -16
- package/src/core/auth-storage.ts +2 -3
- package/src/core/bash-executor.ts +50 -10
- package/src/core/compaction/branch-summarization.ts +5 -5
- package/src/core/compaction/compaction.ts +3 -3
- package/src/core/compaction/index.ts +3 -3
- 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 +232 -0
- package/src/core/custom-commands/types.ts +112 -0
- package/src/core/custom-tools/index.ts +3 -3
- package/src/core/custom-tools/loader.ts +10 -8
- package/src/core/custom-tools/types.ts +11 -6
- package/src/core/custom-tools/wrapper.ts +2 -1
- package/src/core/exec.ts +22 -12
- package/src/core/export-html/index.ts +38 -123
- package/src/core/export-html/template.css +0 -7
- package/src/core/export-html/template.html +3 -4
- package/src/core/export-html/template.macro.ts +24 -0
- package/src/core/file-mentions.ts +54 -0
- package/src/core/hooks/index.ts +5 -5
- package/src/core/hooks/loader.ts +21 -16
- package/src/core/hooks/runner.ts +6 -6
- package/src/core/hooks/tool-wrapper.ts +2 -2
- package/src/core/hooks/types.ts +12 -15
- package/src/core/index.ts +6 -6
- package/src/core/logger.ts +112 -0
- package/src/core/mcp/client.ts +3 -3
- package/src/core/mcp/config.ts +1 -1
- package/src/core/mcp/index.ts +12 -12
- package/src/core/mcp/loader.ts +2 -2
- package/src/core/mcp/manager.ts +6 -6
- package/src/core/mcp/tool-bridge.ts +3 -3
- package/src/core/mcp/transports/http.ts +1 -1
- package/src/core/mcp/transports/index.ts +2 -2
- package/src/core/mcp/transports/stdio.ts +1 -1
- package/src/core/messages.ts +22 -0
- package/src/core/model-registry.ts +2 -2
- package/src/core/model-resolver.ts +2 -2
- package/src/core/plugins/doctor.ts +1 -1
- package/src/core/plugins/index.ts +6 -6
- package/src/core/plugins/installer.ts +4 -4
- package/src/core/plugins/loader.ts +4 -9
- package/src/core/plugins/manager.ts +5 -5
- package/src/core/plugins/paths.ts +3 -3
- package/src/core/sdk.ts +77 -35
- package/src/core/session-manager.ts +6 -6
- package/src/core/settings-manager.ts +16 -3
- package/src/core/skills.ts +5 -5
- package/src/core/slash-commands.ts +60 -45
- package/src/core/system-prompt.ts +6 -6
- package/src/core/title-generator.ts +2 -2
- package/src/core/tools/bash.ts +32 -155
- package/src/core/tools/context.ts +2 -2
- package/src/core/tools/edit-diff.ts +3 -3
- package/src/core/tools/edit.ts +18 -5
- package/src/core/tools/exa/company.ts +3 -3
- package/src/core/tools/exa/index.ts +16 -17
- package/src/core/tools/exa/linkedin.ts +3 -3
- package/src/core/tools/exa/mcp-client.ts +9 -9
- package/src/core/tools/exa/render.ts +5 -5
- package/src/core/tools/exa/researcher.ts +3 -3
- package/src/core/tools/exa/search.ts +6 -5
- package/src/core/tools/exa/types.ts +5 -6
- package/src/core/tools/exa/websets.ts +3 -3
- package/src/core/tools/find.ts +3 -3
- package/src/core/tools/grep.ts +3 -3
- package/src/core/tools/index.ts +48 -34
- package/src/core/tools/ls.ts +4 -4
- package/src/core/tools/lsp/client.ts +161 -90
- package/src/core/tools/lsp/config.ts +1 -1
- package/src/core/tools/lsp/edits.ts +2 -2
- package/src/core/tools/lsp/index.ts +15 -13
- package/src/core/tools/lsp/render.ts +2 -2
- package/src/core/tools/lsp/rust-analyzer.ts +3 -3
- package/src/core/tools/lsp/utils.ts +1 -1
- package/src/core/tools/notebook.ts +1 -1
- package/src/core/tools/output.ts +175 -0
- package/src/core/tools/read.ts +7 -7
- package/src/core/tools/renderers.ts +92 -13
- package/src/core/tools/review.ts +268 -0
- package/src/core/tools/task/agents.ts +22 -38
- package/src/core/tools/task/bundled-agents/reviewer.md +52 -37
- package/src/core/tools/task/commands.ts +31 -10
- package/src/core/tools/task/discovery.ts +2 -2
- package/src/core/tools/task/executor.ts +145 -28
- package/src/core/tools/task/index.ts +78 -30
- package/src/core/tools/task/model-resolver.ts +30 -20
- package/src/core/tools/task/parallel.ts +1 -1
- package/src/core/tools/task/render.ts +219 -30
- package/src/core/tools/task/subprocess-tool-registry.ts +89 -0
- package/src/core/tools/task/types.ts +36 -2
- package/src/core/tools/web-fetch.ts +5 -3
- package/src/core/tools/web-search/auth.ts +1 -1
- package/src/core/tools/web-search/index.ts +17 -15
- package/src/core/tools/web-search/providers/anthropic.ts +2 -2
- package/src/core/tools/web-search/providers/exa.ts +3 -5
- package/src/core/tools/web-search/providers/perplexity.ts +1 -1
- package/src/core/tools/web-search/render.ts +3 -3
- package/src/core/tools/write.ts +4 -4
- package/src/index.ts +29 -18
- package/src/main.ts +50 -33
- package/src/migrations.ts +3 -3
- package/src/modes/index.ts +5 -5
- package/src/modes/interactive/components/armin.ts +1 -1
- package/src/modes/interactive/components/assistant-message.ts +1 -1
- package/src/modes/interactive/components/bash-execution.ts +4 -4
- package/src/modes/interactive/components/bordered-loader.ts +2 -2
- package/src/modes/interactive/components/branch-summary-message.ts +2 -2
- package/src/modes/interactive/components/compaction-summary-message.ts +2 -2
- package/src/modes/interactive/components/diff.ts +1 -1
- package/src/modes/interactive/components/dynamic-border.ts +1 -1
- package/src/modes/interactive/components/footer.ts +5 -5
- package/src/modes/interactive/components/hook-editor.ts +2 -2
- package/src/modes/interactive/components/hook-input.ts +2 -2
- package/src/modes/interactive/components/hook-message.ts +3 -3
- package/src/modes/interactive/components/hook-selector.ts +2 -2
- package/src/modes/interactive/components/model-selector.ts +281 -59
- package/src/modes/interactive/components/oauth-selector.ts +3 -3
- package/src/modes/interactive/components/plugin-settings.ts +4 -4
- package/src/modes/interactive/components/queue-mode-selector.ts +2 -2
- package/src/modes/interactive/components/session-selector.ts +4 -4
- package/src/modes/interactive/components/settings-defs.ts +1 -1
- package/src/modes/interactive/components/settings-selector.ts +5 -5
- package/src/modes/interactive/components/show-images-selector.ts +2 -2
- package/src/modes/interactive/components/theme-selector.ts +2 -2
- package/src/modes/interactive/components/thinking-selector.ts +2 -2
- package/src/modes/interactive/components/tool-execution.ts +26 -8
- package/src/modes/interactive/components/tree-selector.ts +3 -3
- package/src/modes/interactive/components/user-message-selector.ts +2 -2
- package/src/modes/interactive/components/user-message.ts +1 -1
- package/src/modes/interactive/components/welcome.ts +2 -2
- package/src/modes/interactive/interactive-mode.ts +86 -42
- package/src/modes/interactive/theme/theme.ts +15 -17
- package/src/modes/print-mode.ts +4 -3
- package/src/modes/rpc/rpc-client.ts +4 -4
- package/src/modes/rpc/rpc-mode.ts +22 -12
- package/src/modes/rpc/rpc-types.ts +3 -3
- package/src/utils/changelog.ts +2 -2
- package/src/utils/clipboard.ts +1 -1
- package/src/utils/shell-snapshot.ts +218 -0
- package/src/utils/shell.ts +93 -13
- package/src/utils/tools-manager.ts +1 -1
- package/examples/custom-tools/subagent/agents/reviewer.md +0 -35
- package/src/core/tools/exa/logger.ts +0 -56
|
@@ -7,10 +7,20 @@
|
|
|
7
7
|
|
|
8
8
|
import path from "node:path";
|
|
9
9
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
10
|
-
import { 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 {
|
|
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
|
+
};
|
|
14
24
|
|
|
15
25
|
/**
|
|
16
26
|
* Format token count for display (e.g., 1.5k, 25k).
|
|
@@ -52,6 +62,8 @@ function getStatusIcon(status: AgentProgress["status"]): string {
|
|
|
52
62
|
return "✓";
|
|
53
63
|
case "failed":
|
|
54
64
|
return "✗";
|
|
65
|
+
case "aborted":
|
|
66
|
+
return "⊘";
|
|
55
67
|
}
|
|
56
68
|
}
|
|
57
69
|
|
|
@@ -82,10 +94,16 @@ function renderAgentProgress(progress: AgentProgress, isLast: boolean, expanded:
|
|
|
82
94
|
const continuePrefix = isLast ? " " : "│ ";
|
|
83
95
|
|
|
84
96
|
const icon = getStatusIcon(progress.status);
|
|
85
|
-
const iconColor =
|
|
97
|
+
const iconColor =
|
|
98
|
+
progress.status === "completed"
|
|
99
|
+
? "success"
|
|
100
|
+
: progress.status === "failed" || progress.status === "aborted"
|
|
101
|
+
? "error"
|
|
102
|
+
: "accent";
|
|
86
103
|
|
|
87
|
-
// Main status line
|
|
88
|
-
|
|
104
|
+
// Main status line - include index for Output tool ID derivation
|
|
105
|
+
const agentId = `${progress.agent}(${progress.index})`;
|
|
106
|
+
let statusLine = `${prefix} ${theme.fg(iconColor, icon)} ${theme.fg("accent", agentId)}`;
|
|
89
107
|
|
|
90
108
|
if (progress.status === "running") {
|
|
91
109
|
const taskPreview = truncate(progress.task, 40);
|
|
@@ -98,6 +116,8 @@ function renderAgentProgress(progress: AgentProgress, isLast: boolean, expanded:
|
|
|
98
116
|
statusLine += `: ${theme.fg("success", "done")}`;
|
|
99
117
|
statusLine += ` · ${theme.fg("dim", `${progress.toolCount} tools`)}`;
|
|
100
118
|
statusLine += ` · ${theme.fg("dim", `${formatTokens(progress.tokens)} tokens`)}`;
|
|
119
|
+
} else if (progress.status === "aborted") {
|
|
120
|
+
statusLine += `: ${theme.fg("error", "aborted")}`;
|
|
101
121
|
} else if (progress.status === "failed") {
|
|
102
122
|
statusLine += `: ${theme.fg("error", "failed")}`;
|
|
103
123
|
}
|
|
@@ -119,6 +139,26 @@ function renderAgentProgress(progress: AgentProgress, isLast: boolean, expanded:
|
|
|
119
139
|
lines.push(toolLine);
|
|
120
140
|
}
|
|
121
141
|
|
|
142
|
+
// Render extracted tool data inline (e.g., review findings)
|
|
143
|
+
if (progress.extractedToolData) {
|
|
144
|
+
for (const [toolName, dataArray] of Object.entries(progress.extractedToolData)) {
|
|
145
|
+
const handler = subprocessToolRegistry.getHandler(toolName);
|
|
146
|
+
if (handler?.renderInline) {
|
|
147
|
+
// Show last few items inline
|
|
148
|
+
const recentData = (dataArray as unknown[]).slice(-3);
|
|
149
|
+
for (const data of recentData) {
|
|
150
|
+
const component = handler.renderInline(data, theme);
|
|
151
|
+
if (component instanceof Text) {
|
|
152
|
+
lines.push(`${continuePrefix}${component.getText()}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (dataArray.length > 3) {
|
|
156
|
+
lines.push(`${continuePrefix}${theme.fg("dim", `... ${dataArray.length - 3} more`)}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
122
162
|
// Expanded view: recent output and tools
|
|
123
163
|
if (expanded && progress.status === "running") {
|
|
124
164
|
// Recent output
|
|
@@ -130,6 +170,93 @@ function renderAgentProgress(progress: AgentProgress, isLast: boolean, expanded:
|
|
|
130
170
|
return lines;
|
|
131
171
|
}
|
|
132
172
|
|
|
173
|
+
/**
|
|
174
|
+
* Render review result with combined verdict + findings in tree structure.
|
|
175
|
+
*/
|
|
176
|
+
function renderReviewResult(
|
|
177
|
+
summary: SubmitReviewDetails,
|
|
178
|
+
findings: ReportFindingDetails[],
|
|
179
|
+
continuePrefix: string,
|
|
180
|
+
expanded: boolean,
|
|
181
|
+
theme: Theme,
|
|
182
|
+
): string[] {
|
|
183
|
+
const lines: string[] = [];
|
|
184
|
+
|
|
185
|
+
// Verdict line
|
|
186
|
+
const verdictColor = summary.overall_correctness === "correct" ? "success" : "error";
|
|
187
|
+
const verdictIcon = summary.overall_correctness === "correct" ? "✓" : "✗";
|
|
188
|
+
lines.push(
|
|
189
|
+
`${continuePrefix}${theme.fg(verdictColor, verdictIcon)} Patch is ${theme.fg(verdictColor, summary.overall_correctness)} ${theme.fg("dim", `(${(summary.confidence * 100).toFixed(0)}% confidence)`)}`,
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
// Explanation preview (first ~80 chars when collapsed, full when expanded)
|
|
193
|
+
if (summary.explanation) {
|
|
194
|
+
if (expanded) {
|
|
195
|
+
// Full explanation, wrapped
|
|
196
|
+
const explanationLines = summary.explanation.split("\n");
|
|
197
|
+
for (const line of explanationLines) {
|
|
198
|
+
lines.push(`${continuePrefix}${theme.fg("dim", line)}`);
|
|
199
|
+
}
|
|
200
|
+
} else {
|
|
201
|
+
// Preview: first sentence or ~100 chars
|
|
202
|
+
const preview = truncate(`${summary.explanation.split(/[.!?]/)[0]}.`, 100);
|
|
203
|
+
lines.push(`${continuePrefix}${theme.fg("dim", preview)}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Findings in tree structure
|
|
208
|
+
if (findings.length > 0) {
|
|
209
|
+
lines.push(`${continuePrefix}`); // Spacing
|
|
210
|
+
lines.push(...renderFindings(findings, continuePrefix, expanded, theme));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return lines;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Render review findings list (used with and without submit_review).
|
|
218
|
+
*/
|
|
219
|
+
function renderFindings(
|
|
220
|
+
findings: ReportFindingDetails[],
|
|
221
|
+
continuePrefix: string,
|
|
222
|
+
expanded: boolean,
|
|
223
|
+
theme: Theme,
|
|
224
|
+
): string[] {
|
|
225
|
+
const lines: string[] = [];
|
|
226
|
+
const displayCount = expanded ? findings.length : Math.min(3, findings.length);
|
|
227
|
+
|
|
228
|
+
for (let i = 0; i < displayCount; i++) {
|
|
229
|
+
const finding = findings[i];
|
|
230
|
+
const isLastFinding = i === displayCount - 1 && (expanded || findings.length <= 3);
|
|
231
|
+
const findingPrefix = isLastFinding ? "└─" : "├─";
|
|
232
|
+
const findingContinue = isLastFinding ? " " : "│ ";
|
|
233
|
+
|
|
234
|
+
const priority = PRIORITY_LABELS[finding.priority] ?? "P?";
|
|
235
|
+
const color = finding.priority === 0 ? "error" : finding.priority === 1 ? "warning" : "muted";
|
|
236
|
+
const titleText = finding.title.replace(/^\[P\d\]\s*/, "");
|
|
237
|
+
const loc = `${path.basename(finding.file_path)}:${finding.line_start}`;
|
|
238
|
+
|
|
239
|
+
lines.push(
|
|
240
|
+
`${continuePrefix}${findingPrefix} ${theme.fg(color, `[${priority}]`)} ${titleText} ${theme.fg("dim", loc)}`,
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
// Show body when expanded
|
|
244
|
+
if (expanded && finding.body) {
|
|
245
|
+
// Wrap body text
|
|
246
|
+
const bodyLines = finding.body.split("\n");
|
|
247
|
+
for (const bodyLine of bodyLines) {
|
|
248
|
+
lines.push(`${continuePrefix}${findingContinue}${theme.fg("dim", bodyLine)}`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (!expanded && findings.length > 3) {
|
|
254
|
+
lines.push(`${continuePrefix}${theme.fg("dim", `... ${findings.length - 3} more findings`)}`);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return lines;
|
|
258
|
+
}
|
|
259
|
+
|
|
133
260
|
/**
|
|
134
261
|
* Render final result for a single agent.
|
|
135
262
|
*/
|
|
@@ -138,14 +265,19 @@ function renderAgentResult(result: SingleResult, isLast: boolean, expanded: bool
|
|
|
138
265
|
const prefix = isLast ? "└─" : "├─";
|
|
139
266
|
const continuePrefix = isLast ? " " : "│ ";
|
|
140
267
|
|
|
141
|
-
const
|
|
142
|
-
const
|
|
268
|
+
const aborted = result.aborted ?? false;
|
|
269
|
+
const success = !aborted && result.exitCode === 0;
|
|
270
|
+
const icon = aborted ? "⊘" : success ? "✓" : "✗";
|
|
143
271
|
const iconColor = success ? "success" : "error";
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
statusLine
|
|
272
|
+
const statusText = aborted ? "aborted" : success ? "done" : "failed";
|
|
273
|
+
|
|
274
|
+
// Main status line - include index for Output tool ID derivation
|
|
275
|
+
const agentId = `${result.agent}(${result.index})`;
|
|
276
|
+
let statusLine = `${prefix} ${theme.fg(iconColor, icon)} ${theme.fg("accent", agentId)}`;
|
|
277
|
+
statusLine += `: ${theme.fg(iconColor, statusText)}`;
|
|
278
|
+
if (result.tokens > 0) {
|
|
279
|
+
statusLine += ` · ${theme.fg("dim", `${formatTokens(result.tokens)} tokens`)}`;
|
|
280
|
+
}
|
|
149
281
|
statusLine += ` · ${theme.fg("dim", formatDuration(result.durationMs))}`;
|
|
150
282
|
|
|
151
283
|
if (result.truncated) {
|
|
@@ -154,16 +286,69 @@ function renderAgentResult(result: SingleResult, isLast: boolean, expanded: bool
|
|
|
154
286
|
|
|
155
287
|
lines.push(statusLine);
|
|
156
288
|
|
|
157
|
-
//
|
|
158
|
-
const
|
|
159
|
-
const
|
|
289
|
+
// Check for review result (submit_review + report_finding)
|
|
290
|
+
const submitReviewData = result.extractedToolData?.submit_review as SubmitReviewDetails[] | undefined;
|
|
291
|
+
const reportFindingData = result.extractedToolData?.report_finding as ReportFindingDetails[] | undefined;
|
|
292
|
+
|
|
293
|
+
if (submitReviewData && submitReviewData.length > 0) {
|
|
294
|
+
// Use combined review renderer
|
|
295
|
+
const summary = submitReviewData[submitReviewData.length - 1];
|
|
296
|
+
const findings = reportFindingData ?? [];
|
|
297
|
+
lines.push(...renderReviewResult(summary, findings, continuePrefix, expanded, theme));
|
|
298
|
+
return lines;
|
|
299
|
+
}
|
|
300
|
+
if (reportFindingData && reportFindingData.length > 0) {
|
|
301
|
+
lines.push(
|
|
302
|
+
`${continuePrefix}${theme.fg("warning", "!")} ${theme.fg("dim", "Review summary missing (submit_review not called)")}`,
|
|
303
|
+
);
|
|
304
|
+
lines.push(`${continuePrefix}`); // Spacing
|
|
305
|
+
lines.push(...renderFindings(reportFindingData, continuePrefix, expanded, theme));
|
|
306
|
+
return lines;
|
|
307
|
+
}
|
|
160
308
|
|
|
161
|
-
for
|
|
162
|
-
|
|
309
|
+
// Check for extracted tool data with custom renderers (skip review tools)
|
|
310
|
+
let hasCustomRendering = false;
|
|
311
|
+
if (result.extractedToolData) {
|
|
312
|
+
for (const [toolName, dataArray] of Object.entries(result.extractedToolData)) {
|
|
313
|
+
// Skip review tools - handled above
|
|
314
|
+
if (toolName === "submit_review" || toolName === "report_finding") continue;
|
|
315
|
+
|
|
316
|
+
const handler = subprocessToolRegistry.getHandler(toolName);
|
|
317
|
+
if (handler?.renderFinal && (dataArray as unknown[]).length > 0) {
|
|
318
|
+
hasCustomRendering = true;
|
|
319
|
+
const component = handler.renderFinal(dataArray as unknown[], theme, expanded);
|
|
320
|
+
if (component instanceof Text) {
|
|
321
|
+
// Prefix each line with continuePrefix
|
|
322
|
+
const text = component.getText();
|
|
323
|
+
for (const line of text.split("\n")) {
|
|
324
|
+
if (line.trim()) {
|
|
325
|
+
lines.push(`${continuePrefix}${line}`);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
} else if (component instanceof Container) {
|
|
329
|
+
// For containers, render each child
|
|
330
|
+
for (const child of (component as Container).children) {
|
|
331
|
+
if (child instanceof Text) {
|
|
332
|
+
lines.push(`${continuePrefix}${child.getText()}`);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
163
338
|
}
|
|
164
339
|
|
|
165
|
-
if
|
|
166
|
-
|
|
340
|
+
// Fallback to output preview if no custom rendering
|
|
341
|
+
if (!hasCustomRendering) {
|
|
342
|
+
const outputLines = result.output.split("\n").filter((l) => l.trim());
|
|
343
|
+
const previewCount = expanded ? 8 : 3;
|
|
344
|
+
|
|
345
|
+
for (const line of outputLines.slice(0, previewCount)) {
|
|
346
|
+
lines.push(`${continuePrefix}${theme.fg("dim", truncate(line, 70))}`);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (outputLines.length > previewCount) {
|
|
350
|
+
lines.push(`${continuePrefix}${theme.fg("dim", `... ${outputLines.length - previewCount} more lines`)}`);
|
|
351
|
+
}
|
|
167
352
|
}
|
|
168
353
|
|
|
169
354
|
// Error message
|
|
@@ -207,21 +392,25 @@ export function renderResult(
|
|
|
207
392
|
});
|
|
208
393
|
|
|
209
394
|
// Summary line
|
|
210
|
-
const
|
|
211
|
-
const
|
|
395
|
+
const abortedCount = details.results.filter((r) => r.aborted).length;
|
|
396
|
+
const successCount = details.results.filter((r) => !r.aborted && r.exitCode === 0).length;
|
|
397
|
+
const failCount = details.results.length - successCount - abortedCount;
|
|
212
398
|
let summary = `\n${theme.fg("dim", "Total:")} `;
|
|
213
|
-
|
|
399
|
+
if (abortedCount > 0) {
|
|
400
|
+
summary += theme.fg("error", `${abortedCount} aborted`);
|
|
401
|
+
if (successCount > 0 || failCount > 0) summary += ", ";
|
|
402
|
+
}
|
|
403
|
+
if (successCount > 0) {
|
|
404
|
+
summary += theme.fg("success", `${successCount} succeeded`);
|
|
405
|
+
if (failCount > 0) summary += ", ";
|
|
406
|
+
}
|
|
214
407
|
if (failCount > 0) {
|
|
215
|
-
summary +=
|
|
408
|
+
summary += theme.fg("error", `${failCount} failed`);
|
|
216
409
|
}
|
|
217
410
|
summary += ` · ${theme.fg("dim", formatDuration(details.totalDurationMs))}`;
|
|
218
411
|
lines.push(summary);
|
|
219
412
|
|
|
220
|
-
// Artifacts
|
|
221
|
-
if (details.outputPaths && details.outputPaths.length > 0) {
|
|
222
|
-
const artifactsDir = path.dirname(details.outputPaths[0]);
|
|
223
|
-
lines.push(`${theme.fg("dim", "Artifacts:")} ${theme.fg("muted", artifactsDir)}`);
|
|
224
|
-
}
|
|
413
|
+
// Artifacts suppressed from user view - available via session file
|
|
225
414
|
}
|
|
226
415
|
|
|
227
416
|
if (lines.length === 0) {
|
|
@@ -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[]>;
|
|
@@ -27,9 +27,12 @@ export const MAX_OUTPUT_LINES = 5000;
|
|
|
27
27
|
/** Maximum agents to show in description */
|
|
28
28
|
export const MAX_AGENTS_IN_DESCRIPTION = 10;
|
|
29
29
|
|
|
30
|
-
/** Environment variable to inhibit subagent spawning */
|
|
30
|
+
/** Environment variable to inhibit subagent spawning (legacy, still checked for backwards compat) */
|
|
31
31
|
export const PI_NO_SUBAGENTS_ENV = "PI_NO_SUBAGENTS";
|
|
32
32
|
|
|
33
|
+
/** Environment variable containing blocked agent name (self-recursion prevention) */
|
|
34
|
+
export const PI_BLOCKED_AGENT_ENV = "PI_BLOCKED_AGENT";
|
|
35
|
+
|
|
33
36
|
/** Task tool parameters */
|
|
34
37
|
export const taskSchema = Type.Object({
|
|
35
38
|
context: Type.Optional(Type.String({ description: "Shared context prepended to all task prompts" })),
|
|
@@ -41,6 +44,30 @@ export const taskSchema = Type.Object({
|
|
|
41
44
|
|
|
42
45
|
export type TaskParams = Static<typeof taskSchema>;
|
|
43
46
|
|
|
47
|
+
/** A code review finding reported by the reviewer agent */
|
|
48
|
+
export interface ReviewFinding {
|
|
49
|
+
title: string;
|
|
50
|
+
body: string;
|
|
51
|
+
priority: number;
|
|
52
|
+
confidence: number;
|
|
53
|
+
file_path: string;
|
|
54
|
+
line_start: number;
|
|
55
|
+
line_end: number;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Review summary submitted by the reviewer agent */
|
|
59
|
+
export interface ReviewSummary {
|
|
60
|
+
overall_correctness: "correct" | "incorrect";
|
|
61
|
+
explanation: string;
|
|
62
|
+
confidence: number;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Structured review data extracted from reviewer agent */
|
|
66
|
+
export interface ReviewData {
|
|
67
|
+
findings: ReviewFinding[];
|
|
68
|
+
summary?: ReviewSummary;
|
|
69
|
+
}
|
|
70
|
+
|
|
44
71
|
/** Agent definition (bundled or discovered) */
|
|
45
72
|
export interface AgentDefinition {
|
|
46
73
|
name: string;
|
|
@@ -58,7 +85,7 @@ export interface AgentProgress {
|
|
|
58
85
|
index: number;
|
|
59
86
|
agent: string;
|
|
60
87
|
agentSource: AgentSource;
|
|
61
|
-
status: "pending" | "running" | "completed" | "failed";
|
|
88
|
+
status: "pending" | "running" | "completed" | "failed" | "aborted";
|
|
62
89
|
task: string;
|
|
63
90
|
currentTool?: string;
|
|
64
91
|
currentToolArgs?: string;
|
|
@@ -69,6 +96,8 @@ export interface AgentProgress {
|
|
|
69
96
|
tokens: number;
|
|
70
97
|
durationMs: number;
|
|
71
98
|
modelOverride?: string;
|
|
99
|
+
/** Data extracted by registered subprocess tool handlers (keyed by tool name) */
|
|
100
|
+
extractedToolData?: Record<string, unknown[]>;
|
|
72
101
|
}
|
|
73
102
|
|
|
74
103
|
/** Result from a single agent execution */
|
|
@@ -85,8 +114,13 @@ export interface SingleResult {
|
|
|
85
114
|
tokens: number;
|
|
86
115
|
modelOverride?: string;
|
|
87
116
|
error?: string;
|
|
117
|
+
aborted?: boolean;
|
|
88
118
|
jsonlEvents?: string[];
|
|
89
119
|
artifactPaths?: { inputPath: string; outputPath: string; jsonlPath?: string };
|
|
120
|
+
/** Data extracted by registered subprocess tool handlers (keyed by tool name) */
|
|
121
|
+
extractedToolData?: Record<string, unknown[]>;
|
|
122
|
+
/** Output metadata for Output tool integration */
|
|
123
|
+
outputMeta?: { lineCount: number; charCount: number };
|
|
90
124
|
}
|
|
91
125
|
|
|
92
126
|
/** Tool details for TUI rendering */
|
|
@@ -5,6 +5,7 @@ import * as path from "node:path";
|
|
|
5
5
|
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
6
6
|
import { Type } from "@sinclair/typebox";
|
|
7
7
|
import { parse as parseHtml } from "node-html-parser";
|
|
8
|
+
import { logger } from "../logger";
|
|
8
9
|
|
|
9
10
|
// =============================================================================
|
|
10
11
|
// Types and Constants
|
|
@@ -174,9 +175,10 @@ async function loadPage(url: string, options: LoadPageOptions = {}): Promise<Loa
|
|
|
174
175
|
}
|
|
175
176
|
|
|
176
177
|
return { content, contentType, finalUrl, ok: true, status: response.status };
|
|
177
|
-
} catch (
|
|
178
|
+
} catch (err) {
|
|
178
179
|
// On last attempt, return failure
|
|
179
180
|
if (attempt === USER_AGENTS.length - 1) {
|
|
181
|
+
logger.debug("Web fetch failed after retries", { url, error: String(err) });
|
|
180
182
|
return { content: "", contentType: "", finalUrl: url, ok: false };
|
|
181
183
|
}
|
|
182
184
|
// Otherwise retry with next UA
|
|
@@ -2203,8 +2205,8 @@ export const webFetchTool = createWebFetchTool(process.cwd());
|
|
|
2203
2205
|
|
|
2204
2206
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
2205
2207
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
2206
|
-
import type { Theme } from "../../modes/interactive/theme/theme
|
|
2207
|
-
import type { CustomTool, CustomToolContext, RenderResultOptions } from "../custom-tools/types
|
|
2208
|
+
import type { Theme } from "../../modes/interactive/theme/theme";
|
|
2209
|
+
import type { CustomTool, CustomToolContext, RenderResultOptions } from "../custom-tools/types";
|
|
2208
2210
|
|
|
2209
2211
|
// Tree formatting constants
|
|
2210
2212
|
const TREE_MID = "├─";
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import * as os from "node:os";
|
|
12
12
|
import * as path from "node:path";
|
|
13
|
-
import type { AnthropicAuthConfig, AuthJson, ModelsJson } from "./types
|
|
13
|
+
import type { AnthropicAuthConfig, AuthJson, ModelsJson } from "./types";
|
|
14
14
|
|
|
15
15
|
const DEFAULT_BASE_URL = "https://api.anthropic.com";
|
|
16
16
|
|
|
@@ -14,16 +14,16 @@
|
|
|
14
14
|
|
|
15
15
|
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
16
16
|
import { Type } from "@sinclair/typebox";
|
|
17
|
-
import type { Theme } from "../../../modes/interactive/theme/theme
|
|
18
|
-
import type { CustomTool, CustomToolContext, RenderResultOptions } from "../../custom-tools/types
|
|
19
|
-
import { callExaTool, findApiKey as findExaKey, formatSearchResults, isSearchResponse } from "../exa/mcp-client
|
|
20
|
-
import { renderExaCall, renderExaResult } from "../exa/render
|
|
21
|
-
import type { ExaRenderDetails } from "../exa/types
|
|
22
|
-
import { searchAnthropic } from "./providers/anthropic
|
|
23
|
-
import { searchExa } from "./providers/exa
|
|
24
|
-
import { findApiKey as findPerplexityKey, searchPerplexity } from "./providers/perplexity
|
|
25
|
-
import { formatAge, renderWebSearchCall, renderWebSearchResult, type WebSearchRenderDetails } from "./render
|
|
26
|
-
import type { WebSearchProvider, WebSearchResponse } from "./types
|
|
17
|
+
import type { Theme } from "../../../modes/interactive/theme/theme";
|
|
18
|
+
import type { CustomTool, CustomToolContext, RenderResultOptions } from "../../custom-tools/types";
|
|
19
|
+
import { callExaTool, findApiKey as findExaKey, formatSearchResults, isSearchResponse } from "../exa/mcp-client";
|
|
20
|
+
import { renderExaCall, renderExaResult } from "../exa/render";
|
|
21
|
+
import type { ExaRenderDetails } from "../exa/types";
|
|
22
|
+
import { searchAnthropic } from "./providers/anthropic";
|
|
23
|
+
import { searchExa } from "./providers/exa";
|
|
24
|
+
import { findApiKey as findPerplexityKey, searchPerplexity } from "./providers/perplexity";
|
|
25
|
+
import { formatAge, renderWebSearchCall, renderWebSearchResult, type WebSearchRenderDetails } from "./render";
|
|
26
|
+
import type { WebSearchProvider, WebSearchResponse } from "./types";
|
|
27
27
|
|
|
28
28
|
/** Web search parameters schema */
|
|
29
29
|
export const webSearchSchema = Type.Object({
|
|
@@ -345,7 +345,9 @@ Parameters:
|
|
|
345
345
|
parameters: webSearchDeepSchema,
|
|
346
346
|
|
|
347
347
|
async execute(_toolCallId, params, _onUpdate, _ctx, _signal) {
|
|
348
|
-
|
|
348
|
+
const { num_results, ...rest } = params as Record<string, unknown>;
|
|
349
|
+
const args = { ...rest, type: "deep", numResults: num_results ?? 10 };
|
|
350
|
+
return executeExaTool("web_search_exa", args, "web_search_deep");
|
|
349
351
|
},
|
|
350
352
|
|
|
351
353
|
renderCall(args, theme) {
|
|
@@ -404,7 +406,7 @@ Parameters:
|
|
|
404
406
|
parameters: webSearchCrawlSchema,
|
|
405
407
|
|
|
406
408
|
async execute(_toolCallId, params, _onUpdate, _ctx, _signal) {
|
|
407
|
-
return executeExaTool("
|
|
409
|
+
return executeExaTool("crawling", params as Record<string, unknown>, "web_search_crawl");
|
|
408
410
|
},
|
|
409
411
|
|
|
410
412
|
renderCall(args, theme) {
|
|
@@ -435,7 +437,7 @@ Parameters:
|
|
|
435
437
|
parameters: webSearchLinkedinSchema,
|
|
436
438
|
|
|
437
439
|
async execute(_toolCallId, params, _onUpdate, _ctx, _signal) {
|
|
438
|
-
return executeExaTool("
|
|
440
|
+
return executeExaTool("linkedin_search", params as Record<string, unknown>, "web_search_linkedin");
|
|
439
441
|
},
|
|
440
442
|
|
|
441
443
|
renderCall(args, theme) {
|
|
@@ -465,7 +467,7 @@ Parameters:
|
|
|
465
467
|
parameters: webSearchCompanySchema,
|
|
466
468
|
|
|
467
469
|
async execute(_toolCallId, params, _onUpdate, _ctx, _signal) {
|
|
468
|
-
return executeExaTool("
|
|
470
|
+
return executeExaTool("company_research", params as Record<string, unknown>, "web_search_company");
|
|
469
471
|
},
|
|
470
472
|
|
|
471
473
|
renderCall(args, theme) {
|
|
@@ -534,4 +536,4 @@ export async function hasExaWebSearch(): Promise<boolean> {
|
|
|
534
536
|
return exaKey !== null;
|
|
535
537
|
}
|
|
536
538
|
|
|
537
|
-
export type { WebSearchProvider, WebSearchResponse } from "./types
|
|
539
|
+
export type { WebSearchProvider, WebSearchResponse } from "./types";
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Returns synthesized answers with citations and source metadata.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { buildAnthropicHeaders, buildAnthropicUrl, findAnthropicAuth, getEnv } from "../auth
|
|
8
|
+
import { buildAnthropicHeaders, buildAnthropicUrl, findAnthropicAuth, getEnv } from "../auth";
|
|
9
9
|
import type {
|
|
10
10
|
AnthropicApiResponse,
|
|
11
11
|
AnthropicAuthConfig,
|
|
@@ -13,7 +13,7 @@ import type {
|
|
|
13
13
|
WebSearchCitation,
|
|
14
14
|
WebSearchResponse,
|
|
15
15
|
WebSearchSource,
|
|
16
|
-
} from "../types
|
|
16
|
+
} from "../types";
|
|
17
17
|
|
|
18
18
|
const DEFAULT_MODEL = "claude-sonnet-4-5-20250514";
|
|
19
19
|
const DEFAULT_MAX_TOKENS = 4096;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import * as os from "node:os";
|
|
9
|
-
import type { WebSearchResponse, WebSearchSource } from "../types
|
|
9
|
+
import type { WebSearchResponse, WebSearchSource } from "../types";
|
|
10
10
|
|
|
11
11
|
const EXA_MCP_URL = "https://mcp.exa.ai/mcp";
|
|
12
12
|
|
|
@@ -248,10 +248,8 @@ export async function searchExa(params: ExaSearchParams): Promise<WebSearchRespo
|
|
|
248
248
|
|
|
249
249
|
const args: Record<string, unknown> = {
|
|
250
250
|
query: params.query,
|
|
251
|
-
|
|
251
|
+
numResults: params.num_results ?? 10,
|
|
252
252
|
type: params.type ?? "auto",
|
|
253
|
-
text: true, // Include text for richer results
|
|
254
|
-
highlights: true,
|
|
255
253
|
};
|
|
256
254
|
|
|
257
255
|
if (params.include_domains?.length) {
|
|
@@ -267,7 +265,7 @@ export async function searchExa(params: ExaSearchParams): Promise<WebSearchRespo
|
|
|
267
265
|
args.end_published_date = params.end_published_date;
|
|
268
266
|
}
|
|
269
267
|
|
|
270
|
-
const response = await callExaMCP(apiKey, "
|
|
268
|
+
const response = await callExaMCP(apiKey, "web_search_exa", args);
|
|
271
269
|
|
|
272
270
|
if (response.error) {
|
|
273
271
|
throw new Error(`Exa MCP error: ${response.error.message}`);
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
8
8
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
9
|
-
import type { Theme } from "../../../modes/interactive/theme/theme
|
|
10
|
-
import type { RenderResultOptions } from "../../custom-tools/types
|
|
11
|
-
import type { WebSearchResponse } from "./types
|
|
9
|
+
import type { Theme } from "../../../modes/interactive/theme/theme";
|
|
10
|
+
import type { RenderResultOptions } from "../../custom-tools/types";
|
|
11
|
+
import type { WebSearchResponse } from "./types";
|
|
12
12
|
|
|
13
13
|
// Tree formatting constants
|
|
14
14
|
const TREE_MID = "├─";
|