@oh-my-pi/pi-coding-agent 3.30.0 → 3.32.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 +85 -0
- package/package.json +5 -5
- package/src/cli/args.ts +4 -0
- package/src/core/agent-session.ts +29 -2
- package/src/core/bash-executor.ts +2 -1
- package/src/core/custom-commands/bundled/review/index.ts +367 -14
- package/src/core/custom-commands/bundled/wt/index.ts +1 -1
- package/src/core/sdk.ts +10 -2
- package/src/core/session-manager.ts +158 -246
- package/src/core/session-storage.ts +379 -0
- package/src/core/settings-manager.ts +155 -4
- package/src/core/slash-commands.ts +39 -13
- package/src/core/system-prompt.ts +62 -64
- package/src/core/tools/ask.ts +5 -4
- package/src/core/tools/bash-interceptor.ts +26 -61
- package/src/core/tools/bash.ts +13 -8
- package/src/core/tools/edit-diff.ts +11 -4
- package/src/core/tools/edit.ts +7 -13
- package/src/core/tools/find.ts +111 -50
- package/src/core/tools/gemini-image.ts +128 -147
- package/src/core/tools/grep.ts +397 -415
- package/src/core/tools/index.test.ts +5 -1
- package/src/core/tools/index.ts +8 -4
- package/src/core/tools/ls.ts +12 -10
- package/src/core/tools/lsp/client.ts +84 -19
- package/src/core/tools/lsp/config.ts +205 -656
- package/src/core/tools/lsp/defaults.json +465 -0
- package/src/core/tools/lsp/index.ts +72 -35
- package/src/core/tools/lsp/rust-analyzer.ts +49 -10
- package/src/core/tools/lsp/types.ts +1 -0
- package/src/core/tools/lsp/utils.ts +1 -1
- package/src/core/tools/read.ts +150 -74
- package/src/core/tools/render-utils.ts +70 -10
- package/src/core/tools/review.ts +38 -126
- package/src/core/tools/task/artifacts.ts +5 -4
- package/src/core/tools/task/commands.ts +4 -0
- package/src/core/tools/task/executor.ts +94 -83
- package/src/core/tools/task/index.ts +130 -92
- package/src/core/tools/task/parallel.ts +30 -3
- package/src/core/tools/task/render.ts +85 -39
- package/src/core/tools/task/types.ts +15 -6
- package/src/core/tools/task/worker.ts +124 -89
- package/src/core/tools/web-fetch.ts +112 -377
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/artifacthub.ts +6 -1
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/arxiv.ts +8 -4
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/aur.ts +6 -2
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/biorxiv.ts +6 -1
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/bluesky.ts +10 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/brew.ts +6 -2
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/cheatsh.ts +6 -1
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/chocolatey.ts +6 -1
- package/src/core/tools/web-scrapers/choosealicense.ts +110 -0
- package/src/core/tools/web-scrapers/cisa-kev.ts +100 -0
- package/src/core/tools/web-scrapers/clojars.ts +180 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/coingecko.ts +6 -1
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/crates-io.ts +7 -2
- package/src/core/tools/web-scrapers/crossref.ts +149 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/devto.ts +8 -4
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/discogs.ts +6 -1
- package/src/core/tools/web-scrapers/discourse.ts +221 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/dockerhub.ts +7 -3
- package/src/core/tools/web-scrapers/fdroid.ts +158 -0
- package/src/core/tools/web-scrapers/firefox-addons.ts +214 -0
- package/src/core/tools/web-scrapers/flathub.ts +239 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/github-gist.ts +6 -2
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/github.ts +63 -32
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/gitlab.ts +31 -19
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/go-pkg.ts +8 -4
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/hackage.ts +6 -1
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/hackernews.ts +18 -18
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/hex.ts +3 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/huggingface.ts +10 -10
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/iacr.ts +8 -4
- package/src/core/tools/web-scrapers/index.ts +250 -0
- package/src/core/tools/web-scrapers/jetbrains-marketplace.ts +169 -0
- package/src/core/tools/web-scrapers/lemmy.ts +220 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/lobsters.ts +3 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/mastodon.ts +11 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/maven.ts +6 -1
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/mdn.ts +2 -2
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/metacpan.ts +13 -7
- package/src/core/tools/web-scrapers/musicbrainz.ts +273 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/npm.ts +12 -5
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/nuget.ts +9 -5
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/nvd.ts +6 -1
- package/src/core/tools/web-scrapers/ollama.ts +267 -0
- package/src/core/tools/web-scrapers/open-vsx.ts +119 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/opencorporates.ts +2 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/openlibrary.ts +18 -12
- package/src/core/tools/web-scrapers/orcid.ts +299 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/osv.ts +6 -1
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/packagist.ts +6 -2
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/pub-dev.ts +3 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/pubmed.ts +8 -4
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/pypi.ts +7 -3
- package/src/core/tools/web-scrapers/rawg.ts +124 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/readthedocs.ts +7 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/reddit.ts +6 -2
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/repology.ts +6 -1
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/rfc.ts +7 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/rubygems.ts +6 -1
- package/src/core/tools/web-scrapers/searchcode.ts +217 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/sec-edgar.ts +6 -1
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/semantic-scholar.ts +2 -2
- package/src/core/tools/web-scrapers/snapcraft.ts +200 -0
- package/src/core/tools/web-scrapers/sourcegraph.ts +373 -0
- package/src/core/tools/web-scrapers/spdx.ts +121 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/spotify.ts +3 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/stackoverflow.ts +3 -2
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/terraform.ts +11 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/tldr.ts +6 -2
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/twitter.ts +15 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/types.ts +98 -27
- package/src/core/tools/web-scrapers/utils.ts +162 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/vimeo.ts +3 -3
- package/src/core/tools/web-scrapers/vscode-marketplace.ts +195 -0
- package/src/core/tools/web-scrapers/w3c.ts +163 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/wikidata.ts +13 -5
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/wikipedia.ts +7 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/youtube.ts +72 -20
- package/src/core/tools/write.ts +21 -18
- package/src/core/voice.ts +3 -2
- package/src/lib/worktree/collapse.ts +2 -1
- package/src/lib/worktree/git.ts +2 -18
- package/src/main.ts +59 -3
- package/src/modes/interactive/components/extensions/extension-dashboard.ts +33 -19
- package/src/modes/interactive/components/extensions/extension-list.ts +15 -8
- package/src/modes/interactive/components/hook-editor.ts +2 -1
- package/src/modes/interactive/components/model-selector.ts +19 -4
- package/src/modes/interactive/interactive-mode.ts +41 -63
- package/src/modes/interactive/theme/theme.ts +58 -58
- package/src/modes/rpc/rpc-mode.ts +10 -9
- package/src/prompts/review-request.md +27 -0
- package/src/prompts/reviewer.md +64 -68
- package/src/prompts/tools/output.md +22 -3
- package/src/prompts/tools/task.md +32 -33
- package/src/utils/clipboard.ts +2 -1
- package/examples/extensions/subagent/agents/reviewer.md +0 -35
- package/src/core/tools/web-fetch-handlers/index.ts +0 -69
- package/src/core/tools/web-fetch-handlers/utils.ts +0 -91
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/academic.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/business.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/dev-platforms.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/documentation.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/finance-media.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/git-hosting.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/media.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/package-managers-2.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/package-managers.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/package-registries.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/research.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/security.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/social-extended.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/social.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/stackexchange.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/standards.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/wikipedia.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/youtube.test.ts +0 -0
package/src/core/tools/review.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Review tools - report_finding
|
|
2
|
+
* Review tools - report_finding for structured code review.
|
|
3
3
|
*
|
|
4
4
|
* Used by the reviewer agent to report findings in a structured way.
|
|
5
|
-
*
|
|
5
|
+
* Hidden by default - only enabled when explicitly listed in agent's tools.
|
|
6
|
+
* Reviewers finish via `complete` tool with SubmitReviewDetails schema.
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
9
|
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
9
10
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
10
|
-
import { Container,
|
|
11
|
+
import { Container, Text } from "@oh-my-pi/pi-tui";
|
|
11
12
|
import { Type } from "@sinclair/typebox";
|
|
12
|
-
import type { Theme } from "../../modes/interactive/theme/theme";
|
|
13
|
-
import { theme } from "../../modes/interactive/theme/theme";
|
|
13
|
+
import type { Theme, ThemeColor } from "../../modes/interactive/theme/theme";
|
|
14
14
|
|
|
15
15
|
const PRIORITY_LABELS: Record<number, string> = {
|
|
16
16
|
0: "P0",
|
|
@@ -19,6 +19,24 @@ const PRIORITY_LABELS: Record<number, string> = {
|
|
|
19
19
|
3: "P3",
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
+
const PRIORITY_META: Record<number, { symbol: "status.error" | "status.warning" | "status.info"; color: ThemeColor }> =
|
|
23
|
+
{
|
|
24
|
+
0: { symbol: "status.error", color: "error" },
|
|
25
|
+
1: { symbol: "status.warning", color: "warning" },
|
|
26
|
+
2: { symbol: "status.warning", color: "muted" },
|
|
27
|
+
3: { symbol: "status.info", color: "accent" },
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function getPriorityDisplay(priority: number, theme: Theme): { label: string; icon: string; color: ThemeColor } {
|
|
31
|
+
const label = PRIORITY_LABELS[priority] ?? "P?";
|
|
32
|
+
const meta = PRIORITY_META[priority] ?? { symbol: "status.info", color: "muted" as const };
|
|
33
|
+
return {
|
|
34
|
+
label,
|
|
35
|
+
icon: theme.styledSymbol(meta.symbol, meta.color),
|
|
36
|
+
color: meta.color,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
22
40
|
// report_finding schema
|
|
23
41
|
const ReportFindingParams = Type.Object({
|
|
24
42
|
title: Type.String({
|
|
@@ -53,7 +71,7 @@ interface ReportFindingDetails {
|
|
|
53
71
|
export const reportFindingTool: AgentTool<typeof ReportFindingParams, ReportFindingDetails, Theme> = {
|
|
54
72
|
name: "report_finding",
|
|
55
73
|
label: "Report Finding",
|
|
56
|
-
description: "Report a code review finding. Use this for each issue found. Call
|
|
74
|
+
description: "Report a code review finding. Use this for each issue found. Call complete when done.",
|
|
57
75
|
parameters: ReportFindingParams,
|
|
58
76
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
59
77
|
const { title, body, priority, confidence, file_path, line_start, line_end } = params;
|
|
@@ -73,11 +91,10 @@ export const reportFindingTool: AgentTool<typeof ReportFindingParams, ReportFind
|
|
|
73
91
|
},
|
|
74
92
|
|
|
75
93
|
renderCall(args, theme): Component {
|
|
76
|
-
const
|
|
77
|
-
const color = args.priority === 0 ? "error" : args.priority === 1 ? "warning" : "muted";
|
|
94
|
+
const { label, icon, color } = getPriorityDisplay(args.priority as number, theme);
|
|
78
95
|
const titleText = String(args.title).replace(/^\[P\d\]\s*/, "");
|
|
79
96
|
return new Text(
|
|
80
|
-
`${theme.fg("toolTitle", theme.bold("report_finding "))}${theme.fg(color, `[${
|
|
97
|
+
`${theme.fg("toolTitle", theme.bold("report_finding "))}${icon} ${theme.fg(color, `[${label}]`)} ${theme.fg(
|
|
81
98
|
"dim",
|
|
82
99
|
titleText,
|
|
83
100
|
)}`,
|
|
@@ -93,111 +110,31 @@ export const reportFindingTool: AgentTool<typeof ReportFindingParams, ReportFind
|
|
|
93
110
|
return new Text(text?.type === "text" ? text.text : "", 0, 0);
|
|
94
111
|
}
|
|
95
112
|
|
|
96
|
-
const
|
|
97
|
-
const color = details.priority === 0 ? "error" : details.priority === 1 ? "warning" : "muted";
|
|
113
|
+
const { label, icon, color } = getPriorityDisplay(details.priority, theme);
|
|
98
114
|
const location = `${details.file_path}:${details.line_start}${
|
|
99
115
|
details.line_end !== details.line_start ? `-${details.line_end}` : ""
|
|
100
116
|
}`;
|
|
101
117
|
|
|
102
118
|
return new Text(
|
|
103
|
-
`${theme.fg("success", theme.status.success)} ${theme.fg(color, `[${
|
|
119
|
+
`${theme.fg("success", theme.status.success)} ${icon} ${theme.fg(color, `[${label}]`)} ${theme.fg(
|
|
120
|
+
"dim",
|
|
121
|
+
location,
|
|
122
|
+
)}`,
|
|
104
123
|
0,
|
|
105
124
|
0,
|
|
106
125
|
);
|
|
107
126
|
},
|
|
108
127
|
};
|
|
109
128
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
overall_correctness: Type.Union([Type.Literal("correct"), Type.Literal("incorrect")], {
|
|
113
|
-
description: "Whether the patch is correct (no bugs, tests won't break)",
|
|
114
|
-
}),
|
|
115
|
-
explanation: Type.String({
|
|
116
|
-
description: "1-3 sentence explanation justifying the verdict",
|
|
117
|
-
}),
|
|
118
|
-
confidence: Type.Number({
|
|
119
|
-
minimum: 0,
|
|
120
|
-
maximum: 1,
|
|
121
|
-
description: "Overall confidence score 0.0-1.0",
|
|
122
|
-
}),
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
interface SubmitReviewDetails {
|
|
129
|
+
/** SubmitReviewDetails - used for rendering review results from complete tool */
|
|
130
|
+
export interface SubmitReviewDetails {
|
|
126
131
|
overall_correctness: "correct" | "incorrect";
|
|
127
132
|
explanation: string;
|
|
128
133
|
confidence: number;
|
|
129
134
|
}
|
|
130
135
|
|
|
131
|
-
export const submitReviewTool: AgentTool<typeof SubmitReviewParams, SubmitReviewDetails, Theme> = {
|
|
132
|
-
name: "submit_review",
|
|
133
|
-
label: "Submit Review",
|
|
134
|
-
description: "Submit the final review verdict. Call this after all findings have been reported.",
|
|
135
|
-
parameters: SubmitReviewParams,
|
|
136
|
-
|
|
137
|
-
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
138
|
-
const { overall_correctness, explanation, confidence } = params;
|
|
139
|
-
|
|
140
|
-
let summary = `## Review Summary\n\n`;
|
|
141
|
-
summary += `**Verdict:** ${
|
|
142
|
-
overall_correctness === "correct"
|
|
143
|
-
? `${theme.status.success} Patch is correct`
|
|
144
|
-
: `${theme.status.error} Patch is incorrect`
|
|
145
|
-
}\n`;
|
|
146
|
-
summary += `**Confidence:** ${(confidence * 100).toFixed(0)}%\n\n`;
|
|
147
|
-
summary += explanation;
|
|
148
|
-
|
|
149
|
-
return {
|
|
150
|
-
content: [{ type: "text", text: summary }],
|
|
151
|
-
details: { overall_correctness, explanation, confidence },
|
|
152
|
-
};
|
|
153
|
-
},
|
|
154
|
-
|
|
155
|
-
renderCall(args, theme): Component {
|
|
156
|
-
const verdict = args.overall_correctness === "correct" ? "correct" : "incorrect";
|
|
157
|
-
const color = args.overall_correctness === "correct" ? "success" : "error";
|
|
158
|
-
return new Text(
|
|
159
|
-
`${theme.fg("toolTitle", theme.bold("submit_review "))}${theme.fg(color, verdict)} ${theme.fg(
|
|
160
|
-
"dim",
|
|
161
|
-
`(${((args.confidence as number) * 100).toFixed(0)}%)`,
|
|
162
|
-
)}`,
|
|
163
|
-
0,
|
|
164
|
-
0,
|
|
165
|
-
);
|
|
166
|
-
},
|
|
167
|
-
|
|
168
|
-
renderResult(result, { expanded }, theme): Component {
|
|
169
|
-
const { details } = result;
|
|
170
|
-
if (!details) {
|
|
171
|
-
const text = result.content[0];
|
|
172
|
-
return new Text(text?.type === "text" ? text.text : "", 0, 0);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const container = new Container();
|
|
176
|
-
const verdictColor = details.overall_correctness === "correct" ? "success" : "error";
|
|
177
|
-
const verdictIcon = details.overall_correctness === "correct" ? theme.status.success : theme.status.error;
|
|
178
|
-
|
|
179
|
-
container.addChild(
|
|
180
|
-
new Text(
|
|
181
|
-
`${theme.fg(verdictColor, verdictIcon)} Patch is ${theme.fg(
|
|
182
|
-
verdictColor,
|
|
183
|
-
details.overall_correctness,
|
|
184
|
-
)} ${theme.fg("dim", `(${(details.confidence * 100).toFixed(0)}% confidence)`)}`,
|
|
185
|
-
0,
|
|
186
|
-
0,
|
|
187
|
-
),
|
|
188
|
-
);
|
|
189
|
-
|
|
190
|
-
if (expanded) {
|
|
191
|
-
container.addChild(new Spacer(1));
|
|
192
|
-
container.addChild(new Text(theme.fg("dim", details.explanation), 0, 0));
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
return container;
|
|
196
|
-
},
|
|
197
|
-
};
|
|
198
|
-
|
|
199
136
|
// Re-export types for external use
|
|
200
|
-
export type { ReportFindingDetails
|
|
137
|
+
export type { ReportFindingDetails };
|
|
201
138
|
|
|
202
139
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
203
140
|
// Subprocess tool handlers - registered for extraction/rendering in task tool
|
|
@@ -211,11 +148,10 @@ subprocessToolRegistry.register<ReportFindingDetails>("report_finding", {
|
|
|
211
148
|
extractData: (event) => event.result?.details as ReportFindingDetails | undefined,
|
|
212
149
|
|
|
213
150
|
renderInline: (data, theme) => {
|
|
214
|
-
const
|
|
215
|
-
const color = data.priority === 0 ? "error" : data.priority === 1 ? "warning" : "muted";
|
|
151
|
+
const { label, icon, color } = getPriorityDisplay(data.priority, theme);
|
|
216
152
|
const titleText = data.title.replace(/^\[P\d\]\s*/, "");
|
|
217
153
|
const loc = `${path.basename(data.file_path)}:${data.line_start}`;
|
|
218
|
-
return new Text(`${theme.fg(color, `[${
|
|
154
|
+
return new Text(`${icon} ${theme.fg(color, `[${label}]`)} ${titleText} ${theme.fg("dim", loc)}`, 0, 0);
|
|
219
155
|
},
|
|
220
156
|
|
|
221
157
|
renderFinal: (allData, theme, expanded) => {
|
|
@@ -224,13 +160,12 @@ subprocessToolRegistry.register<ReportFindingDetails>("report_finding", {
|
|
|
224
160
|
|
|
225
161
|
for (let i = 0; i < displayCount; i++) {
|
|
226
162
|
const data = allData[i];
|
|
227
|
-
const
|
|
228
|
-
const color = data.priority === 0 ? "error" : data.priority === 1 ? "warning" : "muted";
|
|
163
|
+
const { label, icon, color } = getPriorityDisplay(data.priority, theme);
|
|
229
164
|
const titleText = data.title.replace(/^\[P\d\]\s*/, "");
|
|
230
165
|
const loc = `${path.basename(data.file_path)}:${data.line_start}`;
|
|
231
166
|
|
|
232
167
|
container.addChild(
|
|
233
|
-
new Text(` ${theme.fg(color, `[${
|
|
168
|
+
new Text(` ${icon} ${theme.fg(color, `[${label}]`)} ${titleText} ${theme.fg("dim", loc)}`, 0, 0),
|
|
234
169
|
);
|
|
235
170
|
|
|
236
171
|
if (expanded && data.body) {
|
|
@@ -251,26 +186,3 @@ subprocessToolRegistry.register<ReportFindingDetails>("report_finding", {
|
|
|
251
186
|
return container;
|
|
252
187
|
},
|
|
253
188
|
});
|
|
254
|
-
|
|
255
|
-
// Register submit_review handler
|
|
256
|
-
subprocessToolRegistry.register<SubmitReviewDetails>("submit_review", {
|
|
257
|
-
extractData: (event) => event.result?.details as SubmitReviewDetails | undefined,
|
|
258
|
-
|
|
259
|
-
// Terminate subprocess after review is submitted
|
|
260
|
-
shouldTerminate: () => true,
|
|
261
|
-
|
|
262
|
-
renderInline: (data, theme) => {
|
|
263
|
-
const verdictColor = data.overall_correctness === "correct" ? "success" : "error";
|
|
264
|
-
const verdictIcon = data.overall_correctness === "correct" ? theme.status.success : theme.status.error;
|
|
265
|
-
return new Text(
|
|
266
|
-
`${theme.fg(verdictColor, verdictIcon)} Review: ${theme.fg(verdictColor, data.overall_correctness)} (${(
|
|
267
|
-
data.confidence * 100
|
|
268
|
-
).toFixed(0)}%)`,
|
|
269
|
-
0,
|
|
270
|
-
0,
|
|
271
|
-
);
|
|
272
|
-
},
|
|
273
|
-
|
|
274
|
-
// Note: renderFinal is NOT used for submit_review - we use the combined
|
|
275
|
-
// renderReviewResult in render.ts to show verdict + findings together
|
|
276
|
-
});
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import * as fs from "node:fs";
|
|
9
9
|
import * as os from "node:os";
|
|
10
10
|
import * as path from "node:path";
|
|
11
|
+
import { nanoid } from "nanoid";
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Derive artifacts directory from session file path.
|
|
@@ -62,14 +63,14 @@ export async function writeArtifacts(
|
|
|
62
63
|
const paths = getArtifactPaths(dir, taskId);
|
|
63
64
|
|
|
64
65
|
// Write input
|
|
65
|
-
await
|
|
66
|
+
await Bun.write(paths.inputPath, input);
|
|
66
67
|
|
|
67
68
|
// Write output
|
|
68
|
-
await
|
|
69
|
+
await Bun.write(paths.outputPath, output);
|
|
69
70
|
|
|
70
71
|
// Write JSONL if events provided
|
|
71
72
|
if (jsonlEvents && jsonlEvents.length > 0) {
|
|
72
|
-
await
|
|
73
|
+
await Bun.write(paths.jsonlPath, jsonlEvents.join("\n"));
|
|
73
74
|
return paths;
|
|
74
75
|
}
|
|
75
76
|
|
|
@@ -80,7 +81,7 @@ export async function writeArtifacts(
|
|
|
80
81
|
* Create a temporary artifacts directory.
|
|
81
82
|
*/
|
|
82
83
|
export function createTempArtifactsDir(runId?: string): string {
|
|
83
|
-
const id = runId ||
|
|
84
|
+
const id = runId || nanoid();
|
|
84
85
|
const dir = path.join(os.tmpdir(), `omp-task-${id}`);
|
|
85
86
|
ensureArtifactsDir(dir);
|
|
86
87
|
return dir;
|
|
@@ -12,13 +12,17 @@ import { loadSync } from "../../../discovery";
|
|
|
12
12
|
import architectPlanMd from "../../../prompts/architect-plan.md" with { type: "text" };
|
|
13
13
|
import implementMd from "../../../prompts/implement.md" with { type: "text" };
|
|
14
14
|
import implementWithCriticMd from "../../../prompts/implement-with-critic.md" with { type: "text" };
|
|
15
|
+
import initMd from "../../../prompts/init.md" with { type: "text" };
|
|
15
16
|
|
|
16
17
|
const EMBEDDED_COMMANDS: { name: string; content: string }[] = [
|
|
17
18
|
{ name: "architect-plan.md", content: architectPlanMd },
|
|
18
19
|
{ name: "implement-with-critic.md", content: implementWithCriticMd },
|
|
19
20
|
{ name: "implement.md", content: implementMd },
|
|
21
|
+
{ name: "init.md", content: initMd },
|
|
20
22
|
];
|
|
21
23
|
|
|
24
|
+
export const EMBEDDED_COMMAND_TEMPLATES: ReadonlyArray<{ name: string; content: string }> = EMBEDDED_COMMANDS;
|
|
25
|
+
|
|
22
26
|
/** Workflow command definition */
|
|
23
27
|
export interface WorkflowCommand {
|
|
24
28
|
name: string;
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
* Runs each subagent in a Bun Worker and forwards AgentEvents for progress tracking.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { writeFileSync } from "node:fs";
|
|
8
7
|
import type { AgentEvent } from "@oh-my-pi/pi-agent-core";
|
|
9
8
|
import type { EventBus } from "../../event-bus";
|
|
10
9
|
import { ensureArtifactsDir, getArtifactPaths } from "./artifacts";
|
|
@@ -50,20 +49,26 @@ function truncateOutput(output: string): { text: string; truncated: boolean } {
|
|
|
50
49
|
|
|
51
50
|
let i = 0;
|
|
52
51
|
let lastNewlineIndex = -1;
|
|
53
|
-
while (i < output.length
|
|
54
|
-
const
|
|
55
|
-
|
|
52
|
+
while (i < output.length) {
|
|
53
|
+
const codePoint = output.codePointAt(i);
|
|
54
|
+
if (codePoint === undefined) break;
|
|
55
|
+
const codeUnitLength = codePoint > 0xffff ? 2 : 1;
|
|
56
|
+
const byteLen = codePoint <= 0x7f ? 1 : codePoint <= 0x7ff ? 2 : codePoint <= 0xffff ? 3 : 4;
|
|
57
|
+
if (byteBudget - byteLen < 0) {
|
|
58
|
+
truncated = true;
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
byteBudget -= byteLen;
|
|
62
|
+
i += codeUnitLength;
|
|
56
63
|
|
|
57
|
-
if (
|
|
64
|
+
if (codePoint === 0x0a) {
|
|
58
65
|
lineBudget--;
|
|
59
|
-
lastNewlineIndex = i;
|
|
66
|
+
lastNewlineIndex = i - 1;
|
|
60
67
|
if (lineBudget <= 0) {
|
|
61
68
|
truncated = true;
|
|
62
69
|
break;
|
|
63
70
|
}
|
|
64
71
|
}
|
|
65
|
-
|
|
66
|
-
i++;
|
|
67
72
|
}
|
|
68
73
|
|
|
69
74
|
if (i < output.length) {
|
|
@@ -186,7 +191,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
186
191
|
|
|
187
192
|
// Write input file immediately (real-time visibility)
|
|
188
193
|
try {
|
|
189
|
-
|
|
194
|
+
await Bun.write(artifactPaths.inputPath, fullTask);
|
|
190
195
|
} catch {
|
|
191
196
|
// Non-fatal, continue without input artifact
|
|
192
197
|
}
|
|
@@ -229,17 +234,18 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
229
234
|
};
|
|
230
235
|
}
|
|
231
236
|
|
|
232
|
-
|
|
237
|
+
const outputChunks: string[] = [];
|
|
238
|
+
const finalOutputChunks: string[] = [];
|
|
233
239
|
let stderr = "";
|
|
234
|
-
let finalOutput = "";
|
|
235
240
|
let resolved = false;
|
|
236
|
-
let pendingTermination = false; // Set when shouldTerminate fires, wait for message_end
|
|
237
241
|
type AbortReason = "signal" | "terminate";
|
|
238
242
|
let abortSent = false;
|
|
239
243
|
let abortReason: AbortReason | undefined;
|
|
240
|
-
let
|
|
241
|
-
let
|
|
244
|
+
let terminationScheduled = false;
|
|
245
|
+
let pendingTerminationController: AbortController | null = null;
|
|
242
246
|
let finalize: ((message: Extract<SubagentWorkerResponse, { type: "done" }>) => void) | null = null;
|
|
247
|
+
const listenerController = new AbortController();
|
|
248
|
+
const listenerSignal = listenerController.signal;
|
|
243
249
|
|
|
244
250
|
// Accumulate usage incrementally from message_end events (no memory for streaming events)
|
|
245
251
|
const accumulatedUsage = {
|
|
@@ -252,11 +258,31 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
252
258
|
};
|
|
253
259
|
let hasUsage = false;
|
|
254
260
|
|
|
255
|
-
const
|
|
256
|
-
if (
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
261
|
+
const scheduleTermination = () => {
|
|
262
|
+
if (terminationScheduled) return;
|
|
263
|
+
terminationScheduled = true;
|
|
264
|
+
const timeoutSignal = AbortSignal.timeout(2000);
|
|
265
|
+
timeoutSignal.addEventListener(
|
|
266
|
+
"abort",
|
|
267
|
+
() => {
|
|
268
|
+
if (resolved) return;
|
|
269
|
+
try {
|
|
270
|
+
worker.terminate();
|
|
271
|
+
} catch {
|
|
272
|
+
// Ignore termination errors
|
|
273
|
+
}
|
|
274
|
+
if (finalize && !resolved) {
|
|
275
|
+
finalize({
|
|
276
|
+
type: "done",
|
|
277
|
+
exitCode: 1,
|
|
278
|
+
durationMs: Date.now() - startTime,
|
|
279
|
+
error: abortReason === "signal" ? "Aborted" : "Worker terminated after tool completion",
|
|
280
|
+
aborted: abortReason === "signal",
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
},
|
|
284
|
+
{ once: true, signal: listenerSignal },
|
|
285
|
+
);
|
|
260
286
|
};
|
|
261
287
|
|
|
262
288
|
const requestAbort = (reason: AbortReason) => {
|
|
@@ -269,33 +295,35 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
269
295
|
if (resolved) return;
|
|
270
296
|
abortSent = true;
|
|
271
297
|
abortReason = reason;
|
|
272
|
-
if (pendingTerminationTimer) clearTimeout(pendingTerminationTimer);
|
|
273
|
-
pendingTerminationTimer = undefined;
|
|
274
298
|
const abortMessage: SubagentWorkerRequest = { type: "abort" };
|
|
275
299
|
try {
|
|
276
300
|
worker.postMessage(abortMessage);
|
|
277
301
|
} catch {
|
|
278
302
|
// Worker already terminated, nothing to do
|
|
279
303
|
}
|
|
280
|
-
if
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
304
|
+
// Cancel pending termination if it exists
|
|
305
|
+
if (pendingTerminationController) {
|
|
306
|
+
pendingTerminationController.abort();
|
|
307
|
+
pendingTerminationController = null;
|
|
308
|
+
}
|
|
309
|
+
scheduleTermination();
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
const schedulePendingTermination = () => {
|
|
313
|
+
if (pendingTerminationController || abortSent || terminationScheduled || resolved) return;
|
|
314
|
+
const readyController = new AbortController();
|
|
315
|
+
pendingTerminationController = readyController;
|
|
316
|
+
const pendingSignal = AbortSignal.any([AbortSignal.timeout(2000), readyController.signal]);
|
|
317
|
+
pendingSignal.addEventListener(
|
|
318
|
+
"abort",
|
|
319
|
+
() => {
|
|
320
|
+
pendingTerminationController = null;
|
|
321
|
+
if (!resolved) {
|
|
322
|
+
requestAbort("terminate");
|
|
296
323
|
}
|
|
297
|
-
}
|
|
298
|
-
|
|
324
|
+
},
|
|
325
|
+
{ once: true, signal: listenerSignal },
|
|
326
|
+
);
|
|
299
327
|
};
|
|
300
328
|
|
|
301
329
|
// Handle abort signal
|
|
@@ -303,7 +331,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
303
331
|
if (!resolved) requestAbort("signal");
|
|
304
332
|
};
|
|
305
333
|
if (signal) {
|
|
306
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
334
|
+
signal.addEventListener("abort", onAbort, { once: true, signal: listenerSignal });
|
|
307
335
|
}
|
|
308
336
|
|
|
309
337
|
const emitProgress = () => {
|
|
@@ -406,14 +434,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
406
434
|
})
|
|
407
435
|
) {
|
|
408
436
|
// Don't terminate immediately - wait for message_end to get token counts
|
|
409
|
-
|
|
410
|
-
// Safety timeout in case message_end never arrives
|
|
411
|
-
if (pendingTerminationTimer) clearTimeout(pendingTerminationTimer);
|
|
412
|
-
pendingTerminationTimer = setTimeout(() => {
|
|
413
|
-
if (!resolved) {
|
|
414
|
-
requestAbort("terminate");
|
|
415
|
-
}
|
|
416
|
-
}, 2000);
|
|
437
|
+
schedulePendingTermination();
|
|
417
438
|
}
|
|
418
439
|
}
|
|
419
440
|
break;
|
|
@@ -446,7 +467,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
446
467
|
if (messageContent && Array.isArray(messageContent)) {
|
|
447
468
|
for (const block of messageContent) {
|
|
448
469
|
if (block.type === "text" && block.text) {
|
|
449
|
-
|
|
470
|
+
outputChunks.push(block.text);
|
|
450
471
|
}
|
|
451
472
|
}
|
|
452
473
|
}
|
|
@@ -455,33 +476,29 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
455
476
|
const messageUsage = getMessageUsage(event.message) || (event as AgentEvent & { usage?: unknown }).usage;
|
|
456
477
|
if (messageUsage && typeof messageUsage === "object") {
|
|
457
478
|
// Only count assistant messages (not tool results, etc.)
|
|
458
|
-
if (
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
event.message?.stopReason !== "error"
|
|
462
|
-
) {
|
|
463
|
-
const usageRecord = messageUsage as Record<string, number | undefined>;
|
|
464
|
-
const costRecord = (messageUsage as { cost?: Record<string, number | undefined> }).cost;
|
|
479
|
+
if (role === "assistant") {
|
|
480
|
+
const usageRecord = messageUsage as Record<string, unknown>;
|
|
481
|
+
const costRecord = (messageUsage as { cost?: Record<string, unknown> }).cost;
|
|
465
482
|
hasUsage = true;
|
|
466
|
-
accumulatedUsage.input += usageRecord
|
|
467
|
-
accumulatedUsage.output += usageRecord
|
|
468
|
-
accumulatedUsage.cacheRead += usageRecord
|
|
469
|
-
accumulatedUsage.cacheWrite += usageRecord
|
|
470
|
-
accumulatedUsage.totalTokens += usageRecord
|
|
483
|
+
accumulatedUsage.input += getNumberField(usageRecord, "input") ?? 0;
|
|
484
|
+
accumulatedUsage.output += getNumberField(usageRecord, "output") ?? 0;
|
|
485
|
+
accumulatedUsage.cacheRead += getNumberField(usageRecord, "cacheRead") ?? 0;
|
|
486
|
+
accumulatedUsage.cacheWrite += getNumberField(usageRecord, "cacheWrite") ?? 0;
|
|
487
|
+
accumulatedUsage.totalTokens += getNumberField(usageRecord, "totalTokens") ?? 0;
|
|
471
488
|
if (costRecord) {
|
|
472
|
-
accumulatedUsage.cost.input += costRecord
|
|
473
|
-
accumulatedUsage.cost.output += costRecord
|
|
474
|
-
accumulatedUsage.cost.cacheRead += costRecord
|
|
475
|
-
accumulatedUsage.cost.cacheWrite += costRecord
|
|
476
|
-
accumulatedUsage.cost.total += costRecord
|
|
489
|
+
accumulatedUsage.cost.input += getNumberField(costRecord, "input") ?? 0;
|
|
490
|
+
accumulatedUsage.cost.output += getNumberField(costRecord, "output") ?? 0;
|
|
491
|
+
accumulatedUsage.cost.cacheRead += getNumberField(costRecord, "cacheRead") ?? 0;
|
|
492
|
+
accumulatedUsage.cost.cacheWrite += getNumberField(costRecord, "cacheWrite") ?? 0;
|
|
493
|
+
accumulatedUsage.cost.total += getNumberField(costRecord, "total") ?? 0;
|
|
477
494
|
}
|
|
478
495
|
}
|
|
479
496
|
// Accumulate tokens for progress display
|
|
480
497
|
progress.tokens += getUsageTokens(messageUsage);
|
|
481
498
|
}
|
|
482
499
|
// If pending termination, now we have tokens - terminate
|
|
483
|
-
if (
|
|
484
|
-
|
|
500
|
+
if (pendingTerminationController) {
|
|
501
|
+
pendingTerminationController.abort();
|
|
485
502
|
}
|
|
486
503
|
break;
|
|
487
504
|
}
|
|
@@ -495,7 +512,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
495
512
|
if (messageContent && Array.isArray(messageContent)) {
|
|
496
513
|
for (const block of messageContent) {
|
|
497
514
|
if (block.type === "text" && block.text) {
|
|
498
|
-
|
|
515
|
+
finalOutputChunks.push(block.text);
|
|
499
516
|
}
|
|
500
517
|
}
|
|
501
518
|
}
|
|
@@ -530,11 +547,8 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
530
547
|
|
|
531
548
|
const done = await new Promise<Extract<SubagentWorkerResponse, { type: "done" }>>((resolve) => {
|
|
532
549
|
const cleanup = () => {
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
worker.removeEventListener("close", onClose);
|
|
536
|
-
worker.removeEventListener("messageerror", onMessageError);
|
|
537
|
-
clearTimers();
|
|
550
|
+
pendingTerminationController = null;
|
|
551
|
+
listenerController.abort();
|
|
538
552
|
};
|
|
539
553
|
finalize = (message) => {
|
|
540
554
|
if (resolved) return;
|
|
@@ -594,10 +608,10 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
594
608
|
aborted: abortReason === "signal",
|
|
595
609
|
});
|
|
596
610
|
};
|
|
597
|
-
worker.addEventListener("message", onMessage);
|
|
598
|
-
worker.addEventListener("error", onError);
|
|
599
|
-
worker.addEventListener("close", onClose);
|
|
600
|
-
worker.addEventListener("messageerror", onMessageError);
|
|
611
|
+
worker.addEventListener("message", onMessage, { signal: listenerSignal });
|
|
612
|
+
worker.addEventListener("error", onError, { signal: listenerSignal });
|
|
613
|
+
worker.addEventListener("close", onClose, { signal: listenerSignal });
|
|
614
|
+
worker.addEventListener("messageerror", onMessageError, { signal: listenerSignal });
|
|
601
615
|
try {
|
|
602
616
|
worker.postMessage(startMessage);
|
|
603
617
|
} catch (err) {
|
|
@@ -611,9 +625,6 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
611
625
|
});
|
|
612
626
|
|
|
613
627
|
// Cleanup
|
|
614
|
-
if (signal) {
|
|
615
|
-
signal.removeEventListener("abort", onAbort);
|
|
616
|
-
}
|
|
617
628
|
try {
|
|
618
629
|
worker.terminate();
|
|
619
630
|
} catch {
|
|
@@ -626,7 +637,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
626
637
|
}
|
|
627
638
|
|
|
628
639
|
// Use final output if available, otherwise accumulated output
|
|
629
|
-
let rawOutput =
|
|
640
|
+
let rawOutput = finalOutputChunks.length > 0 ? finalOutputChunks.join("") : outputChunks.join("");
|
|
630
641
|
let abortedViaComplete = false;
|
|
631
642
|
const completeItems = progress.extractedToolData?.complete as
|
|
632
643
|
| Array<{ data?: unknown; status?: "success" | "aborted"; error?: string }>
|
|
@@ -675,7 +686,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
675
686
|
let outputMeta: { lineCount: number; charCount: number } | undefined;
|
|
676
687
|
if (artifactPaths) {
|
|
677
688
|
try {
|
|
678
|
-
|
|
689
|
+
await Bun.write(artifactPaths.outputPath, rawOutput);
|
|
679
690
|
outputMeta = {
|
|
680
691
|
lineCount: rawOutput.split("\n").length,
|
|
681
692
|
charCount: rawOutput.length,
|