@sema-agent/core 5.65.0 → 7.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 +58 -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 +18 -0
  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 +13 -3
  33. package/dist/core/governance-codes.js +15 -0
  34. package/dist/core/hooks.d.ts +39 -7
  35. package/dist/core/hooks.js +38 -21
  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 +3 -0
  45. package/dist/core/memory-engine/engine.d.ts +328 -15
  46. package/dist/core/memory-engine/engine.js +355 -29
  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 +64 -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 +62 -2
  65. package/dist/core/permission-rule-consent.js +105 -14
  66. package/dist/core/permission-rule-model.d.ts +118 -16
  67. package/dist/core/permission-rule-model.js +57 -9
  68. package/dist/core/permission-rule-session.d.ts +124 -0
  69. package/dist/core/permission-rule-session.js +121 -0
  70. package/dist/core/permission-rule-store.d.ts +65 -2
  71. package/dist/core/permission-rule-store.js +75 -16
  72. package/dist/core/permission-rule-sync.d.ts +9 -0
  73. package/dist/core/permission-rule-sync.js +52 -19
  74. package/dist/core/retention-policy.d.ts +9 -0
  75. package/dist/core/retention-policy.js +5 -2
  76. package/dist/core/retention.d.ts +13 -2
  77. package/dist/core/runner/assemble-result.d.ts +19 -1
  78. package/dist/core/runner/assemble-result.js +17 -2
  79. package/dist/core/runner/compaction-call-options.d.ts +93 -0
  80. package/dist/core/runner/compaction-call-options.js +3 -0
  81. package/dist/core/runner/memory-capture-optout.d.ts +80 -0
  82. package/dist/core/runner/memory-capture-optout.js +53 -0
  83. package/dist/core/runner/prepare-config-doors.d.ts +5 -0
  84. package/dist/core/runner/prepare-config-doors.js +16 -0
  85. package/dist/core/runner/prepare-hands-readface.d.ts +110 -5
  86. package/dist/core/runner/prepare-hands-readface.js +99 -7
  87. package/dist/core/runner/prepare-memory.d.ts +88 -0
  88. package/dist/core/runner/prepare-memory.js +305 -24
  89. package/dist/core/runner/prepare-task.d.ts +141 -1
  90. package/dist/core/runner/prepare-task.js +478 -81
  91. package/dist/core/runner/runtask.d.ts +9 -20
  92. package/dist/core/runner/runtask.js +133 -96
  93. package/dist/core/runner/session-file-state-replay.d.ts +18 -10
  94. package/dist/core/runner/session-file-state-replay.js +52 -1
  95. package/dist/core/runner/tool-disclosure.js +2 -1
  96. package/dist/core/runner/turn-attachments.d.ts +22 -12
  97. package/dist/core/session-store.d.ts +1 -1
  98. package/dist/core/session-store.js +6 -1
  99. package/dist/core/session.d.ts +34 -1
  100. package/dist/core/store-contracts/file-history-store-contract.d.ts +3 -0
  101. package/dist/core/store-contracts/file-history-store-contract.js +720 -0
  102. package/dist/core/store-contracts/permission-rule-sync-contract.js +15 -1
  103. package/dist/core/task-registry-shared.js +11 -1
  104. package/dist/core/tool-errors.js +1 -0
  105. package/dist/core/tool-policy.d.ts +172 -1
  106. package/dist/core/tool-policy.js +32 -1
  107. package/dist/core/tool-result-store.js +2 -1
  108. package/dist/core/trace.d.ts +31 -2
  109. package/dist/core/types.d.ts +795 -89
  110. package/dist/core/types.js +4 -3
  111. package/dist/core/untrusted-text.d.ts +1 -1
  112. package/dist/core/untrusted-text.js +8 -0
  113. package/dist/core/workflow-run-store-contract.js +6 -2
  114. package/dist/core/workflow-run-store.d.ts +4 -1
  115. package/dist/engine/compaction/compaction.d.ts +88 -10
  116. package/dist/engine/compaction/compaction.js +109 -30
  117. package/dist/engine/execution-env/node-execution-env.d.ts +9 -1
  118. package/dist/engine/execution-env/node-execution-env.js +28 -0
  119. package/dist/engine/harness/agent-harness.d.ts +52 -1
  120. package/dist/engine/harness/agent-harness.js +36 -1
  121. package/dist/engine/harness/types.d.ts +26 -1
  122. package/dist/engine/llm/types.d.ts +50 -4
  123. package/dist/engine/loop/agent-loop.d.ts +5 -1
  124. package/dist/engine/loop/agent-loop.js +25 -0
  125. package/dist/engine/loop/types.d.ts +19 -0
  126. package/dist/engine/lsp/node-lsp-manager.d.ts +1 -1
  127. package/dist/engine/session/session.js +1 -1
  128. package/dist/index.d.ts +20 -9
  129. package/dist/index.js +16 -7
  130. package/dist/orchestration/run-workflow-tool.d.ts +20 -2
  131. package/dist/orchestration/run-workflow-tool.js +22 -3
  132. package/dist/orchestration/workflow-governance.d.ts +59 -1
  133. package/dist/orchestration/workflow-governance.js +61 -8
  134. package/dist/orchestration/workflow-meta.d.ts +4 -2
  135. package/dist/orchestration/workflow-primitives.js +56 -13
  136. package/dist/orchestration/workflow-types.d.ts +78 -2
  137. package/dist/orchestration/workflow.d.ts +20 -0
  138. package/dist/orchestration/workflow.js +163 -14
  139. package/dist/prompt-assembly/event-registry.js +1 -1
  140. package/dist/prompts/default.d.ts +7 -7
  141. package/dist/stores/file/file-history-store.d.ts +368 -0
  142. package/dist/stores/file/file-history-store.js +1248 -0
  143. package/dist/stores/file/index.d.ts +22 -13
  144. package/dist/stores/file/index.js +4 -4
  145. package/dist/stores/file/permission-rule-store.d.ts +11 -0
  146. package/dist/stores/file/permission-rule-store.js +23 -9
  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 +169 -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
