@sema-agent/core 7.6.3 → 7.8.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 (164) hide show
  1. package/CHANGELOG.md +70 -10
  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/peer-admission.d.ts +1 -1
  6. package/dist/agents/send-message-tool.js +2 -1
  7. package/dist/agents/subagent-steps.js +5 -4
  8. package/dist/agents/subagent.js +2 -3
  9. package/dist/brain/anthropic.js +8 -2
  10. package/dist/brain/open-responses.js +5 -3
  11. package/dist/brain/openai.js +31 -8
  12. package/dist/brain/reasoning.d.ts +32 -0
  13. package/dist/brain/reasoning.js +18 -0
  14. package/dist/core/ask-question.js +2 -2
  15. package/dist/core/auto-mode-defaults.d.ts +16 -0
  16. package/dist/core/auto-mode-defaults.js +1 -0
  17. package/dist/core/auto-mode.d.ts +19 -0
  18. package/dist/core/auto-mode.js +74 -56
  19. package/dist/core/checkpoint-execution-record.d.ts +110 -0
  20. package/dist/core/checkpoint-execution-record.js +49 -0
  21. package/dist/core/checkpoint-store.d.ts +114 -2
  22. package/dist/core/checkpoint-store.js +35 -2
  23. package/dist/core/context-edit.d.ts +0 -22
  24. package/dist/core/context-edit.js +2 -11
  25. package/dist/core/engine-notice.d.ts +17 -0
  26. package/dist/core/fs-write-gate-policy.d.ts +7 -1
  27. package/dist/core/fs-write-gate-policy.js +7 -7
  28. package/dist/core/gate-fold.js +2 -2
  29. package/dist/core/gate-lanes.js +4 -3
  30. package/dist/core/governance-codes.d.ts +1 -1
  31. package/dist/core/governance-codes.js +8 -0
  32. package/dist/core/hooks.d.ts +8 -2
  33. package/dist/core/hooks.js +1 -1
  34. package/dist/core/lsp.js +2 -2
  35. package/dist/core/mcp-server-spec.d.ts +17 -0
  36. package/dist/core/mcp.js +3 -1
  37. package/dist/core/memory-engine/tools.js +4 -12
  38. package/dist/core/permission-rules.d.ts +32 -2
  39. package/dist/core/permission-rules.js +43 -37
  40. package/dist/core/present-plan-tool.js +3 -4
  41. package/dist/core/runner/active-skill-scope.js +7 -4
  42. package/dist/core/runner/clock-and-limits.d.ts +117 -0
  43. package/dist/core/runner/clock-and-limits.js +118 -0
  44. package/dist/core/runner/compaction-call-options.d.ts +1 -1
  45. package/dist/core/runner/contracts.d.ts +37 -3
  46. package/dist/core/runner/decide-continuation.d.ts +98 -0
  47. package/dist/core/runner/decide-continuation.js +133 -0
  48. package/dist/core/runner/execution-record.d.ts +26 -0
  49. package/dist/core/runner/execution-record.js +19 -0
  50. package/dist/core/runner/git-leg-delivery.d.ts +28 -0
  51. package/dist/core/runner/git-leg-delivery.js +94 -0
  52. package/dist/core/runner/initial-run-state.d.ts +14 -0
  53. package/dist/core/runner/initial-run-state.js +11 -0
  54. package/dist/core/runner/prepare-ask-lane.d.ts +1 -1
  55. package/dist/core/runner/prepare-caps-and-workflow.d.ts +5 -4
  56. package/dist/core/runner/prepare-caps-and-workflow.js +33 -11
  57. package/dist/core/runner/prepare-defer-classify.d.ts +5 -26
  58. package/dist/core/runner/prepare-defer-classify.js +10 -10
  59. package/dist/core/runner/prepare-delegation-surface.d.ts +5 -4
  60. package/dist/core/runner/prepare-delegation-surface.js +15 -17
  61. package/dist/core/runner/prepare-gate-stations.d.ts +4 -1
  62. package/dist/core/runner/prepare-gate-stations.js +3 -1
  63. package/dist/core/runner/prepare-hands-readface.d.ts +3 -2
  64. package/dist/core/runner/prepare-hands-readface.js +3 -2
  65. package/dist/core/runner/prepare-inherited-gate.d.ts +1 -1
  66. package/dist/core/runner/prepare-inherited-gate.js +1 -0
  67. package/dist/core/runner/prepare-lsp.d.ts +4 -3
  68. package/dist/core/runner/prepare-lsp.js +3 -2
  69. package/dist/core/runner/prepare-memory-engine-session.d.ts +4 -4
  70. package/dist/core/runner/prepare-memory-engine-session.js +4 -2
  71. package/dist/core/runner/prepare-offload-wrappers.js +7 -5
  72. package/dist/core/runner/prepare-park-ask.d.ts +4 -0
  73. package/dist/core/runner/prepare-park-ask.js +4 -1
  74. package/dist/core/runner/prepare-project-context.d.ts +5 -23
  75. package/dist/core/runner/prepare-project-context.js +7 -5
  76. package/dist/core/runner/prepare-prompt-assembly.d.ts +1 -1
  77. package/dist/core/runner/prepare-protocol-tools.d.ts +5 -1
  78. package/dist/core/runner/prepare-protocol-tools.js +38 -21
  79. package/dist/core/runner/prepare-question-face.d.ts +4 -3
  80. package/dist/core/runner/prepare-question-face.js +5 -3
  81. package/dist/core/runner/prepare-safety-scan.js +42 -2
  82. package/dist/core/runner/prepare-task.js +37 -19
  83. package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +5 -2
  84. package/dist/core/runner/prepare-tool-disclosure-mount.js +3 -1
  85. package/dist/core/runner/prepare-wiring-manifest.d.ts +4 -0
  86. package/dist/core/runner/prepare-wiring-manifest.js +3 -8
  87. package/dist/core/runner/run-harness-handlers.d.ts +46 -0
  88. package/dist/core/runner/run-harness-handlers.js +345 -0
  89. package/dist/core/runner/run-turn-boundary.d.ts +69 -0
  90. package/dist/core/runner/run-turn-boundary.js +694 -0
  91. package/dist/core/runner/runtask.d.ts +0 -68
  92. package/dist/core/runner/runtask.js +66 -1486
  93. package/dist/core/runner/session-rule-policy.js +9 -4
  94. package/dist/core/runner/steer-admission.d.ts +17 -0
  95. package/dist/core/runner/steer-admission.js +17 -0
  96. package/dist/core/runner/synthetic-tools.js +5 -8
  97. package/dist/core/runner/tool-disclosure.d.ts +4 -0
  98. package/dist/core/runner/tool-disclosure.js +9 -5
  99. package/dist/core/runner/tool-end-body.d.ts +71 -0
  100. package/dist/core/runner/tool-end-body.js +75 -0
  101. package/dist/core/runner/tool-face-overlay.d.ts +40 -0
  102. package/dist/core/runner/tool-face-overlay.js +125 -0
  103. package/dist/core/runner/tool-output-projection.d.ts +10 -0
  104. package/dist/core/runner/tool-output-projection.js +33 -10
  105. package/dist/core/sensitive-path-policy.d.ts +3 -0
  106. package/dist/core/sensitive-path-policy.js +6 -5
  107. package/dist/core/shared-memory/tools.js +3 -6
  108. package/dist/core/store-contracts/checkpoint-store-contract.d.ts +4 -0
  109. package/dist/core/store-contracts/checkpoint-store-contract.js +108 -0
  110. package/dist/core/task-event.d.ts +10 -0
  111. package/dist/core/task-registry.js +4 -5
  112. package/dist/core/task-spec.d.ts +8 -0
  113. package/dist/core/tool-catalog-entries.d.ts +48 -0
  114. package/dist/core/tool-catalog-entries.js +174 -0
  115. package/dist/core/tool-conformance.d.ts +22 -0
  116. package/dist/core/tool-conformance.js +37 -0
  117. package/dist/core/tool-face.d.ts +171 -0
  118. package/dist/core/tool-face.js +25 -0
  119. package/dist/core/tool-policy.d.ts +24 -0
  120. package/dist/core/tool-policy.js +4 -2
  121. package/dist/core/tool-registry.d.ts +114 -0
  122. package/dist/core/tool-registry.js +114 -0
  123. package/dist/core/tool-result-budget.d.ts +0 -3
  124. package/dist/core/tool-result-budget.js +4 -3
  125. package/dist/core/tool-result-store.d.ts +0 -18
  126. package/dist/core/tool-result-store.js +2 -18
  127. package/dist/core/tool-roster.d.ts +450 -0
  128. package/dist/core/tool-roster.js +497 -0
  129. package/dist/core/tool-spec.d.ts +54 -3
  130. package/dist/core/tools.js +7 -0
  131. package/dist/core/trace.d.ts +24 -0
  132. package/dist/core/wiring-manifest.d.ts +13 -0
  133. package/dist/core/wiring-manifest.js +2 -1
  134. package/dist/core/write-protect.d.ts +2 -1
  135. package/dist/core/write-protect.js +5 -4
  136. package/dist/engine/compaction/utils.d.ts +6 -0
  137. package/dist/engine/compaction/utils.js +19 -19
  138. package/dist/engine/loop/types.d.ts +6 -0
  139. package/dist/index.d.ts +20 -8
  140. package/dist/index.js +19 -7
  141. package/dist/orchestration/goal.js +2 -1
  142. package/dist/orchestration/run-spec.js +3 -5
  143. package/dist/orchestration/run-workflow-tool.js +2 -2
  144. package/dist/prompt-assembly/event-registry.js +3 -3
  145. package/dist/prompt-assembly/tool-catalog.d.ts +2 -2
  146. package/dist/stores/file/checkpoint-store.d.ts +7 -0
  147. package/dist/stores/file/checkpoint-store.js +20 -1
  148. package/dist/tools/fs/fs-bash.d.ts +1 -1
  149. package/dist/tools/fs/fs-bash.js +7 -11
  150. package/dist/tools/fs/fs-read.js +2 -2
  151. package/dist/tools/fs/fs-search-tools.d.ts +0 -7
  152. package/dist/tools/fs/fs-search-tools.js +3 -16
  153. package/dist/tools/fs/fs-write.js +4 -6
  154. package/dist/tools/fs/repo-map.js +2 -2
  155. package/dist/tools/fs/safety.d.ts +2 -1
  156. package/dist/tools/fs/safety.js +6 -5
  157. package/dist/tools/monitor.js +2 -2
  158. package/dist/tools/scheduler-tools.js +5 -8
  159. package/dist/tools/task-list.js +5 -8
  160. package/dist/tools/todo.js +2 -2
  161. package/dist/tools/web.js +3 -6
  162. package/dist/tools/worktree.js +3 -4
  163. package/package.json +4 -1
  164. package/test/export-surface.snapshot.json +183 -7
