@vibe-commit/capture 0.10.0 → 0.11.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.
- package/README.md +24 -0
- package/dist/agent_hooks.js +2 -2
- package/dist/cli.d.ts +35 -0
- package/dist/cli.js +412 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/backfill.js +19 -2
- package/dist/commands/backfill.js.map +1 -1
- package/dist/commands/finalize.d.ts +37 -1
- package/dist/commands/finalize.js +129 -29
- package/dist/commands/finalize.js.map +1 -1
- package/dist/commands/status.js +7 -0
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/status_machine.js +5 -0
- package/dist/commands/status_machine.js.map +1 -1
- package/dist/commands/upload_worker.d.ts +63 -0
- package/dist/commands/upload_worker.js +336 -0
- package/dist/commands/upload_worker.js.map +1 -0
- package/dist/copy/strings.d.ts +23 -7
- package/dist/copy/strings.js +40 -7
- package/dist/copy/strings.js.map +1 -1
- package/dist/drain.d.ts +68 -0
- package/dist/drain.js +234 -0
- package/dist/drain.js.map +1 -0
- package/dist/firing.d.ts +169 -0
- package/dist/firing.js +334 -0
- package/dist/firing.js.map +1 -0
- package/dist/hooks/entry.d.ts +171 -26
- package/dist/hooks/entry.js +333 -507
- package/dist/hooks/entry.js.map +1 -1
- package/dist/hooks/fire_lock.d.ts +36 -87
- package/dist/hooks/fire_lock.js +40 -99
- package/dist/hooks/fire_lock.js.map +1 -1
- package/dist/hooks/post_commit.d.ts +2 -0
- package/dist/hooks/post_commit.js +7 -3
- package/dist/hooks/post_commit.js.map +1 -1
- package/dist/identity.d.ts +32 -0
- package/dist/identity.js +38 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +3 -17
- package/dist/index.js +59 -450
- package/dist/index.js.map +1 -1
- package/dist/paths.d.ts +18 -0
- package/dist/paths.js +31 -0
- package/dist/paths.js.map +1 -1
- package/dist/pending_prune.d.ts +2 -2
- package/dist/pending_prune.js +2 -2
- package/dist/policy.d.ts +17 -0
- package/dist/policy.js +7 -0
- package/dist/policy.js.map +1 -1
- package/dist/post.d.ts +52 -4
- package/dist/post.js +59 -21
- package/dist/post.js.map +1 -1
- package/dist/replay.d.ts +167 -0
- package/dist/replay.js +301 -0
- package/dist/replay.js.map +1 -0
- package/dist/session_targets.d.ts +12 -0
- package/dist/session_targets.js +6 -1
- package/dist/session_targets.js.map +1 -1
- package/dist/spool.d.ts +44 -16
- package/dist/spool.js +174 -33
- package/dist/spool.js.map +1 -1
- package/dist/spool_union.d.ts +18 -2
- package/dist/spool_union.js +23 -2
- package/dist/spool_union.js.map +1 -1
- package/dist/state.js +21 -14
- package/dist/state.js.map +1 -1
- package/dist/stdin.d.ts +2 -0
- package/dist/stdin.js +13 -0
- package/dist/stdin.js.map +1 -0
- package/dist/uploader_status.d.ts +34 -0
- package/dist/uploader_status.js +25 -0
- package/dist/uploader_status.js.map +1 -0
- package/dist/uploader_view.d.ts +39 -0
- package/dist/uploader_view.js +106 -0
- package/dist/uploader_view.js.map +1 -0
- package/dist/verbs.d.ts +11 -0
- package/dist/verbs.js +13 -0
- package/dist/verbs.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
package/dist/firing.d.ts
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE FIRING LOG AND THE HOOK'S LIVENESS STAMP — U3 of the detached uploader.
|
|
3
|
+
*
|
|
4
|
+
* A *firing* is one hook that ended a turn: today's hook body, deferred. The hook appends one line saying "this turn ended here, at this
|
|
5
|
+
* size, in this folder, with these repos" and returns; the upload worker (U5) replays the lines in order, each ALONE, with the values
|
|
6
|
+
* recorded at the moment the hook fired (plan §3.2-§3.4, D15). This module is the storage half and nothing in the hook calls it yet:
|
|
7
|
+
* it is inert until the hook is flipped (U6).
|
|
8
|
+
*
|
|
9
|
+
* ## What it owns
|
|
10
|
+
* - `<sid>.firings.jsonl` — append-only, one `O_APPEND` write per line, no lock (hooks never wait).
|
|
11
|
+
* - `<sid>.firings.cursor` — the worker's progress: a byte offset, and how many times the firing at that offset made the worker THROW.
|
|
12
|
+
* Written atomically, and only by a holder of the session's writer lock.
|
|
13
|
+
* - `<repo digest>/<sid>.live` — the hook-only liveness stamp, and the ONE function that reads liveness (`liveAtOf`).
|
|
14
|
+
*
|
|
15
|
+
* ## What a firing holds: paths and numbers only
|
|
16
|
+
* No credential, no transcript content. The worker re-confines `transcript` and `announced` with `admitsTranscript` before every read.
|
|
17
|
+
*
|
|
18
|
+
* ## ⛔ A LEAF MODULE. It imports `paths` and `atomic_write` and nothing else of this package's, because `spool.ts` and `state.ts` both
|
|
19
|
+
* read liveness through it and neither may import the other.
|
|
20
|
+
*/
|
|
21
|
+
import { type SessionKey } from "./paths.js";
|
|
22
|
+
/** The line format this build writes and reads. A reader sees a NEWER `v` as a stop sign, never as garbage (§3.14). */
|
|
23
|
+
export declare const FIRING_VERSION = 1;
|
|
24
|
+
/** A line longer than this is not written: a firing is ~300 bytes, and one `O_APPEND` write of a small buffer is what keeps hooks lock-free. */
|
|
25
|
+
export declare const MAX_FIRING_BYTES: number;
|
|
26
|
+
/** After this many uncaught throws on one firing, the worker skips it. A firing only sets an EOF, so the next one still covers its bytes. */
|
|
27
|
+
export declare const MAX_FIRING_THROWS = 3;
|
|
28
|
+
/**
|
|
29
|
+
* Per repo key: the liveness the hook read BEFORE it stamped (plan §3.4, P2 point 4). Stamping first would flip `stale-own` to `first-turn`.
|
|
30
|
+
* - a number: the stamp (`SessionState.liveAt`, or the `.live` file, whichever is later).
|
|
31
|
+
* - `null`: a state file exists with NO `liveAt` (what `finalize` and `backfill` leave). Reads as `stale-own`.
|
|
32
|
+
* - key ABSENT: no state file at all. Reads as `first-turn`.
|
|
33
|
+
* A plain number-or-omitted map would read a resumed session as `first-turn` and keep a lingering-id line.
|
|
34
|
+
*/
|
|
35
|
+
export type PriorLiveness = Readonly<Record<string, number | null>>;
|
|
36
|
+
export interface Firing {
|
|
37
|
+
readonly v: number;
|
|
38
|
+
/** The hook's own clock, epoch ms: "the moment its hook runs". Promotion, pending mode and the commit pin are all judged as of it. */
|
|
39
|
+
readonly at: number;
|
|
40
|
+
/**
|
|
41
|
+
* The session id as the agent named it. The log's FILE NAME is `sanitise(sid)` (capped at 128 characters), which loses characters the
|
|
42
|
+
* wire id keeps, so the worker takes the id to send from here and never from a file name.
|
|
43
|
+
*/
|
|
44
|
+
readonly sid: string;
|
|
45
|
+
/** `Stop`, `PreCompact`, `SessionEnd`, ... as the dialect named it. */
|
|
46
|
+
readonly event: string;
|
|
47
|
+
/** The main transcript's size when the hook ran. `SessionEnd` is settled by the worker, not here. */
|
|
48
|
+
readonly eof: number;
|
|
49
|
+
readonly transcript: string;
|
|
50
|
+
/** The turn-end folder (stdin's `cwd`). */
|
|
51
|
+
readonly cwd: string;
|
|
52
|
+
/** The turn-end repo's keys as step 3 resolved them. Re-deriving from `cwd` fails once a work tree is gone (§3.2). Absent: not in a repo. */
|
|
53
|
+
readonly keys?: {
|
|
54
|
+
readonly worktree: string;
|
|
55
|
+
readonly consent: string;
|
|
56
|
+
};
|
|
57
|
+
/** Present only when step 3's consent check got a REAL slug, never the `local:` fallback. */
|
|
58
|
+
readonly slug?: string;
|
|
59
|
+
readonly agent: string;
|
|
60
|
+
/** The identity digest (§3.13): a worker serves only firings of its own. */
|
|
61
|
+
readonly id: string;
|
|
62
|
+
readonly prior: PriorLiveness;
|
|
63
|
+
/** A sub-agent transcript the dialect announced for this firing. */
|
|
64
|
+
readonly announced?: string;
|
|
65
|
+
/**
|
|
66
|
+
* U6b (F5, C33): each sub-agent file's size when the hook ran, by file key. A replay sends no sub-agent byte past these (a file absent here
|
|
67
|
+
* sends nothing in this step), so a late replay cannot read sub-agent bytes that did not exist at the firing and a delayed worker produces what
|
|
68
|
+
* a prompt one does. ABSENT: an older firing, or one whose map would not fit the line; its sub-agents are sent to their real size, as before.
|
|
69
|
+
*/
|
|
70
|
+
readonly sub?: Readonly<Record<string, number>>;
|
|
71
|
+
}
|
|
72
|
+
/** More sub-agent files than this in one firing: the map is left out (uncapped) rather than growing the line past `MAX_FIRING_BYTES`. */
|
|
73
|
+
export declare const MAX_FIRING_SUB_FILES = 200;
|
|
74
|
+
/** One firing as read back: where it starts and ends in the log, so a cursor can step over exactly it. */
|
|
75
|
+
export interface ReadFiring {
|
|
76
|
+
readonly firing: Firing;
|
|
77
|
+
readonly start: number;
|
|
78
|
+
readonly end: number;
|
|
79
|
+
}
|
|
80
|
+
export interface FiringRead {
|
|
81
|
+
/** Valid firings at or after the cursor, oldest first, up to the first line of a NEWER version. */
|
|
82
|
+
readonly firings: readonly ReadFiring[];
|
|
83
|
+
/**
|
|
84
|
+
* The offset after the last COMPLETE line this read judged (a malformed one included, a partial trailing one not, a newer-version one
|
|
85
|
+
* not). Advancing the cursor here steps over torn lines that no valid firing follows.
|
|
86
|
+
*/
|
|
87
|
+
readonly through: number;
|
|
88
|
+
/** True when a line of a newer `v` stopped the read. The worker leaves it unconsumed and exits (a newer build owns it). */
|
|
89
|
+
readonly blockedByNewer: boolean;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Append one firing. Returns false on any failure — a hook must never throw, and a missed firing self-heals: the NEXT firing's `eof`
|
|
93
|
+
* covers the same bytes.
|
|
94
|
+
*
|
|
95
|
+
* ⛔ ONE `O_APPEND` WRITE. Two hooks (Cursor double-fires) interleave whole lines, never halves, so no lock is needed. A line over
|
|
96
|
+
* `MAX_FIRING_BYTES` is refused rather than split.
|
|
97
|
+
*/
|
|
98
|
+
export declare function appendFiring(home: string, sessionId: string, firing: Firing): boolean;
|
|
99
|
+
/**
|
|
100
|
+
* What the hook calls: `appendFiring`, and if the line is refused while it carries a `sub` map, once more WITHOUT it (uncapped, the behaviour before
|
|
101
|
+
* the cap existed). The map is the one part of a line that grows with the session; the cap must never cost a firing. Never throws.
|
|
102
|
+
*/
|
|
103
|
+
export declare function recordFiring(home: string, sessionId: string, firing: Firing): boolean;
|
|
104
|
+
/**
|
|
105
|
+
* The firings from `from` (a cursor offset) on. Never throws: an unreadable log reads as empty.
|
|
106
|
+
*
|
|
107
|
+
* A malformed COMPLETE line is skipped and counted in `through`. A trailing line with no newline is a hook mid-append and is left alone.
|
|
108
|
+
* A line of a newer `v` stops the read and is not consumed (`blockedByNewer`).
|
|
109
|
+
*/
|
|
110
|
+
export declare function readFirings(home: string, sessionId: string, from?: number): FiringRead;
|
|
111
|
+
/**
|
|
112
|
+
* Every session that has a firing log: the file stems, oldest-modified first (so a session that has waited longest is served first). A
|
|
113
|
+
* stem is `sanitise(sid)`; it names the log and cursor files and nothing else, because the id to SEND is `Firing.sid`. Never throws.
|
|
114
|
+
*/
|
|
115
|
+
export declare function firingSessions(home: string): string[];
|
|
116
|
+
/** Forget a session's firing log and cursor (with its target record, on the existing 30-day schedule). Never throws. */
|
|
117
|
+
export declare function forgetFirings(home: string, sessionId: string): void;
|
|
118
|
+
export interface FiringCursor {
|
|
119
|
+
/** Byte offset of the first unconsumed firing. */
|
|
120
|
+
readonly offset: number;
|
|
121
|
+
/** Uncaught throws on the firing at `offset`. Reset whenever the cursor moves. */
|
|
122
|
+
readonly throws: number;
|
|
123
|
+
}
|
|
124
|
+
export declare const START_OF_LOG: FiringCursor;
|
|
125
|
+
/**
|
|
126
|
+
* The cursor, or the start of the log when it is missing or unreadable. Starting over is SAFE: replaying a consumed firing is a no-op
|
|
127
|
+
* (`firing.eof <= sentOffset`, trap 1), just slower.
|
|
128
|
+
*/
|
|
129
|
+
export declare function readCursor(home: string, sessionId: string): FiringCursor;
|
|
130
|
+
/**
|
|
131
|
+
* Persist the cursor atomically. ⛔ ONLY A HOLDER OF THE SESSION'S WRITER LOCK may call this, and only after a heartbeat that said the
|
|
132
|
+
* lock is still its own (§3.3: "every cursor write beats first"); a worker that lost its lock writes nothing. Returns false on failure.
|
|
133
|
+
*/
|
|
134
|
+
export declare function writeCursor(home: string, sessionId: string, cursor: FiringCursor): boolean;
|
|
135
|
+
/** Move the cursor to `offset`; the throw count belongs to the firing it left, so it resets. */
|
|
136
|
+
export declare const advanced: (cursor: FiringCursor, offset: number) => FiringCursor;
|
|
137
|
+
/** The firing at the cursor made the worker throw once more. */
|
|
138
|
+
export declare const threw: (cursor: FiringCursor) => FiringCursor;
|
|
139
|
+
/** Skip the firing at the cursor: it has thrown `MAX_FIRING_THROWS` times. */
|
|
140
|
+
export declare const shouldSkip: (cursor: FiringCursor) => boolean;
|
|
141
|
+
/**
|
|
142
|
+
* Stamp "this session's hook ran at `atMs`" for one repo key. HOOK-ONLY: the worker runs `deliver` with `byHook: false` and never calls
|
|
143
|
+
* this. A JSON object (`{at}`) rather than a bare number so P2's T7 can add `transcript` without a format change; the reader ignores
|
|
144
|
+
* unknown fields. Atomic replace. Never throws; false on failure (the commit then falls to the cold rung or goes unrecorded, as today
|
|
145
|
+
* before a first hook).
|
|
146
|
+
*/
|
|
147
|
+
export declare function stampLive(home: string, key: SessionKey, atMs: number): boolean;
|
|
148
|
+
/** The `.live` stamp at `path`, or null when absent, unreadable or not a positive number. Unknown fields are ignored. */
|
|
149
|
+
export declare function readLiveFile(path: string): number | null;
|
|
150
|
+
/**
|
|
151
|
+
* ⛔ THE ONE LIVENESS READER (§3.5). A session is live as of the LATER of its state file's `liveAt` (stamped by `deliver()` for a hook
|
|
152
|
+
* delivery) and its `.live` file (stamped by the hook itself, before the worker has delivered anything). All three readers go through
|
|
153
|
+
* this: `liveSessions` and `pendingModeFor` (`spool.ts`) and the two `status` classifiers (`state.ts`). Read straight from the files,
|
|
154
|
+
* never from the mtime: a `finalize` that rewrites a dead session's state must not make it live (VG review 2, D2).
|
|
155
|
+
*/
|
|
156
|
+
export declare function liveAtOf(stateLiveAt: number | null, livePath: string | null): number | null;
|
|
157
|
+
/**
|
|
158
|
+
* Which repo keys a hook stamps `.live` for (§3.5).
|
|
159
|
+
* - the turn-end repo, when allowed;
|
|
160
|
+
* - for a dialect that ISOLATES its content records (Claude Code, Codex): also every ledger key in the session's target record;
|
|
161
|
+
* - for one that does not (Cursor): the turn-end repo ONLY. Sticky targets stay in Cursor's record but are not delivered, and only
|
|
162
|
+
* delivered targets stamp today. Stamping them would keep a repo Cursor has left live for 30 minutes past every turn, a human's commit
|
|
163
|
+
* there would become `sole_live_session`, and the row is permanent.
|
|
164
|
+
*/
|
|
165
|
+
export declare function liveStampKeys(input: {
|
|
166
|
+
readonly isolates: boolean;
|
|
167
|
+
readonly turnEnd: string | null;
|
|
168
|
+
readonly recordLedgerKeys: readonly string[];
|
|
169
|
+
}): string[];
|
package/dist/firing.js
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE FIRING LOG AND THE HOOK'S LIVENESS STAMP — U3 of the detached uploader.
|
|
3
|
+
*
|
|
4
|
+
* A *firing* is one hook that ended a turn: today's hook body, deferred. The hook appends one line saying "this turn ended here, at this
|
|
5
|
+
* size, in this folder, with these repos" and returns; the upload worker (U5) replays the lines in order, each ALONE, with the values
|
|
6
|
+
* recorded at the moment the hook fired (plan §3.2-§3.4, D15). This module is the storage half and nothing in the hook calls it yet:
|
|
7
|
+
* it is inert until the hook is flipped (U6).
|
|
8
|
+
*
|
|
9
|
+
* ## What it owns
|
|
10
|
+
* - `<sid>.firings.jsonl` — append-only, one `O_APPEND` write per line, no lock (hooks never wait).
|
|
11
|
+
* - `<sid>.firings.cursor` — the worker's progress: a byte offset, and how many times the firing at that offset made the worker THROW.
|
|
12
|
+
* Written atomically, and only by a holder of the session's writer lock.
|
|
13
|
+
* - `<repo digest>/<sid>.live` — the hook-only liveness stamp, and the ONE function that reads liveness (`liveAtOf`).
|
|
14
|
+
*
|
|
15
|
+
* ## What a firing holds: paths and numbers only
|
|
16
|
+
* No credential, no transcript content. The worker re-confines `transcript` and `announced` with `admitsTranscript` before every read.
|
|
17
|
+
*
|
|
18
|
+
* ## ⛔ A LEAF MODULE. It imports `paths` and `atomic_write` and nothing else of this package's, because `spool.ts` and `state.ts` both
|
|
19
|
+
* read liveness through it and neither may import the other.
|
|
20
|
+
*/
|
|
21
|
+
import { appendFileSync, chmodSync, closeSync, fstatSync, mkdirSync, openSync, readdirSync, readFileSync, readSync, rmSync, statSync } from "node:fs";
|
|
22
|
+
import { dirname } from "node:path";
|
|
23
|
+
import { writeFileAtomic } from "./atomic_write.js";
|
|
24
|
+
import { sessionFileStem, sessionFiringsCursorPath, sessionFiringsPath, sessionLivePath, targetsDir } from "./paths.js";
|
|
25
|
+
/** The line format this build writes and reads. A reader sees a NEWER `v` as a stop sign, never as garbage (§3.14). */
|
|
26
|
+
export const FIRING_VERSION = 1;
|
|
27
|
+
/** A line longer than this is not written: a firing is ~300 bytes, and one `O_APPEND` write of a small buffer is what keeps hooks lock-free. */
|
|
28
|
+
export const MAX_FIRING_BYTES = 32 * 1024;
|
|
29
|
+
/** After this many uncaught throws on one firing, the worker skips it. A firing only sets an EOF, so the next one still covers its bytes. */
|
|
30
|
+
export const MAX_FIRING_THROWS = 3;
|
|
31
|
+
/** More sub-agent files than this in one firing: the map is left out (uncapped) rather than growing the line past `MAX_FIRING_BYTES`. */
|
|
32
|
+
export const MAX_FIRING_SUB_FILES = 200;
|
|
33
|
+
// --- the log -------------------------------------------------------------------------------------------------------------------
|
|
34
|
+
/**
|
|
35
|
+
* Append one firing. Returns false on any failure — a hook must never throw, and a missed firing self-heals: the NEXT firing's `eof`
|
|
36
|
+
* covers the same bytes.
|
|
37
|
+
*
|
|
38
|
+
* ⛔ ONE `O_APPEND` WRITE. Two hooks (Cursor double-fires) interleave whole lines, never halves, so no lock is needed. A line over
|
|
39
|
+
* `MAX_FIRING_BYTES` is refused rather than split.
|
|
40
|
+
*/
|
|
41
|
+
export function appendFiring(home, sessionId, firing) {
|
|
42
|
+
try {
|
|
43
|
+
const line = `${JSON.stringify(firing)}\n`;
|
|
44
|
+
if (Buffer.byteLength(line) > MAX_FIRING_BYTES)
|
|
45
|
+
return false;
|
|
46
|
+
const path = sessionFiringsPath(home, sessionId);
|
|
47
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
48
|
+
appendFileSync(path, line, { mode: 0o600 });
|
|
49
|
+
chmodSync(path, 0o600);
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* What the hook calls: `appendFiring`, and if the line is refused while it carries a `sub` map, once more WITHOUT it (uncapped, the behaviour before
|
|
58
|
+
* the cap existed). The map is the one part of a line that grows with the session; the cap must never cost a firing. Never throws.
|
|
59
|
+
*/
|
|
60
|
+
export function recordFiring(home, sessionId, firing) {
|
|
61
|
+
if (appendFiring(home, sessionId, firing))
|
|
62
|
+
return true;
|
|
63
|
+
if (firing.sub === undefined)
|
|
64
|
+
return false;
|
|
65
|
+
const { sub: _dropped, ...withoutSub } = firing;
|
|
66
|
+
return appendFiring(home, sessionId, withoutSub);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The firings from `from` (a cursor offset) on. Never throws: an unreadable log reads as empty.
|
|
70
|
+
*
|
|
71
|
+
* A malformed COMPLETE line is skipped and counted in `through`. A trailing line with no newline is a hook mid-append and is left alone.
|
|
72
|
+
* A line of a newer `v` stops the read and is not consumed (`blockedByNewer`).
|
|
73
|
+
*/
|
|
74
|
+
export function readFirings(home, sessionId, from = 0) {
|
|
75
|
+
const empty = { firings: [], through: from, blockedByNewer: false };
|
|
76
|
+
let fd = null;
|
|
77
|
+
try {
|
|
78
|
+
fd = openSync(sessionFiringsPath(home, sessionId), "r");
|
|
79
|
+
const size = fstatSync(fd).size;
|
|
80
|
+
if (!Number.isSafeInteger(from) || from < 0 || from >= size)
|
|
81
|
+
return { ...empty, through: Math.min(Math.max(from, 0), size) };
|
|
82
|
+
const buf = Buffer.alloc(size - from);
|
|
83
|
+
let got = 0;
|
|
84
|
+
while (got < buf.length) {
|
|
85
|
+
const n = readSync(fd, buf, got, buf.length - got, from + got);
|
|
86
|
+
if (n <= 0)
|
|
87
|
+
break;
|
|
88
|
+
got += n;
|
|
89
|
+
}
|
|
90
|
+
const firings = [];
|
|
91
|
+
let through = from;
|
|
92
|
+
let start = 0;
|
|
93
|
+
for (let nl = buf.indexOf(0x0a, 0); nl !== -1 && nl < got; nl = buf.indexOf(0x0a, start)) {
|
|
94
|
+
const parsed = parseFiring(buf.subarray(start, nl).toString("utf8"), sessionFileStem(sessionId));
|
|
95
|
+
const lineStart = from + start;
|
|
96
|
+
const lineEnd = from + nl + 1;
|
|
97
|
+
if (parsed === "newer")
|
|
98
|
+
return { firings, through, blockedByNewer: true };
|
|
99
|
+
if (parsed !== null)
|
|
100
|
+
firings.push({ firing: parsed, start: lineStart, end: lineEnd });
|
|
101
|
+
through = lineEnd;
|
|
102
|
+
start = nl + 1;
|
|
103
|
+
}
|
|
104
|
+
return { firings, through, blockedByNewer: false };
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return empty;
|
|
108
|
+
}
|
|
109
|
+
finally {
|
|
110
|
+
if (fd !== null) {
|
|
111
|
+
try {
|
|
112
|
+
closeSync(fd);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
/* nothing to do */
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/** A valid firing, `"newer"` for a line of a version this build does not know, or null for anything else. */
|
|
121
|
+
function parseFiring(text, stem) {
|
|
122
|
+
let raw;
|
|
123
|
+
try {
|
|
124
|
+
raw = JSON.parse(text);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw))
|
|
130
|
+
return null;
|
|
131
|
+
const o = raw;
|
|
132
|
+
if (typeof o.v !== "number" || !Number.isInteger(o.v) || o.v < 1)
|
|
133
|
+
return null;
|
|
134
|
+
if (o.v > FIRING_VERSION)
|
|
135
|
+
return "newer";
|
|
136
|
+
const str = (x) => typeof x === "string" && x !== "";
|
|
137
|
+
if (typeof o.at !== "number" || !Number.isFinite(o.at) || o.at <= 0)
|
|
138
|
+
return null;
|
|
139
|
+
if (typeof o.eof !== "number" || !Number.isSafeInteger(o.eof) || o.eof < 0)
|
|
140
|
+
return null;
|
|
141
|
+
// `sid` must be THIS log's session: a line whose id sanitises to another file name could otherwise be replayed under another session's cursor,
|
|
142
|
+
// lock and ledger. It is deliberately NOT required to be header-safe HERE: `sid` goes on the wire as `x-session-id` and delivery refuses an id the
|
|
143
|
+
// wire cannot carry (`sessionIdSafe`, post.ts) as a LOUD write-off (`never`, a gap `status` counts). Dropping such a line at the parse would be a
|
|
144
|
+
// new, differently-silent no-op (the 182 ruling); the id never reaches a header, a file name or a lock path except through `sanitise`.
|
|
145
|
+
if (!str(o.sid) || o.sid.length > 512 || sessionFileStem(o.sid) !== stem)
|
|
146
|
+
return null;
|
|
147
|
+
if (!str(o.event) || !str(o.transcript) || !str(o.agent) || !str(o.id) || typeof o.cwd !== "string")
|
|
148
|
+
return null;
|
|
149
|
+
const prior = {};
|
|
150
|
+
if (o.prior !== undefined) {
|
|
151
|
+
if (o.prior === null || typeof o.prior !== "object" || Array.isArray(o.prior))
|
|
152
|
+
return null;
|
|
153
|
+
for (const [key, value] of Object.entries(o.prior)) {
|
|
154
|
+
if (value === null)
|
|
155
|
+
prior[key] = null;
|
|
156
|
+
else if (typeof value === "number" && Number.isFinite(value) && value > 0)
|
|
157
|
+
prior[key] = value;
|
|
158
|
+
else
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
let keys;
|
|
163
|
+
if (o.keys !== undefined) {
|
|
164
|
+
const k = o.keys;
|
|
165
|
+
if (k === null || typeof k !== "object" || !str(k.worktree) || !str(k.consent))
|
|
166
|
+
return null;
|
|
167
|
+
keys = { worktree: k.worktree, consent: k.consent };
|
|
168
|
+
}
|
|
169
|
+
if (o.slug !== undefined && !str(o.slug))
|
|
170
|
+
return null;
|
|
171
|
+
if (o.announced !== undefined && !str(o.announced))
|
|
172
|
+
return null;
|
|
173
|
+
// A `sub` map that is not exactly {key: non-negative integer} is IGNORED (the firing is uncapped, today's behaviour), never a reason to drop the firing.
|
|
174
|
+
let sub;
|
|
175
|
+
if (o.sub !== null && typeof o.sub === "object" && !Array.isArray(o.sub)) {
|
|
176
|
+
const entries = Object.entries(o.sub);
|
|
177
|
+
if (entries.length <= MAX_FIRING_SUB_FILES && entries.every(([, size]) => typeof size === "number" && Number.isSafeInteger(size) && size >= 0)) {
|
|
178
|
+
sub = Object.fromEntries(entries);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
v: o.v,
|
|
183
|
+
at: o.at,
|
|
184
|
+
sid: o.sid,
|
|
185
|
+
event: o.event,
|
|
186
|
+
eof: o.eof,
|
|
187
|
+
transcript: o.transcript,
|
|
188
|
+
cwd: o.cwd,
|
|
189
|
+
...(keys === undefined ? {} : { keys }),
|
|
190
|
+
...(o.slug === undefined ? {} : { slug: o.slug }),
|
|
191
|
+
agent: o.agent,
|
|
192
|
+
id: o.id,
|
|
193
|
+
prior,
|
|
194
|
+
...(o.announced === undefined ? {} : { announced: o.announced }),
|
|
195
|
+
...(sub === undefined ? {} : { sub }),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
const LOG_SUFFIX = ".firings.jsonl";
|
|
199
|
+
/**
|
|
200
|
+
* Every session that has a firing log: the file stems, oldest-modified first (so a session that has waited longest is served first). A
|
|
201
|
+
* stem is `sanitise(sid)`; it names the log and cursor files and nothing else, because the id to SEND is `Firing.sid`. Never throws.
|
|
202
|
+
*/
|
|
203
|
+
export function firingSessions(home) {
|
|
204
|
+
try {
|
|
205
|
+
const dir = targetsDir(home);
|
|
206
|
+
return readdirSync(dir)
|
|
207
|
+
.filter((name) => name.endsWith(LOG_SUFFIX) && name.length > LOG_SUFFIX.length)
|
|
208
|
+
.map((name) => ({ stem: name.slice(0, -LOG_SUFFIX.length), at: modifiedAt(`${dir}/${name}`) }))
|
|
209
|
+
.sort((a, b) => a.at - b.at || (a.stem < b.stem ? -1 : 1))
|
|
210
|
+
.map((entry) => entry.stem);
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
return [];
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
function modifiedAt(path) {
|
|
217
|
+
try {
|
|
218
|
+
return statSync(path).mtimeMs;
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
return 0;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/** Forget a session's firing log and cursor (with its target record, on the existing 30-day schedule). Never throws. */
|
|
225
|
+
export function forgetFirings(home, sessionId) {
|
|
226
|
+
for (const path of [sessionFiringsPath(home, sessionId), sessionFiringsCursorPath(home, sessionId)]) {
|
|
227
|
+
try {
|
|
228
|
+
rmSync(path, { force: true });
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
/* an undeletable file is retried next schedule */
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
export const START_OF_LOG = { offset: 0, throws: 0 };
|
|
236
|
+
/**
|
|
237
|
+
* The cursor, or the start of the log when it is missing or unreadable. Starting over is SAFE: replaying a consumed firing is a no-op
|
|
238
|
+
* (`firing.eof <= sentOffset`, trap 1), just slower.
|
|
239
|
+
*/
|
|
240
|
+
export function readCursor(home, sessionId) {
|
|
241
|
+
try {
|
|
242
|
+
const raw = JSON.parse(readFileSync(sessionFiringsCursorPath(home, sessionId), "utf8"));
|
|
243
|
+
const offset = raw.offset;
|
|
244
|
+
const throws = raw.throws;
|
|
245
|
+
if (typeof offset !== "number" || !Number.isSafeInteger(offset) || offset < 0)
|
|
246
|
+
return START_OF_LOG;
|
|
247
|
+
return { offset, throws: typeof throws === "number" && Number.isSafeInteger(throws) && throws > 0 ? throws : 0 };
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
return START_OF_LOG;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Persist the cursor atomically. ⛔ ONLY A HOLDER OF THE SESSION'S WRITER LOCK may call this, and only after a heartbeat that said the
|
|
255
|
+
* lock is still its own (§3.3: "every cursor write beats first"); a worker that lost its lock writes nothing. Returns false on failure.
|
|
256
|
+
*/
|
|
257
|
+
export function writeCursor(home, sessionId, cursor) {
|
|
258
|
+
try {
|
|
259
|
+
const path = sessionFiringsCursorPath(home, sessionId);
|
|
260
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
261
|
+
writeFileAtomic(path, `${JSON.stringify({ v: 1, offset: cursor.offset, throws: cursor.throws })}\n`, 0o600);
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
/** Move the cursor to `offset`; the throw count belongs to the firing it left, so it resets. */
|
|
269
|
+
export const advanced = (cursor, offset) => ({ offset: Math.max(cursor.offset, offset), throws: 0 });
|
|
270
|
+
/** The firing at the cursor made the worker throw once more. */
|
|
271
|
+
export const threw = (cursor) => ({ offset: cursor.offset, throws: cursor.throws + 1 });
|
|
272
|
+
/** Skip the firing at the cursor: it has thrown `MAX_FIRING_THROWS` times. */
|
|
273
|
+
export const shouldSkip = (cursor) => cursor.throws >= MAX_FIRING_THROWS;
|
|
274
|
+
// --- liveness ------------------------------------------------------------------------------------------------------------------
|
|
275
|
+
/**
|
|
276
|
+
* Stamp "this session's hook ran at `atMs`" for one repo key. HOOK-ONLY: the worker runs `deliver` with `byHook: false` and never calls
|
|
277
|
+
* this. A JSON object (`{at}`) rather than a bare number so P2's T7 can add `transcript` without a format change; the reader ignores
|
|
278
|
+
* unknown fields. Atomic replace. Never throws; false on failure (the commit then falls to the cold rung or goes unrecorded, as today
|
|
279
|
+
* before a first hook).
|
|
280
|
+
*/
|
|
281
|
+
export function stampLive(home, key, atMs) {
|
|
282
|
+
const path = sessionLivePath(home, key);
|
|
283
|
+
if (path === null || !Number.isFinite(atMs) || atMs <= 0)
|
|
284
|
+
return false;
|
|
285
|
+
try {
|
|
286
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
287
|
+
writeFileAtomic(path, `${JSON.stringify({ at: atMs })}\n`, 0o600);
|
|
288
|
+
return true;
|
|
289
|
+
}
|
|
290
|
+
catch {
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
/** The `.live` stamp at `path`, or null when absent, unreadable or not a positive number. Unknown fields are ignored. */
|
|
295
|
+
export function readLiveFile(path) {
|
|
296
|
+
try {
|
|
297
|
+
const at = JSON.parse(readFileSync(path, "utf8")).at;
|
|
298
|
+
return typeof at === "number" && Number.isFinite(at) && at > 0 ? at : null;
|
|
299
|
+
}
|
|
300
|
+
catch {
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* ⛔ THE ONE LIVENESS READER (§3.5). A session is live as of the LATER of its state file's `liveAt` (stamped by `deliver()` for a hook
|
|
306
|
+
* delivery) and its `.live` file (stamped by the hook itself, before the worker has delivered anything). All three readers go through
|
|
307
|
+
* this: `liveSessions` and `pendingModeFor` (`spool.ts`) and the two `status` classifiers (`state.ts`). Read straight from the files,
|
|
308
|
+
* never from the mtime: a `finalize` that rewrites a dead session's state must not make it live (VG review 2, D2).
|
|
309
|
+
*/
|
|
310
|
+
export function liveAtOf(stateLiveAt, livePath) {
|
|
311
|
+
const stamped = livePath === null ? null : readLiveFile(livePath);
|
|
312
|
+
if (stamped === null)
|
|
313
|
+
return stateLiveAt;
|
|
314
|
+
return stateLiveAt === null ? stamped : Math.max(stateLiveAt, stamped);
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Which repo keys a hook stamps `.live` for (§3.5).
|
|
318
|
+
* - the turn-end repo, when allowed;
|
|
319
|
+
* - for a dialect that ISOLATES its content records (Claude Code, Codex): also every ledger key in the session's target record;
|
|
320
|
+
* - for one that does not (Cursor): the turn-end repo ONLY. Sticky targets stay in Cursor's record but are not delivered, and only
|
|
321
|
+
* delivered targets stamp today. Stamping them would keep a repo Cursor has left live for 30 minutes past every turn, a human's commit
|
|
322
|
+
* there would become `sole_live_session`, and the row is permanent.
|
|
323
|
+
*/
|
|
324
|
+
export function liveStampKeys(input) {
|
|
325
|
+
const keys = new Set();
|
|
326
|
+
if (input.turnEnd !== null && input.turnEnd !== "")
|
|
327
|
+
keys.add(input.turnEnd);
|
|
328
|
+
if (input.isolates)
|
|
329
|
+
for (const key of input.recordLedgerKeys)
|
|
330
|
+
if (key !== "")
|
|
331
|
+
keys.add(key);
|
|
332
|
+
return [...keys];
|
|
333
|
+
}
|
|
334
|
+
//# sourceMappingURL=firing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"firing.js","sourceRoot":"","sources":["../src/firing.ts"],"names":[],"mappings":"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"}
|