@sema-agent/core 7.11.0 → 7.11.2

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 (48) hide show
  1. package/CHANGELOG.md +39 -1
  2. package/dist/core/auto-mode-defaults.d.ts +13 -9
  3. package/dist/core/auto-mode-defaults.js +1 -1
  4. package/dist/core/governance-codes.d.ts +1 -0
  5. package/dist/core/governance-codes.js +1 -0
  6. package/dist/core/runner/compaction-knobs.d.ts +45 -0
  7. package/dist/core/runner/compaction-knobs.js +3 -0
  8. package/dist/core/runner/contracts.d.ts +78 -1
  9. package/dist/core/runner/prepare-caps-and-workflow.js +23 -8
  10. package/dist/core/runner/run-attachment-seats.d.ts +20 -0
  11. package/dist/core/runner/run-attachment-seats.js +187 -0
  12. package/dist/core/runner/run-brain-sinks.d.ts +29 -0
  13. package/dist/core/runner/run-brain-sinks.js +61 -0
  14. package/dist/core/runner/run-clock-and-content.d.ts +52 -0
  15. package/dist/core/runner/run-clock-and-content.js +26 -0
  16. package/dist/core/runner/run-compaction-machinery.d.ts +35 -0
  17. package/dist/core/runner/run-compaction-machinery.js +98 -0
  18. package/dist/core/runner/run-git-lane.d.ts +64 -0
  19. package/dist/core/runner/run-git-lane.js +102 -0
  20. package/dist/core/runner/run-identity-wiring.d.ts +96 -0
  21. package/dist/core/runner/run-identity-wiring.js +84 -0
  22. package/dist/core/runner/run-reasoning-seat.d.ts +27 -0
  23. package/dist/core/runner/run-reasoning-seat.js +48 -0
  24. package/dist/core/runner/run-recovery-lanes.d.ts +54 -0
  25. package/dist/core/runner/run-recovery-lanes.js +180 -0
  26. package/dist/core/runner/run-stop-and-final-verify.d.ts +32 -0
  27. package/dist/core/runner/run-stop-and-final-verify.js +159 -0
  28. package/dist/core/runner/run-telemetry-and-budget-seats.d.ts +38 -0
  29. package/dist/core/runner/run-telemetry-and-budget-seats.js +159 -0
  30. package/dist/core/runner/run-tool-mount-facts.d.ts +26 -0
  31. package/dist/core/runner/run-tool-mount-facts.js +58 -0
  32. package/dist/core/runner/run-turn-boundary.d.ts +0 -35
  33. package/dist/core/runner/run-turn-boundary.js +1 -3
  34. package/dist/core/runner/runtask.js +126 -1624
  35. package/dist/core/runner/stream-halt-verbs.d.ts +38 -0
  36. package/dist/core/runner/stream-halt-verbs.js +82 -0
  37. package/dist/core/runner/stream-lifecycle-verbs.d.ts +34 -0
  38. package/dist/core/runner/stream-lifecycle-verbs.js +126 -0
  39. package/dist/core/runner/stream-reap.d.ts +30 -0
  40. package/dist/core/runner/stream-reap.js +40 -0
  41. package/dist/core/runner/stream-settle-backstop.d.ts +38 -0
  42. package/dist/core/runner/stream-settle-backstop.js +113 -0
  43. package/dist/core/runner/stream-steer-verb.d.ts +30 -0
  44. package/dist/core/runner/stream-steer-verb.js +185 -0
  45. package/dist/tools/fs/bash-readonly-classifier.d.ts +5 -0
  46. package/dist/tools/fs/bash-readonly-classifier.js +1 -0
  47. package/dist/tools/fs/fs-bash.js +6 -4
  48. package/package.json +1 -1
@@ -1,28 +1,23 @@
1
- import { createSafeNotifier, observeThenableRejection } from "../safe-notify.js";
2
- import { mintSystemReminder, openSystemReminder } from "../reminder-mint.js";
3
- import { deliverDelegationLifecycle, deliverEngineNotice, undrainedUserInputNotices } from "../types.js";
4
- import { planRejectionClears, resolveTriggerWindow } from "../context-edit.js";
1
+ import { mintSystemReminder } from "../reminder-mint.js";
2
+ import { deliverEngineNotice, undrainedUserInputNotices } from "../types.js";
5
3
  import { AgentHarness, DEFAULT_COMPACTION_SETTINGS, isSyntheticApiErrorMessage, uuidv7 } from "../../internal/harness.js";
6
4
  import { snapshotActorAssertion } from "../../internal/llm.js";
7
- import { CheckpointError, remainingBudgetMicroUsd, readPendingSteerQueue, remainingTokens, resolveCheckpointStore, LEGACY_PENDING_STEER_INPUT_ID, MAX_STEER_INPUT_ID_CHARS, realApprovalOrgFact } from "../checkpoint-store.js";
8
- import { GIT_STATUS_ECHO_PREVIEW, stripGitStatusUnits } from "./git-status-frame.js";
5
+ import { CheckpointError, remainingBudgetMicroUsd, readPendingSteerQueue, resolveCheckpointStore, realApprovalOrgFact } from "../checkpoint-store.js";
6
+ import { GIT_STATUS_ECHO_PREVIEW } from "./git-status-frame.js";
9
7
  import { settleExecutionRecord } from "./execution-record.js";
10
- import { engineVersion } from "../version.js";
11
- import { CONFIG_CATALOG_VERSION, declarationReasons, resolveEffectiveConfig } from "../../config/catalog.js";
12
8
  import { eventDefaultOn } from "../../prompt-assembly/event-registry.js";
13
- import { DEFAULT_COMPACTION_INSTRUCTIONS, createRapidRefillState, maybeCompact, sanitizeCompactionSettings } from "../auto-compaction.js";
9
+ import { DEFAULT_COMPACTION_INSTRUCTIONS, createRapidRefillState, maybeCompact } from "../auto-compaction.js";
14
10
  import { ASK_USER_QUESTION_TOOL_NAME } from "../ask-question.js";
15
11
  import { boundInputHashOf } from "../canonical-json.js";
16
- import { computeCostMicroUsd, isModelPriced, malformedPricingField, modelCostToPricing } from "../pricing.js";
12
+ import { computeCostMicroUsd, modelCostToPricing } from "../pricing.js";
17
13
  import { emitTrace } from "../trace.js";
18
14
  import { ORG_ADJUDICATION_TIMEOUT_MS, settleOrgVerdictWithin } from "../permission-rule-org.js";
19
15
  import { emitTaskOutcome } from "../task-outcome.js";
20
16
  import { isDegenerateCutMessage } from "../../brain/terminal-cause.js";
21
17
  import { redactThenCut } from "../../agents/subagent-steps.js";
22
- import { resolveReasoning } from "../../brain/reasoning.js";
23
18
  import { adjudicateDerivedRoute, authCarrierFingerprint, fallbackToPrimaryNotice, normalizeBaseUrl, sameRouteIdentity } from "../../brain/route-adjudicator.js";
24
- import { runWithBrainTelemetry, runWithReasoningWireFacts, runWithStatusSink } from "../../brain/status-sink.js";
25
- import { expandTiers, resolveModel, resolveTaskModel } from "../roles.js";
19
+ import { runWithReasoningWireFacts } from "../../brain/status-sink.js";
20
+ import { expandTiers, resolveTaskModel } from "../roles.js";
26
21
  import { screenSwappableDeps } from "../swappable-deps.js";
27
22
  import { AutoModeBreakerLedger } from "../auto-mode.js";
28
23
  import { runSideQuery } from "../side-query.js";
@@ -30,11 +25,9 @@ import { generatePromptSuggestions } from "./prompt-suggestions.js";
30
25
  import { PushQueue } from "../push-queue.js";
31
26
  import { TtlSessionStore } from "../session-store.js";
32
27
  import { toImageContent } from "./image.js";
33
- import { SKILLS_LISTING_PROBE_HEADER, resolveOutputRetries } from "./synthetic-tools.js";
34
- import { cacheFamilyOf, usageCostMicroUsd } from "./usage-accounting.js";
35
28
  import { assembleResult, errorCodeOf } from "./assemble-result.js";
36
29
  import { amendTerminal, terminalProjection } from "./terminal-projection.js";
37
- import { AGENT_LISTING_REMOVED_HEADER, SKILLS_LISTING_DELTA_HEADER, SKILLS_LISTING_REMOVED_HEADER, agentListingDeltaHeader, attachmentEnvelopeTags, agentListingInitialHeader, replayAnnouncedListing, replayAnnouncedModels, commitAgentListing, commitSkillsListing, createAttachmentState, rebaseCadenceWindows, reduceToolEnd, renderAgentListingDelta, renderOrphanedBackgroundTasks, renderSkillsListingDelta, stampWriteAnchor } from "./turn-attachments.js";
30
+ import { attachmentEnvelopeTags, commitAgentListing, commitSkillsListing, reduceToolEnd, renderAgentListingDelta, renderOrphanedBackgroundTasks, renderSkillsListingDelta, stampWriteAnchor } from "./turn-attachments.js";
38
31
  import { buildWorkingFileAttachments, centerAdoptionOption, contextInstructionFilesOption, emitInputTruncated, forkContextOption } from "./compaction-call-options.js";
39
32
  import { effectiveDelegationFacts, prepareTask } from "./prepare-task.js";
40
33
  import { drainForwardedFramesBeforeDone, forwardsSubagentEvents } from "./prepare-run-refs.js";
@@ -46,7 +39,7 @@ import { settleTeardownLeg } from "./teardown-bounded.js";
46
39
  import { hasDestroy, isIsolated } from "../remote-env.js";
47
40
  import { hasBackgroundShell, sweepBackgroundShells } from "../background-shell.js";
48
41
  import { formatHookFeedback, hookSeatExpiredError, resolveHookTimeoutMs, runHookSeat } from "../hooks.js";
49
- import { buildHumanInputEvent, frameMidTurnUserInput, projectHumanInput } from "../human-input-projection.js";
42
+ import { buildHumanInputEvent, projectHumanInput } from "../human-input-projection.js";
50
43
  import { delimitUntrusted, inlineUntrusted, REVIEWER_NOTE_MAX_BODY, sanitizeUntrustedText } from "../untrusted-text.js";
51
44
  import { appendInterruptionMarker, reconcileInterruptedSession } from "../session-reconcile.js";
52
45
  import { RunnerSharedToolResultStore } from "../tool-result-store.js";
@@ -54,16 +47,17 @@ import { PAUSE_REGISTRY } from "../pause-registry.js";
54
47
  import { refuseOutOfContractDecision, toolPolicyNameSets } from "../tool-policy.js";
55
48
  import { mintGateOutcome } from "./gate-exit.js";
56
49
  import { defaultTaskRegistry } from "../task-registry.js";
57
- import { discloseDroppedPending, isDelegatedAgentTerminal, isSystemInjectionPriority, isTerminalTaskNotification, PendingSessionNotifications, renderTaskNotificationXml, SYSTEM_INJECTION_PRIORITIES, SystemInjectionQueue, taskNotificationDedupKey } from "../task-notification.js";
50
+ import { isDelegatedAgentTerminal, isTerminalTaskNotification, PendingSessionNotifications, renderTaskNotificationXml, SystemInjectionQueue, taskNotificationDedupKey } from "../task-notification.js";
58
51
  import { ToolDetachHub } from "../tool-detach.js";
59
52
  import { createPeerInboundChainRef, createPeerSelfRef } from "../../agents/peer-admission.js";
60
53
  import { createRunState } from "./initial-run-state.js";
61
- import { nextHumanInputSeq, sameAcceptedSteerInput } from "./steer-admission.js";
62
- import { reconciledToolEndBody, toolEndBodyFrom, toolResultMsg, writeFamilyOfCanonical } from "./tool-end-body.js";
54
+ import { nextHumanInputSeq } from "./steer-admission.js";
55
+ import { reconciledToolEndBody, toolEndBodyFrom, toolResultMsg } from "./tool-end-body.js";
63
56
  import { answerFaceForRedeemedCall, deepJsonEqual, DEFERRED_REISSUE, pendingContentAskCallId, resumeContinuation, resumeDecisionWasNegative } from "./decide-continuation.js";
64
- import { awaitChargeWithSlowDisclosure, DEFAULT_PRECALL_OUTPUT_TOKENS, discloseUnevaluableWindow, platformLimitTerminal, resolveMaxTurns, startTimeout, TIMER_LATENESS_REPORT_MS } from "./clock-and-limits.js";
57
+ import { awaitChargeWithSlowDisclosure, DEFAULT_PRECALL_OUTPUT_TOKENS, discloseUnevaluableWindow, platformLimitTerminal, TIMER_LATENESS_REPORT_MS } from "./clock-and-limits.js";
65
58
  import { gitRestateOption, resolveGitLegDelivery, wrapGitFrame } from "./git-leg-delivery.js";
66
- import { COMPACTION_FREED_EPSILON, COMPACTION_REGROWTH_FACTOR, MAX_CONSECUTIVE_COMPACTION_FAILURES, createTurnBoundary } from "./run-turn-boundary.js";
59
+ import { createTurnBoundary } from "./run-turn-boundary.js";
60
+ import { MAX_CONSECUTIVE_COMPACTION_FAILURES } from "./compaction-knobs.js";
67
61
  import { createHarnessHandlers } from "./run-harness-handlers.js";
68
62
  import { resumeAdmission } from "./resume-admission.js";
69
63
  import { resumeReviewOutcome } from "./resume-review-outcome.js";
@@ -73,7 +67,22 @@ import { resumeInternalsAndConfig } from "./resume-internals-and-config.js";
73
67
  import { resumeApply } from "./resume-apply.js";
74
68
  import { resumePreflight } from "./resume-preflight.js";
75
69
  import { resumeClaim } from "./resume-claim.js";