@@ -0,0 +1,345 @@
1
+ import { createSafeNotifier, observeThenableRejection } from "../safe-notify.js";
2
+ import { emitTrace } from "../trace.js";
3
+ import { primaryActivityArg } from "../arg-summary.js";
4
+ import { readDegradation } from "../../brain/degrading.js";
5
+ import { OUTPUT_TOOL_NAME } from "./synthetic-tools.js";
6
+ import { usageCostMicroUsd } from "./usage-accounting.js";
7
+ import { advanceCadenceClock, reduceToolEnd } from "./turn-attachments.js";
8
+ import { gatedCallIdOf } from "./park-commit.js";
9
+ import { hasVerifiableStructureSignal } from "./grounding-signal.js";
10
+ import { batchResponseDigest, toolEndBodyFrom } from "./tool-end-body.js";
11
+ export function createHarnessHandlers(input) {
12
+ const { prepared, stats, rs, deps } = input;
13
+ const { spec, queue, internals, ident, parentToolCallId, subagentName, pushContent, emitCommitted, startedToolCallIds, toolStartAt, writeFamilyOf, toolLabels, postToolBatchHook, batchArgs } = deps;
14
+ const internalsNotifier = createSafeNotifier({
15
+ onError: (f) => console.warn(`[sema-core] ${f.site}: run-internals activity sink threw (contained; further failures counted, not re-disclosed): ${f.error.message}`),
16
+ });
17
+ let lastWorkspaceCwd = prepared.cwdRef?.current;
18
+ const announceWorkspaceMove = () => {
19
+ const cwdNow = prepared.cwdRef?.current;
20
+ if (cwdNow !== undefined && cwdNow !== lastWorkspaceCwd) {
21
+ lastWorkspaceCwd = cwdNow;
22
+ queue.push({ type: "workspace_changed", cwd: cwdNow, ...ident() });
23
+ }
24
+ };
25
+ const onMessageUpdate = (event) => {
26
+ const ev = event.assistantMessageEvent;
27
+ if (rs.turn.callStartAt === undefined)
28
+ rs.turn.callStartAt = Date.now();
29
+ if (ev.type === "text_delta" && ev.delta) {
30
+ if (rs.turn.firstTokenAt === undefined)
31
+ rs.turn.firstTokenAt = Date.now();
32
+ pushContent({ type: "text_delta", delta: ev.delta, ...ident() });
33
+ }
34
+ else if (ev.type === "text_end" && typeof ev.content === "string" && ev.content.length !== 0) {
35
+ pushContent({ type: "text_end", content: ev.content, ...ident() });
36
+ }
37
+ else if (ev.type === "thinking_delta" && ev.delta) {
38
+ pushContent({ type: "reasoning_delta", delta: ev.delta, ...ident() });
39
+ }
40
+ if (rs.budget.streamCancel && !rs.limits.budgetHit && (ev.type === "text_delta" || ev.type === "thinking_delta") && ev.delta) {
41
+ rs.budget.callOutputChars += ev.delta.length;
42
+ if (rs.budget.callOutputChars - rs.budget.lastStreamBudgetCheck >= 256) {
43
+ rs.budget.lastStreamBudgetCheck = rs.budget.callOutputChars;
44
+ const projectedAxis = rs.budget.projectedOverBudget();
45
+ if (projectedAxis !== undefined) {
46
+ rs.limits.budgetHit = "exceeded";
47
+ rs.limits.budgetAxis = projectedAxis;
48
+ void prepared.harness.abort();
49
+ prepared.abortController.abort();
50
+ }
51
+ }
52
+ }
53
+ };
54
+ const onMessageEnd = (event) => {
55
+ const m = event.message;
56
+ if (event.entryId !== undefined && (m.role === "user" || m.role === "assistant" || m.role === "toolResult")) {
57
+ emitCommitted(event.entryId, m.role, m.role === "toolResult" ? m.toolCallId : undefined);
58
+ }
59
+ if (event.entryId !== undefined && m.role === "user")
60
+ prepared.fileHistoryBoundary?.begin(event.entryId);
61
+ if (event.entryId !== undefined && m.role === "user" && prepared.gitStatusRef.pendingReceipt !== undefined) {
62
+ const pr = prepared.gitStatusRef.pendingReceipt;
63
+ const c = m.content;
64
+ const text = typeof c === "string"
65
+ ? c
66
+ : Array.isArray(c) && c.length >= 1 && c[0].type === "text"
67
+ ? c[0].text
68
+ : undefined;
69
+ if (text === pr.text) {
70
+ delete prepared.gitStatusRef.pendingReceipt;
71
+ pr.commit(event.entryId);
72
+ }
73
+ }
74
+ if (rs.degrade.degraded === undefined && m.role === "assistant") {
75
+ const deg = readDegradation(m);
76
+ if (deg)
77
+ rs.degrade.recordDegraded({ from: deg.from, to: deg.to, reason: deg.reason, atTurn: stats.turns + 1, ...(deg.chain && { chain: deg.chain }) });
78
+ }
79
+ if (m.role === "assistant" && "usage" in m && m.usage) {
80
+ if (m.usageMissing)
81
+ rs.turn.turnUsageMissing = true;
82
+ const u = m.usage;
83
+ stats.tokens += u.totalTokens || 0;
84
+ const { totalInputTokens: turnTotal, uncachedInputTokens: turnUncached, costMicroUsd: turnCostMicroUsd, } = usageCostMicroUsd(rs.telemetry.cacheFamily, u, rs.telemetry.pricing);
85
+ if (!rs.telemetry.pricingConfigured)
86
+ rs.telemetry.unpricedSpend = true;
87
+ stats.promptTokens += turnUncached;
88
+ stats.totalInputTokens += turnTotal;
89
+ stats.cachedTokens += u.cacheRead || 0;
90
+ stats.cacheWriteTokens += u.cacheWrite || 0;
91
+ stats.outputTokens += u.output || 0;
92
+ stats.costMicroUsd += turnCostMicroUsd;
93
+ rs.turn.turnUsage ??= { inputTokens: 0, totalInputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0, costMicroUsd: 0 };
94
+ rs.turn.turnUsage.inputTokens += turnUncached;
95
+ rs.turn.turnUsage.totalInputTokens += turnTotal;
96
+ rs.turn.turnUsage.outputTokens += u.output || 0;
97
+ rs.turn.turnUsage.cacheReadTokens += u.cacheRead || 0;
98
+ rs.turn.turnUsage.cacheWriteTokens += u.cacheWrite || 0;
99
+ rs.turn.turnUsage.costMicroUsd += turnCostMicroUsd;
100
+ const callEndAt = Date.now();
101
+ const callIssuedAt = prepared.callIssuedAtRef.current;
102
+ prepared.callIssuedAtRef.current = undefined;
103
+ emitTrace(rs.telemetry.tracer, () => ({
104
+ kind: "brain.call",
105
+ version: 1,
106
+ taskId: rs.telemetry.taskId,
107
+ model: m.model,
108
+ provider: m.provider,
109
+ turn: stats.turns + 1,
110
+ promptTokens: turnUncached,
111
+ totalInputTokens: turnTotal,
112
+ completionTokens: u.output || 0,
113
+ cacheRead: u.cacheRead || 0,
114
+ cacheWrite: u.cacheWrite || 0,
115
+ latencyMs: rs.turn.callStartAt !== undefined ? callEndAt - rs.turn.callStartAt : 0,
116
+ firstTokenMs: rs.turn.callStartAt !== undefined && rs.turn.firstTokenAt !== undefined ? rs.turn.firstTokenAt - rs.turn.callStartAt : undefined,
117
+ ...(callIssuedAt !== undefined ? { callStartedAt: callIssuedAt } : {}),
118
+ ...(rs.telemetry.pricingConfigured ? { costMicroUsd: turnCostMicroUsd } : {}),
119
+ ...(m.role === "assistant" && typeof m.stopReason === "string"
120
+ ? { stopReason: m.stopReason }
121
+ : {}),
122
+ ts: callEndAt,
123
+ }));
124
+ const cacheRowUnknown = m.usageMissing === true || m.stopReason === "aborted";
125
+ if (prepared.cacheBreakDetector && prepared.cacheFingerprint && !cacheRowUnknown) {
126
+ const finding = prepared.cacheBreakDetector.observe({
127
+ turn: stats.turns + 1,
128
+ systemPrompt: prepared.cacheFingerprint.systemPrompt,
129
+ tools: prepared.cacheFingerprint.tools,
130
+ modelKey: `${m.provider}:${m.model}`,
131
+ cacheRead: u.cacheRead || 0,
132
+ });
133
+ if (finding) {
134
+ rs.telemetry.cacheBreakReported = true;
135
+ deps.runnerHooks.onError?.(new Error(`prompt-cache: break at turn ${finding.turn} — cache_read ${finding.cacheReadBefore}→${finding.cacheReadAfter}; ${finding.detail}. See design/31.`), { phase: "prompt-cache", sessionId: prepared.sessionId, classification: finding.cause });
136
+ }
137
+ }
138
+ }
139
+ if (m.role === "assistant" && typeof m.stopReason === "string") {
140
+ rs.turn.turnStopReason = m.stopReason;
141
+ }
142
+ if (m.role === "assistant") {
143
+ const rep = m.repetition;
144
+ if (rep !== undefined) {
145
+ const observed = [
146
+ ...(rep.cut !== undefined ? [{ action: "cut", e: rep.cut }] : []),
147
+ ...(rep.spared ?? []).map((e) => ({ action: "spared", e })),
148
+ ];
149
+ for (const { action, e } of observed) {
150
+ if (action === "cut")
151
+ rs.counters.repetitionCuts += 1;
152
+ else
153
+ rs.counters.repetitionSpared += 1;
154
+ if (rs.counters.repetitionEvents.length < rs.counters.REPETITION_EVENTS_CAP) {
155
+ rs.counters.repetitionEvents.push({ turn: stats.turns + 1, action, rule: e.rule, period: e.period, reps: e.reps, segment: e.segment });
156
+ }
157
+ emitTrace(rs.telemetry.tracer, () => ({
158
+ kind: "repetition.detected",
159
+ version: 1,
160
+ taskId: rs.telemetry.taskId,
161
+ turn: stats.turns + 1,
162
+ action,
163
+ rule: e.rule,
164
+ period: e.period,
165
+ reps: e.reps,
166
+ segment: e.segment,
167
+ ts: Date.now(),
168
+ }));
169
+ }
170
+ }
171
+ }
172
+ if (m.role === "assistant") {
173
+ const content = m.content;
174
+ if (rs.attach.attachState !== undefined)
175
+ rs.counters.cadenceTurns = advanceCadenceClock(rs.attach.attachState, rs.counters.cadenceTurns, content, writeFamilyOf);
176
+ rs.turn.lastTurnHadToolCalls = Array.isArray(content) && content.some((c) => c?.type === "toolCall");
177
+ }
178
+ rs.turn.callStartAt = undefined;
179
+ rs.turn.firstTokenAt = undefined;
180
+ rs.budget.callOutputChars = 0;
181
+ rs.budget.lastStreamBudgetCheck = 0;
182
+ };
183
+ const onToolStart = (event) => {
184
+ const startAt = Date.now();
185
+ toolStartAt.set(event.toolCallId, startAt);
186
+ startedToolCallIds.add(event.toolCallId);
187
+ batchArgs?.set(event.toolCallId, event.args);
188
+ stats.toolCalls += 1;
189
+ if (spec.finalVerification === true && !(rs.counters.finalVerifyInjections === 0 ? rs.counters.groundingSignalPreR9 : rs.counters.groundingSignalPostR9) && hasVerifiableStructureSignal(event.args)) {
190
+ if (rs.counters.finalVerifyInjections === 0)
191
+ rs.counters.groundingSignalPreR9 = true;
192
+ else
193
+ rs.counters.groundingSignalPostR9 = true;
194
+ }
195
+ const activityArg = primaryActivityArg(event.args);
196
+ internalsNotifier.notify(() => observeThenableRejection(internals?.onActivity?.({ phase: "start", toolCallId: event.toolCallId, toolName: event.toolName, at: startAt, ...(activityArg !== undefined ? { arg: activityArg } : {}) }), internalsNotifier, "runtask.onActivity.start"), "runtask.onActivity.start");
197
+ pushContent({
198
+ type: "tool_start",
199
+ toolCallId: event.toolCallId,
200
+ toolName: event.toolName,
201
+ ...(toolLabels.get(event.toolName) !== undefined ? { label: toolLabels.get(event.toolName) } : {}),
202
+ args: event.args,
203
+ ...ident(),
204
+ });
205
+ };
206
+ const onToolEnd = (event) => {
207
+ const toolStarted = toolStartAt.get(event.toolCallId);
208
+ toolStartAt.delete(event.toolCallId);
209
+ const toolNow = Date.now();
210
+ if (!rs.counters.wroteThisRun && (prepared.toolEffects.get(event.toolName) ?? "write") !== "read") {
211
+ rs.counters.wroteThisRun = true;
212
+ }
213
+ if (rs.attach.attachState !== undefined && !event.isError) {
214
+ const det = event.result?.details;
215
+ if (det !== undefined)
216
+ reduceToolEnd(rs.attach.attachState, det);
217
+ }
218
+ if (postToolBatchHook !== undefined) {
219
+ if (prepared.blockedToolCalls.delete(event.toolCallId) || event.notExecuted === true) {
220
+ batchArgs?.delete(event.toolCallId);
221
+ }
222
+ else {
223
+ const digest = batchResponseDigest(event.result);
224
+ rs.turn.toolBatch.push({
225
+ toolName: event.toolName,
226
+ input: batchArgs?.get(event.toolCallId),
227
+ toolCallId: event.toolCallId,
228
+ ...(digest !== undefined ? { response: digest } : {}),
229
+ isError: event.isError,
230
+ });
231
+ batchArgs?.delete(event.toolCallId);
232
+ }
233
+ }
234
+ emitTrace(rs.telemetry.tracer, () => ({
235
+ kind: "tool.call",
236
+ version: 1,
237
+ taskId: rs.telemetry.taskId,
238
+ name: event.toolName,
239
+ toolCallId: event.toolCallId,
240
+ turn: stats.turns + 1,
241
+ durationMs: toolStarted !== undefined ? toolNow - toolStarted : 0,
242
+ ok: !event.isError,
243
+ ts: toolNow,
244
+ }));
245
+ internalsNotifier.notify(() => observeThenableRejection(internals?.onActivity?.({ phase: "end", toolCallId: event.toolCallId, toolName: event.toolName, isError: event.isError, at: toolNow }), internalsNotifier, "runtask.onActivity.end"), "runtask.onActivity.end");
246
+ if (prepared.lspDiagnostics && !event.isError) {
247
+ const name = event.toolName;
248
+ if (name === "Edit" || name === "Write" || name === "NotebookEdit") {
249
+ const d = event.result?.details;
250
+ const p = typeof d?.filePath === "string" ? d.filePath : typeof d?.notebookPath === "string" ? d.notebookPath : undefined;
251
+ if (p)
252
+ prepared.lspDiagnostics.nudge(p);
253
+ }
254
+ }
255
+ const gate = prepared.gateOutcomes.get(event.toolCallId);
256
+ if (gate !== undefined)
257
+ prepared.gateOutcomes.delete(event.toolCallId);
258
+ pushContent({
259
+ type: "tool_end",
260
+ toolCallId: event.toolCallId,
261
+ toolName: event.toolName,
262
+ ...(toolLabels.get(event.toolName) !== undefined ? { label: toolLabels.get(event.toolName) } : {}),
263
+ isError: event.isError,
264
+ ...toolEndBodyFrom(event.result, event.isError, gate, prepared.abortController.signal.aborted ? gatedCallIdOf(prepared) : undefined),
265
+ ...ident(),
266
+ });
267
+ announceWorkspaceMove();
268
+ if (spec.outputSchema && event.toolName === OUTPUT_TOOL_NAME) {
269
+ if (event.isError) {
270
+ rs.degrade.outputErrorStreak += 1;
271
+ if (rs.degrade.outputErrorStreak > rs.limits.outputRetryCap) {
272
+ rs.degrade.outputInvalid = true;
273
+ void prepared.harness.abort();
274
+ prepared.abortController.abort();
275
+ }
276
+ }
277
+ else {
278
+ rs.degrade.outputErrorStreak = 0;
279
+ }
280
+ }
281
+ };
282
+ const onTurnEnd = () => {
283
+ stats.turns += 1;
284
+ const stopExtra = rs.turn.turnStopReason !== undefined ? { stopReason: rs.turn.turnStopReason } : {};
285
+ queue.push(rs.turn.turnUsage
286
+ ? { type: "turn_end", usage: rs.turn.turnUsage, ...(rs.turn.turnUsageMissing ? { usageMissing: true } : {}), ...stopExtra, ...ident() }
287
+ : { type: "turn_end", usageMissing: true, ...stopExtra, ...ident() });
288
+ rs.turn.turnUsageMissing = false;
289
+ rs.turn.turnStopReason = undefined;
290
+ if (parentToolCallId !== undefined) {
291
+ const progress = {
292
+ type: "task_progress",
293
+ taskId: rs.telemetry.taskId,
294
+ ...(internals?.delegationTaskType !== undefined ? { taskType: internals.delegationTaskType } : {}),
295
+ ...(internals?.cycleSeq !== undefined ? { seq: internals.cycleSeq } : {}),
296
+ ...(internals?.parentTaskId !== undefined ? { parentTaskId: internals.parentTaskId } : {}),
297
+ ...(subagentName ? { name: subagentName } : {}),
298
+ model: prepared.model.id,
299
+ usage: { totalTokens: stats.tokens, toolUses: stats.toolCalls, durationMs: Date.now() - rs.telemetry.taskStart },
300
+ status: "running",
301
+ ...ident(),
302
+ };
303
+ queue.push(progress);
304
+ try {
305
+ internals?.onForwardEvent?.(progress);
306
+ }
307
+ catch {
308
+ }
309
+ }
310
+ emitTrace(rs.telemetry.tracer, () => ({ kind: "turn.end", version: 1, taskId: rs.telemetry.taskId, turn: stats.turns, ts: Date.now() }));
311
+ rs.turn.turnUsage = undefined;
312
+ rs.turn.callStartAt = undefined;
313
+ rs.turn.firstTokenAt = undefined;
314
+ rs.budget.callOutputChars = 0;
315
+ rs.budget.lastStreamBudgetCheck = 0;
316
+ toolStartAt.clear();
317
+ if (rs.limits.effectiveMaxTurns !== undefined && rs.limits.effectiveMaxTurns > 0 && stats.turns >= rs.limits.effectiveMaxTurns) {
318
+ rs.limits.turnsExceeded = true;
319
+ if (prepared.suspendForResource === undefined) {
320
+ void prepared.harness.abort();
321
+ prepared.abortController.abort();
322
+ }
323
+ }
324
+ else {
325
+ if (rs.degrade.degraded === undefined &&
326
+ spec.limits?.degrade &&
327
+ rs.degrade.degradeToModel &&
328
+ rs.budget.maxCostMicroUsd !== undefined &&
329
+ stats.costMicroUsd >= spec.limits.degrade.atCostFraction * rs.budget.maxCostMicroUsd) {
330
+ void prepared.harness.setModel(rs.degrade.degradeToModel);
331
+ rs.degrade.recordDegraded({ from: prepared.model.id, to: rs.degrade.degradeToModel.id, reason: "budget", atTurn: stats.turns }, rs.degrade.degradeToModel);
332
+ }
333
+ const overAxis = rs.limits.budgetHit ? undefined : rs.budget.overBudget();
334
+ if (overAxis !== undefined) {
335
+ rs.limits.budgetHit = "exceeded";
336
+ rs.limits.budgetAxis = overAxis;
337
+ if (prepared.suspendForResource === undefined) {
338
+ void prepared.harness.abort();
339
+ prepared.abortController.abort();
340
+ }
341
+ }
342
+ }
343
+ };
344
+ return { onMessageUpdate, onMessageEnd, onToolStart, onToolEnd, onTurnEnd, announceWorkspaceMove };
345
+ }
@@ -0,0 +1,69 @@
1
+ import type { Model } from "../../internal/llm.js";
2
+ import type { Stats } from "./assemble-result.js";
3
+ import type { Prepared, RunState, TurnBoundaryDeps } from "./contracts.js";
4
+ /**
5
+ * §17.4 (design/64 GAP-25): stop attempting within-task compaction after this many CONSECUTIVE
6
+ * failures in one task — a never-recovering summary gateway must not burn an API call at every turn
7
+ * boundary (CC: MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3). Reset on any success; an open breaker also
8
+ * skips the end-of-task attempt in `finish()` (teardown unaffected). Per-task scope.
9
+ *
10
+ * RB-190 — this used to say "a flaky/never-recovering gateway", and the word `flaky` was not true. Because
11
+ * any success zeroes the counter, a gateway that alternates never reaches three CONSECUTIVE failures and
12
+ * the breaker never opens; it keeps paying for a call at every failed boundary, indefinitely.
13
+ *
14
+ * The behaviour is right and stays: a gateway succeeding half the time is compacting half the time, and
15
+ * opening the breaker on it would disable compaction outright and let the context overflow — trading a
16
+ * wasted call for a failed task. What was wrong is the comment claiming a guarantee the code does not
17
+ * make. An alternating gateway is deliberately NOT caught here; if that ever needs catching it wants a
18
+ * different signal (a failure RATE over a window), not a consecutive counter.
19
+ */
20
+ export declare const MAX_CONSECUTIVE_COMPACTION_FAILURES = 3;
21
+ /**
22
+ * §25.2 anti-thrash (LONGRUN-1b, search [84]): after a compaction, the context must regrow past
23
+ * (tokensBefore − freed) × this factor — the post-compaction size in trigger units — before another
24
+ * summary call fires. An EFFECTIVE compaction (freed large) leaves the floor far below the threshold,
25
+ * so behavior is unchanged; an INEFFECTIVE one (small window, chunky turns, large summary ⇒ freed
26
+ * small, still at/over the threshold) raises the floor so attempts space out instead of firing at
27
+ * every boundary (measured: 26/39 boundaries, +135% wall). The floor binds only when post-compaction
28
+ * size > threshold/1.5 ≈ compaction freed <33% headroom. Degrades gracefully: the retained summary
29
+ * chain keeps early facts; only the un-summarized tail falls back to trim.
30
+ */
31
+ export declare const COMPACTION_REGROWTH_FACTOR = 1.5;
32
+ /**
33
+ * design/84 Seam C reuse guard (b): a compaction that frees fewer than this many (structural) tokens is
34
+ * treated as NO-EFFECTIVE-COMPACTION and does NOT raise the anti-thrash floor. Guards against a reused
35
+ * provider summary (or a pathological LLM summary) that reclaims no headroom yet suppresses the next real
36
+ * summary by inflating the floor to trigger×factor.
37
+ */
38
+ export declare const COMPACTION_FREED_EPSILON = 256;
39
+ /**
40
+ * What the driver hands the turn-boundary lane, once per leg. The four seats are the four positional
41
+ * parameters the factory used to take; the Input is a carrier of REFERENCES, not a snapshot — the handler
42
+ * reads `prepared` / `rs` / `stats` / `deps` live on every boundary, exactly as the closure did.
43
+ */
44
+ export interface RunTurnBoundaryInput {
45
+ /** borrowed-mutable — the leg's prepared seat. Read throughout; written only into its per-run ref cells
46
+ * (`reviewRequestRef.pending`, `announcedListingsRef.*`, `trimPressureRef.droppedMessages`), the
47
+ * same cells the closure wrote inside the driver. */
48
+ prepared: Prepared;
49
+ /** borrowed-readonly — the run's usage counters; the boundary reads them (the governance commit, the
50
+ * budget pass, the degrade record) and never writes them. The harness-handlers lane is their writer. */
51
+ stats: Stats;
52
+ /** borrowed-mutable — the run's mutable state (counters, attach, turn, limits, budget, compaction,
53
+ * telemetry); the boundary is one of its writers, per the writer table on the type. */
54
+ rs: RunState;
55
+ /** borrowed-readonly — the driver-declared const seats + the hook seat over the runner's methods
56
+ * (see {@link TurnBoundaryDeps}). `loopLatch.userHalted` is read LIVE on every boundary. */
57
+ deps: TurnBoundaryDeps;
58
+ }
59
+ /**
60
+ * The handler the driver registers on the harness's `turn_boundary` hook. It resolves to `undefined`
61
+ * explicitly — the hook's result type is `undefined` (a pure timing signal) and `on()` requires the
62
+ * handler's Promise to match it exactly.
63
+ */
64
+ export interface RunTurnBoundaryResult {
65
+ (event: {
66
+ model: Model;
67
+ }): Promise<undefined>;
68
+ }
69
+ export declare function createTurnBoundary(input: RunTurnBoundaryInput): RunTurnBoundaryResult;