@sema-agent/core 2.0.1 → 2.1.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 (35) hide show
  1. package/dist/agents/observer.js +8 -3
  2. package/dist/agents/send-message-tool.js +112 -81
  3. package/dist/agents/subagent.d.ts +10 -4
  4. package/dist/agents/subagent.js +103 -53
  5. package/dist/core/memory-engine/dual-root.js +2 -0
  6. package/dist/core/memory-engine/engine.d.ts +4 -0
  7. package/dist/core/memory-engine/engine.js +6 -1
  8. package/dist/core/runner/prepare-memory.d.ts +4 -0
  9. package/dist/core/runner/prepare-memory.js +4 -1
  10. package/dist/core/runner/prepare-task.d.ts +8 -2
  11. package/dist/core/runner/prepare-task.js +39 -8
  12. package/dist/core/runner/runtask.js +910 -865
  13. package/dist/core/runner/turn-attachments.d.ts +15 -1
  14. package/dist/core/runner/turn-attachments.js +68 -9
  15. package/dist/core/tool-result-budget.js +2 -2
  16. package/dist/core/tool-result-store.d.ts +2 -0
  17. package/dist/core/tool-result-store.js +27 -2
  18. package/dist/core/types.d.ts +1 -1
  19. package/dist/core/workflow-journal-store.d.ts +13 -0
  20. package/dist/engine/session/import-validate.js +29 -0
  21. package/dist/orchestration/workflow.js +28 -1
  22. package/dist/prompt-assembly/event-registry.js +1 -1
  23. package/dist/stores/cc/task-list-store.js +3 -3
  24. package/dist/stores/file/memory-store.d.ts +3 -0
  25. package/dist/stores/file/memory-store.js +39 -12
  26. package/dist/stores/file/tool-result-store.js +16 -2
  27. package/dist/stores/file/workflow-journal-store.d.ts +17 -0
  28. package/dist/stores/file/workflow-journal-store.js +102 -2
  29. package/dist/tools/fs/bash-readonly-classifier.js +1 -1
  30. package/dist/tools/fs/fs-read.js +2 -2
  31. package/dist/tools/fs/safety.js +13 -6
  32. package/dist/tools/task-list.d.ts +1 -0
  33. package/dist/tools/task-list.js +13 -2
  34. package/dist/tools/web.js +36 -6
  35. package/package.json +1 -1
@@ -48,9 +48,9 @@ function createRunState() {
48
48
  degrade: { degradeToModel: undefined, degraded: undefined, outputErrorStreak: 0, outputInvalid: false, recordDegraded: () => { } },
49
49
  limits: { turnsExceeded: false, budgetHit: undefined, outputRetryCap: 0, effectiveMaxTurns: undefined },
50
50
  budget: { remainingMicroUsd: undefined, maxCostMicroUsd: undefined, overBudget: () => false, streamCancel: false, callOutputChars: 0, lastStreamBudgetCheck: 0, projectedOverBudget: () => false },
51
- turn: { callStartAt: undefined, firstTokenAt: undefined, turnUsage: undefined, turnUsageMissing: false, turnStopReason: undefined, lastTurnHadToolCalls: false },
52
- counters: { nudgesSent: 0, callCutoffs: 0, repetitionCuts: 0, repetitionSpared: 0, repetitionEvents: [], REPETITION_EVENTS_CAP: 0, preemptIgnoredReported: false, wroteThisRun: false, finalVerifyInjections: 0, groundingSignalPreR9: false, groundingSignalPostR9: false, cadenceTurns: 0 },
53
- attach: { attachmentsCfg: undefined, agentListingOn: false, skillsListingOn: false, attachState: undefined, dateState: undefined, instrProbe: undefined, instrState: undefined },
51
+ turn: { callStartAt: undefined, firstTokenAt: undefined, turnUsage: undefined, turnUsageMissing: false, turnStopReason: undefined, lastTurnHadToolCalls: false, toolBatch: [] },
52
+ 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 },
53
+ attach: { attachmentsCfg: undefined, agentListingOn: false, skillsListingOn: false, attachState: undefined, dateState: undefined, instrProbe: undefined, instrState: undefined, attachmentsInjected: 0 },
54
54
  };
55
55
  }
56
56
  const MAX_CONSECUTIVE_COMPACTION_FAILURES = 3;
@@ -217,6 +217,882 @@ function resumeContinuation(resume) {
217
217
  `conversation above. Do NOT restart the task or re-run any tool you already ran; continue from this ` +
218
218
  `exact point, building on the existing results, and finish the remaining work.`);
219
219
  }
