orcareplay 0.1.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 (109) hide show
  1. package/dist/api.d.ts +127 -0
  2. package/dist/api.d.ts.map +1 -0
  3. package/dist/api.js +159 -0
  4. package/dist/api.js.map +1 -0
  5. package/dist/args.d.ts +24 -0
  6. package/dist/args.d.ts.map +1 -0
  7. package/dist/args.js +124 -0
  8. package/dist/args.js.map +1 -0
  9. package/dist/cli.d.ts +3 -0
  10. package/dist/cli.d.ts.map +1 -0
  11. package/dist/cli.js +4 -0
  12. package/dist/cli.js.map +1 -0
  13. package/dist/commands/compare.d.ts +27 -0
  14. package/dist/commands/compare.d.ts.map +1 -0
  15. package/dist/commands/compare.js +182 -0
  16. package/dist/commands/compare.js.map +1 -0
  17. package/dist/commands/doctor.d.ts +24 -0
  18. package/dist/commands/doctor.d.ts.map +1 -0
  19. package/dist/commands/doctor.js +358 -0
  20. package/dist/commands/doctor.js.map +1 -0
  21. package/dist/commands/gc.d.ts +24 -0
  22. package/dist/commands/gc.d.ts.map +1 -0
  23. package/dist/commands/gc.js +326 -0
  24. package/dist/commands/gc.js.map +1 -0
  25. package/dist/commands/inspect.d.ts +24 -0
  26. package/dist/commands/inspect.d.ts.map +1 -0
  27. package/dist/commands/inspect.js +262 -0
  28. package/dist/commands/inspect.js.map +1 -0
  29. package/dist/commands/record.d.ts +17 -0
  30. package/dist/commands/record.d.ts.map +1 -0
  31. package/dist/commands/record.js +497 -0
  32. package/dist/commands/record.js.map +1 -0
  33. package/dist/commands/replay.d.ts +43 -0
  34. package/dist/commands/replay.d.ts.map +1 -0
  35. package/dist/commands/replay.js +800 -0
  36. package/dist/commands/replay.js.map +1 -0
  37. package/dist/commands/scrub.d.ts +34 -0
  38. package/dist/commands/scrub.d.ts.map +1 -0
  39. package/dist/commands/scrub.js +452 -0
  40. package/dist/commands/scrub.js.map +1 -0
  41. package/dist/commands/setup.d.ts +23 -0
  42. package/dist/commands/setup.d.ts.map +1 -0
  43. package/dist/commands/setup.js +177 -0
  44. package/dist/commands/setup.js.map +1 -0
  45. package/dist/config.d.ts +80 -0
  46. package/dist/config.d.ts.map +1 -0
  47. package/dist/config.js +117 -0
  48. package/dist/config.js.map +1 -0
  49. package/dist/exchange-events.d.ts +72 -0
  50. package/dist/exchange-events.d.ts.map +1 -0
  51. package/dist/exchange-events.js +170 -0
  52. package/dist/exchange-events.js.map +1 -0
  53. package/dist/flags.d.ts +10 -0
  54. package/dist/flags.d.ts.map +1 -0
  55. package/dist/flags.js +83 -0
  56. package/dist/flags.js.map +1 -0
  57. package/dist/fs-events.d.ts +19 -0
  58. package/dist/fs-events.d.ts.map +1 -0
  59. package/dist/fs-events.js +53 -0
  60. package/dist/fs-events.js.map +1 -0
  61. package/dist/index.d.ts +18 -0
  62. package/dist/index.d.ts.map +1 -0
  63. package/dist/index.js +18 -0
  64. package/dist/index.js.map +1 -0
  65. package/dist/main.d.ts +2 -0
  66. package/dist/main.d.ts.map +1 -0
  67. package/dist/main.js +282 -0
  68. package/dist/main.js.map +1 -0
  69. package/dist/mcp-server.d.ts +44 -0
  70. package/dist/mcp-server.d.ts.map +1 -0
  71. package/dist/mcp-server.js +245 -0
  72. package/dist/mcp-server.js.map +1 -0
  73. package/dist/mcp.d.ts +111 -0
  74. package/dist/mcp.d.ts.map +1 -0
  75. package/dist/mcp.js +221 -0
  76. package/dist/mcp.js.map +1 -0
  77. package/dist/out.d.ts +69 -0
  78. package/dist/out.d.ts.map +1 -0
  79. package/dist/out.js +138 -0
  80. package/dist/out.js.map +1 -0
  81. package/dist/rasterize.d.ts +62 -0
  82. package/dist/rasterize.d.ts.map +1 -0
  83. package/dist/rasterize.js +154 -0
  84. package/dist/rasterize.js.map +1 -0
  85. package/dist/serial.d.ts +19 -0
  86. package/dist/serial.d.ts.map +1 -0
  87. package/dist/serial.js +34 -0
  88. package/dist/serial.js.map +1 -0
  89. package/dist/share-card.d.ts +73 -0
  90. package/dist/share-card.d.ts.map +1 -0
  91. package/dist/share-card.js +413 -0
  92. package/dist/share-card.js.map +1 -0
  93. package/dist/snapshot.d.ts +30 -0
  94. package/dist/snapshot.d.ts.map +1 -0
  95. package/dist/snapshot.js +43 -0
  96. package/dist/snapshot.js.map +1 -0
  97. package/dist/tls-capture.d.ts +57 -0
  98. package/dist/tls-capture.d.ts.map +1 -0
  99. package/dist/tls-capture.js +198 -0
  100. package/dist/tls-capture.js.map +1 -0
  101. package/dist/upstream.d.ts +20 -0
  102. package/dist/upstream.d.ts.map +1 -0
  103. package/dist/upstream.js +31 -0
  104. package/dist/upstream.js.map +1 -0
  105. package/dist/version.d.ts +2 -0
  106. package/dist/version.d.ts.map +1 -0
  107. package/dist/version.js +2 -0
  108. package/dist/version.js.map +1 -0
  109. package/package.json +49 -0
