pi-subagents 0.40.0 → 0.42.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 +97 -0
- package/README.md +54 -1879
- package/agents/oracle.md +1 -0
- package/inspector-runner.mjs +11 -0
- package/package.json +13 -5
- 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 +9 -7
- package/skills/pi-subagents/references/constraints-and-recipes.md +24 -32
- package/skills/pi-subagents/references/execution-controls.md +99 -97
- package/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
- package/skills/pi-subagents/references/prompting-and-roles.md +23 -30
- 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 +374 -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 +110 -50
- package/src/runs/background/async-job-tracker.ts +27 -4
- 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 +34 -9
- package/src/runs/foreground/subagent-executor.ts +1036 -274
- 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/mcp-direct-tool-allowlist.ts +2 -0
- 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 +468 -109
- 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 +143 -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 +57 -25
- package/src/tui/render.ts +368 -152
- package/src/types/pi-runtime-compat.d.ts +8 -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
|
@@ -28,7 +28,7 @@ import { resolveExpectedWorktreeAgentCwd } from "../shared/worktree.ts";
|
|
|
28
28
|
import { buildWorkflowGraphSnapshot } from "../shared/workflow-graph.ts";
|
|
29
29
|
import { ChainOutputValidationError, validateChainOutputBindings } from "../shared/chain-outputs.ts";
|
|
30
30
|
import { createStructuredOutputRuntime } from "../shared/structured-output.ts";
|
|
31
|
-
import { resolveEffectiveAcceptance } from "../shared/acceptance.ts";
|
|
31
|
+
import { resolveEffectiveAcceptance, validateAcceptanceInput, validateExecutionAcceptance } from "../shared/acceptance.ts";
|
|
32
32
|
import {
|
|
33
33
|
type AcceptanceInput,
|
|
34
34
|
type AgentContract,
|
|
@@ -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;
|
|
@@ -925,12 +943,21 @@ export function executeAsyncChain(
|
|
|
925
943
|
nestedRoute,
|
|
926
944
|
} = params;
|
|
927
945
|
const resultMode = params.resultMode ?? "chain";
|
|
946
|
+
const acceptanceErrors = validateExecutionAcceptance({
|
|
947
|
+
chain: chain.map((step) => {
|
|
948
|
+
if (isCheckpointStep(step)) return {};
|
|
949
|
+
if (isParallelStep(step)) return { parallel: step.parallel };
|
|
950
|
+
if (isDynamicParallelStep(step)) return { acceptance: step.acceptance, parallel: step.parallel };
|
|
951
|
+
return { acceptance: step.acceptance };
|
|
952
|
+
}),
|
|
953
|
+
});
|
|
954
|
+
if (acceptanceErrors.length > 0) return formatAsyncStartError(resultMode, acceptanceErrors.join(" "));
|
|
928
955
|
const capabilityCeiling = params.capabilityCeiling ?? resolveCurrentSubagentCapabilityCeiling(ctx.currentSessionId);
|
|
929
956
|
const inheritedNestedRoute = resolveInheritedNestedRouteFromEnv();
|
|
930
957
|
const nestedAddress = inheritedNestedRoute ? resolveNestedParentAddressFromEnv() : undefined;
|
|
931
958
|
const asyncDir = inheritedNestedRoute
|
|
932
959
|
? path.join(TEMP_ROOT_DIR, "nested-subagent-runs", inheritedNestedRoute.rootRunId, id)
|
|
933
|
-
: path.join(
|
|
960
|
+
: path.join(DIRS.async, id);
|
|
934
961
|
try {
|
|
935
962
|
fs.mkdirSync(asyncDir, { recursive: true });
|
|
936
963
|
} catch (error) {
|
|
@@ -981,7 +1008,7 @@ export function executeAsyncChain(
|
|
|
981
1008
|
const initialUsageBudget = usageBudgetState(params.usageBudget, undefined);
|
|
982
1009
|
let childTargetIndex = 0;
|
|
983
1010
|
const childIntercomTargets = childIntercomTarget ? steps.flatMap((step) => {
|
|
984
|
-
if (!("parallel" in step) && step.importAsyncRoot) {
|
|
1011
|
+
if (!("parallel" in step) && "importAsyncRoot" in step && step.importAsyncRoot) {
|
|
985
1012
|
childTargetIndex++;
|
|
986
1013
|
return [undefined];
|
|
987
1014
|
}
|
|
@@ -992,7 +1019,7 @@ export function executeAsyncChain(
|
|
|
992
1019
|
}
|
|
993
1020
|
return step.parallel.map((task) => childIntercomTarget(task.agent, childTargetIndex++));
|
|
994
1021
|
}
|
|
995
|
-
return [childIntercomTarget(step.agent, childTargetIndex++)];
|
|
1022
|
+
return "agent" in step ? [childIntercomTarget(step.agent, childTargetIndex++)] : [undefined];
|
|
996
1023
|
}) : undefined;
|
|
997
1024
|
|
|
998
1025
|
let spawnResult: { pid?: number; error?: string } = {};
|
|
@@ -1001,7 +1028,7 @@ export function executeAsyncChain(
|
|
|
1001
1028
|
{
|
|
1002
1029
|
id,
|
|
1003
1030
|
steps,
|
|
1004
|
-
resultPath: inheritedNestedRoute ? nestedResultsPath(inheritedNestedRoute.rootRunId, id) : path.join(
|
|
1031
|
+
resultPath: inheritedNestedRoute ? nestedResultsPath(inheritedNestedRoute.rootRunId, id) : path.join(DIRS.results, `${id}.json`),
|
|
1005
1032
|
cwd: runnerCwd,
|
|
1006
1033
|
placeholder: "{previous}",
|
|
1007
1034
|
maxOutput,
|
|
@@ -1029,6 +1056,8 @@ export function executeAsyncChain(
|
|
|
1029
1056
|
deadlineAt,
|
|
1030
1057
|
globalConcurrencyLimit: params.globalConcurrencyLimit,
|
|
1031
1058
|
workflowGraph,
|
|
1059
|
+
...(params.parentWorkflowRunId ? { parentWorkflowRunId: params.parentWorkflowRunId } : {}),
|
|
1060
|
+
...(params.workflowKey ? { workflowKey: params.workflowKey } : {}),
|
|
1032
1061
|
nestedRoute: nestedRoute ?? inheritedNestedRoute,
|
|
1033
1062
|
nestedSelf: inheritedNestedRoute && nestedAddress ? {
|
|
1034
1063
|
parentRunId: nestedAddress.parentRunId,
|
|
@@ -1052,6 +1081,9 @@ export function executeAsyncChain(
|
|
|
1052
1081
|
|
|
1053
1082
|
if (spawnResult.pid) {
|
|
1054
1083
|
const eventFirstStep = eventChain[0];
|
|
1084
|
+
if (!eventFirstStep) {
|
|
1085
|
+
return formatAsyncStartError(resultMode, `Failed to start async ${resultMode} '${id}': event chain has no steps`);
|
|
1086
|
+
}
|
|
1055
1087
|
const firstAgents = isParallelStep(eventFirstStep)
|
|
1056
1088
|
? eventFirstStep.parallel.map((t) => t.agent)
|
|
1057
1089
|
: isDynamicParallelStep(eventFirstStep)
|
|
@@ -1140,6 +1172,8 @@ export function executeAsyncChain(
|
|
|
1140
1172
|
...(initialTurnBudget ? { turnBudget: initialTurnBudget } : {}),
|
|
1141
1173
|
...(initialUsageBudget ? { usageBudget: initialUsageBudget } : {}),
|
|
1142
1174
|
...(capabilityCeiling ? { capabilityCeiling } : {}),
|
|
1175
|
+
...(params.parentWorkflowRunId ? { parentWorkflowRunId: params.parentWorkflowRunId } : {}),
|
|
1176
|
+
...(params.workflowKey ? { workflowKey: params.workflowKey } : {}),
|
|
1143
1177
|
nestedRoute,
|
|
1144
1178
|
});
|
|
1145
1179
|
}
|
|
@@ -1152,7 +1186,7 @@ export function executeAsyncChain(
|
|
|
1152
1186
|
|
|
1153
1187
|
return {
|
|
1154
1188
|
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 } : {}) },
|
|
1189
|
+
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
1190
|
};
|
|
1157
1191
|
}
|
|
1158
1192
|
|
|
@@ -1184,6 +1218,22 @@ export function executeAsyncSingle(
|
|
|
1184
1218
|
nestedRoute,
|
|
1185
1219
|
} = params;
|
|
1186
1220
|
const task = params.task ?? "";
|
|
1221
|
+
const acceptanceErrors = validateAcceptanceInput(params.acceptance);
|
|
1222
|
+
if (acceptanceErrors.length > 0) return formatAsyncStartError("single", acceptanceErrors.join(" "));
|
|
1223
|
+
const externalRunner = agentConfig.runner?.type === "external-cli";
|
|
1224
|
+
const permissionRules = resolvePermissionRules(ctx.permissions, agentConfig.permissions);
|
|
1225
|
+
if (externalRunner) {
|
|
1226
|
+
const unsupported: string[] = [];
|
|
1227
|
+
if (params.modelOverride !== undefined) unsupported.push("model override");
|
|
1228
|
+
if (params.thinkingOverride !== undefined) unsupported.push("thinking override");
|
|
1229
|
+
if (params.structuredOutputSchema !== undefined) unsupported.push("structured output");
|
|
1230
|
+
if (params.acceptance !== undefined || params.agentContract !== undefined) unsupported.push("acceptance/agent contract");
|
|
1231
|
+
if (params.toolBudget !== undefined || agentConfig.toolBudget !== undefined || params.configToolBudget !== undefined) unsupported.push("tool budget");
|
|
1232
|
+
if (params.context === "fork") unsupported.push("fork context");
|
|
1233
|
+
if ((params.skills?.length ?? 0) > 0) unsupported.push("skills");
|
|
1234
|
+
if (permissionRules) unsupported.push("native Pi child permissions");
|
|
1235
|
+
if (unsupported.length > 0) return formatAsyncStartError("single", `Agent '${agentConfig.name}' uses runner.type='external-cli' and does not support: ${unsupported.join(", ")}.`);
|
|
1236
|
+
}
|
|
1187
1237
|
const capabilityCeiling = intersectSubagentCapabilityCeilings(params.capabilityCeiling ?? resolveCurrentSubagentCapabilityCeiling(ctx.currentSessionId), decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]));
|
|
1188
1238
|
try {
|
|
1189
1239
|
assertAgentAllowedByCapabilityCeiling(agentConfig.name, capabilityCeiling);
|
|
@@ -1215,7 +1265,7 @@ export function executeAsyncSingle(
|
|
|
1215
1265
|
const nestedAddress = inheritedNestedRoute ? resolveNestedParentAddressFromEnv() : undefined;
|
|
1216
1266
|
const asyncDir = inheritedNestedRoute
|
|
1217
1267
|
? path.join(TEMP_ROOT_DIR, "nested-subagent-runs", inheritedNestedRoute.rootRunId, id)
|
|
1218
|
-
: path.join(
|
|
1268
|
+
: path.join(DIRS.async, id);
|
|
1219
1269
|
try {
|
|
1220
1270
|
fs.mkdirSync(asyncDir, { recursive: true });
|
|
1221
1271
|
} catch (error) {
|
|
@@ -1234,14 +1284,14 @@ export function executeAsyncSingle(
|
|
|
1234
1284
|
const validationError = validateFileOnlyOutputMode(outputMode, outputPath, `Async single run (${agent})`);
|
|
1235
1285
|
if (validationError) return formatAsyncStartError("single", validationError);
|
|
1236
1286
|
const taskWithOutputInstruction = injectSingleOutputInstruction(task, outputPath, agentConfig);
|
|
1237
|
-
const primaryModel = resolveSubagentModelOverride(
|
|
1287
|
+
const primaryModel = externalRunner ? undefined : resolveSubagentModelOverride(
|
|
1238
1288
|
params.modelOverride ?? agentConfig.model,
|
|
1239
1289
|
ctx.currentModel,
|
|
1240
1290
|
availableModels,
|
|
1241
1291
|
ctx.currentModelProvider,
|
|
1242
1292
|
);
|
|
1243
|
-
const effectiveThinking = params.thinkingOverride ?? agentConfig.thinking;
|
|
1244
|
-
const model = applyThinkingSuffix(primaryModel, effectiveThinking, params.thinkingOverride !== undefined);
|
|
1293
|
+
const effectiveThinking = externalRunner ? undefined : params.thinkingOverride ?? agentConfig.thinking;
|
|
1294
|
+
const model = externalRunner ? undefined : applyThinkingSuffix(primaryModel, effectiveThinking, params.thinkingOverride !== undefined);
|
|
1245
1295
|
const toolBudgetInput = params.toolBudget ?? agentConfig.toolBudget ?? params.configToolBudget;
|
|
1246
1296
|
const resolvedToolBudget = validateToolBudgetConfig(toolBudgetInput, params.toolBudget ? "toolBudget" : agentConfig.toolBudget ? "agent.toolBudget" : "config.toolBudget");
|
|
1247
1297
|
if (resolvedToolBudget.error) return formatAsyncStartError("single", resolvedToolBudget.error);
|
|
@@ -1256,9 +1306,11 @@ export function executeAsyncSingle(
|
|
|
1256
1306
|
const structuredOutput = params.structuredOutputSchema
|
|
1257
1307
|
? createStructuredOutputRuntime(params.structuredOutputSchema, path.join(asyncDir, "structured-output"))
|
|
1258
1308
|
: undefined;
|
|
1259
|
-
const modelCandidates = buildModelCandidates(primaryModel, agentConfig.fallbackModels, availableModels, ctx.currentModelProvider, { scope: ctx.modelScope })
|
|
1260
|
-
|
|
1261
|
-
|
|
1309
|
+
const modelCandidates = buildModelCandidates(primaryModel, agentConfig.fallbackModels, availableModels, ctx.currentModelProvider, { scope: ctx.modelScope })
|
|
1310
|
+
.flatMap((candidate) => {
|
|
1311
|
+
const resolved = applyThinkingSuffix(candidate, effectiveThinking, params.thinkingOverride !== undefined);
|
|
1312
|
+
return resolved ? [resolved] : [];
|
|
1313
|
+
});
|
|
1262
1314
|
const effectiveSystemPrompt = appendTurnBudgetSystemPrompt(systemPrompt, params.turnBudget);
|
|
1263
1315
|
const toolPlan = resolvePiLaunchToolPlan({
|
|
1264
1316
|
tools: agentConfig.tools,
|
|
@@ -1272,7 +1324,7 @@ export function executeAsyncSingle(
|
|
|
1272
1324
|
inheritedCapabilityCeiling: decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]),
|
|
1273
1325
|
agentName: agentConfig.name,
|
|
1274
1326
|
});
|
|
1275
|
-
const launchResolvedExtensions = projectLaunchResolvedChildExtensions(toolPlan);
|
|
1327
|
+
const launchResolvedExtensions = externalRunner ? undefined : projectLaunchResolvedChildExtensions(toolPlan);
|
|
1276
1328
|
const launchContractDigest = launchBindingDigest({
|
|
1277
1329
|
definitionDigest: agentDefinitionDigest(agentConfig),
|
|
1278
1330
|
task,
|
|
@@ -1331,7 +1383,7 @@ export function executeAsyncSingle(
|
|
|
1331
1383
|
...(params.acceptance !== undefined ? { acceptance: params.acceptance } : {}),
|
|
1332
1384
|
...(controlConfig ? { controlConfig } : {}),
|
|
1333
1385
|
...(deadlineAt !== undefined ? { absoluteDeadlineAt: deadlineAt } : {}),
|
|
1334
|
-
...(
|
|
1386
|
+
...(initialTurnBudget ? { initialTurnBudget } : {}),
|
|
1335
1387
|
...(resolvedToolBudget.budget ? { initialToolBudget: resolvedToolBudget.budget } : {}),
|
|
1336
1388
|
maxSubagentDepth: resolveChildMaxSubagentDepth(maxSubagentDepth, agentConfig.maxSubagentDepth),
|
|
1337
1389
|
...(maxOutput ? { maxOutput } : {}),
|
|
@@ -1341,10 +1393,12 @@ export function executeAsyncSingle(
|
|
|
1341
1393
|
artifactConfig,
|
|
1342
1394
|
...(capabilityCeiling ? { capabilityCeiling } : {}),
|
|
1343
1395
|
};
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1396
|
+
if (!externalRunner) {
|
|
1397
|
+
try {
|
|
1398
|
+
writePrivateAtomicJson(path.join(asyncDir, "recovery-descriptor.json"), recoveryDescriptor);
|
|
1399
|
+
} catch (error) {
|
|
1400
|
+
return formatAsyncStartError("single", `Failed to persist async recovery descriptor for '${id}': ${error instanceof Error ? error.message : String(error)}`);
|
|
1401
|
+
}
|
|
1348
1402
|
}
|
|
1349
1403
|
let spawnResult: { pid?: number; error?: string } = {};
|
|
1350
1404
|
try {
|
|
@@ -1354,9 +1408,11 @@ export function executeAsyncSingle(
|
|
|
1354
1408
|
steps: [
|
|
1355
1409
|
{
|
|
1356
1410
|
parentSessionId: ctx.parentSessionId ?? ctx.currentSessionId,
|
|
1411
|
+
permissionRules,
|
|
1357
1412
|
...(capabilityCeiling ? { capabilityCeiling } : {}),
|
|
1358
1413
|
agent,
|
|
1359
1414
|
task: taskWithOutputInstruction,
|
|
1415
|
+
...(agentConfig.runner ? { runner: agentConfig.runner } : {}),
|
|
1360
1416
|
...(params.context ? { context: params.context } : {}),
|
|
1361
1417
|
cwd: runnerCwd,
|
|
1362
1418
|
model,
|
|
@@ -1374,7 +1430,7 @@ export function executeAsyncSingle(
|
|
|
1374
1430
|
skills: resolvedSkills.map((r) => r.name),
|
|
1375
1431
|
outputPath,
|
|
1376
1432
|
outputMode,
|
|
1377
|
-
sessionFile,
|
|
1433
|
+
...(!externalRunner && sessionFile ? { sessionFile } : {}),
|
|
1378
1434
|
maxSubagentDepth: resolveChildMaxSubagentDepth(maxSubagentDepth, agentConfig.maxSubagentDepth),
|
|
1379
1435
|
waitToolEnabled: params.waitToolEnabled,
|
|
1380
1436
|
...(params.agentContract ? { agentContract: params.agentContract } : {}),
|
|
@@ -1388,7 +1444,7 @@ export function executeAsyncSingle(
|
|
|
1388
1444
|
...(resolvedToolBudget.budget ? { toolBudget: resolvedToolBudget.budget } : {}),
|
|
1389
1445
|
},
|
|
1390
1446
|
],
|
|
1391
|
-
resultPath: inheritedNestedRoute ? nestedResultsPath(inheritedNestedRoute.rootRunId, id) : path.join(
|
|
1447
|
+
resultPath: inheritedNestedRoute ? nestedResultsPath(inheritedNestedRoute.rootRunId, id) : path.join(DIRS.results, `${id}.json`),
|
|
1392
1448
|
cwd: runnerCwd,
|
|
1393
1449
|
placeholder: "{previous}",
|
|
1394
1450
|
maxOutput,
|
|
@@ -1415,6 +1471,8 @@ export function executeAsyncSingle(
|
|
|
1415
1471
|
resultMode: "single",
|
|
1416
1472
|
launchContractDigest,
|
|
1417
1473
|
launchResolvedExtensions,
|
|
1474
|
+
...(params.parentWorkflowRunId ? { parentWorkflowRunId: params.parentWorkflowRunId } : {}),
|
|
1475
|
+
...(params.workflowKey ? { workflowKey: params.workflowKey } : {}),
|
|
1418
1476
|
...(params.revivalLease ? { revivalLease: params.revivalLease } : {}),
|
|
1419
1477
|
nestedRoute: nestedRoute ?? inheritedNestedRoute,
|
|
1420
1478
|
nestedSelf: inheritedNestedRoute && nestedAddress ? {
|
|
@@ -1487,6 +1545,8 @@ export function executeAsyncSingle(
|
|
|
1487
1545
|
asyncDir,
|
|
1488
1546
|
launchContractDigest,
|
|
1489
1547
|
launchResolvedExtensions,
|
|
1548
|
+
...(params.parentWorkflowRunId ? { parentWorkflowRunId: params.parentWorkflowRunId } : {}),
|
|
1549
|
+
...(params.workflowKey ? { workflowKey: params.workflowKey } : {}),
|
|
1490
1550
|
...(timeoutMs !== undefined ? { timeoutMs, deadlineAt } : {}),
|
|
1491
1551
|
...(initialTurnBudget ? { turnBudget: initialTurnBudget } : {}),
|
|
1492
1552
|
...(initialUsageBudget ? { usageBudget: initialUsageBudget } : {}),
|
|
@@ -1497,6 +1557,6 @@ export function executeAsyncSingle(
|
|
|
1497
1557
|
|
|
1498
1558
|
return {
|
|
1499
1559
|
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 } : {}) },
|
|
1560
|
+
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
1561
|
};
|
|
1502
1562
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import { renderWidget, widgetRenderKey } from "../../tui/render.ts";
|
|
4
|
+
import { renderWidget, requestWidgetRender, widgetRenderKey } from "../../tui/render.ts";
|
|
5
5
|
import { formatControlNoticeMessage } from "../shared/subagent-control.ts";
|
|
6
6
|
import {
|
|
7
7
|
type AsyncJobState,
|
|
@@ -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,
|
|
@@ -34,6 +34,7 @@ const CONTROL_EVENT_READ_CHUNK_BYTES = 64 * 1024;
|
|
|
34
34
|
const MAX_CONTROL_EVENT_LINE_BYTES = 1024 * 1024;
|
|
35
35
|
const CONTROL_EVENT_SCAN_WINDOW_BYTES = 2 * 1024 * 1024;
|
|
36
36
|
const MAX_RECENT_FLEET_JOBS = 20;
|
|
37
|
+
const WIDGET_ANIMATION_REFRESH_MS = 500;
|
|
37
38
|
|
|
38
39
|
function rememberFleetJob(state: SubagentState, job: AsyncJobState): void {
|
|
39
40
|
state.fleetJobs ??= new Map();
|
|
@@ -54,11 +55,16 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
54
55
|
} {
|
|
55
56
|
const completionRetentionMs = options.completionRetentionMs ?? 10000;
|
|
56
57
|
const pollIntervalMs = options.pollIntervalMs ?? POLL_INTERVAL_MS;
|
|
57
|
-
const resultsDir = options.resultsDir ??
|
|
58
|
+
const resultsDir = options.resultsDir ?? DIRS.results;
|
|
58
59
|
const steeringNoticeSeen = new Map<string, number>();
|
|
60
|
+
let lastWidgetAnimationAt = 0;
|
|
61
|
+
const requestStatusRender = (ctx: ExtensionContext) => {
|
|
62
|
+
if (requestWidgetRender()) return;
|
|
63
|
+
(ctx.ui as { requestRender?: () => void }).requestRender?.();
|
|
64
|
+
};
|
|
59
65
|
const rerenderWidget = (ctx: ExtensionContext, jobs = Array.from(state.asyncJobs.values())) => {
|
|
60
66
|
renderWidget(ctx, options.widgetEnabled === false ? [] : jobs);
|
|
61
|
-
ctx
|
|
67
|
+
requestStatusRender(ctx);
|
|
62
68
|
};
|
|
63
69
|
const rerenderLastWidget = (jobs = Array.from(state.asyncJobs.values())) => {
|
|
64
70
|
const ctx = state.lastUiContext;
|
|
@@ -74,6 +80,14 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
74
80
|
}
|
|
75
81
|
};
|
|
76
82
|
const refreshWidget = (ctx: ExtensionContext) => rerenderWidget(ctx);
|
|
83
|
+
const hasRunningWidgetJobs = () => options.widgetEnabled !== false && [...state.asyncJobs.values()].some((job) => job.status === "running");
|
|
84
|
+
const refreshWidgetAnimation = () => {
|
|
85
|
+
if (!hasRunningWidgetJobs()) return;
|
|
86
|
+
const now = Date.now();
|
|
87
|
+
if (now - lastWidgetAnimationAt < WIDGET_ANIMATION_REFRESH_MS) return;
|
|
88
|
+
lastWidgetAnimationAt = now;
|
|
89
|
+
requestWidgetRender();
|
|
90
|
+
};
|
|
77
91
|
const restoredControlEventCursor = (asyncDir: string) => {
|
|
78
92
|
try {
|
|
79
93
|
return fs.statSync(path.join(asyncDir, "events.jsonl")).size;
|
|
@@ -131,6 +145,9 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
131
145
|
sessionFile: run.sessionFile,
|
|
132
146
|
controlEventCursor: restoredControlEventCursor(run.asyncDir),
|
|
133
147
|
nestedChildren: run.nestedChildren,
|
|
148
|
+
parentWorkflowRunId: run.parentWorkflowRunId,
|
|
149
|
+
workflowKey: run.workflowKey,
|
|
150
|
+
workflow: run.workflow,
|
|
134
151
|
};
|
|
135
152
|
};
|
|
136
153
|
const cancelCleanup = (asyncId: string) => {
|
|
@@ -332,6 +349,9 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
332
349
|
job.toolCount = status.toolCount ?? job.toolCount;
|
|
333
350
|
job.steering = status.steering ?? job.steering;
|
|
334
351
|
job.mode = status.mode;
|
|
352
|
+
job.parentWorkflowRunId = status.parentWorkflowRunId ?? job.parentWorkflowRunId;
|
|
353
|
+
job.workflowKey = status.workflowKey ?? job.workflowKey;
|
|
354
|
+
job.workflow = status.workflow ?? job.workflow;
|
|
335
355
|
job.currentStep = status.currentStep ?? job.currentStep;
|
|
336
356
|
job.chainStepCount = status.chainStepCount ?? job.chainStepCount;
|
|
337
357
|
job.startedAt = status.startedAt ?? job.startedAt;
|
|
@@ -394,6 +414,7 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
394
414
|
}
|
|
395
415
|
|
|
396
416
|
if (widgetChanged) rerenderLastWidget();
|
|
417
|
+
else refreshWidgetAnimation();
|
|
397
418
|
}, pollIntervalMs);
|
|
398
419
|
state.poller.unref?.();
|
|
399
420
|
};
|
|
@@ -432,6 +453,8 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
432
453
|
timeoutMs: info.timeoutMs,
|
|
433
454
|
deadlineAt: info.deadlineAt,
|
|
434
455
|
turnBudget: info.turnBudget,
|
|
456
|
+
parentWorkflowRunId: info.parentWorkflowRunId,
|
|
457
|
+
workflowKey: info.workflowKey,
|
|
435
458
|
controlEventCursor: 0,
|
|
436
459
|
});
|
|
437
460
|
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
|