pi-subagents 0.36.0 → 0.37.1
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 +38 -0
- package/README.md +111 -16
- package/agents/delegate.md +2 -0
- package/agents/planner.md +2 -1
- package/agents/worker.md +2 -0
- package/package.json +6 -4
- package/skills/pi-subagents/SKILL.md +18 -973
- package/skills/pi-subagents/references/constraints-and-recipes.md +256 -0
- package/skills/pi-subagents/references/execution-controls.md +411 -0
- package/skills/pi-subagents/references/management-authoring-rpc.md +140 -0
- package/skills/pi-subagents/references/prompting-and-roles.md +268 -0
- package/src/agents/agent-management.ts +7 -1
- package/src/agents/agents.ts +99 -12
- package/src/api/capability-ceiling.ts +17 -0
- package/src/api/delegation.ts +6 -1
- package/src/api/preflight.ts +399 -0
- package/src/extension/index.ts +16 -7
- package/src/extension/rpc.ts +29 -2
- package/src/extension/schemas.ts +10 -4
- package/src/extension/tool-description.ts +4 -2
- package/src/intercom/intercom-bridge.ts +5 -2
- package/src/runs/background/async-execution.ts +125 -14
- package/src/runs/background/async-job-tracker.ts +19 -12
- package/src/runs/background/async-resume.ts +27 -9
- package/src/runs/background/async-status.ts +18 -0
- package/src/runs/background/notify.ts +3 -0
- package/src/runs/background/process-terminal.ts +280 -0
- package/src/runs/background/result-watcher.ts +9 -3
- package/src/runs/background/run-status.ts +7 -1
- package/src/runs/background/scheduled-runs.ts +6 -1
- package/src/runs/background/stale-run-reconciler.ts +6 -0
- package/src/runs/background/subagent-runner.ts +182 -12
- package/src/runs/foreground/chain-execution.ts +5 -0
- package/src/runs/foreground/execution.ts +59 -13
- package/src/runs/foreground/subagent-executor.ts +142 -47
- package/src/runs/shared/acceptance.ts +41 -30
- package/src/runs/shared/capability-ceiling.ts +177 -0
- package/src/runs/shared/dynamic-fanout.ts +1 -1
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +56 -17
- package/src/runs/shared/model-fallback.ts +8 -0
- package/src/runs/shared/nested-events.ts +8 -1
- package/src/runs/shared/parallel-utils.ts +5 -0
- package/src/runs/shared/pi-args.ts +141 -58
- package/src/runs/shared/session-lease.ts +25 -5
- package/src/runs/shared/subagent-prompt-runtime.ts +14 -1
- package/src/runs/shared/task-intent.ts +1 -1
- package/src/runs/shared/tool-availability.ts +18 -2
- package/src/shared/launch-contract.ts +123 -0
- package/src/shared/types.ts +118 -8
- package/src/shared/utils.ts +17 -42
- package/src/slash/delegation-adapters.ts +6 -1
- package/src/slash/slash-commands.ts +1 -1
- package/src/tui/fleet-status.ts +62 -16
- package/src/tui/fleet.ts +1 -1
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
|
+
import { randomUUID } from "node:crypto";
|
|
6
7
|
import * as fs from "node:fs";
|
|
7
8
|
import * as os from "node:os";
|
|
8
9
|
import * as path from "node:path";
|
|
@@ -11,7 +12,7 @@ import { createRequire } from "node:module";
|
|
|
11
12
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
12
13
|
import type { AgentConfig } from "../../agents/agents.ts";
|
|
13
14
|
import { writePrivateAtomicJson } from "../../shared/atomic-json.ts";
|
|
14
|
-
import { applyThinkingSuffix } from "../shared/pi-args.ts";
|
|
15
|
+
import { applyThinkingSuffix, resolvePiLaunchToolPlan } from "../shared/pi-args.ts";
|
|
15
16
|
import { injectOutputPathSystemPrompt, injectSingleOutputInstruction, normalizeSingleOutputOverride, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
|
|
16
17
|
import { buildChainInstructions, isDynamicParallelStep, isParallelStep, resolveStepBehavior, suppressProgressForReadOnlyTask, writeInitialProgressFile, type ChainStep, type ResolvedStepBehavior, type SequentialStep, type StepOverrides } from "../../shared/settings.ts";
|
|
17
18
|
import type { RunnerStep } from "../shared/parallel-utils.ts";
|
|
@@ -49,11 +50,15 @@ import {
|
|
|
49
50
|
getAsyncConfigPath,
|
|
50
51
|
resolveChildMaxSubagentDepth,
|
|
51
52
|
} from "../../shared/types.ts";
|
|
52
|
-
import { nestedResultsPath, resolveInheritedNestedRouteFromEnv, resolveNestedParentAddressFromEnv, writeNestedEvent } from "../shared/nested-events.ts";
|
|
53
|
-
import { initialTurnBudgetState } from "../shared/turn-budget.ts";
|
|
53
|
+
import { nestedResultsPath, nestedSummaryFromAsyncStatus, resolveInheritedNestedRouteFromEnv, resolveNestedParentAddressFromEnv, writeNestedEvent } from "../shared/nested-events.ts";
|
|
54
|
+
import { appendTurnBudgetSystemPrompt, initialTurnBudgetState } from "../shared/turn-budget.ts";
|
|
54
55
|
import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
|
|
55
56
|
import type { ImportedAsyncRoot } from "./chain-root-attachment.ts";
|
|
56
57
|
import type { SessionLeaseRequest } from "../shared/session-lease.ts";
|
|
58
|
+
import { finalizeProcessTerminal, readProcessTerminal } from "./process-terminal.ts";
|
|
59
|
+
import { SUBAGENT_PROCESS_TERMINAL_EVENT } from "../../shared/types.ts";
|
|
60
|
+
import { decodeSubagentCapabilityCeiling, resolveCurrentSubagentCapabilityCeiling, SUBAGENT_CAPABILITY_CEILING_ENV, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
|
|
61
|
+
import { agentDefinitionDigest, launchBindingDigest } from "../../shared/launch-contract.ts";
|
|
57
62
|
|
|
58
63
|
const require = createRequire(import.meta.url);
|
|
59
64
|
const piPackageRoot = resolvePiPackageRoot();
|
|
@@ -157,6 +162,7 @@ interface AsyncChainParams {
|
|
|
157
162
|
configToolBudget?: ResolvedToolBudget;
|
|
158
163
|
/** Global cap on simultaneously-running subagent tasks within the async run. */
|
|
159
164
|
globalConcurrencyLimit?: number;
|
|
165
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
160
166
|
}
|
|
161
167
|
|
|
162
168
|
interface AsyncSingleParams {
|
|
@@ -200,6 +206,7 @@ interface AsyncSingleParams {
|
|
|
200
206
|
turnBudget?: ResolvedTurnBudget;
|
|
201
207
|
toolBudget?: ResolvedToolBudget;
|
|
202
208
|
configToolBudget?: ResolvedToolBudget;
|
|
209
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
203
210
|
}
|
|
204
211
|
|
|
205
212
|
interface AsyncExecutionResult {
|
|
@@ -232,6 +239,7 @@ export interface AsyncRunnerStepBuildParams {
|
|
|
232
239
|
validateOutputBindings?: boolean;
|
|
233
240
|
toolBudget?: ResolvedToolBudget;
|
|
234
241
|
configToolBudget?: ResolvedToolBudget;
|
|
242
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
235
243
|
}
|
|
236
244
|
|
|
237
245
|
export type AsyncRunnerStepBuildResult =
|
|
@@ -397,7 +405,7 @@ function terminateRunnerBeforeProceed(pid: number): void {
|
|
|
397
405
|
}
|
|
398
406
|
}
|
|
399
407
|
|
|
400
|
-
function spawnRunner(cfg: object, suffix: string, cwd: string): { pid?: number; error?: string } {
|
|
408
|
+
function spawnRunner(cfg: object, suffix: string, cwd: string, onProcessTerminal?: (proof: unknown) => void): { pid?: number; error?: string } {
|
|
401
409
|
if (!jitiCliPath) {
|
|
402
410
|
return { error: "upstream jiti for TypeScript execution could not be found; ensure package dependencies are installed" };
|
|
403
411
|
}
|
|
@@ -413,12 +421,14 @@ function spawnRunner(cfg: object, suffix: string, cwd: string): { pid?: number;
|
|
|
413
421
|
|
|
414
422
|
fs.mkdirSync(TEMP_ROOT_DIR, { recursive: true });
|
|
415
423
|
const cfgPath = getAsyncConfigPath(suffix);
|
|
416
|
-
|
|
424
|
+
const runnerProcessInstanceId = randomUUID();
|
|
425
|
+
const launchConfig = { ...cfg, runnerProcessInstanceId };
|
|
426
|
+
fs.writeFileSync(cfgPath, JSON.stringify(launchConfig));
|
|
417
427
|
const runner = path.join(path.dirname(fileURLToPath(import.meta.url)), "subagent-runner.ts");
|
|
418
428
|
const nodeCommand = resolveAsyncRunnerNodeCommand();
|
|
419
|
-
const startupPath = typeof (
|
|
420
|
-
&& typeof (
|
|
421
|
-
? path.join((
|
|
429
|
+
const startupPath = typeof (launchConfig as { revivalLease?: unknown; asyncDir?: unknown }).revivalLease === "object"
|
|
430
|
+
&& typeof (launchConfig as { asyncDir?: unknown }).asyncDir === "string"
|
|
431
|
+
? path.join((launchConfig as { asyncDir: string }).asyncDir, "runner-startup.json")
|
|
422
432
|
: undefined;
|
|
423
433
|
const startupAckPath = startupPath ? path.join(path.dirname(startupPath), "runner-startup-ack.json") : undefined;
|
|
424
434
|
const startupProceedPath = startupPath ? path.join(path.dirname(startupPath), "runner-startup-proceed.json") : undefined;
|
|
@@ -426,7 +436,7 @@ function spawnRunner(cfg: object, suffix: string, cwd: string): { pid?: number;
|
|
|
426
436
|
if (startupAckPath) fs.rmSync(startupAckPath, { force: true });
|
|
427
437
|
if (startupProceedPath) fs.rmSync(startupProceedPath, { force: true });
|
|
428
438
|
|
|
429
|
-
const logPaths = resolveAsyncRunnerLogPaths(
|
|
439
|
+
const logPaths = resolveAsyncRunnerLogPaths(launchConfig);
|
|
430
440
|
let stdoutFd: number | undefined;
|
|
431
441
|
let stderrFd: number | undefined;
|
|
432
442
|
try {
|
|
@@ -450,6 +460,56 @@ function spawnRunner(cfg: object, suffix: string, cwd: string): { pid?: number;
|
|
|
450
460
|
proc.on("error", (error) => {
|
|
451
461
|
console.error(`[pi-subagents] async spawn failed: ${error.message}`);
|
|
452
462
|
});
|
|
463
|
+
proc.once("close", (exitCode, signal) => {
|
|
464
|
+
const launch = launchConfig as { asyncDir?: unknown; id?: unknown; nestedRoute?: NestedRouteInfo; nestedSelf?: { parentRunId: string; parentStepIndex?: number; depth: number; path?: Array<{ runId: string; stepIndex?: number; agent?: string }> } };
|
|
465
|
+
const asyncDir = launch.asyncDir;
|
|
466
|
+
const runId = launch.id;
|
|
467
|
+
if (typeof asyncDir !== "string" || typeof runId !== "string") return;
|
|
468
|
+
finalizeProcessTerminal(asyncDir, runId, {
|
|
469
|
+
processInstanceId: runnerProcessInstanceId,
|
|
470
|
+
closeObservedAt: Date.now(),
|
|
471
|
+
exitCode,
|
|
472
|
+
signal,
|
|
473
|
+
});
|
|
474
|
+
const persisted = readProcessTerminal(asyncDir, { runId, runnerProcessInstanceId });
|
|
475
|
+
if (!persisted) return;
|
|
476
|
+
if (launch.nestedRoute && launch.nestedSelf) {
|
|
477
|
+
try {
|
|
478
|
+
let status: import("../../shared/types.ts").AsyncStatus;
|
|
479
|
+
try {
|
|
480
|
+
status = JSON.parse(fs.readFileSync(path.join(asyncDir, "status.json"), "utf-8")) as import("../../shared/types.ts").AsyncStatus;
|
|
481
|
+
status.processTerminal = persisted;
|
|
482
|
+
} catch {
|
|
483
|
+
status = {
|
|
484
|
+
runId,
|
|
485
|
+
mode: "single",
|
|
486
|
+
state: persisted.state === "observed" ? "complete" : "failed",
|
|
487
|
+
startedAt: persisted.observedAt ?? Date.now(),
|
|
488
|
+
lastUpdate: Date.now(),
|
|
489
|
+
processTerminal: persisted,
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
writeNestedEvent(launch.nestedRoute, {
|
|
493
|
+
type: "subagent.nested.completed",
|
|
494
|
+
ts: Date.now(),
|
|
495
|
+
parentRunId: launch.nestedSelf.parentRunId,
|
|
496
|
+
parentStepIndex: launch.nestedSelf.parentStepIndex,
|
|
497
|
+
child: nestedSummaryFromAsyncStatus(status, asyncDir, {
|
|
498
|
+
id: runId,
|
|
499
|
+
parentRunId: launch.nestedSelf.parentRunId,
|
|
500
|
+
parentStepIndex: launch.nestedSelf.parentStepIndex,
|
|
501
|
+
depth: launch.nestedSelf.depth,
|
|
502
|
+
path: launch.nestedSelf.path,
|
|
503
|
+
mode: status.mode,
|
|
504
|
+
ts: Date.now(),
|
|
505
|
+
}),
|
|
506
|
+
});
|
|
507
|
+
} catch (error) {
|
|
508
|
+
console.error("Failed to emit final nested process-terminal status:", error);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
onProcessTerminal?.(persisted);
|
|
512
|
+
});
|
|
453
513
|
if (typeof proc.pid !== "number") {
|
|
454
514
|
return { error: `async runner did not produce a pid for cwd: ${cwd}` };
|
|
455
515
|
}
|
|
@@ -637,6 +697,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
637
697
|
const agentContract = s.agentContract ?? params.agentContract;
|
|
638
698
|
return {
|
|
639
699
|
parentSessionId: ctx.parentSessionId ?? ctx.currentSessionId,
|
|
700
|
+
...(params.capabilityCeiling ? { capabilityCeiling: params.capabilityCeiling } : {}),
|
|
640
701
|
agent: s.agent,
|
|
641
702
|
task,
|
|
642
703
|
...(params.contextForAgent ? { context: params.contextForAgent(s.agent) } : {}),
|
|
@@ -834,6 +895,7 @@ export function executeAsyncChain(
|
|
|
834
895
|
nestedRoute,
|
|
835
896
|
} = params;
|
|
836
897
|
const resultMode = params.resultMode ?? "chain";
|
|
898
|
+
const capabilityCeiling = params.capabilityCeiling ?? resolveCurrentSubagentCapabilityCeiling(ctx.currentSessionId);
|
|
837
899
|
const inheritedNestedRoute = resolveInheritedNestedRouteFromEnv();
|
|
838
900
|
const nestedAddress = inheritedNestedRoute ? resolveNestedParentAddressFromEnv() : undefined;
|
|
839
901
|
const asyncDir = inheritedNestedRoute
|
|
@@ -873,6 +935,7 @@ export function executeAsyncChain(
|
|
|
873
935
|
asyncDir,
|
|
874
936
|
toolBudget: params.toolBudget,
|
|
875
937
|
configToolBudget: params.configToolBudget,
|
|
938
|
+
capabilityCeiling,
|
|
876
939
|
});
|
|
877
940
|
if ("error" in built) {
|
|
878
941
|
try {
|
|
@@ -917,6 +980,7 @@ export function executeAsyncChain(
|
|
|
917
980
|
sessionDir: sessionRoot ? path.join(sessionRoot, `async-${id}`) : undefined,
|
|
918
981
|
asyncDir,
|
|
919
982
|
sessionId: ctx.currentSessionId,
|
|
983
|
+
...(capabilityCeiling ? { capabilityCeiling } : {}),
|
|
920
984
|
piPackageRoot,
|
|
921
985
|
piArgv1: process.argv[1],
|
|
922
986
|
worktreeSetupHook,
|
|
@@ -943,6 +1007,7 @@ export function executeAsyncChain(
|
|
|
943
1007
|
},
|
|
944
1008
|
id,
|
|
945
1009
|
runnerCwd,
|
|
1010
|
+
(proof) => ctx.pi.events.emit(SUBAGENT_PROCESS_TERMINAL_EVENT, proof),
|
|
946
1011
|
);
|
|
947
1012
|
} catch (error) {
|
|
948
1013
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1014,6 +1079,7 @@ export function executeAsyncChain(
|
|
|
1014
1079
|
...(initialTurnBudget ? { turnBudget: initialTurnBudget } : {}),
|
|
1015
1080
|
startedAt: now,
|
|
1016
1081
|
lastUpdate: now,
|
|
1082
|
+
...(capabilityCeiling ? { capabilityCeiling } : {}),
|
|
1017
1083
|
},
|
|
1018
1084
|
});
|
|
1019
1085
|
} catch (error) {
|
|
@@ -1040,6 +1106,7 @@ export function executeAsyncChain(
|
|
|
1040
1106
|
asyncDir,
|
|
1041
1107
|
...(params.timeoutMs !== undefined ? { timeoutMs: params.timeoutMs, deadlineAt } : {}),
|
|
1042
1108
|
...(initialTurnBudget ? { turnBudget: initialTurnBudget } : {}),
|
|
1109
|
+
...(capabilityCeiling ? { capabilityCeiling } : {}),
|
|
1043
1110
|
nestedRoute,
|
|
1044
1111
|
});
|
|
1045
1112
|
}
|
|
@@ -1052,7 +1119,7 @@ export function executeAsyncChain(
|
|
|
1052
1119
|
|
|
1053
1120
|
return {
|
|
1054
1121
|
content: [{ type: "text", text: formatAsyncStartedMessage(`Async ${resultMode}: ${chainDesc} [${id}]`, ctx.interactive === true) }],
|
|
1055
|
-
details: { mode: resultMode, runId: id, results: [], asyncId: id, asyncDir, workflowGraph, ...(params.timeoutMs !== undefined ? { timeoutMs: params.timeoutMs, deadlineAt } : {}), ...(params.turnBudget ? { turnBudget: params.turnBudget } : {}), ...(params.toolBudget ? { toolBudget: params.toolBudget } : {}) },
|
|
1122
|
+
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 } : {}) },
|
|
1056
1123
|
};
|
|
1057
1124
|
}
|
|
1058
1125
|
|
|
@@ -1084,6 +1151,7 @@ export function executeAsyncSingle(
|
|
|
1084
1151
|
nestedRoute,
|
|
1085
1152
|
} = params;
|
|
1086
1153
|
const task = params.task ?? "";
|
|
1154
|
+
const capabilityCeiling = params.capabilityCeiling ?? resolveCurrentSubagentCapabilityCeiling(ctx.currentSessionId);
|
|
1087
1155
|
const runnerCwd = resolveChildCwd(ctx.cwd, cwd);
|
|
1088
1156
|
const skillNames = params.skills ?? agentConfig.skills ?? [];
|
|
1089
1157
|
const availableModels = params.availableModels;
|
|
@@ -1149,6 +1217,39 @@ export function executeAsyncSingle(
|
|
|
1149
1217
|
const structuredOutput = params.structuredOutputSchema
|
|
1150
1218
|
? createStructuredOutputRuntime(params.structuredOutputSchema, path.join(asyncDir, "structured-output"))
|
|
1151
1219
|
: undefined;
|
|
1220
|
+
const modelCandidates = buildModelCandidates(primaryModel, agentConfig.fallbackModels, availableModels, ctx.currentModelProvider, { scope: ctx.modelScope }).map((candidate) =>
|
|
1221
|
+
applyThinkingSuffix(candidate, effectiveThinking, params.thinkingOverride !== undefined),
|
|
1222
|
+
);
|
|
1223
|
+
const effectiveSystemPrompt = appendTurnBudgetSystemPrompt(systemPrompt, params.turnBudget);
|
|
1224
|
+
const toolPlan = resolvePiLaunchToolPlan({
|
|
1225
|
+
tools: agentConfig.tools,
|
|
1226
|
+
extensions: agentConfig.extensions,
|
|
1227
|
+
subagentOnlyExtensions: agentConfig.subagentOnlyExtensions,
|
|
1228
|
+
mcpDirectTools: agentConfig.mcpDirectTools,
|
|
1229
|
+
cwd: runnerCwd,
|
|
1230
|
+
requireReadTool: Boolean(resolvedSkills.length),
|
|
1231
|
+
structuredOutput: Boolean(params.structuredOutputSchema),
|
|
1232
|
+
capabilityCeiling,
|
|
1233
|
+
inheritedCapabilityCeiling: decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]),
|
|
1234
|
+
});
|
|
1235
|
+
const launchContractDigest = launchBindingDigest({
|
|
1236
|
+
definitionDigest: agentDefinitionDigest(agentConfig),
|
|
1237
|
+
task,
|
|
1238
|
+
...(model ? { model } : {}),
|
|
1239
|
+
modelCandidates,
|
|
1240
|
+
...(resolveEffectiveThinking(model, effectiveThinking) ? { thinking: resolveEffectiveThinking(model, effectiveThinking) } : {}),
|
|
1241
|
+
systemPrompt: effectiveSystemPrompt,
|
|
1242
|
+
systemPromptMode: agentConfig.systemPromptMode,
|
|
1243
|
+
inheritProjectContext: agentConfig.inheritProjectContext,
|
|
1244
|
+
inheritSkills: agentConfig.inheritSkills,
|
|
1245
|
+
skills: resolvedSkills.map((skill) => skill.name),
|
|
1246
|
+
tools: toolPlan.effectiveToolAllowlist,
|
|
1247
|
+
extensions: toolPlan.extensionArgs,
|
|
1248
|
+
mcpDirectTools: toolPlan.effectiveMcpTools,
|
|
1249
|
+
...(outputPath ? { outputPath } : {}),
|
|
1250
|
+
outputMode,
|
|
1251
|
+
...(params.structuredOutputSchema ? { structuredOutputSchema: params.structuredOutputSchema } : {}),
|
|
1252
|
+
});
|
|
1152
1253
|
const resolvedAcceptance = resolveEffectiveAcceptance({
|
|
1153
1254
|
explicit: params.acceptance,
|
|
1154
1255
|
agentName: agent,
|
|
@@ -1160,6 +1261,7 @@ export function executeAsyncSingle(
|
|
|
1160
1261
|
});
|
|
1161
1262
|
const recoveryDescriptor: SteeringRecoveryDescriptor = {
|
|
1162
1263
|
version: 1,
|
|
1264
|
+
launchContractDigest,
|
|
1163
1265
|
sourceRunId: id,
|
|
1164
1266
|
...(params.agentContract ? { agentContract: params.agentContract } : {}),
|
|
1165
1267
|
agent,
|
|
@@ -1195,6 +1297,7 @@ export function executeAsyncSingle(
|
|
|
1195
1297
|
...(resolvedSessionDir ? { sessionDir: resolvedSessionDir } : {}),
|
|
1196
1298
|
...(artifactsDir ? { artifactsDir } : {}),
|
|
1197
1299
|
artifactConfig,
|
|
1300
|
+
...(capabilityCeiling ? { capabilityCeiling } : {}),
|
|
1198
1301
|
};
|
|
1199
1302
|
try {
|
|
1200
1303
|
writePrivateAtomicJson(path.join(asyncDir, "recovery-descriptor.json"), recoveryDescriptor);
|
|
@@ -1209,15 +1312,14 @@ export function executeAsyncSingle(
|
|
|
1209
1312
|
steps: [
|
|
1210
1313
|
{
|
|
1211
1314
|
parentSessionId: ctx.parentSessionId ?? ctx.currentSessionId,
|
|
1315
|
+
...(capabilityCeiling ? { capabilityCeiling } : {}),
|
|
1212
1316
|
agent,
|
|
1213
1317
|
task: taskWithOutputInstruction,
|
|
1214
1318
|
...(params.context ? { context: params.context } : {}),
|
|
1215
1319
|
cwd: runnerCwd,
|
|
1216
1320
|
model,
|
|
1217
1321
|
thinking: resolveEffectiveThinking(model, effectiveThinking),
|
|
1218
|
-
modelCandidates
|
|
1219
|
-
applyThinkingSuffix(candidate, effectiveThinking, params.thinkingOverride !== undefined),
|
|
1220
|
-
),
|
|
1322
|
+
modelCandidates,
|
|
1221
1323
|
tools: agentConfig.tools,
|
|
1222
1324
|
extensions: agentConfig.extensions,
|
|
1223
1325
|
subagentOnlyExtensions: agentConfig.subagentOnlyExtensions,
|
|
@@ -1234,6 +1336,9 @@ export function executeAsyncSingle(
|
|
|
1234
1336
|
maxSubagentDepth: resolveChildMaxSubagentDepth(maxSubagentDepth, agentConfig.maxSubagentDepth),
|
|
1235
1337
|
waitToolEnabled: params.waitToolEnabled,
|
|
1236
1338
|
...(params.agentContract ? { agentContract: params.agentContract } : {}),
|
|
1339
|
+
definitionDigest: agentDefinitionDigest(agentConfig),
|
|
1340
|
+
launchBindingTask: task,
|
|
1341
|
+
launchContractDigest,
|
|
1237
1342
|
effectiveAcceptance: resolvedAcceptance,
|
|
1238
1343
|
...(structuredOutput ? { structuredOutput } : {}),
|
|
1239
1344
|
...(params.structuredOutputSchema ? { structuredOutputSchema: params.structuredOutputSchema } : {}),
|
|
@@ -1250,6 +1355,7 @@ export function executeAsyncSingle(
|
|
|
1250
1355
|
sessionDir: resolvedSessionDir,
|
|
1251
1356
|
asyncDir,
|
|
1252
1357
|
sessionId: ctx.currentSessionId,
|
|
1358
|
+
...(capabilityCeiling ? { capabilityCeiling } : {}),
|
|
1253
1359
|
piPackageRoot,
|
|
1254
1360
|
piArgv1: process.argv[1],
|
|
1255
1361
|
worktreeSetupHook,
|
|
@@ -1263,6 +1369,7 @@ export function executeAsyncSingle(
|
|
|
1263
1369
|
controlIntercomTarget,
|
|
1264
1370
|
childIntercomTargets: childIntercomTarget ? [childIntercomTarget(agent, 0)] : undefined,
|
|
1265
1371
|
resultMode: "single",
|
|
1372
|
+
launchContractDigest,
|
|
1266
1373
|
...(params.revivalLease ? { revivalLease: params.revivalLease } : {}),
|
|
1267
1374
|
nestedRoute: nestedRoute ?? inheritedNestedRoute,
|
|
1268
1375
|
nestedSelf: inheritedNestedRoute && nestedAddress ? {
|
|
@@ -1274,6 +1381,7 @@ export function executeAsyncSingle(
|
|
|
1274
1381
|
},
|
|
1275
1382
|
id,
|
|
1276
1383
|
runnerCwd,
|
|
1384
|
+
(proof) => ctx.pi.events.emit(SUBAGENT_PROCESS_TERMINAL_EVENT, proof),
|
|
1277
1385
|
);
|
|
1278
1386
|
} catch (error) {
|
|
1279
1387
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1314,6 +1422,7 @@ export function executeAsyncSingle(
|
|
|
1314
1422
|
...(initialTurnBudget ? { turnBudget: initialTurnBudget } : {}),
|
|
1315
1423
|
startedAt: now,
|
|
1316
1424
|
lastUpdate: now,
|
|
1425
|
+
...(capabilityCeiling ? { capabilityCeiling } : {}),
|
|
1317
1426
|
},
|
|
1318
1427
|
});
|
|
1319
1428
|
} catch (error) {
|
|
@@ -1331,14 +1440,16 @@ export function executeAsyncSingle(
|
|
|
1331
1440
|
goal: (params.goal ?? task).slice(0, 120),
|
|
1332
1441
|
cwd: runnerCwd,
|
|
1333
1442
|
asyncDir,
|
|
1443
|
+
launchContractDigest,
|
|
1334
1444
|
...(timeoutMs !== undefined ? { timeoutMs, deadlineAt } : {}),
|
|
1335
1445
|
...(initialTurnBudget ? { turnBudget: initialTurnBudget } : {}),
|
|
1446
|
+
...(capabilityCeiling ? { capabilityCeiling } : {}),
|
|
1336
1447
|
nestedRoute,
|
|
1337
1448
|
});
|
|
1338
1449
|
}
|
|
1339
1450
|
|
|
1340
1451
|
return {
|
|
1341
1452
|
content: [{ type: "text", text: formatAsyncStartedMessage(`Async: ${agent} [${id}]`, ctx.interactive === true) }],
|
|
1342
|
-
details: { mode: "single", runId: id, results: [], asyncId: id, asyncDir, ...(params.context ? { context: params.context } : {}), ...(timeoutMs !== undefined ? { timeoutMs, deadlineAt } : {}), ...(params.turnBudget ? { turnBudget: params.turnBudget } : {}), ...(params.toolBudget ? { toolBudget: params.toolBudget } : {}) },
|
|
1453
|
+
details: { mode: "single", runId: id, results: [], asyncId: id, asyncDir, launchContractDigest, ...(capabilityCeiling ? { capabilityCeiling } : {}), ...(params.context ? { context: params.context } : {}), ...(timeoutMs !== undefined ? { timeoutMs, deadlineAt } : {}), ...(params.turnBudget ? { turnBudget: params.turnBudget } : {}), ...(params.toolBudget ? { toolBudget: params.toolBudget } : {}) },
|
|
1343
1454
|
};
|
|
1344
1455
|
}
|
|
@@ -60,6 +60,19 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
60
60
|
renderWidget(ctx, options.widgetEnabled === false ? [] : jobs);
|
|
61
61
|
ctx.ui.requestRender?.();
|
|
62
62
|
};
|
|
63
|
+
const rerenderLastWidget = (jobs = Array.from(state.asyncJobs.values())) => {
|
|
64
|
+
const ctx = state.lastUiContext;
|
|
65
|
+
if (!ctx) return;
|
|
66
|
+
try {
|
|
67
|
+
if (ctx.hasUI) rerenderWidget(ctx, jobs);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
if (error instanceof Error && error.message.includes("extension ctx is stale")) {
|
|
70
|
+
state.lastUiContext = null;
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
63
76
|
const refreshWidget = (ctx: ExtensionContext) => rerenderWidget(ctx);
|
|
64
77
|
const restoredControlEventCursor = (asyncDir: string) => {
|
|
65
78
|
try {
|
|
@@ -131,9 +144,7 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
131
144
|
const timer = setTimeout(() => {
|
|
132
145
|
state.cleanupTimers.delete(asyncId);
|
|
133
146
|
state.asyncJobs.delete(asyncId);
|
|
134
|
-
|
|
135
|
-
rerenderWidget(state.lastUiContext);
|
|
136
|
-
}
|
|
147
|
+
rerenderLastWidget();
|
|
137
148
|
}, completionRetentionMs);
|
|
138
149
|
state.cleanupTimers.set(asyncId, timer);
|
|
139
150
|
};
|
|
@@ -254,7 +265,7 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
254
265
|
if (state.poller) return;
|
|
255
266
|
state.poller = setInterval(() => {
|
|
256
267
|
if (state.asyncJobs.size === 0) {
|
|
257
|
-
|
|
268
|
+
rerenderLastWidget([]);
|
|
258
269
|
if (state.poller) {
|
|
259
270
|
clearInterval(state.poller);
|
|
260
271
|
state.poller = null;
|
|
@@ -378,7 +389,7 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
378
389
|
if (widgetRenderKey(job) !== widgetStateBefore) widgetChanged = true;
|
|
379
390
|
}
|
|
380
391
|
|
|
381
|
-
if (widgetChanged
|
|
392
|
+
if (widgetChanged) rerenderLastWidget();
|
|
382
393
|
}, pollIntervalMs);
|
|
383
394
|
state.poller.unref?.();
|
|
384
395
|
};
|
|
@@ -421,9 +432,7 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
421
432
|
});
|
|
422
433
|
rememberFleetJob(state, state.asyncJobs.get(info.id)!);
|
|
423
434
|
ensurePoller();
|
|
424
|
-
|
|
425
|
-
rerenderWidget(state.lastUiContext);
|
|
426
|
-
}
|
|
435
|
+
rerenderLastWidget();
|
|
427
436
|
};
|
|
428
437
|
|
|
429
438
|
const handleComplete = (data: unknown) => {
|
|
@@ -446,9 +455,7 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
446
455
|
}
|
|
447
456
|
}
|
|
448
457
|
if (job) rememberFleetJob(state, job);
|
|
449
|
-
|
|
450
|
-
rerenderWidget(state.lastUiContext);
|
|
451
|
-
}
|
|
458
|
+
rerenderLastWidget();
|
|
452
459
|
if (!nestedRefreshFailed && !hasLiveNestedDescendants(job?.nestedChildren)) scheduleCleanup(asyncId);
|
|
453
460
|
};
|
|
454
461
|
|
|
@@ -485,7 +492,7 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
485
492
|
}
|
|
486
493
|
if (runs.length === 0) return;
|
|
487
494
|
ensurePoller();
|
|
488
|
-
|
|
495
|
+
rerenderLastWidget();
|
|
489
496
|
};
|
|
490
497
|
|
|
491
498
|
return { ensurePoller, refreshWidget, handleStarted, handleComplete, resetJobs, restoreActiveJobs };
|
|
@@ -4,6 +4,7 @@ import { ASYNC_DIR, RESULTS_DIR, type AcceptanceInput, type AsyncStatus, type St
|
|
|
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";
|
|
7
|
+
import { intersectSubagentCapabilityCeilings, parseSubagentCapabilityCeiling, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
|
|
7
8
|
import { resolveTurnBudgetConfig } from "../shared/turn-budget.ts";
|
|
8
9
|
import { reconcileAsyncRun } from "./stale-run-reconciler.ts";
|
|
9
10
|
|
|
@@ -38,6 +39,8 @@ export type AsyncResumeTarget = {
|
|
|
38
39
|
model?: string;
|
|
39
40
|
thinking?: string;
|
|
40
41
|
recoveryDescriptor?: SteeringRecoveryDescriptor;
|
|
42
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
43
|
+
launchContractDigest?: string;
|
|
41
44
|
};
|
|
42
45
|
|
|
43
46
|
interface AsyncResultFile {
|
|
@@ -52,7 +55,9 @@ interface AsyncResultFile {
|
|
|
52
55
|
sessionFile?: string;
|
|
53
56
|
model?: string;
|
|
54
57
|
thinking?: string;
|
|
55
|
-
|
|
58
|
+
launchContractDigest?: string;
|
|
59
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
60
|
+
results?: Array<{ agent?: string; success?: boolean; sessionFile?: string; intercomTarget?: string; model?: string; thinking?: string; launchContractDigest?: string; capabilityCeiling?: ResolvedSubagentCapabilityCeiling }>;
|
|
56
61
|
}
|
|
57
62
|
|
|
58
63
|
export interface AsyncRunLocation {
|
|
@@ -92,9 +97,11 @@ function validateResultFile(value: unknown, resultPath: string): AsyncResultFile
|
|
|
92
97
|
const intercomTarget = validateOptionalString(child, "intercomTarget", resultPath, `results[${index}].intercomTarget`);
|
|
93
98
|
const model = validateOptionalString(child, "model", resultPath, `results[${index}].model`);
|
|
94
99
|
const thinking = validateOptionalString(child, "thinking", resultPath, `results[${index}].thinking`);
|
|
100
|
+
const launchContractDigest = validateOptionalString(child, "launchContractDigest", resultPath, `results[${index}].launchContractDigest`);
|
|
101
|
+
const capabilityCeiling = child.capabilityCeiling === undefined ? undefined : parseSubagentCapabilityCeiling(child.capabilityCeiling, `async result file '${resultPath}' results[${index}].capabilityCeiling`);
|
|
95
102
|
const success = child.success;
|
|
96
103
|
if (success !== undefined && typeof success !== "boolean") throw new Error(`Invalid async result file '${resultPath}': results[${index}].success must be a boolean.`);
|
|
97
|
-
return { agent, sessionFile, intercomTarget, model, thinking, ...(typeof success === "boolean" ? { success } : {}) };
|
|
104
|
+
return { agent, sessionFile, intercomTarget, model, thinking, launchContractDigest, ...(capabilityCeiling ? { capabilityCeiling } : {}), ...(typeof success === "boolean" ? { success } : {}) };
|
|
98
105
|
});
|
|
99
106
|
}
|
|
100
107
|
const success = data.success;
|
|
@@ -110,6 +117,8 @@ function validateResultFile(value: unknown, resultPath: string): AsyncResultFile
|
|
|
110
117
|
sessionFile: validateOptionalString(data, "sessionFile", resultPath),
|
|
111
118
|
model: validateOptionalString(data, "model", resultPath),
|
|
112
119
|
thinking: validateOptionalString(data, "thinking", resultPath),
|
|
120
|
+
launchContractDigest: validateOptionalString(data, "launchContractDigest", resultPath),
|
|
121
|
+
...(data.capabilityCeiling === undefined ? {} : { capabilityCeiling: parseSubagentCapabilityCeiling(data.capabilityCeiling, `async result file '${resultPath}' capabilityCeiling`) }),
|
|
113
122
|
...(typeof success === "boolean" ? { success } : {}),
|
|
114
123
|
...(results ? { results } : {}),
|
|
115
124
|
};
|
|
@@ -237,6 +246,7 @@ function validateStatusForResume(status: AsyncStatus | null, source: string): vo
|
|
|
237
246
|
if (status.sessionId !== undefined && typeof status.sessionId !== "string") throw new Error(`Invalid async status '${source}': sessionId must be a string.`);
|
|
238
247
|
if (status.cwd !== undefined && typeof status.cwd !== "string") throw new Error(`Invalid async status '${source}': cwd must be a string.`);
|
|
239
248
|
if (status.sessionFile !== undefined && typeof status.sessionFile !== "string") throw new Error(`Invalid async status '${source}': sessionFile must be a string.`);
|
|
249
|
+
if (status.capabilityCeiling !== undefined) status.capabilityCeiling = parseSubagentCapabilityCeiling(status.capabilityCeiling, `async status '${source}' capabilityCeiling`);
|
|
240
250
|
if (status.steps !== undefined) {
|
|
241
251
|
if (!Array.isArray(status.steps)) throw new Error(`Invalid async status '${source}': steps must be an array.`);
|
|
242
252
|
status.steps.forEach((step, index) => {
|
|
@@ -246,6 +256,8 @@ function validateStatusForResume(status: AsyncStatus | null, source: string): vo
|
|
|
246
256
|
if (stepRecord.sessionFile !== undefined && typeof stepRecord.sessionFile !== "string") throw new Error(`Invalid async status '${source}': steps[${index}].sessionFile must be a string.`);
|
|
247
257
|
if (stepRecord.model !== undefined && typeof stepRecord.model !== "string") throw new Error(`Invalid async status '${source}': steps[${index}].model must be a string.`);
|
|
248
258
|
if (stepRecord.thinking !== undefined && typeof stepRecord.thinking !== "string") throw new Error(`Invalid async status '${source}': steps[${index}].thinking must be a string.`);
|
|
259
|
+
if (stepRecord.launchContractDigest !== undefined && typeof stepRecord.launchContractDigest !== "string") throw new Error(`Invalid async status '${source}': steps[${index}].launchContractDigest must be a string.`);
|
|
260
|
+
if (stepRecord.capabilityCeiling !== undefined) stepRecord.capabilityCeiling = parseSubagentCapabilityCeiling(stepRecord.capabilityCeiling, `async status '${source}' steps[${index}].capabilityCeiling`);
|
|
249
261
|
});
|
|
250
262
|
}
|
|
251
263
|
}
|
|
@@ -254,10 +266,6 @@ function normalizeRecoveryAcceptance(value: unknown, descriptorPath: string): Ac
|
|
|
254
266
|
if (value && typeof value === "object" && !Array.isArray(value) && ("explicit" in value || "inferredReason" in value)) {
|
|
255
267
|
const { explicit, inferredReason: _inferredReason, ...publicAcceptance } = value as Record<string, unknown>;
|
|
256
268
|
if (explicit === false) return undefined;
|
|
257
|
-
if (publicAcceptance.level === "reviewed") {
|
|
258
|
-
publicAcceptance.level = "verified";
|
|
259
|
-
delete publicAcceptance.review;
|
|
260
|
-
}
|
|
261
269
|
value = publicAcceptance;
|
|
262
270
|
}
|
|
263
271
|
const errors = validateAcceptanceInput(value, "recoveryDescriptor.acceptance");
|
|
@@ -278,10 +286,10 @@ export function readAsyncRecoveryDescriptor(asyncDir: string | undefined): Steer
|
|
|
278
286
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': expected an object.`);
|
|
279
287
|
const parsed = value as Record<string, unknown>;
|
|
280
288
|
const allowedFields = new Set([
|
|
281
|
-
"version", "sourceRunId", "agentContract", "agent", "sessionFile", "cwd", "model", "fallbackModels", "thinking", "tools", "extensions",
|
|
289
|
+
"version", "launchContractDigest", "sourceRunId", "agentContract", "agent", "sessionFile", "cwd", "model", "fallbackModels", "thinking", "tools", "extensions",
|
|
282
290
|
"subagentOnlyExtensions", "mcpDirectTools", "systemPrompt", "systemPromptMode", "inheritProjectContext", "inheritSkills", "skills",
|
|
283
291
|
"skillPath", "agentFilePath", "completionGuard", "memory", "outputPath", "outputMode", "structuredOutputSchema", "acceptance", "sessionDir", "artifactConfig",
|
|
284
|
-
"artifactsDir", "maxOutput", "controlConfig", "absoluteDeadlineAt", "initialTurnBudget", "initialToolBudget", "maxSubagentDepth", "share",
|
|
292
|
+
"artifactsDir", "maxOutput", "controlConfig", "absoluteDeadlineAt", "initialTurnBudget", "initialToolBudget", "maxSubagentDepth", "share", "capabilityCeiling",
|
|
285
293
|
]);
|
|
286
294
|
for (const field of Object.keys(parsed)) {
|
|
287
295
|
if (!allowedFields.has(field)) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': unknown field '${field}'.`);
|
|
@@ -291,6 +299,7 @@ export function readAsyncRecoveryDescriptor(asyncDir: string | undefined): Steer
|
|
|
291
299
|
if (typeof parsed[field] !== "string" || !(parsed[field] as string).trim()) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': ${field} must be a non-empty string.`);
|
|
292
300
|
}
|
|
293
301
|
if (parsed.version !== 1) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': version must be 1.`);
|
|
302
|
+
if (parsed.capabilityCeiling !== undefined) parsed.capabilityCeiling = parseSubagentCapabilityCeiling(parsed.capabilityCeiling, `async recovery descriptor '${descriptorPath}' capabilityCeiling`);
|
|
294
303
|
if (parsed.agentContract !== undefined) {
|
|
295
304
|
if (!parsed.agentContract || typeof parsed.agentContract !== "object" || Array.isArray(parsed.agentContract)) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': agentContract must be an object.`);
|
|
296
305
|
const contract = parsed.agentContract as Record<string, unknown>;
|
|
@@ -307,7 +316,7 @@ export function readAsyncRecoveryDescriptor(asyncDir: string | undefined): Steer
|
|
|
307
316
|
if (item !== undefined && (!Array.isArray(item) || item.some((entry) => typeof entry !== "string" || !entry.trim()))) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': ${field} must contain non-empty strings.`);
|
|
308
317
|
}
|
|
309
318
|
if (parsed.systemPrompt !== undefined && typeof parsed.systemPrompt !== "string") throw new Error(`Invalid async recovery descriptor '${descriptorPath}': systemPrompt must be a string.`);
|
|
310
|
-
for (const field of ["sessionFile", "model", "thinking", "agentFilePath", "outputPath", "sessionDir", "artifactsDir"] as const) {
|
|
319
|
+
for (const field of ["launchContractDigest", "sessionFile", "model", "thinking", "agentFilePath", "outputPath", "sessionDir", "artifactsDir"] as const) {
|
|
311
320
|
if (parsed[field] !== undefined && (typeof parsed[field] !== "string" || !(parsed[field] as string).trim())) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': ${field} must be a non-empty string.`);
|
|
312
321
|
}
|
|
313
322
|
if (parsed.completionGuard !== undefined && typeof parsed.completionGuard !== "boolean") throw new Error(`Invalid async recovery descriptor '${descriptorPath}': completionGuard must be a boolean.`);
|
|
@@ -407,6 +416,7 @@ export function resolveAsyncResumeTarget(params: AsyncResumeParams, deps: AsyncR
|
|
|
407
416
|
if (requestedIndex < 0 || requestedIndex >= stepCount) throw new Error(`Async run '${runId}' has ${stepCount} children. Index ${requestedIndex} is out of range.`);
|
|
408
417
|
const selectedStep = statusSteps[requestedIndex];
|
|
409
418
|
if (selectedStep?.status === "running") {
|
|
419
|
+
const capabilityCeiling = intersectSubagentCapabilityCeilings(status?.capabilityCeiling, selectedStep.capabilityCeiling);
|
|
410
420
|
return {
|
|
411
421
|
kind: "live",
|
|
412
422
|
runId,
|
|
@@ -418,6 +428,8 @@ export function resolveAsyncResumeTarget(params: AsyncResumeParams, deps: AsyncR
|
|
|
418
428
|
sessionFile: selectedStep.sessionFile ?? status?.sessionFile ?? result?.sessionFile,
|
|
419
429
|
model: selectedStep.model,
|
|
420
430
|
thinking: selectedStep.thinking,
|
|
431
|
+
launchContractDigest: selectedStep.launchContractDigest ?? result?.results?.[requestedIndex]?.launchContractDigest ?? result?.launchContractDigest ?? recoveryDescriptor?.launchContractDigest,
|
|
432
|
+
...(capabilityCeiling ? { capabilityCeiling } : {}),
|
|
421
433
|
...(recoveryDescriptor ? { recoveryDescriptor } : {}),
|
|
422
434
|
};
|
|
423
435
|
}
|
|
@@ -431,6 +443,7 @@ export function resolveAsyncResumeTarget(params: AsyncResumeParams, deps: AsyncR
|
|
|
431
443
|
if (!selected) {
|
|
432
444
|
throw new Error(`Async run '${runId}' has ${running.length} running children. Provide index to choose one.`);
|
|
433
445
|
}
|
|
446
|
+
const capabilityCeiling = intersectSubagentCapabilityCeilings(status?.capabilityCeiling, selected.step.capabilityCeiling);
|
|
434
447
|
return {
|
|
435
448
|
kind: "live",
|
|
436
449
|
runId,
|
|
@@ -442,6 +455,8 @@ export function resolveAsyncResumeTarget(params: AsyncResumeParams, deps: AsyncR
|
|
|
442
455
|
sessionFile: selected.step.sessionFile ?? status?.sessionFile ?? result?.sessionFile,
|
|
443
456
|
model: selected.step.model,
|
|
444
457
|
thinking: selected.step.thinking,
|
|
458
|
+
launchContractDigest: selected.step.launchContractDigest ?? result?.results?.[selected.index]?.launchContractDigest ?? result?.launchContractDigest ?? recoveryDescriptor?.launchContractDigest,
|
|
459
|
+
...(capabilityCeiling ? { capabilityCeiling } : {}),
|
|
445
460
|
...(recoveryDescriptor ? { recoveryDescriptor } : {}),
|
|
446
461
|
};
|
|
447
462
|
}
|
|
@@ -463,6 +478,7 @@ export function resolveAsyncResumeTarget(params: AsyncResumeParams, deps: AsyncR
|
|
|
463
478
|
const resolvedSessionFile = sessionFile ? validateResumeSessionFile(runId, sessionFile) : undefined;
|
|
464
479
|
const stepModel = statusSteps[index]?.model ?? resultSteps[index]?.model ?? (stepCount === 1 ? result?.model : undefined);
|
|
465
480
|
const stepThinking = statusSteps[index]?.thinking ?? resultSteps[index]?.thinking ?? (stepCount === 1 ? result?.thinking : undefined);
|
|
481
|
+
const capabilityCeiling = intersectSubagentCapabilityCeilings(status?.capabilityCeiling, statusSteps[index]?.capabilityCeiling, result?.capabilityCeiling, resultSteps[index]?.capabilityCeiling);
|
|
466
482
|
|
|
467
483
|
return {
|
|
468
484
|
kind: "revive",
|
|
@@ -475,6 +491,8 @@ export function resolveAsyncResumeTarget(params: AsyncResumeParams, deps: AsyncR
|
|
|
475
491
|
...(resolvedSessionFile ? { sessionFile: resolvedSessionFile } : {}),
|
|
476
492
|
...(stepModel ? { model: stepModel } : {}),
|
|
477
493
|
...(stepThinking ? { thinking: stepThinking } : {}),
|
|
494
|
+
launchContractDigest: statusSteps[index]?.launchContractDigest ?? resultSteps[index]?.launchContractDigest ?? result?.launchContractDigest ?? recoveryDescriptor?.launchContractDigest,
|
|
495
|
+
...(capabilityCeiling ? { capabilityCeiling } : {}),
|
|
478
496
|
...(recoveryDescriptor ? { recoveryDescriptor } : {}),
|
|
479
497
|
};
|
|
480
498
|
}
|
|
@@ -3,12 +3,14 @@ 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
5
|
import { type ActivityState, type AsyncJobStep, type AsyncParallelGroupStatus, type AsyncStatus, type CostSummary, type NestedRunSummary, type SteeringStatus, type SubagentRunMode, type TokenUsage, type TurnBudgetState } from "../../shared/types.ts";
|
|
6
|
+
import type { ResolvedSubagentCapabilityCeiling, SubagentCapabilityAudit } from "../shared/capability-ceiling.ts";
|
|
6
7
|
import { readStatus } from "../../shared/utils.ts";
|
|
7
8
|
import { attachRootChildrenToSteps, buildNestedRouteIndex, type NestedRoute, projectNestedEvents } from "../shared/nested-events.ts";
|
|
8
9
|
import { formatNestedRunStatusLines } from "../shared/nested-render.ts";
|
|
9
10
|
import { flatToLogicalStepIndex, normalizeParallelGroups } from "./parallel-groups.ts";
|
|
10
11
|
import { contextModeLabel, summarizeContextModes, type ContextMode, type ContextSummary } from "../shared/context-mode.ts";
|
|
11
12
|
import { reconcileAsyncRun, reconcileNestedAsyncDescendants } from "./stale-run-reconciler.ts";
|
|
13
|
+
import { readProcessTerminal, sanitizeProcessTerminal } from "./process-terminal.ts";
|
|
12
14
|
|
|
13
15
|
interface AsyncRunStepSummary {
|
|
14
16
|
index: number;
|
|
@@ -50,6 +52,9 @@ interface AsyncRunStepSummary {
|
|
|
50
52
|
execution?: AsyncJobStep["execution"];
|
|
51
53
|
review?: AsyncJobStep["review"];
|
|
52
54
|
effects?: AsyncJobStep["effects"];
|
|
55
|
+
processTerminal?: AsyncJobStep["processTerminal"];
|
|
56
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
57
|
+
capabilityAudit?: SubagentCapabilityAudit;
|
|
53
58
|
children?: NestedRunSummary[];
|
|
54
59
|
}
|
|
55
60
|
|
|
@@ -92,6 +97,9 @@ export interface AsyncRunSummary {
|
|
|
92
97
|
sessionFile?: string;
|
|
93
98
|
nestedChildren?: NestedRunSummary[];
|
|
94
99
|
nestedWarnings?: string[];
|
|
100
|
+
processTerminal?: AsyncStatus["processTerminal"];
|
|
101
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
102
|
+
capabilityAudit?: SubagentCapabilityAudit;
|
|
95
103
|
}
|
|
96
104
|
|
|
97
105
|
interface AsyncRunListOptions {
|
|
@@ -194,6 +202,8 @@ function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string
|
|
|
194
202
|
throw new Error(`Invalid async status '${path.join(asyncDir, "status.json")}': sessionId must be a string.`);
|
|
195
203
|
}
|
|
196
204
|
const { activityState, lastActivityAt } = deriveAsyncActivityState(asyncDir, status);
|
|
205
|
+
const processTerminal = readProcessTerminal(asyncDir, { runId: status.runId, runnerProcessInstanceId: status.processTerminal?.runnerProcessInstanceId })
|
|
206
|
+
?? sanitizeProcessTerminal(status.processTerminal, { runId: status.runId, runnerProcessInstanceId: status.processTerminal?.runnerProcessInstanceId }, path.join(asyncDir, "status.json"));
|
|
197
207
|
const steps = status.steps ?? [];
|
|
198
208
|
const chainStepCount = status.chainStepCount ?? steps.length;
|
|
199
209
|
const parallelGroups = normalizeParallelGroups(status.parallelGroups, steps.length, chainStepCount);
|
|
@@ -247,9 +257,13 @@ function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string
|
|
|
247
257
|
...(step.wrapUpRequested !== undefined ? { wrapUpRequested: step.wrapUpRequested } : {}),
|
|
248
258
|
...(step.acceptance ? { acceptance: step.acceptance } : {}),
|
|
249
259
|
...(step.agentContract ? { agentContract: step.agentContract } : {}),
|
|
260
|
+
...(step.launchContractDigest ? { launchContractDigest: step.launchContractDigest } : {}),
|
|
250
261
|
...(step.execution ? { execution: step.execution } : {}),
|
|
251
262
|
...(step.review ? { review: step.review } : {}),
|
|
252
263
|
...(step.effects ? { effects: step.effects } : {}),
|
|
264
|
+
...(step.processTerminal ? { processTerminal: sanitizeProcessTerminal(step.processTerminal, { runId: status.runId, runnerProcessInstanceId: step.processTerminal.runnerProcessInstanceId }, `${path.join(asyncDir, "status.json")} step ${index}`) } : {}),
|
|
265
|
+
...(step.capabilityCeiling ? { capabilityCeiling: step.capabilityCeiling } : {}),
|
|
266
|
+
...(step.capabilityAudit ? { capabilityAudit: step.capabilityAudit } : {}),
|
|
253
267
|
...(step.children?.length ? { children: step.children } : {}),
|
|
254
268
|
};
|
|
255
269
|
});
|
|
@@ -288,6 +302,10 @@ function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string
|
|
|
288
302
|
steps: summarizedSteps,
|
|
289
303
|
...(nestedChildren.length ? { nestedChildren } : {}),
|
|
290
304
|
...(nestedWarnings.length ? { nestedWarnings } : {}),
|
|
305
|
+
...(processTerminal ? { processTerminal } : {}),
|
|
306
|
+
...(status.launchContractDigest ? { launchContractDigest: status.launchContractDigest } : {}),
|
|
307
|
+
...(status.capabilityCeiling ? { capabilityCeiling: status.capabilityCeiling } : {}),
|
|
308
|
+
...(status.capabilityAudit ? { capabilityAudit: status.capabilityAudit } : {}),
|
|
291
309
|
...(status.sessionDir ? { sessionDir: status.sessionDir } : {}),
|
|
292
310
|
...(status.outputFile ? { outputFile: status.outputFile } : {}),
|
|
293
311
|
...(status.totalTokens ? { totalTokens: status.totalTokens } : {}),
|