@sema-agent/core 7.7.0 → 7.9.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 (186) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/dist/agents/agent-transcript-tool.js +2 -2
  3. package/dist/agents/list-agents-tool.js +2 -3
  4. package/dist/agents/observer.js +2 -2
  5. package/dist/agents/send-message-tool.js +2 -1
  6. package/dist/agents/subagent-steps.js +5 -4
  7. package/dist/agents/subagent.d.ts +1 -1
  8. package/dist/agents/subagent.js +2 -3
  9. package/dist/core/ask-origin.d.ts +12 -1
  10. package/dist/core/ask-origin.js +5 -1
  11. package/dist/core/ask-question.js +2 -2
  12. package/dist/core/checkpoint-store.d.ts +64 -24
  13. package/dist/core/context-edit.d.ts +0 -22
  14. package/dist/core/context-edit.js +2 -11
  15. package/dist/core/engine-notice.d.ts +6 -0
  16. package/dist/core/fs-write-gate-policy.d.ts +7 -1
  17. package/dist/core/fs-write-gate-policy.js +7 -7
  18. package/dist/core/gate-fold.js +2 -2
  19. package/dist/core/gate-lanes.js +53 -26
  20. package/dist/core/gate-outcome.d.ts +8 -4
  21. package/dist/core/gate-outcome.js +2 -1
  22. package/dist/core/governance-codes.d.ts +1 -1
  23. package/dist/core/governance-codes.js +6 -0
  24. package/dist/core/hooks.d.ts +64 -33
  25. package/dist/core/hooks.js +1 -1
  26. package/dist/core/lsp.js +2 -2
  27. package/dist/core/mcp-server-spec.d.ts +17 -0
  28. package/dist/core/mcp.js +3 -1
  29. package/dist/core/memory-engine/tools.js +4 -12
  30. package/dist/core/permission-rule-consent.d.ts +29 -36
  31. package/dist/core/permission-rule-consent.js +110 -60
  32. package/dist/core/permission-rule-model.d.ts +254 -55
  33. package/dist/core/permission-rule-model.js +323 -43
  34. package/dist/core/permission-rule-org.d.ts +9 -8
  35. package/dist/core/permission-rule-org.js +6 -15
  36. package/dist/core/permission-rule-provider.d.ts +10 -6
  37. package/dist/core/permission-rule-provider.js +12 -8
  38. package/dist/core/permission-rule-session.d.ts +7 -6
  39. package/dist/core/permission-rule-session.js +29 -10
  40. package/dist/core/permission-rule-store.d.ts +40 -18
  41. package/dist/core/permission-rule-store.js +68 -42
  42. package/dist/core/permission-rule-sync.d.ts +9 -3
  43. package/dist/core/permission-rule-sync.js +29 -25
  44. package/dist/core/permission-rule-syntax.d.ts +30 -0
  45. package/dist/core/permission-rule-syntax.js +44 -0
  46. package/dist/core/permission-rules.d.ts +85 -33
  47. package/dist/core/permission-rules.js +104 -88
  48. package/dist/core/persisted-rule-arms.d.ts +56 -0
  49. package/dist/core/persisted-rule-arms.js +48 -0
  50. package/dist/core/present-plan-tool.js +3 -4
  51. package/dist/core/runner/active-skill-scope.js +7 -4
  52. package/dist/core/runner/compaction-call-options.d.ts +1 -1
  53. package/dist/core/runner/contracts.d.ts +46 -3
  54. package/dist/core/runner/permission-rule-lanes.d.ts +33 -26
  55. package/dist/core/runner/permission-rule-lanes.js +27 -21
  56. package/dist/core/runner/prepare-ask-lane.d.ts +1 -1
  57. package/dist/core/runner/prepare-caps-and-workflow.d.ts +5 -4
  58. package/dist/core/runner/prepare-caps-and-workflow.js +16 -11
  59. package/dist/core/runner/prepare-defer-classify.d.ts +5 -26
  60. package/dist/core/runner/prepare-defer-classify.js +10 -10
  61. package/dist/core/runner/prepare-delegation-surface.d.ts +5 -4
  62. package/dist/core/runner/prepare-delegation-surface.js +15 -17
  63. package/dist/core/runner/prepare-gate-stations.d.ts +4 -1
  64. package/dist/core/runner/prepare-gate-stations.js +4 -2
  65. package/dist/core/runner/prepare-hands-readface.d.ts +3 -2
  66. package/dist/core/runner/prepare-hands-readface.js +3 -2
  67. package/dist/core/runner/prepare-inherited-gate.d.ts +1 -1
  68. package/dist/core/runner/prepare-inherited-gate.js +1 -0
  69. package/dist/core/runner/prepare-lsp.d.ts +4 -3
  70. package/dist/core/runner/prepare-lsp.js +3 -2
  71. package/dist/core/runner/prepare-memory-engine-session.d.ts +4 -4
  72. package/dist/core/runner/prepare-memory-engine-session.js +4 -2
  73. package/dist/core/runner/prepare-offload-wrappers.js +7 -5
  74. package/dist/core/runner/prepare-park-ask.d.ts +4 -0
  75. package/dist/core/runner/prepare-park-ask.js +4 -1
  76. package/dist/core/runner/prepare-project-context.d.ts +5 -23
  77. package/dist/core/runner/prepare-project-context.js +7 -5
  78. package/dist/core/runner/prepare-prompt-assembly.d.ts +1 -1
  79. package/dist/core/runner/prepare-protocol-tools.d.ts +5 -1
  80. package/dist/core/runner/prepare-protocol-tools.js +38 -21
  81. package/dist/core/runner/prepare-question-face.d.ts +4 -3
  82. package/dist/core/runner/prepare-question-face.js +5 -3
  83. package/dist/core/runner/prepare-run-refs.d.ts +16 -0
  84. package/dist/core/runner/prepare-run-refs.js +6 -0
  85. package/dist/core/runner/prepare-safety-scan.js +48 -2
  86. package/dist/core/runner/prepare-task.js +37 -19
  87. package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +5 -2
  88. package/dist/core/runner/prepare-tool-disclosure-mount.js +3 -1
  89. package/dist/core/runner/prepare-wiring-manifest.d.ts +4 -0
  90. package/dist/core/runner/prepare-wiring-manifest.js +3 -8
  91. package/dist/core/runner/resume-admission.d.ts +53 -0
  92. package/dist/core/runner/resume-admission.js +83 -0
  93. package/dist/core/runner/resume-apply.d.ts +50 -0
  94. package/dist/core/runner/resume-apply.js +184 -0
  95. package/dist/core/runner/resume-checkpoint-screen.d.ts +18 -0
  96. package/dist/core/runner/resume-checkpoint-screen.js +108 -0
  97. package/dist/core/runner/resume-claim.d.ts +32 -0
  98. package/dist/core/runner/resume-claim.js +27 -0
  99. package/dist/core/runner/resume-internals-and-config.d.ts +33 -0
  100. package/dist/core/runner/resume-internals-and-config.js +50 -0
  101. package/dist/core/runner/resume-policy-outcome.d.ts +31 -0
  102. package/dist/core/runner/resume-policy-outcome.js +127 -0
  103. package/dist/core/runner/resume-preflight.d.ts +40 -0
  104. package/dist/core/runner/resume-preflight.js +122 -0
  105. package/dist/core/runner/resume-review-outcome.d.ts +30 -0
  106. package/dist/core/runner/resume-review-outcome.js +88 -0
  107. package/dist/core/runner/run-harness-handlers.d.ts +46 -0
  108. package/dist/core/runner/run-harness-handlers.js +345 -0
  109. package/dist/core/runner/run-turn-boundary.d.ts +69 -0
  110. package/dist/core/runner/run-turn-boundary.js +694 -0
  111. package/dist/core/runner/runtask.d.ts +20 -0
  112. package/dist/core/runner/runtask.js +129 -1774
  113. package/dist/core/runner/session-rule-policy.js +9 -4
  114. package/dist/core/runner/synthetic-tools.js +5 -8
  115. package/dist/core/runner/tool-disclosure.d.ts +4 -0
  116. package/dist/core/runner/tool-disclosure.js +9 -5
  117. package/dist/core/runner/tool-end-body.d.ts +12 -5
  118. package/dist/core/runner/tool-end-body.js +8 -7
  119. package/dist/core/runner/tool-face-overlay.d.ts +40 -0
  120. package/dist/core/runner/tool-face-overlay.js +125 -0
  121. package/dist/core/runner/tool-output-projection.d.ts +14 -1
  122. package/dist/core/runner/tool-output-projection.js +24 -18
  123. package/dist/core/runner/turn-attachments.d.ts +2 -2
  124. package/dist/core/sensitive-path-policy.d.ts +3 -0
  125. package/dist/core/sensitive-path-policy.js +6 -5
  126. package/dist/core/shared-memory/tools.js +3 -6
  127. package/dist/core/store-contracts/checkpoint-store-contract.js +23 -0
  128. package/dist/core/store-contracts/permission-rule-sync-contract.js +29 -10
  129. package/dist/core/task-event.d.ts +10 -0
  130. package/dist/core/task-registry.js +4 -5
  131. package/dist/core/task-spec.d.ts +8 -0
  132. package/dist/core/tool-catalog-entries.d.ts +48 -0
  133. package/dist/core/tool-catalog-entries.js +174 -0
  134. package/dist/core/tool-conformance.d.ts +22 -0
  135. package/dist/core/tool-conformance.js +37 -0
  136. package/dist/core/tool-face.d.ts +171 -0
  137. package/dist/core/tool-face.js +25 -0
  138. package/dist/core/tool-policy.d.ts +24 -0
  139. package/dist/core/tool-policy.js +4 -2
  140. package/dist/core/tool-registry.d.ts +114 -0
  141. package/dist/core/tool-registry.js +114 -0
  142. package/dist/core/tool-result-budget.d.ts +0 -3
  143. package/dist/core/tool-result-budget.js +4 -3
  144. package/dist/core/tool-result-store.d.ts +0 -18
  145. package/dist/core/tool-result-store.js +2 -18
  146. package/dist/core/tool-roster.d.ts +459 -0
  147. package/dist/core/tool-roster.js +507 -0
  148. package/dist/core/tool-spec.d.ts +54 -3
  149. package/dist/core/tools.js +7 -0
  150. package/dist/core/wiring-manifest.d.ts +13 -0
  151. package/dist/core/wiring-manifest.js +2 -1
  152. package/dist/core/write-protect.d.ts +2 -1
  153. package/dist/core/write-protect.js +5 -4
  154. package/dist/engine/compaction/utils.d.ts +6 -0
  155. package/dist/engine/compaction/utils.js +19 -19
  156. package/dist/engine/loop/types.d.ts +6 -0
  157. package/dist/index.d.ts +17 -6
  158. package/dist/index.js +17 -6
  159. package/dist/orchestration/goal.js +2 -1
  160. package/dist/orchestration/run-spec.js +3 -5
  161. package/dist/orchestration/run-workflow-tool.js +2 -2
  162. package/dist/prompt-assembly/event-registry.js +3 -3
  163. package/dist/prompt-assembly/tool-catalog.d.ts +2 -2
  164. package/dist/stores/file/checkpoint-store.js +1 -1
  165. package/dist/stores/file/permission-rule-store.d.ts +28 -23
  166. package/dist/stores/file/permission-rule-store.js +64 -16
  167. package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -1
  168. package/dist/tools/fs/fs-bash.d.ts +1 -1
  169. package/dist/tools/fs/fs-bash.js +7 -11
  170. package/dist/tools/fs/fs-pdf.d.ts +1 -1
  171. package/dist/tools/fs/fs-read.js +2 -2
  172. package/dist/tools/fs/fs-search-tools.d.ts +0 -7
  173. package/dist/tools/fs/fs-search-tools.js +3 -16
  174. package/dist/tools/fs/fs-write.js +4 -6
  175. package/dist/tools/fs/notebook.d.ts +1 -1
  176. package/dist/tools/fs/repo-map.js +2 -2
  177. package/dist/tools/fs/safety.d.ts +3 -2
  178. package/dist/tools/fs/safety.js +6 -5
  179. package/dist/tools/monitor.js +2 -2
  180. package/dist/tools/scheduler-tools.js +5 -8
  181. package/dist/tools/task-list.js +5 -8
  182. package/dist/tools/todo.js +2 -2
  183. package/dist/tools/web.js +3 -6
  184. package/dist/tools/worktree.js +3 -4
  185. package/package.json +4 -1
  186. package/test/export-surface.snapshot.json +189 -17
