@cr1ms0n/pi-subagent 0.8.9 → 0.9.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/CHANGELOG.md +11 -1
- package/README.md +218 -115
- package/docs/ARCHITECTURE.md +56 -13
- package/docs/COST-ACCOUNTING.md +116 -66
- package/docs/RELEASING.md +32 -32
- package/docs/SECURITY.md +42 -5
- package/docs/UX.md +158 -141
- package/package.json +2 -2
- package/skills/subagent/SKILL.md +78 -49
- package/src/backends/pi.ts +164 -94
- package/src/child-preflight.ts +166 -0
- package/src/config.ts +254 -252
- package/src/dispatch-preflight.ts +87 -0
- package/src/dispatch-routing.ts +56 -0
- package/src/extension.ts +366 -158
- package/src/format.ts +436 -365
- package/src/jev-router.ts +1036 -0
- package/src/orchestrator.ts +75 -19
- package/src/persistence.ts +643 -335
- package/src/policy.ts +120 -89
- package/src/process-lock.ts +730 -687
- package/src/protocol.ts +320 -290
- package/src/registry.ts +730 -632
- package/src/routing-policy.ts +268 -0
- package/src/routing-types.ts +217 -0
- package/src/runner.ts +1299 -850
- package/src/schema.ts +10 -10
- package/src/startup-check.ts +481 -0
- package/src/types.ts +208 -198
- package/src/usage.ts +316 -274
- package/src/model-policy.ts +0 -169
package/src/orchestrator.ts
CHANGED
|
@@ -94,51 +94,89 @@ export async function runTasks(
|
|
|
94
94
|
const worktrees = options.worktrees ?? new WorktreeManager();
|
|
95
95
|
const handles: Array<WorktreeHandle | undefined> = new Array(specs.length);
|
|
96
96
|
const prepared: TaskSpec[] = [];
|
|
97
|
+
let setupTimedOut = false;
|
|
98
|
+
const progress: Partial<TaskResult>[] = [];
|
|
99
|
+
const checkpoint = (index: number, partial: Partial<TaskResult>) => {
|
|
100
|
+
progress[index] = { ...progress[index], ...partial };
|
|
101
|
+
options.onTaskProgress?.(index, partial);
|
|
102
|
+
};
|
|
97
103
|
|
|
98
104
|
try {
|
|
99
105
|
for (let index = 0; index < specs.length; index++) {
|
|
100
106
|
if (options.signal?.aborted) throw new Error("Subagent run cancelled before worktree setup");
|
|
101
107
|
const spec = { ...specs[index]! };
|
|
108
|
+
const setupController = new AbortController();
|
|
109
|
+
const setupSignal = options.signal ? AbortSignal.any([options.signal, setupController.signal]) : setupController.signal;
|
|
110
|
+
let setupTimer: NodeJS.Timeout | undefined;
|
|
111
|
+
if (spec.deadline !== undefined) {
|
|
112
|
+
const remaining = spec.deadline - Date.now();
|
|
113
|
+
if (remaining <= 0) {
|
|
114
|
+
setupTimedOut = true;
|
|
115
|
+
throw new Error("Subagent deadline expired before setup");
|
|
116
|
+
}
|
|
117
|
+
setupTimer = setTimeout(() => { setupTimedOut = true; setupController.abort(); }, remaining);
|
|
118
|
+
setupTimer.unref?.();
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
102
121
|
if (spec.isolation === "worktree") {
|
|
103
|
-
const handle = await worktrees.create(spec.cwd || process.cwd(), spec.task.slice(0, 20),
|
|
122
|
+
const handle = await worktrees.create(spec.cwd || process.cwd(), spec.task.slice(0, 20), setupSignal, {
|
|
104
123
|
includeWip: spec.includeWip === true,
|
|
105
124
|
});
|
|
106
125
|
handles[index] = handle;
|
|
107
126
|
spec.cwd = handle.cwd;
|
|
108
127
|
// Announce the worktree immediately so live runs can shield it from GC sweeps.
|
|
109
|
-
|
|
128
|
+
checkpoint(index, {
|
|
110
129
|
worktree: { cwd: handle.cwd, branch: handle.branch, baseCommit: handle.baseCommit, changed: false },
|
|
111
130
|
});
|
|
112
131
|
}
|
|
132
|
+
if (setupSignal.aborted) throw new Error("Subagent setup was cancelled or exceeded its task deadline");
|
|
113
133
|
prepared.push(spec);
|
|
134
|
+
} finally { if (setupTimer) clearTimeout(setupTimer); }
|
|
114
135
|
}
|
|
115
136
|
} catch (error) {
|
|
116
|
-
//
|
|
117
|
-
|
|
118
|
-
|
|
137
|
+
// Keep retained worktree pointers and report cleanup failures per task.
|
|
138
|
+
const setupErrors: Array<string | undefined> = [];
|
|
139
|
+
for (let index = 0; index < handles.length; index++) {
|
|
140
|
+
const handle = handles[index];
|
|
141
|
+
if (!handle) continue;
|
|
142
|
+
try {
|
|
143
|
+
const final = await worktrees.finalize(handle);
|
|
144
|
+
progress[index] = { ...progress[index], worktree: final.changed
|
|
145
|
+
? { cwd: final.cwd, branch: final.branch, baseCommit: final.baseCommit, changed: true, diffSummary: final.diffSummary }
|
|
146
|
+
: undefined };
|
|
147
|
+
} catch (error) {
|
|
148
|
+
setupErrors[index] = `Worktree finalization failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
149
|
+
// Its state is uncertain; preserve the handle for inspection/recovery.
|
|
150
|
+
progress[index] = { ...progress[index], worktree: { cwd: handle.cwd, branch: handle.branch, baseCommit: handle.baseCommit, changed: true } };
|
|
151
|
+
}
|
|
119
152
|
}
|
|
120
|
-
|
|
153
|
+
{
|
|
154
|
+
const setupState: RunState = setupTimedOut ? "timeout" : options.signal?.aborted ? "cancelled" : "failed";
|
|
121
155
|
const results = specs.map<TaskResult>((spec, index) => ({
|
|
122
|
-
|
|
156
|
+
...progress[index],
|
|
157
|
+
index,
|
|
158
|
+
label: spec.label || `task-${index + 1}`,
|
|
123
159
|
task: spec.task,
|
|
124
160
|
model: spec.model,
|
|
125
|
-
|
|
161
|
+
routing: spec.routing,
|
|
162
|
+
state: setupState,
|
|
126
163
|
exitCode: 1,
|
|
127
|
-
messages: [],
|
|
128
|
-
stderr: "",
|
|
129
|
-
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, reasoning: 0, cost: 0, contextTokens: 0, turns: 0 },
|
|
130
|
-
stopReason: "cancelled",
|
|
131
|
-
|
|
164
|
+
messages: progress[index]?.messages ?? [],
|
|
165
|
+
stderr: progress[index]?.stderr ?? "",
|
|
166
|
+
usage: progress[index]?.usage ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, reasoning: 0, cost: 0, contextTokens: 0, turns: 0 },
|
|
167
|
+
stopReason: setupTimedOut ? "timeout" : options.signal?.aborted ? "cancelled" : "setup_error",
|
|
168
|
+
timeoutPhase: setupTimedOut ? "starting" : undefined,
|
|
169
|
+
errorMessage: [error instanceof Error ? error.message : String(error), setupErrors[index]].filter(Boolean).join("; "),
|
|
132
170
|
thinking: spec.thinking,
|
|
133
171
|
profile: spec.profile,
|
|
172
|
+
backend: spec.backend ?? "pi",
|
|
134
173
|
canWrite: spec.canWrite,
|
|
135
174
|
outputFile: spec.output,
|
|
136
175
|
outputMode: spec.outputMode,
|
|
137
176
|
protocol: { headerSeen: false, assistantEndSeen: false, agentEndSeen: false, agentSettledSeen: false, validEvents: 0, parseErrors: 0 },
|
|
138
177
|
}));
|
|
139
|
-
return { mode: specs.length > 1 ? "parallel" : "single", results, state:
|
|
178
|
+
return { mode: specs.length > 1 ? "parallel" : "single", results, state: setupState, summary: summarize(results) };
|
|
140
179
|
}
|
|
141
|
-
throw error;
|
|
142
180
|
}
|
|
143
181
|
|
|
144
182
|
const runOne = async (index: number): Promise<TaskResult> => {
|
|
@@ -168,7 +206,7 @@ export async function runTasks(
|
|
|
168
206
|
semaphore,
|
|
169
207
|
options.getPiCommand ?? createGetPiCommand(),
|
|
170
208
|
options.sessionDir,
|
|
171
|
-
(partial) =>
|
|
209
|
+
(partial) => checkpoint(index, {
|
|
172
210
|
...partial,
|
|
173
211
|
attempts: attempt > 1 ? attempt : undefined,
|
|
174
212
|
usage: partial.usage && priorUsage ? addUsage(priorUsage, partial.usage) : partial.usage,
|
|
@@ -184,11 +222,11 @@ export async function runTasks(
|
|
|
184
222
|
result = await runner.run(attemptSpec, options.signal);
|
|
185
223
|
if (priorUsage) result.usage = addUsage(priorUsage, result.usage);
|
|
186
224
|
|
|
187
|
-
const canRetry = attempt < maxAttempts && !options.signal?.aborted && isTransientFailure(result);
|
|
225
|
+
const canRetry = attempt < maxAttempts && !options.signal?.aborted && (spec.deadline === undefined || Date.now() < spec.deadline) && isTransientFailure(result);
|
|
188
226
|
if (!canRetry) break;
|
|
189
227
|
priorUsage = result.usage;
|
|
190
228
|
const nextModel = fallbacks[attempt - 1];
|
|
191
|
-
|
|
229
|
+
checkpoint(index, {
|
|
192
230
|
state: "queued",
|
|
193
231
|
model: nextModel ?? spec.model,
|
|
194
232
|
attempts: attempt + 1,
|
|
@@ -203,6 +241,7 @@ export async function runTasks(
|
|
|
203
241
|
result.errorMessage += ` (after ${attemptedModels.length} attempts: ${attemptedModels.join(" → ")})`;
|
|
204
242
|
}
|
|
205
243
|
}
|
|
244
|
+
result.routing = spec.routing;
|
|
206
245
|
result.index = index;
|
|
207
246
|
result.label = spec.label || `task-${index + 1}`;
|
|
208
247
|
result.outputMode = spec.outputMode;
|
|
@@ -237,7 +276,24 @@ export async function runTasks(
|
|
|
237
276
|
return result;
|
|
238
277
|
};
|
|
239
278
|
|
|
240
|
-
|
|
279
|
+
// One unexpected task failure must not release run ownership while siblings still
|
|
280
|
+
// execute, or discard their results. Each task settles before the aggregate does.
|
|
281
|
+
const results = await Promise.all(prepared.map(async (spec, index): Promise<TaskResult> => {
|
|
282
|
+
try { return await runOne(index); }
|
|
283
|
+
catch (error) {
|
|
284
|
+
const partial = progress[index];
|
|
285
|
+
return {
|
|
286
|
+
...partial, index, label: spec.label || `task-${index + 1}`, task: spec.task,
|
|
287
|
+
model: partial?.model ?? spec.model, routing: spec.routing, thinking: spec.thinking,
|
|
288
|
+
profile: spec.profile, backend: spec.backend ?? "pi", canWrite: spec.canWrite,
|
|
289
|
+
outputFile: spec.output, outputMode: spec.outputMode,
|
|
290
|
+
state: "failed", exitCode: 1, messages: partial?.messages ?? [], stderr: partial?.stderr ?? "",
|
|
291
|
+
usage: partial?.usage ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 },
|
|
292
|
+
stopReason: "error", errorMessage: error instanceof Error ? error.message : String(error),
|
|
293
|
+
protocol: partial?.protocol ?? { headerSeen: false, assistantEndSeen: false, agentEndSeen: false, agentSettledSeen: false, validEvents: 0, parseErrors: 0 },
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
}));
|
|
241
297
|
return {
|
|
242
298
|
mode: results.length > 1 ? "parallel" : "single",
|
|
243
299
|
results,
|