@sema-agent/core 7.17.7 → 7.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/CHANGELOG.md +140 -13
  2. package/dist/agents/send-message-tool.js +1 -0
  3. package/dist/agents/subagent.d.ts +1 -0
  4. package/dist/agents/subagent.js +5 -0
  5. package/dist/agents/teacher.js +1 -0
  6. package/dist/agents/verify.js +3 -0
  7. package/dist/brain/route-adjudicator.d.ts +18 -0
  8. package/dist/brain/route-adjudicator.js +16 -0
  9. package/dist/core/agent-definition.d.ts +10 -0
  10. package/dist/core/artifact-host.d.ts +41 -4
  11. package/dist/core/artifact-host.js +18 -2
  12. package/dist/core/auto-mode.d.ts +141 -45
  13. package/dist/core/auto-mode.js +109 -3
  14. package/dist/core/checkpoint-store.d.ts +16 -2
  15. package/dist/core/engine-notice.d.ts +47 -1
  16. package/dist/core/engine-notice.js +7 -0
  17. package/dist/core/gate-lanes.js +2 -1
  18. package/dist/core/gate-outcome.d.ts +17 -2
  19. package/dist/core/gate-outcome.js +16 -0
  20. package/dist/core/governance-codes.d.ts +1 -1
  21. package/dist/core/governance-codes.js +8 -0
  22. package/dist/core/hook-seats.d.ts +66 -0
  23. package/dist/core/hook-seats.js +37 -0
  24. package/dist/core/runner/advertised-writable-dirs.d.ts +8 -3
  25. package/dist/core/runner/assemble-result.d.ts +2 -1
  26. package/dist/core/runner/assemble-result.js +1 -1
  27. package/dist/core/runner/classifier-ladder.d.ts +89 -0
  28. package/dist/core/runner/classifier-ladder.js +65 -0
  29. package/dist/core/runner/contracts.d.ts +69 -13
  30. package/dist/core/runner/decide-continuation.d.ts +1 -3
  31. package/dist/core/runner/decide-continuation.js +1 -7
  32. package/dist/core/runner/deferral-census.d.ts +52 -0
  33. package/dist/core/runner/deferral-census.js +44 -0
  34. package/dist/core/runner/derived-route-fallback.d.ts +8 -4
  35. package/dist/core/runner/derived-route-fallback.js +8 -3
  36. package/dist/core/runner/gate-exit.d.ts +12 -0
  37. package/dist/core/runner/gate-exit.js +6 -3
  38. package/dist/core/runner/prepare-artifact.d.ts +10 -2
  39. package/dist/core/runner/prepare-artifact.js +5 -4
  40. package/dist/core/runner/prepare-caps-and-workflow.d.ts +4 -2
  41. package/dist/core/runner/prepare-caps-and-workflow.js +30 -31
  42. package/dist/core/runner/prepare-config-doors.d.ts +1 -1
  43. package/dist/core/runner/prepare-config-doors.js +5 -3
  44. package/dist/core/runner/prepare-defer-classify.d.ts +13 -19
  45. package/dist/core/runner/prepare-defer-classify.js +8 -3
  46. package/dist/core/runner/prepare-gate-stations.d.ts +26 -0
  47. package/dist/core/runner/prepare-gate-stations.js +1 -1
  48. package/dist/core/runner/prepare-hands-readface.d.ts +24 -7
  49. package/dist/core/runner/prepare-hands-readface.js +60 -24
  50. package/dist/core/runner/prepare-inherited-gate.d.ts +17 -4
  51. package/dist/core/runner/prepare-inherited-gate.js +10 -2
  52. package/dist/core/runner/prepare-lsp.d.ts +2 -2
  53. package/dist/core/runner/prepare-lsp.js +1 -1
  54. package/dist/core/runner/prepare-policy-chain.js +2 -0
  55. package/dist/core/runner/prepare-project-context.js +4 -2
  56. package/dist/core/runner/prepare-prompt-inputs.d.ts +4 -2
  57. package/dist/core/runner/prepare-prompt-inputs.js +4 -1
  58. package/dist/core/runner/prepare-run-refs.d.ts +9 -1
  59. package/dist/core/runner/prepare-run-refs.js +3 -2
  60. package/dist/core/runner/prepare-suspend-saga.d.ts +5 -0
  61. package/dist/core/runner/prepare-suspend-saga.js +5 -2
  62. package/dist/core/runner/prepare-task.js +18 -13
  63. package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +5 -0
  64. package/dist/core/runner/prepare-tool-disclosure-mount.js +10 -2
  65. package/dist/core/runner/prepare-turn-wiring.d.ts +8 -4
  66. package/dist/core/runner/prepare-turn-wiring.js +3 -2
  67. package/dist/core/runner/prepare-wiring-manifest.d.ts +5 -5
  68. package/dist/core/runner/prepare-wiring-manifest.js +5 -2
  69. package/dist/core/runner/resume-checkpoint-screen.js +7 -0
  70. package/dist/core/runner/run-harness-handlers.js +4 -1
  71. package/dist/core/runner/run-identity-wiring.js +6 -2
  72. package/dist/core/runner/run-leg.d.ts +2 -0
  73. package/dist/core/runner/run-leg.js +35 -41
  74. package/dist/core/runner/run-notification-lane.js +55 -27
  75. package/dist/core/runner/run-settle-and-teardown.js +1 -0
  76. package/dist/core/runner/run-terminal-adoption.js +16 -13
  77. package/dist/core/runner/run-turn-boundary.js +3 -1
  78. package/dist/core/runner/runtask.d.ts +6 -5
  79. package/dist/core/runner/runtask.js +26 -14
  80. package/dist/core/runner/skill-base-dir.d.ts +54 -0
  81. package/dist/core/runner/skill-base-dir.js +42 -0
  82. package/dist/core/runner/synthetic-tools.d.ts +16 -19
  83. package/dist/core/runner/synthetic-tools.js +18 -10
  84. package/dist/core/runner/tool-defer-gate.d.ts +22 -9
  85. package/dist/core/runner/tool-defer-gate.js +15 -5
  86. package/dist/core/runner/tool-disclosure.d.ts +5 -0
  87. package/dist/core/runner/tool-disclosure.js +2 -0
  88. package/dist/core/runner/tool-progress-lane.d.ts +42 -0
  89. package/dist/core/runner/tool-progress-lane.js +61 -0
  90. package/dist/core/runner-deps.d.ts +48 -7
  91. package/dist/core/swappable-deps.d.ts +9 -7
  92. package/dist/core/swappable-deps.js +7 -3
  93. package/dist/core/task-event.d.ts +96 -29
  94. package/dist/core/task-notification.d.ts +142 -16
  95. package/dist/core/task-notification.js +106 -0
  96. package/dist/core/task-result.d.ts +9 -1
  97. package/dist/core/task-spec.d.ts +20 -3
  98. package/dist/core/tighten-task-spec.js +7 -5
  99. package/dist/core/tool-model-gate.d.ts +1 -1
  100. package/dist/core/tool-search-setting.d.ts +78 -0
  101. package/dist/core/tool-search-setting.js +90 -0
  102. package/dist/core/tool-spec.d.ts +8 -0
  103. package/dist/core/types.d.ts +1 -1
  104. package/dist/core/types.js +1 -1
  105. package/dist/core/wiring-manifest.d.ts +46 -0
  106. package/dist/core/wiring-manifest.js +9 -1
  107. package/dist/engine/execution-env/node-execution-env.js +10 -4
  108. package/dist/engine/harness/agent-harness.d.ts +42 -0
  109. package/dist/engine/harness/agent-harness.js +61 -29
  110. package/dist/engine/loop/agent-loop.d.ts +68 -3
  111. package/dist/engine/loop/agent-loop.js +109 -16
  112. package/dist/index.d.ts +10 -5
  113. package/dist/index.js +9 -5
  114. package/dist/internal/harness.d.ts +1 -1
  115. package/dist/orchestration/run-workflow-tool.d.ts +3 -0
  116. package/dist/orchestration/run-workflow-tool.js +6 -1
  117. package/dist/orchestration/workflow-governance.d.ts +7 -1
  118. package/dist/orchestration/workflow-governance.js +9 -5
  119. package/dist/orchestration/workflow-primitives.d.ts +3 -0
  120. package/dist/orchestration/workflow-primitives.js +6 -1
  121. package/dist/orchestration/workflow-types.d.ts +48 -13
  122. package/dist/orchestration/workflow-types.js +1 -1
  123. package/dist/orchestration/workflow.js +45 -17
  124. package/dist/prompt-assembly/epoch.js +2 -0
  125. package/dist/prompt-assembly/packs/sema-default.js +2 -2
  126. package/dist/prompt-assembly/section-usage.d.ts +26 -0
  127. package/dist/prompt-assembly/section-usage.js +10 -0
  128. package/dist/prompt-assembly/types.d.ts +8 -6
  129. package/dist/prompts/default.d.ts +13 -0
  130. package/dist/prompts/default.js +16 -8
  131. package/dist/tools/artifact/artifact-text.d.ts +10 -1
  132. package/dist/tools/artifact/artifact-text.js +6 -3
  133. package/dist/tools/artifact/artifact-tool.d.ts +12 -2
  134. package/dist/tools/artifact/artifact-tool.js +203 -70
  135. package/dist/tools/artifact/conformance.d.ts +56 -0
  136. package/dist/tools/artifact/conformance.js +349 -0
  137. package/dist/tools/artifact/local-stub.js +1 -1
  138. package/dist/tools/artifact/reject-breaker.d.ts +28 -0
  139. package/dist/tools/artifact/reject-breaker.js +42 -0
  140. package/dist/tools/fs/bash-lexer.d.ts +132 -0
  141. package/dist/tools/fs/bash-lexer.js +563 -0
  142. package/dist/tools/fs/bash-program-position.d.ts +142 -0
  143. package/dist/tools/fs/bash-program-position.js +125 -0
  144. package/dist/tools/fs/bash-readonly-classifier.d.ts +54 -16
  145. package/dist/tools/fs/bash-readonly-classifier.js +121 -552
  146. package/dist/tools/fs/containment-face.d.ts +89 -0
  147. package/dist/tools/fs/containment-face.js +81 -0
  148. package/dist/tools/fs/fs-bash.d.ts +46 -1
  149. package/dist/tools/fs/fs-bash.js +158 -22
  150. package/dist/tools/fs/fs-read.d.ts +1 -1
  151. package/dist/tools/fs/fs-search-tools.d.ts +3 -3
  152. package/dist/tools/fs/fs-write.d.ts +4 -3
  153. package/dist/tools/fs/fs-write.js +9 -9
  154. package/dist/tools/fs/index.d.ts +24 -5
  155. package/dist/tools/fs/index.js +18 -6
  156. package/dist/tools/fs/repo-map.d.ts +2 -2
  157. package/dist/tools/fs/safety.d.ts +3 -3
  158. package/dist/tools/fs/safety.js +4 -4
  159. package/package.json +1 -1
  160. package/test/export-surface.snapshot.json +85 -9
  161. package/wire-changes/core@7.17.7.json +49 -0
  162. package/dist/tools/fs/read-face.d.ts +0 -68
  163. package/dist/tools/fs/read-face.js +0 -47
