@oh-my-pi/pi-coding-agent 1.341.0 → 2.0.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 +73 -0
- package/README.md +1 -1
- package/examples/custom-tools/subagent/index.ts +1 -1
- package/package.json +5 -3
- 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.ts +1 -1
- package/src/config.ts +3 -3
- package/src/core/agent-session.ts +157 -15
- 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 +5 -5
- 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 +1 -1
- package/src/core/tools/task/bundled-agents/reviewer.md +52 -37
- 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 +37 -32
- 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 +85 -41
- package/src/modes/interactive/theme/theme.ts +8 -7
- package/src/modes/print-mode.ts +4 -3
- package/src/modes/rpc/rpc-client.ts +4 -4
- package/src/modes/rpc/rpc-mode.ts +21 -11
- 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
|
@@ -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.
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
import * as fs from "node:fs";
|
|
14
14
|
import * as os from "node:os";
|
|
15
15
|
import * as path from "node:path";
|
|
16
|
-
import { loadBundledAgents } from "./agents
|
|
17
|
-
import type { AgentDefinition, AgentSource } from "./types
|
|
16
|
+
import { loadBundledAgents } from "./agents";
|
|
17
|
+
import type { AgentDefinition, AgentSource } from "./types";
|
|
18
18
|
|
|
19
19
|
/** Result of agent discovery */
|
|
20
20
|
export interface DiscoveryResult {
|
|
@@ -10,15 +10,17 @@ import * as fs from "node:fs";
|
|
|
10
10
|
import * as os from "node:os";
|
|
11
11
|
import * as path from "node:path";
|
|
12
12
|
import * as readline from "node:readline";
|
|
13
|
-
import {
|
|
13
|
+
import { ensureArtifactsDir, getArtifactPaths } from "./artifacts";
|
|
14
|
+
import { resolveModelPattern } from "./model-resolver";
|
|
15
|
+
import { subprocessToolRegistry } from "./subprocess-tool-registry";
|
|
14
16
|
import {
|
|
15
17
|
type AgentDefinition,
|
|
16
18
|
type AgentProgress,
|
|
17
19
|
MAX_OUTPUT_BYTES,
|
|
18
20
|
MAX_OUTPUT_LINES,
|
|
19
|
-
|
|
21
|
+
PI_BLOCKED_AGENT_ENV,
|
|
20
22
|
type SingleResult,
|
|
21
|
-
} from "./types
|
|
23
|
+
} from "./types";
|
|
22
24
|
|
|
23
25
|
/** pi command: 'pi.cmd' on Windows, 'pi' elsewhere */
|
|
24
26
|
const PI_CMD = process.platform === "win32" ? "pi.cmd" : "pi";
|
|
@@ -166,6 +168,23 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
166
168
|
// Build full task with context
|
|
167
169
|
const fullTask = context ? `${context}\n\n${task}` : task;
|
|
168
170
|
|
|
171
|
+
// Set up artifact paths and write input file upfront if artifacts dir provided
|
|
172
|
+
let artifactPaths: { inputPath: string; outputPath: string; jsonlPath: string } | undefined;
|
|
173
|
+
let subtaskSessionFile: string | undefined;
|
|
174
|
+
|
|
175
|
+
if (options.artifactsDir) {
|
|
176
|
+
ensureArtifactsDir(options.artifactsDir);
|
|
177
|
+
artifactPaths = getArtifactPaths(options.artifactsDir, agent.name, index);
|
|
178
|
+
subtaskSessionFile = artifactPaths.jsonlPath;
|
|
179
|
+
|
|
180
|
+
// Write input file immediately (real-time visibility)
|
|
181
|
+
try {
|
|
182
|
+
fs.writeFileSync(artifactPaths.inputPath, fullTask, "utf-8");
|
|
183
|
+
} catch {
|
|
184
|
+
// Non-fatal, continue without input artifact
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
169
188
|
// Build args
|
|
170
189
|
const args: string[] = ["--mode", "json", "--non-interactive"];
|
|
171
190
|
|
|
@@ -183,8 +202,10 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
183
202
|
args.push("--model", resolvedModel);
|
|
184
203
|
}
|
|
185
204
|
|
|
186
|
-
// Add session options
|
|
187
|
-
if (
|
|
205
|
+
// Add session options - use subtask-specific session file for real-time streaming
|
|
206
|
+
if (subtaskSessionFile) {
|
|
207
|
+
args.push("--session", subtaskSessionFile);
|
|
208
|
+
} else if (options.sessionFile) {
|
|
188
209
|
args.push("--session", options.sessionFile);
|
|
189
210
|
} else {
|
|
190
211
|
args.push("--no-session");
|
|
@@ -193,10 +214,10 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
193
214
|
// Add task as prompt
|
|
194
215
|
args.push("--prompt", fullTask);
|
|
195
216
|
|
|
196
|
-
// Set up environment
|
|
217
|
+
// Set up environment - block same-agent recursion unless explicitly recursive
|
|
197
218
|
const env = { ...process.env };
|
|
198
219
|
if (!agent.recursive) {
|
|
199
|
-
env[
|
|
220
|
+
env[PI_BLOCKED_AGENT_ENV] = agent.name;
|
|
200
221
|
}
|
|
201
222
|
|
|
202
223
|
// Spawn subprocess
|
|
@@ -211,6 +232,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
211
232
|
let stderr = "";
|
|
212
233
|
let finalOutput = "";
|
|
213
234
|
let resolved = false;
|
|
235
|
+
let pendingTermination = false; // Set when shouldTerminate fires, wait for message_end
|
|
214
236
|
const jsonlEvents: string[] = [];
|
|
215
237
|
|
|
216
238
|
// Handle abort signal
|
|
@@ -242,7 +264,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
242
264
|
progress.currentToolStartMs = now;
|
|
243
265
|
break;
|
|
244
266
|
|
|
245
|
-
case "tool_execution_end":
|
|
267
|
+
case "tool_execution_end": {
|
|
246
268
|
if (progress.currentTool) {
|
|
247
269
|
progress.recentTools.unshift({
|
|
248
270
|
tool: progress.currentTool,
|
|
@@ -257,24 +279,73 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
257
279
|
progress.currentTool = undefined;
|
|
258
280
|
progress.currentToolArgs = undefined;
|
|
259
281
|
progress.currentToolStartMs = undefined;
|
|
282
|
+
|
|
283
|
+
// Check for registered subprocess tool handler
|
|
284
|
+
const handler = subprocessToolRegistry.getHandler(event.toolName);
|
|
285
|
+
if (handler) {
|
|
286
|
+
// Extract data using handler
|
|
287
|
+
if (handler.extractData) {
|
|
288
|
+
const data = handler.extractData({
|
|
289
|
+
toolName: event.toolName,
|
|
290
|
+
toolCallId: event.toolCallId,
|
|
291
|
+
args: event.args,
|
|
292
|
+
result: event.result,
|
|
293
|
+
isError: event.isError,
|
|
294
|
+
});
|
|
295
|
+
if (data !== undefined) {
|
|
296
|
+
progress.extractedToolData = progress.extractedToolData || {};
|
|
297
|
+
progress.extractedToolData[event.toolName] = progress.extractedToolData[event.toolName] || [];
|
|
298
|
+
progress.extractedToolData[event.toolName].push(data);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Check if handler wants to terminate subprocess
|
|
303
|
+
if (
|
|
304
|
+
handler.shouldTerminate?.({
|
|
305
|
+
toolName: event.toolName,
|
|
306
|
+
toolCallId: event.toolCallId,
|
|
307
|
+
args: event.args,
|
|
308
|
+
result: event.result,
|
|
309
|
+
isError: event.isError,
|
|
310
|
+
})
|
|
311
|
+
) {
|
|
312
|
+
// Don't kill immediately - wait for message_end to get token counts
|
|
313
|
+
pendingTermination = true;
|
|
314
|
+
// Safety timeout in case message_end never arrives
|
|
315
|
+
setTimeout(() => {
|
|
316
|
+
if (!resolved) {
|
|
317
|
+
resolved = true;
|
|
318
|
+
proc.kill("SIGTERM");
|
|
319
|
+
}
|
|
320
|
+
}, 2000);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
case "message_update": {
|
|
327
|
+
// Extract text for progress display only (replace, don't accumulate)
|
|
328
|
+
const updateContent = event.message?.content || event.content;
|
|
329
|
+
if (updateContent && Array.isArray(updateContent)) {
|
|
330
|
+
const allText: string[] = [];
|
|
331
|
+
for (const block of updateContent) {
|
|
332
|
+
if (block.type === "text" && block.text) {
|
|
333
|
+
const lines = block.text.split("\n").filter((l: string) => l.trim());
|
|
334
|
+
allText.push(...lines);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
// Show last 8 lines from current state (not accumulated)
|
|
338
|
+
progress.recentOutput = allText.slice(-8).reverse();
|
|
339
|
+
}
|
|
260
340
|
break;
|
|
341
|
+
}
|
|
261
342
|
|
|
262
|
-
case "message_update":
|
|
263
343
|
case "message_end": {
|
|
264
|
-
// Extract text content
|
|
344
|
+
// Extract final text content from completed message
|
|
265
345
|
const messageContent = event.message?.content || event.content;
|
|
266
346
|
if (messageContent && Array.isArray(messageContent)) {
|
|
267
347
|
for (const block of messageContent) {
|
|
268
348
|
if (block.type === "text" && block.text) {
|
|
269
|
-
const lines = block.text.split("\n").filter((l: string) => l.trim());
|
|
270
|
-
for (const l of lines) {
|
|
271
|
-
if (!progress.recentOutput.includes(l)) {
|
|
272
|
-
progress.recentOutput.unshift(l);
|
|
273
|
-
if (progress.recentOutput.length > 8) {
|
|
274
|
-
progress.recentOutput.pop();
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
349
|
output += block.text;
|
|
279
350
|
}
|
|
280
351
|
}
|
|
@@ -282,7 +353,13 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
282
353
|
// Extract usage (prefer message.usage, fallback to event.usage)
|
|
283
354
|
const messageUsage = event.message?.usage || event.usage;
|
|
284
355
|
if (messageUsage) {
|
|
285
|
-
|
|
356
|
+
// Accumulate tokens across messages (not overwrite)
|
|
357
|
+
progress.tokens += (messageUsage.input_tokens || 0) + (messageUsage.output_tokens || 0);
|
|
358
|
+
}
|
|
359
|
+
// If pending termination, now we have tokens - terminate
|
|
360
|
+
if (pendingTermination && !resolved) {
|
|
361
|
+
resolved = true;
|
|
362
|
+
proc.kill("SIGTERM");
|
|
286
363
|
}
|
|
287
364
|
break;
|
|
288
365
|
}
|
|
@@ -304,7 +381,8 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
304
381
|
}
|
|
305
382
|
|
|
306
383
|
progress.durationMs = now - startTime;
|
|
307
|
-
|
|
384
|
+
// Clone progress object before passing to callback to prevent mutation during render
|
|
385
|
+
onProgress?.({ ...progress });
|
|
308
386
|
} catch {
|
|
309
387
|
// Ignore non-JSON lines
|
|
310
388
|
}
|
|
@@ -316,16 +394,35 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
316
394
|
stderr += stderrDecoder.decode(chunk, { stream: true });
|
|
317
395
|
});
|
|
318
396
|
|
|
319
|
-
// Wait for
|
|
397
|
+
// Wait for readline to finish BEFORE resolving
|
|
320
398
|
const exitCode = await new Promise<number>((resolve) => {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
399
|
+
let code: number | null = null;
|
|
400
|
+
let rlClosed = false;
|
|
401
|
+
let procClosed = false;
|
|
402
|
+
|
|
403
|
+
const maybeResolve = () => {
|
|
404
|
+
if (rlClosed && procClosed) {
|
|
405
|
+
resolved = true;
|
|
406
|
+
resolve(code ?? 1);
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
rl.on("close", () => {
|
|
411
|
+
rlClosed = true;
|
|
412
|
+
maybeResolve();
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
proc.on("close", (c) => {
|
|
416
|
+
code = c;
|
|
417
|
+
procClosed = true;
|
|
418
|
+
maybeResolve();
|
|
324
419
|
});
|
|
420
|
+
|
|
325
421
|
proc.on("error", (err) => {
|
|
326
|
-
resolved = true;
|
|
327
422
|
stderr += `\nProcess error: ${err.message}`;
|
|
328
|
-
|
|
423
|
+
code = 1;
|
|
424
|
+
procClosed = true;
|
|
425
|
+
maybeResolve();
|
|
329
426
|
});
|
|
330
427
|
});
|
|
331
428
|
|
|
@@ -344,8 +441,24 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
344
441
|
const rawOutput = finalOutput || output;
|
|
345
442
|
const { text: truncatedOutput, truncated } = truncateOutput(rawOutput);
|
|
346
443
|
|
|
444
|
+
// Write output artifact (input and jsonl already written in real-time)
|
|
445
|
+
// Compute output metadata for Output tool integration
|
|
446
|
+
let outputMeta: { lineCount: number; charCount: number } | undefined;
|
|
447
|
+
if (artifactPaths) {
|
|
448
|
+
try {
|
|
449
|
+
fs.writeFileSync(artifactPaths.outputPath, rawOutput, "utf-8");
|
|
450
|
+
outputMeta = {
|
|
451
|
+
lineCount: rawOutput.split("\n").length,
|
|
452
|
+
charCount: rawOutput.length,
|
|
453
|
+
};
|
|
454
|
+
} catch {
|
|
455
|
+
// Non-fatal
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
347
459
|
// Update final progress
|
|
348
|
-
|
|
460
|
+
const wasAborted = signal?.aborted ?? false;
|
|
461
|
+
progress.status = wasAborted ? "aborted" : exitCode === 0 ? "completed" : "failed";
|
|
349
462
|
progress.durationMs = Date.now() - startTime;
|
|
350
463
|
onProgress?.(progress);
|
|
351
464
|
|
|
@@ -362,6 +475,10 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
362
475
|
tokens: progress.tokens,
|
|
363
476
|
modelOverride,
|
|
364
477
|
error: exitCode !== 0 && stderr ? stderr : undefined,
|
|
478
|
+
aborted: wasAborted,
|
|
365
479
|
jsonlEvents,
|
|
480
|
+
artifactPaths,
|
|
481
|
+
extractedToolData: progress.extractedToolData,
|
|
482
|
+
outputMeta,
|
|
366
483
|
};
|
|
367
484
|
}
|
|
@@ -14,33 +14,50 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
17
|
-
import type { Theme } from "../../../modes/interactive/theme/theme
|
|
18
|
-
import { cleanupTempDir, createTempArtifactsDir, getArtifactsDir
|
|
19
|
-
import { discoverAgents, getAgent } from "./discovery
|
|
20
|
-
import { runSubprocess } from "./executor
|
|
21
|
-
import { mapWithConcurrencyLimit } from "./parallel
|
|
22
|
-
import { formatDuration, renderCall, renderResult } from "./render
|
|
17
|
+
import type { Theme } from "../../../modes/interactive/theme/theme";
|
|
18
|
+
import { cleanupTempDir, createTempArtifactsDir, getArtifactsDir } from "./artifacts";
|
|
19
|
+
import { discoverAgents, getAgent } from "./discovery";
|
|
20
|
+
import { runSubprocess } from "./executor";
|
|
21
|
+
import { mapWithConcurrencyLimit } from "./parallel";
|
|
22
|
+
import { formatDuration, renderCall, renderResult } from "./render";
|
|
23
23
|
import {
|
|
24
24
|
type AgentProgress,
|
|
25
25
|
MAX_AGENTS_IN_DESCRIPTION,
|
|
26
26
|
MAX_CONCURRENCY,
|
|
27
27
|
MAX_PARALLEL_TASKS,
|
|
28
|
+
PI_BLOCKED_AGENT_ENV,
|
|
28
29
|
PI_NO_SUBAGENTS_ENV,
|
|
29
30
|
type TaskToolDetails,
|
|
30
31
|
taskSchema,
|
|
31
|
-
} from "./types
|
|
32
|
+
} from "./types";
|
|
33
|
+
|
|
34
|
+
// Import review tools for side effects (registers subprocess tool handlers)
|
|
35
|
+
import "../review";
|
|
36
|
+
|
|
37
|
+
/** Format byte count for display */
|
|
38
|
+
function formatBytes(bytes: number): string {
|
|
39
|
+
if (bytes < 1024) return `${bytes}B`;
|
|
40
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}K`;
|
|
41
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}M`;
|
|
42
|
+
}
|
|
32
43
|
|
|
33
44
|
/** Session context interface */
|
|
34
45
|
interface SessionContext {
|
|
35
46
|
getSessionFile: () => string | null;
|
|
36
47
|
}
|
|
37
48
|
|
|
49
|
+
/** Task tool options */
|
|
50
|
+
interface TaskToolOptions {
|
|
51
|
+
/** Set of available tool names (for cross-tool awareness) */
|
|
52
|
+
availableTools?: Set<string>;
|
|
53
|
+
}
|
|
54
|
+
|
|
38
55
|
// Re-export types and utilities
|
|
39
|
-
export { loadBundledAgents as BUNDLED_AGENTS } from "./agents
|
|
40
|
-
export { discoverCommands, expandCommand, getCommand } from "./commands
|
|
41
|
-
export { discoverAgents, getAgent } from "./discovery
|
|
42
|
-
export type { AgentDefinition, AgentProgress, SingleResult, TaskParams, TaskToolDetails } from "./types
|
|
43
|
-
export { taskSchema } from "./types
|
|
56
|
+
export { loadBundledAgents as BUNDLED_AGENTS } from "./agents";
|
|
57
|
+
export { discoverCommands, expandCommand, getCommand } from "./commands";
|
|
58
|
+
export { discoverAgents, getAgent } from "./discovery";
|
|
59
|
+
export type { AgentDefinition, AgentProgress, SingleResult, TaskParams, TaskToolDetails } from "./types";
|
|
60
|
+
export { taskSchema } from "./types";
|
|
44
61
|
|
|
45
62
|
/**
|
|
46
63
|
* Build dynamic tool description listing available agents.
|
|
@@ -168,8 +185,10 @@ function buildDescription(cwd: string): string {
|
|
|
168
185
|
export function createTaskTool(
|
|
169
186
|
cwd: string,
|
|
170
187
|
sessionContext?: SessionContext,
|
|
188
|
+
options?: TaskToolOptions,
|
|
171
189
|
): AgentTool<typeof taskSchema, TaskToolDetails, Theme> {
|
|
172
|
-
|
|
190
|
+
const hasOutputTool = options?.availableTools?.has("output") ?? false;
|
|
191
|
+
// Check if subagents are completely inhibited (legacy recursion prevention)
|
|
173
192
|
if (process.env[PI_NO_SUBAGENTS_ENV]) {
|
|
174
193
|
return {
|
|
175
194
|
name: "task",
|
|
@@ -187,6 +206,9 @@ export function createTaskTool(
|
|
|
187
206
|
};
|
|
188
207
|
}
|
|
189
208
|
|
|
209
|
+
// Check for same-agent blocking (allows other agent types)
|
|
210
|
+
const blockedAgent = process.env[PI_BLOCKED_AGENT_ENV];
|
|
211
|
+
|
|
190
212
|
return {
|
|
191
213
|
name: "task",
|
|
192
214
|
label: "Task",
|
|
@@ -258,7 +280,31 @@ export function createTaskTool(
|
|
|
258
280
|
};
|
|
259
281
|
|
|
260
282
|
try {
|
|
261
|
-
|
|
283
|
+
let tasks = params.tasks;
|
|
284
|
+
let skippedSelfRecursion = 0;
|
|
285
|
+
|
|
286
|
+
// Filter out blocked agent (self-recursion prevention)
|
|
287
|
+
if (blockedAgent) {
|
|
288
|
+
const blockedTasks = tasks.filter((t) => t.agent === blockedAgent);
|
|
289
|
+
tasks = tasks.filter((t) => t.agent !== blockedAgent);
|
|
290
|
+
skippedSelfRecursion = blockedTasks.length;
|
|
291
|
+
|
|
292
|
+
if (skippedSelfRecursion > 0 && tasks.length === 0) {
|
|
293
|
+
return {
|
|
294
|
+
content: [
|
|
295
|
+
{
|
|
296
|
+
type: "text",
|
|
297
|
+
text: `Cannot spawn ${blockedAgent} agent from within itself (recursion prevention). Use a different agent type.`,
|
|
298
|
+
},
|
|
299
|
+
],
|
|
300
|
+
details: {
|
|
301
|
+
projectAgentsDir,
|
|
302
|
+
results: [],
|
|
303
|
+
totalDurationMs: Date.now() - startTime,
|
|
304
|
+
},
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
}
|
|
262
308
|
|
|
263
309
|
// Validate all agents exist
|
|
264
310
|
for (const task of tasks) {
|
|
@@ -316,40 +362,42 @@ export function createTaskTool(
|
|
|
316
362
|
artifactsDir: effectiveArtifactsDir,
|
|
317
363
|
signal,
|
|
318
364
|
onProgress: (progress) => {
|
|
319
|
-
progressMap.set(index, progress);
|
|
365
|
+
progressMap.set(index, structuredClone(progress));
|
|
320
366
|
emitProgress();
|
|
321
367
|
},
|
|
322
368
|
});
|
|
323
369
|
});
|
|
324
370
|
|
|
325
|
-
//
|
|
371
|
+
// Collect output paths (artifacts already written by executor in real-time)
|
|
326
372
|
const outputPaths: string[] = [];
|
|
327
373
|
for (const result of results) {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
result.agent,
|
|
332
|
-
result.index,
|
|
333
|
-
fullTask,
|
|
334
|
-
result.output,
|
|
335
|
-
result.jsonlEvents,
|
|
336
|
-
);
|
|
337
|
-
outputPaths.push(paths.outputPath);
|
|
338
|
-
result.artifactPaths = paths;
|
|
374
|
+
if (result.artifactPaths) {
|
|
375
|
+
outputPaths.push(result.artifactPaths.outputPath);
|
|
376
|
+
}
|
|
339
377
|
}
|
|
340
378
|
|
|
341
379
|
// Build final output - match plugin format
|
|
342
380
|
const successCount = results.filter((r) => r.exitCode === 0).length;
|
|
343
381
|
const totalDuration = Date.now() - startTime;
|
|
344
382
|
|
|
345
|
-
const summaries = results.map((r
|
|
383
|
+
const summaries = results.map((r) => {
|
|
346
384
|
const status = r.exitCode === 0 ? "completed" : `failed (exit ${r.exitCode})`;
|
|
347
385
|
const output = r.output.trim() || r.stderr.trim() || "(no output)";
|
|
348
386
|
const preview = output.split("\n").slice(0, 5).join("\n");
|
|
349
|
-
|
|
387
|
+
// Include output metadata and ID; include path only if Output tool unavailable (for Read fallback)
|
|
388
|
+
const outputId = `${r.agent}_${r.index}`;
|
|
389
|
+
const meta = r.outputMeta
|
|
390
|
+
? ` [${r.outputMeta.lineCount} lines, ${formatBytes(r.outputMeta.charCount)}]`
|
|
391
|
+
: "";
|
|
392
|
+
const pathInfo = !hasOutputTool && r.artifactPaths?.outputPath ? ` (${r.artifactPaths.outputPath})` : "";
|
|
393
|
+
return `[${r.agent}] ${status}${meta} → ${outputId}${pathInfo}\n${preview}`;
|
|
350
394
|
});
|
|
351
395
|
|
|
352
|
-
const
|
|
396
|
+
const skippedNote =
|
|
397
|
+
skippedSelfRecursion > 0
|
|
398
|
+
? ` (${skippedSelfRecursion} ${blockedAgent} task${skippedSelfRecursion > 1 ? "s" : ""} skipped - self-recursion blocked)`
|
|
399
|
+
: "";
|
|
400
|
+
const summary = `${successCount}/${results.length} succeeded${skippedNote} [${formatDuration(totalDuration)}]\n\n${summaries.join("\n\n---\n\n")}`;
|
|
353
401
|
|
|
354
402
|
// Cleanup temp directory if used
|
|
355
403
|
if (tempArtifactsDir) {
|