76
- const STOP_HOOK_BLOCK_CAP = 8;
70
+ import { streamSettleBackstop } from "./stream-settle-backstop.js";
71
+ import { streamReap } from "./stream-reap.js";
72
+ import { streamSteerVerb } from "./stream-steer-verb.js";
73
+ import { streamLifecycleVerbs } from "./stream-lifecycle-verbs.js";
74
+ import { streamHaltVerbs } from "./stream-halt-verbs.js";
75
+ import { runIdentityWiring } from "./run-identity-wiring.js";
76
+ import { runTelemetryAndBudgetSeats } from "./run-telemetry-and-budget-seats.js";
77
+ import { runAttachmentSeats } from "./run-attachment-seats.js";
78
+ import { runToolMountFacts } from "./run-tool-mount-facts.js";
79
+ import { runReasoningSeat } from "./run-reasoning-seat.js";
80
+ import { runClockAndContent } from "./run-clock-and-content.js";
81
+ import { runBrainSinks } from "./run-brain-sinks.js";
82
+ import { runCompactionMachinery } from "./run-compaction-machinery.js";
83
+ import { runStopAndFinalVerify } from "./run-stop-and-final-verify.js";
84
+ import { runRecoveryLanes } from "./run-recovery-lanes.js";
85
+ import { runGitLane } from "./run-git-lane.js";
77
86
  const ORG_DISCLOSURE_MAX_CHARS = 600;
78
87
  const SUGGESTIONS_DEFAULT_COUNT = 3;
79
88
  const SUGGESTIONS_MAX_COUNT = 8;
@@ -308,7 +317,6 @@ export class Runner {
308
317
  return Promise.race([p, timeout]).finally(() => clearTimeout(t));
309
318
  };
310
319
  let reapHandle;
311
- let steerChain = Promise.resolve();
312
320
  const acceptedSteerInputs = new Map();
313
321
  const notifyRef = {};
314
322
  const captureOptOutRef = {};
