pi-subagents 0.50.0 → 0.52.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 +103 -0
- package/agents/oracle.md +3 -1
- package/agents/reviewer.md +1 -0
- package/agents/scout.md +2 -2
- package/agents/worker.md +2 -1
- package/async-retention-discovery-worker.mjs +180 -0
- package/docs/agents.md +40 -2
- package/docs/configuration.md +30 -12
- package/docs/extension-api.md +42 -1
- package/docs/models.md +2 -0
- package/docs/observability.md +41 -5
- package/docs/tool-reference.md +38 -39
- package/docs/workflows.md +171 -3
- package/package.json +4 -2
- package/skills/pi-subagents/SKILL.md +6 -4
- package/skills/pi-subagents/references/constraints-and-recipes.md +12 -6
- package/skills/pi-subagents/references/execution-controls.md +29 -15
- package/skills/pi-subagents/references/management-authoring-rpc.md +3 -3
- package/skills/pi-subagents/references/prompting-and-roles.md +4 -2
- package/src/agents/agent-management.ts +124 -351
- package/src/agents/agents.ts +157 -31
- package/src/agents/skills.ts +1 -1
- package/src/api/external-job-provider.ts +185 -0
- package/src/api/preflight.ts +36 -8
- package/src/api/shared-types.ts +2 -0
- package/src/extension/config.ts +3 -3
- package/src/extension/doctor.ts +3 -6
- package/src/extension/fanout-child.ts +2 -2
- package/src/extension/index.ts +166 -88
- package/src/extension/public-execution.ts +31 -2
- package/src/extension/schemas.ts +12 -35
- package/src/extension/tool-description.ts +35 -24
- package/src/inspectors/herdr/actions.ts +2 -2
- package/src/inspectors/herdr/inspector-runner.ts +2 -1
- package/src/inspectors/herdr/project-panes.ts +2 -2
- package/src/intercom/native-supervisor-channel.ts +32 -10
- package/src/missions/lifecycle.ts +6 -1
- package/src/missions/store.ts +4 -9
- package/src/missions/workflow-state.ts +2 -2
- package/src/profiles/profiles.ts +3 -1
- package/src/runs/background/active-run-index.ts +31 -8
- package/src/runs/background/async-execution.ts +68 -40
- package/src/runs/background/async-job-tracker.ts +20 -4
- package/src/runs/background/async-resume.ts +47 -15
- package/src/runs/background/async-retention.ts +886 -0
- package/src/runs/background/async-status.ts +39 -53
- package/src/runs/background/chain-append.ts +3 -33
- package/src/runs/background/completion-dedupe.ts +5 -1
- package/src/runs/background/completion-replay.ts +22 -12
- package/src/runs/background/control-channel.ts +14 -68
- package/src/runs/background/fleet-view.ts +68 -19
- package/src/runs/background/index-segment.ts +59 -0
- package/src/runs/background/inspect-rpc.ts +443 -0
- package/src/runs/background/notify.ts +31 -5
- package/src/runs/background/result-files.ts +158 -90
- package/src/runs/background/result-watcher.ts +116 -20
- package/src/runs/background/resume-guidance.ts +8 -5
- package/src/runs/background/retained-children.ts +13 -3
- package/src/runs/background/run-id-query.ts +7 -0
- package/src/runs/background/run-id-resolver.ts +11 -9
- package/src/runs/background/run-status.ts +27 -18
- package/src/runs/background/scheduled-runs.ts +24 -4
- package/src/runs/background/stale-run-reconciler.ts +6 -3
- package/src/runs/background/steering.ts +11 -1
- package/src/runs/background/subagent-runner.ts +253 -140
- package/src/runs/background/subagent-wait.ts +8 -8
- package/src/runs/background/terminal-run-index.ts +129 -0
- package/src/runs/background/wait-completions.ts +21 -4
- package/src/runs/background/wait-subscriptions.ts +81 -2
- package/src/runs/foreground/async-steering-action.ts +21 -14
- package/src/runs/foreground/execution.ts +3 -1
- package/src/runs/foreground/subagent-executor.ts +666 -1579
- package/src/runs/foreground/workflow-detach-reconcile.ts +194 -0
- package/src/runs/foreground/workflow-foreground-steering.ts +6 -5
- package/src/runs/shared/acceptance.ts +4 -4
- package/src/runs/shared/chain-outputs.ts +1 -3
- package/src/runs/shared/external-job-bridge.ts +444 -0
- package/src/runs/shared/external-job-runner.ts +286 -0
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +14 -0
- package/src/runs/shared/model-fallback.ts +98 -38
- package/src/runs/shared/orca-progress-tabs.ts +84 -22
- package/src/runs/shared/parallel-handoff.ts +46 -4
- package/src/runs/shared/parallel-utils.ts +4 -15
- package/src/runs/shared/permissions.ts +5 -1
- package/src/runs/shared/pi-args.ts +8 -1
- package/src/runs/shared/session-lease.ts +0 -6
- package/src/runs/shared/subagent-control.ts +26 -4
- package/src/runs/shared/subagent-prompt-runtime.ts +12 -6
- package/src/runs/shared/workflow-graph.ts +1 -23
- package/src/runs/shared/worktree.ts +14 -2
- package/src/shared/atomic-json.ts +22 -2
- package/src/shared/capacity-resilient-json.ts +102 -0
- package/src/shared/completion-owner.ts +14 -0
- package/src/shared/file-system-retry.ts +49 -1
- package/src/shared/fork-context.ts +42 -0
- package/src/shared/prompt-resources.ts +0 -40
- package/src/shared/settings.ts +3 -27
- package/src/shared/types.ts +71 -26
- package/src/shared/utils.ts +8 -0
- package/src/shared/watch-strategy.ts +10 -0
- package/src/slash/slash-bridge.ts +2 -1
- package/src/slash/slash-commands.ts +29 -3
- package/src/tui/fleet.ts +63 -15
- package/src/watchdog/change-signature.ts +1 -1
- package/src/watchdog/lsp-diagnostics.ts +1 -0
- package/src/workflows/chat-progress.ts +2 -2
- package/src/workflows/scripted-workflow.ts +220 -67
- package/src/runs/foreground/chain-clarify.ts +0 -1354
- package/src/runs/foreground/chain-execution.ts +0 -1581
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { DIRS, type AcceptanceInput, type AsyncStatus, type ResolvedTurnBudget, type SteeringRecoveryDescriptor } from "../../shared/types.ts";
|
|
3
|
+
import { DIRS, type AcceptanceInput, type AsyncStatus, type ResolvedTurnBudget, type SteeringRecoveryDescriptor, type SubagentRunMode } from "../../shared/types.ts";
|
|
4
4
|
import type { AgentConfig } from "../../agents/agents.ts";
|
|
5
5
|
import { validateAcceptanceInput } from "../shared/acceptance.ts";
|
|
6
6
|
import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
|
|
@@ -8,7 +8,9 @@ import { intersectSubagentCapabilityCeilings, parseSubagentCapabilityCeiling, ty
|
|
|
8
8
|
import { validateRunFanoutBudgetDescriptor } from "../shared/run-fanout-budget.ts";
|
|
9
9
|
import { resolveTurnBudgetConfig } from "../shared/turn-budget.ts";
|
|
10
10
|
import { reconcileAsyncRun } from "./stale-run-reconciler.ts";
|
|
11
|
-
import { resultFilePath } from "./result-files.ts";
|
|
11
|
+
import { resultFilePath, resultPayloadPathForIndexedRun } from "./result-files.ts";
|
|
12
|
+
import { canScanAsyncRunPrefix, MIN_SAFE_ASYNC_RUN_PREFIX_LENGTH } from "./run-id-query.ts";
|
|
13
|
+
import { parallelHandoffPath, resolveRetainedWorktreeCwd } from "../shared/parallel-handoff.ts";
|
|
12
14
|
|
|
13
15
|
export interface AsyncResumeParams {
|
|
14
16
|
id?: string;
|
|
@@ -34,6 +36,7 @@ export type AsyncResumeTarget = {
|
|
|
34
36
|
runId: string;
|
|
35
37
|
asyncDir?: string;
|
|
36
38
|
state: AsyncStatus["state"];
|
|
39
|
+
mode?: SubagentRunMode;
|
|
37
40
|
agent: string;
|
|
38
41
|
index: number;
|
|
39
42
|
cwd?: string;
|
|
@@ -173,6 +176,8 @@ function prefixedRunIds(dir: string, prefix: string, suffix = ""): string[] {
|
|
|
173
176
|
}
|
|
174
177
|
|
|
175
178
|
function exactResultPath(resultsDir: string, runId: string): string | null {
|
|
179
|
+
const indexed = resultPayloadPathForIndexedRun(resultsDir, runId);
|
|
180
|
+
if (indexed) return indexed;
|
|
176
181
|
const resultPath = resultFilePath(resultsDir, runId);
|
|
177
182
|
assertInsideRoot(resultsDir, resultPath, "Async result file");
|
|
178
183
|
return fs.existsSync(resultPath) ? resultPath : null;
|
|
@@ -181,6 +186,7 @@ function exactResultPath(resultsDir: string, runId: string): string | null {
|
|
|
181
186
|
export function findAsyncRunPrefixMatches(prefix: string, asyncDirRoot: string, resultsDir: string): Array<{ id: string; location: AsyncRunLocation }> {
|
|
182
187
|
const requestedId = assertRunId(prefix, "id");
|
|
183
188
|
if (!requestedId) return [];
|
|
189
|
+
if (!canScanAsyncRunPrefix(requestedId)) return [];
|
|
184
190
|
const asyncRoot = path.resolve(asyncDirRoot);
|
|
185
191
|
const resultRoot = path.resolve(resultsDir);
|
|
186
192
|
const matchingIds = prefixedRunIds(asyncRoot, requestedId).sort();
|
|
@@ -223,6 +229,10 @@ export function resolveAsyncRunLocation(params: AsyncResumeParams, asyncDirRoot:
|
|
|
223
229
|
resolvedId: requestedId,
|
|
224
230
|
};
|
|
225
231
|
}
|
|
232
|
+
if (requestedId.length < MIN_SAFE_ASYNC_RUN_PREFIX_LENGTH) {
|
|
233
|
+
throw new Error(`Async run id prefix '${requestedId}' is too short. Provide at least ${MIN_SAFE_ASYNC_RUN_PREFIX_LENGTH} characters.`);
|
|
234
|
+
}
|
|
235
|
+
if (!canScanAsyncRunPrefix(requestedId)) return { asyncDir: null, resultPath: null, resolvedId: requestedId };
|
|
226
236
|
|
|
227
237
|
const matching = findAsyncRunPrefixMatches(requestedId, asyncRoot, resultRoot);
|
|
228
238
|
if (matching.length === 0) return { asyncDir: null, resultPath: null, resolvedId: requestedId };
|
|
@@ -261,12 +271,7 @@ function validateStatusForResume(status: AsyncStatus | null, source: string): vo
|
|
|
261
271
|
}
|
|
262
272
|
}
|
|
263
273
|
|
|
264
|
-
function normalizeRecoveryAcceptance(value: unknown, descriptorPath: string): AcceptanceInput
|
|
265
|
-
if (value && typeof value === "object" && !Array.isArray(value) && ("explicit" in value || "inferredReason" in value)) {
|
|
266
|
-
const { explicit, inferredReason: _inferredReason, ...publicAcceptance } = value as Record<string, unknown>;
|
|
267
|
-
if (explicit === false) return undefined;
|
|
268
|
-
value = publicAcceptance;
|
|
269
|
-
}
|
|
274
|
+
function normalizeRecoveryAcceptance(value: unknown, descriptorPath: string): AcceptanceInput {
|
|
270
275
|
const errors = validateAcceptanceInput(value, "recoveryDescriptor.acceptance");
|
|
271
276
|
if (errors.length) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': ${errors.join(" ")}`);
|
|
272
277
|
return value as AcceptanceInput;
|
|
@@ -289,6 +294,17 @@ function normalizeRecoveryTurnBudget(value: unknown, descriptorPath: string): Re
|
|
|
289
294
|
return result.turnBudget;
|
|
290
295
|
}
|
|
291
296
|
|
|
297
|
+
export function asyncReviveRequiresRecoveryDescriptor(target: Pick<AsyncResumeTarget, "recoveryDescriptor" | "mode" | "sessionFile">): boolean {
|
|
298
|
+
if (target.recoveryDescriptor) return false;
|
|
299
|
+
return !(target.mode === "workflow" && Boolean(target.sessionFile));
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function resumeTargetMode(status: AsyncStatus | null, result: AsyncResultFile | undefined): SubagentRunMode | undefined {
|
|
303
|
+
if (status?.mode) return status.mode;
|
|
304
|
+
if (result?.mode === "single" || result?.mode === "parallel" || result?.mode === "chain" || result?.mode === "workflow") return result.mode;
|
|
305
|
+
return undefined;
|
|
306
|
+
}
|
|
307
|
+
|
|
292
308
|
export function readAsyncRecoveryDescriptor(asyncDir: string | undefined): SteeringRecoveryDescriptor | undefined {
|
|
293
309
|
if (!asyncDir) return undefined;
|
|
294
310
|
const descriptorPath = path.join(asyncDir, "recovery-descriptor.json");
|
|
@@ -302,7 +318,7 @@ export function readAsyncRecoveryDescriptor(asyncDir: string | undefined): Steer
|
|
|
302
318
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': expected an object.`);
|
|
303
319
|
const parsed = value as Record<string, unknown>;
|
|
304
320
|
const allowedFields = new Set([
|
|
305
|
-
"version", "launchContractDigest", "sourceRunId", "agentContract", "agent", "sessionFile", "cwd", "model", "fallbackModels", "thinking", "tools", "extensions",
|
|
321
|
+
"version", "launchContractDigest", "sourceRunId", "agentContract", "agent", "sessionFile", "cwd", "model", "modelOverrideFromParent", "fallbackModels", "thinking", "tools", "extensions",
|
|
306
322
|
"subagentOnlyExtensions", "mcpDirectTools", "systemPrompt", "systemPromptMode", "inheritProjectContext", "inheritSkills", "skills",
|
|
307
323
|
"skillPath", "agentFilePath", "completionGuard", "memory", "outputPath", "outputMode", "structuredOutputSchema", "acceptance", "sessionDir", "artifactConfig",
|
|
308
324
|
"artifactsDir", "maxOutput", "controlConfig", "intercomBridge", "absoluteDeadlineAt", "initialTurnBudget", "initialToolBudget", "maxSubagentDepth", "share", "capabilityCeiling",
|
|
@@ -329,6 +345,7 @@ export function readAsyncRecoveryDescriptor(asyncDir: string | undefined): Steer
|
|
|
329
345
|
}
|
|
330
346
|
if (parsed.systemPromptMode !== "append" && parsed.systemPromptMode !== "replace") throw new Error(`Invalid async recovery descriptor '${descriptorPath}': systemPromptMode is invalid.`);
|
|
331
347
|
if (parsed.outputMode !== "inline" && parsed.outputMode !== "file-only") throw new Error(`Invalid async recovery descriptor '${descriptorPath}': outputMode is invalid.`);
|
|
348
|
+
if (parsed.modelOverrideFromParent !== undefined && typeof parsed.modelOverrideFromParent !== "boolean") throw new Error(`Invalid async recovery descriptor '${descriptorPath}': modelOverrideFromParent must be a boolean.`);
|
|
332
349
|
for (const field of ["inheritProjectContext", "inheritSkills", "share"] as const) {
|
|
333
350
|
if (typeof parsed[field] !== "boolean") throw new Error(`Invalid async recovery descriptor '${descriptorPath}': ${field} must be a boolean.`);
|
|
334
351
|
}
|
|
@@ -395,11 +412,7 @@ export function readAsyncRecoveryDescriptor(asyncDir: string | undefined): Steer
|
|
|
395
412
|
if (!Array.isArray(control.notifyOn) || control.notifyOn.some((item) => item !== "active_long_running" && item !== "needs_attention")) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': controlConfig.notifyOn is invalid.`);
|
|
396
413
|
if (!Array.isArray(control.notifyChannels) || control.notifyChannels.some((item) => item !== "event" && item !== "async" && item !== "intercom")) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': controlConfig.notifyChannels is invalid.`);
|
|
397
414
|
}
|
|
398
|
-
if (parsed.acceptance !== undefined)
|
|
399
|
-
const acceptance = normalizeRecoveryAcceptance(parsed.acceptance, descriptorPath);
|
|
400
|
-
if (acceptance === undefined) delete parsed.acceptance;
|
|
401
|
-
else parsed.acceptance = acceptance;
|
|
402
|
-
}
|
|
415
|
+
if (parsed.acceptance !== undefined) parsed.acceptance = normalizeRecoveryAcceptance(parsed.acceptance, descriptorPath);
|
|
403
416
|
return parsed as unknown as SteeringRecoveryDescriptor;
|
|
404
417
|
}
|
|
405
418
|
|
|
@@ -410,6 +423,17 @@ function validateResumeSessionFile(runId: string, sessionFile: string): string {
|
|
|
410
423
|
return resolved;
|
|
411
424
|
}
|
|
412
425
|
|
|
426
|
+
function validateResumeCwd(runId: string, cwd: string | undefined): string | undefined {
|
|
427
|
+
if (!cwd) return undefined;
|
|
428
|
+
const resolved = path.resolve(cwd);
|
|
429
|
+
try {
|
|
430
|
+
if (!fs.statSync(resolved).isDirectory()) throw new Error("path is not a directory");
|
|
431
|
+
} catch (error) {
|
|
432
|
+
throw new Error(`Async run '${runId}' required cwd does not exist: ${cwd}`, { cause: error instanceof Error ? error : undefined });
|
|
433
|
+
}
|
|
434
|
+
return resolved;
|
|
435
|
+
}
|
|
436
|
+
|
|
413
437
|
export function resolveAsyncResumeTarget(params: AsyncResumeParams, deps: AsyncResumeDeps = {}, options: AsyncResumeOptions = {}): AsyncResumeTarget {
|
|
414
438
|
const asyncDirRoot = deps.asyncDirRoot ?? DIRS.async;
|
|
415
439
|
const resultsDir = deps.resultsDir ?? DIRS.results;
|
|
@@ -427,6 +451,7 @@ export function resolveAsyncResumeTarget(params: AsyncResumeParams, deps: AsyncR
|
|
|
427
451
|
const recoveryDescriptor = readAsyncRecoveryDescriptor(location.asyncDir ?? undefined);
|
|
428
452
|
const result = location.resultPath ? readResultFile(location.resultPath) : undefined;
|
|
429
453
|
const runId = status?.runId ?? result?.runId ?? result?.id ?? location.resolvedId ?? (location.asyncDir ? path.basename(location.asyncDir) : "unknown");
|
|
454
|
+
const mode = resumeTargetMode(status, result);
|
|
430
455
|
if (options.sessionId && ((status && status.sessionId !== options.sessionId) || (result && result.sessionId !== options.sessionId))) {
|
|
431
456
|
throw new Error(`Async run '${runId}' was not found in the active session.`);
|
|
432
457
|
}
|
|
@@ -453,6 +478,7 @@ export function resolveAsyncResumeTarget(params: AsyncResumeParams, deps: AsyncR
|
|
|
453
478
|
runId,
|
|
454
479
|
asyncDir: location.asyncDir ?? undefined,
|
|
455
480
|
state,
|
|
481
|
+
...(mode ? { mode } : {}),
|
|
456
482
|
agent: selectedStep.agent,
|
|
457
483
|
index: requestedIndex,
|
|
458
484
|
cwd: status?.cwd ?? result?.cwd,
|
|
@@ -480,6 +506,7 @@ export function resolveAsyncResumeTarget(params: AsyncResumeParams, deps: AsyncR
|
|
|
480
506
|
runId,
|
|
481
507
|
asyncDir: location.asyncDir ?? undefined,
|
|
482
508
|
state,
|
|
509
|
+
...(mode ? { mode } : {}),
|
|
483
510
|
agent: selected.step.agent,
|
|
484
511
|
index: selected.index,
|
|
485
512
|
cwd: status?.cwd ?? result?.cwd,
|
|
@@ -510,15 +537,20 @@ export function resolveAsyncResumeTarget(params: AsyncResumeParams, deps: AsyncR
|
|
|
510
537
|
const stepModel = statusSteps[index]?.model ?? resultSteps[index]?.model ?? (stepCount === 1 ? result?.model : undefined);
|
|
511
538
|
const stepThinking = statusSteps[index]?.thinking ?? resultSteps[index]?.thinking ?? (stepCount === 1 ? result?.thinking : undefined);
|
|
512
539
|
const capabilityCeiling = intersectSubagentCapabilityCeilings(status?.capabilityCeiling, statusSteps[index]?.capabilityCeiling, result?.capabilityCeiling, resultSteps[index]?.capabilityCeiling);
|
|
540
|
+
const managedWorktreeCwd = location.asyncDir
|
|
541
|
+
? resolveRetainedWorktreeCwd(parallelHandoffPath(location.asyncDir), runId, index)
|
|
542
|
+
: undefined;
|
|
543
|
+
const resumeCwd = validateResumeCwd(runId, managedWorktreeCwd ?? status?.cwd ?? result?.cwd ?? recoveryDescriptor?.cwd);
|
|
513
544
|
|
|
514
545
|
return {
|
|
515
546
|
kind: "revive",
|
|
516
547
|
runId,
|
|
517
548
|
asyncDir: location.asyncDir ?? undefined,
|
|
518
549
|
state,
|
|
550
|
+
...(mode ? { mode } : {}),
|
|
519
551
|
agent,
|
|
520
552
|
index,
|
|
521
|
-
cwd:
|
|
553
|
+
...(resumeCwd ? { cwd: resumeCwd } : {}),
|
|
522
554
|
...(resolvedSessionFile ? { sessionFile: resolvedSessionFile } : {}),
|
|
523
555
|
...(stepModel ? { model: stepModel } : {}),
|
|
524
556
|
...(stepThinking ? { thinking: stepThinking } : {}),
|