@ferris1225/pi-subagents 4.1.8 → 4.1.9
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 +82 -51
- package/agents/cleaner.md +13 -14
- package/agents/documenter.md +10 -17
- package/agents/explorer.md +6 -16
- package/agents/reviewer.md +28 -29
- package/agents/worker.md +14 -33
- package/package.json +1 -1
- package/src/announcements.ts +8 -0
- package/src/background.ts +21 -3
- package/src/dispatch.ts +721 -746
- package/src/durable.ts +336 -0
- package/src/fixloop.ts +30 -34
- package/src/format.ts +1 -8
- package/src/index.ts +7 -0
- package/src/monitor.ts +28 -29
- package/src/prompt.ts +4 -4
- package/src/rpc-run.ts +22 -228
- package/src/runtime.ts +69 -44
- package/src/session-fork.ts +7 -2
- package/src/spawn.ts +31 -28
- package/src/temp-hygiene.ts +194 -0
- package/src/thread-lifecycle.ts +1410 -1324
- package/src/tools.ts +21 -108
- package/src/widget.ts +3 -3
- package/src/worktree.ts +144 -4
package/src/announcements.ts
CHANGED
|
@@ -40,6 +40,14 @@ export function registerAnnouncements(pi: ExtensionAPI, runtime: SubagentRuntime
|
|
|
40
40
|
pruneResultArtifacts();
|
|
41
41
|
await announceRecoveryRecords(runtime.configPath, ctx);
|
|
42
42
|
await migrateUnavailableAgentModels(ctx, runtime);
|
|
43
|
+
if (!runtime.restoredNotified && runtime.restoredRunIds.length > 0) {
|
|
44
|
+
runtime.restoredNotified = true;
|
|
45
|
+
const ids = runtime.restoredRunIds.map((id) => `#${id}`).join(", ");
|
|
46
|
+
ctx.ui.notify(
|
|
47
|
+
`pi-subagents: restored ${runtime.restoredRunIds.length} resumable thread${runtime.restoredRunIds.length === 1 ? "" : "s"} from the previous session (${ids}). subagent_status lists them; subagent_control resume continues one.`,
|
|
48
|
+
"info",
|
|
49
|
+
);
|
|
50
|
+
}
|
|
43
51
|
if (ctx.mode !== "tui") return;
|
|
44
52
|
installActiveRunsWidget(ctx);
|
|
45
53
|
});
|
package/src/background.ts
CHANGED
|
@@ -29,13 +29,19 @@ interface PendingTask {
|
|
|
29
29
|
|
|
30
30
|
/** How many sub-agent processes may run at once, and how many tasks one
|
|
31
31
|
* parallel `subagent` call may contain. Fixed by design: the queue sheds load
|
|
32
|
-
* by waiting, so the knob bought nothing worth its maintenance.
|
|
32
|
+
* by waiting, so the knob bought nothing worth its maintenance. Only manually
|
|
33
|
+
* dispatched top-level generations hold slots; runtime-initiated managed
|
|
34
|
+
* continuations (gate reviews, auto-fix rounds, documentation sync) suspend
|
|
35
|
+
* their task's slot so they never starve manual dispatches. */
|
|
33
36
|
export const MAX_CONCURRENT_SUBAGENTS = 4;
|
|
34
37
|
|
|
35
38
|
export class BackgroundTaskQueue {
|
|
36
39
|
private concurrency: number;
|
|
37
40
|
private readonly pending: PendingTask[] = [];
|
|
38
41
|
private readonly active = new Set<AbortController>();
|
|
42
|
+
/** Active tasks that no longer count toward the concurrency limit. They keep
|
|
43
|
+
* every other guarantee: abortable, awaited by waitForTask/waitForIdle. */
|
|
44
|
+
private readonly suspended = new Set<AbortController>();
|
|
39
45
|
private readonly completions = new WeakMap<AbortController, Promise<void>>();
|
|
40
46
|
private readonly idleWaiters = new Set<() => void>();
|
|
41
47
|
private stopped = false;
|
|
@@ -71,6 +77,16 @@ export class BackgroundTaskQueue {
|
|
|
71
77
|
return this.completions.get(controller) ?? Promise.resolve();
|
|
72
78
|
}
|
|
73
79
|
|
|
80
|
+
/** Stop counting a running task toward the concurrency limit. Its body keeps
|
|
81
|
+
* running under the same abort signal; completion still releases everything
|
|
82
|
+
* waitForTask/waitForIdle promise. Frees a slot for queued work immediately. */
|
|
83
|
+
suspend(controller: AbortController | undefined): void {
|
|
84
|
+
if (!controller || this.stopped) return;
|
|
85
|
+
if (!this.active.delete(controller)) return;
|
|
86
|
+
this.suspended.add(controller);
|
|
87
|
+
this.drain();
|
|
88
|
+
}
|
|
89
|
+
|
|
74
90
|
/** Cancel one queued/running task. Queued entries are removed immediately;
|
|
75
91
|
* active entries resolve waitForTask only after their body and error handler
|
|
76
92
|
* have quiesced and the concurrency slot has been released. */
|
|
@@ -89,7 +105,7 @@ export class BackgroundTaskQueue {
|
|
|
89
105
|
|
|
90
106
|
/** Resolve once no queued or running task remains. */
|
|
91
107
|
waitForIdle(): Promise<void> {
|
|
92
|
-
if (this.pending.length === 0 && this.active.size === 0) return Promise.resolve();
|
|
108
|
+
if (this.pending.length === 0 && this.active.size === 0 && this.suspended.size === 0) return Promise.resolve();
|
|
93
109
|
return new Promise<void>((resolve) => this.idleWaiters.add(resolve));
|
|
94
110
|
}
|
|
95
111
|
|
|
@@ -104,6 +120,7 @@ export class BackgroundTaskQueue {
|
|
|
104
120
|
entry.complete();
|
|
105
121
|
}
|
|
106
122
|
for (const controller of this.active) controller.abort();
|
|
123
|
+
for (const controller of this.suspended) controller.abort();
|
|
107
124
|
this.resolveIdleWaiters();
|
|
108
125
|
}
|
|
109
126
|
|
|
@@ -145,6 +162,7 @@ export class BackgroundTaskQueue {
|
|
|
145
162
|
})
|
|
146
163
|
.finally(() => {
|
|
147
164
|
this.active.delete(entry.controller);
|
|
165
|
+
this.suspended.delete(entry.controller);
|
|
148
166
|
entry.complete();
|
|
149
167
|
this.drain();
|
|
150
168
|
this.resolveIdleWaiters();
|
|
@@ -153,7 +171,7 @@ export class BackgroundTaskQueue {
|
|
|
153
171
|
}
|
|
154
172
|
|
|
155
173
|
private resolveIdleWaiters(): void {
|
|
156
|
-
if (this.pending.length > 0 || this.active.size > 0) return;
|
|
174
|
+
if (this.pending.length > 0 || this.active.size > 0 || this.suspended.size > 0) return;
|
|
157
175
|
for (const resolve of this.idleWaiters) resolve();
|
|
158
176
|
this.idleWaiters.clear();
|
|
159
177
|
}
|