@sema-agent/core 5.7.0 → 5.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 (102) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/dist/agents/cascade.d.ts +1 -1
  3. package/dist/agents/cascade.js +24 -0
  4. package/dist/agents/subagent.d.ts +7 -7
  5. package/dist/agents/subagent.js +74 -33
  6. package/dist/agents/teacher.js +3 -3
  7. package/dist/agents/team.d.ts +1 -1
  8. package/dist/agents/team.js +1 -1
  9. package/dist/agents/verify.js +1 -1
  10. package/dist/brain/anthropic.js +24 -35
  11. package/dist/brain/open-responses.d.ts +11 -0
  12. package/dist/brain/open-responses.js +721 -0
  13. package/dist/brain/openai.js +22 -33
  14. package/dist/brain/request-params.d.ts +1 -0
  15. package/dist/brain/request-params.js +16 -0
  16. package/dist/brain/stream-engine.d.ts +0 -2
  17. package/dist/brain/stream-engine.js +5 -47
  18. package/dist/brain/stream-shared.d.ts +0 -10
  19. package/dist/brain/stream-shared.js +0 -23
  20. package/dist/brain/terminal-cause.d.ts +0 -1
  21. package/dist/brain/terminal-cause.js +0 -3
  22. package/dist/brain/timeout.d.ts +4 -2
  23. package/dist/brain/timeout.js +4 -14
  24. package/dist/config/catalog.d.ts +3 -11
  25. package/dist/config/catalog.js +30 -69
  26. package/dist/config/defaults.d.ts +0 -3
  27. package/dist/config/defaults.js +0 -3
  28. package/dist/core/auto-compaction.d.ts +2 -11
  29. package/dist/core/auto-compaction.js +3 -67
  30. package/dist/core/checkpoint-store.d.ts +10 -8
  31. package/dist/core/checkpoint-store.js +7 -7
  32. package/dist/core/fs-write-gate-policy.js +2 -2
  33. package/dist/core/lsp-diagnostics.d.ts +3 -2
  34. package/dist/core/lsp-diagnostics.js +20 -7
  35. package/dist/core/memory-engine/file-backend.js +20 -17
  36. package/dist/core/memory-engine/layout.d.ts +5 -0
  37. package/dist/core/memory-engine/layout.js +72 -47
  38. package/dist/core/runner/assemble-result.d.ts +3 -8
  39. package/dist/core/runner/assemble-result.js +24 -18
  40. package/dist/core/runner/prepare-task.d.ts +27 -6
  41. package/dist/core/runner/prepare-task.js +209 -144
  42. package/dist/core/runner/runtask.d.ts +0 -2
  43. package/dist/core/runner/runtask.js +228 -266
  44. package/dist/core/runner/session-rule-policy.d.ts +1 -0
  45. package/dist/core/runner/session-rule-policy.js +4 -3
  46. package/dist/core/session-policy-store.d.ts +5 -0
  47. package/dist/core/session-policy-store.js +3 -1
  48. package/dist/core/store-contracts/checkpoint-store-contract.js +2 -2
  49. package/dist/core/task-registry-agent.d.ts +2 -0
  50. package/dist/core/task-registry-agent.js +33 -1
  51. package/dist/core/task-registry-shared.d.ts +0 -1
  52. package/dist/core/task-registry.d.ts +2 -0
  53. package/dist/core/task-registry.js +9 -1
  54. package/dist/core/tool-errors.js +10 -2
  55. package/dist/core/tool-policy.d.ts +8 -0
  56. package/dist/core/tool-policy.js +11 -0
  57. package/dist/core/trace.d.ts +1 -10
  58. package/dist/core/types.d.ts +27 -30
  59. package/dist/core/usage-window-store.d.ts +39 -0
  60. package/dist/core/usage-window-store.js +115 -0
  61. package/dist/engine/execution-env/node-execution-env.js +0 -22
  62. package/dist/engine/harness/agent-harness.d.ts +1 -7
  63. package/dist/engine/harness/agent-harness.js +16 -53
  64. package/dist/engine/harness/types.d.ts +3 -15
  65. package/dist/engine/llm/types.d.ts +3 -75
  66. package/dist/engine/loop/agent-loop.d.ts +1 -1
  67. package/dist/engine/loop/agent-loop.js +7 -173
  68. package/dist/engine/loop/types.d.ts +1 -18
  69. package/dist/engine/session/repo-utils.d.ts +1 -2
  70. package/dist/engine/session/repo-utils.js +0 -7
  71. package/dist/index.d.ts +13 -7
  72. package/dist/index.js +10 -5
  73. package/dist/internal/llm.d.ts +1 -1
  74. package/dist/orchestration/goal.js +1 -5
  75. package/dist/orchestration/run-workflow-tool.d.ts +1 -1
  76. package/dist/orchestration/run-workflow-tool.js +1 -2
  77. package/dist/orchestration/workflow-governance.d.ts +6 -6
  78. package/dist/orchestration/workflow-governance.js +33 -47
  79. package/dist/orchestration/workflow.js +14 -5
  80. package/dist/prompt-assembly/event-registry.js +2 -3
  81. package/dist/stores/cc/lockfile.js +9 -11
  82. package/dist/stores/cc/mailbox-store.js +49 -38
  83. package/dist/stores/file/index.d.ts +3 -0
  84. package/dist/stores/file/index.js +4 -0
  85. package/dist/stores/file/usage-window-store.d.ts +9 -0
  86. package/dist/stores/file/usage-window-store.js +86 -0
  87. package/dist/tools/fs/fs-bash.d.ts +1 -10
  88. package/dist/tools/fs/fs-bash.js +20 -60
  89. package/dist/tools/fs/index.d.ts +0 -2
  90. package/dist/tools/fs/index.js +1 -2
  91. package/dist/tools/web.js +20 -19
  92. package/package.json +4 -5
  93. package/dist/bin/sema-tb.d.ts +0 -31
  94. package/dist/bin/sema-tb.js +0 -448
  95. package/dist/bin/tb-env.d.ts +0 -2
  96. package/dist/bin/tb-env.js +0 -17
  97. package/dist/brain/walltime.d.ts +0 -1
  98. package/dist/brain/walltime.js +0 -1
  99. package/dist/core/runner/call-cap.d.ts +0 -67
  100. package/dist/core/runner/call-cap.js +0 -145
  101. package/dist/core/runner/cut-kill.d.ts +0 -10
  102. package/dist/core/runner/cut-kill.js +0 -37
@@ -1,15 +1,14 @@
1
1
  import { AgentHarness, DEFAULT_COMPACTION_SETTINGS, uuidv7 } from "../../internal/harness.js";
2
- import { CheckpointError, BINDING_CHECKPOINT_VERSION, checkpointVersionOf, MAX_SUPPORTED_CHECKPOINT_VERSION, remainingBudgetMicroUsd, remainingWalltimeMs, validatePendingSteer, winnerFromOutcome, } from "../checkpoint-store.js";
3
- import { effectiveCushionMs, estimateCycleMs, hasRealContent, minLiveWriteoutWindowMs, recordCallSample, recordToolCycleSample, recordToolSample, recordTtftSample, softExecDeadlineMs, writeoutCushionMs } from "./call-cap.js";
2
+ import { CheckpointError, BINDING_CHECKPOINT_VERSION, checkpointVersionOf, MAX_SUPPORTED_CHECKPOINT_VERSION, remainingBudgetMicroUsd, remainingTokens, validatePendingSteer, winnerFromOutcome, } from "../checkpoint-store.js";
4
3
  import { engineVersion } from "../version.js";
5
4
  import { CONFIG_CATALOG_VERSION, declarationReasons, resolveEffectiveConfig } from "../../config/catalog.js";
6
5
  import { eventDefaultOn } from "../../prompt-assembly/event-registry.js";
7
- import { DEFAULT_COMPACTION_INSTRUCTIONS, createRapidRefillState, isCompactionManualCancel, isCompactionWalltimeAbort, maybeCompact, nextTrimForceBackoff, recordCompactionAndCheckRapidRefill } from "../auto-compaction.js";
6
+ import { DEFAULT_COMPACTION_INSTRUCTIONS, createRapidRefillState, isCompactionManualCancel, maybeCompact, nextTrimForceBackoff, recordCompactionAndCheckRapidRefill } from "../auto-compaction.js";
8
7
  import { ASK_USER_QUESTION_TOOL_NAME, QUESTION_AWAITS_RESUME } from "../ask-question.js";
9
8
  import { computeCostMicroUsd, modelCostToPricing } from "../pricing.js";
10
9
  import { emitTrace } from "../trace.js";
11
10
  import { emitTaskOutcome } from "../task-outcome.js";
12
- import { isDegenerateCutMessage, isWalltimeCutMessage } from "../../brain/terminal-cause.js";
11
+ import { isDegenerateCutMessage } from "../../brain/terminal-cause.js";
13
12
  import { primaryActivityArg } from "../arg-summary.js";
14
13
  import { resolveReasoning } from "../../brain/reasoning.js";
15
14
  import { readDegradation } from "../../brain/degrading.js";
@@ -41,17 +40,15 @@ import { defaultTaskRegistry } from "../task-registry.js";
41
40
  import { discloseDroppedPending, isDelegatedAgentTerminal, PendingSessionNotifications, renderTaskNotificationXml, SystemInjectionQueue, taskNotificationDedupKey } from "../task-notification.js";