@@ -1,13 +1,14 @@
1
1
  import { bindPeerLaneDrain } from "../../agents/peer-session-drain.js";
2
2
  import { resolveCheckpointStore } from "../checkpoint-store.js";
3
3
  import { createHookEnvCapabilities, mintHookInvocationIdentity } from "../hooks.js";
4
+ import { wiredHookEntries } from "../hook-seats.js";
4
5
  import { isRemoteExecutionEnv, missingRestoreSurface } from "../remote-env.js";
5
6
  import { isVolatileOffloadStore } from "../tool-result-store.js";
6
7
  import { durableMandateSourceOf, isLiveApproverSeat } from "../tool-policy.js";
7
8
  import { deliverEngineNotice } from "../types.js";
8
9
  import { inlineUntrusted } from "../untrusted-text.js";
9
10
  import { GLOBAL_USAGE_KEY, usageRetryAfterMs, windowsGovernCost } from "../usage-window-store.js";
10
- import { countElicitOptIns, deriveWiringManifest, resolveAskSeamForm, resolveDeclaredDurability, resolveElicitSeam, resolveQuestionSeam, resolveSubagentTranscriptTier } from "../wiring-manifest.js";
11
+ import { LSP_CONTRACT_ID, countElicitOptIns, deriveLspSeam, deriveWiringManifest, resolveAskSeamForm, resolveDeclaredDurability, resolveElicitSeam, resolveQuestionSeam, resolveSubagentTranscriptTier } from "../wiring-manifest.js";
11
12
  import { DEFAULT_IRREVERSIBLE_SCOPE } from "./checkpoint-scope.js";
