@vibe-commit/capture 0.8.0 → 0.10.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 (111) hide show
  1. package/README.md +149 -1
  2. package/dist/agents/claude_code.js +13 -0
  3. package/dist/agents/claude_code.js.map +1 -1
  4. package/dist/agents/codex_cli.js +11 -0
  5. package/dist/agents/codex_cli.js.map +1 -1
  6. package/dist/agents/cursor.js +7 -1
  7. package/dist/agents/cursor.js.map +1 -1
  8. package/dist/agents/registry.d.ts +7 -0
  9. package/dist/agents/registry.js +9 -0
  10. package/dist/agents/registry.js.map +1 -1
  11. package/dist/agents/types.d.ts +32 -0
  12. package/dist/agents/types.js.map +1 -1
  13. package/dist/bounded_id.d.ts +29 -0
  14. package/dist/bounded_id.js +42 -0
  15. package/dist/bounded_id.js.map +1 -0
  16. package/dist/codex_folders.d.ts +340 -0
  17. package/dist/codex_folders.js +1448 -0
  18. package/dist/codex_folders.js.map +1 -0
  19. package/dist/commands/backfill.d.ts +29 -7
  20. package/dist/commands/backfill.js +387 -109
  21. package/dist/commands/backfill.js.map +1 -1
  22. package/dist/commands/connect.d.ts +1 -1
  23. package/dist/commands/connect.js +1 -1
  24. package/dist/commands/connect.js.map +1 -1
  25. package/dist/commands/connect_picker.js +48 -7
  26. package/dist/commands/connect_picker.js.map +1 -1
  27. package/dist/commands/finalize.d.ts +67 -0
  28. package/dist/commands/finalize.js +452 -114
  29. package/dist/commands/finalize.js.map +1 -1
  30. package/dist/commands/scan.d.ts +28 -0
  31. package/dist/commands/scan.js +11 -0
  32. package/dist/commands/scan.js.map +1 -1
  33. package/dist/commands/status_machine.js +47 -10
  34. package/dist/commands/status_machine.js.map +1 -1
  35. package/dist/commands/why.d.ts +19 -1
  36. package/dist/commands/why.js +35 -3
  37. package/dist/commands/why.js.map +1 -1
  38. package/dist/commit_files.d.ts +64 -0
  39. package/dist/commit_files.js +83 -0
  40. package/dist/commit_files.js.map +1 -0
  41. package/dist/consent.d.ts +22 -0
  42. package/dist/consent.js +38 -1
  43. package/dist/consent.js.map +1 -1
  44. package/dist/copy/strings.d.ts +55 -9
  45. package/dist/copy/strings.js +60 -12
  46. package/dist/copy/strings.js.map +1 -1
  47. package/dist/folder_grants.d.ts +44 -0
  48. package/dist/folder_grants.js +101 -0
  49. package/dist/folder_grants.js.map +1 -0
  50. package/dist/git.d.ts +26 -4
  51. package/dist/git.js +82 -11
  52. package/dist/git.js.map +1 -1
  53. package/dist/hooks/entry.d.ts +30 -50
  54. package/dist/hooks/entry.js +595 -559
  55. package/dist/hooks/entry.js.map +1 -1
  56. package/dist/hooks/fire_lock.d.ts +31 -0
  57. package/dist/hooks/fire_lock.js +120 -12
  58. package/dist/hooks/fire_lock.js.map +1 -1
  59. package/dist/hooks/post_commit.d.ts +9 -0
  60. package/dist/hooks/post_commit.js +9 -5
  61. package/dist/hooks/post_commit.js.map +1 -1
  62. package/dist/machine_state.d.ts +44 -1
  63. package/dist/machine_state.js +75 -12
  64. package/dist/machine_state.js.map +1 -1
  65. package/dist/oauth/discovery.js +2 -1
  66. package/dist/oauth/discovery.js.map +1 -1
  67. package/dist/oauth/pkce.js +2 -1
  68. package/dist/oauth/pkce.js.map +1 -1
  69. package/dist/paths.d.ts +34 -1
  70. package/dist/paths.js +62 -12
  71. package/dist/paths.js.map +1 -1
  72. package/dist/physical_path.d.ts +46 -0
  73. package/dist/physical_path.js +151 -0
  74. package/dist/physical_path.js.map +1 -0
  75. package/dist/picker.d.ts +52 -3
  76. package/dist/picker.js +129 -5
  77. package/dist/picker.js.map +1 -1
  78. package/dist/picker_checklist.d.ts +9 -1
  79. package/dist/picker_checklist.js +41 -2
  80. package/dist/picker_checklist.js.map +1 -1
  81. package/dist/policy.d.ts +9 -1
  82. package/dist/policy.js +11 -2
  83. package/dist/policy.js.map +1 -1
  84. package/dist/post.d.ts +25 -0
  85. package/dist/post.js +49 -13
  86. package/dist/post.js.map +1 -1
  87. package/dist/project.d.ts +89 -0
  88. package/dist/project.js +123 -0
  89. package/dist/project.js.map +1 -1
  90. package/dist/redact.d.ts +77 -2
  91. package/dist/redact.js +713 -47
  92. package/dist/redact.js.map +1 -1
  93. package/dist/session_targets.d.ts +713 -0
  94. package/dist/session_targets.js +2602 -0
  95. package/dist/session_targets.js.map +1 -0
  96. package/dist/spool.d.ts +23 -5
  97. package/dist/spool.js +37 -6
  98. package/dist/spool.js.map +1 -1
  99. package/dist/spool_union.d.ts +51 -0
  100. package/dist/spool_union.js +57 -0
  101. package/dist/spool_union.js.map +1 -0
  102. package/dist/state.d.ts +5 -0
  103. package/dist/state.js +19 -1
  104. package/dist/state.js.map +1 -1
  105. package/dist/text_tail.d.ts +8 -0
  106. package/dist/text_tail.js +19 -0
  107. package/dist/text_tail.js.map +1 -0
  108. package/dist/version.d.ts +1 -1
  109. package/dist/version.js +1 -1
  110. package/dist/version.js.map +1 -1
  111. package/package.json +3 -3
