@ferris1225/pi-subagents 4.1.18 → 4.1.21
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 +384 -337
- package/agents/cleaner.md +50 -45
- package/agents/documenter.md +40 -42
- package/agents/explorer.md +40 -45
- package/agents/reviewer.md +82 -82
- package/agents/synthesizer.md +39 -0
- package/agents/worker.md +43 -45
- package/package.json +55 -55
- package/src/agents.ts +25 -5
- package/src/announcements.ts +78 -75
- package/src/background.ts +11 -0
- package/src/completion.ts +19 -9
- package/src/config.ts +3 -10
- package/src/dispatch.ts +817 -647
- package/src/durable.ts +443 -402
- package/src/format.ts +173 -179
- package/src/index.ts +6 -6
- package/src/models.ts +4 -6
- package/src/monitor.ts +56 -5
- package/src/prompt.ts +14 -21
- package/src/recovery.ts +145 -145
- package/src/rpc-run.ts +993 -993
- package/src/runtime.ts +22 -4
- package/src/session-fork.ts +2 -0
- package/src/setup.ts +23 -43
- package/src/spawn.ts +668 -654
- package/src/temp-hygiene.ts +230 -174
- package/src/thread-lifecycle.ts +1487 -1399
- package/src/tools.ts +384 -712
- package/src/widget.ts +195 -157
- package/src/workflow.ts +24 -8
- package/src/worktree.ts +18 -0
package/src/runtime.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared per-session runtime state for pi-subagents.
|
|
3
3
|
*
|
|
4
|
-
* The extension registers several tools (subagent,
|
|
4
|
+
* The extension registers several tools (subagent, subagent_control/stop)
|
|
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
|
|
@@ -25,6 +25,7 @@ import { isRunActiveStatus, monitor } from "./monitor.ts";
|
|
|
25
25
|
import type { RpcRunControl } from "./rpc-run.ts";
|
|
26
26
|
import type { StartBackgroundInternal } from "./thread-lifecycle.ts";
|
|
27
27
|
import { isFailedResult, type SingleResult } from "./spawn.ts";
|
|
28
|
+
import type { ReviewMode } from "./workflow.ts";
|
|
28
29
|
import type { IsolationMode, WorktreeFinalization, WorktreeIsolation } from "./worktree.ts";
|
|
29
30
|
|
|
30
31
|
export type ThreadState =
|
|
@@ -50,6 +51,9 @@ export interface SubagentThread {
|
|
|
50
51
|
executionCwd: string;
|
|
51
52
|
thinkingLevel?: ThinkingLevel;
|
|
52
53
|
isolation: IsolationMode;
|
|
54
|
+
/** Dispatch-time gate intensity; "none" skips the automatic post-writer
|
|
55
|
+
* reviewer for this thread (kept across resumes and reloads). */
|
|
56
|
+
review?: ReviewMode;
|
|
53
57
|
worktree?: WorktreeIsolation;
|
|
54
58
|
state: ThreadState;
|
|
55
59
|
control: RpcRunControl;
|
|
@@ -90,6 +94,13 @@ export interface SubagentRuntime {
|
|
|
90
94
|
/** The process-wide background dispatcher. Set at tool registration so
|
|
91
95
|
* threads restored from the durable manifest can resume before any dispatch. */
|
|
92
96
|
dispatcher?: StartBackgroundInternal;
|
|
97
|
+
/** Resolves when the load-time durable restore pass has finished. Everything
|
|
98
|
+
* that answers "which threads exist" awaits it — the lookup tools, a fresh
|
|
99
|
+
* dispatch before it allocates a run id, and the restored-thread notice — so
|
|
100
|
+
* a reload can never report parked work as missing, or hand a new run an id a
|
|
101
|
+
* record still owns, while the manifest is being read. Resolved by default;
|
|
102
|
+
* `bootstrapDurableState` publishes the real pass. */
|
|
103
|
+
durableRestore: Promise<void>;
|
|
93
104
|
/** Run ids restored from the durable manifest at load; consumed by the
|
|
94
105
|
* one-time session-start notice. */
|
|
95
106
|
restoredRunIds: number[];
|
|
@@ -100,8 +111,9 @@ export interface SubagentRuntime {
|
|
|
100
111
|
completionBatcher: CompletionBatcher<CompletionMessageItem>;
|
|
101
112
|
/** Abort controllers per active run, so subagent_stop can cancel a run in-turn. */
|
|
102
113
|
runControllers: Map<number, AbortController>;
|
|
103
|
-
/** Final results keyed by run id, so
|
|
104
|
-
* actual result in-turn instead of it sleeping/polling for a
|
|
114
|
+
/** Final results keyed by run id, so a dispatch with wait: true can hand the
|
|
115
|
+
* model the actual result in-turn instead of it sleeping/polling for a
|
|
116
|
+
* wake-up message. */
|
|
105
117
|
settledRuns: Map<number, SingleResult>;
|
|
106
118
|
settledListeners: Map<number, Set<(result: SingleResult) => void>>;
|
|
107
119
|
registerRunResult: (runId: number, result: SingleResult) => void;
|
|
@@ -127,6 +139,7 @@ export function createRuntime(pi: ExtensionAPI, configPath: string): SubagentRun
|
|
|
127
139
|
backgroundQueue,
|
|
128
140
|
getActiveTools: () => pi.getActiveTools(),
|
|
129
141
|
sessionActive: true,
|
|
142
|
+
durableRestore: Promise.resolve(),
|
|
130
143
|
restoredRunIds: [],
|
|
131
144
|
restoredNotified: false,
|
|
132
145
|
sendCompletionGroup: (items) => {
|
|
@@ -140,7 +153,12 @@ export function createRuntime(pi: ExtensionAPI, configPath: string): SubagentRun
|
|
|
140
153
|
const active = monitor
|
|
141
154
|
.getRuns()
|
|
142
155
|
.filter((run) => isRunActiveStatus(run.status))
|
|
143
|
-
.map((run) => ({
|
|
156
|
+
.map((run) => ({
|
|
157
|
+
id: run.id,
|
|
158
|
+
agent: run.agent,
|
|
159
|
+
label: run.label,
|
|
160
|
+
...(run.status === "queued" && run.waitReason ? { wait: run.waitReason } : {}),
|
|
161
|
+
}));
|
|
144
162
|
const message = {
|
|
145
163
|
customType: "subagent-result",
|
|
146
164
|
content: formatCompletionMessage(items) + formatActiveRunsFooter(active),
|
package/src/session-fork.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { SessionManager } from "@earendil-works/pi-coding-agent";
|
|
|
4
4
|
import { existsSync } from "node:fs";
|
|
5
5
|
import { mkdir, mkdtemp, rm } from "node:fs/promises";
|
|
6
6
|
import { join } from "node:path";
|
|
7
|
+
import { writeTempOwnerMarker } from "./temp-hygiene.ts";
|
|
7
8
|
|
|
8
9
|
export interface ForkedSession {
|
|
9
10
|
sessionDir: string;
|
|
@@ -53,6 +54,7 @@ export async function forkRetainedSession(options: {
|
|
|
53
54
|
const root = options.targetRoot;
|
|
54
55
|
await mkdir(root, { recursive: true });
|
|
55
56
|
const sessionDir = await mkdtemp(join(root, "pi-subagent-session-fork-"));
|
|
57
|
+
writeTempOwnerMarker(sessionDir);
|
|
56
58
|
try {
|
|
57
59
|
// Supplying the new directory makes createBranchedSession write there.
|
|
58
60
|
// cwdOverride rewrites the cloned header so a settled isolated session can
|
package/src/setup.ts
CHANGED
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
* Interactive configuration wizard for /subagents-setup.
|
|
3
3
|
*
|
|
4
4
|
* The wizard stays one level deep and exposes only what most users touch:
|
|
5
|
-
* which agents run
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* aware Auto.
|
|
5
|
+
* which agents run and the model and thinking strength each runs on.
|
|
6
|
+
* Everything else (agent scope, idle timeout, result lines, notifications) is
|
|
7
|
+
* config-file-only; model failures hand directly to the current main model,
|
|
8
|
+
* and thinking defaults to capability-aware Auto.
|
|
10
9
|
*/
|
|
11
10
|
|
|
12
11
|
import { stat } from "node:fs/promises";
|
|
@@ -40,10 +39,11 @@ import { promptSelectMany, promptSelectOne } from "./ui.ts";
|
|
|
40
39
|
|
|
41
40
|
/** Short, selection-friendly descriptions for the built-in agents. */
|
|
42
41
|
const MODULE_HINTS: Record<string, string> = {
|
|
43
|
-
explorer: "read-only codebase recon (fast
|
|
42
|
+
explorer: "read-only codebase recon (fast, read-only tools)",
|
|
44
43
|
worker: "implement / fix / refactor / test (full tools)",
|
|
45
44
|
cleaner: "apply proven cleanup and deduplicate code (full tools)",
|
|
46
45
|
documenter: "sync diff or whole-codebase comments/docs (docs write)",
|
|
46
|
+
synthesizer: "merge fan-out results/long sources into one brief (read-only)",
|
|
47
47
|
reviewer: "read-only audits and pre-commit gates",
|
|
48
48
|
};
|
|
49
49
|
|
|
@@ -230,14 +230,6 @@ async function configureOneAgent(
|
|
|
230
230
|
}
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
-
async function pickInjection(ctx: ExtensionCommandContext, current: boolean): Promise<boolean | undefined> {
|
|
234
|
-
const on = "On — inject the delegation directive (recommended)";
|
|
235
|
-
const off = "Off — rely on tool descriptions only";
|
|
236
|
-
const choice = await ctx.ui.select("Proactive dispatch injection?", [current ? `${on} (current)` : on, current ? off : `${off} (current)`]);
|
|
237
|
-
if (choice === undefined) return undefined;
|
|
238
|
-
return choice.startsWith("On");
|
|
239
|
-
}
|
|
240
|
-
|
|
241
233
|
function keepAgentEntries<T>(record: Record<string, T>, enabled: readonly string[]): Record<string, T> {
|
|
242
234
|
const keep = new Set(enabled);
|
|
243
235
|
return Object.fromEntries(Object.entries(record).filter(([name]) => keep.has(name)));
|
|
@@ -254,9 +246,6 @@ async function runFullSetup(ctx: ExtensionCommandContext, configPath: string, ba
|
|
|
254
246
|
agentModels = applyAgentModelChoice(agentModels, agentName, choice);
|
|
255
247
|
}
|
|
256
248
|
|
|
257
|
-
const injection = await pickInjection(ctx, base.proactiveInjection);
|
|
258
|
-
if (injection === undefined) return false;
|
|
259
|
-
|
|
260
249
|
const next: SubagentsConfig = {
|
|
261
250
|
enabledAgents: enabled,
|
|
262
251
|
agentModels,
|
|
@@ -264,7 +253,6 @@ async function runFullSetup(ctx: ExtensionCommandContext, configPath: string, ba
|
|
|
264
253
|
agentThinkingLevels: {},
|
|
265
254
|
notifyOnReviewPass: base.notifyOnReviewPass,
|
|
266
255
|
maxResultLines: base.maxResultLines,
|
|
267
|
-
proactiveInjection: injection,
|
|
268
256
|
agentScope: base.agentScope,
|
|
269
257
|
idleTimeoutSec: base.idleTimeoutSec,
|
|
270
258
|
};
|
|
@@ -278,7 +266,6 @@ async function runMenu(ctx: ExtensionCommandContext, configPath: string, config:
|
|
|
278
266
|
const choice = await ctx.ui.select("pi-subagents settings", [
|
|
279
267
|
"Enable/disable agents",
|
|
280
268
|
"Configure an agent (model + thinking)",
|
|
281
|
-
"Proactive injection",
|
|
282
269
|
"Full re-setup",
|
|
283
270
|
]);
|
|
284
271
|
if (choice === undefined) return;
|
|
@@ -296,31 +283,28 @@ async function runMenu(ctx: ExtensionCommandContext, configPath: string, config:
|
|
|
296
283
|
const enabled = await pickEnabledAgents(ctx, config.enabledAgents);
|
|
297
284
|
if (enabled === undefined) continue;
|
|
298
285
|
next.enabledAgents = enabled;
|
|
299
|
-
//
|
|
300
|
-
// thinking level, so the file reflects what
|
|
301
|
-
// instead of silently falling back to the current main model
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
286
|
+
// A newly enabled role inherits a kindred role's configured model and
|
|
287
|
+
// thinking level, so the file reflects what it will actually run
|
|
288
|
+
// instead of silently falling back to the current main model: cleaner
|
|
289
|
+
// follows the reviewer; documenter and synthesizer intentionally
|
|
290
|
+
// follow the faster explorer route.
|
|
291
|
+
const modelInheritance: ReadonlyArray<[agent: string, from: string]> = [
|
|
292
|
+
["cleaner", "reviewer"],
|
|
293
|
+
["documenter", "explorer"],
|
|
294
|
+
["synthesizer", "explorer"],
|
|
295
|
+
];
|
|
296
|
+
for (const [agent, from] of modelInheritance) {
|
|
297
|
+
if (config.enabledAgents.includes(agent) || !enabled.includes(agent)) continue;
|
|
298
|
+
if (!next.agentModels[agent] && config.agentModels[from]) {
|
|
299
|
+
next.agentModels[agent] = config.agentModels[from];
|
|
308
300
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
// is re-enabled after being explicitly disabled, it inherits any
|
|
312
|
-
// explorer overrides instead of silently choosing a stronger model.
|
|
313
|
-
if (!config.enabledAgents.includes("documenter") && enabled.includes("documenter")) {
|
|
314
|
-
if (!next.agentModels.documenter && config.agentModels.explorer) {
|
|
315
|
-
next.agentModels.documenter = config.agentModels.explorer;
|
|
316
|
-
}
|
|
317
|
-
if (!next.agentThinkingLevels.documenter && config.agentThinkingLevels.explorer) {
|
|
318
|
-
next.agentThinkingLevels.documenter = config.agentThinkingLevels.explorer;
|
|
301
|
+
if (!next.agentThinkingLevels[agent] && config.agentThinkingLevels[from]) {
|
|
302
|
+
next.agentThinkingLevels[agent] = config.agentThinkingLevels[from];
|
|
319
303
|
}
|
|
320
304
|
}
|
|
321
305
|
next.agentModels = keepAgentEntries(next.agentModels, enabled);
|
|
322
306
|
next.agentThinkingLevels = keepAgentEntries(next.agentThinkingLevels, enabled);
|
|
323
|
-
} else
|
|
307
|
+
} else {
|
|
324
308
|
// Per-agent loop: thinking Esc returns to that agent's model picker;
|
|
325
309
|
// model Esc returns to the agent picker; agent-picker Esc saves completed
|
|
326
310
|
// choices and returns to this settings menu.
|
|
@@ -338,10 +322,6 @@ async function runMenu(ctx: ExtensionCommandContext, configPath: string, config:
|
|
|
338
322
|
ctx.ui.notify(`pi-subagents updated. Saved to ${configPath}`, "info");
|
|
339
323
|
config = next;
|
|
340
324
|
continue;
|
|
341
|
-
} else {
|
|
342
|
-
const injection = await pickInjection(ctx, next.proactiveInjection);
|
|
343
|
-
if (injection === undefined) continue;
|
|
344
|
-
next.proactiveInjection = injection;
|
|
345
325
|
}
|
|
346
326
|
|
|
347
327
|
await saveConfig(next, configPath);
|