@sema-agent/core 7.19.0 → 7.20.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 (106) hide show
  1. package/CHANGELOG.md +183 -2
  2. package/dist/agents/subagent.js +12 -20
  3. package/dist/agents/teacher.js +2 -5
  4. package/dist/agents/verify.js +21 -10
  5. package/dist/brain/anthropic.js +3 -1
  6. package/dist/brain/open-responses.js +4 -2
  7. package/dist/brain/openai.js +3 -1
  8. package/dist/brain/stream-engine.d.ts +4 -0
  9. package/dist/brain/stream-engine.js +4 -0
  10. package/dist/core/auto-mode.d.ts +37 -18
  11. package/dist/core/auto-mode.js +1 -1
  12. package/dist/core/child-clamp-seats.d.ts +79 -0
  13. package/dist/core/child-clamp-seats.js +29 -0
  14. package/dist/core/engine-notice.d.ts +67 -49
  15. package/dist/core/fs-write-gate-policy.d.ts +17 -9
  16. package/dist/core/fs-write-gate-policy.js +51 -4
  17. package/dist/core/gate-bounded-await.d.ts +38 -0
  18. package/dist/core/gate-bounded-await.js +53 -0
  19. package/dist/core/gate-fold.js +16 -48
  20. package/dist/core/gate-lanes.js +11 -3
  21. package/dist/core/gate-outcome.d.ts +9 -6
  22. package/dist/core/governance-codes.d.ts +16 -1
  23. package/dist/core/governance-codes.js +12 -0
  24. package/dist/core/hooks.d.ts +8 -8
  25. package/dist/core/memory-engine/engine.d.ts +14 -9
  26. package/dist/core/memory-engine/engine.js +21 -8
  27. package/dist/core/memory-engine/layout.d.ts +31 -0
  28. package/dist/core/memory-engine/layout.js +68 -3
  29. package/dist/core/memory-engine/types.d.ts +24 -0
  30. package/dist/core/memory.d.ts +19 -0
  31. package/dist/core/memory.js +12 -0
  32. package/dist/core/remote-env.d.ts +32 -0
  33. package/dist/core/remote-env.js +4 -1
  34. package/dist/core/runner/advertised-writable-dirs.d.ts +59 -1
  35. package/dist/core/runner/advertised-writable-dirs.js +51 -3
  36. package/dist/core/runner/announce-once-ledger.d.ts +15 -0
  37. package/dist/core/runner/announce-once-ledger.js +8 -0
  38. package/dist/core/runner/classifier-ladder.d.ts +5 -0
  39. package/dist/core/runner/classifier-ladder.js +13 -2
  40. package/dist/core/runner/contracts.d.ts +44 -1
  41. package/dist/core/runner/denial-limit-arms.d.ts +14 -1
  42. package/dist/core/runner/denial-limit-arms.js +5 -4
  43. package/dist/core/runner/env-capability-census.d.ts +30 -0
  44. package/dist/core/runner/env-capability-census.js +38 -0
  45. package/dist/core/runner/forward-sink.d.ts +32 -0
  46. package/dist/core/runner/forward-sink.js +41 -0
  47. package/dist/core/runner/gate-exit.d.ts +4 -0
  48. package/dist/core/runner/gate-exit.js +4 -1
  49. package/dist/core/runner/git-status-frame.d.ts +0 -9
  50. package/dist/core/runner/listing-fold.d.ts +57 -0
  51. package/dist/core/runner/listing-fold.js +75 -0
  52. package/dist/core/runner/prepare-execution-env.d.ts +1 -1
  53. package/dist/core/runner/prepare-execution-env.js +2 -0
  54. package/dist/core/runner/prepare-gate-stations.d.ts +8 -0
  55. package/dist/core/runner/prepare-gate-stations.js +6 -2
  56. package/dist/core/runner/prepare-hands-readface.d.ts +4 -0
  57. package/dist/core/runner/prepare-hands-readface.js +3 -5
  58. package/dist/core/runner/prepare-listings.d.ts +3 -0
  59. package/dist/core/runner/prepare-listings.js +4 -2
  60. package/dist/core/runner/prepare-memory.js +7 -1
  61. package/dist/core/runner/prepare-policy-chain.js +5 -5
  62. package/dist/core/runner/prepare-prompt-inputs.d.ts +2 -2
  63. package/dist/core/runner/prepare-run-refs.d.ts +4 -2
  64. package/dist/core/runner/prepare-run-refs.js +5 -4
  65. package/dist/core/runner/prepare-task.js +7 -5
  66. package/dist/core/runner/run-clock-and-content.js +3 -7
  67. package/dist/core/runner/run-harness-handlers.js +10 -10
  68. package/dist/core/runner/run-leg.js +28 -19
  69. package/dist/core/runner/run-settle-and-teardown.js +2 -5
  70. package/dist/core/runner/run-turn-boundary.js +44 -33
  71. package/dist/core/runner/runtask.js +2 -1
  72. package/dist/core/runner/synthetic-tools.d.ts +13 -39
  73. package/dist/core/runner/synthetic-tools.js +15 -59
  74. package/dist/core/runner/task-root-canonical.d.ts +45 -0
  75. package/dist/core/runner/task-root-canonical.js +18 -0
  76. package/dist/core/runner/turn-attachments.d.ts +125 -49
  77. package/dist/core/runner/turn-attachments.js +124 -66
  78. package/dist/core/safe-notify.d.ts +5 -0
  79. package/dist/core/safe-notify.js +24 -2
  80. package/dist/core/sensitive-path-policy.d.ts +53 -27
  81. package/dist/core/sensitive-path-policy.js +16 -26
  82. package/dist/core/skill-listing-budget.d.ts +34 -40
  83. package/dist/core/skill-listing-budget.js +25 -26
  84. package/dist/core/skills-directory.js +17 -10
  85. package/dist/core/task-registry.js +2 -1
  86. package/dist/core/task-spec.d.ts +8 -7
  87. package/dist/core/tool-policy.d.ts +12 -1
  88. package/dist/core/tool-policy.js +39 -25
  89. package/dist/core/write-protect.d.ts +98 -18
  90. package/dist/core/write-protect.js +88 -34
  91. package/dist/index.d.ts +6 -5
  92. package/dist/index.js +5 -4
  93. package/dist/orchestration/workflow-primitives.js +10 -20
  94. package/dist/stores/file/memory-store.d.ts +5 -2
  95. package/dist/stores/file/memory-store.js +8 -4
  96. package/dist/tools/fs/bash-readonly-classifier.d.ts +13 -2
  97. package/dist/tools/fs/bash-readonly-classifier.js +317 -35
  98. package/dist/tools/fs/fs-bash.d.ts +1 -1
  99. package/dist/tools/fs/fs-bash.js +12 -25
  100. package/dist/tools/fs/fs-shared.d.ts +22 -0
  101. package/dist/tools/fs/fs-shared.js +30 -0
  102. package/dist/tools/fs/index.d.ts +1 -1
  103. package/dist/tools/fs/index.js +1 -1
  104. package/package.json +2 -1
  105. package/test/export-surface.snapshot.json +19 -3
  106. package/wire-changes/core@7.20.0.json +568 -0