@@ -325,33 +333,48 @@ export class Runner {
325
333
  ? await this.acquireSessionLock(spec.sessionId)
326
334
  : undefined;
327
335
  try {
328
- await this.runLocked(spec, queue, (r) => {
329
- resultValue = r;
330
- const pcs = internals?.inheritedGate?.parentConstraints;
331
- if (resume !== undefined)
332
- this.locallyClaimedTokens.delete(resume.cp.token);
333
- const rToken = r.terminal.kind === "paused" ? r.terminal.token : undefined;
334
- if (resume !== undefined && rToken !== resume.cp.token) {
335
- this.parentConstraintRegistry.delete(resume.cp.token);
336
- this.suspendedEnvReaps.delete(resume.cp.token);
337
- }
338
- if (r.terminal.kind === "paused" && PAUSE_REGISTRY[r.terminal.gate.kind].taskStatus === "suspended" && pcs !== undefined && pcs.length > 0) {
339
- if (this.parentConstraintRegistry.size >= Runner.PARENT_CONSTRAINT_REGISTRY_CAP) {
340
- const oldest = this.parentConstraintRegistry.keys().next().value;
341
- if (oldest !== undefined)
342
- this.parentConstraintRegistry.delete(oldest);
336
+ await this.runLocked({
337
+ spec,
338
+ queue,
339
+ setResult: (r) => {
340
+ resultValue = r;
341
+ const pcs = internals?.inheritedGate?.parentConstraints;
342
+ if (resume !== undefined)
343
+ this.locallyClaimedTokens.delete(resume.cp.token);
344
+ const rToken = r.terminal.kind === "paused" ? r.terminal.token : undefined;
345
+ if (resume !== undefined && rToken !== resume.cp.token) {
346
+ this.parentConstraintRegistry.delete(resume.cp.token);
347
+ this.suspendedEnvReaps.delete(resume.cp.token);
343
348
  }
344
- this.parentConstraintRegistry.set(r.terminal.token, pcs);
345
- }
346
- }, (p) => {
347
- suggestionsDone = p;
348
- }, (h) => {
349
- handle = h;
350
- publishReady(h);
351
- }, (s) => {
352
- reapHandle = s;
353
- this.suspendedEnvReaps.set(s.token, s);
354
- }, manualCompactRef, taskIdRef, resume, { ...(internals ?? {}), detachHub }, notifyRef, captureOptOutRef, entryActor, entryTracer);
349
+ if (r.terminal.kind === "paused" && PAUSE_REGISTRY[r.terminal.gate.kind].taskStatus === "suspended" && pcs !== undefined && pcs.length > 0) {
350
+ if (this.parentConstraintRegistry.size >= Runner.PARENT_CONSTRAINT_REGISTRY_CAP) {
351
+ const oldest = this.parentConstraintRegistry.keys().next().value;
352
+ if (oldest !== undefined)
353
+ this.parentConstraintRegistry.delete(oldest);
354
+ }
355
+ this.parentConstraintRegistry.set(r.terminal.token, pcs);
356
+ }
357
+ },
358
+ onSuggestions: (p) => {
359
+ suggestionsDone = p;
360
+ },
361
+ onReady: (h) => {
362
+ handle = h;
363
+ publishReady(h);
364
+ },
365
+ onSuspend: (s) => {
366
+ reapHandle = s;
367
+ this.suspendedEnvReaps.set(s.token, s);
368
+ },
369
+ manualCompactRef,
370
+ taskIdRef,
371
+ resume,
372
+ internals: { ...(internals ?? {}), detachHub },
373
+ notifyRef,
374
+ captureOptOutRef,
375
+ entryActor,
376
+ entryTracer,
377
+ });
355
378
  }
356
379
  finally {
357
380
  releaseLock?.();
@@ -363,149 +386,29 @@ export class Runner {
363
386
  drainManualCompactWaiters("mooted");
364
387
  }
365
388
  })();
366
- const settled = run.catch(async (err) => {
367
- let code = errorCodeOf(err);
368
- const reopenReason = code === "resume.tool_unavailable" || code === "resume.tool_contract_mismatch" ? "tool_unavailable" : "env_failed";
369
- const reopenable = resume !== undefined && resume.pendingActionStarted !== true && reopenReason !== undefined && !(resumeDecisionWasNegative(resume) && resume.decisionDelivered === true);
370
- let reopenCommitted = false;
371
- let reopenStateUnknown = false;
372
- if (reopenable && resume?.onEnvRestoreFailed) {
373
- try {
374
- await resume.onEnvRestoreFailed(reopenReason);
375
- reopenCommitted = true;
376
- }
377
- catch (reopenErr) {
378
- this.parentConstraintRegistry.delete(resume.cp.token);
379
- const original = err instanceof Error ? err : new Error(String(err));
380
- const reopenMsg = reopenErr instanceof Error ? reopenErr.message : String(reopenErr);
381
- const definitive = errorCodeOf(reopenErr) === "checkpoint.reopen_failed";
382
- reopenStateUnknown = !definitive;
383
- err = Object.assign(new Error(definitive
384
- ? `${reopenMsg} (original resume failure: ${original.message})`
385
- : `checkpoint reopen threw mid-flight — state UNKNOWN, confirm via the checkpoint store before retrying: ${reopenMsg} (original resume failure: ${original.message})`, { cause: original }), { code: "checkpoint.reopen_failed" });
386
- code = "checkpoint.reopen_failed";
387
- }
388
- }
389
- if (resume)
390
- this.locallyClaimedTokens.delete(resume.cp.token);
391
- if (resume && !reopenCommitted) {
392
- this.parentConstraintRegistry.delete(resume.cp.token);
393
- await settleTeardownLeg(() => this.sessions.unpin?.(resume.cp.sessionId), "sessions.unpin (resume-failure leg)", (e) => this.deps.onError?.(e, { phase: "config", sessionId: resume.cp.sessionId }));
394
- if (!reopenStateUnknown) {
395
- const failedReap = this.suspendedEnvReaps.get(resume.cp.token);
396
- this.suspendedEnvReaps.delete(resume.cp.token);
397
- if (failedReap?.env !== undefined && hasDestroy(failedReap.env)) {
398
- const failedEnv = failedReap.env;
399
- await settleTeardownLeg(() => failedEnv.destroy(), "terminal resume-failure env destroy", (e) => this.deps.onError?.(e, { phase: "config", sessionId: resume.cp.sessionId }));
400
- }
401
- }
402
- }
403
- if (resultValue !== undefined) {
404
- try {
405
- this.deps.onError?.(err instanceof Error ? err : new Error(String(err)), { phase: "config", sessionId: resultValue.sessionId });
406
- }
407
- catch {
408
- }
409
- queue.close();
410
- return;
411
- }
412
- const remoteEnvFailure = (() => {
413
- let cur = err;
414
- for (let depth = 0; cur && typeof cur === "object" && depth < 8; depth++) {
415
- const note = cur.remoteEnvFailure;
416
- if (note !== undefined)
417
- return [note];
418
- cur = cur.cause;
419
- }
420
- return undefined;
421
- })();
422
- resultValue = {
423
- taskId: taskIdRef.current ?? "unknown",
424
- sessionId: taskIdRef.sessionId ?? "unknown",
425
- ...(taskIdRef.runId !== undefined ? { runId: taskIdRef.runId } : {}),
426
- terminal: { kind: "failed", ...(code !== undefined ? { code } : {}), message: err instanceof Error ? err.message : String(err) },
427
- result: "",
428
- ...(remoteEnvFailure !== undefined ? { remoteEnvFailures: remoteEnvFailure } : {}),
429
- ...(taskIdRef.effectiveMemoryScopes !== undefined ? { effectiveMemoryScopes: taskIdRef.effectiveMemoryScopes } : {}),
430
- ...(taskIdRef.effectiveReasoning !== undefined ? { effectiveReasoning: taskIdRef.effectiveReasoning } : {}),
431
- ...(() => {
432
- const observed = taskIdRef.editedFiles?.();
433
- return observed !== undefined && observed.length > 0 ? { editedFiles: observed } : {};
434
- })(),
435
- ...(() => {
436
- const hinted = err.retryAfterMs;
437
- return code === "memory.admission_required" && typeof hinted === "number" && Number.isFinite(hinted) && hinted > 0
438
- ? { retryAfterMs: hinted }
439
- : {};
440
- })(),
441
- stats: { turns: 0, tokens: 0, toolCalls: 0, cachedTokens: 0, costMicroUsd: 0 },
442
- };
443
- emitTrace(entryTracer.tracer, () => ({
444
- kind: "task.end",
445
- version: 1,
446
- taskId: taskIdRef.current ?? "unknown",
447
- ...(taskIdRef.runId !== undefined ? { runId: taskIdRef.runId } : {}),
448
- status: "failed",
449
- errorCode: code,
450
- turns: 0,
451
- tokens: 0,
452
- durationMs: Date.now() - runStartedAt,
453
- ts: Date.now(),
454
- }));
455
- const owedTerminal = taskIdRef.delegationTerminalOwed;
456
- if (owedTerminal !== undefined) {
457
- taskIdRef.delegationTerminalOwed = undefined;
458
- deliverDelegationLifecycle(this.deps.onDelegationLifecycle, { phase: "terminal", identity: owedTerminal, status: "failed", turns: 0, ...(code !== undefined ? { errorCode: code } : {}) }, createSafeNotifier({
459
- onError: (f) => console.warn(`[sema-core] ${f.site}: delegation-lifecycle observer threw (contained; further failures counted, not re-disclosed): ${f.error.message}`),
460
- }), "runtask.onDelegationLifecycle");
461
- }
462
- if (forwardsSubagentEvents(spec))
463
- await drainForwardedFramesBeforeDone();
464
- queue.push({ type: "done", result: resultValue });
465
- queue.close();
466
- });
389
+ const live = {
390
+ get resultValue() { return resultValue; },
391
+ set resultValue(v) { resultValue = v; },
392
+ get handle() { return handle; },
393
+ get reapHandle() { return reapHandle; },
394
+ };
395
+ const settled = run.catch(streamSettleBackstop({
396
+ spec, resume, queue, taskIdRef, entryTracer, runStartedAt, live, runner: this.depsSeat, sessions: this.sessions,
397
+ locallyClaimedTokens: this.locallyClaimedTokens, parentConstraintRegistry: this.parentConstraintRegistry, suspendedEnvReaps: this.suspendedEnvReaps,
398
+ }).onRunRejected);
467
399
  void settled.then(() => acceptedSteerInputs.clear(), () => acceptedSteerInputs.clear());
468
400
  const steeringError = (msg, code = "steering.not_running") => {
469
401
  const e = new Error(`cannot steer: ${msg}`);
470
402
  e.code = code;
471
403
  return e;
472
404
  };
473
- let destroyOnce;
474
- const reapSuspended = async () => {
475
- const rh = reapHandle;
476
- if (!rh)
477
- return;
478
- const reportErr = (err) => {
479
- try {
480
- this.deps.onError?.(err, { phase: "config", sessionId: rh.sessionId });
481
- }
482
- catch {
483
- }
484
- };
485
- let won;
486
- try {
487
- won = await rh.store.expire(rh.token, rh.scope);
488
- }
489
- catch (err) {
490
- reportErr(err);
491
- return;
492
- }
493
- if (!won) {
494
- if (!this.locallyClaimedTokens.has(rh.token))
495
- this.suspendedEnvReaps.delete(rh.token);
496
- return;
497
- }
498
- this.parentConstraintRegistry.delete(rh.token);
499
- this.suspendedEnvReaps.delete(rh.token);
500
- if (rh.env && hasDestroy(rh.env)) {
501
- try {
502
- await rh.env.destroy();
503
- }
504
- catch (err) {
505
- reportErr(err);
506
- }
507
- }
508
- };
405
+ const { reapSuspended } = streamReap({
406
+ live, runner: this.depsSeat,
407
+ locallyClaimedTokens: this.locallyClaimedTokens, parentConstraintRegistry: this.parentConstraintRegistry, suspendedEnvReaps: this.suspendedEnvReaps,
408
+ });
409
+ const { steer } = streamSteerVerb({ spec, internals, queue, acceptedSteerInputs, live, ready, orTimeout, readyTimeoutMs: READY_TIMEOUT_MS, steeringError, runner: this.depsSeat });
410
+ const { notify, optOutMemoryCapture, compact, detach, interrupt } = streamLifecycleVerbs({ live, ready, orTimeout, steeringError, notifyRef, captureOptOutRef, manualCompactRef, detachHub });
411
+ const { halt, destroy } = streamHaltVerbs({ spec, live, ready, orTimeout, readyTimeoutMs: READY_TIMEOUT_MS, run, reapSuspended, runner: this.depsSeat });
509
412
  return {
510
413
  [Symbol.asyncIterator]: () => queue[Symbol.asyncIterator](),
511
414
  result: async () => {
@@ -516,377 +419,18 @@ export class Runner {
516
419
  await run.catch(() => { });
517
420
  return suggestionsDone.catch(() => []);
518
421
  },
519
- steer: async (text, options) => {
520
- const trusted = options?.trusted ? true : false;
521
- if (trusted && sanitizeUntrustedText(text) !== text) {
522
- throw steeringError("trusted steering text must not contain a </system-reminder> tag", "steering.invalid_content");
523
- }
524
- const inputId = options?.inputId;
525
- if (inputId !== undefined) {
526
- if (typeof inputId !== "string") {
527
- throw steeringError("inputId must be a string when supplied", "steering.invalid_content");
528
- }
529
- if (inputId === "" || inputId.length > MAX_STEER_INPUT_ID_CHARS) {
530
- throw steeringError(`inputId must be a non-empty string of at most ${MAX_STEER_INPUT_ID_CHARS} characters`, "steering.invalid_content");
531
- }
532
- if (inputId === LEGACY_PENDING_STEER_INPUT_ID) {
533
- throw steeringError(`inputId "${LEGACY_PENDING_STEER_INPUT_ID}" is reserved for a pre-queue parked steer and cannot be supplied by a caller`, "steering.invalid_content");
534
- }
535
- }
536
- const priorityIn = options?.priority;
537
- if (priorityIn !== undefined && !isSystemInjectionPriority(priorityIn)) {
538
- throw steeringError(`priority must be one of ${SYSTEM_INJECTION_PRIORITIES.join("/")} when supplied`, "steering.invalid_content");
539
- }
540
- const priority = priorityIn ?? "next";
541
- const actorIn = options?.actor;
542
- const actor = actorIn === undefined ? undefined : snapshotActorAssertion(actorIn);
543
- const projected = projectHumanInput({ text, actor, source: "steer" });
544
- const effectiveInputId = typeof inputId === "string" ? inputId : uuidv7();
545
- const parkRecord = {
546
- text,
547
- trusted,
548
- inputId: effectiveInputId,
549
- ...(priorityIn !== undefined ? { priority } : {}),
550
- ...(actor !== undefined ? { actor } : {}),
551
- };
552
- let payload;
553
- let mintsAFrame;
554
- let replay;
555
- const noteAccepted = (h) => {
556
- if (!mintsAFrame)
557
- return;
558
- if (typeof inputId === "string")
559
- acceptedSteerInputs.set(inputId, replay);
560
- queue.push({
561
- ...buildHumanInputEvent({
562
- carrier: "steer",
563
- source: "steer",
564
- delivery: "queued",
565
- sessionSeq: nextHumanInputSeq(h.harness),
566
- inputId: effectiveInputId,
567
- ...(actor !== undefined ? { actor } : {}),
568
- ...(actor?.issuer !== undefined ? { issuer: actor.issuer } : {}),
569
- ...(spec.principal !== undefined ? { principal: spec.principal } : {}),
570
- }),
571
- eventId: uuidv7(),
572
- ...(internals?.parentToolCallId !== undefined
573
- ? { parentToolCallId: internals.parentToolCallId, ...(spec.taskId !== undefined ? { sourceTaskId: spec.taskId } : {}) }
574
- : {}),
575
- });
576
- };
577
- const deliver = async () => {
578
- if (resultValue)
579
- throw steeringError("the task has already finished");
580
- const h = handle ?? (await orTimeout(ready));
581
- if (!h)
582
- throw steeringError("the task is not running");
583
- payload = trusted ? formatHookFeedback(projected, h.reminderMark) : frameMidTurnUserInput(projected);
584
- mintsAFrame = payload.trim().length !== 0;
585
- replay = { payload, trusted, priority, ...(actor !== undefined ? { actor } : {}) };
586
- if (typeof inputId === "string") {
587
- const prior = acceptedSteerInputs.get(inputId);
588
- if (prior !== undefined) {
589
- if (resultValue !== undefined || h.loop.ended)
590
- throw steeringError("the task is no longer running");
591
- if (!sameAcceptedSteerInput(prior, replay)) {
592
- throw steeringError("a different steering instruction was already accepted under this inputId — re-issue this one with a fresh inputId " +
593
- "(an identical payload would have been an idempotent retry)", "steering.duplicate_input_id");
594
- }
595
- return;
596
- }
597
- }
598
- if (resultValue !== undefined || h.loop.ended)
599
- throw steeringError("the task is no longer running");
600
- if (mintsAFrame) {
601
- const screen = (spec.hooks ?? this.deps.hooks)?.userPromptSubmit;
602
- if (screen !== undefined) {
603
- let decision;
604
- try {
605
- const seat = await runHookSeat("userPromptSubmit", { timeoutMs: h.hookTimeoutMs, signal: h.abortController.signal, abortEnds: true }, (sig) => screen(text, { identity: h.hookIdentity, signal: sig, source: "steer", inputId: effectiveInputId, ...(actor !== undefined ? { actor: snapshotActorAssertion(actor) } : {}) }));
606
- if (seat.expired) {
607
- if (seat.cause === "timeout") {
608
- try {
609
- this.deps.onError?.(hookSeatExpiredError("userPromptSubmit", h.hookTimeoutMs, seat.cause, "the steering input was NOT accepted (fail-closed) and the caller was refused typed"), { phase: "hook", sessionId: h.sessionId });
610
- }
611
- catch {
612
- }
613
- }
614
- throw steeringError(seat.cause === "timeout"
615
- ? `the deployment's userPromptSubmit hook did not answer within its ${h.hookTimeoutMs}ms bound while screening this steering input; the input was NOT accepted (fail-closed)`
616
- : `the task was cancelled while the deployment's userPromptSubmit hook was still screening this steering input; the input was NOT accepted (fail-closed)`, "steering.blocked_by_hook");
617
- }
618
- decision = seat.value;
619
- }
620
- catch (hookErr) {
621
- if (hookErr instanceof Error && hookErr.code === "steering.blocked_by_hook")
622
- throw hookErr;
623
- const err = hookErr instanceof Error ? hookErr : new Error(String(hookErr));
624
- try {
625
- this.deps.onError?.(err, { phase: "hook", sessionId: h.sessionId });
626
- }
627
- catch {
628
- }
629
- throw steeringError(`the deployment's userPromptSubmit hook crashed while screening this steering input (${inlineUntrusted(err.message)}); the input was NOT accepted (fail-closed)`, "steering.blocked_by_hook");
630
- }
631
- if (decision?.block !== undefined && decision.block !== "") {
632
- throw steeringError(`the deployment's userPromptSubmit hook blocked this steering input: ${inlineUntrusted(decision.block)}`, "steering.blocked_by_hook");
633
- }
634
- if (decision?.additionalContext !== undefined && decision.additionalContext !== "") {
635
- payload = `${formatHookFeedback(decision.additionalContext, h.reminderMark)}\n\n${payload}`;
636
- }
637
- }
638
- }
639
- const injectFramed = async () => {
640
- const noteOptions = { provenance: "engine-note", callerAuthored: true, parkRecord, ...(actor !== undefined ? { actor } : {}) };
641
- if (priority === "later") {
642
- await h.harness.followUp(payload, noteOptions);
643
- noteAccepted(h);
644
- return;
645
- }
646
- const frame = await h.harness.steer(payload, { ...noteOptions, ...(priority === "now" ? { immediate: true } : {}) });
647
- noteAccepted(h);
648
- if (priority === "now" && frame !== undefined && h.harness.interruptTurn(frame)) {
649
- deliverEngineNotice(this.deps.onNotice, {
650
- code: "task.turn_interrupted",
651
- message: "a caller-provenance steer with priority \"now\" interrupted the running turn: in-flight work was cut at " +
652
- "a manufactured boundary (finished tool calls keep their real results; never-started ones settle as " +
653
- "interrupted) and the run continues with the steer at the queue head.",
654
- detail: {
655
- inputId: effectiveInputId,
656
- sessionId: h.sessionId,
657
- runId: h.runId,
658
- ...(actor?.id !== undefined ? { actorId: actor.id } : {}),
659
- ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}),
660
- },
661
- });
662
- }
663
- };
664
- try {
665
- await injectFramed();
666
- return;
667
- }
668
- catch (e) {
669
- if (!(e instanceof Error && e.code === "invalid_state"))
670
- throw e;
671
- }
672
- const birthDeadline = Date.now() + READY_TIMEOUT_MS;
673
- while (resultValue === undefined && !h.loop.ended && Date.now() < birthDeadline) {
674
- try {
675
- await injectFramed();
676
- return;
677
- }
678
- catch (e2) {
679
- if (!(e2 instanceof Error && e2.code === "invalid_state"))
680
- throw e2;
681
- }
682
- await new Promise((r) => setTimeout(r, 10));
683
- }
684
- throw steeringError("the task is no longer running");
685
- };
686
- const p = steerChain.then(deliver);
687
- steerChain = p.then(() => undefined, () => undefined);
688
- return p;
689
- },
690
- notify: async (input, opts) => {
691
- const notifyError = (msg, code) => {
692
- const e = new Error(`cannot notify: ${msg}`);
693
- e.code = code;
694
- return e;
695
- };
696
- if (typeof input?.task_id !== "string" || input.task_id.trim() === "") {
697
- throw notifyError("input.task_id must be a non-empty string", "notify.invalid_payload");
698
- }
699
- const VALID_STATUSES = new Set(["completed", "failed", "killed", "cancelled", "event"]);
700
- if (!VALID_STATUSES.has(input.status)) {
701
- throw notifyError(`input.status must be one of ${[...VALID_STATUSES].join("/")}`, "notify.invalid_payload");
702
- }
703
- if (typeof input.summary !== "string" || input.summary.trim() === "") {
704
- throw notifyError("input.summary must be a non-empty string", "notify.invalid_payload");
705
- }
706
- if (input.result !== undefined && typeof input.result !== "string") {
707
- throw notifyError("input.result must be a string when present", "notify.invalid_payload");
708
- }
709
- if (input.seq !== undefined && (typeof input.seq !== "number" || !Number.isFinite(input.seq))) {
710
- throw notifyError("input.seq must be a finite number when present", "notify.invalid_payload");
711
- }
712
- if (input.source !== undefined && typeof input.source !== "string") {
713
- throw notifyError("input.source must be a string when present", "notify.invalid_payload");
714
- }
715
- const priorityIn = opts?.priority;
716
- if (priorityIn !== undefined && !isSystemInjectionPriority(priorityIn)) {
717
- throw notifyError(`opts.priority must be one of ${SYSTEM_INJECTION_PRIORITIES.join("/")} when present`, "notify.invalid_payload");
718
- }
719
- const priority = priorityIn;
720
- if (priority === "now") {
721
- throw notifyError('priority "now" (turn interrupt) is not a notification-lane power — it belongs to the steer face; use "next" for earliest-boundary delivery', "notify.invalid_priority");
722
- }
723
- const payload = {
724
- task_id: input.task_id,
725
- task_type: "external",
726
- status: input.status,
727
- summary: input.summary,
728
- ...(input.result !== undefined ? { result: input.result } : {}),
729
- ...(input.seq !== undefined ? { seq: input.seq } : {}),
730
- ...(input.source !== undefined ? { source: input.source } : {}),
731
- };
732
- const h = handle ?? (await orTimeout(ready));
733
- if (!h || notifyRef.inject === undefined) {
734
- throw notifyError("the task is not running", "notify.not_running");
735
- }
736
- notifyRef.inject(payload, priority !== undefined ? { priority } : undefined);
737
- },
738
- optOutMemoryCapture: async (options) => {
739
- const reasonIn = options?.reason;
740
- if (reasonIn !== undefined && typeof reasonIn !== "string") {
741
- const e = new Error(`cannot opt out of memory capture: options.reason must be a string when present`);
742
- e.code = "steering.invalid_content";
743
- throw e;
744
- }
745
- const reason = reasonIn;
746
- if (resultValue)
747
- throw steeringError("the task has already finished");
748
- const h = handle ?? (await orTimeout(ready));
749
- if (!h)
750
- throw steeringError("the task is not running");
751
- if (captureOptOutRef.flip === undefined) {
752
- const e = new Error(`cannot opt out of memory capture: this run mounted no memory session (no memory spec / no backend / prepare degraded memory-less) — there is nothing to opt out of`);
753
- e.code = "memory.capture_optout_unavailable";
754
- throw e;
755
- }
756
- return captureOptOutRef.flip(reason);
757
- },
758
- compact: async (opts) => {
759
- if (resultValue)
760
- throw steeringError("the task has already finished");
761
- if (opts?.signal?.aborted)
762
- return "mooted";
763
- const h = handle ?? (await orTimeout(ready));
764
- if (!h)
765
- throw steeringError("the task is not running");
766
- if (opts?.signal?.aborted)
767
- return "mooted";
768
- if (manualCompactRef.closed)
769
- return "mooted";
770
- return new Promise((resolve) => {
771
- const signal = opts?.signal;
772
- const entry = {
773
- resolve,
774
- ...(signal !== undefined ? { signal } : {}),
775
- ...(opts?.instructions !== undefined ? { instructions: opts.instructions } : {}),
776
- };
777
- if (signal !== undefined) {
778
- const onAbort = () => {
779
- const i = manualCompactRef.waiters.indexOf(entry);
780
- if (i >= 0) {
781
- manualCompactRef.waiters.splice(i, 1);
782
- if (manualCompactRef.waiters.length === 0) {
783
- manualCompactRef.requested = false;
784
- }
785
- manualCompactRef.emitMooted?.("cancelled");
786
- entry.resolve("mooted");
787
- }
788
- };
789
- entry.resolve = (outcome) => {
790
- signal.removeEventListener("abort", onAbort);
791
- resolve(outcome);
792
- };
793
- signal.addEventListener("abort", onAbort, { once: true });
794
- }
795
- manualCompactRef.requested = true;
796
- manualCompactRef.waiters.push(entry);
797
- });
798
- },
799
- detach: (toolCallId) => {
800
- detachHub.request(toolCallId);
801
- },
802
- interrupt: async () => {
803
- const h = handle ?? (await orTimeout(ready));
804
- if (!h)
805
- return;
806
- if (!h.abortController.signal.aborted)
807
- h.loop.userInterrupted = true;
808
- h.abortController.abort();
809
- void h.harness.abort();
810
- },
811
- halt: async () => {
812
- const haltRefused = (msg) => {
813
- const e = new Error(`cannot halt: ${msg}`);
814
- e.code = "steering.not_running";
815
- return e;
816
- };
817
- if (resultValue)
818
- throw haltRefused("the task has already finished");
819
- const h = handle ?? (await orTimeout(ready));
820
- if (!h)
821
- throw haltRefused("the task is not running");
822
- const apply = () => {
823
- const abortOwnedBeforeHalt = h.abortController.signal.aborted;
824
- const receipt = h.harness.halt();
825
- if (receipt.accepted && !abortOwnedBeforeHalt) {
826
- h.loop.userHalted = true;
827
- }
828
- else {
829
- deliverEngineNotice(this.deps.onNotice, {
830
- code: "task.halt_unconsumed",
831
- message: "a user halt arrived while the run was already ending for its own reason: nothing was cut or stopped " +
832
- "by it — the run's own ending stands, and the result will not carry haltedByUser for this halt.",
833
- detail: {
834
- sessionId: h.sessionId,
835
- runId: h.runId,
836
- ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}),
837
- },
838
- });
839
- }
840
- if (receipt.turnCut) {
841
- deliverEngineNotice(this.deps.onNotice, {
842
- code: "task.turn_interrupted",
843
- message: "a bare user halt cut the running turn: in-flight work was cut at a manufactured boundary " +
844
- "(finished tool calls keep their real results; never-started ones settle as interrupted) and " +
845
- "the run is collecting to a clean, resumable stop — no further model turn will start.",
846
- detail: {
847
- cause: "user_halt",
848
- sessionId: h.sessionId,
849
- runId: h.runId,
850
- ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}),
851
- },
852
- });
853
- }
854
- return { turnCut: receipt.turnCut };
855
- };
856
- try {
857
- if (resultValue !== undefined || h.loop.ended)
858
- throw haltRefused("the task is no longer running");
859
- return apply();
860
- }
861
- catch (e) {
862
- if (!(e instanceof Error && e.code === "invalid_state"))
863
- throw e;
864
- }
865
- const birthDeadline = Date.now() + READY_TIMEOUT_MS;
866
- while (resultValue === undefined && !h.loop.ended && Date.now() < birthDeadline) {
867
- try {
868
- return apply();
869
- }
870
- catch (e2) {
871
- if (!(e2 instanceof Error && e2.code === "invalid_state"))
872
- throw e2;
873
- }
874
- await new Promise((r) => setTimeout(r, 10));
875
- }
876
- throw haltRefused("the task is no longer running");
877
- },
878
- destroy: () => (destroyOnce ??= (async () => {
879
- const h = handle ?? (await orTimeout(ready));
880
- if (h) {
881
- h.abortController.abort();
882
- void h.harness.abort();
883
- }
884
- await orTimeout(run.catch(() => { }));
885
- await reapSuspended();
886
- })()),
422
+ steer,
423
+ notify,
424
+ optOutMemoryCapture,
425
+ compact,
426
+ detach,
427
+ interrupt,
428
+ halt,
429
+ destroy,
887
430
  };
