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.
Files changed (94) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/bundle/agents.js +5 -5
  3. package/bundle/background-work.js +1 -1
  4. package/bundle/capability-ceiling.js +1 -1
  5. package/bundle/child-tool-plan.js +4 -4
  6. package/bundle/{chunk-RMQFMMAT.js → chunk-2D52JFLF.js} +231 -10
  7. package/bundle/{chunk-52UBALJL.js → chunk-2DHZTCOL.js} +2 -2
  8. package/bundle/{chunk-43SCHIMD.js → chunk-2S4K53MV.js} +1 -1
  9. package/bundle/{chunk-IVAE5Z6Q.js → chunk-5YIITCPD.js} +31 -27
  10. package/bundle/{chunk-JYUKW2SY.js → chunk-AHIFYGR3.js} +13 -13
  11. package/bundle/{chunk-XSWOTIA3.js → chunk-C5P5CHYK.js} +1 -1
  12. package/bundle/{chunk-PDZWJCHD.js → chunk-C7JMPU5F.js} +10 -7
  13. package/bundle/{chunk-5TYMSZX3.js → chunk-D3JAFGMO.js} +2 -2
  14. package/bundle/{chunk-OYF6AFDY.js → chunk-F4ASL6OV.js} +2 -2
  15. package/bundle/{chunk-ZIV4J4HG.js → chunk-F5DBXJ2G.js} +51 -34
  16. package/bundle/{chunk-YKEWIB4M.js → chunk-G2Z6ZH26.js} +1 -1
  17. package/bundle/{chunk-G4GZD3OJ.js → chunk-IPGZMQXV.js} +11 -11
  18. package/bundle/{chunk-5LFLVSTM.js → chunk-JEZOSEWG.js} +2 -0
  19. package/bundle/{chunk-PLCCBLPY.js → chunk-LFDBD4PW.js} +2 -2
  20. package/bundle/{chunk-CIZQXJ5Q.js → chunk-LNRJCP2Y.js} +56 -20
  21. package/bundle/{chunk-UTT254JE.js → chunk-MABL3FUM.js} +8 -4
  22. package/bundle/{chunk-3JCDWF4V.js → chunk-NPIYWVOD.js} +12 -18
  23. package/bundle/{chunk-UCPP4GNU.js → chunk-QP5ZWNIS.js} +12 -5
  24. package/bundle/{chunk-Z7KVHQHL.js → chunk-QXASAHIA.js} +2 -2
  25. package/bundle/{chunk-46XKCPJR.js → chunk-RNIZLSEA.js} +2221 -3260
  26. package/bundle/{chunk-GJECHSAR.js → chunk-SSG7IEP5.js} +2 -2
  27. package/bundle/{chunk-VSUSLDNW.js → chunk-UFL7IYCW.js} +10 -8
  28. package/bundle/{chunk-QE4AGIMG.js → chunk-UUP372DR.js} +482 -705
  29. package/bundle/{chunk-6XJB7PIX.js → chunk-UYK5MZWE.js} +16 -12
  30. package/bundle/{chunk-4SDU2BZX.js → chunk-V2BZYLEP.js} +3 -3
  31. package/bundle/{chunk-FXUXCX2K.js → chunk-VLFZIN6D.js} +6 -6
  32. package/bundle/{chunk-N2BN7BF5.js → chunk-XA3DY6UG.js} +7 -5
  33. package/bundle/{chunk-PBK2I7WY.js → chunk-YXFJVCOL.js} +6 -6
  34. package/bundle/{chunk-4Y2ROR7L.js → chunk-ZVOAMTUQ.js} +5 -5
  35. package/bundle/control-channel.js +4 -4
  36. package/bundle/index.js +1 -1
  37. package/bundle/intercom-bridge.js +4 -4
  38. package/bundle/preflight.js +10 -10
  39. package/bundle/project-panes.js +5 -5
  40. package/bundle/shared-types.js +1 -1
  41. package/docs/configuration.md +13 -2
  42. package/docs/extension-api.md +21 -4
  43. package/docs/missions.md +2 -0
  44. package/docs/observability.md +1 -0
  45. package/docs/tool-reference.md +2 -2
  46. package/package.json +1 -1
  47. package/skills/pi-subagents/SKILL.md +6 -6
  48. package/src/agents/agent-management.d.ts +1 -1
  49. package/src/agents/agent-management.js +10 -16
  50. package/src/agents/agent-serializer.js +1 -2
  51. package/src/agents/agents.js +20 -5
  52. package/src/agents/frontmatter.js +22 -18
  53. package/src/api/background-work.d.ts +9 -6
  54. package/src/api/background-work.js +14 -7
  55. package/src/extension/index.js +6 -3
  56. package/src/extension/public-execution.d.ts +2 -2
  57. package/src/extension/public-execution.js +3 -3
  58. package/src/extension/reply-workflow-script.js +4 -5
  59. package/src/extension/rpc.js +3 -1
  60. package/src/extension/subagent-options.js +17 -4
  61. package/src/missions/actions.js +7 -2
  62. package/src/missions/lifecycle.d.ts +11 -0
  63. package/src/missions/lifecycle.js +37 -6
  64. package/src/missions/store.js +1 -0
  65. package/src/missions/types.d.ts +2 -0
  66. package/src/runs/background/async-execution.d.ts +1 -1
  67. package/src/runs/background/async-execution.js +7 -4
  68. package/src/runs/background/async-job-tracker.js +2 -1
  69. package/src/runs/background/async-resume.js +3 -2
  70. package/src/runs/background/auto-drain.js +2 -3
  71. package/src/runs/background/fleet-view.js +1 -1
  72. package/src/runs/background/retained-children.js +1 -1
  73. package/src/runs/background/run-child-session.js +22 -6
  74. package/src/runs/background/subagent-wait.d.ts +2 -1
  75. package/src/runs/background/subagent-wait.js +5 -2
  76. package/src/runs/foreground/execution.js +22 -6
  77. package/src/runs/foreground/subagent-executor.js +26 -56
  78. package/src/runs/shared/capability-ceiling.d.ts +2 -1
  79. package/src/runs/shared/capability-ceiling.js +3 -2
  80. package/src/runs/shared/child-launch.js +5 -2
  81. package/src/runs/shared/child-session.d.ts +6 -0
  82. package/src/runs/shared/child-session.js +20 -4
  83. package/src/runs/shared/herdr-machine.d.ts +2 -0
  84. package/src/runs/shared/herdr-machine.js +2 -0
  85. package/src/runs/shared/herdr-placed-run.js +11 -4
  86. package/src/runs/shared/parallel-handoff.d.ts +1 -1
  87. package/src/runs/shared/parallel-handoff.js +10 -6
  88. package/src/runs/shared/subagent-prompt-runtime.js +11 -17
  89. package/src/shared/disabled-features.d.ts +3 -1
  90. package/src/shared/disabled-features.js +11 -3
  91. package/src/shared/types.d.ts +4 -2
  92. package/src/shared/types.js +2 -0
  93. package/src/workflows/scripted-workflow.js +13 -1071
  94. package/workflow-script-worker.cjs +1068 -0