@@ -303,9 +303,9 @@ export async function preparePolicyChain(input) {
303
303
  }
304
304
  if (re.updatedInput !== undefined)
305
305
  editArgs = re.updatedInput;
306
- const judged = await judgeInheritedClassifier({ autoMode: ancestorDecider !== undefined ? { decider: ancestorDecider, denialTracking: ancestorTracker } : undefined, ask: re, req: { ...creq, args: editArgs }, signal: csignal ?? abortController.signal, subject: "the approved edit" });
306
+ const judged = await judgeInheritedClassifier({ autoMode: ancestorDecider !== undefined ? { decider: ancestorDecider, denialTracking: ancestorTracker } : undefined, ask: re, req: { ...creq, args: editArgs }, signal: csignal ?? abortController.signal, subject: "the approved edit", allowRewrite: editArgs });
307
307
  if (judged.kind === "allow")
308
- return { action: "allow", updatedInput: editArgs };
308
+ return judged.result;
309
309
  if (judged.kind === "deny")
310
310
  return judged.result;
311
311
  const { ask: editAsk, fallback: editFallback, mintedHere: editMintedHere } = judged;
@@ -371,7 +371,7 @@ export async function preparePolicyChain(input) {
371
371
  }
372
372
  const judged = await judgeInheritedClassifier({ autoMode: pc.autoMode, ask: first, req: creq, signal: csignal ?? abortController.signal, subject: "this call" });
373
373
  if (judged.kind === "allow")
374
- return { action: "allow" };
374
+ return judged.result;
375
375
  if (judged.kind === "deny")
376
376
  return judged.result;
377
377
  const { ask: inheritedAsk, fallback, mintedHere } = judged;
@@ -439,9 +439,9 @@ export async function preparePolicyChain(input) {
439
439
  return mandateUnreconstructibleDeny(creq);
440
440
  }
441
441
  const presentedArgs = decision.updatedInput !== undefined ? decision.updatedInput : creq.args;
442
- const judged = await judgeInheritedClassifier({ autoMode: pc.autoMode, ask: decision, req: { ...creq, args: presentedArgs }, signal: csignal ?? abortController.signal, subject: "this call" });
442
+ const judged = await judgeInheritedClassifier({ autoMode: pc.autoMode, ask: decision, req: { ...creq, args: presentedArgs }, signal: csignal ?? abortController.signal, subject: "this call", allowRewrite: decision.updatedInput });
443
443
  if (judged.kind === "allow")
444
- return decision.updatedInput !== undefined ? { action: "allow", updatedInput: decision.updatedInput } : { action: "allow" };
444
+ return judged.result;
445
445
  if (judged.kind === "deny")
446
446
  return judged.result;
447
447
  const { ask: inheritedAsk, fallback, mintedHere } = judged;
@@ -117,8 +117,8 @@ export interface PreparePromptInputsResult {
117
117
  tzValid: boolean;
118
118
  /** owned — the client's declared zone as given (paired with `tzValid`; unvalidated on its own). */
119
119
  userTz: string | undefined;
120
- /** owned — the git-status lane's run-local state (empty on a hands-less leg); the run loop and the
121
- * checkpoint serializer mutate its fields from here on (`announced`, `pendingReceipt`, …). */
120
+ /** owned — the git-status lane's run-local state (empty on a hands-less leg); the run loop and the checkpoint
121
+ * serializer mutate its fields from here on (`announced`, `mirrorOwed`, …; the frame's receipt rides the leg's shared `Prepared.frameReceipts`). */
122
122
  gitStatusRef: GitStatusLaneRef;
123
123
  }
124
124
  /** The M10 phase body — prepareTask's prompt-inputs stretch, verbatim (see the module header). */
@@ -28,8 +28,10 @@ export interface PrepareRunRefsInput {
28
28
  * worktree helper). Never mutated. */
29
29
  spec: Pick<TaskSpec, "forwardSubagentEvents" | "retainSubagentSessions" | "handsReadOnly">;
30
30
  /** borrowed-readonly — the trusted spawn-side channel; only `onForwardEvent` (the deployment's display
31
- * sink) is read, once, to decide whether a forwarding wrapper exists at all. */
32
- internals: Pick<RunInternals, "onForwardEvent"> | undefined;
31
+ * sink) is read, to decide whether a forwarding wrapper exists at all, and `forwardScope` (the run's
32
+ * forwarding containment, read by the door). The OBJECT is what the wrapper hands the door rather than
33
+ * the pulled-out function: the sink keeps its receiver and the run keeps one failure count. */
34
+ internals: Pick<RunInternals, "onForwardEvent" | "forwardScope"> | undefined;
33
35
  /** borrowed-readonly — the run's resolved env; the worktree helper closes over it (lazy: no I/O until a
34
36
  * delegation actually mints a worktree). */
35
37
  executionEnv: ExecutionEnv;
@@ -1,6 +1,7 @@
1
1
  import { createSubagentWorktreeHelper } from "../../agents/subagent.js";
2
2
  import { SubagentRetainLedger } from "../../agents/retain-ledger.js";
3
3
  import { ActiveSkillScope } from "./active-skill-scope.js";
4
+ import { forwardSafely } from "./forward-sink.js";
4
5
  import { createEditedFilesLedger } from "./edited-files-ledger.js";
