pi-subagents 0.23.1 → 0.24.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.
- package/CHANGELOG.md +18 -0
- package/README.md +13 -76
- package/package.json +14 -13
- package/prompts/parallel-cleanup.md +11 -1
- package/prompts/parallel-review.md +11 -1
- package/skills/pi-subagents/SKILL.md +11 -12
- package/src/agents/agent-management.ts +2 -2
- package/src/agents/agent-serializer.ts +0 -42
- package/src/agents/agents.ts +1 -1
- package/src/extension/control-notices.ts +1 -1
- package/src/extension/index.ts +6 -6
- package/src/intercom/intercom-bridge.ts +1 -2
- package/src/runs/background/async-execution.ts +37 -11
- package/src/runs/background/async-job-tracker.ts +1 -1
- package/src/runs/background/async-status.ts +16 -50
- package/src/runs/background/notify.ts +1 -1
- package/src/runs/background/result-watcher.ts +1 -1
- package/src/runs/background/run-status.ts +9 -10
- package/src/runs/background/subagent-runner.ts +5 -19
- package/src/runs/foreground/chain-clarify.ts +186 -221
- package/src/runs/foreground/chain-execution.ts +2 -2
- package/src/runs/foreground/execution.ts +1 -1
- package/src/runs/foreground/subagent-executor.ts +6 -6
- package/src/runs/shared/completion-guard.ts +1 -1
- package/src/runs/shared/pi-spawn.ts +32 -15
- package/src/runs/shared/subagent-prompt-runtime.ts +1 -1
- package/src/shared/fork-context.ts +22 -7
- package/src/shared/status-format.ts +49 -0
- package/src/shared/types.ts +3 -8
- package/src/shared/utils.ts +1 -1
- package/src/slash/slash-bridge.ts +2 -2
- package/src/slash/slash-commands.ts +10 -77
- package/src/slash/slash-live-state.ts +2 -2
- package/src/tui/render-helpers.ts +2 -2
- package/src/tui/render.ts +35 -61
- package/src/agents/agent-templates.ts +0 -60
- package/src/manager-ui/agent-manager-chain-detail.ts +0 -164
- package/src/manager-ui/agent-manager-detail.ts +0 -235
- package/src/manager-ui/agent-manager-edit.ts +0 -456
- package/src/manager-ui/agent-manager-list.ts +0 -283
- package/src/manager-ui/agent-manager-parallel.ts +0 -302
- package/src/manager-ui/agent-manager.ts +0 -732
- package/src/tui/subagents-status.ts +0 -621
- package/src/tui/text-editor.ts +0 -286
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { formatDuration, formatTokens, shortenPath } from "../../shared/formatters.ts";
|
|
4
|
-
import {
|
|
4
|
+
import { formatActivityLabel, formatParallelOutcome } from "../../shared/status-format.ts";
|
|
5
|
+
import { type ActivityState, type AsyncJobStep, type AsyncParallelGroupStatus, type AsyncStatus, type SubagentRunMode, type TokenUsage } from "../../shared/types.ts";
|
|
5
6
|
import { readStatus } from "../../shared/utils.ts";
|
|
6
7
|
import { flatToLogicalStepIndex, normalizeParallelGroups } from "./parallel-groups.ts";
|
|
7
8
|
import { reconcileAsyncRun } from "./stale-run-reconciler.ts";
|
|
@@ -9,7 +10,7 @@ import { reconcileAsyncRun } from "./stale-run-reconciler.ts";
|
|
|
9
10
|
interface AsyncRunStepSummary {
|
|
10
11
|
index: number;
|
|
11
12
|
agent: string;
|
|
12
|
-
status:
|
|
13
|
+
status: AsyncJobStep["status"];
|
|
13
14
|
activityState?: ActivityState;
|
|
14
15
|
lastActivityAt?: number;
|
|
15
16
|
currentTool?: string;
|
|
@@ -65,16 +66,6 @@ interface AsyncRunListOptions {
|
|
|
65
66
|
reconcile?: boolean;
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
export interface AsyncRunOverlayData {
|
|
69
|
-
active: AsyncRunSummary[];
|
|
70
|
-
recent: AsyncRunSummary[];
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export interface AsyncRunOverlayOptions {
|
|
74
|
-
recentLimit?: number;
|
|
75
|
-
sessionId?: string;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
69
|
function getErrorMessage(error: unknown): string {
|
|
79
70
|
return error instanceof Error ? error.message : String(error);
|
|
80
71
|
}
|
|
@@ -185,9 +176,9 @@ function sortRuns(runs: AsyncRunSummary[]): AsyncRunSummary[] {
|
|
|
185
176
|
switch (state) {
|
|
186
177
|
case "running": return 0;
|
|
187
178
|
case "queued": return 1;
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
179
|
+
case "failed": return 2;
|
|
180
|
+
case "paused": return 2;
|
|
181
|
+
case "complete": return 3;
|
|
191
182
|
}
|
|
192
183
|
};
|
|
193
184
|
return [...runs].sort((a, b) => {
|
|
@@ -229,35 +220,15 @@ export function listAsyncRuns(asyncDirRoot: string, options: AsyncRunListOptions
|
|
|
229
220
|
return options.limit !== undefined ? sorted.slice(0, options.limit) : sorted;
|
|
230
221
|
}
|
|
231
222
|
|
|
232
|
-
export function listAsyncRunsForOverlay(asyncDirRoot: string, options: AsyncRunOverlayOptions = {}): AsyncRunOverlayData {
|
|
233
|
-
const recentLimit = options.recentLimit ?? 5;
|
|
234
|
-
const all = listAsyncRuns(asyncDirRoot, { sessionId: options.sessionId });
|
|
235
|
-
const recent = all
|
|
236
|
-
.filter((run) => run.state === "complete" || run.state === "failed" || run.state === "paused")
|
|
237
|
-
.sort((a, b) => (b.lastUpdate ?? b.endedAt ?? b.startedAt) - (a.lastUpdate ?? a.endedAt ?? a.startedAt))
|
|
238
|
-
.slice(0, recentLimit);
|
|
239
|
-
return {
|
|
240
|
-
active: all.filter((run) => run.state === "queued" || run.state === "running"),
|
|
241
|
-
recent,
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
|
|
245
223
|
function formatActivityFacts(input: { activityState?: ActivityState; lastActivityAt?: number; currentTool?: string; currentToolStartedAt?: number; currentPath?: string; turnCount?: number; toolCount?: number }): string | undefined {
|
|
246
224
|
const facts: string[] = [];
|
|
247
|
-
if (input.currentTool && input.currentToolStartedAt) facts.push(`tool ${input.currentTool} ${formatDuration(Math.max(0, Date.now() - input.currentToolStartedAt))}`);
|
|
225
|
+
if (input.currentTool && input.currentToolStartedAt !== undefined) facts.push(`tool ${input.currentTool} ${formatDuration(Math.max(0, Date.now() - input.currentToolStartedAt))}`);
|
|
248
226
|
else if (input.currentTool) facts.push(`tool ${input.currentTool}`);
|
|
249
227
|
if (input.currentPath) facts.push(shortenPath(input.currentPath));
|
|
250
228
|
if (input.turnCount !== undefined) facts.push(`${input.turnCount} turns`);
|
|
251
229
|
if (input.toolCount !== undefined) facts.push(`${input.toolCount} tools`);
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
if (input.activityState === "active_long_running") return ["active but long-running", ...facts].join(" | ");
|
|
255
|
-
return facts.length ? facts.join(" | ") : undefined;
|
|
256
|
-
}
|
|
257
|
-
const elapsed = formatDuration(Math.max(0, Date.now() - input.lastActivityAt));
|
|
258
|
-
if (input.activityState === "needs_attention") return [`no activity for ${elapsed}`, ...facts].join(" | ");
|
|
259
|
-
if (input.activityState === "active_long_running") return [`active but long-running; last activity ${elapsed} ago`, ...facts].join(" | ");
|
|
260
|
-
return [`active ${elapsed} ago`, ...facts].join(" | ");
|
|
230
|
+
const activity = formatActivityLabel(input.lastActivityAt, input.activityState);
|
|
231
|
+
return activity || facts.length ? [activity, ...facts].filter(Boolean).join(" | ") : undefined;
|
|
261
232
|
}
|
|
262
233
|
|
|
263
234
|
function formatStepLine(step: AsyncRunStepSummary): string {
|
|
@@ -270,16 +241,9 @@ function formatStepLine(step: AsyncRunStepSummary): string {
|
|
|
270
241
|
return parts.join(" | ");
|
|
271
242
|
}
|
|
272
243
|
|
|
273
|
-
function
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
const failed = steps.filter((step) => step.status === "failed").length;
|
|
277
|
-
const paused = steps.filter((step) => step.status === "paused").length;
|
|
278
|
-
const parts = [`${done}/${total} done`];
|
|
279
|
-
if (showRunning && running > 0) parts.unshift(running === 1 ? "1 agent running" : `${running} agents running`);
|
|
280
|
-
if (failed > 0) parts.push(`${failed} failed`);
|
|
281
|
-
if (paused > 0) parts.push(`${paused} paused`);
|
|
282
|
-
return parts.join(" · ");
|
|
244
|
+
export function formatAsyncRunOutputPath(run: Pick<AsyncRunSummary, "asyncDir" | "outputFile">): string | undefined {
|
|
245
|
+
if (!run.outputFile) return undefined;
|
|
246
|
+
return path.isAbsolute(run.outputFile) ? run.outputFile : path.join(run.asyncDir, run.outputFile);
|
|
283
247
|
}
|
|
284
248
|
|
|
285
249
|
export function formatAsyncRunProgressLabel(run: Pick<AsyncRunSummary, "mode" | "state" | "currentStep" | "chainStepCount" | "parallelGroups" | "steps">): string {
|
|
@@ -291,11 +255,11 @@ export function formatAsyncRunProgressLabel(run: Pick<AsyncRunSummary, "mode" |
|
|
|
291
255
|
: undefined;
|
|
292
256
|
if (activeGroup) {
|
|
293
257
|
const groupSteps = run.steps.slice(activeGroup.start, activeGroup.start + activeGroup.count);
|
|
294
|
-
const groupLabel =
|
|
258
|
+
const groupLabel = formatParallelOutcome(groupSteps, activeGroup.count, { showRunning: run.state === "running" });
|
|
295
259
|
if (run.mode === "parallel") return groupLabel;
|
|
296
260
|
return `step ${activeGroup.stepIndex + 1}/${chainStepCount} · parallel group: ${groupLabel}`;
|
|
297
261
|
}
|
|
298
|
-
if (run.mode === "parallel") return
|
|
262
|
+
if (run.mode === "parallel") return formatParallelOutcome(run.steps, stepCount, { showRunning: run.state === "running" });
|
|
299
263
|
if (run.mode === "chain" && run.currentStep !== undefined && groups.length > 0) {
|
|
300
264
|
const logicalStep = flatToLogicalStepIndex(run.currentStep, chainStepCount, groups);
|
|
301
265
|
return `step ${logicalStep + 1}/${chainStepCount}`;
|
|
@@ -319,6 +283,8 @@ export function formatAsyncRunList(runs: AsyncRunSummary[], heading = "Active as
|
|
|
319
283
|
for (const step of run.steps) {
|
|
320
284
|
lines.push(` ${formatStepLine(step)}`);
|
|
321
285
|
}
|
|
286
|
+
const outputPath = formatAsyncRunOutputPath(run);
|
|
287
|
+
if (outputPath) lines.push(` output: ${shortenPath(outputPath)}`);
|
|
322
288
|
if (run.sessionFile) lines.push(` session: ${shortenPath(run.sessionFile)}`);
|
|
323
289
|
lines.push("");
|
|
324
290
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Subagent completion notifications.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import type { ExtensionAPI } from "@
|
|
5
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
6
6
|
import { buildCompletionKey, getGlobalSeenMap, markSeenWithTtl } from "./completion-dedupe.ts";
|
|
7
7
|
import { SUBAGENT_ASYNC_COMPLETE_EVENT } from "../../shared/types.ts";
|
|
8
8
|
|
|
@@ -87,7 +87,7 @@ export function createResultWatcher(
|
|
|
87
87
|
intercomTarget?: string;
|
|
88
88
|
};
|
|
89
89
|
if (data.sessionId && data.sessionId !== state.currentSessionId) return;
|
|
90
|
-
if (!data.sessionId && data.cwd && data.cwd !== state.baseCwd) return;
|
|
90
|
+
if (!data.sessionId && data.cwd && (!state.baseCwd || data.cwd !== state.baseCwd)) return;
|
|
91
91
|
|
|
92
92
|
const now = Date.now();
|
|
93
93
|
const completionKey = buildCompletionKey(data, `result:${file}`);
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import type { AgentToolResult } from "@
|
|
4
|
-
import { formatAsyncRunList, formatAsyncRunProgressLabel, listAsyncRuns } from "./async-status.ts";
|
|
3
|
+
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
4
|
+
import { formatAsyncRunList, formatAsyncRunOutputPath, formatAsyncRunProgressLabel, listAsyncRuns } from "./async-status.ts";
|
|
5
|
+
import { formatActivityLabel } from "../../shared/status-format.ts";
|
|
5
6
|
import { ASYNC_DIR, RESULTS_DIR, type AsyncStatus, type Details } from "../../shared/types.ts";
|
|
6
7
|
import { resolveSubagentIntercomTarget } from "../../intercom/intercom-bridge.ts";
|
|
7
8
|
import { resolveAsyncRunLocation } from "./async-resume.ts";
|
|
@@ -22,12 +23,6 @@ interface RunStatusDeps {
|
|
|
22
23
|
now?: () => number;
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
function activityText(activityState: unknown, lastActivityAt: unknown): string | undefined {
|
|
26
|
-
if (typeof lastActivityAt !== "number") return undefined;
|
|
27
|
-
const seconds = Math.floor(Math.max(0, Date.now() - lastActivityAt) / 1000);
|
|
28
|
-
return activityState === "needs_attention" ? `no activity for ${seconds}s` : `active ${seconds}s ago`;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
26
|
function hasExistingSessionFile(value: unknown): value is string {
|
|
32
27
|
return typeof value === "string" && fs.existsSync(value);
|
|
33
28
|
}
|
|
@@ -119,6 +114,7 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
119
114
|
const logPath = path.join(asyncDir, `subagent-log-${effectiveRunId}.md`);
|
|
120
115
|
const eventsPath = path.join(asyncDir, "events.jsonl");
|
|
121
116
|
if (status) {
|
|
117
|
+
const outputPath = formatAsyncRunOutputPath({ asyncDir, outputFile: status.outputFile });
|
|
122
118
|
const progressLabel = formatAsyncRunProgressLabel({
|
|
123
119
|
mode: status.mode,
|
|
124
120
|
state: status.state,
|
|
@@ -129,7 +125,7 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
129
125
|
});
|
|
130
126
|
const started = new Date(status.startedAt).toISOString();
|
|
131
127
|
const updated = status.lastUpdate ? new Date(status.lastUpdate).toISOString() : "n/a";
|
|
132
|
-
const statusActivityText = status.state === "running" ?
|
|
128
|
+
const statusActivityText = status.state === "running" ? formatActivityLabel(status.lastActivityAt, status.activityState) : undefined;
|
|
133
129
|
|
|
134
130
|
const lines = [
|
|
135
131
|
`Run: ${status.runId}`,
|
|
@@ -140,13 +136,16 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
140
136
|
`Started: ${started}`,
|
|
141
137
|
`Updated: ${updated}`,
|
|
142
138
|
`Dir: ${asyncDir}`,
|
|
139
|
+
outputPath ? `Output: ${outputPath}` : undefined,
|
|
143
140
|
reconciliation.message ? `Diagnosis: ${reconciliation.message}` : undefined,
|
|
144
141
|
reconciliation.resultPath && fs.existsSync(reconciliation.resultPath) ? `Result: ${reconciliation.resultPath}` : undefined,
|
|
145
142
|
].filter((line): line is string => Boolean(line));
|
|
146
143
|
for (const [index, step] of (status.steps ?? []).entries()) {
|
|
147
|
-
const stepActivityText = step.status === "running" ?
|
|
144
|
+
const stepActivityText = step.status === "running" ? formatActivityLabel(step.lastActivityAt, step.activityState) : undefined;
|
|
148
145
|
const errorText = step.error ? `, error: ${step.error}` : "";
|
|
149
146
|
lines.push(`${stepLineLabel(status, index)}: ${step.agent} ${step.status}${stepActivityText ? `, ${stepActivityText}` : ""}${errorText}`);
|
|
147
|
+
const stepOutputPath = path.join(asyncDir, `output-${index}.log`);
|
|
148
|
+
if (stepOutputPath !== outputPath && fs.existsSync(stepOutputPath)) lines.push(` Output: ${stepOutputPath}`);
|
|
150
149
|
if (step.status === "running") {
|
|
151
150
|
lines.push(` Intercom target: ${resolveSubagentIntercomTarget(status.runId, step.agent, index)} (if registered)`);
|
|
152
151
|
}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { spawn, spawnSync } from "node:child_process";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
|
-
import { createRequire } from "node:module";
|
|
4
3
|
import * as path from "node:path";
|
|
5
4
|
import { pathToFileURL } from "node:url";
|
|
6
|
-
import type { Message } from "@
|
|
5
|
+
import type { Message } from "@earendil-works/pi-ai";
|
|
7
6
|
import { writeAtomicJson } from "../../shared/atomic-json.ts";
|
|
8
7
|
import { appendJsonl, getArtifactPaths } from "../../shared/artifacts.ts";
|
|
9
|
-
import { getPiSpawnCommand } from "../shared/pi-spawn.ts";
|
|
8
|
+
import { PI_CODING_AGENT_PACKAGE, getPiSpawnCommand, resolveInstalledPiPackageRoot } from "../shared/pi-spawn.ts";
|
|
10
9
|
import { captureSingleOutputSnapshot, finalizeSingleOutput, formatSavedOutputReference, resolveSingleOutput, type SingleOutputSnapshot } from "../shared/single-output.ts";
|
|
11
10
|
import {
|
|
12
11
|
type ActivityState,
|
|
@@ -109,7 +108,6 @@ interface StepResult {
|
|
|
109
108
|
truncated?: boolean;
|
|
110
109
|
}
|
|
111
110
|
|
|
112
|
-
const require = createRequire(import.meta.url);
|
|
113
111
|
const ASYNC_INTERRUPT_SIGNAL: NodeJS.Signals = process.platform === "win32" ? "SIGBREAK" : "SIGUSR2";
|
|
114
112
|
|
|
115
113
|
function findLatestSessionFile(sessionDir: string): string | null {
|
|
@@ -424,21 +422,9 @@ function runPiStreaming(
|
|
|
424
422
|
}
|
|
425
423
|
|
|
426
424
|
function resolvePiPackageRootFallback(): string {
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
let dir = path.dirname(entryPoint);
|
|
431
|
-
while (dir !== path.dirname(dir)) {
|
|
432
|
-
const pkgJsonPath = path.join(dir, "package.json");
|
|
433
|
-
try {
|
|
434
|
-
const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
|
|
435
|
-
if (pkg.name === "@mariozechner/pi-coding-agent") return dir;
|
|
436
|
-
} catch {
|
|
437
|
-
// Keep walking up until a readable package.json is found.
|
|
438
|
-
}
|
|
439
|
-
dir = path.dirname(dir);
|
|
440
|
-
}
|
|
441
|
-
throw new Error("Could not resolve @mariozechner/pi-coding-agent package root");
|
|
425
|
+
const root = resolveInstalledPiPackageRoot();
|
|
426
|
+
if (root) return root;
|
|
427
|
+
throw new Error(`Could not resolve ${PI_CODING_AGENT_PACKAGE} package root`);
|
|
442
428
|
}
|
|
443
429
|
|
|
444
430
|
async function exportSessionHtml(sessionFile: string, outputDir: string, piPackageRoot?: string): Promise<string> {
|