@sema-agent/core 7.28.1 → 7.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/CHANGELOG.md +473 -1
  2. package/dist/agents/child-model-seat.d.ts +59 -7
  3. package/dist/agents/child-model-seat.js +33 -7
  4. package/dist/agents/cross-session-judge.d.ts +27 -5
  5. package/dist/agents/cross-session-judge.js +23 -12
  6. package/dist/agents/list-agents-tool.d.ts +31 -6
  7. package/dist/agents/list-agents-tool.js +66 -14
  8. package/dist/agents/peer-admission.d.ts +20 -8
  9. package/dist/agents/peer-admission.js +21 -12
  10. package/dist/agents/peer-directory.d.ts +29 -0
  11. package/dist/agents/peer-directory.js +20 -0
  12. package/dist/agents/peer-held-queue.d.ts +31 -4
  13. package/dist/agents/peer-held-queue.js +32 -1
  14. package/dist/agents/peer-session-drain.d.ts +91 -13
  15. package/dist/agents/peer-session-drain.js +409 -102
  16. package/dist/agents/read-inbox-tool.d.ts +22 -0
  17. package/dist/agents/read-inbox-tool.js +39 -0
  18. package/dist/agents/roster-store.d.ts +2 -4
  19. package/dist/agents/roster-store.js +1 -1
  20. package/dist/agents/send-message-tool.d.ts +4 -4
  21. package/dist/agents/send-message-tool.js +22 -2
  22. package/dist/agents/subagent.js +62 -44
  23. package/dist/agents/team-role.d.ts +34 -0
  24. package/dist/agents/team-role.js +17 -0
  25. package/dist/agents/verify.js +2 -2
  26. package/dist/brain/anthropic.js +4 -2
  27. package/dist/brain/open-responses.js +4 -3
  28. package/dist/brain/openai.js +2 -1
  29. package/dist/brain/stream-engine.js +6 -0
  30. package/dist/brain/stream-shared.d.ts +8 -0
  31. package/dist/brain/stream-shared.js +8 -0
  32. package/dist/core/auto-compaction.d.ts +42 -0
  33. package/dist/core/auto-compaction.js +35 -2
  34. package/dist/core/child-clamp-seats.d.ts +11 -0
  35. package/dist/core/child-clamp-seats.js +3 -0
  36. package/dist/core/context-edit.d.ts +3 -2
  37. package/dist/core/context-edit.js +6 -5
  38. package/dist/core/context-guard.d.ts +2 -1
  39. package/dist/core/context-guard.js +3 -2
  40. package/dist/core/context-settings.d.ts +103 -0
  41. package/dist/core/context-settings.js +60 -0
  42. package/dist/core/engine-notice.d.ts +18 -1
  43. package/dist/core/frame-settlement.d.ts +97 -0
  44. package/dist/core/frame-settlement.js +36 -0
  45. package/dist/core/governance-codes.d.ts +1 -1
  46. package/dist/core/governance-codes.js +4 -0
  47. package/dist/core/hooks.d.ts +10 -10
  48. package/dist/core/mailbox-store.d.ts +41 -7
  49. package/dist/core/mailbox-store.js +9 -3
  50. package/dist/core/message-utils.d.ts +13 -0
  51. package/dist/core/message-utils.js +6 -0
  52. package/dist/core/parent-spec-seat.d.ts +14 -1
  53. package/dist/core/parent-spec-seat.js +6 -0
  54. package/dist/core/permission-rule-model.js +1 -1
  55. package/dist/core/roles.d.ts +20 -0
  56. package/dist/core/roles.js +35 -16
  57. package/dist/core/runner/assemble-result.d.ts +2 -0
  58. package/dist/core/runner/assemble-result.js +3 -1
  59. package/dist/core/runner/compaction-call-options.d.ts +26 -2
  60. package/dist/core/runner/compaction-call-options.js +9 -0
  61. package/dist/core/runner/compaction-seams.d.ts +4 -3
  62. package/dist/core/runner/compaction-seams.js +13 -6
  63. package/dist/core/runner/contracts.d.ts +61 -7
  64. package/dist/core/runner/decide-continuation.d.ts +2 -1
  65. package/dist/core/runner/decide-continuation.js +11 -10
  66. package/dist/core/runner/initial-run-state.js +2 -2
  67. package/dist/core/runner/prepare-caps-and-workflow.js +9 -1
  68. package/dist/core/runner/prepare-config-doors.js +4 -1
  69. package/dist/core/runner/prepare-context-lane.d.ts +9 -2
  70. package/dist/core/runner/prepare-context-lane.js +15 -10
  71. package/dist/core/runner/prepare-delegation-surface.d.ts +3 -2
  72. package/dist/core/runner/prepare-delegation-surface.js +9 -3
  73. package/dist/core/runner/prepare-file-history.js +10 -8
  74. package/dist/core/runner/prepare-gate-stations.d.ts +4 -0
  75. package/dist/core/runner/prepare-gate-stations.js +3 -1
  76. package/dist/core/runner/prepare-inherited-gate.js +1 -0
  77. package/dist/core/runner/prepare-prompt-inputs.d.ts +6 -0
  78. package/dist/core/runner/prepare-prompt-inputs.js +2 -1
  79. package/dist/core/runner/prepare-task.js +22 -9
  80. package/dist/core/runner/prepare-wiring-manifest.d.ts +6 -1
  81. package/dist/core/runner/prepare-wiring-manifest.js +6 -1
  82. package/dist/core/runner/response-model-seat.d.ts +16 -0
  83. package/dist/core/runner/response-model-seat.js +27 -0
  84. package/dist/core/runner/retired-runner-deps.d.ts +3 -1
  85. package/dist/core/runner/retired-runner-deps.js +1 -0
  86. package/dist/core/runner/run-compaction-machinery.d.ts +4 -2
  87. package/dist/core/runner/run-compaction-machinery.js +5 -3
  88. package/dist/core/runner/run-harness-handlers.js +9 -2
  89. package/dist/core/runner/run-identity-wiring.js +19 -1
  90. package/dist/core/runner/run-leg.js +1 -1
  91. package/dist/core/runner/run-notification-lane.js +8 -2
  92. package/dist/core/runner/run-recovery-lanes.js +9 -5
  93. package/dist/core/runner/run-settle-and-teardown.d.ts +4 -22
  94. package/dist/core/runner/run-settle-and-teardown.js +6 -10
  95. package/dist/core/runner/run-stop-and-final-verify.js +1 -0
  96. package/dist/core/runner/run-terminal-adoption.js +5 -5
  97. package/dist/core/runner/run-turn-boundary.js +32 -5
  98. package/dist/core/runner/runtask.d.ts +8 -1
  99. package/dist/core/runner/runtask.js +33 -2
  100. package/dist/core/runner-deps.d.ts +44 -13
  101. package/dist/core/side-query.d.ts +6 -5
  102. package/dist/core/side-query.js +3 -1
  103. package/dist/core/swappable-deps.d.ts +20 -4
  104. package/dist/core/swappable-deps.js +6 -1
  105. package/dist/core/task-event.d.ts +13 -0
  106. package/dist/core/task-notification.d.ts +144 -2
  107. package/dist/core/task-notification.js +154 -5
  108. package/dist/core/task-result.d.ts +27 -3
  109. package/dist/core/task-spec.d.ts +7 -0
  110. package/dist/core/tool-catalog-entries.js +2 -1
  111. package/dist/core/tool-conformance.js +1 -0
  112. package/dist/core/tool-policy.d.ts +9 -4
  113. package/dist/core/tool-spec.d.ts +14 -0
  114. package/dist/core/wiring-manifest.d.ts +12 -0
  115. package/dist/core/wiring-manifest.js +5 -1
  116. package/dist/engine/harness/agent-harness.d.ts +14 -1
  117. package/dist/engine/harness/agent-harness.js +28 -3
  118. package/dist/engine/harness/types.d.ts +3 -1
  119. package/dist/engine/llm/types.d.ts +8 -7
  120. package/dist/engine/llm/types.js +1 -1
  121. package/dist/engine/session/import-validate.js +4 -0
  122. package/dist/index.d.ts +10 -7
  123. package/dist/index.js +7 -5
  124. package/dist/orchestration/run-workflow-tool.d.ts +4 -4
  125. package/dist/orchestration/workflow.d.ts +1 -1
  126. package/dist/orchestration/workflow.js +8 -5
  127. package/dist/shell/lex.d.ts +30 -51
  128. package/dist/shell/lex.js +237 -284
  129. package/dist/stores/cc/mailbox-store.js +3 -2
  130. package/dist/stores/file/corrupt-read-sink.d.ts +28 -2
  131. package/dist/stores/file/file-history-store.d.ts +2 -4
  132. package/dist/stores/file/file-history-store.js +1 -1
  133. package/dist/stores/file/fs-atomic.d.ts +16 -11
  134. package/dist/stores/file/fs-atomic.js +17 -6
  135. package/dist/stores/file/index.d.ts +5 -6
  136. package/dist/stores/file/index.js +1 -1
  137. package/dist/stores/file/mailbox-store.d.ts +4 -9
  138. package/dist/stores/file/mailbox-store.js +29 -30
  139. package/dist/stores/file/session-policy-store.d.ts +2 -0
  140. package/dist/stores/file/session-policy-store.js +1 -1
  141. package/dist/stores/file/session-store.d.ts +11 -7
  142. package/dist/stores/file/session-store.js +23 -16
  143. package/dist/stores/file/shared-ledger.js +3 -14
  144. package/dist/stores/file/tool-result-store.d.ts +2 -0
  145. package/dist/stores/file/tool-result-store.js +5 -2
  146. package/dist/stores/file/workflow-journal-store.js +14 -9
  147. package/dist/tools/fs/bash-program-position.d.ts +15 -24
  148. package/dist/tools/fs/bash-program-position.js +11 -29
  149. package/dist/tools/fs/bash-readonly-classifier.d.ts +125 -117
  150. package/dist/tools/fs/bash-readonly-classifier.js +400 -272
  151. package/dist/tools/fs/shell-read-check.js +3 -3
  152. package/dist/tools/fs/shell-read-establish.js +1 -1
  153. package/dist/tools/fs/shell-read-plan.d.ts +11 -6
  154. package/dist/tools/fs/shell-read-plan.js +167 -183
  155. package/dist/tools/fs/shell-read-word.d.ts +50 -0
  156. package/dist/tools/fs/shell-read-word.js +59 -0
  157. package/dist/tools/fs/shell-reentry-text.d.ts +10 -0
  158. package/dist/tools/fs/shell-reentry-text.js +73 -0
  159. package/package.json +1 -1
  160. package/test/export-surface.snapshot.json +58 -8
  161. package/wire-changes/core@7.29.0.json +185 -0
  162. package/wire-changes/core@7.30.0.json +1821 -0