@@ -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 used by permission-system ask forwarding. */
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 capabilityCeiling = params.capabilityCeiling ?? resolveCurrentSubagentCapabilityCeiling(ctx.currentSessionId);
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
- throw new Error("Async run not found. Provide id or dir.");
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 || snapshotBackgroundWork(sessionId, nowMs).items.length > 0 || detachedForeground;
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.args === undefined ? "" : ` ${stringifyJsonPreview(entry.args)}`}]` });
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
- if (input.launch.capture.finalDrainHeld() || observeQueuedDrainHold()) {
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
- cleanTerminalAssistantStopReceived = false;
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(sessionId: string, nowMs: number): BackgroundWorkSnapshot;
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
- return deps.backgroundWork?.snapshot(sessionId, nowMs) ?? snapshotBackgroundWork(sessionId, nowMs);
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
- if (capture.finalDrainHeld() || observeQueuedDrainHold()) {
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
- cleanTerminalAssistantStopReceived = false;
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 = getAsyncInterruptTarget(state, runId, location, { fallbackToNewest: runId === undefined });
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 ?? "current"}'.` }],
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.currentSessionId)),
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(currentSessionId),
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.deps.state.currentSessionId)),
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.deps.state.currentSessionId)),
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(currentSessionId));
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(resolveCurrentSessionId(ctx.sessionManager)));
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 [topic, section] = paramsWithResolvedCwd.topic?.split("/") ?? [];
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: "No stoppable async run found in this session." }],
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
- if (targetRunId) {
7076
- try {
7077
- resolved = resolveSubagentRunId(targetRunId, omitUndefinedProperties({ state: deps.state, nested: nestedResolutionScopeForExecutor(deps) }));
7078
- }
7079
- catch (error) {
7080
- const message = error instanceof Error ? error.message : String(error);
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: "No interrupt-capable run found in this session." }],
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
- currentSessionId: deps.state.currentSessionId ?? ctx.sessionManager.getSessionId() ?? undefined,
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(requestSessionId)), deps.childRuntime?.capabilityCeiling);
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(requestSessionId)),
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
- export declare function resolveCurrentSubagentCapabilityCeiling(sessionId: string | undefined): ResolvedSubagentCapabilityCeiling | undefined;
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
- export function resolveCurrentSubagentCapabilityCeiling(sessionId) {
139
- return resolveSubagentCapabilityCeiling(sessionId, undefined);
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; only the runner applies them. */
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
- ...(input.host === "runner" ? { processEnv: childProcessEnv(input, toolPlan) } : {}),
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;