@clanker-code/pi-subagents 0.10.5

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 (130) hide show
  1. package/.plans/PLAN-next-changes.md +183 -0
  2. package/.plans/README.md +14 -0
  3. package/AGENTS.md +31 -0
  4. package/CHANGELOG.md +583 -0
  5. package/CLAUDE.md +1 -0
  6. package/LICENSE +21 -0
  7. package/README.md +630 -0
  8. package/RELEASE.md +39 -0
  9. package/dist/abort-resend.d.ts +35 -0
  10. package/dist/abort-resend.js +71 -0
  11. package/dist/agent-details.d.ts +17 -0
  12. package/dist/agent-details.js +22 -0
  13. package/dist/agent-manager.d.ts +132 -0
  14. package/dist/agent-manager.js +493 -0
  15. package/dist/agent-runner.d.ts +165 -0
  16. package/dist/agent-runner.js +732 -0
  17. package/dist/agent-tool-description.d.ts +9 -0
  18. package/dist/agent-tool-description.js +147 -0
  19. package/dist/agent-types.d.ts +60 -0
  20. package/dist/agent-types.js +157 -0
  21. package/dist/context.d.ts +12 -0
  22. package/dist/context.js +56 -0
  23. package/dist/cross-extension-rpc.d.ts +46 -0
  24. package/dist/cross-extension-rpc.js +76 -0
  25. package/dist/custom-agents.d.ts +14 -0
  26. package/dist/custom-agents.js +149 -0
  27. package/dist/default-agents.d.ts +7 -0
  28. package/dist/default-agents.js +119 -0
  29. package/dist/enabled-models.d.ts +49 -0
  30. package/dist/enabled-models.js +145 -0
  31. package/dist/env.d.ts +6 -0
  32. package/dist/env.js +28 -0
  33. package/dist/group-join.d.ts +32 -0
  34. package/dist/group-join.js +116 -0
  35. package/dist/index.d.ts +36 -0
  36. package/dist/index.js +1918 -0
  37. package/dist/invocation-config.d.ts +25 -0
  38. package/dist/invocation-config.js +19 -0
  39. package/dist/memory.d.ts +49 -0
  40. package/dist/memory.js +151 -0
  41. package/dist/model-resolver.d.ts +19 -0
  42. package/dist/model-resolver.js +62 -0
  43. package/dist/notifications.d.ts +6 -0
  44. package/dist/notifications.js +107 -0
  45. package/dist/output-file.d.ts +24 -0
  46. package/dist/output-file.js +86 -0
  47. package/dist/peek.d.ts +37 -0
  48. package/dist/peek.js +121 -0
  49. package/dist/prompts.d.ts +40 -0
  50. package/dist/prompts.js +95 -0
  51. package/dist/schedule-store.d.ts +38 -0
  52. package/dist/schedule-store.js +155 -0
  53. package/dist/schedule.d.ts +109 -0
  54. package/dist/schedule.js +338 -0
  55. package/dist/settings.d.ts +135 -0
  56. package/dist/settings.js +168 -0
  57. package/dist/skill-loader.d.ts +24 -0
  58. package/dist/skill-loader.js +93 -0
  59. package/dist/status-note.d.ts +13 -0
  60. package/dist/status-note.js +24 -0
  61. package/dist/types.d.ts +184 -0
  62. package/dist/types.js +7 -0
  63. package/dist/ui/agent-tool-rendering.d.ts +34 -0
  64. package/dist/ui/agent-tool-rendering.js +154 -0
  65. package/dist/ui/agent-widget-tree.d.ts +33 -0
  66. package/dist/ui/agent-widget-tree.js +130 -0
  67. package/dist/ui/agent-widget.d.ts +156 -0
  68. package/dist/ui/agent-widget.js +408 -0
  69. package/dist/ui/conversation-viewer.d.ts +47 -0
  70. package/dist/ui/conversation-viewer.js +290 -0
  71. package/dist/ui/menu-select.d.ts +20 -0
  72. package/dist/ui/menu-select.js +46 -0
  73. package/dist/ui/schedule-menu.d.ts +16 -0
  74. package/dist/ui/schedule-menu.js +99 -0
  75. package/dist/ui/viewer-keys.d.ts +20 -0
  76. package/dist/ui/viewer-keys.js +17 -0
  77. package/dist/usage.d.ts +50 -0
  78. package/dist/usage.js +49 -0
  79. package/dist/wait.d.ts +10 -0
  80. package/dist/wait.js +37 -0
  81. package/dist/worktree.d.ts +45 -0
  82. package/dist/worktree.js +160 -0
  83. package/docs/design/default-extension-tool-exposure.md +56 -0
  84. package/docs/superpowers/plans/2026-06-19-recursive-subagent-widget.md +600 -0
  85. package/docs/superpowers/specs/2026-06-19-recursive-subagent-widget-design.md +189 -0
  86. package/examples/agent-tool-description.md +45 -0
  87. package/package.json +56 -0
  88. package/reviews/proposal-structured-output-schema.md +135 -0
  89. package/reviews/recursive-subagent-widget-preview-rev2.png +0 -0
  90. package/reviews/recursive-subagent-widget-preview.html +137 -0
  91. package/reviews/recursive-subagent-widget-preview.png +0 -0
  92. package/reviews/subagent-features-comparison.md +350 -0
  93. package/src/abort-resend.ts +75 -0
  94. package/src/agent-details.ts +31 -0
  95. package/src/agent-manager.ts +596 -0
  96. package/src/agent-runner.ts +872 -0
  97. package/src/agent-tool-description.ts +163 -0
  98. package/src/agent-types.ts +189 -0
  99. package/src/context.ts +58 -0
  100. package/src/cross-extension-rpc.ts +122 -0
  101. package/src/custom-agents.ts +160 -0
  102. package/src/default-agents.ts +123 -0
  103. package/src/enabled-models.ts +180 -0
  104. package/src/env.ts +33 -0
  105. package/src/group-join.ts +141 -0
  106. package/src/index.ts +2115 -0
  107. package/src/invocation-config.ts +42 -0
  108. package/src/memory.ts +165 -0
  109. package/src/model-resolver.ts +81 -0
  110. package/src/notifications.ts +120 -0
  111. package/src/output-file.ts +96 -0
  112. package/src/peek.ts +155 -0
  113. package/src/prompts.ts +129 -0
  114. package/src/schedule-store.ts +153 -0
  115. package/src/schedule.ts +365 -0
  116. package/src/settings.ts +289 -0
  117. package/src/skill-loader.ts +102 -0
  118. package/src/status-note.ts +25 -0
  119. package/src/types.ts +195 -0
  120. package/src/ui/agent-tool-rendering.ts +175 -0
  121. package/src/ui/agent-widget-tree.ts +169 -0
  122. package/src/ui/agent-widget.ts +497 -0
  123. package/src/ui/conversation-viewer.ts +297 -0
  124. package/src/ui/menu-select.ts +68 -0
  125. package/src/ui/schedule-menu.ts +105 -0
  126. package/src/ui/viewer-keys.ts +39 -0
  127. package/src/usage.ts +60 -0
  128. package/src/wait.ts +44 -0
  129. package/src/worktree.ts +191 -0
  130. package/vitest.config.ts +25 -0