888
431
  }
889
- async runLocked(spec, queue, setResult, onSuggestions, onReady, onSuspend, manualCompactRef, taskIdRef, resume, internals, notifyRef, captureOptOutRef, entryActor, entryTracer) {
432
+ async runLocked(input) {
433
+ const { spec, queue, setResult, onSuggestions, onReady, onSuspend, manualCompactRef, taskIdRef, resume, internals, notifyRef, captureOptOutRef, entryActor, entryTracer } = input;
890
434
  const prepareResume = resume
891
435
  ? {
892
436
  leafId: resume.cp.leafId,
@@ -1001,577 +545,26 @@ export class Runner {
1001
545
  injectTaskNotification(notification, opts);
1002
546
  },
1003
547
  }, this, taskIdRef);
1004
- notificationHarness = prepared.harness;
1005
- notificationSessionId = prepared.sessionId;
1006
- if (taskIdRef)
1007
- taskIdRef.effectiveMemoryScopes = prepared.effectiveMemoryScopes;
1008
- if (taskIdRef)
1009
- taskIdRef.editedFiles = prepared.editedFilesSnapshot;
1010
- const runSourceTaskId = spec.taskId ?? prepared.sessionId;
1011
- const parentToolCallId = internals?.parentToolCallId;
1012
- const ident = () => parentToolCallId !== undefined ? { eventId: uuidv7(), parentToolCallId, sourceTaskId: runSourceTaskId } : { eventId: uuidv7() };
1013
- notificationIdent = ident;
1014
- queue.push({ type: "wiring_manifest", manifest: prepared.wiringManifest, ...ident() });
1015
- prepared.toolRosterDeltas.subscribe((delta) => queue.push({ type: "tool_roster_delta", delta, ...ident() }));
1016
- const delegationLifecycleNotifier = createSafeNotifier({
1017
- onError: (f) => console.warn(`[sema-core] ${f.site}: delegation-lifecycle observer threw (contained; further failures counted, not re-disclosed): ${f.error.message}`),
1018
- });
1019
- const emitDelegationLifecycle = (event) => {
1020
- if (!prepared.hookIdentity.isDelegatedChild)
1021
- return;
1022
- if (this.deps.onDelegationLifecycle === undefined)
1023
- return;
1024
- deliverDelegationLifecycle(this.deps.onDelegationLifecycle, event, delegationLifecycleNotifier, "runtask.onDelegationLifecycle");
1025
- };
1026
- emitDelegationLifecycle({ phase: "spawn", identity: prepared.hookIdentity });
1027
- if (taskIdRef !== undefined && prepared.hookIdentity.isDelegatedChild && this.deps.onDelegationLifecycle !== undefined) {
1028
- taskIdRef.delegationTerminalOwed = prepared.hookIdentity;
1029
- }
1030
- manualCompactRef.emitMooted = (reason) => {
1031
- queue.push({ type: "compaction_outcome", outcome: "mooted", trigger: "manual", reason, ...ident() });
1032
- };
1033
- prepared.harness.onUndrainedEngineNotes = (payloads) => {
1034
- if (notificationSessionId === undefined)
1035
- return;
1036
- for (const p of payloads)
1037
- this.pendingSessionNotifications.pend(notificationSessionId, p);
1038
- };
1039
- prepared.harness.engineInjectionsHeld = () => prepared.batchHaltRef.current !== undefined;
1040
548
  let undrainedUserAtEnd;
1041
- prepared.harness.onUndrainedUserInputs = (counts) => {
1042
- if (prepared.pausedRef.current !== undefined) {
1043
- undrainedUserAtEnd = counts;
1044
- return;
1045
- }
1046
- for (const notice of undrainedUserInputNotices(counts, spec.taskId ?? prepared.sessionId, prepared.sessionId, prepared.runId)) {
1047
- deliverEngineNotice(this.deps.onNotice, notice);
1048
- }
1049
- };
1050
- prepared.harness.onEngineNoteConsumed = (p) => {
1051
- const peer = p?.peer;
1052
- if (peer !== undefined && Array.isArray(peer.hopChain))
1053
- peerInboundChainRef.current = [...peer.hopChain];
1054
- };
1055
- if (notifyRef)
1056
- notifyRef.inject = injectTaskNotification;
1057
- if (captureOptOutRef && prepared.memoryEngineSession?.captureOptOut !== undefined) {
1058
- captureOptOutRef.flip = prepared.memoryEngineSession.captureOptOut.flip;
1059
- }
1060
- try {
1061
- internals?.onNotifyInjectorReady?.(injectTaskNotification);
1062
- }
1063
- catch {
1064
- }
1065
- {
1066
- const pendingIdle = this.pendingSessionNotifications.drain(prepared.sessionId);
1067
- if (pendingIdle !== undefined) {
1068
- for (const payload of discloseDroppedPending(pendingIdle)) {
1069
- const parkedPriority = pendingIdle.priorities?.get(payload);
1070
- deliveredAtTurnOpen.add(taskNotificationDedupKey(payload));
1071
- queue.push({ type: "task_notification", notification: payload, ...(parkedPriority !== undefined ? { priority: parkedPriority } : {}), ...ident() });
1072
- void prepared.harness.nextTurn(renderTaskNotificationXml(payload), { provenance: "engine-note", enginePayload: payload }).catch(() => {
1073
- this.pendingSessionNotifications.pend(prepared.sessionId, payload, parkedPriority);
1074
- });
1075
- }
1076
- }
1077
- }
1078
- const loopLatch = { ended: false, userInterrupted: false, userHalted: false };
1079
- onReady({ harness: prepared.harness, abortController: prepared.abortController, loop: loopLatch, reminderMark: prepared.reminderMark, sessionId: prepared.sessionId, runId: prepared.runId, hookTimeoutMs: prepared.hookTimeoutMs, hookIdentity: prepared.hookIdentity });
1080
- const stats = { turns: 0, tokens: 0, toolCalls: 0, promptTokens: 0, totalInputTokens: 0, cachedTokens: 0, cacheWriteTokens: 0, cacheWriteTokensLong: 0, outputTokens: 0, costMicroUsd: 0 };
1081
- prepared.liveSpendRef.get = () => ({ costMicroUsd: stats.costMicroUsd, tokens: stats.tokens, turns: stats.turns, walltimeMs: Math.round(performance.now() - rs.telemetry.taskStartMonotonic) });
1082
- if (resume &&
1083
- resume.cp.suspendedAt !== undefined &&
1084
- (resume.cp.gate.kind === "human" ||
1085
- resume.cp.gate.kind === "irreversible_ask" ||
1086
- resume.cp.gate.kind === "needs_review" ||
1087
- resume.cp.gate.kind === "plan_review")) {
1088
- const waitMs = Math.max(0, prepared.now() - resume.cp.suspendedAt);
1089
- const decision = resume.outcome.gate === "wake" ? undefined : resume.outcome.decision;
1090
- prepared.humanReviewRef.count += 1;
1091
- prepared.humanReviewRef.totalWaitMs += waitMs;
1092
- const gateToolName = "toolName" in resume.cp.gate ? resume.cp.gate.toolName : undefined;
1093
- prepared.humanReviewRef.gates.push({ kind: resume.cp.gate.kind, waitMs, ...(decision !== undefined ? { decision } : {}), ...(gateToolName !== undefined ? { toolName: gateToolName } : {}) });
1094
- }
1095
- if (resume !== undefined && resume.outcome.gate === "plan_review" && resume.outcome.decision === "reject") {
1096
- prepared.planModeRef.active = true;
1097
- }
1098
- const rs = createRunState();
1099
- rs.telemetry.cacheFamily = cacheFamilyOf(prepared.model);
1100
- rs.telemetry.pricing = this.deps.pricing?.[prepared.model.id] ?? modelCostToPricing(prepared.model.cost);
1101
- rs.telemetry.pricingConfigured = isModelPriced(prepared.model, this.deps.pricing);
1102
- if (spec.limits?.degrade) {
1103
- try {
1104
- rs.degrade.degradeToModel = resolveModel(spec.limits.degrade.to, modelCatalog);
1105
- }
1106
- catch (e) {
1107
- this.deps.onError?.(e, { phase: "degraded", sessionId: prepared.sessionId });
1108
- }
1109
- }
1110
- const famOverride = prepared.model.params?.promptCacheFamily;
1111
- if (famOverride && !["input-includes-cached", "input-excludes-cached", "openai", "anthropic"].includes(famOverride)) {
1112
- this.deps.onError?.(new Error(`prompt-cache: unrecognized model.params.promptCacheFamily "${famOverride}" — ignored (fell back to model.api inference). Use "input-includes-cached" | "input-excludes-cached" (aliases "openai" | "anthropic").`), { phase: "prompt-cache", sessionId: prepared.sessionId });
1113
- }
1114
- rs.limits.turnsExceeded = false;
1115
- rs.degrade.outputErrorStreak = 0;
1116
- rs.degrade.outputInvalid = false;
1117
- rs.telemetry.cacheBreakReported = false;
1118
- rs.limits.outputRetryCap = resolveOutputRetries(spec.outputRetries);
1119
- rs.limits.effectiveMaxTurns = resolveMaxTurns(spec.limits);
1120
- rs.telemetry.tracer = entryTracer !== undefined ? entryTracer.tracer : (spec.tracer ?? this.deps.tracer);
1121
- rs.telemetry.taskId = runSourceTaskId;
1122
- rs.telemetry.runId = prepared.runId;
1123
- if (taskIdRef) {
1124
- taskIdRef.current = rs.telemetry.taskId;
1125
- taskIdRef.sessionId = prepared.sessionId;
1126
- }
1127
- rs.telemetry.taskStart = Date.now();
1128
- rs.telemetry.taskStartMonotonic = performance.now();
1129
- const discloseNoteTaskRunFailure = (err) => {
1130
- try {
1131
- this.deps.onError?.(err instanceof Error ? err : new Error(String(err)), { phase: "config", sessionId: prepared.sessionId });
1132
- }
1133
- catch {
1134
- }
1135
- };
1136
- try {
1137
- void Promise.resolve(this.sessions.noteTaskRun?.(prepared.sessionId, rs.telemetry.taskId, rs.telemetry.runId)).catch(discloseNoteTaskRunFailure);
1138
- }
1139
- catch (err) {
1140
- discloseNoteTaskRunFailure(err);
1141
- }
1142
- const noteUnevaluablePriceTable = (p) => {
1143
- if (prepared.usageGovernance?.governsCost !== true)
1144
- return;
1145
- if (malformedPricingField(p) !== undefined)
1146
- rs.telemetry.unpricedSpend = true;
549
+ const notificationLane = {
550
+ get harness() { return notificationHarness; }, set harness(h) { notificationHarness = h; },
551
+ get sessionId() { return notificationSessionId; }, set sessionId(s) { notificationSessionId = s; },
552
+ get ident() { return notificationIdent; }, set ident(f) { notificationIdent = f; },
1147
553
  };
1148
- rs.degrade.recordDegraded = (info, toModel) => {
1149
- if (rs.degrade.degraded !== undefined)
1150
- return;
1151
- let m = toModel;
1152
- if (!m) {
1153
- try {
1154
- m = resolveModel(info.to, modelCatalog);
1155
- }
1156
- catch {
1157
- m = undefined;
1158
- }
1159
- }
1160
- if (m) {
1161
- rs.telemetry.pricing = this.deps.pricing?.[m.id] ?? modelCostToPricing(m.cost);
1162
- rs.telemetry.pricingConfigured = isModelPriced(m, this.deps.pricing);
1163
- noteUnevaluablePriceTable(rs.telemetry.pricing);
1164
- rs.telemetry.cacheFamily = cacheFamilyOf(m);
1165
- }
1166
- else {
1167
- if (this.deps.pricing?.[info.to]) {
1168
- rs.telemetry.pricing = this.deps.pricing[info.to];
1169
- rs.telemetry.pricingConfigured = true;
1170
- noteUnevaluablePriceTable(rs.telemetry.pricing);
1171
- }
1172
- else {
1173
- rs.telemetry.pricingConfigured = false;
1174
- }
1175
- rs.telemetry.cacheFamily = "input-excludes-cached";
1176
- }
1177
- rs.degrade.degraded = info;
1178
- emitTrace(rs.telemetry.tracer, () => ({ kind: "task.degraded", version: 1, taskId: rs.telemetry.taskId, from: info.from, to: info.to, reason: info.reason, atTurn: info.atTurn, ts: Date.now() }));
1179
- try {
1180
- this.deps.onError?.(new Error(`degraded to a cheaper model: ${info.from} → ${info.to} (${info.reason}) at turn ${info.atTurn}`), { phase: "degraded", sessionId: prepared.sessionId });
1181
- }
1182
- catch {
1183
- }
1184
- };
1185
- const sliceWindowMicroUsd = spec.limits?.maxCostUsd !== undefined ? Math.round(spec.limits.maxCostUsd * 1e6) : undefined;
1186
- rs.budget.remainingMicroUsd = prepared.resourceLedger
1187
- ? remainingBudgetMicroUsd(prepared.resourceLedger)
1188
- : spec.resourceSuspend?.totalBudgetUsd !== undefined
1189
- ? Math.round(spec.resourceSuspend.totalBudgetUsd * 1e6)
1190
- : undefined;
1191
- rs.budget.maxCostMicroUsd =
1192
- sliceWindowMicroUsd !== undefined && rs.budget.remainingMicroUsd !== undefined
1193
- ? Math.min(sliceWindowMicroUsd, rs.budget.remainingMicroUsd)
1194
- : (sliceWindowMicroUsd ?? rs.budget.remainingMicroUsd);
1195
- rs.budget.remainingTokens = prepared.resourceLedger
1196
- ? remainingTokens(prepared.resourceLedger)
1197
- : spec.resourceSuspend?.totalTokens;
1198
- rs.budget.maxTokensWindow =
1199
- spec.limits?.maxTokens !== undefined && rs.budget.remainingTokens !== undefined
1200
- ? Math.min(spec.limits.maxTokens, rs.budget.remainingTokens)
1201
- : (spec.limits?.maxTokens ?? rs.budget.remainingTokens);
1202
- rs.budget.overBudget = () => {
1203
- if (rs.budget.maxTokensWindow !== undefined && stats.tokens > rs.budget.maxTokensWindow)
1204
- return "tokens";
1205
- if (rs.budget.maxCostMicroUsd !== undefined && stats.costMicroUsd > rs.budget.maxCostMicroUsd)
1206
- return "cost";
1207
- return undefined;
1208
- };
1209
- rs.budget.streamCancel =
1210
- (spec.limits?.budgetStreamCancel ?? rs.budget.maxCostMicroUsd !== undefined) && prepared.suspendForResource === undefined;
1211
- rs.budget.callOutputChars = 0;
1212
- rs.budget.lastStreamBudgetCheck = 0;
1213
- rs.budget.projectedOverBudget = () => {
1214
- const estOut = Math.ceil(rs.budget.callOutputChars / 4);
1215
- if (rs.budget.maxTokensWindow !== undefined && stats.tokens + estOut > rs.budget.maxTokensWindow)
1216
- return "tokens";
1217
- if (rs.budget.maxCostMicroUsd !== undefined) {
1218
- const estOutMicro = computeCostMicroUsd({ totalInputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0, cacheWriteTokensLong: 0, outputTokens: estOut }, rs.telemetry.pricing);
1219
- if (stats.costMicroUsd + estOutMicro > rs.budget.maxCostMicroUsd)
1220
- return "cost";
1221
- }
1222
- return undefined;
1223
- };
1224
- rs.turn.turnUsageMissing = false;
1225
- rs.counters.repetitionCuts = 0;
1226
- rs.counters.repetitionSpared = 0;
1227
- rs.counters.repetitionEvents = [];
1228
- rs.counters.REPETITION_EVENTS_CAP = 20;
1229
- rs.counters.preemptIgnoredReported = false;
1230
- rs.counters.wroteThisRun = false;
1231
- rs.counters.finalVerifyInjections = 0;
1232
- rs.counters.groundingSignalPreR9 = false;
1233
- rs.counters.groundingSignalPostR9 = false;
1234
- rs.attach.attachmentsCfg = spec.attachments;
1235
- rs.attach.agentListingOn = rs.attach.attachmentsCfg?.agentListing !== false && eventDefaultOn("agent_listing");
1236
- rs.attach.skillsListingOn = rs.attach.attachmentsCfg?.skillsListing !== false && eventDefaultOn("skills_listing");
1237
- const listingsLive = (rs.attach.agentListingOn && prepared.agentListing !== undefined) || (rs.attach.skillsListingOn && prepared.skillsListing !== undefined);
1238
- const backgroundTasksLive = (rs.attach.attachmentsCfg?.backgroundTasks ?? eventDefaultOn("background_tasks")) === true;
1239
- rs.attach.attachState = rs.attach.attachmentsCfg !== undefined || listingsLive || backgroundTasksLive ? createAttachmentState() : undefined;
1240
- rs.attach.dateState = prepared.dateChange !== undefined ? { announcedDate: prepared.dateChange.legDate } : undefined;
1241
- rs.attach.instrProbe = this.deps.probeInstructionSources;
1242
- rs.attach.instrState =
1243
- rs.attach.instrProbe !== undefined && prepared.instructionSources !== undefined && prepared.instructionSources.length > 0
1244
- ? { lastAnnouncedHash: new Map(prepared.instructionSources.map((s) => [s.path, s.contentHash])) }
1245
- : undefined;
1246
- rs.attach.sizeGuidelineState =
1247
- prepared.workflowSizeGuideline !== undefined
1248
- ? { announcedGuideline: prepared.workflowSizeGuideline.legGuideline, current: prepared.workflowSizeGuideline.current }
1249
- : undefined;
1250
- rs.counters.cadenceTurns = 0;
1251
- rs.turn.lastTurnHadToolCalls = false;
1252
- if (rs.attach.attachState !== undefined && (rs.attach.attachmentsCfg?.backgroundTasks ?? eventDefaultOn("background_tasks")) === true) {
1253
- try {
1254
- const branch = await prepared.session.getBranch();
1255
- for (let i = branch.length - 1; i >= 0; i--) {
1256
- const e = branch[i];
1257
- if (e.type === "compaction") {
1258
- rs.attach.attachState.postCompactPending = true;
1259
- break;
1260
- }
1261
- if (e.type === "message" && e.message.role === "assistant")
1262
- break;
1263
- }
1264
- }
1265
- catch {
1266
- }
1267
- }
1268
- if (rs.attach.attachState !== undefined &&
1269
- ((rs.attach.agentListingOn && prepared.agentListing?.seedAnnounced === true) ||
1270
- (rs.attach.skillsListingOn && prepared.skillsListing?.seedAnnounced === true))) {
1271
- const cpListings = resume?.cp.state.announcedListings;
1272
- let entryListings;
1273
- try {
1274
- entryListings = await prepared.session.getAnnouncedListing();
1275
- }
1276
- catch {
1277
- entryListings = undefined;
1278
- }
1279
- const textOf = (m) => {
1280
- const c = m.content;
1281
- if (typeof c === "string")
1282
- return c;
1283
- if (Array.isArray(c)) {
1284
- return c
1285
- .map((b) => (b !== null && typeof b === "object" && b.type === "text" ? String(b.text ?? "") : ""))
1286
- .join("\n");
1287
- }
1288
- return "";
1289
- };
1290
- let branchTexts = null;
1291
- const branchLoad = async () => {
1292
- if (branchTexts === null) {
1293
- const branch = await prepared.session.getBranch();
1294
- branchTexts = branch.flatMap((e) => {
1295
- if (e.type !== "message")
1296
- return [];
1297
- const m = e.message;
1298
- if (m.role !== "user")
1299
- return [];
1300
- const full = textOf(e.message);
1301
- if (m.engineMinted === true)
1302
- return [stripGitStatusUnits(full, prepared.reminderMark)];
1303
- if (Array.isArray(m.engineSegments) && m.engineSegments.length > 0) {
1304
- return m.engineSegments.map((s) => stripGitStatusUnits(full.slice(Math.max(0, s.start), Math.max(0, s.end)), prepared.reminderMark));
1305
- }
1306
- if (typeof m.enginePrefixChars === "number" && m.enginePrefixChars > 0)
1307
- return [stripGitStatusUnits(full.slice(0, m.enginePrefixChars), prepared.reminderMark)];
1308
- return [];
1309
- });
1310
- }
1311
- return branchTexts;
1312
- };
1313
- const branchReplay = async (headers) => {
1314
- try {
1315
- return replayAnnouncedListing(await branchLoad(), headers);
1316
- }
1317
- catch {
1318
- return undefined;
1319
- }
1320
- };
1321
- if (rs.attach.agentListingOn && prepared.agentListing?.seedAnnounced === true) {
1322
- const listing = prepared.agentListing;
1323
- if (cpListings?.agents === undefined && entryListings?.agents !== undefined) {
1324
- const descOf = new Map(listing.entries.map((e) => [e.name, e.description]));
1325
- rs.attach.attachState.announcedAgentTypes = new Map(entryListings.agents.map((n) => [n, descOf.get(n) ?? ""]));
1326
- prepared.announcedListingsRef.agents = [...entryListings.agents];
1327
- if (entryListings.models !== undefined) {
1328
- rs.attach.attachState.announcedModels = [...entryListings.models];
1329
- prepared.announcedListingsRef.models = [...entryListings.models];
1330
- }
1331
- }
1332
- else if (cpListings?.agents !== undefined) {
1333
- const descOf = new Map(listing.entries.map((e) => [e.name, e.description]));
1334
- rs.attach.attachState.announcedAgentTypes = new Map(cpListings.agents.map((n) => [n, descOf.get(n) ?? ""]));
1335
- prepared.announcedListingsRef.agents = [...cpListings.agents];
1336
- if (cpListings.models !== undefined) {
1337
- rs.attach.attachState.announcedModels = [...cpListings.models];
1338
- prepared.announcedListingsRef.models = [...cpListings.models];
1339
- }
1340
- }
1341
- else {
1342
- const replayed = await branchReplay({
1343
- initial: agentListingInitialHeader(listing.toolName),
1344
- delta: agentListingDeltaHeader(listing.toolName),
1345
- removed: AGENT_LISTING_REMOVED_HEADER,
1346
- });
1347
- if (replayed !== undefined) {
1348
- const descOf = new Map(listing.entries.map((e) => [e.name, e.description]));
1349
- rs.attach.attachState.announcedAgentTypes = new Map([...replayed].map((n) => [n, descOf.get(n) ?? ""]));
1350
- prepared.announcedListingsRef.agents = [...replayed];
1351
- const replayedModels = replayAnnouncedModels(await branchLoad());
1352
- if (replayedModels !== undefined) {
1353
- rs.attach.attachState.announcedModels = [...replayedModels];
1354
- prepared.announcedListingsRef.models = [...replayedModels];
1355
- }
1356
- }
1357
- }
1358
- }
1359
- if (rs.attach.skillsListingOn && prepared.skillsListing?.seedAnnounced === true) {
1360
- const listing = prepared.skillsListing;
1361
- if (cpListings?.skills === undefined && entryListings?.skills !== undefined) {
1362
- const descOf = new Map(listing.entries.map((e) => [e.name, e.description]));
1363
- rs.attach.attachState.announcedSkills = new Map(entryListings.skills.map((n) => [n, descOf.get(n) ?? ""]));
1364
- prepared.announcedListingsRef.skills = [...entryListings.skills];
1365
- }
1366
- else if (cpListings?.skills !== undefined) {
1367
- const descOf = new Map(listing.entries.map((e) => [e.name, e.description]));
1368
- rs.attach.attachState.announcedSkills = new Map(cpListings.skills.map((n) => [n, descOf.get(n) ?? ""]));
1369
- prepared.announcedListingsRef.skills = [...cpListings.skills];
1370
- }
1371
- else {
1372
- const replayed = await branchReplay({
1373
- initial: SKILLS_LISTING_PROBE_HEADER,
1374
- delta: SKILLS_LISTING_DELTA_HEADER,
1375
- removed: SKILLS_LISTING_REMOVED_HEADER,
1376
- });
1377
- if (replayed !== undefined) {
1378
- const descOf = new Map(listing.entries.map((e) => [e.name, e.description]));
1379
- rs.attach.attachState.announcedSkills = new Map([...replayed].map((n) => [n, descOf.get(n) ?? ""]));
1380
- prepared.announcedListingsRef.skills = [...replayed];
1381
- }
1382
- }
1383
- }
1384
- }
1385
- if (spec.finalVerification === true && resume !== undefined) {
1386
- try {
1387
- const branch = await prepared.session.getBranch();
1388
- for (const e of branch) {
1389
- if (e.type !== "message")
1390
- continue;
1391
- const m = e.message;
1392
- if (m.role === "user") {
1393
- const text = typeof m.content === "string"
1394
- ? m.content
1395
- : m.content.map((b) => (b !== null && typeof b === "object" && b.type === "text" ? String(b.text ?? "") : "")).join("\n");
1396
- const fvMarked = m.engineMinted === true && text.includes(`<system-reminder mark="${prepared.reminderMark}">[final verification]`);
1397
- if ((fvMarked || text.includes("<system-reminder>[final verification]")) && rs.counters.finalVerifyInjections < 2)
1398
- rs.counters.finalVerifyInjections += 1;
1399
- }
1400
- else if (m.role === "toolResult" && !rs.counters.wroteThisRun && (prepared.toolEffects.get(m.toolName) ?? "write") !== "read") {
1401
- rs.counters.wroteThisRun = true;
1402
- }
1403
- }
1404
- }
1405
- catch {
1406
- }
1407
- }
1408
- rs.attach.attachmentsInjected = 0;
1409
- const todoToolMounted = rs.attach.attachState !== undefined && prepared.tools.some((t) => t.name === "TodoWrite");
1410
- const taskToolsMounted = rs.attach.attachState !== undefined && prepared.tools.some((t) => t.name === "TaskCreate");
1411
- const writeFamilyByName = new Map();
1412
- if (rs.attach.attachState !== undefined) {
1413
- for (const t of prepared.tools) {
1414
- const family = writeFamilyOfCanonical(t.name);
1415
- if (family !== undefined)
1416
- for (const n of [t.name, ...(t.aliases ?? [])])
1417
- writeFamilyByName.set(n, family);
1418
- }
1419
- }
1420
- const writeFamilyOf = (name) => writeFamilyByName.get(name) ?? writeFamilyOfCanonical(name);
1421
- const toolStartAt = new Map();
1422
- const startedToolCallIds = new Set();
1423
- emitTrace(rs.telemetry.tracer, () => ({ kind: "task.start", version: 1, taskId: rs.telemetry.taskId, runId: rs.telemetry.runId, model: prepared.model.id, engineVersion: engineVersion(), ts: rs.telemetry.taskStart }));
1424
- emitTrace(rs.telemetry.tracer, () => ({
1425
- kind: "prompt.assembled",
1426
- version: 1,
1427
- taskId: rs.telemetry.taskId,
1428
- constitution: prepared.promptManifest.constitution,
1429
- blocks: prepared.promptManifest.blocks,
1430
- ...(prepared.promptManifest.sections ? { sections: prepared.promptManifest.sections } : {}),
1431
- ...(prepared.promptManifest.tools ? { tools: prepared.promptManifest.tools } : {}),
1432
- ...(prepared.promptManifest.snapshot ? { snapshot: prepared.promptManifest.snapshot } : {}),
1433
- ...(prepared.promptManifest.lowering ? { lowering: prepared.promptManifest.lowering } : {}),
1434
- ...(prepared.promptManifest.toolDisclosure ? { toolDisclosure: prepared.promptManifest.toolDisclosure } : {}),
1435
- totalChars: prepared.promptManifest.blocks.reduce((n, b) => n + b.chars, 0),
1436
- ts: rs.telemetry.taskStart,
1437
- }));
1438
- emitTrace(rs.telemetry.tracer, () => {
1439
- const reasons = declarationReasons(spec.configOverrides);
1440
- return {
1441
- kind: "config.assembled",
1442
- version: 1,
1443
- taskId: rs.telemetry.taskId,
1444
- catalogVersion: CONFIG_CATALOG_VERSION,
1445
- fields: resolveEffectiveConfig(spec, { modelMaxTokens: prepared.model.maxTokens }),
1446
- ...(Object.keys(reasons).length > 0 ? { overrideReasons: reasons } : {}),
1447
- ts: rs.telemetry.taskStart,
1448
- };
1449
- });
1450
- if (prepared.turnSnapshot !== undefined) {
1451
- prepared.turnSnapshot.onChange = (snap) => emitTrace(rs.telemetry.tracer, () => ({
1452
- kind: "prompt.snapshot_changed",
1453
- version: 1,
1454
- taskId: rs.telemetry.taskId,
1455
- snapshot: { cacheIdentity: snap.cacheIdentity, elements: { ...snap.elements } },
1456
- ts: Date.now(),
1457
- }));
1458
- }
1459
- let reasoningResolution = prepared.thinking && prepared.thinking !== "off" ? resolveReasoning(prepared.thinking, prepared.model) : undefined;
1460
- const publishReasoningResolution = (r) => {
1461
- reasoningResolution = r;
1462
- if (taskIdRef)
1463
- taskIdRef.effectiveReasoning = r;
1464
- emitTrace(rs.telemetry.tracer, () => ({
1465
- kind: "reasoning.resolved",
1466
- version: 1,
1467
- taskId: rs.telemetry.taskId,
1468
- model: prepared.model.id,
1469
- requested: r.requested,
1470
- effective: r.effective,
1471
- graded: r.graded,
1472
- clamped: r.clamped,
1473
- format: r.format,
1474
- endpoint: r.endpoint,
1475
- ...(r.dropped === true ? { dropped: true } : {}),
1476
- ts: Date.now(),
1477
- }));
1478
- };
1479
- if (reasoningResolution !== undefined)
1480
- publishReasoningResolution(reasoningResolution);
1481
- let reasoningFactsConsumed = false;
1482
- const observeReasoningWireFacts = (facts) => {
1483
- if (reasoningFactsConsumed)
1484
- return;
1485
- reasoningFactsConsumed = true;
1486
- if (prepared.thinking === undefined || prepared.thinking === "off")
1487
- return;
1488
- const next = resolveReasoning(prepared.thinking, prepared.model, facts);
1489
- const current = reasoningResolution;
1490
- if (current !== undefined &&
1491
- current.effective === next.effective &&
1492
- current.graded === next.graded &&
1493
- current.clamped === next.clamped &&
1494
- current.format === next.format &&
1495
- current.endpoint === next.endpoint &&
1496
- current.dropped === next.dropped) {
1497
- return;
1498
- }
1499
- publishReasoningResolution(next);
1500
- };
1501
- const effectiveTimeoutMs = spec.limits?.maxWalltimeMs;
1502
- const walltimeMonotonicDeadline = effectiveTimeoutMs !== undefined ? rs.telemetry.taskStartMonotonic + effectiveTimeoutMs : undefined;
1503
- rs.counters.walltimeSyncBackstopFired = false;
1504
- const timeout = startTimeout(prepared.harness, prepared.abortController, walltimeMonotonicDeadline !== undefined ? walltimeMonotonicDeadline - performance.now() : undefined, prepared.suspendForResource !== undefined);
1505
- const pushContent = (e) => {
1506
- queue.push(e);
1507
- if (parentToolCallId !== undefined && internals?.onForwardEvent) {
1508
- try {
1509
- internals.onForwardEvent(e);
1510
- }
1511
- catch {
1512
- }
1513
- }
1514
- };
1515
- const ownCommittedTailRef = { current: undefined };
1516
- const emitCommitted = (entryId, role, toolCallId) => {
1517
- ownCommittedTailRef.current = entryId;
1518
- if (role === "toolResult" && toolCallId !== undefined)
1519
- startedToolCallIds.delete(toolCallId);
1520
- queue.push({ type: "message_committed", entryId, role, ...(toolCallId !== undefined ? { toolCallId } : {}), ...ident() });
1521
- };
1522
- const subagentName = parentToolCallId !== undefined && internals?.agentName !== undefined ? inlineUntrusted(internals.agentName.slice(0, 320), 80) : undefined;
1523
- const statusSinkNotifier = createSafeNotifier({
1524
- onError: (f) => console.warn(`[sema-core] ${f.site}: run-internals status sink threw (contained; further failures counted, not re-disclosed): ${f.error.message}`),
554
+ const undrainedUserAtEndCell = { get current() { return undrainedUserAtEnd; }, set current(v) { undrainedUserAtEnd = v; } };
555
+ const { runSourceTaskId, parentToolCallId, ident, emitDelegationLifecycle, loopLatch, stats } = runIdentityWiring({
556
+ spec, queue, prepared, internals, taskIdRef, onReady, manualCompactRef, notifyRef, captureOptOutRef, injectTaskNotification, deliveredAtTurnOpen, peerInboundChainRef,
557
+ notificationLane, undrainedUserAtEnd: undrainedUserAtEndCell, pendingSessionNotifications: this.pendingSessionNotifications, runner: this.depsSeat,
1525
558
  });
1526
- const statusEmit = (s) => {
1527
- const frame = {
1528
- type: "status",
1529
- phase: s.phase,
1530
- ...(s.detail !== undefined ? { detail: s.detail } : {}),
1531
- ...(s.retryInSec !== undefined ? { retryInSec: s.retryInSec } : {}),
1532
- ...(s.retryInMs !== undefined ? { retryInMs: s.retryInMs } : {}),
1533
- ...(s.retryAtMs !== undefined ? { retryAtMs: s.retryAtMs } : {}),
1534
- ...(s.elapsedMs !== undefined ? { elapsedMs: s.elapsedMs } : {}),
1535
- ...(s.timeoutMs !== undefined ? { timeoutMs: s.timeoutMs } : {}),
1536
- ...(s.attempt !== undefined ? { attempt: s.attempt } : {}),
1537
- ...(s.maxRetries !== undefined ? { maxRetries: s.maxRetries } : {}),
1538
- ...(s.errClass !== undefined ? { errClass: s.errClass } : {}),
1539
- ...(s.errorStatus !== undefined ? { errorStatus: s.errorStatus } : {}),
1540
- ...ident(),
1541
- };
1542
- Object.freeze(frame);
1543
- const accepted = queue.push(frame);
1544
- if (accepted && internals?.onStatusEvent !== undefined) {
1545
- statusSinkNotifier.notify(() => observeThenableRejection(internals.onStatusEvent?.(frame), statusSinkNotifier, "runtask.onStatusEvent"), "runtask.onStatusEvent");
1546
- }
1547
- };
1548
- const telemetryEmit = (t) => {
1549
- emitTrace(rs.telemetry.tracer, () => t.kind === "failover"
1550
- ? {
1551
- kind: "brain.failover",
1552
- version: 1,
1553
- taskId: rs.telemetry.taskId,
1554
- servedIndex: t.servedIndex,
1555
- total: t.total,
1556
- ...(t.errorCode !== undefined ? { errorCode: t.errorCode } : {}),
1557
- ts: Date.now(),
1558
- }
1559
- : t.kind === "breaker"
1560
- ? { kind: "breaker.transition", version: 1, taskId: rs.telemetry.taskId, key: t.key, phase: t.phase, failures: t.failures, ts: Date.now() }
1561
- : t.kind === "retry"
1562
- ? {
1563
- kind: "brain.retry",
1564
- version: 1,
1565
- taskId: rs.telemetry.taskId,
1566
- attempt: t.attempt,
1567
- phase: t.phase,
1568
- ...(t.errClass !== undefined ? { errClass: t.errClass } : {}),
1569
- ...(t.nextDelayMs !== undefined ? { nextDelayMs: t.nextDelayMs } : {}),
1570
- ts: Date.now(),
1571
- }
1572
- : { kind: "vision.placeholder", version: 1, taskId: rs.telemetry.taskId, count: t.count, ts: Date.now() });
1573
- };
1574
- const withBrainSinks = (fn) => runWithStatusSink(statusEmit, () => runWithBrainTelemetry(telemetryEmit, () => runWithReasoningWireFacts(observeReasoningWireFacts, fn)));
559
+ const rs = createRunState();
560
+ const { noteUnevaluablePriceTable } = runTelemetryAndBudgetSeats({ spec, prepared, resume, stats, taskIdRef, entryTracer, modelCatalog, runSourceTaskId, rs, runner: this.depsSeat, sessions: this.sessions });
561
+ await runAttachmentSeats({ spec, prepared, resume, rs, runner: this.depsSeat });
562
+ const { todoToolMounted, taskToolsMounted, writeFamilyOf, toolStartAt, startedToolCallIds } = runToolMountFacts({ spec, prepared, rs });
563
+ let reasoningResolution;
564
+ const reasoningResolutionCell = { get current() { return reasoningResolution; }, set current(v) { reasoningResolution = v; } };
565
+ const { observeReasoningWireFacts } = runReasoningSeat({ prepared, rs, taskIdRef, reasoningResolution: reasoningResolutionCell });
566
+ const { effectiveTimeoutMs, walltimeMonotonicDeadline, timeout, pushContent, ownCommittedTailRef, emitCommitted } = runClockAndContent({ spec, prepared, queue, internals, rs, ident, parentToolCallId, startedToolCallIds });
567
+ const { subagentName, withBrainSinks } = runBrainSinks({ queue, internals, rs, ident, parentToolCallId, observeReasoningWireFacts });
1575
568
  const toolLabels = new Map(prepared.tools.flatMap((t) => (t.label !== undefined && t.label !== t.name ? [[t.name, t.label]] : [])));
1576
569
  for (const orphan of prepared.wakeRecovered) {
1577
570
  queue.push({ type: "tool_end", toolCallId: orphan.toolCallId, toolName: orphan.toolName, ...(toolLabels.has(orphan.toolName) ? { label: toolLabels.get(orphan.toolName) } : {}), isError: true, ...reconciledToolEndBody(orphan, prepared.structuredProjector), ...ident() });
@@ -1589,412 +582,16 @@ export class Runner {
1589
582
  runnerHooks: { onError: (e, info) => this.deps.onError?.(e, info) },
1590
583
  },
1591
584
  });
1592
- const recordCompactionUsage = (m, msg) => {
1593
- const u = msg?.usage;
1594
- if (!u)
1595
- return;
1596
- const fam = cacheFamilyOf(m);
1597
- const price = this.deps.pricing?.[m.id] ?? modelCostToPricing(m.cost);
1598
- const priced = isModelPriced(m, this.deps.pricing);
1599
- if (!priced)
1600
- rs.telemetry.unpricedSpend = true;
1601
- noteUnevaluablePriceTable(price);
1602
- const { totalInputTokens, uncachedInputTokens, costMicroUsd } = usageCostMicroUsd(fam, u, price);
1603
- stats.tokens += u.totalTokens || 0;
1604
- stats.promptTokens += uncachedInputTokens;
1605
- stats.totalInputTokens += totalInputTokens;
1606
- stats.cachedTokens += u.cacheRead || 0;
1607
- stats.cacheWriteTokens += u.cacheWrite || 0;
1608
- stats.outputTokens += u.output || 0;
1609
- stats.costMicroUsd += costMicroUsd;
1610
- stats.compactionMicroUsd = (stats.compactionMicroUsd ?? 0) + costMicroUsd;
1611
- emitTrace(rs.telemetry.tracer, () => ({
1612
- kind: "brain.call", version: 1, taskId: rs.telemetry.taskId, model: m.id, provider: m.provider,
1613
- promptTokens: uncachedInputTokens, totalInputTokens, completionTokens: u.output || 0,
1614
- cacheRead: u.cacheRead || 0, cacheWrite: u.cacheWrite || 0,
1615
- latencyMs: 0, ...(priced ? { costMicroUsd } : {}), ...(typeof msg?.stopReason === "string" ? { stopReason: msg.stopReason } : {}), ts: Date.now(),
1616
- }));
1617
- };
1618
- const compactionBrain = {
1619
- stream: this.deps.brain.stream,
1620
- complete: async (m, c, o) => {
1621
- const msg = await runWithStatusSink(() => { }, async () => await runWithReasoningWireFacts(() => { }, async () => this.deps.brain.complete
1622
- ? await this.deps.brain.complete(m, c, o)
1623
- : await (await Promise.resolve(this.deps.brain.stream(m, c, o))).result()));
1624
- recordCompactionUsage(m, msg);
1625
- return msg;
1626
- },
1627
- };
1628
- const withinTaskCompaction = (spec.compaction?.enabled ?? true) && (spec.compaction?.withinTask ?? true);
1629
- const compactionBreaker = { failures: 0 };
1630
- if (spec.compaction?.enabled ?? true) {
1631
- const prefixWindow = resolveTriggerWindow(prepared.model).window;
1632
- if (Number.isFinite(prefixWindow) && prefixWindow > 0) {
1633
- const prefixSettings = sanitizeCompactionSettings({ ...DEFAULT_COMPACTION_SETTINGS, ...spec.compaction }, prefixWindow);
1634
- const prefixCompactAt = prefixWindow - prefixSettings.reserveTokens;
1635
- if (prepared.promptOverheadTokens >= prefixCompactAt) {
1636
- this.deps.onError?.(new Error(`compaction cannot help: the fixed request prefix (system prompt + tool schemas, ≈${prepared.promptOverheadTokens} tokens) ` +
1637
- `already meets or exceeds the compaction threshold (${prefixCompactAt} of a ${prefixWindow}-token window). ` +
1638
- `Compaction only shrinks conversation history, so this run will re-trigger or overflow regardless — ` +
1639
- `shrink the system prompt/tool surface or use a larger-window model.`), { phase: "config", sessionId: prepared.sessionId });
1640
- }
1641
- }
1642
- }
1643
- const windowSafetyOptions = (mainModel) => ({
1644
- ...(rs.budget.maxCostMicroUsd !== undefined
1645
- ? {
1646
- fallbackBudget: {
1647
- spentMicroUsd: () => stats.costMicroUsd,
1648
- capMicroUsd: rs.budget.maxCostMicroUsd,
1649
- mainInputPer1M: (this.deps.pricing?.[mainModel.id] ?? modelCostToPricing(mainModel.cost)).inputPer1M,
1650
- },
1651
- }
1652
- : {}),
1653
- onWindowSafety: (info) => {
1654
- if (info.kind === "fallback") {
1655
- emitTrace(rs.telemetry.tracer, () => ({
1656
- kind: "compaction.model_fallback",
1657
- version: 1,
1658
- taskId: rs.telemetry.taskId,
1659
- contentTokens: info.contentTokens,
1660
- headroomTokens: info.headroomTokens ?? 0,
1661
- truncationRatio: info.truncationRatio,
1662
- ...(info.estCostMicroUsd !== undefined ? { estCostMicroUsd: info.estCostMicroUsd } : {}),
1663
- ts: Date.now(),
1664
- }));
1665
- }
1666
- else {
1667
- emitTrace(rs.telemetry.tracer, () => ({
1668
- kind: "compaction.clamp_disclosure",
1669
- version: 1,
1670
- taskId: rs.telemetry.taskId,
1671
- truncationRatio: info.truncationRatio,
1672
- reason: info.reason ?? "tolerance",
1673
- ...(info.estCostMicroUsd !== undefined ? { estCostMicroUsd: info.estCostMicroUsd } : {}),
1674
- ts: Date.now(),
1675
- }));
1676
- }
585
+ const { compactionBrain, withinTaskCompaction, compactionBreaker, windowSafetyOptions } = runCompactionMachinery({ spec, prepared, rs, stats, noteUnevaluablePriceTable, runner: this.depsSeat });
586
+ runStopAndFinalVerify({ spec, prepared, queue, rs, stats, ident, walltimeMonotonicDeadline, runner: this.depsSeat });
587
+ runRecoveryLanes({
588
+ spec, prepared, queue, rs, ident, compactionBrain, compactionBreaker, windowSafetyOptions, runner: this.depsSeat,
589
+ compactionSeats: {
590
+ seamCCompactionOptions: (p) => this.seamCCompactionOptions(p),
591
+ compactionHookOptions: (s, sid, trig, identity, seatBound) => this.compactionHookOptions(s, sid, trig, identity, seatBound),
592
+ recordCompactionReuse: (p, c) => this.recordCompactionReuse(p, c),
1677
593
  },
1678
594
  });
1679
- const stopHook = (spec.hooks ?? this.deps.hooks)?.stop;
1680
- const finalVerificationOn = spec.finalVerification === true;
1681
- const finalVerifyBudgetFill = () => {
1682
- let worst = 0;
1683
- if (rs.budget.maxTokensWindow !== undefined && rs.budget.maxTokensWindow > 0)
1684
- worst = Math.max(worst, stats.tokens / rs.budget.maxTokensWindow);
1685
- if (rs.budget.maxCostMicroUsd !== undefined && rs.budget.maxCostMicroUsd > 0)
1686
- worst = Math.max(worst, stats.costMicroUsd / rs.budget.maxCostMicroUsd);
1687
- if (walltimeMonotonicDeadline !== undefined && prepared.suspendForResource === undefined) {
1688
- const windowMs = walltimeMonotonicDeadline - rs.telemetry.taskStartMonotonic;
1689
- if (windowMs > 0)
1690
- worst = Math.max(worst, (performance.now() - rs.telemetry.taskStartMonotonic) / windowMs);
1691
- }
1692
- return worst;
1693
- };
1694
- const emitFinalVerifyEcho = (body) => {
1695
- queue.push({ type: "steering_injected", source: "final_verification", preview: body.slice(0, 220), ...ident() });
1696
- };
1697
- if (stopHook || finalVerificationOn) {
1698
- let consecutiveBlocks = 0;
1699
- prepared.harness.setStopGate(async () => {
1700
- if (prepared.abortController.signal.aborted || prepared.pausedRef.current !== undefined || prepared.batchHaltRef.current !== undefined)
1701
- return [];
1702
- if (finalVerificationOn &&
1703
- (rs.counters.finalVerifyInjections === 0 || (rs.counters.finalVerifyInjections === 1 && rs.counters.groundingSignalPreR9 && !rs.counters.groundingSignalPostR9)) &&
1704
- rs.counters.wroteThisRun &&
1705
- prepared.outputRef.set !== true &&
1706
- !(rs.limits.effectiveMaxTurns !== undefined && rs.limits.effectiveMaxTurns > 0 && stats.turns >= rs.limits.effectiveMaxTurns - 1) &&
1707
- finalVerifyBudgetFill() < 0.9) {
1708
- rs.counters.finalVerifyInjections += 1;
1709
- if (rs.counters.finalVerifyInjections === 2) {
1710
- const reentryBody = openSystemReminder(prepared.reminderMark) +
1711
- "[final verification] Your tool calls in this run worked with raw bytes, structural parsing, " +
1712
- "or checksum/digest computation — the deliverable very likely embeds verifiable structure (structural fields, an " +
1713
- "embedded checksum-family value, reference data it must match, or a replayable deterministic path). You MUST " +
1714
- "execute the grounding check that structure supports — recompute the embedded value and compare it against the " +
1715
- "declared one, re-parse the structure from the raw bytes and reconcile it with your output, compare against the " +
1716
- "reference data, or replay the deterministic path — and REPORT the check's concrete result before finishing. " +
1717
- "A closing statement without a reported check result is not verification. If you already ran such a check, state " +
1718
- "its concrete result now; if the check mismatches, fix the deliverable first. This is the final reminder from " +
1719
- "this verification gate — it will not intervene again.</system-reminder>";
1720
- emitFinalVerifyEcho(reentryBody);
1721
- return [
1722
- {
1723
- role: "user",
1724
- engineMinted: true,
1725
- content: reentryBody,
1726
- timestamp: Date.now(),
1727
- },
1728
- ];
1729
- }
1730
- const nudgeBody = openSystemReminder(prepared.reminderMark) +
1731
- "[final verification] Before finishing: re-verify the FINAL deliverable through its REAL entry point, " +
1732
- "exactly as the acceptance criteria would exercise it — execute the binary/function/endpoint directly and read the ACTUAL " +
1733
- "output and exit code. Do NOT rely on earlier self-tests, shell redirections, or assumptions (a program that prints to " +
1734
- "stdout is not a program that writes the required file). If anything mismatches the task's requirements, fix it before " +
1735
- "finishing. " +
1736
- "Treat verification writes as state-harmless: when the deliverable itself is a persisted final " +
1737
- "state (for example a committed or pushed file, a deployed artifact, or a required output file), " +
1738
- "do not change that state merely to test it. This constrains HOW you verify — it is never a " +
1739
- "license to skip the real acceptance path or to check a substitute of your own making: expected " +
1740
- "values must come from the task's requirements, never from content you generated. If the real " +
1741
- "acceptance path requires a write, use disposable inputs or an isolated target, end in the exact " +
1742
- "required final state, and verify that final state before finishing. " +
1743
- "If the work relied on a third-party API, library, or model, check the usage contract the object itself declares " +
1744
- "(docstrings, metadata, configuration — e.g. prompt conventions shipped with a model) and confirm your calls follow " +
1745
- "it rather than a default symmetric usage. Verify not only that the deliverable EXISTS but that the METHOD that " +
1746
- "produced it matches the task's requirements. " +
1747
- "Choose the verification SURFACE deliberately: check against the reference data, oracle, or evaluation tooling the " +
1748
- "task itself provides — re-running your own implementation and getting the same answer is self-consistency, not " +
1749
- "correctness — and cross-check through an independent second path where the task or environment offers one " +
1750
- "(checksums, runtime artifacts). Verify the PERSISTED artifact — re-read what is actually on disk or committed, " +
1751
- "not in-memory state — against every hard constraint from the original task text (numeric bounds, allowed-value " +
1752
- "lists, naming semantics, required files), reconciling whole-set completeness: nothing missing, nothing duplicated. " +
1753
- "If the deliverable embeds verifiable structure — structural fields, an embedded checksum-family value, " +
1754
- "reference data it must match, or a replayable deterministic path — you MUST execute the grounding check " +
1755
- "that structure supports and REPORT its concrete result in your closing summary: for such a deliverable, " +
1756
- "no reported check result means the work is not finished. " +
1757
- "If the task produced neither an executable deliverable nor any verifiable structure or acceptance " +
1758
- "oracle to check against, briefly confirm completion and stop. " +
1759
- "Residue YOUR OWN testing created (scratch files, running processes, generated outputs the task does not ask for) " +
1760
- "is not protected state — if the task's required final state is a clean target, removing your own residue is part " +
1761
- "of delivering it. " +
1762
- "Verification must never LAUNDER uncertainty: if part of your conclusion was uncertain before this check, keep " +
1763
- "reporting it as uncertain unless the check you actually ran resolved it — a re-stated conclusion is not new " +
1764
- "evidence.</system-reminder>";
1765
- emitFinalVerifyEcho(nudgeBody);
1766
- return [
1767
- {
1768
- role: "user",
1769
- engineMinted: true,
1770
- content: nudgeBody,
1771
- timestamp: Date.now(),
1772
- },
1773
- ];
1774
- }
1775
- if (!stopHook)
1776
- return [];
1777
- let result;
1778
- try {
1779
- const stopSeat = await runHookSeat("stop", { timeoutMs: prepared.hookTimeoutMs, signal: prepared.abortController.signal, abortEnds: true }, (sig) => stopHook({
1780
- stopHookActive: consecutiveBlocks > 0,
1781
- consecutiveBlocks,
1782
- getBranch: () => prepared.session.getBranch(),
1783
- identity: prepared.hookIdentity,
1784
- signal: sig,
1785
- }));
1786
- if (stopSeat.expired) {
1787
- if (stopSeat.cause === "timeout") {
1788
- this.deps.onError?.(hookSeatExpiredError("stop", prepared.hookTimeoutMs, stopSeat.cause, "the run was allowed to END (the seat's own no-opinion answer); no pushback and no additional context were injected"), { phase: "hook", sessionId: prepared.sessionId });
1789
- }
1790
- consecutiveBlocks = 0;
1791
- return [];
1792
- }
1793
- result = stopSeat.value;
1794
- }
1795
- catch (err) {
1796
- this.deps.onError?.(err instanceof Error ? err : new Error(String(err)), { phase: "hook", sessionId: prepared.sessionId });
1797
- return [];
1798
- }
1799
- const messages = [];
1800
- if (result?.additionalContext) {
1801
- messages.push({
1802
- role: "user",
1803
- engineMinted: true,
1804
- content: formatHookFeedback(`Stop hook additional context: ${result.additionalContext}`, prepared.reminderMark),
1805
- timestamp: Date.now(),
1806
- });
1807
- }
1808
- if (messages.length === 0 && !result?.block) {
1809
- consecutiveBlocks = 0;
1810
- return messages;
1811
- }
1812
- consecutiveBlocks++;
1813
- const cap = STOP_HOOK_BLOCK_CAP;
1814
- if (consecutiveBlocks > cap) {
1815
- this.deps.onError?.(new Error(`a Stop hook kept the turn from ending ${consecutiveBlocks} consecutive times — overriding and ending the run. ` +
1816
- `Both a block and an additionalContext-only push-back count (CC 2.1.220). ` +
1817
- `Check ctx.stopHookActive in the hook and return success while it's true.`), { phase: "hook", sessionId: prepared.sessionId });
1818
- return [];
1819
- }
1820
- if (result?.block) {
1821
- messages.push({
1822
- role: "user",
1823
- engineMinted: true,
1824
- content: formatHookFeedback(`Stop hook stopped continuation: ${result.block}`, prepared.reminderMark),
1825
- timestamp: Date.now(),
1826
- });
1827
- }
1828
- return messages;
1829
- });
1830
- }
1831
- const runForcedCompactionPass = async (lane, turnSignal) => {
1832
- if (!(spec.compaction?.enabled ?? true))
1833
- return false;
1834
- if (compactionBreaker.failures >= MAX_CONSECUTIVE_COMPACTION_FAILURES)
1835
- return false;
1836
- const passSignal = turnSignal !== undefined ? AbortSignal.any([prepared.abortController.signal, turnSignal]) : prepared.abortController.signal;
1837
- try {
1838
- const comp = await maybeCompact({
1839
- session: prepared.session,
1840
- epochDeclaredSections: prepared.epochDeclaredSections,
1841
- ...centerAdoptionOption(prepared),
1842
- model: prepared.harness.getModel(),
1843
- compactionModel: prepared.compModel,
1844
- ...forkContextOption(prepared, true),
1845
- brain: compactionBrain,
1846
- getApiKeyAndHeaders: spec.getApiKeyAndHeaders,
1847
- thinking: prepared.thinking,
1848
- settings: { ...DEFAULT_COMPACTION_SETTINGS, ...spec.compaction },
1849
- customInstructions: spec.compaction?.instructions ?? DEFAULT_COMPACTION_INSTRUCTIONS,
1850
- signal: passSignal,
1851
- minTokens: 0,
1852
- force: true,
1853
- overheadTokens: prepared.promptOverheadTokens,
1854
- ...(prepared.activeTools.size > 0 ? { activeTools: [...prepared.activeTools] } : {}),
1855
- onInputTruncated: emitInputTruncated(rs.telemetry.tracer, rs.telemetry.taskId),
1856
- workingFileAttachments: buildWorkingFileAttachments(spec, prepared),
1857
- ...contextInstructionFilesOption(prepared),
1858
- ...(prepared.onCompactionApplied ? { onApplied: prepared.onCompactionApplied } : {}),
1859
- ...this.seamCCompactionOptions(prepared),
1860
- ...gitRestateOption(prepared),
1861
- ...windowSafetyOptions(prepared.harness.getModel()),
1862
- ...this.compactionHookOptions(spec, prepared.sessionId, "forced", prepared.hookIdentity, { timeoutMs: prepared.hookTimeoutMs, signal: passSignal }),
1863
- });
1864
- if (comp.compacted) {
1865
- compactionBreaker.failures = 0;
1866
- this.recordCompactionReuse(prepared, comp);
1867
- if ((comp.freedTokens ?? 0) >= COMPACTION_FREED_EPSILON) {
1868
- const postSize = comp.postTriggerTokens ?? Math.max(0, (comp.triggerTokens ?? comp.tokensBefore ?? 0) - (comp.freedTokens ?? 0));
1869
- rs.counters.compactionFloor = Math.ceil(postSize * COMPACTION_REGROWTH_FACTOR);
1870
- }
1871
- queue.push({
1872
- type: "compacted",
1873
- trigger: "forced",
1874
- tokensBefore: comp.tokensBefore ?? 0,
1875
- ...(comp.postTriggerTokens !== undefined ? { tokensAfter: comp.postTriggerTokens } : {}),
1876
- ...(comp.triggerTokens !== undefined ? { triggerTokensBefore: comp.triggerTokens } : {}),
1877
- ...(comp.durationMs !== undefined ? { durationMs: comp.durationMs } : {}),
1878
- ...(comp.phaseDurations !== undefined ? { phaseDurations: comp.phaseDurations } : {}),
1879
- ...(comp.firstKeptEntryId !== undefined ? { preserved_segment: { firstKeptEntryId: comp.firstKeptEntryId } } : {}),
1880
- ...(comp.attachedFiles !== undefined ? { attachedFiles: comp.attachedFiles } : {}),
1881
- ...(comp.modelFallback ? { modelFallback: true } : {}),
1882
- ...(comp.fallbackReason !== undefined ? { fallbackReason: comp.fallbackReason } : {}),
1883
- ...(comp.clampedRatio !== undefined ? { clampedRatio: comp.clampedRatio } : {}),
1884
- ...(comp.clampReason !== undefined ? { clampReason: comp.clampReason } : {}),
1885
- ...ident(),
1886
- });
1887
- if (comp.phaseDurations !== undefined && comp.durationMs !== undefined) {
1888
- const pd = comp.phaseDurations;
1889
- const pdDur = comp.durationMs;
1890
- emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.phase_timings", version: 1, taskId: rs.telemetry.taskId, ...pd, durationMs: pdDur, ts: Date.now() }));
1891
- }
1892
- prepared.cacheBreakDetector?.notifyCompaction();
1893
- if (rs.attach.attachState !== undefined) {
1894
- rs.attach.attachState.postCompactPending = true;
1895
- rebaseCadenceWindows(rs.attach.attachState, rs.counters.cadenceTurns);
1896
- }
1897
- }
1898
- else {
1899
- if (comp.noop) {
1900
- compactionBreaker.failures = 0;
1901
- }
1902
- else {
1903
- compactionBreaker.failures += 1;
1904
- }
1905
- const declineReason = lane === "rejection" ? "prompt-too-long recovery pass did not land" : "guard-chain forced compaction pass did not land";
1906
- if (!comp.noop) {
1907
- this.deps.onError?.(new Error(declineReason), { phase: "compaction", sessionId: prepared.sessionId });
1908
- }
1909
- queue.push({
1910
- type: "compaction_outcome",
1911
- outcome: comp.noop ? "noop" : "failed",
1912
- trigger: "forced",
1913
- reason: declineReason,
1914
- ...ident(),
1915
- });
1916
- }
1917
- return comp.compacted === true;
1918
- }
1919
- catch (err) {
1920
- if (turnSignal?.aborted === true && !prepared.abortController.signal.aborted) {
1921
- const interruptReason = lane === "rejection"
1922
- ? "prompt-too-long recovery pass cut short by a turn interrupt"
1923
- : "guard-chain forced compaction cut short by a turn interrupt";
1924
- queue.push({ type: "compaction_outcome", outcome: "failed", trigger: "forced", reason: interruptReason, ...ident() });
1925
- return false;
1926
- }
1927
- compactionBreaker.failures += 1;
1928
- this.deps.onError?.(err instanceof Error ? err : new Error(String(err)), { phase: "compaction", sessionId: prepared.sessionId });
1929
- const msg = String(err instanceof Error ? err.message : err);
1930
- queue.push({ type: "compaction_outcome", outcome: "failed", trigger: "forced", reason: msg.length > 512 ? `${msg.slice(0, 512)}…` : msg, ...ident() });
1931
- return false;
1932
- }
1933
- };
1934
- prepared.harness.setLoopRecovery({
1935
- truncatedOutput: {},
1936
- malformedToolUse: {},
1937
- thinkingOnly: {},
1938
- degenerateOutput: { detect: (m) => isDegenerateCutMessage(m) },
1939
- promptTooLong: {
1940
- recover: async (attempt, turnSignal) => {
1941
- if (prepared.abortController.signal.aborted || prepared.pausedRef.current !== undefined)
1942
- return false;
1943
- if (turnSignal?.aborted === true)
1944
- return false;
1945
- if (prepared.microCompact.clearOnRejection && attempt === 1) {
1946
- const proj = prepared.microCompact.projectionRef.current;
1947
- const compactionAvailable = (spec.compaction?.enabled ?? true) && compactionBreaker.failures < MAX_CONSECUTIVE_COMPACTION_FAILURES;
1948
- const declineArm = compactionAvailable ? "forced_compaction" : "none";
1949
- const plan = proj === undefined
1950
- ? { declined: "no_candidates" }
1951
- : planRejectionClears(proj.messages, {
1952
- keyOf: proj.keyOf,
1953
- ...(prepared.microCompact.offloadPersist ? { offload: { persist: prepared.microCompact.offloadPersist } } : {}),
1954
- });
1955
- if ("declined" in plan) {
1956
- emitTrace(rs.telemetry.tracer, () => ({
1957
- kind: "context.mc_null",
1958
- version: 1,
1959
- taskId: rs.telemetry.taskId,
1960
- reason: plan.declined,
1961
- nextArm: declineArm,
1962
- ts: Date.now(),
1963
- }));
1964
- }
1965
- else {
1966
- for (const e of plan.cleared) {
1967
- prepared.microCompact.ledger.entries.set(e.key, { marker: e.marker, groupCount: e.groupCount, fp: e.fp });
1968
- }
1969
- emitTrace(rs.telemetry.tracer, () => ({
1970
- kind: "context.mc_clear",
1971
- version: 1,
1972
- taskId: rs.telemetry.taskId,
1973
- clearedCount: plan.cleared.length,
1974
- tokensSavedEstimate: plan.tokensSavedEstimate,
1975
- trigger: "refusal",
1976
- ts: Date.now(),
1977
- }));
1978
- return true;
1979
- }
1980
- }
1981
- return runForcedCompactionPass("rejection", turnSignal);
1982
- },
1983
- },
1984
- });
1985
- prepared.microCompact.inTurnCompactionRef.current = async (turnSignal, anchoredEstimate) => {
1986
- if (prepared.abortController.signal.aborted || prepared.pausedRef.current !== undefined)
1987
- return false;
1988
- if (turnSignal?.aborted === true)
1989
- return false;
1990
- if (anchoredEstimate !== undefined && rs.counters.compactionFloor > 0 && anchoredEstimate < rs.counters.compactionFloor) {
1991
- const floor = rs.counters.compactionFloor;
1992
- emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.suppressed", version: 1, taskId: rs.telemetry.taskId, estTokens: anchoredEstimate, floor, ts: Date.now() }));
1993
- queue.push({ type: "compaction_outcome", outcome: "suppressed", trigger: "forced", ...ident() });
1994
- return false;
1995
- }
1996
- return runForcedCompactionPass("guard", turnSignal);
1997
- };
1998
595
  const rapidRefill = createRapidRefillState();
1999
596
  const drainManualCompact = (outcome) => {
2000
597
  manualCompactRef.requested = false;
@@ -2016,102 +613,7 @@ export class Runner {
2016
613
  },
2017
614
  },
2018
615
  });
2019
- prepared.gitStatusRef.reassert = async () => {
2020
- const ref = prepared.gitStatusRef;
2021
- const frame = ref.frame;
2022
- if (frame === undefined)
2023
- return;
2024
- delete ref.mirrorOwed;
2025
- const use = ref.overBudgetShrunk && frame.shrunk !== undefined
2026
- ? { kind: "degraded", body: frame.shrunk.body, hash: frame.shrunk.hash }
2027
- : { kind: frame.kind, body: frame.body, hash: frame.hash };
2028
- const wrapped = wrapGitFrame(use.body, prepared.reminderMark);
2029
- try {
2030
- const entryId = await prepared.session.appendMessage({
2031
- role: "user",
2032
- content: [{ type: "text", text: wrapped }],
2033
- timestamp: Date.now(),
2034
- engineMinted: true,
2035
- });
2036
- ref.announced = { kind: use.kind, hash: use.hash, entryId };
2037
- ref.protectedText = wrapped;
2038
- if (use.kind === "full" && frame.shrunk !== undefined) {
2039
- ref.wrappedShrink = { find: wrapped, replace: wrapGitFrame(frame.shrunk.body, prepared.reminderMark) };
2040
- }
2041
- else {
2042
- delete ref.wrappedShrink;
2043
- }
2044
- queue.push({ type: "steering_injected", source: "git_status", preview: GIT_STATUS_ECHO_PREVIEW[use.kind], ...ident() });
2045
- rs.attach.attachmentsInjected += 1;
2046
- try {
2047
- await prepared.session.appendGitAnnouncement?.({ kind: use.kind, hash: use.hash, entryId });
2048
- }
2049
- catch (mirrorErr) {
2050
- try {
2051
- this.deps.onError?.(new Error(`git announcement mirror write failed (frame delivered; next leg re-announces): ${mirrorErr instanceof Error ? mirrorErr.message : String(mirrorErr)}`), { phase: "degraded", sessionId: prepared.sessionId });
2052
- }
2053
- catch {
2054
- }
2055
- }
2056
- }
2057
- catch (err) {
2058
- ref.announced = { kind: use.kind, hash: use.hash, pending: true };
2059
- try {
2060
- this.deps.onError?.(new Error(`git status frame re-assert append failed (pending; retried at the next boundary): ${err instanceof Error ? err.message : String(err)}`), { phase: "degraded", sessionId: prepared.sessionId });
2061
- }
2062
- catch {
2063
- }
2064
- }
2065
- };
2066
- const flushGitMirror = async () => {
2067
- const owed = prepared.gitStatusRef.mirrorOwed;
2068
- if (owed === undefined)
2069
- return;
2070
- if (prepared.gitStatusRef.overBudgetShrunk && owed.kind === "full") {
2071
- delete prepared.gitStatusRef.mirrorOwed;
2072
- return;
2073
- }
2074
- delete prepared.gitStatusRef.mirrorOwed;
2075
- try {
2076
- await prepared.session.appendGitAnnouncement?.(owed);
2077
- }
2078
- catch (mirrorErr) {
2079
- prepared.gitStatusRef.mirrorOwed = owed;
2080
- try {
2081
- this.deps.onError?.(new Error(`git announcement mirror write failed (frame delivered; retried at the next serialization point): ${mirrorErr instanceof Error ? mirrorErr.message : String(mirrorErr)}`), { phase: "degraded", sessionId: prepared.sessionId });
2082
- }
2083
- catch {
2084
- }
2085
- }
2086
- };
2087
- const unsubGitRetry = prepared.harness.on("turn_boundary", async () => {
2088
- await flushGitMirror();
2089
- if (prepared.gitStatusRef.announced?.pending === true)
2090
- await prepared.gitStatusRef.reassert?.();
2091
- return undefined;
2092
- });
2093
- const unsubBoundary = prepared.harness.on("turn_boundary", onTurnBoundary);
2094
- const unsub = prepared.harness.subscribe((event) => {
2095
- switch (event.type) {
2096
- case "message_update":
2097
- onMessageUpdate(event);
2098
- break;
2099
- case "message_end":
2100
- onMessageEnd(event);
2101
- break;
2102
- case "tool_execution_start":
2103
- onToolStart(event);
2104
- break;
2105
- case "tool_execution_end":
2106
- onToolEnd(event);
2107
- break;
2108
- case "turn_end":
2109
- onTurnEnd();
2110
- break;
2111
- default:
2112
- break;
2113
- }
2114
- });
616
+ const { flushGitMirror, unsubGitRetry, unsubBoundary, unsub } = runGitLane({ prepared, queue, rs, ident, onMessageUpdate, onMessageEnd, onToolStart, onToolEnd, onTurnEnd, onTurnBoundary, runner: this.depsSeat });
2115
617
  let final;
2116
618
  let threw;
2117
619
  let abortedLive = false;