@@ -0,0 +1,326 @@
1
+ import { createReadStream } from 'node:fs';
2
+ import { readFile, readdir, rm, rmdir, stat, unlink } from 'node:fs/promises';
3
+ import { basename, dirname, join, resolve } from 'node:path';
4
+ import { tmpdir } from 'node:os';
5
+ import { createInterface } from 'node:readline';
6
+ import { listRuns, runsDir } from '@orcareplay/core';
7
+ const UNITS = {
8
+ s: 1_000,
9
+ m: 60_000,
10
+ h: 3_600_000,
11
+ d: 86_400_000,
12
+ w: 604_800_000,
13
+ };
14
+ const HEX64 = /^[0-9a-f]{64}$/;
15
+ /** Any digest mentioned anywhere in an event line counts as a reference. See {@link inspectRun}. */
16
+ const DIGEST_IN_LINE = /[0-9a-f]{64}/g;
17
+ /** Milliseconds for `7d`, `24h`, `30m`; null for anything it will not guess at. */
18
+ export function parseDuration(text) {
19
+ const match = /^(\d+(?:\.\d+)?)([smhdw])$/.exec(text.trim().toLowerCase());
20
+ if (!match)
21
+ return null;
22
+ const unit = UNITS[match[2]];
23
+ if (unit === undefined)
24
+ return null;
25
+ return Number(match[1]) * unit;
26
+ }
27
+ /**
28
+ * `orca gc` — reclaim the space a trace store grows into.
29
+ *
30
+ * A run is append-only and content-addressed, so nothing inside one grows unboundedly. What grows
31
+ * is the number of runs, and the blobs left behind when a run died between writing a payload and
32
+ * writing the event that referenced it. Removing runs is therefore opt-in: with no retention flag
33
+ * this only sweeps that debris and reports what it would take if asked.
34
+ */
35
+ export async function gcCommand(args, out, cwd = process.cwd()) {
36
+ const dryRun = args.bool('dry-run');
37
+ const olderThanMs = readOlderThan(args);
38
+ const keep = readKeep(args);
39
+ const runs = await listRuns(cwd);
40
+ if (runs.length === 0) {
41
+ out.plain(`no runs in ${runsDir(cwd)} — nothing to reclaim`);
42
+ return { runsRemoved: 0, worktreesRemoved: 0, blobsRemoved: 0, bytesReclaimed: 0 };
43
+ }
44
+ const facts = [];
45
+ for (const run of runs)
46
+ facts.push(await inspectRun(run.runId, run.dir, run.createdAt));
47
+ // `facts` is newest first, so --keep is simply the first n entries.
48
+ const cutoff = olderThanMs === undefined ? undefined : Date.now() - olderThanMs;
49
+ const doomed = new Set();
50
+ facts.forEach((run, index) => {
51
+ if (cutoff === undefined && keep === undefined)
52
+ return;
53
+ // Both flags are constraints, not alternatives: --keep is a floor under --older-than.
54
+ if (cutoff !== undefined && run.createdMs >= cutoff)
55
+ return;
56
+ if (keep !== undefined && index < keep)
57
+ return;
58
+ doomed.add(run.runId);
59
+ });
60
+ // Forking is the point of this tool, and a fork's provenance is a pointer to a run that must
61
+ // still be there. Rescuing a parent can expose a grandparent, so this runs to a fixed point.
62
+ const rescued = new Map();
63
+ for (let changed = true; changed;) {
64
+ changed = false;
65
+ for (const run of facts) {
66
+ if (doomed.has(run.runId))
67
+ continue;
68
+ const parent = run.parentRun;
69
+ if (parent !== undefined && doomed.delete(parent)) {
70
+ rescued.set(parent, run.runId);
71
+ changed = true;
72
+ }
73
+ }
74
+ }
75
+ if (dryRun)
76
+ out.plain('dry run — nothing will be removed');
77
+ out.table(['RUN', 'CREATED', 'SIZE', 'ACTION'], facts.map((run) => [
78
+ run.runId,
79
+ run.createdAt,
80
+ formatBytes(run.bytes),
81
+ doomed.has(run.runId)
82
+ ? 'remove'
83
+ : rescued.has(run.runId)
84
+ ? `keep — parent of ${rescued.get(run.runId)}`
85
+ : 'keep',
86
+ ]));
87
+ for (const [parent, child] of rescued) {
88
+ out.warn('gc.parent_kept', { run: parent, child, why: 'a surviving run forked from it' });
89
+ }
90
+ let runsRemoved = 0;
91
+ let worktreesRemoved = 0;
92
+ let blobsRemoved = 0;
93
+ let bytesReclaimed = 0;
94
+ for (const run of facts) {
95
+ if (!doomed.has(run.runId))
96
+ continue;
97
+ runsRemoved += 1;
98
+ bytesReclaimed += run.bytes;
99
+ if (!dryRun)
100
+ await rm(run.dir, { recursive: true, force: true });
101
+ // The fork's scratch worktree goes with its run. It is deliberately *not* removed when the
102
+ // fork finishes — it holds what the model actually did, so deleting it then would destroy the
103
+ // thing you forked in order to look at — but nothing reclaimed it either, so a single
104
+ // `orca compare --models a,b,c,d` left four full copies of the workspace behind forever.
105
+ if (run.worktree !== undefined) {
106
+ worktreesRemoved += 1;
107
+ bytesReclaimed += await runDirBytes(run.worktree);
108
+ if (!dryRun)
109
+ await rm(run.worktree, { recursive: true, force: true });
110
+ }
111
+ }
112
+ for (const run of facts) {
113
+ if (doomed.has(run.runId))
114
+ continue;
115
+ if (run.skippedReason !== undefined) {
116
+ out.warn('gc.blobs_skipped', { run: run.runId, why: run.skippedReason });
117
+ continue;
118
+ }
119
+ if (run.orphans.length === 0)
120
+ continue;
121
+ const shards = new Set();
122
+ for (const orphan of run.orphans) {
123
+ blobsRemoved += 1;
124
+ bytesReclaimed += orphan.bytes;
125
+ shards.add(orphan.shard);
126
+ if (!dryRun)
127
+ await unlink(orphan.path).catch(() => undefined);
128
+ }
129
+ out.info('gc.orphans', {
130
+ run: run.runId,
131
+ blobs: run.orphans.length,
132
+ bytes: run.orphans.reduce((sum, o) => sum + o.bytes, 0),
133
+ });
134
+ // An empty shard directory is just clutter; a non-empty one refuses and that is fine.
135
+ if (!dryRun)
136
+ for (const shard of shards)
137
+ await rmdir(shard).catch(() => undefined);
138
+ }
139
+ if (olderThanMs === undefined && keep === undefined) {
140
+ out.plain('');
141
+ out.plain(' no run was removed — say which ones you mean:');
142
+ out.plain(' orca gc --older-than 7d # by age');
143
+ out.plain(' orca gc --keep 10 # by count');
144
+ out.plain(' add --dry-run to see the plan first');
145
+ }
146
+ out.phase('gc', {
147
+ runs: runsRemoved,
148
+ worktrees: worktreesRemoved || undefined,
149
+ blobs: blobsRemoved,
150
+ bytes: bytesReclaimed,
151
+ dry_run: dryRun || undefined,
152
+ });
153
+ return { runsRemoved, worktreesRemoved, blobsRemoved, bytesReclaimed };
154
+ }
155
+ /**
156
+ * Does this path look like a directory orca made for a fork to run in?
157
+ *
158
+ * Deliberately narrow, and deliberately only half the test — the caller also requires the run to
159
+ * carry a `fork_point`. Directly inside the OS temp directory, with the prefix `replayFork` uses.
160
+ * Anything else is left alone, including a fork whose worktree someone moved somewhere they care
161
+ * about: an unreclaimed directory costs disk, and the alternative costs somebody's work.
162
+ *
163
+ * So `orca replay --worktree` is out of reach here, because an exact replay's trace has no fork
164
+ * point. That is the right way round: you asked for a scratch copy to look at, orca printed where
165
+ * it put it, and a garbage collector that deletes the thing you asked to keep is worse than one
166
+ * that leaves a directory behind. The pre-replay safety snapshot is not gc's problem either —
167
+ * replay owns that one and removes it once your files are back.
168
+ */
169
+ function isScratchWorktree(dir) {
170
+ return resolve(dirname(dir)) === resolve(tmpdir()) && basename(dir).startsWith('orca-');
171
+ }
172
+ function readOlderThan(args) {
173
+ if (!args.has('older-than'))
174
+ return undefined;
175
+ const raw = args.str('older-than');
176
+ const ms = raw === undefined ? null : parseDuration(raw);
177
+ if (ms === null) {
178
+ throw new Error(`--older-than needs a duration like 7d, 24h or 30m${raw === undefined ? '' : ` — got ${JSON.stringify(raw)}`}\n` +
179
+ 'units are s, m, h, d, w; for example: orca gc --older-than 7d --dry-run');
180
+ }
181
+ return ms;
182
+ }
183
+ function readKeep(args) {
184
+ if (!args.has('keep'))
185
+ return undefined;
186
+ const n = args.num('keep');
187
+ if (n === undefined || !Number.isInteger(n) || n < 0) {
188
+ throw new Error('--keep needs a whole number of runs to keep, like --keep 10\n' +
189
+ 'it keeps the n most recent runs and removes the rest');
190
+ }
191
+ return n;
192
+ }
193
+ async function inspectRun(runId, dir, createdAt) {
194
+ const facts = {
195
+ runId,
196
+ dir,
197
+ createdAt,
198
+ createdMs: Date.parse(createdAt),
199
+ bytes: await runDirBytes(dir),
200
+ orphans: [],
201
+ };
202
+ if (Number.isNaN(facts.createdMs))
203
+ facts.createdMs = 0;
204
+ let sealed = false;
205
+ try {
206
+ const manifest = JSON.parse(await readFile(join(dir, 'manifest.json'), 'utf8'));
207
+ if (typeof manifest.parent_run === 'string')
208
+ facts.parentRun = manifest.parent_run;
209
+ // Only a fork's cwd is ever ours to delete. A plain recording runs in the user's project, and
210
+ // `gc` removing that would be the worst bug this tool could have — so the fork check is the
211
+ // load-bearing half of the guard, and the path check is the belt.
212
+ //
213
+ // The fork check is `fork_point`, not `parent_run`. It used to be the latter, back when the
214
+ // only run with a parent was a fork; an exact replay now writes a trace of its own findings
215
+ // that also names a parent, and it ran in the user's *own* directory. A project that happens
216
+ // to live at $TMPDIR/orca-something would have satisfied the belt on its own, and the run
217
+ // holding the evidence of what happened there is the one gc would have used to delete it.
218
+ if (typeof manifest.fork_point === 'number' &&
219
+ typeof manifest.cwd === 'string' &&
220
+ isScratchWorktree(manifest.cwd)) {
221
+ facts.worktree = manifest.cwd;
222
+ }
223
+ sealed = typeof manifest.ended_at === 'string';
224
+ }
225
+ catch {
226
+ // A run whose manifest is unreadable is still a directory with a size; it just cannot be
227
+ // proven finished, so the guards below leave its blobs alone.
228
+ }
229
+ const eventsPath = join(dir, 'events.jsonl');
230
+ const referenced = new Set();
231
+ let readable = false;
232
+ try {
233
+ const lines = createInterface({
234
+ input: createReadStream(eventsPath),
235
+ crlfDelay: Number.POSITIVE_INFINITY,
236
+ });
237
+ try {
238
+ for await (const line of lines) {
239
+ // Match digests in the raw line rather than walking parsed payloads: a blob referenced
240
+ // from somewhere this build does not know about must still count as referenced.
241
+ for (const digest of line.match(DIGEST_IN_LINE) ?? [])
242
+ referenced.add(digest);
243
+ if (facts.parentRun === undefined && line.includes('"fork"')) {
244
+ facts.parentRun = forkParentOf(line);
245
+ }
246
+ }
247
+ }
248
+ finally {
249
+ lines.close();
250
+ }
251
+ readable = true;
252
+ }
253
+ catch {
254
+ readable = false;
255
+ }
256
+ if (!sealed) {
257
+ facts.skippedReason = 'run is not sealed — it may still be recording';
258
+ return facts;
259
+ }
260
+ if (!readable) {
261
+ facts.skippedReason = 'events.jsonl is unreadable, so every blob would look unreferenced';
262
+ return facts;
263
+ }
264
+ facts.orphans = await orphanBlobs(join(dir, 'blobs'), referenced);
265
+ return facts;
266
+ }
267
+ /** The parent a forked run names in its own `fork` event — where replay records provenance. */
268
+ function forkParentOf(line) {
269
+ try {
270
+ const event = JSON.parse(line);
271
+ if (event.type !== 'fork')
272
+ return undefined;
273
+ const parent = event.attrs?.parent_run;
274
+ return typeof parent === 'string' ? parent : undefined;
275
+ }
276
+ catch {
277
+ return undefined;
278
+ }
279
+ }
280
+ async function orphanBlobs(blobsRoot, referenced) {
281
+ const orphans = [];
282
+ for (const shard of await readdir(blobsRoot, { withFileTypes: true }).catch(() => [])) {
283
+ if (!shard.isDirectory())
284
+ continue;
285
+ const shardDir = join(blobsRoot, shard.name);
286
+ for (const name of await readdir(shardDir).catch(() => [])) {
287
+ // Only files named as a digest: a half-written `.tmp` belongs to whoever is writing it.
288
+ if (!HEX64.test(name) || referenced.has(name))
289
+ continue;
290
+ const path = join(shardDir, name);
291
+ const info = await stat(path).catch(() => null);
292
+ if (!info?.isFile())
293
+ continue;
294
+ orphans.push({ path, shard: shardDir, bytes: info.size });
295
+ }
296
+ }
297
+ return orphans;
298
+ }
299
+ /** Every byte of file content under a run directory. Shared with `orca doctor`. */
300
+ export async function runDirBytes(dir) {
301
+ let total = 0;
302
+ for (const entry of await readdir(dir, { withFileTypes: true }).catch(() => [])) {
303
+ const path = join(dir, entry.name);
304
+ if (entry.isDirectory()) {
305
+ total += await runDirBytes(path);
306
+ continue;
307
+ }
308
+ const info = await stat(path).catch(() => null);
309
+ if (info?.isFile())
310
+ total += info.size;
311
+ }
312
+ return total;
313
+ }
314
+ export function formatBytes(bytes) {
315
+ if (bytes < 1024)
316
+ return `${bytes} B`;
317
+ const units = ['KB', 'MB', 'GB', 'TB'];
318
+ let value = bytes / 1024;
319
+ let unit = 0;
320
+ while (value >= 1024 && unit < units.length - 1) {
321
+ value /= 1024;
322
+ unit += 1;
323
+ }
324
+ return `${value.toFixed(1)} ${units[unit]}`;
325
+ }
326
+ //# sourceMappingURL=gc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gc.js","sourceRoot":"","sources":["../../src/commands/gc.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import type { RunGraph } from '@orcareplay/core';
2
+ import type { Output } from '../out.js';
3
+ import type { ParsedArgs } from '../args.js';
4
+ /** `orca list` — what runs are here, newest first. */
5
+ export declare function listCommand(args: ParsedArgs, out: Output, cwd?: string): Promise<void>;
6
+ /** `orca show` — the timeline, in the terminal. */
7
+ export declare function showCommand(args: ParsedArgs, out: Output, cwd?: string): Promise<void>;
8
+ /** `orca checkpoints` — where can I fork from? */
9
+ export declare function checkpointsCommand(args: ParsedArgs, out: Output, cwd?: string): Promise<void>;
10
+ /**
11
+ * `orca graph` — what caused what.
12
+ *
13
+ * The timeline says what happened in what order; this says what produced what. Every row names
14
+ * both ends and, crucially, whether the trace vouches for the edge or a rule here worked it out —
15
+ * a reader who cannot tell the two apart has been handed a guess dressed as a fact.
16
+ */
17
+ export declare function graphCommand(args: ParsedArgs, out: Output, cwd?: string): Promise<void>;
18
+ /** The whole graph, or just the chain that produced `to`. */
19
+ export declare function narrow(graph: RunGraph, to: number | undefined): RunGraph;
20
+ /** `orca export` — the single self-contained file that makes a trace shareable. */
21
+ export declare function exportCommand(args: ParsedArgs, out: Output, cwd?: string): Promise<void>;
22
+ /** `orca ui` — the same viewer, served locally. */
23
+ export declare function uiCommand(args: ParsedArgs, out: Output, cwd?: string): Promise<void>;
24
+ //# sourceMappingURL=inspect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inspect.d.ts","sourceRoot":"","sources":["../../src/commands/inspect.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AASjD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAK7C,sDAAsD;AACtD,wBAAsB,WAAW,CAC/B,IAAI,EAAE,UAAU,EAChB,GAAG,EAAE,MAAM,EACX,GAAG,SAAgB,GAClB,OAAO,CAAC,IAAI,CAAC,CAyBf;AAED,mDAAmD;AACnD,wBAAsB,WAAW,CAC/B,IAAI,EAAE,UAAU,EAChB,GAAG,EAAE,MAAM,EACX,GAAG,SAAgB,GAClB,OAAO,CAAC,IAAI,CAAC,CAsEf;AAED,kDAAkD;AAClD,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,UAAU,EAChB,GAAG,EAAE,MAAM,EACX,GAAG,SAAgB,GAClB,OAAO,CAAC,IAAI,CAAC,CAmBf;AAED;;;;;;GAMG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,UAAU,EAChB,GAAG,EAAE,MAAM,EACX,GAAG,SAAgB,GAClB,OAAO,CAAC,IAAI,CAAC,CAgCf;AAED,6DAA6D;AAC7D,wBAAgB,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,GAAG,SAAS,GAAG,QAAQ,CAExE;AAyBD,mFAAmF;AACnF,wBAAsB,aAAa,CACjC,IAAI,EAAE,UAAU,EAChB,GAAG,EAAE,MAAM,EACX,GAAG,SAAgB,GAClB,OAAO,CAAC,IAAI,CAAC,CA+Ef;AAED,mDAAmD;AACnD,wBAAsB,SAAS,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,SAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAQjG"}
@@ -0,0 +1,262 @@
1
+ import { resolve } from 'node:path';
2
+ import { writeFile } from 'node:fs/promises';
3
+ import { TraceReader, chainTo, deriveCheckpoints, listRuns, pickChainTarget, resolveRunSelector, runGraph, } from '@orcareplay/core';
4
+ import { buildTimeline, detectLoops, exportTraceHtml, serveViewer, summarize, } from '@orcareplay/viewer';
5
+ import { priceFor } from '@orcareplay/providers';
6
+ import { formatCost } from './compare.js';
7
+ import { renderChainCard, renderGraphCard, scopeForCard } from '../share-card.js';
8
+ import { cardTarget, gifFrames, svgToPng, svgsToGif } from '../rasterize.js';
9
+ /** `orca list` — what runs are here, newest first. */
10
+ export async function listCommand(args, out, cwd = process.cwd()) {
11
+ const runs = await listRuns(cwd);
12
+ if (runs.length === 0) {
13
+ out.plain('no runs recorded yet');
14
+ out.plain('');
15
+ out.plain(' orca record claude # record one');
16
+ return;
17
+ }
18
+ // FROM is what makes a directory full of `orca compare` output readable: four runs with no
19
+ // stated relationship are four mysteries, and the relationship is already in every manifest.
20
+ out.table(['RUN', 'CREATED', 'FROM', 'DIR'], runs.map((r) => [
21
+ r.runId,
22
+ r.createdAt,
23
+ // `@<seq>` only where there is a fork point. A replay trace has a parent and no checkpoint,
24
+ // and `run_x@?` reads as missing data rather than as the different thing it is.
25
+ r.parentRun === undefined
26
+ ? ''
27
+ : r.forkPoint === undefined
28
+ ? r.parentRun
29
+ : `${r.parentRun}@${r.forkPoint}`,
30
+ r.dir,
31
+ ]));
32
+ }
33
+ /** `orca show` — the timeline, in the terminal. */
34
+ export async function showCommand(args, out, cwd = process.cwd()) {
35
+ const reader = await TraceReader.open((await resolveRunSelector(cwd, args.positionals[0] ?? 'last')).dir);
36
+ const manifest = reader.manifest();
37
+ const events = await reader.events();
38
+ const summary = summarize(manifest, events);
39
+ out.plain(`${summary.runId} ${summary.adapter} ${summary.eventCount} events exit ${summary.exitCode ?? 0}`);
40
+ // A forked run's timeline opens mid-conversation in a worktree that no longer exists. Without
41
+ // this line the only way to learn where it came from is to cat the manifest — for the feature
42
+ // the tool is named after.
43
+ if (manifest.parent_run !== undefined) {
44
+ // `parent_run` no longer implies a fork. An exact replay writes its own run with a parent and
45
+ // deliberately no `fork_point`, so keying the wording on that field is what tells the two
46
+ // apart — and stops the line reading "at checkpoint undefined", which looks like a corrupt
47
+ // trace rather than the different thing it actually is.
48
+ if (manifest.fork_point === undefined) {
49
+ out.plain(` replay of ${manifest.parent_run}`);
50
+ }
51
+ else {
52
+ const model = manifest.fork_model === undefined ? '' : ` on ${manifest.fork_model}`;
53
+ out.plain(` forked from ${manifest.parent_run} at checkpoint ${manifest.fork_point}${model}`);
54
+ }
55
+ }
56
+ out.plain('');
57
+ // Both halves of the row. Rendering only `meta` showed token counts for a model response and an
58
+ // empty cell for everything else — which quietly dropped a shell command's exit code, a tool
59
+ // result's success or failure, and the stop reason, leaving a call and its result looking like
60
+ // the same row printed twice.
61
+ // Indented by delegation depth, so a sub-agent's model and tool calls read as its work rather
62
+ // than as more of the parent's. `└─` on the first nested row marks where the delegation begins.
63
+ const timeline = buildTimeline(events);
64
+ const rows = timeline.map((r, i) => [
65
+ String(r.seq),
66
+ r.kind,
67
+ `${nestPrefix(r.depth, timeline[i - 1]?.depth ?? 0)}${r.label}`,
68
+ [r.detail, r.meta].filter((part) => part !== undefined && part !== '').join(' · '),
69
+ ]);
70
+ out.table(['SEQ', 'KIND', 'WHAT', 'DETAIL'], rows);
71
+ const loops = detectLoops(events);
72
+ for (const loop of loops) {
73
+ out.plain('');
74
+ // The signature is the point of the warning: "the same grep, four times" is something an
75
+ // operator can act on, where a list of turn numbers is something they have to go and look up.
76
+ out.warn('loop.detected', {
77
+ kind: loop.kind,
78
+ turns: loop.turns.join(','),
79
+ repeats: loop.repeats,
80
+ action: loop.signature,
81
+ tree: loop.tree?.slice(0, 8),
82
+ });
83
+ }
84
+ const cost = totalCost(events);
85
+ if (cost !== null) {
86
+ out.plain('');
87
+ out.info('usage', {
88
+ input: summary.totalUsage.input,
89
+ output: summary.totalUsage.output,
90
+ // Same scaling as the compare table: four fixed decimals render a genuinely cheap run as
91
+ // `$0.0000`, which reads as free rather than as small.
92
+ cost: formatCost(cost),
93
+ });
94
+ }
95
+ }
96
+ /** `orca checkpoints` — where can I fork from? */
97
+ export async function checkpointsCommand(args, out, cwd = process.cwd()) {
98
+ const reader = await TraceReader.open((await resolveRunSelector(cwd, args.positionals[0] ?? 'last')).dir);
99
+ const events = await reader.events();
100
+ const checkpoints = deriveCheckpoints(events);
101
+ if (checkpoints.length === 0) {
102
+ out.plain('no checkpoints in this run');
103
+ out.plain(' checkpoints need a filesystem snapshot; was this recorded with --no-fs?');
104
+ return;
105
+ }
106
+ out.table(['SEQ', 'TURN', 'TREE'], checkpoints.map((c) => [String(c.seq), String(c.turn), (c.fsTree ?? '').slice(0, 12)]));
107
+ out.plain('');
108
+ out.plain(` orca replay last --from ${checkpoints.at(-1).seq} --model <other-model>`);
109
+ }
110
+ /**
111
+ * `orca graph` — what caused what.
112
+ *
113
+ * The timeline says what happened in what order; this says what produced what. Every row names
114
+ * both ends and, crucially, whether the trace vouches for the edge or a rule here worked it out —
115
+ * a reader who cannot tell the two apart has been handed a guess dressed as a fact.
116
+ */
117
+ export async function graphCommand(args, out, cwd = process.cwd()) {
118
+ const reader = await TraceReader.open((await resolveRunSelector(cwd, args.positionals[0] ?? 'last')).dir);
119
+ const events = await reader.events();
120
+ const graph = narrow(runGraph(events), args.num('to'));
121
+ const label = new Map(graph.nodes.map((n) => [n.seq, n.type]));
122
+ if (graph.edges.length === 0) {
123
+ out.plain('no causal edges in this run');
124
+ // Naming the likely cause beats leaving someone to wonder whether the feature works at all.
125
+ out.plain(' a run with no tool calls has nothing to connect; try `orca show` for the timeline');
126
+ return;
127
+ }
128
+ out.table(['FROM', 'TO', 'KIND', 'WHY'], graph.edges.map((e) => [
129
+ `${e.from} ${label.get(e.from) ?? ''}`.trim(),
130
+ `${e.to} ${label.get(e.to) ?? ''}`.trim(),
131
+ e.kind,
132
+ e.rule,
133
+ ]));
134
+ const inferred = graph.edges.filter((e) => e.kind === 'inferred').length;
135
+ if (inferred > 0) {
136
+ out.plain('');
137
+ out.plain(` ${inferred} inferred — derived from this trace, not recorded in it`);
138
+ }
139
+ }
140
+ /** The whole graph, or just the chain that produced `to`. */
141
+ export function narrow(graph, to) {
142
+ return to === undefined ? graph : chainTo(graph, to);
143
+ }
144
+ /**
145
+ * Write a card in whichever format was asked for.
146
+ *
147
+ * SVG is the source and never needs anything; PNG and GIF go through the optional raster path,
148
+ * which explains itself when its packages are absent rather than failing obscurely.
149
+ */
150
+ async function writeCard(path, format, svgs, delays) {
151
+ if (format === 'svg') {
152
+ const svg = svgs[svgs.length - 1] ?? '';
153
+ await writeFile(path, svg, 'utf8');
154
+ return Buffer.byteLength(svg);
155
+ }
156
+ const buffer = format === 'png' ? await svgToPng(svgs[svgs.length - 1] ?? '') : await svgsToGif(svgs, delays);
157
+ await writeFile(path, buffer);
158
+ return buffer.length;
159
+ }
160
+ /** `orca export` — the single self-contained file that makes a trace shareable. */
161
+ export async function exportCommand(args, out, cwd = process.cwd()) {
162
+ const runDir = (await resolveRunSelector(cwd, args.positionals[0] ?? 'last')).dir;
163
+ const reader = await TraceReader.open(runDir);
164
+ const manifest = reader.manifest();
165
+ // `--card` is a different artefact from `--out`, not a variation on it: one chain as a picture
166
+ // rather than the whole trace as a page. It leaks far less too — a card carries the events on
167
+ // one chain and none of the payloads — so it does not print the disclosure the page needs.
168
+ // The graph card shows the shape of a run; the chain card follows one path through it. Two
169
+ // different pictures, so two flags rather than a mode switch on one.
170
+ if (args.has('graph-card')) {
171
+ const target = cardTarget(args.str('graph-card') ?? 'graph.svg', '--graph-card');
172
+ const cardPath = resolve(cwd, target.path);
173
+ const events = await reader.events();
174
+ const graph = runGraph(events);
175
+ const to = args.num('to') ?? pickChainTarget(events);
176
+ const highlight = new Set(to === undefined ? [] : chainTo(graph, to).nodes.map((n) => n.seq));
177
+ const svg = renderGraphCard(scopeForCard(graph, highlight), {
178
+ runId: manifest.run_id,
179
+ highlight,
180
+ });
181
+ // A graph is one picture: there is no sequence to animate, so a .gif of it would be a
182
+ // one-frame file pretending to be a clip.
183
+ if (target.format === 'gif') {
184
+ throw new Error('--graph-card draws one picture, so it has no animation. Use .svg or .png.');
185
+ }
186
+ const bytes = await writeCard(cardPath, target.format, [svg], []);
187
+ out.phase('carded', { path: cardPath, bytes, kind: 'graph', format: target.format });
188
+ return;
189
+ }
190
+ if (args.has('card')) {
191
+ const target = cardTarget(args.str('card') ?? 'chain.svg', '--card');
192
+ const cardPath = resolve(cwd, target.path);
193
+ const events = await reader.events();
194
+ const to = args.num('to') ?? pickChainTarget(events);
195
+ if (to === undefined) {
196
+ // Refusing beats drawing something arbitrary: a card gets screenshotted whether or not it
197
+ // happens to be the interesting one, so a bad pick travels further than no card at all.
198
+ throw new Error(`nothing in ${manifest.run_id} stands out as the subject of a card — ` +
199
+ 'no failing command, no tool error and no file change. ' +
200
+ 'Name the event yourself with `--to <seq>`, or `orca show` to find one.');
201
+ }
202
+ const chain = chainTo(runGraph(events), to);
203
+ const frames = gifFrames(chain.nodes.length);
204
+ const svgs = target.format === 'gif'
205
+ ? frames.map((f) => renderChainCard(chain, { runId: manifest.run_id, reveal: f.reveal }))
206
+ : [renderChainCard(chain, { runId: manifest.run_id })];
207
+ const bytes = await writeCard(cardPath, target.format, svgs, frames.map((f) => f.delayMs));
208
+ out.phase('carded', { path: cardPath, bytes, to, format: target.format });
209
+ return;
210
+ }
211
+ const target = resolve(cwd, args.str('o') ?? args.str('out') ?? 'trace.html');
212
+ // Say what is about to leave the machine before it does. A trace can contain file contents and
213
+ // shell output, and the person exporting it is usually about to attach it to a public issue.
214
+ out.plain('about to write a single self-contained file containing:');
215
+ out.plain(` run ${manifest.run_id}`);
216
+ out.plain(` events ${manifest.counts?.events ?? '?'}`);
217
+ out.plain(` blobs ${manifest.counts?.blobs ?? '?'} (model requests, tool output, diffs)`);
218
+ out.plain(` redactions ${Object.values(manifest.redaction?.rules_fired ?? {}).reduce((a, b) => a + b, 0)} applied`);
219
+ out.plain(` cwd ${manifest.cwd}`);
220
+ out.plain('');
221
+ out.plain(' redaction is best-effort. skim the file before sharing it.');
222
+ out.plain('');
223
+ const result = await exportTraceHtml(runDir, target);
224
+ out.phase('exported', { path: result.path, bytes: result.bytes });
225
+ }
226
+ /** `orca ui` — the same viewer, served locally. */
227
+ export async function uiCommand(args, out, cwd = process.cwd()) {
228
+ const runDir = (await resolveRunSelector(cwd, args.positionals[0] ?? 'last')).dir;
229
+ const server = await serveViewer({ runDir, port: args.num('port') ?? 0 });
230
+ out.phase('viewer', { url: server.url });
231
+ out.plain(' ctrl-c to stop');
232
+ await new Promise((resolve) => {
233
+ process.on('SIGINT', () => void server.close().then(resolve));
234
+ });
235
+ }
236
+ function totalCost(events) {
237
+ let total = 0;
238
+ let priced = false;
239
+ for (const e of events) {
240
+ if (e.type !== 'model.response')
241
+ continue;
242
+ const model = String(e.attrs?.model ?? '');
243
+ const money = priceFor({
244
+ input_tokens: Number(e.attrs?.input_tokens ?? 0),
245
+ output_tokens: Number(e.attrs?.output_tokens ?? 0),
246
+ }, model);
247
+ // An unknown model prices as null rather than zero: a confidently wrong cost is worse than an
248
+ // absent one, because it ends up in a comparison someone makes a decision from.
249
+ if (money) {
250
+ total += money.amount;
251
+ priced = true;
252
+ }
253
+ }
254
+ return priced ? total : null;
255
+ }
256
+ /** ` `-per-level indent, with `└─` on the row where a delegation's work starts. */
257
+ function nestPrefix(depth, previousDepth) {
258
+ if (depth === 0)
259
+ return '';
260
+ return ' '.repeat(depth - 1) + (depth > previousDepth ? '└─ ' : ' ');
261
+ }
262
+ //# sourceMappingURL=inspect.js.map