220
+ function makeTurnBoundary(prepared, stats, rs, deps) {
221
+ const { spec, queue, manualCompactRef, todoToolMounted, taskToolsMounted, turnToolSpan, walltimeDeadlineMs, walltimeMonotonicDeadline, nudgeSchedule, buildFinalizeText, timeout, ident, postToolBatchHook, batchArgs, compactionBrain, withinTaskCompaction, compactionBreaker, windowSafetyOptions, rapidRefill, drainManualCompact, runnerHooks } = deps;
222
+ const onTurnBoundary = async (event) => {
223
+ if (prepared.callCapRef && turnToolSpan.startMin !== undefined && turnToolSpan.endMax !== undefined) {
224
+ recordToolCycleSample(prepared.callCapRef.state, turnToolSpan.endMax - turnToolSpan.startMin);
225
+ }
226
+ turnToolSpan.startMin = undefined;
227
+ turnToolSpan.endMax = undefined;
228
+ let boundarySteered = false;
229
+ if (prepared.suspendForResource === undefined) {
230
+ const nowMs = Date.now();
231
+ if (!rs.counters.finalizeInjected &&
232
+ spec.limits?.gracefulFinalize !== false &&
233
+ walltimeDeadlineMs !== undefined &&
234
+ prepared.callCapRef !== undefined) {
235
+ const remainingMs = walltimeDeadlineMs - nowMs;
236
+ if (remainingMs > 0 && remainingMs < estimateCycleMs(prepared.callCapRef.state) + (effectiveCushionMs(prepared.callCapRef) ?? 0)) {
237
+ rs.counters.finalizeInjected = true;
238
+ prepared.callCapRef.finalizeMode = true;
239
+ void prepared.harness.setThinkingLevel("off").catch(() => { });
240
+ prepared.turnSnapshot?.refreshRequestPolicy({ thinkingLevel: "off" });
241
+ rs.counters.nudgeIdx = nudgeSchedule.length;
242
+ const finalizeText = buildFinalizeText();
243
+ void prepared.harness.steer(finalizeText, { engineMinted: true }).catch(() => { });
244
+ boundarySteered = true;
245
+ queue.push({ type: "steering_injected", source: "finalize", preview: finalizeText.slice(0, 220), ...ident() });
246
+ }
247
+ }
248
+ let due;
249
+ while (rs.counters.nudgeIdx < nudgeSchedule.length && nowMs >= nudgeSchedule[rs.counters.nudgeIdx].atMs) {
250
+ due = nudgeSchedule[rs.counters.nudgeIdx++];
251
+ }
252
+ if (due) {
253
+ void prepared.harness.steer(due.text, { engineMinted: true }).catch(() => { });
254
+ rs.counters.nudgesSent += 1;
255
+ boundarySteered = true;
256
+ queue.push({ type: "steering_injected", source: "deadline_nudge", preview: due.text.slice(0, 220), ...ident() });
257
+ }
258
+ }
259
+ const walltimeHit = walltimeMonotonicDeadline !== undefined && performance.now() >= walltimeMonotonicDeadline;
260
+ const preemptHit = spec.preemptSignal?.aborted === true;
261
+ if (preemptHit && !prepared.suspendForResource && !rs.counters.preemptIgnoredReported) {
262
+ rs.counters.preemptIgnoredReported = true;
263
+ emitTrace(rs.telemetry.tracer, () => ({ kind: "preempt.ignored", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() }));
264
+ }
265
+ if (prepared.suspendForResource &&
266
+ (rs.limits.turnsExceeded || rs.limits.budgetHit !== undefined || walltimeHit || preemptHit) &&
267
+ prepared.suspendRef.token === undefined) {
268
+ const reason = preemptHit
269
+ ? "preempt"
270
+ : rs.limits.budgetHit !== undefined
271
+ ? "budget"
272
+ : walltimeHit
273
+ ? "walltime"
274
+ : "turns";
275
+ const sliceSpend = { costMicroUsd: stats.costMicroUsd, tokens: stats.tokens, turns: stats.turns, walltimeMs: Math.round(performance.now() - rs.telemetry.taskStartMonotonic) };
276
+ if (await prepared.suspendForResource(reason, sliceSpend)) {
277
+ rs.limits.turnsExceeded = false;
278
+ rs.limits.budgetHit = undefined;
279
+ }
280
+ else {
281
+ void prepared.harness.abort();
282
+ prepared.abortController.abort();
283
+ }
284
+ return undefined;
285
+ }
286
+ if (walltimeMonotonicDeadline !== undefined &&
287
+ prepared.suspendForResource === undefined &&
288
+ performance.now() >= walltimeMonotonicDeadline) {
289
+ if (!timeout.fired) {
290
+ timeout.fired = true;
291
+ timeout.clear();
292
+ timeout.latenessMs = Math.max(timeout.latenessMs ?? 0, performance.now() - walltimeMonotonicDeadline);
293
+ rs.counters.walltimeSyncBackstopFired = true;
294
+ void prepared.harness.abort();
295
+ prepared.abortController.abort();
296
+ }
297
+ return undefined;
298
+ }
299
+ if (prepared.reviewRequestRef.pending !== undefined) {
300
+ const { reason } = prepared.reviewRequestRef.pending;
301
+ prepared.reviewRequestRef.pending = undefined;
302
+ const honorable = prepared.suspendForReview !== undefined &&
303
+ !prepared.abortController.signal.aborted &&
304
+ prepared.suspendRef.token === undefined &&
305
+ prepared.reviewRef.token === undefined;
306
+ if (honorable && prepared.suspendForReview) {
307
+ await prepared.suspendForReview(reason);
308
+ return undefined;
309
+ }
310
+ emitTrace(rs.telemetry.tracer, () => ({ kind: "review.dropped", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() }));
311
+ }
312
+ if (prepared.lspDiagnostics && !prepared.lspDiagnostics.registry.isEmpty() && !prepared.abortController.signal.aborted) {
313
+ const files = prepared.lspDiagnostics.registry.drain();
314
+ if (files.length > 0) {
315
+ queue.push({ type: "diagnostics", files, isNew: true, ...ident() });
316
+ const block = formatDiagnosticsBlock(files);
317
+ void prepared.harness.steer(block, { provenance: "engine-note" }).catch(() => undefined);
318
+ boundarySteered = true;
319
+ }
320
+ }
321
+ let boundaryAttachmentBytes = 0;
322
+ let attachmentsPayload;
323
+ if ((rs.attach.dateState !== undefined || rs.attach.instrState !== undefined || rs.attach.attachState !== undefined) &&
324
+ !boundarySteered &&
325
+ !rs.counters.finalizeInjected &&
326
+ rs.counters.finalVerifyInjections === 0 &&
327
+ !prepared.abortController.signal.aborted) {
328
+ const due = [];
329
+ if (rs.attach.attachState !== undefined) {
330
+ const changedFilesOn = Boolean(rs.attach.attachmentsCfg?.changedFiles);
331
+ let changed;
332
+ if (changedFilesOn && prepared.detectExternalChanges !== undefined) {
333
+ const maxFiles = typeof rs.attach.attachmentsCfg?.changedFiles === "object"
334
+ ? (rs.attach.attachmentsCfg.changedFiles.maxFiles ?? CHANGED_FILES_MAX)
335
+ : CHANGED_FILES_MAX;
336
+ const SCAN_CEILING_MS = 2_000;
337
+ const scan = await Promise.race([
338
+ prepared.detectExternalChanges(maxFiles),
339
+ new Promise((resolve) => {
340
+ const t = setTimeout(() => resolve({ changed: [], evicted: [] }), SCAN_CEILING_MS);
341
+ t.unref?.();
342
+ }),
343
+ ]);
344
+ for (const p of scan.evicted)
345
+ rs.attach.attachState.surfacedMtime.delete(p);
346
+ changed = scan.changed;
347
+ }
348
+ const backgroundTasksOn = rs.attach.attachmentsCfg?.backgroundTasks === true;
349
+ const bgTasks = backgroundTasksOn && rs.attach.attachState.postCompactPending ? prepared.listBackgroundTasks() : undefined;
350
+ const toolsDeltaOn = rs.attach.attachmentsCfg?.toolsDelta === true;
351
+ const tdRef = toolsDeltaOn ? prepared.toolsDeltaRef : undefined;
352
+ const pendingTools = tdRef !== undefined && tdRef.pending.length > 0 ? [...tdRef.pending] : undefined;
353
+ const pendingRemovedTools = tdRef !== undefined && tdRef.pendingRemoved.length > 0 ? [...tdRef.pendingRemoved] : undefined;
354
+ const pendingReaddedTools = tdRef !== undefined && tdRef.pendingReadded.length > 0 ? [...tdRef.pendingReadded] : undefined;
355
+ const pendingFailedMcp = tdRef !== undefined && tdRef.pendingFailed.length > 0 ? tdRef.pendingFailed.map((f) => ({ ...f })) : undefined;
356
+ const mcpToolsDelta = pendingRemovedTools !== undefined || pendingReaddedTools !== undefined || pendingFailedMcp !== undefined
357
+ ? {
358
+ ...(pendingRemovedTools !== undefined ? { removed: pendingRemovedTools } : {}),
359
+ ...(pendingReaddedTools !== undefined ? { readded: pendingReaddedTools } : {}),
360
+ ...(pendingFailedMcp !== undefined ? { failedServers: pendingFailedMcp } : {}),
361
+ }
362
+ : undefined;
363
+ const mcpInstructionsOn = rs.attach.attachmentsCfg?.mcpInstructions === true;
364
+ const mcpDelta = prepared.mcp.instructionsDelta;
365
+ const mcpAdds = mcpInstructionsOn && mcpDelta.pendingAdds.length > 0 ? [...mcpDelta.pendingAdds] : undefined;
366
+ const mcpRemovals = mcpInstructionsOn && mcpDelta.pendingRemovals.length > 0 ? [...mcpDelta.pendingRemovals] : undefined;
367
+ const mcpDropped = mcpInstructionsOn && prepared.mcp.droppedTools.length > 0 ? selectMcpDroppedBatch(prepared.mcp.droppedTools) : undefined;
368
+ const budgetUsdOn = rs.attach.attachmentsCfg?.budgetUsd === true && rs.budget.maxCostMicroUsd !== undefined && rs.turn.lastTurnHadToolCalls;
369
+ due.push(...collectDueAttachments(rs.attach.attachState, {
370
+ turn: stats.turns,
371
+ cadenceTurn: rs.counters.cadenceTurns,
372
+ todoMounted: todoToolMounted,
373
+ taskListMounted: taskToolsMounted,
374
+ planActive: prepared.planModeRef.active,
375
+ config: {
376
+ todoReminder: rs.attach.attachmentsCfg?.todoReminder === true,
377
+ ...(rs.attach.attachmentsCfg?.todoReminderMode !== undefined ? { todoReminderMode: rs.attach.attachmentsCfg.todoReminderMode } : {}),
378
+ changedFiles: changedFilesOn,
379
+ planModeReminder: rs.attach.attachmentsCfg?.planModeReminder === true,
380
+ budgetUsd: budgetUsdOn,
381
+ backgroundTasks: backgroundTasksOn,
382
+ toolsDelta: toolsDeltaOn,
383
+ agentListing: rs.attach.agentListingOn,
384
+ skillsListing: rs.attach.skillsListingOn,
385
+ mcpInstructions: mcpInstructionsOn,
386
+ },
387
+ ...(changed !== undefined ? { changedFiles: changed } : {}),
388
+ ...(budgetUsdOn && rs.budget.maxCostMicroUsd !== undefined
389
+ ? { budgetUsd: { used: stats.costMicroUsd / 1e6, total: rs.budget.maxCostMicroUsd / 1e6 } }
390
+ : {}),
391
+ ...(bgTasks !== undefined ? { backgroundTasks: bgTasks } : {}),
392
+ ...(pendingTools !== undefined ? { newTools: pendingTools } : {}),
393
+ ...(mcpToolsDelta !== undefined ? { mcpToolsDelta } : {}),
394
+ ...(rs.attach.agentListingOn && prepared.agentListing !== undefined
395
+ ? {
396
+ agentListing: prepared.agentListing.entries,
397
+ agentToolName: prepared.agentListing.toolName,
398
+ ...(prepared.agentListing.models !== undefined ? { agentModels: prepared.agentListing.models } : {}),
399
+ }
400
+ : {}),
401
+ ...(rs.attach.skillsListingOn && prepared.skillsListing !== undefined ? { skillsListing: prepared.skillsListing.entries } : {}),
402
+ ...(mcpAdds !== undefined || mcpRemovals !== undefined
403
+ ? { mcpInstructions: { added: mcpAdds ?? [], removed: mcpRemovals ?? [] } }
404
+ : {}),
405
+ ...(mcpDropped !== undefined ? { mcpDroppedTools: mcpDropped } : {}),
406
+ }));
407
+ if (pendingTools !== undefined || mcpToolsDelta !== undefined) {
408
+ const exact = renderToolsDelta({ ...(pendingTools !== undefined ? { added: pendingTools } : {}), ...(mcpToolsDelta ?? {}) });
409
+ if (exact !== undefined && due.some((a) => a.source === "tools_delta" && a.body === exact)) {
410
+ const ref = prepared.toolsDeltaRef;
411
+ if (pendingTools !== undefined)
412
+ ref.pending.splice(0, pendingTools.length);
413
+ if (pendingRemovedTools !== undefined)
414
+ ref.pendingRemoved.splice(0, pendingRemovedTools.length);
415
+ if (pendingReaddedTools !== undefined)
416
+ ref.pendingReadded.splice(0, pendingReaddedTools.length);
417
+ if (pendingFailedMcp !== undefined)
418
+ ref.pendingFailed.splice(0, pendingFailedMcp.length);
419
+ }
420
+ }
421
+ if ((mcpAdds !== undefined || mcpRemovals !== undefined) &&
422
+ due.some((a) => a.source === "mcp_instructions" && a.body === renderMcpInstructionsDelta(mcpAdds ?? [], mcpRemovals ?? []))) {
423
+ if (mcpAdds !== undefined)
424
+ mcpDelta.pendingAdds.splice(0, mcpAdds.length);
425
+ if (mcpRemovals !== undefined)
426
+ mcpDelta.pendingRemovals.splice(0, mcpRemovals.length);
427
+ }
428
+ if (mcpDropped !== undefined &&
429
+ due.some((a) => a.source === "mcp_dropped_tools" && a.body === renderMcpDroppedTools(mcpDropped))) {
430
+ prepared.mcp.droppedTools.splice(0, mcpDropped.length);
431
+ }
432
+ if (rs.attach.agentListingOn && prepared.agentListing !== undefined && due.some((a) => a.source === "agent_listing")) {
433
+ const listing = prepared.agentListing;
434
+ const intactBody = renderAgentListingDelta(rs.attach.attachState, listing.entries, listing.toolName, listing.models);
435
+ if (intactBody !== undefined && due.some((a) => a.source === "agent_listing" && a.body === intactBody)) {
436
+ commitAgentListing(rs.attach.attachState, listing.entries, listing.models);
437
+ prepared.announcedListingsRef.agents = listing.entries.map((e) => e.name);
438
+ if (listing.models !== undefined)
439
+ prepared.announcedListingsRef.models = [...listing.models];
440
+ await prepared.session.appendAnnouncedListing(prepared.announcedListingsRef).catch(() => undefined);
441
+ }
442
+ }
443
+ if (rs.attach.skillsListingOn && prepared.skillsListing !== undefined && due.some((a) => a.source === "skills_listing")) {
444
+ const listing = prepared.skillsListing;
445
+ const intactBody = renderSkillsListingDelta(rs.attach.attachState, listing.entries);
446
+ if (intactBody !== undefined && due.some((a) => a.source === "skills_listing" && a.body === intactBody)) {
447
+ commitSkillsListing(rs.attach.attachState, listing.entries);
448
+ prepared.announcedListingsRef.skills = listing.entries.map((e) => e.name);
449
+ await prepared.session.appendAnnouncedListing(prepared.announcedListingsRef).catch(() => undefined);
450
+ }
451
+ }
452
+ }
453
+ if (rs.attach.dateState !== undefined && rs.turn.lastTurnHadToolCalls) {
454
+ const today = prepared.dateChange.today();
455
+ const cand = collectDateChange(rs.attach.dateState, today);
456
+ if (cand !== undefined) {
457
+ due.unshift(cand);
458
+ rs.attach.dateState.announcedDate = today;
459
+ }
460
+ }
461
+ if (rs.attach.instrState !== undefined && rs.attach.instrProbe !== undefined && rs.turn.lastTurnHadToolCalls) {
462
+ let probed = null;
463
+ try {
464
+ const PROBE_CEILING_MS = 2_000;
465
+ probed = await Promise.race([
466
+ rs.attach.instrProbe(prepared.instructionSources),
467
+ new Promise((resolve) => {
468
+ const t = setTimeout(() => resolve(null), PROBE_CEILING_MS);
469
+ t.unref?.();
470
+ }),
471
+ ]);
472
+ }
473
+ catch {
474
+ probed = null;
475
+ }
476
+ const cand = collectInstructionsChange(rs.attach.instrState, probed);
477
+ if (cand !== undefined) {
478
+ due.splice(due.length > 0 && due[0].source === "date_change" ? 1 : 0, 0, cand.attachment);
479
+ commitInstructionsChange(rs.attach.instrState, cand.announced);
480
+ }
481
+ }
482
+ if (due.length > 0) {
483
+ attachmentsPayload = due.map((a) => `<system-reminder>\n${sanitizeUntrustedText(a.body)}\n</system-reminder>`).join("\n");
484
+ for (const a of due)
485
+ boundaryAttachmentBytes += Buffer.byteLength(a.body, "utf8");
486
+ rs.attach.attachmentsInjected += due.length;
487
+ for (const a of due) {
488
+ queue.push({ type: "steering_injected", source: a.source, preview: a.body.slice(0, 220), ...ident() });
489
+ }
490
+ }
491
+ }
492
+ let batchContextBlock;
493
+ if (postToolBatchHook !== undefined && rs.turn.toolBatch.length > 0) {
494
+ const batch = rs.turn.toolBatch;
495
+ rs.turn.toolBatch = [];
496
+ batchArgs?.clear();
497
+ if (!boundarySteered && !rs.counters.finalizeInjected && rs.counters.finalVerifyInjections === 0 && !prepared.abortController.signal.aborted) {
498
+ try {
499
+ const r = await postToolBatchHook(batch);
500
+ if (r?.additionalContext) {
501
+ const body = sanitizeUntrustedText(r.additionalContext);
502
+ const budget = ATTACHMENT_BYTE_CAP - boundaryAttachmentBytes;
503
+ if (budget >= BATCH_CONTEXT_MIN_KEEP_BYTES) {
504
+ const clipped = clipToByteBudget(body, budget) === body
505
+ ? body
506
+ : `${clipToByteBudget(body, budget - Buffer.byteLength(BATCH_TRUNCATION_MARKER, "utf8"))}${BATCH_TRUNCATION_MARKER}`;
507
+ batchContextBlock = `<system-reminder>\n${clipped}\n</system-reminder>`;
508
+ }
509
+ }
510
+ }
511
+ catch (err) {
512
+ try {
513
+ runnerHooks.onError?.(err instanceof Error ? err : new Error(String(err)), { phase: "hook", sessionId: prepared.sessionId });
514
+ }
515
+ catch {
516
+ }
517
+ }
518
+ }
519
+ }
520
+ if (attachmentsPayload !== undefined || batchContextBlock !== undefined) {
521
+ const payload = attachmentsPayload !== undefined && batchContextBlock !== undefined ? `${attachmentsPayload}\n${batchContextBlock}` : (attachmentsPayload ?? batchContextBlock);
522
+ void prepared.harness.steer(payload, { engineMinted: true }).catch(() => { });
523
+ }
524
+ const forceManual = manualCompactRef.requested;
525
+ if ((!withinTaskCompaction && !forceManual) ||
526
+ (rs.counters.finalizeInjected && !forceManual) ||
527
+ prepared.abortController.signal.aborted ||
528
+ prepared.suspendRef.token !== undefined ||
529
+ prepared.reviewRef.token !== undefined) {
530
+ if (forceManual) {
531
+ emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.mooted", version: 1, taskId: rs.telemetry.taskId, reason: "task_ending", ts: Date.now() }));
532
+ drainManualCompact("mooted");
533
+ }
534
+ return undefined;
535
+ }
536
+ if (compactionBreaker.failures >= MAX_CONSECUTIVE_COMPACTION_FAILURES) {
537
+ if (forceManual) {
538
+ emitTrace(rs.telemetry.tracer, () => ({
539
+ kind: "compaction.failed",
540
+ version: 1,
541
+ taskId: rs.telemetry.taskId,
542
+ trigger: "manual",
543
+ reason: "compaction circuit breaker open (3 consecutive failures this task) — manual request drained without an attempt",
544
+ ts: Date.now(),
545
+ }));
546
+ drainManualCompact("failed");
547
+ }
548
+ return undefined;
549
+ }
550
+ const claimedManual = forceManual
551
+ ? (() => {
552
+ const waiters = manualCompactRef.waiters.splice(0);
553
+ let instructions;
554
+ for (let i = waiters.length - 1; i >= 0; i--) {
555
+ if (waiters[i].instructions !== undefined) {
556
+ instructions = waiters[i].instructions;
557
+ break;
558
+ }
559
+ }
560
+ return { instructions, waiters };
561
+ })()
562
+ : undefined;
563
+ if (forceManual) {
564
+ manualCompactRef.requested = false;
565
+ }
566
+ const resolveClaimedWaiters = (outcome) => {
567
+ if (claimedManual !== undefined)
568
+ for (const w of claimedManual.waiters.splice(0))
569
+ w.resolve(outcome);
570
+ };
571
+ const claimedCancelSignals = claimedManual?.waiters.flatMap((w) => (w.signal !== undefined ? [w.signal] : [])) ?? [];
572
+ const manualCancelSignal = claimedCancelSignals.length === 0
573
+ ? undefined
574
+ : claimedCancelSignals.length === 1
575
+ ? claimedCancelSignals[0]
576
+ : AbortSignal.any(claimedCancelSignals);
577
+ const compactionPassSignal = claimedCancelSignals.length > 0
578
+ ? AbortSignal.any([prepared.abortController.signal, ...claimedCancelSignals])
579
+ : prepared.abortController.signal;
580
+ const trimPressureArmed = prepared.trimPressureRef.droppedMessages;
581
+ if (trimPressureArmed)
582
+ prepared.trimPressureRef.droppedMessages = false;
583
+ const trimPressure = trimPressureArmed && !rs.counters.trimForceBackoff;
584
+ let manualOutcome = "failed";
585
+ if (rs.counters.finalizeInjected && forceManual) {
586
+ emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.manual_in_finalize", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() }));
587
+ }
588
+ const manualInstructions = claimedManual?.instructions;
589
+ try {
590
+ const comp = await maybeCompact({
591
+ session: prepared.session,
592
+ epochDeclaredSections: prepared.epochDeclaredSections,
593
+ ...(() => {
594
+ const ca = prepared.centerCompactionCandidate?.();
595
+ return ca !== undefined ? { centerAdoption: ca } : {};
596
+ })(),
597
+ model: event.model,
598
+ compactionModel: prepared.compModel,
599
+ brain: compactionBrain,
600
+ getApiKeyAndHeaders: spec.getApiKeyAndHeaders,
601
+ thinking: prepared.thinking,
602
+ settings: { ...DEFAULT_COMPACTION_SETTINGS, ...spec.compaction },
603
+ customInstructions: manualInstructions ?? spec.compaction?.instructions ?? DEFAULT_COMPACTION_INSTRUCTIONS,
604
+ signal: compactionPassSignal,
605
+ ...(manualCancelSignal !== undefined ? { manualCancelSignal } : {}),
606
+ minTokens: forceManual ? 0 : rs.counters.compactionFloor,
607
+ force: forceManual,
608
+ onInputTruncated: (info) => emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.input_truncated", version: 1, taskId: rs.telemetry.taskId, label: info.label, droppedChars: info.droppedChars, keptChars: info.keptChars, ts: Date.now() })),
609
+ forceUnderThreshold: trimPressure,
610
+ overheadTokens: prepared.promptOverheadTokens,
611
+ workingFileAttachments: spec.compaction?.attachWorkingFiles !== false && prepared.readTaskFile
612
+ ? {
613
+ readFile: prepared.readTaskFile,
614
+ ...(prepared.recentlyReadFiles ? { recentlyReadFiles: prepared.recentlyReadFiles } : {}),
615
+ ...(prepared.normalizeAttachmentPath ? { normalizePath: prepared.normalizeAttachmentPath } : {}),
616
+ ...(prepared.isDedupStubResult ? { isDedupStubResult: prepared.isDedupStubResult } : {}),
617
+ ...(prepared.instructionSources
618
+ ? { instructionSourcePaths: prepared.instructionSources.filter((s) => s.contentHash !== null).map((s) => s.path) }
619
+ : {}),
620
+ ...(typeof spec.compaction?.attachWorkingFiles === "object" ? spec.compaction.attachWorkingFiles : undefined),
621
+ }
622
+ : undefined,
623
+ ...(prepared.onCompactionApplied ? { onApplied: prepared.onCompactionApplied } : {}),
624
+ ...runnerHooks.seamCCompactionOptions(prepared),
625
+ ...windowSafetyOptions(event.model),
626
+ ...runnerHooks.compactionHookOptions(spec, prepared.sessionId, trimPressure ? "forced" : forceManual ? "manual" : "auto"),
627
+ });
628
+ if (comp.blocked) {
629
+ emitTrace(rs.telemetry.tracer, () => ({
630
+ kind: "compaction.blocked",
631
+ version: 1,
632
+ taskId: rs.telemetry.taskId,
633
+ trigger: forceManual ? "manual" : "auto",
634
+ ts: Date.now(),
635
+ }));
636
+ }
637
+ else {
638
+ compactionBreaker.failures = 0;
639
+ }
640
+ rs.counters.trimForceBackoff = nextTrimForceBackoff(rs.counters.trimForceBackoff, comp, trimPressure);
641
+ if (comp.contextUsage) {
642
+ queue.push({ type: "context_usage", ...comp.contextUsage, ...ident() });
643
+ }
644
+ if (comp.suppressedByFloor) {
645
+ const s = comp.suppressedByFloor;
646
+ emitTrace(rs.telemetry.tracer, () => ({
647
+ kind: "compaction.suppressed",
648
+ version: 1,
649
+ taskId: rs.telemetry.taskId,
650
+ estTokens: s.estTokens,
651
+ floor: s.floor,
652
+ ts: Date.now(),
653
+ }));
654
+ }
655
+ manualOutcome = comp.compacted ? "compacted" : comp.blocked ? "blocked" : comp.disabled ? "disabled" : "noop";
656
+ if (forceManual && !comp.compacted && !comp.blocked) {
657
+ if (comp.disabled) {
658
+ emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.disabled", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() }));
659
+ }
660
+ else {
661
+ emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.noop", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() }));
662
+ }
663
+ }
664
+ if (comp.compacted && !forceManual) {
665
+ if (recordCompactionAndCheckRapidRefill(rapidRefill, stats.turns)) {
666
+ compactionBreaker.failures = MAX_CONSECUTIVE_COMPACTION_FAILURES;
667
+ runnerHooks.onError?.(new Error("compaction.rapid_refill: the context refilled within <3 turns of compaction 3 times in a row — compaction disabled for the rest of this task (thrash spiral; the transcript is dominated by incompressible content)"), { phase: "compaction", sessionId: prepared.sessionId });
668
+ }
669
+ }
670
+ runnerHooks.recordCompactionReuse(prepared, comp);
671
+ if (comp.compacted) {
672
+ queue.push({
673
+ type: "compacted",
674
+ trigger: forceManual ? "manual" : "auto",
675
+ tokensBefore: comp.tokensBefore ?? 0,
676
+ ...(comp.postTriggerTokens !== undefined ? { tokensAfter: comp.postTriggerTokens } : {}),
677
+ ...(comp.triggerTokens !== undefined ? { triggerTokensBefore: comp.triggerTokens } : {}),
678
+ ...(comp.durationMs !== undefined ? { durationMs: comp.durationMs } : {}),
679
+ ...(comp.phaseDurations !== undefined ? { phaseDurations: comp.phaseDurations } : {}),
680
+ ...(comp.firstKeptEntryId !== undefined ? { preserved_segment: { firstKeptEntryId: comp.firstKeptEntryId } } : {}),
681
+ ...(comp.attachedFiles !== undefined ? { attachedFiles: comp.attachedFiles } : {}),
682
+ ...(comp.modelFallback ? { modelFallback: true } : {}),
683
+ ...(comp.fallbackReason !== undefined ? { fallbackReason: comp.fallbackReason } : {}),
684
+ ...(comp.clampedRatio !== undefined ? { clampedRatio: comp.clampedRatio } : {}),
685
+ ...(comp.clampReason !== undefined ? { clampReason: comp.clampReason } : {}),
686
+ });
687
+ if (comp.phaseDurations !== undefined && comp.durationMs !== undefined) {
688
+ const pd = comp.phaseDurations;
689
+ const pdDur = comp.durationMs;
690
+ emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.phase_timings", version: 1, taskId: rs.telemetry.taskId, ...pd, durationMs: pdDur, ts: Date.now() }));
691
+ }
692
+ prepared.cacheBreakDetector?.notifyCompaction();
693
+ if (rs.attach.attachState !== undefined) {
694
+ rs.attach.attachState.postCompactPending = true;
695
+ rebaseCadenceWindows(rs.attach.attachState, rs.counters.cadenceTurns);
696
+ }
697
+ if ((comp.freedTokens ?? 0) >= COMPACTION_FREED_EPSILON) {
698
+ const postSize = comp.postTriggerTokens ?? Math.max(0, (comp.triggerTokens ?? comp.tokensBefore ?? 0) - (comp.freedTokens ?? 0));
699
+ rs.counters.compactionFloor = Math.ceil(postSize * COMPACTION_REGROWTH_FACTOR);
700
+ }
701
+ }
702
+ }
703
+ catch (err) {
704
+ const manualCancelAbort = !prepared.abortController.signal.aborted && isCompactionManualCancel(err);
705
+ if (manualCancelAbort) {
706
+ emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.mooted", version: 1, taskId: rs.telemetry.taskId, reason: "cancelled", ts: Date.now() }));
707
+ manualOutcome = "mooted";
708
+ }
709
+ else if (!prepared.abortController.signal.aborted) {
710
+ const walltimeAbort = isCompactionWalltimeAbort(err);
711
+ if (!walltimeAbort)
712
+ compactionBreaker.failures += 1;
713
+ runnerHooks.onError?.(err, { phase: "compaction", sessionId: prepared.sessionId });
714
+ const msg = String(err instanceof Error ? err.message : err);
715
+ emitTrace(rs.telemetry.tracer, () => ({
716
+ kind: "compaction.failed",
717
+ version: 1,
718
+ taskId: rs.telemetry.taskId,
719
+ trigger: trimPressure ? "forced" : forceManual ? "manual" : "auto",
720
+ reason: walltimeAbort
721
+ ? "walltime soft-deadline abort (engine-initiated write-out protection; not counted toward the breaker)"
722
+ : msg.length > 512
723
+ ? `${msg.slice(0, 512)}…`
724
+ : msg,
725
+ ts: Date.now(),
726
+ }));
727
+ manualOutcome = "failed";
728
+ }
729
+ else {
730
+ manualOutcome = "mooted";
731
+ }
732
+ }
733
+ if (forceManual)
734
+ resolveClaimedWaiters(manualOutcome);
735
+ return undefined;
736
+ };
737
+ return onTurnBoundary;
738
+ }
739
+ function makeHarnessHandlers(prepared, stats, rs, deps) {
740
+ const { spec, queue, internals, buildFinalizeText, ident, nudgeSchedule, parentToolCallId, subagentName, pushContent, emitCommitted, startedToolCallIds, toolStartAt, turnToolSpan, writeFamilyOf, toolLabels, postToolBatchHook, batchArgs } = deps;
741
+ let lastWorkspaceCwd = prepared.cwdRef?.current;
742
+ const onMessageUpdate = (event) => {
743
+ const ev = event.assistantMessageEvent;
744
+ if (rs.turn.callStartAt === undefined)
745
+ rs.turn.callStartAt = Date.now();
746
+ if (ev.type === "text_delta" && ev.delta) {
747
+ if (rs.turn.firstTokenAt === undefined)
748
+ rs.turn.firstTokenAt = Date.now();
749
+ pushContent({ type: "text_delta", delta: ev.delta, ...ident() });
750
+ }
751
+ else if (ev.type === "thinking_delta" && ev.delta) {
752
+ pushContent({ type: "reasoning_delta", delta: ev.delta, ...ident() });
753
+ }
754
+ if (rs.budget.streamCancel && !rs.limits.budgetHit && (ev.type === "text_delta" || ev.type === "thinking_delta") && ev.delta) {
755
+ rs.budget.callOutputChars += ev.delta.length;
756
+ if (rs.budget.callOutputChars - rs.budget.lastStreamBudgetCheck >= 256) {
757
+ rs.budget.lastStreamBudgetCheck = rs.budget.callOutputChars;
758
+ if (rs.budget.projectedOverBudget()) {
759
+ rs.limits.budgetHit = "exceeded";
760
+ void prepared.harness.abort();
761
+ prepared.abortController.abort();
762
+ }
763
+ }
764
+ }
765
+ };
766
+ const onMessageEnd = (event) => {
767
+ const m = event.message;
768
+ if (event.entryId !== undefined && (m.role === "user" || m.role === "assistant" || m.role === "toolResult")) {
769
+ emitCommitted(event.entryId, m.role, m.role === "toolResult" ? m.toolCallId : undefined);
770
+ }
771
+ if (rs.degrade.degraded === undefined && m.role === "assistant") {
772
+ const deg = readDegradation(m);
773
+ if (deg)
774
+ rs.degrade.recordDegraded({ from: deg.from, to: deg.to, reason: deg.reason, atTurn: stats.turns + 1, ...(deg.chain && { chain: deg.chain }) });
775
+ }
776
+ if (m.role === "assistant" && "usage" in m && m.usage) {
777
+ if (m.usageMissing)
778
+ rs.turn.turnUsageMissing = true;
779
+ const u = m.usage;
780
+ stats.tokens += u.totalTokens || 0;
781
+ const { totalInputTokens: turnInput, costMicroUsd: turnCostMicroUsd } = usageCostMicroUsd(rs.telemetry.cacheFamily, u, rs.telemetry.pricing);
782
+ stats.promptTokens += turnInput;
783
+ stats.cachedTokens += u.cacheRead || 0;
784
+ stats.cacheWriteTokens += u.cacheWrite || 0;
785
+ stats.outputTokens += u.output || 0;
786
+ stats.costMicroUsd += turnCostMicroUsd;
787
+ rs.turn.turnUsage ??= { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0, costMicroUsd: 0 };
788
+ rs.turn.turnUsage.inputTokens += turnInput;
789
+ rs.turn.turnUsage.outputTokens += u.output || 0;
790
+ rs.turn.turnUsage.cacheReadTokens += u.cacheRead || 0;
791
+ rs.turn.turnUsage.cacheWriteTokens += u.cacheWrite || 0;
792
+ rs.turn.turnUsage.costMicroUsd += turnCostMicroUsd;
793
+ const callEndAt = Date.now();
794
+ const callIssuedAt = prepared.callIssuedAtRef?.current;
795
+ if (prepared.callIssuedAtRef)
796
+ prepared.callIssuedAtRef.current = undefined;
797
+ const capLast = prepared.callCapRef?.state.last;
798
+ if (prepared.callCapRef) {
799
+ recordCallSample(prepared.callCapRef.state, u.output || 0, rs.turn.callStartAt !== undefined ? callEndAt - rs.turn.callStartAt : 0, {
800
+ contentValid: m.usageMissing === true && hasRealContent(m.content),
801
+ });
802
+ if (rs.turn.callStartAt !== undefined && rs.turn.firstTokenAt !== undefined) {
803
+ recordTtftSample(prepared.callCapRef.state, rs.turn.firstTokenAt - rs.turn.callStartAt);
804
+ }
805
+ prepared.callCapRef.state.last = undefined;
806
+ }
807
+ emitTrace(rs.telemetry.tracer, () => ({
808
+ kind: "brain.call",
809
+ version: 1,
810
+ taskId: rs.telemetry.taskId,
811
+ model: m.model,
812
+ provider: m.provider,
813
+ promptTokens: turnInput,
814
+ completionTokens: u.output || 0,
815
+ cacheRead: u.cacheRead || 0,
816
+ cacheWrite: u.cacheWrite || 0,
817
+ latencyMs: rs.turn.callStartAt !== undefined ? callEndAt - rs.turn.callStartAt : 0,
818
+ firstTokenMs: rs.turn.callStartAt !== undefined && rs.turn.firstTokenAt !== undefined ? rs.turn.firstTokenAt - rs.turn.callStartAt : undefined,
819
+ ...(callIssuedAt !== undefined ? { callStartedAt: callIssuedAt } : {}),
820
+ costMicroUsd: turnCostMicroUsd,
821
+ ...(capLast !== undefined ? { callCap: capLast.cap, capThinkingSkipped: capLast.thinkingSkipped } : {}),
822
+ ...(m.role === "assistant" && typeof m.stopReason === "string"
823
+ ? { stopReason: m.stopReason }
824
+ : {}),
825
+ ts: callEndAt,
826
+ }));
827
+ if (prepared.callCapRef &&
828
+ prepared.callCapRef.reasoningObserved !== true &&
829
+ capLast !== undefined &&
830
+ m.role === "assistant" &&
831
+ m.stopReason === "length") {
832
+ const blocks = Array.isArray(m.content)
833
+ ? (m.content)
834
+ : [];
835
+ const productive = blocks.some((c) => c?.type === "toolCall") ||
836
+ blocks.some((c) => c?.type === "text" && typeof c.text === "string" && c.text.trim() !== "");
837
+ if (!productive)
838
+ prepared.callCapRef.reasoningObserved = true;
839
+ }
840
+ if (prepared.cacheBreakDetector && prepared.cacheFingerprint) {
841
+ const finding = prepared.cacheBreakDetector.observe({
842
+ turn: stats.turns + 1,
843
+ systemPrompt: prepared.cacheFingerprint.systemPrompt,
844
+ tools: prepared.cacheFingerprint.tools,
845
+ modelKey: `${m.provider}:${m.model}`,
846
+ cacheRead: u.cacheRead || 0,
847
+ });
848
+ if (finding) {
849
+ rs.telemetry.cacheBreakReported = true;
850
+ 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 });
851
+ }
852
+ }
853
+ }
854
+ if (m.role === "assistant" && typeof m.stopReason === "string") {
855
+ rs.turn.turnStopReason = m.stopReason;
856
+ }
857
+ if (m.role === "assistant" && m.errorMessage === WALLTIME_CUTOFF_MESSAGE) {
858
+ rs.counters.callCutoffs += 1;
859
+ if (prepared.callCapRef)
860
+ prepared.callCapRef.finalizeMode = true;
861
+ if (!rs.counters.finalizeInjected) {
862
+ const finalizeText = buildFinalizeText();
863
+ void prepared.harness.steer(finalizeText, { engineMinted: true }).catch(() => { });
864
+ void prepared.harness.setThinkingLevel("off").catch(() => { });
865
+ prepared.turnSnapshot?.refreshRequestPolicy({ thinkingLevel: "off" });
866
+ rs.counters.finalizeInjected = true;
867
+ rs.counters.nudgeIdx = nudgeSchedule.length;
868
+ queue.push({ type: "steering_injected", source: "finalize", preview: finalizeText.slice(0, 220), ...ident() });
869
+ }
870
+ }
871
+ if (m.role === "assistant") {
872
+ const rep = m.repetition;
873
+ if (rep !== undefined) {
874
+ const observed = [
875
+ ...(rep.cut !== undefined ? [{ action: "cut", e: rep.cut }] : []),
876
+ ...(rep.spared ?? []).map((e) => ({ action: "spared", e })),
877
+ ];
878
+ for (const { action, e } of observed) {
879
+ if (action === "cut")
880
+ rs.counters.repetitionCuts += 1;
881
+ else
882
+ rs.counters.repetitionSpared += 1;
883
+ if (rs.counters.repetitionEvents.length < rs.counters.REPETITION_EVENTS_CAP) {
884
+ rs.counters.repetitionEvents.push({ turn: stats.turns + 1, action, rule: e.rule, period: e.period, reps: e.reps, segment: e.segment });
885
+ }
886
+ emitTrace(rs.telemetry.tracer, () => ({
887
+ kind: "repetition.detected",
888
+ version: 1,
889
+ taskId: rs.telemetry.taskId,
890
+ turn: stats.turns + 1,
891
+ action,
892
+ rule: e.rule,
893
+ period: e.period,
894
+ reps: e.reps,
895
+ segment: e.segment,
896
+ ts: Date.now(),
897
+ }));
898
+ }
899
+ }
900
+ }
901
+ if (prepared.callCapRef &&
902
+ prepared.callCapRef.reasoningObserved !== true &&
903
+ m.role === "assistant" &&
904
+ Array.isArray(m.content) &&
905
+ m.content.some((c) => c?.type === "thinking")) {
906
+ prepared.callCapRef.reasoningObserved = true;
907
+ }
908
+ if (m.role === "assistant") {
909
+ const content = m.content;
910
+ if (rs.attach.attachState !== undefined)
911
+ rs.counters.cadenceTurns = advanceCadenceClock(rs.attach.attachState, rs.counters.cadenceTurns, content, writeFamilyOf);
912
+ rs.turn.lastTurnHadToolCalls = Array.isArray(content) && content.some((c) => c?.type === "toolCall");
913
+ }
914
+ rs.turn.callStartAt = undefined;
915
+ rs.turn.firstTokenAt = undefined;
916
+ rs.budget.callOutputChars = 0;
917
+ rs.budget.lastStreamBudgetCheck = 0;
918
+ };
919
+ const onToolStart = (event) => {
920
+ const startAt = Date.now();
921
+ toolStartAt.set(event.toolCallId, startAt);
922
+ startedToolCallIds.add(event.toolCallId);
923
+ batchArgs?.set(event.toolCallId, event.args);
924
+ stats.toolCalls += 1;
925
+ if (spec.finalVerification === true && !(rs.counters.finalVerifyInjections === 0 ? rs.counters.groundingSignalPreR9 : rs.counters.groundingSignalPostR9) && hasVerifiableStructureSignal(event.args)) {
926
+ if (rs.counters.finalVerifyInjections === 0)
927
+ rs.counters.groundingSignalPreR9 = true;
928
+ else
929
+ rs.counters.groundingSignalPostR9 = true;
930
+ }
931
+ const activityArg = primaryActivityArg(event.args);
932
+ internals?.onActivity?.({ phase: "start", toolCallId: event.toolCallId, toolName: event.toolName, at: startAt, ...(activityArg !== undefined ? { arg: activityArg } : {}) });
933
+ pushContent({
934
+ type: "tool_start",
935
+ toolCallId: event.toolCallId,
936
+ toolName: event.toolName,
937
+ ...(toolLabels.get(event.toolName) !== undefined ? { label: toolLabels.get(event.toolName) } : {}),
938
+ args: event.args,
939
+ ...ident(),
940
+ });
941
+ };
942
+ const onToolEnd = (event) => {
943
+ const toolStarted = toolStartAt.get(event.toolCallId);
944
+ toolStartAt.delete(event.toolCallId);
945
+ const toolNow = Date.now();
946
+ if (prepared.callCapRef && toolStarted !== undefined) {
947
+ recordToolSample(prepared.callCapRef.state, toolNow - toolStarted);
948
+ turnToolSpan.startMin = turnToolSpan.startMin === undefined ? toolStarted : Math.min(turnToolSpan.startMin, toolStarted);
949
+ turnToolSpan.endMax = turnToolSpan.endMax === undefined ? toolNow : Math.max(turnToolSpan.endMax, toolNow);
950
+ }
951
+ if (!rs.counters.wroteThisRun && (prepared.toolEffects.get(event.toolName) ?? "write") !== "read") {
952
+ rs.counters.wroteThisRun = true;
953
+ }
954
+ if (rs.attach.attachState !== undefined && !event.isError) {
955
+ const det = event.result?.details;
956
+ if (det !== undefined)
957
+ reduceToolEnd(rs.attach.attachState, det);
958
+ }
959
+ if (postToolBatchHook !== undefined) {
960
+ if (prepared.blockedToolCalls.delete(event.toolCallId)) {
961
+ batchArgs?.delete(event.toolCallId);
962
+ }
963
+ else {
964
+ const digest = batchResponseDigest(event.result);
965
+ rs.turn.toolBatch.push({
966
+ toolName: event.toolName,
967
+ input: batchArgs?.get(event.toolCallId),
968
+ toolCallId: event.toolCallId,
969
+ ...(digest !== undefined ? { response: digest } : {}),
970
+ isError: event.isError,
971
+ });
972
+ batchArgs?.delete(event.toolCallId);
973
+ }
974
+ }
975
+ emitTrace(rs.telemetry.tracer, () => ({
976
+ kind: "tool.call",
977
+ version: 1,
978
+ taskId: rs.telemetry.taskId,
979
+ name: event.toolName,
980
+ durationMs: toolStarted !== undefined ? toolNow - toolStarted : 0,
981
+ ok: !event.isError,
982
+ ts: toolNow,
983
+ }));
984
+ internals?.onActivity?.({ phase: "end", toolCallId: event.toolCallId, toolName: event.toolName, isError: event.isError, at: toolNow });
985
+ if (prepared.lspDiagnostics && !event.isError) {
986
+ const name = canonicalToolName(event.toolName);
987
+ if (name === "Edit" || name === "Write" || name === "NotebookEdit") {
988
+ const d = event.result?.details;
989
+ const p = typeof d?.filePath === "string" ? d.filePath : typeof d?.notebookPath === "string" ? d.notebookPath : undefined;
990
+ if (p)
991
+ prepared.lspDiagnostics.nudge(p);
992
+ }
993
+ }
994
+ pushContent({
995
+ type: "tool_end",
996
+ toolCallId: event.toolCallId,
997
+ toolName: event.toolName,
998
+ ...(toolLabels.get(event.toolName) !== undefined ? { label: toolLabels.get(event.toolName) } : {}),
999
+ isError: event.isError,
1000
+ ...(() => {
1001
+ const o = toolOutputFrom(event.result);
1002
+ return o !== undefined
1003
+ ? { output: o.output, ...(o.truncated ? { truncated: true } : {}), ...(o.totalChars !== undefined ? { totalChars: o.totalChars } : {}) }
1004
+ : {};
1005
+ })(),
1006
+ ...(() => {
1007
+ const st = structuredFrom(event.result);
1008
+ return st !== undefined ? { structured: st } : {};
1009
+ })(),
1010
+ ...ident(),
1011
+ });
1012
+ const cwdNow = prepared.cwdRef?.current;
1013
+ if (cwdNow !== undefined && cwdNow !== lastWorkspaceCwd) {
1014
+ lastWorkspaceCwd = cwdNow;
1015
+ queue.push({ type: "workspace_changed", cwd: cwdNow, ...ident() });
1016
+ }
1017
+ if (spec.outputSchema && event.toolName === OUTPUT_TOOL_NAME) {
1018
+ if (event.isError) {
1019
+ rs.degrade.outputErrorStreak += 1;
1020
+ if (rs.degrade.outputErrorStreak > rs.limits.outputRetryCap) {
1021
+ rs.degrade.outputInvalid = true;
1022
+ void prepared.harness.abort();
1023
+ prepared.abortController.abort();
1024
+ }
1025
+ }
1026
+ else {
1027
+ rs.degrade.outputErrorStreak = 0;
1028
+ }
1029
+ }
1030
+ };
1031
+ const onTurnEnd = () => {
1032
+ stats.turns += 1;
1033
+ const stopExtra = rs.turn.turnStopReason !== undefined ? { stopReason: rs.turn.turnStopReason } : {};
1034
+ queue.push(rs.turn.turnUsage
1035
+ ? { type: "turn_end", usage: rs.turn.turnUsage, ...(rs.turn.turnUsageMissing ? { usageMissing: true } : {}), ...stopExtra, ...ident() }
1036
+ : { type: "turn_end", usageMissing: true, ...stopExtra, ...ident() });
1037
+ rs.turn.turnUsageMissing = false;
1038
+ rs.turn.turnStopReason = undefined;
1039
+ if (parentToolCallId !== undefined) {
1040
+ const progress = {
1041
+ type: "task_progress",
1042
+ taskId: rs.telemetry.taskId,
1043
+ ...(internals?.parentTaskId !== undefined ? { parentTaskId: internals.parentTaskId } : {}),
1044
+ ...(subagentName ? { name: subagentName } : {}),
1045
+ usage: { totalTokens: stats.tokens, toolUses: stats.toolCalls, durationMs: Date.now() - rs.telemetry.taskStart },
1046
+ status: "running",
1047
+ ...ident(),
1048
+ };
1049
+ queue.push(progress);
1050
+ try {
1051
+ internals?.onForwardEvent?.(progress);
1052
+ }
1053
+ catch {
1054
+ }
1055
+ }
1056
+ emitTrace(rs.telemetry.tracer, () => ({ kind: "turn.end", version: 1, taskId: rs.telemetry.taskId, turn: stats.turns, ts: Date.now() }));
1057
+ rs.turn.turnUsage = undefined;
1058
+ rs.turn.callStartAt = undefined;
1059
+ rs.turn.firstTokenAt = undefined;
1060
+ rs.budget.callOutputChars = 0;
1061
+ rs.budget.lastStreamBudgetCheck = 0;
1062
+ toolStartAt.clear();
1063
+ if (rs.limits.effectiveMaxTurns !== undefined && rs.limits.effectiveMaxTurns > 0 && stats.turns >= rs.limits.effectiveMaxTurns) {
1064
+ rs.limits.turnsExceeded = true;
1065
+ if (prepared.suspendForResource === undefined) {
1066
+ void prepared.harness.abort();
1067
+ prepared.abortController.abort();
1068
+ }
1069
+ }
1070
+ else {
1071
+ if (rs.degrade.degraded === undefined &&
1072
+ spec.degrade &&
1073
+ rs.degrade.degradeToModel &&
1074
+ rs.budget.maxCostMicroUsd !== undefined &&
1075
+ stats.costMicroUsd >= spec.degrade.atCostFraction * rs.budget.maxCostMicroUsd) {
1076
+ void prepared.harness.setModel(rs.degrade.degradeToModel);
1077
+ rs.degrade.recordDegraded({ from: prepared.model.id, to: rs.degrade.degradeToModel.id, reason: "budget", atTurn: stats.turns }, rs.degrade.degradeToModel);
1078
+ if (prepared.callCapRef &&
1079
+ spec.limits?.maxOutputTokens === undefined &&
1080
+ rs.degrade.degradeToModel.maxTokens !== undefined &&
1081
+ (prepared.callCapRef.staticCapTokens === undefined || rs.degrade.degradeToModel.maxTokens < prepared.callCapRef.staticCapTokens)) {
1082
+ prepared.callCapRef.staticCapTokens = rs.degrade.degradeToModel.maxTokens;
1083
+ }
1084
+ }
1085
+ if (!rs.limits.budgetHit && rs.budget.overBudget()) {
1086
+ rs.limits.budgetHit = "exceeded";
1087
+ if (prepared.suspendForResource === undefined) {
1088
+ void prepared.harness.abort();
1089
+ prepared.abortController.abort();
1090
+ }
1091
+ }
1092
+ }
1093
+ };
1094
+ return { onMessageUpdate, onMessageEnd, onToolStart, onToolEnd, onTurnEnd };
1095
+ }
220
1096
  export class Runner {
221
1097
  deps;
222
1098
  sessions;
@@ -1004,7 +1880,7 @@ export class Runner {
1004
1880
  catch {
1005
1881
  }
1006
1882
  }
1007
- let attachmentsInjected = 0;
1883
+ rs.attach.attachmentsInjected = 0;
1008
1884
  const todoToolMounted = rs.attach.attachState !== undefined && prepared.tools.some((t) => canonicalToolName(t.name) === "TodoWrite");
1009
1885
  const taskToolsMounted = rs.attach.attachState !== undefined && prepared.tools.some((t) => canonicalToolName(t.name) === "TaskCreate");
1010
1886
  const writeFamilyByName = new Map();
@@ -1091,8 +1967,8 @@ export class Runner {
1091
1967
  prepared.callCapRef.budgetMs = effectiveTimeoutMs;
1092
1968
  }
1093
1969
  }