5
6
  export function forwardsSubagentEvents(spec) {
6
7
  return spec.forwardSubagentEvents === true;
@@ -14,14 +15,14 @@ export function prepareRunRefs(input) {
14
15
  const harnessRef = {};
15
16
  const toolProgressRef = {};
16
17
  const skillScope = new ActiveSkillScope();
17
- const forwardSink = internals?.onForwardEvent;
18
- const forwardEvent = forwardSink
18
+ const sinkWired = internals?.onForwardEvent !== undefined;
19
+ const forwardEvent = sinkWired
19
20
  ? (e) => {
20
21
  if (e.type === "task_progress")
21
- forwardSink(e);
22
+ forwardSafely(internals, e);
22
23
  else if (spec.forwardSubagentEvents === true &&
23
24
  (e.type === "text_delta" || e.type === "text_end" || e.type === "reasoning_delta" || e.type === "tool_start" || e.type === "tool_progress" || e.type === "tool_end")) {
24
- forwardSink(e);
25
+ forwardSafely(internals, e);
25
26
  }
26
27
  }
27
28
  : undefined;
@@ -78,6 +78,7 @@ import { ToolRosterDeltaSeat } from "../tool-roster.js";
78
78
  import { faceOf } from "../tool-roster.js";
79
79
  import { catalogEntriesNamed, pathTargetOf } from "../tool-registry.js";
80
80
  import { AdvertisedWritableDirs } from "./advertised-writable-dirs.js";
81
+ import { resolveTaskRootCanonical } from "./task-root-canonical.js";
81
82
  import { createStructuredProjector } from "./tool-output-projection.js";
82
83
  function warnCompactionWindowHazard(tracer, spec, model, compModel, hostTaskId) {
83
84
  if (compModel === undefined)
@@ -365,6 +366,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
365
366
  }
366
367
  const ownedEnv = ownedEnvSeat.current;
367
368
  const { taskRootFinal, rebaseRestoredPath } = await prepareWorkspaceRestore({ ownedEnv, deps, spec, internals, resume, taskRootInitial, abortController, sessionId });
369
+ const { canonical: taskRootCanonical } = await resolveTaskRootCanonical({ env: executionEnv, given: taskRootFinal, onNotice: deps.onNotice, sessionId, runId });
368
370
  const { trackFileEdit, fileHistoryBoundary, restoredFilePaths, rewindNotes, fileHistoryEnabled, historyScope, historyRoot, historyFs } = await prepareFileHistory({ deps, spec, internals, executionEnv, taskRootFinal, handsEnabled, sessionId, session, resumeAtBeforeParentId, abortController });
369
371
  const { noteFileEdited, editedFilesSnapshot, harnessRef, toolProgressRef, skillScope, forwardEvent, reviewRequestRef, requestReview, stopRequestedRef, requestStopAfterTurn, planModeRef, enterPlanMode, subagentRetain, worktreeIsolation } = prepareRunRefs({ spec, internals, executionEnv, taskRootFinal });
370
372
  let resolvedReadFace;
@@ -438,7 +440,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
438
440
  const callIssuedAtRef = {};
439
441
  const workCutRef = { continuation: false, abortedBeforeTurnEnd: false };
440
442
  const advertisedWritableDirs = handsEnabled
441
- ? new AdvertisedWritableDirs(executionEnv, (dir, reason) => deps.onError?.(new Error(`${dir.advertiser} advertised writable directory skipped (cannot canonicalize): ${dir.path} — ${reason}`), { phase: "config", sessionId }))
443
+ ? new AdvertisedWritableDirs(executionEnv, (dir, reason) => deps.onError?.(new Error(`${dir.advertiser} advertised writable directory skipped: ${dir.path} — ${reason}`), { phase: "config", sessionId }), taskRootCanonical, [...(spec.additionalDirectories ?? []), ...(spec.additionalReadDirectories ?? [])])
442
444
  : undefined;
443
445
  const handsWriteHalfPlanned = handsEnabled && handsWriteHalfMounts({ readOnly: spec.handsReadOnly === true });
444
446
  const memoryPairNameDomain = new Set();
@@ -508,7 +510,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
508
510
  if (internals?.ownOrgAdmissionRef !== undefined && ownOrgVerdictRef.current !== undefined) {
509
511
  internals.ownOrgAdmissionRef.current = ownOrgVerdictRef.current;
510
512
  }
511
- const handsReadFaceInput = { handsEnabled, executionEnv, taskRootFinal, rebaseRestoredPath, resume, session, sessionId, hostTaskId, taskScope, fullShellReachable, effectiveShellGate, toolFaceSnapshot, model, spec, deps, internals, memoryWriteGate, ...(advertisedWritableDirs !== undefined ? { advertisedWritableDirs } : {}), firstPartyOffload, roster, egressTools, irreversibilityTier, irreversibleTools, reversibilityProbes, reminderMark, reminderDisclosureCounts, ...(trackFileEdit !== undefined ? { trackFileEdit } : {}), onFileEdited: noteFileEdited, harnessRef, toolProgressRef, ...(restoredFilePaths.length > 0 ? { restoredFilePaths } : {}) };
513
+ const handsReadFaceInput = { handsEnabled, executionEnv, taskRootFinal, taskRootCanonical, rebaseRestoredPath, resume, session, sessionId, hostTaskId, taskScope, fullShellReachable, effectiveShellGate, toolFaceSnapshot, model, spec, deps, internals, memoryWriteGate, ...(advertisedWritableDirs !== undefined ? { advertisedWritableDirs } : {}), firstPartyOffload, roster, egressTools, irreversibilityTier, irreversibleTools, reversibilityProbes, reminderMark, reminderDisclosureCounts, ...(trackFileEdit !== undefined ? { trackFileEdit } : {}), onFileEdited: noteFileEdited, harnessRef, toolProgressRef, ...(restoredFilePaths.length > 0 ? { restoredFilePaths } : {}) };
512
514
  const handsReadFace = handsEnabled ? await prepareHandsMount(handsReadFaceInput) : resolveHandsLessReadFace(handsReadFaceInput);
513
515
  const { readFileStateForCheckpoint, seedContextFiles, handsCwdRef, workspaceStateSettle, wsSnapshot, rebaseWsPath, backgroundTaskToolsActive, additionalRootsCanonical, additionalReadRootsCanonical, attachmentRootCanonical, readDenyMatcher, envHandToolNames, sealReadStateSeat } = handsReadFace;
514
516
  resolvedReadFace = handsReadFace.resolvedReadFace;
@@ -590,7 +592,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
590
592
  declaredCards: toolRoster.entries.flatMap((e) => e.renderHints?.resultCards ?? []),
591
593
  onUndeclared: (type) => deliverEngineNotice(deps.onNotice, { code: "config.tool_card_undeclared", message: `a tool minted a \`details.type\` card ${JSON.stringify(type)} its definition never declared — dropped from tool_end.structured (declare it on the definition's catalog row or the caller's renderHints.resultCards)`, detail: { type, sessionId, runId } }),
592
594
  });
593
- const { agentListing, announcedListingsRef, dateChange } = prepareListings({ spec, tools, onceLedger, sessionId, toolFaceSnapshot, activeTools, resume, skillsListing, listingRideRef, renderWithDate, envFacts, tzValid, userTz });
595
+ const { agentListing, announcedListingsRef, dateChange } = prepareListings({ spec, tools, onceLedger, sessionId, toolFaceSnapshot, activeTools, resume, skillsListing, listingBudget: doors.skillListingBudget, listingRideRef, renderWithDate, envFacts, tzValid, userTz });
594
596
  const lastBrainContextRef = {};
595
597
  const requestLossyRef = { current: false };
596
598
  const staleOffloadWrittenRefs = new Set();
