@nklisch/pi-enhanced 0.2.7 → 0.3.1

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 (70) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/node_modules/@nklisch/pi-clearance/native/clearance-core.linux-x64-gnu.node +0 -0
  3. package/node_modules/@nklisch/pi-clearance/native/clearance-core.win32-x64-msvc.node +0 -0
  4. package/node_modules/@nklisch/pi-plugins/README.md +4 -2
  5. package/node_modules/@nklisch/pi-plugins/dist/hooks.js +81 -8
  6. package/node_modules/@nklisch/pi-plugins/dist/hooks.js.map +1 -1
  7. package/node_modules/@nklisch/pi-plugins/dist/mcp.js +32 -1
  8. package/node_modules/@nklisch/pi-plugins/dist/mcp.js.map +1 -1
  9. package/node_modules/@nklisch/pi-plugins/dist/pi/plugin-manager.d.ts +10 -4
  10. package/node_modules/@nklisch/pi-plugins/dist/pi/plugin-manager.js +144 -10
  11. package/node_modules/@nklisch/pi-plugins/dist/pi/plugin-manager.js.map +1 -1
  12. package/node_modules/@nklisch/pi-plugins/dist/runtime-discovery.js +40 -14
  13. package/node_modules/@nklisch/pi-plugins/dist/runtime-discovery.js.map +1 -1
  14. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/CHANGELOG.md +27 -0
  15. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/README.md +76 -47
  16. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/dist/public.d.ts +171 -140
  17. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/dist/settings.d.ts +2 -2
  18. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/FORK-MAINTENANCE.md +8 -7
  19. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/VISION.md +12 -10
  20. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/architecture/architecture.md +118 -104
  21. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/comparison-with-upstream.md +3 -3
  22. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/decisions/0004-reconsider-ui-direction.md +5 -0
  23. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/package.json +1 -1
  24. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/config/custom-agents.ts +31 -4
  25. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/config/default-agents.ts +1 -1
  26. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/config/invocation-config.ts +7 -4
  27. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/index.ts +18 -9
  28. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/layered-settings.ts +2 -2
  29. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/child-lifecycle.ts +10 -63
  30. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/concurrency-limiter.ts +122 -76
  31. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/create-subagent-session.ts +3 -5
  32. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/lifecycle-interceptor.ts +4 -3
  33. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/run-listeners.ts +2 -23
  34. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/subagent-manager.ts +177 -252
  35. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/subagent-session.ts +90 -280
  36. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/subagent-state.ts +153 -239
  37. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/subagent.ts +569 -700
  38. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/workspace.ts +1 -1
  39. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/observation/notification.ts +78 -122
  40. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/observation/renderer.ts +15 -42
  41. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/observation/subagent-events-observer.ts +44 -95
  42. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/service/service-adapter.ts +157 -90
  43. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/service/service.ts +60 -68
  44. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/session/conversation.ts +3 -3
  45. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/session/query-source.ts +28 -0
  46. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/session/query.ts +488 -0
  47. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/settings.ts +1 -1
  48. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/agent-tool.ts +171 -249
  49. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/get-result-tool.ts +51 -94
  50. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/helpers.ts +9 -6
  51. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/list-tool.ts +46 -0
  52. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/parent-tool-registry.ts +17 -0
  53. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/query-session-tool.ts +412 -0
  54. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/result-renderer.ts +27 -111
  55. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/resume-tool.ts +105 -0
  56. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/spawn-config.ts +26 -8
  57. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/steer-tool.ts +27 -111
  58. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/stop-tool.ts +53 -0
  59. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/types.ts +11 -5
  60. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/agent-widget.ts +33 -27
  61. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/display.ts +6 -3
  62. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/session-navigation.ts +121 -10
  63. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/session-navigator.ts +430 -112
  64. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/subagents-settings.ts +1 -1
  65. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/widget-renderer.ts +8 -9
  66. package/node_modules/@nklisch/pi-plugins/package.json +2 -2
  67. package/package.json +1 -1
  68. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/background-spawner.ts +0 -81
  69. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/foreground-runner.ts +0 -149
  70. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/get-result-report.ts +0 -77