1094
- let finalizeInjected = false;
1095
- let walltimeSyncBackstopFired = false;
1970
+ rs.counters.finalizeInjected = false;
1971
+ rs.counters.walltimeSyncBackstopFired = false;
1096
1972
  const nudgeSchedule = [];
1097
1973
  {
1098
1974
  const conf = spec.limits?.deadlineNudge;
@@ -1119,7 +1995,7 @@ export class Runner {
1119
1995
  }
1120
1996
  }
1121
1997
  }
1122
- let nudgeIdx = 0;
1998
+ rs.counters.nudgeIdx = 0;
1123
1999
  const buildFinalizeText = () => `<system-reminder>[deadline] the wall-clock budget is exhausted — this is the FINAL turn. ` +
1124
2000
  `Write your best current work to the deliverable path NOW, even if incomplete — persist to disk FIRST; everything else comes after. ` +
1125
2001
  `If a better version exists anywhere else (a draft, scratch or temp file), copy it to the deliverable path now. ` +
@@ -1148,7 +2024,6 @@ export class Runner {
1148
2024
  queue.push({ type: "message_committed", entryId, role, ...(toolCallId !== undefined ? { toolCallId } : {}), ...ident() });
1149
2025
  };
1150
2026
  const subagentName = parentToolCallId !== undefined && internals?.agentName !== undefined ? inlineUntrusted(internals.agentName.slice(0, 320), 80) : undefined;
