pi-subagents 0.47.0 → 0.48.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 +37 -0
- package/README.md +2 -0
- package/agents/reviewer.md +3 -4
- package/docs/configuration.md +46 -2
- package/docs/observability.md +1 -1
- package/docs/tool-reference.md +1 -1
- package/package.json +1 -1
- package/src/agents/agents.ts +9 -3
- package/src/extension/config.ts +16 -0
- package/src/extension/doctor.ts +40 -0
- package/src/extension/index.ts +30 -11
- package/src/extension/public-execution.ts +5 -0
- package/src/extension/rpc.ts +7 -9
- package/src/extension/schemas.ts +4 -4
- package/src/intercom/intercom-bridge.ts +4 -1
- package/src/intercom/native-supervisor-channel.ts +102 -4
- package/src/missions/lifecycle.ts +4 -7
- package/src/missions/store.ts +4 -4
- package/src/missions/workflow-state.ts +6 -2
- package/src/runs/background/active-async-capacity.ts +374 -0
- package/src/runs/background/active-run-index.ts +46 -0
- package/src/runs/background/async-execution.ts +117 -29
- package/src/runs/background/async-job-tracker.ts +279 -134
- package/src/runs/background/async-resume.ts +7 -1
- package/src/runs/background/async-status.ts +33 -4
- package/src/runs/background/chain-append.ts +33 -15
- package/src/runs/background/control-channel.ts +55 -17
- package/src/runs/background/owned-process-tree.ts +104 -0
- package/src/runs/background/process-terminal.ts +17 -3
- package/src/runs/background/result-watcher.ts +87 -6
- package/src/runs/background/run-status.ts +23 -1
- package/src/runs/background/scheduled-runs.ts +23 -0
- package/src/runs/background/stale-run-reconciler.ts +10 -3
- package/src/runs/background/subagent-runner.ts +75 -33
- package/src/runs/foreground/async-dismiss-action.ts +85 -0
- package/src/runs/foreground/async-steering-action.ts +6 -7
- package/src/runs/foreground/chain-execution.ts +37 -2
- package/src/runs/foreground/execution.ts +90 -19
- package/src/runs/foreground/foreground-control.ts +12 -0
- package/src/runs/foreground/prompt-audit.ts +171 -0
- package/src/runs/foreground/subagent-executor.ts +648 -195
- package/src/runs/shared/acceptance.ts +13 -4
- package/src/runs/shared/llm-intent-arbiter.ts +286 -0
- package/src/runs/shared/parallel-utils.ts +2 -0
- package/src/runs/shared/pi-args.ts +44 -1
- package/src/runs/shared/run-fanout-budget.ts +280 -0
- package/src/runs/shared/single-output.ts +4 -2
- package/src/runs/shared/subagent-prompt-runtime.ts +24 -5
- package/src/runs/shared/task-intent.ts +19 -3
- package/src/runs/shared/worktree.ts +17 -5
- package/src/shared/artifacts.ts +1 -1
- package/src/shared/file-coalescer.ts +9 -0
- package/src/shared/types.ts +97 -1
- package/src/shared/utils.ts +3 -1
- package/src/tui/fleet-status.ts +7 -5
- package/src/tui/fleet.ts +225 -12
- package/src/workflows/scripted-workflow.ts +26 -6
|
@@ -21,7 +21,7 @@ import type { ContextMode } from "../shared/context-mode.ts";
|
|
|
21
21
|
import { resolvePiPackageRoot } from "../shared/pi-spawn.ts";
|
|
22
22
|
import { buildSkillInjection, normalizeSkillInput, resolveSkillsWithFallback } from "../../agents/skills.ts";
|
|
23
23
|
import { buildAgentMemoryInjection } from "../../agents/agent-memory.ts";
|
|
24
|
-
import { PI_CODING_AGENT_PACKAGE_ROOT_ENV, resolveChildCwd } from "../../shared/utils.ts";
|
|
24
|
+
import { PI_CODING_AGENT_PACKAGE_ROOT_ENV, PROMPT_REDACTED, resolveChildCwd } from "../../shared/utils.ts";
|
|
25
25
|
import { buildModelCandidates, resolveEffectiveSubagentModel, resolveModelCandidate, resolveSubagentModelOverride, type AvailableModelInfo, type ParentModel } from "../shared/model-fallback.ts";
|
|
26
26
|
import type { ModelScopeConfig } from "../shared/model-scope.ts";
|
|
27
27
|
import { resolveEffectiveThinking } from "../../shared/model-info.ts";
|
|
@@ -30,9 +30,11 @@ import { buildWorkflowGraphSnapshot } from "../shared/workflow-graph.ts";
|
|
|
30
30
|
import { ChainOutputValidationError, validateChainOutputBindings } from "../shared/chain-outputs.ts";
|
|
31
31
|
import { createStructuredOutputRuntime } from "../shared/structured-output.ts";
|
|
32
32
|
import { resolveEffectiveAcceptance, validateAcceptanceInput, validateExecutionAcceptance } from "../shared/acceptance.ts";
|
|
33
|
+
import { createRunFanoutBudget, writeRunFanoutBudgetDescriptor } from "../shared/run-fanout-budget.ts";
|
|
33
34
|
import {
|
|
34
35
|
type AcceptanceInput,
|
|
35
36
|
type AgentContract,
|
|
37
|
+
type AsyncStatus,
|
|
36
38
|
type ArtifactConfig,
|
|
37
39
|
type Details,
|
|
38
40
|
type JsonSchemaObject,
|
|
@@ -41,6 +43,7 @@ import {
|
|
|
41
43
|
type ResolvedControlConfig,
|
|
42
44
|
type ResolvedTurnBudget,
|
|
43
45
|
type ResolvedToolBudget,
|
|
46
|
+
type RunFanoutBudgetDescriptor,
|
|
44
47
|
type ToolBudgetConfig,
|
|
45
48
|
type SubagentRunMode,
|
|
46
49
|
type SteeringRecoveryDescriptor,
|
|
@@ -59,6 +62,7 @@ import { usageBudgetState } from "../shared/usage-budget.ts";
|
|
|
59
62
|
import type { ImportedAsyncRoot } from "./chain-root-attachment.ts";
|
|
60
63
|
import type { SessionLeaseRequest } from "../shared/session-lease.ts";
|
|
61
64
|
import { finalizeProcessTerminal, readProcessTerminal } from "./process-terminal.ts";
|
|
65
|
+
import type { ActiveAsyncCapacityHandle } from "./active-async-capacity.ts";
|
|
62
66
|
import { SUBAGENT_PROCESS_TERMINAL_EVENT } from "../../shared/types.ts";
|
|
63
67
|
import { assertAgentAllowedByCapabilityCeiling, decodeSubagentCapabilityCeiling, intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling, SUBAGENT_CAPABILITY_CEILING_ENV, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
|
|
64
68
|
import { agentDefinitionDigest, launchBindingDigest } from "../../shared/launch-contract.ts";
|
|
@@ -171,8 +175,10 @@ interface AsyncChainParams {
|
|
|
171
175
|
/** Global cap on simultaneously-running subagent tasks within the async run. */
|
|
172
176
|
globalConcurrencyLimit?: number;
|
|
173
177
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
178
|
+
runFanoutBudget?: RunFanoutBudgetDescriptor;
|
|
174
179
|
parentWorkflowRunId?: string;
|
|
175
180
|
workflowKey?: string;
|
|
181
|
+
activeAsyncCapacity?: ActiveAsyncCapacityHandle;
|
|
176
182
|
}
|
|
177
183
|
|
|
178
184
|
interface AsyncSingleParams {
|
|
@@ -220,8 +226,10 @@ interface AsyncSingleParams {
|
|
|
220
226
|
configToolBudget?: ResolvedToolBudget;
|
|
221
227
|
allowZeroToolBudget?: boolean;
|
|
222
228
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
229
|
+
runFanoutBudget?: RunFanoutBudgetDescriptor;
|
|
223
230
|
parentWorkflowRunId?: string;
|
|
224
231
|
workflowKey?: string;
|
|
232
|
+
activeAsyncCapacity?: ActiveAsyncCapacityHandle;
|
|
225
233
|
}
|
|
226
234
|
|
|
227
235
|
interface AsyncExecutionResult {
|
|
@@ -343,7 +351,7 @@ type RunnerStartupState = "ready" | "acknowledged";
|
|
|
343
351
|
|
|
344
352
|
type RunnerStartupWaitResult =
|
|
345
353
|
| { ok: true; token: string }
|
|
346
|
-
| { ok: false; error: string };
|
|
354
|
+
| { ok: false; error: string; startupDidNotProceed?: boolean };
|
|
347
355
|
|
|
348
356
|
function waitForStartupInterval(delayMs = 20): void {
|
|
349
357
|
if (RUNNER_STARTUP_WAIT_VIEW) {
|
|
@@ -360,14 +368,14 @@ function readRunnerStartup(startupPath: string, expectedState: RunnerStartupStat
|
|
|
360
368
|
if (!fs.existsSync(startupPath)) return undefined;
|
|
361
369
|
try {
|
|
362
370
|
const payload = JSON.parse(fs.readFileSync(startupPath, "utf-8")) as { state?: unknown; token?: unknown; error?: unknown };
|
|
363
|
-
if (payload.state === "error" && typeof payload.error === "string") return { ok: false, error: payload.error };
|
|
371
|
+
if (payload.state === "error" && typeof payload.error === "string") return { ok: false, error: payload.error, startupDidNotProceed: true };
|
|
364
372
|
if (payload.state !== expectedState) return undefined;
|
|
365
373
|
if (typeof payload.token !== "string" || (expectedToken !== undefined && payload.token !== expectedToken)) {
|
|
366
|
-
return { ok: false, error: `Async runner wrote an invalid ${expectedState} startup handshake: ${startupPath}
|
|
374
|
+
return { ok: false, error: `Async runner wrote an invalid ${expectedState} startup handshake: ${startupPath}`, startupDidNotProceed: true };
|
|
367
375
|
}
|
|
368
376
|
return { ok: true, token: payload.token };
|
|
369
377
|
} catch (error) {
|
|
370
|
-
return { ok: false, error: `Failed to read async runner startup handshake '${startupPath}': ${error instanceof Error ? error.message : String(error)}
|
|
378
|
+
return { ok: false, error: `Failed to read async runner startup handshake '${startupPath}': ${error instanceof Error ? error.message : String(error)}`, startupDidNotProceed: true };
|
|
371
379
|
}
|
|
372
380
|
}
|
|
373
381
|
|
|
@@ -381,7 +389,7 @@ function waitForRunnerStartup(startupPath: string, expectedState: RunnerStartupS
|
|
|
381
389
|
}
|
|
382
390
|
const finalResult = readRunnerStartup(startupPath, expectedState, expectedToken);
|
|
383
391
|
if (finalResult) return finalResult;
|
|
384
|
-
return { ok: false, error: `Timed out after ${timeoutMs}ms waiting for the async runner startup state '${expectedState}'
|
|
392
|
+
return { ok: false, error: `Timed out after ${timeoutMs}ms waiting for the async runner startup state '${expectedState}'.`, startupDidNotProceed: true };
|
|
385
393
|
}
|
|
386
394
|
|
|
387
395
|
function writeRunnerStartupControl(filePath: string, payload: { action: "ack" | "proceed"; token: string }): void {
|
|
@@ -402,20 +410,63 @@ function runnerIsAlive(pid: number): boolean {
|
|
|
402
410
|
}
|
|
403
411
|
}
|
|
404
412
|
|
|
405
|
-
function terminateRunnerBeforeProceed(pid: number):
|
|
413
|
+
function terminateRunnerBeforeProceed(pid: number): boolean {
|
|
406
414
|
for (const signal of ["SIGTERM", "SIGKILL"] as const) {
|
|
407
|
-
if (!runnerIsAlive(pid)) return;
|
|
415
|
+
if (!runnerIsAlive(pid)) return true;
|
|
408
416
|
try {
|
|
409
417
|
process.kill(pid, signal);
|
|
410
418
|
} catch {
|
|
411
|
-
if (!runnerIsAlive(pid)) return;
|
|
419
|
+
if (!runnerIsAlive(pid)) return true;
|
|
412
420
|
}
|
|
413
421
|
const deadline = Date.now() + 1000;
|
|
414
422
|
while (runnerIsAlive(pid) && Date.now() < deadline) waitForStartupInterval();
|
|
415
423
|
}
|
|
424
|
+
return !runnerIsAlive(pid);
|
|
416
425
|
}
|
|
417
426
|
|
|
418
|
-
function
|
|
427
|
+
function persistPreProceedStartupFailure(asyncDir: string, runId: string, runnerProcessInstanceId: string, sessionId: string | undefined, message: string): void {
|
|
428
|
+
const now = Date.now();
|
|
429
|
+
try {
|
|
430
|
+
const statusPath = path.join(asyncDir, "status.json");
|
|
431
|
+
let status: Partial<AsyncStatus> = {};
|
|
432
|
+
try {
|
|
433
|
+
status = JSON.parse(fs.readFileSync(statusPath, "utf-8")) as Partial<AsyncStatus>;
|
|
434
|
+
} catch {}
|
|
435
|
+
writePrivateAtomicJson(statusPath, {
|
|
436
|
+
...status,
|
|
437
|
+
runId,
|
|
438
|
+
...(sessionId ? { sessionId } : {}),
|
|
439
|
+
state: "failed",
|
|
440
|
+
lastUpdate: now,
|
|
441
|
+
error: message,
|
|
442
|
+
processTerminal: {
|
|
443
|
+
version: 1,
|
|
444
|
+
state: "not-started",
|
|
445
|
+
runId,
|
|
446
|
+
runnerProcessInstanceId,
|
|
447
|
+
},
|
|
448
|
+
});
|
|
449
|
+
writePrivateAtomicJson(path.join(asyncDir, "process-terminal-candidate.json"), {
|
|
450
|
+
version: 1,
|
|
451
|
+
runId,
|
|
452
|
+
runnerProcessInstanceId,
|
|
453
|
+
writers: {},
|
|
454
|
+
expectedWriters: { 0: 0 },
|
|
455
|
+
});
|
|
456
|
+
} catch {
|
|
457
|
+
// Startup failures must still return the original launch error.
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
interface SpawnRunnerResult {
|
|
462
|
+
pid?: number;
|
|
463
|
+
runnerProcessInstanceId?: string;
|
|
464
|
+
error?: string;
|
|
465
|
+
terminationObserved?: boolean;
|
|
466
|
+
startupDidNotProceed?: boolean;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function spawnRunner(cfg: object, suffix: string, cwd: string, onProcessTerminal?: (proof: unknown) => void): SpawnRunnerResult {
|
|
419
470
|
if (!jitiCliPath) {
|
|
420
471
|
return { error: "upstream jiti for TypeScript execution could not be found; ensure package dependencies are installed" };
|
|
421
472
|
}
|
|
@@ -436,8 +487,10 @@ function spawnRunner(cfg: object, suffix: string, cwd: string, onProcessTerminal
|
|
|
436
487
|
fs.writeFileSync(cfgPath, JSON.stringify(launchConfig));
|
|
437
488
|
const runner = path.join(path.dirname(fileURLToPath(import.meta.url)), "subagent-runner.ts");
|
|
438
489
|
const nodeCommand = resolveAsyncRunnerNodeCommand();
|
|
439
|
-
const launchForStartup = launchConfig as typeof launchConfig & { asyncDir?: unknown; revivalLease?: unknown };
|
|
490
|
+
const launchForStartup = launchConfig as typeof launchConfig & { asyncDir?: unknown; id?: unknown; sessionId?: unknown; revivalLease?: unknown };
|
|
440
491
|
const launchAsyncDir = typeof launchForStartup.asyncDir === "string" ? launchForStartup.asyncDir : undefined;
|
|
492
|
+
const launchRunId = typeof launchForStartup.id === "string" ? launchForStartup.id : suffix;
|
|
493
|
+
const launchSessionId = typeof launchForStartup.sessionId === "string" ? launchForStartup.sessionId : undefined;
|
|
441
494
|
const startupPath = typeof launchForStartup.revivalLease === "object" && launchAsyncDir
|
|
442
495
|
? path.join(launchAsyncDir, "runner-startup.json")
|
|
443
496
|
: undefined;
|
|
@@ -526,27 +579,36 @@ function spawnRunner(cfg: object, suffix: string, cwd: string, onProcessTerminal
|
|
|
526
579
|
}
|
|
527
580
|
proc.unref();
|
|
528
581
|
if (startupPath && startupAckPath && startupProceedPath) {
|
|
582
|
+
const persistStartupFailure = (message: string) => {
|
|
583
|
+
if (launchAsyncDir) persistPreProceedStartupFailure(launchAsyncDir, launchRunId, runnerProcessInstanceId, launchSessionId, message);
|
|
584
|
+
};
|
|
529
585
|
const ready = waitForRunnerStartup(startupPath, "ready", RUNNER_STARTUP_TIMEOUT_MS);
|
|
530
586
|
if (ready.ok === false) {
|
|
531
|
-
|
|
532
|
-
|
|
587
|
+
persistStartupFailure(ready.error);
|
|
588
|
+
const terminationObserved = terminateRunnerBeforeProceed(proc.pid);
|
|
589
|
+
return { pid: proc.pid, runnerProcessInstanceId, error: ready.error, terminationObserved, startupDidNotProceed: ready.startupDidNotProceed };
|
|
533
590
|
}
|
|
534
591
|
try {
|
|
535
592
|
writeRunnerStartupControl(startupAckPath, { action: "ack", token: ready.token });
|
|
536
593
|
} catch (error) {
|
|
537
|
-
|
|
538
|
-
|
|
594
|
+
const message = `Failed to acknowledge async runner startup: ${error instanceof Error ? error.message : String(error)}`;
|
|
595
|
+
persistStartupFailure(message);
|
|
596
|
+
const terminationObserved = terminateRunnerBeforeProceed(proc.pid);
|
|
597
|
+
return { pid: proc.pid, runnerProcessInstanceId, error: message, terminationObserved, startupDidNotProceed: true };
|
|
539
598
|
}
|
|
540
599
|
const acknowledged = waitForRunnerStartup(startupPath, "acknowledged", RUNNER_STARTUP_TIMEOUT_MS, ready.token);
|
|
541
600
|
if (acknowledged.ok === false) {
|
|
542
|
-
|
|
543
|
-
|
|
601
|
+
persistStartupFailure(acknowledged.error);
|
|
602
|
+
const terminationObserved = terminateRunnerBeforeProceed(proc.pid);
|
|
603
|
+
return { pid: proc.pid, runnerProcessInstanceId, error: acknowledged.error, terminationObserved, startupDidNotProceed: acknowledged.startupDidNotProceed };
|
|
544
604
|
}
|
|
545
605
|
try {
|
|
546
606
|
writeRunnerStartupControl(startupProceedPath, { action: "proceed", token: ready.token });
|
|
547
607
|
} catch (error) {
|
|
548
|
-
|
|
549
|
-
|
|
608
|
+
const message = `Failed to authorize async runner startup: ${error instanceof Error ? error.message : String(error)}`;
|
|
609
|
+
persistStartupFailure(message);
|
|
610
|
+
const terminationObserved = terminateRunnerBeforeProceed(proc.pid);
|
|
611
|
+
return { pid: proc.pid, runnerProcessInstanceId, error: message, terminationObserved, startupDidNotProceed: true };
|
|
550
612
|
}
|
|
551
613
|
try {
|
|
552
614
|
fs.rmSync(startupPath, { force: true });
|
|
@@ -554,7 +616,7 @@ function spawnRunner(cfg: object, suffix: string, cwd: string, onProcessTerminal
|
|
|
554
616
|
// Proceed is the commit point; handshake cleanup cannot turn a running revival into a start error.
|
|
555
617
|
}
|
|
556
618
|
}
|
|
557
|
-
return { pid: proc.pid };
|
|
619
|
+
return { pid: proc.pid, runnerProcessInstanceId };
|
|
558
620
|
} catch (error) {
|
|
559
621
|
closeFd(stdoutFd);
|
|
560
622
|
closeFd(stderrFd);
|
|
@@ -648,7 +710,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
648
710
|
...(s.model !== undefined ? { model: s.model } : {}),
|
|
649
711
|
};
|
|
650
712
|
};
|
|
651
|
-
const buildSeqStep = (s: SequentialStep, sessionFile?: string, behaviorCwd?: string, progressPrecreated = false, resolvedBehavior?: ResolvedStepBehavior, flatIndex?: number, parallelOutputNamespace?: { stepIndex: number; taskIndex?: number }) => {
|
|
713
|
+
const buildSeqStep = (s: SequentialStep, sessionFile?: string, behaviorCwd?: string, progressPrecreated = false, resolvedBehavior?: ResolvedStepBehavior, flatIndex?: number, parallelOutputNamespace?: { stepIndex: number; taskIndex?: number }, runFanoutPath?: string) => {
|
|
652
714
|
const a = agents.find((x) => x.name === s.agent)!;
|
|
653
715
|
const externalRunner = a.runner?.type === "external-cli";
|
|
654
716
|
if (externalRunner) {
|
|
@@ -748,6 +810,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
748
810
|
parentSessionId: ctx.parentSessionId ?? ctx.currentSessionId,
|
|
749
811
|
permissionRules,
|
|
750
812
|
...(params.capabilityCeiling ? { capabilityCeiling: params.capabilityCeiling } : {}),
|
|
813
|
+
...(runFanoutPath ? { runFanoutPath } : {}),
|
|
751
814
|
agent: s.agent,
|
|
752
815
|
task,
|
|
753
816
|
...(a.runner ? { runner: a.runner } : {}),
|
|
@@ -839,7 +902,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
839
902
|
}
|
|
840
903
|
}
|
|
841
904
|
const staticStep = nextFlatStep();
|
|
842
|
-
return buildSeqStep({ ...t, agentContract: t.agentContract ?? s.agentContract, gateOn: t.gateOn ?? s.gateOn }, staticStep.sessionFile, behaviorCwd, progressPrecreated, parallelBehaviors[taskIndex], staticStep.index, { stepIndex, taskIndex });
|
|
905
|
+
return buildSeqStep({ ...t, agentContract: t.agentContract ?? s.agentContract, gateOn: t.gateOn ?? s.gateOn }, staticStep.sessionFile, behaviorCwd, progressPrecreated, parallelBehaviors[taskIndex], staticStep.index, { stepIndex, taskIndex }, resultMode === "parallel" ? `tasks[${taskIndex}]` : `chain[${stepIndex}].parallel[${taskIndex}]`);
|
|
843
906
|
}),
|
|
844
907
|
concurrency: s.concurrency,
|
|
845
908
|
failFast: s.failFast,
|
|
@@ -884,7 +947,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
884
947
|
};
|
|
885
948
|
}
|
|
886
949
|
const staticStep = nextFlatStep();
|
|
887
|
-
return buildSeqStep(s as SequentialStep, staticStep.sessionFile, undefined, false, undefined, staticStep.index);
|
|
950
|
+
return buildSeqStep(s as SequentialStep, staticStep.sessionFile, undefined, false, undefined, staticStep.index, undefined, `chain[${stepIndex}]`);
|
|
888
951
|
});
|
|
889
952
|
const steps = params.attachRoot
|
|
890
953
|
? [{
|
|
@@ -966,8 +1029,11 @@ export function executeAsyncChain(
|
|
|
966
1029
|
const asyncDir = inheritedNestedRoute
|
|
967
1030
|
? path.join(TEMP_ROOT_DIR, "nested-subagent-runs", inheritedNestedRoute.rootRunId, id)
|
|
968
1031
|
: path.join(DIRS.async, id);
|
|
1032
|
+
let runFanoutBudget: RunFanoutBudgetDescriptor;
|
|
969
1033
|
try {
|
|
1034
|
+
runFanoutBudget = params.runFanoutBudget ?? createRunFanoutBudget(id, 64);
|
|
970
1035
|
fs.mkdirSync(asyncDir, { recursive: true });
|
|
1036
|
+
writeRunFanoutBudgetDescriptor(asyncDir, runFanoutBudget);
|
|
971
1037
|
} catch (error) {
|
|
972
1038
|
const message = error instanceof Error ? error.message : String(error);
|
|
973
1039
|
return {
|
|
@@ -1030,7 +1096,7 @@ export function executeAsyncChain(
|
|
|
1030
1096
|
return "agent" in step ? [childIntercomTarget(step.agent, childTargetIndex++)] : [undefined];
|
|
1031
1097
|
}) : undefined;
|
|
1032
1098
|
|
|
1033
|
-
let spawnResult:
|
|
1099
|
+
let spawnResult: SpawnRunnerResult = {};
|
|
1034
1100
|
try {
|
|
1035
1101
|
spawnResult = spawnRunner(
|
|
1036
1102
|
{
|
|
@@ -1063,6 +1129,7 @@ export function executeAsyncChain(
|
|
|
1063
1129
|
timeoutMs: params.timeoutMs,
|
|
1064
1130
|
deadlineAt,
|
|
1065
1131
|
globalConcurrencyLimit: params.globalConcurrencyLimit,
|
|
1132
|
+
runFanoutBudget,
|
|
1066
1133
|
workflowGraph,
|
|
1067
1134
|
...(params.parentWorkflowRunId ? { parentWorkflowRunId: params.parentWorkflowRunId } : {}),
|
|
1068
1135
|
...(params.workflowKey ? { workflowKey: params.workflowKey } : {}),
|
|
@@ -1079,13 +1146,21 @@ export function executeAsyncChain(
|
|
|
1079
1146
|
(proof) => ctx.pi.events.emit(SUBAGENT_PROCESS_TERMINAL_EVENT, proof),
|
|
1080
1147
|
);
|
|
1081
1148
|
} catch (error) {
|
|
1149
|
+
params.activeAsyncCapacity?.rollback();
|
|
1082
1150
|
const message = error instanceof Error ? error.message : String(error);
|
|
1083
1151
|
return formatAsyncStartError(resultMode, `Failed to start async ${resultMode} '${id}': ${message}`);
|
|
1084
1152
|
}
|
|
1085
1153
|
|
|
1086
1154
|
if (spawnResult.error) {
|
|
1155
|
+
if (!spawnResult.pid || !spawnResult.runnerProcessInstanceId || (spawnResult.startupDidNotProceed && spawnResult.terminationObserved)) params.activeAsyncCapacity?.rollback();
|
|
1156
|
+
else params.activeAsyncCapacity?.markStarted(spawnResult.runnerProcessInstanceId);
|
|
1087
1157
|
return formatAsyncStartError(resultMode, `Failed to start async ${resultMode} '${id}': ${spawnResult.error}`);
|
|
1088
1158
|
}
|
|
1159
|
+
if (!spawnResult.pid || !spawnResult.runnerProcessInstanceId) {
|
|
1160
|
+
params.activeAsyncCapacity?.rollback();
|
|
1161
|
+
return formatAsyncStartError(resultMode, `Failed to start async ${resultMode} '${id}': runner identity unavailable`);
|
|
1162
|
+
}
|
|
1163
|
+
params.activeAsyncCapacity?.markStarted(spawnResult.runnerProcessInstanceId);
|
|
1089
1164
|
|
|
1090
1165
|
if (spawnResult.pid) {
|
|
1091
1166
|
const eventFirstStep = eventChain[0];
|
|
@@ -1166,8 +1241,8 @@ export function executeAsyncChain(
|
|
|
1166
1241
|
mode: resultMode,
|
|
1167
1242
|
agent: firstAgents[0],
|
|
1168
1243
|
agents: flatAgents,
|
|
1169
|
-
task: firstTask?.
|
|
1170
|
-
goal: workflowGoal?.
|
|
1244
|
+
task: firstTask?.trim() ? PROMPT_REDACTED : undefined,
|
|
1245
|
+
goal: workflowGoal?.trim() ? PROMPT_REDACTED : undefined,
|
|
1171
1246
|
chain: eventChain.map((s) =>
|
|
1172
1247
|
isParallelStep(s) ? `[${s.parallel.map((t) => t.agent).join("+")}]` : isDynamicParallelStep(s) ? `expand:${s.parallel.agent}` : (s as SequentialStep).agent,
|
|
1173
1248
|
),
|
|
@@ -1279,8 +1354,11 @@ export function executeAsyncSingle(
|
|
|
1279
1354
|
const asyncDir = inheritedNestedRoute
|
|
1280
1355
|
? path.join(TEMP_ROOT_DIR, "nested-subagent-runs", inheritedNestedRoute.rootRunId, id)
|
|
1281
1356
|
: path.join(DIRS.async, id);
|
|
1357
|
+
let runFanoutBudget: RunFanoutBudgetDescriptor;
|
|
1282
1358
|
try {
|
|
1359
|
+
runFanoutBudget = params.runFanoutBudget ?? createRunFanoutBudget(id, 64);
|
|
1283
1360
|
fs.mkdirSync(asyncDir, { recursive: true });
|
|
1361
|
+
writeRunFanoutBudgetDescriptor(asyncDir, runFanoutBudget);
|
|
1284
1362
|
} catch (error) {
|
|
1285
1363
|
const message = error instanceof Error ? error.message : String(error);
|
|
1286
1364
|
return {
|
|
@@ -1376,6 +1454,7 @@ export function executeAsyncSingle(
|
|
|
1376
1454
|
const recoveryDescriptor: SteeringRecoveryDescriptor = {
|
|
1377
1455
|
version: 1,
|
|
1378
1456
|
launchContractDigest,
|
|
1457
|
+
runFanoutBudget,
|
|
1379
1458
|
sourceRunId: id,
|
|
1380
1459
|
...(params.agentContract ? { agentContract: params.agentContract } : {}),
|
|
1381
1460
|
agent,
|
|
@@ -1421,7 +1500,7 @@ export function executeAsyncSingle(
|
|
|
1421
1500
|
return formatAsyncStartError("single", `Failed to persist async recovery descriptor for '${id}': ${error instanceof Error ? error.message : String(error)}`);
|
|
1422
1501
|
}
|
|
1423
1502
|
}
|
|
1424
|
-
let spawnResult:
|
|
1503
|
+
let spawnResult: SpawnRunnerResult = {};
|
|
1425
1504
|
try {
|
|
1426
1505
|
spawnResult = spawnRunner(
|
|
1427
1506
|
{
|
|
@@ -1494,6 +1573,7 @@ export function executeAsyncSingle(
|
|
|
1494
1573
|
resultMode: "single",
|
|
1495
1574
|
launchContractDigest,
|
|
1496
1575
|
launchResolvedExtensions,
|
|
1576
|
+
runFanoutBudget,
|
|
1497
1577
|
...(params.parentWorkflowRunId ? { parentWorkflowRunId: params.parentWorkflowRunId } : {}),
|
|
1498
1578
|
...(params.workflowKey ? { workflowKey: params.workflowKey } : {}),
|
|
1499
1579
|
...(params.revivalLease ? { revivalLease: params.revivalLease } : {}),
|
|
@@ -1510,13 +1590,21 @@ export function executeAsyncSingle(
|
|
|
1510
1590
|
(proof) => ctx.pi.events.emit(SUBAGENT_PROCESS_TERMINAL_EVENT, proof),
|
|
1511
1591
|
);
|
|
1512
1592
|
} catch (error) {
|
|
1593
|
+
params.activeAsyncCapacity?.rollback();
|
|
1513
1594
|
const message = error instanceof Error ? error.message : String(error);
|
|
1514
1595
|
return formatAsyncStartError("single", `Failed to start async run '${id}': ${message}`);
|
|
1515
1596
|
}
|
|
1516
1597
|
|
|
1517
1598
|
if (spawnResult.error) {
|
|
1599
|
+
if (!spawnResult.pid || !spawnResult.runnerProcessInstanceId || (spawnResult.startupDidNotProceed && spawnResult.terminationObserved)) params.activeAsyncCapacity?.rollback();
|
|
1600
|
+
else params.activeAsyncCapacity?.markStarted(spawnResult.runnerProcessInstanceId);
|
|
1518
1601
|
return formatAsyncStartError("single", `Failed to start async run '${id}': ${spawnResult.error}`);
|
|
1519
1602
|
}
|
|
1603
|
+
if (!spawnResult.pid || !spawnResult.runnerProcessInstanceId) {
|
|
1604
|
+
params.activeAsyncCapacity?.rollback();
|
|
1605
|
+
return formatAsyncStartError("single", `Failed to start async run '${id}': runner identity unavailable`);
|
|
1606
|
+
}
|
|
1607
|
+
params.activeAsyncCapacity?.markStarted(spawnResult.runnerProcessInstanceId);
|
|
1520
1608
|
|
|
1521
1609
|
if (spawnResult.pid) {
|
|
1522
1610
|
if (inheritedNestedRoute && nestedAddress) {
|
|
@@ -1562,8 +1650,8 @@ export function executeAsyncSingle(
|
|
|
1562
1650
|
sessionId: ctx.currentSessionId,
|
|
1563
1651
|
mode: "single",
|
|
1564
1652
|
agent,
|
|
1565
|
-
task: task?.
|
|
1566
|
-
goal: (params.goal ?? task).
|
|
1653
|
+
task: task?.trim() ? PROMPT_REDACTED : undefined,
|
|
1654
|
+
goal: (params.goal ?? task).trim() ? PROMPT_REDACTED : undefined,
|
|
1567
1655
|
cwd: runnerCwd,
|
|
1568
1656
|
asyncDir,
|
|
1569
1657
|
launchContractDigest,
|