@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
|
@@ -6,19 +6,20 @@
|
|
|
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 { AskToolDetails } from "./ask
|
|
12
|
-
import type { FindToolDetails } from "./find
|
|
13
|
-
import type { GrepToolDetails } from "./grep
|
|
14
|
-
import type { LsToolDetails } from "./ls
|
|
15
|
-
import { renderCall as renderLspCall, renderResult as renderLspResult } from "./lsp/render
|
|
16
|
-
import type { LspToolDetails } from "./lsp/types
|
|
17
|
-
import type { NotebookToolDetails } from "./notebook
|
|
18
|
-
import {
|
|
19
|
-
import
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
9
|
+
import type { Theme } from "../../modes/interactive/theme/theme";
|
|
10
|
+
import type { RenderResultOptions } from "../custom-tools/types";
|
|
11
|
+
import type { AskToolDetails } from "./ask";
|
|
12
|
+
import type { FindToolDetails } from "./find";
|
|
13
|
+
import type { GrepToolDetails } from "./grep";
|
|
14
|
+
import type { LsToolDetails } from "./ls";
|
|
15
|
+
import { renderCall as renderLspCall, renderResult as renderLspResult } from "./lsp/render";
|
|
16
|
+
import type { LspToolDetails } from "./lsp/types";
|
|
17
|
+
import type { NotebookToolDetails } from "./notebook";
|
|
18
|
+
import type { OutputToolDetails } from "./output";
|
|
19
|
+
import { renderCall as renderTaskCall, renderResult as renderTaskResult } from "./task/render";
|
|
20
|
+
import type { TaskToolDetails } from "./task/types";
|
|
21
|
+
import { renderWebFetchCall, renderWebFetchResult, type WebFetchToolDetails } from "./web-fetch";
|
|
22
|
+
import { renderWebSearchCall, renderWebSearchResult, type WebSearchRenderDetails } from "./web-search/render";
|
|
22
23
|
|
|
23
24
|
// Tree drawing characters
|
|
24
25
|
const TREE_MID = "├─";
|
|
@@ -416,6 +417,83 @@ const lspRenderer: ToolRenderer<LspArgs, LspToolDetails> = {
|
|
|
416
417
|
renderResult: renderLspResult,
|
|
417
418
|
};
|
|
418
419
|
|
|
420
|
+
// ============================================================================
|
|
421
|
+
// Output Renderer
|
|
422
|
+
// ============================================================================
|
|
423
|
+
|
|
424
|
+
interface OutputArgs {
|
|
425
|
+
ids: string[];
|
|
426
|
+
format?: "raw" | "json" | "stripped";
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/** Format byte count for display */
|
|
430
|
+
function formatBytes(bytes: number): string {
|
|
431
|
+
if (bytes < 1024) return `${bytes}B`;
|
|
432
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}K`;
|
|
433
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}M`;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const outputRenderer: ToolRenderer<OutputArgs, OutputToolDetails> = {
|
|
437
|
+
renderCall(args, theme) {
|
|
438
|
+
const ids = args.ids?.join(", ") ?? "?";
|
|
439
|
+
const label = theme.fg("toolTitle", theme.bold("output"));
|
|
440
|
+
const format = args.format && args.format !== "raw" ? theme.fg("muted", ` (${args.format})`) : "";
|
|
441
|
+
return new Text(`${label} ${theme.fg("dim", ids)}${format}`, 0, 0);
|
|
442
|
+
},
|
|
443
|
+
|
|
444
|
+
renderResult(result, { expanded }, theme) {
|
|
445
|
+
const details = result.details;
|
|
446
|
+
|
|
447
|
+
// Error case: some IDs not found
|
|
448
|
+
if (details?.notFound?.length) {
|
|
449
|
+
let text = `${theme.fg("error", ICON_ERROR)} Not found: ${details.notFound.join(", ")}`;
|
|
450
|
+
if (details.availableIds?.length) {
|
|
451
|
+
text += `\n${theme.fg("dim", "Available:")} ${details.availableIds.join(", ")}`;
|
|
452
|
+
} else {
|
|
453
|
+
text += `\n${theme.fg("dim", "No outputs available in current session")}`;
|
|
454
|
+
}
|
|
455
|
+
return new Text(text, 0, 0);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const outputs = details?.outputs ?? [];
|
|
459
|
+
|
|
460
|
+
// No session case
|
|
461
|
+
if (outputs.length === 0) {
|
|
462
|
+
const textContent = result.content?.find((c: any) => c.type === "text")?.text;
|
|
463
|
+
return new Text(
|
|
464
|
+
`${theme.fg("warning", ICON_WARNING)} ${theme.fg("muted", textContent || "No outputs")}`,
|
|
465
|
+
0,
|
|
466
|
+
0,
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Success: single output
|
|
471
|
+
if (outputs.length === 1) {
|
|
472
|
+
const o = outputs[0];
|
|
473
|
+
const summary = `read ${o.id}.out.md (${o.lineCount} lines, ${formatBytes(o.charCount)})`;
|
|
474
|
+
return new Text(`${theme.fg("success", ICON_SUCCESS)} ${theme.fg("dim", summary)}`, 0, 0);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Success: multiple outputs (tree display)
|
|
478
|
+
const expandHint = expanded ? "" : theme.fg("dim", " (Ctrl+O to expand)");
|
|
479
|
+
let text = `${theme.fg("success", ICON_SUCCESS)} ${theme.fg("dim", `read ${outputs.length} outputs`)}${expandHint}`;
|
|
480
|
+
|
|
481
|
+
const maxOutputs = expanded ? outputs.length : Math.min(outputs.length, 5);
|
|
482
|
+
for (let i = 0; i < maxOutputs; i++) {
|
|
483
|
+
const o = outputs[i];
|
|
484
|
+
const isLast = i === maxOutputs - 1 && (expanded || outputs.length <= 5);
|
|
485
|
+
const branch = isLast ? TREE_END : TREE_MID;
|
|
486
|
+
text += `\n ${theme.fg("dim", branch)} ${theme.fg("accent", o.id)} ${theme.fg("dim", `(${o.lineCount} lines)`)}`;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
if (!expanded && outputs.length > 5) {
|
|
490
|
+
text += `\n ${theme.fg("dim", TREE_END)} ${theme.fg("muted", `… ${outputs.length - 5} more outputs`)}`;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
return new Text(text, 0, 0);
|
|
494
|
+
},
|
|
495
|
+
};
|
|
496
|
+
|
|
419
497
|
// ============================================================================
|
|
420
498
|
// Task Renderer
|
|
421
499
|
// ============================================================================
|
|
@@ -534,6 +612,7 @@ export const toolRenderers: Record<
|
|
|
534
612
|
notebook: notebookRenderer,
|
|
535
613
|
ls: lsRenderer,
|
|
536
614
|
lsp: lspRenderer,
|
|
615
|
+
output: outputRenderer,
|
|
537
616
|
task: taskRenderer,
|
|
538
617
|
web_fetch: webFetchRenderer,
|
|
539
618
|
web_search: webSearchRenderer,
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Review tools - report_finding and submit_review
|
|
3
|
+
*
|
|
4
|
+
* Used by the reviewer agent to report findings in a structured way.
|
|
5
|
+
* Both tools are hidden by default - only enabled when explicitly listed in agent's tools.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
9
|
+
import type { Component } from "@oh-my-pi/pi-tui";
|
|
10
|
+
import { Container, Spacer, Text } from "@oh-my-pi/pi-tui";
|
|
11
|
+
import { Type } from "@sinclair/typebox";
|
|
12
|
+
import type { Theme } from "../../modes/interactive/theme/theme";
|
|
13
|
+
|
|
14
|
+
const PRIORITY_LABELS: Record<number, string> = {
|
|
15
|
+
0: "P0",
|
|
16
|
+
1: "P1",
|
|
17
|
+
2: "P2",
|
|
18
|
+
3: "P3",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const _PRIORITY_DESCRIPTIONS: Record<number, string> = {
|
|
22
|
+
0: "Drop everything to fix. Blocking release, operations, or major usage.",
|
|
23
|
+
1: "Urgent. Should be addressed in the next cycle.",
|
|
24
|
+
2: "Normal. To be fixed eventually.",
|
|
25
|
+
3: "Low. Nice to have.",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// report_finding schema
|
|
29
|
+
const ReportFindingParams = Type.Object({
|
|
30
|
+
title: Type.String({
|
|
31
|
+
description: "≤80 chars, imperative, prefixed with [P0-P3]. E.g., '[P1] Un-padding slices along wrong dimension'",
|
|
32
|
+
}),
|
|
33
|
+
body: Type.String({
|
|
34
|
+
description: "Markdown explaining why this is a problem. One paragraph max.",
|
|
35
|
+
}),
|
|
36
|
+
priority: Type.Union([Type.Literal(0), Type.Literal(1), Type.Literal(2), Type.Literal(3)], {
|
|
37
|
+
description: "0=P0 (critical), 1=P1 (urgent), 2=P2 (normal), 3=P3 (low)",
|
|
38
|
+
}),
|
|
39
|
+
confidence: Type.Number({
|
|
40
|
+
minimum: 0,
|
|
41
|
+
maximum: 1,
|
|
42
|
+
description: "Confidence score 0.0-1.0",
|
|
43
|
+
}),
|
|
44
|
+
file_path: Type.String({ description: "Absolute path to the file" }),
|
|
45
|
+
line_start: Type.Number({ description: "Start line of the issue" }),
|
|
46
|
+
line_end: Type.Number({ description: "End line of the issue" }),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
interface ReportFindingDetails {
|
|
50
|
+
title: string;
|
|
51
|
+
body: string;
|
|
52
|
+
priority: number;
|
|
53
|
+
confidence: number;
|
|
54
|
+
file_path: string;
|
|
55
|
+
line_start: number;
|
|
56
|
+
line_end: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const reportFindingTool: AgentTool<typeof ReportFindingParams, ReportFindingDetails, Theme> = {
|
|
60
|
+
name: "report_finding",
|
|
61
|
+
label: "Report Finding",
|
|
62
|
+
description: "Report a code review finding. Use this for each issue found. Call submit_review when done.",
|
|
63
|
+
parameters: ReportFindingParams,
|
|
64
|
+
hidden: true,
|
|
65
|
+
|
|
66
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
67
|
+
const { title, body, priority, confidence, file_path, line_start, line_end } = params;
|
|
68
|
+
const location = `${file_path}:${line_start}${line_end !== line_start ? `-${line_end}` : ""}`;
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
content: [
|
|
72
|
+
{
|
|
73
|
+
type: "text",
|
|
74
|
+
text: `Finding recorded: ${PRIORITY_LABELS[priority]} ${title}\nLocation: ${location}\nConfidence: ${(confidence * 100).toFixed(0)}%`,
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
details: { title, body, priority, confidence, file_path, line_start, line_end },
|
|
78
|
+
};
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
renderCall(args, theme): Component {
|
|
82
|
+
const priority = PRIORITY_LABELS[args.priority as number] ?? "P?";
|
|
83
|
+
const color = args.priority === 0 ? "error" : args.priority === 1 ? "warning" : "muted";
|
|
84
|
+
const titleText = String(args.title).replace(/^\[P\d\]\s*/, "");
|
|
85
|
+
return new Text(
|
|
86
|
+
`${theme.fg("toolTitle", theme.bold("report_finding "))}${theme.fg(color, `[${priority}]`)} ${theme.fg("dim", titleText)}`,
|
|
87
|
+
0,
|
|
88
|
+
0,
|
|
89
|
+
);
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
renderResult(result, _options, theme): Component {
|
|
93
|
+
const { details } = result;
|
|
94
|
+
if (!details) {
|
|
95
|
+
const text = result.content[0];
|
|
96
|
+
return new Text(text?.type === "text" ? text.text : "", 0, 0);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const priority = PRIORITY_LABELS[details.priority] ?? "P?";
|
|
100
|
+
const color = details.priority === 0 ? "error" : details.priority === 1 ? "warning" : "muted";
|
|
101
|
+
const location = `${details.file_path}:${details.line_start}${details.line_end !== details.line_start ? `-${details.line_end}` : ""}`;
|
|
102
|
+
|
|
103
|
+
return new Text(
|
|
104
|
+
`${theme.fg("success", "✓")} ${theme.fg(color, `[${priority}]`)} ${theme.fg("dim", location)}`,
|
|
105
|
+
0,
|
|
106
|
+
0,
|
|
107
|
+
);
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// submit_review schema
|
|
112
|
+
const SubmitReviewParams = Type.Object({
|
|
113
|
+
overall_correctness: Type.Union([Type.Literal("correct"), Type.Literal("incorrect")], {
|
|
114
|
+
description: "Whether the patch is correct (no bugs, tests won't break)",
|
|
115
|
+
}),
|
|
116
|
+
explanation: Type.String({
|
|
117
|
+
description: "1-3 sentence explanation justifying the verdict",
|
|
118
|
+
}),
|
|
119
|
+
confidence: Type.Number({
|
|
120
|
+
minimum: 0,
|
|
121
|
+
maximum: 1,
|
|
122
|
+
description: "Overall confidence score 0.0-1.0",
|
|
123
|
+
}),
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
interface SubmitReviewDetails {
|
|
127
|
+
overall_correctness: "correct" | "incorrect";
|
|
128
|
+
explanation: string;
|
|
129
|
+
confidence: number;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export const submitReviewTool: AgentTool<typeof SubmitReviewParams, SubmitReviewDetails, Theme> = {
|
|
133
|
+
name: "submit_review",
|
|
134
|
+
label: "Submit Review",
|
|
135
|
+
description: "Submit the final review verdict. Call this after all findings have been reported.",
|
|
136
|
+
parameters: SubmitReviewParams,
|
|
137
|
+
hidden: true,
|
|
138
|
+
|
|
139
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
140
|
+
const { overall_correctness, explanation, confidence } = params;
|
|
141
|
+
|
|
142
|
+
let summary = `## Review Summary\n\n`;
|
|
143
|
+
summary += `**Verdict:** ${overall_correctness === "correct" ? "✓ Patch is correct" : "✗ Patch is incorrect"}\n`;
|
|
144
|
+
summary += `**Confidence:** ${(confidence * 100).toFixed(0)}%\n\n`;
|
|
145
|
+
summary += explanation;
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
content: [{ type: "text", text: summary }],
|
|
149
|
+
details: { overall_correctness, explanation, confidence },
|
|
150
|
+
};
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
renderCall(args, theme): Component {
|
|
154
|
+
const verdict = args.overall_correctness === "correct" ? "correct" : "incorrect";
|
|
155
|
+
const color = args.overall_correctness === "correct" ? "success" : "error";
|
|
156
|
+
return new Text(
|
|
157
|
+
`${theme.fg("toolTitle", theme.bold("submit_review "))}${theme.fg(color, verdict)} ${theme.fg("dim", `(${((args.confidence as number) * 100).toFixed(0)}%)`)}`,
|
|
158
|
+
0,
|
|
159
|
+
0,
|
|
160
|
+
);
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
renderResult(result, { expanded }, theme): Component {
|
|
164
|
+
const { details } = result;
|
|
165
|
+
if (!details) {
|
|
166
|
+
const text = result.content[0];
|
|
167
|
+
return new Text(text?.type === "text" ? text.text : "", 0, 0);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const container = new Container();
|
|
171
|
+
const verdictColor = details.overall_correctness === "correct" ? "success" : "error";
|
|
172
|
+
const verdictIcon = details.overall_correctness === "correct" ? "✓" : "✗";
|
|
173
|
+
|
|
174
|
+
container.addChild(
|
|
175
|
+
new Text(
|
|
176
|
+
`${theme.fg(verdictColor, verdictIcon)} Patch is ${theme.fg(verdictColor, details.overall_correctness)} ${theme.fg("dim", `(${(details.confidence * 100).toFixed(0)}% confidence)`)}`,
|
|
177
|
+
0,
|
|
178
|
+
0,
|
|
179
|
+
),
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
if (expanded) {
|
|
183
|
+
container.addChild(new Spacer(1));
|
|
184
|
+
container.addChild(new Text(theme.fg("dim", details.explanation), 0, 0));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return container;
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
export function createReportFindingTool(): AgentTool<typeof ReportFindingParams, ReportFindingDetails, Theme> {
|
|
192
|
+
return reportFindingTool;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function createSubmitReviewTool(): AgentTool<typeof SubmitReviewParams, SubmitReviewDetails, Theme> {
|
|
196
|
+
return submitReviewTool;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Re-export types for external use
|
|
200
|
+
export type { ReportFindingDetails, SubmitReviewDetails };
|
|
201
|
+
|
|
202
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
203
|
+
// Subprocess tool handlers - registered for extraction/rendering in task tool
|
|
204
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
205
|
+
|
|
206
|
+
import path from "node:path";
|
|
207
|
+
import { subprocessToolRegistry } from "./task/subprocess-tool-registry";
|
|
208
|
+
|
|
209
|
+
// Register report_finding handler
|
|
210
|
+
subprocessToolRegistry.register<ReportFindingDetails>("report_finding", {
|
|
211
|
+
extractData: (event) => event.result?.details as ReportFindingDetails | undefined,
|
|
212
|
+
|
|
213
|
+
renderInline: (data, theme) => {
|
|
214
|
+
const priority = PRIORITY_LABELS[data.priority] ?? "P?";
|
|
215
|
+
const color = data.priority === 0 ? "error" : data.priority === 1 ? "warning" : "muted";
|
|
216
|
+
const titleText = data.title.replace(/^\[P\d\]\s*/, "");
|
|
217
|
+
const loc = `${path.basename(data.file_path)}:${data.line_start}`;
|
|
218
|
+
return new Text(`${theme.fg(color, `[${priority}]`)} ${titleText} ${theme.fg("dim", loc)}`, 0, 0);
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
renderFinal: (allData, theme, expanded) => {
|
|
222
|
+
const container = new Container();
|
|
223
|
+
const displayCount = expanded ? allData.length : Math.min(3, allData.length);
|
|
224
|
+
|
|
225
|
+
for (let i = 0; i < displayCount; i++) {
|
|
226
|
+
const data = allData[i];
|
|
227
|
+
const priority = PRIORITY_LABELS[data.priority] ?? "P?";
|
|
228
|
+
const color = data.priority === 0 ? "error" : data.priority === 1 ? "warning" : "muted";
|
|
229
|
+
const titleText = data.title.replace(/^\[P\d\]\s*/, "");
|
|
230
|
+
const loc = `${path.basename(data.file_path)}:${data.line_start}`;
|
|
231
|
+
|
|
232
|
+
container.addChild(
|
|
233
|
+
new Text(` ${theme.fg(color, `[${priority}]`)} ${titleText} ${theme.fg("dim", loc)}`, 0, 0),
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
if (expanded && data.body) {
|
|
237
|
+
container.addChild(new Text(` ${theme.fg("dim", data.body)}`, 0, 0));
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (allData.length > displayCount) {
|
|
242
|
+
container.addChild(new Text(theme.fg("dim", ` ... ${allData.length - displayCount} more findings`), 0, 0));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return container;
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// Register submit_review handler
|
|
250
|
+
subprocessToolRegistry.register<SubmitReviewDetails>("submit_review", {
|
|
251
|
+
extractData: (event) => event.result?.details as SubmitReviewDetails | undefined,
|
|
252
|
+
|
|
253
|
+
// Terminate subprocess after review is submitted
|
|
254
|
+
shouldTerminate: () => true,
|
|
255
|
+
|
|
256
|
+
renderInline: (data, theme) => {
|
|
257
|
+
const verdictColor = data.overall_correctness === "correct" ? "success" : "error";
|
|
258
|
+
const verdictIcon = data.overall_correctness === "correct" ? "✓" : "✗";
|
|
259
|
+
return new Text(
|
|
260
|
+
`${theme.fg(verdictColor, verdictIcon)} Review: ${theme.fg(verdictColor, data.overall_correctness)} (${(data.confidence * 100).toFixed(0)}%)`,
|
|
261
|
+
0,
|
|
262
|
+
0,
|
|
263
|
+
);
|
|
264
|
+
},
|
|
265
|
+
|
|
266
|
+
// Note: renderFinal is NOT used for submit_review - we use the combined
|
|
267
|
+
// renderReviewResult in render.ts to show verdict + findings together
|
|
268
|
+
});
|
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Bundled agent definitions.
|
|
3
3
|
*
|
|
4
|
-
* Agents are
|
|
5
|
-
* These serve as defaults when no user/project agents are discovered.
|
|
4
|
+
* Agents are embedded at build time via Bun's import with { type: "text" }.
|
|
6
5
|
*/
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
// Embed agent markdown files at build time
|
|
8
|
+
import browserMd from "./bundled-agents/browser.md" with { type: "text" };
|
|
9
|
+
import exploreMd from "./bundled-agents/explore.md" with { type: "text" };
|
|
10
|
+
import planMd from "./bundled-agents/plan.md" with { type: "text" };
|
|
11
|
+
import reviewerMd from "./bundled-agents/reviewer.md" with { type: "text" };
|
|
12
|
+
import taskMd from "./bundled-agents/task.md" with { type: "text" };
|
|
13
|
+
import type { AgentDefinition, AgentSource } from "./types";
|
|
14
|
+
|
|
15
|
+
const EMBEDDED_AGENTS: { name: string; content: string }[] = [
|
|
16
|
+
{ name: "browser.md", content: browserMd },
|
|
17
|
+
{ name: "explore.md", content: exploreMd },
|
|
18
|
+
{ name: "plan.md", content: planMd },
|
|
19
|
+
{ name: "reviewer.md", content: reviewerMd },
|
|
20
|
+
{ name: "task.md", content: taskMd },
|
|
21
|
+
];
|
|
15
22
|
|
|
16
23
|
/**
|
|
17
24
|
* Parse YAML frontmatter from markdown content.
|
|
@@ -47,16 +54,9 @@ function parseFrontmatter(content: string): { frontmatter: Record<string, string
|
|
|
47
54
|
}
|
|
48
55
|
|
|
49
56
|
/**
|
|
50
|
-
*
|
|
57
|
+
* Parse an agent from embedded content.
|
|
51
58
|
*/
|
|
52
|
-
function
|
|
53
|
-
let content: string;
|
|
54
|
-
try {
|
|
55
|
-
content = fs.readFileSync(filePath, "utf-8");
|
|
56
|
-
} catch {
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
|
-
|
|
59
|
+
function parseAgent(fileName: string, content: string, source: AgentSource): AgentDefinition | null {
|
|
60
60
|
const { frontmatter, body } = parseFrontmatter(content);
|
|
61
61
|
|
|
62
62
|
if (!frontmatter.name || !frontmatter.description) {
|
|
@@ -79,7 +79,7 @@ function loadAgentFromFile(filePath: string, source: AgentSource): AgentDefiniti
|
|
|
79
79
|
recursive,
|
|
80
80
|
systemPrompt: body,
|
|
81
81
|
source,
|
|
82
|
-
filePath
|
|
82
|
+
filePath: `embedded:${fileName}`,
|
|
83
83
|
};
|
|
84
84
|
}
|
|
85
85
|
|
|
@@ -87,7 +87,7 @@ function loadAgentFromFile(filePath: string, source: AgentSource): AgentDefiniti
|
|
|
87
87
|
let bundledAgentsCache: AgentDefinition[] | null = null;
|
|
88
88
|
|
|
89
89
|
/**
|
|
90
|
-
* Load all bundled agents from
|
|
90
|
+
* Load all bundled agents from embedded content.
|
|
91
91
|
* Results are cached after first load.
|
|
92
92
|
*/
|
|
93
93
|
export function loadBundledAgents(): AgentDefinition[] {
|
|
@@ -97,24 +97,8 @@ export function loadBundledAgents(): AgentDefinition[] {
|
|
|
97
97
|
|
|
98
98
|
const agents: AgentDefinition[] = [];
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
return agents;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
let entries: fs.Dirent[];
|
|
106
|
-
try {
|
|
107
|
-
entries = fs.readdirSync(BUNDLED_AGENTS_DIR, { withFileTypes: true });
|
|
108
|
-
} catch {
|
|
109
|
-
bundledAgentsCache = agents;
|
|
110
|
-
return agents;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
for (const entry of entries) {
|
|
114
|
-
if (!entry.name.endsWith(".md")) continue;
|
|
115
|
-
|
|
116
|
-
const filePath = path.join(BUNDLED_AGENTS_DIR, entry.name);
|
|
117
|
-
const agent = loadAgentFromFile(filePath, "bundled");
|
|
100
|
+
for (const { name, content } of EMBEDDED_AGENTS) {
|
|
101
|
+
const agent = parseAgent(name, content, "bundled");
|
|
118
102
|
if (agent) {
|
|
119
103
|
agents.push(agent);
|
|
120
104
|
}
|
|
@@ -1,59 +1,74 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: reviewer
|
|
3
|
-
description:
|
|
4
|
-
tools: read, grep,
|
|
3
|
+
description: Code review specialist for quality and security analysis
|
|
4
|
+
tools: read, grep, find, ls, bash, task, report_finding, submit_review
|
|
5
5
|
model: pi/slow, gpt-5.2-codex, gpt-5.2, codex, gpt
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
You are
|
|
8
|
+
You are acting as a reviewer for a proposed code change made by another engineer.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Bash is for read-only commands only: `git diff`, `git log`, `git show`, `gh pr diff`. Do NOT modify files or run builds.
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
2. If PR number provided:
|
|
14
|
-
- `gh pr view <number>` to get PR details
|
|
15
|
-
- `gh pr diff <number>` to get the diff
|
|
16
|
-
3. Analyze changes and provide review
|
|
12
|
+
# Review Strategy
|
|
17
13
|
|
|
18
|
-
|
|
14
|
+
1. Run `git diff` (or `gh pr diff <number>`) to see the changes
|
|
15
|
+
2. Read the modified files for full context
|
|
16
|
+
3. For large changes spanning multiple files/modules, use `task` with `explore` agents in parallel to gather context faster
|
|
17
|
+
4. Analyze for bugs, security issues, and code quality problems
|
|
18
|
+
5. Use `report_finding` for each issue found
|
|
19
|
+
6. Use `submit_review` to provide final verdict
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
# Parallelization
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
For reviews touching many files, spawn `explore` agents to research in parallel:
|
|
24
|
+
- Each agent can investigate a different module or concern
|
|
25
|
+
- Example: one explores test coverage, another checks related implementations
|
|
26
|
+
- Gather their findings, then synthesize into your review
|
|
25
27
|
|
|
26
|
-
|
|
28
|
+
# What to Flag
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
- **Project Conventions**: Does it follow existing patterns?
|
|
30
|
-
- **Performance**: Any performance implications?
|
|
31
|
-
- **Test Coverage**: Are changes adequately tested?
|
|
32
|
-
- **Security**: Any security considerations?
|
|
33
|
-
- **Edge Cases**: Are edge cases handled?
|
|
30
|
+
Only flag issues where ALL of these apply:
|
|
34
31
|
|
|
35
|
-
|
|
32
|
+
1. It meaningfully impacts the accuracy, performance, security, or maintainability of the code
|
|
33
|
+
2. The bug is discrete and actionable (not a general issue or combination of multiple issues)
|
|
34
|
+
3. Fixing it doesn't demand rigor not present elsewhere in the codebase
|
|
35
|
+
4. The bug was introduced in this commit (don't flag pre-existing bugs)
|
|
36
|
+
5. The author would likely fix the issue if made aware of it
|
|
37
|
+
6. The bug doesn't rely on unstated assumptions about the codebase or author's intent
|
|
38
|
+
7. You can identify specific code that is provably affected (speculation is not enough)
|
|
39
|
+
8. The issue is clearly not an intentional change by the author
|
|
36
40
|
|
|
37
|
-
|
|
41
|
+
# Priority Levels
|
|
38
42
|
|
|
39
|
-
|
|
43
|
+
- **P0**: Drop everything to fix. Blocking release, operations, or major usage. Only use for universal issues that do not depend on assumptions about inputs.
|
|
44
|
+
- **P1**: Urgent. Should be addressed in the next cycle.
|
|
45
|
+
- **P2**: Normal. To be fixed eventually.
|
|
46
|
+
- **P3**: Low. Nice to have.
|
|
40
47
|
|
|
41
|
-
|
|
48
|
+
# Comment Guidelines
|
|
42
49
|
|
|
43
|
-
|
|
50
|
+
1. Be clear about WHY the issue is a bug
|
|
51
|
+
2. Communicate severity appropriately - don't overstate
|
|
52
|
+
3. Keep body to one paragraph max
|
|
53
|
+
4. Code snippets should be ≤3 lines, wrapped in markdown code tags
|
|
54
|
+
5. Clearly state what conditions are necessary for the bug to arise
|
|
55
|
+
6. Tone: matter-of-fact, not accusatory or overly positive
|
|
56
|
+
7. Write so the author can immediately grasp the idea without close reading
|
|
57
|
+
8. Avoid flattery and phrases like "Great job...", "Thanks for..."
|
|
44
58
|
|
|
45
|
-
|
|
59
|
+
# CRITICAL
|
|
46
60
|
|
|
47
|
-
|
|
61
|
+
You MUST call `submit_review` before ending your response, even if you found no issues.
|
|
62
|
+
The review is only considered complete when `submit_review` is called.
|
|
63
|
+
Failure to call `submit_review` means the review was not submitted.
|
|
48
64
|
|
|
49
|
-
|
|
65
|
+
# Output
|
|
50
66
|
|
|
51
|
-
|
|
67
|
+
- Use `report_finding` for each issue. Continue until you've listed every qualifying finding.
|
|
68
|
+
- If there is no finding that a person would definitely want to fix, prefer outputting no findings.
|
|
69
|
+
- Ignore trivial style unless it obscures meaning or violates documented standards.
|
|
70
|
+
- Use `submit_review` at the end with your overall verdict:
|
|
71
|
+
- **correct**: Existing code and tests will not break, patch is free of bugs and blocking issues
|
|
72
|
+
- **incorrect**: Has bugs or blocking issues that must be addressed
|
|
52
73
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
- ✅ **Approve**: Ready to merge/complete
|
|
56
|
-
- 🔄 **Request Changes**: Issues must be addressed
|
|
57
|
-
- 💬 **Comment**: Minor suggestions, can proceed
|
|
58
|
-
|
|
59
|
-
Keep reviews concise but thorough. Focus on substance over style nitpicks.
|
|
74
|
+
Ignore non-blocking issues (style, formatting, typos, documentation, nits) when determining correctness.
|
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Workflow commands for orchestrating multi-agent workflows.
|
|
3
3
|
*
|
|
4
|
-
* Commands are
|
|
5
|
-
* They define multi-step workflows that chain agent outputs.
|
|
4
|
+
* Commands are embedded at build time via Bun's import with { type: "text" }.
|
|
6
5
|
*/
|
|
7
6
|
|
|
8
7
|
import * as fs from "node:fs";
|
|
9
8
|
import * as os from "node:os";
|
|
10
9
|
import * as path from "node:path";
|
|
11
|
-
import { fileURLToPath } from "node:url";
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
// Embed command markdown files at build time
|
|
12
|
+
import architectPlanMd from "./bundled-commands/architect-plan.md" with { type: "text" };
|
|
13
|
+
import implementMd from "./bundled-commands/implement.md" with { type: "text" };
|
|
14
|
+
import implementWithCriticMd from "./bundled-commands/implement-with-critic.md" with { type: "text" };
|
|
15
|
+
|
|
16
|
+
const EMBEDDED_COMMANDS: { name: string; content: string }[] = [
|
|
17
|
+
{ name: "architect-plan.md", content: architectPlanMd },
|
|
18
|
+
{ name: "implement-with-critic.md", content: implementWithCriticMd },
|
|
19
|
+
{ name: "implement.md", content: implementMd },
|
|
20
|
+
];
|
|
15
21
|
|
|
16
22
|
/** Workflow command definition */
|
|
17
23
|
export interface WorkflowCommand {
|
|
@@ -56,9 +62,9 @@ function parseFrontmatter(content: string): { frontmatter: Record<string, string
|
|
|
56
62
|
}
|
|
57
63
|
|
|
58
64
|
/**
|
|
59
|
-
* Load commands from a directory.
|
|
65
|
+
* Load commands from a directory (for user/project commands).
|
|
60
66
|
*/
|
|
61
|
-
function loadCommandsFromDir(dir: string, source: "
|
|
67
|
+
function loadCommandsFromDir(dir: string, source: "user" | "project"): WorkflowCommand[] {
|
|
62
68
|
const commands: WorkflowCommand[] = [];
|
|
63
69
|
|
|
64
70
|
if (!fs.existsSync(dir)) {
|
|
@@ -137,15 +143,30 @@ function findNearestDir(cwd: string, relPath: string): string | null {
|
|
|
137
143
|
let bundledCommandsCache: WorkflowCommand[] | null = null;
|
|
138
144
|
|
|
139
145
|
/**
|
|
140
|
-
* Load all bundled commands.
|
|
146
|
+
* Load all bundled commands from embedded content.
|
|
141
147
|
*/
|
|
142
148
|
export function loadBundledCommands(): WorkflowCommand[] {
|
|
143
149
|
if (bundledCommandsCache !== null) {
|
|
144
150
|
return bundledCommandsCache;
|
|
145
151
|
}
|
|
146
152
|
|
|
147
|
-
|
|
148
|
-
|
|
153
|
+
const commands: WorkflowCommand[] = [];
|
|
154
|
+
|
|
155
|
+
for (const { name, content } of EMBEDDED_COMMANDS) {
|
|
156
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
157
|
+
const cmdName = name.replace(/\.md$/, "");
|
|
158
|
+
|
|
159
|
+
commands.push({
|
|
160
|
+
name: cmdName,
|
|
161
|
+
description: frontmatter.description || "",
|
|
162
|
+
instructions: body,
|
|
163
|
+
source: "bundled",
|
|
164
|
+
filePath: `embedded:${name}`,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
bundledCommandsCache = commands;
|
|
169
|
+
return commands;
|
|
149
170
|
}
|
|
150
171
|
|
|
151
172
|
/**
|