@@ -1,11 +1,10 @@
1
1
  import { DEFAULT_COMPACTION_INSTRUCTIONS, maybeCompact } from "../auto-compaction.js";
2
- import { DEFAULT_COMPACTION_SETTINGS } from "../../internal/harness.js";
3
2
  import { eventDefaultOn } from "../../prompt-assembly/event-registry.js";
4
3
  import { appendInterruptionMarker, reconcileInterruptedSession } from "../session-reconcile.js";
5
4
  import { emitTrace } from "../trace.js";
6
5
  import { answerSettled, errorCodeOf } from "./assemble-result.js";
7
6
  import { awaitChargeWithSlowDisclosure, discloseUnevaluableWindow } from "./clock-and-limits.js";
8
- import { buildWorkingFileAttachments, centerAdoptionOption, contextInstructionFilesOption, emitInputTruncated, forkContextOption } from "./compaction-call-options.js";
7
+ import { buildWorkingFileAttachments, centerAdoptionOption, contextInstructionFilesOption, emitInputTruncated, forkContextOption, legCompactionSettings, legContextOverlayOption } from "./compaction-call-options.js";
9
8
  import { compactionHookOptions, recordCompactionReuse, seamCCompactionOptions } from "./compaction-seams.js";
10
9
  import { compactionRestateOptions } from "./compaction-restate.js";
11
10
  import { MAX_CONSECUTIVE_COMPACTION_FAILURES } from "./compaction-knobs.js";
@@ -118,7 +117,7 @@ export async function runTerminalAdoption(input) {
118
117
  skipCompaction: durablyPaused || (abortedLive && settled) || compactionBreaker.failures >= MAX_CONSECUTIVE_COMPACTION_FAILURES,
119
118
  minTokens: rs.counters.compactionFloor,
120
119
  brain: compactionBrain,
121
- windowSafety: windowSafetyOptions(prepared.model),
120
+ windowSafety: windowSafetyOptions(prepared.harness.getModel()),
122
121
  onCompactionFailed: (reason) => queue.push({ type: "compaction_outcome", outcome: "failed", trigger: "auto", reason, ...ident() }),
123
122
  cancelled: abortedLive,
124
123
  }, runner, sessions);
