pi-subagents 0.40.0 → 0.41.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 +76 -0
- package/README.md +246 -525
- package/agents/oracle.md +1 -0
- package/package.json +12 -4
- package/prompts/parallel-context-build.md +1 -1
- package/prompts/parallel-handoff-plan.md +1 -1
- package/prompts/review-loop.md +1 -1
- package/skills/pi-subagents/SKILL.md +6 -6
- package/skills/pi-subagents/references/constraints-and-recipes.md +19 -26
- package/skills/pi-subagents/references/execution-controls.md +98 -97
- package/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
- package/skills/pi-subagents/references/prompting-and-roles.md +18 -27
- package/src/agents/agent-management.ts +155 -65
- package/src/agents/agent-serializer.ts +19 -0
- package/src/agents/agents.ts +154 -71
- package/src/agents/chain-serializer.ts +10 -7
- package/src/agents/frontmatter.ts +5 -3
- package/src/agents/identity.ts +1 -1
- package/src/agents/proactive-skills.ts +13 -10
- package/src/agents/skills.ts +23 -6
- package/src/api/control-channel.ts +4 -0
- package/src/api/delegation.ts +26 -194
- package/src/api/external-runs.ts +129 -0
- package/src/api/intercom-bridge.ts +3 -0
- package/src/api/pi-args.ts +5 -0
- package/src/api/preflight.ts +3 -3
- package/src/api/shared-types.ts +19 -0
- package/src/extension/config.ts +10 -0
- package/src/extension/control-notices.ts +5 -39
- package/src/extension/doctor.ts +10 -9
- package/src/extension/fanout-child.ts +7 -4
- package/src/extension/index.ts +232 -68
- package/src/extension/rpc.ts +18 -12
- package/src/extension/schemas.ts +48 -37
- package/src/extension/tool-description.ts +36 -86
- package/src/inspectors/herdr/actions.ts +229 -0
- package/src/inspectors/herdr/client.ts +130 -0
- package/src/inspectors/herdr/inspector-runner.ts +141 -0
- package/src/inspectors/herdr/project-panes.ts +154 -0
- package/src/integrations/herdr-status.ts +330 -0
- package/src/intercom/intercom-bridge.ts +3 -2
- package/src/intercom/result-intercom.ts +5 -1
- package/src/missions/actions.ts +372 -0
- package/src/missions/lifecycle.ts +314 -0
- package/src/missions/store.ts +442 -0
- package/src/missions/types.ts +135 -0
- package/src/policy/authority.ts +46 -0
- package/src/profiles/profiles.ts +29 -3
- package/src/runs/background/async-execution.ts +98 -49
- package/src/runs/background/async-job-tracker.ts +10 -2
- package/src/runs/background/async-resume.ts +6 -6
- package/src/runs/background/async-status.ts +29 -1
- package/src/runs/background/auto-drain.ts +3 -3
- package/src/runs/background/chain-append.ts +3 -2
- package/src/runs/background/control-channel.ts +9 -7
- package/src/runs/background/fleet-view.ts +3 -4
- package/src/runs/background/notify.ts +2 -1
- package/src/runs/background/process-terminal.ts +5 -5
- package/src/runs/background/result-watcher.ts +13 -5
- package/src/runs/background/run-id-resolver.ts +3 -3
- package/src/runs/background/run-status.ts +35 -8
- package/src/runs/background/scheduled-runs.ts +602 -375
- package/src/runs/background/stale-run-reconciler.ts +3 -3
- package/src/runs/background/subagent-runner.ts +608 -445
- package/src/runs/background/subagent-wait.ts +50 -9
- package/src/runs/background/wait-subscriptions.ts +253 -0
- package/src/runs/background/wait-tool.ts +12 -4
- package/src/runs/foreground/async-steering-action.ts +3 -3
- package/src/runs/foreground/chain-clarify.ts +8 -4
- package/src/runs/foreground/chain-execution.ts +56 -30
- package/src/runs/foreground/execution.ts +15 -2
- package/src/runs/foreground/subagent-executor.ts +1023 -273
- package/src/runs/shared/acceptance.ts +28 -6
- package/src/runs/shared/child-protocol.ts +302 -22
- package/src/runs/shared/dynamic-fanout.ts +1 -1
- package/src/runs/shared/external-cli-runner.ts +130 -0
- package/src/runs/shared/long-running-guard.ts +42 -1
- package/src/runs/shared/nested-events.ts +59 -5
- package/src/runs/shared/nested-render.ts +9 -4
- package/src/runs/shared/parallel-handoff.ts +86 -2
- package/src/runs/shared/parallel-utils.ts +11 -2
- package/src/runs/shared/permissions.ts +95 -0
- package/src/runs/shared/pi-args.ts +11 -1
- package/src/runs/shared/pi-spawn.ts +11 -1
- package/src/runs/shared/run-history.ts +1 -1
- package/src/runs/shared/subagent-prompt-runtime.ts +36 -5
- package/src/runs/shared/subagent-startup-retry.ts +5 -2
- package/src/runs/shared/turn-budget.ts +6 -6
- package/src/runs/shared/worktree.ts +122 -12
- package/src/shared/accessible-dir.ts +29 -7
- package/src/shared/artifacts.ts +18 -1
- package/src/shared/fork-context.ts +3 -2
- package/src/shared/launch-contract.ts +1 -0
- package/src/shared/settings.ts +10 -0
- package/src/shared/types.ts +156 -14
- package/src/shared/utils.ts +8 -6
- package/src/slash/delegation-adapters.ts +32 -194
- package/src/slash/delegation-request.ts +43 -126
- package/src/slash/prompt-template-bridge.ts +158 -205
- package/src/slash/prompt-workflows.ts +21 -57
- package/src/slash/slash-bridge.ts +14 -0
- package/src/slash/slash-commands.ts +31 -632
- package/src/slash/subagents-admin.ts +18 -14
- package/src/tui/fleet-status.ts +156 -21
- package/src/tui/fleet-transcript.ts +110 -5
- package/src/tui/fleet.ts +56 -24
- package/src/tui/render.ts +291 -109
- package/src/types/pi-runtime-compat.d.ts +14 -0
- package/src/watchdog/lsp-diagnostics.ts +12 -7
- package/src/watchdog/model-selection.ts +2 -2
- package/src/watchdog/permission-arbiter.ts +145 -0
- package/src/watchdog/register-child.ts +1 -1
- package/src/watchdog/register-main.ts +1 -1
- package/src/watchdog/review.ts +4 -1
- package/src/watchdog/runtime.ts +3 -2
- package/src/workflows/chat-progress.ts +140 -0
- package/src/workflows/scripted-workflow.ts +415 -0
- package/agents/advisor.md +0 -73
- package/src/extension/chain-validation.ts +0 -181
|
@@ -40,11 +40,11 @@ import {
|
|
|
40
40
|
type ResolvedControlConfig,
|
|
41
41
|
type ResolvedTurnBudget,
|
|
42
42
|
type ResolvedToolBudget,
|
|
43
|
+
type ToolBudgetConfig,
|
|
43
44
|
type SubagentRunMode,
|
|
44
45
|
type SteeringRecoveryDescriptor,
|
|
45
46
|
type UsageBudgetConfig,
|
|
46
|
-
|
|
47
|
-
RESULTS_DIR,
|
|
47
|
+
DIRS,
|
|
48
48
|
SUBAGENT_ASYNC_STARTED_EVENT,
|
|
49
49
|
SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
|
|
50
50
|
TEMP_ROOT_DIR,
|
|
@@ -61,6 +61,7 @@ import { finalizeProcessTerminal, readProcessTerminal } from "./process-terminal
|
|
|
61
61
|
import { SUBAGENT_PROCESS_TERMINAL_EVENT } from "../../shared/types.ts";
|
|
62
62
|
import { assertAgentAllowedByCapabilityCeiling, decodeSubagentCapabilityCeiling, intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling, SUBAGENT_CAPABILITY_CEILING_ENV, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
|
|
63
63
|
import { agentDefinitionDigest, launchBindingDigest } from "../../shared/launch-contract.ts";
|
|
64
|
+
import { resolvePermissionRules, type PermissionConfig } from "../shared/permissions.ts";
|
|
64
65
|
|
|
65
66
|
const require = createRequire(import.meta.url);
|
|
66
67
|
const piPackageRoot = resolvePiPackageRoot();
|
|
@@ -117,6 +118,7 @@ interface AsyncExecutionContext {
|
|
|
117
118
|
currentSessionId: string;
|
|
118
119
|
/** Parent session id used by permission-system ask forwarding. */
|
|
119
120
|
parentSessionId?: string;
|
|
121
|
+
permissions?: PermissionConfig;
|
|
120
122
|
currentModelProvider?: string;
|
|
121
123
|
currentModel?: ParentModel;
|
|
122
124
|
/** Optional model-scope enforcement resolved from subagent settings. */
|
|
@@ -166,6 +168,8 @@ interface AsyncChainParams {
|
|
|
166
168
|
/** Global cap on simultaneously-running subagent tasks within the async run. */
|
|
167
169
|
globalConcurrencyLimit?: number;
|
|
168
170
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
171
|
+
parentWorkflowRunId?: string;
|
|
172
|
+
workflowKey?: string;
|
|
169
173
|
}
|
|
170
174
|
|
|
171
175
|
interface AsyncSingleParams {
|
|
@@ -206,11 +210,14 @@ interface AsyncSingleParams {
|
|
|
206
210
|
acceptance?: AcceptanceInput;
|
|
207
211
|
timeoutMs?: number;
|
|
208
212
|
absoluteDeadlineAt?: number;
|
|
209
|
-
turnBudget?:
|
|
210
|
-
toolBudget?: ResolvedToolBudget;
|
|
213
|
+
turnBudget?: { maxTurns: number; graceTurns?: number };
|
|
214
|
+
toolBudget?: ResolvedToolBudget | ToolBudgetConfig;
|
|
211
215
|
usageBudget?: UsageBudgetConfig;
|
|
212
216
|
configToolBudget?: ResolvedToolBudget;
|
|
217
|
+
allowZeroToolBudget?: boolean;
|
|
213
218
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
219
|
+
parentWorkflowRunId?: string;
|
|
220
|
+
workflowKey?: string;
|
|
214
221
|
}
|
|
215
222
|
|
|
216
223
|
interface AsyncExecutionResult {
|
|
@@ -373,18 +380,12 @@ function waitForRunnerStartup(startupPath: string, expectedState: RunnerStartupS
|
|
|
373
380
|
}
|
|
374
381
|
|
|
375
382
|
function writeRunnerStartupControl(filePath: string, payload: { action: "ack" | "proceed"; token: string }): void {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
fs.rmSync(tempPath, { force: true });
|
|
383
|
-
} catch {
|
|
384
|
-
// Preserve the startup-control write error.
|
|
385
|
-
}
|
|
386
|
-
throw error;
|
|
387
|
-
}
|
|
383
|
+
// Delegate to the shared atomic JSON writer (temp file + rename, retrying
|
|
384
|
+
// transient Windows EPERM/EBUSY/EACCES locks and cleaning up the temp file
|
|
385
|
+
// on failure), so the startup handshake gets the same locking resilience as
|
|
386
|
+
// every other async control/result file. This is exercised by
|
|
387
|
+
// test/unit/atomic-json.test.ts.
|
|
388
|
+
writePrivateAtomicJson(filePath, payload);
|
|
388
389
|
}
|
|
389
390
|
|
|
390
391
|
function runnerIsAlive(pid: number): boolean {
|
|
@@ -430,9 +431,10 @@ function spawnRunner(cfg: object, suffix: string, cwd: string, onProcessTerminal
|
|
|
430
431
|
fs.writeFileSync(cfgPath, JSON.stringify(launchConfig));
|
|
431
432
|
const runner = path.join(path.dirname(fileURLToPath(import.meta.url)), "subagent-runner.ts");
|
|
432
433
|
const nodeCommand = resolveAsyncRunnerNodeCommand();
|
|
433
|
-
const
|
|
434
|
-
|
|
435
|
-
|
|
434
|
+
const launchForStartup = launchConfig as typeof launchConfig & { asyncDir?: unknown; revivalLease?: unknown };
|
|
435
|
+
const launchAsyncDir = typeof launchForStartup.asyncDir === "string" ? launchForStartup.asyncDir : undefined;
|
|
436
|
+
const startupPath = typeof launchForStartup.revivalLease === "object" && launchAsyncDir
|
|
437
|
+
? path.join(launchAsyncDir, "runner-startup.json")
|
|
436
438
|
: undefined;
|
|
437
439
|
const startupAckPath = startupPath ? path.join(path.dirname(startupPath), "runner-startup-ack.json") : undefined;
|
|
438
440
|
const startupProceedPath = startupPath ? path.join(path.dirname(startupPath), "runner-startup-proceed.json") : undefined;
|
|
@@ -520,7 +522,7 @@ function spawnRunner(cfg: object, suffix: string, cwd: string, onProcessTerminal
|
|
|
520
522
|
proc.unref();
|
|
521
523
|
if (startupPath && startupAckPath && startupProceedPath) {
|
|
522
524
|
const ready = waitForRunnerStartup(startupPath, "ready", RUNNER_STARTUP_TIMEOUT_MS);
|
|
523
|
-
if (
|
|
525
|
+
if (ready.ok === false) {
|
|
524
526
|
terminateRunnerBeforeProceed(proc.pid);
|
|
525
527
|
return { error: ready.error };
|
|
526
528
|
}
|
|
@@ -531,7 +533,7 @@ function spawnRunner(cfg: object, suffix: string, cwd: string, onProcessTerminal
|
|
|
531
533
|
return { error: `Failed to acknowledge async runner startup: ${error instanceof Error ? error.message : String(error)}` };
|
|
532
534
|
}
|
|
533
535
|
const acknowledged = waitForRunnerStartup(startupPath, "acknowledged", RUNNER_STARTUP_TIMEOUT_MS, ready.token);
|
|
534
|
-
if (
|
|
536
|
+
if (acknowledged.ok === false) {
|
|
535
537
|
terminateRunnerBeforeProceed(proc.pid);
|
|
536
538
|
return { error: acknowledged.error };
|
|
537
539
|
}
|
|
@@ -643,6 +645,16 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
643
645
|
};
|
|
644
646
|
const buildSeqStep = (s: SequentialStep, sessionFile?: string, behaviorCwd?: string, progressPrecreated = false, resolvedBehavior?: ResolvedStepBehavior, flatIndex?: number, parallelOutputNamespace?: { stepIndex: number; taskIndex?: number }) => {
|
|
645
647
|
const a = agents.find((x) => x.name === s.agent)!;
|
|
648
|
+
const externalRunner = a.runner?.type === "external-cli";
|
|
649
|
+
if (externalRunner) {
|
|
650
|
+
const unsupported: string[] = [];
|
|
651
|
+
if (s.model !== undefined) unsupported.push("model override");
|
|
652
|
+
if (s.outputSchema !== undefined) unsupported.push("structured output");
|
|
653
|
+
if (s.acceptance !== undefined || params.agentContract !== undefined || s.agentContract !== undefined) unsupported.push("acceptance/agent contract");
|
|
654
|
+
if (s.toolBudget !== undefined || params.toolBudget !== undefined || a.toolBudget !== undefined || params.configToolBudget !== undefined) unsupported.push("tool budget");
|
|
655
|
+
if (params.contextForAgent?.(s.agent) === "fork") unsupported.push("fork context");
|
|
656
|
+
if (unsupported.length > 0) throw new AsyncStartValidationError(`Agent '${a.name}' uses runner.type='external-cli' and does not support: ${unsupported.join(", ")}.`);
|
|
657
|
+
}
|
|
646
658
|
try {
|
|
647
659
|
assertAgentAllowedByCapabilityCeiling(a.name, intersectSubagentCapabilityCeilings(params.capabilityCeiling, decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV])));
|
|
648
660
|
} catch (error) {
|
|
@@ -658,7 +670,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
658
670
|
if (inheritedRelativeParallelOutput && parallelOutputNamespace.taskIndex !== undefined) {
|
|
659
671
|
behavior = {
|
|
660
672
|
...behavior,
|
|
661
|
-
output: path.join(`parallel-${parallelOutputNamespace.stepIndex}`, `${parallelOutputNamespace.taskIndex}-${s.agent}`, behavior.output),
|
|
673
|
+
output: path.join(`parallel-${parallelOutputNamespace.stepIndex}`, `${parallelOutputNamespace.taskIndex}-${s.agent}`, behavior.output as string),
|
|
662
674
|
};
|
|
663
675
|
}
|
|
664
676
|
const namespaceOutputPath = Boolean(inheritedRelativeParallelOutput && parallelOutputNamespace.taskIndex === undefined);
|
|
@@ -696,7 +708,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
696
708
|
const taskText = `${readInstructions.prefix}${taskTemplate}${progressInstructions.suffix}`;
|
|
697
709
|
const task = namespaceOutputPath ? taskText : injectSingleOutputInstruction(taskText, outputPath, a);
|
|
698
710
|
|
|
699
|
-
const primaryModel = resolveEffectiveSubagentModel(
|
|
711
|
+
const primaryModel = externalRunner ? undefined : resolveEffectiveSubagentModel(
|
|
700
712
|
s.model,
|
|
701
713
|
a.model,
|
|
702
714
|
ctx.currentModel,
|
|
@@ -705,8 +717,8 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
705
717
|
{ scope: ctx.modelScope },
|
|
706
718
|
);
|
|
707
719
|
const thinkingOverride = flatIndex === undefined ? undefined : thinkingOverridesByFlatIndex?.[flatIndex];
|
|
708
|
-
const effectiveThinking = thinkingOverride ?? a.thinking;
|
|
709
|
-
const model = applyThinkingSuffix(primaryModel, effectiveThinking, thinkingOverride !== undefined);
|
|
720
|
+
const effectiveThinking = externalRunner ? undefined : thinkingOverride ?? a.thinking;
|
|
721
|
+
const model = externalRunner ? undefined : applyThinkingSuffix(primaryModel, effectiveThinking, thinkingOverride !== undefined);
|
|
710
722
|
const agentContract = s.agentContract ?? params.agentContract;
|
|
711
723
|
const toolPlan = resolvePiLaunchToolPlan({
|
|
712
724
|
tools: a.tools,
|
|
@@ -720,12 +732,18 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
720
732
|
inheritedCapabilityCeiling: decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]),
|
|
721
733
|
agentName: a.name,
|
|
722
734
|
});
|
|
723
|
-
const launchResolvedExtensions = projectLaunchResolvedChildExtensions(toolPlan);
|
|
735
|
+
const launchResolvedExtensions = externalRunner ? undefined : projectLaunchResolvedChildExtensions(toolPlan);
|
|
736
|
+
const permissionRules = resolvePermissionRules(ctx.permissions, a.permissions);
|
|
737
|
+
if (externalRunner && permissionRules) {
|
|
738
|
+
throw new AsyncStartValidationError(`Agent '${a.name}' uses runner.type='external-cli', which cannot enforce native Pi child permission rules.`);
|
|
739
|
+
}
|
|
724
740
|
return {
|
|
725
741
|
parentSessionId: ctx.parentSessionId ?? ctx.currentSessionId,
|
|
742
|
+
permissionRules,
|
|
726
743
|
...(params.capabilityCeiling ? { capabilityCeiling: params.capabilityCeiling } : {}),
|
|
727
744
|
agent: s.agent,
|
|
728
745
|
task,
|
|
746
|
+
...(a.runner ? { runner: a.runner } : {}),
|
|
729
747
|
...(params.contextForAgent ? { context: params.contextForAgent(s.agent) } : {}),
|
|
730
748
|
...(agentContract ? { agentContract } : {}),
|
|
731
749
|
phase: s.phase,
|
|
@@ -736,7 +754,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
736
754
|
model,
|
|
737
755
|
thinking: resolveEffectiveThinking(model, effectiveThinking),
|
|
738
756
|
launchResolvedExtensions,
|
|
739
|
-
modelCandidates: buildModelCandidates(primaryModel, a.fallbackModels, availableModels, ctx.currentModelProvider, { scope: ctx.modelScope }).map((candidate) =>
|
|
757
|
+
modelCandidates: externalRunner ? undefined : buildModelCandidates(primaryModel, a.fallbackModels, availableModels, ctx.currentModelProvider, { scope: ctx.modelScope }).map((candidate) =>
|
|
740
758
|
applyThinkingSuffix(candidate, effectiveThinking, thinkingOverride !== undefined),
|
|
741
759
|
),
|
|
742
760
|
tools: a.tools,
|
|
@@ -889,7 +907,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
889
907
|
seen.set(task.outputPath, { index, agent: task.agent });
|
|
890
908
|
}
|
|
891
909
|
}
|
|
892
|
-
return { steps, runnerCwd, workflowGraph, eventChain: graphChain, ...(originalTask !== undefined ? { originalTask } : {}) };
|
|
910
|
+
return { steps: steps as RunnerStep[], runnerCwd, workflowGraph, eventChain: graphChain, ...(originalTask !== undefined ? { originalTask } : {}) };
|
|
893
911
|
} catch (error) {
|
|
894
912
|
if (error instanceof UnavailableSubagentSkillError || error instanceof AsyncStartValidationError) return { error: error.message };
|
|
895
913
|
throw error;
|
|
@@ -930,7 +948,7 @@ export function executeAsyncChain(
|
|
|
930
948
|
const nestedAddress = inheritedNestedRoute ? resolveNestedParentAddressFromEnv() : undefined;
|
|
931
949
|
const asyncDir = inheritedNestedRoute
|
|
932
950
|
? path.join(TEMP_ROOT_DIR, "nested-subagent-runs", inheritedNestedRoute.rootRunId, id)
|
|
933
|
-
: path.join(
|
|
951
|
+
: path.join(DIRS.async, id);
|
|
934
952
|
try {
|
|
935
953
|
fs.mkdirSync(asyncDir, { recursive: true });
|
|
936
954
|
} catch (error) {
|
|
@@ -981,7 +999,7 @@ export function executeAsyncChain(
|
|
|
981
999
|
const initialUsageBudget = usageBudgetState(params.usageBudget, undefined);
|
|
982
1000
|
let childTargetIndex = 0;
|
|
983
1001
|
const childIntercomTargets = childIntercomTarget ? steps.flatMap((step) => {
|
|
984
|
-
if (!("parallel" in step) && step.importAsyncRoot) {
|
|
1002
|
+
if (!("parallel" in step) && "importAsyncRoot" in step && step.importAsyncRoot) {
|
|
985
1003
|
childTargetIndex++;
|
|
986
1004
|
return [undefined];
|
|
987
1005
|
}
|
|
@@ -992,7 +1010,7 @@ export function executeAsyncChain(
|
|
|
992
1010
|
}
|
|
993
1011
|
return step.parallel.map((task) => childIntercomTarget(task.agent, childTargetIndex++));
|
|
994
1012
|
}
|
|
995
|
-
return [childIntercomTarget(step.agent, childTargetIndex++)];
|
|
1013
|
+
return "agent" in step ? [childIntercomTarget(step.agent, childTargetIndex++)] : [undefined];
|
|
996
1014
|
}) : undefined;
|
|
997
1015
|
|
|
998
1016
|
let spawnResult: { pid?: number; error?: string } = {};
|
|
@@ -1001,7 +1019,7 @@ export function executeAsyncChain(
|
|
|
1001
1019
|
{
|
|
1002
1020
|
id,
|
|
1003
1021
|
steps,
|
|
1004
|
-
resultPath: inheritedNestedRoute ? nestedResultsPath(inheritedNestedRoute.rootRunId, id) : path.join(
|
|
1022
|
+
resultPath: inheritedNestedRoute ? nestedResultsPath(inheritedNestedRoute.rootRunId, id) : path.join(DIRS.results, `${id}.json`),
|
|
1005
1023
|
cwd: runnerCwd,
|
|
1006
1024
|
placeholder: "{previous}",
|
|
1007
1025
|
maxOutput,
|
|
@@ -1029,6 +1047,8 @@ export function executeAsyncChain(
|
|
|
1029
1047
|
deadlineAt,
|
|
1030
1048
|
globalConcurrencyLimit: params.globalConcurrencyLimit,
|
|
1031
1049
|
workflowGraph,
|
|
1050
|
+
...(params.parentWorkflowRunId ? { parentWorkflowRunId: params.parentWorkflowRunId } : {}),
|
|
1051
|
+
...(params.workflowKey ? { workflowKey: params.workflowKey } : {}),
|
|
1032
1052
|
nestedRoute: nestedRoute ?? inheritedNestedRoute,
|
|
1033
1053
|
nestedSelf: inheritedNestedRoute && nestedAddress ? {
|
|
1034
1054
|
parentRunId: nestedAddress.parentRunId,
|
|
@@ -1052,6 +1072,9 @@ export function executeAsyncChain(
|
|
|
1052
1072
|
|
|
1053
1073
|
if (spawnResult.pid) {
|
|
1054
1074
|
const eventFirstStep = eventChain[0];
|
|
1075
|
+
if (!eventFirstStep) {
|
|
1076
|
+
return formatAsyncStartError(resultMode, `Failed to start async ${resultMode} '${id}': event chain has no steps`);
|
|
1077
|
+
}
|
|
1055
1078
|
const firstAgents = isParallelStep(eventFirstStep)
|
|
1056
1079
|
? eventFirstStep.parallel.map((t) => t.agent)
|
|
1057
1080
|
: isDynamicParallelStep(eventFirstStep)
|
|
@@ -1140,6 +1163,8 @@ export function executeAsyncChain(
|
|
|
1140
1163
|
...(initialTurnBudget ? { turnBudget: initialTurnBudget } : {}),
|
|
1141
1164
|
...(initialUsageBudget ? { usageBudget: initialUsageBudget } : {}),
|
|
1142
1165
|
...(capabilityCeiling ? { capabilityCeiling } : {}),
|
|
1166
|
+
...(params.parentWorkflowRunId ? { parentWorkflowRunId: params.parentWorkflowRunId } : {}),
|
|
1167
|
+
...(params.workflowKey ? { workflowKey: params.workflowKey } : {}),
|
|
1143
1168
|
nestedRoute,
|
|
1144
1169
|
});
|
|
1145
1170
|
}
|
|
@@ -1152,7 +1177,7 @@ export function executeAsyncChain(
|
|
|
1152
1177
|
|
|
1153
1178
|
return {
|
|
1154
1179
|
content: [{ type: "text", text: formatAsyncStartedMessage(`Async ${resultMode}: ${chainDesc} [${id}]`, ctx.interactive === true) }],
|
|
1155
|
-
details: { mode: resultMode, runId: id, results: [], asyncId: id, asyncDir, workflowGraph, ...(capabilityCeiling ? { capabilityCeiling } : {}), ...(params.timeoutMs !== undefined ? { timeoutMs: params.timeoutMs, deadlineAt } : {}), ...(params.turnBudget ? { turnBudget: params.turnBudget } : {}), ...(params.toolBudget ? { toolBudget: params.toolBudget } : {}), ...(initialUsageBudget ? { usageBudget: initialUsageBudget } : {}) },
|
|
1180
|
+
details: { mode: resultMode, runId: id, results: [], asyncId: id, asyncDir, workflowGraph, ...(capabilityCeiling ? { capabilityCeiling } : {}), ...(params.parentWorkflowRunId ? { parentWorkflowRunId: params.parentWorkflowRunId } : {}), ...(params.workflowKey ? { workflowKey: params.workflowKey } : {}), ...(params.timeoutMs !== undefined ? { timeoutMs: params.timeoutMs, deadlineAt } : {}), ...(params.turnBudget ? { turnBudget: params.turnBudget } : {}), ...(params.toolBudget ? { toolBudget: params.toolBudget } : {}), ...(initialUsageBudget ? { usageBudget: initialUsageBudget } : {}) },
|
|
1156
1181
|
};
|
|
1157
1182
|
}
|
|
1158
1183
|
|
|
@@ -1184,6 +1209,20 @@ export function executeAsyncSingle(
|
|
|
1184
1209
|
nestedRoute,
|
|
1185
1210
|
} = params;
|
|
1186
1211
|
const task = params.task ?? "";
|
|
1212
|
+
const externalRunner = agentConfig.runner?.type === "external-cli";
|
|
1213
|
+
const permissionRules = resolvePermissionRules(ctx.permissions, agentConfig.permissions);
|
|
1214
|
+
if (externalRunner) {
|
|
1215
|
+
const unsupported: string[] = [];
|
|
1216
|
+
if (params.modelOverride !== undefined) unsupported.push("model override");
|
|
1217
|
+
if (params.thinkingOverride !== undefined) unsupported.push("thinking override");
|
|
1218
|
+
if (params.structuredOutputSchema !== undefined) unsupported.push("structured output");
|
|
1219
|
+
if (params.acceptance !== undefined || params.agentContract !== undefined) unsupported.push("acceptance/agent contract");
|
|
1220
|
+
if (params.toolBudget !== undefined || agentConfig.toolBudget !== undefined || params.configToolBudget !== undefined) unsupported.push("tool budget");
|
|
1221
|
+
if (params.context === "fork") unsupported.push("fork context");
|
|
1222
|
+
if ((params.skills?.length ?? 0) > 0) unsupported.push("skills");
|
|
1223
|
+
if (permissionRules) unsupported.push("native Pi child permissions");
|
|
1224
|
+
if (unsupported.length > 0) return formatAsyncStartError("single", `Agent '${agentConfig.name}' uses runner.type='external-cli' and does not support: ${unsupported.join(", ")}.`);
|
|
1225
|
+
}
|
|
1187
1226
|
const capabilityCeiling = intersectSubagentCapabilityCeilings(params.capabilityCeiling ?? resolveCurrentSubagentCapabilityCeiling(ctx.currentSessionId), decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]));
|
|
1188
1227
|
try {
|
|
1189
1228
|
assertAgentAllowedByCapabilityCeiling(agentConfig.name, capabilityCeiling);
|
|
@@ -1215,7 +1254,7 @@ export function executeAsyncSingle(
|
|
|
1215
1254
|
const nestedAddress = inheritedNestedRoute ? resolveNestedParentAddressFromEnv() : undefined;
|
|
1216
1255
|
const asyncDir = inheritedNestedRoute
|
|
1217
1256
|
? path.join(TEMP_ROOT_DIR, "nested-subagent-runs", inheritedNestedRoute.rootRunId, id)
|
|
1218
|
-
: path.join(
|
|
1257
|
+
: path.join(DIRS.async, id);
|
|
1219
1258
|
try {
|
|
1220
1259
|
fs.mkdirSync(asyncDir, { recursive: true });
|
|
1221
1260
|
} catch (error) {
|
|
@@ -1234,14 +1273,14 @@ export function executeAsyncSingle(
|
|
|
1234
1273
|
const validationError = validateFileOnlyOutputMode(outputMode, outputPath, `Async single run (${agent})`);
|
|
1235
1274
|
if (validationError) return formatAsyncStartError("single", validationError);
|
|
1236
1275
|
const taskWithOutputInstruction = injectSingleOutputInstruction(task, outputPath, agentConfig);
|
|
1237
|
-
const primaryModel = resolveSubagentModelOverride(
|
|
1276
|
+
const primaryModel = externalRunner ? undefined : resolveSubagentModelOverride(
|
|
1238
1277
|
params.modelOverride ?? agentConfig.model,
|
|
1239
1278
|
ctx.currentModel,
|
|
1240
1279
|
availableModels,
|
|
1241
1280
|
ctx.currentModelProvider,
|
|
1242
1281
|
);
|
|
1243
|
-
const effectiveThinking = params.thinkingOverride ?? agentConfig.thinking;
|
|
1244
|
-
const model = applyThinkingSuffix(primaryModel, effectiveThinking, params.thinkingOverride !== undefined);
|
|
1282
|
+
const effectiveThinking = externalRunner ? undefined : params.thinkingOverride ?? agentConfig.thinking;
|
|
1283
|
+
const model = externalRunner ? undefined : applyThinkingSuffix(primaryModel, effectiveThinking, params.thinkingOverride !== undefined);
|
|
1245
1284
|
const toolBudgetInput = params.toolBudget ?? agentConfig.toolBudget ?? params.configToolBudget;
|
|
1246
1285
|
const resolvedToolBudget = validateToolBudgetConfig(toolBudgetInput, params.toolBudget ? "toolBudget" : agentConfig.toolBudget ? "agent.toolBudget" : "config.toolBudget");
|
|
1247
1286
|
if (resolvedToolBudget.error) return formatAsyncStartError("single", resolvedToolBudget.error);
|
|
@@ -1256,9 +1295,11 @@ export function executeAsyncSingle(
|
|
|
1256
1295
|
const structuredOutput = params.structuredOutputSchema
|
|
1257
1296
|
? createStructuredOutputRuntime(params.structuredOutputSchema, path.join(asyncDir, "structured-output"))
|
|
1258
1297
|
: undefined;
|
|
1259
|
-
const modelCandidates = buildModelCandidates(primaryModel, agentConfig.fallbackModels, availableModels, ctx.currentModelProvider, { scope: ctx.modelScope })
|
|
1260
|
-
|
|
1261
|
-
|
|
1298
|
+
const modelCandidates = buildModelCandidates(primaryModel, agentConfig.fallbackModels, availableModels, ctx.currentModelProvider, { scope: ctx.modelScope })
|
|
1299
|
+
.flatMap((candidate) => {
|
|
1300
|
+
const resolved = applyThinkingSuffix(candidate, effectiveThinking, params.thinkingOverride !== undefined);
|
|
1301
|
+
return resolved ? [resolved] : [];
|
|
1302
|
+
});
|
|
1262
1303
|
const effectiveSystemPrompt = appendTurnBudgetSystemPrompt(systemPrompt, params.turnBudget);
|
|
1263
1304
|
const toolPlan = resolvePiLaunchToolPlan({
|
|
1264
1305
|
tools: agentConfig.tools,
|
|
@@ -1272,7 +1313,7 @@ export function executeAsyncSingle(
|
|
|
1272
1313
|
inheritedCapabilityCeiling: decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]),
|
|
1273
1314
|
agentName: agentConfig.name,
|
|
1274
1315
|
});
|
|
1275
|
-
const launchResolvedExtensions = projectLaunchResolvedChildExtensions(toolPlan);
|
|
1316
|
+
const launchResolvedExtensions = externalRunner ? undefined : projectLaunchResolvedChildExtensions(toolPlan);
|
|
1276
1317
|
const launchContractDigest = launchBindingDigest({
|
|
1277
1318
|
definitionDigest: agentDefinitionDigest(agentConfig),
|
|
1278
1319
|
task,
|
|
@@ -1331,7 +1372,7 @@ export function executeAsyncSingle(
|
|
|
1331
1372
|
...(params.acceptance !== undefined ? { acceptance: params.acceptance } : {}),
|
|
1332
1373
|
...(controlConfig ? { controlConfig } : {}),
|
|
1333
1374
|
...(deadlineAt !== undefined ? { absoluteDeadlineAt: deadlineAt } : {}),
|
|
1334
|
-
...(
|
|
1375
|
+
...(initialTurnBudget ? { initialTurnBudget } : {}),
|
|
1335
1376
|
...(resolvedToolBudget.budget ? { initialToolBudget: resolvedToolBudget.budget } : {}),
|
|
1336
1377
|
maxSubagentDepth: resolveChildMaxSubagentDepth(maxSubagentDepth, agentConfig.maxSubagentDepth),
|
|
1337
1378
|
...(maxOutput ? { maxOutput } : {}),
|
|
@@ -1341,10 +1382,12 @@ export function executeAsyncSingle(
|
|
|
1341
1382
|
artifactConfig,
|
|
1342
1383
|
...(capabilityCeiling ? { capabilityCeiling } : {}),
|
|
1343
1384
|
};
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1385
|
+
if (!externalRunner) {
|
|
1386
|
+
try {
|
|
1387
|
+
writePrivateAtomicJson(path.join(asyncDir, "recovery-descriptor.json"), recoveryDescriptor);
|
|
1388
|
+
} catch (error) {
|
|
1389
|
+
return formatAsyncStartError("single", `Failed to persist async recovery descriptor for '${id}': ${error instanceof Error ? error.message : String(error)}`);
|
|
1390
|
+
}
|
|
1348
1391
|
}
|
|
1349
1392
|
let spawnResult: { pid?: number; error?: string } = {};
|
|
1350
1393
|
try {
|
|
@@ -1354,9 +1397,11 @@ export function executeAsyncSingle(
|
|
|
1354
1397
|
steps: [
|
|
1355
1398
|
{
|
|
1356
1399
|
parentSessionId: ctx.parentSessionId ?? ctx.currentSessionId,
|
|
1400
|
+
permissionRules,
|
|
1357
1401
|
...(capabilityCeiling ? { capabilityCeiling } : {}),
|
|
1358
1402
|
agent,
|
|
1359
1403
|
task: taskWithOutputInstruction,
|
|
1404
|
+
...(agentConfig.runner ? { runner: agentConfig.runner } : {}),
|
|
1360
1405
|
...(params.context ? { context: params.context } : {}),
|
|
1361
1406
|
cwd: runnerCwd,
|
|
1362
1407
|
model,
|
|
@@ -1374,7 +1419,7 @@ export function executeAsyncSingle(
|
|
|
1374
1419
|
skills: resolvedSkills.map((r) => r.name),
|
|
1375
1420
|
outputPath,
|
|
1376
1421
|
outputMode,
|
|
1377
|
-
sessionFile,
|
|
1422
|
+
...(!externalRunner && sessionFile ? { sessionFile } : {}),
|
|
1378
1423
|
maxSubagentDepth: resolveChildMaxSubagentDepth(maxSubagentDepth, agentConfig.maxSubagentDepth),
|
|
1379
1424
|
waitToolEnabled: params.waitToolEnabled,
|
|
1380
1425
|
...(params.agentContract ? { agentContract: params.agentContract } : {}),
|
|
@@ -1388,7 +1433,7 @@ export function executeAsyncSingle(
|
|
|
1388
1433
|
...(resolvedToolBudget.budget ? { toolBudget: resolvedToolBudget.budget } : {}),
|
|
1389
1434
|
},
|
|
1390
1435
|
],
|
|
1391
|
-
resultPath: inheritedNestedRoute ? nestedResultsPath(inheritedNestedRoute.rootRunId, id) : path.join(
|
|
1436
|
+
resultPath: inheritedNestedRoute ? nestedResultsPath(inheritedNestedRoute.rootRunId, id) : path.join(DIRS.results, `${id}.json`),
|
|
1392
1437
|
cwd: runnerCwd,
|
|
1393
1438
|
placeholder: "{previous}",
|
|
1394
1439
|
maxOutput,
|
|
@@ -1415,6 +1460,8 @@ export function executeAsyncSingle(
|
|
|
1415
1460
|
resultMode: "single",
|
|
1416
1461
|
launchContractDigest,
|
|
1417
1462
|
launchResolvedExtensions,
|
|
1463
|
+
...(params.parentWorkflowRunId ? { parentWorkflowRunId: params.parentWorkflowRunId } : {}),
|
|
1464
|
+
...(params.workflowKey ? { workflowKey: params.workflowKey } : {}),
|
|
1418
1465
|
...(params.revivalLease ? { revivalLease: params.revivalLease } : {}),
|
|
1419
1466
|
nestedRoute: nestedRoute ?? inheritedNestedRoute,
|
|
1420
1467
|
nestedSelf: inheritedNestedRoute && nestedAddress ? {
|
|
@@ -1487,6 +1534,8 @@ export function executeAsyncSingle(
|
|
|
1487
1534
|
asyncDir,
|
|
1488
1535
|
launchContractDigest,
|
|
1489
1536
|
launchResolvedExtensions,
|
|
1537
|
+
...(params.parentWorkflowRunId ? { parentWorkflowRunId: params.parentWorkflowRunId } : {}),
|
|
1538
|
+
...(params.workflowKey ? { workflowKey: params.workflowKey } : {}),
|
|
1490
1539
|
...(timeoutMs !== undefined ? { timeoutMs, deadlineAt } : {}),
|
|
1491
1540
|
...(initialTurnBudget ? { turnBudget: initialTurnBudget } : {}),
|
|
1492
1541
|
...(initialUsageBudget ? { usageBudget: initialUsageBudget } : {}),
|
|
@@ -1497,6 +1546,6 @@ export function executeAsyncSingle(
|
|
|
1497
1546
|
|
|
1498
1547
|
return {
|
|
1499
1548
|
content: [{ type: "text", text: formatAsyncStartedMessage(`Async: ${agent} [${id}]`, ctx.interactive === true) }],
|
|
1500
|
-
details: { mode: "single", runId: id, results: [], asyncId: id, asyncDir, launchContractDigest, launchResolvedExtensions, ...(capabilityCeiling ? { capabilityCeiling } : {}), ...(params.context ? { context: params.context } : {}), ...(timeoutMs !== undefined ? { timeoutMs, deadlineAt } : {}), ...(params.turnBudget ? { turnBudget: params.turnBudget } : {}), ...(params.toolBudget ? { toolBudget: params.toolBudget } : {}), ...(initialUsageBudget ? { usageBudget: initialUsageBudget } : {}) },
|
|
1549
|
+
details: { mode: "single", runId: id, results: [], asyncId: id, asyncDir, launchContractDigest, launchResolvedExtensions, ...(capabilityCeiling ? { capabilityCeiling } : {}), ...(params.context ? { context: params.context } : {}), ...(timeoutMs !== undefined ? { timeoutMs, deadlineAt } : {}), ...(params.turnBudget ? { turnBudget: params.turnBudget } : {}), ...(params.toolBudget ? { toolBudget: resolvedToolBudget.budget ?? params.toolBudget } : {}), ...(initialUsageBudget ? { usageBudget: initialUsageBudget } : {}) } as Details,
|
|
1501
1550
|
};
|
|
1502
1551
|
}
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
type SteeringNotice,
|
|
11
11
|
type SubagentState,
|
|
12
12
|
POLL_INTERVAL_MS,
|
|
13
|
-
|
|
13
|
+
DIRS,
|
|
14
14
|
SUBAGENT_CONTROL_EVENT,
|
|
15
15
|
SUBAGENT_CONTROL_INTERCOM_EVENT,
|
|
16
16
|
SUBAGENT_STEERING_NOTICE_EVENT,
|
|
@@ -54,7 +54,7 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
54
54
|
} {
|
|
55
55
|
const completionRetentionMs = options.completionRetentionMs ?? 10000;
|
|
56
56
|
const pollIntervalMs = options.pollIntervalMs ?? POLL_INTERVAL_MS;
|
|
57
|
-
const resultsDir = options.resultsDir ??
|
|
57
|
+
const resultsDir = options.resultsDir ?? DIRS.results;
|
|
58
58
|
const steeringNoticeSeen = new Map<string, number>();
|
|
59
59
|
const rerenderWidget = (ctx: ExtensionContext, jobs = Array.from(state.asyncJobs.values())) => {
|
|
60
60
|
renderWidget(ctx, options.widgetEnabled === false ? [] : jobs);
|
|
@@ -131,6 +131,9 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
131
131
|
sessionFile: run.sessionFile,
|
|
132
132
|
controlEventCursor: restoredControlEventCursor(run.asyncDir),
|
|
133
133
|
nestedChildren: run.nestedChildren,
|
|
134
|
+
parentWorkflowRunId: run.parentWorkflowRunId,
|
|
135
|
+
workflowKey: run.workflowKey,
|
|
136
|
+
workflow: run.workflow,
|
|
134
137
|
};
|
|
135
138
|
};
|
|
136
139
|
const cancelCleanup = (asyncId: string) => {
|
|
@@ -332,6 +335,9 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
332
335
|
job.toolCount = status.toolCount ?? job.toolCount;
|
|
333
336
|
job.steering = status.steering ?? job.steering;
|
|
334
337
|
job.mode = status.mode;
|
|
338
|
+
job.parentWorkflowRunId = status.parentWorkflowRunId ?? job.parentWorkflowRunId;
|
|
339
|
+
job.workflowKey = status.workflowKey ?? job.workflowKey;
|
|
340
|
+
job.workflow = status.workflow ?? job.workflow;
|
|
335
341
|
job.currentStep = status.currentStep ?? job.currentStep;
|
|
336
342
|
job.chainStepCount = status.chainStepCount ?? job.chainStepCount;
|
|
337
343
|
job.startedAt = status.startedAt ?? job.startedAt;
|
|
@@ -432,6 +438,8 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
432
438
|
timeoutMs: info.timeoutMs,
|
|
433
439
|
deadlineAt: info.deadlineAt,
|
|
434
440
|
turnBudget: info.turnBudget,
|
|
441
|
+
parentWorkflowRunId: info.parentWorkflowRunId,
|
|
442
|
+
workflowKey: info.workflowKey,
|
|
435
443
|
controlEventCursor: 0,
|
|
436
444
|
});
|
|
437
445
|
rememberFleetJob(state, state.asyncJobs.get(info.id)!);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { DIRS, type AcceptanceInput, type AsyncStatus, type SteeringRecoveryDescriptor } 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";
|
|
@@ -381,8 +381,8 @@ function validateResumeSessionFile(runId: string, sessionFile: string): string {
|
|
|
381
381
|
}
|
|
382
382
|
|
|
383
383
|
export function resolveAsyncResumeTarget(params: AsyncResumeParams, deps: AsyncResumeDeps = {}, options: AsyncResumeOptions = {}): AsyncResumeTarget {
|
|
384
|
-
const asyncDirRoot = deps.asyncDirRoot ??
|
|
385
|
-
const resultsDir = deps.resultsDir ??
|
|
384
|
+
const asyncDirRoot = deps.asyncDirRoot ?? DIRS.async;
|
|
385
|
+
const resultsDir = deps.resultsDir ?? DIRS.results;
|
|
386
386
|
const requireSessionFile = options.requireSessionFile ?? true;
|
|
387
387
|
const location = resolveAsyncRunLocation(params, asyncDirRoot, resultsDir);
|
|
388
388
|
if (!location.asyncDir && !location.resultPath) {
|
|
@@ -394,7 +394,7 @@ export function resolveAsyncResumeTarget(params: AsyncResumeParams, deps: AsyncR
|
|
|
394
394
|
: undefined;
|
|
395
395
|
const status = reconciliation?.status ?? null;
|
|
396
396
|
validateStatusForResume(status, location.asyncDir ? path.join(location.asyncDir, "status.json") : "status.json");
|
|
397
|
-
const recoveryDescriptor = readAsyncRecoveryDescriptor(location.asyncDir);
|
|
397
|
+
const recoveryDescriptor = readAsyncRecoveryDescriptor(location.asyncDir ?? undefined);
|
|
398
398
|
const result = location.resultPath ? readResultFile(location.resultPath) : undefined;
|
|
399
399
|
const runId = status?.runId ?? result?.runId ?? result?.id ?? location.resolvedId ?? (location.asyncDir ? path.basename(location.asyncDir) : "unknown");
|
|
400
400
|
if (options.sessionId && ((status && status.sessionId !== options.sessionId) || (result && result.sessionId !== options.sessionId))) {
|
|
@@ -508,13 +508,13 @@ export function applySteeringRecoveryAgentConfig(agentConfig: AgentConfig, descr
|
|
|
508
508
|
extensions: descriptor.extensions ? [...descriptor.extensions] : undefined,
|
|
509
509
|
subagentOnlyExtensions: descriptor.subagentOnlyExtensions ? [...descriptor.subagentOnlyExtensions] : undefined,
|
|
510
510
|
mcpDirectTools: descriptor.mcpDirectTools ? [...descriptor.mcpDirectTools] : undefined,
|
|
511
|
-
systemPrompt: descriptor.systemPrompt,
|
|
511
|
+
systemPrompt: descriptor.systemPrompt ?? agentConfig.systemPrompt,
|
|
512
512
|
systemPromptMode: descriptor.systemPromptMode,
|
|
513
513
|
inheritProjectContext: descriptor.inheritProjectContext,
|
|
514
514
|
inheritSkills: descriptor.inheritSkills,
|
|
515
515
|
skills: descriptor.skills ? [...descriptor.skills] : undefined,
|
|
516
516
|
skillPath: descriptor.skillPath ? [...descriptor.skillPath] : undefined,
|
|
517
|
-
filePath: descriptor.agentFilePath,
|
|
517
|
+
filePath: descriptor.agentFilePath as string,
|
|
518
518
|
completionGuard: descriptor.completionGuard,
|
|
519
519
|
memory: descriptor.memory ? { ...descriptor.memory } : undefined,
|
|
520
520
|
output: descriptor.outputPath,
|
|
@@ -2,7 +2,7 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { formatDuration, formatModelThinking, formatTokens, shortenPath } from "../../shared/formatters.ts";
|
|
4
4
|
import { formatActivityLabel, formatParallelOutcome } from "../../shared/status-format.ts";
|
|
5
|
-
import { type ActivityState, type AsyncJobStep, type AsyncParallelGroupStatus, type AsyncStatus, type CostSummary, type LaunchResolvedChildExtensionsV1, type RuntimeAcknowledgedChildExtensionsV1, type NestedRunSummary, type SteeringStatus, type SubagentRunMode, type TokenUsage, type TurnBudgetState, type UsageBudgetState, type ChainCheckpointState } from "../../shared/types.ts";
|
|
5
|
+
import { type ActivityState, type AsyncJobStep, type AsyncParallelGroupStatus, type AsyncStatus, type CostSummary, type Details, type LaunchResolvedChildExtensionsV1, type RuntimeAcknowledgedChildExtensionsV1, type NestedRunSummary, type SteeringStatus, type SubagentRunMode, type TokenUsage, type TurnBudgetState, type UsageBudgetState, type ChainCheckpointState } from "../../shared/types.ts";
|
|
6
6
|
import type { ResolvedSubagentCapabilityCeiling, SubagentCapabilityAudit } from "../shared/capability-ceiling.ts";
|
|
7
7
|
import { readStatus } from "../../shared/utils.ts";
|
|
8
8
|
import { attachRootChildrenToSteps, buildNestedRouteIndex, type NestedRoute, projectNestedEvents } from "../shared/nested-events.ts";
|
|
@@ -108,12 +108,17 @@ export interface AsyncRunSummary {
|
|
|
108
108
|
runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensionsV1;
|
|
109
109
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
110
110
|
capabilityAudit?: SubagentCapabilityAudit;
|
|
111
|
+
parentWorkflowRunId?: string;
|
|
112
|
+
workflowKey?: string;
|
|
113
|
+
workflow?: Details["workflow"];
|
|
111
114
|
}
|
|
112
115
|
|
|
113
116
|
interface AsyncRunListOptions {
|
|
114
117
|
states?: Array<AsyncRunSummary["state"]>;
|
|
115
118
|
sessionId?: string;
|
|
116
119
|
limit?: number;
|
|
120
|
+
/** Limits status-file reads after candidates are ordered by status mtime. */
|
|
121
|
+
entryLimit?: number;
|
|
117
122
|
resultsDir?: string;
|
|
118
123
|
kill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean;
|
|
119
124
|
now?: () => number;
|
|
@@ -321,6 +326,9 @@ function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string
|
|
|
321
326
|
...(status.runtimeAcknowledgedExtensions ? { runtimeAcknowledgedExtensions: status.runtimeAcknowledgedExtensions } : {}),
|
|
322
327
|
...(status.capabilityCeiling ? { capabilityCeiling: status.capabilityCeiling } : {}),
|
|
323
328
|
...(status.capabilityAudit ? { capabilityAudit: status.capabilityAudit } : {}),
|
|
329
|
+
...(status.parentWorkflowRunId ? { parentWorkflowRunId: status.parentWorkflowRunId } : {}),
|
|
330
|
+
...(status.workflowKey ? { workflowKey: status.workflowKey } : {}),
|
|
331
|
+
...(status.workflow ? { workflow: status.workflow } : {}),
|
|
324
332
|
...(status.sessionDir ? { sessionDir: status.sessionDir } : {}),
|
|
325
333
|
...(status.outputFile ? { outputFile: status.outputFile } : {}),
|
|
326
334
|
...(status.totalTokens ? { totalTokens: status.totalTokens } : {}),
|
|
@@ -343,6 +351,7 @@ function sortRuns(runs: AsyncRunSummary[]): AsyncRunSummary[] {
|
|
|
343
351
|
case "stopped": return 2;
|
|
344
352
|
case "paused": return 2;
|
|
345
353
|
case "complete": return 3;
|
|
354
|
+
default: return 4;
|
|
346
355
|
}
|
|
347
356
|
};
|
|
348
357
|
return [...runs].sort((a, b) => {
|
|
@@ -377,6 +386,25 @@ export function listAsyncRuns(asyncDirRoot: string, options: AsyncRunListOptions
|
|
|
377
386
|
});
|
|
378
387
|
}
|
|
379
388
|
|
|
389
|
+
if (options.entryLimit !== undefined) {
|
|
390
|
+
const limit = Math.max(0, Math.floor(options.entryLimit));
|
|
391
|
+
entries = entries
|
|
392
|
+
.map((entry) => {
|
|
393
|
+
try {
|
|
394
|
+
return { entry, mtimeMs: fs.statSync(path.join(asyncDirRoot, entry, "status.json")).mtimeMs };
|
|
395
|
+
} catch (error) {
|
|
396
|
+
if (isNotFoundError(error)) return undefined;
|
|
397
|
+
throw new Error(`Failed to inspect async status file for '${entry}': ${getErrorMessage(error)}`, {
|
|
398
|
+
cause: error instanceof Error ? error : undefined,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
})
|
|
402
|
+
.filter((candidate): candidate is { entry: string; mtimeMs: number } => candidate !== undefined)
|
|
403
|
+
.sort((left, right) => right.mtimeMs - left.mtimeMs)
|
|
404
|
+
.slice(0, limit)
|
|
405
|
+
.map((candidate) => candidate.entry);
|
|
406
|
+
}
|
|
407
|
+
|
|
380
408
|
const allowedStates = options.states ? new Set(options.states) : undefined;
|
|
381
409
|
const runs: AsyncRunSummary[] = [];
|
|
382
410
|
// Route resolution for every run shares a single index built from the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
2
2
|
import { snapshotBackgroundWork } from "../../api/background-work.ts";
|
|
3
|
-
import {
|
|
3
|
+
import { DIRS, type Details, type SubagentState } from "../../shared/types.ts";
|
|
4
4
|
import { listAsyncRuns } from "./async-status.ts";
|
|
5
5
|
import { waitForSubagents, type SubagentWaitDeps, type SubagentWaitParams, type WaitEventBus } from "./subagent-wait.ts";
|
|
6
6
|
|
|
@@ -24,10 +24,10 @@ function resultText(value: AgentToolResult<Details>): string {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
function hasOutstandingWork(sessionId: string, nowMs: number): boolean {
|
|
27
|
-
const asyncRuns = listAsyncRuns(
|
|
27
|
+
const asyncRuns = listAsyncRuns(DIRS.async, {
|
|
28
28
|
states: ["queued", "running"],
|
|
29
29
|
sessionId,
|
|
30
|
-
resultsDir:
|
|
30
|
+
resultsDir: DIRS.results,
|
|
31
31
|
now: () => nowMs,
|
|
32
32
|
});
|
|
33
33
|
return asyncRuns.length > 0 || snapshotBackgroundWork(sessionId, nowMs).items.length > 0;
|
|
@@ -104,9 +104,10 @@ export function enqueueChainAppendRequest(input: {
|
|
|
104
104
|
function readAppendRequest(filePath: string): ChainAppendRequest | undefined {
|
|
105
105
|
const raw = JSON.parse(fs.readFileSync(filePath, "utf-8")) as Partial<ChainAppendRequest>;
|
|
106
106
|
if (!raw.id || typeof raw.id !== "string") return undefined;
|
|
107
|
-
|
|
107
|
+
const createdAt = raw.createdAt;
|
|
108
|
+
if (typeof createdAt !== "number" || !Number.isFinite(createdAt)) return undefined;
|
|
108
109
|
if (!Array.isArray(raw.steps) || raw.steps.length === 0) return undefined;
|
|
109
|
-
return { id: raw.id, createdAt
|
|
110
|
+
return { id: raw.id, createdAt, steps: raw.steps as RunnerStep[] };
|
|
110
111
|
}
|
|
111
112
|
|
|
112
113
|
export function readPendingChainAppendRequests(asyncDir: string): ChainAppendRequest[] {
|
|
@@ -331,19 +331,21 @@ function parseSteerCapability(raw: unknown): SteerCapability | undefined {
|
|
|
331
331
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return undefined;
|
|
332
332
|
const input = raw as Partial<SteerCapability>;
|
|
333
333
|
if (input.type !== "steer-capability" || input.protocolVersion !== 1) return undefined;
|
|
334
|
-
|
|
335
|
-
if (
|
|
336
|
-
|
|
334
|
+
const { index, pid, readyAt, supported } = input;
|
|
335
|
+
if (typeof index !== "number" || !Number.isInteger(index) || index < 0 || index > 1_000_000) return undefined;
|
|
336
|
+
if (typeof pid !== "number" || typeof readyAt !== "number" || !Number.isInteger(pid) || pid <= 0 || !Number.isFinite(readyAt) || readyAt <= 0 || typeof supported !== "boolean") return undefined;
|
|
337
|
+
return { type: "steer-capability", protocolVersion: 1, index, pid, readyAt, supported };
|
|
337
338
|
}
|
|
338
339
|
|
|
339
340
|
function parseSteerAck(raw: unknown): SteerAck | undefined {
|
|
340
341
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return undefined;
|
|
341
342
|
const input = raw as Partial<SteerAck>;
|
|
342
343
|
if (input.type !== "steer-ack" || input.protocolVersion !== 1 || typeof input.requestId !== "string" || !/^[^\s]+$/.test(input.requestId) || input.requestId.length > 256) return undefined;
|
|
343
|
-
|
|
344
|
-
if (
|
|
345
|
-
if (
|
|
346
|
-
|
|
344
|
+
const { index, ts, state, message } = input;
|
|
345
|
+
if (typeof index !== "number" || typeof ts !== "number" || !Number.isInteger(index) || index < 0 || index > 1_000_000 || !Number.isFinite(ts) || ts <= 0) return undefined;
|
|
346
|
+
if (state !== "delivered" && state !== "failed") return undefined;
|
|
347
|
+
if (typeof message !== "string" || !message.trim() || message.length > 1000) return undefined;
|
|
348
|
+
return { type: "steer-ack", protocolVersion: 1, requestId: input.requestId, index, ts, state, message: message.trim() };
|
|
347
349
|
}
|
|
348
350
|
|
|
349
351
|
export function readSteerCapability(asyncDir: string, index: number): SteerCapability | undefined {
|