@@ -699,12 +701,12 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
699
701
  const { saga } = prepareSuspendSaga({ gateMachineryActive, abortController, harness, checkpointStore, deps, sessionId, hostTaskId, taskScope, liveSpendRef, resume, nestedStats, internals, activeTools, outputRef, readFileStateForCheckpoint, faceCheckpointSection, writeFaceCheckpointSection, reminderMark, handsCwdRef, worktreeSessionRef, undeliveredContinuationRef, inheritedParentConstraints, seedInheritedGate, inheritedAncestorRules, inheritedShellGate, autoModeIntent, inheritedAdmittedOrgScopes, ownOrgVerdictRef, orgGovernedProvenance, announcedListingsRef, gitStatusRef, hookIdentity, placementRootResolved, externalContentTargetActive, remoteEnvFailures, memoryEngineSession, suspendLoopRef, ownedEnv, incompleteSuspendAdapter });
700
702
  const { suspendForResource, suspendForPlatformLimit, suspendForReview } = prepareBoundaryParks({ saga, spec, checkpointStore, abortController, harness, session, sessions, sessionId, deps, priorLedger, maxSlices, maxSuspends, resourceTotal, priorSuspendCount, suspendChainBase, humanReviewRef, liveSpendRef, now, faceCheckpointState, f012CheckpointState, orgAdmissionCheckpointState, pausedRef, remoteEnvFailures, resourceSuspendEligible, durableSuspendInfraReady, incompleteSuspendAdapter });
701
703
  const { parkAsk } = prepareParkAsk({ askLane, saga, spec, deps, sessionId, checkpointStore, toolRosterDeltas: rosterSeat, parkLaneArmed, contentAskRoutable, liveQuestionFace, mountedQuestionTool, contentAskBindings, lateStrandedAnswers, discloseStrandedAnswers, onAsk, runtimeCaps, inheritedUnavailableAsks, basePolicyForResumeEdit, budgetSnapshot, handsCwdRef, advertisedWritableDirs, offloadStore, ownedEnv, incompleteSuspendAdapter, session, sessions, suspendChainBase, maxSuspends, remoteEnvFailures, shellGatedTools, effectiveShellGate, durableApproval, priorLedger, liveSpendRef, resourceTotal, faceCheckpointState, f012CheckpointState, orgAdmissionCheckpointState, ruleOffersOf, now, humanReviewRef, abortController, harness, pausedRef });
702
- prepareGateStations({ askLane, parkAsk, tools, toolRosterDeltas: rosterSeat, toolEffects, deps, toolCallGateArmedRef, effectivePolicy, hooks, egressTools, irreversibleTools, spec, complianceDenies, harness, blockedToolCalls, inheritedAskGrants, inheritedUnavailableAsks, foldAskClasses, ancestorSandboxAdmissions, preToolContexts, gateOutcomes, batchHaltRef, blockedTracked, hookIdentity, reminderMark, planModeRef, hostTaskId, sessionId, ownGatePreToolUse, hookTimeoutMs, handsCwdRef, hookEnvFace, irreversibilityTier, reversibilityProbes, abortController, shellGatedTools, autoModeDecider, autoModeDenialTracking, stopForDenialLimit, permissionRuleLane, permissionRuleOrgLane, questionToolMounted, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, delegation, notifyOwnHookCrash });
704
+ prepareGateStations({ askLane, parkAsk, tools, toolRosterDeltas: rosterSeat, toolEffects, deps, toolCallGateArmedRef, effectivePolicy, hooks, egressTools, irreversibleTools, spec, complianceDenies, harness, blockedToolCalls, inheritedAskGrants, inheritedUnavailableAsks, foldAskClasses, ancestorSandboxAdmissions, preToolContexts, gateOutcomes, batchHaltRef, blockedTracked, hookIdentity, reminderMark, planModeRef, hostTaskId, sessionId, ownGatePreToolUse, hookTimeoutMs, handsCwdRef, hookEnvFace, handsEnv: handsEnabled ? executionEnv : undefined, taskRootFinal, irreversibilityTier, reversibilityProbes, abortController, shellGatedTools, autoModeDecider, autoModeDenialTracking, stopForDenialLimit, permissionRuleLane, permissionRuleOrgLane, questionToolMounted, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, delegation, notifyOwnHookCrash });
703
705
  const { microCompact, charsPerToken } = prepareContextLane({ model, deps, sessionId, runId, hostTaskId, offloadStore, microCompactKnob, harness, batchHaltRef, gitStatusRef, requestLossyRef, trimPressureRef });
704
706
  const preparedHolder = {};
705
707
  const { cacheBreakDetector, cacheFingerprint, promptOverheadTokens, readTaskFile, normalizeAttachmentPath, isDedupStubResult, recentlyReadFiles, onCompactionApplied, detectExternalChanges, listBackgroundTasks, centerCompactionCandidate, effectiveReadFaceObserved, effectiveWriteFaceObserved, effectiveReadDenyObserved } = prepareTurnWiring({ deps, systemPromptSeat, harnessTools, fpRef, model, epochArtifactDigestForSnapshot, promptProfile, fableMitigations, systemBlocks, thinking, spec, sessionId, turnSnapshotRef, promptManifest, charsPerToken, handsEnabled, executionEnv, attachmentRootCanonical, additionalRootsCanonical, additionalReadRootsCanonical, readDenyMatcher, resolvedReadFace, handsCwdRef, readFileStateForCheckpoint, denyNarrowingPolicy, abortController, hostTaskId, taskScope, buildAssembleInputs, centerAdoptionRef, harnessRef, epochDeclaredSections, carrierReadFace, carrierWriteFace, readDenyAdditionsNormalized, preparedHolder });
706
708
  await onceLedger.settle(session, announcedListingsRef);