@@ -0,0 +1,694 @@
1
+ import { mintSystemReminder } from "../reminder-mint.js";
2
+ import { DEFAULT_COMPACTION_SETTINGS } from "../../internal/harness.js";
3
+ import { eventDefaultOn } from "../../prompt-assembly/event-registry.js";
4
+ import { DEFAULT_COMPACTION_INSTRUCTIONS, isCompactionManualCancel, maybeCompact, nextTrimForceBackoff, recordCompactionAndCheckRapidRefill } from "../auto-compaction.js";
5
+ import { emitTrace } from "../trace.js";
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, renderSkillsListingDelta, renderToolsDelta } from "./turn-attachments.js";
8
+ import { buildWorkingFileAttachments, centerAdoptionOption, contextInstructionFilesOption, emitInputTruncated, forkContextOption } from "./compaction-call-options.js";
9
+ import { hookSeatExpiredError, runHookSeat } from "../hooks.js";
10
+ import { sanitizeUntrustedText, SHELLED_BODY_ENVELOPE_TAGS } from "../untrusted-text.js";
11
+ import { droppedToolDigest, withAnnounceDigests } from "./announce-once-ledger.js";
12
+ import { formatDiagnosticsBlock } from "../lsp-diagnostics.js";
13
+ import { workflowSizeGuidelineChangeNotice } from "../../orchestration/workflow-size-guideline.js";
14
+ import { awaitChargeWithSlowDisclosure, discloseUnevaluableWindow, ENV_DUE_GOVERNANCE_READ_BUDGET_MS, GOVERNANCE_READ_STALLED, LIMIT_APPROACH_DEFAULT_THRESHOLDS, limitApproachFrames, platformLimitTerminal, raceUntilDeadline } from "./clock-and-limits.js";
15
+ import { gitRestateOption } from "./git-leg-delivery.js";
16
+ export const MAX_CONSECUTIVE_COMPACTION_FAILURES = 3;
17
+ export const COMPACTION_REGROWTH_FACTOR = 1.5;
18
+ export const COMPACTION_FREED_EPSILON = 256;
19
+ const BATCH_CONTEXT_MIN_KEEP_BYTES = 160;
20
+ const BATCH_TRUNCATION_MARKER = "\n…[truncated]";
21
+ export function createTurnBoundary(input) {
22
+ const { prepared, stats, rs, deps } = input;
23
+ const { spec, queue, manualCompactRef, todoToolMounted, taskToolsMounted, walltimeMonotonicDeadline, timeout, ident, postToolBatchHook, batchArgs, compactionBrain, withinTaskCompaction, compactionBreaker, windowSafetyOptions, rapidRefill, drainManualCompact, runnerHooks } = deps;
24
+ let finalVerifySeenAtLastBoundary = 0;
25
+ const onTurnBoundary = async (event) => {
26
+ let boundarySteered = false;
27
+ const noNextRequest = () => prepared.stopRequestedRef.current || deps.loopLatch.userHalted;
28
+ const walltimeHit = walltimeMonotonicDeadline !== undefined && performance.now() >= walltimeMonotonicDeadline;
29
+ const preemptHit = spec.preemptSignal?.aborted === true;
30
+ if (preemptHit && !prepared.suspendForResource && !rs.counters.preemptIgnoredReported) {
31
+ rs.counters.preemptIgnoredReported = true;
32
+ emitTrace(rs.telemetry.tracer, () => ({ kind: "preempt.ignored", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() }));
33
+ }
34
+ let platformCause;
35
+ let platformRetryAfterMs;
36
+ const preemptWillSuspend = preemptHit && prepared.suspendForResource !== undefined;
37
+ if (!preemptWillSuspend && prepared.pausedRef.current === undefined && !prepared.abortController.signal.aborted) {
38
+ const ledgerReadDeadline = prepared.envLifetimeSuspendAt !== undefined ? prepared.envLifetimeSuspendAt + ENV_DUE_GOVERNANCE_READ_BUDGET_MS : undefined;
39
+ if (prepared.usageGovernance !== undefined) {
40
+ const governance = prepared.usageGovernance;
41
+ try {
42
+ const at = Date.now();
43
+ await awaitChargeWithSlowDisclosure(governance.commit(stats.tokens, rs.telemetry.unpricedSpend ? undefined : stats.costMicroUsd, at), () => runnerHooks.onError?.(new Error("the usage ledger CHARGE has not settled after 10s — still waiting (a charge is never abandoned: walking away would double-charge on the end-of-task flush). A wedged ledger store wedges this boundary, now visibly."), { phase: "config", sessionId: prepared.sessionId }));
44
+ const windowRead = governance.check(at);
45
+ let answer;
46
+ if (ledgerReadDeadline !== undefined) {
47
+ answer = await raceUntilDeadline(windowRead, ledgerReadDeadline);
48
+ if (answer === GOVERNANCE_READ_STALLED) {
49
+ void windowRead.catch((lateErr) => {
50
+ try {
51
+ runnerHooks.onError?.(lateErr instanceof Error ? lateErr : new Error(String(lateErr)), { phase: "config", sessionId: prepared.sessionId });
52
+ }
53
+ catch {
54
+ }
55
+ });
56
+ try {
57
+ runnerHooks.onError?.(new Error("the usage ledger did not answer before the execution environment's suspend deadline — stopping for the environment, the cause this run can still prove. The window state for this boundary is UNKNOWN and was not enforced."), { phase: "config", sessionId: prepared.sessionId });
58
+ }
59
+ catch {
60
+ }
61
+ }
62
+ }
63
+ else {
64
+ answer = await windowRead;
65
+ }
66
+ if (answer !== GOVERNANCE_READ_STALLED) {
67
+ platformRetryAfterMs = answer;
68
+ if (platformRetryAfterMs !== undefined)
69
+ platformCause = "usage_window";
70
+ }
71
+ }
72
+ catch (govErr) {
73
+ rs.limits.platformTerminal = govErr instanceof Error ? govErr : new Error(String(govErr));
74
+ void prepared.harness.abort();
75
+ prepared.abortController.abort();
76
+ return undefined;
77
+ }
78
+ }
79
+ if (platformCause === undefined && prepared.envLifetimeSuspendAt !== undefined && Date.now() >= prepared.envLifetimeSuspendAt) {
80
+ platformCause = "env_lifetime";
81
+ }
82
+ }
83
+ if (platformCause !== undefined) {
84
+ const platformSpend = { costMicroUsd: stats.costMicroUsd, tokens: stats.tokens, turns: stats.turns, walltimeMs: Math.round(performance.now() - rs.telemetry.taskStartMonotonic) };
85
+ const committed = prepared.suspendForPlatformLimit !== undefined &&
86
+ (await prepared.suspendForPlatformLimit(platformCause, platformSpend, platformRetryAfterMs !== undefined ? { resumeAfterMs: platformRetryAfterMs } : undefined));
87
+ if (committed) {
88
+ rs.limits.turnsExceeded = false;
89
+ rs.limits.budgetHit = undefined;
90
+ return undefined;
91
+ }
92
+ const terminal = platformLimitTerminal(platformCause, platformRetryAfterMs);
93
+ rs.limits.platformTerminal = terminal;
94
+ try {
95
+ runnerHooks.onError?.(terminal, { phase: "config", sessionId: prepared.sessionId });
96
+ }
97
+ catch {
98
+ }
99
+ void prepared.harness.abort();
100
+ prepared.abortController.abort();
101
+ return undefined;
102
+ }
103
+ if (prepared.suspendForResource &&
104
+ (rs.limits.turnsExceeded || rs.limits.budgetHit !== undefined || walltimeHit || preemptHit) &&
105
+ prepared.pausedRef.current === undefined) {
106
+ const reason = preemptHit
107
+ ? "preempt"
108
+ : rs.limits.budgetHit !== undefined
109
+ ? rs.limits.budgetAxis === "tokens"
110
+ ? "budget_tokens"
111
+ : "budget_cost"
112
+ : rs.limits.turnsExceeded
113
+ ? "turns"
114
+ : "walltime";
115
+ const sliceSpend = { costMicroUsd: stats.costMicroUsd, tokens: stats.tokens, turns: stats.turns, walltimeMs: Math.round(performance.now() - rs.telemetry.taskStartMonotonic) };
116
+ if (await prepared.suspendForResource(reason, sliceSpend)) {
117
+ rs.limits.turnsExceeded = false;
118
+ rs.limits.budgetHit = undefined;
119
+ }
120
+ else {
121
+ void prepared.harness.abort();
122
+ prepared.abortController.abort();
123
+ }
124
+ return undefined;
125
+ }
126
+ if (walltimeMonotonicDeadline !== undefined &&
127
+ prepared.suspendForResource === undefined &&
128
+ performance.now() >= walltimeMonotonicDeadline) {
129
+ if (!timeout.fired) {
130
+ timeout.fired = true;
131
+ timeout.clear();
132
+ timeout.latenessMs = Math.max(timeout.latenessMs ?? 0, performance.now() - walltimeMonotonicDeadline);
133
+ rs.counters.walltimeSyncBackstopFired = true;
134
+ void prepared.harness.abort();
135
+ prepared.abortController.abort();
136
+ }
137
+ return undefined;
138
+ }
139
+ const approachCfg = spec.limits?.approachNotice;
140
+ if (approachCfg !== false &&
141
+ rs.counters.approachNoticesSent < LIMIT_APPROACH_DEFAULT_THRESHOLDS.length &&
142
+ prepared.suspendForResource === undefined &&
143
+ rs.turn.lastTurnHadToolCalls &&
144
+ prepared.batchHaltRef.current === undefined &&
145
+ !boundarySteered &&
146
+ !prepared.abortController.signal.aborted) {
147
+ const thresholds = approachCfg?.at ?? LIMIT_APPROACH_DEFAULT_THRESHOLDS;
148
+ const ratios = [];
149
+ if (rs.budget.maxTokensWindow !== undefined && rs.budget.maxTokensWindow > 0) {
150
+ ratios.push({ axis: "token budget", ratio: stats.tokens / rs.budget.maxTokensWindow });
151
+ }
152
+ if (rs.budget.maxCostMicroUsd !== undefined && rs.budget.maxCostMicroUsd > 0) {
153
+ ratios.push({ axis: "cost budget", ratio: stats.costMicroUsd / rs.budget.maxCostMicroUsd });
154
+ }
155
+ const declaredMaxTurns = spec.limits?.maxTurns;
156
+ if (declaredMaxTurns !== undefined && declaredMaxTurns > 0) {
157
+ ratios.push({ axis: "turn budget", ratio: (stats.turns + 1) / declaredMaxTurns });
158
+ }
159
+ if (walltimeMonotonicDeadline !== undefined) {
160
+ const walltimeWindowMs = walltimeMonotonicDeadline - rs.telemetry.taskStartMonotonic;
161
+ if (walltimeWindowMs > 0) {
162
+ ratios.push({ axis: "walltime budget", ratio: (performance.now() - rs.telemetry.taskStartMonotonic) / walltimeWindowMs });
163
+ }
164
+ }
165
+ let tightest;
166
+ for (const r of ratios)
167
+ if (tightest === undefined || r.ratio > tightest.ratio)
168
+ tightest = r;
169
+ const index = rs.counters.approachNoticesSent;
170
+ if (tightest !== undefined && tightest.ratio >= thresholds[index]) {
171
+ rs.counters.approachNoticesSent += 1;
172
+ const percent = Math.min(100, Math.round(tightest.ratio * 100));
173
+ const body = limitApproachFrames(index === 0 ? 0 : 1, tightest.axis, percent, prepared.reminderMark);
174
+ void prepared.harness.steer(body, { engineMinted: true }).catch(() => undefined);
175
+ queue.push({ type: "steering_injected", source: "limit_approach", preview: body.slice(0, 220), ...ident() });
176
+ boundarySteered = true;
177
+ }
178
+ }
179
+ if (prepared.reviewRequestRef.pending !== undefined) {
180
+ const { reason } = prepared.reviewRequestRef.pending;
181
+ prepared.reviewRequestRef.pending = undefined;
182
+ const honorable = prepared.suspendForReview !== undefined &&
183
+ !prepared.abortController.signal.aborted &&
184
+ prepared.pausedRef.current === undefined;
185
+ if (honorable && prepared.suspendForReview) {
186
+ await prepared.suspendForReview(reason);
187
+ return undefined;
188
+ }
189
+ emitTrace(rs.telemetry.tracer, () => ({ kind: "review.dropped", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() }));
190
+ }
191
+ if (prepared.lspDiagnostics &&
192
+ !prepared.lspDiagnostics.registry.isEmpty() &&
193
+ !prepared.abortController.signal.aborted &&
194
+ prepared.batchHaltRef.current === undefined) {
195
+ const files = prepared.lspDiagnostics.registry.drain(prepared.lspDiagnostics.runIdent);
196
+ if (files.length > 0) {
197
+ queue.push({ type: "diagnostics", files, isNew: true, ...ident() });
198
+ const block = mintSystemReminder(formatDiagnosticsBlock(files), prepared.reminderMark);
199
+ void prepared.harness.steer(block, { provenance: "engine-note" }).catch(() => undefined);
200
+ boundarySteered = true;
201
+ }
202
+ }
203
+ let boundaryAttachmentBytes = 0;
204
+ let attachmentsPayload;
205
+ if ((rs.attach.dateState !== undefined ||
206
+ rs.attach.instrState !== undefined ||
207
+ rs.attach.sizeGuidelineState !== undefined ||
208
+ rs.attach.attachState !== undefined) &&
209
+ !boundarySteered &&
210
+ prepared.batchHaltRef.current === undefined &&
211
+ !noNextRequest() &&
212
+ rs.counters.finalVerifyInjections === 0 &&
213
+ !prepared.abortController.signal.aborted) {
214
+ const due = [];
215
+ if (rs.attach.attachState !== undefined) {
216
+ const changedFilesOn = Boolean(rs.attach.attachmentsCfg?.changedFiles);
217
+ let changed;
218
+ if (changedFilesOn && prepared.detectExternalChanges !== undefined) {
219
+ const maxFiles = typeof rs.attach.attachmentsCfg?.changedFiles === "object"
220
+ ? (rs.attach.attachmentsCfg.changedFiles.maxFiles ?? CHANGED_FILES_MAX)
221
+ : CHANGED_FILES_MAX;
222
+ const SCAN_CEILING_MS = 2_000;
223
+ const scan = await Promise.race([
224
+ prepared.detectExternalChanges(maxFiles),
225
+ new Promise((resolve) => {
226
+ setTimeout(() => resolve({ changed: [], evicted: [] }), SCAN_CEILING_MS);
227
+ }),
228
+ ]);
229
+ for (const p of scan.evicted)
230
+ rs.attach.attachState.surfacedMtime.delete(p);
231
+ changed = scan.changed;
232
+ }
233
+ const backgroundTasksOn = (rs.attach.attachmentsCfg?.backgroundTasks ?? eventDefaultOn("background_tasks")) === true;
234
+ const bgTasks = backgroundTasksOn && rs.attach.attachState.postCompactPending ? prepared.listBackgroundTasks() : undefined;
235
+ const toolsDeltaOn = rs.attach.attachmentsCfg?.toolsDelta === true;
236
+ let announcedOnceDirty = false;
237
+ const tdRef = toolsDeltaOn ? prepared.toolsDeltaRef : undefined;
238
+ const pendingTools = tdRef !== undefined && tdRef.pending.length > 0 ? [...tdRef.pending] : undefined;
239
+ const pendingRemovedTools = tdRef !== undefined && tdRef.pendingRemoved.length > 0 ? [...tdRef.pendingRemoved] : undefined;
240
+ const pendingReaddedTools = tdRef !== undefined && tdRef.pendingReadded.length > 0 ? [...tdRef.pendingReadded] : undefined;
241
+ const pendingFailedMcp = tdRef !== undefined && tdRef.pendingFailed.length > 0 ? tdRef.pendingFailed.map((f) => ({ ...f })) : undefined;
242
+ const mcpToolsDelta = pendingRemovedTools !== undefined || pendingReaddedTools !== undefined || pendingFailedMcp !== undefined
243
+ ? {
244
+ ...(pendingRemovedTools !== undefined ? { removed: pendingRemovedTools } : {}),
245
+ ...(pendingReaddedTools !== undefined ? { readded: pendingReaddedTools } : {}),
246
+ ...(pendingFailedMcp !== undefined ? { failedServers: pendingFailedMcp } : {}),
247
+ }
248
+ : undefined;
249
+ const mcpInstructionsOn = rs.attach.attachmentsCfg?.mcpInstructions === true;
250
+ const mcpDelta = prepared.mcp.instructionsDelta;
251
+ const mcpAdds = mcpInstructionsOn && mcpDelta.pendingAdds.length > 0 ? [...mcpDelta.pendingAdds] : undefined;
252
+ const mcpRemovals = mcpInstructionsOn && mcpDelta.pendingRemovals.length > 0 ? [...mcpDelta.pendingRemovals] : undefined;
253
+ const mcpDropped = mcpInstructionsOn && prepared.mcp.droppedTools.length > 0 ? selectMcpDroppedBatch(prepared.mcp.droppedTools) : undefined;
254
+ const budgetUsdOn = rs.attach.attachmentsCfg?.budgetUsd === true && rs.budget.maxCostMicroUsd !== undefined && rs.turn.lastTurnHadToolCalls;
255
+ const totalTokensDeclared = rs.attach.attachmentsCfg?.totalTokensReminder;
256
+ const totalTokensMode = rs.attach.attachmentsCfg?.totalTokensReminderMode ?? TOTAL_TOKENS_REMINDER_DEFAULT_MODE;
257
+ const totalTokensArmedByDefault = totalTokensDeclared === undefined &&
258
+ eventDefaultOn("total_tokens_reminder") &&
259
+ totalTokensMode !== "fixed" &&
260
+ totalTokensMode !== "infinite";
261
+ const totalTokensOn = (totalTokensDeclared === true || totalTokensArmedByDefault) && rs.turn.lastTurnHadToolCalls;
262
+ const toolSearchReminderOn = rs.attach.attachmentsCfg?.toolSearchReminder === true && prepared.deferredToolNames !== undefined;
263
+ const undiscoveredTools = toolSearchReminderOn
264
+ ? [...prepared.deferredToolNames].filter((n) => !prepared.activeTools.has(n)).sort()
265
+ : undefined;
266
+ due.push(...collectDueAttachments(rs.attach.attachState, {
267
+ turn: stats.turns,
268
+ cadenceTurn: rs.counters.cadenceTurns,
269
+ todoMounted: todoToolMounted,
270
+ taskListMounted: taskToolsMounted,
271
+ planActive: prepared.planModeRef.active,
272
+ config: {
273
+ todoReminder: rs.attach.attachmentsCfg?.todoReminder === true,
274
+ ...(rs.attach.attachmentsCfg?.todoReminderMode !== undefined ? { todoReminderMode: rs.attach.attachmentsCfg.todoReminderMode } : {}),
275
+ toolSearchReminder: toolSearchReminderOn,
276
+ changedFiles: changedFilesOn,
277
+ planModeReminder: rs.attach.attachmentsCfg?.planModeReminder === true,
278
+ budgetUsd: budgetUsdOn,
279
+ totalTokensReminder: totalTokensOn,
280
+ ...(rs.attach.attachmentsCfg?.totalTokensReminderMode !== undefined
281
+ ? { totalTokensReminderMode: rs.attach.attachmentsCfg.totalTokensReminderMode }
282
+ : {}),
283
+ backgroundTasks: backgroundTasksOn,
284
+ toolsDelta: toolsDeltaOn,
285
+ agentListing: rs.attach.agentListingOn,
286
+ skillsListing: rs.attach.skillsListingOn,
287
+ mcpInstructions: mcpInstructionsOn,
288
+ },
289
+ ...(undiscoveredTools !== undefined && undiscoveredTools.length > 0
290
+ ? { undiscoveredTools, deferDirectCall: prepared.deferDirectCall }
291
+ : {}),
292
+ ...(changed !== undefined ? { changedFiles: changed } : {}),
293
+ ...(budgetUsdOn && rs.budget.maxCostMicroUsd !== undefined
294
+ ? { budgetUsd: { used: stats.costMicroUsd / 1e6, total: rs.budget.maxCostMicroUsd / 1e6 } }
295
+ : {}),
296
+ ...(totalTokensOn && rs.budget.maxTokensWindow !== undefined
297
+ ? { totalTokens: { used: stats.tokens, total: rs.budget.maxTokensWindow } }
298
+ : {}),
299
+ ...(bgTasks !== undefined ? { backgroundTasks: bgTasks } : {}),
300
+ ...(pendingTools !== undefined ? { newTools: pendingTools } : {}),
301
+ ...(prepared.toolMaterializeStatic ? { newToolsStaticFace: true } : {}),
302
+ ...(prepared.toolMaterializeStatic && pendingTools !== undefined
303
+ ? { newToolsSwappedUnderStatic: pendingTools.filter((n) => prepared.staticFaceFor?.(n) !== true) }
304
+ : {}),
305
+ ...(prepared.toolMaterializeStatic && pendingReaddedTools !== undefined
306
+ ? { readdedToolsSwappedUnderStatic: pendingReaddedTools.filter((n) => prepared.staticFaceFor?.(n) !== true) }
307
+ : {}),
308
+ ...(mcpToolsDelta !== undefined ? { mcpToolsDelta } : {}),
309
+ ...(rs.attach.agentListingOn && prepared.agentListing !== undefined
310
+ ? {
311
+ agentListing: prepared.agentListing.entries,
312
+ agentToolName: prepared.agentListing.toolName,
313
+ ...(prepared.agentListing.models !== undefined ? { agentModels: prepared.agentListing.models } : {}),
314
+ }
315
+ : {}),
316
+ ...(rs.attach.skillsListingOn && prepared.skillsListing !== undefined ? { skillsListing: prepared.skillsListing.entries } : {}),
317
+ ...(mcpAdds !== undefined || mcpRemovals !== undefined
318
+ ? { mcpInstructions: { added: mcpAdds ?? [], removed: mcpRemovals ?? [] } }
319
+ : {}),
320
+ ...(mcpDropped !== undefined ? { mcpDroppedTools: mcpDropped } : {}),
321
+ }));
322
+ if (pendingTools !== undefined || mcpToolsDelta !== undefined) {
323
+ const exact = renderToolsDelta({
324
+ ...(pendingTools !== undefined ? { added: pendingTools } : {}),
325
+ ...(prepared.toolMaterializeStatic ? { staticFace: true } : {}),
326
+ ...(prepared.toolMaterializeStatic && pendingTools !== undefined
327
+ ? { swappedUnderStatic: pendingTools.filter((n) => prepared.staticFaceFor?.(n) !== true) }
328
+ : {}),
329
+ ...(prepared.toolMaterializeStatic && pendingReaddedTools !== undefined
330
+ ? { readdedSwappedUnderStatic: pendingReaddedTools.filter((n) => prepared.staticFaceFor?.(n) !== true) }
331
+ : {}),
332
+ ...(mcpToolsDelta ?? {}),
333
+ });
334
+ if (exact !== undefined && due.some((a) => a.source === "tools_delta" && a.body === exact)) {
335
+ const ref = prepared.toolsDeltaRef;
336
+ if (pendingTools !== undefined)
337
+ ref.pending.splice(0, pendingTools.length);
338
+ if (pendingRemovedTools !== undefined)
339
+ ref.pendingRemoved.splice(0, pendingRemovedTools.length);
340
+ if (pendingReaddedTools !== undefined)
341
+ ref.pendingReadded.splice(0, pendingReaddedTools.length);
342
+ if (pendingFailedMcp !== undefined) {
343
+ ref.pendingFailed.splice(0, pendingFailedMcp.length);
344
+ if (!noNextRequest()) {
345
+ prepared.announcedListingsRef.mcpFailed = [...(prepared.announcedListingsRef.mcpFailed ?? []), ...pendingFailedMcp];
346
+ announcedOnceDirty = true;
347
+ }
348
+ }
349
+ }
350
+ }
351
+ if ((mcpAdds !== undefined || mcpRemovals !== undefined) &&
352
+ due.some((a) => a.source === "mcp_instructions" && a.body === renderMcpInstructionsDelta(mcpAdds ?? [], mcpRemovals ?? []))) {
353
+ if (mcpAdds !== undefined)
354
+ mcpDelta.pendingAdds.splice(0, mcpAdds.length);
355
+ if (mcpRemovals !== undefined)
356
+ mcpDelta.pendingRemovals.splice(0, mcpRemovals.length);
357
+ }
358
+ if (mcpDropped !== undefined &&
359
+ due.some((a) => a.source === "mcp_dropped_tools" && a.body === renderMcpDroppedTools(mcpDropped))) {
360
+ prepared.mcp.droppedTools.splice(0, mcpDropped.length);
361
+ if (!noNextRequest()) {
362
+ prepared.announcedListingsRef.advisories = withAnnounceDigests(prepared.announcedListingsRef.advisories, mcpDropped.map(droppedToolDigest));
363
+ announcedOnceDirty = true;
364
+ }
365
+ }
366
+ if (rs.attach.agentListingOn && prepared.agentListing !== undefined && due.some((a) => a.source === "agent_listing")) {
367
+ const listing = prepared.agentListing;
368
+ const intactBody = renderAgentListingDelta(rs.attach.attachState, listing.entries, listing.toolName, listing.models);
369
+ if (intactBody !== undefined && due.some((a) => a.source === "agent_listing" && a.body === intactBody)) {
370
+ commitAgentListing(rs.attach.attachState, listing.entries, listing.models);
371
+ prepared.announcedListingsRef.agents = listing.entries.map((e) => e.name);
372
+ if (listing.models !== undefined)
373
+ prepared.announcedListingsRef.models = [...listing.models];
374
+ if (prepared.announcedSnapshotRecovered)
375
+ await prepared.session.appendAnnouncedListing(prepared.announcedListingsRef).catch(() => undefined);
376
+ }
377
+ }
378
+ if (rs.attach.skillsListingOn && prepared.skillsListing !== undefined && due.some((a) => a.source === "skills_listing")) {
379
+ const listing = prepared.skillsListing;
380
+ const intactBody = renderSkillsListingDelta(rs.attach.attachState, listing.entries);
381
+ if (intactBody !== undefined && due.some((a) => a.source === "skills_listing" && a.body === intactBody)) {
382
+ commitSkillsListing(rs.attach.attachState, listing.entries);
383
+ prepared.announcedListingsRef.skills = listing.entries.map((e) => e.name);
384
+ if (prepared.announcedSnapshotRecovered)
385
+ await prepared.session.appendAnnouncedListing(prepared.announcedListingsRef).catch(() => undefined);
386
+ }
387
+ }
388
+ if (announcedOnceDirty && prepared.announcedSnapshotRecovered)
389
+ await prepared.session.appendAnnouncedListing(prepared.announcedListingsRef).catch(() => undefined);
390
+ }
391
+ if (rs.attach.dateState !== undefined && rs.turn.lastTurnHadToolCalls) {
392
+ const today = prepared.dateChange.today();
393
+ const cand = collectDateChange(rs.attach.dateState, today);
394
+ if (cand !== undefined) {
395
+ due.unshift(cand);
396
+ rs.attach.dateState.announcedDate = today;
397
+ }
398
+ }
399
+ if (rs.attach.sizeGuidelineState !== undefined && rs.turn.lastTurnHadToolCalls) {
400
+ const guideline = rs.attach.sizeGuidelineState.current();
401
+ if (guideline !== rs.attach.sizeGuidelineState.announcedGuideline) {
402
+ due.splice(due.length > 0 && due[0].source === "date_change" ? 1 : 0, 0, {
403
+ source: "workflow_size_guideline_change",
404
+ body: workflowSizeGuidelineChangeNotice(guideline),
405
+ });
406
+ rs.attach.sizeGuidelineState.announcedGuideline = guideline;
407
+ }
408
+ }
409
+ if (rs.attach.instrState !== undefined && rs.attach.instrProbe !== undefined && rs.turn.lastTurnHadToolCalls) {
410
+ let probed = null;
411
+ try {
412
+ const PROBE_CEILING_MS = 2_000;
413
+ probed = await Promise.race([
414
+ rs.attach.instrProbe(prepared.instructionSources),
415
+ new Promise((resolve) => {
416
+ setTimeout(() => resolve(null), PROBE_CEILING_MS);
417
+ }),
418
+ ]);
419
+ }
420
+ catch {
421
+ probed = null;
422
+ }
423
+ const cand = collectInstructionsChange(rs.attach.instrState, probed);
424
+ if (cand !== undefined) {
425
+ due.splice(due.length > 0 && due[0].source === "date_change" ? 1 : 0, 0, cand.attachment);
426
+ commitInstructionsChange(rs.attach.instrState, cand.announced);
427
+ }
428
+ }
429
+ if (due.length > 0) {
430
+ attachmentsPayload = due.map((a) => mintSystemReminder(sanitizeUntrustedText(a.body, attachmentEnvelopeTags(a.source)), prepared.reminderMark)).join("\n");
431
+ for (const a of due)
432
+ boundaryAttachmentBytes += Buffer.byteLength(a.body, "utf8");
433
+ rs.attach.attachmentsInjected += due.length;
434
+ for (const a of due) {
435
+ queue.push({ type: "steering_injected", source: a.source, preview: redactThenCut(a.body, 220), ...ident() });
436
+ }
437
+ }
438
+ }
439
+ let batchContextBlock;
440
+ const finalVerifyInjectedThisTurn = rs.counters.finalVerifyInjections !== finalVerifySeenAtLastBoundary;
441
+ finalVerifySeenAtLastBoundary = rs.counters.finalVerifyInjections;
442
+ if (postToolBatchHook !== undefined && rs.turn.toolBatch.length > 0) {
443
+ const batch = rs.turn.toolBatch;
444
+ rs.turn.toolBatch = [];
445
+ batchArgs?.clear();
446
+ const injectedThisTurn = finalVerifyInjectedThisTurn ? "final_verification" : undefined;
447
+ if (!boundarySteered && !prepared.abortController.signal.aborted) {
448
+ try {
449
+ const batchSeat = await runHookSeat("postToolBatch", { timeoutMs: prepared.hookTimeoutMs, signal: prepared.abortController.signal }, (sig) => postToolBatchHook(batch, injectedThisTurn !== undefined ? { injectedThisTurn } : undefined, { identity: prepared.hookIdentity, signal: sig }));
450
+ if (batchSeat.expired) {
451
+ runnerHooks.onError?.(hookSeatExpiredError("postToolBatch", prepared.hookTimeoutMs, batchSeat.cause, "the boundary observation was abandoned and its additionalContext dropped; the turn boundary itself is unchanged"), { phase: "hook", sessionId: prepared.sessionId });
452
+ }
453
+ const r = batchSeat.expired ? undefined : batchSeat.value;
454
+ if (r?.additionalContext && injectedThisTurn === undefined) {
455
+ const body = sanitizeUntrustedText(r.additionalContext, SHELLED_BODY_ENVELOPE_TAGS);
456
+ const budget = ATTACHMENT_BYTE_CAP - boundaryAttachmentBytes;
457
+ if (budget >= BATCH_CONTEXT_MIN_KEEP_BYTES) {
458
+ const clipped = clipToBytes(body, budget) === body
459
+ ? body
460
+ : `${clipToBytes(body, budget - Buffer.byteLength(BATCH_TRUNCATION_MARKER, "utf8"))}${BATCH_TRUNCATION_MARKER}`;
461
+ batchContextBlock = mintSystemReminder(clipped, prepared.reminderMark);
462
+ }
463
+ }
464
+ }
465
+ catch (err) {
466
+ try {
467
+ runnerHooks.onError?.(err instanceof Error ? err : new Error(String(err)), { phase: "hook", sessionId: prepared.sessionId });
468
+ }
469
+ catch {
470
+ }
471
+ }
472
+ }
473
+ }
474
+ if ((attachmentsPayload !== undefined || batchContextBlock !== undefined) && prepared.batchHaltRef.current === undefined) {
475
+ const payload = attachmentsPayload !== undefined && batchContextBlock !== undefined ? `${attachmentsPayload}\n${batchContextBlock}` : (attachmentsPayload ?? batchContextBlock);
476
+ void prepared.harness.steer(payload, { engineMinted: true }).catch(() => { });
477
+ }
478
+ const forceManual = manualCompactRef.requested;
479
+ if ((!withinTaskCompaction && !forceManual) ||
480
+ prepared.abortController.signal.aborted ||
481
+ prepared.pausedRef.current !== undefined) {
482
+ if (forceManual) {
483
+ emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.mooted", version: 1, taskId: rs.telemetry.taskId, reason: "task_ending", ts: Date.now() }));
484
+ queue.push({ type: "compaction_outcome", outcome: "mooted", trigger: "manual", reason: "task_ending", ...ident() });
485
+ drainManualCompact("mooted");
486
+ }
487
+ return undefined;
488
+ }
489
+ if (compactionBreaker.failures >= MAX_CONSECUTIVE_COMPACTION_FAILURES) {
490
+ if (forceManual) {
491
+ emitTrace(rs.telemetry.tracer, () => ({
492
+ kind: "compaction.failed",
493
+ version: 1,
494
+ taskId: rs.telemetry.taskId,
495
+ trigger: "manual",
496
+ reason: "compaction circuit breaker open (3 consecutive failures this task) — manual request drained without an attempt",
497
+ ts: Date.now(),
498
+ }));
499
+ queue.push({ type: "compaction_outcome", outcome: "failed", trigger: "manual", reason: "compaction circuit breaker open (3 consecutive failures this task) — manual request drained without an attempt", ...ident() });
500
+ drainManualCompact("failed");
501
+ }
502
+ return undefined;
503
+ }
504
+ const claimedManual = forceManual
505
+ ? (() => {
506
+ const waiters = manualCompactRef.waiters.splice(0);
507
+ let instructions;
508
+ for (let i = waiters.length - 1; i >= 0; i--) {
509
+ if (waiters[i].instructions !== undefined) {
510
+ instructions = waiters[i].instructions;
511
+ break;
512
+ }
513
+ }
514
+ return { instructions, waiters };
515
+ })()
516
+ : undefined;
517
+ if (forceManual) {
518
+ manualCompactRef.requested = false;
519
+ }
520
+ const resolveClaimedWaiters = (outcome) => {
521
+ if (claimedManual !== undefined)
522
+ for (const w of claimedManual.waiters.splice(0))
523
+ w.resolve(outcome);
524
+ };
525
+ const claimedCancelSignals = claimedManual?.waiters.flatMap((w) => (w.signal !== undefined ? [w.signal] : [])) ?? [];
526
+ const manualCancelSignal = claimedCancelSignals.length === 0
527
+ ? undefined
528
+ : claimedCancelSignals.length === 1
529
+ ? claimedCancelSignals[0]
530
+ : AbortSignal.any(claimedCancelSignals);
531
+ const compactionPassSignal = claimedCancelSignals.length > 0
532
+ ? AbortSignal.any([prepared.abortController.signal, ...claimedCancelSignals])
533
+ : prepared.abortController.signal;
534
+ const trimPressureArmed = prepared.trimPressureRef.droppedMessages;
535
+ if (trimPressureArmed)
536
+ prepared.trimPressureRef.droppedMessages = false;
537
+ const trimPressure = trimPressureArmed && !rs.counters.trimForceBackoff;
538
+ const passTrigger = trimPressure ? "forced" : forceManual ? "manual" : "auto";
539
+ let manualOutcome = "failed";
540
+ const manualInstructions = claimedManual?.instructions;
541
+ try {
542
+ const comp = await maybeCompact({
543
+ session: prepared.session,
544
+ epochDeclaredSections: prepared.epochDeclaredSections,
545
+ ...centerAdoptionOption(prepared),
546
+ model: event.model,
547
+ compactionModel: prepared.compModel,
548
+ ...forkContextOption(prepared, false),
549
+ brain: compactionBrain,
550
+ getApiKeyAndHeaders: spec.getApiKeyAndHeaders,
551
+ thinking: prepared.thinking,
552
+ settings: { ...DEFAULT_COMPACTION_SETTINGS, ...spec.compaction },
553
+ customInstructions: manualInstructions ?? spec.compaction?.instructions ?? DEFAULT_COMPACTION_INSTRUCTIONS,
554
+ signal: compactionPassSignal,
555
+ ...(manualCancelSignal !== undefined ? { manualCancelSignal } : {}),
556
+ minTokens: forceManual ? 0 : rs.counters.compactionFloor,
557
+ force: forceManual,
558
+ onInputTruncated: emitInputTruncated(rs.telemetry.tracer, rs.telemetry.taskId),
559
+ forceUnderThreshold: trimPressure,
560
+ overheadTokens: prepared.promptOverheadTokens,
561
+ ...(prepared.activeTools.size > 0 ? { activeTools: [...prepared.activeTools] } : {}),
562
+ workingFileAttachments: buildWorkingFileAttachments(spec, prepared),
563
+ ...contextInstructionFilesOption(prepared),
564
+ ...(prepared.onCompactionApplied ? { onApplied: prepared.onCompactionApplied } : {}),
565
+ ...runnerHooks.seamCCompactionOptions(prepared),
566
+ ...gitRestateOption(prepared),
567
+ ...windowSafetyOptions(event.model),
568
+ ...runnerHooks.compactionHookOptions(spec, prepared.sessionId, passTrigger),
569
+ });
570
+ if (comp.blocked) {
571
+ emitTrace(rs.telemetry.tracer, () => ({
572
+ kind: "compaction.blocked",
573
+ version: 1,
574
+ taskId: rs.telemetry.taskId,
575
+ trigger: forceManual ? "manual" : "auto",
576
+ ts: Date.now(),
577
+ }));
578
+ }
579
+ else {
580
+ compactionBreaker.failures = 0;
581
+ }
582
+ rs.counters.trimForceBackoff = nextTrimForceBackoff(rs.counters.trimForceBackoff, comp, trimPressure);
583
+ if (comp.contextUsage) {
584
+ queue.push({ type: "context_usage", ...comp.contextUsage, ...ident() });
585
+ }
586
+ if (comp.unevaluableWindow) {
587
+ discloseUnevaluableWindow(comp.unevaluableWindow, prepared, rs.telemetry.tracer, rs.telemetry.taskId, runnerHooks.onError);
588
+ }
589
+ if (comp.suppressedByFloor) {
590
+ const s = comp.suppressedByFloor;
591
+ emitTrace(rs.telemetry.tracer, () => ({
592
+ kind: "compaction.suppressed",
593
+ version: 1,
594
+ taskId: rs.telemetry.taskId,
595
+ estTokens: s.estTokens,
596
+ floor: s.floor,
597
+ ts: Date.now(),
598
+ }));
599
+ }
600
+ manualOutcome = comp.compacted ? "compacted" : comp.blocked ? "blocked" : comp.disabled ? "disabled" : "noop";
601
+ if (forceManual && !comp.compacted && !comp.blocked) {
602
+ if (comp.disabled) {
603
+ emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.disabled", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() }));
604
+ }
605
+ else {
606
+ emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.noop", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() }));
607
+ }
608
+ }
609
+ if (!comp.compacted) {
610
+ const outcome = comp.blocked
611
+ ? "blocked"
612
+ : comp.disabled
613
+ ? (forceManual || trimPressure ? "disabled" : undefined)
614
+ : comp.suppressedByFloor
615
+ ? "suppressed"
616
+ : comp.noop || forceManual
617
+ ? "noop"
618
+ : undefined;
619
+ if (outcome !== undefined) {
620
+ queue.push({ type: "compaction_outcome", outcome, trigger: passTrigger, ...ident() });
621
+ }
622
+ }
623
+ runnerHooks.recordCompactionReuse(prepared, comp);
624
+ if (comp.compacted) {
625
+ queue.push({
626
+ type: "compacted",
627
+ trigger: passTrigger,
628
+ tokensBefore: comp.tokensBefore ?? 0,
629
+ ...(comp.postTriggerTokens !== undefined ? { tokensAfter: comp.postTriggerTokens } : {}),
630
+ ...(comp.triggerTokens !== undefined ? { triggerTokensBefore: comp.triggerTokens } : {}),
631
+ ...(comp.durationMs !== undefined ? { durationMs: comp.durationMs } : {}),
632
+ ...(comp.phaseDurations !== undefined ? { phaseDurations: comp.phaseDurations } : {}),
633
+ ...(comp.firstKeptEntryId !== undefined ? { preserved_segment: { firstKeptEntryId: comp.firstKeptEntryId } } : {}),
634
+ ...(comp.attachedFiles !== undefined ? { attachedFiles: comp.attachedFiles } : {}),
635
+ ...(comp.modelFallback ? { modelFallback: true } : {}),
636
+ ...(comp.fallbackReason !== undefined ? { fallbackReason: comp.fallbackReason } : {}),
637
+ ...(comp.clampedRatio !== undefined ? { clampedRatio: comp.clampedRatio } : {}),
638
+ ...(comp.clampReason !== undefined ? { clampReason: comp.clampReason } : {}),
639
+ ...ident(),
640
+ });
641
+ if (!forceManual && recordCompactionAndCheckRapidRefill(rapidRefill, stats.turns)) {
642
+ compactionBreaker.failures = MAX_CONSECUTIVE_COMPACTION_FAILURES;
643
+ queue.push({ type: "compaction_outcome", outcome: "disabled", trigger: passTrigger, reason: "rapid_refill: compaction disabled for the rest of this task", ...ident() });
644
+ runnerHooks.onError?.(new Error("compaction.rapid_refill: the context refilled within <3 turns of compaction 3 times in a row — compaction disabled for the rest of this task (thrash spiral; the transcript is dominated by incompressible content)"), { phase: "compaction", sessionId: prepared.sessionId });
645
+ }
646
+ if (comp.phaseDurations !== undefined && comp.durationMs !== undefined) {
647
+ const pd = comp.phaseDurations;
648
+ const pdDur = comp.durationMs;
649
+ emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.phase_timings", version: 1, taskId: rs.telemetry.taskId, ...pd, durationMs: pdDur, ts: Date.now() }));
650
+ }
651
+ prepared.cacheBreakDetector?.notifyCompaction();
652
+ if (rs.attach.attachState !== undefined) {
653
+ rs.attach.attachState.postCompactPending = true;
654
+ rebaseCadenceWindows(rs.attach.attachState, rs.counters.cadenceTurns);
655
+ }
656
+ if ((comp.freedTokens ?? 0) >= COMPACTION_FREED_EPSILON) {
657
+ const postSize = comp.postTriggerTokens ?? Math.max(0, (comp.triggerTokens ?? comp.tokensBefore ?? 0) - (comp.freedTokens ?? 0));
658
+ rs.counters.compactionFloor = Math.ceil(postSize * COMPACTION_REGROWTH_FACTOR);
659
+ }
660
+ }
661
+ }
662
+ catch (err) {
663
+ const manualCancelAbort = !prepared.abortController.signal.aborted && isCompactionManualCancel(err);
664
+ if (manualCancelAbort) {
665
+ emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.mooted", version: 1, taskId: rs.telemetry.taskId, reason: "cancelled", ts: Date.now() }));
666
+ queue.push({ type: "compaction_outcome", outcome: "mooted", trigger: passTrigger, reason: "cancelled", ...ident() });
667
+ manualOutcome = "mooted";
668
+ }
669
+ else if (!prepared.abortController.signal.aborted) {
670
+ compactionBreaker.failures += 1;
671
+ runnerHooks.onError?.(err, { phase: "compaction", sessionId: prepared.sessionId });
672
+ const msg = String(err instanceof Error ? err.message : err);
673
+ const failReason = msg.length > 512 ? `${msg.slice(0, 512)}…` : msg;
674
+ emitTrace(rs.telemetry.tracer, () => ({
675
+ kind: "compaction.failed",
676
+ version: 1,
677
+ taskId: rs.telemetry.taskId,
678
+ trigger: passTrigger,
679
+ reason: failReason,
680
+ ts: Date.now(),
681
+ }));
682
+ queue.push({ type: "compaction_outcome", outcome: "failed", trigger: passTrigger, reason: failReason, ...ident() });
683
+ manualOutcome = "failed";
684
+ }
685
+ else {
686
+ manualOutcome = "mooted";
687
+ }
688
+ }
689
+ if (forceManual)
690
+ resolveClaimedWaiters(manualOutcome);
691
+ return undefined;
692
+ };
693
+ return onTurnBoundary;
694
+ }