12
13
  export function mcpManifestEntries(entries, statuses) {
13
14
  return (entries ?? []).map((entry) => {
@@ -110,7 +111,7 @@ function announceDurableGateUnavailable(args) {
110
111
  });
111
112
  }
112
113
  export function prepareWiringManifest(input) {
113
- const { spec, deps, internals, resume, sessionId, runId, hostTaskId, taskScope, hooks, preToolUseObservational, policyLayers, effectivePolicy, mcp, irreversibleTools, egressTools, toolEffects, ownedEnv, executionEnv, offloadStore, onceLedger, usageWindows, envLifetimeSuspendAt, runtimeCaps, resolvedInteractionPosture, durableQuestionFace, liveQuestionFace, checkpointStore, sessions, modelGateManifest, autoModeArmReason, lockedPreflight, peerLaneRefs, peerLaneActive, harness, pausedRef, frozenOnAsk, liveInheritedGate, delegation, toolRoster } = input;
114
+ const { spec, deps, internals, resume, sessionId, runId, hostTaskId, taskScope, hooks, preToolUseObservational, policyLayers, effectivePolicy, mcp, irreversibleTools, egressTools, toolEffects, ownedEnv, executionEnv, offloadStore, onceLedger, usageWindows, envLifetimeSuspendAt, runtimeCaps, resolvedInteractionPosture, durableQuestionFace, liveQuestionFace, checkpointStore, sessions, modelGateManifest, autoModeArmReason, lockedPreflight, peerLaneRefs, peerLaneActive, harness, pausedRef, frozenOnAsk, liveInheritedGate, delegation, toolRoster, lspManagerWired, lspDiagnosticsLive } = input;
114
115
  const onAsk = spec.onAsk ?? deps.onAsk;
115
116
  const handWriteTools = toolRoster.entries.filter((e) => e.mountedBy === "hands" && (e.effect === "write" || e.effect === "idempotent")).map((e) => e.name);
116
117
  const hasEffectAwareGate = Boolean(policyLayers.length > 0 || (hooks?.preToolUse !== undefined && !preToolUseObservational));
@@ -204,6 +205,8 @@ export function prepareWiringManifest(input) {
204
205
  autoMode: { ...autoModeArmReason },
205
206
  mcp: mcpManifestEntries(lockedPreflight.mcp, mcp.statuses),
206
207
  tools: toolRoster,
208
+ hooks: wiredHookEntries(hooks, spec.hooks !== undefined ? "spec" : "deps", preToolUseObservational),
209
+ ...(lspManagerWired ? { lsp: deriveLspSeam(toolRoster.entries.some((e) => e.contract.contractId === LSP_CONTRACT_ID), lspDiagnosticsLive) } : {}),
207
210
  });
208
211
  peerLaneRefs.askEffective = wiringManifest.ask.effective;
209
212
  if (peerLaneActive && internals?.onTaskNotification !== undefined) {
@@ -38,6 +38,13 @@ export function resumeCheckpointScreen(input) {
38
38
  ? `a v${checkpointVersionOf(cp)} checkpoint must carry a well-formed read-face section (state.readFace) — this row's is missing or malformed; refusing to resume a damaged read-face row (corruption / downgrade guard), the checkpoint stays pending`
39
39
  : `a v${checkpointVersionOf(cp)} checkpoint carries a read-face section no release of that version ever minted — refusing to honor a fabricated read-face state (corruption / forgery guard), the checkpoint stays pending`, { reason: checkpointVersionOf(cp) >= FACE_CHECKPOINT_VERSION ? "read_face_damaged" : "read_face_forged" });
40
40
  }
41
+ const writeFaceSectionRaw = cp.state.writeFace;
42
+ if (writeFaceSectionRaw !== undefined) {
43
+ const c = typeof writeFaceSectionRaw === "object" && writeFaceSectionRaw !== null ? writeFaceSectionRaw : undefined;
44
+ if (c === undefined || !(c.face === "open" || c.face === "roots")) {
45
+ throw new CheckpointError("checkpoint.invalid_outcome", `this checkpoint carries a write-face section (state.writeFace) that is not well-formed — refusing to resume a damaged write-face row (corruption guard), the checkpoint stays pending`, { reason: "write_face_damaged" });
46
+ }
47
+ }
41
48
  }