@@ -59,7 +59,7 @@ export class SubagentsSettingsHandler {
59
59
 
60
60
  if (choice.startsWith("Max concurrency")) {
61
61
  const val = await ui.input(
62
- "Max concurrent background agents",
62
+ "Max concurrent subagent runs",
63
63
  String(this.settings.maxConcurrent),
64
64
  );
65
65
  if (val) {
@@ -7,6 +7,7 @@
7
7
 
8
8
  import { truncateToWidth } from "@earendil-works/pi-tui";
9
9
  import type { AgentConfigLookup } from "#src/config/agent-types";
10
+ import type { SubagentTerminalReason } from "#src/lifecycle/subagent-state";
10
11
  import type { LifetimeUsage } from "#src/lifecycle/usage";
11
12
  import { getLifetimeTotal } from "#src/lifecycle/usage";
12
13
  import type { SubagentType, ThinkingLevel } from "#src/types";
@@ -29,12 +30,14 @@ export interface WidgetAgent {
29
30
  readonly id: string;
30
31
  readonly type: SubagentType;
31
32
  readonly status: string;
33
+ readonly terminalReason?: SubagentTerminalReason;
32
34
  readonly description: string;
33
35
  readonly modelLabel: string;
34
36
  readonly thinkingLevel: ThinkingLevel;
35
37
  readonly toolUses: number;
36
38
  readonly startedAt: number;
37
39
  readonly completedAt?: number;
40
+ readonly activeRuntimeMs: number;
38
41
  readonly error?: string;
39
42
  readonly lifetimeUsage?: Readonly<LifetimeUsage>;
40
43
  readonly compactionCount: number;
@@ -57,27 +60,23 @@ export function renderFinishedLine(
57
60
  ): string {
58
61
  const name = getDisplayName(agent.type, registry);
59
62
  const modeLabel = getPromptModeLabel(agent.type, registry);
60
- const duration = formatMs((agent.completedAt ?? Date.now()) - agent.startedAt);
63
+ const duration = formatMs(agent.activeRuntimeMs);
61
64
 
62
65
  let icon: string;
63
66
  let statusText: string;
64
67
  if (agent.status === "completed") {
65
68
  icon = theme.fg("success", "✓");
66
69
  statusText = "";
67
- } else if (agent.status === "steered") {
68
- icon = theme.fg("warning", "✓");
69
- statusText = theme.fg("warning", " (turn limit)");
70
70
  } else if (agent.status === "stopped") {
71
71
  icon = theme.fg("dim", "■");
72
- statusText = theme.fg("dim", " stopped");
72
+ statusText = theme.fg("dim", ` stopped${agent.terminalReason ? ` (${agent.terminalReason.replaceAll("_", " ")})` : ""}`);
73
73
  } else if (agent.status === "error") {
74
74
  icon = theme.fg("error", "✗");
75
75
  const errMsg = agent.error ? `: ${agent.error.slice(0, 60)}` : "";
76
76
  statusText = theme.fg("error", ` error${errMsg}`);
77
77
  } else {
78
- // aborted
79
- icon = theme.fg("error", "✗");
80
- statusText = theme.fg("warning", " aborted");
78
+ icon = theme.fg("dim", "•");
79
+ statusText = "";
81
80
  }
82
81
 
83
82
  const parts: string[] = [formatModelThinking(agent.modelLabel, agent.thinkingLevel)];
@@ -99,7 +98,7 @@ export function renderRunningLines(
99
98
  const name = getDisplayName(agent.type, registry);
100
99
  const modeLabel = getPromptModeLabel(agent.type, registry);
101
100
  const modeTag = modeLabel ? ` ${theme.fg("dim", `(${modeLabel})`)}` : "";
102
- const elapsed = formatMs(Date.now() - agent.startedAt);
101
+ const elapsed = formatMs(agent.activeRuntimeMs);
103
102
 
104
103
  const tokens = getLifetimeTotal(agent.lifetimeUsage);
105
104
  const tokenText = tokens > 0 ? formatSessionTokens(tokens, agent.contextPercent, theme, agent.compactionCount) : "";
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nklisch/pi-plugins",
3
- "version": "0.6.2",
3
+ "version": "0.8.1",
4
4
  "private": false,
5
5
  "description": "A small filesystem-first marketplace and plugin host for Pi.",
6
6
  "license": "MIT",
@@ -63,7 +63,7 @@
63
63
  ],
64
64
  "dependencies": {
65
65
  "@nklisch/pi-mcp-adapter": "2.21.0-nklisch.2",
66
- "@nklisch/pi-subagents": "18.1.0-nklisch.4",
66
+ "@nklisch/pi-subagents": "18.2.0-nklisch.1",
67
67
  "jiti": "2.7.0"
68
68
  },
69
69
  "devDependencies": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nklisch/pi-enhanced",
3
- "version": "0.2.7",
3
+ "version": "0.3.1",
4
4
  "description": "Pi, enhanced — one install for nklisch's full harness: policy-gated command review, plugin marketplace, subagents, background tasks, research tools, search, model modes, and a curated UX set.",
5
5
  "author": {
6
6
  "name": "nklisch"
@@ -1,81 +0,0 @@
1
- import type { ParentSnapshot } from "#src/lifecycle/parent-snapshot";
2
- import type { AgentSpawnConfig } from "#src/lifecycle/subagent-manager";
3
- import { textResult } from "#src/tools/helpers";
4
- import { formatModelThinking } from "#src/ui/display";
5
- import type { ResolvedSpawnConfig } from "#src/tools/spawn-config";
6
- import type { ParentSessionInfo, Subagent } from "#src/types";
7
-
8
- /** Narrow manager interface for the background spawner. */
9
- export interface BackgroundManagerDeps {
10
- spawn(snapshot: ParentSnapshot, type: string, prompt: string, opts: AgentSpawnConfig): string;
11
- getRecord(id: string): Subagent | undefined;
12
- }
13
-
14
- /** All values the background spawner needs beyond the resolved config. */
15
- export interface BackgroundParams {
16
- config: ResolvedSpawnConfig;
17
- snapshot: ParentSnapshot;
18
- parentSession: ParentSessionInfo;
19
- settings: { readonly maxConcurrent: number };
20
- }
21
-
22
- /**
23
- * Spawn a background agent and return the tool result immediately.
24
- * Owns: launch message formatting.
25
- */
26
- export function spawnBackground(
27
- manager: BackgroundManagerDeps,
28
- params: BackgroundParams,
29
- ) {
30
- const { identity, execution, presentation } = params.config;
31
-
32
- let id: string;
33
- try {
34
- id = manager.spawn(params.snapshot, identity.subagentType, execution.prompt, {
35
- parentSession: params.parentSession,
36
- description: execution.description,
37
- model: execution.model,
38
- maxTurns: execution.effectiveMaxTurns,
39
- inheritContext: execution.inheritContext,
40
- thinkingLevel: execution.effectiveThinkingLevel,
41
- isBackground: true,
42
- origin: "tool",
43
- invocation: execution.agentInvocation,
44
- });
45
- } catch (err) {
46
- return textResult(err instanceof Error ? err.message : String(err));
47
- }
48
-
49
- const record = manager.getRecord(id);
50
-
51
- const isQueued = record?.status === "queued";
52
- return textResult(
53
- `Agent ${isQueued ? "queued" : "started"} in background.\n` +
54
- `Agent ID: ${id}\n` +
55
- `Type: ${identity.displayName}\n` +
56
- `Model: ${formatModelThinking(
57
- record?.modelLabel ?? presentation.modelName,
58
- record?.effectiveThinkingLevel ?? execution.effectiveThinkingLevel,
59
- )}\n` +
60
- `Runtime: 0.0s\n` +
61
- `Description: ${execution.description}\n` +
62
- (identity.fellBack ? `Fallback: requested ${identity.rawType}; using ${identity.subagentType}\n` : "") +
63
- (record?.outputFile ? `Output file: ${record.outputFile}\n` : "") +
64
- (isQueued
65
- ? `Position: queued (max ${params.settings.maxConcurrent} concurrent)\n`
66
- : "") +
67
- `\nCompletion will automatically notify and wake you with a result preview. Continue other work; do not poll.\n` +
68
- `Use steer_subagent to redirect this agent while it runs.\n` +
69
- `Use get_subagent_result only for full output, verbose conversation, an explicit status check or synchronization point, or recovery after a missed notification.\n` +
70
- `Do not duplicate this agent's work.`,
71
- {
72
- ...presentation.detailBase,
73
- thinkingLevel: record?.effectiveThinkingLevel ?? execution.effectiveThinkingLevel,
74
- toolUses: 0,
75
- tokens: "",
76
- durationMs: 0,
77
- status: "background" as const,
78
- agentId: id,
79
- },
80
- );
81
- }
@@ -1,149 +0,0 @@
1
- import type { AgentToolResult } from "@earendil-works/pi-coding-agent";
2
- import { runSafely } from "#src/debug";
3
- import type { ParentSnapshot } from "#src/lifecycle/parent-snapshot";
4
- import type { AgentSpawnConfig } from "#src/lifecycle/subagent-manager";
5
- import {
6
- buildDetails,
7
- formatLifetimeTokens,
8
- getStatusNote,
9
- textResult,
10
- } from "#src/tools/helpers";
11
- import type { ResolvedSpawnConfig } from "#src/tools/spawn-config";
12
- import type { ParentSessionInfo, Subagent } from "#src/types";
13
- import {
14
- type AgentDetails,
15
- describeActivity,
16
- formatModelThinking,
17
- formatMs,
18
- SPINNER,
19
- } from "#src/ui/display";
20
-
21
- /** Narrow manager interface for the foreground runner. */
22
- export interface ForegroundManagerDeps {
23
- spawnAndWait(
24
- snapshot: ParentSnapshot,
25
- type: string,
26
- prompt: string,
27
- opts: Omit<AgentSpawnConfig, "isBackground">,
28
- ): Promise<Subagent>;
29
- }
30
-
31
- /** All values the foreground runner needs beyond the resolved config. */
32
- export interface ForegroundParams {
33
- config: ResolvedSpawnConfig;
34
- snapshot: ParentSnapshot;
35
- parentSession: ParentSessionInfo;
36
- }
37
-
38
- /**
39
- * Run an agent synchronously in the foreground, streaming spinner updates.
40
- * Owns: spinner interval, streaming onUpdate callbacks, cleanup, and result formatting.
41
- */
42
- export async function runForeground(
43
- manager: ForegroundManagerDeps,
44
- params: ForegroundParams,
45
- signal: AbortSignal | undefined,
46
- onUpdate: ((update: AgentToolResult<any>) => void) | undefined,
47
- ) {
48
- const { identity, execution, presentation } = params.config;
49
- let spinnerFrame = 0;
50
- const startedAt = Date.now();
51
-
52
- let recordRef: Subagent | undefined;
53
-
54
- const streamUpdate = (): void => {
55
- runSafely("foreground progress update", () => {
56
- const toolUses = recordRef?.toolUses ?? 0;
57
- const details: AgentDetails = {
58
- ...presentation.detailBase,
59
- toolUses,
60
- tokens: recordRef ? formatLifetimeTokens(recordRef) : "",
61
- // Read activity off the record; fall back to safe defaults before onSessionCreated fires
62
- turnCount: recordRef?.turnCount ?? 1,
63
- maxTurns: recordRef?.maxTurns ?? execution.effectiveMaxTurns,
64
- durationMs: Date.now() - startedAt,
65
- status: "running",
66
- activity: describeActivity(
67
- recordRef?.activeTools ?? new Map(),
68
- recordRef?.responseText ?? "",
69
- ),
70
- spinnerFrame: spinnerFrame % SPINNER.length,
71
- };
72
- onUpdate?.({
73
- content: [{ type: "text", text: `${toolUses} tool uses...` }],
74
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- Pi SDK ToolCallUpdate details type is not exported
75
- details: details as any,
76
- });
77
- });
78
- };
79
-
80
- // Each update traverses the parent TUI tree; avoid doing that at animation
81
- // speed while the foreground tool already owns the progress surface.
82
- const spinnerInterval = setInterval(() => {
83
- spinnerFrame++;
84
- streamUpdate();
85
- }, 500);
86
-
87
- let record: Subagent;
88
- try {
89
- streamUpdate();
90
-
91
- record = await manager.spawnAndWait(
92
- params.snapshot,
93
- identity.subagentType,
94
- execution.prompt,
95
- {
96
- description: execution.description,
97
- model: execution.model,
98
- maxTurns: execution.effectiveMaxTurns,
99
- inheritContext: execution.inheritContext,
100
- thinkingLevel: execution.effectiveThinkingLevel,
101
- origin: "tool",
102
- invocation: execution.agentInvocation,
103
- signal,
104
- parentSession: params.parentSession,
105
- observer: {
106
- onSessionCreated: (agent) => {
107
- recordRef = agent;
108
- },
109
- },
110
- },
111
- );
112
- } catch (err) {
113
- // Only the spawn boundary converts to an error-text result. Post-spawn
114
- // formatting is internal code; its defects stay thrown tool errors so Pi
115
- // reports isError rather than a success-shaped result.
116
- return textResult(err instanceof Error ? err.message : String(err));
117
- } finally {
118
- clearInterval(spinnerInterval);
119
- }
120
-
121
- // Foreground delivery returns the complete outcome inline, so retention can
122
- // use the shorter consumed-session window.
123
- record.markConsumed();
124
-
125
- const tokenText = formatLifetimeTokens(record);
126
- const details = buildDetails(presentation.detailBase, record, { tokens: tokenText });
127
-
128
- const fallbackNote = identity.fellBack
129
- ? `Note: Unknown agent type "${identity.rawType}" — using ${identity.subagentType}.\n\n`
130
- : "";
131
-
132
- if (record.status === "error") {
133
- const partial = record.result?.trim();
134
- return textResult(
135
- `${fallbackNote}Model: ${formatModelThinking(record.modelLabel, record.effectiveThinkingLevel)}\nRuntime: ${formatMs(details.durationMs)}\nAgent failed: ${record.error}` +
136
- (partial ? `\n\nPartial output before the failure:\n${partial}` : ""),
137
- details,
138
- );
139
- }
140
-
141
- const durationMs = (record.completedAt ?? Date.now()) - record.startedAt;
142
- const statsParts = [`${record.toolUses} tool uses`];
143
- if (tokenText) statsParts.push(tokenText);
144
- return textResult(
145
- `${fallbackNote}Model: ${formatModelThinking(record.modelLabel, record.effectiveThinkingLevel)}\nRuntime: ${formatMs(durationMs)}\nAgent completed (${statsParts.join(", ")})${getStatusNote(record.status)}.\n\n` +
146
- (record.result?.trim() ?? "No output."),
147
- details,
148
- );
149
- }
@@ -1,77 +0,0 @@
1
- /**
2
- * get-result-report.ts — Pure report assembly for get_subagent_result.
3
- *
4
- * All functions are stateless: they receive an AgentReport, returning
5
- * formatted strings. No SDK types, no timers, no side effects.
6
- * Consumed by GetResultTool.execute in get-result-tool.ts. Mirrors the
7
- * result-renderer.ts pattern used by the subagent tool's TUI renderer.
8
- */
9
-
10
- import type { SubagentStatus } from "#src/lifecycle/subagent";
11
- import type { ThinkingLevel } from "#src/types";
12
- import { formatModelThinking } from "#src/ui/display";
13
-
14
- /** The data a get_subagent_result report renders from — only what the formatter reads. */
15
- export interface AgentReport {
16
- id: string;
17
- displayName: string;
18
- modelLabel: string;
19
- thinkingLevel: ThinkingLevel;
20
- status: SubagentStatus;
21
- toolUses: number;
22
- /** Pre-formatted lifetime token total; "" when zero. */
23
- tokens: string;
24
- contextPercent: number | null;
25
- compactionCount: number;
26
- /** Pre-formatted duration string. */
27
- duration: string;
28
- description: string;
29
- result: string | undefined;
30
- error: string | undefined;
31
- stoppedWhileQueued: boolean;
32
- /** Present only when verbose was requested and a conversation is available. */
33
- conversation?: string;
34
- /** Persisted transcript remains readable after the live session is released. */
35
- transcriptPath?: string;
36
- }
37
-
38
- /** Assemble the stats parts: Tool uses / tokens? / Context? / Compactions? / Duration. */
39
- export function renderStatsParts(report: AgentReport): string[] {
40
- const parts = [`Tool uses: ${report.toolUses}`];
41
- if (report.tokens) parts.push(report.tokens);
42
- if (report.contextPercent !== null) parts.push(`Context: ${Math.round(report.contextPercent)}%`);
43
- if (report.compactionCount) parts.push(`Compactions: ${report.compactionCount}`);
44
- parts.push(`Duration: ${report.duration}`);
45
- return parts;
46
- }
47
-
48
- /** Select the per-status body: running note, error line, or trimmed result. */
49
- export function renderReportBody(report: AgentReport): string {
50
- if (report.status === "queued")
51
- return "Agent is queued and has not started. Use wait: true to synchronize, or rely on the automatic completion notification.";
52
- if (report.status === "running")
53
- return "Agent is still running. Use wait: true to synchronize, or rely on the automatic completion notification.";
54
- if (report.stoppedWhileQueued)
55
- return "Agent was stopped while queued and never started. No work was performed.";
56
- if (report.status === "error") {
57
- const partial = report.result?.trim();
58
- return partial
59
- ? `Error: ${report.error}\n\nPartial output before the failure:\n${partial}`
60
- : `Error: ${report.error}`;
61
- }
62
- return report.result?.trim() ?? "No output.";
63
- }
64
-
65
- /** Assemble the full get_subagent_result report text. */
66
- export function formatAgentReport(report: AgentReport): string {
67
- let output =
68
- `Agent: ${report.id}\n` +
69
- `Type: ${report.displayName} | Model: ${formatModelThinking(report.modelLabel, report.thinkingLevel)} | Status: ${report.status} | ${renderStatsParts(report).join(" | ")}\n` +
70
- `Description: ${report.description}\n\n`;
71
- output += renderReportBody(report);
72
- if (report.conversation) {
73
- output += `\n\n--- Agent Conversation ---\n${report.conversation}`;
74
- }
75
- if (report.transcriptPath) output += `\n\nFull transcript: ${report.transcriptPath}`;
76
- return output;
77
- }