@vibe-commit/capture 0.10.0 → 0.11.1
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 +36 -1
- 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 +65 -0
- package/dist/commands/upload_worker.js +337 -0
- package/dist/commands/upload_worker.js.map +1 -0
- package/dist/copy/strings.d.ts +25 -8
- package/dist/copy/strings.js +43 -9
- 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 +337 -508
- 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/hooks/session_start.d.ts +18 -0
- package/dist/hooks/session_start.js +29 -0
- package/dist/hooks/session_start.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 +172 -0
- package/dist/replay.js +302 -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/hooks/entry.d.ts
CHANGED
|
@@ -36,11 +36,12 @@
|
|
|
36
36
|
import { type IngestCredential } from "../credential.js";
|
|
37
37
|
import { type Delivery, type FileKey } from "../post.js";
|
|
38
38
|
import { type RecordCutter } from "../policy.js";
|
|
39
|
-
import { type Isolation } from "../session_targets.js";
|
|
39
|
+
import { type Isolation, type Resolution, type Target } from "../session_targets.js";
|
|
40
40
|
import type { HookEventName } from "../agents/types.js";
|
|
41
|
+
import { type PriorLiveness } from "../firing.js";
|
|
41
42
|
import { type SessionKey } from "../paths.js";
|
|
42
43
|
export { confinementRoots } from "../project.js";
|
|
43
|
-
import { turnStart, type TurnStart } from "../pending_bound.js";
|
|
44
|
+
import { turnStart, type PendingMode, type TurnStart } from "../pending_bound.js";
|
|
44
45
|
/**
|
|
45
46
|
* Default wall-clock budget. `CR-028` sets the real per-event `timeout` in
|
|
46
47
|
* `claude-plugin/hooks/hooks.json`; this is the client-side backstop, which must
|
|
@@ -62,30 +63,6 @@ export declare const DEFAULT_HOOK_BUDGET_MS = 5000;
|
|
|
62
63
|
export declare function sendTimeoutMs(budgetMs: number, consumedMs: number): number;
|
|
63
64
|
/** A promise that resolves after `ms`. The one clock the settle path needs. */
|
|
64
65
|
export declare function wait(ms: number): Promise<void>;
|
|
65
|
-
/**
|
|
66
|
-
* The budget the SENDS may spend — the whole budget, less the stamp's share.
|
|
67
|
-
*
|
|
68
|
-
* A function, and exported, for the reason `sendTimeoutMs` is: the carve-out is
|
|
69
|
-
* the thing this task changes, and a wall-clock test cannot pin it. Every
|
|
70
|
-
* discriminator that distinguishes reserve from no-reserve through a real
|
|
71
|
-
* process turned out to slide with per-send overhead — a 149 ms window that
|
|
72
|
-
* moves by 25 ms for every 10 ms of connect-and-compress cost — so pinning it
|
|
73
|
-
* there would be tuning a constant to one machine. Here it is arithmetic.
|
|
74
|
-
*
|
|
75
|
-
* ⛔ `budgetMs` itself still governs the SETTLE and the WATCHDOG. Only the sends
|
|
76
|
-
* are carved.
|
|
77
|
-
*/
|
|
78
|
-
/**
|
|
79
|
-
* ⛔ **TAKES THE RESOLVED EVENT, NEVER THE RAW `hook_event_name` (`CR-195`).**
|
|
80
|
-
*
|
|
81
|
-
* It used to take a `string`, and that is precisely how Cursor's `sessionEnd`
|
|
82
|
-
* bought a full-budget send with no reserve: the comparison was against
|
|
83
|
-
* `"SessionEnd"`, so an unrecognised spelling was `false` rather than an error.
|
|
84
|
-
* `HookEventName | null` makes the resolution a thing the CALLER must already
|
|
85
|
-
* have done — `null` is the honest "none of the three", and an unresolved wire
|
|
86
|
-
* string is no longer expressible here at all.
|
|
87
|
-
*/
|
|
88
|
-
export declare function sendBudgetMs(budgetMs: number, event: HookEventName | null, sinceHookStartMs?: number): number;
|
|
89
66
|
export interface HookInput {
|
|
90
67
|
readonly sessionId: string;
|
|
91
68
|
readonly transcriptPath: string;
|
|
@@ -205,6 +182,139 @@ export declare function runHook(ctx: HookEnvironment): Promise<never>;
|
|
|
205
182
|
* `VIBECOMMIT_HOOK_TIMEOUT_MS` still wins outright, as it always has.
|
|
206
183
|
*/
|
|
207
184
|
export declare function hookBudgetMs(env: NodeJS.ProcessEnv, agentId: string): number;
|
|
185
|
+
/**
|
|
186
|
+
* U4 — HOW A DELIVERY IS PACED. A HOOK has a wall clock: one shared deadline, every request's timeout trimmed to what is left, a floor
|
|
187
|
+
* under the budget below which a further file is not started. The UPLOAD WORKER has none, and ⛔ must not be given an infinite one:
|
|
188
|
+
* `setTimeout(Infinity)` fires at 1 ms (trap 4), and any deadline at all brings back the abandonment this build removes (`withinBudget`
|
|
189
|
+
* trims the timeout near the end of a round, the client aborts, the server applies). So it passes a FIXED request timeout and no deadline.
|
|
190
|
+
*/
|
|
191
|
+
export type Pace = {
|
|
192
|
+
readonly kind: "hook";
|
|
193
|
+
readonly settleStart: number;
|
|
194
|
+
readonly sendBudget: number;
|
|
195
|
+
readonly consumedMs: number;
|
|
196
|
+
} | {
|
|
197
|
+
readonly kind: "unbounded";
|
|
198
|
+
readonly timeoutMs: number;
|
|
199
|
+
};
|
|
200
|
+
/**
|
|
201
|
+
* U4 — THE INPUTS A REPLAY FIXES AT THE MOMENT THE HOOK FIRED (plan §3.4). Absent for the inline hook, `finalize`, `backfill` and the drain,
|
|
202
|
+
* which are "now" and read the whole spool.
|
|
203
|
+
*/
|
|
204
|
+
export interface ReplayAsOf {
|
|
205
|
+
/** `firing.at`: the clock for promotion and pending mode, and the commit pin. */
|
|
206
|
+
readonly at: number;
|
|
207
|
+
/** `firing.prior`: liveness as the hook read it BEFORE it stamped (`firing.ts`), keyed by ledger key. */
|
|
208
|
+
readonly prior: PriorLiveness;
|
|
209
|
+
/** U6b (F5): `firing.sub`, the ceiling per sub-agent file. Left out when the firing has none or is a settled `SessionEnd` (real sizes). */
|
|
210
|
+
readonly subCap?: Readonly<Record<string, number>>;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* U4 — WHAT HAPPENED TO ONE TARGET, in the terms the worker's cursor needs. `Delivery.kind` alone cannot say: `nothing-to-send` is also a lost
|
|
214
|
+
* lock, a failed read and an unterminated record, and `ok` can be one delta of several. Derived from the delivery AND the ledger (plan §3.3).
|
|
215
|
+
*/
|
|
216
|
+
export type TargetReason =
|
|
217
|
+
/** `sentOffset >= eof`: this firing's main bytes are delivered (or were already). */
|
|
218
|
+
{
|
|
219
|
+
readonly kind: "caught-up";
|
|
220
|
+
}
|
|
221
|
+
/** A RECORDED state keeps the bytes owed; they ride this target's next delivery, and it never holds back another target. */
|
|
222
|
+
| {
|
|
223
|
+
readonly kind: "held";
|
|
224
|
+
readonly why: "capture-not-approved" | "stopped" | "unterminated" | "folder-forbidden";
|
|
225
|
+
}
|
|
226
|
+
/** The server refused these bytes for good; they were advanced past and counted. */
|
|
227
|
+
| {
|
|
228
|
+
readonly kind: "never";
|
|
229
|
+
}
|
|
230
|
+
/** A `later` with NO recorded state (a timeout, a 5xx): the bytes are still owed and the worker backs off. */
|
|
231
|
+
| {
|
|
232
|
+
readonly kind: "later";
|
|
233
|
+
}
|
|
234
|
+
/** Credential-level: the worker stops. */
|
|
235
|
+
| {
|
|
236
|
+
readonly kind: "fatal";
|
|
237
|
+
}
|
|
238
|
+
/** The caller's lock heartbeat failed: nothing was written, and nothing may be called done. */
|
|
239
|
+
| {
|
|
240
|
+
readonly kind: "lock-lost";
|
|
241
|
+
}
|
|
242
|
+
/** The span could not be read, or the ledger could not be written: retried, and counted toward the throw limit. */
|
|
243
|
+
| {
|
|
244
|
+
readonly kind: "not-progressed";
|
|
245
|
+
readonly why: "read-failed" | "unwritable";
|
|
246
|
+
}
|
|
247
|
+
/** Sent, but `sentOffset < eof` still: more owed, call again. */
|
|
248
|
+
| {
|
|
249
|
+
readonly kind: "more-owed";
|
|
250
|
+
};
|
|
251
|
+
/** What delivering one target produced. */
|
|
252
|
+
export interface TargetResult {
|
|
253
|
+
readonly target: Target;
|
|
254
|
+
readonly delivery: Delivery;
|
|
255
|
+
readonly subagents: DelegatedDeliveries;
|
|
256
|
+
/** The server refused THIS target for good (C3): nothing else about the firing changes. */
|
|
257
|
+
readonly stopped: boolean;
|
|
258
|
+
/** U4: see `TargetReason`. */
|
|
259
|
+
readonly reason: TargetReason;
|
|
260
|
+
}
|
|
261
|
+
/** Pure. The reason for one target from its delivery and where its main ledger stands against the `eof` it was asked to reach. */
|
|
262
|
+
export declare function targetReason(delivery: Delivery, sentOffset: number, eof: number, stopped: boolean): TargetReason;
|
|
263
|
+
/**
|
|
264
|
+
* U4 — DELIVER ONE FIRING TO ITS RESOLVED TARGETS. The loop `hookBody` always ran inline, callable by the upload worker with a replay's
|
|
265
|
+
* as-of inputs and no wall clock. Behaviour for the inline hook is unchanged: `pace.kind === "hook"` and no `asOf` is the old code.
|
|
266
|
+
*
|
|
267
|
+
* Order and fairness are as before: least-recently-sent target first, so a 20 MB backlog on one cannot starve the rest.
|
|
268
|
+
*/
|
|
269
|
+
export declare function deliverFiring(opts: {
|
|
270
|
+
ctx: HookEnvironment;
|
|
271
|
+
input: HookInput;
|
|
272
|
+
url: string;
|
|
273
|
+
credential: IngestCredential;
|
|
274
|
+
resolution: Resolution;
|
|
275
|
+
eof: number;
|
|
276
|
+
event: HookEventName | null;
|
|
277
|
+
isSessionEnd: boolean;
|
|
278
|
+
pace: Pace;
|
|
279
|
+
asOf?: ReplayAsOf;
|
|
280
|
+
/**
|
|
281
|
+
* U5: the caller's session-lock heartbeat. Called before a target is touched and before every request and write beneath it (`beforeRequest`,
|
|
282
|
+
* U1). `false` means the lock is no longer ours: nothing more is written, and that target reports `lock-lost`. The inline hook passes none.
|
|
283
|
+
*/
|
|
284
|
+
heartbeat?: () => boolean;
|
|
285
|
+
}): Promise<TargetResult[]>;
|
|
286
|
+
/**
|
|
287
|
+
* U4 — `pendingModeFor`, answered from `firing.prior` (plan §3.4, P2 point 4). `prior` keeps `pendingModeFor`'s three outcomes:
|
|
288
|
+
* - a NUMBER is the stamp the hook read before it stamped: live within the window of `firing.at` is `first-turn`, older is `stale-own`;
|
|
289
|
+
* - an explicit `null` is a state file with NO `liveAt` (what `finalize` and `backfill` leave): `stale-own`;
|
|
290
|
+
* - an ABSENT key is no state file at hook time: `first-turn`. A key the hook did not record at all (a second work tree, a parent-folder
|
|
291
|
+
* repo on its first firing) falls back to `pendingModeFor` judged at `firing.at`, never `Date.now()`, which is safe because stamps arrive in
|
|
292
|
+
* firing order.
|
|
293
|
+
* A plain number-or-omitted map would read a resumed session as `first-turn` and keep a lingering-id line.
|
|
294
|
+
*/
|
|
295
|
+
export declare function modeFromPrior(home: string, key: SessionKey, asOf: ReplayAsOf): PendingMode;
|
|
296
|
+
/**
|
|
297
|
+
* U6b (F2d): THE WORKER'S PACE BETWEEN REQUESTS (D9): sleep `max(250 ms, the request's own elapsed)`, capped at 30 s. It lives here, beside the delivery loops
|
|
298
|
+
* that must honour it, because ONE worker step can put several requests on the wire (the main delta, then one per sub-agent file) and pacing only
|
|
299
|
+
* after the whole step let a fast server take thirteen of them back to back.
|
|
300
|
+
*/
|
|
301
|
+
export declare const UNBOUNDED_PACE_FLOOR_MS = 250;
|
|
302
|
+
export declare const UNBOUNDED_PACE_CAP_MS = 30000;
|
|
303
|
+
export declare function unboundedPaceMs(elapsedMs: number): number;
|
|
304
|
+
/** A session with no firing of its own for this long is IDLE: another session's firing may finish its owed bytes (R19 · D22). */
|
|
305
|
+
export declare const DRAIN_IDLE_MS: number;
|
|
306
|
+
/** Session records one firing looks at. Examined ones move to the back of the line (their mtime moves), so none starves. */
|
|
307
|
+
export declare const DRAIN_EXAMINE_MAX = 40;
|
|
308
|
+
/** A record whose transcript is gone and that owes nothing is forgotten after this long. */
|
|
309
|
+
export declare const DRAIN_FORGET_MS: number;
|
|
310
|
+
/**
|
|
311
|
+
* An owed target whose transcript is GONE (Claude Code deletes transcripts ~30 days on) can never be sent: its hold becomes the
|
|
312
|
+
* honest gap now (R19 · D22), and the hold is cleared. Whatever was already delivered is untouched.
|
|
313
|
+
*/
|
|
314
|
+
export declare function writeOffOwed(home: string, record: {
|
|
315
|
+
readonly sessionId: string;
|
|
316
|
+
readonly targets: readonly Target[];
|
|
317
|
+
}, nowMs: number): void;
|
|
208
318
|
/** One sub-agent transcript that passed confinement and the key bound. */
|
|
209
319
|
interface SubagentFile {
|
|
210
320
|
readonly path: string;
|
|
@@ -276,8 +386,23 @@ export declare function deliverSubagents(opts: {
|
|
|
276
386
|
* bytes this target was never allowed to send. Absent for a repo target: it sends to each file's end, as it always did.
|
|
277
387
|
*/
|
|
278
388
|
sizeCap?: Readonly<Record<string, number>>;
|
|
389
|
+
/**
|
|
390
|
+
* U6b (F5): a REPLAYED firing's ceiling per sub-agent file, `Firing.sub` (what existed when the hook ran). Combined with `sizeCap` by `min`; a file
|
|
391
|
+
* with no key sends nothing. Absent for a hook, the drain, `finalize` and `backfill` (they read the real size), and for a settled `SessionEnd`.
|
|
392
|
+
*/
|
|
393
|
+
firingCap?: Readonly<Record<string, number>>;
|
|
279
394
|
/** A-T4: what the delegated streams may contain (R17 / R6). Default: a repo target's own `redactionRoots`. */
|
|
280
395
|
isolation?: Isolation;
|
|
396
|
+
/** U4: the upload worker's stamp (`DeliveryContext.liveAtMs`); requires `byHook: false`. */
|
|
397
|
+
liveAtMs?: number;
|
|
398
|
+
/**
|
|
399
|
+
* U4 — **THE WORKER'S PACE**: a fixed request timeout and NO budget. When set, `startedAt`/`sendBudget` are ignored: no remainder arithmetic,
|
|
400
|
+
* no floor under which a further file is skipped, no `deadlineAtMs`, and no cap of `MAX_SUBAGENT_FILES` per firing (it loops until none is
|
|
401
|
+
* owed). Never `Infinity`: `setTimeout(Infinity)` fires at 1 ms (trap 4).
|
|
402
|
+
*/
|
|
403
|
+
unboundedTimeoutMs?: number;
|
|
404
|
+
/** U6b (F2d): in an unbounded step, wait this long before the FIRST request (the main delta just went); each later one waits `unboundedPaceMs` of the previous request. */
|
|
405
|
+
pauseFirstMs?: number;
|
|
281
406
|
/**
|
|
282
407
|
* C25: the caller's session-lock heartbeat (`finalize`, `backfill`). Called before each delegated file and before every request
|
|
283
408
|
* inside it; `false` means the lock is gone, and the loop stops with nothing more written. A hook passes none.
|
|
@@ -302,6 +427,26 @@ export interface DelegatedDeliveries {
|
|
|
302
427
|
*/
|
|
303
428
|
readonly deliveries: readonly Delivery[];
|
|
304
429
|
}
|
|
430
|
+
/**
|
|
431
|
+
* Every `agent-*.jsonl` under this session's `subagents/`, oldest first.
|
|
432
|
+
*
|
|
433
|
+
* **`agent-*.jsonl`, never `agent-*`.** The directory carries an
|
|
434
|
+
* `agent-<id>.meta.json` beside every transcript — 636 of each on this machine,
|
|
435
|
+
* exactly 1:1 — so the looser glob would double every upload and send metadata
|
|
436
|
+
* as transcript content.
|
|
437
|
+
*
|
|
438
|
+
* **Every path is confined.** `isInsideAny(transcriptRoots(...))` gates each
|
|
439
|
+
* entry before it is opened, exactly as the main transcript is gated: this adds
|
|
440
|
+
* a new SET of readers to the surface `/cso` finding 1 rated HIGH, and a
|
|
441
|
+
* symlinked entry in `subagents/` must not walk out of the transcript roots.
|
|
442
|
+
*
|
|
443
|
+
* Oldest first, because it cannot starve. Ordering by pending bytes would let a
|
|
444
|
+
* small file lose every round forever; by mtime, every file reaches the front.
|
|
445
|
+
*
|
|
446
|
+
* A missing or unreadable directory is simply "no sub-agents" — silent, per the
|
|
447
|
+
* hook contract.
|
|
448
|
+
*/
|
|
449
|
+
export declare function findSubagentFiles(ctx: HookEnvironment, transcriptPath: string): SubagentFile[];
|
|
305
450
|
/** Size in bytes, or null if it cannot be read. */
|
|
306
451
|
export declare function fileSize(path: string): number | null;
|
|
307
452
|
/**
|