707
- const buildPrepared = () => ({ harness, session, sessionId, runId, reminderMark, reminderDisclosureCounts, editedFilesSnapshot, taskRootPath: taskRootFinal, model, thinking, compModel: effectiveCompModel, mcp, ...(a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, gateOutcomes, batchHaltRef, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), ...(fileHistoryBoundary !== undefined ? { fileHistoryBoundary } : {}), ...(effectiveReadFaceObserved !== undefined ? { effectiveReadFace: effectiveReadFaceObserved, effectiveWriteFace: effectiveWriteFaceObserved } : {}), ...(effectiveReadDenyObserved !== undefined ? { effectiveReadDenyPatterns: effectiveReadDenyObserved } : {}), effectiveMemoryScopes: memoryEffectiveScopes, cwdRef: handsCwdRef, toolProgressRef, ...(advertisedWritableDirs !== undefined ? { advertisedWritableDirs } : {}), ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), ...(sealReadStateSeat !== undefined ? { sealReadStateSeat } : {}), denyNarrowingPolicy, ...(permissionRuleLane !== undefined ? { persistedRuleLane: permissionRuleLane } : {}), ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, pausedRef, undeliveredContinuationRef, suspendProgressRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, workCutRef, brainCallGuardrailRef, gateStopRef, suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolRoster, toolRosterDeltas: rosterSeat, structuredProjector, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, microCompact, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, stopRequestedRef, announcedSnapshotRecovered: onceLedger.recovered, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(projectInstructionContent !== undefined ? { projectInstructionContent } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, gitStatusRef, listBackgroundTasks, hookIdentity, hookTimeoutMs, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}), ...(toolDisclosureSeat !== undefined ? { toolDisclosure: toolDisclosureSeat } : {}) });
709
+ const buildPrepared = () => ({ harness, session, sessionId, runId, reminderMark, reminderDisclosureCounts, editedFilesSnapshot, taskRootPath: taskRootFinal, model, thinking, compModel: effectiveCompModel, mcp, ...(a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, gateOutcomes, batchHaltRef, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), ...(fileHistoryBoundary !== undefined ? { fileHistoryBoundary } : {}), ...(effectiveReadFaceObserved !== undefined ? { effectiveReadFace: effectiveReadFaceObserved, effectiveWriteFace: effectiveWriteFaceObserved } : {}), ...(effectiveReadDenyObserved !== undefined ? { effectiveReadDenyPatterns: effectiveReadDenyObserved } : {}), effectiveMemoryScopes: memoryEffectiveScopes, cwdRef: handsCwdRef, toolProgressRef, ...(advertisedWritableDirs !== undefined ? { advertisedWritableDirs } : {}), ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), ...(sealReadStateSeat !== undefined ? { sealReadStateSeat } : {}), denyNarrowingPolicy, ...(permissionRuleLane !== undefined ? { persistedRuleLane: permissionRuleLane } : {}), ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, pausedRef, undeliveredContinuationRef, suspendProgressRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, workCutRef, brainCallGuardrailRef, gateStopRef, suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolRoster, toolRosterDeltas: rosterSeat, structuredProjector, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, microCompact, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, stopRequestedRef, announcedSnapshotRecovered: onceLedger.recovered, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(projectInstructionContent !== undefined ? { projectInstructionContent } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, frameReceipts: { pending: [], listingSnapshotOwed: false }, gitStatusRef, listBackgroundTasks, hookIdentity, hookTimeoutMs, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}), ...(toolDisclosureSeat !== undefined ? { toolDisclosure: toolDisclosureSeat } : {}) });
708
710
  rollback.commit();
709
711
  const prepared = buildPrepared();
710
712
  preparedHolder.current = prepared;
@@ -1,4 +1,5 @@
1
1
  import { startTimeout } from "./clock-and-limits.js";
