@sweny-ai/core 0.1.178 → 0.1.179
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/dist/agent-env.d.ts +5 -3
- package/dist/agent-env.js +12 -7
- package/dist/cli/final-output.js +4 -1
- package/dist/cli/main.js +3 -2
- package/dist/cli/resume.d.ts +10 -1
- package/dist/cli/resume.js +76 -7
- package/dist/executor.d.ts +1 -1
- package/dist/executor.js +77 -4
- package/dist/harness/acp.js +2 -0
- package/dist/harness/claude-code.d.ts +18 -0
- package/dist/harness/claude-code.js +51 -6
- package/dist/harness/codex.js +2 -0
- package/dist/harness/pi.js +2 -0
- package/dist/harness/sandbox-wrapper.js +8 -8
- package/dist/journal.d.ts +225 -70
- package/dist/journal.js +713 -202
- package/dist/skills/github.js +39 -0
- package/dist/types.d.ts +6 -0
- package/package.json +1 -1
package/dist/journal.d.ts
CHANGED
|
@@ -2,23 +2,42 @@
|
|
|
2
2
|
* Run journal (#363): an append-only log of one workflow run, enough to resume
|
|
3
3
|
* a killed or failed run without re-running the nodes that already finished.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* a per-run key). Each append is written and fsync'd before the executor moves
|
|
9
|
-
* on, so a record on disk is a fact.
|
|
5
|
+
* Location: the journal never lives in the workspace. Each run gets a private
|
|
6
|
+
* directory in the user's sweny state dir (`$SWENY_STATE_DIR`, else
|
|
7
|
+
* `$XDG_STATE_HOME/sweny`, else `~/.local/state/sweny`):
|
|
10
8
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* the
|
|
9
|
+
* runs/<workspace-hash>/<run-id>/ (dirs 0700, files 0600, opened O_NOFOLLOW)
|
|
10
|
+
* key 32 random bytes, the run's HMAC key
|
|
11
|
+
* journal.ndjson one JSON record per line, fsync'd per append
|
|
12
|
+
* head.json the record being appended, written (atomically) first
|
|
13
|
+
* meta.json run id, workspace hash, creation time; authenticated, drives retention
|
|
14
|
+
* lock the process that owns the run (pid + start time)
|
|
15
|
+
*
|
|
16
|
+
* Both agent sandboxes deny reading AND writing the whole `runs/` tree, so an
|
|
17
|
+
* agent confined to the workspace cannot read the key, edit or cut the
|
|
18
|
+
* journal, delete the lock or fake a run. The workspace keeps only user-facing
|
|
19
|
+
* artifacts (`.sweny/runs/<run-id>/output.md` and the run history record).
|
|
20
|
+
* Journals an older sweny wrote in the workspace are refused for resume.
|
|
21
|
+
*
|
|
22
|
+
* Integrity: every record carries the format version (`v`), a sequence number
|
|
23
|
+
* (`seq`) and `h`, an HMAC-SHA256 under the run key over the run id, the seq,
|
|
24
|
+
* the previous record's `h` and the record body. The chain means records
|
|
25
|
+
* cannot be reordered, duplicated, or spliced in from another run. The head
|
|
26
|
+
* file holds the full record being appended, written and renamed into place
|
|
27
|
+
* BEFORE the journal append: a crash between the two leaves a journal exactly
|
|
28
|
+
* one record short, and resume restores that record from the head. A journal
|
|
29
|
+
* shorter than that, or one that disagrees with the head, is refused, so the
|
|
30
|
+
* end of the journal cannot be cut away even by something that can write the
|
|
31
|
+
* state dir but not read the key.
|
|
32
|
+
*
|
|
33
|
+
* All of this holds only for an agent under an enforced sandbox. An
|
|
34
|
+
* unsandboxed agent running as the same OS user can read the key and rewrite
|
|
35
|
+
* the state dir. Each such visit is journaled (`agent:unsandboxed`), and a
|
|
36
|
+
* resume of that run warns and marks the receipt `journal_unsandboxed`.
|
|
37
|
+
* Resume also checks the record sequence (see {@link buildResumePlan}) and
|
|
38
|
+
* every replayed route against the workflow's real edges. A state dir whose
|
|
39
|
+
* real path is inside the workspace is refused. A resume on another machine
|
|
40
|
+
* needs the same state dir.
|
|
22
41
|
*
|
|
23
42
|
* Records, in run order:
|
|
24
43
|
* run:start workflow / instruction / input / tool hashes, harness id, input (redacted)
|
|
@@ -30,17 +49,31 @@
|
|
|
30
49
|
* output:applied the write returned (key + the ids it produced)
|
|
31
50
|
* node:end final result of the visit and the write-stage counters
|
|
32
51
|
* route the edge taken after the visit (null = the run ended), and the rung that chose it
|
|
33
|
-
* usage spend of one agent attempt (cumulative;
|
|
52
|
+
* usage spend of one agent attempt (cumulative; one per model turn that raised it, written
|
|
53
|
+
* synchronously, and the final figure when the attempt returns)
|
|
54
|
+
* agent:unsandboxed the visit's agent ran without an enforced sandbox
|
|
34
55
|
* run:end success | failed | crashed
|
|
35
56
|
*
|
|
57
|
+
* Failure: an append that cannot be written (head or journal) is fatal. The
|
|
58
|
+
* executor stops the run before the next model call or side effect and fails
|
|
59
|
+
* the node with the reason, so a resume never under-counts spend or repeats a
|
|
60
|
+
* write the journal did not see.
|
|
61
|
+
*
|
|
36
62
|
* Spend: a resume seeds the run budget with every attempt's journaled usage,
|
|
37
|
-
* so a crash never resets the whole-run ceiling.
|
|
63
|
+
* from authenticated records only, so a crash never resets the whole-run ceiling.
|
|
64
|
+
*
|
|
65
|
+
* Recovery: an unterminated tail that is not complete JSON (power loss
|
|
66
|
+
* mid-append) is truncated. An unterminated tail that IS complete JSON is
|
|
67
|
+
* never assumed torn: it must authenticate as the next record, or the journal
|
|
68
|
+
* is refused. Corruption in the middle of the file is refused.
|
|
38
69
|
*
|
|
39
|
-
*
|
|
40
|
-
* the
|
|
70
|
+
* Lock: taken with O_CREAT|O_EXCL before anything is read or repaired, and
|
|
71
|
+
* held for the whole run or resume. A lock whose process is gone (pid not
|
|
72
|
+
* alive, or alive with a different start time) is taken over under a
|
|
73
|
+
* second exclusive lock, re-checked first, so two resumes cannot both win.
|
|
41
74
|
*
|
|
42
|
-
* Privacy: the journal
|
|
43
|
-
*
|
|
75
|
+
* Privacy: the journal holds what the run needs to continue (node data, eval
|
|
76
|
+
* verdicts, safe-output intents). It never holds environment values:
|
|
44
77
|
* secret-looking keys, known token shapes, and the values of secret env vars
|
|
45
78
|
* and skill credentials are replaced with `[redacted]` before anything is
|
|
46
79
|
* written. Tool call inputs and outputs are not journaled.
|
|
@@ -56,11 +89,15 @@ import type { Logger, NodeResult, NodeUsage, RouteRung, Skill, Workflow } from "
|
|
|
56
89
|
import type { SafeOutputIntent, WriteStageState } from "./safe-outputs.js";
|
|
57
90
|
import { type Spend } from "./budget.js";
|
|
58
91
|
import type { DeciderCounters } from "./decider.js";
|
|
59
|
-
/**
|
|
60
|
-
|
|
61
|
-
|
|
92
|
+
/**
|
|
93
|
+
* v3: each record's HMAC chains the run id and the previous record's HMAC.
|
|
94
|
+
* (v2 authenticated records one by one; v1 used a public checksum. Both are refused.)
|
|
95
|
+
*/
|
|
96
|
+
export declare const JOURNAL_SCHEMA_VERSION = 3;
|
|
97
|
+
/** Where journals lived before v3: in the workspace. Never read for resume, only recognized to refuse. */
|
|
98
|
+
export declare const LEGACY_JOURNAL_DIR: string;
|
|
62
99
|
export declare const JOURNAL_FILE = "journal.ndjson";
|
|
63
|
-
/** Journals kept
|
|
100
|
+
/** Journals kept per workspace; the oldest beyond this are deleted when a new run starts. */
|
|
64
101
|
export declare const JOURNAL_KEEP = 20;
|
|
65
102
|
export declare const REDACTED = "[redacted]";
|
|
66
103
|
/** sha256 (hex) of `value` as canonical JSON: keys sorted, undefined dropped. Same as run history's workflow hash. */
|
|
@@ -88,16 +125,69 @@ export declare function instructionHash(sources: Record<string, {
|
|
|
88
125
|
export declare function toolsHash(skills: Map<string, Skill>, swenyVersion?: string, harnessId?: string, secrets?: string[]): string;
|
|
89
126
|
/** sweny's per-user state dir: `$SWENY_STATE_DIR`, else `$XDG_STATE_HOME/sweny`, else `~/.local/state/sweny`. */
|
|
90
127
|
export declare function swenyStateDir(env?: Record<string, string | undefined>): string;
|
|
91
|
-
/** Where run
|
|
92
|
-
export declare function
|
|
93
|
-
/** The
|
|
94
|
-
export declare function
|
|
128
|
+
/** Where every run journal (and its key, head, meta and lock) lives. Agents can neither read nor write it. */
|
|
129
|
+
export declare function runStateRoot(env?: Record<string, string | undefined>): string;
|
|
130
|
+
/** The workspace's directory name under {@link runStateRoot}: its real path hashed, never stored. */
|
|
131
|
+
export declare function workspaceScope(cwd: string): string;
|
|
132
|
+
/** One run's private directory: `<state>/runs/<workspace-hash>/<run-id>/`. */
|
|
133
|
+
export declare function journalDir(cwd: string, runId: string, root?: string): string;
|
|
134
|
+
/** The run's key file (inside its private directory). */
|
|
135
|
+
export declare function runKeyFile(cwd: string, runId: string, root?: string): string;
|
|
136
|
+
/** Thrown when the journal's state dir resolves into the workspace, where an agent could edit it. */
|
|
137
|
+
export declare class JournalLocationError extends Error {
|
|
138
|
+
constructor(dir: string, cwd: string);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Refuse a state dir that resolves into the workspace. Real paths are compared
|
|
142
|
+
* (the dir must exist), so a symlink anywhere in either chain counts as where
|
|
143
|
+
* it points. Throws {@link JournalLocationError}.
|
|
144
|
+
*/
|
|
145
|
+
export declare function assertStateOutsideWorkspace(dir: string, cwd: string): void;
|
|
95
146
|
/** Thrown when a journal's key is missing or unreadable: its records cannot be checked. */
|
|
96
147
|
export declare class JournalKeyError extends Error {
|
|
97
148
|
constructor(file: string, why: string);
|
|
98
149
|
}
|
|
99
150
|
/** Read a run key. Throws {@link JournalKeyError}. */
|
|
100
151
|
export declare function loadRunKey(file: string): Buffer;
|
|
152
|
+
/** Thrown when the journal is shorter than, or disagrees with, the record its head file holds. */
|
|
153
|
+
export declare class JournalRollbackError extends Error {
|
|
154
|
+
constructor(message: string);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* The record being appended (seq 0 and no record before the first append).
|
|
158
|
+
* Written atomically BEFORE the record goes to the journal, so the journal is
|
|
159
|
+
* either at the head or exactly one record short of it (a crash in between),
|
|
160
|
+
* never anything else.
|
|
161
|
+
*/
|
|
162
|
+
export interface JournalHead {
|
|
163
|
+
seq: number;
|
|
164
|
+
record: JournalRecord | null;
|
|
165
|
+
}
|
|
166
|
+
/** Read and authenticate a run's head file. Throws {@link JournalRollbackError}. */
|
|
167
|
+
export declare function loadJournalHead(dir: string, key: Buffer, runId: string): JournalHead;
|
|
168
|
+
/** Thrown when another live process holds the run. */
|
|
169
|
+
export declare class JournalLockedError extends Error {
|
|
170
|
+
readonly pid: number;
|
|
171
|
+
constructor(pid: number);
|
|
172
|
+
}
|
|
173
|
+
/** Start time (ms since epoch) of a process, where the OS exposes it cheaply (Linux); else undefined. */
|
|
174
|
+
export declare function processStartTime(pid: number): number | undefined;
|
|
175
|
+
/** Test seam for the lock: called after a stale lock was read, before it is taken over. */
|
|
176
|
+
export interface LockHooks {
|
|
177
|
+
afterStaleRead?(): void;
|
|
178
|
+
}
|
|
179
|
+
/** An acquired run lock. */
|
|
180
|
+
export interface RunLock {
|
|
181
|
+
readonly file: string;
|
|
182
|
+
release(): void;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Take a run's lock: O_CREAT|O_EXCL in the run's private dir. A lock whose
|
|
186
|
+
* process is gone is taken over under a second exclusive lock, and only if it
|
|
187
|
+
* still holds the exact bytes judged stale, so two processes racing for a
|
|
188
|
+
* stale lock cannot both win. Throws {@link JournalLockedError}.
|
|
189
|
+
*/
|
|
190
|
+
export declare function acquireRunLock(dir: string, hooks?: LockHooks): RunLock;
|
|
101
191
|
/** True for keys like `GITHUB_TOKEN`, `apiKey`, `client-secret`, `Authorization`. */
|
|
102
192
|
export declare function isSecretKey(key: string): boolean;
|
|
103
193
|
/** Values of secret-named env vars / config entries, longest first (8+ chars, so short words never match). */
|
|
@@ -185,15 +275,20 @@ export declare class JournalMismatchError extends Error {
|
|
|
185
275
|
readonly what: string[];
|
|
186
276
|
constructor(what: string[]);
|
|
187
277
|
}
|
|
188
|
-
/** Thrown when
|
|
189
|
-
export declare class
|
|
190
|
-
constructor(
|
|
278
|
+
/** Thrown when the journal cannot be written. Fatal: the executor stops the run before anything else happens. */
|
|
279
|
+
export declare class JournalWriteError extends Error {
|
|
280
|
+
constructor(message: string);
|
|
191
281
|
}
|
|
192
282
|
export interface JournalRead {
|
|
193
283
|
file: string;
|
|
284
|
+
/** Authenticated, chained records only: everything a resume replays or seeds spend from. */
|
|
194
285
|
records: JournalRecord[];
|
|
195
|
-
/** Bytes dropped from a torn
|
|
286
|
+
/** Bytes dropped from a torn tail (0 when the file was clean). */
|
|
196
287
|
truncatedBytes: number;
|
|
288
|
+
/** The last record is whole and authenticated but lost its line end (repair restores it). */
|
|
289
|
+
missingNewline?: boolean;
|
|
290
|
+
/** The last record was missing from the journal (a crash after the head write) and was restored from the head. */
|
|
291
|
+
restored?: boolean;
|
|
197
292
|
/**
|
|
198
293
|
* Set when an invalid record sits before valid ones (not a torn tail), or when
|
|
199
294
|
* a whole record fails authentication (tampering, never a torn write). Not repairable.
|
|
@@ -202,18 +297,31 @@ export interface JournalRead {
|
|
|
202
297
|
/** The line whose record failed authentication, when that is why the journal is refused. */
|
|
203
298
|
forgedAtLine?: number;
|
|
204
299
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
* last valid record. Records are authenticated with the run's key (`key`, else
|
|
208
|
-
* loaded from the state dir for the run the path names). Throws
|
|
209
|
-
* {@link JournalVersionError} for another format and {@link JournalKeyError}
|
|
210
|
-
* when the key is missing.
|
|
211
|
-
*/
|
|
212
|
-
export declare function readJournal(file: string, opts?: {
|
|
300
|
+
export interface ReadJournalOptions {
|
|
301
|
+
/** Truncate a torn tail on disk, restore a lost final line end, and re-append a record restored from the head. */
|
|
213
302
|
repair?: boolean;
|
|
303
|
+
/** The run key; default: the `key` file next to the journal. */
|
|
214
304
|
key?: Buffer;
|
|
215
|
-
|
|
216
|
-
|
|
305
|
+
/** The run id the records are chained to; default: the journal's directory name. */
|
|
306
|
+
runId?: string;
|
|
307
|
+
/**
|
|
308
|
+
* The run's head; default: the authenticated `head.json` next to the journal.
|
|
309
|
+
* `false` skips the length check: only for inspecting raw bytes, never on a
|
|
310
|
+
* path that resumes.
|
|
311
|
+
*/
|
|
312
|
+
head?: JournalHead | false;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Read a journal from a run's private directory. Records are authenticated
|
|
316
|
+
* and chained with the run key, then checked against the head: the journal
|
|
317
|
+
* must end at the head's record, or one short of it (then that record is
|
|
318
|
+
* restored from the head). Only an unterminated final segment that is not
|
|
319
|
+
* complete JSON is a torn append; with `repair` it is cut. Throws
|
|
320
|
+
* {@link JournalVersionError} for another format, {@link JournalKeyError}
|
|
321
|
+
* when the key is missing and {@link JournalRollbackError} when the journal
|
|
322
|
+
* was cut back or does not match its head.
|
|
323
|
+
*/
|
|
324
|
+
export declare function readJournal(file: string, opts?: ReadJournalOptions): JournalRead;
|
|
217
325
|
export type VisitAction = "replay" | "write-stage" | "rerun";
|
|
218
326
|
export interface PlannedVisit {
|
|
219
327
|
node: string;
|
|
@@ -250,6 +358,12 @@ export interface ResumePlan {
|
|
|
250
358
|
receipts: Map<string, OutputRecord>;
|
|
251
359
|
/** Spend every earlier attempt journaled: the run budget starts here on resume. */
|
|
252
360
|
priorSpend: Spend;
|
|
361
|
+
/**
|
|
362
|
+
* Nodes whose agent ran without an enforced sandbox in an earlier attempt.
|
|
363
|
+
* Such an agent could read the run key, so the journal's spend and write
|
|
364
|
+
* records are only as trustworthy as that agent.
|
|
365
|
+
*/
|
|
366
|
+
unsandboxed: string[];
|
|
253
367
|
/** The decider's mode at run start and its counters after the last journaled route (#357). */
|
|
254
368
|
decider?: JournalDeciderState;
|
|
255
369
|
}
|
|
@@ -321,8 +435,9 @@ export type JournalReplay = {
|
|
|
321
435
|
kind: "checkpoint";
|
|
322
436
|
} & JournalCheckpoint);
|
|
323
437
|
/**
|
|
324
|
-
* The executor's view of a journal (`ExecuteOptions.journal`).
|
|
325
|
-
*
|
|
438
|
+
* The executor's view of a journal (`ExecuteOptions.journal`). A hook that
|
|
439
|
+
* cannot write its record throws {@link JournalWriteError}: the executor
|
|
440
|
+
* stops the run there. At resume, `begin` refuses a changed run.
|
|
326
441
|
*/
|
|
327
442
|
export interface ExecutionJournal {
|
|
328
443
|
begin(info: JournalBeginInfo): void;
|
|
@@ -337,23 +452,30 @@ export interface ExecutionJournal {
|
|
|
337
452
|
route(from: string, to: string | null, rung?: RouteRung, decider?: DeciderCounters): void;
|
|
338
453
|
/**
|
|
339
454
|
* Spend of one agent attempt, cumulative for the attempt: `final` when the
|
|
340
|
-
* attempt returned, otherwise a live report (
|
|
455
|
+
* attempt returned, otherwise a live report (written at once when it raises the spend).
|
|
341
456
|
*/
|
|
342
457
|
usage?(node: string, iteration: number, attempt: number, usage: NodeUsage, final: boolean): void;
|
|
458
|
+
/** The visit's agent ran without an enforced sandbox: recorded so a resume can say so. */
|
|
459
|
+
uncontained?(node: string, iteration: number): void;
|
|
343
460
|
/** Spend earlier attempts of this run already journaled (resume); the run budget starts from it. */
|
|
344
461
|
priorSpend?(): Spend | undefined;
|
|
462
|
+
/** Receipt `degraded` entries this journal adds to the run (e.g. `journal_unsandboxed`). */
|
|
463
|
+
degraded?(): string[];
|
|
345
464
|
/** The decider state earlier attempts of this run journaled (resume); undefined otherwise. */
|
|
346
465
|
deciderResume?(): JournalDeciderState | undefined;
|
|
347
466
|
}
|
|
348
|
-
/**
|
|
349
|
-
export declare const USAGE_JOURNAL_INTERVAL_MS = 2000;
|
|
350
|
-
/** Test seam: called before each append; a throw simulates the process dying there. */
|
|
467
|
+
/** Test seams: a throw simulates the process dying at that point of an append. */
|
|
351
468
|
export interface JournalFaults {
|
|
469
|
+
/** Before anything of the record reaches the disk. */
|
|
352
470
|
beforeAppend?(record: JournalRecord): void;
|
|
471
|
+
/** After the head holds the record, before the journal append. */
|
|
472
|
+
afterHeadUpdate?(record: JournalRecord): void;
|
|
473
|
+
/** Replaces the journal write (a throw is an I/O failure, not a process death). */
|
|
474
|
+
writeJournal?(fd: number, line: string): void;
|
|
353
475
|
}
|
|
354
476
|
export interface RunJournalOptions {
|
|
355
477
|
runId: string;
|
|
356
|
-
/**
|
|
478
|
+
/** The workspace the run belongs to (its real path, hashed, names the run's state dir). Default: process.cwd(). */
|
|
357
479
|
cwd?: string;
|
|
358
480
|
/** Workflow file the run came from, so `resume` can load it again. */
|
|
359
481
|
workflowFile?: string;
|
|
@@ -362,57 +484,80 @@ export interface RunJournalOptions {
|
|
|
362
484
|
env?: Record<string, string | undefined>;
|
|
363
485
|
logger?: Logger;
|
|
364
486
|
faults?: JournalFaults;
|
|
365
|
-
/** Journals kept
|
|
487
|
+
/** Journals kept per workspace (default {@link JOURNAL_KEEP}). */
|
|
366
488
|
keep?: number;
|
|
367
|
-
/** Where run
|
|
368
|
-
|
|
489
|
+
/** Where run journals live (default {@link runStateRoot}). Never inside the workspace. */
|
|
490
|
+
stateRoot?: string;
|
|
369
491
|
}
|
|
370
492
|
export interface ResumeJournalOptions extends RunJournalOptions {
|
|
371
493
|
read: JournalRead;
|
|
372
494
|
plan: ResumePlan;
|
|
495
|
+
/** The run's lock, taken before the journal was read ({@link lockRun}). */
|
|
496
|
+
lock: RunLock;
|
|
373
497
|
/** Resume even though the workflow, instructions, input or tools changed. */
|
|
374
498
|
force?: boolean;
|
|
375
499
|
/** Re-send writes whose outcome cannot be confirmed on the provider. */
|
|
376
500
|
allowRepeatWrites?: boolean;
|
|
377
501
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
502
|
+
/** A run's journal file. */
|
|
503
|
+
export declare function journalFile(cwd: string, runId: string, root?: string): string;
|
|
504
|
+
/**
|
|
505
|
+
* Runs of this workspace with a journal, oldest first. Only the state dir is
|
|
506
|
+
* read, and only runs whose metadata authenticates count: nothing in the
|
|
507
|
+
* workspace can add, hide or reorder a run.
|
|
508
|
+
*/
|
|
509
|
+
export declare function listJournalRuns(cwd?: string, root?: string): string[];
|
|
510
|
+
/** Exact run id, or a unique prefix, among this workspace's journaled runs. */
|
|
511
|
+
export declare function findJournalRun(ref: string, cwd?: string, root?: string): string | undefined;
|
|
512
|
+
/** A journal an older sweny left in the workspace for this run id (or unique prefix), if any. Never read. */
|
|
513
|
+
export declare function findLegacyJournal(ref: string, cwd?: string): string | undefined;
|
|
514
|
+
/**
|
|
515
|
+
* Delete this workspace's oldest journals beyond `keep`, never `except`, by
|
|
516
|
+
* their authenticated creation time. Runs whose metadata does not
|
|
517
|
+
* authenticate are never deleted. Never throws.
|
|
518
|
+
*/
|
|
519
|
+
export declare function pruneJournals(cwd: string, keep?: number, except?: string, root?: string): number;
|
|
520
|
+
/** Take a run's lock before reading its journal (resume). Throws {@link JournalLockedError}. */
|
|
521
|
+
export declare function lockRun(cwd: string, runId: string, root?: string, hooks?: LockHooks): RunLock;
|
|
385
522
|
export declare class RunJournal implements ExecutionJournal {
|
|
386
523
|
readonly runId: string;
|
|
387
524
|
readonly dir: string;
|
|
388
525
|
readonly file: string;
|
|
389
526
|
private readonly cwd;
|
|
527
|
+
private readonly root;
|
|
390
528
|
private readonly opts;
|
|
391
529
|
private readonly logger;
|
|
392
530
|
private readonly resume?;
|
|
393
531
|
private fd;
|
|
394
532
|
private seq;
|
|
395
|
-
|
|
533
|
+
/** MAC of the last record written: the next record chains to it. */
|
|
534
|
+
private prev;
|
|
535
|
+
/** Set once an append failed: every later append throws it again. */
|
|
536
|
+
private failure;
|
|
537
|
+
/** Set when a fault seam simulated the process dying: nothing more is written. */
|
|
538
|
+
private killed;
|
|
396
539
|
private began;
|
|
397
540
|
private closed;
|
|
398
541
|
private secrets;
|
|
399
542
|
private consumed;
|
|
400
|
-
/** Per-run HMAC key; lives only here and in the
|
|
543
|
+
/** Per-run HMAC key; lives only here and in the run's private state dir. */
|
|
401
544
|
private key;
|
|
402
|
-
private
|
|
545
|
+
private lock;
|
|
403
546
|
/** Last live usage record per attempt: time and spend, for throttling. */
|
|
404
547
|
private lastUsage;
|
|
548
|
+
/** Visits already recorded as run without an enforced sandbox. */
|
|
549
|
+
private uncontainedVisits;
|
|
405
550
|
private constructor();
|
|
406
551
|
/** A journal for a new run. Nothing touches disk until the executor calls `begin`. */
|
|
407
552
|
static create(opts: RunJournalOptions): RunJournal;
|
|
408
|
-
/** A journal that continues `opts.read` (already repaired) under the resume plan. */
|
|
553
|
+
/** A journal that continues `opts.read` (already repaired, under `opts.lock`) under the resume plan. */
|
|
409
554
|
static openForResume(opts: ResumeJournalOptions): RunJournal;
|
|
410
|
-
/** False once an append failed
|
|
555
|
+
/** False once an append failed (or the process was simulated dead): the run cannot be resumed past that point. */
|
|
411
556
|
get active(): boolean;
|
|
412
|
-
private takeLock;
|
|
413
|
-
/** The run dir is private (0700); no link on the path from cwd is followed (safe-file.ts). */
|
|
414
|
-
private makeDir;
|
|
415
557
|
private releaseLock;
|
|
558
|
+
/** Mark the journal failed and throw: the run must stop here. */
|
|
559
|
+
private fail;
|
|
560
|
+
private simulatedDeath;
|
|
416
561
|
private append;
|
|
417
562
|
private closeFd;
|
|
418
563
|
private clean;
|
|
@@ -423,9 +568,19 @@ export declare class RunJournal implements ExecutionJournal {
|
|
|
423
568
|
checkpoint(node: string, iteration: number, cp: JournalCheckpoint): void;
|
|
424
569
|
nodeEnd(node: string, iteration: number, result: NodeResult, writeState: WriteStageState): void;
|
|
425
570
|
route(from: string, to: string | null, rung?: RouteRung, decider?: DeciderCounters): void;
|
|
571
|
+
/**
|
|
572
|
+
* Journal one usage report, synchronously, whenever it raises the attempt's
|
|
573
|
+
* spend (reports are cumulative; the largest figure seen per unit counts).
|
|
574
|
+
* Nothing is held back: harnesses report once per model turn (Claude Code
|
|
575
|
+
* per assistant message, ACP per usage update), so every turn's spend is on
|
|
576
|
+
* disk before the next turn runs, and a process killed at any point has lost
|
|
577
|
+
* at most the turn it was in.
|
|
578
|
+
*/
|
|
426
579
|
usage(node: string, iteration: number, attempt: number, usage: NodeUsage, final: boolean): void;
|
|
580
|
+
uncontained(node: string, iteration: number): void;
|
|
581
|
+
degraded(): string[];
|
|
427
582
|
priorSpend(): Spend | undefined;
|
|
428
|
-
/** Close the run: `run:end
|
|
583
|
+
/** Close the run: `run:end` (when the journal still works), then release the lock. Never throws. */
|
|
429
584
|
end(status: "success" | "failed" | "crashed"): void;
|
|
430
585
|
wrapWrites(node: string, iteration: number, skills: Map<string, Skill>): Map<string, Skill>;
|
|
431
586
|
private wrapTool;
|