@leing2021/super-pi 0.22.1 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +253 -211
- package/extensions/ce-core/index.ts +190 -7
- package/extensions/ce-core/tools/parallel-subagent.ts +3 -3
- package/extensions/ce-core/tools/subagent.ts +2 -2
- package/package.json +4 -20
- package/rules/common/naming.md +154 -0
- package/skills/02-plan/SKILL.md +3 -2
- package/skills/03-work/SKILL.md +9 -6
- package/skills/04-review/SKILL.md +3 -2
- package/skills/06-next/SKILL.md +38 -10
- package/skills/06-next/references/recommendation-logic.md +2 -2
- package/skills/07-worktree/SKILL.md +9 -8
- package/skills/{09-help → 08-help}/SKILL.md +3 -3
- package/skills/{09-help → 08-help}/references/workflow-sequence.md +2 -1
- package/skills/references/language-detection.md +44 -0
- package/LICENSE +0 -21
- package/THIRD-PARTY-NOTICES.md +0 -35
- package/extensions/subagent/__tests__/async-job-tracker.test.ts +0 -21
- package/extensions/subagent/__tests__/execution-activity.test.ts +0 -15
- package/extensions/subagent/__tests__/parallel-render-stress.test.ts +0 -82
- package/extensions/subagent/__tests__/render-dedup.test.ts +0 -98
- package/extensions/subagent/__tests__/render-widget.test.ts +0 -28
- package/extensions/subagent/__tests__/throttle.test.ts +0 -90
- package/extensions/subagent/agent-management.ts +0 -596
- package/extensions/subagent/agent-manager-chain-detail.ts +0 -163
- package/extensions/subagent/agent-manager-detail.ts +0 -232
- package/extensions/subagent/agent-manager-edit.ts +0 -391
- package/extensions/subagent/agent-manager-list.ts +0 -279
- package/extensions/subagent/agent-manager-parallel.ts +0 -305
- package/extensions/subagent/agent-manager.ts +0 -706
- package/extensions/subagent/agent-scope.ts +0 -9
- package/extensions/subagent/agent-selection.ts +0 -26
- package/extensions/subagent/agent-serializer.ts +0 -124
- package/extensions/subagent/agent-templates.ts +0 -63
- package/extensions/subagent/agents/context-builder.md +0 -37
- package/extensions/subagent/agents/delegate.md +0 -9
- package/extensions/subagent/agents/oracle.md +0 -73
- package/extensions/subagent/agents/planner.md +0 -52
- package/extensions/subagent/agents/researcher.md +0 -50
- package/extensions/subagent/agents/reviewer.md +0 -38
- package/extensions/subagent/agents/scout.md +0 -48
- package/extensions/subagent/agents/worker.md +0 -52
- package/extensions/subagent/agents.ts +0 -762
- package/extensions/subagent/artifacts.ts +0 -101
- package/extensions/subagent/async-execution.ts +0 -521
- package/extensions/subagent/async-job-tracker.ts +0 -240
- package/extensions/subagent/async-status.ts +0 -242
- package/extensions/subagent/chain-clarify.ts +0 -1365
- package/extensions/subagent/chain-execution.ts +0 -854
- package/extensions/subagent/chain-serializer.ts +0 -127
- package/extensions/subagent/completion-dedupe.ts +0 -66
- package/extensions/subagent/doctor.ts +0 -201
- package/extensions/subagent/execution.ts +0 -748
- package/extensions/subagent/file-coalescer.ts +0 -43
- package/extensions/subagent/fork-context.ts +0 -64
- package/extensions/subagent/formatters.ts +0 -123
- package/extensions/subagent/frontmatter.ts +0 -32
- package/extensions/subagent/index.ts +0 -586
- package/extensions/subagent/intercom-bridge.ts +0 -241
- package/extensions/subagent/jsonl-writer.ts +0 -84
- package/extensions/subagent/model-fallback.ts +0 -109
- package/extensions/subagent/notify.ts +0 -111
- package/extensions/subagent/parallel-utils.ts +0 -109
- package/extensions/subagent/pi-args.ts +0 -139
- package/extensions/subagent/pi-spawn.ts +0 -101
- package/extensions/subagent/post-exit-stdio-guard.ts +0 -88
- package/extensions/subagent/prompt-template-bridge.ts +0 -400
- package/extensions/subagent/prompts/gather-context-and-clarify.md +0 -13
- package/extensions/subagent/prompts/parallel-cleanup.md +0 -42
- package/extensions/subagent/prompts/parallel-research.md +0 -50
- package/extensions/subagent/prompts/parallel-review.md +0 -40
- package/extensions/subagent/render-helpers.ts +0 -83
- package/extensions/subagent/render.ts +0 -908
- package/extensions/subagent/result-intercom.ts +0 -238
- package/extensions/subagent/result-watcher.ts +0 -172
- package/extensions/subagent/run-history.ts +0 -58
- package/extensions/subagent/run-status.ts +0 -137
- package/extensions/subagent/schemas.ts +0 -165
- package/extensions/subagent/session-tokens.ts +0 -51
- package/extensions/subagent/settings.ts +0 -368
- package/extensions/subagent/single-output.ts +0 -98
- package/extensions/subagent/skills.ts +0 -627
- package/extensions/subagent/slash-bridge.ts +0 -177
- package/extensions/subagent/slash-commands.ts +0 -304
- package/extensions/subagent/slash-live-state.ts +0 -295
- package/extensions/subagent/subagent-control.ts +0 -151
- package/extensions/subagent/subagent-executor.ts +0 -1915
- package/extensions/subagent/subagent-prompt-runtime.ts +0 -76
- package/extensions/subagent/subagent-runner.ts +0 -1471
- package/extensions/subagent/subagents-status.ts +0 -473
- package/extensions/subagent/text-editor.ts +0 -273
- package/extensions/subagent/throttle.ts +0 -77
- package/extensions/subagent/top-level-async.ts +0 -16
- package/extensions/subagent/types.ts +0 -624
- package/extensions/subagent/utils.ts +0 -457
- package/extensions/subagent/worktree.ts +0 -580
- package/extensions/super-pi-extension/agents/ce-oracle.md +0 -109
- package/extensions/super-pi-extension/agents/ce-planner.md +0 -116
- package/extensions/super-pi-extension/agents/ce-reviewer.md +0 -99
- package/extensions/super-pi-extension/agents/ce-scout.md +0 -80
- package/extensions/super-pi-extension/agents/ce-worker.md +0 -78
- package/extensions/super-pi-extension/chains/ce-parallel-review.chain.md +0 -34
- package/extensions/super-pi-extension/chains/ce-review-only.chain.md +0 -12
- package/extensions/super-pi-extension/chains/ce-standard.chain.md +0 -28
- package/extensions/super-pi-extension/index.ts +0 -129
- package/extensions/super-pi-extension/model-sync.ts +0 -204
- package/skills/08-status/SKILL.md +0 -41
- package/skills/08-status/references/artifact-locations.md +0 -10
- package/skills/10-rules/SKILL.md +0 -76
- 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
|
-
}
|