42
41
  import { ToolDetachHub } from "../tool-detach.js";
43
42
  import { workflowSizeGuidelineChangeNotice } from "../../orchestration/workflow-size-guideline.js";
44
- import { DEFAULT_MAX_TURNS } from "../../config/defaults.js";
45
43
  import { structuredFrom, toolOutputFrom } from "./tool-output-projection.js";
46
- export { DEFAULT_MAX_TURNS };
47
44
  function createRunState() {
48
45
  return {
49
46
  telemetry: { cacheFamily: "input-excludes-cached", pricing: { inputPer1M: 0, outputPer1M: 0 }, pricingConfigured: true, unpricedSpend: false, tracer: undefined, taskId: "", taskStart: 0, taskStartMonotonic: 0, cacheBreakReported: false },
50
47
  degrade: { degradeToModel: undefined, degraded: undefined, outputErrorStreak: 0, outputInvalid: false, recordDegraded: () => { } },
51
- limits: { turnsExceeded: false, budgetHit: undefined, outputRetryCap: 0, effectiveMaxTurns: undefined },
52
- budget: { remainingMicroUsd: undefined, maxCostMicroUsd: undefined, overBudget: () => false, streamCancel: false, callOutputChars: 0, lastStreamBudgetCheck: 0, projectedOverBudget: () => false },
48
+ limits: { turnsExceeded: false, budgetHit: undefined, budgetAxis: undefined, platformTerminal: undefined, outputRetryCap: 0, effectiveMaxTurns: undefined },
49
+ budget: { remainingMicroUsd: undefined, maxCostMicroUsd: undefined, remainingTokens: undefined, maxTokensWindow: undefined, overBudget: () => undefined, streamCancel: false, callOutputChars: 0, lastStreamBudgetCheck: 0, projectedOverBudget: () => undefined },
53
50
  turn: { callStartAt: undefined, firstTokenAt: undefined, turnUsage: undefined, turnUsageMissing: false, turnStopReason: undefined, lastTurnHadToolCalls: false, toolBatch: [] },
54
- counters: { nudgesSent: 0, nudgeIdx: 0, finalizeInjected: false, walltimeSyncBackstopFired: false, compactionFloor: 0, trimForceBackoff: false, callCutoffs: 0, repetitionCuts: 0, repetitionSpared: 0, repetitionEvents: [], REPETITION_EVENTS_CAP: 0, preemptIgnoredReported: false, wroteThisRun: false, finalVerifyInjections: 0, groundingSignalPreR9: false, groundingSignalPostR9: false, cadenceTurns: 0 },
51
+ counters: { approachNoticesSent: 0, walltimeSyncBackstopFired: false, compactionFloor: 0, trimForceBackoff: false, repetitionCuts: 0, repetitionSpared: 0, repetitionEvents: [], REPETITION_EVENTS_CAP: 0, preemptIgnoredReported: false, wroteThisRun: false, finalVerifyInjections: 0, groundingSignalPreR9: false, groundingSignalPostR9: false, cadenceTurns: 0 },
55
52
  attach: { attachmentsCfg: undefined, agentListingOn: false, skillsListingOn: false, attachState: undefined, dateState: undefined, instrProbe: undefined, instrState: undefined, sizeGuidelineState: undefined, attachmentsInjected: 0 },
56
53
  };
57
54
  }
@@ -96,14 +93,19 @@ const SUGGESTIONS_TIMEOUT_MS = 30_000;
96
93
  const SUGGESTIONS_TRANSCRIPT_MESSAGES = 12;
97
94
  const SNAPSHOT_TIMEOUT_MS = 30_000;
98
95
  const SNAPSHOT_TOO_LARGE_TTL_MS = 30 * 60_000;
96
+ const LIMIT_APPROACH_DEFAULT_THRESHOLDS = [0.8, 0.95];
97
+ const DEFAULT_PRECALL_OUTPUT_TOKENS = 4096;
98
+ function limitApproachFrames(index, axis, percent) {
99
+ return index === 0
100
+ ? `<system-reminder>[limit approach] This task has used about ${percent}% of its ${axis}. ` +
101
+ `Start converging: finish the step you are on, drop work that is not required for the deliverable, and ` +
102
+ `avoid starting anything you cannot complete within what is left.</system-reminder>`
103
+ : `<system-reminder>[limit approach] About ${percent}% of the ${axis} is gone. Deliver now: ` +
104
+ `stop exploring, write up what you have — including what is incomplete and what you would do next — ` +
105
+ `and end the task. Work that is not reported before the allowance runs out is lost.</system-reminder>`;
106
+ }
99
107
  function resolveMaxTurns(limits) {
100
- if (limits?.maxTurns !== undefined) {
101
- return limits.maxTurns;
102
- }
103
- if (limits?.timeoutSec !== undefined && limits.timeoutSec > 0) {
104
- return undefined;
105
- }
106
- return DEFAULT_MAX_TURNS;
108
+ return limits?.maxTurns;
107
109
  }
