@mariozechner/pi-coding-agent 0.27.9 → 0.29.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 +37 -1
- package/README.md +17 -18
- package/dist/cli/list-models.d.ts +2 -2
- package/dist/cli/list-models.d.ts.map +1 -1
- package/dist/cli/list-models.js +2 -7
- package/dist/cli/list-models.js.map +1 -1
- package/dist/config.d.ts +2 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -3
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts +6 -3
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +23 -25
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts +104 -0
- package/dist/core/auth-storage.d.ts.map +1 -0
- package/dist/core/auth-storage.js +232 -0
- package/dist/core/auth-storage.js.map +1 -0
- package/dist/core/custom-tools/types.d.ts +2 -2
- package/dist/core/custom-tools/types.d.ts.map +1 -1
- package/dist/core/custom-tools/types.js.map +1 -1
- package/dist/core/hooks/types.d.ts +3 -3
- package/dist/core/hooks/types.d.ts.map +1 -1
- package/dist/core/hooks/types.js.map +1 -1
- package/dist/core/model-registry.d.ts +50 -0
- package/dist/core/model-registry.d.ts.map +1 -0
- package/dist/core/model-registry.js +268 -0
- package/dist/core/model-registry.js.map +1 -0
- package/dist/core/model-resolver.d.ts +7 -7
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +12 -44
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/sdk.d.ts +13 -26
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +24 -101
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/settings-manager.d.ts +0 -5
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +0 -19
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +15 -1
- package/dist/core/skills.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -8
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +37 -22
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +3 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +4 -3
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts +3 -1
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +21 -14
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.d.ts +3 -1
- package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.js +6 -6
- package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +56 -51
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/docs/custom-tools.md +3 -3
- package/docs/hooks.md +9 -9
- package/docs/sdk.md +86 -61
- package/examples/custom-tools/hello/index.ts +15 -15
- package/examples/custom-tools/question/index.ts +3 -3
- package/examples/custom-tools/subagent/agents.ts +1 -2
- package/examples/custom-tools/subagent/index.ts +332 -125
- package/examples/custom-tools/todo/index.ts +30 -12
- package/examples/hooks/confirm-destructive.ts +6 -8
- package/examples/hooks/custom-compaction.ts +7 -7
- package/examples/hooks/dirty-repo-guard.ts +7 -15
- package/examples/hooks/permission-gate.ts +1 -5
- package/examples/sdk/02-custom-model.ts +20 -7
- package/examples/sdk/04-skills.ts +1 -1
- package/examples/sdk/05-tools.ts +11 -14
- package/examples/sdk/06-hooks.ts +1 -1
- package/examples/sdk/07-context-files.ts +1 -1
- package/examples/sdk/08-slash-commands.ts +3 -3
- package/examples/sdk/09-api-keys-and-oauth.ts +36 -26
- package/examples/sdk/10-settings.ts +2 -2
- package/examples/sdk/12-full-control.ts +19 -20
- package/examples/sdk/README.md +26 -13
- package/package.json +4 -5
- package/dist/core/model-config.d.ts +0 -58
- package/dist/core/model-config.d.ts.map +0 -1
- package/dist/core/model-config.js +0 -384
- package/dist/core/model-config.js.map +0 -1
- package/dist/core/oauth/index.d.ts +0 -41
- package/dist/core/oauth/index.d.ts.map +0 -1
- package/dist/core/oauth/index.js +0 -84
- package/dist/core/oauth/index.js.map +0 -1
|
@@ -16,11 +16,16 @@ import { spawn } from "node:child_process";
|
|
|
16
16
|
import * as fs from "node:fs";
|
|
17
17
|
import * as os from "node:os";
|
|
18
18
|
import * as path from "node:path";
|
|
19
|
-
import { Type } from "@sinclair/typebox";
|
|
20
19
|
import type { AgentToolResult, Message } from "@mariozechner/pi-ai";
|
|
21
20
|
import { StringEnum } from "@mariozechner/pi-ai";
|
|
21
|
+
import {
|
|
22
|
+
type CustomAgentTool,
|
|
23
|
+
type CustomToolFactory,
|
|
24
|
+
getMarkdownTheme,
|
|
25
|
+
type ToolAPI,
|
|
26
|
+
} from "@mariozechner/pi-coding-agent";
|
|
22
27
|
import { Container, Markdown, Spacer, Text } from "@mariozechner/pi-tui";
|
|
23
|
-
import {
|
|
28
|
+
import { Type } from "@sinclair/typebox";
|
|
24
29
|
import { type AgentConfig, type AgentScope, discoverAgents, formatAgentList } from "./agents.js";
|
|
25
30
|
|
|
26
31
|
const MAX_PARALLEL_TASKS = 8;
|
|
@@ -30,12 +35,23 @@ const COLLAPSED_ITEM_COUNT = 10;
|
|
|
30
35
|
|
|
31
36
|
function formatTokens(count: number): string {
|
|
32
37
|
if (count < 1000) return count.toString();
|
|
33
|
-
if (count < 10000) return (count / 1000).toFixed(1)
|
|
34
|
-
if (count < 1000000) return Math.round(count / 1000)
|
|
35
|
-
return (count / 1000000).toFixed(1)
|
|
38
|
+
if (count < 10000) return `${(count / 1000).toFixed(1)}k`;
|
|
39
|
+
if (count < 1000000) return `${Math.round(count / 1000)}k`;
|
|
40
|
+
return `${(count / 1000000).toFixed(1)}M`;
|
|
36
41
|
}
|
|
37
42
|
|
|
38
|
-
function formatUsageStats(
|
|
43
|
+
function formatUsageStats(
|
|
44
|
+
usage: {
|
|
45
|
+
input: number;
|
|
46
|
+
output: number;
|
|
47
|
+
cacheRead: number;
|
|
48
|
+
cacheWrite: number;
|
|
49
|
+
cost: number;
|
|
50
|
+
contextTokens?: number;
|
|
51
|
+
turns?: number;
|
|
52
|
+
},
|
|
53
|
+
model?: string,
|
|
54
|
+
): string {
|
|
39
55
|
const parts: string[] = [];
|
|
40
56
|
if (usage.turns) parts.push(`${usage.turns} turn${usage.turns > 1 ? "s" : ""}`);
|
|
41
57
|
if (usage.input) parts.push(`↑${formatTokens(usage.input)}`);
|
|
@@ -50,16 +66,20 @@ function formatUsageStats(usage: { input: number; output: number; cacheRead: num
|
|
|
50
66
|
return parts.join(" ");
|
|
51
67
|
}
|
|
52
68
|
|
|
53
|
-
function formatToolCall(
|
|
69
|
+
function formatToolCall(
|
|
70
|
+
toolName: string,
|
|
71
|
+
args: Record<string, unknown>,
|
|
72
|
+
themeFg: (color: any, text: string) => string,
|
|
73
|
+
): string {
|
|
54
74
|
const shortenPath = (p: string) => {
|
|
55
75
|
const home = os.homedir();
|
|
56
|
-
return p.startsWith(home) ?
|
|
76
|
+
return p.startsWith(home) ? `~${p.slice(home.length)}` : p;
|
|
57
77
|
};
|
|
58
78
|
|
|
59
79
|
switch (toolName) {
|
|
60
80
|
case "bash": {
|
|
61
81
|
const command = (args.command as string) || "...";
|
|
62
|
-
const preview = command.length > 60 ? command.slice(0, 60)
|
|
82
|
+
const preview = command.length > 60 ? `${command.slice(0, 60)}...` : command;
|
|
63
83
|
return themeFg("muted", "$ ") + themeFg("toolOutput", preview);
|
|
64
84
|
}
|
|
65
85
|
case "read": {
|
|
@@ -100,11 +120,15 @@ function formatToolCall(toolName: string, args: Record<string, unknown>, themeFg
|
|
|
100
120
|
case "grep": {
|
|
101
121
|
const pattern = (args.pattern || "") as string;
|
|
102
122
|
const rawPath = (args.path || ".") as string;
|
|
103
|
-
return
|
|
123
|
+
return (
|
|
124
|
+
themeFg("muted", "grep ") +
|
|
125
|
+
themeFg("accent", `/${pattern}/`) +
|
|
126
|
+
themeFg("dim", ` in ${shortenPath(rawPath)}`)
|
|
127
|
+
);
|
|
104
128
|
}
|
|
105
129
|
default: {
|
|
106
130
|
const argsStr = JSON.stringify(args);
|
|
107
|
-
const preview = argsStr.length > 50 ? argsStr.slice(0, 50)
|
|
131
|
+
const preview = argsStr.length > 50 ? `${argsStr.slice(0, 50)}...` : argsStr;
|
|
108
132
|
return themeFg("accent", toolName) + themeFg("dim", ` ${preview}`);
|
|
109
133
|
}
|
|
110
134
|
}
|
|
@@ -171,7 +195,7 @@ function getDisplayItems(messages: Message[]): DisplayItem[] {
|
|
|
171
195
|
async function mapWithConcurrencyLimit<TIn, TOut>(
|
|
172
196
|
items: TIn[],
|
|
173
197
|
concurrency: number,
|
|
174
|
-
fn: (item: TIn, index: number) => Promise<TOut
|
|
198
|
+
fn: (item: TIn, index: number) => Promise<TOut>,
|
|
175
199
|
): Promise<TOut[]> {
|
|
176
200
|
if (items.length === 0) return [];
|
|
177
201
|
const limit = Math.max(1, Math.min(concurrency, items.length));
|
|
@@ -207,7 +231,7 @@ async function runSingleAgent(
|
|
|
207
231
|
step: number | undefined,
|
|
208
232
|
signal: AbortSignal | undefined,
|
|
209
233
|
onUpdate: OnUpdateCallback | undefined,
|
|
210
|
-
makeDetails: (results: SingleResult[]) => SubagentDetails
|
|
234
|
+
makeDetails: (results: SingleResult[]) => SubagentDetails,
|
|
211
235
|
): Promise<SingleResult> {
|
|
212
236
|
const agent = agents.find((a) => a.name === agentName);
|
|
213
237
|
|
|
@@ -270,7 +294,11 @@ async function runSingleAgent(
|
|
|
270
294
|
const processLine = (line: string) => {
|
|
271
295
|
if (!line.trim()) return;
|
|
272
296
|
let event: any;
|
|
273
|
-
try {
|
|
297
|
+
try {
|
|
298
|
+
event = JSON.parse(line);
|
|
299
|
+
} catch {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
274
302
|
|
|
275
303
|
if (event.type === "message_end" && event.message) {
|
|
276
304
|
const msg = event.message as Message;
|
|
@@ -307,20 +335,26 @@ async function runSingleAgent(
|
|
|
307
335
|
for (const line of lines) processLine(line);
|
|
308
336
|
});
|
|
309
337
|
|
|
310
|
-
proc.stderr.on("data", (data) => {
|
|
338
|
+
proc.stderr.on("data", (data) => {
|
|
339
|
+
currentResult.stderr += data.toString();
|
|
340
|
+
});
|
|
311
341
|
|
|
312
342
|
proc.on("close", (code) => {
|
|
313
343
|
if (buffer.trim()) processLine(buffer);
|
|
314
344
|
resolve(code ?? 0);
|
|
315
345
|
});
|
|
316
346
|
|
|
317
|
-
proc.on("error", () => {
|
|
347
|
+
proc.on("error", () => {
|
|
348
|
+
resolve(1);
|
|
349
|
+
});
|
|
318
350
|
|
|
319
351
|
if (signal) {
|
|
320
352
|
const killProc = () => {
|
|
321
353
|
wasAborted = true;
|
|
322
354
|
proc.kill("SIGTERM");
|
|
323
|
-
setTimeout(() => {
|
|
355
|
+
setTimeout(() => {
|
|
356
|
+
if (!proc.killed) proc.kill("SIGKILL");
|
|
357
|
+
}, 5000);
|
|
324
358
|
};
|
|
325
359
|
if (signal.aborted) killProc();
|
|
326
360
|
else signal.addEventListener("abort", killProc, { once: true });
|
|
@@ -331,8 +365,18 @@ async function runSingleAgent(
|
|
|
331
365
|
if (wasAborted) throw new Error("Subagent was aborted");
|
|
332
366
|
return currentResult;
|
|
333
367
|
} finally {
|
|
334
|
-
if (tmpPromptPath)
|
|
335
|
-
|
|
368
|
+
if (tmpPromptPath)
|
|
369
|
+
try {
|
|
370
|
+
fs.unlinkSync(tmpPromptPath);
|
|
371
|
+
} catch {
|
|
372
|
+
/* ignore */
|
|
373
|
+
}
|
|
374
|
+
if (tmpPromptDir)
|
|
375
|
+
try {
|
|
376
|
+
fs.rmdirSync(tmpPromptDir);
|
|
377
|
+
} catch {
|
|
378
|
+
/* ignore */
|
|
379
|
+
}
|
|
336
380
|
}
|
|
337
381
|
}
|
|
338
382
|
|
|
@@ -359,7 +403,9 @@ const SubagentParams = Type.Object({
|
|
|
359
403
|
tasks: Type.Optional(Type.Array(TaskItem, { description: "Array of {agent, task} for parallel execution" })),
|
|
360
404
|
chain: Type.Optional(Type.Array(ChainItem, { description: "Array of {agent, task} for sequential execution" })),
|
|
361
405
|
agentScope: Type.Optional(AgentScopeSchema),
|
|
362
|
-
confirmProjectAgents: Type.Optional(
|
|
406
|
+
confirmProjectAgents: Type.Optional(
|
|
407
|
+
Type.Boolean({ description: "Prompt before running project-local agents. Default: true.", default: true }),
|
|
408
|
+
),
|
|
363
409
|
cwd: Type.Optional(Type.String({ description: "Working directory for the agent process (single mode)" })),
|
|
364
410
|
});
|
|
365
411
|
|
|
@@ -397,13 +443,26 @@ const factory: CustomToolFactory = (pi) => {
|
|
|
397
443
|
const hasSingle = Boolean(params.agent && params.task);
|
|
398
444
|
const modeCount = Number(hasChain) + Number(hasTasks) + Number(hasSingle);
|
|
399
445
|
|
|
400
|
-
const makeDetails =
|
|
401
|
-
mode
|
|
402
|
-
|
|
446
|
+
const makeDetails =
|
|
447
|
+
(mode: "single" | "parallel" | "chain") =>
|
|
448
|
+
(results: SingleResult[]): SubagentDetails => ({
|
|
449
|
+
mode,
|
|
450
|
+
agentScope,
|
|
451
|
+
projectAgentsDir: discovery.projectAgentsDir,
|
|
452
|
+
results,
|
|
453
|
+
});
|
|
403
454
|
|
|
404
455
|
if (modeCount !== 1) {
|
|
405
456
|
const available = agents.map((a) => `${a.name} (${a.source})`).join(", ") || "none";
|
|
406
|
-
return {
|
|
457
|
+
return {
|
|
458
|
+
content: [
|
|
459
|
+
{
|
|
460
|
+
type: "text",
|
|
461
|
+
text: `Invalid parameters. Provide exactly one mode.\nAvailable agents: ${available}`,
|
|
462
|
+
},
|
|
463
|
+
],
|
|
464
|
+
details: makeDetails("single")([]),
|
|
465
|
+
};
|
|
407
466
|
}
|
|
408
467
|
|
|
409
468
|
if ((agentScope === "project" || agentScope === "both") && confirmProjectAgents && pi.hasUI) {
|
|
@@ -419,51 +478,88 @@ const factory: CustomToolFactory = (pi) => {
|
|
|
419
478
|
if (projectAgentsRequested.length > 0) {
|
|
420
479
|
const names = projectAgentsRequested.map((a) => a.name).join(", ");
|
|
421
480
|
const dir = discovery.projectAgentsDir ?? "(unknown)";
|
|
422
|
-
const ok = await pi.ui.confirm(
|
|
423
|
-
|
|
481
|
+
const ok = await pi.ui.confirm(
|
|
482
|
+
"Run project-local agents?",
|
|
483
|
+
`Agents: ${names}\nSource: ${dir}\n\nProject agents are repo-controlled. Only continue for trusted repositories.`,
|
|
484
|
+
);
|
|
485
|
+
if (!ok)
|
|
486
|
+
return {
|
|
487
|
+
content: [{ type: "text", text: "Canceled: project-local agents not approved." }],
|
|
488
|
+
details: makeDetails(hasChain ? "chain" : hasTasks ? "parallel" : "single")([]),
|
|
489
|
+
};
|
|
424
490
|
}
|
|
425
491
|
}
|
|
426
492
|
|
|
427
493
|
if (params.chain && params.chain.length > 0) {
|
|
428
494
|
const results: SingleResult[] = [];
|
|
429
495
|
let previousOutput = "";
|
|
430
|
-
|
|
496
|
+
|
|
431
497
|
for (let i = 0; i < params.chain.length; i++) {
|
|
432
498
|
const step = params.chain[i];
|
|
433
499
|
const taskWithContext = step.task.replace(/\{previous\}/g, previousOutput);
|
|
434
|
-
|
|
500
|
+
|
|
435
501
|
// Create update callback that includes all previous results
|
|
436
|
-
const chainUpdate: OnUpdateCallback | undefined = onUpdate
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
502
|
+
const chainUpdate: OnUpdateCallback | undefined = onUpdate
|
|
503
|
+
? (partial) => {
|
|
504
|
+
// Combine completed results with current streaming result
|
|
505
|
+
const currentResult = partial.details?.results[0];
|
|
506
|
+
if (currentResult) {
|
|
507
|
+
const allResults = [...results, currentResult];
|
|
508
|
+
onUpdate({
|
|
509
|
+
content: partial.content,
|
|
510
|
+
details: makeDetails("chain")(allResults),
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
: undefined;
|
|
515
|
+
|
|
516
|
+
const result = await runSingleAgent(
|
|
517
|
+
pi,
|
|
518
|
+
agents,
|
|
519
|
+
step.agent,
|
|
520
|
+
taskWithContext,
|
|
521
|
+
step.cwd,
|
|
522
|
+
i + 1,
|
|
523
|
+
signal,
|
|
524
|
+
chainUpdate,
|
|
525
|
+
makeDetails("chain"),
|
|
526
|
+
);
|
|
449
527
|
results.push(result);
|
|
450
|
-
|
|
451
|
-
const isError =
|
|
528
|
+
|
|
529
|
+
const isError =
|
|
530
|
+
result.exitCode !== 0 || result.stopReason === "error" || result.stopReason === "aborted";
|
|
452
531
|
if (isError) {
|
|
453
|
-
const errorMsg =
|
|
454
|
-
|
|
532
|
+
const errorMsg =
|
|
533
|
+
result.errorMessage || result.stderr || getFinalOutput(result.messages) || "(no output)";
|
|
534
|
+
return {
|
|
535
|
+
content: [{ type: "text", text: `Chain stopped at step ${i + 1} (${step.agent}): ${errorMsg}` }],
|
|
536
|
+
details: makeDetails("chain")(results),
|
|
537
|
+
isError: true,
|
|
538
|
+
};
|
|
455
539
|
}
|
|
456
540
|
previousOutput = getFinalOutput(result.messages);
|
|
457
541
|
}
|
|
458
|
-
return {
|
|
542
|
+
return {
|
|
543
|
+
content: [{ type: "text", text: getFinalOutput(results[results.length - 1].messages) || "(no output)" }],
|
|
544
|
+
details: makeDetails("chain")(results),
|
|
545
|
+
};
|
|
459
546
|
}
|
|
460
547
|
|
|
461
548
|
if (params.tasks && params.tasks.length > 0) {
|
|
462
|
-
if (params.tasks.length > MAX_PARALLEL_TASKS)
|
|
463
|
-
|
|
549
|
+
if (params.tasks.length > MAX_PARALLEL_TASKS)
|
|
550
|
+
return {
|
|
551
|
+
content: [
|
|
552
|
+
{
|
|
553
|
+
type: "text",
|
|
554
|
+
text: `Too many parallel tasks (${params.tasks.length}). Max is ${MAX_PARALLEL_TASKS}.`,
|
|
555
|
+
},
|
|
556
|
+
],
|
|
557
|
+
details: makeDetails("parallel")([]),
|
|
558
|
+
};
|
|
559
|
+
|
|
464
560
|
// Track all results for streaming updates
|
|
465
561
|
const allResults: SingleResult[] = new Array(params.tasks.length);
|
|
466
|
-
|
|
562
|
+
|
|
467
563
|
// Initialize placeholder results
|
|
468
564
|
for (let i = 0; i < params.tasks.length; i++) {
|
|
469
565
|
allResults[i] = {
|
|
@@ -476,21 +572,29 @@ const factory: CustomToolFactory = (pi) => {
|
|
|
476
572
|
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 },
|
|
477
573
|
};
|
|
478
574
|
}
|
|
479
|
-
|
|
575
|
+
|
|
480
576
|
const emitParallelUpdate = () => {
|
|
481
577
|
if (onUpdate) {
|
|
482
|
-
const running = allResults.filter(r => r.exitCode === -1).length;
|
|
483
|
-
const done = allResults.filter(r => r.exitCode !== -1).length;
|
|
578
|
+
const running = allResults.filter((r) => r.exitCode === -1).length;
|
|
579
|
+
const done = allResults.filter((r) => r.exitCode !== -1).length;
|
|
484
580
|
onUpdate({
|
|
485
|
-
content: [
|
|
581
|
+
content: [
|
|
582
|
+
{ type: "text", text: `Parallel: ${done}/${allResults.length} done, ${running} running...` },
|
|
583
|
+
],
|
|
486
584
|
details: makeDetails("parallel")([...allResults]),
|
|
487
585
|
});
|
|
488
586
|
}
|
|
489
587
|
};
|
|
490
|
-
|
|
588
|
+
|
|
491
589
|
const results = await mapWithConcurrencyLimit(params.tasks, MAX_CONCURRENCY, async (t, index) => {
|
|
492
590
|
const result = await runSingleAgent(
|
|
493
|
-
pi,
|
|
591
|
+
pi,
|
|
592
|
+
agents,
|
|
593
|
+
t.agent,
|
|
594
|
+
t.task,
|
|
595
|
+
t.cwd,
|
|
596
|
+
undefined,
|
|
597
|
+
signal,
|
|
494
598
|
// Per-task update callback
|
|
495
599
|
(partial) => {
|
|
496
600
|
if (partial.details?.results[0]) {
|
|
@@ -498,63 +602,106 @@ const factory: CustomToolFactory = (pi) => {
|
|
|
498
602
|
emitParallelUpdate();
|
|
499
603
|
}
|
|
500
604
|
},
|
|
501
|
-
makeDetails("parallel")
|
|
605
|
+
makeDetails("parallel"),
|
|
502
606
|
);
|
|
503
607
|
allResults[index] = result;
|
|
504
608
|
emitParallelUpdate();
|
|
505
609
|
return result;
|
|
506
610
|
});
|
|
507
|
-
|
|
611
|
+
|
|
508
612
|
const successCount = results.filter((r) => r.exitCode === 0).length;
|
|
509
613
|
const summaries = results.map((r) => {
|
|
510
614
|
const output = getFinalOutput(r.messages);
|
|
511
615
|
const preview = output.slice(0, 100) + (output.length > 100 ? "..." : "");
|
|
512
616
|
return `[${r.agent}] ${r.exitCode === 0 ? "completed" : "failed"}: ${preview || "(no output)"}`;
|
|
513
617
|
});
|
|
514
|
-
return {
|
|
618
|
+
return {
|
|
619
|
+
content: [
|
|
620
|
+
{
|
|
621
|
+
type: "text",
|
|
622
|
+
text: `Parallel: ${successCount}/${results.length} succeeded\n\n${summaries.join("\n\n")}`,
|
|
623
|
+
},
|
|
624
|
+
],
|
|
625
|
+
details: makeDetails("parallel")(results),
|
|
626
|
+
};
|
|
515
627
|
}
|
|
516
628
|
|
|
517
629
|
if (params.agent && params.task) {
|
|
518
|
-
const result = await runSingleAgent(
|
|
630
|
+
const result = await runSingleAgent(
|
|
631
|
+
pi,
|
|
632
|
+
agents,
|
|
633
|
+
params.agent,
|
|
634
|
+
params.task,
|
|
635
|
+
params.cwd,
|
|
636
|
+
undefined,
|
|
637
|
+
signal,
|
|
638
|
+
onUpdate,
|
|
639
|
+
makeDetails("single"),
|
|
640
|
+
);
|
|
519
641
|
const isError = result.exitCode !== 0 || result.stopReason === "error" || result.stopReason === "aborted";
|
|
520
642
|
if (isError) {
|
|
521
|
-
const errorMsg =
|
|
522
|
-
|
|
643
|
+
const errorMsg =
|
|
644
|
+
result.errorMessage || result.stderr || getFinalOutput(result.messages) || "(no output)";
|
|
645
|
+
return {
|
|
646
|
+
content: [{ type: "text", text: `Agent ${result.stopReason || "failed"}: ${errorMsg}` }],
|
|
647
|
+
details: makeDetails("single")([result]),
|
|
648
|
+
isError: true,
|
|
649
|
+
};
|
|
523
650
|
}
|
|
524
|
-
return {
|
|
651
|
+
return {
|
|
652
|
+
content: [{ type: "text", text: getFinalOutput(result.messages) || "(no output)" }],
|
|
653
|
+
details: makeDetails("single")([result]),
|
|
654
|
+
};
|
|
525
655
|
}
|
|
526
656
|
|
|
527
657
|
const available = agents.map((a) => `${a.name} (${a.source})`).join(", ") || "none";
|
|
528
|
-
return {
|
|
658
|
+
return {
|
|
659
|
+
content: [{ type: "text", text: `Invalid parameters. Available agents: ${available}` }],
|
|
660
|
+
details: makeDetails("single")([]),
|
|
661
|
+
};
|
|
529
662
|
},
|
|
530
663
|
|
|
531
664
|
renderCall(args, theme) {
|
|
532
665
|
const scope: AgentScope = args.agentScope ?? "user";
|
|
533
666
|
if (args.chain && args.chain.length > 0) {
|
|
534
|
-
let text =
|
|
667
|
+
let text =
|
|
668
|
+
theme.fg("toolTitle", theme.bold("subagent ")) +
|
|
669
|
+
theme.fg("accent", `chain (${args.chain.length} steps)`) +
|
|
670
|
+
theme.fg("muted", ` [${scope}]`);
|
|
535
671
|
for (let i = 0; i < Math.min(args.chain.length, 3); i++) {
|
|
536
672
|
const step = args.chain[i];
|
|
537
673
|
// Clean up {previous} placeholder for display
|
|
538
674
|
const cleanTask = step.task.replace(/\{previous\}/g, "").trim();
|
|
539
|
-
const preview = cleanTask.length > 40 ? cleanTask.slice(0, 40)
|
|
540
|
-
text +=
|
|
675
|
+
const preview = cleanTask.length > 40 ? `${cleanTask.slice(0, 40)}...` : cleanTask;
|
|
676
|
+
text +=
|
|
677
|
+
"\n " +
|
|
678
|
+
theme.fg("muted", `${i + 1}.`) +
|
|
679
|
+
" " +
|
|
680
|
+
theme.fg("accent", step.agent) +
|
|
681
|
+
theme.fg("dim", ` ${preview}`);
|
|
541
682
|
}
|
|
542
|
-
if (args.chain.length > 3) text +=
|
|
683
|
+
if (args.chain.length > 3) text += `\n ${theme.fg("muted", `... +${args.chain.length - 3} more`)}`;
|
|
543
684
|
return new Text(text, 0, 0);
|
|
544
685
|
}
|
|
545
686
|
if (args.tasks && args.tasks.length > 0) {
|
|
546
|
-
let text =
|
|
687
|
+
let text =
|
|
688
|
+
theme.fg("toolTitle", theme.bold("subagent ")) +
|
|
689
|
+
theme.fg("accent", `parallel (${args.tasks.length} tasks)`) +
|
|
690
|
+
theme.fg("muted", ` [${scope}]`);
|
|
547
691
|
for (const t of args.tasks.slice(0, 3)) {
|
|
548
|
-
const preview = t.task.length > 40 ? t.task.slice(0, 40)
|
|
549
|
-
text +=
|
|
692
|
+
const preview = t.task.length > 40 ? `${t.task.slice(0, 40)}...` : t.task;
|
|
693
|
+
text += `\n ${theme.fg("accent", t.agent)}${theme.fg("dim", ` ${preview}`)}`;
|
|
550
694
|
}
|
|
551
|
-
if (args.tasks.length > 3) text +=
|
|
695
|
+
if (args.tasks.length > 3) text += `\n ${theme.fg("muted", `... +${args.tasks.length - 3} more`)}`;
|
|
552
696
|
return new Text(text, 0, 0);
|
|
553
697
|
}
|
|
554
698
|
const agentName = args.agent || "...";
|
|
555
|
-
const preview = args.task ? (args.task.length > 60 ? args.task.slice(0, 60)
|
|
556
|
-
let text =
|
|
557
|
-
|
|
699
|
+
const preview = args.task ? (args.task.length > 60 ? `${args.task.slice(0, 60)}...` : args.task) : "...";
|
|
700
|
+
let text =
|
|
701
|
+
theme.fg("toolTitle", theme.bold("subagent ")) +
|
|
702
|
+
theme.fg("accent", agentName) +
|
|
703
|
+
theme.fg("muted", ` [${scope}]`);
|
|
704
|
+
text += `\n ${theme.fg("dim", preview)}`;
|
|
558
705
|
return new Text(text, 0, 0);
|
|
559
706
|
},
|
|
560
707
|
|
|
@@ -575,9 +722,9 @@ const factory: CustomToolFactory = (pi) => {
|
|
|
575
722
|
for (const item of toShow) {
|
|
576
723
|
if (item.type === "text") {
|
|
577
724
|
const preview = expanded ? item.text : item.text.split("\n").slice(0, 3).join("\n");
|
|
578
|
-
text += theme.fg("toolOutput", preview)
|
|
725
|
+
text += `${theme.fg("toolOutput", preview)}\n`;
|
|
579
726
|
} else {
|
|
580
|
-
text += theme.fg("muted", "→ ") + formatToolCall(item.name, item.args, theme.fg.bind(theme))
|
|
727
|
+
text += `${theme.fg("muted", "→ ") + formatToolCall(item.name, item.args, theme.fg.bind(theme))}\n`;
|
|
581
728
|
}
|
|
582
729
|
}
|
|
583
730
|
return text.trimEnd();
|
|
@@ -592,10 +739,11 @@ const factory: CustomToolFactory = (pi) => {
|
|
|
592
739
|
|
|
593
740
|
if (expanded) {
|
|
594
741
|
const container = new Container();
|
|
595
|
-
let header = icon
|
|
596
|
-
if (isError && r.stopReason) header +=
|
|
742
|
+
let header = `${icon} ${theme.fg("toolTitle", theme.bold(r.agent))}${theme.fg("muted", ` (${r.agentSource})`)}`;
|
|
743
|
+
if (isError && r.stopReason) header += ` ${theme.fg("error", `[${r.stopReason}]`)}`;
|
|
597
744
|
container.addChild(new Text(header, 0, 0));
|
|
598
|
-
if (isError && r.errorMessage)
|
|
745
|
+
if (isError && r.errorMessage)
|
|
746
|
+
container.addChild(new Text(theme.fg("error", `Error: ${r.errorMessage}`), 0, 0));
|
|
599
747
|
container.addChild(new Spacer(1));
|
|
600
748
|
container.addChild(new Text(theme.fg("muted", "─── Task ───"), 0, 0));
|
|
601
749
|
container.addChild(new Text(theme.fg("dim", r.task), 0, 0));
|
|
@@ -605,7 +753,14 @@ const factory: CustomToolFactory = (pi) => {
|
|
|
605
753
|
container.addChild(new Text(theme.fg("muted", "(no output)"), 0, 0));
|
|
606
754
|
} else {
|
|
607
755
|
for (const item of displayItems) {
|
|
608
|
-
if (item.type === "toolCall")
|
|
756
|
+
if (item.type === "toolCall")
|
|
757
|
+
container.addChild(
|
|
758
|
+
new Text(
|
|
759
|
+
theme.fg("muted", "→ ") + formatToolCall(item.name, item.args, theme.fg.bind(theme)),
|
|
760
|
+
0,
|
|
761
|
+
0,
|
|
762
|
+
),
|
|
763
|
+
);
|
|
609
764
|
}
|
|
610
765
|
if (finalOutput) {
|
|
611
766
|
container.addChild(new Spacer(1));
|
|
@@ -613,20 +768,23 @@ const factory: CustomToolFactory = (pi) => {
|
|
|
613
768
|
}
|
|
614
769
|
}
|
|
615
770
|
const usageStr = formatUsageStats(r.usage, r.model);
|
|
616
|
-
if (usageStr) {
|
|
771
|
+
if (usageStr) {
|
|
772
|
+
container.addChild(new Spacer(1));
|
|
773
|
+
container.addChild(new Text(theme.fg("dim", usageStr), 0, 0));
|
|
774
|
+
}
|
|
617
775
|
return container;
|
|
618
776
|
}
|
|
619
777
|
|
|
620
|
-
let text = icon
|
|
621
|
-
if (isError && r.stopReason) text +=
|
|
622
|
-
if (isError && r.errorMessage) text +=
|
|
623
|
-
else if (displayItems.length === 0) text +=
|
|
778
|
+
let text = `${icon} ${theme.fg("toolTitle", theme.bold(r.agent))}${theme.fg("muted", ` (${r.agentSource})`)}`;
|
|
779
|
+
if (isError && r.stopReason) text += ` ${theme.fg("error", `[${r.stopReason}]`)}`;
|
|
780
|
+
if (isError && r.errorMessage) text += `\n${theme.fg("error", `Error: ${r.errorMessage}`)}`;
|
|
781
|
+
else if (displayItems.length === 0) text += `\n${theme.fg("muted", "(no output)")}`;
|
|
624
782
|
else {
|
|
625
|
-
text +=
|
|
626
|
-
if (displayItems.length > COLLAPSED_ITEM_COUNT) text +=
|
|
783
|
+
text += `\n${renderDisplayItems(displayItems, COLLAPSED_ITEM_COUNT)}`;
|
|
784
|
+
if (displayItems.length > COLLAPSED_ITEM_COUNT) text += `\n${theme.fg("muted", "(Ctrl+O to expand)")}`;
|
|
627
785
|
}
|
|
628
786
|
const usageStr = formatUsageStats(r.usage, r.model);
|
|
629
|
-
if (usageStr) text +=
|
|
787
|
+
if (usageStr) text += `\n${theme.fg("dim", usageStr)}`;
|
|
630
788
|
return new Text(text, 0, 0);
|
|
631
789
|
}
|
|
632
790
|
|
|
@@ -646,37 +804,58 @@ const factory: CustomToolFactory = (pi) => {
|
|
|
646
804
|
if (details.mode === "chain") {
|
|
647
805
|
const successCount = details.results.filter((r) => r.exitCode === 0).length;
|
|
648
806
|
const icon = successCount === details.results.length ? theme.fg("success", "✓") : theme.fg("error", "✗");
|
|
649
|
-
|
|
807
|
+
|
|
650
808
|
if (expanded) {
|
|
651
809
|
const container = new Container();
|
|
652
|
-
container.addChild(
|
|
653
|
-
|
|
810
|
+
container.addChild(
|
|
811
|
+
new Text(
|
|
812
|
+
icon +
|
|
813
|
+
" " +
|
|
814
|
+
theme.fg("toolTitle", theme.bold("chain ")) +
|
|
815
|
+
theme.fg("accent", `${successCount}/${details.results.length} steps`),
|
|
816
|
+
0,
|
|
817
|
+
0,
|
|
818
|
+
),
|
|
819
|
+
);
|
|
820
|
+
|
|
654
821
|
for (const r of details.results) {
|
|
655
822
|
const rIcon = r.exitCode === 0 ? theme.fg("success", "✓") : theme.fg("error", "✗");
|
|
656
823
|
const displayItems = getDisplayItems(r.messages);
|
|
657
824
|
const finalOutput = getFinalOutput(r.messages);
|
|
658
|
-
|
|
825
|
+
|
|
659
826
|
container.addChild(new Spacer(1));
|
|
660
|
-
container.addChild(
|
|
827
|
+
container.addChild(
|
|
828
|
+
new Text(
|
|
829
|
+
`${theme.fg("muted", `─── Step ${r.step}: `) + theme.fg("accent", r.agent)} ${rIcon}`,
|
|
830
|
+
0,
|
|
831
|
+
0,
|
|
832
|
+
),
|
|
833
|
+
);
|
|
661
834
|
container.addChild(new Text(theme.fg("muted", "Task: ") + theme.fg("dim", r.task), 0, 0));
|
|
662
|
-
|
|
835
|
+
|
|
663
836
|
// Show tool calls
|
|
664
837
|
for (const item of displayItems) {
|
|
665
838
|
if (item.type === "toolCall") {
|
|
666
|
-
container.addChild(
|
|
839
|
+
container.addChild(
|
|
840
|
+
new Text(
|
|
841
|
+
theme.fg("muted", "→ ") + formatToolCall(item.name, item.args, theme.fg.bind(theme)),
|
|
842
|
+
0,
|
|
843
|
+
0,
|
|
844
|
+
),
|
|
845
|
+
);
|
|
667
846
|
}
|
|
668
847
|
}
|
|
669
|
-
|
|
848
|
+
|
|
670
849
|
// Show final output as markdown
|
|
671
850
|
if (finalOutput) {
|
|
672
851
|
container.addChild(new Spacer(1));
|
|
673
852
|
container.addChild(new Markdown(finalOutput.trim(), 0, 0, mdTheme));
|
|
674
853
|
}
|
|
675
|
-
|
|
854
|
+
|
|
676
855
|
const stepUsage = formatUsageStats(r.usage, r.model);
|
|
677
856
|
if (stepUsage) container.addChild(new Text(theme.fg("dim", stepUsage), 0, 0));
|
|
678
857
|
}
|
|
679
|
-
|
|
858
|
+
|
|
680
859
|
const usageStr = formatUsageStats(aggregateUsage(details.results));
|
|
681
860
|
if (usageStr) {
|
|
682
861
|
container.addChild(new Spacer(1));
|
|
@@ -684,19 +863,23 @@ const factory: CustomToolFactory = (pi) => {
|
|
|
684
863
|
}
|
|
685
864
|
return container;
|
|
686
865
|
}
|
|
687
|
-
|
|
866
|
+
|
|
688
867
|
// Collapsed view
|
|
689
|
-
let text =
|
|
868
|
+
let text =
|
|
869
|
+
icon +
|
|
870
|
+
" " +
|
|
871
|
+
theme.fg("toolTitle", theme.bold("chain ")) +
|
|
872
|
+
theme.fg("accent", `${successCount}/${details.results.length} steps`);
|
|
690
873
|
for (const r of details.results) {
|
|
691
874
|
const rIcon = r.exitCode === 0 ? theme.fg("success", "✓") : theme.fg("error", "✗");
|
|
692
875
|
const displayItems = getDisplayItems(r.messages);
|
|
693
|
-
text +=
|
|
694
|
-
if (displayItems.length === 0) text +=
|
|
695
|
-
else text +=
|
|
876
|
+
text += `\n\n${theme.fg("muted", `─── Step ${r.step}: `)}${theme.fg("accent", r.agent)} ${rIcon}`;
|
|
877
|
+
if (displayItems.length === 0) text += `\n${theme.fg("muted", "(no output)")}`;
|
|
878
|
+
else text += `\n${renderDisplayItems(displayItems, 5)}`;
|
|
696
879
|
}
|
|
697
880
|
const usageStr = formatUsageStats(aggregateUsage(details.results));
|
|
698
|
-
if (usageStr) text +=
|
|
699
|
-
text +=
|
|
881
|
+
if (usageStr) text += `\n\n${theme.fg("dim", `Total: ${usageStr}`)}`;
|
|
882
|
+
text += `\n${theme.fg("muted", "(Ctrl+O to expand)")}`;
|
|
700
883
|
return new Text(text, 0, 0);
|
|
701
884
|
}
|
|
702
885
|
|
|
@@ -705,41 +888,59 @@ const factory: CustomToolFactory = (pi) => {
|
|
|
705
888
|
const successCount = details.results.filter((r) => r.exitCode === 0).length;
|
|
706
889
|
const failCount = details.results.filter((r) => r.exitCode > 0).length;
|
|
707
890
|
const isRunning = running > 0;
|
|
708
|
-
const icon = isRunning
|
|
709
|
-
|
|
891
|
+
const icon = isRunning
|
|
892
|
+
? theme.fg("warning", "⏳")
|
|
893
|
+
: failCount > 0
|
|
894
|
+
? theme.fg("warning", "◐")
|
|
895
|
+
: theme.fg("success", "✓");
|
|
896
|
+
const status = isRunning
|
|
710
897
|
? `${successCount + failCount}/${details.results.length} done, ${running} running`
|
|
711
898
|
: `${successCount}/${details.results.length} tasks`;
|
|
712
|
-
|
|
899
|
+
|
|
713
900
|
if (expanded && !isRunning) {
|
|
714
901
|
const container = new Container();
|
|
715
|
-
container.addChild(
|
|
716
|
-
|
|
902
|
+
container.addChild(
|
|
903
|
+
new Text(
|
|
904
|
+
`${icon} ${theme.fg("toolTitle", theme.bold("parallel "))}${theme.fg("accent", status)}`,
|
|
905
|
+
0,
|
|
906
|
+
0,
|
|
907
|
+
),
|
|
908
|
+
);
|
|
909
|
+
|
|
717
910
|
for (const r of details.results) {
|
|
718
911
|
const rIcon = r.exitCode === 0 ? theme.fg("success", "✓") : theme.fg("error", "✗");
|
|
719
912
|
const displayItems = getDisplayItems(r.messages);
|
|
720
913
|
const finalOutput = getFinalOutput(r.messages);
|
|
721
|
-
|
|
914
|
+
|
|
722
915
|
container.addChild(new Spacer(1));
|
|
723
|
-
container.addChild(
|
|
916
|
+
container.addChild(
|
|
917
|
+
new Text(`${theme.fg("muted", "─── ") + theme.fg("accent", r.agent)} ${rIcon}`, 0, 0),
|
|
918
|
+
);
|
|
724
919
|
container.addChild(new Text(theme.fg("muted", "Task: ") + theme.fg("dim", r.task), 0, 0));
|
|
725
|
-
|
|
920
|
+
|
|
726
921
|
// Show tool calls
|
|
727
922
|
for (const item of displayItems) {
|
|
728
923
|
if (item.type === "toolCall") {
|
|
729
|
-
container.addChild(
|
|
924
|
+
container.addChild(
|
|
925
|
+
new Text(
|
|
926
|
+
theme.fg("muted", "→ ") + formatToolCall(item.name, item.args, theme.fg.bind(theme)),
|
|
927
|
+
0,
|
|
928
|
+
0,
|
|
929
|
+
),
|
|
930
|
+
);
|
|
730
931
|
}
|
|
731
932
|
}
|
|
732
|
-
|
|
933
|
+
|
|
733
934
|
// Show final output as markdown
|
|
734
935
|
if (finalOutput) {
|
|
735
936
|
container.addChild(new Spacer(1));
|
|
736
937
|
container.addChild(new Markdown(finalOutput.trim(), 0, 0, mdTheme));
|
|
737
938
|
}
|
|
738
|
-
|
|
939
|
+
|
|
739
940
|
const taskUsage = formatUsageStats(r.usage, r.model);
|
|
740
941
|
if (taskUsage) container.addChild(new Text(theme.fg("dim", taskUsage), 0, 0));
|
|
741
942
|
}
|
|
742
|
-
|
|
943
|
+
|
|
743
944
|
const usageStr = formatUsageStats(aggregateUsage(details.results));
|
|
744
945
|
if (usageStr) {
|
|
745
946
|
container.addChild(new Spacer(1));
|
|
@@ -747,21 +948,27 @@ const factory: CustomToolFactory = (pi) => {
|
|
|
747
948
|
}
|
|
748
949
|
return container;
|
|
749
950
|
}
|
|
750
|
-
|
|
951
|
+
|
|
751
952
|
// Collapsed view (or still running)
|
|
752
|
-
let text = icon
|
|
953
|
+
let text = `${icon} ${theme.fg("toolTitle", theme.bold("parallel "))}${theme.fg("accent", status)}`;
|
|
753
954
|
for (const r of details.results) {
|
|
754
|
-
const rIcon =
|
|
955
|
+
const rIcon =
|
|
956
|
+
r.exitCode === -1
|
|
957
|
+
? theme.fg("warning", "⏳")
|
|
958
|
+
: r.exitCode === 0
|
|
959
|
+
? theme.fg("success", "✓")
|
|
960
|
+
: theme.fg("error", "✗");
|
|
755
961
|
const displayItems = getDisplayItems(r.messages);
|
|
756
|
-
text +=
|
|
757
|
-
if (displayItems.length === 0)
|
|
758
|
-
|
|
962
|
+
text += `\n\n${theme.fg("muted", "─── ")}${theme.fg("accent", r.agent)} ${rIcon}`;
|
|
963
|
+
if (displayItems.length === 0)
|
|
964
|
+
text += `\n${theme.fg("muted", r.exitCode === -1 ? "(running...)" : "(no output)")}`;
|
|
965
|
+
else text += `\n${renderDisplayItems(displayItems, 5)}`;
|
|
759
966
|
}
|
|
760
967
|
if (!isRunning) {
|
|
761
968
|
const usageStr = formatUsageStats(aggregateUsage(details.results));
|
|
762
|
-
if (usageStr) text +=
|
|
969
|
+
if (usageStr) text += `\n\n${theme.fg("dim", `Total: ${usageStr}`)}`;
|
|
763
970
|
}
|
|
764
|
-
if (!expanded) text +=
|
|
971
|
+
if (!expanded) text += `\n${theme.fg("muted", "(Ctrl+O to expand)")}`;
|
|
765
972
|
return new Text(text, 0, 0);
|
|
766
973
|
}
|
|
767
974
|
|