@sema-agent/core 5.64.0 → 6.0.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 (165) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/dist/agents/subagent.d.ts +2 -2
  3. package/dist/agents/subagent.js +11 -0
  4. package/dist/agents/verify.d.ts +1 -1
  5. package/dist/brain/anthropic.js +1 -1
  6. package/dist/brain/errors.d.ts +29 -0
  7. package/dist/brain/errors.js +20 -0
  8. package/dist/brain/open-responses.js +2 -2
  9. package/dist/brain/route-adjudicator.d.ts +8 -1
  10. package/dist/brain/route-adjudicator.js +1 -0
  11. package/dist/brain/status-sink.js +12 -1
  12. package/dist/brain/stream-engine.js +17 -6
  13. package/dist/core/auto-compaction.d.ts +26 -0
  14. package/dist/core/auto-compaction.js +7 -2
  15. package/dist/core/auto-mode-arming.d.ts +138 -0
  16. package/dist/core/auto-mode-arming.js +181 -0
  17. package/dist/core/auto-mode-defaults.d.ts +13 -0
  18. package/dist/core/auto-mode-defaults.js +5 -0
  19. package/dist/core/auto-mode-prompt.d.ts +14 -3
  20. package/dist/core/auto-mode-prompt.js +10 -7
  21. package/dist/core/auto-mode-rebuild.d.ts +75 -0
  22. package/dist/core/auto-mode-rebuild.js +41 -0
  23. package/dist/core/auto-mode.d.ts +15 -0
  24. package/dist/core/auto-mode.js +4 -2
  25. package/dist/core/checkpoint-store.d.ts +113 -4
  26. package/dist/core/context-edit.d.ts +47 -5
  27. package/dist/core/context-guard.d.ts +1 -1
  28. package/dist/core/file-history-retention.d.ts +106 -0
  29. package/dist/core/file-history-retention.js +36 -0
  30. package/dist/core/file-history-store.d.ts +768 -0
  31. package/dist/core/file-history-store.js +880 -0
  32. package/dist/core/governance-codes.d.ts +2 -1
  33. package/dist/core/governance-codes.js +14 -0
  34. package/dist/core/hooks.d.ts +48 -8
  35. package/dist/core/hooks.js +39 -22
  36. package/dist/core/lsp.d.ts +2 -2
  37. package/dist/core/mcp.d.ts +29 -7
  38. package/dist/core/memory-engine/consolidation-driver.d.ts +11 -0
  39. package/dist/core/memory-engine/consolidation-driver.js +71 -4
  40. package/dist/core/memory-engine/consolidation.d.ts +25 -2
  41. package/dist/core/memory-engine/consolidation.js +4 -1
  42. package/dist/core/memory-engine/distiller.d.ts +84 -1
  43. package/dist/core/memory-engine/distiller.js +68 -0
  44. package/dist/core/memory-engine/dual-root.js +6 -0
  45. package/dist/core/memory-engine/engine.d.ts +329 -15
  46. package/dist/core/memory-engine/engine.js +364 -34
  47. package/dist/core/memory-engine/file-backend.d.ts +30 -0
  48. package/dist/core/memory-engine/file-backend.js +14 -13
  49. package/dist/core/memory-engine/frontmatter.d.ts +22 -1
  50. package/dist/core/memory-engine/frontmatter.js +3 -0
  51. package/dist/core/memory-engine/header-hints.d.ts +5 -0
  52. package/dist/core/memory-engine/index.d.ts +5 -4
  53. package/dist/core/memory-engine/index.js +5 -4
  54. package/dist/core/memory-engine/layout.d.ts +88 -2
  55. package/dist/core/memory-engine/layout.js +112 -3
  56. package/dist/core/memory-engine/provenance-wording.d.ts +7 -0
  57. package/dist/core/memory-engine/provenance-wording.js +3 -0
  58. package/dist/core/memory-engine/tools.d.ts +89 -8
  59. package/dist/core/memory-engine/tools.js +263 -22
  60. package/dist/core/memory-engine/types.d.ts +80 -1
  61. package/dist/core/memory-recall.d.ts +6 -0
  62. package/dist/core/memory.d.ts +27 -1
  63. package/dist/core/memory.js +16 -2
  64. package/dist/core/permission-rule-consent.d.ts +20 -0
  65. package/dist/core/permission-rule-consent.js +12 -3
  66. package/dist/core/permission-rule-model.d.ts +67 -7
  67. package/dist/core/permission-rule-model.js +53 -7
  68. package/dist/core/permission-rule-store.js +15 -10
  69. package/dist/core/permission-rule-sync.js +15 -11
  70. package/dist/core/remote-env.d.ts +3 -3
  71. package/dist/core/retention-policy.d.ts +9 -0
  72. package/dist/core/retention-policy.js +5 -2
  73. package/dist/core/retention.d.ts +13 -2
  74. package/dist/core/runner/assemble-result.d.ts +19 -1
  75. package/dist/core/runner/assemble-result.js +17 -2
  76. package/dist/core/runner/compaction-call-options.d.ts +93 -0
  77. package/dist/core/runner/compaction-call-options.js +3 -0
  78. package/dist/core/runner/memory-capture-optout.d.ts +80 -0
  79. package/dist/core/runner/memory-capture-optout.js +53 -0
  80. package/dist/core/runner/prepare-config-doors.d.ts +5 -0
  81. package/dist/core/runner/prepare-config-doors.js +16 -0
  82. package/dist/core/runner/prepare-hands-readface.d.ts +119 -5
  83. package/dist/core/runner/prepare-hands-readface.js +103 -8
  84. package/dist/core/runner/prepare-memory.d.ts +88 -0
  85. package/dist/core/runner/prepare-memory.js +306 -25
  86. package/dist/core/runner/prepare-task.d.ts +156 -5
  87. package/dist/core/runner/prepare-task.js +488 -98
  88. package/dist/core/runner/runtask.d.ts +27 -20
  89. package/dist/core/runner/runtask.js +283 -99
  90. package/dist/core/runner/session-file-state-replay.d.ts +18 -10
  91. package/dist/core/runner/session-file-state-replay.js +52 -1
  92. package/dist/core/runner/tool-disclosure.js +2 -1
  93. package/dist/core/runner/turn-attachments.d.ts +22 -12
  94. package/dist/core/session-store.d.ts +1 -1
  95. package/dist/core/session-store.js +6 -1
  96. package/dist/core/session.d.ts +34 -1
  97. package/dist/core/store-contracts/file-history-store-contract.d.ts +3 -0
  98. package/dist/core/store-contracts/file-history-store-contract.js +720 -0
  99. package/dist/core/stub-env.d.ts +4 -0
  100. package/dist/core/stub-env.js +1 -0
  101. package/dist/core/task-registry-shared.js +30 -2
  102. package/dist/core/tool-errors.js +1 -0
  103. package/dist/core/tool-policy.d.ts +172 -1
  104. package/dist/core/tool-policy.js +32 -1
  105. package/dist/core/tool-result-store.js +2 -1
  106. package/dist/core/trace.d.ts +24 -0
  107. package/dist/core/types.d.ts +875 -97
  108. package/dist/core/types.js +4 -3
  109. package/dist/core/untrusted-text.d.ts +1 -1
  110. package/dist/core/untrusted-text.js +8 -0
  111. package/dist/core/workflow-run-store-contract.js +17 -0
  112. package/dist/core/workflow-run-store.d.ts +20 -0
  113. package/dist/core/workflow-run-store.js +1 -0
  114. package/dist/engine/compaction/compaction.d.ts +88 -10
  115. package/dist/engine/compaction/compaction.js +109 -30
  116. package/dist/engine/execution-env/node-execution-env.d.ts +9 -1
  117. package/dist/engine/execution-env/node-execution-env.js +28 -0
  118. package/dist/engine/harness/agent-harness.d.ts +52 -1
  119. package/dist/engine/harness/agent-harness.js +36 -1
  120. package/dist/engine/harness/types.d.ts +44 -1
  121. package/dist/engine/llm/types.d.ts +50 -4
  122. package/dist/engine/loop/agent-loop.d.ts +5 -1
  123. package/dist/engine/loop/agent-loop.js +25 -0
  124. package/dist/engine/loop/types.d.ts +19 -0
  125. package/dist/engine/lsp/node-lsp-manager.d.ts +1 -1
  126. package/dist/engine/session/session.js +1 -1
  127. package/dist/index.d.ts +18 -8
  128. package/dist/index.js +14 -6
  129. package/dist/orchestration/run-workflow-tool.d.ts +20 -2
  130. package/dist/orchestration/run-workflow-tool.js +22 -3
  131. package/dist/orchestration/workflow-governance.d.ts +59 -1
  132. package/dist/orchestration/workflow-governance.js +61 -8
  133. package/dist/orchestration/workflow-meta.d.ts +4 -2
  134. package/dist/orchestration/workflow-primitives.js +56 -13
  135. package/dist/orchestration/workflow-types.d.ts +112 -1
  136. package/dist/orchestration/workflow-types.js +2 -2
  137. package/dist/orchestration/workflow.d.ts +20 -0
  138. package/dist/orchestration/workflow.js +182 -14
  139. package/dist/prompt-assembly/event-registry.js +1 -1
  140. package/dist/prompts/default.d.ts +15 -7
  141. package/dist/prompts/default.js +3 -0
  142. package/dist/stores/file/file-history-store.d.ts +368 -0
  143. package/dist/stores/file/file-history-store.js +1248 -0
  144. package/dist/stores/file/index.d.ts +22 -13
  145. package/dist/stores/file/index.js +4 -4
  146. package/dist/stores/file/permission-rule-store.js +1 -0
  147. package/dist/stores/file/strategy-store.d.ts +3 -3
  148. package/dist/tools/fs/bash-readonly-classifier.d.ts +87 -3
  149. package/dist/tools/fs/bash-readonly-classifier.js +106 -4
  150. package/dist/tools/fs/fs-bash.js +9 -5
  151. package/dist/tools/fs/fs-shared.d.ts +52 -1
  152. package/dist/tools/fs/fs-shared.js +14 -0
  153. package/dist/tools/fs/fs-write.d.ts +5 -5
  154. package/dist/tools/fs/fs-write.js +71 -14
  155. package/dist/tools/fs/index.d.ts +6 -1
  156. package/dist/tools/fs/index.js +1 -1
  157. package/dist/tools/web.js +2 -1
  158. package/package.json +5 -1
  159. package/test/export-surface.snapshot.json +159 -23
  160. package/dist/core/file-snapshot-store.d.ts +0 -165
  161. package/dist/core/file-snapshot-store.js +0 -259
  162. package/dist/core/store-contracts/file-snapshot-store-contract.d.ts +0 -13
  163. package/dist/core/store-contracts/file-snapshot-store-contract.js +0 -134
  164. package/dist/stores/file/file-snapshot-store.d.ts +0 -58
  165. package/dist/stores/file/file-snapshot-store.js +0 -353
