@leing2021/super-pi 0.22.1 → 0.23.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 (117) hide show
  1. package/README.md +66 -293
  2. package/extensions/ce-core/index.ts +276 -48
  3. package/extensions/ce-core/tools/parallel-subagent.ts +3 -3
  4. package/extensions/ce-core/tools/subagent.ts +2 -2
  5. package/package.json +4 -20
  6. package/rules/common/naming.md +154 -0
  7. package/skills/01-brainstorm/SKILL.md +42 -79
  8. package/skills/01-brainstorm/references/ce-brainstorm-mode.md +47 -0
  9. package/skills/02-plan/SKILL.md +43 -61
  10. package/skills/02-plan/references/solution-search.md +46 -0
  11. package/skills/03-work/SKILL.md +34 -52
  12. package/skills/03-work/references/completion-report.md +51 -0
  13. package/skills/04-review/SKILL.md +44 -51
  14. package/skills/04-review/references/solution-search.md +46 -0
  15. package/skills/06-next/SKILL.md +35 -20
  16. package/skills/06-next/references/recommendation-logic.md +47 -2
  17. package/skills/07-worktree/SKILL.md +21 -20
  18. package/skills/08-help/SKILL.md +46 -0
  19. package/skills/08-help/references/workflow-sequence.md +135 -0
  20. package/skills/references/language-detection.md +44 -0
  21. package/LICENSE +0 -21
  22. package/THIRD-PARTY-NOTICES.md +0 -35
  23. package/extensions/subagent/__tests__/async-job-tracker.test.ts +0 -21
  24. package/extensions/subagent/__tests__/execution-activity.test.ts +0 -15
  25. package/extensions/subagent/__tests__/parallel-render-stress.test.ts +0 -82
  26. package/extensions/subagent/__tests__/render-dedup.test.ts +0 -98
  27. package/extensions/subagent/__tests__/render-widget.test.ts +0 -28
  28. package/extensions/subagent/__tests__/throttle.test.ts +0 -90
  29. package/extensions/subagent/agent-management.ts +0 -596
  30. package/extensions/subagent/agent-manager-chain-detail.ts +0 -163
  31. package/extensions/subagent/agent-manager-detail.ts +0 -232
  32. package/extensions/subagent/agent-manager-edit.ts +0 -391
  33. package/extensions/subagent/agent-manager-list.ts +0 -279
  34. package/extensions/subagent/agent-manager-parallel.ts +0 -305
  35. package/extensions/subagent/agent-manager.ts +0 -706
  36. package/extensions/subagent/agent-scope.ts +0 -9
  37. package/extensions/subagent/agent-selection.ts +0 -26
  38. package/extensions/subagent/agent-serializer.ts +0 -124
  39. package/extensions/subagent/agent-templates.ts +0 -63
  40. package/extensions/subagent/agents/context-builder.md +0 -37
  41. package/extensions/subagent/agents/delegate.md +0 -9
  42. package/extensions/subagent/agents/oracle.md +0 -73
  43. package/extensions/subagent/agents/planner.md +0 -52
  44. package/extensions/subagent/agents/researcher.md +0 -50
  45. package/extensions/subagent/agents/reviewer.md +0 -38
  46. package/extensions/subagent/agents/scout.md +0 -48
  47. package/extensions/subagent/agents/worker.md +0 -52
  48. package/extensions/subagent/agents.ts +0 -762
  49. package/extensions/subagent/artifacts.ts +0 -101
  50. package/extensions/subagent/async-execution.ts +0 -521
  51. package/extensions/subagent/async-job-tracker.ts +0 -240
  52. package/extensions/subagent/async-status.ts +0 -242
  53. package/extensions/subagent/chain-clarify.ts +0 -1365
  54. package/extensions/subagent/chain-execution.ts +0 -854
  55. package/extensions/subagent/chain-serializer.ts +0 -127
  56. package/extensions/subagent/completion-dedupe.ts +0 -66
  57. package/extensions/subagent/doctor.ts +0 -201
  58. package/extensions/subagent/execution.ts +0 -748
  59. package/extensions/subagent/file-coalescer.ts +0 -43
  60. package/extensions/subagent/fork-context.ts +0 -64
  61. package/extensions/subagent/formatters.ts +0 -123
  62. package/extensions/subagent/frontmatter.ts +0 -32
  63. package/extensions/subagent/index.ts +0 -586
  64. package/extensions/subagent/intercom-bridge.ts +0 -241
  65. package/extensions/subagent/jsonl-writer.ts +0 -84
  66. package/extensions/subagent/model-fallback.ts +0 -109
  67. package/extensions/subagent/notify.ts +0 -111
  68. package/extensions/subagent/parallel-utils.ts +0 -109
  69. package/extensions/subagent/pi-args.ts +0 -139
  70. package/extensions/subagent/pi-spawn.ts +0 -101
  71. package/extensions/subagent/post-exit-stdio-guard.ts +0 -88
  72. package/extensions/subagent/prompt-template-bridge.ts +0 -400
  73. package/extensions/subagent/prompts/gather-context-and-clarify.md +0 -13
  74. package/extensions/subagent/prompts/parallel-cleanup.md +0 -42
  75. package/extensions/subagent/prompts/parallel-research.md +0 -50
  76. package/extensions/subagent/prompts/parallel-review.md +0 -40
  77. package/extensions/subagent/render-helpers.ts +0 -83
  78. package/extensions/subagent/render.ts +0 -908
  79. package/extensions/subagent/result-intercom.ts +0 -238
  80. package/extensions/subagent/result-watcher.ts +0 -172
  81. package/extensions/subagent/run-history.ts +0 -58
  82. package/extensions/subagent/run-status.ts +0 -137
  83. package/extensions/subagent/schemas.ts +0 -165
  84. package/extensions/subagent/session-tokens.ts +0 -51
  85. package/extensions/subagent/settings.ts +0 -368
  86. package/extensions/subagent/single-output.ts +0 -98
  87. package/extensions/subagent/skills.ts +0 -627
  88. package/extensions/subagent/slash-bridge.ts +0 -177
  89. package/extensions/subagent/slash-commands.ts +0 -304
  90. package/extensions/subagent/slash-live-state.ts +0 -295
  91. package/extensions/subagent/subagent-control.ts +0 -151
  92. package/extensions/subagent/subagent-executor.ts +0 -1915
  93. package/extensions/subagent/subagent-prompt-runtime.ts +0 -76
  94. package/extensions/subagent/subagent-runner.ts +0 -1471
  95. package/extensions/subagent/subagents-status.ts +0 -473
  96. package/extensions/subagent/text-editor.ts +0 -273
  97. package/extensions/subagent/throttle.ts +0 -77
  98. package/extensions/subagent/top-level-async.ts +0 -16
  99. package/extensions/subagent/types.ts +0 -624
  100. package/extensions/subagent/utils.ts +0 -457
  101. package/extensions/subagent/worktree.ts +0 -580
  102. package/extensions/super-pi-extension/agents/ce-oracle.md +0 -109
  103. package/extensions/super-pi-extension/agents/ce-planner.md +0 -116
  104. package/extensions/super-pi-extension/agents/ce-reviewer.md +0 -99
  105. package/extensions/super-pi-extension/agents/ce-scout.md +0 -80
  106. package/extensions/super-pi-extension/agents/ce-worker.md +0 -78
  107. package/extensions/super-pi-extension/chains/ce-parallel-review.chain.md +0 -34
  108. package/extensions/super-pi-extension/chains/ce-review-only.chain.md +0 -12
  109. package/extensions/super-pi-extension/chains/ce-standard.chain.md +0 -28
  110. package/extensions/super-pi-extension/index.ts +0 -129
  111. package/extensions/super-pi-extension/model-sync.ts +0 -204
  112. package/skills/08-status/SKILL.md +0 -41
  113. package/skills/08-status/references/artifact-locations.md +0 -10
  114. package/skills/09-help/SKILL.md +0 -37
  115. package/skills/09-help/references/workflow-sequence.md +0 -9
  116. package/skills/10-rules/SKILL.md +0 -76
  117. package/skills/pi-subagents/SKILL.md +0 -566