@@ -0,0 +1,713 @@
1
+ /**
2
+ * `session_targets.ts` — what does this session belong to? (any-folder capture, R1 · R2 · R5 · R6 · R13 · R16 · R18)
3
+ *
4
+ * One module owns three things that used to be one hard-wired derivation ("the repo the turn ended in"):
5
+ *
6
+ * 1. **THE READER** — `scanTranscriptKeys`, the ONLY code that reads a field out of a transcript for attribution.
7
+ * D60 §D6 exception 1 was "one value (the first `cwd`), backfill only"; D2 · A widened it to two field kinds on
8
+ * every new record (each record's folder, and the path keys redaction already parses), in hooks and backfill, and
9
+ * D15 · A made that surface ONE function so one test file and one audit cover it. It parses only lines that name a
10
+ * key, keeps nothing but the set of strings found, and sends none of them anywhere.
11
+ * 2. **THE RESOLVER** — `resolveTargets` turns evidence into `Target[]`: which granted repos (and, at the end of a
12
+ * repo-free session, which granted folder) this session's bytes go to, under which slug, ledger key and redaction
13
+ * roots.
14
+ * 3. **THE TARGET RECORD** — per-session state that is sticky across firings: scan cursors, resolved roots, deferred
15
+ * roots, targets, undecided names.
16
+ *
17
+ * ## ⛔ What this module must never do
18
+ *
19
+ * - **Never mint a fallback identity.** A spawn refused while naming a repo DEFERS that root to the next firing; it never
20
+ * becomes `local:<hash of the developer's path>` (R16 · D18, `spawn_budget.ts`'s own warning).
21
+ * - **Never write `grants.json`.** Hooks never write the consent file; this module only READS consent.
22
+ * - **Never decide a folder target before the end** (R18 · D21): only SessionEnd, `finalize` and backfill, after every
23
+ * line was read.
24
+ */
25
+ import { CodexTracker, type CallInfo, type CodexFolderEnv, type IdEntry } from "./codex_folders.js";
26
+ import type { CwdKey } from "./agents/types.js";
27
+ import type { RedactionScope } from "./redact.js";
28
+ import { type CaptureStanding } from "./consent.js";
29
+ import { type ProjectKeys } from "./project.js";
30
+ /**
31
+ * The most bytes a firing STARTS scanning per stream (R16 · D18). A line that begins before this window is completed even
32
+ * if it ends beyond it (R18 · D21: a long line is read whole), so a firing always advances at least one line.
33
+ *
34
+ * ⚠ ITS OWN CONSTANT, not `pending_bound.ts`'s: that one bounds a different scan (the promotion bound) with different
35
+ * reasoning. The reasoning here is the spawn window: scanning feeds root resolution, and an unbounded first scan after an
36
+ * upgrade (a 14 MB session) could spend a firing.
37
+ */
38
+ export declare const EVIDENCE_SCAN_WINDOW_BYTES: number;
39
+ /**
40
+ * The longest single line the reader will hold to parse. Above it the line is SKIPPED for evidence, counted, and the
41
+ * session becomes "unknown" for folder purposes (R18). Equal to the window by design: "a line over 8 MiB is unknown".
42
+ */
43
+ export declare const EVIDENCE_MAX_LINE_BYTES: number;
44
+ /** What the reader looks for. Built from the registry row, never hand-written here (A3: one list, redaction's). */
45
+ export interface EvidenceSpec {
46
+ /** Where a record names its folder (the dialect's `cwdKeys`). */
47
+ readonly cwdKeys: readonly CwdKey[];
48
+ /** Keys whose string value is a path, at any depth (`RedactionRules.pathKeys`). */
49
+ readonly pathKeys: ReadonlySet<string>;
50
+ /** Keys whose string value is itself a JSON document to descend into (`RedactionRules.nestedJsonKeys`). */
51
+ readonly nestedJsonKeys: ReadonlySet<string>;
52
+ /**
53
+ * C40 (1): also record, per line, which tool calls NAMED A FILE (a Claude `tool_use` whose input holds a path key) and which calls got their result
54
+ * (a `tool_result`), with the line's byte offset. Set only for a dialect whose results can name no file themselves (Claude's image Read).
55
+ */
56
+ readonly trackCalls?: boolean;
57
+ /**
58
+ * Part (b): the dialect is Codex, whose records name a folder on few types. With it the scan also keeps the TURN-FOLDER timeline (every change of the
59
+ * `session_meta` / `turn_context` cwd, with its byte offset) and records EVERY call (its folders; `[]` when they cannot be told) and the output that
60
+ * closes it, so a record's effective folder can be told later from the checkpoint alone (`codex_folders.ts`). Implies `trackCalls`. Every line is parsed:
61
+ * a Codex record that names no folder is judged by the turn folder in force, so no line can be skipped on its text.
62
+ */
63
+ readonly codexFolders?: boolean;
64
+ }
65
+ /**
66
+ * ⛔ The spec backfill's folder check uses: a top-level `cwd` and NOTHING else (the 10-24 one-value reading of D60 §D6
67
+ * exception 1). The hook's resolver builds its own spec from the registry; backfill's gate must not widen when that does.
68
+ */
69
+ export declare const TOP_LEVEL_CWD_ONLY: EvidenceSpec;
70
+ export type KeyKind = "cwd" | "path";
71
+ export interface KeyHit {
72
+ readonly kind: KeyKind;
73
+ readonly value: string;
74
+ }
75
+ export type KeyScan = {
76
+ readonly kind: "unreadable";
77
+ } | {
78
+ readonly kind: "ok";
79
+ /** Distinct `(kind, value)` pairs found, in first-seen order. */
80
+ readonly hits: readonly KeyHit[];
81
+ /**
82
+ * The byte offset after the last line fully processed. Resume here. Equal to `from` when nothing complete was found
83
+ * (an unterminated tail), so a caller can never skip bytes it did not read.
84
+ */
85
+ readonly scannedTo: number;
86
+ /** Lines over `EVIDENCE_MAX_LINE_BYTES`, skipped. Any non-zero count makes the session unknown for R18. */
87
+ readonly oversizeLines: number;
88
+ /**
89
+ * Evidence was DROPPED: more distinct strings than the scan keeps, or a record nested past the depth limit. "Every line
90
+ * was read" no longer holds (R18), so the caller treats the session as unknown, like an oversize line.
91
+ */
92
+ readonly capped: boolean;
93
+ /** The read budget ended inside an oversize line being skipped: the next call passes `continueSkipping` from `scannedTo`. */
94
+ readonly skipping: boolean;
95
+ /** A FINAL file's unterminated tail did not parse, so it was not consumed (`includeUnterminatedTail`): the stream cannot finish. */
96
+ readonly torn: boolean;
97
+ /** The file's size when it was read. `scannedTo === size` means this stream is fully scanned. */
98
+ readonly size: number;
99
+ /** C40 (1), only when `spec.trackCalls`: the calls opened and closed by the lines read, in file order. */
100
+ readonly calls: CallEvents;
101
+ /** Part (b), meaningful only when `spec.codexFolders`: the turn-folder changes the lines read made, the turn folder at `scannedTo`, and whether a record's folder could not be told. */
102
+ readonly folders: FolderEvents;
103
+ };
104
+ export interface FolderEvents {
105
+ /** `dir: null`: an UNREADABLE header cwd, the turn folder is unknown from `at` until the next readable one (C49 (6)). */
106
+ readonly changes: readonly {
107
+ readonly at: number;
108
+ readonly dir: string | null;
109
+ }[];
110
+ /** The session map (process id -> folders and where each was learned) and the cell map (script id -> ...) as they stand at `scannedTo`. */
111
+ readonly sessions: readonly (readonly [string, IdEntry])[];
112
+ readonly cells: readonly (readonly [string, IdEntry])[];
113
+ /** A call whose folder could not be told, an output whose call is not known, or a record that cannot be placed: the session's folders are not all determined (a folder target needs them). */
114
+ readonly unknown: boolean;
115
+ /** C56 (8): the offset of the first line this read skipped (too long to read), when it skipped one. */
116
+ readonly skipped?: number;
117
+ }
118
+ /** A tool call that named files (`paths` is never empty) at the byte offset of its line, or a result that closed one. */
119
+ export interface CallEvents {
120
+ readonly opens: readonly {
121
+ readonly id: string;
122
+ readonly at: number;
123
+ readonly paths: readonly string[];
124
+ readonly incomplete?: true;
125
+ readonly tool?: string;
126
+ readonly files?: readonly string[];
127
+ readonly named?: true;
128
+ }[];
129
+ readonly closes: readonly {
130
+ readonly id: string;
131
+ readonly at: number;
132
+ }[];
133
+ /** Reserved: a read that cannot vouch for every call. A call whose input was too deep is not this (C44 (4)): its entry is marked `incomplete` instead. */
134
+ readonly overflow: boolean;
135
+ }
136
+ export interface ScanOptions {
137
+ /** Stop at the first `cwd` hit and return only that (backfill's folder check). */
138
+ readonly first?: boolean;
139
+ /** Treat bytes after the last newline as a final record (a finished transcript with no trailing newline). */
140
+ readonly includeUnterminatedTail?: boolean;
141
+ /** Test seam: the longest line held. */
142
+ readonly maxLineBytes?: number;
143
+ /** Test seam: the most distinct strings kept. */
144
+ readonly maxDistinctHits?: number;
145
+ /** The most bytes READ from disk in this call (default `EVIDENCE_SCAN_WINDOW_BYTES`). A line the budget ends inside is left for the next call. */
146
+ readonly maxReadBytes?: number;
147
+ /** Resume skipping an oversize line the previous call's budget ended inside. */
148
+ readonly continueSkipping?: boolean;
149
+ /** Part (b), `spec.codexFolders` only: where a resumed scan picks up (the turn folder, the session and cell maps) and the calls the checkpoint already knows. */
150
+ readonly codex?: {
151
+ readonly turn: string | null;
152
+ readonly sessions: readonly (readonly [string, IdEntry])[];
153
+ readonly cells: readonly (readonly [string, IdEntry])[];
154
+ readonly calls: (id: string) => CallInfo | undefined;
155
+ /** C62 (3): has the checkpoint seen a call with this id? */
156
+ readonly seen?: (id: string) => boolean;
157
+ /** The calls still waiting for an output at the resume point, oldest first (the tracker's open-call bound counts them). */
158
+ readonly openCalls: readonly (readonly [string, CallInfo])[];
159
+ };
160
+ }
161
+ /**
162
+ * Read `[from, …)` of a transcript and return the folders and paths its records name. ⛔ **THE ONLY TRANSCRIPT-FIELD
163
+ * READER** (D15 · A). It starts a line only before `to`, finishes that line however long (up to `maxLineBytes`), and stops.
164
+ * A line is parsed only if it mentions one of the keys; everything but the matching string values is discarded.
165
+ *
166
+ * ## ⛔ Bounded in what it READS, not only in what it parses (P1 review of A-T2)
167
+ *
168
+ * `maxReadBytes` caps the bytes read from disk in one call. A line the budget ends inside is NOT consumed: the cursor stays at
169
+ * its start and a fresh firing re-reads it whole (every line up to `maxLineBytes` fits one fresh budget). A line over
170
+ * `maxLineBytes` is never buffered: it is SKIPPED, counted once, and the scan keeps going to its newline; if the budget ends
171
+ * first, `skipping` is returned with the cursor advanced, and the next call passes `continueSkipping` so it resumes the skip
172
+ * instead of re-reading the line.
173
+ *
174
+ * `unreadable` (cannot open or stat) and a result with no hits are different facts and are told apart: a caller keeping sticky
175
+ * targets treats `unreadable` as "no NEW targets this firing" and carries on (Q4).
176
+ *
177
+ * ⚠ Keys are matched by their plain names. A writer that escaped an ASCII key (`"file\u005fpath"`) would be missed: no real
178
+ * transcript writer does (they emit keys verbatim), and the redactor, which parses the same lines, has the same reach.
179
+ */
180
+ export declare function scanTranscriptKeys(path: string, from: number, to: number, spec: EvidenceSpec, options?: ScanOptions): KeyScan;
181
+ /** Returns true when it could NOT look at everything (the depth limit): the caller treats that like a capped scan. */
182
+ /**
183
+ * The folders ONE parsed record names, by the dialect's `cwdKeys`. ⛔ **Part of the single transcript-field reader**: record
184
+ * isolation (R17) asks this same function which folder a record ran in, so "where a record ran" has one definition.
185
+ */
186
+ export declare function cwdsOfRecord(record: Record<string, unknown>, cwdKeys: readonly CwdKey[]): string[];
187
+ /**
188
+ * How many firings may fail to name a root that has a `.git` before it is recorded `nonrepo`. A `.git` that `git rev-parse`
189
+ * will not answer for (dubious ownership, a corrupt repo, a stale worktree file, a timeout) is evidence of a repo we cannot
190
+ * name; it is retried a few times (a timeout heals) and then stops costing a slot per firing. Either way it STILL counts as a
191
+ * named repo for R6's folder rule: `nonrepo` here means "unnameable", never "not a repo".
192
+ */
193
+ export declare const MAX_NAMING_FAILURES = 3;
194
+ /** Candidate roots resolved per firing (R16 · D18). Each costs up to four spawns; the rest wait for the next firing. */
195
+ export declare const MAX_NEW_ROOTS_PER_FIRING = 8;
196
+ /** One place a session's bytes go. */
197
+ export interface Target {
198
+ readonly kind: "repo" | "folder";
199
+ /** How this repo's work trees were grouped (`groupKey`); the sticky key. Absent on a folder. */
200
+ readonly groupKey?: string;
201
+ /** The wire identity (`X-Repo-Slug`), exactly as resolved. Never a fallback (R16). */
202
+ readonly slug: string;
203
+ /** ⛔ The ledger bucket: `SessionKey.repoKey`. ONE per (repo slug, session) (R5 · D6). */
204
+ readonly ledgerKey: string;
205
+ /** The redaction roots for this target's stream: its repo's work trees and main clone, or the folder. */
206
+ readonly redactionRoots: readonly string[];
207
+ /** Every work tree of this repo this session was seen in, first-seen order. `[]` for a folder. */
208
+ readonly worktrees: readonly string[];
209
+ readonly commonDir: string | null;
210
+ /** The launch folder (folder targets only). */
211
+ readonly folder?: string;
212
+ /** Worktree ledgers for this (slug, session) that were NOT adopted (O6): left unread, never merged. */
213
+ readonly setAsideLedgers: readonly string[];
214
+ /**
215
+ * C27: this session has named a work tree other than this target's (or its status is unknown). Sticky and monotonic, computed only from scanned
216
+ * bytes: once true it never goes back, so a record cannot be sent in a "single-repo" copy after the session turned out to be multi. A copy
217
+ * delivered BEFORE the session first named another tree kept its records (the accepted residual).
218
+ */
219
+ readonly multi?: true;
220
+ }
221
+ /** A repo or folder the session's evidence named that capture was never told about (R8 · D11 · B). Names only. */
222
+ export interface UndecidedName {
223
+ readonly kind: "repo" | "folder";
224
+ readonly name: string;
225
+ readonly at: number;
226
+ }
227
+ export type RootInfo = {
228
+ readonly state: "repo";
229
+ readonly toplevel: string;
230
+ readonly commonDir: string;
231
+ readonly slug: string;
232
+ /** `[worktree, main clone]` as `confinementRoots` computed them, resolved ONCE. */
233
+ readonly roots: readonly string[];
234
+ } | {
235
+ readonly state: "nonrepo";
236
+ };
237
+ interface StreamCursor {
238
+ readonly offset: number;
239
+ readonly size: number;
240
+ /** The previous firing's read budget ended inside an oversize line: resume skipping it, never re-read it. */
241
+ readonly skipping?: true;
242
+ }
243
+ /** C40 (1): the most calls still waiting for a result that one stream's checkpoint keeps; the oldest is dropped (and the log is `overflowed`). */
244
+ export declare const MAX_OPEN_CALLS = 256;
245
+ /** C44 (7a): the most dropped open-call ids a log remembers before it falls back to the sticky `overflowed`. */
246
+ export declare const MAX_DROPPED_IDS = 4096;
247
+ /** Calls whose result was seen, kept until every copy is past them (a late target starts at offset 0); the oldest is dropped (the `horizon` rises). */
248
+ export declare const MAX_CLOSED_CALLS = 4096;
249
+ /** Part (b): the most turn-folder change points one stream's timeline keeps; the oldest is dropped, and a position before the first kept point is unknown. */
250
+ export declare const MAX_FOLDER_CHANGES = 4096;
251
+ /**
252
+ * C75 (9): the timeline keeps at most `MAX_FOLDER_CHANGES` change points AND `CALL_LOG_BUDGET_BYTES` of folder text (4,096 points of 4,096 bytes are 16 MiB): the OLDEST points are
253
+ * dropped, and a position before the first kept point is unknown (the existing rule), so nothing is silently judged by a point that is gone.
254
+ */
255
+ export declare function boundedChanges(changes: readonly {
256
+ readonly at: number;
257
+ readonly dir: string | null;
258
+ }[]): {
259
+ at: number;
260
+ dir: string | null;
261
+ }[];
262
+ /** Part (b): a Codex stream's turn-folder timeline, ascending by byte offset. Written only from bytes the resolver scanned. */
263
+ export interface FolderLog {
264
+ /** `dir: null`: an unreadable header cwd, the turn folder is unknown from `at` (C49 (6)). */
265
+ readonly changes: readonly {
266
+ readonly at: number;
267
+ readonly dir: string | null;
268
+ }[];
269
+ /** The session map (a process an `exec_command` started -> the folders of that call and the offset each was learned at), oldest first, at most `MAX_ID_MAP`. An id not here is unknown. */
270
+ readonly sessions?: readonly (readonly [string, IdEntry])[];
271
+ /** The cell map (a script an `exec` program started -> the folders of that program), same bounds. */
272
+ readonly cells?: readonly (readonly [string, IdEntry])[];
273
+ /** C56 (8): the offset of the first line of this stream the scan SKIPPED (too long to read). Sticky: an output whose call is unknown after it is unjudged. */
274
+ readonly skipped?: number;
275
+ }
276
+ /** One tool call that named files (C40 (1)). The paths are LOCAL checkpoint state, never sent; the entry is removed once every copy is past its result. */
277
+ export interface CallEntry {
278
+ readonly id: string;
279
+ /** The byte offset of the line holding the call. */
280
+ readonly at: number;
281
+ readonly paths: readonly string[];
282
+ /** The byte offset of the line holding its result, or null while it has none. */
283
+ readonly closedAt: number | null;
284
+ /** C44 (4): the call's input was nested past the walk's depth, so `paths` may be partial: its result is `unknown`, never judged by them. */
285
+ readonly incomplete?: true;
286
+ /** Part (b), Codex: the tool the call named (`exec_command`, `exec`, `write_stdin`, ...): what its output teaches depends on it. */
287
+ readonly tool?: string;
288
+ /** Part (b), Codex (C48 (11)): the subset of `paths` that are FILES the call named (a patch's headers, `view_image`'s path): judged like a Claude path, by each copy. */
289
+ readonly files?: readonly string[];
290
+ /** C56 (1), Codex: the call carried files or evidence (evidence is not kept, only that there was some): evicting the entry must leave a mark that its output is unjudged. */
291
+ readonly named?: true;
292
+ }
293
+ /**
294
+ * One stream's call checkpoint, written only from bytes the resolver scanned. A result whose id is not an entry is `none` (a call that named no
295
+ * file: a stated residual) only when the log is complete AND the result is past the horizon; otherwise it is `unknown` and withheld.
296
+ */
297
+ export interface CallLog {
298
+ readonly entries: readonly CallEntry[];
299
+ /** A result at or below this offset whose id is not an entry may belong to an entry that was dropped: unknown. */
300
+ readonly horizon: number;
301
+ /** A line was not read, the log began mid-stream, or `droppedIds` itself overflowed: every unmatched result is unknown. */
302
+ readonly overflowed: boolean;
303
+ /**
304
+ * C44 (7a): the ids of open calls the 256-entry cap dropped (a call that never got a result, in the biggest sessions). A result with one of these ids is
305
+ * `unknown`; an unmatched id that is not here, above the horizon, is `none`. Bounded; past the bound the log is `overflowed` for good.
306
+ */
307
+ readonly droppedIds?: readonly string[];
308
+ /** C56 (1), Codex: the ids of EVICTED calls (open past the cap, or closed past it) that carried files or evidence. Their outputs are UNJUDGED. Bounded like `droppedIds`. */
309
+ readonly droppedNamed?: readonly string[];
310
+ /** C56 (1): `droppedNamed` overflowed: every output whose call is not an entry is unjudged from here on. */
311
+ readonly droppedNamedAll?: true;
312
+ /**
313
+ * C62 (4): the ids of EVERY entry `pruneCalls` removed (named or not), bounded at MAX_DROPPED_IDS (the oldest is forgotten; NOT sticky). A call that reopens one of
314
+ * them is a REPEATED id (poisoned), whichever view judges it. Costs at most MAX_DROPPED_IDS short strings per stream in the persisted record.
315
+ */
316
+ readonly prunedIds?: readonly string[];
317
+ }
318
+ /**
319
+ * The per-session state that is sticky across firings. ⛔ Separate from every target's upload offsets (those live in the
320
+ * ledgers): R19's factual correction puts the discovery cursor, the unresolved roots and the deferred roots HERE so "the
321
+ * rest next firing" has somewhere to resume from, and so a ledger never carries discovery state.
322
+ */
323
+ export interface TargetRecord {
324
+ readonly v: 1;
325
+ readonly sessionId: string;
326
+ /** The first folder any record named (Claude's first `cwd`; Codex's `session_meta`/`turn_context`). R6's "launch folder". */
327
+ readonly launchFolder: string | null;
328
+ /** Scan cursor per stream file key (`main`, `agent-<id>`). */
329
+ readonly scan: Readonly<Record<string, StreamCursor>>;
330
+ /** C40 (1): the call checkpoint per stream file key. Absent for a stream scanned before it existed: that stream fails closed. */
331
+ readonly calls: Readonly<Record<string, CallLog>>;
332
+ /** Part (b): the turn-folder timeline per Codex stream file key. Absent for a dialect that does not need it. */
333
+ readonly folders: Readonly<Record<string, FolderLog>>;
334
+ /** Part (b), sticky: a Codex record's folder could not be told (a call whose folder is unreadable, an output whose call is unknown, a record before any turn folder). A folder target needs every folder determined. */
335
+ readonly folderUnknown?: true;
336
+ /** A line over `EVIDENCE_MAX_LINE_BYTES` was skipped, or hits were dropped past the cap: "no repo" can never be proven (R18). */
337
+ readonly oversize: boolean;
338
+ /** How many times naming a root failed for a reason other than the spawn window (git would not name it). Bounded: see `MAX_NAMING_FAILURES`. */
339
+ readonly failures: Readonly<Record<string, number>>;
340
+ /** Resolved candidate roots, in first-seen order (JS preserves string-key insertion order). */
341
+ readonly roots: Readonly<Record<string, RootInfo>>;
342
+ /** Collapsed candidate roots not yet resolved: over the per-firing cap, or the spawn window ran out (R16). */
343
+ readonly unresolved: readonly string[];
344
+ /**
345
+ * Roots known ONLY from a ledger note (a ledger for this session sits under that work tree's digest) and never named by this
346
+ * transcript's evidence. They may be adopted into a repo group that evidence DID name (R5: the ledger to keep), but they never
347
+ * create a target on their own: a ledger written for another transcript of the same session id is not evidence about this one.
348
+ */
349
+ readonly ledgerOnly: readonly string[];
350
+ /** Sticky: once granted and created, a target keeps its ledger key for the life of the session (R5). */
351
+ readonly targets: readonly Target[];
352
+ readonly undecided: readonly UndecidedName[];
353
+ /** Targets that must stop receiving bytes, by repo group or folder slug (C3: `folder_repository_forbidden`). Sticky; `status` names a stopped folder (`readStoppedFolders`). */
354
+ readonly stopped: Readonly<Record<string, {
355
+ readonly at: number;
356
+ readonly code: string;
357
+ }>>;
358
+ /** `Resolution.settled` as of the last resolve: discovery exhausted. The drain skips a settled session's discovery (R19). */
359
+ readonly settled: boolean;
360
+ /** The transcript this session's hooks were handed, so a LATER firing of another session can finish its owed bytes (R19). */
361
+ readonly transcriptPath: string | null;
362
+ /** When a real hook firing last resolved this session (the drain does not count): "idle for ≥10 min" is measured from it. */
363
+ readonly firedAt: number;
364
+ readonly updatedAt: number;
365
+ }
366
+ export declare function emptyTargetRecord(sessionId: string): TargetRecord;
367
+ /** The record, or null when absent or unreadable. ⚠ Null is a RESYNC, not an error: the scan restarts and ledgers are adopted. */
368
+ export declare function loadTargetRecord(home: string, sessionId: string): TargetRecord | null;
369
+ /** Atomic (temp + rename), mode 0600. ⛔ The CALLER holds the one per-session lock (O5); this module never takes it. */
370
+ export declare function saveTargetRecord(home: string, record: TargetRecord): boolean;
371
+ /** Every session's record on this machine, with its file and mtime, for the idle-session drain (R19). Unreadable ones are skipped. */
372
+ export declare function listTargetRecords(home: string): {
373
+ path: string;
374
+ mtimeMs: number;
375
+ record: TargetRecord;
376
+ }[];
377
+ /** Names recent sessions' evidence left undecided, for `vibecommit status` (names only: no transcript is opened). */
378
+ export declare function readUndecidedNames(home: string, sinceMs: number): readonly UndecidedName[];
379
+ /** A folder target the server refused for good (C3), as `status` names it. */
380
+ export interface StoppedFolder {
381
+ /** The folder's real path, as the target recorded it. */
382
+ readonly folder: string;
383
+ readonly slug: string;
384
+ readonly at: number;
385
+ readonly code: string;
386
+ }
387
+ /**
388
+ * The folder targets that were stopped (C3: `folder_repository_forbidden`) in the last `sinceMs`, newest per folder, for
389
+ * `vibecommit status`. Reads only the per-session target records through the same directory listing the drain and
390
+ * `readUndecidedNames` use: no transcript is opened, and a record the drain has forgotten is simply not there.
391
+ */
392
+ export declare function readStoppedFolders(home: string, sinceMs: number): readonly StoppedFolder[];
393
+ /**
394
+ * ⛔ A FOLDER TARGET'S LEDGER KEY IS NAMESPACED (C17 #1): `folder:<realpath>`, never the bare path. A ledger is keyed by the string
395
+ * its digest is taken of, and a work tree's ledger key IS its path; if the folder later became a git repo and the session resumed,
396
+ * the repo target would have adopted the folder's ledger and sent its offsets to the repo's server stream. A prefixed key can never
397
+ * equal a work-tree path, so the two can never collide.
398
+ */
399
+ export declare const FOLDER_LEDGER_PREFIX = "folder:";
400
+ export declare function folderLedgerKey(realLaunchFolder: string): string;
401
+ /** `local:folder:<first 12 lowercase hex of sha256(realpath(launch folder))>` — C2. A no-repo remote keeps `local:<12 hex>`. */
402
+ export declare function folderSlug(realLaunchFolder: string): string;
403
+ /** Everything `resolveTargets` needs from its caller. The `seams` are for tests; production passes none. */
404
+ /** The caller's session lock was lost while it resolved targets (C25). Nothing was written. */
405
+ export declare class LockLostError extends Error {
406
+ constructor();
407
+ }
408
+ export interface ResolveContext {
409
+ readonly home: string;
410
+ readonly env: NodeJS.ProcessEnv;
411
+ readonly sessionId: string;
412
+ readonly transcriptPath: string;
413
+ /** The cwd the hook was handed (Claude: the shell's current folder). Null for finalize/backfill, which have none. */
414
+ readonly turnEndCwd: string | null;
415
+ /**
416
+ * `end`: SessionEnd, `finalize` or backfill — the only callers that may decide a FOLDER target, and only after the whole
417
+ * file was read (R18 · D21). `turn`: any other hook firing.
418
+ */
419
+ readonly mode: "turn" | "end";
420
+ readonly nowMs: number;
421
+ /**
422
+ * A drain of an IDLE session by another session's firing (R19): consent and discovery proceed, but this is not a firing OF the
423
+ * session. The file is read as FINAL for tail purposes only, so a crashed session's torn last line settles it; it never makes a
424
+ * folder target (that stays `end` mode's).
425
+ */
426
+ readonly drain?: boolean;
427
+ /**
428
+ * The roots whose naming failure THIS INVOCATION has already counted (C21 iv). An interactive verb (`finalize`, `backfill`) calls the
429
+ * resolver in a loop; the failure count exists to give up on a root that fails across SEPARATE invocations, so within one loop a
430
+ * root counts at most once, the first time it fails, in whatever round. Counting every round made a single git timeout, retried
431
+ * three times in milliseconds, mark the repository `nonrepo` for the session for good; counting nothing after round 1 let roots first
432
+ * tried in a later round go uncounted. Absent (a hook: one call per firing): every failure counts.
433
+ */
434
+ readonly countedThisInvocation?: Set<string>;
435
+ /**
436
+ * C25: the caller's session-lock heartbeat (`finalize`, `backfill`), called before each root is named (the slow step: a few git
437
+ * calls, each with its own timeout). When it says the lock is gone, `LockLostError` is thrown BEFORE anything is written, so a
438
+ * holder that lost the session never persists a record over its new owner's. A hook passes none.
439
+ */
440
+ readonly heartbeat?: () => boolean;
441
+ /**
442
+ * The turn-end repo the hook ALREADY resolved (its keys and its memoized slug thunk). Reused when a candidate root IS that
443
+ * work tree, so the hook still spends exactly one `rev-parse --show-toplevel` and one `remote get-url` for the repo it is in
444
+ * (`TODOS[87]`), instead of resolving it again here.
445
+ */
446
+ readonly known?: {
447
+ readonly keys: ProjectKeys;
448
+ readonly slugOf: () => string | null;
449
+ };
450
+ readonly seams?: {
451
+ readonly resolveKeys?: (dir: string) => ProjectKeys | null;
452
+ /** `null` = git could not say (a timeout or error): the root is DEFERRED, never given a fallback identity. */
453
+ readonly slugOf?: (toplevel: string) => string | null;
454
+ readonly rootsOf?: (keys: ProjectKeys) => readonly string[];
455
+ /** False when the spawn window is spent (R16). Default: the real `spawn_budget`. */
456
+ readonly spawnOk?: () => boolean;
457
+ /** Where a folder stands. Default `undecided` until folder grants exist (A-T5 wires `consent.ts`). */
458
+ readonly folderStanding?: (realFolder: string) => CaptureStanding;
459
+ readonly scanWindowBytes?: number;
460
+ readonly maxNewRoots?: number;
461
+ readonly maxLineBytes?: number;
462
+ readonly maxDistinctHits?: number;
463
+ };
464
+ }
465
+ export interface Resolution {
466
+ /** What to deliver this firing: sticky targets whose consent still holds. Never contains an undecided or denied name. */
467
+ readonly targets: readonly Target[];
468
+ readonly undecided: readonly UndecidedName[];
469
+ /**
470
+ * Every stream read to its end, no unresolved or deferred root, no oversize line, nothing unreadable. R18's "no repo" is
471
+ * decided only when this holds; R19's drain keeps calling until it does.
472
+ */
473
+ readonly complete: boolean;
474
+ /**
475
+ * DISCOVERY IS EXHAUSTED: every stream is at the furthest point it can ever reach (its end; for a final file a torn tail or an
476
+ * oversize skip that ran to the end), no root is unresolved, nothing vanished unfinished. Nothing a later firing could still
477
+ * discover. `settled && !complete` is exactly "fully read, and the FOLDER decision is permanently blocked" (an oversize or
478
+ * capped scan, a torn tail, a spool no note maps). R19's drain retires a settled session's DISCOVERY (its repo targets still
479
+ * deliver owed bytes either way), so a session that can never be fully read is not re-picked every firing and cannot starve the
480
+ * others. NOT settled: a window is left, a root is deferred, or a stream is unreadable: a later firing may still find a repo.
481
+ */
482
+ readonly settled: boolean;
483
+ /** False when the record could not be written: targets that would have been NEW this firing were deferred. */
484
+ readonly persisted: boolean;
485
+ /** Normalised slug of the repo the turn ended in, when it resolved as one: R19's "a target named by its turn-end cwd". */
486
+ readonly turnEndSlug: string | null;
487
+ readonly record: TargetRecord;
488
+ }
489
+ /** A stream the resolver reads for evidence: the main transcript or one delegated transcript. */
490
+ export interface EvidenceStream {
491
+ readonly fileKey: string;
492
+ readonly path: string;
493
+ }
494
+ /**
495
+ * Every `agent-*.jsonl` under this session's `subagents/`, oldest first, each confined to the transcript roots.
496
+ *
497
+ * ⛔ **THE ONE COPY of sub-agent discovery** (A-T3 deletes `hooks/entry.ts`'s and imports this): `agent-*.jsonl`, never
498
+ * `agent-*` (the `.meta.json` sidecar would double every upload); every entry gated by `isInsideAny` so a symlink cannot walk
499
+ * out; the glob runs only for a dialect that declares `discover`.
500
+ */
501
+ export declare function findSubagentTranscripts(home: string, env: NodeJS.ProcessEnv, transcriptPath: string): {
502
+ path: string;
503
+ fileKey: string;
504
+ size: number;
505
+ mtimeMs: number;
506
+ }[];
507
+ /**
508
+ * `findSubagentTranscripts` that also says whether the directory could not be LISTED (C17 #2): ENOENT is "no delegated streams",
509
+ * but EACCES or EIO is "unknown", and the resolver must not read unknown as empty (a folder could be authorized over a stream it
510
+ * never saw).
511
+ */
512
+ export declare function findSubagentTranscriptsChecked(home: string, env: NodeJS.ProcessEnv, transcriptPath: string): {
513
+ found: {
514
+ path: string;
515
+ fileKey: string;
516
+ size: number;
517
+ mtimeMs: number;
518
+ }[];
519
+ listFailed: boolean;
520
+ };
521
+ /**
522
+ * Whether the dialect that owns this transcript can keep one repository's copy free of another's records (`isolatesContentRecords`).
523
+ * A transcript outside every dialect's root, or inside more than one, is NOT isolatable: fail closed.
524
+ */
525
+ export declare function dialectIsolates(home: string, env: NodeJS.ProcessEnv, transcriptPath: string): boolean;
526
+ /** The evidence spec for a transcript: redaction's key lists (A3: one list) plus the dialect's `cwdKeys`. */
527
+ export declare function evidenceSpecFor(home: string, env: NodeJS.ProcessEnv, transcriptPath: string): EvidenceSpec;
528
+ /**
529
+ * What a target's stream may contain beyond the structured-path rule (A-T4). `repo`: records that ran inside a work tree other
530
+ * than `ownRoots` are isolated (R17). `folder`: nothing inside any git work tree is kept (R6's exclusion roots).
531
+ */
532
+ export interface Isolation {
533
+ readonly kind: "repo" | "folder";
534
+ /** A repo target's own roots (its work trees and main clone). Empty for a folder. */
535
+ readonly ownRoots: readonly string[];
536
+ /**
537
+ * C27: this copy belongs to a session that named another work tree, or whose status is unknown (a record that could not be read counts as multi).
538
+ * Absent only for a caller that has no record at all (a test): then no record is aggregated and no call is judged.
539
+ */
540
+ readonly multi?: boolean;
541
+ /**
542
+ * C40 (1): the call checkpoint per stream file key, as the resolver last wrote it; `null` when the record was unreadable (every result is then
543
+ * unknown and withheld). Absent with `multi`: no call is judged.
544
+ */
545
+ readonly calls?: Readonly<Record<string, CallLog>> | null;
546
+ /** Part (b): the turn-folder timeline per Codex stream, as the resolver last wrote it. */
547
+ readonly folders?: Readonly<Record<string, FolderLog>>;
548
+ /**
549
+ * C42: the hook may deliver a REPO copy past the scan cursor. Per-record decisions stay exact there; the two facts the cursor's checkpoint cannot
550
+ * hold (a call opened past it, a second work tree first named past it) are learned IN-SPAN, in file order, by this observer. Absent for finalize and
551
+ * backfill (they resolve to the end of the file first) and for a folder (it never delivers past the cursor).
552
+ */
553
+ readonly past?: PastCursor;
554
+ }
555
+ /** What one delivery learns from the bytes the persisted checkpoint cannot judge (C42 past the scan cursor, C43 at or below the call horizon). Lives for one delivery; never persisted, never sent. */
556
+ interface SpanFacts {
557
+ /** The first byte this observer saw. Below it the persisted call log and multi flag decide; from it, in order, every record is observed. */
558
+ readonly start: number;
559
+ readonly spec: EvidenceSpec;
560
+ /** Every byte in `[start, coveredTo)` has been observed, in order. */
561
+ coveredTo: number;
562
+ /** Part (b), Codex: this stream's in-order state (turn folder, session and cell maps, the calls seen), the same tracker the resolver's scan uses. Bounded like the checkpoint. Replaced by a copy of the snapshot when the same bytes are read again (C65 (1)). */
563
+ tracker?: CodexTracker;
564
+ /** EVERY tool call opened at or after `start` (id -> position and paths; empty `paths` is a call that named no file, C44 (7b); `incomplete` is a truncated input). */
565
+ calls: Map<string, {
566
+ readonly at: number;
567
+ readonly paths: readonly string[];
568
+ readonly incomplete?: true;
569
+ }>;
570
+ /** Some bytes after `start` could not be observed: every result whose call is not known fails closed. */
571
+ unreliable: boolean;
572
+ /**
573
+ * C65 (1): the stream's facts as they stood when the latest read began (`at` is where it began, never below `start`). `deliver()` reads `[from, to)` again from
574
+ * the same `from` after a frame split or a 413; each byte must be stepped ONCE and every record judged with exactly the state after `[start, record)`, so a read
575
+ * that begins at `at` again starts from a copy of this, and a read that begins anywhere else below `coveredTo` fails closed.
576
+ */
577
+ snap?: {
578
+ readonly at: number;
579
+ readonly tracker?: CodexTracker;
580
+ readonly calls: Map<string, {
581
+ readonly at: number;
582
+ readonly paths: readonly string[];
583
+ readonly incomplete?: true;
584
+ }>;
585
+ readonly unreliable: boolean;
586
+ };
587
+ }
588
+ export interface PastCursor {
589
+ readonly cursors: Readonly<Record<string, number>>;
590
+ readonly facts: Map<string, SpanFacts>;
591
+ /**
592
+ * C44 (3): the in-span multi flag is SESSION-wide within one delivery (call state stays per stream), monotonic: the first record of any stream that names
593
+ * another work tree makes the rest of THAT stream multi and every stream delivered after it multi from its start. A stream already delivered keeps what
594
+ * it was judged with (the C36 (6) "before the first naming" class).
595
+ */
596
+ multi(fileKey: string, position: number): boolean;
597
+ /** Part (b): what places a Codex record at `position` of this stream: this observer's live state from where it started, else the persisted checkpoint. */
598
+ codexEnv(fileKey: string, position: number): CodexFolderEnv;
599
+ /** Make the stream's facts current for a read of `[from, to)`; true when the read needs the observer (past the cursor, or at or below the call horizon). */
600
+ begin(spec: EvidenceSpec, path: string, fileKey: string, from: number, to: number): boolean;
601
+ /** The read is done: its bytes are observed. */
602
+ end(fileKey: string, to: number): void;
603
+ observe(fileKey: string, record: Record<string, unknown>, offset: number): void;
604
+ }
605
+ export interface PastCursorOptions {
606
+ /** Test seam: the most gap bytes re-read (default 64 MiB). */
607
+ readonly maxGapBytes?: number;
608
+ /** Test seam: the chunk size of the gap read (default 4 MiB). */
609
+ readonly chunkBytes?: number;
610
+ /** The hook's shared send deadline (epoch ms). Between chunks, a remaining budget that cannot cover the next one fails the stream closed for the firing. Absent: unbounded (finalize, backfill). */
611
+ readonly deadlineAtMs?: number;
612
+ /** Test seam: the clock. */
613
+ readonly nowMs?: () => number;
614
+ /** Test seam: the longest gap line the observer will parse (default 4 MiB). */
615
+ readonly maxGapLineBytes?: number;
616
+ }
617
+ /**
618
+ * C42/C43: the observer for one delivery. `foreign` is the same stat-only test as record isolation (`workTreeRootOf`, nearest existing ancestor for a
619
+ * gone path, no spawn, cached); a path outside every work tree (the global CLAUDE.md, /tmp) is NOT another work tree and sets nothing.
620
+ *
621
+ * A stream needs it when the read goes past the scan cursor (C42: calls and a second work tree the checkpoint never saw), and when the read reaches a
622
+ * position at or below the call HORIZON (C43: calls were pruned once every then-existing copy was past them, so a copy created later, a repo granted
623
+ * after the fact, cannot judge a result by the log). Then it observes its OWN delivered bytes from the first one it delivers, in order. A call seen
624
+ * earlier gives its paths; one not seen gives `none` only when the observer started at byte 0 of the stream (it saw every byte before the result);
625
+ * a copy that starts mid-stream below the horizon keeps the persisted verdict (`unknown`), so a call the 4,096-entry cap dropped stays withheld.
626
+ */
627
+ export declare function pastCursorOf(isolation: Isolation, cursors: Readonly<Record<string, number>>, options?: PastCursorOptions): PastCursor;
628
+ /** `record`: the session's target record. `undefined`: no facts (legacy callers, tests). `null`: unreadable, so the copy fails closed. */
629
+ export declare function isolationOf(target: Target, record?: TargetRecord | null): Isolation;
630
+ /**
631
+ * The redaction scope for one stream (A-T4). Pure `stat` walks, memoised for the span (`workTreeRootOf` caches per directory);
632
+ * no git spawn, so it is free against a hook's budget. A cwd that is gone, or outside every work tree, is NOT another work tree:
633
+ * the record is kept (the structured-path rule still covers a file read from there).
634
+ *
635
+ * `stream` (C40 (1)): the file key of the stream being cut, and whether the dialect tracks calls; with it the scope judges each tool result by the
636
+ * checkpoint's record of its call, by the position the span reports (`at`).
637
+ */
638
+ export declare function redactionScopeFor(isolation: Isolation, cwdKeys: readonly CwdKey[], stream?: {
639
+ readonly fileKey: string;
640
+ readonly trackCalls: boolean; /** Part (b): the dialect is Codex: a record's folder is its EFFECTIVE folder (`codex_folders.ts`). */
641
+ readonly codex?: boolean;
642
+ }): RedactionScope;
643
+ /**
644
+ * What makes two work trees ONE repository for a target (R5). A remote repo is its normalised slug. A repo with NO remote has a
645
+ * `local:<hash of its path>` slug that differs per work tree, yet its work trees share one git common dir and are one
646
+ * repository; grouping by slug alone would split a main clone and its worktrees into two targets and send a session twice.
647
+ * The slug SENT is the preferred member's (the turn-end work tree first), exactly as 0.8.0 sent the turn-end tree's.
648
+ */
649
+ export declare function groupKey(info: Extract<RootInfo, {
650
+ state: "repo";
651
+ }>): string;
652
+ /** The log with its oldest path-bearing entries evicted until `free` bytes of path TEXT are freed (the table's own budget). */
653
+ export declare function shedCalls(log: CallLog, free: number, includePathless?: boolean, measure?: "text" | "full", keepNewest?: number): CallLog;
654
+ /**
655
+ * C76 (3): ONE budget for a whole SESSION. Every stream (the main transcript and each subagent's) has its own tables, each held to its own bound, and a session can have any number of
656
+ * streams. After a firing has folded its events, the bytes the session's streams store (call entries, the three id lists, the Codex id maps and the folder timeline) may not pass
657
+ * `SESSION_BUDGET_BYTES`. C77: they are counted by an UPPER bound of their JSON size (the fixed cost of an entry, the escaped length of every string), so the record on disk is within the
658
+ * budget, not near it.
659
+ */
660
+ export declare const SESSION_BUDGET_BYTES: number;
661
+ /** What a call entry takes in the record (an upper bound). */
662
+ export declare function callEntryBytes(entry: CallEntry): number;
663
+ /** What a whole session's records take by the same measure. Exported for the cells. */
664
+ export declare function sessionBytes(calls: Readonly<Record<string, CallLog>>, folders: Readonly<Record<string, FolderLog>>): number;
665
+ /**
666
+ * Shed the session down to `budget`, in place on the two records. Returns true when a Codex id map lost evidence. The stream that holds the most sheds (a heap gives it in O(log streams)),
667
+ * down to the next largest, or its share of the excess when several tie, never more than the excess; so no stream is emptied while another keeps its oldest calls. Two phases:
668
+ * A. each pass takes at least `MIN_SHED_CHUNK` and runs, stopping as soon as its chunk is freed, (i) the stream's call entries STRICTLY OLDEST FIRST by position, with or without path
669
+ * text, each with its departure marks, except its `RECENT_CALLS` newest (a result arrives right after its call: without that floor, a stream whose id lists alone fill its share would
670
+ * lose its newest calls too); (ii) the oldest ids of its id lists, prunedIds first (not sticky), then droppedNamed, then droppedIds, each list's sticky mark set ONLY when it lost ids;
671
+ * (iii) the oldest Codex id-map tombstones (the whole map reads UNKNOWN from there); (iv) the oldest text of its id maps (collapse); (v) the oldest timeline points.
672
+ * B. only when NO stream has anything older left to shed, (vi) the newest call entries, by plain water-filling with no floor: a stream never loses the calls it was just scanned for
673
+ * while another still keeps older content.
674
+ * Every step returns the exact bytes it freed and the sizes are kept by subtraction, a growth (an eviction that adds a mark) booked as a negative amount; a stream is left out only when no
675
+ * step changed anything. A firing costs O(streams + evictions), plus one measure of every stream.
676
+ */
677
+ export declare function shedSession(calls: Record<string, CallLog>, folders: Record<string, FolderLog>, budget?: number): boolean;
678
+ /**
679
+ * C40 (1): fold the calls one scan read into a stream's checkpoint. Opens first, then closes (a result always follows its call in the file). More than
680
+ * `MAX_OPEN_CALLS` waiting drops the oldest and marks the log overflowed; more than `MAX_CLOSED_CALLS` closed drops the oldest and raises the horizon.
681
+ * `unread` (an oversize line, an input too deep to read) marks the log overflowed: a call may have been missed.
682
+ */
683
+ export declare function applyCallEvents(log: CallLog, events: CallEvents, unread: boolean): CallLog;
684
+ /**
685
+ * C40 (1): remove the entries whose result EVERY copy is already past (`lowWater` is the smallest sent offset of the stream over the session's
686
+ * targets): the paths are gone as soon as nobody can need them. A later copy that still needs one (a target created late starts at offset 0) finds
687
+ * the horizon raised and reads the call as unknown: withheld, never kept.
688
+ */
689
+ export declare function pruneCalls(log: CallLog, lowWater: number): CallLog;
690
+ /**
691
+ * C40 (1): what the checkpoint says about the result of call `id` found at byte `at` of its stream. The entry's paths (judged per copy by the
692
+ * caller), `none` (the call named no file: kept, a stated residual), or `unknown` (withheld, failing closed).
693
+ */
694
+ export declare function lookupCall(log: CallLog | undefined, id: string, at: number): readonly string[] | "none" | "unknown";
695
+ /**
696
+ * Turn a session's evidence into targets. ⛔ **The caller holds the one per-session lock** (O5); this reads the record,
697
+ * scans new bytes, resolves a bounded number of new roots, and saves the record.
698
+ *
699
+ * Evidence (R2 · D2 · A): the turn-end folder, each stream's new records' folders and path keys, and repos holding a spooled
700
+ * commit for this session. Nothing else is read, kept or sent. Granted repos become sticky repo targets; the rest are
701
+ * recorded by NAME as undecided (R8) and capture nothing. A folder target exists only in `end` mode, after every line was
702
+ * read, when no repo at all was named and the launch folder is granted (R6 · R18).
703
+ */
704
+ export declare function resolveTargets(ctx: ResolveContext): Resolution;
705
+ /** The key a target is stopped under (`TargetRecord.stopped`): its repo group, or its folder slug. */
706
+ export declare function stopKeyOf(target: Target): string;
707
+ /**
708
+ * Stop ONE target for the rest of the session (C3: `folder_repository_forbidden`). Sticky, per target, never credential-wide;
709
+ * `status` names it. Returns false when the record could not be written (the target then retries and is refused again, which
710
+ * costs a request, not a byte).
711
+ */
712
+ export declare function stopTarget(home: string, sessionId: string, target: Target, code: string, nowMs: number): boolean;
713
+ export {};