2
+ import { forwardSafely } from "./forward-sink.js";
2
3
  export function runClockAndContent(input) {
3
4
  const { spec, prepared, queue, internals, rs, ident, parentToolCallId, startedToolCallIds } = input;
4
5
  const effectiveTimeoutMs = spec.limits?.maxWalltimeMs;
@@ -7,13 +8,8 @@ export function runClockAndContent(input) {
7
8
  const timeout = startTimeout(prepared.harness, prepared.abortController, walltimeMonotonicDeadline !== undefined ? walltimeMonotonicDeadline - performance.now() : undefined, prepared.suspendForResource !== undefined);
8
9
  const pushContent = (e) => {
9
10
  queue.push(e);
10
- if (parentToolCallId !== undefined && internals?.onForwardEvent) {
11
- try {
12
- internals.onForwardEvent(e);
13
- }
14
- catch {
15
- }
16
- }
11
+ if (parentToolCallId !== undefined)
12
+ forwardSafely(internals, e);
17
13
  };
18
14
  const ownCommittedTailRef = { current: undefined };
19
15
  const emitCommitted = (entryId, role, toolCallId) => {
@@ -1,4 +1,5 @@
1
1
  import { createSafeNotifier, observeThenableRejection } from "../safe-notify.js";
2
+ import { forwardSafely } from "./forward-sink.js";
2
3
  import { emitTrace } from "../trace.js";
3
4
  import { primaryActivityArg } from "../arg-summary.js";
4
5
  import { readDegradation } from "../../brain/degrading.js";
@@ -58,17 +59,20 @@ export function createHarnessHandlers(input) {
58
59
  }
59
60
  if (event.entryId !== undefined && m.role === "user")
60
61
  prepared.fileHistoryBoundary?.begin(event.entryId);
61
- if (event.entryId !== undefined && m.role === "user" && prepared.gitStatusRef.pendingReceipt !== undefined) {
62
- const pr = prepared.gitStatusRef.pendingReceipt;
62
+ if (event.entryId !== undefined && m.role === "user" && prepared.frameReceipts.pending.length > 0) {
63
63
  const c = m.content;
64
64
  const text = typeof c === "string"
65
65
  ? c
66
66
  : Array.isArray(c) && c.length >= 1 && c[0].type === "text"
67
67
  ? c[0].text
68
68
  : undefined;
69
- if (text === pr.text) {
70
- delete prepared.gitStatusRef.pendingReceipt;
71
- pr.commit(event.entryId);
69
+ if (text !== undefined) {
70
+ const settled = prepared.frameReceipts.pending.filter((r) => r.text === text);
71
+ if (settled.length > 0) {
72
+ prepared.frameReceipts.pending = prepared.frameReceipts.pending.filter((r) => r.text !== text);
73
+ for (const r of settled)
74
+ r.commit(event.entryId);
75
+ }
72
76
  }
73
77
  }
74
78
  if (rs.degrade.degraded === undefined && m.role === "assistant") {
@@ -309,11 +313,7 @@ export function createHarnessHandlers(input) {
309
313
  ...ident(),
310
314
  };
311
315
  queue.push(progress);
312
- try {
313
- internals?.onForwardEvent?.(progress);
314
- }
315
- catch {
316
- }
316
+ forwardSafely(internals, progress);
317
317
  }
318
318
  emitTrace(rs.telemetry.tracer, () => ({ kind: "turn.end", version: 1, taskId: rs.telemetry.taskId, turn: stats.turns, ts: Date.now() }));
319
319
  rs.turn.turnUsage = undefined;
@@ -26,8 +26,9 @@ import { maybeHumanRejectionHalt } from "./prepare-gate-stations.js";
26
26
  import { nextHumanInputSeq } from "./steer-admission.js";
27
27
  import { settleTeardownLeg } from "./teardown-bounded.js";
28
28
  import { toolEndBodyFrom, toolResultMsg } from "./tool-end-body.js";
29
- import { announceSkillListingTruncation } from "../skill-listing-budget.js";
30
- import { attachmentEnvelopeTags, commitAgentListing, commitSkillsListing, reduceToolEnd, renderAgentListingDelta, renderOrphanedBackgroundTasks, renderSkillsListingFrame, stampWriteAnchor } from "./turn-attachments.js";
29
+ import { announceListingTruncation } from "../skill-listing-budget.js";
30
+ import { attachmentEnvelopeTags, commitAgentListing, commitSkillsListing, reduceToolEnd, renderAgentListingFrame, renderOrphanedBackgroundTasks, renderSkillsListingFrame, stampWriteAnchor } from "./turn-attachments.js";
31
+ import { flushOwedAnnouncedSnapshot } from "./announce-once-ledger.js";
31
32
  export async function runLeg(input) {
32
33
  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;
33
34
  let final;
@@ -234,7 +235,8 @@ export async function runLeg(input) {
234
235
  if (gitResumeDelivered !== undefined) {
235
236
  const delivered = gitResumeDelivered;
236
237
  prepared.gitStatusRef.protectedText = delivered.wrapped;
237
- prepared.gitStatusRef.pendingReceipt = {
238
+ prepared.frameReceipts.pending.push({
239
+ source: "git_status",
238
240
  text: continuation,
239
241
  commit: (entryId) => {
240
242
  prepared.gitStatusRef.announced = { kind: delivered.kind, hash: delivered.hash, entryId };
@@ -242,7 +244,7 @@ export async function runLeg(input) {
242
244
  rs.attach.attachmentsInjected += 1;
243
245
  prepared.gitStatusRef.mirrorOwed = { kind: delivered.kind, hash: delivered.hash, entryId };
244
246
  },
245
- };
247
+ });
246
248
  }
247
249
  const pending = resume.cp.pendingAction;
248
250
  const rejectionHalt = pending.kind === "tool_approval" && resume.outcome.gate === "policy_ask"
@@ -332,22 +334,22 @@ export async function runLeg(input) {
332
334
  }
333
335
  }
334
336
  const firstFrames = [];
335
- let skillsListingCost;
336
337
  if (rs.attach.attachState !== undefined) {
337
338
  const attach = rs.attach.attachState;
338
339
  if (rs.attach.agentListingOn && prepared.agentListing !== undefined) {
339
340
  const listing = prepared.agentListing;
340
- const body = renderAgentListingDelta(attach, listing.entries, listing.toolName, listing.models);
341
- if (body !== undefined) {
341
+ const frame = renderAgentListingFrame(attach, listing.entries, listing.toolName, listing.models, listing.budget);
342
+ if (frame !== undefined) {
342
343
  firstFrames.push({
343
344
  source: "agent_listing",
344
- body,
345
+ body: frame.text,
345
346
  commit: () => {
346
347
  commitAgentListing(attach, listing.entries, listing.models);
347
348
  prepared.announcedListingsRef.agents = listing.entries.map((e) => e.name);
348
349
  if (listing.models !== undefined)
349
350
  prepared.announcedListingsRef.models = [...listing.models];
350
351
  },
352
+ disclose: () => announceListingTruncation(runner.deps.onNotice, { listing: "agents", total: frame.listed, nameOnly: frame.nameOnly, dropped: frame.dropped, budget: listing.budget, sessionId: prepared.sessionId }),
351
353
  });
352
354
  }
353
355
  }
@@ -361,8 +363,8 @@ export async function runLeg(input) {
361
363
  commit: () => {
362
364
  commitSkillsListing(attach, listing.entries);
363
365
  prepared.announcedListingsRef.skills = listing.entries.map((e) => e.name);
364
- skillsListingCost = { total: frame.listed, nameOnly: frame.nameOnly, dropped: frame.dropped, budget: listing.budget };
365
366
  },
367
+ disclose: () => announceListingTruncation(runner.deps.onNotice, { listing: "skills", total: frame.listed, nameOnly: frame.nameOnly, dropped: frame.dropped, budget: listing.budget, sessionId: prepared.sessionId }),
366
368
  });
367
369
  }
368
370
  }
@@ -418,7 +420,8 @@ export async function runLeg(input) {
418
420
  if (delivered.standalone)
419
421
  await prepared.harness.nextTurn(delivered.wrapped, { engineMinted: true });
420
422
  prepared.gitStatusRef.protectedText = delivered.wrapped;
421
- prepared.gitStatusRef.pendingReceipt = {
423
+ prepared.frameReceipts.pending.push({
424
+ source: "git_status",
422
425
  text: delivered.standalone ? delivered.wrapped : effectiveObjective,
423
426
  commit: (entryId) => {
424
427
  prepared.gitStatusRef.announced = { kind: delivered.kind, hash: delivered.hash, entryId };
@@ -426,17 +429,20 @@ export async function runLeg(input) {
426
429
  rs.attach.attachmentsInjected += 1;
427
430
  prepared.gitStatusRef.mirrorOwed = { kind: delivered.kind, hash: delivered.hash, entryId };
428
431
  },
429
- };
432
+ });
430
433
  }
431
434
  for (const f of firstFrames) {
432
- f.commit();
433
- queue.push({ type: "steering_injected", source: f.source, preview: redactThenCut(f.body, 220), ...ident() });
434
- }
435
- rs.attach.attachmentsInjected += firstFrames.length;
436
- if (firstFrames.length > 0 && prepared.announcedSnapshotRecovered)
437
- await prepared.session.appendAnnouncedListing(prepared.announcedListingsRef).catch(() => undefined);
438
- if (skillsListingCost !== undefined) {
439
- announceSkillListingTruncation(runner.deps.onNotice, { ...skillsListingCost, sessionId: prepared.sessionId });
435
+ prepared.frameReceipts.pending.push({
436
+ source: f.source,
437
+ text: effectiveObjective,
438
+ commit: () => {
439
+ f.commit();
440
+ queue.push({ type: "steering_injected", source: f.source, preview: redactThenCut(f.body, 220), ...ident() });
441
+ rs.attach.attachmentsInjected += 1;
442
+ prepared.frameReceipts.listingSnapshotOwed = true;
443
+ f.disclose();
444
+ },
445
+ });
440
446
  }
441
447
  const enginePrefixChars = effectiveObjective.length - projectedObjective.length;
442
448
  queue.push({
@@ -481,6 +487,9 @@ export async function runLeg(input) {
481
487
  timeout.clear();
482
488
  prepared.lspDiagnostics?.registry.releaseRun(prepared.lspDiagnostics.runIdent);
483
489
  prepared.toolProgressRef.current?.clear();
490
+ if (prepared.frameReceipts.listingSnapshotOwed) {
491
+ await settleTeardownLeg(() => flushOwedAnnouncedSnapshot(prepared), "announced-listing snapshot (leg exit)", (e) => runner.deps.onError?.(e, { phase: "degraded", sessionId: prepared.sessionId }));
492
+ }
484
493
  prepared.abortController.abort();
485
494
  prepared.releaseSignal();
486
495
  strandedHumanAnswers = prepared.settleContentAskBindings();
@@ -12,6 +12,7 @@ import { emitTrace } from "../trace.js";
12
12
  import { deliverEngineNotice } from "../types.js";
13
13
  import { assembleResult } from "./assemble-result.js";
14
14
  import { discloseUnconsumedHalt } from "./halt-attribution.js";
15
+ import { forwardSafely } from "./forward-sink.js";
15
16
  import { TIMER_LATENESS_REPORT_MS } from "./clock-and-limits.js";
16
17
  import { resumeDecisionWasNegative } from "./decide-continuation.js";
17
18
  import { drainForwardedFramesBeforeDone, forwardsSubagentEvents } from "./prepare-run-refs.js";
@@ -276,11 +277,7 @@ export async function runSettleAndTeardown(input) {
276
277
  ...ident(),
277
278
  };
278
279
  queue.push(terminalTick);
279
- try {
280
- internals?.onForwardEvent?.(terminalTick);
281
- }
282
- catch {
283
- }
280
+ forwardSafely(internals, terminalTick);
284
281
  }
285
282
  if (manualCompactRef.waiters.length > 0) {
286
283
  queue.push({ type: "compaction_outcome", outcome: "mooted", trigger: "manual", reason: "task_ending", ...ident() });
@@ -4,8 +4,8 @@ import { eventDefaultOn } from "../../prompt-assembly/event-registry.js";
4
4
  import { DEFAULT_COMPACTION_INSTRUCTIONS, isCompactionManualCancel, maybeCompact, nextTrimForceBackoff, recordCompactionAndCheckRapidRefill } from "../auto-compaction.js";
5
5
  import { emitTrace } from "../trace.js";
6
6
  import { redactThenCut } from "../../agents/subagent-steps.js";
7
- import { ATTACHMENT_BYTE_CAP, CHANGED_FILES_MAX, TOTAL_TOKENS_REMINDER_DEFAULT_MODE, attachmentEnvelopeTags, clipToBytes, collectDateChange, collectDueAttachments, collectInstructionsChange, commitAgentListing, commitInstructionsChange, commitSkillsListing, rebaseCadenceWindows, renderAgentListingDelta, renderMcpDroppedTools, renderMcpInstructionsDelta, selectMcpDroppedBatch, renderSkillsListingFrame, renderToolsDelta } from "./turn-attachments.js";
8
- import { announceSkillListingTruncation } from "../skill-listing-budget.js";
7
+ import { ATTACHMENT_BYTE_CAP, CHANGED_FILES_MAX, TOTAL_TOKENS_REMINDER_DEFAULT_MODE, attachmentEnvelopeTags, clipToBytes, collectDateChange, collectDueAttachments, collectInstructionsChange, commitAgentListing, commitInstructionsChange, commitSkillsListing, isIntactSource, rebaseCadenceWindows, renderAgentListingFrame, renderMcpDroppedTools, renderMcpInstructionsDelta, selectMcpDroppedBatch, renderSkillsListingFrame, renderToolsDelta } from "./turn-attachments.js";
8
+ import { announceListingTruncation } from "../skill-listing-budget.js";
9
9
  import { buildWorkingFileAttachments, centerAdoptionOption, contextInstructionFilesOption, emitInputTruncated, forkContextOption } from "./compaction-call-options.js";
10
10
  import { hookSeatExpiredError, runHookSeat } from "../hooks.js";
11
11
  import { sanitizeUntrustedText, SHELLED_BODY_ENVELOPE_TAGS } from "../untrusted-text.js";
@@ -203,7 +203,7 @@ export function createTurnBoundary(input) {
203
203
  }
204
204
  let boundaryAttachmentBytes = 0;
205
205
  let attachmentsPayload;
206
- let skillsListingCost;
206
+ const listingCommits = [];
207
207
  if ((rs.attach.dateState !== undefined ||
208
208
  rs.attach.instrState !== undefined ||
209
209
  rs.attach.sizeGuidelineState !== undefined ||
@@ -215,6 +215,7 @@ export function createTurnBoundary(input) {
215
215
  !prepared.abortController.signal.aborted) {
216
216
  const due = [];
217
217
  if (rs.attach.attachState !== undefined) {
218
+ const attachState = rs.attach.attachState;
218
219
  const changedFilesOn = Boolean(rs.attach.attachmentsCfg?.changedFiles);
219
220
  let changed;
220
221
  if (changedFilesOn && prepared.detectExternalChanges !== undefined) {
@@ -265,6 +266,10 @@ export function createTurnBoundary(input) {
265
266
  const undiscoveredTools = toolSearchReminderOn
266
267
  ? [...prepared.deferredToolNames].filter((n) => !prepared.activeTools.has(n)).sort()
267
268
  : undefined;
269
+ const receiptPending = (source) => prepared.frameReceipts.pending.some((r) => r.source === source);
270
+ const agentListingDue = rs.attach.agentListingOn && prepared.agentListing !== undefined && !receiptPending("agent_listing");
271
+ const skillsListingDue = rs.attach.skillsListingOn && prepared.skillsListing !== undefined && !receiptPending("skills_listing");
272
+ const listingBudget = prepared.skillsListing?.budget ?? prepared.agentListing?.budget;
268
273
  due.push(...collectDueAttachments(rs.attach.attachState, {
269
274
  turn: stats.turns,
270
275
  cadenceTurn: rs.counters.cadenceTurns,
@@ -308,16 +313,15 @@ export function createTurnBoundary(input) {
308
313
  ? { readdedToolsSwappedUnderStatic: pendingReaddedTools.filter((n) => prepared.staticFaceFor?.(n) !== true) }
309
314
  : {}),
310
315
  ...(mcpToolsDelta !== undefined ? { mcpToolsDelta } : {}),
311
- ...(rs.attach.agentListingOn && prepared.agentListing !== undefined
316
+ ...(agentListingDue
312
317
  ? {
313
318
  agentListing: prepared.agentListing.entries,
314
319
  agentToolName: prepared.agentListing.toolName,
315
320
  ...(prepared.agentListing.models !== undefined ? { agentModels: prepared.agentListing.models } : {}),
316
321
  }
317
322
  : {}),
318
- ...(rs.attach.skillsListingOn && prepared.skillsListing !== undefined
319
- ? { skillsListing: prepared.skillsListing.entries, skillsListingBudget: prepared.skillsListing.budget }
320
- : {}),
323
+ ...(skillsListingDue ? { skillsListing: prepared.skillsListing.entries } : {}),
324
+ ...(listingBudget !== undefined ? { listingBudget } : {}),
321
325
  ...(mcpAdds !== undefined || mcpRemovals !== undefined
322
326
  ? { mcpInstructions: { added: mcpAdds ?? [], removed: mcpRemovals ?? [] } }
323
327
  : {}),
@@ -367,31 +371,36 @@ export function createTurnBoundary(input) {
367
371
  announcedOnceDirty = true;
368
372
  }
369
373
  }
370
- if (rs.attach.agentListingOn && prepared.agentListing !== undefined && due.some((a) => a.source === "agent_listing")) {
374
+ if (agentListingDue && due.some((a) => a.source === "agent_listing")) {
371
375
  const listing = prepared.agentListing;
372
- const intactBody = renderAgentListingDelta(rs.attach.attachState, listing.entries, listing.toolName, listing.models);
373
- if (intactBody !== undefined && due.some((a) => a.source === "agent_listing" && a.body === intactBody)) {
374
- commitAgentListing(rs.attach.attachState, listing.entries, listing.models);
375
- prepared.announcedListingsRef.agents = listing.entries.map((e) => e.name);
376
- if (listing.models !== undefined)
377
- prepared.announcedListingsRef.models = [...listing.models];
378
- if (prepared.announcedSnapshotRecovered)
379
- await prepared.session.appendAnnouncedListing(prepared.announcedListingsRef).catch(() => undefined);
380
- }
376
+ const frame = renderAgentListingFrame(attachState, listing.entries, listing.toolName, listing.models, listing.budget);
377
+ listingCommits.push({
378
+ source: "agent_listing",
379
+ commit: () => {
380
+ commitAgentListing(attachState, listing.entries, listing.models);
381
+ prepared.announcedListingsRef.agents = listing.entries.map((e) => e.name);
382
+ if (listing.models !== undefined)
383
+ prepared.announcedListingsRef.models = [...listing.models];
384
+ prepared.frameReceipts.listingSnapshotOwed = true;
385
+ announceListingTruncation(deps.runner.deps.onNotice, { listing: "agents", total: frame.listed, nameOnly: frame.nameOnly, dropped: frame.dropped, budget: listing.budget, sessionId: prepared.sessionId });
386
+ },
387
+ });
381
388
  }
382
- if (rs.attach.skillsListingOn && prepared.skillsListing !== undefined && due.some((a) => a.source === "skills_listing")) {
389
+ if (skillsListingDue && due.some((a) => a.source === "skills_listing")) {
383
390
  const listing = prepared.skillsListing;
384
- const intact = renderSkillsListingFrame(rs.attach.attachState, listing.entries, listing.budget);
385
- if (intact !== undefined && due.some((a) => a.source === "skills_listing" && a.body === intact.text)) {
386
- commitSkillsListing(rs.attach.attachState, listing.entries);
387
- skillsListingCost = { total: intact.listed, nameOnly: intact.nameOnly, dropped: intact.dropped, budget: listing.budget };
388
- prepared.announcedListingsRef.skills = listing.entries.map((e) => e.name);
389
- if (prepared.announcedSnapshotRecovered)
390
- await prepared.session.appendAnnouncedListing(prepared.announcedListingsRef).catch(() => undefined);
391
- }
391
+ const frame = renderSkillsListingFrame(attachState, listing.entries, listing.budget);
392
+ listingCommits.push({
393
+ source: "skills_listing",
394
+ commit: () => {
395
+ commitSkillsListing(attachState, listing.entries);
396
+ prepared.announcedListingsRef.skills = listing.entries.map((e) => e.name);
397
+ prepared.frameReceipts.listingSnapshotOwed = true;
398
+ announceListingTruncation(deps.runner.deps.onNotice, { listing: "skills", total: frame.listed, nameOnly: frame.nameOnly, dropped: frame.dropped, budget: listing.budget, sessionId: prepared.sessionId });
399
+ },
400
+ });
392
401
  }
393
- if (announcedOnceDirty && prepared.announcedSnapshotRecovered)
394
- await prepared.session.appendAnnouncedListing(prepared.announcedListingsRef).catch(() => undefined);
402
+ if (announcedOnceDirty)
403
+ prepared.frameReceipts.listingSnapshotOwed = true;
395
404
  }
396
405
  if (rs.attach.dateState !== undefined && rs.turn.lastTurnHadToolCalls) {
397
406
  const today = prepared.dateChange.today();
@@ -434,7 +443,8 @@ export function createTurnBoundary(input) {
434
443
  if (due.length > 0) {
435
444
  attachmentsPayload = due.map((a) => mintSystemReminder(sanitizeUntrustedText(a.body, attachmentEnvelopeTags(a.source)), prepared.reminderMark)).join("\n");
436
445
  for (const a of due)
437
- boundaryAttachmentBytes += Buffer.byteLength(a.body, "utf8");
446
+ if (!isIntactSource(a.source))
447
+ boundaryAttachmentBytes += Buffer.byteLength(a.body, "utf8");
438
448
  rs.attach.attachmentsInjected += due.length;
439
449
  for (const a of due) {
440
450
  queue.push({ type: "steering_injected", source: a.source, preview: redactThenCut(a.body, 220), ...ident() });
@@ -478,10 +488,11 @@ export function createTurnBoundary(input) {
478
488
  }
479
489
  if ((attachmentsPayload !== undefined || batchContextBlock !== undefined) && prepared.batchHaltRef.current === undefined) {
480
490
  const payload = attachmentsPayload !== undefined && batchContextBlock !== undefined ? `${attachmentsPayload}\n${batchContextBlock}` : (attachmentsPayload ?? batchContextBlock);
481
- void prepared.harness.steer(payload, { engineMinted: true }).catch(() => { });
482
- if (skillsListingCost !== undefined) {
483
- announceSkillListingTruncation(deps.runner.deps.onNotice, { ...skillsListingCost, sessionId: prepared.sessionId });
484
- }
491
+ for (const c of listingCommits)
492
+ prepared.frameReceipts.pending.push({ source: c.source, text: payload, commit: c.commit });
493
+ void prepared.harness.steer(payload, { engineMinted: true }).catch(() => {
494
+ prepared.frameReceipts.pending = prepared.frameReceipts.pending.filter((r) => r.text !== payload);
495
+ });
485
496
  }
486
497
  const forceManual = manualCompactRef.requested;
487
498
  if ((!withinTaskCompaction && !forceManual) ||
@@ -50,6 +50,7 @@ import { runToolMountFacts } from "./run-tool-mount-facts.js";
50
50
  import { runReasoningSeat } from "./run-reasoning-seat.js";
51
51
  import { runClockAndContent } from "./run-clock-and-content.js";
52
52
  import { createToolProgressLane } from "./tool-progress-lane.js";
53
+ import { createForwardSinkScope } from "./forward-sink.js";
53
54
  import { runBrainSinks } from "./run-brain-sinks.js";
54
55
  import { runCompactionMachinery } from "./run-compaction-machinery.js";
55
56
  import { runStopAndFinalVerify } from "./run-stop-and-final-verify.js";
@@ -344,7 +345,7 @@ export class Runner {
344
345
  manualCompactRef,
345
346
  taskIdRef,
346
347
  resume,
347
- internals: { ...(internals ?? {}), detachHub },
348
+ internals: { ...(internals ?? {}), detachHub, forwardScope: createForwardSinkScope(this.deps.onNotice) },
348
349
  notifyRef,
349
350
  captureOptOutRef,
350
351
  entryActor,