108
110
  function resumeDecisionWasNegative(resume) {
109
111
  const o = resume.outcome;
@@ -111,6 +113,16 @@ function resumeDecisionWasNegative(resume) {
111
113
  }
112
114
  const DEFERRED_REISSUE = "[DEFERRED] This tool call shared a batch with a call that suspended for durable approval, so it was " +
113
115
  "NOT executed on resume. If you still need it, issue it again now.";
116
+ function toolEndBodyFrom(result, isError) {
117
+ const o = toolOutputFrom(result);
118
+ const st = structuredFrom(result);
119
+ const code = isError ? result?.details?.code : undefined;
120
+ return {
121
+ ...(o !== undefined ? { output: o.output, ...(o.truncated ? { truncated: true } : {}), ...(o.totalChars !== undefined ? { totalChars: o.totalChars } : {}) } : {}),
122
+ ...(st !== undefined ? { structured: st } : {}),
123
+ ...(typeof code === "string" ? { errorCode: code } : {}),
124
+ };
125
+ }
114
126
  function deepJsonEqual(a, b) {
115
127
  if (a === b)
116
128
  return true;
@@ -228,62 +240,85 @@ function resumeContinuation(resume) {
228
240
  `conversation above. Do NOT restart the task or re-run any tool you already ran; continue from this ` +
229
241
  `exact point, building on the existing results, and finish the remaining work.`);
230
242
  }
243
+ function platformLimitTerminal(reason, retryAfterMs, moment = "turn_boundary") {
244
+ const message = moment === "entry"
245
+ ? `a deployment usage window is exhausted (RunnerDeps.usageWindows), so the task was refused before its first model call — nothing ran and nothing was spent. There is no checkpoint to suspend into at this point, so the caller re-submits after the window frees, in ${String(retryAfterMs ?? 0)}ms.`
246
+ : reason === "env_lifetime"
247
+ ? "the execution environment's declared lifetime is expiring and the run could not be suspended durably (not eligible, or the suspend was refused — see remoteEnvFailures / onError). It was stopped at a clean turn boundary instead of being killed mid-turn when the platform reclaims the environment."
248
+ : `a deployment usage window is exhausted (RunnerDeps.usageWindows) and the run could not be suspended durably, so it was stopped at a clean turn boundary. The window frees in ${String(retryAfterMs ?? 0)}ms.`;
249
+ const e = new Error(message);
250
+ e.code = reason === "env_lifetime" ? "env.lifetime_expired" : "usage.window_exhausted";
251
+ if (retryAfterMs !== undefined)
252
+ e.retryAfterMs = retryAfterMs;
253
+ return e;
254
+ }
231
255
  function makeTurnBoundary(prepared, stats, rs, deps) {
232
- const { spec, queue, manualCompactRef, todoToolMounted, taskToolsMounted, turnToolSpan, walltimeDeadlineMs, walltimeMonotonicDeadline, nudgeSchedule, buildFinalizeText, timeout, ident, postToolBatchHook, batchArgs, compactionBrain, withinTaskCompaction, compactionBreaker, windowSafetyOptions, rapidRefill, drainManualCompact, runnerHooks } = deps;
256
+ const { spec, queue, manualCompactRef, todoToolMounted, taskToolsMounted, walltimeMonotonicDeadline, timeout, ident, postToolBatchHook, batchArgs, compactionBrain, withinTaskCompaction, compactionBreaker, windowSafetyOptions, rapidRefill, drainManualCompact, runnerHooks } = deps;
233
257
  let finalVerifySeenAtLastBoundary = 0;
234
258
  const onTurnBoundary = async (event) => {
235
- if (prepared.callCapRef && turnToolSpan.startMin !== undefined && turnToolSpan.endMax !== undefined) {
236
- recordToolCycleSample(prepared.callCapRef.state, turnToolSpan.endMax - turnToolSpan.startMin);
237
- }
238
- turnToolSpan.startMin = undefined;
239
- turnToolSpan.endMax = undefined;
240
259
  let boundarySteered = false;
241
- if (prepared.suspendForResource === undefined) {
242
- const nowMs = Date.now();
243
- if (!rs.counters.finalizeInjected &&
244
- spec.limits?.gracefulFinalize !== false &&
245
- walltimeDeadlineMs !== undefined &&
246
- prepared.callCapRef !== undefined) {
247
- const remainingMs = walltimeDeadlineMs - nowMs;
248
- if (remainingMs > 0 && remainingMs < estimateCycleMs(prepared.callCapRef.state) + (effectiveCushionMs(prepared.callCapRef) ?? 0)) {
249
- rs.counters.finalizeInjected = true;
250
- prepared.callCapRef.finalizeMode = true;
251
- void prepared.harness.setThinkingLevel("off").catch(() => { });
252
- prepared.turnSnapshot?.refreshRequestPolicy({ thinkingLevel: "off" });
253
- rs.counters.nudgeIdx = nudgeSchedule.length;
254
- const finalizeText = buildFinalizeText();
255
- void prepared.harness.steer(finalizeText, { engineMinted: true }).catch(() => { });
256
- boundarySteered = true;
257
- queue.push({ type: "steering_injected", source: "finalize", preview: finalizeText.slice(0, 220), ...ident() });
258
- }
259
- }
260
- let due;
261
- while (rs.counters.nudgeIdx < nudgeSchedule.length && nowMs >= nudgeSchedule[rs.counters.nudgeIdx].atMs) {
262
- due = nudgeSchedule[rs.counters.nudgeIdx++];
263
- }
264
- if (due) {
265
- void prepared.harness.steer(due.text, { engineMinted: true }).catch(() => { });
266
- rs.counters.nudgesSent += 1;
267
- boundarySteered = true;
268
- queue.push({ type: "steering_injected", source: "deadline_nudge", preview: due.text.slice(0, 220), ...ident() });
269
- }
270
- }
271
260
  const walltimeHit = walltimeMonotonicDeadline !== undefined && performance.now() >= walltimeMonotonicDeadline;
272
261
  const preemptHit = spec.preemptSignal?.aborted === true;
273
262
  if (preemptHit && !prepared.suspendForResource && !rs.counters.preemptIgnoredReported) {
274
263
  rs.counters.preemptIgnoredReported = true;
275
264
  emitTrace(rs.telemetry.tracer, () => ({ kind: "preempt.ignored", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() }));
276
265
  }
266
+ let platformCause;
267
+ let platformRetryAfterMs;
268
+ const preemptWillSuspend = preemptHit && prepared.suspendForResource !== undefined;
269
+ if (!preemptWillSuspend && prepared.suspendRef.token === undefined && !prepared.abortController.signal.aborted) {
270
+ if (prepared.envLifetimeSuspendAt !== undefined && Date.now() >= prepared.envLifetimeSuspendAt) {
271
+ platformCause = "env_lifetime";
272
+ }
273
+ else if (prepared.usageGovernance !== undefined) {
274
+ const governance = prepared.usageGovernance;
275
+ try {
276
+ const at = Date.now();
277
+ await governance.commit(stats.tokens, at);
278
+ platformRetryAfterMs = await governance.check(at);
279
+ if (platformRetryAfterMs !== undefined)
280
+ platformCause = "usage_window";
281
+ }
282
+ catch (govErr) {
283
+ rs.limits.platformTerminal = govErr instanceof Error ? govErr : new Error(String(govErr));
284
+ void prepared.harness.abort();
285
+ prepared.abortController.abort();
286
+ return undefined;
287
+ }
288
+ }
289
+ }
290
+ if (platformCause !== undefined) {
291
+ const platformSpend = { costMicroUsd: stats.costMicroUsd, tokens: stats.tokens, turns: stats.turns, walltimeMs: Math.round(performance.now() - rs.telemetry.taskStartMonotonic) };
292
+ const committed = prepared.suspendForPlatformLimit !== undefined &&
293
+ (await prepared.suspendForPlatformLimit(platformCause, platformSpend, platformRetryAfterMs !== undefined ? { resumeAfterMs: platformRetryAfterMs } : undefined));
294
+ if (committed) {
295
+ rs.limits.turnsExceeded = false;
296
+ rs.limits.budgetHit = undefined;
297
+ return undefined;
298
+ }
299
+ const terminal = platformLimitTerminal(platformCause, platformRetryAfterMs);
300
+ rs.limits.platformTerminal = terminal;
301
+ try {
302
+ runnerHooks.onError?.(terminal, { phase: "config", sessionId: prepared.sessionId });
303
+ }
304
+ catch {
305
+ }
306
+ void prepared.harness.abort();
307
+ prepared.abortController.abort();
308
+ return undefined;
309
+ }
277
310
  if (prepared.suspendForResource &&
278
311
  (rs.limits.turnsExceeded || rs.limits.budgetHit !== undefined || walltimeHit || preemptHit) &&
279
312
  prepared.suspendRef.token === undefined) {
280
313
  const reason = preemptHit
281
314
  ? "preempt"
282
315
  : rs.limits.budgetHit !== undefined
283
- ? "budget"
284
- : walltimeHit
285
- ? "walltime"
286
- : "turns";
316
+ ? rs.limits.budgetAxis === "tokens"
317
+ ? "budget_tokens"
318
+ : "budget_cost"
319
+ : rs.limits.turnsExceeded
320
+ ? "turns"
321
+ : "walltime";
287
322
  const sliceSpend = { costMicroUsd: stats.costMicroUsd, tokens: stats.tokens, turns: stats.turns, walltimeMs: Math.round(performance.now() - rs.telemetry.taskStartMonotonic) };
288
323
  if (await prepared.suspendForResource(reason, sliceSpend)) {
289
324
  rs.limits.turnsExceeded = false;
@@ -308,6 +343,39 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
308
343
  }
309
344
  return undefined;
310
345
  }
346
+ const approachCfg = spec.limits?.approachNotice;
347
+ if (approachCfg !== false &&
348
+ rs.counters.approachNoticesSent < LIMIT_APPROACH_DEFAULT_THRESHOLDS.length &&
349
+ prepared.suspendForResource === undefined &&
350
+ rs.turn.lastTurnHadToolCalls &&
351
+ !boundarySteered &&
352
+ !prepared.abortController.signal.aborted) {
353
+ const thresholds = approachCfg?.at ?? LIMIT_APPROACH_DEFAULT_THRESHOLDS;
354
+ const ratios = [];
355
+ if (rs.budget.maxTokensWindow !== undefined && rs.budget.maxTokensWindow > 0) {
356
+ ratios.push({ axis: "token budget", ratio: stats.tokens / rs.budget.maxTokensWindow });
357
+ }
358
+ if (rs.budget.maxCostMicroUsd !== undefined && rs.budget.maxCostMicroUsd > 0) {
359
+ ratios.push({ axis: "cost budget", ratio: stats.costMicroUsd / rs.budget.maxCostMicroUsd });
360
+ }
361
+ const declaredMaxTurns = spec.limits?.maxTurns;
362
+ if (declaredMaxTurns !== undefined && declaredMaxTurns > 0) {
363
+ ratios.push({ axis: "turn budget", ratio: (stats.turns + 1) / declaredMaxTurns });
364
+ }
365
+ let tightest;
366
+ for (const r of ratios)
367
+ if (tightest === undefined || r.ratio > tightest.ratio)
368
+ tightest = r;
369
+ const index = rs.counters.approachNoticesSent;
370
+ if (tightest !== undefined && tightest.ratio >= thresholds[index]) {
371
+ rs.counters.approachNoticesSent += 1;
372
+ const percent = Math.min(100, Math.round(tightest.ratio * 100));
373
+ const body = limitApproachFrames(index === 0 ? 0 : 1, tightest.axis, percent);
374
+ void prepared.harness.steer(body, { engineMinted: true }).catch(() => undefined);
375
+ queue.push({ type: "steering_injected", source: "limit_approach", preview: body.slice(0, 220), ...ident() });
376
+ boundarySteered = true;
377
+ }
378
+ }
311
379
  if (prepared.reviewRequestRef.pending !== undefined) {
312
380
  const { reason } = prepared.reviewRequestRef.pending;
313
381
  prepared.reviewRequestRef.pending = undefined;
@@ -322,7 +390,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
322
390
  emitTrace(rs.telemetry.tracer, () => ({ kind: "review.dropped", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() }));
323
391
  }
324
392
  if (prepared.lspDiagnostics && !prepared.lspDiagnostics.registry.isEmpty() && !prepared.abortController.signal.aborted) {
325
- const files = prepared.lspDiagnostics.registry.drain();
393
+ const files = prepared.lspDiagnostics.registry.drain(prepared.lspDiagnostics.runIdent);
326
394
  if (files.length > 0) {
327
395
  queue.push({ type: "diagnostics", files, isNew: true, ...ident() });
328
396
  const block = formatDiagnosticsBlock(files);
@@ -337,7 +405,6 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
337
405
  rs.attach.sizeGuidelineState !== undefined ||
338
406
  rs.attach.attachState !== undefined) &&
339
407
  !boundarySteered &&
340
- !rs.counters.finalizeInjected &&
341
408
  rs.counters.finalVerifyInjections === 0 &&
342
409
  !prepared.abortController.signal.aborted) {
343
410
  const due = [];
@@ -525,7 +592,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
525
592
  const batch = rs.turn.toolBatch;
526
593
  rs.turn.toolBatch = [];
527
594
  batchArgs?.clear();
528
- const injectedThisTurn = finalVerifyInjectedThisTurn ? "final_verification" : rs.counters.finalizeInjected ? "finalize" : undefined;
595
+ const injectedThisTurn = finalVerifyInjectedThisTurn ? "final_verification" : undefined;
529
596
  if (!boundarySteered && !prepared.abortController.signal.aborted) {
530
597
  try {
531
598
  const r = await postToolBatchHook(batch, injectedThisTurn !== undefined ? { injectedThisTurn } : undefined);
@@ -555,7 +622,6 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
555
622
  }
556
623
  const forceManual = manualCompactRef.requested;
557
624
  if ((!withinTaskCompaction && !forceManual) ||
558
- (rs.counters.finalizeInjected && !forceManual) ||
559
625
  prepared.abortController.signal.aborted ||
560
626
  prepared.suspendRef.token !== undefined ||
561
627
  prepared.reviewRef.token !== undefined) {
@@ -617,9 +683,6 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
617
683
  const trimPressure = trimPressureArmed && !rs.counters.trimForceBackoff;
618
684
  const passTrigger = trimPressure ? "forced" : forceManual ? "manual" : "auto";
619
685
  let manualOutcome = "failed";
620
- if (rs.counters.finalizeInjected && forceManual) {
621
- emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.manual_in_finalize", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() }));
622
- }
623
686
  const manualInstructions = claimedManual?.instructions;
624
687
  try {
625
688
  const comp = await maybeCompact({
@@ -744,16 +807,10 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
744
807
  manualOutcome = "mooted";
745
808
  }
746
809
  else if (!prepared.abortController.signal.aborted) {
747
- const walltimeAbort = isCompactionWalltimeAbort(err);
748
- if (!walltimeAbort)
749
- compactionBreaker.failures += 1;
810
+ compactionBreaker.failures += 1;
750
811
  runnerHooks.onError?.(err, { phase: "compaction", sessionId: prepared.sessionId });
751
812
  const msg = String(err instanceof Error ? err.message : err);
752
- const failReason = walltimeAbort
753
- ? "walltime soft-deadline abort (engine-initiated write-out protection; not counted toward the breaker)"
754
- : msg.length > 512
755
- ? `${msg.slice(0, 512)}…`
756
- : msg;
813
+ const failReason = msg.length > 512 ? `${msg.slice(0, 512)}…` : msg;
757
814
  emitTrace(rs.telemetry.tracer, () => ({
758
815
  kind: "compaction.failed",
759
816
  version: 1,
@@ -776,7 +833,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
776
833
  return onTurnBoundary;
777
834
  }
778
835
  function makeHarnessHandlers(prepared, stats, rs, deps) {
779
- const { spec, queue, internals, buildFinalizeText, ident, nudgeSchedule, parentToolCallId, subagentName, pushContent, emitCommitted, startedToolCallIds, toolStartAt, turnToolSpan, writeFamilyOf, toolLabels, postToolBatchHook, batchArgs } = deps;
836
+ const { spec, queue, internals, ident, parentToolCallId, subagentName, pushContent, emitCommitted, startedToolCallIds, toolStartAt, writeFamilyOf, toolLabels, postToolBatchHook, batchArgs } = deps;
780
837
  let lastWorkspaceCwd = prepared.cwdRef?.current;
781
838
  const announceWorkspaceMove = () => {
782
839
  const cwdNow = prepared.cwdRef?.current;
@@ -801,8 +858,10 @@ function makeHarnessHandlers(prepared, stats, rs, deps) {
801
858
  rs.budget.callOutputChars += ev.delta.length;
802
859
  if (rs.budget.callOutputChars - rs.budget.lastStreamBudgetCheck >= 256) {
803
860
  rs.budget.lastStreamBudgetCheck = rs.budget.callOutputChars;
804
- if (rs.budget.projectedOverBudget()) {
861
+ const projectedAxis = rs.budget.projectedOverBudget();
862
+ if (projectedAxis !== undefined) {
805
863
  rs.limits.budgetHit = "exceeded";
864
+ rs.limits.budgetAxis = projectedAxis;
806
865
  void prepared.harness.abort();
807
866
  prepared.abortController.abort();
808
867
  }
@@ -841,19 +900,8 @@ function makeHarnessHandlers(prepared, stats, rs, deps) {
841
900
  rs.turn.turnUsage.cacheWriteTokens += u.cacheWrite || 0;
842
901
  rs.turn.turnUsage.costMicroUsd += turnCostMicroUsd;
843
902
  const callEndAt = Date.now();
844
- const callIssuedAt = prepared.callIssuedAtRef?.current;
845
- if (prepared.callIssuedAtRef)
846
- prepared.callIssuedAtRef.current = undefined;
847
- const capLast = prepared.callCapRef?.state.last;
848
- if (prepared.callCapRef) {
849
- recordCallSample(prepared.callCapRef.state, u.output || 0, rs.turn.callStartAt !== undefined ? callEndAt - rs.turn.callStartAt : 0, {
850
- contentValid: m.usageMissing === true && hasRealContent(m.content),
851
- });
852
- if (rs.turn.callStartAt !== undefined && rs.turn.firstTokenAt !== undefined) {
853
- recordTtftSample(prepared.callCapRef.state, rs.turn.firstTokenAt - rs.turn.callStartAt);
854
- }
855
- prepared.callCapRef.state.last = undefined;
856
- }
903
+ const callIssuedAt = prepared.callIssuedAtRef.current;
904
+ prepared.callIssuedAtRef.current = undefined;
857
905
  emitTrace(rs.telemetry.tracer, () => ({
858
906
  kind: "brain.call",
859
907
  version: 1,
@@ -870,25 +918,11 @@ function makeHarnessHandlers(prepared, stats, rs, deps) {
870
918
  firstTokenMs: rs.turn.callStartAt !== undefined && rs.turn.firstTokenAt !== undefined ? rs.turn.firstTokenAt - rs.turn.callStartAt : undefined,
871
919
  ...(callIssuedAt !== undefined ? { callStartedAt: callIssuedAt } : {}),
872
920
  ...(rs.telemetry.pricingConfigured ? { costMicroUsd: turnCostMicroUsd } : {}),
873
- ...(capLast !== undefined ? { callCap: capLast.cap, capThinkingSkipped: capLast.thinkingSkipped } : {}),
874
921
  ...(m.role === "assistant" && typeof m.stopReason === "string"
875
922
  ? { stopReason: m.stopReason }
876
923
  : {}),
877
924
  ts: callEndAt,
878
925
  }));
879
- if (prepared.callCapRef &&
880
- prepared.callCapRef.reasoningObserved !== true &&
881
- capLast !== undefined &&
882
- m.role === "assistant" &&
883
- m.stopReason === "length") {
884
- const blocks = Array.isArray(m.content)
885
- ? (m.content)
886
- : [];
887
- const productive = blocks.some((c) => c?.type === "toolCall") ||
888
- blocks.some((c) => c?.type === "text" && typeof c.text === "string" && c.text.trim() !== "");
889
- if (!productive)
890
- prepared.callCapRef.reasoningObserved = true;
891
- }
892
926
  if (prepared.cacheBreakDetector && prepared.cacheFingerprint) {
893
927
  const finding = prepared.cacheBreakDetector.observe({
894
928
  turn: stats.turns + 1,
@@ -906,20 +940,6 @@ function makeHarnessHandlers(prepared, stats, rs, deps) {
906
940
  if (m.role === "assistant" && typeof m.stopReason === "string") {
907
941
  rs.turn.turnStopReason = m.stopReason;
908
942
  }
909
- if (m.role === "assistant" && isWalltimeCutMessage(m)) {
910
- rs.counters.callCutoffs += 1;
911
- if (prepared.callCapRef)
912
- prepared.callCapRef.finalizeMode = true;
913
- if (!rs.counters.finalizeInjected) {
914
- const finalizeText = buildFinalizeText();
915
- void prepared.harness.steer(finalizeText, { engineMinted: true }).catch(() => { });
916
- void prepared.harness.setThinkingLevel("off").catch(() => { });
917
- prepared.turnSnapshot?.refreshRequestPolicy({ thinkingLevel: "off" });
918
- rs.counters.finalizeInjected = true;
919
- rs.counters.nudgeIdx = nudgeSchedule.length;
920
- queue.push({ type: "steering_injected", source: "finalize", preview: finalizeText.slice(0, 220), ...ident() });
921
- }
922
- }
923
943
  if (m.role === "assistant") {
924
944
  const rep = m.repetition;
925
945
  if (rep !== undefined) {
@@ -950,13 +970,6 @@ function makeHarnessHandlers(prepared, stats, rs, deps) {
950
970
  }
951
971
  }
952
972
  }
953
- if (prepared.callCapRef &&
954
- prepared.callCapRef.reasoningObserved !== true &&
955
- m.role === "assistant" &&
956
- Array.isArray(m.content) &&
957
- m.content.some((c) => c?.type === "thinking")) {
958
- prepared.callCapRef.reasoningObserved = true;
959
- }
960
973
  if (m.role === "assistant") {
961
974
  const content = m.content;
962
975
  if (rs.attach.attachState !== undefined)
@@ -995,11 +1008,6 @@ function makeHarnessHandlers(prepared, stats, rs, deps) {
995
1008
  const toolStarted = toolStartAt.get(event.toolCallId);
996
1009
  toolStartAt.delete(event.toolCallId);
997
1010
  const toolNow = Date.now();
998
- if (prepared.callCapRef && toolStarted !== undefined) {
999
- recordToolSample(prepared.callCapRef.state, toolNow - toolStarted);
1000
- turnToolSpan.startMin = turnToolSpan.startMin === undefined ? toolStarted : Math.min(turnToolSpan.startMin, toolStarted);
1001
- turnToolSpan.endMax = turnToolSpan.endMax === undefined ? toolNow : Math.max(turnToolSpan.endMax, toolNow);
1002
- }
1003
1011
  if (!rs.counters.wroteThisRun && (prepared.toolEffects.get(event.toolName) ?? "write") !== "read") {
1004
1012
  rs.counters.wroteThisRun = true;
1005
1013
  }
@@ -1051,16 +1059,7 @@ function makeHarnessHandlers(prepared, stats, rs, deps) {
1051
1059
  toolName: event.toolName,
1052
1060
  ...(toolLabels.get(event.toolName) !== undefined ? { label: toolLabels.get(event.toolName) } : {}),
1053
1061
  isError: event.isError,
1054
- ...(() => {
1055
- const o = toolOutputFrom(event.result);
1056
- return o !== undefined
1057
- ? { output: o.output, ...(o.truncated ? { truncated: true } : {}), ...(o.totalChars !== undefined ? { totalChars: o.totalChars } : {}) }
1058
- : {};
1059
- })(),
1060
- ...(() => {
1061
- const st = structuredFrom(event.result);
1062
- return st !== undefined ? { structured: st } : {};
1063
- })(),
1062
+ ...toolEndBodyFrom(event.result, event.isError),
1064
1063
  ...ident(),
1065
1064
  });
1066
1065
  announceWorkspaceMove();
@@ -1119,21 +1118,17 @@ function makeHarnessHandlers(prepared, stats, rs, deps) {
1119
1118
  }
1120
1119
  else {
1121
1120
  if (rs.degrade.degraded === undefined &&
1122
- spec.degrade &&
1121
+ spec.limits?.degrade &&
1123
1122
  rs.degrade.degradeToModel &&
1124
1123
  rs.budget.maxCostMicroUsd !== undefined &&
1125
- stats.costMicroUsd >= spec.degrade.atCostFraction * rs.budget.maxCostMicroUsd) {
1124
+ stats.costMicroUsd >= spec.limits.degrade.atCostFraction * rs.budget.maxCostMicroUsd) {
1126
1125
  void prepared.harness.setModel(rs.degrade.degradeToModel);
1127
1126
  rs.degrade.recordDegraded({ from: prepared.model.id, to: rs.degrade.degradeToModel.id, reason: "budget", atTurn: stats.turns }, rs.degrade.degradeToModel);
1128
- if (prepared.callCapRef &&
1129
- spec.limits?.maxOutputTokens === undefined &&
1130
- rs.degrade.degradeToModel.maxTokens !== undefined &&
1131
- (prepared.callCapRef.staticCapTokens === undefined || rs.degrade.degradeToModel.maxTokens < prepared.callCapRef.staticCapTokens)) {
1132
- prepared.callCapRef.staticCapTokens = rs.degrade.degradeToModel.maxTokens;
1133
- }
1134
1127
  }
1135
- if (!rs.limits.budgetHit && rs.budget.overBudget()) {
1128
+ const overAxis = rs.limits.budgetHit ? undefined : rs.budget.overBudget();
1129
+ if (overAxis !== undefined) {
1136
1130
  rs.limits.budgetHit = "exceeded";
1131
+ rs.limits.budgetAxis = overAxis;
1137
1132
  if (prepared.suspendForResource === undefined) {
1138
1133
  void prepared.harness.abort();
1139
1134
  prepared.abortController.abort();
@@ -1702,9 +1697,9 @@ export class Runner {
1702
1697
  rs.telemetry.cacheFamily = cacheFamilyOf(prepared.model);
1703
1698
  rs.telemetry.pricing = this.deps.pricing?.[prepared.model.id] ?? modelCostToPricing(prepared.model.cost);
1704
1699
  rs.telemetry.pricingConfigured = this.deps.pricing?.[prepared.model.id] !== undefined || prepared.model.cost !== undefined;
1705
- if (spec.degrade) {
1700
+ if (spec.limits?.degrade) {
1706
1701
  try {
1707
- rs.degrade.degradeToModel = resolveModel(spec.degrade.to, this.deps.models);
1702
+ rs.degrade.degradeToModel = resolveModel(spec.limits.degrade.to, this.deps.models);
1708
1703
  }
1709
1704
  catch (e) {
1710
1705
  this.deps.onError?.(e, { phase: "degraded", sessionId: prepared.sessionId });
@@ -1776,7 +1771,7 @@ export class Runner {
1776
1771
  catch {
1777
1772
  }
1778
1773
  };
1779
- const sliceWindowMicroUsd = spec.maxCostUsd !== undefined ? Math.round(spec.maxCostUsd * 1e6) : undefined;
1774
+ const sliceWindowMicroUsd = spec.limits?.maxCostUsd !== undefined ? Math.round(spec.limits.maxCostUsd * 1e6) : undefined;
1780
1775
  rs.budget.remainingMicroUsd = prepared.resourceLedger
1781
1776
  ? remainingBudgetMicroUsd(prepared.resourceLedger)
1782
1777
  : spec.resourceSuspend?.totalBudgetUsd !== undefined
@@ -1786,24 +1781,36 @@ export class Runner {
1786
1781
  sliceWindowMicroUsd !== undefined && rs.budget.remainingMicroUsd !== undefined
1787
1782
  ? Math.min(sliceWindowMicroUsd, rs.budget.remainingMicroUsd)
1788
1783
  : (sliceWindowMicroUsd ?? rs.budget.remainingMicroUsd);
1789
- rs.budget.overBudget = () => (rs.budget.maxCostMicroUsd !== undefined && stats.costMicroUsd > rs.budget.maxCostMicroUsd) ||
1790
- (spec.maxTokens !== undefined && stats.tokens > spec.maxTokens);
1784
+ rs.budget.remainingTokens = prepared.resourceLedger
1785
+ ? remainingTokens(prepared.resourceLedger)
1786
+ : spec.resourceSuspend?.totalTokens;
1787
+ rs.budget.maxTokensWindow =
1788
+ spec.limits?.maxTokens !== undefined && rs.budget.remainingTokens !== undefined
1789
+ ? Math.min(spec.limits.maxTokens, rs.budget.remainingTokens)
1790
+ : (spec.limits?.maxTokens ?? rs.budget.remainingTokens);
1791
+ rs.budget.overBudget = () => {
1792
+ if (rs.budget.maxTokensWindow !== undefined && stats.tokens > rs.budget.maxTokensWindow)
1793
+ return "tokens";
1794
+ if (rs.budget.maxCostMicroUsd !== undefined && stats.costMicroUsd > rs.budget.maxCostMicroUsd)
1795
+ return "cost";
1796
+ return undefined;
1797
+ };
1791
1798
  rs.budget.streamCancel =
1792
- (spec.budgetStreamCancel ?? rs.budget.maxCostMicroUsd !== undefined) && prepared.suspendForResource === undefined;
1799
+ (spec.limits?.budgetStreamCancel ?? rs.budget.maxCostMicroUsd !== undefined) && prepared.suspendForResource === undefined;
1793
1800
  rs.budget.callOutputChars = 0;
1794
1801
  rs.budget.lastStreamBudgetCheck = 0;
1795
1802
  rs.budget.projectedOverBudget = () => {
1796
1803
  const estOut = Math.ceil(rs.budget.callOutputChars / 4);
1804
+ if (rs.budget.maxTokensWindow !== undefined && stats.tokens + estOut > rs.budget.maxTokensWindow)
1805
+ return "tokens";
1797
1806
  if (rs.budget.maxCostMicroUsd !== undefined) {
1798
1807
  const estOutMicro = computeCostMicroUsd({ totalInputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0, cacheWriteTokensLong: 0, outputTokens: estOut }, rs.telemetry.pricing);
1799
1808
  if (stats.costMicroUsd + estOutMicro > rs.budget.maxCostMicroUsd)
1800
- return true;
1809
+ return "cost";
1801
1810
  }
1802
- return spec.maxTokens !== undefined && stats.tokens + estOut > spec.maxTokens;
1811
+ return undefined;
1803
1812
  };
1804
1813
  rs.turn.turnUsageMissing = false;
1805
- rs.counters.nudgesSent = 0;
1806
- rs.counters.callCutoffs = 0;
1807
1814
  rs.counters.repetitionCuts = 0;
1808
1815
  rs.counters.repetitionSpared = 0;
1809
1816
  rs.counters.repetitionEvents = [];
@@ -1999,7 +2006,6 @@ export class Runner {
1999
2006
  }
2000
2007
  const writeFamilyOf = (name) => writeFamilyByName.get(name) ?? writeFamilyOfCanonical(name);
2001
2008
  const toolStartAt = new Map();
2002
- const turnToolSpan = {};
2003
2009
  const startedToolCallIds = new Set();
2004
2010
  emitTrace(rs.telemetry.tracer, () => ({ kind: "task.start", version: 1, taskId: rs.telemetry.taskId, model: prepared.model.id, engineVersion: engineVersion(), ts: rs.telemetry.taskStart }));
2005
2011
  emitTrace(rs.telemetry.tracer, () => ({
@@ -2054,61 +2060,9 @@ export class Runner {
2054
2060
  };
2055
2061
  });
2056
2062
  }
2057
- const sliceWindowWalltimeMs = spec.limits?.timeoutSec ? spec.limits.timeoutSec * 1000 : undefined;
2058
- const remainingWalltimeBudgetMs = prepared.resourceLedger?.totalWalltimeSec !== undefined
2059
- ? remainingWalltimeMs(prepared.resourceLedger)
2060
- : spec.resourceSuspend?.totalWalltimeSec !== undefined
2061
- ? Math.max(0, spec.resourceSuspend.totalWalltimeSec * 1000 - (prepared.resourceLedger?.spentWalltimeMs ?? 0))
2062
- : undefined;
2063
- const effectiveTimeoutMs = sliceWindowWalltimeMs !== undefined && remainingWalltimeBudgetMs !== undefined
2064
- ? Math.min(sliceWindowWalltimeMs, remainingWalltimeBudgetMs)
2065
- : (sliceWindowWalltimeMs ?? remainingWalltimeBudgetMs);
2066
- const walltimeDeadlineMs = effectiveTimeoutMs !== undefined ? rs.telemetry.taskStart + effectiveTimeoutMs : undefined;
2063
+ const effectiveTimeoutMs = spec.limits?.maxWalltimeMs;
2067
2064
  const walltimeMonotonicDeadline = effectiveTimeoutMs !== undefined ? rs.telemetry.taskStartMonotonic + effectiveTimeoutMs : undefined;
2068
- if (prepared.callCapRef && prepared.suspendForResource === undefined && walltimeDeadlineMs !== undefined) {
2069
- prepared.callCapRef.deadlineMs = walltimeDeadlineMs;
2070
- if (spec.limits?.gracefulFinalize !== false) {
2071
- prepared.callCapRef.cushionMs = writeoutCushionMs(effectiveTimeoutMs);
2072
- prepared.callCapRef.budgetMs = effectiveTimeoutMs;
2073
- }
2074
- }
2075
- rs.counters.finalizeInjected = false;
2076
2065
  rs.counters.walltimeSyncBackstopFired = false;
2077
- const nudgeSchedule = [];
2078
- {
2079
- const conf = spec.limits?.deadlineNudge;
2080
- const budgetSec = effectiveTimeoutMs !== undefined ? effectiveTimeoutMs / 1000 : undefined;
2081
- if (conf !== false && budgetSec && budgetSec > 0) {
2082
- const [a, b] = conf?.at ?? [0.8, 0.95];
2083
- if (a > 0 && a < 1) {
2084
- const t1 = rs.telemetry.taskStart + a * budgetSec * 1000;
2085
- const remainMin = Math.max(1, Math.round((budgetSec * 1000 * (1 - a)) / 60_000));
2086
- const budgetMin = Math.max(1, Math.round(budgetSec / 60));
2087
- nudgeSchedule.push({
2088
- atMs: t1,
2089
- text: `<system-reminder>[deadline] ~${remainMin} min of the ~${budgetMin}-min wall-clock budget remain. ` +
2090
- `Start converging: persist finished work (write files / commit), prefer completing in-flight items over opening new ones.</system-reminder>`,
2091
- });
2092
- const t2 = rs.telemetry.taskStart + Math.max(Math.min(b * budgetSec * 1000, budgetSec * 1000 - 120_000), a * budgetSec * 1000 + 1);
2093
- if (b > a && b < 1) {
2094
- nudgeSchedule.push({
2095
- atMs: t2,
2096
- text: `<system-reminder>[deadline] the wall-clock budget is nearly exhausted. STOP new work — ` +
2097
- `write out deliverables and give your final answer NOW; an unfinished answer beats a hard timeout.</system-reminder>`,
2098
- });
2099
- }
2100
- }
2101
- }
2102
- }
2103
- rs.counters.nudgeIdx = 0;
2104
- const buildFinalizeText = () => `<system-reminder>[deadline] the wall-clock budget is exhausted — this is the FINAL turn. ` +
2105
- `Write your best current work to the deliverable path NOW, even if incomplete — persist to disk FIRST; everything else comes after. ` +
2106
- `If a better version exists anywhere else (a draft, scratch or temp file), copy it to the deliverable path now. ` +
2107
- `Do NOT open new work, new investigations, or long commands; write out and stop.` +
2108
- (finalVerificationOn
2109
- ? ` After writing, if seconds permit, confirm the deliverable file actually EXISTS at the required path with your latest content.`
2110
- : "") +
2111
- `</system-reminder>`;
2112
2066
  const timeout = startTimeout(prepared.harness, prepared.abortController, walltimeMonotonicDeadline !== undefined ? walltimeMonotonicDeadline - performance.now() : undefined, prepared.suspendForResource !== undefined);
2113
2067
  const parentToolCallId = internals?.parentToolCallId;
2114
2068
  const ident = () => parentToolCallId !== undefined ? { eventId: uuidv7(), parentToolCallId, sourceTaskId: rs.telemetry.taskId } : { eventId: uuidv7() };
@@ -2164,8 +2118,8 @@ export class Runner {
2164
2118
  const batchArgs = postToolBatchHook ? new Map() : undefined;
2165
2119
  rs.turn.toolBatch = [];
2166
2120
  const { onMessageUpdate, onMessageEnd, onToolStart, onToolEnd, onTurnEnd, announceWorkspaceMove } = makeHarnessHandlers(prepared, stats, rs, {
2167
- spec, queue, internals, buildFinalizeText, ident, nudgeSchedule, parentToolCallId, subagentName,
2168
- pushContent, emitCommitted, startedToolCallIds, toolStartAt, turnToolSpan, writeFamilyOf,
2121
+ spec, queue, internals, ident, parentToolCallId, subagentName,
2122
+ pushContent, emitCommitted, startedToolCallIds, toolStartAt, writeFamilyOf,
2169
2123
  toolLabels, postToolBatchHook, batchArgs,
2170
2124
  runnerHooks: { onError: (e, info) => this.deps.onError?.(e, info) },
2171
2125
  });
@@ -2206,7 +2160,6 @@ export class Runner {
2206
2160
  };
2207
2161
  const withinTaskCompaction = (spec.compaction?.enabled ?? true) && (spec.compaction?.withinTask ?? true);
2208
2162
  const compactionBreaker = { failures: 0 };
2209
- const compactionThroughputRef = {};
2210
2163
  const windowSafetyOptions = (mainModel) => ({
2211
2164
  ...(rs.budget.maxCostMicroUsd !== undefined
2212
2165
  ? {
@@ -2217,15 +2170,6 @@ export class Runner {
2217
2170
  },
2218
2171
  }
2219
2172
  : {}),
2220
- ...(walltimeDeadlineMs !== undefined && prepared.callCapRef !== undefined
2221
- ? {
2222
- walltime: {
2223
- deadlineMs: walltimeDeadlineMs,
2224
- cushionMs: () => effectiveCushionMs(prepared.callCapRef) ?? 0,
2225
- throughputRef: compactionThroughputRef,
2226
- },
2227
- }
2228
- : {}),
2229
2173
  onWindowSafety: (info) => {
2230
2174
  if (info.kind === "fallback") {
2231
2175
  emitTrace(rs.telemetry.tracer, () => ({
@@ -2263,7 +2207,6 @@ export class Runner {
2263
2207
  (rs.counters.finalVerifyInjections === 0 || (rs.counters.finalVerifyInjections === 1 && rs.counters.groundingSignalPreR9 && !rs.counters.groundingSignalPostR9)) &&
2264
2208
  rs.counters.wroteThisRun &&
2265
2209
  prepared.outputRef.set !== true &&
2266
- !rs.counters.finalizeInjected &&
2267
2210
  !(rs.limits.effectiveMaxTurns !== undefined && rs.limits.effectiveMaxTurns > 0 && stats.turns >= rs.limits.effectiveMaxTurns - 1)) {
2268
2211
  rs.counters.finalVerifyInjections += 1;
2269
2212
  if (rs.counters.finalVerifyInjections === 2) {
@@ -2371,18 +2314,6 @@ export class Runner {
2371
2314
  malformedToolUse: {},
2372
2315
  thinkingOnly: {},
2373
2316
  degenerateOutput: { detect: (m) => isDegenerateCutMessage(m) },
2374
- walltimeCutoff: {
2375
- detect: (m) => isWalltimeCutMessage(m),
2376
- hasLiveWriteoutWindow: () => {
2377
- const ref = prepared.callCapRef;
2378
- if (ref === undefined)
2379
- return true;
2380
- const soft = softExecDeadlineMs(ref);
2381
- if (soft === undefined)
2382
- return true;
2383
- return soft - Date.now() >= minLiveWriteoutWindowMs(ref.state);
2384
- },
2385
- },
2386
2317
  promptTooLong: {
2387
2318
  recover: async () => {
2388
2319
  if (!(spec.compaction?.enabled ?? true))
@@ -2456,8 +2387,7 @@ export class Runner {
2456
2387
  return comp.compacted === true;
2457
2388
  }
2458
2389
  catch (err) {
2459
- if (!isCompactionWalltimeAbort(err))
2460
- compactionBreaker.failures += 1;
2390
+ compactionBreaker.failures += 1;
2461
2391
  const msg = String(err instanceof Error ? err.message : err);
2462
2392
  queue.push({ type: "compaction_outcome", outcome: "failed", trigger: "forced", reason: msg.length > 512 ? `${msg.slice(0, 512)}…` : msg, ...ident() });
2463
2393
  return false;
@@ -2472,8 +2402,8 @@ export class Runner {
2472
2402
  w.resolve(outcome);
2473
2403
  };
2474
2404
  const onTurnBoundary = makeTurnBoundary(prepared, stats, rs, {
2475
- spec, queue, manualCompactRef, todoToolMounted, taskToolsMounted, turnToolSpan,
2476
- walltimeDeadlineMs, walltimeMonotonicDeadline, nudgeSchedule, buildFinalizeText,
2405
+ spec, queue, manualCompactRef, todoToolMounted, taskToolsMounted,
2406
+ walltimeMonotonicDeadline,
2477
2407
  timeout, ident, postToolBatchHook, batchArgs, compactionBrain, withinTaskCompaction,
2478
2408
  compactionBreaker, windowSafetyOptions, rapidRefill, drainManualCompact,
2479
2409
  runnerHooks: {
@@ -2517,7 +2447,7 @@ export class Runner {
2517
2447
  if (resume) {
2518
2448
  const walltimeExhaustedResume = effectiveTimeoutMs !== undefined && effectiveTimeoutMs <= 0;
2519
2449
  if (!walltimeExhaustedResume && resume.outcome.gate !== "wake") {
2520
- await this.applyResumeDecision(prepared, resume, (e) => queue.push(e), emitCommitted, (toolName, details) => {
2450
+ await this.applyResumeDecision(prepared, resume, (e) => pushContent({ ...e, ...ident() }), emitCommitted, (toolName, details) => {
2521
2451
  if (rs.attach.attachState === undefined)
2522
2452
  return;
2523
2453
  const family = writeFamilyOf(toolName);
@@ -2541,8 +2471,13 @@ export class Runner {
2541
2471
  void prepared.harness.abort();
2542
2472
  prepared.abortController.abort();
2543
2473
  }
2474
+ else if (rs.budget.maxTokensWindow !== undefined && rs.budget.maxTokensWindow <= 0) {
2475
+ rs.limits.budgetHit = "exceeded";
2476
+ rs.limits.budgetAxis = "tokens";
2477
+ }
2544
2478
  else if (rs.budget.maxCostMicroUsd !== undefined && rs.budget.maxCostMicroUsd <= 0) {
2545
2479
  rs.limits.budgetHit = "exceeded";
2480
+ rs.limits.budgetAxis = "cost";
2546
2481
  }
2547
2482
  else {
2548
2483
  let continuation = resumeContinuation(resume);
@@ -2641,13 +2576,30 @@ export class Runner {
2641
2576
  cacheReadTokens: 0,
2642
2577
  cacheWriteTokens: 0,
2643
2578
  cacheWriteTokensLong: 0,
2644
- outputTokens: prepared.model.maxTokens ?? 4096,
2579
+ outputTokens: prepared.model.maxTokens ?? DEFAULT_PRECALL_OUTPUT_TOKENS,
2645
2580
  }, rs.telemetry.pricing);
2646
2581
  const precallCeilingMicroUsd = prepared.suspendForResource !== undefined ? rs.budget.remainingMicroUsd : rs.budget.maxCostMicroUsd;
2582
+ const precallTokens = Math.ceil(effectiveObjective.length / 4) + (prepared.model.maxTokens ?? DEFAULT_PRECALL_OUTPUT_TOKENS);
2583
+ const precallCeilingTokens = prepared.suspendForResource !== undefined ? rs.budget.remainingTokens : rs.budget.maxTokensWindow;
2584
+ const entryUsageRetryAfterMs = prepared.usageGovernance !== undefined ? await prepared.usageGovernance.check(Date.now()) : undefined;
2647
2585
  if (promptBlocked) {
2648
2586
  }
2587
+ else if (entryUsageRetryAfterMs !== undefined) {
2588
+ const terminal = platformLimitTerminal("usage_window", entryUsageRetryAfterMs, "entry");
2589
+ rs.limits.platformTerminal = terminal;
2590
+ try {
2591
+ this.deps.onError?.(terminal, { phase: "config", sessionId: prepared.sessionId });
2592
+ }
2593
+ catch {
2594
+ }
2595
+ }
2596
+ else if (precallCeilingTokens !== undefined && precallTokens > precallCeilingTokens) {
2597
+ rs.limits.budgetHit = "precall";
2598
+ rs.limits.budgetAxis = "tokens";
2599
+ }
2649
2600
  else if (precallCeilingMicroUsd !== undefined && precallMicroUsd > precallCeilingMicroUsd) {
2650
2601
  rs.limits.budgetHit = "precall";
2602
+ rs.limits.budgetAxis = "cost";
2651
2603
  }
2652
2604
  else if (effectiveTimeoutMs !== undefined && effectiveTimeoutMs <= 0) {
2653
2605
  timeout.fired = true;
@@ -2687,6 +2639,7 @@ export class Runner {
2687
2639
  }
2688
2640
  finally {
2689
2641
  timeout.clear();
2642
+ prepared.lspDiagnostics?.registry.releaseRun(prepared.lspDiagnostics.runIdent);
2690
2643
  prepared.abortController.abort();
2691
2644
  prepared.releaseSignal();
2692
2645
  try {
@@ -2702,6 +2655,17 @@ export class Runner {
2702
2655
  if (threw === undefined && prepared.brainCallGuardrailRef.timedOut !== undefined) {
2703
2656
  threw = prepared.brainCallGuardrailRef.timedOut;
2704
2657
  }
2658
+ if (rs.limits.platformTerminal !== undefined) {
2659
+ threw = rs.limits.platformTerminal;
2660
+ }
2661
+ if (prepared.usageGovernance !== undefined) {
2662
+ try {
2663
+ await prepared.usageGovernance.commit(stats.tokens, Date.now());
2664
+ }
2665
+ catch (flushErr) {
2666
+ this.deps.onError?.(flushErr, { phase: "config", sessionId: prepared.sessionId });
2667
+ }
2668
+ }
2705
2669
  const suspended = prepared.suspendRef.token !== undefined && prepared.suspendRef.gate !== undefined;
2706
2670
  const reviewPaused = prepared.reviewRef.token !== undefined && prepared.reviewRef.gate !== undefined;
2707
2671
  const durablyPaused = suspended || reviewPaused;
@@ -2800,24 +2764,12 @@ export class Runner {
2800
2764
  gates: [...prepared.humanReviewRef.gates],
2801
2765
  };
2802
2766
  }
2803
- const capShrinks = prepared.callCapRef?.shrinks ?? 0;
2804
- const toolClamps = prepared.callCapRef?.toolClamps ?? 0;
2805
- const toolCuts = prepared.callCapRef?.toolCuts ?? 0;
2806
- const pendingCutTools = prepared.callCapRef?.pendingCutTools ?? 0;
2807
- const toolCutKills = prepared.cutKills?.kills ?? 0;
2808
- if (rs.counters.finalizeInjected || rs.counters.nudgesSent > 0 || capShrinks > 0 || rs.counters.callCutoffs > 0 || toolClamps > 0 || toolCuts > 0 || toolCutKills > 0 || pendingCutTools > 0 || rs.counters.finalVerifyInjections > 0 || rs.attach.attachmentsInjected > 0 || rs.counters.repetitionCuts > 0 || rs.counters.repetitionSpared > 0) {
2767
+ if (rs.counters.finalVerifyInjections > 0 || rs.attach.attachmentsInjected > 0 || rs.counters.repetitionCuts > 0 || rs.counters.repetitionSpared > 0 || rs.counters.approachNoticesSent > 0) {
2809
2768
  stats.mechanisms = {
2810
- ...(rs.counters.finalizeInjected ? { finalizeInjected: true } : {}),
2811
- ...(rs.counters.nudgesSent > 0 ? { nudgesSent: rs.counters.nudgesSent } : {}),
2812
- ...(capShrinks > 0 ? { capShrinks } : {}),
2813
- ...(rs.counters.callCutoffs > 0 ? { callCutoffs: rs.counters.callCutoffs } : {}),
2814
- ...(toolClamps > 0 ? { toolClamps } : {}),
2815
- ...(toolCuts > 0 ? { toolCuts } : {}),
2816
- ...(toolCutKills > 0 ? { toolCutKills } : {}),
2817
- ...(pendingCutTools > 0 ? { pendingCutTools } : {}),
2818
2769
  ...(rs.counters.finalVerifyInjections > 0 ? { finalVerifyInjected: true } : {}),
2819
2770
  ...(rs.counters.finalVerifyInjections > 0 ? { finalVerifyInjections: rs.counters.finalVerifyInjections } : {}),
2820
2771
  ...(rs.attach.attachmentsInjected > 0 ? { attachmentsInjected: rs.attach.attachmentsInjected } : {}),
2772
+ ...(rs.counters.approachNoticesSent > 0 ? { approachNoticesSent: rs.counters.approachNoticesSent } : {}),
2821
2773
  ...(rs.counters.repetitionCuts > 0 ? { repetitionCuts: rs.counters.repetitionCuts } : {}),
2822
2774
  ...(rs.counters.repetitionSpared > 0 ? { repetitionSpared: rs.counters.repetitionSpared } : {}),
2823
2775
  ...(rs.counters.repetitionEvents.length > 0 ? { repetitionEvents: rs.counters.repetitionEvents } : {}),
@@ -2829,10 +2781,12 @@ export class Runner {
2829
2781
  unpricedSpend: rs.telemetry.unpricedSpend,
2830
2782
  rewindNotes: prepared.rewindNotes,
2831
2783
  remoteEnvFailures: prepared.remoteEnvFailures,
2784
+ retryAfterMs: rs.limits.platformTerminal?.retryAfterMs,
2832
2785
  abortedForTimeout: timeout.fired,
2833
2786
  abortedForTurns: rs.limits.turnsExceeded,
2834
2787
  abortedLive,
2835
2788
  budgetHit: rs.limits.budgetHit,
2789
+ budgetAxis: rs.limits.budgetAxis,
2836
2790
  blockedReason: prepared.blockedRef.reason,
2837
2791
  conflict: prepared.conflictRef.hit,
2838
2792
  outputInvalid: rs.degrade.outputInvalid,
@@ -2891,7 +2845,6 @@ export class Runner {
2891
2845
  result.status === "failed" &&
2892
2846
  final?.stopReason === "error" &&
2893
2847
  !isDegenerateCutMessage(final) &&
2894
- !isWalltimeCutMessage(final) &&
2895
2848
  threw === undefined &&
2896
2849
  rs.limits.budgetHit === undefined &&
2897
2850
  !rs.degrade.outputInvalid &&
@@ -2959,7 +2912,7 @@ export class Runner {
2959
2912
  ts: Date.now(),
2960
2913
  }));
2961
2914
  setResult(result);
2962
- if (parentToolCallId !== undefined && (result.status === "completed" || result.status === "failed" || result.status === "timeout" || result.status === "blocked")) {
2915
+ if (parentToolCallId !== undefined && (result.status === "completed" || result.status === "failed" || result.status === "blocked")) {
2963
2916
  const terminalTick = {
2964
2917
  type: "task_progress",
2965
2918
  taskId: rs.telemetry.taskId,
@@ -3298,6 +3251,12 @@ export class Runner {
3298
3251
  if (checkpointVersionOf(cp) > MAX_SUPPORTED_CHECKPOINT_VERSION) {
3299
3252
  throw new CheckpointError("checkpoint.unsupported_version", `checkpoint version ${checkpointVersionOf(cp)} is newer than this worker supports (max ${MAX_SUPPORTED_CHECKPOINT_VERSION})`);
3300
3253
  }
3254
+ const retiredWalltimeTotal = cp.resourceLedger?.totalWalltimeSec;
3255
+ if (retiredWalltimeTotal !== undefined) {
3256
+ throw new CheckpointError("checkpoint.walltime_axis_retired", "this checkpoint's ledger carries a cross-slice WALL-CLOCK allocation (resourceLedger.totalWalltimeSec), an axis this engine version retired — " +
3257
+ "resuming here would run the leg with that ceiling unenforced. Rejected pre-CAS (the checkpoint stays pending): finish it on a worker of the " +
3258
+ "previous release, or start a fresh task bounded by resourceSuspend.totalTokens / totalBudgetUsd.");
3259
+ }
3301
3260
  if (taskConfig.signal?.aborted === true && cp.status === "pending") {
3302
3261
  throw new CheckpointError("checkpoint.resume_aborted", "the resume was handed an ALREADY-ABORTED signal — refusing to consume the approval on a leg that cannot run it (the checkpoint stays pending and is resumable with a live signal)");
3303
3262
  }
@@ -3410,7 +3369,7 @@ export class Runner {
3410
3369
  }
3411
3370
  else if (!completed.has(id)) {
3412
3371
  const name = names.get(id) ?? "unknown";
3413
- emit({ type: "tool_end", toolCallId: id, toolName: name, ...((() => { const l = prepared.tools.find((t) => t.name === name)?.label; return l !== undefined && l !== name ? { label: l } : {}; })()), isError: true });
3372
+ emit({ type: "tool_end", toolCallId: id, toolName: name, ...((() => { const l = prepared.tools.find((t) => t.name === name)?.label; return l !== undefined && l !== name ? { label: l } : {}; })()), isError: true, ...toolEndBodyFrom({ content: formatHookFeedback(DEFERRED_REISSUE) }, true) });
3414
3373
  const eid = await prepared.session.appendMessage(toolResultMsg(id, name, formatHookFeedback(DEFERRED_REISSUE), true));
3415
3374
  emitCommitted(eid, "toolResult", id);
3416
3375
  }
@@ -3424,10 +3383,10 @@ export class Runner {
3424
3383
  const resolvedArgs = pendingAction.toolName === ASK_USER_QUESTION_TOOL_NAME ? pendingAction.args : (outcome.updatedInput ?? pendingAction.args);
3425
3384
  const pendingLabel = (() => { const l = prepared.tools.find((t) => t.name === pendingAction.toolName)?.label; return l !== undefined && l !== pendingAction.toolName ? { label: l } : {}; })();
3426
3385
  emit({ type: "tool_start", toolCallId: pendingAction.toolCallId, toolName: pendingAction.toolName, ...pendingLabel, args: resolvedArgs });
3427
- const emitEnd = (isError) => emit({ type: "tool_end", toolCallId: pendingAction.toolCallId, toolName: pendingAction.toolName, ...pendingLabel, isError });
3386
+ const emitEnd = (isError, result) => emit({ type: "tool_end", toolCallId: pendingAction.toolCallId, toolName: pendingAction.toolName, ...pendingLabel, isError, ...toolEndBodyFrom(result, isError) });
3428
3387
  if (outcome.decision === "deny") {
3429
- emitEnd(true);
3430
3388
  const reason = outcome.reason ? delimitUntrusted("reviewer note", outcome.reason) : `The pending tool call "${pendingAction.toolName}" was denied by an approver.`;
3389
+ emitEnd(true, { content: formatHookFeedback(reason) });
3431
3390
  const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, formatHookFeedback(reason), true));
3432
3391
  emitCommitted(eid, "toolResult", pendingAction.toolCallId);
3433
3392
  return;
@@ -3435,8 +3394,9 @@ export class Runner {
3435
3394
  if (outcome.decision === "allow" && outcome.updatedInput !== undefined && prepared.basePolicyForResumeEdit) {
3436
3395
  const rechecked = refuseOutOfContractDecision(await prepared.basePolicyForResumeEdit.check({ toolName: pendingAction.toolName, args: resolvedArgs, toolCallId: pendingAction.toolCallId }, prepared.abortController.signal));
3437
3396
  if (rechecked.action === "deny") {
3438
- emitEnd(true);
3439
- const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, formatHookFeedback(`The approver EDITED this call's input; the edited call is denied by the deployment's tool policy and was not executed${rechecked.message ? `: ${rechecked.message}` : ""}.`), true));
3397
+ const editedDenial = formatHookFeedback(`The approver EDITED this call's input; the edited call is denied by the deployment's tool policy and was not executed${rechecked.message ? `: ${rechecked.message}` : ""}.`);
3398
+ emitEnd(true, { content: editedDenial });
3399
+ const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, editedDenial, true));
3440
3400
  emitCommitted(eid, "toolResult", pendingAction.toolCallId);
3441
3401
  return;
3442
3402
  }
@@ -3444,8 +3404,9 @@ export class Runner {
3444
3404
  if (prepared.denyNarrowingPolicy) {
3445
3405
  const narrowed = refuseOutOfContractDecision(await prepared.denyNarrowingPolicy.check({ toolName: pendingAction.toolName, args: resolvedArgs, toolCallId: pendingAction.toolCallId }, prepared.abortController.signal));
3446
3406
  if (narrowed.action === "deny") {
3447
- emitEnd(true);
3448
- const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, formatHookFeedback(`The approved tool call "${pendingAction.toolName}" is now denied by a session rule and was not executed${narrowed.message ? `: ${narrowed.message}` : ""}.`), true));
3407
+ const narrowedDenial = formatHookFeedback(`The approved tool call "${pendingAction.toolName}" is now denied by a session rule and was not executed${narrowed.message ? `: ${narrowed.message}` : ""}.`);
3408
+ emitEnd(true, { content: narrowedDenial });
3409
+ const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, narrowedDenial, true));
3449
3410
  emitCommitted(eid, "toolResult", pendingAction.toolCallId);
3450
3411
  return;
3451
3412
  }
@@ -3463,13 +3424,14 @@ export class Runner {
3463
3424
  res = await tool.execute(pendingAction.toolCallId, args, prepared.abortController.signal);
3464
3425
  }
3465
3426
  catch (err) {
3466
- emitEnd(true);
3467
- const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, `Error: ${err instanceof Error ? err.message : String(err)}`, true));
3427
+ const execError = `Error: ${err instanceof Error ? err.message : String(err)}`;
3428
+ emitEnd(true, { content: execError });
3429
+ const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, execError, true));
3468
3430
  emitCommitted(eid, "toolResult", pendingAction.toolCallId);
3469
3431
  return;
3470
3432
  }
3471
3433
  const executedIsError = res.isError === true;
3472
- emitEnd(executedIsError);
3434
+ emitEnd(executedIsError, res);
3473
3435
  onResolvedToolSuccess?.(pendingAction.toolName, executedIsError ? undefined : res.details);
3474
3436
  const eid = await prepared.session.appendMessage({
3475
3437
  role: "toolResult",