1151
- let lastWorkspaceCwd = prepared.cwdRef?.current;
1152
2027
  const statusEmit = (s) => {
1153
2028
  queue.push({
1154
2029
  type: "status",
@@ -1176,362 +2051,16 @@ export class Runner {
1176
2051
  : { kind: "vision.placeholder", version: 1, taskId: rs.telemetry.taskId, count: t.count, ts: Date.now() });
1177
2052
  };
1178
2053
  const withBrainSinks = (fn) => runWithStatusSink(statusEmit, () => runWithBrainTelemetry(telemetryEmit, fn));
1179
- const onMessageUpdate = (event) => {
1180
- const ev = event.assistantMessageEvent;
1181
- if (rs.turn.callStartAt === undefined)
1182
- rs.turn.callStartAt = Date.now();
1183
- if (ev.type === "text_delta" && ev.delta) {
1184
- if (rs.turn.firstTokenAt === undefined)
1185
- rs.turn.firstTokenAt = Date.now();
1186
- pushContent({ type: "text_delta", delta: ev.delta, ...ident() });
1187
- }
1188
- else if (ev.type === "thinking_delta" && ev.delta) {
1189
- pushContent({ type: "reasoning_delta", delta: ev.delta, ...ident() });
1190
- }
1191
- if (rs.budget.streamCancel && !rs.limits.budgetHit && (ev.type === "text_delta" || ev.type === "thinking_delta") && ev.delta) {
1192
- rs.budget.callOutputChars += ev.delta.length;
1193
- if (rs.budget.callOutputChars - rs.budget.lastStreamBudgetCheck >= 256) {
1194
- rs.budget.lastStreamBudgetCheck = rs.budget.callOutputChars;
1195
- if (rs.budget.projectedOverBudget()) {
1196
- rs.limits.budgetHit = "exceeded";
1197
- void prepared.harness.abort();
1198
- prepared.abortController.abort();
1199
- }
1200
- }
1201
- }
1202
- };
1203
- const onMessageEnd = (event) => {
1204
- const m = event.message;
1205
- if (event.entryId !== undefined && (m.role === "user" || m.role === "assistant" || m.role === "toolResult")) {
1206
- emitCommitted(event.entryId, m.role, m.role === "toolResult" ? m.toolCallId : undefined);
1207
- }
1208
- if (rs.degrade.degraded === undefined && m.role === "assistant") {
1209
- const deg = readDegradation(m);
1210
- if (deg)
1211
- rs.degrade.recordDegraded({ from: deg.from, to: deg.to, reason: deg.reason, atTurn: stats.turns + 1, ...(deg.chain && { chain: deg.chain }) });
1212
- }
1213
- if (m.role === "assistant" && "usage" in m && m.usage) {
1214
- if (m.usageMissing)
1215
- rs.turn.turnUsageMissing = true;
1216
- const u = m.usage;
1217
- stats.tokens += u.totalTokens || 0;
1218
- const { totalInputTokens: turnInput, costMicroUsd: turnCostMicroUsd } = usageCostMicroUsd(rs.telemetry.cacheFamily, u, rs.telemetry.pricing);
1219
- stats.promptTokens += turnInput;
1220
- stats.cachedTokens += u.cacheRead || 0;
1221
- stats.cacheWriteTokens += u.cacheWrite || 0;
1222
- stats.outputTokens += u.output || 0;
1223
- stats.costMicroUsd += turnCostMicroUsd;
1224
- rs.turn.turnUsage ??= { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0, costMicroUsd: 0 };
1225
- rs.turn.turnUsage.inputTokens += turnInput;
1226
- rs.turn.turnUsage.outputTokens += u.output || 0;
1227
- rs.turn.turnUsage.cacheReadTokens += u.cacheRead || 0;
1228
- rs.turn.turnUsage.cacheWriteTokens += u.cacheWrite || 0;
1229
- rs.turn.turnUsage.costMicroUsd += turnCostMicroUsd;
1230
- const callEndAt = Date.now();
1231
- const callIssuedAt = prepared.callIssuedAtRef?.current;
1232
- if (prepared.callIssuedAtRef)
1233
- prepared.callIssuedAtRef.current = undefined;
1234
- const capLast = prepared.callCapRef?.state.last;
1235
- if (prepared.callCapRef) {
1236
- recordCallSample(prepared.callCapRef.state, u.output || 0, rs.turn.callStartAt !== undefined ? callEndAt - rs.turn.callStartAt : 0, {
1237
- contentValid: m.usageMissing === true && hasRealContent(m.content),
1238
- });
1239
- if (rs.turn.callStartAt !== undefined && rs.turn.firstTokenAt !== undefined) {
1240
- recordTtftSample(prepared.callCapRef.state, rs.turn.firstTokenAt - rs.turn.callStartAt);
1241
- }
1242
- prepared.callCapRef.state.last = undefined;
1243
- }
1244
- emitTrace(rs.telemetry.tracer, () => ({
1245
- kind: "brain.call",
1246
- version: 1,
1247
- taskId: rs.telemetry.taskId,
1248
- model: m.model,
1249
- provider: m.provider,
1250
- promptTokens: turnInput,
1251
- completionTokens: u.output || 0,
1252
- cacheRead: u.cacheRead || 0,
1253
- cacheWrite: u.cacheWrite || 0,
1254
- latencyMs: rs.turn.callStartAt !== undefined ? callEndAt - rs.turn.callStartAt : 0,
1255
- firstTokenMs: rs.turn.callStartAt !== undefined && rs.turn.firstTokenAt !== undefined ? rs.turn.firstTokenAt - rs.turn.callStartAt : undefined,
1256
- ...(callIssuedAt !== undefined ? { callStartedAt: callIssuedAt } : {}),
1257
- costMicroUsd: turnCostMicroUsd,
1258
- ...(capLast !== undefined ? { callCap: capLast.cap, capThinkingSkipped: capLast.thinkingSkipped } : {}),
1259
- ...(m.role === "assistant" && typeof m.stopReason === "string"
1260
- ? { stopReason: m.stopReason }
1261
- : {}),
1262
- ts: callEndAt,
1263
- }));
1264
- if (prepared.callCapRef &&
1265
- prepared.callCapRef.reasoningObserved !== true &&
1266
- capLast !== undefined &&
1267
- m.role === "assistant" &&
1268
- m.stopReason === "length") {
1269
- const blocks = Array.isArray(m.content)
1270
- ? (m.content)
1271
- : [];
1272
- const productive = blocks.some((c) => c?.type === "toolCall") ||
1273
- blocks.some((c) => c?.type === "text" && typeof c.text === "string" && c.text.trim() !== "");
1274
- if (!productive)
1275
- prepared.callCapRef.reasoningObserved = true;
1276
- }
1277
- if (prepared.cacheBreakDetector && prepared.cacheFingerprint) {
1278
- const finding = prepared.cacheBreakDetector.observe({
1279
- turn: stats.turns + 1,
1280
- systemPrompt: prepared.cacheFingerprint.systemPrompt,
1281
- tools: prepared.cacheFingerprint.tools,
1282
- modelKey: `${m.provider}:${m.model}`,
1283
- cacheRead: u.cacheRead || 0,
1284
- });
1285
- if (finding) {
1286
- rs.telemetry.cacheBreakReported = true;
1287
- this.deps.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 });
1288
- }
1289
- }
1290
- }
1291
- if (m.role === "assistant" && typeof m.stopReason === "string") {
1292
- rs.turn.turnStopReason = m.stopReason;
1293
- }
1294
- if (m.role === "assistant" && m.errorMessage === WALLTIME_CUTOFF_MESSAGE) {
1295
- rs.counters.callCutoffs += 1;
1296
- if (prepared.callCapRef)
1297
- prepared.callCapRef.finalizeMode = true;
1298
- if (!finalizeInjected) {
1299
- const finalizeText = buildFinalizeText();
1300
- void prepared.harness.steer(finalizeText, { engineMinted: true }).catch(() => { });
1301
- void prepared.harness.setThinkingLevel("off").catch(() => { });
1302
- prepared.turnSnapshot?.refreshRequestPolicy({ thinkingLevel: "off" });
1303
- finalizeInjected = true;
1304
- nudgeIdx = nudgeSchedule.length;
1305
- queue.push({ type: "steering_injected", source: "finalize", preview: finalizeText.slice(0, 220), ...ident() });
1306
- }
1307
- }
1308
- if (m.role === "assistant") {
1309
- const rep = m.repetition;
1310
- if (rep !== undefined) {
1311
- const observed = [
1312
- ...(rep.cut !== undefined ? [{ action: "cut", e: rep.cut }] : []),
1313
- ...(rep.spared ?? []).map((e) => ({ action: "spared", e })),
1314
- ];
1315
- for (const { action, e } of observed) {
1316
- if (action === "cut")
1317
- rs.counters.repetitionCuts += 1;
1318
- else
1319
- rs.counters.repetitionSpared += 1;
1320
- if (rs.counters.repetitionEvents.length < rs.counters.REPETITION_EVENTS_CAP) {
1321
- rs.counters.repetitionEvents.push({ turn: stats.turns + 1, action, rule: e.rule, period: e.period, reps: e.reps, segment: e.segment });
1322
- }
1323
- emitTrace(rs.telemetry.tracer, () => ({
1324
- kind: "repetition.detected",
1325
- version: 1,
1326
- taskId: rs.telemetry.taskId,
1327
- turn: stats.turns + 1,
1328
- action,
1329
- rule: e.rule,
1330
- period: e.period,
1331
- reps: e.reps,
1332
- segment: e.segment,
1333
- ts: Date.now(),
1334
- }));
1335
- }
1336
- }
1337
- }
1338
- if (prepared.callCapRef &&
1339
- prepared.callCapRef.reasoningObserved !== true &&
1340
- m.role === "assistant" &&
1341
- Array.isArray(m.content) &&
1342
- m.content.some((c) => c?.type === "thinking")) {
1343
- prepared.callCapRef.reasoningObserved = true;
1344
- }
1345
- if (m.role === "assistant") {
1346
- const content = m.content;
1347
- if (rs.attach.attachState !== undefined)
1348
- rs.counters.cadenceTurns = advanceCadenceClock(rs.attach.attachState, rs.counters.cadenceTurns, content, writeFamilyOf);
1349
- rs.turn.lastTurnHadToolCalls = Array.isArray(content) && content.some((c) => c?.type === "toolCall");
1350
- }
1351
- rs.turn.callStartAt = undefined;
1352
- rs.turn.firstTokenAt = undefined;
1353
- rs.budget.callOutputChars = 0;
1354
- rs.budget.lastStreamBudgetCheck = 0;
1355
- };
1356
2054
  const toolLabels = new Map(prepared.tools.flatMap((t) => (t.label !== undefined && t.label !== t.name ? [[t.name, t.label]] : [])));
