@ferris1225/pi-subagents 4.3.9 → 4.3.11
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 +39 -0
- package/README.md +136 -166
- package/agents/artisan.md +7 -11
- package/agents/scout.md +7 -11
- package/agents/sentinel.md +7 -8
- package/agents/steward.md +8 -9
- package/index.ts +1 -1
- package/package.json +12 -10
- package/src/configuration/setup.ts +16 -14
- package/src/delegation/agents.ts +1 -1
- package/src/delegation/dispatch.ts +18 -23
- package/src/delegation/phase-scope.ts +7 -37
- package/src/delegation/prompt.ts +15 -32
- package/src/execution/rpc-control.ts +2 -29
- package/src/execution/rpc-run.ts +29 -30
- package/src/execution/spawn.ts +21 -20
- package/src/isolation/temp-hygiene.ts +7 -9
- package/src/isolation/worktree.ts +13 -88
- package/src/lifecycle/durable.ts +6 -8
- package/src/lifecycle/runtime.ts +23 -70
- package/src/lifecycle/thread-lifecycle.ts +56 -459
- package/src/lifecycle/thread-restore.ts +13 -35
- package/src/lifecycle/thread-shared.ts +5 -65
- package/src/lifecycle/tools.ts +87 -252
- package/src/presentation/announcements.ts +1 -1
- package/src/presentation/format.ts +6 -16
- package/src/presentation/monitor.ts +0 -91
- package/src/presentation/widget.ts +7 -17
- package/src/execution/session-fork.ts +0 -86
package/src/lifecycle/runtime.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared per-session runtime state for pi-subagents.
|
|
3
3
|
*
|
|
4
|
-
* The extension registers
|
|
4
|
+
* The extension registers dispatch, read-only status, and destructive stop tools
|
|
5
5
|
* that share the background queue, completion batcher, abort controllers per
|
|
6
6
|
* run, and settled-results store.
|
|
7
7
|
* `createRuntime` builds those once per extension load and hands the same object
|
|
8
8
|
* to every registration site, so state stays in one place without globals.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import type { ExtensionAPI
|
|
12
|
-
import type { PhaseScope
|
|
11
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
12
|
+
import type { PhaseScope } from "../delegation/phase-scope.ts";
|
|
13
13
|
import { rmSync } from "node:fs";
|
|
14
14
|
import { resolveSubagentConcurrency, BackgroundTaskQueue } from "../execution/background.ts";
|
|
15
15
|
import {
|
|
@@ -23,21 +23,12 @@ import { type ThinkingLevel } from "../configuration/config.ts";
|
|
|
23
23
|
import { removeThreadRecord, threadRecordFromThread, upsertThreadRecord, type ThreadRecord } from "./durable.ts";
|
|
24
24
|
import { isRunActiveStatus, monitor } from "../presentation/monitor.ts";
|
|
25
25
|
import type { RpcRunControl } from "../execution/rpc-control.ts";
|
|
26
|
-
import type { StartBackgroundInternal } from "./thread-shared.ts";
|
|
27
26
|
import { isFailedResult, type SingleResult } from "../execution/spawn.ts";
|
|
28
27
|
import type { IsolationMode, WorktreeFinalization, WorktreeIsolation } from "../isolation/worktree.ts";
|
|
29
28
|
|
|
30
|
-
export type ThreadState =
|
|
31
|
-
| "queued"
|
|
32
|
-
| "resuming"
|
|
33
|
-
| "running"
|
|
34
|
-
| "interrupting"
|
|
35
|
-
| "parked"
|
|
36
|
-
| "completed"
|
|
37
|
-
| "failed"
|
|
38
|
-
| "stopped";
|
|
29
|
+
export type ThreadState = "queued" | "running" | "interrupting" | "parked" | "completed" | "failed" | "stopped";
|
|
39
30
|
|
|
40
|
-
export type ThreadLifecycleOperation = "
|
|
31
|
+
export type ThreadLifecycleOperation = "stop" | "settle";
|
|
41
32
|
|
|
42
33
|
export interface SubagentThread {
|
|
43
34
|
id: number;
|
|
@@ -46,53 +37,36 @@ export interface SubagentThread {
|
|
|
46
37
|
task: string;
|
|
47
38
|
phaseId?: string;
|
|
48
39
|
scope?: PhaseScope;
|
|
49
|
-
/** Monotonic continuation scope visible while resume preflight is in flight. */
|
|
50
|
-
admissionScope?: PhaseScope;
|
|
51
40
|
/** Capability snapshot used by declared-scope admission, including after restore. */
|
|
52
41
|
writeCapable?: boolean;
|
|
53
42
|
/** Caller-facing cwd in the original worktree. */
|
|
54
43
|
cwd: string;
|
|
55
44
|
/** Actual child cwd (the equivalent path inside an isolated worktree). */
|
|
56
45
|
executionCwd: string;
|
|
57
|
-
/** Level actually used, after clamping to the effective model's capability. */
|
|
58
46
|
thinkingLevel?: ThinkingLevel;
|
|
59
|
-
/** Level the dispatch asked for, before clamping; replayed on every resume. */
|
|
60
47
|
requestedThinkingLevel?: ThinkingLevel;
|
|
61
48
|
isolation: IsolationMode;
|
|
62
49
|
worktree?: WorktreeIsolation;
|
|
63
|
-
/** Durable restoration failure that permanently blocks continuation. */
|
|
64
|
-
resumeUnavailableReason?: string;
|
|
65
50
|
/** Original durable evidence retained when its worktree handle is unavailable. */
|
|
66
51
|
restorationRecord?: ThreadRecord;
|
|
67
52
|
state: ThreadState;
|
|
68
53
|
control: RpcRunControl;
|
|
69
54
|
queueController?: AbortController;
|
|
70
|
-
/** Resolves
|
|
71
|
-
* finalization, and queue work have fully quiesced and released their
|
|
72
|
-
* concurrency slot. */
|
|
55
|
+
/** Resolves after the child, isolation finalization, and queue work fully quiesce. */
|
|
73
56
|
generationCompletion: Promise<void>;
|
|
74
|
-
/**
|
|
57
|
+
/** Arbitration between asynchronous settlement and destructive stop. */
|
|
75
58
|
lifecycleVersion: number;
|
|
76
59
|
lifecycleOperation?: ThreadLifecycleOperation;
|
|
77
60
|
sessionId?: string;
|
|
78
61
|
sessionDir?: string;
|
|
79
|
-
/** Active execution time accumulated across retained resume generations. */
|
|
80
62
|
elapsedMs: number;
|
|
81
|
-
/**
|
|
63
|
+
/** Terminal or interrupted partial result; independent of the transient monitor row. */
|
|
82
64
|
lastResult?: SingleResult;
|
|
83
65
|
/** A destructive stop retires context even if the active child settles later. */
|
|
84
66
|
retireOnSettle?: boolean;
|
|
85
67
|
retired?: boolean;
|
|
86
|
-
/**
|
|
87
|
-
resume: (
|
|
88
|
-
objective?: string,
|
|
89
|
-
ctx?: ExtensionContext,
|
|
90
|
-
metadata?: { scope?: PhaseScopeInput },
|
|
91
|
-
) => Promise<SingleResult>;
|
|
92
|
-
/** Dispatch-owned, generation-guarded worktree settlement hook. Its apply
|
|
93
|
-
* runs under the canonical original-repository lane. */
|
|
68
|
+
/** All owners finalize under the same original-repository lane. */
|
|
94
69
|
finalizeIsolation: (generation: number, result?: SingleResult) => Promise<WorktreeFinalization | undefined>;
|
|
95
|
-
/** Best-effort shutdown notification for retained integration artifacts. */
|
|
96
70
|
notifyIsolationFailure?: (finalization: WorktreeFinalization) => void;
|
|
97
71
|
isolationFailureNotified?: boolean;
|
|
98
72
|
}
|
|
@@ -104,9 +78,6 @@ export interface SubagentRuntime {
|
|
|
104
78
|
getActiveTools: () => string[];
|
|
105
79
|
/** False after session_shutdown; guards delivery and queue work. */
|
|
106
80
|
sessionActive: boolean;
|
|
107
|
-
/** The process-wide background dispatcher. Set at tool registration so
|
|
108
|
-
* threads restored from the durable manifest can resume before any dispatch. */
|
|
109
|
-
dispatcher?: StartBackgroundInternal;
|
|
110
81
|
/** Resolves when the load-time durable restore pass has finished. Everything
|
|
111
82
|
* that answers "which threads exist" awaits it — the lookup tools, a fresh
|
|
112
83
|
* dispatch before it allocates a run id, and the restored-thread notice — so
|
|
@@ -140,9 +111,6 @@ export interface SubagentRuntime {
|
|
|
140
111
|
registerRunResult: (runId: number, result: SingleResult) => void;
|
|
141
112
|
/** Logical threads outlive process attempts and completed generations. */
|
|
142
113
|
threads: Map<number, SubagentThread>;
|
|
143
|
-
/** Resume setup that has claimed a thread but has not yet enqueued its
|
|
144
|
-
* next generation. Shutdown invalidates these claims and waits for cleanup. */
|
|
145
|
-
preflightOperations: Set<Promise<void>>;
|
|
146
114
|
/** Every session directory retained for this parent session. */
|
|
147
115
|
sessionDirs: Set<string>;
|
|
148
116
|
retainSession: (result: Pick<SingleResult, "sessionDir">) => void;
|
|
@@ -242,7 +210,6 @@ export function createRuntime(pi: ExtensionAPI, configPath: string): SubagentRun
|
|
|
242
210
|
settledRuns: new Map<number, SingleResult>(),
|
|
243
211
|
settledListeners: new Map<number, Set<(result: SingleResult) => void>>(),
|
|
244
212
|
threads: new Map<number, SubagentThread>(),
|
|
245
|
-
preflightOperations: new Set<Promise<void>>(),
|
|
246
213
|
sessionDirs: new Set<string>(),
|
|
247
214
|
retainSession: (result) => {
|
|
248
215
|
if (result.sessionDir) runtime.sessionDirs.add(result.sessionDir);
|
|
@@ -278,11 +245,9 @@ export function createRuntime(pi: ExtensionAPI, configPath: string): SubagentRun
|
|
|
278
245
|
if (!runtime.sessionActive) return;
|
|
279
246
|
runtime.sessionActive = false;
|
|
280
247
|
const shutdownThreads = [...runtime.threads.values()];
|
|
281
|
-
const liveStates = new Set(["queued", "
|
|
248
|
+
const liveStates = new Set(["queued", "running", "interrupting"]);
|
|
282
249
|
const previousStates = new Map(shutdownThreads.map((thread) => [thread.id, thread.state] as const));
|
|
283
|
-
// Invalidate
|
|
284
|
-
// Resume preflight checks both this version and sessionActive, then
|
|
285
|
-
// cleans any worktree/session it created before resolving its tracker.
|
|
250
|
+
// Invalidate pending lifecycle claims synchronously before the first await.
|
|
286
251
|
// A generation already inside its settlement keeps its own claim: it
|
|
287
252
|
// finalizes its worktree and persists its terminal record itself.
|
|
288
253
|
const interrupting = shutdownThreads.filter((thread) =>
|
|
@@ -299,29 +264,21 @@ export function createRuntime(pi: ExtensionAPI, configPath: string): SubagentRun
|
|
|
299
264
|
if (thread.lifecycleOperation === "settle") continue;
|
|
300
265
|
thread.lifecycleOperation = "stop";
|
|
301
266
|
// Deliberately NOT retireOnSettle: shutdown interrupts to the last
|
|
302
|
-
// checkpoint
|
|
267
|
+
// checkpoint and preserves session/worktree artifacts for manual recovery.
|
|
303
268
|
thread.retireOnSettle = false;
|
|
304
269
|
if (liveStates.has(thread.state)) thread.state = "stopped";
|
|
305
270
|
}
|
|
306
|
-
const preflights = [...runtime.preflightOperations];
|
|
307
271
|
runtime.completionBatcher.dispose();
|
|
308
|
-
//
|
|
309
|
-
// session is gone, so there is no window left to deliver them into.
|
|
272
|
+
// The parent session is gone; no window remains for buffered delivery.
|
|
310
273
|
heldCompletions = [];
|
|
311
274
|
compactionInFlight = false;
|
|
312
275
|
runtime.backgroundQueue.cancelAll();
|
|
313
|
-
//
|
|
314
|
-
// before persisting records or releasing ownership maps.
|
|
276
|
+
// Quiesce child processes and owned queue work before persisting records.
|
|
315
277
|
await Promise.all([
|
|
316
|
-
|
|
317
|
-
interrupting.map((thread) =>
|
|
318
|
-
thread.control.stop("Parent session shut down").catch(() => undefined),
|
|
319
|
-
),
|
|
320
|
-
),
|
|
321
|
-
Promise.allSettled(preflights),
|
|
278
|
+
...interrupting.map((thread) => thread.control.stop("Parent session shut down").catch(() => undefined)),
|
|
322
279
|
runtime.backgroundQueue.waitForIdle(),
|
|
323
280
|
]);
|
|
324
|
-
// Only interrupted
|
|
281
|
+
// Only interrupted work keeps recovery artifacts across reloads:
|
|
325
282
|
// each keeps its durable record and retained artifacts. Settled
|
|
326
283
|
// threads drop their record — the manifest exists only while
|
|
327
284
|
// unfinished work needs it — and their sessions are deleted now. A
|
|
@@ -332,16 +289,13 @@ export function createRuntime(pi: ExtensionAPI, configPath: string): SubagentRun
|
|
|
332
289
|
const records: ThreadRecord[] = [];
|
|
333
290
|
for (const thread of runtime.threads.values()) {
|
|
334
291
|
if (thread.retired) continue;
|
|
335
|
-
if (thread.
|
|
336
|
-
//
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
elapsedMs: thread.elapsedMs,
|
|
343
|
-
});
|
|
344
|
-
}
|
|
292
|
+
if (thread.restorationRecord) {
|
|
293
|
+
// Keep recovery evidence until an explicit destructive stop retires it.
|
|
294
|
+
records.push({
|
|
295
|
+
...thread.restorationRecord,
|
|
296
|
+
updatedAt: Date.now(),
|
|
297
|
+
elapsedMs: thread.elapsedMs,
|
|
298
|
+
});
|
|
345
299
|
continue;
|
|
346
300
|
}
|
|
347
301
|
const previous = previousStates.get(thread.id) ?? thread.state;
|
|
@@ -382,7 +336,6 @@ export function createRuntime(pi: ExtensionAPI, configPath: string): SubagentRun
|
|
|
382
336
|
// sessionDirs entries still referenced by records stay owned by the
|
|
383
337
|
// manifest; the next process re-registers them at restore.
|
|
384
338
|
runtime.sessionDirs.clear();
|
|
385
|
-
runtime.preflightOperations.clear();
|
|
386
339
|
runtime.threads.clear();
|
|
387
340
|
monitor.clear();
|
|
388
341
|
},
|