pi-subagents 0.82.1 → 0.83.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 +49 -0
- package/bundle/agents.js +5 -5
- package/bundle/background-work.js +1 -1
- package/bundle/capability-ceiling.js +1 -1
- package/bundle/child-tool-plan.js +4 -4
- package/bundle/{chunk-RMQFMMAT.js → chunk-2D52JFLF.js} +231 -10
- package/bundle/{chunk-52UBALJL.js → chunk-2DHZTCOL.js} +2 -2
- package/bundle/{chunk-43SCHIMD.js → chunk-2S4K53MV.js} +1 -1
- package/bundle/{chunk-IVAE5Z6Q.js → chunk-5YIITCPD.js} +31 -27
- package/bundle/{chunk-JYUKW2SY.js → chunk-AHIFYGR3.js} +13 -13
- package/bundle/{chunk-XSWOTIA3.js → chunk-C5P5CHYK.js} +1 -1
- package/bundle/{chunk-PDZWJCHD.js → chunk-C7JMPU5F.js} +10 -7
- package/bundle/{chunk-5TYMSZX3.js → chunk-D3JAFGMO.js} +2 -2
- package/bundle/{chunk-OYF6AFDY.js → chunk-F4ASL6OV.js} +2 -2
- package/bundle/{chunk-ZIV4J4HG.js → chunk-F5DBXJ2G.js} +51 -34
- package/bundle/{chunk-YKEWIB4M.js → chunk-G2Z6ZH26.js} +1 -1
- package/bundle/{chunk-G4GZD3OJ.js → chunk-IPGZMQXV.js} +11 -11
- package/bundle/{chunk-5LFLVSTM.js → chunk-JEZOSEWG.js} +2 -0
- package/bundle/{chunk-PLCCBLPY.js → chunk-LFDBD4PW.js} +2 -2
- package/bundle/{chunk-CIZQXJ5Q.js → chunk-LNRJCP2Y.js} +56 -20
- package/bundle/{chunk-UTT254JE.js → chunk-MABL3FUM.js} +8 -4
- package/bundle/{chunk-3JCDWF4V.js → chunk-NPIYWVOD.js} +12 -18
- package/bundle/{chunk-UCPP4GNU.js → chunk-QP5ZWNIS.js} +12 -5
- package/bundle/{chunk-Z7KVHQHL.js → chunk-QXASAHIA.js} +2 -2
- package/bundle/{chunk-46XKCPJR.js → chunk-RNIZLSEA.js} +2221 -3260
- package/bundle/{chunk-GJECHSAR.js → chunk-SSG7IEP5.js} +2 -2
- package/bundle/{chunk-VSUSLDNW.js → chunk-UFL7IYCW.js} +10 -8
- package/bundle/{chunk-QE4AGIMG.js → chunk-UUP372DR.js} +482 -705
- package/bundle/{chunk-6XJB7PIX.js → chunk-UYK5MZWE.js} +16 -12
- package/bundle/{chunk-4SDU2BZX.js → chunk-V2BZYLEP.js} +3 -3
- package/bundle/{chunk-FXUXCX2K.js → chunk-VLFZIN6D.js} +6 -6
- package/bundle/{chunk-N2BN7BF5.js → chunk-XA3DY6UG.js} +7 -5
- package/bundle/{chunk-PBK2I7WY.js → chunk-YXFJVCOL.js} +6 -6
- package/bundle/{chunk-4Y2ROR7L.js → chunk-ZVOAMTUQ.js} +5 -5
- package/bundle/control-channel.js +4 -4
- package/bundle/index.js +1 -1
- package/bundle/intercom-bridge.js +4 -4
- package/bundle/preflight.js +10 -10
- package/bundle/project-panes.js +5 -5
- package/bundle/shared-types.js +1 -1
- package/docs/configuration.md +13 -2
- package/docs/extension-api.md +21 -4
- package/docs/missions.md +2 -0
- package/docs/observability.md +1 -0
- package/docs/tool-reference.md +2 -2
- package/package.json +1 -1
- package/skills/pi-subagents/SKILL.md +6 -6
- package/src/agents/agent-management.d.ts +1 -1
- package/src/agents/agent-management.js +10 -16
- package/src/agents/agent-serializer.js +1 -2
- package/src/agents/agents.js +20 -5
- package/src/agents/frontmatter.js +22 -18
- package/src/api/background-work.d.ts +9 -6
- package/src/api/background-work.js +14 -7
- package/src/extension/index.js +6 -3
- package/src/extension/public-execution.d.ts +2 -2
- package/src/extension/public-execution.js +3 -3
- package/src/extension/reply-workflow-script.js +4 -5
- package/src/extension/rpc.js +3 -1
- package/src/extension/subagent-options.js +17 -4
- package/src/missions/actions.js +7 -2
- package/src/missions/lifecycle.d.ts +11 -0
- package/src/missions/lifecycle.js +37 -6
- package/src/missions/store.js +1 -0
- package/src/missions/types.d.ts +2 -0
- package/src/runs/background/async-execution.d.ts +1 -1
- package/src/runs/background/async-execution.js +7 -4
- package/src/runs/background/async-job-tracker.js +2 -1
- package/src/runs/background/async-resume.js +3 -2
- package/src/runs/background/auto-drain.js +2 -3
- package/src/runs/background/fleet-view.js +1 -1
- package/src/runs/background/retained-children.js +1 -1
- package/src/runs/background/run-child-session.js +22 -6
- package/src/runs/background/subagent-wait.d.ts +2 -1
- package/src/runs/background/subagent-wait.js +5 -2
- package/src/runs/foreground/execution.js +22 -6
- package/src/runs/foreground/subagent-executor.js +26 -56
- package/src/runs/shared/capability-ceiling.d.ts +2 -1
- package/src/runs/shared/capability-ceiling.js +3 -2
- package/src/runs/shared/child-launch.js +5 -2
- package/src/runs/shared/child-session.d.ts +6 -0
- package/src/runs/shared/child-session.js +20 -4
- package/src/runs/shared/herdr-machine.d.ts +2 -0
- package/src/runs/shared/herdr-machine.js +2 -0
- package/src/runs/shared/herdr-placed-run.js +11 -4
- package/src/runs/shared/parallel-handoff.d.ts +1 -1
- package/src/runs/shared/parallel-handoff.js +10 -6
- package/src/runs/shared/subagent-prompt-runtime.js +11 -17
- package/src/shared/disabled-features.d.ts +3 -1
- package/src/shared/disabled-features.js +11 -3
- package/src/shared/types.d.ts +4 -2
- package/src/shared/types.js +2 -0
- package/src/workflows/scripted-workflow.js +13 -1071
- package/workflow-script-worker.cjs +1068 -0
package/src/missions/types.d.ts
CHANGED
|
@@ -57,6 +57,8 @@ export interface MissionWorkflowChild {
|
|
|
57
57
|
sessionPath?: string;
|
|
58
58
|
artifactPaths: string[];
|
|
59
59
|
heartbeat?: MissionChildHeartbeat;
|
|
60
|
+
/** Latest acceptance ledger status reported for the child. */
|
|
61
|
+
acceptanceStatus?: string;
|
|
60
62
|
}
|
|
61
63
|
export type MissionWorkflowChildUpdate = Pick<MissionWorkflowChild, "workflowRunId" | "key" | "status"> & Partial<Omit<MissionWorkflowChild, "workflowRunId" | "key" | "status" | "startedAt" | "updatedAt" | "artifactPaths" | "heartbeat">> & {
|
|
62
64
|
startedAt?: string;
|
|
@@ -25,7 +25,7 @@ interface AsyncExecutionContext {
|
|
|
25
25
|
cwd: string;
|
|
26
26
|
currentSessionId: string;
|
|
27
27
|
completionOwnerId?: string;
|
|
28
|
-
/** Parent session id
|
|
28
|
+
/** Parent Pi session id (`getSessionId()`): keys permission-system ask forwarding and capability ceilings. */
|
|
29
29
|
parentSessionId?: string;
|
|
30
30
|
/** Parent session file, recorded as each new child session's `parentSession` header. */
|
|
31
31
|
parentSessionFile?: string;
|
|
@@ -13,7 +13,7 @@ import { childCacheRetentionEnv } from "../../shared/child-cache-retention.js";
|
|
|
13
13
|
import { buildEffectiveSystemPrompt } from "../shared/effective-system-prompt.js";
|
|
14
14
|
import { currentCompletionOwnerId } from "../../shared/completion-owner.js";
|
|
15
15
|
import { planChildLaunch, projectChainOutputSchemas, resolveStepBehavior, suppressProgressForReadOnlyTask } from "../shared/child-launch-plan.js";
|
|
16
|
-
import { formatHerdrMachineRunnerUnsupported, resolveMachinePlacement } from "../shared/herdr-machine.js";
|
|
16
|
+
import { formatHerdrMachineRunnerUnsupported, PLACED_RESTORE_UNSUPPORTED, resolveMachinePlacement } from "../shared/herdr-machine.js";
|
|
17
17
|
import { applyThinkingSuffix, projectLaunchResolvedChildExtensions, resolvePiLaunchToolPlan } from "../shared/child-tool-plan.js";
|
|
18
18
|
import { assertClaudeCodeModelScope, isClaudeCodeAdapterId, resolveClaudeCodeOverride } from "../shared/claude-code-adapter.js";
|
|
19
19
|
import { injectSingleOutputInstruction, normalizeSingleOutputOverride, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.js";
|
|
@@ -1233,7 +1233,8 @@ export function executeAsyncChain(id, params) {
|
|
|
1233
1233
|
.find((name) => name !== undefined && !lookupRunnerLauncher(ctx.runnerLaunchers, name));
|
|
1234
1234
|
if (undefinedLauncher !== undefined)
|
|
1235
1235
|
return formatAsyncStartError(resultMode, `Launcher '${undefinedLauncher}' used by this ${resultMode} is not defined in runnerLaunchers in the user subagent config.`);
|
|
1236
|
-
const
|
|
1236
|
+
const launchParentSessionId = ctx.parentSessionId ?? ctx.currentSessionId;
|
|
1237
|
+
const capabilityCeiling = params.capabilityCeiling ?? resolveCurrentSubagentCapabilityCeiling(launchParentSessionId);
|
|
1237
1238
|
const inheritedNestedRoute = inheritedNestedRouteOf(ctx.childRuntime);
|
|
1238
1239
|
const nestedAddress = inheritedNestedRoute ? inheritedNestedParentAddressOf(ctx.childRuntime) : undefined;
|
|
1239
1240
|
const asyncDir = inheritedNestedRoute
|
|
@@ -1336,7 +1337,6 @@ export function executeAsyncChain(id, params) {
|
|
|
1336
1337
|
const initialStatusAt = Date.now();
|
|
1337
1338
|
const initialCompletionOwnerId = ctx.completionOwnerId ?? currentCompletionOwnerId();
|
|
1338
1339
|
const completionOwnerProcess = currentRecordedPid();
|
|
1339
|
-
const launchParentSessionId = ctx.parentSessionId ?? ctx.currentSessionId;
|
|
1340
1340
|
try {
|
|
1341
1341
|
writeRetainedRequiredChildExtensions(asyncDir, params.requiredExtensions ?? ctx.childRuntime?.requiredExtensions ?? resolveRequiredChildExtensions(launchParentSessionId ?? undefined));
|
|
1342
1342
|
}
|
|
@@ -1600,7 +1600,7 @@ export function executeAsyncSingle(id, params) {
|
|
|
1600
1600
|
if (unsupported.length > 0)
|
|
1601
1601
|
return formatAsyncStartError("single", `Agent '${agentConfig.name}' uses runner.type='${externalRunnerType}' and does not support: ${unsupported.join(", ")}.`);
|
|
1602
1602
|
}
|
|
1603
|
-
const capabilityCeiling = intersectSubagentCapabilityCeilings(params.capabilityCeiling ?? resolveCurrentSubagentCapabilityCeiling(ctx.currentSessionId), ctx.childRuntime?.capabilityCeiling);
|
|
1603
|
+
const capabilityCeiling = intersectSubagentCapabilityCeilings(params.capabilityCeiling ?? resolveCurrentSubagentCapabilityCeiling(ctx.parentSessionId ?? ctx.currentSessionId), ctx.childRuntime?.capabilityCeiling);
|
|
1604
1604
|
try {
|
|
1605
1605
|
assertAgentAllowedByCapabilityCeiling(agentConfig.name, capabilityCeiling);
|
|
1606
1606
|
}
|
|
@@ -1612,6 +1612,9 @@ export function executeAsyncSingle(id, params) {
|
|
|
1612
1612
|
const machineUnsupported = formatHerdrMachineRunnerUnsupported({ machine: requestedMachine, agentName: agentConfig.name, runnerType: agentConfig.runner?.type, adapter: agentConfig.runner?.type === "external-cli" ? agentConfig.runner.adapter : undefined, worktree: params.worktree });
|
|
1613
1613
|
if (machineUnsupported)
|
|
1614
1614
|
return formatAsyncStartError("single", machineUnsupported);
|
|
1615
|
+
// Refuse before the runner starts; the placed launch would refuse only after the run exists.
|
|
1616
|
+
if (requestedMachine && params.revivalLease)
|
|
1617
|
+
return formatAsyncStartError("single", PLACED_RESTORE_UNSUPPORTED);
|
|
1615
1618
|
const resolvedLauncher = resolveAgentRunnerLauncher(agentConfig, ctx.runnerLaunchers, requestedMachine);
|
|
1616
1619
|
if (resolvedLauncher.error)
|
|
1617
1620
|
return formatAsyncStartError("single", resolvedLauncher.error);
|
|
@@ -2,7 +2,7 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { renderWidget, widgetRenderKey } from "../../tui/render.js";
|
|
4
4
|
import { formatControlNoticeMessage } from "../shared/subagent-control.js";
|
|
5
|
-
import { DIRS, SUBAGENT_CHILD_STATUS_EVENT, SUBAGENT_CONTROL_EVENT, SUBAGENT_CONTROL_INTERCOM_EVENT, SUBAGENT_STEERING_NOTICE_EVENT, WIDGET_ANIMATION_INTERVAL_MS, } from "../../shared/types.js";
|
|
5
|
+
import { DIRS, SUBAGENT_CHILD_STATUS_EVENT, SUBAGENT_CONTROL_EVENT, SUBAGENT_CONTROL_INTERCOM_EVENT, SUBAGENT_JOBS_CHANGED_EVENT, SUBAGENT_STEERING_NOTICE_EVENT, WIDGET_ANIMATION_INTERVAL_MS, } from "../../shared/types.js";
|
|
6
6
|
import { readStatus, resolveWatchPath } from "../../shared/utils.js";
|
|
7
7
|
import { normalizeParallelGroups } from "./parallel-groups.js";
|
|
8
8
|
import { reconcileAsyncRun, reconcileNestedAsyncDescendants } from "./stale-run-reconciler.js";
|
|
@@ -63,6 +63,7 @@ export function createAsyncJobTracker(pi, state, asyncDirRoot, options = {}) {
|
|
|
63
63
|
};
|
|
64
64
|
const rerenderWidget = (ctx, jobs = Array.from(state.asyncJobs.values())) => {
|
|
65
65
|
options.onJobsChanged?.();
|
|
66
|
+
pi.events.emit(SUBAGENT_JOBS_CHANGED_EVENT, { version: 1 });
|
|
66
67
|
if (state.widgetsSuspended)
|
|
67
68
|
return;
|
|
68
69
|
renderWidget(ctx, options.widgetEnabled === false ? [] : jobs, options.widgetCollapsed, options.widgetLayout);
|
|
@@ -484,7 +484,8 @@ export function resolveAsyncResumeTarget(params, deps = {}, options = {}) {
|
|
|
484
484
|
const requireSessionFile = options.requireSessionFile ?? true;
|
|
485
485
|
const location = resolveAsyncRunLocation(params, asyncDirRoot, resultsDir);
|
|
486
486
|
if (!location.asyncDir && !location.resultPath) {
|
|
487
|
-
|
|
487
|
+
const requested = params.id ?? params.runId;
|
|
488
|
+
throw new Error(requested ? `Async run not found for '${requested}'.` : "Async run not found. Provide id or dir.");
|
|
488
489
|
}
|
|
489
490
|
const reconciliation = location.asyncDir
|
|
490
491
|
? reconcileAsyncRun(location.asyncDir, { resultsDir, kill: deps.kill, now: deps.now })
|
|
@@ -606,7 +607,7 @@ export function resolveAsyncResumeTarget(params, deps = {}, options = {}) {
|
|
|
606
607
|
? intersectSubagentCapabilityCeilings(status?.admissionCapabilityCeiling, result?.admissionCapabilityCeiling)
|
|
607
608
|
: intersectSubagentCapabilityCeilings(status?.capabilityCeiling, statusSteps[index]?.capabilityCeiling, result?.capabilityCeiling, resultSteps[index]?.capabilityCeiling);
|
|
608
609
|
const managedWorktreeCwd = location.asyncDir
|
|
609
|
-
? resolveRetainedWorktreeCwd(parallelHandoffPath(location.asyncDir), runId, index)
|
|
610
|
+
? resolveRetainedWorktreeCwd(parallelHandoffPath(location.asyncDir), runId, index, statusSteps[index]?.worktreePath)
|
|
610
611
|
: undefined;
|
|
611
612
|
const resumeCwd = validateResumeCwd(runId, managedWorktreeCwd ?? status?.cwd ?? result?.cwd ?? recoveryDescriptor?.cwd);
|
|
612
613
|
return {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { snapshotBackgroundWork } from "../../api/background-work.js";
|
|
2
1
|
import { listAsyncRuns } from "./async-status.js";
|
|
3
|
-
import { waitForSubagents, waitRunScopes } from "./subagent-wait.js";
|
|
2
|
+
import { backgroundWorkForSession, waitForSubagents, waitRunScopes } from "./subagent-wait.js";
|
|
4
3
|
export const DEFAULT_AUTO_DRAIN_TIMEOUT_MS = 30 * 60 * 1000;
|
|
5
4
|
function resultText(value) {
|
|
6
5
|
return value.content.map((part) => part.type === "text" ? part.text : "").join(" ").trim();
|
|
@@ -14,7 +13,7 @@ function hasOutstandingWork(deps, sessionId, nowMs, observation) {
|
|
|
14
13
|
includeNested: false,
|
|
15
14
|
}, observation?.status));
|
|
16
15
|
const detachedForeground = [...(deps.state.foregroundRuns?.values() ?? [])].some((run) => run.sessionId === sessionId && run.children.some((child) => child.status === "detached"));
|
|
17
|
-
return asyncRuns.length > 0 ||
|
|
16
|
+
return asyncRuns.length > 0 || backgroundWorkForSession(deps, nowMs).items.length > 0 || detachedForeground;
|
|
18
17
|
}
|
|
19
18
|
/** Drain all work owned by the current headless session, including work added while draining. */
|
|
20
19
|
export async function drainOutstandingWork(deps, observation) {
|
|
@@ -164,7 +164,7 @@ function sessionMessageParts(record) {
|
|
|
164
164
|
}
|
|
165
165
|
if (entry.type === "toolCall" || entry.type === "tool_call") {
|
|
166
166
|
const name = typeof entry.name === "string" ? entry.name : typeof entry.toolName === "string" ? entry.toolName : "tool";
|
|
167
|
-
parts.push({ role, kind: "toolCall", name, text: `[tool: ${name}${entry.
|
|
167
|
+
parts.push({ role, kind: "toolCall", name, text: `[tool: ${name}${entry.arguments === undefined ? "" : ` ${stringifyJsonPreview(entry.arguments)}`}]` });
|
|
168
168
|
continue;
|
|
169
169
|
}
|
|
170
170
|
if (entry.type === "toolResult" || entry.type === "tool_result") {
|
|
@@ -52,7 +52,7 @@ function childResumability(run, step) {
|
|
|
52
52
|
}
|
|
53
53
|
let requiredCwd;
|
|
54
54
|
try {
|
|
55
|
-
requiredCwd = resolveRetainedWorktreeCwd(parallelHandoffPath(run.asyncDir), run.id, step.index) ?? recoveryDescriptor.cwd ?? run.cwd;
|
|
55
|
+
requiredCwd = resolveRetainedWorktreeCwd(parallelHandoffPath(run.asyncDir), run.id, step.index, step.worktreePath) ?? recoveryDescriptor.cwd ?? run.cwd;
|
|
56
56
|
if (!requiredCwd || !fs.statSync(requiredCwd).isDirectory())
|
|
57
57
|
return { state: "not-resumable", reason: `required cwd is missing: ${requiredCwd ?? "unknown"}` };
|
|
58
58
|
}
|
|
@@ -69,6 +69,8 @@ export function runChildSession(input) {
|
|
|
69
69
|
let queuedDrainHold = false;
|
|
70
70
|
let compactionStartedReceived = false;
|
|
71
71
|
let afterCompactionSettlement = false;
|
|
72
|
+
let terminalAgentEndReceived = false;
|
|
73
|
+
let compactionAfterFinalOpen = false;
|
|
72
74
|
let finalDrainTimer;
|
|
73
75
|
let finalHardFinishTimer;
|
|
74
76
|
let watchdogTailTimer;
|
|
@@ -216,7 +218,8 @@ export function runChildSession(input) {
|
|
|
216
218
|
finalDrainTimer = setTimeout(() => {
|
|
217
219
|
if (settled || promptSettled)
|
|
218
220
|
return;
|
|
219
|
-
|
|
221
|
+
// Pi runs auto-compaction after the final agent_end and only then emits agent_settled.
|
|
222
|
+
if (input.launch.capture.finalDrainHeld() || observeQueuedDrainHold() || compactionAfterFinalOpen) {
|
|
220
223
|
finalDrainTimer = undefined;
|
|
221
224
|
armFinalDrainTimer();
|
|
222
225
|
return;
|
|
@@ -231,16 +234,22 @@ export function runChildSession(input) {
|
|
|
231
234
|
return;
|
|
232
235
|
settle(undefined, true);
|
|
233
236
|
}, HARD_FINISH_MS);
|
|
234
|
-
finalHardFinishTimer.unref?.();
|
|
235
237
|
}, FINAL_STOP_GRACE_MS);
|
|
236
|
-
finalDrainTimer.unref?.();
|
|
237
238
|
}
|
|
238
|
-
const applyChildLifecycle = (action) => {
|
|
239
|
+
const applyChildLifecycle = (action, event) => {
|
|
239
240
|
if (action === "cancel-drain") {
|
|
240
|
-
|
|
241
|
+
// After a clean final stop, a non-retrying agent_end may be the child's last event
|
|
242
|
+
// (Pi may not emit agent_settled), so it re-arms the drain instead of cancelling it.
|
|
243
|
+
terminalAgentEndReceived = event?.type === "agent_end" && event.willRetry !== true && cleanTerminalAssistantStopReceived;
|
|
241
244
|
agentSettledReceived = false;
|
|
242
245
|
clearFinalDrainTimers();
|
|
243
246
|
clearWatchdogTailTimer();
|
|
247
|
+
if (terminalAgentEndReceived) {
|
|
248
|
+
startFinalDrain();
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
cleanTerminalAssistantStopReceived = false;
|
|
252
|
+
compactionAfterFinalOpen = false;
|
|
244
253
|
return;
|
|
245
254
|
}
|
|
246
255
|
if (action === "start-drain")
|
|
@@ -314,11 +323,18 @@ export function runChildSession(input) {
|
|
|
314
323
|
compactionStartedReceived = true;
|
|
315
324
|
if (agentSettledReceived)
|
|
316
325
|
afterCompactionSettlement = true;
|
|
326
|
+
if (terminalAgentEndReceived)
|
|
327
|
+
compactionAfterFinalOpen = true;
|
|
317
328
|
}
|
|
318
329
|
if (event.type === "compaction_end" && event.willRetry === true) {
|
|
319
330
|
compactionStartedReceived = false;
|
|
320
331
|
afterCompactionSettlement = false;
|
|
321
332
|
}
|
|
333
|
+
if (event.type === "compaction_end" && event.willRetry !== true && compactionAfterFinalOpen) {
|
|
334
|
+
compactionAfterFinalOpen = false;
|
|
335
|
+
clearFinalDrainTimers();
|
|
336
|
+
startFinalDrain();
|
|
337
|
+
}
|
|
322
338
|
if (event.type === "turn_start" || event.type === "agent_start" || event.type === "auto_retry_start") {
|
|
323
339
|
queuedDrainHold = false;
|
|
324
340
|
}
|
|
@@ -331,7 +347,7 @@ export function runChildSession(input) {
|
|
|
331
347
|
agentSettledReceived = true;
|
|
332
348
|
afterCompactionSettlement = compactionStartedReceived;
|
|
333
349
|
}
|
|
334
|
-
applyChildLifecycle(lifecycleAction);
|
|
350
|
+
applyChildLifecycle(lifecycleAction, event);
|
|
335
351
|
if (isChildWatchdogStatusEvent(event)) {
|
|
336
352
|
if (!input.childWatchdog)
|
|
337
353
|
return;
|
|
@@ -100,7 +100,7 @@ export interface SubagentWaitDeps {
|
|
|
100
100
|
};
|
|
101
101
|
/** Injectable provider protocol surfaces for deterministic tests. */
|
|
102
102
|
backgroundWork?: {
|
|
103
|
-
snapshot(
|
|
103
|
+
snapshot(sessionIds: readonly string[], nowMs: number): BackgroundWorkSnapshot;
|
|
104
104
|
wakeChannels(): readonly string[];
|
|
105
105
|
};
|
|
106
106
|
/**
|
|
@@ -111,6 +111,7 @@ export interface SubagentWaitDeps {
|
|
|
111
111
|
*/
|
|
112
112
|
events?: WaitEventBus;
|
|
113
113
|
}
|
|
114
|
+
export declare function backgroundWorkForSession(deps: Pick<SubagentWaitDeps, "state" | "backgroundWork">, nowMs: number): BackgroundWorkSnapshot;
|
|
114
115
|
export declare function waitRunScopes(deps: Pick<SubagentWaitDeps, "asyncDirRoot" | "resultsDir" | "nestedRootRunId">): Array<{
|
|
115
116
|
asyncDirRoot: string;
|
|
116
117
|
resultsDir: string;
|
|
@@ -181,11 +181,14 @@ function hasSupervisorTool(run) {
|
|
|
181
181
|
function backgroundWorkIdentity(item) {
|
|
182
182
|
return `${item.provider}\0${item.sessionId}\0${item.id}`;
|
|
183
183
|
}
|
|
184
|
-
function backgroundWorkForSession(deps, nowMs) {
|
|
184
|
+
export function backgroundWorkForSession(deps, nowMs) {
|
|
185
185
|
const sessionId = deps.state.currentSessionId;
|
|
186
186
|
if (!sessionId)
|
|
187
187
|
throw new Error("bg_wait requires an active session identity to scope background work safely.");
|
|
188
|
-
|
|
188
|
+
// Providers may key items by the session file path or by getSessionId(); accept both.
|
|
189
|
+
const piSessionId = deps.state.supervisorOwnerSessionId;
|
|
190
|
+
const sessionIds = piSessionId ? [sessionId, piSessionId] : [sessionId];
|
|
191
|
+
return deps.backgroundWork?.snapshot(sessionIds, nowMs) ?? snapshotBackgroundWork(sessionIds, nowMs);
|
|
189
192
|
}
|
|
190
193
|
export function waitRunScopes(deps) {
|
|
191
194
|
return [
|
|
@@ -561,6 +561,8 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
|
|
|
561
561
|
let agentSettledReceived = false;
|
|
562
562
|
let queuedDrainHold = false;
|
|
563
563
|
let compactionStartedReceived = false;
|
|
564
|
+
let terminalAgentEndReceived = false;
|
|
565
|
+
let compactionAfterFinalOpen = false;
|
|
564
566
|
let finalDrainTimer;
|
|
565
567
|
let finalHardFinishTimer;
|
|
566
568
|
let watchdogTailTimer;
|
|
@@ -608,7 +610,8 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
|
|
|
608
610
|
finalDrainTimer = setTimeout(() => {
|
|
609
611
|
if (lifecycleFinished || sessionSettled)
|
|
610
612
|
return;
|
|
611
|
-
|
|
613
|
+
// Pi runs auto-compaction after the final agent_end and only then emits agent_settled.
|
|
614
|
+
if (capture.finalDrainHeld() || observeQueuedDrainHold() || compactionAfterFinalOpen) {
|
|
612
615
|
finalDrainTimer = undefined;
|
|
613
616
|
armFinalDrainTimer();
|
|
614
617
|
return;
|
|
@@ -623,9 +626,7 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
|
|
|
623
626
|
return;
|
|
624
627
|
settle(undefined, true);
|
|
625
628
|
}, HARD_FINISH_MS);
|
|
626
|
-
finalHardFinishTimer.unref?.();
|
|
627
629
|
}, FINAL_STOP_GRACE_MS);
|
|
628
|
-
finalDrainTimer.unref?.();
|
|
629
630
|
};
|
|
630
631
|
function armWatchdogTail() {
|
|
631
632
|
if ((!cleanTerminalAssistantStopReceived && !agentSettledReceived) || watchdogTailTimer || lifecycleFinished || sessionSettled)
|
|
@@ -644,12 +645,20 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
|
|
|
644
645
|
}, childWatchdog?.watchdogTailTimeoutMs ?? 120_000);
|
|
645
646
|
watchdogTailTimer.unref?.();
|
|
646
647
|
}
|
|
647
|
-
const applyChildLifecycle = (action) => {
|
|
648
|
+
const applyChildLifecycle = (action, event) => {
|
|
648
649
|
if (action === "cancel-drain") {
|
|
649
|
-
|
|
650
|
+
// After a clean final stop, a non-retrying agent_end may be the child's last event
|
|
651
|
+
// (Pi may not emit agent_settled), so it re-arms the drain instead of cancelling it.
|
|
652
|
+
terminalAgentEndReceived = event?.type === "agent_end" && event.willRetry !== true && cleanTerminalAssistantStopReceived;
|
|
650
653
|
agentSettledReceived = false;
|
|
651
654
|
clearFinalDrainTimers();
|
|
652
655
|
clearWatchdogTailTimer();
|
|
656
|
+
if (terminalAgentEndReceived) {
|
|
657
|
+
startFinalDrain();
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
cleanTerminalAssistantStopReceived = false;
|
|
661
|
+
compactionAfterFinalOpen = false;
|
|
653
662
|
return;
|
|
654
663
|
}
|
|
655
664
|
if (action === "start-drain")
|
|
@@ -925,11 +934,18 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
|
|
|
925
934
|
compactionStartedReceived = true;
|
|
926
935
|
if (agentSettledReceived)
|
|
927
936
|
afterCompactionSettlement = true;
|
|
937
|
+
if (terminalAgentEndReceived)
|
|
938
|
+
compactionAfterFinalOpen = true;
|
|
928
939
|
}
|
|
929
940
|
if (evt.type === "compaction_end" && evt.willRetry === true) {
|
|
930
941
|
compactionStartedReceived = false;
|
|
931
942
|
afterCompactionSettlement = false;
|
|
932
943
|
}
|
|
944
|
+
if (evt.type === "compaction_end" && evt.willRetry !== true && compactionAfterFinalOpen) {
|
|
945
|
+
compactionAfterFinalOpen = false;
|
|
946
|
+
clearFinalDrainTimers();
|
|
947
|
+
startFinalDrain();
|
|
948
|
+
}
|
|
933
949
|
if (evt.type === "turn_start" || evt.type === "agent_start" || evt.type === "auto_retry_start") {
|
|
934
950
|
queuedDrainHold = false;
|
|
935
951
|
}
|
|
@@ -956,7 +972,7 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
|
|
|
956
972
|
agentSettledReceived = true;
|
|
957
973
|
afterCompactionSettlement = compactionStartedReceived;
|
|
958
974
|
}
|
|
959
|
-
applyChildLifecycle(lifecycleAction);
|
|
975
|
+
applyChildLifecycle(lifecycleAction, evt);
|
|
960
976
|
if (isChildWatchdogStatusEvent(evt)) {
|
|
961
977
|
if (!childWatchdog)
|
|
962
978
|
return;
|
|
@@ -17,7 +17,7 @@ import { buildDoctorReport } from "../../extension/doctor.js";
|
|
|
17
17
|
import { readSubagentGuide } from "../../extension/subagent-guide.js";
|
|
18
18
|
import { isWorkflowScriptPath, normalizePublicSubagentExecution, validateWorkflowCapacityOverrides } from "../../extension/public-execution.js";
|
|
19
19
|
import { readReplyWorkflowScript } from "../../extension/reply-workflow-script.js";
|
|
20
|
-
import { disabledFeatureNotice, disabledFeatureUseError, resolveDisabledFeatureSurface } from "../../shared/disabled-features.js";
|
|
20
|
+
import { disabledFeatureNotice, disabledFeatureSectionNotice, disabledFeatureUseError, resolveDisabledFeatureSurface } from "../../shared/disabled-features.js";
|
|
21
21
|
import { runSync } from "./execution.js";
|
|
22
22
|
import { commandAction } from "./command-action.js";
|
|
23
23
|
import { handleWatchdogToolAction, WATCHDOG_TOOL_ACTIONS } from "../../watchdog/tool-actions.js";
|
|
@@ -87,7 +87,7 @@ import { formatHerdrMachineRunnerUnsupported, resolveMachinePlacement } from "..
|
|
|
87
87
|
import { lookupRunnerLauncher, resolveAgentRunnerLauncher } from "../shared/runner-launcher.js";
|
|
88
88
|
import { applyForceTopLevelAsyncOverride } from "../background/top-level-async.js";
|
|
89
89
|
import { handleMissionAction, MISSION_ACTIONS } from "../../missions/actions.js";
|
|
90
|
-
import { attachMissionToLaunchResult, prepareMissionLaunch, writeMissionAsyncBinding } from "../../missions/lifecycle.js";
|
|
90
|
+
import { attachMissionToLaunchResult, missionAcceptanceStatus, prepareMissionLaunch, writeMissionAsyncBinding } from "../../missions/lifecycle.js";
|
|
91
91
|
import { MissionNotFoundError, updateMission } from "../../missions/store.js";
|
|
92
92
|
import { createMissionWorkflowState } from "../../missions/workflow-state.js";
|
|
93
93
|
import { resolveAuthorityDecision } from "../../policy/authority.js";
|
|
@@ -678,9 +678,6 @@ function resolveForegroundResumeTarget(params, state, options = {}) {
|
|
|
678
678
|
...(child.capabilityCeiling ? { capabilityCeiling: child.capabilityCeiling } : {}),
|
|
679
679
|
};
|
|
680
680
|
}
|
|
681
|
-
function isAsyncRunNotFound(error) {
|
|
682
|
-
return error instanceof Error && error.message.startsWith("Async run not found.");
|
|
683
|
-
}
|
|
684
681
|
function isMissingExactAsyncStatusFile(error, requested) {
|
|
685
682
|
return error instanceof Error && error.message === `Status file not found for async run '${requested}'.`;
|
|
686
683
|
}
|
|
@@ -750,38 +747,12 @@ function resolveResumeTarget(params, state, options = {}) {
|
|
|
750
747
|
throw foregroundError;
|
|
751
748
|
return asyncTarget;
|
|
752
749
|
}
|
|
753
|
-
if (foregroundError && !isAsyncRunNotFound(asyncError))
|
|
754
|
-
throw foregroundError;
|
|
755
750
|
if (foregroundError)
|
|
756
751
|
throw foregroundError;
|
|
757
752
|
if (asyncError)
|
|
758
753
|
throw asyncError;
|
|
759
754
|
throw new Error("Run not found. Provide id or runId.");
|
|
760
755
|
}
|
|
761
|
-
function getAsyncInterruptTarget(state, runId, location, options = {}) {
|
|
762
|
-
if (location?.asyncDir) {
|
|
763
|
-
return {
|
|
764
|
-
asyncId: location.resolvedId ?? runId ?? path.basename(location.asyncDir),
|
|
765
|
-
asyncDir: location.asyncDir,
|
|
766
|
-
};
|
|
767
|
-
}
|
|
768
|
-
if (runId) {
|
|
769
|
-
const direct = state.asyncJobs.get(runId);
|
|
770
|
-
if (direct)
|
|
771
|
-
return { asyncId: direct.asyncId, asyncDir: direct.asyncDir };
|
|
772
|
-
if (options.fallbackToNewest === false)
|
|
773
|
-
return undefined;
|
|
774
|
-
}
|
|
775
|
-
let newest;
|
|
776
|
-
for (const job of state.asyncJobs.values()) {
|
|
777
|
-
if (job.status !== "running")
|
|
778
|
-
continue;
|
|
779
|
-
if (!newest || (job.updatedAt ?? 0) > newest.updatedAt) {
|
|
780
|
-
newest = { asyncId: job.asyncId, asyncDir: job.asyncDir, updatedAt: job.updatedAt ?? 0 };
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
return newest ? { asyncId: newest.asyncId, asyncDir: newest.asyncDir } : undefined;
|
|
784
|
-
}
|
|
785
756
|
function isStaleExtensionContextError(error) {
|
|
786
757
|
if (!(error instanceof Error))
|
|
787
758
|
return false;
|
|
@@ -853,13 +824,13 @@ function emitControlNotification(input) {
|
|
|
853
824
|
}
|
|
854
825
|
}
|
|
855
826
|
function interruptAsyncRun(state, runId, kill, location) {
|
|
856
|
-
const target =
|
|
827
|
+
const target = location?.asyncDir ? { asyncId: location.resolvedId ?? runId, asyncDir: location.asyncDir } : state.asyncJobs.get(runId);
|
|
857
828
|
if (!target)
|
|
858
829
|
return null;
|
|
859
830
|
const status = reconcileAsyncRun(target.asyncDir, omitUndefinedProperties({ kill })).status;
|
|
860
831
|
if (!status || status.state !== "running" || typeof status.pid !== "number") {
|
|
861
832
|
return {
|
|
862
|
-
content: [{ type: "text", text: `No running async run with an interrupt-capable pid was found for '${runId
|
|
833
|
+
content: [{ type: "text", text: `No running async run with an interrupt-capable pid was found for '${runId}'.` }],
|
|
863
834
|
isError: true,
|
|
864
835
|
details: { mode: "management", results: [] },
|
|
865
836
|
};
|
|
@@ -1086,7 +1057,7 @@ function appendStepToAsyncChain(input) {
|
|
|
1086
1057
|
worktreeBranchPrefix: input.deps.config.worktreeBranchPrefix,
|
|
1087
1058
|
asyncDir: resolved.location.asyncDir,
|
|
1088
1059
|
validateOutputBindings: false,
|
|
1089
|
-
capabilityCeiling: intersectSubagentCapabilityCeilings(status.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(asyncCtx.
|
|
1060
|
+
capabilityCeiling: intersectSubagentCapabilityCeilings(status.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(asyncCtx.parentSessionId)),
|
|
1090
1061
|
requiredExtensions: retainedRequiredExtensions,
|
|
1091
1062
|
}));
|
|
1092
1063
|
if ("error" in built) {
|
|
@@ -1562,7 +1533,7 @@ async function resumeExternalJobFollowUp(input) {
|
|
|
1562
1533
|
availableModels: input.ctx.modelRegistry.getAvailable().map(toModelInfo),
|
|
1563
1534
|
outputBaseDir: resolveSingleRunOutputBaseDir(input.deps, artifactsDir, runId),
|
|
1564
1535
|
...(input.absoluteDeadlineAt !== undefined ? { absoluteDeadlineAt: input.absoluteDeadlineAt } : {}),
|
|
1565
|
-
capabilityCeiling: resolveCurrentSubagentCapabilityCeiling(
|
|
1536
|
+
capabilityCeiling: resolveCurrentSubagentCapabilityCeiling(input.ctx.sessionManager.getSessionId() ?? undefined),
|
|
1566
1537
|
runFanoutBudget: createRunFanoutBudget(runId, resolveMaxSubagentSpawnsPerRun(input.deps.config.maxSubagentSpawnsPerRun)),
|
|
1567
1538
|
activeAsyncCapacity,
|
|
1568
1539
|
externalJobFollowUp: { sourceRunId: input.target.runId, sourceStepIndex: input.target.index, parentProviderJobId: externalJob.providerJobId, requestId, requestDigest },
|
|
@@ -1858,7 +1829,7 @@ async function resumeAsyncRun(input) {
|
|
|
1858
1829
|
childIntercomTarget: intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
|
|
1859
1830
|
globalConcurrencyLimit: input.deps.config.globalConcurrencyLimit,
|
|
1860
1831
|
runFanoutBudget: createRunFanoutBudget(runId, resolveMaxSubagentSpawnsPerRun(input.deps.config.maxSubagentSpawnsPerRun)),
|
|
1861
|
-
capabilityCeiling: intersectSubagentCapabilityCeilings("capabilityCeiling" in target ? target.capabilityCeiling : undefined, resolveCurrentSubagentCapabilityCeiling(input.
|
|
1832
|
+
capabilityCeiling: intersectSubagentCapabilityCeilings("capabilityCeiling" in target ? target.capabilityCeiling : undefined, resolveCurrentSubagentCapabilityCeiling(input.ctx.sessionManager.getSessionId() ?? undefined)),
|
|
1862
1833
|
thinkingCeiling: target.thinkingCeiling,
|
|
1863
1834
|
activeAsyncCapacity,
|
|
1864
1835
|
}));
|
|
@@ -2028,7 +1999,7 @@ async function resumeAsyncRun(input) {
|
|
|
2028
1999
|
...(input.params.toolBudget !== undefined ? { toolBudget: input.params.toolBudget } : {}),
|
|
2029
2000
|
// Recovery descriptors, remembered foreground runs, and current workflow roots
|
|
2030
2001
|
// preserve parent authority before the selected agent's descendant ceiling.
|
|
2031
|
-
capabilityCeiling: intersectSubagentCapabilityCeilings(recoveryDescriptor?.capabilityCeiling ?? (target.source === "foreground" || (target.source === "async" && target.mode === "workflow") ? target.capabilityCeiling : undefined), resolveCurrentSubagentCapabilityCeiling(input.
|
|
2002
|
+
capabilityCeiling: intersectSubagentCapabilityCeilings(recoveryDescriptor?.capabilityCeiling ?? (target.source === "foreground" || (target.source === "async" && target.mode === "workflow") ? target.capabilityCeiling : undefined), resolveCurrentSubagentCapabilityCeiling(input.ctx.sessionManager.getSessionId() ?? undefined)),
|
|
2032
2003
|
runFanoutBudget: input.params.runFanoutBudget ?? recoveryDescriptor?.runFanoutBudget ?? createRunFanoutBudget(runId, resolveMaxSubagentSpawnsPerRun(input.deps.config.maxSubagentSpawnsPerRun)),
|
|
2033
2004
|
parentWorkflowRunId: input.params.workflowParentRunId,
|
|
2034
2005
|
workflowKey: input.params.workflowKey,
|
|
@@ -5093,7 +5064,7 @@ export function createSubagentExecutor(deps) {
|
|
|
5093
5064
|
const eventsPath = path.join(asyncDir, "events.jsonl");
|
|
5094
5065
|
const startedAt = Date.now();
|
|
5095
5066
|
const currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
5096
|
-
const workflowCapabilityCeiling = intersectSubagentCapabilityCeilings(requestParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(
|
|
5067
|
+
const workflowCapabilityCeiling = intersectSubagentCapabilityCeilings(requestParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(ctx.sessionManager.getSessionId() ?? undefined));
|
|
5097
5068
|
const completionOwnerId = deps.state.completionOwnerId ?? currentCompletionOwnerId();
|
|
5098
5069
|
deps.state.completionOwnerId = completionOwnerId;
|
|
5099
5070
|
const workflowScriptSourceDigest = workflowScriptDigest(requestParams.workflowScript);
|
|
@@ -5971,6 +5942,7 @@ export function createSubagentExecutor(deps) {
|
|
|
5971
5942
|
...(result.details.results[0]?.sessionFile ? { sessionPath: result.details.results[0].sessionFile } : {}),
|
|
5972
5943
|
artifactPaths: child.artifactPaths,
|
|
5973
5944
|
...(["completed", "failed"].includes(childStatus) ? { completedAt: new Date().toISOString() } : {}),
|
|
5945
|
+
acceptanceStatus: missionAcceptanceStatus(result.details.results),
|
|
5974
5946
|
heartbeat: { status: childStatus, ...(childPhase ? { phase: childPhase } : {}) },
|
|
5975
5947
|
});
|
|
5976
5948
|
if (result.details.asyncId) {
|
|
@@ -6137,7 +6109,7 @@ export function createSubagentExecutor(deps) {
|
|
|
6137
6109
|
let lastProgressAt = 0;
|
|
6138
6110
|
let workflowProgressClosed = false;
|
|
6139
6111
|
const workflowDeadlineAt = timeout === undefined ? undefined : Date.now() + timeout;
|
|
6140
|
-
const workflowCapabilityCeiling = intersectSubagentCapabilityCeilings(requestParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(
|
|
6112
|
+
const workflowCapabilityCeiling = intersectSubagentCapabilityCeilings(requestParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(ctx.sessionManager.getSessionId() ?? undefined));
|
|
6141
6113
|
const sendWorkflowProgress = () => {
|
|
6142
6114
|
if (workflowProgressClosed)
|
|
6143
6115
|
return;
|
|
@@ -6294,6 +6266,7 @@ export function createSubagentExecutor(deps) {
|
|
|
6294
6266
|
...(result.details.results[0]?.sessionFile ? { sessionPath: result.details.results[0].sessionFile } : {}),
|
|
6295
6267
|
artifactPaths: child.artifactPaths,
|
|
6296
6268
|
...(["completed", "failed"].includes(childStatus) ? { completedAt: new Date().toISOString() } : {}),
|
|
6269
|
+
acceptanceStatus: missionAcceptanceStatus(result.details.results),
|
|
6297
6270
|
heartbeat: { status: childStatus, ...(childPhase ? { phase: childPhase } : {}) },
|
|
6298
6271
|
});
|
|
6299
6272
|
return child;
|
|
@@ -6689,10 +6662,7 @@ export function createSubagentExecutor(deps) {
|
|
|
6689
6662
|
if (action === "guide") {
|
|
6690
6663
|
try {
|
|
6691
6664
|
const guide = readSubagentGuide(paramsWithResolvedCwd.topic);
|
|
6692
|
-
const
|
|
6693
|
-
const fullNotice = topic === "tool-reference" ? disabledFeatureNotice(disabledFeatures) : undefined;
|
|
6694
|
-
// Sections are capped at 8,000 chars, so they get a one-line pointer instead of the full list.
|
|
6695
|
-
const notice = fullNotice && section !== undefined ? "Some options and actions documented here are disabled by config in this session; guide topic tool-reference lists them." : fullNotice;
|
|
6665
|
+
const notice = paramsWithResolvedCwd.topic?.includes("/") ? disabledFeatureSectionNotice(disabledFeatures) : disabledFeatureNotice(disabledFeatures);
|
|
6696
6666
|
return {
|
|
6697
6667
|
content: [{ type: "text", text: notice ? `${notice}\n\n${guide}` : guide }],
|
|
6698
6668
|
details: { mode: "management", results: [] },
|
|
@@ -7064,22 +7034,22 @@ export function createSubagentExecutor(deps) {
|
|
|
7064
7034
|
if (stopResult)
|
|
7065
7035
|
return stopResult;
|
|
7066
7036
|
return {
|
|
7067
|
-
content: [{ type: "text", text:
|
|
7037
|
+
content: [{ type: "text", text: `No stoppable async run found for '${targetRunId}'.` }],
|
|
7068
7038
|
isError: true,
|
|
7069
7039
|
details: { mode: "management", results: [] },
|
|
7070
7040
|
};
|
|
7071
7041
|
}
|
|
7072
7042
|
if (action === "interrupt") {
|
|
7073
7043
|
const targetRunId = paramsWithResolvedCwd.runId ?? paramsWithResolvedCwd.id;
|
|
7044
|
+
if (!targetRunId)
|
|
7045
|
+
return { content: [{ type: "text", text: "action='interrupt' requires id." }], isError: true, details: { mode: "management", results: [] } };
|
|
7074
7046
|
let resolved;
|
|
7075
|
-
|
|
7076
|
-
|
|
7077
|
-
|
|
7078
|
-
|
|
7079
|
-
|
|
7080
|
-
|
|
7081
|
-
return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
|
|
7082
|
-
}
|
|
7047
|
+
try {
|
|
7048
|
+
resolved = resolveSubagentRunId(targetRunId, omitUndefinedProperties({ state: deps.state, nested: nestedResolutionScopeForExecutor(deps) }));
|
|
7049
|
+
}
|
|
7050
|
+
catch (error) {
|
|
7051
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
7052
|
+
return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
|
|
7083
7053
|
}
|
|
7084
7054
|
if (resolved?.kind === "nested")
|
|
7085
7055
|
return interruptNestedRun(resolved);
|
|
@@ -7104,7 +7074,7 @@ export function createSubagentExecutor(deps) {
|
|
|
7104
7074
|
if (asyncInterruptResult)
|
|
7105
7075
|
return asyncInterruptResult;
|
|
7106
7076
|
return {
|
|
7107
|
-
content: [{ type: "text", text:
|
|
7077
|
+
content: [{ type: "text", text: `No interrupt-capable run found for '${targetRunId}'.` }],
|
|
7108
7078
|
isError: true,
|
|
7109
7079
|
details: { mode: "management", results: [] },
|
|
7110
7080
|
};
|
|
@@ -7120,7 +7090,7 @@ export function createSubagentExecutor(deps) {
|
|
|
7120
7090
|
...ctx,
|
|
7121
7091
|
cwd: requestCwd,
|
|
7122
7092
|
config: deps.config,
|
|
7123
|
-
|
|
7093
|
+
piSessionId: ctx.sessionManager.getSessionId() ?? undefined,
|
|
7124
7094
|
discoverAgentsAll: deps.discoverAgentsAll,
|
|
7125
7095
|
runtimeAgentOwner: deps.pi,
|
|
7126
7096
|
onAgentsChanged: deps.onAgentsChanged,
|
|
@@ -7221,7 +7191,7 @@ export function createSubagentExecutor(deps) {
|
|
|
7221
7191
|
let prepareForkSessionForIndex = async () => { };
|
|
7222
7192
|
// Check before pruner/session preparation, then recheck immediately before each fork
|
|
7223
7193
|
// because the session capability ceiling can tighten while preparation awaits.
|
|
7224
|
-
const launchCapabilityCeiling = () => intersectSubagentCapabilityCeilings(intersectSubagentCapabilityCeilings(effectiveParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(
|
|
7194
|
+
const launchCapabilityCeiling = () => intersectSubagentCapabilityCeilings(intersectSubagentCapabilityCeilings(effectiveParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(requestPiSessionId)), deps.childRuntime?.capabilityCeiling);
|
|
7225
7195
|
const assertLaunchableAgentsAllowedBeforeFork = () => {
|
|
7226
7196
|
if (!contextPolicy.usesFork)
|
|
7227
7197
|
return;
|
|
@@ -7502,7 +7472,7 @@ export function createSubagentExecutor(deps) {
|
|
|
7502
7472
|
scopedModelIds: scopedModelIdsFromContext(ctx),
|
|
7503
7473
|
parentSessionId: requestSessionId,
|
|
7504
7474
|
parentPiSessionId: requestPiSessionId,
|
|
7505
|
-
capabilityCeiling: intersectSubagentCapabilityCeilings(effectiveParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(
|
|
7475
|
+
capabilityCeiling: intersectSubagentCapabilityCeilings(effectiveParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(requestPiSessionId)),
|
|
7506
7476
|
runFanoutBudget,
|
|
7507
7477
|
topLevelAsyncCapacityEligible,
|
|
7508
7478
|
activeAsyncCapacity,
|
|
@@ -48,7 +48,8 @@ export declare function parseSubagentCapabilityCeiling(value: unknown, field?: s
|
|
|
48
48
|
export declare function registerSubagentCapabilityCeiling(options: RegisterSubagentCapabilityCeilingOptions): SubagentCapabilityCeilingHandle;
|
|
49
49
|
export declare function intersectSubagentCapabilityCeilings(...ceilings: Array<ResolvedSubagentCapabilityCeiling | undefined>): ResolvedSubagentCapabilityCeiling | undefined;
|
|
50
50
|
export declare function resolveSubagentCapabilityCeiling(sessionId: string | undefined, inherited?: ResolvedSubagentCapabilityCeiling): ResolvedSubagentCapabilityCeiling | undefined;
|
|
51
|
-
|
|
51
|
+
/** Ceilings are registered under `ctx.sessionManager.getSessionId()`; pass that id, never the session file path. */
|
|
52
|
+
export declare function resolveCurrentSubagentCapabilityCeiling(piSessionId: string | undefined): ResolvedSubagentCapabilityCeiling | undefined;
|
|
52
53
|
export declare function isAgentAllowedByCapabilityCeiling(agentName: string, ceiling: ResolvedSubagentCapabilityCeiling | undefined): boolean;
|
|
53
54
|
export declare function capabilityCeilingAgentRestrictionMessage(agentName: string, ceiling: ResolvedSubagentCapabilityCeiling | undefined): string | undefined;
|
|
54
55
|
export declare function assertAgentAllowedByCapabilityCeiling(agentName: string, ceiling: ResolvedSubagentCapabilityCeiling | undefined): void;
|
|
@@ -135,8 +135,9 @@ export function resolveSubagentCapabilityCeiling(sessionId, inherited) {
|
|
|
135
135
|
}
|
|
136
136
|
return intersectSubagentCapabilityCeilings(inherited, ...active);
|
|
137
137
|
}
|
|
138
|
-
|
|
139
|
-
|
|
138
|
+
/** Ceilings are registered under `ctx.sessionManager.getSessionId()`; pass that id, never the session file path. */
|
|
139
|
+
export function resolveCurrentSubagentCapabilityCeiling(piSessionId) {
|
|
140
|
+
return resolveSubagentCapabilityCeiling(piSessionId, undefined);
|
|
140
141
|
}
|
|
141
142
|
export function isAgentAllowedByCapabilityCeiling(agentName, ceiling) {
|
|
142
143
|
return ceiling?.allowedAgents === undefined || ceiling.allowedAgents.includes(agentName);
|
|
@@ -45,7 +45,7 @@ function escapeXmlAttr(value) {
|
|
|
45
45
|
function inheritedCapabilityCeiling(inherited) {
|
|
46
46
|
return inherited?.capabilityCeiling;
|
|
47
47
|
}
|
|
48
|
-
/** Environment values external child extensions read
|
|
48
|
+
/** Environment values external child extensions read in the runner, which hosts only children. */
|
|
49
49
|
function childProcessEnv(input, toolPlan) {
|
|
50
50
|
const env = {};
|
|
51
51
|
env[PI_SUBAGENT_EXTENSION_BINDINGS_ENV] = encodeExtensionBindings(input.extensionBindings);
|
|
@@ -222,7 +222,10 @@ export function buildInProcessChildLaunch(input) {
|
|
|
222
222
|
requiredExtensions: toolPlan.requiredExtensions,
|
|
223
223
|
ambientExtensions,
|
|
224
224
|
hooks: capturedHooks.hooks,
|
|
225
|
-
|
|
225
|
+
// A foreground child gets only its own bindings, so it never sees those of the session that launched it.
|
|
226
|
+
...(input.host === "runner"
|
|
227
|
+
? { processEnv: childProcessEnv(input, toolPlan) }
|
|
228
|
+
: { processEnv: { [PI_SUBAGENT_EXTENSION_BINDINGS_ENV]: encodeExtensionBindings(input.extensionBindings) }, restoreProcessEnv: true }),
|
|
226
229
|
runtime: config,
|
|
227
230
|
noSkills: !input.inheritSkills,
|
|
228
231
|
noContextFiles: !input.inheritProjectContext,
|
|
@@ -80,6 +80,12 @@ export interface ChildSessionLaunch {
|
|
|
80
80
|
* window one at a time. An undefined value removes the variable.
|
|
81
81
|
*/
|
|
82
82
|
processEnv?: Record<string, string | undefined>;
|
|
83
|
+
/**
|
|
84
|
+
* Restore the hosting process's previous values of `processEnv` once that
|
|
85
|
+
* window ends, so a child hosted in the parent's own process leaves the
|
|
86
|
+
* parent's environment as it found it.
|
|
87
|
+
*/
|
|
88
|
+
restoreProcessEnv?: boolean;
|
|
83
89
|
/** The typed runtime config the hooks were built from; informational for factories. */
|
|
84
90
|
runtime: ChildRuntimeConfig;
|
|
85
91
|
onExtensionError?: (error: ChildSessionExtensionError) => void;
|