1357
2055
  const postToolBatchHook = (spec.hooks ?? this.deps.hooks)?.postToolBatch;
1358
2056
  const batchArgs = postToolBatchHook ? new Map() : undefined;
1359
- let toolBatch = [];
1360
- const onToolStart = (event) => {
1361
- const startAt = Date.now();
1362
- toolStartAt.set(event.toolCallId, startAt);
1363
- startedToolCallIds.add(event.toolCallId);
1364
- batchArgs?.set(event.toolCallId, event.args);
1365
- stats.toolCalls += 1;
1366
- if (spec.finalVerification === true && !(rs.counters.finalVerifyInjections === 0 ? rs.counters.groundingSignalPreR9 : rs.counters.groundingSignalPostR9) && hasVerifiableStructureSignal(event.args)) {
1367
- if (rs.counters.finalVerifyInjections === 0)
1368
- rs.counters.groundingSignalPreR9 = true;
1369
- else
1370
- rs.counters.groundingSignalPostR9 = true;
1371
- }
1372
- const activityArg = primaryActivityArg(event.args);
1373
- internals?.onActivity?.({ phase: "start", toolCallId: event.toolCallId, toolName: event.toolName, at: startAt, ...(activityArg !== undefined ? { arg: activityArg } : {}) });
1374
- pushContent({
1375
- type: "tool_start",
1376
- toolCallId: event.toolCallId,
1377
- toolName: event.toolName,
1378
- ...(toolLabels.get(event.toolName) !== undefined ? { label: toolLabels.get(event.toolName) } : {}),
1379
- args: event.args,
1380
- ...ident(),
1381
- });
1382
- };
1383
- const onToolEnd = (event) => {
1384
- const toolStarted = toolStartAt.get(event.toolCallId);
1385
- toolStartAt.delete(event.toolCallId);
1386
- const toolNow = Date.now();
1387
- if (prepared.callCapRef && toolStarted !== undefined) {
1388
- recordToolSample(prepared.callCapRef.state, toolNow - toolStarted);
1389
- turnToolSpan.startMin = turnToolSpan.startMin === undefined ? toolStarted : Math.min(turnToolSpan.startMin, toolStarted);
1390
- turnToolSpan.endMax = turnToolSpan.endMax === undefined ? toolNow : Math.max(turnToolSpan.endMax, toolNow);
1391
- }
1392
- if (!rs.counters.wroteThisRun && (prepared.toolEffects.get(event.toolName) ?? "write") !== "read") {
1393
- rs.counters.wroteThisRun = true;
1394
- }
1395
- if (rs.attach.attachState !== undefined && !event.isError) {
1396
- const det = event.result?.details;
1397
- if (det !== undefined)
1398
- reduceToolEnd(rs.attach.attachState, det);
1399
- }
1400
- if (postToolBatchHook !== undefined) {
1401
- if (prepared.blockedToolCalls.delete(event.toolCallId)) {
1402
- batchArgs?.delete(event.toolCallId);
1403
- }
1404
- else {
1405
- const digest = batchResponseDigest(event.result);
1406
- toolBatch.push({
1407
- toolName: event.toolName,
1408
- input: batchArgs?.get(event.toolCallId),
1409
- toolCallId: event.toolCallId,
1410
- ...(digest !== undefined ? { response: digest } : {}),
1411
- isError: event.isError,
1412
- });
1413
- batchArgs?.delete(event.toolCallId);
1414
- }
1415
- }
1416
- emitTrace(rs.telemetry.tracer, () => ({
1417
- kind: "tool.call",
1418
- version: 1,
1419
- taskId: rs.telemetry.taskId,
1420
- name: event.toolName,
1421
- durationMs: toolStarted !== undefined ? toolNow - toolStarted : 0,
1422
- ok: !event.isError,
1423
- ts: toolNow,
1424
- }));
1425
- internals?.onActivity?.({ phase: "end", toolCallId: event.toolCallId, toolName: event.toolName, isError: event.isError, at: toolNow });
1426
- if (prepared.lspDiagnostics && !event.isError) {
1427
- const name = canonicalToolName(event.toolName);
1428
- if (name === "Edit" || name === "Write" || name === "NotebookEdit") {
1429
- const d = event.result?.details;
1430
- const p = typeof d?.filePath === "string" ? d.filePath : typeof d?.notebookPath === "string" ? d.notebookPath : undefined;
1431
- if (p)
1432
- prepared.lspDiagnostics.nudge(p);
1433
- }
1434
- }
1435
- pushContent({
1436
- type: "tool_end",
1437
- toolCallId: event.toolCallId,
1438
- toolName: event.toolName,
1439
- ...(toolLabels.get(event.toolName) !== undefined ? { label: toolLabels.get(event.toolName) } : {}),
1440
- isError: event.isError,
1441
- ...(() => {
1442
- const o = toolOutputFrom(event.result);
1443
- return o !== undefined
1444
- ? { output: o.output, ...(o.truncated ? { truncated: true } : {}), ...(o.totalChars !== undefined ? { totalChars: o.totalChars } : {}) }
1445
- : {};
1446
- })(),
1447
- ...(() => {
1448
- const st = structuredFrom(event.result);
1449
- return st !== undefined ? { structured: st } : {};
1450
- })(),
1451
- ...ident(),
1452
- });
1453
- const cwdNow = prepared.cwdRef?.current;
1454
- if (cwdNow !== undefined && cwdNow !== lastWorkspaceCwd) {
1455
- lastWorkspaceCwd = cwdNow;
1456
- queue.push({ type: "workspace_changed", cwd: cwdNow, ...ident() });
1457
- }
1458
- if (spec.outputSchema && event.toolName === OUTPUT_TOOL_NAME) {
1459
- if (event.isError) {
1460
- rs.degrade.outputErrorStreak += 1;
1461
- if (rs.degrade.outputErrorStreak > rs.limits.outputRetryCap) {
1462
- rs.degrade.outputInvalid = true;
1463
- void prepared.harness.abort();
1464
- prepared.abortController.abort();
1465
- }
1466
- }
1467
- else {
1468
- rs.degrade.outputErrorStreak = 0;
1469
- }
1470
- }
1471
- };
1472
- const onTurnEnd = () => {
1473
- stats.turns += 1;
1474
- const stopExtra = rs.turn.turnStopReason !== undefined ? { stopReason: rs.turn.turnStopReason } : {};
1475
- queue.push(rs.turn.turnUsage
1476
- ? { type: "turn_end", usage: rs.turn.turnUsage, ...(rs.turn.turnUsageMissing ? { usageMissing: true } : {}), ...stopExtra, ...ident() }
1477
- : { type: "turn_end", usageMissing: true, ...stopExtra, ...ident() });
1478
- rs.turn.turnUsageMissing = false;
1479
- rs.turn.turnStopReason = undefined;
1480
- if (parentToolCallId !== undefined) {
1481
- const progress = {
1482
- type: "task_progress",
1483
- taskId: rs.telemetry.taskId,
1484
- ...(internals?.parentTaskId !== undefined ? { parentTaskId: internals.parentTaskId } : {}),
1485
- ...(subagentName ? { name: subagentName } : {}),
1486
- usage: { totalTokens: stats.tokens, toolUses: stats.toolCalls, durationMs: Date.now() - rs.telemetry.taskStart },
1487
- status: "running",
1488
- ...ident(),
1489
- };
1490
- queue.push(progress);
1491
- try {
1492
- internals?.onForwardEvent?.(progress);
1493
- }
1494
- catch {
1495
- }
1496
- }
1497
- emitTrace(rs.telemetry.tracer, () => ({ kind: "turn.end", version: 1, taskId: rs.telemetry.taskId, turn: stats.turns, ts: Date.now() }));
1498
- rs.turn.turnUsage = undefined;
1499
- rs.turn.callStartAt = undefined;
1500
- rs.turn.firstTokenAt = undefined;
1501
- rs.budget.callOutputChars = 0;
1502
- rs.budget.lastStreamBudgetCheck = 0;
1503
- toolStartAt.clear();
1504
- if (rs.limits.effectiveMaxTurns !== undefined && rs.limits.effectiveMaxTurns > 0 && stats.turns >= rs.limits.effectiveMaxTurns) {
1505
- rs.limits.turnsExceeded = true;
1506
- if (prepared.suspendForResource === undefined) {
1507
- void prepared.harness.abort();
1508
- prepared.abortController.abort();
1509
- }
1510
- }
1511
- else {
1512
- if (rs.degrade.degraded === undefined &&
1513
- spec.degrade &&
1514
- rs.degrade.degradeToModel &&
1515
- rs.budget.maxCostMicroUsd !== undefined &&
1516
- stats.costMicroUsd >= spec.degrade.atCostFraction * rs.budget.maxCostMicroUsd) {
1517
- void prepared.harness.setModel(rs.degrade.degradeToModel);
1518
- rs.degrade.recordDegraded({ from: prepared.model.id, to: rs.degrade.degradeToModel.id, reason: "budget", atTurn: stats.turns }, rs.degrade.degradeToModel);
1519
- if (prepared.callCapRef &&
1520
- spec.limits?.maxOutputTokens === undefined &&
1521
- rs.degrade.degradeToModel.maxTokens !== undefined &&
1522
- (prepared.callCapRef.staticCapTokens === undefined || rs.degrade.degradeToModel.maxTokens < prepared.callCapRef.staticCapTokens)) {
1523
- prepared.callCapRef.staticCapTokens = rs.degrade.degradeToModel.maxTokens;
1524
- }
1525
- }
1526
- if (!rs.limits.budgetHit && rs.budget.overBudget()) {
1527
- rs.limits.budgetHit = "exceeded";
1528
- if (prepared.suspendForResource === undefined) {
1529
- void prepared.harness.abort();
1530
- prepared.abortController.abort();
1531
- }
1532
- }
1533
- }
1534
- };
2057
+ rs.turn.toolBatch = [];
2058
+ const { onMessageUpdate, onMessageEnd, onToolStart, onToolEnd, onTurnEnd } = makeHarnessHandlers(prepared, stats, rs, {
2059
+ spec, queue, internals, buildFinalizeText, ident, nudgeSchedule, parentToolCallId, subagentName,
2060
+ pushContent, emitCommitted, startedToolCallIds, toolStartAt, turnToolSpan, writeFamilyOf,
2061
+ toolLabels, postToolBatchHook, batchArgs,
2062
+ runnerHooks: { onError: (e, info) => this.deps.onError?.(e, info) },
2063
+ });
1535
2064
  const recordCompactionUsage = (m, msg) => {
1536
2065
  const u = msg?.usage;
1537
2066
  if (!u)
@@ -1622,7 +2151,7 @@ export class Runner {
1622
2151
  (rs.counters.finalVerifyInjections === 0 || (rs.counters.finalVerifyInjections === 1 && rs.counters.groundingSignalPreR9 && !rs.counters.groundingSignalPostR9)) &&
1623
2152
  rs.counters.wroteThisRun &&
1624
2153
  prepared.outputRef.set !== true &&
1625
- !finalizeInjected &&
2154
+ !rs.counters.finalizeInjected &&
1626
2155
  !(rs.limits.effectiveMaxTurns !== undefined && rs.limits.effectiveMaxTurns > 0 && stats.turns >= rs.limits.effectiveMaxTurns - 1)) {
1627
2156
  rs.counters.finalVerifyInjections += 1;
1628
2157
  if (rs.counters.finalVerifyInjections === 2) {
@@ -1792,7 +2321,7 @@ export class Runner {
1792
2321
  this.recordCompactionReuse(prepared, comp);
1793
2322
  if ((comp.freedTokens ?? 0) >= COMPACTION_FREED_EPSILON) {
1794
2323
  const postSize = comp.postTriggerTokens ?? Math.max(0, (comp.triggerTokens ?? comp.tokensBefore ?? 0) - (comp.freedTokens ?? 0));
1795
- compactionFloor = Math.ceil(postSize * COMPACTION_REGROWTH_FACTOR);
2324
+ rs.counters.compactionFloor = Math.ceil(postSize * COMPACTION_REGROWTH_FACTOR);
1796
2325
  }
1797
2326
  queue.push({
1798
2327
  type: "compacted",
@@ -1834,507 +2363,23 @@ export class Runner {
1834
2363
  },
1835
2364
  });
1836
2365
  const rapidRefill = createRapidRefillState();
1837
- let compactionFloor = 0;
1838
- let trimForceBackoff = false;
1839
2366
  const drainManualCompact = (outcome) => {
1840
2367
  manualCompactRef.requested = false;
1841
2368
  for (const w of manualCompactRef.waiters.splice(0))
1842
2369
  w.resolve(outcome);
1843
2370
  };
1844
- const onTurnBoundary = async (event) => {
1845
- if (prepared.callCapRef && turnToolSpan.startMin !== undefined && turnToolSpan.endMax !== undefined) {
1846
- recordToolCycleSample(prepared.callCapRef.state, turnToolSpan.endMax - turnToolSpan.startMin);
1847
- }
1848
- turnToolSpan.startMin = undefined;
1849
- turnToolSpan.endMax = undefined;
1850
- let boundarySteered = false;
1851
- if (prepared.suspendForResource === undefined) {
1852
- const nowMs = Date.now();
1853
- if (!finalizeInjected &&
1854
- spec.limits?.gracefulFinalize !== false &&
1855
- walltimeDeadlineMs !== undefined &&
1856
- prepared.callCapRef !== undefined) {
1857
- const remainingMs = walltimeDeadlineMs - nowMs;
1858
- if (remainingMs > 0 && remainingMs < estimateCycleMs(prepared.callCapRef.state) + (effectiveCushionMs(prepared.callCapRef) ?? 0)) {
1859
- finalizeInjected = true;
1860
- prepared.callCapRef.finalizeMode = true;
1861
- void prepared.harness.setThinkingLevel("off").catch(() => { });
1862
- prepared.turnSnapshot?.refreshRequestPolicy({ thinkingLevel: "off" });
1863
- nudgeIdx = nudgeSchedule.length;
1864
- const finalizeText = buildFinalizeText();
1865
- void prepared.harness.steer(finalizeText, { engineMinted: true }).catch(() => { });
1866
- boundarySteered = true;
1867
- queue.push({ type: "steering_injected", source: "finalize", preview: finalizeText.slice(0, 220), ...ident() });
1868
- }
1869
- }
1870
- let due;
1871
- while (nudgeIdx < nudgeSchedule.length && nowMs >= nudgeSchedule[nudgeIdx].atMs) {
1872
- due = nudgeSchedule[nudgeIdx++];
1873
- }
1874
- if (due) {
1875
- void prepared.harness.steer(due.text, { engineMinted: true }).catch(() => { });
1876
- rs.counters.nudgesSent += 1;
1877
- boundarySteered = true;
1878
- queue.push({ type: "steering_injected", source: "deadline_nudge", preview: due.text.slice(0, 220), ...ident() });
1879
- }
1880
- }
1881
- const walltimeHit = walltimeMonotonicDeadline !== undefined && performance.now() >= walltimeMonotonicDeadline;
1882
- const preemptHit = spec.preemptSignal?.aborted === true;
1883
- if (preemptHit && !prepared.suspendForResource && !rs.counters.preemptIgnoredReported) {
1884
- rs.counters.preemptIgnoredReported = true;
1885
- emitTrace(rs.telemetry.tracer, () => ({ kind: "preempt.ignored", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() }));
1886
- }
1887
- if (prepared.suspendForResource &&
1888
- (rs.limits.turnsExceeded || rs.limits.budgetHit !== undefined || walltimeHit || preemptHit) &&
1889
- prepared.suspendRef.token === undefined) {
1890
- const reason = preemptHit
1891
- ? "preempt"
1892
- : rs.limits.budgetHit !== undefined
1893
- ? "budget"
1894
- : walltimeHit
1895
- ? "walltime"
1896
- : "turns";
1897
- const sliceSpend = { costMicroUsd: stats.costMicroUsd, tokens: stats.tokens, turns: stats.turns, walltimeMs: Math.round(performance.now() - rs.telemetry.taskStartMonotonic) };
1898
- if (await prepared.suspendForResource(reason, sliceSpend)) {
1899
- rs.limits.turnsExceeded = false;
1900
- rs.limits.budgetHit = undefined;
1901
- }
1902
- else {
1903
- void prepared.harness.abort();
1904
- prepared.abortController.abort();
1905
- }
1906
- return undefined;
1907
- }
1908
- if (walltimeMonotonicDeadline !== undefined &&
1909
- prepared.suspendForResource === undefined &&
1910
- performance.now() >= walltimeMonotonicDeadline) {
1911
- if (!timeout.fired) {
1912
- timeout.fired = true;
1913
- timeout.clear();
1914
- timeout.latenessMs = Math.max(timeout.latenessMs ?? 0, performance.now() - walltimeMonotonicDeadline);
1915
- walltimeSyncBackstopFired = true;
1916
- void prepared.harness.abort();
1917
- prepared.abortController.abort();
1918
- }
1919
- return undefined;
1920
- }
1921
- if (prepared.reviewRequestRef.pending !== undefined) {
1922
- const { reason } = prepared.reviewRequestRef.pending;
1923
- prepared.reviewRequestRef.pending = undefined;
1924
- const honorable = prepared.suspendForReview !== undefined &&
1925
- !prepared.abortController.signal.aborted &&
1926
- prepared.suspendRef.token === undefined &&
1927
- prepared.reviewRef.token === undefined;
1928
- if (honorable && prepared.suspendForReview) {
1929
- await prepared.suspendForReview(reason);
1930
- return undefined;
1931
- }
1932
- emitTrace(rs.telemetry.tracer, () => ({ kind: "review.dropped", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() }));
1933
- }
1934
- if (prepared.lspDiagnostics && !prepared.lspDiagnostics.registry.isEmpty() && !prepared.abortController.signal.aborted) {
1935
- const files = prepared.lspDiagnostics.registry.drain();
1936
- if (files.length > 0) {
1937
- queue.push({ type: "diagnostics", files, isNew: true, ...ident() });
1938
- const block = formatDiagnosticsBlock(files);
1939
- void prepared.harness.steer(block, { provenance: "engine-note" }).catch(() => undefined);
1940
- boundarySteered = true;
1941
- }
1942
- }
1943
- let boundaryAttachmentBytes = 0;
1944
- let attachmentsPayload;
1945
- if ((rs.attach.dateState !== undefined || rs.attach.instrState !== undefined || rs.attach.attachState !== undefined) &&
1946
- !boundarySteered &&
1947
- !finalizeInjected &&
1948
- rs.counters.finalVerifyInjections === 0 &&
1949
- !prepared.abortController.signal.aborted) {
1950
- const due = [];
1951
- if (rs.attach.attachState !== undefined) {
1952
- const changedFilesOn = Boolean(rs.attach.attachmentsCfg?.changedFiles);
1953
- let changed;
1954
- if (changedFilesOn && prepared.detectExternalChanges !== undefined) {
1955
- const maxFiles = typeof rs.attach.attachmentsCfg?.changedFiles === "object"
1956
- ? (rs.attach.attachmentsCfg.changedFiles.maxFiles ?? CHANGED_FILES_MAX)
1957
- : CHANGED_FILES_MAX;
1958
- const SCAN_CEILING_MS = 2_000;
1959
- const scan = await Promise.race([
1960
- prepared.detectExternalChanges(maxFiles),
1961
- new Promise((resolve) => {
1962
- const t = setTimeout(() => resolve({ changed: [], evicted: [] }), SCAN_CEILING_MS);
1963
- t.unref?.();
1964
- }),
1965
- ]);
1966
- for (const p of scan.evicted)
1967
- rs.attach.attachState.surfacedMtime.delete(p);
1968
- changed = scan.changed;
1969
- }
1970
- const backgroundTasksOn = rs.attach.attachmentsCfg?.backgroundTasks === true;
1971
- const bgTasks = backgroundTasksOn && rs.attach.attachState.postCompactPending ? prepared.listBackgroundTasks() : undefined;
1972
- const toolsDeltaOn = rs.attach.attachmentsCfg?.toolsDelta === true;
1973
- const pendingTools = toolsDeltaOn && prepared.toolsDeltaRef !== undefined && prepared.toolsDeltaRef.pending.length > 0
1974
- ? [...prepared.toolsDeltaRef.pending]
1975
- : undefined;
1976
- const mcpInstructionsOn = rs.attach.attachmentsCfg?.mcpInstructions === true;
1977
- const mcpDelta = prepared.mcp.instructionsDelta;
1978
- const mcpAdds = mcpInstructionsOn && mcpDelta.pendingAdds.length > 0 ? [...mcpDelta.pendingAdds] : undefined;
1979
- const mcpRemovals = mcpInstructionsOn && mcpDelta.pendingRemovals.length > 0 ? [...mcpDelta.pendingRemovals] : undefined;
1980
- const mcpDropped = mcpInstructionsOn && prepared.mcp.droppedTools.length > 0 ? selectMcpDroppedBatch(prepared.mcp.droppedTools) : undefined;
1981
- const budgetUsdOn = rs.attach.attachmentsCfg?.budgetUsd === true && rs.budget.maxCostMicroUsd !== undefined && rs.turn.lastTurnHadToolCalls;
1982
- due.push(...collectDueAttachments(rs.attach.attachState, {
1983
- turn: stats.turns,
1984
- cadenceTurn: rs.counters.cadenceTurns,
1985
- todoMounted: todoToolMounted,
1986
- taskListMounted: taskToolsMounted,
1987
- planActive: prepared.planModeRef.active,
1988
- config: {
1989
- todoReminder: rs.attach.attachmentsCfg?.todoReminder === true,
1990
- ...(rs.attach.attachmentsCfg?.todoReminderMode !== undefined ? { todoReminderMode: rs.attach.attachmentsCfg.todoReminderMode } : {}),
1991
- changedFiles: changedFilesOn,
1992
- planModeReminder: rs.attach.attachmentsCfg?.planModeReminder === true,
1993
- budgetUsd: budgetUsdOn,
1994
- backgroundTasks: backgroundTasksOn,
1995
- toolsDelta: toolsDeltaOn,
1996
- agentListing: rs.attach.agentListingOn,
1997
- skillsListing: rs.attach.skillsListingOn,
1998
- mcpInstructions: mcpInstructionsOn,
1999
- },
2000
- ...(changed !== undefined ? { changedFiles: changed } : {}),
2001
- ...(budgetUsdOn && rs.budget.maxCostMicroUsd !== undefined
2002
- ? { budgetUsd: { used: stats.costMicroUsd / 1e6, total: rs.budget.maxCostMicroUsd / 1e6 } }
2003
- : {}),
2004
- ...(bgTasks !== undefined ? { backgroundTasks: bgTasks } : {}),
2005
- ...(pendingTools !== undefined ? { newTools: pendingTools } : {}),
2006
- ...(rs.attach.agentListingOn && prepared.agentListing !== undefined
2007
- ? {
2008
- agentListing: prepared.agentListing.entries,
2009
- agentToolName: prepared.agentListing.toolName,
2010
- ...(prepared.agentListing.models !== undefined ? { agentModels: prepared.agentListing.models } : {}),
2011
- }
2012
- : {}),
2013
- ...(rs.attach.skillsListingOn && prepared.skillsListing !== undefined ? { skillsListing: prepared.skillsListing.entries } : {}),
2014
- ...(mcpAdds !== undefined || mcpRemovals !== undefined
2015
- ? { mcpInstructions: { added: mcpAdds ?? [], removed: mcpRemovals ?? [] } }
2016
- : {}),
2017
- ...(mcpDropped !== undefined ? { mcpDroppedTools: mcpDropped } : {}),
2018
- }));
2019
- if (pendingTools !== undefined && due.some((a) => a.source === "tools_delta" && a.body === renderToolsDelta(pendingTools))) {
2020
- prepared.toolsDeltaRef.pending.splice(0, pendingTools.length);
2021
- }
2022
- if ((mcpAdds !== undefined || mcpRemovals !== undefined) &&
2023
- due.some((a) => a.source === "mcp_instructions" && a.body === renderMcpInstructionsDelta(mcpAdds ?? [], mcpRemovals ?? []))) {
2024
- if (mcpAdds !== undefined)
2025
- mcpDelta.pendingAdds.splice(0, mcpAdds.length);
2026
- if (mcpRemovals !== undefined)
2027
- mcpDelta.pendingRemovals.splice(0, mcpRemovals.length);
2028
- }
2029
- if (mcpDropped !== undefined &&
2030
- due.some((a) => a.source === "mcp_dropped_tools" && a.body === renderMcpDroppedTools(mcpDropped))) {
2031
- prepared.mcp.droppedTools.splice(0, mcpDropped.length);
2032
- }
2033
- if (rs.attach.agentListingOn && prepared.agentListing !== undefined && due.some((a) => a.source === "agent_listing")) {
2034
- const listing = prepared.agentListing;
2035
- const intactBody = renderAgentListingDelta(rs.attach.attachState, listing.entries, listing.toolName, listing.models);
2036
- if (intactBody !== undefined && due.some((a) => a.source === "agent_listing" && a.body === intactBody)) {
2037
- commitAgentListing(rs.attach.attachState, listing.entries, listing.models);
2038
- prepared.announcedListingsRef.agents = listing.entries.map((e) => e.name);
2039
- if (listing.models !== undefined)
2040
- prepared.announcedListingsRef.models = [...listing.models];
2041
- await prepared.session.appendAnnouncedListing(prepared.announcedListingsRef).catch(() => undefined);
2042
- }
2043
- }
2044
- if (rs.attach.skillsListingOn && prepared.skillsListing !== undefined && due.some((a) => a.source === "skills_listing")) {
2045
- const listing = prepared.skillsListing;
2046
- const intactBody = renderSkillsListingDelta(rs.attach.attachState, listing.entries);
2047
- if (intactBody !== undefined && due.some((a) => a.source === "skills_listing" && a.body === intactBody)) {
2048
- commitSkillsListing(rs.attach.attachState, listing.entries);
2049
- prepared.announcedListingsRef.skills = listing.entries.map((e) => e.name);
2050
- await prepared.session.appendAnnouncedListing(prepared.announcedListingsRef).catch(() => undefined);
2051
- }
2052
- }
2053
- }
2054
- if (rs.attach.dateState !== undefined && rs.turn.lastTurnHadToolCalls) {
2055
- const today = prepared.dateChange.today();
2056
- const cand = collectDateChange(rs.attach.dateState, today);
2057
- if (cand !== undefined) {
2058
- due.unshift(cand);
2059
- rs.attach.dateState.announcedDate = today;
2060
- }
2061
- }
2062
- if (rs.attach.instrState !== undefined && rs.attach.instrProbe !== undefined && rs.turn.lastTurnHadToolCalls) {
2063
- let probed = null;
2064
- try {
2065
- const PROBE_CEILING_MS = 2_000;
2066
- probed = await Promise.race([
2067
- rs.attach.instrProbe(prepared.instructionSources),
2068
- new Promise((resolve) => {
2069
- const t = setTimeout(() => resolve(null), PROBE_CEILING_MS);
2070
- t.unref?.();
2071
- }),
2072
- ]);
2073
- }
2074
- catch {
2075
- probed = null;
2076
- }
2077
- const cand = collectInstructionsChange(rs.attach.instrState, probed);
2078
- if (cand !== undefined) {
2079
- due.splice(due.length > 0 && due[0].source === "date_change" ? 1 : 0, 0, cand.attachment);
2080
- commitInstructionsChange(rs.attach.instrState, cand.announced);
2081
- }
2082
- }
2083
- if (due.length > 0) {
2084
- attachmentsPayload = due.map((a) => `<system-reminder>\n${sanitizeUntrustedText(a.body)}\n</system-reminder>`).join("\n");
2085
- for (const a of due)
2086
- boundaryAttachmentBytes += Buffer.byteLength(a.body, "utf8");
2087
- attachmentsInjected += due.length;
2088
- for (const a of due) {
2089
- queue.push({ type: "steering_injected", source: a.source, preview: a.body.slice(0, 220), ...ident() });
2090
- }
2091
- }
2092
- }
2093
- let batchContextBlock;
2094
- if (postToolBatchHook !== undefined && toolBatch.length > 0) {
2095
- const batch = toolBatch;
2096
- toolBatch = [];
2097
- batchArgs?.clear();
2098
- if (!boundarySteered && !finalizeInjected && rs.counters.finalVerifyInjections === 0 && !prepared.abortController.signal.aborted) {
2099
- try {
2100
- const r = await postToolBatchHook(batch);
2101
- if (r?.additionalContext) {
2102
- const body = sanitizeUntrustedText(r.additionalContext);
2103
- const budget = ATTACHMENT_BYTE_CAP - boundaryAttachmentBytes;
2104
- if (budget >= BATCH_CONTEXT_MIN_KEEP_BYTES) {
2105
- const clipped = clipToByteBudget(body, budget) === body
2106
- ? body
2107
- : `${clipToByteBudget(body, budget - Buffer.byteLength(BATCH_TRUNCATION_MARKER, "utf8"))}${BATCH_TRUNCATION_MARKER}`;
2108
- batchContextBlock = `<system-reminder>\n${clipped}\n</system-reminder>`;
2109
- }
2110
- }
2111
- }
2112
- catch (err) {
2113
- try {
2114
- this.deps.onError?.(err instanceof Error ? err : new Error(String(err)), { phase: "hook", sessionId: prepared.sessionId });
2115
- }
2116
- catch {
2117
- }
2118
- }
2119
- }
2120
- }
2121
- if (attachmentsPayload !== undefined || batchContextBlock !== undefined) {
2122
- const payload = attachmentsPayload !== undefined && batchContextBlock !== undefined ? `${attachmentsPayload}\n${batchContextBlock}` : (attachmentsPayload ?? batchContextBlock);
2123
- void prepared.harness.steer(payload, { engineMinted: true }).catch(() => { });
2124
- }
2125
- const forceManual = manualCompactRef.requested;
2126
- if ((!withinTaskCompaction && !forceManual) ||
2127
- (finalizeInjected && !forceManual) ||
2128
- prepared.abortController.signal.aborted ||
2129
- prepared.suspendRef.token !== undefined ||
2130
- prepared.reviewRef.token !== undefined) {
2131
- if (forceManual) {
2132
- emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.mooted", version: 1, taskId: rs.telemetry.taskId, reason: "task_ending", ts: Date.now() }));
2133
- drainManualCompact("mooted");
2134
- }
2135
- return undefined;
2136
- }
2137
- if (compactionBreaker.failures >= MAX_CONSECUTIVE_COMPACTION_FAILURES) {
2138
- if (forceManual) {
2139
- emitTrace(rs.telemetry.tracer, () => ({
2140
- kind: "compaction.failed",
2141
- version: 1,
2142
- taskId: rs.telemetry.taskId,
2143
- trigger: "manual",
2144
- reason: "compaction circuit breaker open (3 consecutive failures this task) — manual request drained without an attempt",
2145
- ts: Date.now(),
2146
- }));
2147
- drainManualCompact("failed");
2148
- }
2149
- return undefined;
2150
- }
2151
- const claimedManual = forceManual
2152
- ? (() => {
2153
- const waiters = manualCompactRef.waiters.splice(0);
2154
- let instructions;
2155
- for (let i = waiters.length - 1; i >= 0; i--) {
2156
- if (waiters[i].instructions !== undefined) {
2157
- instructions = waiters[i].instructions;
2158
- break;
2159
- }
2160
- }
2161
- return { instructions, waiters };
2162
- })()
2163
- : undefined;
2164
- if (forceManual) {
2165
- manualCompactRef.requested = false;
2166
- }
2167
- const resolveClaimedWaiters = (outcome) => {
2168
- if (claimedManual !== undefined)
2169
- for (const w of claimedManual.waiters.splice(0))
2170
- w.resolve(outcome);
2171
- };
2172
- const claimedCancelSignals = claimedManual?.waiters.flatMap((w) => (w.signal !== undefined ? [w.signal] : [])) ?? [];
2173
- const manualCancelSignal = claimedCancelSignals.length === 0
2174
- ? undefined
2175
- : claimedCancelSignals.length === 1
2176
- ? claimedCancelSignals[0]
2177
- : AbortSignal.any(claimedCancelSignals);
2178
- const compactionPassSignal = claimedCancelSignals.length > 0
2179
- ? AbortSignal.any([prepared.abortController.signal, ...claimedCancelSignals])
2180
- : prepared.abortController.signal;
2181
- const trimPressureArmed = prepared.trimPressureRef.droppedMessages;
2182
- if (trimPressureArmed)
2183
- prepared.trimPressureRef.droppedMessages = false;
2184
- const trimPressure = trimPressureArmed && !trimForceBackoff;
2185
- let manualOutcome = "failed";
2186
- if (finalizeInjected && forceManual) {
2187
- emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.manual_in_finalize", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() }));
2188
- }
2189
- const manualInstructions = claimedManual?.instructions;
2190
- try {
2191
- const comp = await maybeCompact({
2192
- session: prepared.session,
2193
- epochDeclaredSections: prepared.epochDeclaredSections,
2194
- ...(() => {
2195
- const ca = prepared.centerCompactionCandidate?.();
2196
- return ca !== undefined ? { centerAdoption: ca } : {};
2197
- })(),
2198
- model: event.model,
2199
- compactionModel: prepared.compModel,
2200
- brain: compactionBrain,
2201
- getApiKeyAndHeaders: spec.getApiKeyAndHeaders,
2202
- thinking: prepared.thinking,
2203
- settings: { ...DEFAULT_COMPACTION_SETTINGS, ...spec.compaction },
2204
- customInstructions: manualInstructions ?? spec.compaction?.instructions ?? DEFAULT_COMPACTION_INSTRUCTIONS,
2205
- signal: compactionPassSignal,
2206
- ...(manualCancelSignal !== undefined ? { manualCancelSignal } : {}),
2207
- minTokens: forceManual ? 0 : compactionFloor,
2208
- force: forceManual,
2209
- onInputTruncated: (info) => emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.input_truncated", version: 1, taskId: rs.telemetry.taskId, label: info.label, droppedChars: info.droppedChars, keptChars: info.keptChars, ts: Date.now() })),
2210
- forceUnderThreshold: trimPressure,
2211
- overheadTokens: prepared.promptOverheadTokens,
2212
- workingFileAttachments: spec.compaction?.attachWorkingFiles !== false && prepared.readTaskFile
2213
- ? {
2214
- readFile: prepared.readTaskFile,
2215
- ...(prepared.recentlyReadFiles ? { recentlyReadFiles: prepared.recentlyReadFiles } : {}),
2216
- ...(prepared.normalizeAttachmentPath ? { normalizePath: prepared.normalizeAttachmentPath } : {}),
2217
- ...(prepared.isDedupStubResult ? { isDedupStubResult: prepared.isDedupStubResult } : {}),
2218
- ...(prepared.instructionSources
2219
- ? { instructionSourcePaths: prepared.instructionSources.filter((s) => s.contentHash !== null).map((s) => s.path) }
2220
- : {}),
2221
- ...(typeof spec.compaction?.attachWorkingFiles === "object" ? spec.compaction.attachWorkingFiles : undefined),
2222
- }
2223
- : undefined,
2224
- ...(prepared.onCompactionApplied ? { onApplied: prepared.onCompactionApplied } : {}),
2225
- ...this.seamCCompactionOptions(prepared),
2226
- ...windowSafetyOptions(event.model),
2227
- ...this.compactionHookOptions(spec, prepared.sessionId, trimPressure ? "forced" : forceManual ? "manual" : "auto"),
2228
- });
2229
- if (comp.blocked) {
2230
- emitTrace(rs.telemetry.tracer, () => ({
2231
- kind: "compaction.blocked",
2232
- version: 1,
2233
- taskId: rs.telemetry.taskId,
2234
- trigger: forceManual ? "manual" : "auto",
2235
- ts: Date.now(),
2236
- }));
2237
- }
2238
- else {
2239
- compactionBreaker.failures = 0;
2240
- }
2241
- trimForceBackoff = nextTrimForceBackoff(trimForceBackoff, comp, trimPressure);
2242
- if (comp.contextUsage) {
2243
- queue.push({ type: "context_usage", ...comp.contextUsage, ...ident() });
2244
- }
2245
- if (comp.suppressedByFloor) {
2246
- const s = comp.suppressedByFloor;
2247
- emitTrace(rs.telemetry.tracer, () => ({
2248
- kind: "compaction.suppressed",
2249
- version: 1,
2250
- taskId: rs.telemetry.taskId,
2251
- estTokens: s.estTokens,
2252
- floor: s.floor,
2253
- ts: Date.now(),
2254
- }));
2255
- }
2256
- manualOutcome = comp.compacted ? "compacted" : comp.blocked ? "blocked" : comp.disabled ? "disabled" : "noop";
2257
- if (forceManual && !comp.compacted && !comp.blocked) {
2258
- if (comp.disabled) {
2259
- emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.disabled", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() }));
2260
- }
2261
- else {
2262
- emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.noop", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() }));
2263
- }
2264
- }
2265
- if (comp.compacted && !forceManual) {
2266
- if (recordCompactionAndCheckRapidRefill(rapidRefill, stats.turns)) {
2267
- compactionBreaker.failures = MAX_CONSECUTIVE_COMPACTION_FAILURES;
2268
- this.deps.onError?.(new Error("compaction.rapid_refill: the context refilled within <3 turns of compaction 3 times in a row — compaction disabled for the rest of this task (thrash spiral; the transcript is dominated by incompressible content)"), { phase: "compaction", sessionId: prepared.sessionId });
2269
- }
2270
- }
2271
- this.recordCompactionReuse(prepared, comp);
2272
- if (comp.compacted) {
2273
- queue.push({
2274
- type: "compacted",
2275
- trigger: forceManual ? "manual" : "auto",
2276
- tokensBefore: comp.tokensBefore ?? 0,
2277
- ...(comp.postTriggerTokens !== undefined ? { tokensAfter: comp.postTriggerTokens } : {}),
2278
- ...(comp.triggerTokens !== undefined ? { triggerTokensBefore: comp.triggerTokens } : {}),
2279
- ...(comp.durationMs !== undefined ? { durationMs: comp.durationMs } : {}),
2280
- ...(comp.phaseDurations !== undefined ? { phaseDurations: comp.phaseDurations } : {}),
2281
- ...(comp.firstKeptEntryId !== undefined ? { preserved_segment: { firstKeptEntryId: comp.firstKeptEntryId } } : {}),
2282
- ...(comp.attachedFiles !== undefined ? { attachedFiles: comp.attachedFiles } : {}),
2283
- ...(comp.modelFallback ? { modelFallback: true } : {}),
2284
- ...(comp.fallbackReason !== undefined ? { fallbackReason: comp.fallbackReason } : {}),
2285
- ...(comp.clampedRatio !== undefined ? { clampedRatio: comp.clampedRatio } : {}),
2286
- ...(comp.clampReason !== undefined ? { clampReason: comp.clampReason } : {}),
2287
- });
2288
- if (comp.phaseDurations !== undefined && comp.durationMs !== undefined) {
2289
- const pd = comp.phaseDurations;
2290
- const pdDur = comp.durationMs;
2291
- emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.phase_timings", version: 1, taskId: rs.telemetry.taskId, ...pd, durationMs: pdDur, ts: Date.now() }));
2292
- }
2293
- prepared.cacheBreakDetector?.notifyCompaction();
2294
- if (rs.attach.attachState !== undefined) {
2295
- rs.attach.attachState.postCompactPending = true;
2296
- rebaseCadenceWindows(rs.attach.attachState, rs.counters.cadenceTurns);
2297
- }
2298
- if ((comp.freedTokens ?? 0) >= COMPACTION_FREED_EPSILON) {
2299
- const postSize = comp.postTriggerTokens ?? Math.max(0, (comp.triggerTokens ?? comp.tokensBefore ?? 0) - (comp.freedTokens ?? 0));
2300
- compactionFloor = Math.ceil(postSize * COMPACTION_REGROWTH_FACTOR);
2301
- }
2302
- }
2303
- }
2304
- catch (err) {
2305
- const manualCancelAbort = !prepared.abortController.signal.aborted && isCompactionManualCancel(err);
2306
- if (manualCancelAbort) {
2307
- emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.mooted", version: 1, taskId: rs.telemetry.taskId, reason: "cancelled", ts: Date.now() }));
2308
- manualOutcome = "mooted";
2309
- }
2310
- else if (!prepared.abortController.signal.aborted) {
2311
- const walltimeAbort = isCompactionWalltimeAbort(err);
2312
- if (!walltimeAbort)
2313
- compactionBreaker.failures += 1;
2314
- this.deps.onError?.(err, { phase: "compaction", sessionId: prepared.sessionId });
2315
- const msg = String(err instanceof Error ? err.message : err);
2316
- emitTrace(rs.telemetry.tracer, () => ({
2317
- kind: "compaction.failed",
2318
- version: 1,
2319
- taskId: rs.telemetry.taskId,
2320
- trigger: trimPressure ? "forced" : forceManual ? "manual" : "auto",
2321
- reason: walltimeAbort
2322
- ? "walltime soft-deadline abort (engine-initiated write-out protection; not counted toward the breaker)"
2323
- : msg.length > 512
2324
- ? `${msg.slice(0, 512)}…`
2325
- : msg,
2326
- ts: Date.now(),
2327
- }));
2328
- manualOutcome = "failed";
2329
- }
2330
- else {
2331
- manualOutcome = "mooted";
2332
- }
2333
- }
2334
- if (forceManual)
2335
- resolveClaimedWaiters(manualOutcome);
2336
- return undefined;
2337
- };
2371
+ const onTurnBoundary = makeTurnBoundary(prepared, stats, rs, {
2372
+ spec, queue, manualCompactRef, todoToolMounted, taskToolsMounted, turnToolSpan,
2373
+ walltimeDeadlineMs, walltimeMonotonicDeadline, nudgeSchedule, buildFinalizeText,
2374
+ timeout, ident, postToolBatchHook, batchArgs, compactionBrain, withinTaskCompaction,
2375
+ compactionBreaker, windowSafetyOptions, rapidRefill, drainManualCompact,
2376
+ runnerHooks: {
2377
+ onError: this.deps.onError,
2378
+ seamCCompactionOptions: (p) => this.seamCCompactionOptions(p),
2379
+ compactionHookOptions: (s, sid, trig) => this.compactionHookOptions(s, sid, trig),
2380
+ recordCompactionReuse: (p, c) => this.recordCompactionReuse(p, c),
2381
+ },
2382
+ });
2338
2383
  const unsubBoundary = prepared.harness.on("turn_boundary", onTurnBoundary);
2339
2384
  const unsub = prepared.harness.subscribe((event) => {
2340
2385
  switch (event.type) {
@@ -2497,7 +2542,7 @@ export class Runner {
2497
2542
  f.commit();
2498
2543
  queue.push({ type: "steering_injected", source: f.source, preview: f.body.slice(0, 220), ...ident() });
2499
2544
  }
2500
- attachmentsInjected += firstFrames.length;
2545
+ rs.attach.attachmentsInjected += firstFrames.length;
2501
2546
  if (firstFrames.length > 0)
2502
2547
  await prepared.session.appendAnnouncedListing(prepared.announcedListingsRef).catch(() => undefined);
2503
2548
  const enginePrefixChars = effectiveObjective.length - spec.objective.length;
@@ -2575,7 +2620,7 @@ export class Runner {
2575
2620
  }
2576
2621
  const comp = await this.finish(spec, prepared, {
2577
2622
  skipCompaction: durablyPaused || compactionBreaker.failures >= MAX_CONSECUTIVE_COMPACTION_FAILURES,
2578
- minTokens: compactionFloor,
2623
+ minTokens: rs.counters.compactionFloor,
2579
2624
  brain: compactionBrain,
2580
2625
  windowSafety: windowSafetyOptions(prepared.model),
2581
2626
  });
@@ -2642,9 +2687,9 @@ export class Runner {
2642
2687
  const toolCuts = prepared.callCapRef?.toolCuts ?? 0;
2643
2688
  const pendingCutTools = prepared.callCapRef?.pendingCutTools ?? 0;
2644
2689
  const toolCutKills = prepared.cutKills?.kills ?? 0;
2645
- if (finalizeInjected || rs.counters.nudgesSent > 0 || capShrinks > 0 || rs.counters.callCutoffs > 0 || toolClamps > 0 || toolCuts > 0 || toolCutKills > 0 || pendingCutTools > 0 || rs.counters.finalVerifyInjections > 0 || attachmentsInjected > 0 || rs.counters.repetitionCuts > 0 || rs.counters.repetitionSpared > 0) {
2690
+ 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) {
2646
2691
  stats.mechanisms = {
2647
- ...(finalizeInjected ? { finalizeInjected: true } : {}),
2692
+ ...(rs.counters.finalizeInjected ? { finalizeInjected: true } : {}),
2648
2693
  ...(rs.counters.nudgesSent > 0 ? { nudgesSent: rs.counters.nudgesSent } : {}),
2649
2694
  ...(capShrinks > 0 ? { capShrinks } : {}),
2650
2695
  ...(rs.counters.callCutoffs > 0 ? { callCutoffs: rs.counters.callCutoffs } : {}),
@@ -2654,7 +2699,7 @@ export class Runner {
2654
2699
  ...(pendingCutTools > 0 ? { pendingCutTools } : {}),
2655
2700
  ...(rs.counters.finalVerifyInjections > 0 ? { finalVerifyInjected: true } : {}),
2656
2701
  ...(rs.counters.finalVerifyInjections > 0 ? { finalVerifyInjections: rs.counters.finalVerifyInjections } : {}),
2657
- ...(attachmentsInjected > 0 ? { attachmentsInjected } : {}),
2702
+ ...(rs.attach.attachmentsInjected > 0 ? { attachmentsInjected: rs.attach.attachmentsInjected } : {}),
2658
2703
  ...(rs.counters.repetitionCuts > 0 ? { repetitionCuts: rs.counters.repetitionCuts } : {}),
2659
2704
  ...(rs.counters.repetitionSpared > 0 ? { repetitionSpared: rs.counters.repetitionSpared } : {}),
2660
2705
  ...(rs.counters.repetitionEvents.length > 0 ? { repetitionEvents: rs.counters.repetitionEvents } : {}),
@@ -2790,7 +2835,7 @@ export class Runner {
2790
2835
  ...(timeout.latenessMs !== undefined && timeout.latenessMs > TIMER_LATENESS_REPORT_MS
2791
2836
  ? { timerLatenessMs: timeout.latenessMs }
2792
2837
  : {}),
2793
- ...(walltimeSyncBackstopFired ? { walltimeSyncBackstop: true } : {}),
2838
+ ...(rs.counters.walltimeSyncBackstopFired ? { walltimeSyncBackstop: true } : {}),
2794
2839
  ts: Date.now(),
2795
2840
  }));
2796
2841
  setResult(result);