@@ -18,11 +18,17 @@
18
18
  * a `git checkout`, a bash command — anything this transcript does not record) therefore fails
19
19
  * the staleness comparison and the edit is refused, exactly as it is inside a single task. A
20
20
  * reconstruction that re-read the file to build its hash would instead wave the dirty edit
21
- * through; that is the one thing this module must never do.
22
- * 2. **Only WHOLE-file records qualify.** A partial read, a notebook projection or an `Edit` result
23
- * does not carry the file's full post-condition text, so there is nothing to hash honestly.
24
- * Those files are simply not reported: the gate then behaves exactly as it does today (the model
25
- * re-reads), which is the pre-existing cost, not a new failure.
21
+ * through; that is the one thing this module must never do. (#483 D1, ruled 2026-08-28: this is
22
+ * deliberately STRICTER than CC's own rehydrate, whose Edit arm re-reads the disk and would
23
+ * admit a between-turns external change same gate satisfaction, no staleness give-up.)
24
+ * 2. **Only records whose full post-condition is PROVABLE qualify.** A whole-file `Read` and a
25
+ * `Write` carry the text verbatim. An `Edit` card (#483 rung-2) carries a diff BASE — the
26
+ * pre-edit whole text plus the resolved mutations — so its post-condition is RECOMPUTED through
27
+ * the same single-source application step the tool itself used (`applyRecordedEdit`); a card
28
+ * that cannot recompute (malformed, offloaded, or its oldString absent from its own base) falls
29
+ * back to RETRACTING the file's record, never to a guess. A partial read or a notebook
30
+ * projection proves nothing and is simply not reported: the gate then behaves exactly as before
31
+ * (the model re-reads), which is the pre-existing cost, not a new failure.
26
32
  */
27
33
  import type { AgentMessage } from "../../internal/harness.js";
28
34
  /** The whole text of one file at the moment the transcript recorded it. */
@@ -39,11 +45,13 @@ export interface TranscriptFileRecord {
39
45
  *
40
46
  * Records are read only from SUCCESSFUL results (an `isError` result changed nothing and proves
41
47
  * nothing). A `Write` proves the post-condition it just persisted; a whole-file `Read` proves what it
42
- * just served. An `Edit` result proves that the file CHANGED without recording its new text, so it
43
- * retracts whatever an earlier record said leaving a stale entry behind would blame the user for
44
- * the agent's own edit ("modified since read") the next turn. Anything else (partial reads, dedup
45
- * stubs, notebook projections, image/document reads, foreign tools) is inert: it neither proves nor
46
- * retracts, because reading a file does not change it.
48
+ * just served. An `Edit` card (#483 rung-2) proves the post-condition RECOMPUTED from its recorded
49
+ * diff base + mutations or, when it cannot prove (malformed / offloaded / inconsistent), retracts
50
+ * whatever an earlier record said (leaving a stale entry behind would blame the user for the agent's
51
+ * own edit, "modified since read", the next turn). A `NotebookEdit` always retracts (deliberate
52
+ * residual see {@link RETRACTING_RESULTS}). Anything else (partial reads, dedup stubs, notebook
53
+ * projections, image/document reads, foreign tools) is inert: it neither proves nor retracts,
54
+ * because reading a file does not change it.
47
55
  *
48
56
  * The parameter is the session's own message lane (`AgentMessage`, custom lanes included) rather than
49
57
  * the narrowed model-facing union: this reads the branch as the session stores it, and every lane that
@@ -1,11 +1,49 @@
1
1
  import { isAbsolutePathForm } from "../../tools/fs/safety.js";
2
+ import { applyRecordedEdit } from "../../tools/fs/fs-shared.js";
3
+ import { normalizeFileText } from "../../tools/fs/encoding.js";
2
4
  import { isOffloadedDetailReplacement } from "../tool-result-store.js";
3
5
  const READ_TOOL = "Read";
4
6
  const WRITE_TOOL = "Write";
7
+ const EDIT_TOOL = "Edit";
5
8
  const RETRACTING_RESULTS = [
6
- { toolName: "Edit", type: "edit", pathField: "filePath" },
7
9
  { toolName: "NotebookEdit", type: "notebook-edit", pathField: "notebookPath" },
8
10
  ];
11
+ function replayedEditContent(rest) {
12
+ const originalFile = rest.originalFile;
13
+ if (typeof originalFile !== "string" || isOffloadedDetailReplacement(originalFile))
14
+ return undefined;
15
+ const member = (v) => (typeof v === "string" && !isOffloadedDetailReplacement(v) ? v : undefined);
16
+ let edits;
17
+ const rawEdits = rest.edits;
18
+ if (Array.isArray(rawEdits) && rawEdits.length > 0) {
19
+ const out = [];
20
+ for (const e of rawEdits) {
21
+ const r = e;
22
+ const oldS = member(r?.oldString);
23
+ const newS = member(r?.newString);
24
+ if (oldS === undefined || newS === undefined)
25
+ return undefined;
26
+ out.push({ oldString: oldS, newString: newS, replaceAll: r.replaceAll === true });
27
+ }
28
+ edits = out;
29
+ }
30
+ else {
31
+ const oldS = member(rest.oldString);
32
+ const newS = member(rest.newString);
33
+ if (oldS === undefined || newS === undefined)
34
+ return undefined;
35
+ edits = [{ oldString: oldS, newString: newS, replaceAll: rest.replaceAll === true }];
36
+ }
37
+ let working = originalFile;
38
+ for (const e of edits) {
39
+ if (e.oldString !== "" && !working.includes(e.oldString))
40
+ return undefined;
41
+ working = applyRecordedEdit(working, e);
42
+ }
43
+ if (normalizeFileText(working) !== working)
44
+ return undefined;
45
+ return working;
46
+ }
9
47
  function cardOf(details) {
10
48
  if (typeof details !== "object" || details === null)
11
49
  return undefined;
@@ -32,6 +70,19 @@ export function wholeFileRecordsFromTranscript(messages) {
32
70
  if (card === undefined)
33
71
  continue;
34
72
  const { type, rest } = card;
73
+ if (m.toolName === EDIT_TOOL && type === "edit") {
74
+ const changed = rest.filePath;
75
+ if (typeof changed !== "string")
76
+ continue;
77
+ const replayed = replayedEditContent(rest);
78
+ if (replayed !== undefined && isAbsolutePathForm(changed)) {
79
+ byPath.set(changed, { path: changed, content: replayed, at: m.timestamp });
80
+ }
81
+ else {
82
+ byPath.delete(changed);
83
+ }
84
+ continue;
85
+ }
35
86
  const retracts = RETRACTING_RESULTS.find((r) => r.toolName === m.toolName && r.type === type);
36
87
  if (retracts !== undefined) {
37
88
  const changed = rest[retracts.pathField];
@@ -2,9 +2,10 @@ import { Type } from "typebox";
2
2
  import { Value } from "typebox/value";
3
3
  import { defineTool, errorResult } from "../tools.js";
4
4
  import { formatZodValidationError, truncateError } from "../tool-errors.js";
5
+ import { DEFAULT_CHARS_PER_TOKEN } from "../../internal/harness.js";
5
6
  export const TOOL_SEARCH_NAME = "ToolSearch";
6
7
  const DEFER_AUTO_FRACTION = 0.1;
7
- const CHARS_PER_TOKEN = 4;
8
+ const CHARS_PER_TOKEN = DEFAULT_CHARS_PER_TOKEN;
8
9
  export const TOOL_SEARCH_DEFAULT_MAX_RESULTS = 5;
9
10
  const MAX_QUERY_RESULTS = 25;
10
11
  export const DEFERRED_NO_PROGRESS_LIMIT = 3;
@@ -437,9 +437,11 @@ export interface AttachmentInputs {
437
437
  /**
438
438
  * RB-318 (裁 B, 2026-08-26) — `total_tokens_reminder` (CC 2.1.245 producer `MPs`, renderer `Oje`,
439
439
  * registered in the SHARED producer group beside `budget_usd`, so a subagent carrying its own
440
- * ceiling sees it too — the RB-311 attribution verbatim). Opt-in, default OFF like every other 133
441
- * member: an un-opted deployment renders no reminder frame the ATTACHMENT lane itself is
442
- * byte-identical to the pre-lane engine. (Scoped deliberately to this lane: registering
440
+ * ceiling sees it too — the RB-311 attribution verbatim). DEFAULT ON since design/374 slice 4
441
+ * (ruled 2026-08-28, matching 2.1.245+'s own default); explicit `false` opts out. Before the
442
+ * flip an un-opted deployment rendered no reminder frame at all. The countdown arms still need a
443
+ * declared ceiling, so a task without one is silent either way. (Scoped deliberately to this lane:
444
+ * registering
443
445
  * `<total_tokens>` in ENGINE_ENVELOPES changed the SANITIZE/DISCLOSURE faces for every
444
446
  * deployment, opted or not — untrusted text carrying that spelling is now defused on the fenced
445
447
  * lanes and envelope-shaped external data trips the design/319 disclosure tail. That is the
@@ -454,6 +456,14 @@ export interface AttachmentInputs {
454
456
  * ({@link AttachmentInputs.totalTokens}), the ceiling is the deployment's own declared
455
457
  * `limits.maxTokens`, and with no ceiling declared the countdown arms simply never fire.
456
458
  *
459
+ * AND WHY IT IS NOW ON BY DEFAULT (design/374 slice 4, 2026-08-28): the same 245 fact that
460
+ * justified BUILDING it is a statement about its DEFAULT, so shipping it opt-in left the engine
461
+ * one ruling short of the anchor. Both halves of "is this safe on by default" hold here — the
462
+ * readout cannot be wrong (it reads the enforced coordinate, never an estimate), and it cannot
463
+ * appear where nobody asked for a ceiling (no ceiling ⇒ no numbers ⇒ silence). The `budget_usd`
464
+ * twin stays opt-in: CC has no default-on posture for it, and inventing one would be the
465
+ * unanchored move.
466
+ *
457
467
  * The arm is chosen by {@link totalTokensReminderMode} (default {@link
458
468
  * TOTAL_TOKENS_REMINDER_DEFAULT_MODE}); `off` there silences the lane without un-wiring it.
459
469
  */
@@ -482,7 +492,7 @@ export interface AttachmentInputs {
482
492
  * activated), sorted, snapshotted by the run loop. CC 220's equivalent is
483
493
  * `tools.filter(iY(u) && !discovered.has(u.name)).map(name).sort()` (@608934-608938). Only passed
484
494
  * when `config.toolSearchReminder` is opted in AND the task actually has deferred tools — an empty
485
- * or absent list silences the lane permanently (CC `no_undiscovered_tools`, @608939). */
495
+ * or absent list silences the lane permanently (CC `no_undiscovered_tools`, @608939 (cli250.js:44368)). */
486
496
  undiscoveredTools?: readonly string[];
487
497
  /** RB-403 — is the direct-call lane mounted on this run's placeholders (`Prepared.deferDirectCall`,
488
498
  * i.e. `TaskSpec.deferSelfResolve` not disabled)? Selects which closing sentence the
@@ -673,7 +683,7 @@ export declare function renderOrphanedBackgroundTasks(tasks: ReadonlyArray<{
673
683
  description?: string;
674
684
  }>): string;
675
685
  /**
676
- * RB-309 — CC's per-frame list cap for the `deferred_tools_delta` arms (`lP`, pretty220.js:518269).
686
+ * RB-309 — CC's per-frame list cap for the `deferred_tools_delta` arms (`lP`, (pretty220.js:518269, historical; not relocated in 250 — see cc-250 anchors)).
677
687
  * Lists longer than this collapse to a grouped one-liner + "…and N more".
678
688
  */
679
689
  export declare const TOOLS_DELTA_LIST_MAX = 30;
@@ -682,13 +692,13 @@ export declare const TOOLS_DELTA_LIST_MAX = 30;
682
692
  * are CALLER-drained on intact cap survival, exactly like {@link AttachmentInputs.newTools}. */
683
693
  export interface McpToolsDeltaFacts {
684
694
  /** Previously-announced deferred tools that LEFT the harness roster (their server withdrew them —
685
- * reached through RefreshMcpTools's prefix re-splice). CC `removedNames` @626530. */
695
+ * reached through RefreshMcpTools's prefix re-splice). CC `removedNames` @626530 (cli250.js:45892). */
686
696
  removed?: readonly string[];
687
697
  /** Previously-announced-then-withdrawn deferred tools that came BACK. CC `readdedNames` @626525. */
688
698
  readded?: readonly string[];
689
699
  /** Declared MCP servers that failed to connect at materialize (`MaterializedMcp.statuses`, status
690
700
  * `"failed"`). `error` is REMOTE/transport text — the producer neutralizes + bounds it at intake.
691
- * CC `failedMcpServers` @626561. */
701
+ * CC `failedMcpServers` @626561 (cli250.js:45904). */
692
702
  failedServers?: ReadonlyArray<{
693
703
  name: string;
694
704
  error?: string;
@@ -704,15 +714,15 @@ export interface McpToolsDeltaFacts {
704
714
  * the middle clause is inverted to match sema's actual contract. Same CC shape: lead sentence +
705
715
  * name list.
706
716
  *
707
- * RB-309 — CC 2.1.220 (`deferred_tools_delta`, pretty220.js:626512-626600) carries SIX arms; sema
717
+ * RB-309 — CC 2.1.220 (`deferred_tools_delta`, cli250.js:45890-45918) carries SIX arms; sema
708
718
  * shipped only the `added` one. The arms below are the ones whose STATE IS REACHABLE in this engine
709
719
  * (data-side audit, RB-309):
710
- * - `readded` (@626525) / `removed` (@626530): reachable via RefreshMcpTools — the refresh splices a
720
+ * - `readded` (@626525) / `removed` (@626530 (cli250.js:45892)): reachable via RefreshMcpTools — the refresh splices a
711
721
  * server's whole `mcp__<server>__` prefix domain out of the live tool array and pushes the fresh
712
722
  * listing back, so a withdrawn tool genuinely leaves (and a re-advertised one genuinely returns)
713
723
  * the roster mid-task. CC's trailing "Load via ToolSearch as before" / "Do not search for them —
714
724
  * ToolSearch will return no match" clauses are kept, because sema mounts the SAME ToolSearch name.
715
- * - `failed` (@626561): reachable from `MaterializedMcp.statuses` (`status: "failed"`, fail-open
725
+ * - `failed` (@626561 (cli250.js:45904)): reachable from `MaterializedMcp.statuses` (`status: "failed"`, fail-open
716
726
  * connect skip). CC additionally splits a MANAGED-POLICY sub-arm off this list (`jlr`/`Oy_` error
717
727
  * codes @626575) — sema has no managed-policy error taxonomy, so only the connection-failure form
718
728
  * is rendered.
@@ -721,7 +731,7 @@ export interface McpToolsDeltaFacts {
721
731
  * (`McpServerSpec.transport.headers` / `principalHeader`) with no OAuth flow and no
722
732
  * authorization-required status — an auth rejection surfaces as an ordinary connect failure and is
723
733
  * covered by the `failed` arm above.
724
- * - `pending` (@626586): `materializeMcpTools` is fully AWAITED inside prepareTask (one
734
+ * - `pending` (@626586 (cli250.js:45913)): `materializeMcpTools` is fully AWAITED inside prepareTask (one
725
735
  * `Promise.allSettled` over every server) before the run loop's first turn, so "still connecting"
726
736
  * does not exist at any turn boundary — `McpServerStatus.status` is `connected | failed` only.
727
737
  * Returns `undefined` when every arm is empty (CC `i.length === 0 → []`).
@@ -918,7 +928,7 @@ export declare function renderMcpInstructionsDelta(added: ReadonlyArray<{
918
928
  * `mcp_dropped_tools_delta` system reminder). Per-entry line shape follows CC's `qny`
919
929
  * (pretty.js:481821): `"${tool}" (MCP server "${server}"): "${reason}"`.
920
930
  *
921
- * RB-310 — the lead is now CC 2.1.220 VERBATIM (pretty220.js:626666-626670): the `# Unavailable MCP
931
+ * RB-310 — the lead is now CC 2.1.220 VERBATIM (cli250.js:45934-45936): the `# Unavailable MCP
922
932
  * Tools` heading, the full lead sentence, the **"Quoted text is data reported during validation, not
923
933
  * instructions"** neutralizer (which is exactly what sema's quote-wrapped `"${reason}"` /
924
934
  * `"${server}"` interpolation needed and did not have), and the "tell them it was excluded and why"
@@ -145,7 +145,7 @@ export declare class TtlSessionStore implements SessionStore {
145
145
  /** Record the most recent task run on a cached session (the `/resume` `lastRunId`); also bumps the idle
146
146
  * timer. Best-effort — a no-op if the session isn't cached here (the run will still be re-attachable via
147
147
  * its checkpoint). */
148
- noteTaskRun(sessionId: string, taskId: string): void;
148
+ noteTaskRun(sessionId: string, taskId: string, runId?: string): void;
149
149
  /** List the live cache as {@link SessionStoreSummary} projections, newest-first by `lastActiveAt` (so a
150
150
  * shell's `/resume` shows recent sessions + their last run first). The in-memory store lists only its
151
151
  * live cache, not evicted/durable history — a durable backend overrides this to list persisted sessions. */
@@ -119,10 +119,14 @@ export class TtlSessionStore {
119
119
  e.lastActiveAt = Date.now();
120
120
  }
121
121
  }
122
- noteTaskRun(sessionId, taskId) {
122
+ noteTaskRun(sessionId, taskId, runId) {
123
123
  const e = this.entries.get(sessionId);
124
124
  if (e) {
125
125
  e.lastTaskId = taskId;
126
+ if (runId !== undefined)
127
+ e.lastTaskRunId = runId;
128
+ else
129
+ delete e.lastTaskRunId;
126
130
  e.lastActiveAt = Date.now();
127
131
  }
128
132
  }
@@ -134,6 +138,7 @@ export class TtlSessionStore {
134
138
  lastActiveAt: e.lastActiveAt,
135
139
  ...(e.createdAt !== undefined && e.createdAt !== "" ? { createdAt: e.createdAt } : {}),
136
140
  ...(e.lastTaskId !== undefined ? { lastTaskId: e.lastTaskId } : {}),
141
+ ...(e.lastTaskRunId !== undefined ? { lastTaskRunId: e.lastTaskRunId } : {}),
137
142
  ...(e.forkedFrom !== undefined ? { forkedFrom: e.forkedFrom } : {}),
138
143
  }))
139
144
  .sort((a, b) => b.lastActiveAt - a.lastActiveAt);
@@ -73,6 +73,30 @@ export interface SessionStoreSummary {
73
73
  /** The most recent task run on this session (core's `taskId`; the service projects it as `lastRunId`).
74
74
  * Absent until a task has run on the session (recorded via {@link SessionStore.noteTaskRun}). */
75
75
  lastTaskId?: string;
76
+ /**
77
+ * #499 — an ENGINE-minted run identity for this session ({@link import("./types.js").TaskResult.runId}),
78
+ * recorded by the most recent {@link SessionStore.noteTaskRun} write to LAND.
79
+ * {@link lastTaskId} above is `spec.taskId ?? sessionId`, so on the ordinary deployment that supplies no
80
+ * host task id it answers the SESSION's own id: a "last run" handle that names the session it hangs off
81
+ * cannot say WHICH run it means. This one names a specific run — read the ORDERING clause below for
82
+ * exactly how strong "most recent" is here, which is weaker than the sibling field's wording implies.
83
+ *
84
+ * Named `lastTaskRunId`, NOT `lastRunId`: the paragraph above this interface records that a service
85
+ * backend renames `lastTaskId` → the WIRE field `lastRunId`, so a store field spelled `lastRunId` would
86
+ * be the same name carrying a different identity one hop apart — a mixed-version mapper (or an object
87
+ * spread) could then publish either value under the one wire name with no type error. Same
88
+ * collision-avoidance as {@link import("../orchestration/workflow-types.js").WorkflowAgentRun.taskRunId}.
89
+ *
90
+ * ORDERING (honest limit): the Runner records this fire-and-forget, exactly as it always has for
91
+ * `lastTaskId`, so on a store whose write is asynchronous two runs of one session race and the loser's
92
+ * id can land last. That race was invisible while both runs wrote the same `lastTaskId`; it is
93
+ * observable here. Read this as "an id of a recent run", and take strict ordering from the runs'
94
+ * own results, not from this projection.
95
+ *
96
+ * Additive and optional: absent on a store that predates it, absent until a task has run, and absent
97
+ * whenever the recording caller had no run id (see {@link SessionStore.noteTaskRun}).
98
+ */
99
+ lastTaskRunId?: string;
76
100
  /** F7 fork-GC seam: the parent session this one was forked from ({@link SessionMetadata.forkedFrom}).
77
101
  * Lets a consumer render/reap fork lineage; absent on non-forked sessions and on cache views that
78
102
  * predate the fork. The durable source of truth is the repo's persisted metadata. */
@@ -175,8 +199,17 @@ export interface SessionStore {
175
199
  * re-attach to the most recent run, even one still in-flight or suspended. **Optional + best-effort**: a
176
200
  * store that surfaces no session list may ignore it; it MUST NOT throw. The engine's `taskId` is the
177
201
  * service's `lastRunId`.
202
+ *
203
+ * `runId` (#499, additive third parameter — an existing implementation keeps typechecking and keeps
204
+ * working): the engine's own identity for THIS run, for {@link SessionStoreSummary.lastTaskRunId}. It
205
+ * is supplied because `taskId` cannot answer "which run": with no host task id it IS the session id.
206
+ *
207
+ * The two seats describe ONE run and MUST move together: a call that omits `runId` records the new
208
+ * `lastTaskId` and CLEARS `lastTaskRunId`. Keeping the previous run's id beside a newer task id would
209
+ * assert that an id belonging to an older run identifies the latest one — absence is the honest answer
210
+ * to "which run was last" when the caller does not know.
178
211
  */
179
- noteTaskRun?(sessionId: string, taskId: string): void | Promise<void>;
212
+ noteTaskRun?(sessionId: string, taskId: string, runId?: string): void | Promise<void>;
180
213
  /**
181
214
  * Enumerate known sessions as lightweight {@link SessionStoreSummary} projections (newest-first by
182
215
  * `lastActiveAt`) so a shell's `/resume` lists sessions + their last run in ONE call. **Optional**: the
@@ -0,0 +1,3 @@
1
+ import type { FileHistoryStore } from "../file-history-store.js";
2
+ import { type ContractAssertionRunner } from "./contract-harness.js";
3
+ export declare function fileHistoryStoreContract(make: () => FileHistoryStore, runAssertion?: ContractAssertionRunner): Promise<void>;