@@ -132,13 +131,14 @@ async function finish(spec, prepared, opts, runner, sessions) {
132
131
  session: prepared.session,
133
132
  epochDeclaredSections: prepared.epochDeclaredSections,
134
133
  ...centerAdoptionOption(prepared),
135
- model: prepared.model,
134
+ model: prepared.harness.getModel(),
135
+ ...legContextOverlayOption(prepared, runner),
136
136
  compactionModel: prepared.compModel,
137
137
  ...forkContextOption(prepared, false),
138
138
  brain: opts?.brain ?? runner.deps.brain,
139
139
  getApiKeyAndHeaders: spec.getApiKeyAndHeaders,
140
140
  thinking: prepared.thinking,
141
- settings: { ...DEFAULT_COMPACTION_SETTINGS, ...spec.compaction },
141
+ settings: legCompactionSettings(spec, legContextOverlayOption(prepared, runner).contextOverlay),
142
142
  customInstructions: spec.compaction?.instructions ?? DEFAULT_COMPACTION_INSTRUCTIONS,
143
143
  minTokens: opts?.minTokens,
144
144
  overheadTokens: prepared.promptOverheadTokens,
@@ -1,12 +1,12 @@
1
+ import { compactionKindOf } from "../task-notification.js";
1
2
  import { mintSystemReminder } from "../reminder-mint.js";
2
- import { DEFAULT_COMPACTION_SETTINGS } from "../../internal/harness.js";
3
3
  import { eventDefaultOn } from "../../prompt-assembly/event-registry.js";
4
4
  import { DEFAULT_COMPACTION_INSTRUCTIONS, isCompactionManualCancel, maybeCompact, mintCompactedEvent, nextTrimForceBackoff, recordCompactionAndCheckRapidRefill } from "../auto-compaction.js";
5
5
  import { emitTrace } from "../trace.js";
6
6
  import { redactThenCut } from "../../agents/subagent-steps.js";
7
7
  import { ATTACHMENT_BYTE_CAP, CHANGED_FILES_MAX, TOTAL_TOKENS_REMINDER_DEFAULT_MODE, attachmentEnvelopeTags, clipToBytes, collectDateChange, collectDueAttachments, collectInstructionsChange, commitAgentListing, commitInstructionsChange, commitSkillsListing, isIntactSource, rebaseCadenceWindows, renderAgentListingFrame, renderMcpDroppedTools, renderMcpInstructionsDelta, selectMcpDroppedBatch, renderSkillsListingFrame, renderToolsDelta } from "./turn-attachments.js";
8
8
  import { announceListingTruncation } from "../skill-listing-budget.js";
9
- import { buildWorkingFileAttachments, centerAdoptionOption, contextInstructionFilesOption, emitInputTruncated, forkContextOption } from "./compaction-call-options.js";
9
+ import { buildWorkingFileAttachments, centerAdoptionOption, contextInstructionFilesOption, emitInputTruncated, forkContextOption, legCompactionSettings, legContextOverlayOption } from "./compaction-call-options.js";
10
10
  import { hookSeatExpiredError, runHookSeat } from "../hooks.js";
11
11
  import { sanitizeUntrustedText, SHELLED_BODY_ENVELOPE_TAGS } from "../untrusted-text.js";
12
12
  import { droppedToolDigest, withAnnounceDigests } from "./announce-once-ledger.js";
@@ -24,7 +24,8 @@ export function createTurnBoundary(input) {
24
24
  const { prepared, stats, rs, deps } = input;
25
25
  const { spec, queue, manualCompactRef, todoToolMounted, taskToolsMounted, walltimeMonotonicDeadline, timeout, ident, postToolBatchHook, batchArgs, compactionBrain, withinTaskCompaction, compactionBreaker, windowSafetyOptions, rapidRefill, drainManualCompact, runnerHooks, runner } = deps;
26
26
  let finalVerifySeenAtLastBoundary = 0;
27
- const onTurnBoundary = async (event) => {
27
+ let legContinues = true;
28
+ const boundaryPass = async (event) => {
28
29
  let boundarySteered = false;
29
30
  const noNextRequest = () => prepared.stopRequestedRef.current || deps.loopLatch.userHalted;
30
31
  const walltimeHit = walltimeMonotonicDeadline !== undefined && performance.now() >= walltimeMonotonicDeadline;
@@ -75,6 +76,7 @@ export function createTurnBoundary(input) {
75
76
  rs.limits.platformTerminal = asThrownError(govErr);
76
77
  void prepared.harness.abort();
77
78
  prepared.abortController.abort();
79
+ legContinues = false;
78
80
  return undefined;
79
81
  }
80
82
  }
@@ -89,6 +91,7 @@ export function createTurnBoundary(input) {
89
91
  if (committed) {
90
92
  rs.limits.turnsExceeded = false;
91
93
  rs.limits.budgetHit = undefined;
94
+ legContinues = false;
92
95
  return undefined;
93
96
  }
94
97
  const terminal = platformLimitTerminal(platformCause, platformRetryAfterMs);
@@ -100,6 +103,7 @@ export function createTurnBoundary(input) {
100
103
  }
101
104
  void prepared.harness.abort();
102
105
  prepared.abortController.abort();
106
+ legContinues = false;
103
107
  return undefined;
104
108
  }
105
109
  if (prepared.suspendForResource &&
@@ -123,6 +127,7 @@ export function createTurnBoundary(input) {
123
127
  void prepared.harness.abort();
124
128
  prepared.abortController.abort();
125
129
  }
130
+ legContinues = false;
126
131
  return undefined;
127
132
  }
128
133
  if (walltimeMonotonicDeadline !== undefined &&
@@ -136,6 +141,7 @@ export function createTurnBoundary(input) {
136
141
  void prepared.harness.abort();
137
142
  prepared.abortController.abort();
138
143
  }
144
+ legContinues = false;
139
145
  return undefined;
140
146
  }
141
147
  const approachCfg = spec.limits?.approachNotice;
@@ -186,6 +192,7 @@ export function createTurnBoundary(input) {
186
192
  prepared.pausedRef.current === undefined;
187
193
  if (honorable && prepared.suspendForReview) {
188
194
  await prepared.suspendForReview(reason);
195
+ legContinues = !prepared.abortController.signal.aborted && prepared.pausedRef.current === undefined;
189
196
  return undefined;
190
197
  }
191
198
  emitTrace(rs.telemetry.tracer, () => ({ kind: "review.dropped", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() }));
@@ -496,7 +503,7 @@ export function createTurnBoundary(input) {
496
503
  });
497
504
  }
498
505
  const forceManual = manualCompactRef.requested;
499
- if ((!withinTaskCompaction && !forceManual) ||
506
+ if ((!withinTaskCompaction() && !forceManual) ||
500
507
  prepared.abortController.signal.aborted ||
501
508
  prepared.pausedRef.current !== undefined) {
502
509
  if (forceManual) {
@@ -504,6 +511,7 @@ export function createTurnBoundary(input) {
504
511
  queue.push({ type: "compaction_outcome", outcome: "mooted", trigger: "manual", reason: "task_ending", ...ident() });
505
512
  drainManualCompact("mooted");
506
513
  }
514
+ legContinues = !prepared.abortController.signal.aborted && prepared.pausedRef.current === undefined;
507
515
  return undefined;
508
516
  }
509
517
  if (compactionBreaker.failures >= MAX_CONSECUTIVE_COMPACTION_FAILURES) {
@@ -564,12 +572,13 @@ export function createTurnBoundary(input) {
564
572
  epochDeclaredSections: prepared.epochDeclaredSections,
565
573
  ...centerAdoptionOption(prepared),
566
574
  model: event.model,
575
+ ...legContextOverlayOption(prepared, runner),
567
576
  compactionModel: prepared.compModel,
568
577
  ...forkContextOption(prepared, false),
569
578
  brain: compactionBrain,
570
579
  getApiKeyAndHeaders: spec.getApiKeyAndHeaders,
571
580
  thinking: prepared.thinking,
572
- settings: { ...DEFAULT_COMPACTION_SETTINGS, ...spec.compaction },
581
+ settings: legCompactionSettings(spec, legContextOverlayOption(prepared, runner).contextOverlay),
573
582
  customInstructions: manualInstructions ?? spec.compaction?.instructions ?? DEFAULT_COMPACTION_INSTRUCTIONS,
574
583
  signal: compactionPassSignal,
575
584
  ...(manualCancelSignal !== undefined ? { manualCancelSignal } : {}),
@@ -644,6 +653,8 @@ export function createTurnBoundary(input) {
644
653
  recordCompactionReuse(runner, prepared, comp);
645
654
  if (comp.compacted) {
646
655
  queue.push(mintCompactedEvent(comp, passTrigger, ident()));
656
+ rs.counters.compactionPasses += 1;
657
+ rs.counters.compactionKind = compactionKindOf(passTrigger);
647
658
  if (!forceManual && recordCompactionAndCheckRapidRefill(rapidRefill, stats.turns)) {
648
659
  compactionBreaker.failures = MAX_CONSECUTIVE_COMPACTION_FAILURES;
649
660
  queue.push({ type: "compaction_outcome", outcome: "disabled", trigger: passTrigger, reason: "rapid_refill: compaction disabled for the rest of this task", ...ident() });
@@ -695,5 +706,21 @@ export function createTurnBoundary(input) {
695
706
  resolveClaimedWaiters(manualOutcome);
696
707
  return undefined;
697
708
  };
709
+ const onTurnBoundary = async (event) => {
710
+ legContinues = true;
711
+ try {
712
+ return await boundaryPass(event);
713
+ }
714
+ finally {
715
+ const round = prepared.peerBoundaryRound;
716
+ if (round !== undefined) {
717
+ try {
718
+ await round({ continues: legContinues });
719
+ }
720
+ catch {
721
+ }
722
+ }
723
+ }
724
+ };
698
725
  return onTurnBoundary;
699
726
  }
@@ -4,7 +4,8 @@ import { type TaskOutcome } from "../task-outcome.js";
4
4
  import { type SwappableDeps } from "../swappable-deps.js";
5
5
  import { type SideQuerySpec, type SideQueryResult } from "../side-query.js";
6
6
  import type { SessionStore } from "../session.js";
7
- import type { AgentDefinition, ModelRoles, RunnerDeps, TaskEvent, TaskResult, TaskSpec, TaskStream } from "../types.js";
7
+ import type { AgentDefinition, ModelRef, ModelRoles, RunnerDeps, TaskEvent, TaskResult, TaskSpec, TaskStream } from "../types.js";
8
+ import { type ContextSettings, type ContextWindowOverlay } from "../context-settings.js";
8
9
  import type { ResumeRun, ResumeTaskConfig, RunInternals, RunnerSelfSeat } from "./contracts.js";
9
10
  export type { ResumeTaskConfig } from "./contracts.js";
10
11
  /**
@@ -77,6 +78,12 @@ export declare class Runner implements RunnerSelfSeat {
77
78
  models?: Record<string, Model>;
78
79
  roles?: ModelRoles;
79
80
  };
81
+ /** The deployment's per-model context-window overlay as declared ({@link RunnerDeps.contextSettings}); a fresh
82
+ * read every call, the overlay objects frozen at their gate. */
83
+ get contextSettings(): ContextSettings | undefined;
84
+ /** The overlay a leg on `ref` reads RIGHT NOW — the one lookup ({@link contextOverlayFor}, tier aliases
85
+ * followed) against the seat and catalog in force, so a `swapDeps` reaches legs already running. */
86
+ contextOverlayOf(ref: ModelRef | undefined): ContextWindowOverlay | undefined;
80
87
  /** The deployment-level gating baseline, exposed READ-ONLY so thin
81
88
  * assemblers over the Runner (`runSpec`) can COMPOSE with it. The task-level engine semantic is a
82
89
  * WHOLE-SLOT override (`spec.toolPolicy ?? deps.toolPolicy`, same for hooks — prepare-task), so any
@@ -21,6 +21,7 @@ import { PAUSE_REGISTRY } from "../pause-registry.js";
21
21
  import { toolPolicyNameSets } from "../tool-policy.js";
22
22
  import { PendingSessionNotifications } from "../task-notification.js";
23
23
  import { ToolDetachHub } from "../tool-detach.js";
24
+ import { assertContextSettingsValue, assertContextSettingsWords, contextOverlayFor } from "../context-settings.js";
24
25
  import { createRunState } from "./initial-run-state.js";
25
26
  import { reconciledToolEndBody } from "./tool-end-body.js";
26
27
  import { answerFaceForRedeemedCall } from "./decide-continuation.js";
@@ -40,6 +41,7 @@ import { streamSteerVerb } from "./stream-steer-verb.js";
40
41
  import { streamLifecycleVerbs } from "./stream-lifecycle-verbs.js";
41
42
  import { streamHaltVerbs } from "./stream-halt-verbs.js";
42
43
  import { runNotificationLane } from "./run-notification-lane.js";
44
+ import { assertNoRetiredRunnerDeps } from "./retired-runner-deps.js";
43
45
  import { runLeg } from "./run-leg.js";
44
46
  import { runTerminalAdoption } from "./run-terminal-adoption.js";
45
47
  import { runSettleAndTeardown } from "./run-settle-and-teardown.js";
@@ -69,12 +71,16 @@ export class Runner {
69
71
  static PARENT_CONSTRAINT_REGISTRY_CAP = 1024;
70
72
  depsSeat;
71
73
  constructor(deps) {
74
+ assertNoRetiredRunnerDeps(deps);
72
75
  const runner = this;
73
76
  this.depsSeat = { get deps() { return runner.deps; } };
74
77
  this.deps = deps;
75
78
  if (this.deps.tiers) {
76
79
  this.deps = { ...this.deps, models: expandTiers(this.deps.models, this.deps.tiers) };
77
80
  }
81
+ if (this.deps.contextSettings !== undefined) {
82
+ this.deps = { ...this.deps, contextSettings: assertContextSettingsWords(assertContextSettingsValue(this.deps.contextSettings, "RunnerDeps"), this.deps.models, "RunnerDeps") };
83
+ }
78
84
  if (this.deps.onError) {
79
85
  const rawOnError = this.deps.onError;
80
86
  const reportSinkFailure = (sinkErr, context) => {
@@ -121,6 +127,12 @@ export class Runner {
121
127
  ...(this.deps.models ? { models: { ...this.deps.models } } : {}), ...(this.deps.roles ? { roles: { ...this.deps.roles } } : {}),
122
128
  };
123
129
  }
130
+ get contextSettings() {
131
+ return this.deps.contextSettings;
132
+ }
133
+ contextOverlayOf(ref) {
134
+ return contextOverlayFor(this.deps.contextSettings, ref, this.deps);
135
+ }
124
136
  get gateBaseline() {
125
137
  const deps = this.deps;
126
138
  const h = deps.hooks;
@@ -232,6 +244,14 @@ export class Runner {
232
244
  else
233
245
  delete writtenFaceSeats[seat];
234
246
  }
247
+ const priorContextSettings = this.deps.contextSettings;
248
+ if (seats.includes("contextSettings")) {
249
+ if (candidate.contextSettings !== undefined)
250
+ written.contextSettings = candidate.contextSettings;
251
+ else
252
+ delete written.contextSettings;
253
+ }
254
+ assertContextSettingsWords(written.contextSettings, written.models, "swapDeps");
235
255
  this.deps = written;
236
256
  if (catalogSwap) {
237
257
  const modelCount = Object.keys(candidate.models).length;
@@ -253,6 +273,17 @@ export class Runner {
253
273
  });
254
274
  }
255
275
  }
276
+ if (seats.includes("contextSettings")) {
277
+ const to = candidate.contextSettings;
278
+ const words = to !== undefined ? Object.keys(to) : [];
279
+ deliverEngineNotice(this.deps.onNotice, {
280
+ code: "config.context_settings_swapped",
281
+ message: `deployment context-window settings swapped: ${words.length} model word(s) declared` +
282
+ `${priorContextSettings !== undefined ? ` (was ${Object.keys(priorContextSettings).length})` : ""}` +
283
+ "; unlike every other swappable seat this one is read LIVE — running legs measure their next boundary against the new windows, while their routing stays on the generation they prepared under",
284
+ detail: { words: words.slice(0, 8), declared: words.length, previousDeclared: priorContextSettings !== undefined ? Object.keys(priorContextSettings).length : 0, occurrenceId: uuidv7() },
285
+ });
286
+ }
256
287
  for (const seat of faceSeats) {
257
288
  const to = candidate[seat] ?? null;
258
289
  const from = priorFaces[seat] ?? null;
@@ -443,7 +474,7 @@ export class Runner {
443
474
  spec, queue, internals, ident, parentToolCallId, subagentName,
444
475
  pushContent, emitCommitted, startedToolCallIds, toolStartAt, toolProgress, writeFamilyOf,
445
476
  toolLabels, postToolBatchHook, batchArgs, internalsNotifier,
446
- runnerHooks: { onError: (e, info) => this.deps.onError?.(e, info) },
477
+ runnerHooks: { onError: (e, info) => this.deps.onError?.(e, info), onNotice: this.deps.onNotice },
447
478
  },
448
479
  });
449
480
  const { compactionBrain, withinTaskCompaction, compactionBreaker, windowSafetyOptions } = runCompactionMachinery({ spec, prepared, rs, stats, noteUnevaluablePriceTable, runner: this.depsSeat });
@@ -476,7 +507,7 @@ export class Runner {
476
507
  undrainedUserAtEnd, userInputLostAtEnd, final, threw, abortedLive, userInterruptedLive, runner: this.depsSeat, sessions: this.sessions,
477
508
  next: ({ threw, comp, answerSettled, owedUserInput }) => runSettleAndTeardown({
478
509
  spec, queue, prepared, resume, internals, rs, stats, ident, parentToolCallId, subagentName, loopLatch, timeout, ownCommittedTailRef,
479
- emitDelegationLifecycle, taskIdRef, manualCompactRef, drainManualCompact, final, threw, abortedLive, userInterruptedLive, answerSettled, strandedHumanAnswers, reasoningResolution, comp, owedUserInput,
510
+ emitDelegationLifecycle, taskIdRef, manualCompactRef, drainManualCompact, final, threw, abortedLive, userInterruptedLive, answerSettled, strandedHumanAnswers, reasoningResolution, comp, compactionBreaker, owedUserInput,
480
511
  setResult, onSuggestions, onSuspend, runner: this.depsSeat, sessions: this.sessions,
481
512
  }),
482
513
  }),
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import type { Model } from "../internal/llm.js";
8
8
  import type { AgentDefinition } from "./agent-definition.js";
9
+ import type { ContextSettings } from "./context-settings.js";
9
10
  import type { BackgroundChildEvent, DelegationLifecycleEvent } from "./delegation-frames.js";
10
11
  import type { EngineNotice } from "./engine-notice.js";
11
12
  import type { HandsBandOptions } from "./hands-band.js";
@@ -113,6 +114,22 @@ export interface RunnerDeps {
113
114
  brain: Brain;
114
115
  /** Catalog used to resolve string ModelRefs to Model objects. */
115
116
  models?: Record<string, Model>;
117
+ /**
118
+ * The deployment's per-model CONTEXT-WINDOW overlay, keyed by CATALOG WORD ({@link ContextSettings}):
119
+ * `contextTokens` / `autoCompactTokens` / `enabled` over what the model card declares.
120
+ *
121
+ * It is the ONE hot-swappable seat an IN-FLIGHT leg re-reads. Every other seat follows the snapshot law
122
+ * (a running task keeps the deps object it prepared under) because those seats are the routing card —
123
+ * endpoint, credentials, price, fence — and a request in flight must reach what it was priced for. The
124
+ * usable window is not the card: it is a setting, and an operator who lowers it is asking the legs
125
+ * running RIGHT NOW to compact sooner, which is exactly what they were unable to do while the number
126
+ * lived on the model object. Read at every turn boundary, every recovery pass, the end-of-task summary
127
+ * and every request build; clamped to the physical window by the one resolver.
128
+ *
129
+ * A leg that was handed a `Model` OBJECT (or resolved one through a `select` criteria) has no catalog
130
+ * word and therefore no overlay — the pin is the caller saying "this exact model, as declared".
131
+ */
132
+ contextSettings?: ContextSettings;
116
133
  /**
117
134
  * design/338 (mid-turn MCP revocation) — the HOST's revocation ledger, probed synchronously at
118
135
  * every MCP dispatch (tool call + the three resource tools) BEFORE the transport. The engine
@@ -512,9 +529,11 @@ export interface RunnerDeps {
512
529
  * {@link InMemoryFileHistoryStore}; a deployment supplies a durable backend behind the same seam
513
530
  * (backends MUST reuse the exported env-half helpers — see the contract JSDoc). Works against ANY
514
531
  * ExecutionEnv (local or remote): bytes are read/written through the env's FileSystem face and
515
- * stored host-side, so an env rebuild does not lose history.
532
+ * stored host-side, so an env rebuild does not lose history. `"disabled"` = the deployment DECIDED
533
+ * not to capture: the omitted-key semantics minus the once-per-Runner `onError` advisory an omitted
534
+ * key earns on a filesystem-capable run (`classification: "file-history-no-store"`).
516
535
  */
517
- fileHistoryStore?: import("./file-history-store.js").FileHistoryStore;
536
+ fileHistoryStore?: import("./file-history-store.js").FileHistoryStore | "disabled";
518
537
  /**
519
538
  * design/381 DV-14 (终审落锤) — what an Edit/Write/NotebookEdit does when its FIRST-TOUCH history
520
539
  * record cannot be persisted (`FileHistoryStore.trackEdit` answered `{ok:false}` = NO durable
@@ -877,12 +896,12 @@ export interface RunnerDeps {
877
896
  maxConcurrent?: number;
878
897
  maxCumulativePerSession?: number;
879
898
  };
880
- /** design/176 — deployment tuning for the always-on peer-message admission gate (SendMessage entry
881
- * judgment: rate/dedup/hop-chain/queue bounds). Per-field range-validated against the upstream
882
- * table with out-of-range values falling back to that field's default; there is NO off switch —
883
- * absent means the default table, not "guard off". Threaded into the auto-mounted SendMessage as
884
- * `SendMessageToolOptions.admission`; read per call (a value change governs the next message). */
885
- peerAdmission?: Partial<import("../agents/peer-admission.js").PeerAdmissionConfig>;
899
+ /** The peer lane's ONE knob table (`PeerLaneConfig`): the always-on admission gate (rate/dedup/hop-chain/
900
+ * queue bounds), the wake and per-pass delivery budgets, and the `ListAgents` freshness window. Per-field
901
+ * range-validated with out-of-range values falling back to that field's default, announced once; there
902
+ * is NO off switch — absent means the default table. Threaded into the auto-mounted SendMessage as
903
+ * `SendMessageToolOptions.lane` and into the session-box drain; read per call / per pass. */
904
+ peerLane?: Partial<import("../agents/peer-admission.js").PeerLaneConfig>;
886
905
  /**
887
906
  * design/385 §2.1 / §5.1 — the peer-session DIRECTORY: the discovery truth of the cross-session
888
907
  * lane (other sessions of this engine for the same user). A HOST implements the contract (cli: the
@@ -917,6 +936,16 @@ export interface RunnerDeps {
917
936
  * {@link peerDirectory}.
918
937
  */
919
938
  crossSessionDialogExpiry?: import("../agents/peer-notices.js").CrossSessionDialogExpiry | (() => import("../agents/peer-notices.js").CrossSessionDialogExpiry | undefined);
939
+ /**
940
+ * The deployment's CAPABILITY FLOOR for durable engine frames: `true` declares that EVERY process
941
+ * that drains this deployment's session boxes reads `engine_frame` records (this engine's codec or
942
+ * newer). With it, an engine-minted task-notification frame addressed to an idle session parks in
943
+ * the session's durable box (`MailboxPeerMeta.kind: "engine_frame"`) and survives a process restart;
944
+ * without it (absent / `false`) such a frame parks in process exactly as before. A deployment flips
945
+ * it only after every drainer is upgraded — a pre-codec drainer reads the record as a peer's words.
946
+ * Inert without a cross-process-safe {@link mailboxStore}.
947
+ */
948
+ sessionBoxEngineFrames?: boolean;
920
949
  /**
921
950
  * design/164 件五 — DEPLOYMENT-level usage governance: allowances that span TASKS, evaluated per
922
951
  * principal (or once for the whole deployment when a task declares none). A different axis from
@@ -1266,11 +1295,13 @@ export interface RunnerDeps {
1266
1295
  phase: "compaction" | "prompt-cache" | "prompt-constitution" | "degraded" | "config" | "memory" | "mcp" | "a2a" | "interrupt-reconcile" | "suggestions" | "rewind" | "hook";
1267
1296
  sessionId: string;
1268
1297
  /**
1269
- * Machine-readable sub-classification when the phase has one — currently the design/31
1270
- * cache-break detector's root cause (`"model-switch" | "tool-schema" | "tool-set" |
1271
- * "system-prefix" | "server-or-ttl"`). Lets the host pick its own log level: `"server-or-ttl"`
1272
- * is usually benign in agentic tasks (slow tools ⇒ 5min+ request gaps expire provider caches —
1273
- * service[64] flag②) and warrants info/warn, while the prefix-bug causes warrant attention.
1298
+ * Machine-readable sub-classification when the site has one (a kebab-case word naming the arm) —
1299
+ * e.g. the design/31 cache-break detector's root cause (`"model-switch" | "tool-schema" |
1300
+ * "tool-set" | "system-prefix" | "server-or-ttl"`), or the file-history liveness arms
1301
+ * (`"file-history-no-store"` / `"file-history-no-env"`). Lets the host pick its own log level:
1302
+ * `"server-or-ttl"` is usually benign in agentic tasks (slow tools ⇒ 5min+ request gaps expire
1303
+ * provider caches — service[64] flag②) and warrants info/warn, while the prefix-bug causes
1304
+ * warrant attention.
1274
1305
  */
1275
1306
  classification?: string;
1276
1307
  }) => void;
@@ -83,15 +83,16 @@ export interface SideQueryResult {
83
83
  args: unknown;
84
84
  }>;
85
85
  /** The RESOLVED pool model id the query was ROUTED to (the caller's ref resolved). See
86
- * `servedModel` for the model that actually answered. */
86
+ * `servedModel` for the engine-selected id the reply came back stamped with. */
87
87
  model: string;
88
- /** The model that ACTUALLY served the reply (a degrading-brain deployment may fall
89
- * back mid-query — billing/quality attribution must follow the real server, not the routing
90
- * intent). Equals `model` when no degradation occurred. */
88
+ /** The ENGINE-SELECTED id the reply came back stamped with (a degrading deployment may fall back
89
+ * mid-query — attribution follows the entry that ran, not the routing intent); equals `model` when
90
+ * nothing degraded. Still THIS catalog's id: what the PROVIDER called it is `responseModel` below. */
91
91
  servedModel: string;
92
92
  /** Structured degradation marker when a fallback model served (from/to/reason/chain). */
93
93
  degraded?: DegradationInfo;
94
- /** Concrete provider-reported model when it differs (e.g. an `auto` route). */
94
+ /** The PROVIDER's own name for the model that answered, when it differs from `servedModel` above.
95
+ * Absent ⇒ it named the same id or named none — one reading, as on `AssistantMessage.responseModel`. */
95
96
  responseModel?: string;
96
97
  /** Usage with `input` NORMALIZED to the uncached prompt quantity (the Anthropic-protocol
97
98
  * `input_tokens` meaning) for EVERY provider family — the same gate the run loop's accounting
@@ -1,6 +1,7 @@
1
1
  import { resolveTaskModel } from "./roles.js";
2
2
  import { readDegradation } from "../brain/degrading.js";
3
3
  import { cacheFamilyOf, uncachedInputTokensOf } from "./runner/usage-accounting.js";
4
+ import { providerReportedModel } from "./message-utils.js";
4
5
  function normalizeSideQueryUsage(model, u) {
5
6
  const input = uncachedInputTokensOf(cacheFamilyOf(model), u);
6
7
  return input === u.input ? u : { ...u, input };
@@ -63,13 +64,14 @@ export async function runSideQuery(spec, deps) {
63
64
  .map((c) => ({ id: c.id, name: c.name, args: c.arguments }));
64
65
  const degraded = readDegradation(msg);
65
66
  const servedModel = typeof msg.model === "string" && msg.model.length > 0 ? msg.model : resolved.model.id;
67
+ const reportedModel = providerReportedModel(msg);
66
68
  return {
67
69
  text,
68
70
  toolCalls,
69
71
  model: resolved.model.id,
70
72
  servedModel,
71
73
  ...(degraded !== undefined ? { degraded } : {}),
72
- ...(msg.responseModel !== undefined && msg.responseModel !== servedModel ? { responseModel: msg.responseModel } : {}),
74
+ ...(reportedModel !== undefined ? { responseModel: reportedModel } : {}),
73
75
  usage: normalizeSideQueryUsage(resolved.model, msg.usage),
74
76
  ...(msg.usageMissing === true ? { usageMissing: true } : {}),
75
77
  stopReason: msg.stopReason,
@@ -12,11 +12,23 @@
12
12
  * the door refuses a key outside the set at run time (never a silent ignore — a hot-config driver
13
13
  * handing `{ brain }` must learn that the brain is not a swappable seat, not watch nothing happen).
14
14
  *
15
- * WHAT A SWAP MEANS — the natural snapshot, one sentence for every seat: a running task reads the deps
16
- * object it was handed at its prepare and holds that reference; a swap replaces the Runner's object, so
15
+ * WHAT A SWAP MEANS — the natural snapshot, one sentence for every ROUTING seat: a running task reads the
16
+ * deps object it was handed at its prepare and holds that reference; a swap replaces the Runner's object, so
17
17
  * every LATER prepare reads the new seat and every in-flight leg keeps the value it prepared under.
18
18
  * Nothing is re-routed, re-priced or re-fenced mid-run; the divergence window is exactly the in-flight
19
- * legs' lifetime, by design (the `swapModels` precedent, now the rule for the whole set).
19
+ * legs' lifetime, by design (the `swapModels` precedent).
20
+ *
21
+ * …WITH ONE SEAT OUTSIDE IT, and the boundary is the point rather than the exception: `contextSettings` is
22
+ * read LIVE by in-flight legs. The snapshot law protects the routing CARD — a request already in flight must
23
+ * reach the endpoint it was priced, credentialed and fenced for, and re-reading any of that mid-run buys
24
+ * nothing and breaks the request. A leg's usable WINDOW is not on that card: it is a setting about how much
25
+ * conversation may accumulate before the leg summarizes, the reference implementation keeps it off the model
26
+ * object for exactly this reason and re-reads it on every request (clamped to the model's physical window),
27
+ * and an operator who lowers it is asking the legs running RIGHT NOW to compact sooner — which is the whole
28
+ * request. So the window declarations live on their own seat (`context-settings.ts`), keyed by catalog word,
29
+ * and the legs read them at every boundary, every recovery pass, the end-of-task summary and every request
30
+ * build. Routing stays snapshotted; geometry follows the operator. The two are not the same question, and
31
+ * one law over both could only be wrong about one of them.
20
32
  *
21
33
  * KEY PRESENCE IS THE INSTRUCTION: a key present on the candidate is REPLACED (an explicit `undefined`
22
34
  * CLEARS an optional seat back to its engine default — `readFace: undefined` ⇒ the deployment declares
@@ -44,8 +56,11 @@ import type { RunnerDeps } from "./runner-deps.js";
44
56
  * deployment seat that resolution reads.
45
57
  * - `writeFace` — the deployment's write-face declaration ({@link RunnerDeps.writeFace}); the read seat's
46
58
  * twin (same value gate, same in-flight snapshot rule, its own notice) and independent of it.
59
+ * - `contextSettings` — the per-model context-window overlay ({@link RunnerDeps.contextSettings}); screened
60
+ * by the same value gate construction applies (`assertContextSettingsValue`). THE ONE SEAT
61
+ * IN-FLIGHT LEGS RE-READ — see the header: it carries geometry, not route.
47
62
  */
48
- export declare const SWAPPABLE_DEP_SEATS: readonly ["models", "tiers", "readFace", "writeFace"];
63
+ export declare const SWAPPABLE_DEP_SEATS: readonly ["models", "tiers", "readFace", "writeFace", "contextSettings"];
49
64
  export type SwappableDepSeat = (typeof SWAPPABLE_DEP_SEATS)[number];
50
65
  /** The candidate a swap takes: the swappable seats of {@link RunnerDeps}, each optional (presence = instruction). */
51
66
  export type SwappableDeps = Pick<RunnerDeps, SwappableDepSeat>;
@@ -61,6 +76,7 @@ export declare const SWAP_SEAT_NOTICE: {
61
76
  readonly tiers: "config.models_swapped";
62
77
  readonly readFace: "config.read_face_swapped";
63
78
  readonly writeFace: "config.write_face_swapped";
79
+ readonly contextSettings: "config.context_settings_swapped";
64
80
  };
65
81
  /** The fence over the notice table: `never` while every seat has a row. */
66
82
  export type SwapNoticeTableCoversEverySeat = AssertAllKeysHandled<Exclude<SwappableDepSeat, keyof typeof SWAP_SEAT_NOTICE>>;
@@ -1,5 +1,6 @@
1
1
  import { assertContainmentFaceValue } from "../tools/fs/containment-face.js";
2
- export const SWAPPABLE_DEP_SEATS = ["models", "tiers", "readFace", "writeFace"];
2
+ import { assertContextSettingsValue } from "./context-settings.js";
3
+ export const SWAPPABLE_DEP_SEATS = ["models", "tiers", "readFace", "writeFace", "contextSettings"];
3
4
  const SWAPPABLE_DEP_SEAT_SET = new Set(SWAPPABLE_DEP_SEATS);
4
5
  export function isSwappableDepSeat(k) {
5
6
  return SWAPPABLE_DEP_SEAT_SET.has(k);
@@ -9,6 +10,7 @@ export const SWAP_SEAT_NOTICE = {
9
10
  tiers: "config.models_swapped",
10
11
  readFace: "config.read_face_swapped",
11
12
  writeFace: "config.write_face_swapped",
13
+ contextSettings: "config.context_settings_swapped",
12
14
  };
13
15
  export function screenSwappableDeps(next) {
14
16
  if (next === null || typeof next !== "object" || Array.isArray(next)) {
@@ -49,6 +51,9 @@ export function screenSwappableDeps(next) {
49
51
  case "writeFace":
50
52
  candidate.writeFace = assertContainmentFaceValue(v, "write", "deps");
51
53
  break;
54
+ case "contextSettings":
55
+ candidate.contextSettings = assertContextSettingsValue(v, "swapDeps");
56
+ break;
52
57
  default: {
53
58
  const _exhaustive = seat;
54
59
  throw new Error(`swapDeps: unhandled seat ${String(_exhaustive)}`);
@@ -581,6 +581,14 @@ export type TaskEvent = ({
581
581
  * no assistant message. Typed `string` (not the internal `StopReason`) to keep the engine's union
582
582
  * private, but the value set is exactly those five tokens. */
583
583
  stopReason?: string;
584
+ /** The PROVIDER'S OWN name for the model that answered this turn, when it DIFFERS from the id the
585
+ * request was sent with — a SECOND fact beside `TaskResult.model` / the trace's `brain.call.model`,
586
+ * which stay the ENGINE-SELECTED pool id a resume re-seeds and a catalog resolves, never an
587
+ * overwrite of either. PRESENCE: the turn's LAST assistant message carried one
588
+ * (`AssistantMessage.responseModel`). ABSENT ⇒ the provider named the requested id or named nothing —
589
+ * ONE reading, not two, as on the reference mismatch latch, also written only for a disagreeing pair.
590
+ * A mismatch is additionally announced once per leg per pair as `route.response_model_mismatch`. */
591
+ responseModel?: string;
584
592
  } & TaskEventIdentity) | ({
585
593
  /** design/99 MF-18: a compaction boundary (or, since design/119 #1, mid-turn when the prompt-too-long recovery force-compacts between a failed request and its retry). `trigger` = what caused it: `"auto"` (the context-threshold
586
594
  * auto-compaction), `"manual"` (the `TaskStream.compact` / `/compact` path), or `"forced"` (RB-398-b
@@ -621,6 +629,11 @@ export type TaskEvent = ({
621
629
  preserved_segment?: {
622
630
  firstKeptEntryId: string;
623
631
  };
632
+ /** Cross-session message frames that sat in the SUMMARIZED prefix of this pass — delivered
633
+ * earlier and visible to the model for the turns since. A record still in the session's box is
634
+ * handed over again by the engine (once; a second loss is disclosed `resupply_exhausted`), so
635
+ * this counts what the summary swallowed, not what is gone. Present iff at least one. */
636
+ summarizedCrossSessionMessages?: number;
624
637
  /** CC post-compact file-restore parity (2026-07-03): the working files the engine
625
638
  * re-read into the summary (attachWorkingFiles), attachment order. A shell renders CC's
626
639
  * `Read {path} (…)` cards under the compaction divider from this. Absent when the feature is