@@ -13,6 +13,91 @@ import type { PrepareResume, RunInternals } from "./prepare-task.js";
13
13
  * the console arm's key is the process itself, which only this seam can refresh. Moved WITH the
14
14
  * phase (design/238 B-5); prepare-task.ts keeps a re-export so the existing import sites stand. */
15
15
  export declare function __resetReadFaceClampAnnouncement(): void;
16
+ /**
17
+ * #483 rung-1 — the Runner's per-session read-file-state seats (CC parity: readFileState is minted
18
+ * once per REPL session and mutated across turns; it has NO turn-boundary and NO abort clear site —
19
+ * its only clears are compaction/fork/subagent-cleanup/dispose). sema's map stays per-task (the §11
20
+ * ruling-A closure wiring), so the SESSION scope lives here instead: prepare adopts each task's live
21
+ * map under its sessionId, and the next same-session, same-Runner task INHERITS it (through the same
22
+ * containment judgment the transcript-replay seed applies — see `inheritSessionReadFileState`).
23
+ *
24
+ * This seat is a CACHE, never an authority:
25
+ * - a durable resume's checkpoint seed stays the sole authority on that leg (the seed was cut
26
+ * atomically with the leaf; this seat may be older OR newer than it);
27
+ * - a cross-process continuation misses here and falls back to the transcript replay (rung-2);
28
+ * - eviction (the LRU cap, a Runner restart) degrades to that same replay — never to wrong state.
29
+ *
30
+ * Bounded: an LRU over sessionIds (insertion-order refresh on read), because the SessionStore's own
31
+ * eviction is not observable from here — a dropped seat costs one transcript replay, nothing else.
32
+ * The stored value is the task's LIVE map (object identity), so in-turn mutations — tool write-backs,
33
+ * and above all the RB-197 compaction clear — are the seat's truth by construction; a snapshot copy
34
+ * here would resurrect summarized-away reads on the next turn.
35
+ *
36
+ * BRANCH BINDING (codex r1+r2, adopted): a sessionId alone does not name a transcript — the same id
37
+ * can be rebranched (`resumeAt`), rewound out-of-band, or DELETED and re-created (`release()` + the
38
+ * default create-on-miss acquire), and the seat's map would then vouch for reads the CURRENT branch
39
+ * never saw. Each seat therefore records `anchorLeafId` — a leaf on the lineage its task produced —
40
+ * and inheritance must first prove the CURRENT branch still descends from that anchor
41
+ * (see {@link sessionSeatAnchorHolds}). The anchor is written twice: at ADOPT (the leaf at prepare;
42
+ * `null` when the branch was still empty) and SEALED at the task's settle to the task's own tail
43
+ * (`reanchor`). A `null` anchor is NEVER inheritable (codex r2: "the branch is non-empty now" proves
44
+ * nothing about WHOSE messages those are — another Runner repopulating a released id, or a session
45
+ * import, makes a non-empty branch that never contained this seat's reads). An unprovable anchor
46
+ * falls back to the transcript replay, which reads the current branch by construction — cost, never
47
+ * wrongness.
48
+ */
49
+ export declare class SessionReadFileStates {
50
+ private readonly seats;
51
+ private static readonly CAP;
52
+ /** The session's seat row, or undefined (cross-process / evicted / first turn) ⇒ caller replays.
53
+ * The caller must verify the anchor against the session's CURRENT branch before using `state`. */
54
+ get(sessionId: string): {
55
+ state: ReadFileState;
56
+ anchorLeafId: string | null;
57
+ } | undefined;
58
+ /** Adopt THIS task's live map as the session's seat (every hands leg: fresh, continuation, resume).
59
+ * `anchorLeafId` = the session's leaf as read at THIS prepare (null = the branch was empty; a
60
+ * null-anchored seat is not inheritable until the settle-time {@link reanchor} names a real leaf). */
61
+ adopt(sessionId: string, state: ReadFileState, anchorLeafId: string | null): void;
62
+ /** #483 codex r2 — the settle-time SEAL: re-anchor the seat to the finished task's own tail, so a
63
+ * first-task (null-anchor) seat becomes inheritable only through a leaf its task provably wrote.
64
+ * Identity-guarded: touches the row only while it still holds THIS task's map (a later prepare on
65
+ * the same session has already replaced the seat and owns its own anchor). */
66
+ reanchor(sessionId: string, state: ReadFileState, anchorLeafId: string): void;
67
+ }
68
+ /**
69
+ * #483 (codex r1+r2) — does the session's CURRENT branch still descend from the seat's anchor?
70
+ * TRUE is the licence to inherit; anything unprovable answers FALSE (the caller then replays the
71
+ * transcript — the fail-closed direction costs a rebuild, never admits a wrong entry).
72
+ *
73
+ * - anchor `null`: NEVER inheritable (r2). The seat was adopted on an empty branch and its task
74
+ * never sealed (crashed pre-settle, or the leaf read failed) — "the branch is non-empty now"
75
+ * would also be true of a released-and-repopulated id or an imported log, whose messages are not
76
+ * this seat's lineage. The settle-time reanchor is what upgrades such a seat to a real leaf.
77
+ * - anchor non-null: holds iff walking parentIds from the current leaf reaches the anchor within
78
+ * a bounded number of steps (the walk length is one task's appends in the ordinary continuation).
79
+ * A rebranch/rewind moved the leaf OFF the anchor's subtree ⇒ the walk misses ⇒ no inherit; a
80
+ * recreated/foreign log does not contain the anchor ⇒ same answer; a re-IMPORT of the same log
81
+ * keeps the anchor's lineage ⇒ inheritance stays correct there by the same test.
82
+ * - a COMPACTION on the walked segment (A-082 idx2): refuses, even though the branch is perfectly
83
+ * intact. The walked segment is exactly what landed AFTER this seat was anchored, and a
84
+ * compaction there summarized older Read results OUT of the context the model will be shown —
85
+ * the very state RB-197 clears from the live map at the landing site. That clear reaches the seat
86
+ * only when the SAME Runner did both (the seat IS that map, by object identity); a second Runner
87
+ * on the shared durable session clears its own map and commits the compaction as an ORDINARY
88
+ * LINEAR APPEND, which every other test here passes. Inheriting across it would let requireRead
89
+ * and checkStale both pass on a read whose card the model no longer has — a blind edit from a
90
+ * summarized-away recollection, the exact failure the RB-197 clear exists to prevent. Refusing
91
+ * costs one transcript replay, and the replay reads the POST-compaction branch by construction,
92
+ * so the fail-closed direction is also the CORRECT state. A compaction at or ABOVE the anchor is
93
+ * not on the walked segment and does not refuse: the seat that observed it already carries its
94
+ * clear (P-483-4), and an ordinary foreign turn with no compaction still inherits (G-483-5) —
95
+ * dropping every seat a foreign Runner touched would restore the cross-turn amnesia #483 ended.
96
+ */
97
+ export declare function sessionSeatAnchorHolds(getEntry: (id: string) => Promise<{
98
+ parentId: string | null;
99
+ type?: string;
100
+ } | null | undefined>, anchorLeafId: string | null, currentLeafId: string | null): Promise<boolean>;
16
101
  export interface PrepareHandsReadFaceInput {
17
102
  /** owned (value) — `ownedEnv !== undefined || deps.executionEnv !== undefined`, computed in the
18
103
  * driver: the phase's own branch condition (hands mount vs the hands-less resolver arm). */
@@ -34,8 +119,9 @@ export interface PrepareHandsReadFaceInput {
34
119
  resume: Pick<PrepareResume, "seed"> | undefined;
35
120
  /** borrowed-readonly — the acquired session (B-3 output), as a Pick (相 API 规则件 R-4: receiver
36
121
  * preserved): `getWorkspaceState` feeds the design/155 continuation rung; `buildContext` feeds
37
- * the transcript replay. */
38
- session: Pick<StoredSession, "getWorkspaceState" | "buildContext">;
122
+ * the transcript replay; `getStorage`/`getEntry` feed the #483 seat-anchor verification (one
123
+ * leaf read per prepare + a bounded parent walk on the inherit leg only). */
124
+ session: Pick<StoredSession, "getWorkspaceState" | "buildContext" | "getStorage" | "getEntry">;
39
125
  /** borrowed-readonly — the acquired session's id, for error/notice tags and the band's session
40
126
  * registration. */
41
127
  sessionId: string;
@@ -59,15 +145,23 @@ export interface PrepareHandsReadFaceInput {
59
145
  model: Model;
60
146
  /** borrowed-readonly — the task-spec seats this phase reads, as a Pick over the SAME spec object
61
147
  * (R-4). `tools` feeds only the band shadow check; `envFacts` only the advertised scratchpad. */
62
- spec: Pick<TaskSpec, "additionalDirectories" | "additionalReadDirectories" | "envFacts" | "handsReadOnly" | "readDenyPatterns" | "readFace" | "oneShot" | "sessionId" | "tools">;
148
+ spec: Pick<TaskSpec, "additionalDirectories" | "additionalReadDirectories" | "envFacts" | "handsReadOnly" | "readDenyPatterns" | "readFace" | "oneShot" | "resumeAt" | "sessionId" | "tools">;
63
149
  /** borrowed-readonly — the deployment seats this phase reads, as a Pick over the SAME deps
64
150
  * object (R-4: `deps.onError?.()` keeps its receiver; a new deps read must widen this Pick).
65
151
  * `readDenyBuiltinTiers`/`readDenyBuiltinExclude` are SNAPSHOTTED before the first await that
66
152
  * precedes the toolkit compile (#245/#244 — the caller retains these arrays by reference). */
67
153
  deps: Pick<RunnerDeps, "onError" | "onNotice" | "tracer" | "readDenyPatterns" | "readDenyBuiltinTiers" | "readDenyBuiltinExclude" | "readFace" | "permissionRuleOrg" | "hands">;
68
154
  /** borrowed-readonly — the trusted spawn-side seats the band forwards: the finished-command
69
- * notification lane and the mid-flight detach hub. */
70
- internals: Pick<RunInternals, "onTaskNotification" | "detachHub"> | undefined;
155
+ * notification lane, the mid-flight detach hub, and (#483 rung-1) the Runner's per-session
156
+ * read-state seats (absent on a standalone prepare ⇒ the transcript-replay rung covers alone). */
157
+ internals: Pick<RunInternals, "onTaskNotification" | "detachHub" | "sessionReadStates"> | undefined;
158
+ /** borrowed-readonly (A-082 idx7) — ABSOLUTE paths this prepare's design/381 restore wrote, deleted,
159
+ * or failed to write. Every read-state seed above describes the PRE-restore tree, so these keys are
160
+ * dropped from the map: the engine replaced those bytes itself, and a surviving entry makes the
161
+ * next refusal blame "the user or a linter" for the engine's own write. Absent (the common case)
162
+ * when no file restore ran. Fail-closed by construction — dropping a read record only ever
163
+ * produces refusals. */
164
+ restoredFilePaths?: readonly string[];
71
165
  /** borrowed (ref cell; the REF itself is never replaced) — design/138 S2-C late-bound memory
72
166
  * write gate: the band's beforeWrite closure reads `.current` call-time; prepareMemory (driver,
73
167
  * after this phase) installs the gate. Declared in the driver because the SAME ref feeds
@@ -107,6 +201,9 @@ export interface PrepareHandsReadFaceInput {
107
201
  * band's Read/notebook/PDF trailer outlets bump keys on it at tool-execute time (the driver owns
108
202
  * the object and folds it into result stats). */
109
203
  reminderDisclosureCounts: import("../reminder-disclosure.js").ReminderDisclosureCounts;
204
+ /** design/381 — the first-touch history hook prepare built (present iff a fileHistoryStore is
205
+ * wired and the run mounts a real fs env); threaded to the write band's trackFileEdit seat. */
206
+ trackFileEdit?: import("../types.js").TrackFileEditHook;
110
207
  }
111
208
  /** The phase's outputs (相 API 规则件 four-class form) — ALL settled before the return; the driver
112
209
  * binds them as fresh consts (R-5) except the inverted-closure trio and the two shellGated bits,
@@ -190,6 +287,14 @@ export interface PrepareHandsReadFaceResult {
190
287
  * misses Monitor/worktree, which are hands — both directions wrong for this consumer). Empty on
191
288
  * a hands-less leg (nothing mounts on the env; the driver's env mounts are hands-gated too). */
192
289
  envHandToolNames: Set<string>;
290
+ /** owned (#483 codex r2+r3) — the settle-time SEAT SEAL: re-anchors the session seat to the
291
+ * task's OWN last-committed entry (see {@link SessionReadFileStates.reanchor}). The driver calls
292
+ * it once at the run's settle (every terminal, suspend included), under the session lock, passing
293
+ * the last entryId ITS OWN run committed (the message_committed lane) — never a re-read of the
294
+ * shared session leaf, which a concurrent writer on another Runner could have moved onto a branch
295
+ * this task's reads were never on (codex r3). No commits observed ⇒ no-op (the adopt-time anchor
296
+ * stands). Synchronous, never throws. Undefined ⇔ hands-less / no seat channel. */
297
+ sealReadStateSeat: ((ownCommittedTailEntryId: string | undefined) => void) | undefined;
193
298
  }
194
299
  export declare function resolveHandsLessReadFace(input: PrepareHandsReadFaceInput): PrepareHandsReadFaceResult;
195
300
  /** The hands-MOUNT branch — the async half of the phase (see the module header for the contract).
@@ -2,8 +2,9 @@ import { emitTrace } from "../trace.js";
2
2
  import { StubExecutionEnv } from "../stub-env.js";
3
3
  import { hasBackgroundShell } from "../background-shell.js";
4
4
  import { defaultTaskRegistry } from "../task-registry.js";
5
- import { bashReversibilityProbe, compileReadDeny, createHandsToolkit, deploymentReadFaceClampNotice, pdfModelCapabilitiesOf, resolveReadDenyBuiltins, resolveReadFace, seedReadFileStateFromContext, seedReadFileStateFromTranscript, } from "../../tools/fs/index.js";
6
- import { resolveKey } from "../../tools/fs/safety.js";
5
+ import { bashReversibilityProbe, compileReadDeny, createHandsToolkit, deploymentReadFaceClampNotice, pdfModelCapabilitiesOf, resolveReadDenyBuiltins, inheritSessionReadFileState, resolveReadFace, seedReadFileStateFromContext, seedReadFileStateFromTranscript, } from "../../tools/fs/index.js";
6
+ import { resolveKey, sha256 } from "../../tools/fs/safety.js";
7
+ import { normalizeFileText } from "../../tools/fs/encoding.js";
7
8
  import { wholeFileRecordsFromTranscript } from "./session-file-state-replay.js";
8
9
  import { deliverEngineNotice } from "../types.js";
9
10
  import { rebaseWorkspacePath } from "./prepare-workspace-restore.js";
@@ -12,6 +13,49 @@ let readFaceClampConsoleAnnounced = false;
12
13
  export function __resetReadFaceClampAnnouncement() {
13
14
  readFaceClampConsoleAnnounced = false;
14
15
  }
16
+ export class SessionReadFileStates {
17
+ seats = new Map();
18
+ static CAP = 1024;
19
+ get(sessionId) {
20
+ const seat = this.seats.get(sessionId);
21
+ if (seat !== undefined) {
22
+ this.seats.delete(sessionId);
23
+ this.seats.set(sessionId, seat);
24
+ }
25
+ return seat;
26
+ }
27
+ adopt(sessionId, state, anchorLeafId) {
28
+ this.seats.delete(sessionId);
29
+ this.seats.set(sessionId, { state, anchorLeafId });
30
+ while (this.seats.size > SessionReadFileStates.CAP) {
31
+ const oldest = this.seats.keys().next().value;
32
+ if (oldest === undefined)
33
+ break;
34
+ this.seats.delete(oldest);
35
+ }
36
+ }
37
+ reanchor(sessionId, state, anchorLeafId) {
38
+ const seat = this.seats.get(sessionId);
39
+ if (seat !== undefined && seat.state === state)
40
+ seat.anchorLeafId = anchorLeafId;
41
+ }
42
+ }
43
+ export async function sessionSeatAnchorHolds(getEntry, anchorLeafId, currentLeafId) {
44
+ if (anchorLeafId === null)
45
+ return false;
46
+ let id = currentLeafId;
47
+ for (let steps = 0; id !== null && steps < 100_000; steps++) {
48
+ if (id === anchorLeafId)
49
+ return true;
50
+ const entry = await getEntry(id);
51
+ if (entry === null || entry === undefined)
52
+ return false;
53
+ if (entry.type === "compaction")
54
+ return false;
55
+ id = entry.parentId;
56
+ }
57
+ return false;
58
+ }
15
59
  function assertStringArraySeat(value, seat, entryLaw) {
16
60
  if (value === undefined)
17
61
  return;
@@ -74,6 +118,7 @@ export function resolveHandsLessReadFace(input) {
74
118
  envHandToolNames: new Set(),
75
119
  shellGatedBash: false,
76
120
  shellGatedMonitor: false,
121
+ sealReadStateSeat: undefined,
77
122
  };
78
123
  }
79
124
  export async function prepareHandsMount(input) {
@@ -84,6 +129,7 @@ export async function prepareHandsMount(input) {
84
129
  assertStringArraySeat(deps.readDenyPatterns, "RunnerDeps.readDenyPatterns", "compiler-owned");
85
130
  assertStringArraySeat(spec.readDenyPatterns, "TaskSpec.readDenyPatterns", "compiler-owned");
86
131
  let readFileStateForCheckpoint;
132
+ let sealReadStateSeat;
87
133
  let seedContextFiles;
88
134
  let handsCwdRef;
89
135
  let workspaceStateSettle;
@@ -205,14 +251,58 @@ export async function prepareHandsMount(input) {
205
251
  if (resume !== undefined && (seedReadFaceSection === undefined || seedReadFaceSection.face === "roots"))
206
252
  liveReadFace = "roots";
207
253
  resolvedReadFace = liveReadFace;
254
+ const sessionSeats = input.internals?.sessionReadStates;
255
+ let seatLeafId;
256
+ try {
257
+ seatLeafId = (await session.getStorage().getLeafId()) ?? null;
258
+ }
259
+ catch {
260
+ seatLeafId = undefined;
261
+ }
262
+ const judgeKey = async (path) => {
263
+ const rk = await resolveKey(executionEnv, rootCanonical, path, undefined, undefined, [...additionalRootsCanonical, ...additionalReadRootsCanonical], undefined, undefined, liveReadFace);
264
+ return rk.ok ? rk.key : undefined;
265
+ };
208
266
  if (resume === undefined && spec.sessionId !== undefined) {
209
- const prior = await session.buildContext().catch(() => undefined);
210
- for (const rec of wholeFileRecordsFromTranscript(prior?.messages ?? [])) {
211
- const rk = await resolveKey(executionEnv, rootCanonical, rec.path, undefined, undefined, [...additionalRootsCanonical, ...additionalReadRootsCanonical], undefined, undefined, liveReadFace);
212
- if (rk.ok)
213
- seedReadFileStateFromTranscript(readFileState, rk.key, rec.content, rec.at);
267
+ const seatRow = spec.resumeAt === undefined && seatLeafId !== undefined ? sessionSeats?.get(sessionId) : undefined;
268
+ const inheritedSeat = seatRow !== undefined && (await sessionSeatAnchorHolds((id) => session.getEntry(id), seatRow.anchorLeafId, seatLeafId))
269
+ ? seatRow.state
270
+ : undefined;
271
+ if (inheritedSeat !== undefined) {
272
+ await inheritSessionReadFileState(readFileState, inheritedSeat, judgeKey);
273
+ }
274
+ else {
275
+ const prior = await session.buildContext().catch(() => undefined);
276
+ for (const rec of wholeFileRecordsFromTranscript(prior?.messages ?? [])) {
277
+ const rk = await judgeKey(rec.path);
278
+ if (rk !== undefined)
279
+ seedReadFileStateFromTranscript(readFileState, rk, rec.content, rec.at);
280
+ }
281
+ }
282
+ }
283
+ if (input.restoredFilePaths !== undefined) {
284
+ for (const abs of input.restoredFilePaths) {
285
+ const k = await judgeKey(abs);
286
+ if (k === undefined)
287
+ continue;
288
+ const entry = readFileState.get(k);
289
+ if (entry === undefined || entry.seededFromContext === true)
290
+ continue;
291
+ const cur = await executionEnv.readTextFile(abs);
292
+ if (cur.ok && entry.hash === sha256(normalizeFileText(cur.value)))
293
+ continue;
294
+ readFileState.delete(k);
214
295
  }
215
296
  }
297
+ if (seatLeafId !== undefined)
298
+ sessionSeats?.adopt(sessionId, readFileState, seatLeafId);
299
+ sealReadStateSeat =
300
+ sessionSeats !== undefined && seatLeafId !== undefined
301
+ ? (ownCommittedTailEntryId) => {
302
+ if (ownCommittedTailEntryId !== undefined)
303
+ sessionSeats.reanchor(sessionId, readFileState, ownCommittedTailEntryId);
304
+ }
305
+ : undefined;
216
306
  const band = createHandsToolkit(executionEnv, readFileState, rootCanonical, {
217
307
  ...(additionalRootsCanonical.length > 0 ? { additionalRoots: additionalRootsCanonical } : {}),
218
308
  ...(additionalReadRootsCanonical.length > 0 ? { additionalReadRoots: additionalReadRootsCanonical } : {}),
@@ -222,6 +312,7 @@ export async function prepareHandsMount(input) {
222
312
  readFace: liveReadFace,
223
313
  reminderMark: input.reminderMark,
224
314
  reminderDisclosureCounts: input.reminderDisclosureCounts,
315
+ ...(input.trackFileEdit !== undefined ? { trackFileEdit: input.trackFileEdit } : {}),
225
316
  includeShell: handsIncludeShell,
226
317
  readOnly: handsReadOnly,
227
318
  ...(handsCwdRef ? { cwdRef: handsCwdRef } : {}),
@@ -309,5 +400,6 @@ export async function prepareHandsMount(input) {
309
400
  envHandToolNames,
310
401
  shellGatedBash,
311
402
  shellGatedMonitor,
403
+ sealReadStateSeat,
312
404
  };
313
405
  }
@@ -1,4 +1,5 @@
1
1
  import type { BeforeWriteHook, EffectiveMemoryScopes, RunnerDeps, TaskSpec, ToolSpec } from "../types.js";
2
+ import { type CaptureEntitlementInput, type MemoryCapturePosture } from "./memory-capture-optout.js";
2
3
  import type { Prepared } from "./prepare-task.js";
3
4
  export interface PrepareMemoryInput {
4
5
  spec: TaskSpec;
@@ -74,6 +75,85 @@ export interface PrepareMemoryInput {
74
75
  * top-level task; for a delegated child the caller passes the fail-closed empty set when the
75
76
  * chain carries no verdict (an older-shape chain must not read as "unconstrained").
76
77
  */
78
+ /**
79
+ * design/383 §3.1 — the SCREENED capture posture (prepare-config-doors; absent deps seat folded
80
+ * to `"open"`) plus whether the deployment DECLARED one at all (the once-per-process "switch in
81
+ * use, no policy configured" operator line fires only on the undeclared arm).
82
+ */
83
+ memoryCapturePolicy?: MemoryCapturePosture;
84
+ memoryCapturePolicyDeclared?: boolean;
85
+ /**
86
+ * design/383 §3.1 — the per-principal entitlement as resolved by prepare-task's ONE caps resolve
87
+ * (`value` = `runtimeCaps.allowMemoryOptOut` when boolean; `faulted` = the resolve threw or the
88
+ * member was non-boolean — per the member's F2 fault-transit contract a fault must never arrive
89
+ * as a coined explicit value). Consumed by the adjudicator on every ingress of this prepare.
90
+ */
91
+ captureEntitlement?: CaptureEntitlementInput;
92
+ /**
93
+ * design/383 §2.1b — the carrier-absence verdict, judged by prepare-task from the DECLARED
94
+ * deployment shape: a remote-execution deployment whose control plane does not follow the run
95
+ * (the per-run sandbox / stateless-replica form) with no
96
+ * {@link RunnerDeps.memoryCaptureRecordStore} supplied. TRUE + a `capture:"off"` declaration ⇒
97
+ * the declaration is refused (`config.memory_capture_unsupported`) — a promise the next resume
98
+ * replica cannot see is a false promise, and a loud refusal beats one.
99
+ */
100
+ captureCarrierUnsupported?: boolean;
101
+ /**
102
+ * design/383 §2.5 — the IMMEDIATE spawner's session id (trusted internals seat, filled by the
103
+ * delegation lanes; absent on top-level runs). The record-query leg of the delegation floor: a
104
+ * child's harvest legs also read the PARENT's opt-out record off the shared write-plane control
105
+ * store, so a parent whose opt-out landed AFTER this child spawned (the mid-run flip) still gets
106
+ * zero admission from in-flight children — the spec-carried floor covers only spawn-time state.
107
+ * Honest bounds (registered): the query sees the CHILD's write-plane store — a parent whose
108
+ * record lives on a different plane (a dual-root split where the two write different planes) is
109
+ * outside its reach, and a lane that fills no parent seat (TIER-3 durable revival) is covered
110
+ * only insofar as the child's OWN record (same sessionId across revival) or spec carriage
111
+ * survives that lane.
112
+ */
113
+ parentSessionId?: string;
114
+ /**
115
+ * design/383 §2.5 — the delegation FLOOR bit off the trusted internals chain
116
+ * (`internals.memoryCaptureFloor === true`): the spawning session was opted out at spawn time.
117
+ * A floored leg runs opted-out WITHOUT re-adjudication (the parent's granted opt-out tightens
118
+ * the tree; re-asking could only loosen) and mints its own one-way record (reason names the
119
+ * floor), so its grandchildren and its own resumes inherit mechanically.
120
+ */
121
+ captureFloor?: boolean;
122
+ /**
123
+ * design/383 §2.5 (rescan post-6.0.0-RC) — the floor seat's THIRD state off the same trusted
124
+ * chain: the spawning session's capture state was INDETERMINATE at spawn time (its record store
125
+ * faulted at the getter read). This leg resolves it against the live record query (see
126
+ * `armCaptureOptOut`): ancestor record found ⇒ floor; still faulting ⇒ this session runs
127
+ * indeterminate itself (mechanical restrictions, no record mint); readable-and-clean ⇒ clean.
128
+ * Mutually exclusive with `captureFloor` at the mint site (the spread keys on optedOut !== true).
129
+ */
130
+ captureFloorIndeterminate?: boolean;
131
+ /**
132
+ * design/383 §2.5 (rescan post-6.0.0-RC) — the HOST-SIDE FORK lane: the session this one was
133
+ * forked FROM (`SessionMetadata.forkedFrom`, read by prepare-task off the acquired session). A
134
+ * fork byte-copies the source's committed transcript, so the source's standing opt-out governs
135
+ * the copy: record found at prepare ⇒ the floor arm (mint, reason naming the fork, no
136
+ * re-adjudication — the source's granted opt-out tightens the lineage); source store faulting ⇒
137
+ * indeterminate (suppress, no mint); clean ⇒ ordinary run. Also seeded into the harvest
138
+ * record-query walk, so a source that flips WHILE the fork runs suppresses it live.
139
+ */
140
+ captureForkOrigin?: string;
141
+ /** design/383 §2.5 (rescan, codex adversarial round) — the fork-lineage read itself FAULTED
142
+ * (session metadata unreadable): whether this session forks an opted-out source is unknowable,
143
+ * so the capture arm runs INDETERMINATE (suppress fail-closed, disclose, mint nothing) — a
144
+ * metadata outage must not read as "no lineage" (读错不许伪装 clean). */
145
+ captureLineageFault?: boolean;
146
+ /** design/383 §2.5 (codex review adopted) — the SPAWNING session's write-plane control dir
147
+ * (trusted internals chain), so the record-query leg reaches a parent on a DIFFERENT memory
148
+ * plane (project-writing parent, personal-writing child). Absent ⇒ same-plane query only. */
149
+ parentCaptureQueryDir?: string;
150
+ /** design/383 §2.5 (codex round 3) — the full ancestor chain's capture coordinates off the
151
+ * trusted internals seat; the harvest closure walks every row (deduped with the parent seat),
152
+ * so a mid-run flip anywhere up the tree suppresses this in-flight descendant. */
153
+ captureAncestors?: ReadonlyArray<{
154
+ sessionId: string;
155
+ controlDir?: string;
156
+ }>;
77
157
  admissionCtx: {
78
158
  orgMemoryDenied: boolean;
79
159
  complianceDegraded: boolean;
@@ -96,6 +176,14 @@ export interface PrepareMemoryResult {
96
176
  * them (atomic pair) and runs the remaining conjuncts.
97
177
  */
98
178
  memoryTools?: ToolSpec[];
179
+ /**
180
+ * #493 单铸 — the recall-discipline segment EXACTLY as this phase appended it to
181
+ * {@link memoryBlock} (present iff {@link memoryTools} is). prepare-task's support-name
182
+ * retraction strips THIS string: the segment's composition now depends on the marked-presence
183
+ * signal only this phase holds, so a strip-site re-composition could mint a string that was
184
+ * never appended and leave the real one standing.
185
+ */
186
+ recallDisciplineSegment?: string;
99
187
  /**
100
188
  * RB-276 — engine-materialized files whose FULL disk text rides in {@link memoryBlock}, for the
101
189
  * caller to pre-mark READ through the parity-204 seeding lane (`prepare-task`'s `seedContextFiles`,