@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.
Files changed (96) hide show
  1. package/CHANGELOG.md +37 -1
  2. package/README.md +17 -18
  3. package/dist/cli/list-models.d.ts +2 -2
  4. package/dist/cli/list-models.d.ts.map +1 -1
  5. package/dist/cli/list-models.js +2 -7
  6. package/dist/cli/list-models.js.map +1 -1
  7. package/dist/config.d.ts +2 -2
  8. package/dist/config.d.ts.map +1 -1
  9. package/dist/config.js +3 -3
  10. package/dist/config.js.map +1 -1
  11. package/dist/core/agent-session.d.ts +6 -3
  12. package/dist/core/agent-session.d.ts.map +1 -1
  13. package/dist/core/agent-session.js +23 -25
  14. package/dist/core/agent-session.js.map +1 -1
  15. package/dist/core/auth-storage.d.ts +104 -0
  16. package/dist/core/auth-storage.d.ts.map +1 -0
  17. package/dist/core/auth-storage.js +232 -0
  18. package/dist/core/auth-storage.js.map +1 -0
  19. package/dist/core/custom-tools/types.d.ts +2 -2
  20. package/dist/core/custom-tools/types.d.ts.map +1 -1
  21. package/dist/core/custom-tools/types.js.map +1 -1
  22. package/dist/core/hooks/types.d.ts +3 -3
  23. package/dist/core/hooks/types.d.ts.map +1 -1
  24. package/dist/core/hooks/types.js.map +1 -1
  25. package/dist/core/model-registry.d.ts +50 -0
  26. package/dist/core/model-registry.d.ts.map +1 -0
  27. package/dist/core/model-registry.js +268 -0
  28. package/dist/core/model-registry.js.map +1 -0
  29. package/dist/core/model-resolver.d.ts +7 -7
  30. package/dist/core/model-resolver.d.ts.map +1 -1
  31. package/dist/core/model-resolver.js +12 -44
  32. package/dist/core/model-resolver.js.map +1 -1
  33. package/dist/core/sdk.d.ts +13 -26
  34. package/dist/core/sdk.d.ts.map +1 -1
  35. package/dist/core/sdk.js +24 -101
  36. package/dist/core/sdk.js.map +1 -1
  37. package/dist/core/settings-manager.d.ts +0 -5
  38. package/dist/core/settings-manager.d.ts.map +1 -1
  39. package/dist/core/settings-manager.js +0 -19
  40. package/dist/core/settings-manager.js.map +1 -1
  41. package/dist/core/skills.d.ts.map +1 -1
  42. package/dist/core/skills.js +15 -1
  43. package/dist/core/skills.js.map +1 -1
  44. package/dist/index.d.ts +3 -3
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.js +4 -8
  47. package/dist/index.js.map +1 -1
  48. package/dist/main.d.ts.map +1 -1
  49. package/dist/main.js +37 -22
  50. package/dist/main.js.map +1 -1
  51. package/dist/modes/interactive/components/footer.d.ts +3 -1
  52. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  53. package/dist/modes/interactive/components/footer.js +4 -3
  54. package/dist/modes/interactive/components/footer.js.map +1 -1
  55. package/dist/modes/interactive/components/model-selector.d.ts +3 -1
  56. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  57. package/dist/modes/interactive/components/model-selector.js +21 -14
  58. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  59. package/dist/modes/interactive/components/oauth-selector.d.ts +3 -1
  60. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  61. package/dist/modes/interactive/components/oauth-selector.js +6 -6
  62. package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  63. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  64. package/dist/modes/interactive/interactive-mode.js +56 -51
  65. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  66. package/docs/custom-tools.md +3 -3
  67. package/docs/hooks.md +9 -9
  68. package/docs/sdk.md +86 -61
  69. package/examples/custom-tools/hello/index.ts +15 -15
  70. package/examples/custom-tools/question/index.ts +3 -3
  71. package/examples/custom-tools/subagent/agents.ts +1 -2
  72. package/examples/custom-tools/subagent/index.ts +332 -125
  73. package/examples/custom-tools/todo/index.ts +30 -12
  74. package/examples/hooks/confirm-destructive.ts +6 -8
  75. package/examples/hooks/custom-compaction.ts +7 -7
  76. package/examples/hooks/dirty-repo-guard.ts +7 -15
  77. package/examples/hooks/permission-gate.ts +1 -5
  78. package/examples/sdk/02-custom-model.ts +20 -7
  79. package/examples/sdk/04-skills.ts +1 -1
  80. package/examples/sdk/05-tools.ts +11 -14
  81. package/examples/sdk/06-hooks.ts +1 -1
  82. package/examples/sdk/07-context-files.ts +1 -1
  83. package/examples/sdk/08-slash-commands.ts +3 -3
  84. package/examples/sdk/09-api-keys-and-oauth.ts +36 -26
  85. package/examples/sdk/10-settings.ts +2 -2
  86. package/examples/sdk/12-full-control.ts +19 -20
  87. package/examples/sdk/README.md +26 -13
  88. package/package.json +4 -5
  89. package/dist/core/model-config.d.ts +0 -58
  90. package/dist/core/model-config.d.ts.map +0 -1
  91. package/dist/core/model-config.js +0 -384
  92. package/dist/core/model-config.js.map +0 -1
  93. package/dist/core/oauth/index.d.ts +0 -41
  94. package/dist/core/oauth/index.d.ts.map +0 -1
  95. package/dist/core/oauth/index.js +0 -84
  96. 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 { getMarkdownTheme, type CustomAgentTool, type CustomToolFactory, type ToolAPI } from "@mariozechner/pi-coding-agent";
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) + "k";
34
- if (count < 1000000) return Math.round(count / 1000) + "k";
35
- return (count / 1000000).toFixed(1) + "M";
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(usage: { input: number; output: number; cacheRead: number; cacheWrite: number; cost: number; contextTokens?: number; turns?: number }, model?: string): string {
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(toolName: string, args: Record<string, unknown>, themeFg: (color: any, text: string) => string): string {
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) ? "~" + p.slice(home.length) : p;
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) + "..." : command;
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 themeFg("muted", "grep ") + themeFg("accent", `/${pattern}/`) + themeFg("dim", ` in ${shortenPath(rawPath)}`);
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) + "..." : argsStr;
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 { event = JSON.parse(line); } catch { return; }
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) => { currentResult.stderr += data.toString(); });
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", () => { resolve(1); });
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(() => { if (!proc.killed) proc.kill("SIGKILL"); }, 5000);
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) try { fs.unlinkSync(tmpPromptPath); } catch { /* ignore */ }
335
- if (tmpPromptDir) try { fs.rmdirSync(tmpPromptDir); } catch { /* ignore */ }
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(Type.Boolean({ description: "Prompt before running project-local agents. Default: true.", default: true })),
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 = (mode: "single" | "parallel" | "chain") => (results: SingleResult[]): SubagentDetails => ({
401
- mode, agentScope, projectAgentsDir: discovery.projectAgentsDir, results,
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 { content: [{ type: "text", text: `Invalid parameters. Provide exactly one mode.\nAvailable agents: ${available}` }], details: makeDetails("single")([]) };
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("Run project-local agents?", `Agents: ${names}\nSource: ${dir}\n\nProject agents are repo-controlled. Only continue for trusted repositories.`);
423
- if (!ok) return { content: [{ type: "text", text: "Canceled: project-local agents not approved." }], details: makeDetails(hasChain ? "chain" : hasTasks ? "parallel" : "single")([]) };
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 ? (partial) => {
437
- // Combine completed results with current streaming result
438
- const currentResult = partial.details?.results[0];
439
- if (currentResult) {
440
- const allResults = [...results, currentResult];
441
- onUpdate({
442
- content: partial.content,
443
- details: makeDetails("chain")(allResults),
444
- });
445
- }
446
- } : undefined;
447
-
448
- const result = await runSingleAgent(pi, agents, step.agent, taskWithContext, step.cwd, i + 1, signal, chainUpdate, makeDetails("chain"));
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 = result.exitCode !== 0 || result.stopReason === "error" || result.stopReason === "aborted";
528
+
529
+ const isError =
530
+ result.exitCode !== 0 || result.stopReason === "error" || result.stopReason === "aborted";
452
531
  if (isError) {
453
- const errorMsg = result.errorMessage || result.stderr || getFinalOutput(result.messages) || "(no output)";
454
- return { content: [{ type: "text", text: `Chain stopped at step ${i + 1} (${step.agent}): ${errorMsg}` }], details: makeDetails("chain")(results), isError: true };
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 { content: [{ type: "text", text: getFinalOutput(results[results.length - 1].messages) || "(no output)" }], details: makeDetails("chain")(results) };
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) return { content: [{ type: "text", text: `Too many parallel tasks (${params.tasks.length}). Max is ${MAX_PARALLEL_TASKS}.` }], details: makeDetails("parallel")([]) };
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: [{ type: "text", text: `Parallel: ${done}/${allResults.length} done, ${running} running...` }],
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, agents, t.agent, t.task, t.cwd, undefined, signal,
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 { content: [{ type: "text", text: `Parallel: ${successCount}/${results.length} succeeded\n\n${summaries.join("\n\n")}` }], details: makeDetails("parallel")(results) };
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(pi, agents, params.agent, params.task, params.cwd, undefined, signal, onUpdate, makeDetails("single"));
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 = result.errorMessage || result.stderr || getFinalOutput(result.messages) || "(no output)";
522
- return { content: [{ type: "text", text: `Agent ${result.stopReason || "failed"}: ${errorMsg}` }], details: makeDetails("single")([result]), isError: true };
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 { content: [{ type: "text", text: getFinalOutput(result.messages) || "(no output)" }], details: makeDetails("single")([result]) };
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 { content: [{ type: "text", text: `Invalid parameters. Available agents: ${available}` }], details: makeDetails("single")([]) };
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 = theme.fg("toolTitle", theme.bold("subagent ")) + theme.fg("accent", `chain (${args.chain.length} steps)`) + theme.fg("muted", ` [${scope}]`);
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) + "..." : cleanTask;
540
- text += "\n " + theme.fg("muted", `${i + 1}.`) + " " + theme.fg("accent", step.agent) + theme.fg("dim", ` ${preview}`);
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 += "\n " + theme.fg("muted", `... +${args.chain.length - 3} more`);
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 = theme.fg("toolTitle", theme.bold("subagent ")) + theme.fg("accent", `parallel (${args.tasks.length} tasks)`) + theme.fg("muted", ` [${scope}]`);
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) + "..." : t.task;
549
- text += "\n " + theme.fg("accent", t.agent) + theme.fg("dim", ` ${preview}`);
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 += "\n " + theme.fg("muted", `... +${args.tasks.length - 3} more`);
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) + "..." : args.task) : "...";
556
- let text = theme.fg("toolTitle", theme.bold("subagent ")) + theme.fg("accent", agentName) + theme.fg("muted", ` [${scope}]`);
557
- text += "\n " + theme.fg("dim", preview);
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) + "\n";
725
+ text += `${theme.fg("toolOutput", preview)}\n`;
579
726
  } else {
580
- text += theme.fg("muted", "→ ") + formatToolCall(item.name, item.args, theme.fg.bind(theme)) + "\n";
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 + " " + theme.fg("toolTitle", theme.bold(r.agent)) + theme.fg("muted", ` (${r.agentSource})`);
596
- if (isError && r.stopReason) header += " " + theme.fg("error", `[${r.stopReason}]`);
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) container.addChild(new Text(theme.fg("error", `Error: ${r.errorMessage}`), 0, 0));
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") container.addChild(new Text(theme.fg("muted", "→ ") + formatToolCall(item.name, item.args, theme.fg.bind(theme)), 0, 0));
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) { container.addChild(new Spacer(1)); container.addChild(new Text(theme.fg("dim", usageStr), 0, 0)); }
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 + " " + theme.fg("toolTitle", theme.bold(r.agent)) + theme.fg("muted", ` (${r.agentSource})`);
621
- if (isError && r.stopReason) text += " " + theme.fg("error", `[${r.stopReason}]`);
622
- if (isError && r.errorMessage) text += "\n" + theme.fg("error", `Error: ${r.errorMessage}`);
623
- else if (displayItems.length === 0) text += "\n" + theme.fg("muted", "(no output)");
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 += "\n" + renderDisplayItems(displayItems, COLLAPSED_ITEM_COUNT);
626
- if (displayItems.length > COLLAPSED_ITEM_COUNT) text += "\n" + theme.fg("muted", "(Ctrl+O to expand)");
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 += "\n" + theme.fg("dim", usageStr);
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(new Text(icon + " " + theme.fg("toolTitle", theme.bold("chain ")) + theme.fg("accent", `${successCount}/${details.results.length} steps`), 0, 0));
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(new Text(theme.fg("muted", `─── Step ${r.step}: `) + theme.fg("accent", r.agent) + " " + rIcon, 0, 0));
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(new Text(theme.fg("muted", "→ ") + formatToolCall(item.name, item.args, theme.fg.bind(theme)), 0, 0));
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 = icon + " " + theme.fg("toolTitle", theme.bold("chain ")) + theme.fg("accent", `${successCount}/${details.results.length} steps`);
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 += "\n\n" + theme.fg("muted", `─── Step ${r.step}: `) + theme.fg("accent", r.agent) + " " + rIcon;
694
- if (displayItems.length === 0) text += "\n" + theme.fg("muted", "(no output)");
695
- else text += "\n" + renderDisplayItems(displayItems, 5);
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 += "\n\n" + theme.fg("dim", `Total: ${usageStr}`);
699
- text += "\n" + theme.fg("muted", "(Ctrl+O to expand)");
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 ? theme.fg("warning", "⏳") : (failCount > 0 ? theme.fg("warning", "◐") : theme.fg("success", "✓"));
709
- const status = isRunning
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(new Text(icon + " " + theme.fg("toolTitle", theme.bold("parallel ")) + theme.fg("accent", status), 0, 0));
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(new Text(theme.fg("muted", "─── ") + theme.fg("accent", r.agent) + " " + rIcon, 0, 0));
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(new Text(theme.fg("muted", "→ ") + formatToolCall(item.name, item.args, theme.fg.bind(theme)), 0, 0));
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 + " " + theme.fg("toolTitle", theme.bold("parallel ")) + theme.fg("accent", status);
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 = r.exitCode === -1 ? theme.fg("warning", "⏳") : (r.exitCode === 0 ? theme.fg("success", "✓") : theme.fg("error", "✗"));
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 += "\n\n" + theme.fg("muted", "─── ") + theme.fg("accent", r.agent) + " " + rIcon;
757
- if (displayItems.length === 0) text += "\n" + theme.fg("muted", r.exitCode === -1 ? "(running...)" : "(no output)");
758
- else text += "\n" + renderDisplayItems(displayItems, 5);
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 += "\n\n" + theme.fg("dim", `Total: ${usageStr}`);
969
+ if (usageStr) text += `\n\n${theme.fg("dim", `Total: ${usageStr}`)}`;
763
970
  }
764
- if (!expanded) text += "\n" + theme.fg("muted", "(Ctrl+O to expand)");
971
+ if (!expanded) text += `\n${theme.fg("muted", "(Ctrl+O to expand)")}`;
765
972
  return new Text(text, 0, 0);
766
973
  }
767
974