@@ -0,0 +1,600 @@
1
+ # Recursive Subagent Widget Implementation Plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Goal:** Render the full recursive subagent tree in the TUI widget, including grandchildren and deeper descendants, with rich default display, automatic compact fallback, configurable compact/rich modes, and a focused path view foundation.
6
+
7
+ **Architecture:** Introduce a small tree-model/render module that converts flat agent snapshots into recursive render rows. `AgentWidget` will merge local manager records with descendant lifecycle/activity snapshots, then render the tree through rich, compact, or auto modes. Recursive manager instances will publish enough lifecycle/activity event data for the root widget to keep a durable aggregate without directly owning child managers.
8
+
9
+ **Tech Stack:** TypeScript, Vitest, existing `@earendil-works/pi-tui` truncation utilities, existing `SubagentsSettings` persistence and `/agents → Settings` UI.
10
+
11
+ ## Global Constraints
12
+
13
+ - Use TDD: add failing tests before implementation changes for each behavior slice.
14
+ - Keep widget output width-safe with `truncateToWidth`.
15
+ - Keep widget height bounded using the existing `MAX_WIDGET_LINES` budget style.
16
+ - Default display behavior is `auto`: rich tree by default, compact fallback when rich output exceeds budget or terminal width is constrained.
17
+ - User-configurable display modes: `auto`, `rich`, `compact`.
18
+ - Preserve existing status-bar truncation behavior.
19
+ - Avoid unrelated refactors; keep new tree logic in a focused module.
20
+ - Use at most 2 test/build threads when commands support parallelism.
21
+
22
+ ---
23
+
24
+ ## File Structure
25
+
26
+ - Create: `src/ui/agent-widget-tree.ts`
27
+ - Pure tree/snapshot types.
28
+ - Merge/sort/build helpers.
29
+ - Rich/compact/focused row rendering helpers.
30
+ - Overflow/collapse helpers.
31
+ - Modify: `src/ui/agent-widget.ts`
32
+ - Delegate tree building/rendering to `agent-widget-tree.ts`.
33
+ - Track descendant snapshots supplied by lifecycle/activity events.
34
+ - Keep status bar aggregation over full visible tree.
35
+ - Modify: `src/index.ts`
36
+ - Emit richer lifecycle/activity snapshots.
37
+ - Wire `subagents:*` event listeners into `AgentWidget` snapshot updates.
38
+ - Add in-memory widget display mode state and `/agents → Settings` entry.
39
+ - Modify: `src/settings.ts`
40
+ - Persist and sanitize `widgetDisplayMode?: "auto" | "rich" | "compact"`.
41
+ - Apply setting through `SettingsAppliers`.
42
+ - Modify: `test/agent-widget.test.ts`
43
+ - Add tree rendering, compact/rich/auto fallback, overflow, orphan, and status-bar descendant tests.
44
+ - Modify: `test/settings.test.ts`
45
+ - Add settings sanitize/apply/save/load coverage for `widgetDisplayMode`.
46
+ - Optional modify: `test/subagents-print-mode-e2e.test.ts`
47
+ - Add a scripted recursive smoke if event propagation needs an integration guard.
48
+
49
+ ---
50
+
51
+ ### Task 1: Add pure tree model tests and helpers
52
+
53
+ **Files:**
54
+ - Create: `src/ui/agent-widget-tree.ts`
55
+ - Modify: `test/agent-widget.test.ts`
56
+
57
+ **Interfaces:**
58
+ - Produces:
59
+ - `export type WidgetDisplayMode = "auto" | "rich" | "compact";`
60
+ - `export interface WidgetAgentSnapshot { id: string; parentAgentId?: string; depth?: number; type: SubagentType; description: string; status: string; startedAt: number; completedAt?: number; error?: string; toolUses: number; invocation?: AgentInvocation; activity?: AgentActivity; }`
61
+ - `export interface WidgetTreeNode { snapshot: WidgetAgentSnapshot; children: WidgetTreeNode[]; orphaned?: boolean; }`
62
+ - `export function buildAgentTree(records: WidgetAgentSnapshot[]): WidgetTreeNode[]`
63
+
64
+ - [ ] **Step 1: Write failing tree tests**
65
+
66
+ Add to `test/agent-widget.test.ts`:
67
+
68
+ ```ts
69
+ import { buildAgentTree, type WidgetAgentSnapshot } from "../src/ui/agent-widget-tree.js";
70
+
71
+ function snap(partial: Partial<WidgetAgentSnapshot> & { id: string }): WidgetAgentSnapshot {
72
+ return {
73
+ type: "general-purpose" as any,
74
+ description: partial.id,
75
+ status: "running",
76
+ startedAt: 1,
77
+ toolUses: 0,
78
+ ...partial,
79
+ };
80
+ }
81
+
82
+ describe("agent widget tree model", () => {
83
+ it("links parent child and grandchild records by parentAgentId", () => {
84
+ const tree = buildAgentTree([
85
+ snap({ id: "parent", depth: 1 }),
86
+ snap({ id: "child", parentAgentId: "parent", depth: 2 }),
87
+ snap({ id: "grandchild", parentAgentId: "child", depth: 3 }),
88
+ ]);
89
+
90
+ expect(tree.map(n => n.snapshot.id)).toEqual(["parent"]);
91
+ expect(tree[0].children.map(n => n.snapshot.id)).toEqual(["child"]);
92
+ expect(tree[0].children[0].children.map(n => n.snapshot.id)).toEqual(["grandchild"]);
93
+ });
94
+
95
+ it("keeps orphaned descendants visible as roots", () => {
96
+ const tree = buildAgentTree([
97
+ snap({ id: "orphan", parentAgentId: "missing-parent", depth: 3 }),
98
+ ]);
99
+
100
+ expect(tree).toHaveLength(1);
101
+ expect(tree[0].snapshot.id).toBe("orphan");
102
+ expect(tree[0].orphaned).toBe(true);
103
+ });
104
+ });
105
+ ```
106
+
107
+ - [ ] **Step 2: Run tests and verify they fail**
108
+
109
+ Run: `npm test -- --run test/agent-widget.test.ts`
110
+
111
+ Expected: FAIL because `src/ui/agent-widget-tree.ts` does not exist.
112
+
113
+ - [ ] **Step 3: Implement minimal tree helpers**
114
+
115
+ Create `src/ui/agent-widget-tree.ts`:
116
+
117
+ ```ts
118
+ import type { AgentActivity } from "./agent-widget.js";
119
+ import type { AgentInvocation, SubagentType } from "../types.js";
120
+
121
+ export type WidgetDisplayMode = "auto" | "rich" | "compact";
122
+
123
+ export interface WidgetAgentSnapshot {
124
+ id: string;
125
+ parentAgentId?: string;
126
+ depth?: number;
127
+ type: SubagentType;
128
+ description: string;
129
+ status: string;
130
+ startedAt: number;
131
+ completedAt?: number;
132
+ error?: string;
133
+ toolUses: number;
134
+ invocation?: AgentInvocation;
135
+ activity?: AgentActivity;
136
+ }
137
+
138
+ export interface WidgetTreeNode {
139
+ snapshot: WidgetAgentSnapshot;
140
+ children: WidgetTreeNode[];
141
+ orphaned?: boolean;
142
+ }
143
+
144
+ function statusRank(status: string): number {
145
+ if (status === "running") return 0;
146
+ if (status === "queued") return 1;
147
+ return 2;
148
+ }
149
+
150
+ function sortNodes(a: WidgetTreeNode, b: WidgetTreeNode): number {
151
+ const status = statusRank(a.snapshot.status) - statusRank(b.snapshot.status);
152
+ if (status !== 0) return status;
153
+ return a.snapshot.startedAt - b.snapshot.startedAt;
154
+ }
155
+
156
+ export function buildAgentTree(records: WidgetAgentSnapshot[]): WidgetTreeNode[] {
157
+ const nodes = new Map<string, WidgetTreeNode>();
158
+ for (const record of records) nodes.set(record.id, { snapshot: record, children: [] });
159
+
160
+ const roots: WidgetTreeNode[] = [];
161
+ for (const node of nodes.values()) {
162
+ const parentId = node.snapshot.parentAgentId;
163
+ const parent = parentId ? nodes.get(parentId) : undefined;
164
+ if (parent) parent.children.push(node);
165
+ else {
166
+ if (parentId) node.orphaned = true;
167
+ roots.push(node);
168
+ }
169
+ }
170
+
171
+ const sortDeep = (items: WidgetTreeNode[]) => {
172
+ items.sort(sortNodes);
173
+ for (const item of items) sortDeep(item.children);
174
+ };
175
+ sortDeep(roots);
176
+ return roots;
177
+ }
178
+ ```
179
+
180
+ - [ ] **Step 4: Run tests and verify they pass**
181
+
182
+ Run: `npm test -- --run test/agent-widget.test.ts`
183
+
184
+ Expected: PASS.
185
+
186
+ - [ ] **Step 5: Commit**
187
+
188
+ ```bash
189
+ git add src/ui/agent-widget-tree.ts test/agent-widget.test.ts
190
+ git commit -m "test: add recursive subagent tree model"
191
+ ```
192
+
193
+ ---
194
+
195
+ ### Task 2: Render recursive compact/rich rows in pure helpers
196
+
197
+ **Files:**
198
+ - Modify: `src/ui/agent-widget-tree.ts`
199
+ - Modify: `test/agent-widget.test.ts`
200
+
201
+ **Interfaces:**
202
+ - Consumes: `WidgetTreeNode`, `WidgetAgentSnapshot`, `WidgetDisplayMode` from Task 1.
203
+ - Produces:
204
+ - `export interface RenderTreeOptions { mode: WidgetDisplayMode; width: number; maxLines: number; theme: Theme; frame: string; now?: number; }`
205
+ - `export function renderAgentTree(records: WidgetAgentSnapshot[], options: RenderTreeOptions): string[]`
206
+ - `export function chooseEffectiveMode(mode: WidgetDisplayMode, width: number, richLineCount: number, maxLines: number): "rich" | "compact"`
207
+
208
+ - [ ] **Step 1: Write failing render tests**
209
+
210
+ Add tests that call pure `renderAgentTree()`:
211
+
212
+ ```ts
213
+ import { renderAgentTree } from "../src/ui/agent-widget-tree.js";
214
+
215
+ const plainTheme = { fg: (_c: string, s: string) => s, bold: (s: string) => s };
216
+
217
+ describe("agent widget tree rendering", () => {
218
+ it("renders a grandchild with recursive connectors", () => {
219
+ const lines = renderAgentTree([
220
+ snap({ id: "parent", description: "parent task", depth: 1 }),
221
+ snap({ id: "child", description: "child task", parentAgentId: "parent", depth: 2 }),
222
+ snap({ id: "grandchild", description: "grandchild task", parentAgentId: "child", depth: 3 }),
223
+ ], { mode: "compact", width: 120, maxLines: 12, theme: plainTheme, frame: "⠋", now: 10_000 });
224
+
225
+ expect(lines.join("\n")).toContain("parent task");
226
+ expect(lines.join("\n")).toContain("│ └─");
227
+ expect(lines.join("\n")).toContain("grandchild task");
228
+ });
229
+
230
+ it("auto mode falls back to compact when rich output exceeds the line budget", () => {
231
+ const lines = renderAgentTree([
232
+ snap({ id: "parent", description: "parent task" }),
233
+ snap({ id: "child", description: "child task", parentAgentId: "parent" }),
234
+ snap({ id: "grandchild", description: "grandchild task", parentAgentId: "child" }),
235
+ ], { mode: "auto", width: 120, maxLines: 4, theme: plainTheme, frame: "⠋", now: 10_000 });
236
+
237
+ expect(lines.length).toBeLessThanOrEqual(4);
238
+ expect(lines.some(l => l.includes("⎿"))).toBe(false);
239
+ });
240
+
241
+ it("collapses overflow by reporting hidden descendants", () => {
242
+ const records = Array.from({ length: 8 }, (_, i) => snap({
243
+ id: `agent-${i}`,
244
+ description: `agent ${i}`,
245
+ parentAgentId: i === 0 ? undefined : `agent-${i - 1}`,
246
+ }));
247
+
248
+ const lines = renderAgentTree(records, { mode: "compact", width: 120, maxLines: 5, theme: plainTheme, frame: "⠋", now: 10_000 });
249
+ expect(lines.length).toBeLessThanOrEqual(5);
250
+ expect(lines.join("\n")).toMatch(/hidden|more/);
251
+ });
252
+ });
253
+ ```
254
+
255
+ - [ ] **Step 2: Run tests and verify they fail**
256
+
257
+ Run: `npm test -- --run test/agent-widget.test.ts`
258
+
259
+ Expected: FAIL because render helpers do not exist.
260
+
261
+ - [ ] **Step 3: Implement render helpers**
262
+
263
+ Implement in `src/ui/agent-widget-tree.ts`:
264
+
265
+ - Header line: `● Agents` plus counts/depth for rich mode.
266
+ - Compact node row: connector + status icon/spinner + display name + description + terse stats.
267
+ - Rich node rows: compact node row plus detail row for running agents.
268
+ - Connector calculation using prefix segments (`│ ` vs ` `) and child `├─`/`└─`.
269
+ - Width truncation via `truncateToWidth(line, width)`.
270
+ - Overflow: if output exceeds `maxLines`, keep the earliest active path rows and append a truncated summary line: `└─ +N more agents hidden`.
271
+
272
+ - [ ] **Step 4: Run tests and verify they pass**
273
+
274
+ Run: `npm test -- --run test/agent-widget.test.ts`
275
+
276
+ Expected: PASS.
277
+
278
+ - [ ] **Step 5: Commit**
279
+
280
+ ```bash
281
+ git add src/ui/agent-widget-tree.ts test/agent-widget.test.ts
282
+ git commit -m "feat: render recursive subagent tree rows"
283
+ ```
284
+
285
+ ---
286
+
287
+ ### Task 3: Wire AgentWidget to the recursive tree renderer
288
+
289
+ **Files:**
290
+ - Modify: `src/ui/agent-widget.ts`
291
+ - Modify: `test/agent-widget.test.ts`
292
+
293
+ **Interfaces:**
294
+ - Consumes: `renderAgentTree`, `WidgetAgentSnapshot`, `WidgetDisplayMode` from Task 2.
295
+ - Produces:
296
+ - `AgentWidget.setDisplayMode(mode: WidgetDisplayMode): void`
297
+ - `AgentWidget.upsertSnapshot(snapshot: WidgetAgentSnapshot): void`
298
+ - `AgentWidget.removeSnapshot(id: string): void`
299
+ - `AgentWidget.clearSnapshots(): void`
300
+
301
+ - [ ] **Step 1: Write failing AgentWidget integration tests**
302
+
303
+ Add tests using fake manager and captured widget callback:
304
+
305
+ ```ts
306
+ it("renders descendant snapshots that are not in the local manager", () => {
307
+ const manager = { listAgents: () => [snap({ id: "parent", description: "parent", status: "running" })] } as any;
308
+ const ui = { setStatus: vi.fn(), setWidget: vi.fn() } as any;
309
+ const widget = new AgentWidget(manager, new Map());
310
+ widget.setUICtx(ui);
311
+ widget.upsertSnapshot(snap({ id: "child", parentAgentId: "parent", description: "child", status: "running" }));
312
+ widget.upsertSnapshot(snap({ id: "grandchild", parentAgentId: "child", description: "grandchild", status: "running" }));
313
+
314
+ widget.update();
315
+ const factory = ui.setWidget.mock.calls.at(-1)[1];
316
+ const component = factory({ terminal: { columns: 120 }, requestRender: vi.fn() }, plainTheme);
317
+ const text = component.render().join("\n");
318
+
319
+ expect(text).toContain("parent");
320
+ expect(text).toContain("child");
321
+ expect(text).toContain("grandchild");
322
+ });
323
+ ```
324
+
325
+ - [ ] **Step 2: Run tests and verify they fail**
326
+
327
+ Run: `npm test -- --run test/agent-widget.test.ts`
328
+
329
+ Expected: FAIL because `upsertSnapshot()` does not exist and widget still uses flat rendering.
330
+
331
+ - [ ] **Step 3: Implement widget snapshot merging**
332
+
333
+ In `src/ui/agent-widget.ts`:
334
+
335
+ - Import tree helpers.
336
+ - Add `private descendantSnapshots = new Map<string, WidgetAgentSnapshot>();`
337
+ - Add `private displayMode: WidgetDisplayMode = "auto";`
338
+ - Implement `setDisplayMode`, `upsertSnapshot`, `removeSnapshot`, `clearSnapshots`.
339
+ - Convert local `manager.listAgents()` records to `WidgetAgentSnapshot` with local `agentActivity` attached.
340
+ - Merge local records over descendant snapshots by id so local live state wins.
341
+ - Replace the current flat render assembly in `renderWidget()` with `renderAgentTree()`.
342
+ - Keep `shouldShowFinished()` filtering for local records; apply a timestamp/status filter to descendant snapshots too.
343
+
344
+ - [ ] **Step 4: Run tests and verify they pass**
345
+
346
+ Run: `npm test -- --run test/agent-widget.test.ts`
347
+
348
+ Expected: PASS.
349
+
350
+ - [ ] **Step 5: Commit**
351
+
352
+ ```bash
353
+ git add src/ui/agent-widget.ts test/agent-widget.test.ts
354
+ git commit -m "feat: wire widget to recursive tree renderer"
355
+ ```
356
+
357
+ ---
358
+
359
+ ### Task 4: Add widget display mode settings
360
+
361
+ **Files:**
362
+ - Modify: `src/settings.ts`
363
+ - Modify: `src/index.ts`
364
+ - Modify: `test/settings.test.ts`
365
+
366
+ **Interfaces:**
367
+ - Consumes: `WidgetDisplayMode` from `src/ui/agent-widget-tree.ts`.
368
+ - Produces:
369
+ - `SubagentsSettings.widgetDisplayMode?: WidgetDisplayMode`
370
+ - `SettingsAppliers.setWidgetDisplayMode(mode: WidgetDisplayMode): void`
371
+
372
+ - [ ] **Step 1: Write failing settings tests**
373
+
374
+ Add to `test/settings.test.ts`:
375
+
376
+ ```ts
377
+ it("sanitize accepts widgetDisplayMode values", () => {
378
+ const dir = mkdtempSync(join(tmpdir(), "pi-sub-set-"));
379
+ try {
380
+ saveSettings({ widgetDisplayMode: "compact" } as any, dir);
381
+ expect(loadSettings(dir).widgetDisplayMode).toBe("compact");
382
+ saveSettings({ widgetDisplayMode: "rich" } as any, dir);
383
+ expect(loadSettings(dir).widgetDisplayMode).toBe("rich");
384
+ saveSettings({ widgetDisplayMode: "auto" } as any, dir);
385
+ expect(loadSettings(dir).widgetDisplayMode).toBe("auto");
386
+ } finally {
387
+ rmSync(dir, { force: true, recursive: true });
388
+ }
389
+ });
390
+
391
+ it("drops invalid widgetDisplayMode values", () => {
392
+ const dir = mkdtempSync(join(tmpdir(), "pi-sub-set-"));
393
+ mkdirSync(join(dir, ".pi"), { recursive: true });
394
+ try {
395
+ writeFileSync(join(dir, ".pi", "subagents.json"), JSON.stringify({ widgetDisplayMode: "wide" }));
396
+ expect(loadSettings(dir).widgetDisplayMode).toBeUndefined();
397
+ } finally {
398
+ rmSync(dir, { force: true, recursive: true });
399
+ }
400
+ });
401
+
402
+ it("applySettings calls setWidgetDisplayMode only for valid values", () => {
403
+ const setWidgetDisplayMode = vi.fn();
404
+ applySettings({ widgetDisplayMode: "rich" } as any, { setWidgetDisplayMode } as never);
405
+ expect(setWidgetDisplayMode).toHaveBeenCalledWith("rich");
406
+ setWidgetDisplayMode.mockClear();
407
+ applySettings({}, { setWidgetDisplayMode } as never);
408
+ expect(setWidgetDisplayMode).not.toHaveBeenCalled();
409
+ });
410
+ ```
411
+
412
+ - [ ] **Step 2: Run tests and verify they fail**
413
+
414
+ Run: `npm test -- --run test/settings.test.ts`
415
+
416
+ Expected: FAIL because settings do not support `widgetDisplayMode`.
417
+
418
+ - [ ] **Step 3: Implement settings support**
419
+
420
+ In `src/settings.ts`:
421
+
422
+ - Import or define `WidgetDisplayMode` without creating a circular runtime dependency. Prefer `import type { WidgetDisplayMode } from "./ui/agent-widget-tree.js";`.
423
+ - Add `widgetDisplayMode?: WidgetDisplayMode` to `SubagentsSettings`.
424
+ - Add `setWidgetDisplayMode` to `SettingsAppliers`.
425
+ - Add valid set: `new Set<WidgetDisplayMode>(["auto", "rich", "compact"])`.
426
+ - Sanitize and apply the field.
427
+
428
+ In `src/index.ts`:
429
+
430
+ - Add local state: `let widgetDisplayMode: WidgetDisplayMode = "auto";`.
431
+ - Add setter that updates state and calls `widget.setDisplayMode(mode)`.
432
+ - Include mode in `snapshotSettings()`.
433
+ - Add a `/agents → Settings` item labeled “Widget display” with values `auto`, `rich`, `compact`.
434
+ - Persist changes via existing `notifyApplied()`.
435
+
436
+ - [ ] **Step 4: Run tests and verify they pass**
437
+
438
+ Run: `npm test -- --run test/settings.test.ts test/agent-widget.test.ts`
439
+
440
+ Expected: PASS.
441
+
442
+ - [ ] **Step 5: Commit**
443
+
444
+ ```bash
445
+ git add src/settings.ts src/index.ts test/settings.test.ts test/agent-widget.test.ts
446
+ git commit -m "feat: add subagent widget display setting"
447
+ ```
448
+
449
+ ---
450
+
451
+ ### Task 5: Publish descendant lifecycle/activity snapshots
452
+
453
+ **Files:**
454
+ - Modify: `src/index.ts`
455
+ - Modify: `src/ui/agent-widget.ts`
456
+ - Modify: `test/agent-widget.test.ts`
457
+ - Optional modify: `test/subagents-print-mode-e2e.test.ts`
458
+
459
+ **Interfaces:**
460
+ - Consumes: `AgentWidget.upsertSnapshot()` from Task 3.
461
+ - Produces lifecycle payloads that include enough fields for `WidgetAgentSnapshot`.
462
+
463
+ - [ ] **Step 1: Write failing event aggregation tests**
464
+
465
+ Add a unit-level test around `AgentWidget.upsertSnapshot()` if event wiring is hard to isolate. If index-level event hooks are easy to capture in existing extension tests, assert that `subagents:started` payloads now include `startedAt`, `status`, `toolUses`, and invocation data.
466
+
467
+ Minimum payload assertion shape:
468
+
469
+ ```ts
470
+ expect(pi.events.emit).toHaveBeenCalledWith("subagents:started", expect.objectContaining({
471
+ id: expect.any(String),
472
+ depth: 1,
473
+ parentAgentId: undefined,
474
+ startedAt: expect.any(Number),
475
+ status: "running",
476
+ toolUses: 0,
477
+ }));
478
+ ```
479
+
480
+ - [ ] **Step 2: Run targeted tests and verify failure**
481
+
482
+ Run the specific test file selected in Step 1.
483
+
484
+ Expected: FAIL because lifecycle payloads are not yet rich enough or widget does not consume them.
485
+
486
+ - [ ] **Step 3: Implement event snapshot wiring**
487
+
488
+ In `src/index.ts`:
489
+
490
+ - Extend `buildEventData(record)` to include `startedAt`, `completedAt`, `invocation`, and `status`.
491
+ - Extend `subagents:started` payload to include `startedAt`, `status: "running"`, `toolUses`, `invocation`, and `description`.
492
+ - Add a helper converting event payloads to `WidgetAgentSnapshot`.
493
+ - Register listeners for `subagents:started`, `subagents:completed`, `subagents:failed`, and `subagents:compacted` that call `widget.upsertSnapshot()`.
494
+ - Avoid duplicate regressions by letting local manager snapshots override event snapshots in `AgentWidget`.
495
+ - When a session starts/switches/shuts down, clear descendant snapshots alongside existing manager cleanup.
496
+
497
+ For live activity, emit a narrow activity snapshot from existing activity callbacks when practical:
498
+
499
+ - On tool start/end, update the local `agentActivity` map as today and emit a `subagents:activity` event with id, status, toolUses, activity text, turn count, token totals, and timestamps.
500
+ - Add a listener that updates the widget snapshot for descendants.
501
+
502
+ If activity propagation is not practical in this task, keep descendants live as `thinking…` until terminal event, and mark richer descendant activity as a follow-up only if tests prove the event data path is insufficient.
503
+
504
+ - [ ] **Step 4: Run targeted tests and verify pass**
505
+
506
+ Run selected event tests and `npm test -- --run test/agent-widget.test.ts`.
507
+
508
+ Expected: PASS.
509
+
510
+ - [ ] **Step 5: Commit**
511
+
512
+ ```bash
513
+ git add src/index.ts src/ui/agent-widget.ts test/agent-widget.test.ts test/subagents-print-mode-e2e.test.ts
514
+ git commit -m "feat: aggregate recursive subagent widget snapshots"
515
+ ```
516
+
517
+ ---
518
+
519
+ ### Task 6: Final validation and cleanup
520
+
521
+ **Files:**
522
+ - Modify only if tests expose issues.
523
+
524
+ **Interfaces:**
525
+ - Consumes all previous tasks.
526
+ - Produces verified feature branch ready for review/merge.
527
+
528
+ - [ ] **Step 1: Run focused tests**
529
+
530
+ Run:
531
+
532
+ ```bash
533
+ npm test -- --run test/agent-widget.test.ts test/settings.test.ts test/steer-render.test.ts test/conversation-viewer.test.ts
534
+ ```
535
+
536
+ Expected: PASS.
537
+
538
+ - [ ] **Step 2: Run typecheck**
539
+
540
+ Run:
541
+
542
+ ```bash
543
+ npm run typecheck
544
+ ```
545
+
546
+ Expected: PASS.
547
+
548
+ - [ ] **Step 3: Run lint**
549
+
550
+ Run:
551
+
552
+ ```bash
553
+ npm run lint
554
+ ```
555
+
556
+ Expected: PASS.
557
+
558
+ - [ ] **Step 4: Review final diff**
559
+
560
+ Run:
561
+
562
+ ```bash
563
+ git diff --stat HEAD
564
+ git diff HEAD -- src/ui/agent-widget-tree.ts src/ui/agent-widget.ts src/index.ts src/settings.ts test/agent-widget.test.ts test/settings.test.ts
565
+ ```
566
+
567
+ Check:
568
+
569
+ - No unrelated code changes.
570
+ - No temporary preview/server artifacts staged.
571
+ - Browser preview remains only in ignored `reviews/` scratch.
572
+ - Tree code is isolated and testable.
573
+
574
+ - [ ] **Step 5: Commit any final fixes**
575
+
576
+ If Step 4 required changes:
577
+
578
+ ```bash
579
+ git add <changed-files>
580
+ git commit -m "fix: polish recursive subagent widget tree"
581
+ ```
582
+
583
+ - [ ] **Step 6: Report completion**
584
+
585
+ Final response should include:
586
+
587
+ - Design preview paths.
588
+ - Spec and plan paths.
589
+ - Summary of implemented behavior.
590
+ - Verification commands and results.
591
+ - Any known remaining risks, especially whether live descendant activity is fully rich or initially falls back to `thinking…`.
592
+
593
+ --- SUMMARY ---
594
+
595
+ - Build one recursive widget tree model from agent snapshots keyed by id and linked by `parentAgentId`.
596
+ - Render the tree through configurable modes: `auto` (default), `rich`, and `compact`.
597
+ - `auto` starts rich and falls back to compact when width/line budget makes rich output too large.
598
+ - Root widget aggregates descendants through lifecycle/activity event snapshots rather than direct child-manager coupling.
599
+ - Focused path view is designed as the subagent-local representation, with full implementation possible after the root recursive tree is stable.
600
+ - Validation centers on deterministic Vitest coverage for tree linking, rendering, overflow, settings, descendant counts, and existing width-safety behavior.