@@ -1,473 +0,0 @@
1
- // SPDX-FileCopyrightText: 2025 Nico Bailon
2
- // SPDX-License-Identifier: MIT
3
- // Source: https://github.com/nicobailon/pi-subagents
4
- import * as fs from "node:fs";
5
- import * as path from "node:path";
6
- import type { Theme } from "@mariozechner/pi-coding-agent";
7
- import type { Component, TUI } from "@mariozechner/pi-tui";
8
- import { matchesKey, truncateToWidth } from "@mariozechner/pi-tui";
9
- import { type AsyncRunOverlayData, type AsyncRunSummary, listAsyncRunsForOverlay } from "./async-status.ts";
10
- import { ASYNC_DIR } from "./types.ts";
11
- import { formatDuration, formatTokens, shortenPath } from "./formatters.ts";
12
- import { formatScrollInfo, renderFooter, renderHeader, row } from "./render-helpers.ts";
13
-
14
- const AUTO_REFRESH_MS = 2000;
15
- const DETAIL_EVENT_LIMIT = 8;
16
- const OUTPUT_TAIL_LINES = 20;
17
- const DETAIL_FILE_TAIL_BYTES = 64 * 1024;
18
- const DETAIL_VIEWPORT_HEIGHT = 18;
19
-
20
- interface StatusRow {
21
- kind: "section" | "run";
22
- label: string;
23
- run?: AsyncRunSummary;
24
- }
25
-
26
- interface StatusOverlayDeps {
27
- listRunsForOverlay?: (asyncDirRoot: string, recentLimit?: number) => AsyncRunOverlayData;
28
- refreshMs?: number;
29
- }
30
-
31
- function statusColor(theme: Theme, status: AsyncRunSummary["state"]): string {
32
- switch (status) {
33
- case "running": return theme.fg("warning", status);
34
- case "queued": return theme.fg("accent", status);
35
- case "complete": return theme.fg("success", status);
36
- case "failed": return theme.fg("error", status);
37
- case "paused": return theme.fg("warning", status);
38
- }
39
- }
40
-
41
- function stepStatusColor(theme: Theme, status: string): string {
42
- if (status === "running") return theme.fg("warning", status);
43
- if (status === "pending") return theme.fg("dim", status);
44
- if (status === "complete" || status === "completed") return theme.fg("success", status);
45
- if (status === "failed") return theme.fg("error", status);
46
- if (status === "paused") return theme.fg("warning", status);
47
- return status;
48
- }
49
-
50
- function runLabel(theme: Theme, run: AsyncRunSummary, selected: boolean): string {
51
- const prefix = selected ? theme.fg("accent", ">") : " ";
52
- const stepCount = run.steps.length || 1;
53
- const stepLabel = run.currentStep !== undefined ? `step ${run.currentStep + 1}/${stepCount}` : `steps ${stepCount}`;
54
- const cwd = shortenPath(run.cwd ?? run.asyncDir);
55
- return `${prefix} ${run.id.slice(0, 8)} ${statusColor(theme, run.state)} | ${run.mode} | ${stepLabel} | ${cwd}`;
56
- }
57
-
58
- function selectedIndex(rows: StatusRow[], cursor: number): number {
59
- const runRows = rows.filter((row) => row.kind === "run");
60
- if (runRows.length === 0) return -1;
61
- return Math.max(0, Math.min(cursor, runRows.length - 1));
62
- }
63
-
64
- function selectedRun(rows: StatusRow[], cursor: number): AsyncRunSummary | undefined {
65
- const runRows = rows.filter((row) => row.kind === "run");
66
- const index = selectedIndex(rows, cursor);
67
- return index >= 0 ? runRows[index]?.run : undefined;
68
- }
69
-
70
- function buildRows(active: AsyncRunSummary[], recent: AsyncRunSummary[]): StatusRow[] {
71
- const rows: StatusRow[] = [];
72
- if (active.length > 0) {
73
- rows.push({ kind: "section", label: "Active" });
74
- for (const run of active) rows.push({ kind: "run", label: run.id, run });
75
- }
76
- if (recent.length > 0) {
77
- rows.push({ kind: "section", label: "Recent" });
78
- for (const run of recent) rows.push({ kind: "run", label: run.id, run });
79
- }
80
- return rows;
81
- }
82
-
83
- function resolveRunPath(asyncDir: string, filePath: string): string {
84
- return path.isAbsolute(filePath) ? filePath : path.join(asyncDir, filePath);
85
- }
86
-
87
- function readTailText(filePath: string): { text?: string; warning?: string } {
88
- let fd: number | undefined;
89
- try {
90
- const stat = fs.statSync(filePath);
91
- if (!stat.isFile()) return { warning: `not a file: ${filePath}` };
92
- const start = Math.max(0, stat.size - DETAIL_FILE_TAIL_BYTES);
93
- const length = stat.size - start;
94
- const buffer = Buffer.alloc(length);
95
- fd = fs.openSync(filePath, "r");
96
- const bytesRead = fs.readSync(fd, buffer, 0, length, start);
97
- return { text: buffer.subarray(0, bytesRead).toString("utf-8") };
98
- } catch (error) {
99
- const code = typeof error === "object" && error !== null && "code" in error
100
- ? (error as NodeJS.ErrnoException).code
101
- : undefined;
102
- return { warning: code === "ENOENT" ? `missing ${path.basename(filePath)}: ${filePath}` : `failed to read ${filePath}: ${error instanceof Error ? error.message : String(error)}` };
103
- } finally {
104
- if (fd !== undefined) {
105
- try {
106
- fs.closeSync(fd);
107
- } catch {
108
- // Best effort cleanup after a bounded detail-view read.
109
- }
110
- }
111
- }
112
- }
113
-
114
- function readTailLines(filePath: string, maxLines: number): { lines: string[]; warning?: string } {
115
- const tail = readTailText(filePath);
116
- if (tail.warning) return { lines: [], warning: tail.warning };
117
- const lines = (tail.text ?? "").split("\n").map((line) => line.trimEnd()).filter((line) => line.trim());
118
- return { lines: lines.slice(Math.max(0, lines.length - maxLines)) };
119
- }
120
-
121
- function formatEventTimestamp(value: unknown): string | undefined {
122
- if (typeof value !== "number" || !Number.isFinite(value)) return undefined;
123
- return new Date(value).toISOString();
124
- }
125
-
126
- function formatEventLine(value: Record<string, unknown>): string {
127
- const type = typeof value.type === "string" ? value.type : "event";
128
- const ts = formatEventTimestamp(value.ts ?? value.observedAt);
129
- const stepIndex = typeof value.stepIndex === "number" ? ` step ${value.stepIndex + 1}` : "";
130
- const agent = typeof value.agent === "string"
131
- ? value.agent
132
- : typeof value.subagentAgent === "string"
133
- ? value.subagentAgent
134
- : undefined;
135
- const status = typeof value.status === "string" ? value.status : undefined;
136
- const exitCode = typeof value.exitCode === "number" ? `exit ${value.exitCode}` : undefined;
137
- const event = value.event && typeof value.event === "object" && !Array.isArray(value.event)
138
- ? value.event as { message?: unknown }
139
- : undefined;
140
- const message = typeof value.message === "string"
141
- ? value.message
142
- : typeof value.error === "string"
143
- ? value.error
144
- : typeof event?.message === "string"
145
- ? event.message
146
- : undefined;
147
- return [ts, type, agent ? `${agent}${stepIndex}` : stepIndex.trim(), status, exitCode, message].filter(Boolean).join(" | ");
148
- }
149
-
150
- function readRecentEvents(eventsPath: string, limit: number): { events: string[]; warning?: string } {
151
- const tail = readTailText(eventsPath);
152
- if (tail.warning) {
153
- return tail.warning.startsWith("missing ") ? { events: [] } : { events: [], warning: tail.warning };
154
- }
155
-
156
- const events: string[] = [];
157
- const lines = (tail.text ?? "").split("\n").filter((line) => line.trim());
158
- for (let i = lines.length - 1; i >= 0 && events.length < limit; i--) {
159
- try {
160
- const parsed = JSON.parse(lines[i]!);
161
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) continue;
162
- events.push(formatEventLine(parsed as Record<string, unknown>));
163
- } catch {
164
- // Skip malformed event records; async writers can be interrupted mid-line.
165
- }
166
- }
167
- return { events: events.reverse() };
168
- }
169
-
170
- export class SubagentsStatusComponent implements Component {
171
- private readonly width = 84;
172
- private readonly viewportHeight = 12;
173
- private readonly listRunsForOverlay: (asyncDirRoot: string, recentLimit?: number) => AsyncRunOverlayData;
174
- private readonly refreshTimer: NodeJS.Timeout;
175
- private screen: "list" | "detail" = "list";
176
- private cursor = 0;
177
- private scrollOffset = 0;
178
- private detailScrollOffset = 0;
179
- private detailRunId: string | undefined;
180
- private active: AsyncRunSummary[] = [];
181
- private recent: AsyncRunSummary[] = [];
182
- private rows: StatusRow[] = [];
183
- private errorMessage?: string;
184
- private tui: TUI;
185
- private theme: Theme;
186
- private done: () => void;
187
-
188
- constructor(
189
- tui: TUI,
190
- theme: Theme,
191
- done: () => void,
192
- deps: StatusOverlayDeps = {},
193
- ) {
194
- this.tui = tui;
195
- this.theme = theme;
196
- this.done = done;
197
- this.listRunsForOverlay = deps.listRunsForOverlay ?? listAsyncRunsForOverlay;
198
- const refreshMs = deps.refreshMs ?? AUTO_REFRESH_MS;
199
- this.reload();
200
- this.refreshTimer = setInterval(() => {
201
- this.reload();
202
- this.tui.requestRender();
203
- }, refreshMs);
204
- this.refreshTimer.unref?.();
205
- }
206
-
207
- private reload(): void {
208
- const previousSelectedId = selectedRun(this.rows, this.cursor)?.id;
209
- try {
210
- const overlayData = this.listRunsForOverlay(ASYNC_DIR, 5);
211
- this.active = overlayData.active;
212
- this.recent = overlayData.recent;
213
- this.rows = buildRows(this.active, this.recent);
214
- this.errorMessage = undefined;
215
- this.restoreSelection(previousSelectedId);
216
- this.ensureScrollVisible();
217
- } catch (error) {
218
- this.active = [];
219
- this.recent = [];
220
- this.rows = [];
221
- this.cursor = 0;
222
- this.scrollOffset = 0;
223
- this.errorMessage = error instanceof Error ? error.message : String(error);
224
- }
225
- }
226
-
227
- private restoreSelection(previousSelectedId?: string): void {
228
- const runRows = this.rows.filter((row) => row.kind === "run");
229
- if (runRows.length === 0) {
230
- this.cursor = 0;
231
- return;
232
- }
233
- if (!previousSelectedId) {
234
- this.cursor = Math.min(this.cursor, runRows.length - 1);
235
- return;
236
- }
237
- const nextIndex = runRows.findIndex((row) => row.run?.id === previousSelectedId);
238
- if (nextIndex !== -1) {
239
- this.cursor = nextIndex;
240
- return;
241
- }
242
- this.cursor = Math.min(this.cursor, runRows.length - 1);
243
- }
244
-
245
- private ensureScrollVisible(): void {
246
- if (this.rows.length <= this.viewportHeight) {
247
- this.scrollOffset = 0;
248
- return;
249
- }
250
- const selected = selectedRun(this.rows, this.cursor);
251
- if (!selected) {
252
- this.scrollOffset = 0;
253
- return;
254
- }
255
- const rowIndex = this.rows.findIndex((row) => row.kind === "run" && row.run?.id === selected.id);
256
- if (rowIndex === -1) return;
257
- if (rowIndex < this.scrollOffset) this.scrollOffset = rowIndex;
258
- if (rowIndex >= this.scrollOffset + this.viewportHeight) {
259
- this.scrollOffset = rowIndex - this.viewportHeight + 1;
260
- }
261
- }
262
-
263
- private renderRunDetails(run: AsyncRunSummary, width: number, innerW: number): string[] {
264
- const lines = [
265
- row(`cwd: ${truncateToWidth(shortenPath(run.cwd ?? run.asyncDir), innerW - 5)}`, width, this.theme),
266
- ];
267
- if (run.outputFile) {
268
- lines.push(row(`output: ${truncateToWidth(shortenPath(run.outputFile), innerW - 8)}`, width, this.theme));
269
- }
270
- if (run.sessionFile) {
271
- lines.push(row(`session: ${truncateToWidth(shortenPath(run.sessionFile), innerW - 9)}`, width, this.theme));
272
- }
273
- lines.push(...this.renderStepRows(run, width, innerW));
274
- return lines;
275
- }
276
-
277
- private renderStepRows(run: AsyncRunSummary, width: number, innerW: number): string[] {
278
- const lines: string[] = [];
279
- for (const step of run.steps) {
280
- const model = step.model ? ` | ${step.model}` : "";
281
- const attempts = step.attemptedModels && step.attemptedModels.length > 1
282
- ? ` | attempts ${step.attemptedModels.length}`
283
- : "";
284
- const duration = step.durationMs !== undefined ? ` | ${formatDuration(step.durationMs)}` : "";
285
- const tokens = step.tokens ? ` | ${formatTokens(step.tokens.total)} tok` : "";
286
- const activity = step.lastActivityAt
287
- ? step.activityState === "needs_attention"
288
- ? ` | no activity for ${formatDuration(Math.max(0, Date.now() - step.lastActivityAt))}`
289
- : ` | active ${formatDuration(Math.max(0, Date.now() - step.lastActivityAt))} ago`
290
- : "";
291
- const line = ` ${step.index + 1}. ${step.agent} | ${stepStatusColor(this.theme, step.status)}${activity}${model}${attempts}${duration}${tokens}`;
292
- lines.push(row(truncateToWidth(line, innerW), width, this.theme));
293
- if (step.error) {
294
- lines.push(row(truncateToWidth(` ${step.error}`, innerW), width, this.theme));
295
- }
296
- }
297
- if (run.steps.length === 0) {
298
- lines.push(row(this.theme.fg("dim", " No step details available yet."), width, this.theme));
299
- }
300
- return lines;
301
- }
302
-
303
- private renderDetail(run: AsyncRunSummary, width: number, innerW: number): string[] {
304
- const stepCount = run.steps.length || 1;
305
- const stepLabel = run.currentStep !== undefined ? `step ${run.currentStep + 1}/${stepCount}` : `steps ${stepCount}`;
306
- const duration = run.endedAt !== undefined
307
- ? formatDuration(Math.max(0, run.endedAt - run.startedAt))
308
- : formatDuration(Math.max(0, Date.now() - run.startedAt));
309
- const activity = run.lastActivityAt
310
- ? run.activityState === "needs_attention"
311
- ? `no activity for ${formatDuration(Math.max(0, Date.now() - run.lastActivityAt))}`
312
- : `active ${formatDuration(Math.max(0, Date.now() - run.lastActivityAt))} ago`
313
- : undefined;
314
-
315
- const body: string[] = [];
316
- body.push(row(`${run.id} | ${statusColor(this.theme, run.state)} | ${run.mode} | ${stepLabel} | ${duration}`, width, this.theme));
317
- if (activity) body.push(row(truncateToWidth(activity, innerW), width, this.theme));
318
- body.push(row("", width, this.theme));
319
- body.push(row(this.theme.fg("accent", "Steps"), width, this.theme));
320
- body.push(...this.renderStepRows(run, width, innerW));
321
-
322
- const eventsPath = path.join(run.asyncDir, "events.jsonl");
323
- const eventResult = readRecentEvents(eventsPath, DETAIL_EVENT_LIMIT);
324
- body.push(row("", width, this.theme));
325
- body.push(row(this.theme.fg("accent", "Recent events"), width, this.theme));
326
- if (eventResult.warning) body.push(row(this.theme.fg("warning", truncateToWidth(eventResult.warning, innerW)), width, this.theme));
327
- if (eventResult.events.length === 0 && !eventResult.warning) body.push(row(this.theme.fg("dim", " No events recorded."), width, this.theme));
328
- for (const event of eventResult.events) {
329
- body.push(row(truncateToWidth(` ${event}`, innerW), width, this.theme));
330
- }
331
-
332
- body.push(row("", width, this.theme));
333
- body.push(row(this.theme.fg("accent", "Output tail"), width, this.theme));
334
- if (run.outputFile) {
335
- const outputPath = resolveRunPath(run.asyncDir, run.outputFile);
336
- const tail = readTailLines(outputPath, OUTPUT_TAIL_LINES);
337
- if (tail.warning) body.push(row(this.theme.fg("warning", truncateToWidth(tail.warning, innerW)), width, this.theme));
338
- else if (tail.lines.length === 0) body.push(row(this.theme.fg("dim", " No output yet."), width, this.theme));
339
- for (const line of tail.lines) body.push(row(truncateToWidth(` ${line}`, innerW), width, this.theme));
340
- } else {
341
- body.push(row(this.theme.fg("dim", " No output file recorded."), width, this.theme));
342
- }
343
-
344
- body.push(row("", width, this.theme));
345
- body.push(row(this.theme.fg("accent", "Paths"), width, this.theme));
346
- body.push(row(truncateToWidth(` cwd: ${shortenPath(run.cwd ?? run.asyncDir)}`, innerW), width, this.theme));
347
- body.push(row(truncateToWidth(` asyncDir: ${shortenPath(run.asyncDir)}`, innerW), width, this.theme));
348
- if (run.outputFile) body.push(row(truncateToWidth(` outputFile: ${shortenPath(resolveRunPath(run.asyncDir, run.outputFile))}`, innerW), width, this.theme));
349
- if (run.sessionFile) body.push(row(truncateToWidth(` sessionFile: ${shortenPath(run.sessionFile)}`, innerW), width, this.theme));
350
- if (run.sessionDir) body.push(row(truncateToWidth(` sessionDir: ${shortenPath(run.sessionDir)}`, innerW), width, this.theme));
351
- const logPath = path.join(run.asyncDir, `subagent-log-${run.id}.md`);
352
- if (fs.existsSync(logPath)) body.push(row(truncateToWidth(` runLog: ${shortenPath(logPath)}`, innerW), width, this.theme));
353
-
354
- const maxOffset = Math.max(0, body.length - DETAIL_VIEWPORT_HEIGHT);
355
- this.detailScrollOffset = Math.min(this.detailScrollOffset, maxOffset);
356
- const visibleBody = body.slice(this.detailScrollOffset, this.detailScrollOffset + DETAIL_VIEWPORT_HEIGHT);
357
- const above = this.detailScrollOffset;
358
- const below = Math.max(0, body.length - (this.detailScrollOffset + visibleBody.length));
359
- const scrollInfo = formatScrollInfo(above, below);
360
- return [
361
- renderHeader(`Subagent Run ${run.id.slice(0, 8)}`, width, this.theme),
362
- ...visibleBody,
363
- scrollInfo ? row(this.theme.fg("dim", scrollInfo), width, this.theme) : row("", width, this.theme),
364
- renderFooter(" ↑↓ scroll esc summary q close read-only detail ", width, this.theme),
365
- ];
366
- }
367
-
368
- handleInput(data: string): void {
369
- if (this.screen === "detail" && matchesKey(data, "escape")) {
370
- this.screen = "list";
371
- this.detailRunId = undefined;
372
- this.tui.requestRender();
373
- return;
374
- }
375
- if (matchesKey(data, "escape") || matchesKey(data, "q") || matchesKey(data, "ctrl+c")) {
376
- this.done();
377
- return;
378
- }
379
- if (this.screen === "detail") {
380
- if (matchesKey(data, "up")) {
381
- this.detailScrollOffset = Math.max(0, this.detailScrollOffset - 1);
382
- this.tui.requestRender();
383
- return;
384
- }
385
- if (matchesKey(data, "down")) {
386
- this.detailScrollOffset++;
387
- this.tui.requestRender();
388
- return;
389
- }
390
- if (matchesKey(data, "pageup")) {
391
- this.detailScrollOffset = Math.max(0, this.detailScrollOffset - DETAIL_VIEWPORT_HEIGHT);
392
- this.tui.requestRender();
393
- return;
394
- }
395
- if (matchesKey(data, "pagedown")) {
396
- this.detailScrollOffset += DETAIL_VIEWPORT_HEIGHT;
397
- this.tui.requestRender();
398
- }
399
- return;
400
- }
401
- if (matchesKey(data, "return")) {
402
- const selected = selectedRun(this.rows, this.cursor);
403
- if (selected) {
404
- this.screen = "detail";
405
- this.detailRunId = selected.id;
406
- this.detailScrollOffset = 0;
407
- this.tui.requestRender();
408
- }
409
- return;
410
- }
411
- if (matchesKey(data, "up")) {
412
- this.cursor = Math.max(0, this.cursor - 1);
413
- this.ensureScrollVisible();
414
- this.tui.requestRender();
415
- return;
416
- }
417
- if (matchesKey(data, "down")) {
418
- const maxCursor = Math.max(0, this.rows.filter((row) => row.kind === "run").length - 1);
419
- this.cursor = Math.min(maxCursor, this.cursor + 1);
420
- this.ensureScrollVisible();
421
- this.tui.requestRender();
422
- }
423
- }
424
-
425
- render(width: number): string[] {
426
- const w = Math.min(width, this.width);
427
- const innerW = w - 2;
428
- const selected = selectedRun(this.rows, this.cursor);
429
- if (this.screen === "detail") {
430
- const detailRun = this.rows.find((row) => row.kind === "run" && row.run?.id === this.detailRunId)?.run;
431
- if (detailRun) return this.renderDetail(detailRun, w, innerW);
432
- return [
433
- renderHeader("Subagent Run", w, this.theme),
434
- row(this.theme.fg("warning", "Selected run is no longer available."), w, this.theme),
435
- renderFooter(" esc summary q close ", w, this.theme),
436
- ];
437
- }
438
- const lines: string[] = [renderHeader("Subagents Status", w, this.theme)];
439
- const rows = this.rows.length > 0 ? this.rows : [{ kind: "section" as const, label: "No async runs found" }];
440
- const visibleRows = rows.slice(this.scrollOffset, this.scrollOffset + this.viewportHeight);
441
- for (const statusRow of visibleRows) {
442
- if (statusRow.kind === "section") {
443
- lines.push(row(this.theme.fg("accent", statusRow.label), w, this.theme));
444
- continue;
445
- }
446
- const isSelected = selected?.id === statusRow.run?.id;
447
- lines.push(row(truncateToWidth(runLabel(this.theme, statusRow.run!, isSelected), innerW), w, this.theme));
448
- }
449
-
450
- const above = this.scrollOffset;
451
- const below = Math.max(0, rows.length - (this.scrollOffset + visibleRows.length));
452
- const scrollInfo = formatScrollInfo(above, below);
453
- if (scrollInfo) lines.push(row(this.theme.fg("dim", scrollInfo), w, this.theme));
454
- else lines.push(row("", w, this.theme));
455
-
456
- if (this.errorMessage) {
457
- lines.push(row(this.theme.fg("error", truncateToWidth(this.errorMessage, innerW)), w, this.theme));
458
- } else if (selected) {
459
- lines.push(row(this.theme.fg("accent", `Selected: ${selected.id}`), w, this.theme));
460
- lines.push(...this.renderRunDetails(selected, w, innerW));
461
- } else {
462
- lines.push(row(this.theme.fg("dim", "No runs selected."), w, this.theme));
463
- }
464
-
465
- const footer = `↑↓ select enter detail esc close summary view ${this.active.length} active / ${this.recent.length} recent`;
466
- lines.push(renderFooter(truncateToWidth(footer, innerW), w, this.theme));
467
- return lines;
468
- }
469
-
470
- dispose(): void {
471
- clearInterval(this.refreshTimer);
472
- }
473
- }