@manyoars/oars 0.3.0 → 0.4.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 +44 -29
- package/dist/cli.d.ts +4 -1
- package/dist/cli.js +102 -32
- package/dist/commands/login.d.ts +1 -1
- package/dist/commands/login.js +1 -1
- package/dist/gate/hook-main.js +13 -1
- package/dist/gate/hook-process.d.ts +11 -1
- package/dist/gate/hook-process.js +50 -2
- package/dist/gate/hook-record.d.ts +3 -0
- package/dist/gate/hook-record.js +8 -0
- package/dist/gate/hook.d.ts +2 -0
- package/dist/gate/hook.js +3 -0
- package/dist/gate/replay.js +2 -5
- package/dist/gate/report.js +1 -1
- package/dist/gate/transcripts.js +1 -1
- package/dist/hook-bin.js +27 -2
- package/dist/issuer.d.ts +7 -9
- package/dist/issuer.js +10 -32
- package/dist/main.js +3 -0
- package/dist/sessions/actions.js +78 -0
- package/dist/sessions/allow.d.ts +10 -1
- package/dist/sessions/allow.js +29 -9
- package/dist/sessions/baseline.d.ts +1 -1
- package/dist/sessions/baseline.js +6 -4
- package/dist/sessions/call-shape.d.ts +19 -0
- package/dist/sessions/call-shape.js +85 -0
- package/dist/sessions/cell-increments.d.ts +5 -1
- package/dist/sessions/cell-increments.js +84 -50
- package/dist/sessions/cells.d.ts +33 -3
- package/dist/sessions/cells.js +89 -9
- package/dist/sessions/codex.js +93 -27
- package/dist/sessions/command.d.ts +11 -1
- package/dist/sessions/command.js +64 -18
- package/dist/sessions/coverage.d.ts +19 -0
- package/dist/sessions/coverage.js +56 -0
- package/dist/sessions/cursor.js +2 -2
- package/dist/sessions/derive.js +72 -6
- package/dist/sessions/exclusion.d.ts +6 -0
- package/dist/sessions/exclusion.js +8 -0
- package/dist/sessions/extract.d.ts +4 -2
- package/dist/sessions/extract.js +58 -26
- package/dist/sessions/hook-calls.js +1 -1
- package/dist/sessions/hook-index.d.ts +14 -1
- package/dist/sessions/hook-index.js +10 -3
- package/dist/sessions/hooks.d.ts +5 -1
- package/dist/sessions/hooks.js +133 -22
- package/dist/sessions/identity.d.ts +8 -3
- package/dist/sessions/identity.js +45 -15
- package/dist/sessions/install.d.ts +18 -2
- package/dist/sessions/install.js +53 -8
- package/dist/sessions/mcp.d.ts +3 -2
- package/dist/sessions/mcp.js +8 -4
- package/dist/sessions/motif.d.ts +2 -1
- package/dist/sessions/motif.js +2 -2
- package/dist/sessions/opencode.d.ts +33 -0
- package/dist/sessions/opencode.js +319 -0
- package/dist/sessions/otel.d.ts +60 -0
- package/dist/sessions/otel.js +494 -0
- package/dist/sessions/package-books.d.ts +48 -0
- package/dist/sessions/package-books.js +120 -0
- package/dist/sessions/package-resolve.d.ts +2 -0
- package/dist/sessions/package-resolve.js +3 -1
- package/dist/sessions/pi.d.ts +36 -0
- package/dist/sessions/pi.js +353 -0
- package/dist/sessions/pool-books.d.ts +21 -0
- package/dist/sessions/pool-books.js +75 -0
- package/dist/sessions/pool-choices.d.ts +5 -0
- package/dist/sessions/pool-choices.js +11 -0
- package/dist/sessions/pool-command.d.ts +76 -5
- package/dist/sessions/pool-command.js +263 -136
- package/dist/sessions/pool-nightly-deps.d.ts +3 -0
- package/dist/sessions/pool-nightly-deps.js +11 -0
- package/dist/sessions/pool-nightly.d.ts +10 -0
- package/dist/sessions/pool-nightly.js +85 -0
- package/dist/sessions/pool-outbox.d.ts +15 -2
- package/dist/sessions/pool-outbox.js +79 -26
- package/dist/sessions/pool-print.d.ts +1 -1
- package/dist/sessions/pool-raw.d.ts +27 -0
- package/dist/sessions/pool-raw.js +68 -0
- package/dist/sessions/pool-replica.d.ts +35 -0
- package/dist/sessions/pool-replica.js +91 -0
- package/dist/sessions/pool-send-lock.d.ts +20 -0
- package/dist/sessions/pool-send-lock.js +87 -0
- package/dist/sessions/pool-sent.d.ts +4 -0
- package/dist/sessions/pool-sent.js +26 -0
- package/dist/sessions/pool-totals.d.ts +10 -0
- package/dist/sessions/pool-totals.js +21 -0
- package/dist/sessions/pool-wire.d.ts +240 -30
- package/dist/sessions/pool-wire.js +695 -215
- package/dist/sessions/public-vocabulary.d.ts +2 -3
- package/dist/sessions/public-vocabulary.js +2 -9
- package/dist/sessions/query.d.ts +16 -4
- package/dist/sessions/query.js +32 -10
- package/dist/sessions/read-cost.js +14 -3
- package/dist/sessions/read-recovery-edge.d.ts +2 -0
- package/dist/sessions/read-recovery-edge.js +26 -0
- package/dist/sessions/read.d.ts +8 -1
- package/dist/sessions/read.js +6 -3
- package/dist/sessions/recall-bundle.d.ts +12 -0
- package/dist/sessions/recall-bundle.js +33 -0
- package/dist/sessions/recall-frames.d.ts +18 -0
- package/dist/sessions/recall-frames.js +44 -0
- package/dist/sessions/recall-next.d.ts +145 -0
- package/dist/sessions/recall-next.js +241 -0
- package/dist/sessions/recall-object.d.ts +2 -0
- package/dist/sessions/recall-object.js +1 -0
- package/dist/sessions/recall-push.d.ts +2 -2
- package/dist/sessions/recall-push.js +3 -51
- package/dist/sessions/recall-reads.d.ts +7 -0
- package/dist/sessions/recall-reads.js +7 -0
- package/dist/sessions/recovery-calls.d.ts +21 -0
- package/dist/sessions/recovery-calls.js +25 -0
- package/dist/sessions/redact.d.ts +11 -0
- package/dist/sessions/redact.js +59 -0
- package/dist/sessions/schema.d.ts +59 -9
- package/dist/sessions/schema.js +22 -3
- package/dist/sessions/session-shape.d.ts +6 -3
- package/dist/sessions/session-shape.js +19 -6
- package/dist/sessions/sources.js +75 -1
- package/dist/sessions/store.d.ts +13 -1
- package/dist/sessions/store.js +36 -16
- package/dist/sessions/survival.d.ts +2 -0
- package/dist/sessions/survival.js +2 -2
- package/dist/sessions/transcript-tail.d.ts +14 -0
- package/dist/sessions/transcript-tail.js +64 -0
- package/dist/sessions/vscode-copilot.js +24 -7
- package/dist/telemetry/command.d.ts +8 -0
- package/dist/telemetry/command.js +85 -0
- package/dist/telemetry/consent.d.ts +17 -0
- package/dist/telemetry/consent.js +63 -0
- package/dist/telemetry/crash.d.ts +8 -0
- package/dist/telemetry/crash.js +52 -0
- package/dist/telemetry/emit.d.ts +31 -0
- package/dist/telemetry/emit.js +42 -0
- package/dist/telemetry/fields.d.ts +16 -0
- package/dist/telemetry/fields.js +26 -0
- package/dist/telemetry/health.d.ts +21 -0
- package/dist/telemetry/health.js +108 -0
- package/dist/telemetry/hook-emit.d.ts +4 -0
- package/dist/telemetry/hook-emit.js +18 -0
- package/dist/telemetry/measures.d.ts +32 -0
- package/dist/telemetry/measures.js +128 -0
- package/dist/telemetry/observe.d.ts +13 -0
- package/dist/telemetry/observe.js +51 -0
- package/dist/telemetry/read-events.d.ts +9 -0
- package/dist/telemetry/read-events.js +17 -0
- package/dist/telemetry/record.d.ts +17 -0
- package/dist/telemetry/record.js +26 -0
- package/dist/telemetry/report.d.ts +77 -0
- package/dist/telemetry/report.js +188 -0
- package/dist/telemetry/schema.d.ts +40 -0
- package/dist/telemetry/schema.js +187 -0
- package/dist/telemetry/send-main.d.ts +1 -0
- package/dist/telemetry/send-main.js +11 -0
- package/dist/telemetry/send.d.ts +36 -0
- package/dist/telemetry/send.js +172 -0
- package/dist/telemetry/spool.d.ts +17 -0
- package/dist/telemetry/spool.js +70 -0
- package/dist/telemetry/wire.d.ts +25 -0
- package/dist/telemetry/wire.js +76 -0
- package/package.json +2 -2
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import type { CanonicalAction } from './actions.js';
|
|
2
|
+
import type { ArgClass, TargetClass } from './call-shape.js';
|
|
2
3
|
import type { RepoSize } from './public-vocabulary.js';
|
|
3
4
|
import type { SessionFunnel, SessionSetup, SessionTrust } from './session-shape.js';
|
|
4
5
|
/** Stamped `cv` on every captured record. Bump it when the captured field list changes or a stored value of an existing captured record or skeleton changes: a bump re-reads every transcript. */
|
|
5
|
-
export declare const CAPTURE_VERSION = "
|
|
6
|
+
export declare const CAPTURE_VERSION = "25";
|
|
6
7
|
/** Capture versions the reader derives from. A version-3 record is upgraded on read; a newer one is read for the fields this code knows. */
|
|
7
8
|
export declare const OLDEST_READABLE_CAPTURE = 3;
|
|
8
9
|
/** Stamped `v` on every derived record. A bump re-derives from the log; no transcript is read. */
|
|
9
|
-
export declare const EXTRACTOR_VERSION = "
|
|
10
|
+
export declare const EXTRACTOR_VERSION = "15";
|
|
10
11
|
export declare const PROMPT_HEAD_CHARS = 300;
|
|
11
12
|
export declare const ERROR_EXAMPLE_CHARS = 160;
|
|
12
13
|
/** The reference scripts cut a normalized signature at 110 characters. */
|
|
@@ -15,6 +16,12 @@ export declare const SIGNATURE_CHARS = 110;
|
|
|
15
16
|
export declare const ERROR_LINE_CHARS = 1000;
|
|
16
17
|
/** Per-session cap on `files` and `fileHashes` (L3): the hand run on the copy store had p99 61 and max 209 files per session; 100 is the round number above p99. */
|
|
17
18
|
export declare const FILES_PER_SESSION_MAX = 100;
|
|
19
|
+
/**
|
|
20
|
+
* What the next human prompt did to a prompt's work (t882, `docs/sessions-store.md` "Prompt intervention"): a closed list, not a grade. `user-stop` (EXTRACTOR 14) is a prompt the human stopped: a rejected call, or an interrupt. `unknown` is a prompt the records cannot say anything about (the last of its
|
|
21
|
+
* transcript, or one whose next prompt ran a shell call captured before `segments`); a prompt derived before EXTRACTOR 13 has no value at all, which is a different thing, and one derived at 13 read a stop as `unknown`.
|
|
22
|
+
*/
|
|
23
|
+
export declare const PROMPT_INTERVENTIONS: readonly ["none", "after-error", "redo", "revert", "user-stop", "unknown"];
|
|
24
|
+
export type PromptIntervention = (typeof PROMPT_INTERVENTIONS)[number];
|
|
18
25
|
export declare const PROMPT_CLASSES: readonly ["no-tools", "orchestration", "research", "edit-verify", "edit-only", "explore-only", "shell-only", "mixed"];
|
|
19
26
|
export type PromptClass = (typeof PROMPT_CLASSES)[number];
|
|
20
27
|
/** The statuses agents recover from differently (pool vocabulary spec Section 7); any other status is `other`. */
|
|
@@ -32,7 +39,14 @@ export interface Usage {
|
|
|
32
39
|
output: number;
|
|
33
40
|
thinking: number;
|
|
34
41
|
}
|
|
35
|
-
|
|
42
|
+
/** The five usage categories, in the order a record lists the ones it observed. */
|
|
43
|
+
export declare const USAGE_FIELDS: readonly ["input", "cacheWrite", "cacheRead", "output", "thinking"];
|
|
44
|
+
export type UsageField = (typeof USAGE_FIELDS)[number];
|
|
45
|
+
/** Whether a stored session or prompt recorded `field`. A record with no `usageObserved` was captured before version 20, when every category read as observed. */
|
|
46
|
+
export declare const observesUsage: (record: {
|
|
47
|
+
usageObserved?: readonly UsageField[];
|
|
48
|
+
}, field: UsageField) => boolean;
|
|
49
|
+
export type Harness = 'claude-code' | 'cursor' | 'codex' | 'copilot' | 'gemini' | 'grok' | 'opencode' | 'pi' | 'vscode-copilot' | 'otel';
|
|
36
50
|
/** The harness a session record came from; a record that does not name one is Claude Code's. */
|
|
37
51
|
export declare const harnessOf: (session: {
|
|
38
52
|
harness?: Harness;
|
|
@@ -171,6 +185,14 @@ export interface SessionObservation {
|
|
|
171
185
|
costUsd: number | null;
|
|
172
186
|
/** Summed assistant usage of the file's own line (a main transcript's sidechain lines are not counted; a subagent transcript is its own line). */
|
|
173
187
|
usage: Usage;
|
|
188
|
+
/**
|
|
189
|
+
* Which `usage` categories at least one counted response reported (class P). A category not listed was never recorded by the source, so its `usage` number is a
|
|
190
|
+
* placeholder zero and not a measurement; a listed one is a sum over `usageResponses` responses, which a partial sum covers only in part. Absent on a record captured
|
|
191
|
+
* before version 20: every category reads as observed there.
|
|
192
|
+
*/
|
|
193
|
+
usageObserved?: UsageField[];
|
|
194
|
+
/** How many responses reported any usage (class P); compare with `assistantTurns` or `turns` for the share a sum covers. Same absence rule as `usageObserved`. */
|
|
195
|
+
usageResponses?: number;
|
|
174
196
|
/** What the transcript shows of the setup the session ran with (class A); absent when it shows none, and on a record captured before version 12. */
|
|
175
197
|
setup?: SessionSetup;
|
|
176
198
|
/** The tool names at each funnel stage the transcript shows (class A); same absence rules. */
|
|
@@ -188,7 +210,7 @@ export type PromptStop = 'interrupt' | 'rejected';
|
|
|
188
210
|
/**
|
|
189
211
|
* One per user prompt: the `prompt_graphs.py` and `cost_per_class.py` records joined. Derived. Classes: `session`,
|
|
190
212
|
* `contextFingerprint`, `frame`, `head`, `ts`, `skills`, `verify`, `stopped`, `commits`, and `redo` (A); `kind`, `v`, `rev`, `index`, `wallSec`, `cls`, `tools`,
|
|
191
|
-
* `shape`, `errors`, `thinking`, `sideTools`, `turns`, `model`, `efforts`, `usage`, `recovery`, `explore`, `act`, and `
|
|
213
|
+
* `shape`, `errors`, `thinking`, `sideTools`, `turns`, `model`, `efforts`, `usage`, `recovery`, `explore`, `act`, `bigrams`, and `intervention` (P).
|
|
192
214
|
*/
|
|
193
215
|
export interface PromptObservation {
|
|
194
216
|
kind: 'prompt';
|
|
@@ -216,6 +238,9 @@ export interface PromptObservation {
|
|
|
216
238
|
efforts: string[];
|
|
217
239
|
skills: string[];
|
|
218
240
|
usage: Usage;
|
|
241
|
+
/** Carried from the capture record: the categories and response count the prompt's `usage` rests on (see `UserPromptObservation`). */
|
|
242
|
+
usageObserved?: UsageField[];
|
|
243
|
+
usageResponses?: number;
|
|
219
244
|
/** The two tools after the first error of each error run (the recovery pair); empty when no error. */
|
|
220
245
|
recovery: string[];
|
|
221
246
|
explore: number;
|
|
@@ -235,6 +260,8 @@ export interface PromptObservation {
|
|
|
235
260
|
commits?: number;
|
|
236
261
|
/** A later prompt edited a path this one edited, within `REDO_WINDOW` prompts, with no commit `commits` can see between them. */
|
|
237
262
|
redo?: boolean;
|
|
263
|
+
/** Whether a human stepped in after this prompt's work, and how (EXTRACTOR 13): what the next prompt did, never a grade. Absent on a prompt derived earlier. Class P: a member of a closed list. */
|
|
264
|
+
intervention?: PromptIntervention;
|
|
238
265
|
}
|
|
239
266
|
/**
|
|
240
267
|
* One per tool error: the `error_signatures.py` record. Derived. Classes: `session`, `agent`, `ts`, `sig`, `example` (A); `kind`, `v`,
|
|
@@ -268,7 +295,7 @@ export interface ErrorObservation {
|
|
|
268
295
|
agent?: string;
|
|
269
296
|
}
|
|
270
297
|
export type ToolStatus = 'ok' | 'error' | 'none';
|
|
271
|
-
/** A package a shell step pulled
|
|
298
|
+
/** A package a shell step pulled. The package name is kept as it is after redaction (`<literal>` when redaction changes it, it is not a name, or it is over 128 characters); the registry is a public name. */
|
|
272
299
|
export interface PullIdentity {
|
|
273
300
|
registry: string;
|
|
274
301
|
name: string;
|
|
@@ -280,6 +307,11 @@ export interface ShellIdentity {
|
|
|
280
307
|
program: string;
|
|
281
308
|
sub: string | null;
|
|
282
309
|
flags: string[];
|
|
310
|
+
/**
|
|
311
|
+
* Only on `git checkout` (CAPTURE 23), the class of what it acts on and never the target's text: `path` where the line proves a path (a `--` marker, or a flag that only takes
|
|
312
|
+
* paths), `word` where a target word the segment drops (a slash, quotes) stands with no such proof, so it is a branch or a path and the line cannot say which. Absent with no such word.
|
|
313
|
+
*/
|
|
314
|
+
target?: 'path' | 'word';
|
|
283
315
|
/** Absent when the command pulled nothing. */
|
|
284
316
|
pulls?: PullIdentity[];
|
|
285
317
|
}
|
|
@@ -295,6 +327,8 @@ export interface StepCost {
|
|
|
295
327
|
/** The output tokens of the call's response, shared evenly among that response's calls; null when it reported none. */
|
|
296
328
|
tokens: number | null;
|
|
297
329
|
durationMs: number | null;
|
|
330
|
+
/** True when `tokens` was divided among the response's calls, not measured for this call; absent when the response made one call. */
|
|
331
|
+
allocated?: true;
|
|
298
332
|
}
|
|
299
333
|
/**
|
|
300
334
|
* How a step came about (recall spec Section 2.3), naming the step it came from by position. `next`: order within a prompt's line of work.
|
|
@@ -315,7 +349,7 @@ export interface StepObservation {
|
|
|
315
349
|
action: CanonicalAction;
|
|
316
350
|
/** The harness's own name for the tool when the action table lists it or every name it holds is public, else `<literal>` (the tool record keeps it, class A). */
|
|
317
351
|
native: string;
|
|
318
|
-
/** What the step acted on,
|
|
352
|
+
/** What the step acted on; a skill-run name, host, and pull name are kept as they are after redaction (`<literal>` when redaction changes them, they are not names, or they are over 128 characters), and every other name is public or `<literal>`. Null for an action that has none (a file's path never appears here). */
|
|
319
353
|
identity: StepIdentity;
|
|
320
354
|
/** The call's status when it reports this segment; `none` for a segment it does not, such as one a pipe hides. */
|
|
321
355
|
status: ToolStatus;
|
|
@@ -361,17 +395,25 @@ export interface ToolObservation {
|
|
|
361
395
|
targetBase: TargetBase | null;
|
|
362
396
|
/** The input's key names, sorted. Class A (EXTRACTOR 11). */
|
|
363
397
|
keys: string[];
|
|
398
|
+
/** The class of the call's path target (`targetClassOf`): a member of `TARGET_CLASSES`, `none` for a call with no path target. Absent on a record captured before CAPTURE 22. Class P: a closed list, never the path or its base name. */
|
|
399
|
+
targetClass?: TargetClass;
|
|
400
|
+
/** The classes of the call's arguments (`argClassOf`), a sorted set of `ARG_CLASSES` members, `['none']` for a call with no argument value. Absent on a record captured before CAPTURE 22. Class P: never a value. */
|
|
401
|
+
argClass?: ArgClass[];
|
|
364
402
|
/** The class of a failed call (`errorCodeOf`): a member of `ERROR_CODES`, never result text. Absent on a call that did not fail and on a record captured before CAPTURE 14. Class P. */
|
|
365
403
|
errorCode?: string;
|
|
366
404
|
/** From the call's line to its first result's line; null when either timestamp is missing or no result is in the file. */
|
|
367
405
|
durationMs: number | null;
|
|
368
406
|
toolUseId: string | null;
|
|
369
407
|
harnessVersion: string | null;
|
|
408
|
+
/** The model the call's own line names (`requestedModel`, else the message's model), so a step is attributed to the model that made it and not only to its prompt's majority (t867). Absent when the line names none and on a record captured before it. Class P, like the prompt's. */
|
|
409
|
+
model?: string;
|
|
410
|
+
/** The skill whose run the call's line sits in (`attributionSkill`): the harness stops naming it on the first line after the run, so that is where a run ends. Absent outside a run and on an earlier record. Class A, since a skill's name can be private. */
|
|
411
|
+
skill?: string;
|
|
370
412
|
/** A sidechain line inside a main transcript. */
|
|
371
413
|
side: boolean;
|
|
372
414
|
/** The prompt index the call ran under; null before the first prompt. */
|
|
373
415
|
prompt: number | null;
|
|
374
|
-
/** What the call was in the canonical vocabulary; absent on a record captured before this field existed. Class P. */
|
|
416
|
+
/** What the call was in the canonical vocabulary; absent on a record captured before this field existed. Class A since CAPTURE 24: a step identity's skill-run name, host, and pull name are kept as they are after redaction and can be private; class P before. */
|
|
375
417
|
steps?: StepObservation[];
|
|
376
418
|
/** Each command of a shell call, in order (CAPTURE 6); on every shell call and no other. Absent on a record captured earlier. */
|
|
377
419
|
segments?: ShellSegment[];
|
|
@@ -457,6 +499,14 @@ export interface UserPromptObservation {
|
|
|
457
499
|
efforts: string[];
|
|
458
500
|
skills: string[];
|
|
459
501
|
usage: Usage;
|
|
502
|
+
/**
|
|
503
|
+
* Which `usage` categories at least one counted response reported (class P). A category not listed was never recorded by the source, so its `usage` number is a
|
|
504
|
+
* placeholder zero and not a measurement; a listed one is a sum over `usageResponses` responses, which a partial sum covers only in part. Absent on a record captured
|
|
505
|
+
* before version 20: every category reads as observed there.
|
|
506
|
+
*/
|
|
507
|
+
usageObserved?: UsageField[];
|
|
508
|
+
/** How many responses reported any usage (class P); compare with `assistantTurns` or `turns` for the share a sum covers. Same absence rule as `usageObserved`. */
|
|
509
|
+
usageResponses?: number;
|
|
460
510
|
/** Class A: the session's prompt fingerprint at the time of the prompt, when one had been seen. */
|
|
461
511
|
promptFingerprint: string | null;
|
|
462
512
|
/** Where and when this prompt ran; absent on a record captured before this field existed. Class A (EXTRACTOR 11): its fields pool only as cells. */
|
|
@@ -590,7 +640,7 @@ export interface RecallCall {
|
|
|
590
640
|
/** One hook invocation, appended to `hook-calls.jsonl` by the hook itself; `measure` counts these per session. Class A: `session`, `at`, `error`; P: `event`, `hit`, `ms`, `startMs`. */
|
|
591
641
|
export interface HookCall {
|
|
592
642
|
at: string;
|
|
593
|
-
event: 'session-start' | 'pre-tool' | 'post-tool' | 'stop' | 'nightly';
|
|
643
|
+
event: 'session-start' | 'prompt' | 'pre-tool' | 'post-tool' | 'stop' | 'nightly';
|
|
594
644
|
/** The Claude Code session the hook ran in; null for the nightly run. */
|
|
595
645
|
session: string | null;
|
|
596
646
|
/** Whether the hook had something to say (an injected block, a recall hit, or a session deposited). */
|
|
@@ -606,7 +656,7 @@ export interface HookCall {
|
|
|
606
656
|
export declare const STEPS: readonly ["deposit", "verify", "derive", "label", "index", "recall", "read"];
|
|
607
657
|
export type Step = (typeof STEPS)[number];
|
|
608
658
|
/** The trigger names of spec 4.1 (the ones nothing local starts are in the schema only: `weekly` (A21) and the hosted `sync`, `cron`, `release`, `run-end`, and `run-start`). The SessionEnd hook is `stop`, never `session-end`. */
|
|
609
|
-
export declare const TRIGGERS: readonly ["session-start", "pre-tool", "post-tool", "stop", "nightly", "manual", "weekly", "sync", "cron", "release", "run-end", "run-start"];
|
|
659
|
+
export declare const TRIGGERS: readonly ["session-start", "prompt", "pre-tool", "post-tool", "stop", "nightly", "manual", "weekly", "sync", "cron", "release", "run-end", "run-start"];
|
|
610
660
|
export type Trigger = (typeof TRIGGERS)[number];
|
|
611
661
|
/** `deferred`: a Stop deposit that found the store locked and recorded its session in the pending list. It is neither a success nor a failed run. */
|
|
612
662
|
export declare const STEP_STATUSES: readonly ["ok", "error", "skipped", "deferred"];
|
package/dist/sessions/schema.js
CHANGED
|
@@ -30,6 +30,16 @@
|
|
|
30
30
|
// reads, not moved ones. A Copilot CLI session with rotated `events.N.jsonl` archives is now read as one session, so its `prompts`, `tools`, `lines`, and skeleton move; one
|
|
31
31
|
// with no archives is unchanged. EXTRACTOR stays 11. A version-16 store's next deposit captures every transcript again from the harness or `raw/`.
|
|
32
32
|
//
|
|
33
|
+
// Version 24 (CAPTURE 24, t903) changes three stored values and no field name: a step identity's skill-run name, host, and pull's package name are kept as they are after the operator's redaction (`redactDomains`, the secret and email patterns, the name shape, and the 128-character cap still apply; the operator's exclusion terms are applied at the deposit's gate, not at capture), where before each was `<literal>` unless a public list held it. A store opened at version 23 or older keeps its `<literal>` values until its next deposit captures every transcript again from the harness or `raw/`; the tool record's `steps` moves from class P to class A because those names can be private.
|
|
34
|
+
//
|
|
35
|
+
// Version 23 (CAPTURE 23) keeps a `git checkout` step's target class, `target` `path` or `word` (never the target's text), so the `revert` rule can tell `git checkout -- src/a.ts` and `git checkout --ours src/a.ts` (a
|
|
36
|
+
// path) from `git checkout feature/x` (a branch or a path). It also writes a Codex `turn_aborted` with reason `interrupted` as the interrupt marker user line, so that prompt record exists and the session's
|
|
37
|
+
// `prompts`, `tsLast` (the marker is a line), and skeleton move; a Codex session with no such turn is unchanged. A `git checkout` step captured before reads as it did (no `target`). EXTRACTOR 15 reads a Codex or Gemini decline in its fixed text as `user-stop`.
|
|
38
|
+
//
|
|
39
|
+
// Version 22 (CAPTURE 22, t870) adds the optional `targetClass` and `argClass` to a tool call (`call-shape.ts`): the class of its path target (`source`, `test`, `config`, `doc`, `lockfile`, `generated`, `data`, `none`, `other`) and the sorted set
|
|
40
|
+
// of the classes of its arguments (`path`, `url`, `pattern`, `command`, `number`, `flag`, `text`, `none`), each computed from the path and the input at capture and stored as a closed-list member, never the path or a value. It changes no value
|
|
41
|
+
// captured before, so EXTRACTOR stays 12; a version-21 store's next deposit captures every transcript again, and a call of an older record has neither field.
|
|
42
|
+
//
|
|
33
43
|
// EXTRACTOR 12 gives a derived prompt the strata of its user-prompt record, so its own `explore` count reaches the pool's wire as a bucket. No transcript is read: a store at
|
|
34
44
|
// 11 re-derives from its log alone.
|
|
35
45
|
//
|
|
@@ -78,11 +88,11 @@
|
|
|
78
88
|
// harness versions, and tool names (an MCP name's part after its prefix is redacted). `redact` masks secrets by pattern and entropy, emails, and the
|
|
79
89
|
// organization domains the operator names. The skeleton holds hashes only.
|
|
80
90
|
/** Stamped `cv` on every captured record. Bump it when the captured field list changes or a stored value of an existing captured record or skeleton changes: a bump re-reads every transcript. */
|
|
81
|
-
export const CAPTURE_VERSION = '
|
|
91
|
+
export const CAPTURE_VERSION = '25';
|
|
82
92
|
/** Capture versions the reader derives from. A version-3 record is upgraded on read; a newer one is read for the fields this code knows. */
|
|
83
93
|
export const OLDEST_READABLE_CAPTURE = 3;
|
|
84
94
|
/** Stamped `v` on every derived record. A bump re-derives from the log; no transcript is read. */
|
|
85
|
-
export const EXTRACTOR_VERSION = '
|
|
95
|
+
export const EXTRACTOR_VERSION = '15';
|
|
86
96
|
export const PROMPT_HEAD_CHARS = 300;
|
|
87
97
|
export const ERROR_EXAMPLE_CHARS = 160;
|
|
88
98
|
/** The reference scripts cut a normalized signature at 110 characters. */
|
|
@@ -91,6 +101,11 @@ export const SIGNATURE_CHARS = 110;
|
|
|
91
101
|
export const ERROR_LINE_CHARS = 1000;
|
|
92
102
|
/** Per-session cap on `files` and `fileHashes` (L3): the hand run on the copy store had p99 61 and max 209 files per session; 100 is the round number above p99. */
|
|
93
103
|
export const FILES_PER_SESSION_MAX = 100;
|
|
104
|
+
/**
|
|
105
|
+
* What the next human prompt did to a prompt's work (t882, `docs/sessions-store.md` "Prompt intervention"): a closed list, not a grade. `user-stop` (EXTRACTOR 14) is a prompt the human stopped: a rejected call, or an interrupt. `unknown` is a prompt the records cannot say anything about (the last of its
|
|
106
|
+
* transcript, or one whose next prompt ran a shell call captured before `segments`); a prompt derived before EXTRACTOR 13 has no value at all, which is a different thing, and one derived at 13 read a stop as `unknown`.
|
|
107
|
+
*/
|
|
108
|
+
export const PROMPT_INTERVENTIONS = ['none', 'after-error', 'redo', 'revert', 'user-stop', 'unknown'];
|
|
94
109
|
export const PROMPT_CLASSES = ['no-tools', 'orchestration', 'research', 'edit-verify', 'edit-only', 'explore-only', 'shell-only', 'mixed'];
|
|
95
110
|
/** The statuses agents recover from differently (pool vocabulary spec Section 7); any other status is `other`. */
|
|
96
111
|
export const HTTP_STATUSES = ['400', '401', '403', '404', '408', '409', '413', '429', '500', '502', '503', '504', '529'];
|
|
@@ -100,6 +115,10 @@ export const EXIT_CLASSES = ['0', '1', '2', '126', '127', 'signal'];
|
|
|
100
115
|
export const HARNESS_ERROR_CLASSES = ['user-rejected', 'interrupted', 'permission-denied', 'input-validation', 'tool-use-error', 'timeout'];
|
|
101
116
|
/** The `errorCode` of a failed call (class P): a member of this set, never text from the result. `other` covers an exit status or HTTP status outside the lists, and a failure that names no class. */
|
|
102
117
|
export const ERROR_CODES = new Set([...EXIT_CLASSES, ...HTTP_STATUSES, ...HARNESS_ERROR_CLASSES, 'other']);
|
|
118
|
+
/** The five usage categories, in the order a record lists the ones it observed. */
|
|
119
|
+
export const USAGE_FIELDS = ['input', 'cacheWrite', 'cacheRead', 'output', 'thinking'];
|
|
120
|
+
/** Whether a stored session or prompt recorded `field`. A record with no `usageObserved` was captured before version 20, when every category read as observed. */
|
|
121
|
+
export const observesUsage = (record, field) => record.usageObserved === undefined || record.usageObserved.includes(field);
|
|
103
122
|
/** The harness a session record came from; a record that does not name one is Claude Code's. */
|
|
104
123
|
export const harnessOf = (session) => session.harness ?? 'claude-code';
|
|
105
124
|
/** A language's share of the files a transcript touched (pool vocabulary spec Section 7): none, incidental, secondary, primary. The lower edge is inclusive, so 10 percent is secondary and 50 is primary. */
|
|
@@ -109,7 +128,7 @@ export const REPO_FILE_BUCKETS = ['under-100', '100-1k', '1k-10k', '10k-100k', '
|
|
|
109
128
|
/** The pipeline steps the log names (runtime-commons pipeline spec 3.1); `label` is the local labeler step (A5) that no command runs yet; `index` is the hook-index write the nightly and manual runs log. */
|
|
110
129
|
export const STEPS = ['deposit', 'verify', 'derive', 'label', 'index', 'recall', 'read'];
|
|
111
130
|
/** The trigger names of spec 4.1 (the ones nothing local starts are in the schema only: `weekly` (A21) and the hosted `sync`, `cron`, `release`, `run-end`, and `run-start`). The SessionEnd hook is `stop`, never `session-end`. */
|
|
112
|
-
export const TRIGGERS = ['session-start', 'pre-tool', 'post-tool', 'stop', 'nightly', 'manual', 'weekly', 'sync', 'cron', 'release', 'run-end', 'run-start'];
|
|
131
|
+
export const TRIGGERS = ['session-start', 'prompt', 'pre-tool', 'post-tool', 'stop', 'nightly', 'manual', 'weekly', 'sync', 'cron', 'release', 'run-end', 'run-start'];
|
|
113
132
|
/** `deferred`: a Stop deposit that found the store locked and recorded its session in the pending list. It is neither a success nor a failed run. */
|
|
114
133
|
export const STEP_STATUSES = ['ok', 'error', 'skipped', 'deferred'];
|
|
115
134
|
const isObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
@@ -4,12 +4,15 @@ type Obj = {
|
|
|
4
4
|
};
|
|
5
5
|
/** The harness's tool-rejection results, both worded for the operator declining: a classifier or hook denial is policy, and the model carries on. */
|
|
6
6
|
export declare const REJECTED_RE: RegExp;
|
|
7
|
-
/** The harness writes this as a user line when the operator cut a turn short; a rejection is followed by it too, so it is not a second interrupt. */
|
|
7
|
+
/** The harness writes this as a user line when the operator cut a turn short; a rejection is followed by it too, so it is not a second interrupt. The Codex reader writes it for a `turn_aborted` with reason `interrupted` (CAPTURE 23). */
|
|
8
8
|
export declare const INTERRUPT_MARKER = "[Request interrupted by user";
|
|
9
|
+
/**
|
|
10
|
+
* A decline in the fixed text Codex or Gemini CLI writes for a call the operator refused: Codex's `exec command rejected by user` and `patch rejected by user` (events.rs:456-457), and Gemini's `[Operation Cancelled]`
|
|
11
|
+
* followed by a user cancel or denial. The text is the whole match, never a guess from other wording; `errorCodeOf` classes the same lines `user-rejected`, and the `user-stop` bucket reads them (EXTRACTOR 15).
|
|
12
|
+
*/
|
|
13
|
+
export declare const DECLINED_RE: RegExp;
|
|
9
14
|
/** An adapter's neutral line naming tools: `offered` and `loaded` are arrays of tool names. */
|
|
10
15
|
export declare const SHAPE_TOOLS_LINE = "oars-tools";
|
|
11
|
-
/** An adapter's neutral line for a turn the operator cut short, for a harness whose record is not a Claude marker line. */
|
|
12
|
-
export declare const SHAPE_INTERRUPT_LINE = "oars-interrupt";
|
|
13
16
|
export type TrustEventKind = 'interrupt' | 'rejection' | 'denial';
|
|
14
17
|
/** One stop, with the tool in flight (null when none is known) and the prompt it fell in (null before the first). */
|
|
15
18
|
export interface TrustEvent {
|
|
@@ -4,14 +4,19 @@
|
|
|
4
4
|
// here is projected or uploaded.
|
|
5
5
|
//
|
|
6
6
|
// One collector per transcript, fed line by line by `captureSession`. It reads the Claude Code line shapes (a census of 2.1.312 to 2.1.318) and
|
|
7
|
-
//
|
|
7
|
+
// one neutral line an adapter may write for a harness whose records are not Claude-shaped (`oars-tools`).
|
|
8
8
|
const isObj = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
9
9
|
const str = (value) => (typeof value === 'string' ? value : undefined);
|
|
10
10
|
const names = (value) => (Array.isArray(value) ? value.filter((item) => typeof item === 'string' && item !== '') : []);
|
|
11
11
|
/** The harness's tool-rejection results, both worded for the operator declining: a classifier or hook denial is policy, and the model carries on. */
|
|
12
12
|
export const REJECTED_RE = /^The user doesn't want to (?:proceed with this tool use|take this action)/;
|
|
13
|
-
/** The harness writes this as a user line when the operator cut a turn short; a rejection is followed by it too, so it is not a second interrupt. */
|
|
13
|
+
/** The harness writes this as a user line when the operator cut a turn short; a rejection is followed by it too, so it is not a second interrupt. The Codex reader writes it for a `turn_aborted` with reason `interrupted` (CAPTURE 23). */
|
|
14
14
|
export const INTERRUPT_MARKER = '[Request interrupted by user';
|
|
15
|
+
/**
|
|
16
|
+
* A decline in the fixed text Codex or Gemini CLI writes for a call the operator refused: Codex's `exec command rejected by user` and `patch rejected by user` (events.rs:456-457), and Gemini's `[Operation Cancelled]`
|
|
17
|
+
* followed by a user cancel or denial. The text is the whole match, never a guess from other wording; `errorCodeOf` classes the same lines `user-rejected`, and the `user-stop` bucket reads them (EXTRACTOR 15).
|
|
18
|
+
*/
|
|
19
|
+
export const DECLINED_RE = /^(?:(?:exec command|patch) rejected by user\s*$|\[Operation Cancelled\] (?:Reason: )?(?:User (?:cancelled|denied)|Operation cancelled by user))/;
|
|
15
20
|
/** A policy refusal: the permission rules or the auto-mode classifier (`Permission ... has been denied`), or a hook (`PreToolUse:Bash hook error`). Not the human's. */
|
|
16
21
|
const DENIED_RE = /^(?:Permission (?:to use .{1,200}?|for this action) has been denied|[A-Za-z]+:[^\s]+ hook error|Hook [A-Za-z]+:[^\s]+ denied)/;
|
|
17
22
|
/** A plugin's MCP server names its tools `mcp__plugin_<plugin>_<server>__<tool>`; that is the only evidence of an installed plugin read here.
|
|
@@ -21,8 +26,6 @@ const PLUGIN_TOOL_RE = /^mcp__plugin_([A-Za-z0-9-]+)_/;
|
|
|
21
26
|
const STOP_HOOK_RE = /^Stop(?::|$)/;
|
|
22
27
|
/** An adapter's neutral line naming tools: `offered` and `loaded` are arrays of tool names. */
|
|
23
28
|
export const SHAPE_TOOLS_LINE = 'oars-tools';
|
|
24
|
-
/** An adapter's neutral line for a turn the operator cut short, for a harness whose record is not a Claude marker line. */
|
|
25
|
-
export const SHAPE_INTERRUPT_LINE = 'oars-interrupt';
|
|
26
29
|
/** The permission modes each harness documents; any other value is stored as `other` (spec row `permissionMode`). Keyed by harness so a new one must add its entry. */
|
|
27
30
|
const PERMISSION_MODES = {
|
|
28
31
|
'claude-code': new Set(['default', 'acceptEdits', 'plan', 'auto', 'dontAsk', 'bypassPermissions']),
|
|
@@ -35,6 +38,12 @@ const PERMISSION_MODES = {
|
|
|
35
38
|
cursor: new Set(),
|
|
36
39
|
// Grok Build's reader reads no mode (its `current_mode_update` carries one, but this reader does not read it), so no value is documented to keep.
|
|
37
40
|
grok: new Set(),
|
|
41
|
+
// OpenCode's reader reads no permission mode (a session records the agent, not a mode), so no value is documented to keep.
|
|
42
|
+
opencode: new Set(),
|
|
43
|
+
// Pi has no permission modes: its tools run as the user, and a session records none.
|
|
44
|
+
pi: new Set(),
|
|
45
|
+
// A trace carries no permission mode.
|
|
46
|
+
otel: new Set(),
|
|
38
47
|
};
|
|
39
48
|
/**
|
|
40
49
|
* Which trust counts a harness's transcript records completely. A zero is stored only where the harness would have shown the event; where it would not,
|
|
@@ -52,6 +61,12 @@ const COMPLETE = {
|
|
|
52
61
|
cursor: { interrupts: false, rejections: false, denials: false, timing: false },
|
|
53
62
|
// Grok Build's reader writes the prompts and replies with their line times (seconds), and reads no cancel, tool refusal, or hook denial.
|
|
54
63
|
grok: { interrupts: false, rejections: false, denials: false, timing: true },
|
|
64
|
+
// OpenCode's reader writes each message with its own time (so the runs are real) and reads no interrupt, refusal, or denial: a refused or aborted tool is an error state with a message.
|
|
65
|
+
opencode: { interrupts: false, rejections: false, denials: false, timing: true },
|
|
66
|
+
// Pi's reader writes the prompts and the responses with their entry times, and reads no interrupt, refusal, or denial: an aborted call is a tool error and no more.
|
|
67
|
+
pi: { interrupts: false, rejections: false, denials: false, timing: true },
|
|
68
|
+
// A trace's spans carry their own start and end times; it records no interrupt, refusal, or denial count.
|
|
69
|
+
otel: { interrupts: false, rejections: false, denials: false, timing: true },
|
|
55
70
|
};
|
|
56
71
|
const firstText = (content) => {
|
|
57
72
|
if (typeof content === 'string')
|
|
@@ -217,8 +232,6 @@ export const createShape = (harness, stored, options) => {
|
|
|
217
232
|
offer(names(line.offered));
|
|
218
233
|
load(names(line.loaded));
|
|
219
234
|
}
|
|
220
|
-
else if (type === SHAPE_INTERRUPT_LINE)
|
|
221
|
-
interrupt(inFlight(), at.prompt);
|
|
222
235
|
else if (type === 'attachment') {
|
|
223
236
|
if (isObj(line.attachment))
|
|
224
237
|
attachment(line.attachment);
|
package/dist/sessions/sources.js
CHANGED
|
@@ -7,6 +7,9 @@ import { copilotRoot, openCopilotStore } from './copilot.js';
|
|
|
7
7
|
import { cursorRoot, openCursorStore } from './cursor.js';
|
|
8
8
|
import { grokRoot, openGrokStore } from './grok.js';
|
|
9
9
|
import { geminiRoot, openGeminiStore } from './gemini.js';
|
|
10
|
+
import { openPiStore, piRoot } from './pi.js';
|
|
11
|
+
import { openOtelStore, otelRoot } from './otel.js';
|
|
12
|
+
import { opencodeRoot, openOpencodeStore } from './opencode.js';
|
|
10
13
|
import { openVscodeCopilotStore, vscodeAppData } from './vscode-copilot.js';
|
|
11
14
|
/** A session handed back to its own store was listed by it, so a miss is a bug in the caller. */
|
|
12
15
|
const need = (value) => {
|
|
@@ -132,6 +135,54 @@ const grok = {
|
|
|
132
135
|
};
|
|
133
136
|
},
|
|
134
137
|
};
|
|
138
|
+
const opencode = {
|
|
139
|
+
name: 'opencode',
|
|
140
|
+
harness: 'opencode',
|
|
141
|
+
label: 'OpenCode',
|
|
142
|
+
root: (_platform, env, home) => opencodeRoot(env, home),
|
|
143
|
+
storeFile: (root) => join(root, 'opencode.db'),
|
|
144
|
+
open: async (root) => {
|
|
145
|
+
const store = await openOpencodeStore(root);
|
|
146
|
+
if (store === undefined)
|
|
147
|
+
return undefined;
|
|
148
|
+
const byId = new Map(store.sessions.map((session) => [session.id, session]));
|
|
149
|
+
return {
|
|
150
|
+
sessions: store.sessions.map((session) => ({
|
|
151
|
+
id: session.id,
|
|
152
|
+
folder: session.folder,
|
|
153
|
+
size: session.size,
|
|
154
|
+
mtimeMs: session.mtimeMs,
|
|
155
|
+
...(session.parent === null ? {} : { subagent: { parent: `opencode-${session.parent}`, agentType: session.agent, spawnedBy: null } }),
|
|
156
|
+
})),
|
|
157
|
+
transcript: (session) => store.transcript(need(byId.get(session.id))),
|
|
158
|
+
rejected: store.rejected,
|
|
159
|
+
unreadable: [],
|
|
160
|
+
where: () => join(root, 'opencode.db'),
|
|
161
|
+
close: store.close,
|
|
162
|
+
};
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
const pi = {
|
|
166
|
+
name: 'pi',
|
|
167
|
+
harness: 'pi',
|
|
168
|
+
label: 'Pi',
|
|
169
|
+
root: (_platform, env, home) => piRoot(env, home),
|
|
170
|
+
storeFile: (root) => join(root, 'sessions'),
|
|
171
|
+
open: async (root) => {
|
|
172
|
+
const store = await openPiStore(root);
|
|
173
|
+
if (store === undefined)
|
|
174
|
+
return undefined;
|
|
175
|
+
const byId = new Map(store.sessions.map((session) => [session.id, session]));
|
|
176
|
+
return {
|
|
177
|
+
sessions: store.sessions.map((session) => ({ id: session.id, folder: session.folder, size: session.size, mtimeMs: session.mtimeMs })),
|
|
178
|
+
transcript: async (session) => await store.transcript(need(byId.get(session.id))),
|
|
179
|
+
rejected: store.rejected,
|
|
180
|
+
unreadable: store.unreadable,
|
|
181
|
+
where: (session) => byId.get(session.id)?.file ?? root,
|
|
182
|
+
close: () => undefined,
|
|
183
|
+
};
|
|
184
|
+
},
|
|
185
|
+
};
|
|
135
186
|
const vscodeCopilot = {
|
|
136
187
|
name: 'vscode-copilot',
|
|
137
188
|
harness: 'vscode-copilot',
|
|
@@ -153,7 +204,30 @@ const vscodeCopilot = {
|
|
|
153
204
|
};
|
|
154
205
|
},
|
|
155
206
|
};
|
|
207
|
+
// A folder of OTLP JSON trace files (`$OARS_OTEL_TRACES`, else the config folder's `otel-traces`): a configured folder rather than an `import` command, so the cursor, the
|
|
208
|
+
// allowlist, the nightly run, and the fail-open reads are the ones every other source has.
|
|
209
|
+
const otel = {
|
|
210
|
+
name: 'otel',
|
|
211
|
+
harness: 'otel',
|
|
212
|
+
label: 'OpenTelemetry GenAI',
|
|
213
|
+
root: (platform, env, home) => otelRoot(env, home, platform),
|
|
214
|
+
storeFile: (root) => root,
|
|
215
|
+
open: async (root) => {
|
|
216
|
+
const store = await openOtelStore(root);
|
|
217
|
+
if (store === undefined)
|
|
218
|
+
return undefined;
|
|
219
|
+
const byId = new Map(store.sessions.map((session) => [session.id, session]));
|
|
220
|
+
return {
|
|
221
|
+
sessions: store.sessions.map((session) => ({ id: session.id, folder: session.folder, size: session.size, mtimeMs: session.mtimeMs, ...(session.subagent === undefined ? {} : { subagent: session.subagent }) })),
|
|
222
|
+
transcript: (session) => store.transcript(need(byId.get(session.id))),
|
|
223
|
+
rejected: store.rejected,
|
|
224
|
+
unreadable: store.unreadable,
|
|
225
|
+
where: (session) => byId.get(session.id)?.file ?? root,
|
|
226
|
+
close: () => undefined,
|
|
227
|
+
};
|
|
228
|
+
},
|
|
229
|
+
};
|
|
156
230
|
/** Every non-Claude source, in the order the deposit reads them. */
|
|
157
|
-
export const SOURCES = [cursor, codex, copilot, gemini, grok, vscodeCopilot];
|
|
231
|
+
export const SOURCES = [cursor, codex, copilot, gemini, grok, opencode, pi, vscodeCopilot, otel];
|
|
158
232
|
/** How a source is named in a deposit line; a name the table does not know (a test's) is its own label. */
|
|
159
233
|
export const sourceLabel = (name, table = SOURCES) => table.find((source) => source.name === name)?.label ?? name;
|
package/dist/sessions/store.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export interface SessionsRuntime {
|
|
|
13
13
|
projectsRoot: string;
|
|
14
14
|
/** Cursor's `User` folder (`cursorRoot` in cursor.ts), kept as the alias of `sourceRoots.cursor`; absent means no Cursor source. */
|
|
15
15
|
cursorRoot?: string;
|
|
16
|
-
/** Where each non-Claude source's store lives, by `Source.name` (sources.ts); a source with no root is not read
|
|
16
|
+
/** Where each non-Claude source's store lives, by `Source.name` (sources.ts); a source with no root is not read; one with a root is read unless the allowlist names other entries only or denies it. */
|
|
17
17
|
sourceRoots?: {
|
|
18
18
|
[source: string]: string;
|
|
19
19
|
};
|
|
@@ -52,7 +52,17 @@ export interface State {
|
|
|
52
52
|
redactDomains: string[];
|
|
53
53
|
/** Every `--allow` entry any deposit used (folders, prefixes, globs): the SessionEnd hook deposits a session only from a folder these name. */
|
|
54
54
|
allow: string[];
|
|
55
|
+
/** What the person chose about sending to a pool: the raw-copy consent and the folders switched down. Never read by capture. */
|
|
56
|
+
pool: PoolChoices;
|
|
55
57
|
}
|
|
58
|
+
/** The answer to the raw-copy prompt (`granted`, `declined`, or not asked yet) and the per-folder switches (an `--allow`-style entry mapped to what it may send). */
|
|
59
|
+
export interface PoolChoices {
|
|
60
|
+
rawConsent: 'granted' | 'declined' | null;
|
|
61
|
+
folders: {
|
|
62
|
+
[entry: string]: FolderSend;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export type FolderSend = 'skeleton' | 'off';
|
|
56
66
|
export declare const paths: (storeDir: string) => {
|
|
57
67
|
observations: string;
|
|
58
68
|
state: string;
|
|
@@ -77,6 +87,8 @@ export declare const sidOf: (salt: string) => string;
|
|
|
77
87
|
*/
|
|
78
88
|
export declare const localSalt: (storeDir: string) => Promise<AccountSalt>;
|
|
79
89
|
export declare const readState: (storeDir: string) => Promise<State>;
|
|
90
|
+
/** Changes the pool choices under the store lock, so a deposit running at the same time keeps them (it writes the state it read). */
|
|
91
|
+
export declare const updatePoolChoices: (storeDir: string, change: (choices: PoolChoices) => void) => Promise<PoolChoices>;
|
|
80
92
|
export declare const readRedactDomains: (storeDir: string) => Promise<string[]>;
|
|
81
93
|
/** The `--allow` entries and `--redact-domain` set earlier deposits used; the nightly run repeats them. */
|
|
82
94
|
export declare const readSaved: (storeDir: string) => Promise<{
|
package/dist/sessions/store.js
CHANGED
|
@@ -4,7 +4,7 @@ import { performance } from 'node:perf_hooks';
|
|
|
4
4
|
import { appendFile, mkdir, open, readdir, readFile, rename, rm, stat, utimes, writeFile } from 'node:fs/promises';
|
|
5
5
|
import { basename, dirname, join, resolve } from 'node:path';
|
|
6
6
|
import { CliError, ExitCode } from '../errors.js';
|
|
7
|
-
import { compileAllow,
|
|
7
|
+
import { compileAllow, mergeAllow, runAllow, slugOf } from './allow.js';
|
|
8
8
|
import { SOURCES } from './sources.js';
|
|
9
9
|
import { buildIndex, indexPath } from './hook-index.js';
|
|
10
10
|
import { archiveRaw } from './archive.js';
|
|
@@ -74,15 +74,30 @@ export const readState = async (storeDir) => {
|
|
|
74
74
|
}
|
|
75
75
|
const domains = Array.isArray(parsed.redactDomains) ? parsed.redactDomains.filter((entry) => typeof entry === 'string') : [];
|
|
76
76
|
const allow = Array.isArray(parsed.allow) ? parsed.allow.filter((entry) => typeof entry === 'string') : [];
|
|
77
|
-
|
|
77
|
+
const pool = isRecord(parsed.pool) ? parsed.pool : {};
|
|
78
|
+
const folders = {};
|
|
79
|
+
if (isRecord(pool.folders))
|
|
80
|
+
for (const [entry, mode] of Object.entries(pool.folders))
|
|
81
|
+
if (mode === 'skeleton' || mode === 'off')
|
|
82
|
+
folders[entry] = mode;
|
|
83
|
+
return { files, redactDomains: domains, allow, pool: { rawConsent: pool.rawConsent === 'granted' || pool.rawConsent === 'declined' ? pool.rawConsent : null, folders } };
|
|
78
84
|
}
|
|
79
85
|
}
|
|
80
86
|
catch (error) {
|
|
81
87
|
if (!isMissing(error) && !(error instanceof SyntaxError))
|
|
82
88
|
throw error;
|
|
83
89
|
}
|
|
84
|
-
return { files: {}, redactDomains: [], allow: [] };
|
|
90
|
+
return { files: {}, redactDomains: [], allow: [], pool: { rawConsent: null, folders: {} } };
|
|
85
91
|
};
|
|
92
|
+
/** Changes the pool choices under the store lock, so a deposit running at the same time keeps them (it writes the state it read). */
|
|
93
|
+
export const updatePoolChoices = (storeDir, change) => withLock(storeDir, async (held) => {
|
|
94
|
+
const state = await readState(storeDir);
|
|
95
|
+
change(state.pool);
|
|
96
|
+
if (!(await held()))
|
|
97
|
+
throw new StoreLockLostError();
|
|
98
|
+
await writeAtomic(paths(storeDir).state, JSON.stringify(state));
|
|
99
|
+
return state.pool;
|
|
100
|
+
});
|
|
86
101
|
export const readRedactDomains = async (storeDir) => (await readState(storeDir)).redactDomains;
|
|
87
102
|
/** The `--allow` entries and `--redact-domain` set earlier deposits used; the nightly run repeats them. */
|
|
88
103
|
export const readSaved = async (storeDir) => {
|
|
@@ -385,14 +400,17 @@ export const refresh = async (runtime, redactDomains, at, trace, held = () => Pr
|
|
|
385
400
|
}
|
|
386
401
|
// The pool is derived for the index's fit reads only: one that cannot be read or written costs the index those, never the index itself (`currentPool` says so on stderr). Loaded here, not at the top: the pool imports every read family, and a lookup hook never reaches this step. It runs before the clock starts, so the index's `ms` is the index and not the derive (the first pool run takes 12 to 17 s on a store copy).
|
|
387
402
|
// A module that will not load costs the index the same fit reads, with one line on stderr, as `currentPool` does when the pool cannot be built.
|
|
388
|
-
const
|
|
403
|
+
const current = await import('./pool.js').then(async ({ currentPool }) => currentPool(runtime.storeDir, store), (error) => {
|
|
389
404
|
process.stderr.write(`oars: the read pool module could not be loaded, and the index keeps no fit reads: ${error instanceof Error ? error.message : String(error)}
|
|
390
405
|
`);
|
|
391
|
-
return
|
|
406
|
+
return undefined;
|
|
392
407
|
});
|
|
408
|
+
const reads = current?.reads ?? [];
|
|
393
409
|
const indexStarted = performance.now();
|
|
394
410
|
try {
|
|
395
|
-
|
|
411
|
+
// The next-node reads for each harness and version, derived over the same pool; a deposit that has no pool leaves the index without them.
|
|
412
|
+
const next = current === undefined ? undefined : (await import('./recall-frames.js')).nextByFrame(current.pool, store);
|
|
413
|
+
const index = buildIndex(store, redactDomains, at, reads, next);
|
|
396
414
|
await requireHold(held);
|
|
397
415
|
await writeAtomic(indexPath(runtime.storeDir), JSON.stringify(index));
|
|
398
416
|
if (trace !== undefined) {
|
|
@@ -529,32 +547,32 @@ export const withLock = async (storeDir, work, options = { waitMs: 10_000, stale
|
|
|
529
547
|
}
|
|
530
548
|
};
|
|
531
549
|
export const deposit = (runtime, options, trace) => withLock(runtime.storeDir, (holds) => depositLocked(runtime, options, holds, trace));
|
|
532
|
-
/**
|
|
550
|
+
/** Throws `StoreLockLostError` when this run's token no longer stands. */
|
|
533
551
|
const requireHold = async (held) => {
|
|
534
552
|
if (!(await held()))
|
|
535
553
|
throw new StoreLockLostError();
|
|
536
554
|
};
|
|
555
|
+
/** Writes `state.json` only while this run holds the lock; a run that lost it throws before the write, so the holder that took over keeps its entries. */
|
|
537
556
|
const writeStateHeld = async (runtime, state, held) => {
|
|
538
|
-
|
|
539
|
-
throw new StoreLockLostError();
|
|
557
|
+
await requireHold(held);
|
|
540
558
|
await writeAtomic(paths(runtime.storeDir).state, JSON.stringify(state));
|
|
541
559
|
};
|
|
542
560
|
const depositLocked = async (runtime, options, holds, trace) => {
|
|
543
|
-
|
|
544
|
-
const
|
|
561
|
+
const state = await readState(runtime.storeDir);
|
|
562
|
+
const entries = runAllow(state.allow, options.allow);
|
|
563
|
+
const allow = compileAllow(entries);
|
|
545
564
|
const folders = await projectFolders(runtime.projectsRoot);
|
|
546
565
|
const allowed = folders.filter((name) => allow.allows(name));
|
|
547
566
|
const roots = sourceRoots(runtime);
|
|
548
567
|
const table = sourcesOf(runtime);
|
|
549
568
|
const missing = allow.unmatched([...folders, ...table.filter((source) => roots.has(source.name)).map((source) => source.name)]);
|
|
550
|
-
const state = await readState(runtime.storeDir);
|
|
551
569
|
refuseNewer(state);
|
|
552
570
|
// A log written by a newer oars is refused even when state.json is older or lost; the Stop-hook path reads only state.json.
|
|
553
571
|
const held = (await scanLog(paths(runtime.storeDir).observations)).newerCaptures;
|
|
554
572
|
if (held > 0)
|
|
555
573
|
throw new CliError(`The log holds captures by a newer oars (sessions: ${String(held)}); upgrade oars.`, ExitCode.UNEXPECTED, 'unexpected');
|
|
556
574
|
state.redactDomains = [...new Set([...state.redactDomains, ...options.redactDomains])];
|
|
557
|
-
state.allow =
|
|
575
|
+
state.allow = mergeAllow(state.allow, options.allow);
|
|
558
576
|
const context = await contextFor(runtime, state, options.now, holds);
|
|
559
577
|
const result = {
|
|
560
578
|
sessions: 0,
|
|
@@ -871,7 +889,8 @@ const rawOnlyTranscripts = async (context, project, onDisk) => {
|
|
|
871
889
|
// Listed from raw/ itself, so a lost state.json (no entries to name the files) still finds them; a file with no entry counts as behind.
|
|
872
890
|
const behind = (await rawRelatives(root, [])).filter((relative) => {
|
|
873
891
|
const entry = context.state.files[`${prefix}${relative}`];
|
|
874
|
-
|
|
892
|
+
// A domain named after the capture changes what the redactor masks, so a file recorded under another domain set is behind too.
|
|
893
|
+
return !onDisk.has(`${prefix}${relative}`) && (entry === undefined || entry.v !== CAPTURE_VERSION || entry.s !== context.salt.sid || !sameSet(entry.d, context.domains));
|
|
875
894
|
});
|
|
876
895
|
const found = [];
|
|
877
896
|
for (const relative of behind) {
|
|
@@ -1214,6 +1233,8 @@ const writeLive = async (file, temp, latest) => {
|
|
|
1214
1233
|
*/
|
|
1215
1234
|
export const compactLog = async (storeDir, renameWaitMs = LOG_RENAME_WAIT_MS, held = () => Promise.resolve(true)) => {
|
|
1216
1235
|
const file = paths(storeDir).observations;
|
|
1236
|
+
// A run that lost the lock must not sweep a temp file the later holder is writing.
|
|
1237
|
+
await requireHold(held);
|
|
1217
1238
|
// Under the lock no other compaction is running, so any temp file here is one a crash left.
|
|
1218
1239
|
for (const entry of await listDir(storeDir)) {
|
|
1219
1240
|
if (entry.name.startsWith(`${basename(file)}.`) && entry.name.endsWith('.tmp'))
|
|
@@ -1230,8 +1251,7 @@ export const compactLog = async (storeDir, renameWaitMs = LOG_RENAME_WAIT_MS, he
|
|
|
1230
1251
|
if ((await stat(file)).size !== size)
|
|
1231
1252
|
throw new Error('the log changed while it was being rewritten');
|
|
1232
1253
|
// The size check cannot see a run that took a broken lock and has not appended yet; its token can.
|
|
1233
|
-
|
|
1234
|
-
throw new StoreLockLostError();
|
|
1254
|
+
await requireHold(held);
|
|
1235
1255
|
await renameWhenFree(temp, file, renameWaitMs);
|
|
1236
1256
|
}
|
|
1237
1257
|
finally {
|
|
@@ -15,6 +15,8 @@ export type SurvivalCounts = {
|
|
|
15
15
|
/** Runs one read-only git command in a repository and answers its stdout; rejects on any failure. */
|
|
16
16
|
export type Git = (root: string, args: readonly string[], stdin?: string) => Promise<string>;
|
|
17
17
|
export declare const survivalFile: (storeDir: string) => string;
|
|
18
|
+
export declare const runGit: Git;
|
|
19
|
+
export declare const cwdOfProject: (runtime: Pick<SessionsRuntime, "projectsRoot" | "storeDir">, project: string, sessions: ReadonlySet<string>) => Promise<string | undefined>;
|
|
18
20
|
/** The latest state of each sha. The file only adds to what `books` shows, so a missing or unreadable one reads as nothing observed yet. */
|
|
19
21
|
export declare const readSurvival: (storeDir: string) => Promise<Map<string, SurvivalState>>;
|
|
20
22
|
/**
|