42
49
  if (realApprovalOrgFact(preCasGateBit) !== undefined && runner.deps.permissionRuleStore?.partitions.org !== true) {
43
50
  throw new CheckpointError("checkpoint.unsupported_version", `this checkpoint's approval was minted under organization governance (org ${String(realApprovalOrgFact(preCasGateBit))}, routed as ${String(preCasGateBit?.origin)}) and this worker has no org adjudication wiring (its permission-rule store carries no org partition) — a governed approval may only be redeemed where governance can be enforced; the checkpoint stays pending, resume it on an org-wired worker`, { reason: "governed_unwired" });
@@ -10,7 +10,7 @@ import { hasVerifiableStructureSignal } from "./grounding-signal.js";
10
10
  import { batchResponseDigest, toolEndBodyFrom } from "./tool-end-body.js";
11
11
  export function createHarnessHandlers(input) {
12
12
  const { prepared, stats, rs, deps } = input;
13
- const { spec, queue, internals, ident, parentToolCallId, subagentName, pushContent, emitCommitted, startedToolCallIds, toolStartAt, writeFamilyOf, toolLabels, postToolBatchHook, batchArgs } = deps;
13
+ const { spec, queue, internals, ident, parentToolCallId, subagentName, pushContent, emitCommitted, startedToolCallIds, toolStartAt, toolProgress, writeFamilyOf, toolLabels, postToolBatchHook, batchArgs } = deps;
14
14
  const internalsNotifier = createSafeNotifier({
15
15
  onError: (f) => console.warn(`[sema-core] ${f.site}: run-internals activity sink threw (contained; further failures counted, not re-disclosed): ${f.error.message}`),
16
16
  });
@@ -186,6 +186,7 @@ export function createHarnessHandlers(input) {
186
186
  const startAt = Date.now();
187
187
  toolStartAt.set(event.toolCallId, startAt);
188
188
  startedToolCallIds.add(event.toolCallId);
189
+ toolProgress.noteStart(event.toolCallId, event.toolName);
189
190
  batchArgs?.set(event.toolCallId, event.args);
190
191
  stats.toolCalls += 1;
191
192
  if (spec.finalVerification === true && !(rs.counters.finalVerifyInjections === 0 ? rs.counters.groundingSignalPreR9 : rs.counters.groundingSignalPostR9) && hasVerifiableStructureSignal(event.args)) {
@@ -208,6 +209,7 @@ export function createHarnessHandlers(input) {
208
209
  const onToolEnd = (event) => {
209
210
  const toolStarted = toolStartAt.get(event.toolCallId);
210
211
  toolStartAt.delete(event.toolCallId);
212
+ toolProgress.noteEnd(event.toolCallId);
211
213
  const toolNow = Date.now();
212
214
  if (!rs.counters.wroteThisRun && (prepared.toolEffects.get(event.toolName) ?? "write") !== "read") {
213
215
  rs.counters.wroteThisRun = true;
@@ -320,6 +322,7 @@ export function createHarnessHandlers(input) {
320
322
  rs.budget.callOutputChars = 0;
321
323
  rs.budget.lastStreamBudgetCheck = 0;
322
324
  toolStartAt.clear();
325
+ toolProgress.clear();
323
326
  if (rs.limits.effectiveMaxTurns !== undefined && rs.limits.effectiveMaxTurns > 0 && stats.turns >= rs.limits.effectiveMaxTurns) {
324
327
  rs.limits.turnsExceeded = true;
325
328
  if (prepared.suspendForResource === undefined) {
@@ -16,6 +16,7 @@ export function runIdentityWiring(input) {
16
16
  notificationLane.ident = ident;
17
17
  queue.push({ type: "wiring_manifest", manifest: prepared.wiringManifest, ...ident() });
18
18
  prepared.toolRosterDeltas.subscribe((delta) => queue.push({ type: "tool_roster_delta", delta, ...ident() }));
19
+ prepared.toolDisclosure?.subscribe((snapshot) => queue.push({ type: "tool_disclosure", ...snapshot, ...ident() }));
19
20
  const delegationLifecycleNotifier = createSafeNotifier({
20
21
  onError: (f) => console.warn(`[sema-core] ${f.site}: delegation-lifecycle observer threw (contained; further failures counted, not re-disclosed): ${f.error.message}`),
21
22
  });
@@ -44,7 +45,7 @@ export function runIdentityWiring(input) {
44
45
  let userInputLostAtEnd = false;
45
46
  prepared.harness.onUndrainedUserInputs = (counts) => {
46
47
  userInputLostAtEnd = counts.steer + counts.followUp > 0;
47
- if (prepared.pausedRef.current !== undefined) {
48
+ if (prepared.pausedRef.current !== undefined || prepared.undeliveredContinuationRef.current.parkedSteers.length > 0) {
48
49
  undrainedUserAtEnd = counts;
49
50
  return;
50
51
  }
@@ -53,9 +54,12 @@ export function runIdentityWiring(input) {
53
54
  }
54
55
  };
55
56
  prepared.harness.onEngineNoteConsumed = (p) => {
56
- const peer = p?.peer;
57
+ const payload = p;
58
+ const peer = payload?.peer;
57
59
  if (peer !== undefined && Array.isArray(peer.hopChain))
58
60
  peerInboundChainRef.current = [...peer.hopChain];
61
+ if (notificationLane.sessionId !== undefined && typeof payload?.task_id === "string" && typeof payload.status === "string")
62
+ pendingSessionNotifications.markDelivered(notificationLane.sessionId, taskNotificationDedupKey(payload));
59
63
  };
60
64
  if (notifyRef)
61
65
  notifyRef.inject = injectTaskNotification;
@@ -38,6 +38,8 @@ export interface RunLegInput<R> {
38
38
  resume: ResumeRun | undefined;
39
39
  /** borrowed-mutable — the run's mutable state: the limit flags and the platform terminal are written by the entrance gates. */
40
40
  rs: RunState;
41
+ /** borrowed-readonly — the trusted spawn-side fact the rejection boundary reads (a delegated child's bare refusal halts no batch). */
42
+ isDelegatedChild: boolean;
41
43
  /** borrowed-readonly — the API-boundary attribution snapshot of the objective's actor (the objective leg's speaker envelope). */
42
44
  entryActor: ActorAssertion | undefined;
43
45
  /** borrowed-readonly — the effective walltime window (undefined = unbounded): `<= 0` refuses the first turn. */
@@ -16,18 +16,19 @@ import { deliverEngineNotice } from "../types.js";
16
16
  import { delimitUntrusted, inlineUntrusted, REVIEWER_NOTE_MAX_BODY, sanitizeUntrustedText } from "../untrusted-text.js";
17
17
  import { errorCodeOf } from "./assemble-result.js";
18
18
  import { DEFAULT_PRECALL_OUTPUT_TOKENS, platformLimitTerminal } from "./clock-and-limits.js";
19
- import { deepJsonEqual, DEFERRED_REISSUE, resumeContinuation } from "./decide-continuation.js";
19
+ import { deepJsonEqual, resumeContinuation } from "./decide-continuation.js";
20
20
  import { settleExecutionRecord } from "./execution-record.js";
21
21
  import { mintGateOutcome } from "./gate-exit.js";
22
22
  import { resolveGitLegDelivery, wrapGitFrame } from "./git-leg-delivery.js";
23
23
  import { GIT_STATUS_ECHO_PREVIEW } from "./git-status-frame.js";
24
24
  import { toImageContent } from "./image.js";
25
+ import { maybeHumanRejectionHalt } from "./prepare-gate-stations.js";
25
26
  import { nextHumanInputSeq } from "./steer-admission.js";
26
27
  import { settleTeardownLeg } from "./teardown-bounded.js";
27
28
  import { toolEndBodyFrom, toolResultMsg } from "./tool-end-body.js";
28
29
  import { attachmentEnvelopeTags, commitAgentListing, commitSkillsListing, reduceToolEnd, renderAgentListingDelta, renderOrphanedBackgroundTasks, renderSkillsListingDelta, stampWriteAnchor } from "./turn-attachments.js";
29
30
  export async function runLeg(input) {
30
- const { spec, queue, prepared, resume, rs, entryActor, effectiveTimeoutMs, timeout, pushContent, ident, emitCommitted, writeFamilyOf, startedToolCallIds, announceWorkspaceMove, withBrainSinks, flushGitMirror, loopLatch, notificationLane, unsubscribeTaskNotifications, unsubGitRetry, unsubBoundary, unsub, runner, next } = input;
31
+ const { spec, queue, prepared, resume, rs, isDelegatedChild, entryActor, effectiveTimeoutMs, timeout, pushContent, ident, emitCommitted, writeFamilyOf, startedToolCallIds, announceWorkspaceMove, withBrainSinks, flushGitMirror, loopLatch, notificationLane, unsubscribeTaskNotifications, unsubGitRetry, unsubBoundary, unsub, runner, next } = input;
31
32
  let final;
32
33
  let threw;
33
34
  let abortedLive = false;
@@ -101,11 +102,12 @@ export async function runLeg(input) {
101
102
  else {
102
103
  let continuation = resumeContinuation(resume, prepared.reminderMark);
103
104
  const bgSnapshot = resume.cp.state.runningBackgroundTasks;
105
+ let orphanTasks = [];
104
106
  if (bgSnapshot !== undefined && bgSnapshot.length > 0) {
105
107
  const alive = new Set(prepared.listBackgroundTasks().map((t) => t.id));
106
- const orphans = bgSnapshot.filter((t) => !alive.has(t.id));
107
- if (orphans.length > 0) {
108
- continuation += "\n\n" + formatHookFeedback(renderOrphanedBackgroundTasks(orphans), prepared.reminderMark);
108
+ orphanTasks = bgSnapshot.filter((t) => !alive.has(t.id));
109
+ if (orphanTasks.length > 0) {
110
+ continuation += "\n\n" + formatHookFeedback(renderOrphanedBackgroundTasks(orphanTasks), prepared.reminderMark);
109
111
  }
110
112
  }
111
113
  let gitResumeDelivered;
@@ -128,10 +130,13 @@ export async function runLeg(input) {
128
130
  }
129
131
  }
130
132
  const engineSegments = continuation.length > 0 ? [{ start: 0, end: continuation.length }] : [];
133
+ const parkedSteers = readPendingSteerQueue(resume.cp.state);
131
134
  const resumeFrames = [
132
- ...readPendingSteerQueue(resume.cp.state).map((entry) => ({ entry, source: "steer" })),
135
+ ...parkedSteers.map((entry) => ({ entry, source: "steer" })),
133
136
  ...(resume.wakeMessage !== undefined ? [{ entry: resume.wakeMessage, source: "wake" }] : []),
134
137
  ];
138
+ const appliedEvents = [];
139
+ const withheldSteerIds = new Set();
135
140
  for (const { entry: steer, source } of resumeFrames) {
136
141
  if (source === "wake" && resume.wakeMessageHookContext !== undefined) {
137
142
  const hookStart = continuation.length;
@@ -166,6 +171,8 @@ export async function runLeg(input) {
166
171
  outcome = { blocked: `the screen crashed (${inlineUntrusted(err.message)}; fail-closed)` };
167
172
  }
168
173
  if ("blocked" in outcome) {
174
+ if (steer.inputId !== undefined)
175
+ withheldSteerIds.add(steer.inputId);
169
176
  deliverEngineNotice(runner.deps.onNotice, {
170
177
  code: "steering.parked_input_blocked",
171
178
  message: "a parked steering input was withheld by the deployment's userPromptSubmit screen when this resume redelivered it: " +
@@ -207,7 +214,7 @@ export async function runLeg(input) {
207
214
  (steer.trusted ? formatHookFeedback(projected, prepared.reminderMark) : delimitUntrusted("supervisor steering message", projected));
208
215
  if (steer.trusted)
209
216
  engineSegments.push({ start, end: continuation.length });
210
- queue.push({
217
+ appliedEvents.push({
211
218
  ...buildHumanInputEvent({
212
219
  carrier: source,
213
220
  source,
@@ -236,7 +243,22 @@ export async function runLeg(input) {
236
243
  },
237
244
  };
238
245
  }
239
- final = await withBrainSinks(() => prepared.harness.prompt(continuation, { engineSegments }));
246
+ const pending = resume.cp.pendingAction;
247
+ const rejectionHalt = pending.kind === "tool_approval" && resume.outcome.gate === "policy_ask"
248
+ ? maybeHumanRejectionHalt({ gate: resume.outcome.gateOutcome, isDelegatedChild, toolName: pending.toolName, toolCallId: pending.toolCallId })
249
+ : undefined;
250
+ if (rejectionHalt !== undefined && pending.kind === "tool_approval")
251
+ prepared.batchHaltRef.current = { rejectedToolCallId: pending.toolCallId };
252
+ prepared.undeliveredContinuationRef.current = { parkedSteers: resumeFrames.map(({ entry }) => entry).filter((e) => !withheldSteerIds.has(e.inputId)), orphanTasks };
253
+ final = await withBrainSinks(() => prepared.harness.prompt(continuation, {
254
+ engineSegments,
255
+ settleUnansweredToolCalls: rejectionHalt !== undefined ? { halt: rejectionHalt } : {},
256
+ onDelivered: () => {
257
+ prepared.undeliveredContinuationRef.current = { parkedSteers: [], orphanTasks: [] };
258
+ for (const e of appliedEvents)
259
+ queue.push(e);
260
+ },
261
+ }));
240
262
  await flushGitMirror();
241
263
  if (prepared.gitStatusRef.announced?.pending === true)
242
264
  await prepared.gitStatusRef.reassert?.();
@@ -452,6 +474,7 @@ export async function runLeg(input) {
452
474
  finally {
453
475
  timeout.clear();
454
476
  prepared.lspDiagnostics?.registry.releaseRun(prepared.lspDiagnostics.runIdent);
477
+ prepared.toolProgressRef.current?.clear();
455
478
  prepared.abortController.abort();
456
479
  prepared.releaseSignal();
457
480
  strandedHumanAnswers = prepared.settleContentAskBindings();
@@ -463,6 +486,7 @@ export async function runLeg(input) {
463
486
  notificationLane.live = false;
464
487
  unsubscribeTaskNotifications();
465
488
  prepared.toolRosterDeltas.unsubscribe();
489
+ prepared.toolDisclosure?.unsubscribe();
466
490
  unsubGitRetry();
467
491
  unsubBoundary();
468
492
  unsub();
@@ -491,39 +515,7 @@ async function applyResumeDecision(runner, prepared, resume, emit, emitCommitted
491
515
  e.code = "resume.tool_contract_mismatch";
492
516
  throw e;
493
517
  }
494
- const completed = new Set(pendingAction.completedCallIds);
495
- const deferredIds = pendingAction.batchToolCallIds.filter((id) => id !== pendingAction.toolCallId && !completed.has(id));
496
- const names = new Map();
497
- const deferredArgs = new Map();
498
- if (deferredIds.length > 0) {
499
- const { messages } = await prepared.session.buildContext();
500
- for (const m of messages) {
501
- if (m.role === "assistant") {
502
- for (const c of m.content) {
503
- if (c.type === "toolCall") {
504
- names.set(c.id, c.name);
505
- deferredArgs.set(c.id, c.arguments);
506
- }
507
- }
508
- }
509
- }
510
- }
511
- for (const id of pendingAction.batchToolCallIds) {
512
- if (id === pendingAction.toolCallId) {
513
- await resolvePendingCall(runner, prepared, resume, emit, emitCommitted, onResolvedToolSuccess, onExecuteStart);
514
- }
515
- else if (!completed.has(id)) {
516
- const name = names.get(id) ?? "unknown";
517
- const displayLabel = (() => {
518
- const l = prepared.tools.find((t) => t.name === name)?.label;
519
- return l !== undefined && l !== name ? { label: l } : {};
520
- })();
521
- emit({ type: "tool_start", toolCallId: id, toolName: name, ...displayLabel, args: deferredArgs.get(id) ?? {} });
522
- emit({ type: "tool_end", toolCallId: id, toolName: name, ...displayLabel, isError: true, ...toolEndBodyFrom({ content: formatHookFeedback(DEFERRED_REISSUE, prepared.reminderMark) }, true, prepared.structuredProjector) });
523
- const eid = await prepared.session.appendMessage(toolResultMsg(id, name, formatHookFeedback(DEFERRED_REISSUE, prepared.reminderMark), true));
524
- emitCommitted(eid, "toolResult", id);
525
- }
526
- }
518
+ await resolvePendingCall(runner, prepared, resume, emit, emitCommitted, onResolvedToolSuccess, onExecuteStart);
527
519
  }
528
520
  async function resolvePendingCall(runner, prepared, resume, emit, emitCommitted, onResolvedToolSuccess, onExecuteStart) {
529
521
  const { pendingAction } = resume.cp;
@@ -538,6 +530,7 @@ async function resolvePendingCall(runner, prepared, resume, emit, emitCommitted,
538
530
  const pendingLabel = (() => { const l = prepared.tools.find((t) => t.name === pendingAction.toolName)?.label; return l !== undefined && l !== pendingAction.toolName ? { label: l } : {}; })();
539
531
  emit({ type: "tool_start", toolCallId: pendingAction.toolCallId, toolName: pendingAction.toolName, ...pendingLabel, args: resolvedArgs });
540
532
  const settleEnd = async (isError, result, gate = decidedGate) => {
533
+ prepared.toolProgressRef.current?.noteEnd(pendingAction.toolCallId);
541
534
  emit({ type: "tool_end", toolCallId: pendingAction.toolCallId, toolName: pendingAction.toolName, ...pendingLabel, isError, ...toolEndBodyFrom(result, isError, prepared.structuredProjector, gate) });
542
535
  const word = await resume.recordExecutionOutcome(gate);
543
536
  settleExecutionRecord(runner.deps.onNotice, word, { toolName: pendingAction.toolName, gate, sessionId: prepared.sessionId, runId: prepared.runId, scope: resume.cp.scope, ...(resume.cp.checkpointId !== undefined ? { checkpointId: resume.cp.checkpointId } : {}) });
@@ -676,6 +669,7 @@ async function resolvePendingCall(runner, prepared, resume, emit, emitCommitted,
676
669
  }
677
670
  const args = resolvedArgs;
678
671
  onExecuteStart?.(pendingAction.toolCallId);
672
+ prepared.toolProgressRef.current?.noteStart(pendingAction.toolCallId, pendingAction.toolName);
679
673
  prepared.suspendProgressRef.executedApproved = true;
680
674
  let res;
681
675
  try {
@@ -1,6 +1,6 @@
1
1
  import { createPeerInboundChainRef, createPeerSelfRef } from "../../agents/peer-admission.js";
2
2
  import { boundInputHashOf } from "../canonical-json.js";
3
- import { isDelegatedAgentTerminal, isTerminalTaskNotification, renderTaskNotificationXml, SystemInjectionQueue, taskNotificationDedupKey } from "../task-notification.js";
3
+ import { createLiveNotificationLane, isDelegatedAgentTerminal, isTerminalTaskNotification, renderTaskNotificationXml, routeOrParkNotification, SystemInjectionQueue } from "../task-notification.js";
4
4
  import { ToolDetachHub } from "../tool-detach.js";
5
5
  import { pendingContentAskCallId } from "./decide-continuation.js";
6
6
  export async function runNotificationLane(input) {
@@ -32,9 +32,13 @@ export async function runNotificationLane(input) {
32
32
  let notificationLaneLive = true;
33
33
  let notificationSessionId;
34
34
  let descendantAnchorDisclosed = false;
35
- const parkTaskNotification = (payload, priority) => {
36
- if (notificationSessionId !== undefined)
37
- pendingSessionNotifications.pend(notificationSessionId, payload, priority);
35
+ const parkTaskNotification = (payload, priority, onDisposition) => {
36
+ laneSeat.handOff(payload);
37
+ const ownSession = notificationSessionId;
38
+ if (ownSession !== undefined)
39
+ routeOrParkNotification(pendingSessionNotifications, ownSession, payload, priority, onDisposition);
40
+ else
41
+ onDisposition?.("parked");
38
42
  if (!isDelegatedAgentTerminal(payload))
39
43
  return;
40
44
  const uplink = internals?.parentNotify;
@@ -49,12 +53,12 @@ export async function runNotificationLane(input) {
49
53
  }
50
54
  const rootAnchor = internals?.rootSessionId;
51
55
  if (rootAnchor !== undefined && rootAnchor !== notificationSessionId) {
52
- pendingSessionNotifications.pend(rootAnchor, payload, priority);
56
+ routeOrParkNotification(pendingSessionNotifications, rootAnchor, payload, priority);
53
57
  return;
54
58
  }
55
59
  if (internals?.parentSessionId !== undefined && !descendantAnchorDisclosed) {
56
60
  descendantAnchorDisclosed = true;
57
- runner.deps.onError?.(new Error(`background-agent terminal ${payload.task_id} parked on this run's own session — the delegation tree's root session is not reachable from this run (no uplink, no root anchor)`), { phase: "degraded", sessionId: notificationSessionId ?? "", classification: "descendant-terminal-unrouted" });
61
+ runner.deps.onError?.(new Error(`background-agent terminal ${payload.task_id} stayed on this run's own session — the delegation tree's root session is not reachable from this run (no uplink, no root anchor)`), { phase: "degraded", sessionId: notificationSessionId ?? "", classification: "descendant-terminal-unrouted" });
58
62
  }
59
63
  };
60
64
  const unsubscribeTaskNotifications = taskNotificationQueue.subscribe((item) => {
@@ -70,34 +74,33 @@ export async function runNotificationLane(input) {
70
74
  ? notificationHarness.followUp(xml, noteOptions)
71
75
  : notificationHarness.steer(xml, { ...noteOptions, ...(item.priority === "now" ? { immediate: true } : {}) });
72
76
  void deliver.then(() => item.onDisposition?.("queued"), () => {
73
- parkTaskNotification(item.payload, item.priority);
74
- item.onDisposition?.("parked");
77
+ parkTaskNotification(item.payload, item.priority, item.onDisposition);
75
78
  });
76
79
  }
77
80
  else {
78
- parkTaskNotification(item.payload, item.priority);
79
- item.onDisposition?.("parked");
81
+ parkTaskNotification(item.payload, item.priority, item.onDisposition);
80
82
  }
81
83
  });
82
84
  const upstreamTaskNotification = internals?.onTaskNotification;
83
85
  const deliveredAtTurnOpen = new Set();
86
+ const laneSeat = createLiveNotificationLane({
87
+ queue: taskNotificationQueue,
88
+ canTakeNow: () => notificationLaneLive && notificationHarness?.acceptsInjections === true,
89
+ isLive: () => notificationLaneLive,
90
+ alreadyDelivered: (key) => deliveredAtTurnOpen.has(key),
91
+ });
92
+ const takeOnThisLane = laneSeat.take;
93
+ const takeAsDestination = laneSeat.takeAsDestination;
84
94
  const injectTaskNotification = (notification, opts) => {
85
- if (deliveredAtTurnOpen.has(taskNotificationDedupKey(notification)))
86
- return Promise.resolve("dropped_duplicate");
87
- if (!notificationLaneLive) {
88
- parkTaskNotification(notification, opts?.priority ?? "later");
89
- return Promise.resolve("parked");
90
- }
95
+ const priority = opts?.priority ?? "later";
91
96
  return new Promise((resolve) => {
92
- const accepted = taskNotificationQueue.enqueue({
93
- kind: "task_notification",
94
- priority: opts?.priority ?? "later",
95
- dedupKey: taskNotificationDedupKey(notification),
96
- payload: notification,
97
- onDisposition: resolve,
98
- });
99
- if (!accepted)
97
+ const own = takeOnThisLane(notification, priority, resolve);
98
+ if (own === "duplicate")
99
+ resolve("dropped_duplicate");
100
+ else if (own === "pending")
100
101
  resolve("queued");
102
+ else if (own === "unavailable")
103
+ parkTaskNotification(notification, priority, resolve);
101
104
  });
102
105
  };
103
106
  const peerSelfRef = internals?.peerSelfRef ?? createPeerSelfRef(internals?.registryScope ?? spec.principal ?? "default");
@@ -120,9 +123,34 @@ export async function runNotificationLane(input) {
120
123
  }, self, taskIdRef);
121
124
  const notificationLane = {
122
125
  get harness() { return notificationHarness; }, set harness(h) { notificationHarness = h; },
123
- get sessionId() { return notificationSessionId; }, set sessionId(s) { notificationSessionId = s; },
126
+ get sessionId() { return notificationSessionId; },
127
+ set sessionId(s) {
128
+ if (notificationSessionId !== undefined)
129
+ pendingSessionNotifications.liveLanes.unregister(notificationSessionId, takeAsDestination);
130
+ notificationSessionId = s;
131
+ if (s !== undefined && notificationLaneLive)
132
+ pendingSessionNotifications.liveLanes.register(s, takeAsDestination);
133
+ },
124
134
  get ident() { return notificationIdent; }, set ident(f) { notificationIdent = f; },
125
- get live() { return notificationLaneLive; }, set live(v) { notificationLaneLive = v; },
135
+ get live() { return notificationLaneLive; },
136
+ set live(v) {
137
+ notificationLaneLive = v;
138
+ if (notificationSessionId === undefined)
139
+ return;
140
+ if (v) {
141
+ pendingSessionNotifications.liveLanes.register(notificationSessionId, takeAsDestination);
142
+ }
143
+ else {
144
+ pendingSessionNotifications.liveLanes.unregister(notificationSessionId, takeAsDestination);
145
+ laneSeat.release();
146
+ }
147
+ },
126
148
  };
127
- return await next({ prepared, injectTaskNotification, deliveredAtTurnOpen, peerInboundChainRef, modelCatalog, notificationLane, unsubscribeTaskNotifications });
149
+ try {
150
+ return await next({ prepared, injectTaskNotification, deliveredAtTurnOpen, peerInboundChainRef, modelCatalog, notificationLane, unsubscribeTaskNotifications });
151
+ }
152
+ finally {
153
+ if (notificationSessionId !== undefined)
154
+ pendingSessionNotifications.liveLanes.unregister(notificationSessionId, takeAsDestination);
155
+ }
128
156
  }
@@ -107,6 +107,7 @@ export async function runSettleAndTeardown(input) {
107
107
  strandedHumanAnswers,
108
108
  remoteEnvFailures: prepared.remoteEnvFailures,
109
109
  effectiveReadFace: prepared.effectiveReadFace,
110
+ effectiveWriteFace: prepared.effectiveWriteFace,
110
111
  effectiveReadDenyPatterns: prepared.effectiveReadDenyPatterns,
111
112
  effectiveMemoryScopes: prepared.effectiveMemoryScopes,
112
113
  effectiveReasoning: reasoningResolution.current,
@@ -3,7 +3,7 @@ import { DEFAULT_COMPACTION_SETTINGS } from "../../internal/harness.js";
3
3
  import { eventDefaultOn } from "../../prompt-assembly/event-registry.js";
4
4
  import { appendInterruptionMarker, reconcileInterruptedSession } from "../session-reconcile.js";
5
5
  import { emitTrace } from "../trace.js";
6
- import { deliverEngineNotice, undrainedUserInputNotices } from "../types.js";
6
+ import { deliverEngineNotice, owedUserInputCounts, undrainedUserInputNotices } from "../types.js";
7
7
  import { answerSettled, errorCodeOf } from "./assemble-result.js";
8
8
  import { awaitChargeWithSlowDisclosure, discloseUnevaluableWindow } from "./clock-and-limits.js";
9
9
  import { buildWorkingFileAttachments, centerAdoptionOption, contextInstructionFilesOption, emitInputTruncated, forkContextOption } from "./compaction-call-options.js";
@@ -14,7 +14,7 @@ import { reconciledToolEndBody } from "./tool-end-body.js";
14
14
  export async function runTerminalAdoption(input) {
15
15
  const { spec, queue, prepared, rs, stats, ident, emitCommitted, startedToolCallIds, toolLabels, timeout, compactionBrain, compactionBreaker, windowSafetyOptions, undrainedUserAtEnd, userInputLostAtEnd, final, abortedLive, userInterruptedLive, runner, sessions, next } = input;
16
16
  let { threw } = input;
17
- const settled = answerSettled(final, userInputLostAtEnd.current || prepared.workCutRef.continuation || prepared.workCutRef.abortedBeforeTurnEnd);
17
+ const settled = answerSettled(final, userInputLostAtEnd.current || prepared.undeliveredContinuationRef.current.parkedSteers.length > 0 || prepared.workCutRef.continuation || prepared.workCutRef.abortedBeforeTurnEnd);
18
18
  if (threw === undefined && prepared.brainCallGuardrailRef.timedOut !== undefined) {
19
19
  threw = prepared.brainCallGuardrailRef.timedOut;
20
20
  }
@@ -77,16 +77,24 @@ export async function runTerminalAdoption(input) {
77
77
  }
78
78
  }
79
79
  let migratedParked = { steer: 0, followUp: 0 };
80
+ let carriedContinuation = 0;
80
81
  if (committedPause !== undefined && runner.deps.checkpointStore !== undefined) {
81
82
  const parkToken = committedPause.cause.token;
82
83
  const parkScope = committedPause.scope;
83
84
  const store = runner.deps.checkpointStore;
84
85
  const carried = [];
85
- for (const record of prepared.harness.readParkableUserInputs()) {
86
+ const owed = [
87
+ ...prepared.undeliveredContinuationRef.current.parkedSteers.map(({ seq: _seq, ...input }) => ({ input })),
88
+ ...prepared.harness.readParkableUserInputs().map((record) => ({ input: record, live: record })),
89
+ ];
90
+ for (const { input, live } of owed) {
86
91
  try {
87
- if (!(await store.setPendingSteer(parkToken, parkScope, record)))
92
+ if (!(await store.setPendingSteer(parkToken, parkScope, input)))
88
93
  break;
89
- carried.push(record);
94
+ if (live !== undefined)
95
+ carried.push(live);
96
+ else
97
+ carriedContinuation += 1;
90
98
  }
91
99
  catch (parkErr) {
92
100
  runner.deps.onError?.(parkErr instanceof Error ? parkErr : new Error(String(parkErr)), {
@@ -101,14 +109,9 @@ export async function runTerminalAdoption(input) {
101
109
  }
102
110
  migratedParked = prepared.harness.dropParkedUserInputs(carried);
103
111
  }
104
- if (undrainedUserAtEnd.current !== undefined) {
105
- const remaining = {
106
- steer: Math.max(0, undrainedUserAtEnd.current.steer - migratedParked.steer),
107
- followUp: Math.max(0, undrainedUserAtEnd.current.followUp - migratedParked.followUp),
108
- };
109
- for (const notice of undrainedUserInputNotices(remaining, spec.taskId ?? prepared.sessionId, prepared.sessionId, prepared.runId)) {
110
- deliverEngineNotice(runner.deps.onNotice, notice);
111
- }
112
+ const owed = owedUserInputCounts(undrainedUserAtEnd.current, migratedParked, { frames: prepared.undeliveredContinuationRef.current.parkedSteers, carried: carriedContinuation, rowMayReturn: committedPause === undefined && !prepared.suspendProgressRef.executedApproved });
113
+ for (const notice of undrainedUserInputNotices(owed, spec.taskId ?? prepared.sessionId, prepared.sessionId, prepared.runId)) {
114
+ deliverEngineNotice(runner.deps.onNotice, notice);
112
115
  }
113
116
  if (rs.attach.attachState?.postCompactPending === true && (rs.attach.attachmentsCfg?.backgroundTasks ?? eventDefaultOn("background_tasks")) === true) {
114
117
  emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.announce_dropped", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() }));
@@ -15,6 +15,7 @@ import { awaitChargeWithSlowDisclosure, discloseUnevaluableWindow, ENV_DUE_GOVER
15
15
  import { gitRestateOption } from "./git-leg-delivery.js";
16
16
  import { COMPACTION_FREED_EPSILON, COMPACTION_REGROWTH_FACTOR, MAX_CONSECUTIVE_COMPACTION_FAILURES } from "./compaction-knobs.js";
17
17
  import { compactionHookOptions, recordCompactionReuse, seamCCompactionOptions } from "./compaction-seams.js";
18
+ import { contextUsageSections } from "../../prompt-assembly/section-usage.js";
18
19
  const BATCH_CONTEXT_MIN_KEEP_BYTES = 160;
19
20
  const BATCH_TRUNCATION_MARKER = "\n…[truncated]";
20
21
  export function createTurnBoundary(input) {
@@ -580,7 +581,8 @@ export function createTurnBoundary(input) {
580
581
  }
581
582
  rs.counters.trimForceBackoff = nextTrimForceBackoff(rs.counters.trimForceBackoff, comp, trimPressure);
582
583
  if (comp.contextUsage) {
583
- queue.push({ type: "context_usage", ...comp.contextUsage, ...ident() });
584
+ const sections = contextUsageSections(prepared.promptManifest.sections);
585
+ queue.push({ type: "context_usage", ...comp.contextUsage, ...(sections !== undefined ? { sections } : {}), ...ident() });
584
586
  }
585
587
  if (comp.unevaluableWindow) {
586
588
  discloseUnevaluableWindow(comp.unevaluableWindow, prepared, rs.telemetry.tracer, rs.telemetry.taskId, runnerHooks.onError);
@@ -91,14 +91,14 @@ export declare class Runner implements RunnerSelfSeat {
91
91
  /**
92
92
  * Hot-swap the deployment seats a running Runner may change without a restart — the ONE door for every
93
93
  * hot-swappable seat ({@link SWAPPABLE_DEP_SEATS}: the model catalog generation `models` + its `tiers`
94
- * bindings, and the deployment's read-face declaration `readFace`), and the ONLY sanctioned way to
94
+ * bindings, and the deployment's two containment-face declarations `readFace` / `writeFace`), and the ONLY sanctioned way to
95
95
  * change any of them after construction. It replaces the former `swapModels` verb (retired, no alias):
96
96
  * one verb per seat restated the same laws with a list of verbs as the only record of what is swappable.
97
97
  *
98
98
  * The laws, stated once (see the vocabulary module for the seat-by-seat meaning):
99
99
  * - **Screened first, written once — atomic.** The candidate is screened whole
100
100
  * ({@link screenSwappableDeps}: the key set is the closed one, at least one seat, `tiers` rides with
101
- * `models`, each seat's own value gate — `readFace` through the very `assertReadFaceValue` every
101
+ * `models`, each seat's own value gate — both face seats through the very `assertContainmentFaceValue` every
102
102
  * prepare door applies) and the catalog is tier-expanded on the candidate (an illegal binding throws
103
103
  * exactly like the constructor's boot error) BEFORE the deps object is replaced. On any throw the
104
104
  * current deps stay in force, byte-identical.
@@ -108,14 +108,15 @@ export declare class Runner implements RunnerSelfSeat {
108
108
  * redirects never re-route or re-price a task mid-run; a read-face change never re-fences a mounted
109
109
  * band; the divergence window is exactly the in-flight legs' lifetime, by design.
110
110
  * - **Presence is the instruction.** A key present on the candidate is replaced (an explicit
111
- * `undefined` clears an optional seat: `tiers: undefined` ⇒ no bindings, `readFace: undefined` ⇒ the
111
+ * `undefined` clears an optional seat: `tiers: undefined` ⇒ no bindings, a face seat `undefined` ⇒ the
112
112
  * engine default); a key absent is kept. `tiers` omitted beside `models` ⇒ the current bindings are
113
113
  * re-applied over the new catalog.
114
114
  * - **Announced per seat.** `config.models_swapped` (models/tiers counts — key material only, never the
115
115
  * catalog itself; plus the advisory `route.base_url_changed_key_unchanged` for same-name entries
116
116
  * whose URL moved while the Model-visible credential fingerprint did not) and
117
- * `config.read_face_swapped` (`{ from, to, occurrenceId }`, the seat's previous and new value, `null`
118
- * = no declaration) — the table {@link SWAP_SEAT_NOTICE} binds every seat to its line.
117
+ * `config.read_face_swapped` / `config.write_face_swapped` (`{ from, to, occurrenceId }`, the seat's
118
+ * previous and new value, `null` = no declaration) — the table {@link SWAP_SEAT_NOTICE} binds every
119
+ * seat to its line.
119
120
  */
120
121
  swapDeps(next: SwappableDeps): void;
121
122
  /** one-shot brain-routed utility query (see {@link runSideQuery} for the full contract):