@manyoars/oars 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +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
package/dist/sessions/derive.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// version-2 extractor line for line (error_signatures.py, prompt_graphs.py, cost_per_class.py), and the recovery episodes match
|
|
4
4
|
// recovery_episodes.py, so the numbers measured before the split reproduce after it.
|
|
5
5
|
import { captureSession, classify, compress, cut, EDITS, EXPLORE, ACT, normalize, SHELL, shortName } from './extract.js';
|
|
6
|
-
import { INTERRUPT_MARKER, REJECTED_RE } from './session-shape.js';
|
|
6
|
+
import { DECLINED_RE, INTERRUPT_MARKER, REJECTED_RE } from './session-shape.js';
|
|
7
7
|
import { ERROR_EXAMPLE_CHARS, EXTRACTOR_VERSION, } from './schema.js';
|
|
8
8
|
const byPosition = (a, b) => a.n - b.n;
|
|
9
9
|
/** The `<tool> | <normalized line>` key; the line was redacted at capture, and normalize can expose a shape the first pass missed. */
|
|
@@ -109,9 +109,15 @@ const stoppedOf = (errors, nextHead) => {
|
|
|
109
109
|
return 'rejected';
|
|
110
110
|
return nextHead?.startsWith(INTERRUPT_MARKER) === true ? 'interrupt' : null;
|
|
111
111
|
};
|
|
112
|
-
/**
|
|
113
|
-
const
|
|
112
|
+
/** A decline in the fixed text Codex or Gemini CLI writes (`DECLINED_RE`). It is read for `intervention` only: `stopped` stays as the books and the gold arms read it. */
|
|
113
|
+
const declinedIn = (errors) => errors.some((error) => DECLINED_RE.test(error.line));
|
|
114
|
+
/**
|
|
115
|
+
* The prompts whose edit a later prompt repeated, and of those the ones the very next prompt repeated (`immediate`, the `redo` bucket of `intervention`). One walk in call order: a commit settles every path
|
|
116
|
+
* edited so far, so it empties the map. `nextOf` gives a prompt's next record.
|
|
117
|
+
*/
|
|
118
|
+
const redonePrompts = (calls, nextOf) => {
|
|
114
119
|
const redone = new Set();
|
|
120
|
+
const immediate = new Set();
|
|
115
121
|
const lastEditor = new Map();
|
|
116
122
|
for (const call of calls) {
|
|
117
123
|
if (commitsIn(call) > 0)
|
|
@@ -120,10 +126,59 @@ const redonePrompts = (calls) => {
|
|
|
120
126
|
const earlier = lastEditor.get(call.target);
|
|
121
127
|
if (earlier !== undefined && earlier !== call.prompt && call.prompt - earlier <= REDO_WINDOW)
|
|
122
128
|
redone.add(earlier);
|
|
129
|
+
if (earlier !== undefined && nextOf.get(earlier) === call.prompt)
|
|
130
|
+
immediate.add(earlier);
|
|
123
131
|
lastEditor.set(call.target, call.prompt);
|
|
124
132
|
}
|
|
125
133
|
}
|
|
126
|
-
return redone;
|
|
134
|
+
return { redone, immediate };
|
|
135
|
+
};
|
|
136
|
+
/**
|
|
137
|
+
* A segment that undoes work in git: a revert that names a commit (a bare `git revert` is `--abort`, `--continue`, or `--quit`, whose flag the segment drops), a restore, or a bare checkout
|
|
138
|
+
* (the segment drops a path, a flag, and a word with a slash or quotes, so `git checkout -- <path>` and `git checkout feature/x` are the same bare word pair; `isGitUndo` narrows it by the step's flags).
|
|
139
|
+
*/
|
|
140
|
+
const GIT_UNDO = /^git (?:revert .|restore(?: |$))|^git checkout(?: \.)?$/;
|
|
141
|
+
const BARE_CHECKOUT = /^git checkout$/;
|
|
142
|
+
/** Flags of a `git checkout` that move to or create a branch, or leave one with a literal `-`: none of them undoes work. `<literal>` is also what the step makes of any flag off the public list (`--theirs`, `--patch`), so those read as no undo too: a limit, pinned in `intervention.test.ts`. */
|
|
143
|
+
const CHECKOUT_MOVES = new Set(['<literal>', '-b', '-B', '--orphan', '--detach']);
|
|
144
|
+
/** A prompt that begins by asking for the undo, in English: `revert that`, `please undo`, `rollback`. */
|
|
145
|
+
const ASKS_UNDO = /^(?:please )?(?:revert|undo|roll ?back)\b/i;
|
|
146
|
+
/** The capture version that keeps a `git checkout` step's `target` class. */
|
|
147
|
+
const TARGET_CLASS_CAPTURE = 23;
|
|
148
|
+
/** Whether the segment at index `at` is a checkout that moves to a branch: the step with the same `seg` holds the flags that the segment drops (the stored step no longer holds `run`). */
|
|
149
|
+
const movesBranch = (tool, at) => {
|
|
150
|
+
const steps = tool.steps?.filter(({ seg }) => seg === at).map(({ identity }) => identity).filter((identity) => identity !== null && 'program' in identity) ?? [];
|
|
151
|
+
// From CAPTURE 23 the step names the class of what the checkout acts on: a proved path is an undo whatever the flags, and an unproved word is never one. Before it, only the flags tell.
|
|
152
|
+
if (Number(tool.cv) >= TARGET_CLASS_CAPTURE) {
|
|
153
|
+
if (steps.some((identity) => identity.target === 'path'))
|
|
154
|
+
return false;
|
|
155
|
+
if (steps.some((identity) => identity.target === 'word'))
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
return steps.some((identity) => identity.flags.some((flag) => CHECKOUT_MOVES.has(flag)));
|
|
159
|
+
};
|
|
160
|
+
/** A clean shell call that undid work in git. `git reset --hard` is read from the step, since a segment keeps no flag. */
|
|
161
|
+
const isGitUndo = (tool) => SHELL.has(tool.tool) &&
|
|
162
|
+
tool.status === 'ok' &&
|
|
163
|
+
(tool.segments?.some(({ run }, at) => GIT_UNDO.test(run) && !(BARE_CHECKOUT.test(run) && movesBranch(tool, at))) === true ||
|
|
164
|
+
tool.steps?.some(({ identity }) => identity !== null && 'program' in identity && identity.program === 'git' && identity.sub === 'reset' && identity.flags.includes('--hard')) === true);
|
|
165
|
+
/**
|
|
166
|
+
* What the next prompt `next` did to the work of a prompt (docs/sessions-store.md, "Prompt intervention"). First match: the last prompt or the marker's own record is `unknown`; then a stop (`stopped` set) is `user-stop`; then `revert`, `redo`,
|
|
167
|
+
* `after-error`; then `unknown` again when the next prompt ran a shell call captured before `segments`, which could have been an undo; else `none`.
|
|
168
|
+
*/
|
|
169
|
+
const interventionOf = (prompt, stopped, next, settled, nextCalls, immediate) => {
|
|
170
|
+
if (next === undefined || prompt.head.startsWith(INTERRUPT_MARKER))
|
|
171
|
+
return 'unknown';
|
|
172
|
+
// A stop is read before the others: a rejected call is settled as an error, so `after-error` would otherwise claim every rejection.
|
|
173
|
+
if (stopped)
|
|
174
|
+
return 'user-stop';
|
|
175
|
+
if (settled.some(isEdit) && (nextCalls.some(isGitUndo) || ASKS_UNDO.test(next.head.trimStart())))
|
|
176
|
+
return 'revert';
|
|
177
|
+
if (immediate)
|
|
178
|
+
return 'redo';
|
|
179
|
+
if (settled.at(-1)?.status === 'error')
|
|
180
|
+
return 'after-error';
|
|
181
|
+
return nextCalls.some((tool) => SHELL.has(tool.tool) && tool.segments === undefined) ? 'unknown' : 'none';
|
|
127
182
|
};
|
|
128
183
|
/** The version-2 prompt and error records of one transcript, with the outcome signals. */
|
|
129
184
|
export const deriveSession = (capture, redact) => {
|
|
@@ -136,13 +191,21 @@ export const deriveSession = (capture, redact) => {
|
|
|
136
191
|
const stopsOf = groupBy(firstCopies(capture.errorLines), (error) => error.prompt);
|
|
137
192
|
const calls = firstCopies(tools);
|
|
138
193
|
const settledOf = groupBy(calls, (tool) => tool.prompt);
|
|
139
|
-
const
|
|
194
|
+
const nextOf = new Map();
|
|
195
|
+
for (let at = 1; at < capture.prompts.length; at++) {
|
|
196
|
+
const before = capture.prompts[at - 1];
|
|
197
|
+
const after = capture.prompts[at];
|
|
198
|
+
if (before !== undefined && after !== undefined)
|
|
199
|
+
nextOf.set(before.index, after.index);
|
|
200
|
+
}
|
|
201
|
+
const { redone, immediate } = redonePrompts(calls, nextOf);
|
|
140
202
|
const prompts = capture.prompts.map((prompt, position) => {
|
|
141
203
|
const mine = toolsOf.get(prompt.index) ?? [];
|
|
142
204
|
const main = mine.filter((tool) => !tool.side);
|
|
143
205
|
const settled = settledOf.get(prompt.index) ?? [];
|
|
144
206
|
const sequence = main.map((tool) => shortName(tool.tool));
|
|
145
207
|
const errors = errorsOf.get(prompt.index) ?? [];
|
|
208
|
+
const stopped = stoppedOf(stopsOf.get(prompt.index) ?? [], capture.prompts[position + 1]?.head);
|
|
146
209
|
// The two main-line calls after the prompt's first main-line error (version 2 re-armed on each error and kept the first two).
|
|
147
210
|
const first = errors[0];
|
|
148
211
|
const recovery = first === undefined ? [] : main.filter((tool) => tool.n >= first.after).slice(0, 2).map((tool) => shortName(tool.tool));
|
|
@@ -171,6 +234,8 @@ export const deriveSession = (capture, redact) => {
|
|
|
171
234
|
efforts: prompt.efforts,
|
|
172
235
|
skills: prompt.skills,
|
|
173
236
|
usage: prompt.usage,
|
|
237
|
+
...(prompt.usageObserved === undefined ? {} : { usageObserved: prompt.usageObserved }),
|
|
238
|
+
...(prompt.usageResponses === undefined ? {} : { usageResponses: prompt.usageResponses }),
|
|
174
239
|
recovery,
|
|
175
240
|
explore: sequence.filter((tool) => EXPLORE.has(tool)).length,
|
|
176
241
|
strata: prompt.strata,
|
|
@@ -178,9 +243,10 @@ export const deriveSession = (capture, redact) => {
|
|
|
178
243
|
bigrams,
|
|
179
244
|
contextFingerprint: prompt.promptFingerprint,
|
|
180
245
|
verify: verifyOf(settled),
|
|
181
|
-
stopped
|
|
246
|
+
stopped,
|
|
182
247
|
commits: settled.reduce((sum, tool) => sum + commitsIn(tool), 0),
|
|
183
248
|
redo: redone.has(prompt.index),
|
|
249
|
+
intervention: interventionOf(prompt, stopped !== null || declinedIn(stopsOf.get(prompt.index) ?? []), capture.prompts[position + 1], settled, settledOf.get(capture.prompts[position + 1]?.index ?? -1) ?? [], immediate.has(prompt.index)),
|
|
184
250
|
...(prompt.frame === undefined ? {} : { frame: prompt.frame }),
|
|
185
251
|
};
|
|
186
252
|
});
|
|
@@ -22,6 +22,12 @@ export interface Verdict {
|
|
|
22
22
|
source: Source;
|
|
23
23
|
}[];
|
|
24
24
|
}
|
|
25
|
+
/** The transcript files of one session, the main one first, one per path under the project folder (the harness's copy before oars' raw archive's): what a raw copy is built from, after the gate has cleared them. */
|
|
26
|
+
export declare const transcriptFilesOf: (runtime: SessionsRuntime, project: string, session: string) => Promise<{
|
|
27
|
+
key: string;
|
|
28
|
+
path: string;
|
|
29
|
+
main: boolean;
|
|
30
|
+
}[]>;
|
|
25
31
|
/**
|
|
26
32
|
* When the newest file of a session was last written, in epoch milliseconds, over the same files the gate scans (the main transcript, its subagents, and oars' raw copies), or 0 when it has none.
|
|
27
33
|
* A transcript that kept growing since the last capture is newer here than in the store, so the deposit command reads it to tell a session still in progress from one that has ended.
|
|
@@ -154,6 +154,14 @@ const filesOf = async (runtime, project, session) => {
|
|
|
154
154
|
const second = await filesIn(raw, 'raw', project, session, files);
|
|
155
155
|
return { files, unreadable: unreadable || first || second };
|
|
156
156
|
};
|
|
157
|
+
/** The transcript files of one session, the main one first, one per path under the project folder (the harness's copy before oars' raw archive's): what a raw copy is built from, after the gate has cleared them. */
|
|
158
|
+
export const transcriptFilesOf = async (runtime, project, session) => {
|
|
159
|
+
const seen = new Map();
|
|
160
|
+
for (const { key, path, main } of (await filesOf(runtime, project, session)).files)
|
|
161
|
+
if (!seen.has(key))
|
|
162
|
+
seen.set(key, { key, path, main });
|
|
163
|
+
return [...seen.values()].sort((a, b) => (a.main === b.main ? (a.key < b.key ? -1 : 1) : a.main ? -1 : 1));
|
|
164
|
+
};
|
|
157
165
|
/** Whether any line of the file holds a term. Streamed, so a large transcript is never held whole. */
|
|
158
166
|
const holdsTerm = async (path, terms) => {
|
|
159
167
|
const stream = createReadStream(path, { encoding: 'utf8' });
|
|
@@ -110,6 +110,7 @@ export declare const pathKey: (path: string, cwd: string | undefined, root: stri
|
|
|
110
110
|
export declare const hashPath: (path: string, cwd: string | undefined, root: string | null, salt: string) => {
|
|
111
111
|
hash: string;
|
|
112
112
|
base: TargetBase;
|
|
113
|
+
key: string;
|
|
113
114
|
};
|
|
114
115
|
/**
|
|
115
116
|
* A shell call's program and subcommand from its first line (`git push`, `just build`, `npm`), as recovery_episodes.py's
|
|
@@ -144,7 +145,7 @@ export interface PlannedCommit {
|
|
|
144
145
|
}
|
|
145
146
|
export interface ShellRead {
|
|
146
147
|
segments: ShellSegment[];
|
|
147
|
-
/** The same commands as `segments`, in public names
|
|
148
|
+
/** The same commands as `segments`, in public names, apart from a pull's package name, which is kept as it is after redaction, with the redacted text they compare by. */
|
|
148
149
|
steps: ShellStepRead[];
|
|
149
150
|
pulls: ToolPull[];
|
|
150
151
|
commitPlan: PlannedCommit[];
|
|
@@ -167,10 +168,11 @@ export declare const commitsRead: (plan: readonly PlannedCommit[], result: strin
|
|
|
167
168
|
export declare const storedToolName: (name: string, redact: Redactor) => string;
|
|
168
169
|
/** An MCP call's full server name, which `shortName` cuts for the tool sequence, redacted because a harness takes it from the operator's config. */
|
|
169
170
|
export declare const pullsOfTool: (name: string, redact: Redactor) => ToolPull[];
|
|
170
|
-
/** The `target` of a tool call (see {@link ToolObservation.target}). */
|
|
171
|
+
/** The `target` of a tool call (see {@link ToolObservation.target}). A path target also returns the `key` it was hashed on, read for its class and then dropped, never stored. */
|
|
171
172
|
export declare const targetOf: (tool: string, input: Obj, cwd: string | undefined, salt: string, redact: Redactor, root?: string | null) => {
|
|
172
173
|
target: string | null;
|
|
173
174
|
base: TargetBase | null;
|
|
175
|
+
key?: string;
|
|
174
176
|
};
|
|
175
177
|
/** The `n` capture gives each call of a transcript, by tool-use id, the first copy of a repeated id winning; a subagent's steps name their parent by it without capturing the parent again. */
|
|
176
178
|
export declare const callPositions: (text: string) => Map<string, number>;
|
package/dist/sessions/extract.js
CHANGED
|
@@ -7,12 +7,13 @@ import { posix } from 'node:path';
|
|
|
7
7
|
import { ACTION_TABLES, actionOf, shellOf } from './actions.js';
|
|
8
8
|
import { exploreCount, strataOf } from './strata.js';
|
|
9
9
|
import { closedFrame, frameIdOf, repoFilesFromIndex, repoSizeOf, stackOf, utcDay } from './frame.js';
|
|
10
|
+
import { argClassOf, targetClassOf } from './call-shape.js';
|
|
10
11
|
import { LITERAL } from './public-vocabulary.js';
|
|
11
12
|
import { linkSteps } from './graph.js';
|
|
12
13
|
import { callIdentity, shellIdentity, UNREAD_SHELL } from './identity.js';
|
|
13
14
|
import { manifestDelta, manifestFileOf, manifestNames, packagesOf } from './package-resolve.js';
|
|
14
|
-
import { createShape, INTERRUPT_MARKER } from './session-shape.js';
|
|
15
|
-
import { CAPTURE_VERSION, ERROR_LINE_CHARS, FILES_PER_SESSION_MAX, HTTP_STATUSES, PROMPT_HEAD_CHARS, SIGNATURE_CHARS, } from './schema.js';
|
|
15
|
+
import { createShape, DECLINED_RE, INTERRUPT_MARKER } from './session-shape.js';
|
|
16
|
+
import { CAPTURE_VERSION, ERROR_LINE_CHARS, FILES_PER_SESSION_MAX, HTTP_STATUSES, PROMPT_HEAD_CHARS, SIGNATURE_CHARS, USAGE_FIELDS, } from './schema.js';
|
|
16
17
|
export const isObj = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
17
18
|
const str = (value) => (typeof value === 'string' ? value : undefined);
|
|
18
19
|
const num = (value) => (typeof value === 'number' && Number.isFinite(value) ? value : undefined);
|
|
@@ -108,7 +109,8 @@ const EXIT_CODE_RE = /^Exit code:? (-?\d+)/;
|
|
|
108
109
|
const HARNESS_ERROR_PATTERNS = [
|
|
109
110
|
[/^\[Request interrupted by user/, 'interrupted'],
|
|
110
111
|
// Claude Code's text, then the refusal sentences Codex (`rejected by user`, written by its adapter and by events.rs:456) and Gemini (an abort `User cancelled tool execution`; a confirmation-prompt denial `Reason: User denied execution.`, `User cancelled operation`, or `Operation cancelled by user`) write.
|
|
111
|
-
|
|
112
|
+
// The Codex and Gemini alternatives are `DECLINED_RE`'s own source, so the error code and the `user-stop` bucket read one pattern.
|
|
113
|
+
[new RegExp(`The user doesn't want to proceed with this tool use|The tool use was rejected|${DECLINED_RE.source}`), 'user-rejected'],
|
|
112
114
|
[/Permission to use [^\n]* has been denied/, 'permission-denied'],
|
|
113
115
|
[/InputValidationError/, 'input-validation'],
|
|
114
116
|
[/<tool_use_error>/, 'tool-use-error'],
|
|
@@ -191,6 +193,17 @@ const addUsage = (total, line) => {
|
|
|
191
193
|
total.output += line.output;
|
|
192
194
|
total.thinking += line.thinking;
|
|
193
195
|
};
|
|
196
|
+
const emptyCoverage = () => ({ observed: new Set(), responses: 0 });
|
|
197
|
+
const addCoverage = (coverage, line) => {
|
|
198
|
+
coverage.responses += 1;
|
|
199
|
+
for (const field of line.observed)
|
|
200
|
+
coverage.observed.add(field);
|
|
201
|
+
};
|
|
202
|
+
/** The record fields for a total: the observed categories in the fixed order, and the responses that reported. */
|
|
203
|
+
const coverageFields = (coverage) => ({
|
|
204
|
+
usageObserved: USAGE_FIELDS.filter((field) => coverage.observed.has(field)),
|
|
205
|
+
usageResponses: coverage.responses,
|
|
206
|
+
});
|
|
194
207
|
/** cost_per_class.py: only numeric fields count; thinking comes from `output_tokens_details`. */
|
|
195
208
|
const readUsage = (message) => {
|
|
196
209
|
const raw = message.usage;
|
|
@@ -198,15 +211,15 @@ const readUsage = (message) => {
|
|
|
198
211
|
return undefined;
|
|
199
212
|
const details = isObj(raw.output_tokens_details) ? raw.output_tokens_details : {};
|
|
200
213
|
const thinking = num(details.thinking_tokens);
|
|
214
|
+
const input = num(raw.input_tokens);
|
|
215
|
+
const cacheWrite = num(raw.cache_creation_input_tokens);
|
|
216
|
+
const cacheRead = num(raw.cache_read_input_tokens);
|
|
217
|
+
const output = num(raw.output_tokens);
|
|
218
|
+
const reported = [['input', input], ['cacheWrite', cacheWrite], ['cacheRead', cacheRead], ['output', output], ['thinking', thinking]];
|
|
201
219
|
return {
|
|
202
|
-
usage: {
|
|
203
|
-
input: num(raw.input_tokens) ?? 0,
|
|
204
|
-
cacheWrite: num(raw.cache_creation_input_tokens) ?? 0,
|
|
205
|
-
cacheRead: num(raw.cache_read_input_tokens) ?? 0,
|
|
206
|
-
output: num(raw.output_tokens) ?? 0,
|
|
207
|
-
thinking: thinking ?? 0,
|
|
208
|
-
},
|
|
220
|
+
usage: { input: input ?? 0, cacheWrite: cacheWrite ?? 0, cacheRead: cacheRead ?? 0, output: output ?? 0, thinking: thinking ?? 0 },
|
|
209
221
|
hasThinking: thinking !== undefined,
|
|
222
|
+
observed: reported.filter(([, value]) => value !== undefined).map(([field]) => field),
|
|
210
223
|
};
|
|
211
224
|
};
|
|
212
225
|
const toSeconds = (timestamp) => {
|
|
@@ -409,7 +422,7 @@ export const pathKey = (path, cwd, root) => {
|
|
|
409
422
|
/** The per-account salted hash of a file path (16 hex characters, class A). */
|
|
410
423
|
export const hashPath = (path, cwd, root, salt) => {
|
|
411
424
|
const { key, base } = pathKey(path, cwd, root);
|
|
412
|
-
return { hash: sha256(`${salt}\n${key}`).slice(0, 16), base };
|
|
425
|
+
return { hash: sha256(`${salt}\n${key}`).slice(0, 16), base, key };
|
|
413
426
|
};
|
|
414
427
|
/** A leading `cd <dir> &&` or `cd <dir>;` hop: the target is the command that ran after it. */
|
|
415
428
|
const CD_HOP = /^\s*(?:cd|Set-Location|pushd)\s+\S+\s*(?:&&|;)\s*/;
|
|
@@ -1091,7 +1104,7 @@ export const readShell = (command, redact) => {
|
|
|
1091
1104
|
}
|
|
1092
1105
|
return {
|
|
1093
1106
|
segments: kept.map((held) => ({ run: runOf(held), read: held.read })),
|
|
1094
|
-
steps: kept.map((held) => ({ identity: shellIdentity(held.program, held.args, pullsOfCommand(held.program, held.args).filter(present)), read: held.read, run: runOf(held) })),
|
|
1107
|
+
steps: kept.map((held) => ({ identity: shellIdentity(held.program, held.args, pullsOfCommand(held.program, held.args).filter(present), redact), read: held.read, run: runOf(held) })),
|
|
1095
1108
|
pulls: [...pulls.values()],
|
|
1096
1109
|
commitPlan: commitPlanOf(commands),
|
|
1097
1110
|
};
|
|
@@ -1196,7 +1209,7 @@ export const pullsOfTool = (name, redact) => {
|
|
|
1196
1209
|
return pull === null ? [] : [pull];
|
|
1197
1210
|
};
|
|
1198
1211
|
/** A call's steps: one per command of a shell call, else one. */
|
|
1199
|
-
const draftsOf = (table, named, native, shell, target, input) => {
|
|
1212
|
+
const draftsOf = (table, named, native, shell, target, input, redact) => {
|
|
1200
1213
|
const { action } = named;
|
|
1201
1214
|
if (action === 'shell.exec') {
|
|
1202
1215
|
const read = shell?.steps ?? [];
|
|
@@ -1206,15 +1219,15 @@ const draftsOf = (table, named, native, shell, target, input) => {
|
|
|
1206
1219
|
}
|
|
1207
1220
|
const url = str(input.url);
|
|
1208
1221
|
const skill = str(input.skill);
|
|
1209
|
-
return [{ action, ...callIdentity(table, action, native, named.native, { skill, url }), read: true, rawKey: `${native}\n${target ?? url ?? skill ?? ''}` }];
|
|
1222
|
+
return [{ action, ...callIdentity(table, action, native, named.native, { skill, url, redact }), read: true, rawKey: `${native}\n${target ?? url ?? skill ?? ''}` }];
|
|
1210
1223
|
};
|
|
1211
|
-
/** The `target` of a tool call (see {@link ToolObservation.target}). */
|
|
1224
|
+
/** The `target` of a tool call (see {@link ToolObservation.target}). A path target also returns the `key` it was hashed on, read for its class and then dropped, never stored. */
|
|
1212
1225
|
export const targetOf = (tool, input, cwd, salt, redact, root = null) => {
|
|
1213
1226
|
for (const key of PATH_KEYS) {
|
|
1214
1227
|
const value = str(input[key]);
|
|
1215
1228
|
if (value !== undefined && value !== '') {
|
|
1216
|
-
const { hash, base } = hashPath(value, cwd, root, salt);
|
|
1217
|
-
return { target: hash, base };
|
|
1229
|
+
const { hash, base, key: hashedKey } = hashPath(value, cwd, root, salt);
|
|
1230
|
+
return { target: hash, base, key: hashedKey };
|
|
1218
1231
|
}
|
|
1219
1232
|
}
|
|
1220
1233
|
if (!SHELL.has(tool))
|
|
@@ -1302,6 +1315,7 @@ export const captureSession = (input) => {
|
|
|
1302
1315
|
costUsd: null,
|
|
1303
1316
|
usage: emptyUsage(),
|
|
1304
1317
|
};
|
|
1318
|
+
const sessionCoverage = emptyCoverage();
|
|
1305
1319
|
const versions = new Set();
|
|
1306
1320
|
const branches = new Set();
|
|
1307
1321
|
const files = new Set();
|
|
@@ -1359,6 +1373,7 @@ export const captureSession = (input) => {
|
|
|
1359
1373
|
efforts: [...cur.efforts].sort(),
|
|
1360
1374
|
skills: [...cur.skills].sort(),
|
|
1361
1375
|
usage: cur.usage,
|
|
1376
|
+
...coverageFields(cur.coverage),
|
|
1362
1377
|
promptFingerprint,
|
|
1363
1378
|
});
|
|
1364
1379
|
promptFrames.push({ version: cur.version, model: majorityOrNull(cur.models) ?? majorityOrNull(cur.messageModels), effort: majorityOrNull(cur.effortCounts), ts: cur.ts });
|
|
@@ -1469,8 +1484,10 @@ export const captureSession = (input) => {
|
|
|
1469
1484
|
}
|
|
1470
1485
|
if (type === 'assistant' && role === 'assistant') {
|
|
1471
1486
|
session.assistantTurns += 1;
|
|
1472
|
-
if (!side && lineUsage !== undefined && firstOfResponse)
|
|
1487
|
+
if (!side && lineUsage !== undefined && firstOfResponse) {
|
|
1473
1488
|
addUsage(session.usage, lineUsage.usage);
|
|
1489
|
+
addCoverage(sessionCoverage, lineUsage);
|
|
1490
|
+
}
|
|
1474
1491
|
}
|
|
1475
1492
|
// prompt_graphs.py and cost_per_class.py: a prompt is a main-line user line with text and no tool result. They also dropped any leading "<", which lost
|
|
1476
1493
|
// every turn a command or a skill opened; that is now `promptOpening`'s call. A line from any other harness keeps the old rule.
|
|
@@ -1481,8 +1498,8 @@ export const captureSession = (input) => {
|
|
|
1481
1498
|
if (type === 'user' && role === 'user' && !side && !hasToolResult(content)) {
|
|
1482
1499
|
const leading = LEADING_TAG.exec(firstTextBlock(content).trimStart())?.[1];
|
|
1483
1500
|
harnessLine = o.isMeta === true || (leading !== undefined && LOCAL_COMMAND_TAGS.has(leading));
|
|
1484
|
-
// `promptOpening` is Claude Code's alone: a prompt from any other harness that starts with `<` gets no opening below, yet it is still the operator typing.
|
|
1485
|
-
operatorTurn = o.isMeta !== true && (leading === 'bash-input' || hasImage(content) || (harness !== 'claude-code' && firstTextBlock(content).startsWith('<')));
|
|
1501
|
+
// `promptOpening` is Claude Code's alone: a prompt from any other harness that starts with `<` gets no opening below, yet it is still the operator typing, except the output of a `!` command, which is the harness's own line (an agent turn, as under Claude Code).
|
|
1502
|
+
operatorTurn = o.isMeta !== true && (leading === 'bash-input' || hasImage(content) || (harness !== 'claude-code' && leading !== 'bash-stdout' && leading !== 'bash-stderr' && firstTextBlock(content).startsWith('<')));
|
|
1486
1503
|
}
|
|
1487
1504
|
if (type === 'user' && role === 'user' && o.isMeta !== true && !side) {
|
|
1488
1505
|
const text = firstTextBlock(content);
|
|
@@ -1510,6 +1527,7 @@ export const captureSession = (input) => {
|
|
|
1510
1527
|
efforts: new Set(),
|
|
1511
1528
|
skills: new Set(),
|
|
1512
1529
|
usage: emptyUsage(),
|
|
1530
|
+
coverage: emptyCoverage(),
|
|
1513
1531
|
turns: 0,
|
|
1514
1532
|
version: version !== undefined && version !== '' ? version : undefined,
|
|
1515
1533
|
effortCounts: new Map(),
|
|
@@ -1536,8 +1554,10 @@ export const captureSession = (input) => {
|
|
|
1536
1554
|
cur.models.set(requested, (cur.models.get(requested) ?? 0) + 1);
|
|
1537
1555
|
if (realMessageModel !== undefined)
|
|
1538
1556
|
bump(cur.messageModels, realMessageModel);
|
|
1539
|
-
if (lineUsage !== undefined && firstOfResponse)
|
|
1557
|
+
if (lineUsage !== undefined && firstOfResponse) {
|
|
1540
1558
|
addUsage(cur.usage, lineUsage.usage);
|
|
1559
|
+
addCoverage(cur.coverage, lineUsage);
|
|
1560
|
+
}
|
|
1541
1561
|
}
|
|
1542
1562
|
}
|
|
1543
1563
|
// prompt_graphs.py reads efforts and skills from every line.
|
|
@@ -1602,7 +1622,7 @@ export const captureSession = (input) => {
|
|
|
1602
1622
|
const shell = SHELL.has(name) || named.action === 'shell.exec' ? readShell(str(toolInput.command) ?? '', redact) : undefined;
|
|
1603
1623
|
for (const held of shell?.steps ?? [])
|
|
1604
1624
|
framePrograms.add(held.identity.program);
|
|
1605
|
-
drafts.push(draftsOf(actions, named, native, shell, aimed.target, toolInput));
|
|
1625
|
+
drafts.push(draftsOf(actions, named, native, shell, aimed.target, toolInput, redact));
|
|
1606
1626
|
responseOfCall.push(response);
|
|
1607
1627
|
callsPerResponse.set(response, (callsPerResponse.get(response) ?? 0) + 1);
|
|
1608
1628
|
const manifestFile = PATH_TOOLS.has(name) && filePath !== undefined ? manifestFileOf(filePath) : null;
|
|
@@ -1611,6 +1631,9 @@ export const captureSession = (input) => {
|
|
|
1611
1631
|
manifestReads.set(id, manifestFile);
|
|
1612
1632
|
const pulls = shell?.pulls ?? pullsOfTool(name, redact);
|
|
1613
1633
|
const packages = packagesOf(pulls, seenPackages);
|
|
1634
|
+
// Who made the call, from its own line: the model is not the prompt's majority, and a line inside a skill's run names the skill (the first line after the run does not).
|
|
1635
|
+
const callModel = requested !== undefined && requested !== '' ? requested : realMessageModel;
|
|
1636
|
+
const callSkill = truthy(o.attributionSkill) ? String(o.attributionSkill) : undefined;
|
|
1614
1637
|
const call = {
|
|
1615
1638
|
kind: 'tool',
|
|
1616
1639
|
cv: CAPTURE_VERSION,
|
|
@@ -1626,9 +1649,14 @@ export const captureSession = (input) => {
|
|
|
1626
1649
|
target: aimed.target,
|
|
1627
1650
|
targetBase: aimed.base,
|
|
1628
1651
|
keys: Object.keys(toolInput).sort(),
|
|
1652
|
+
// The class reads the key the target is hashed on (relative to the repository or the cwd), never the absolute path: the folders above a repository say nothing about the file.
|
|
1653
|
+
targetClass: targetClassOf(aimed.key, aimed.base),
|
|
1654
|
+
argClass: argClassOf(toolInput),
|
|
1629
1655
|
durationMs: null,
|
|
1630
1656
|
toolUseId: id ?? null,
|
|
1631
1657
|
harnessVersion: version ?? null,
|
|
1658
|
+
...(callModel === undefined ? {} : { model: callModel }),
|
|
1659
|
+
...(callSkill === undefined ? {} : { skill: callSkill }),
|
|
1632
1660
|
side,
|
|
1633
1661
|
prompt: cur?.index ?? null,
|
|
1634
1662
|
...(shell === undefined ? {} : { segments: shell.segments }),
|
|
@@ -1684,7 +1712,7 @@ export const captureSession = (input) => {
|
|
|
1684
1712
|
}
|
|
1685
1713
|
}
|
|
1686
1714
|
finish();
|
|
1687
|
-
Object.assign(session, shape.finish());
|
|
1715
|
+
Object.assign(session, shape.finish(), coverageFields(sessionCoverage));
|
|
1688
1716
|
session.versions = [...versions].sort();
|
|
1689
1717
|
session.branches = [...branches].sort();
|
|
1690
1718
|
const kept = [...files].sort().slice(0, FILES_PER_SESSION_MAX);
|
|
@@ -1724,14 +1752,18 @@ export const captureSession = (input) => {
|
|
|
1724
1752
|
const response = responseOfCall[position];
|
|
1725
1753
|
const output = response === undefined ? undefined : outputPerResponse.get(response);
|
|
1726
1754
|
const tokens = response === undefined || output === undefined || output === 0 ? null : Math.round(output / (callsPerResponse.get(response) ?? 1));
|
|
1727
|
-
|
|
1755
|
+
const draftsOfCall = drafts[position] ?? [];
|
|
1756
|
+
// A failed call of several read commands (`a && b`) says that the call failed, not which command did: the result carries one exit, so no segment is `error` on that evidence and the failure stays on the call.
|
|
1757
|
+
const chainFailed = call.status === 'error' && draftsOfCall.filter((draft) => draft.read).length > 1;
|
|
1758
|
+
const allocated = tokens !== null && response !== undefined && (callsPerResponse.get(response) ?? 1) > 1;
|
|
1759
|
+
call.steps = draftsOfCall.map((draft, seg) => ({
|
|
1728
1760
|
n: call.n,
|
|
1729
1761
|
seg,
|
|
1730
1762
|
action: draft.action,
|
|
1731
1763
|
native: draft.native,
|
|
1732
1764
|
identity: draft.identity,
|
|
1733
|
-
status: draft.read ? call.status : 'none',
|
|
1734
|
-
cost: seg === 0 ? { tokens, durationMs: call.durationMs } : { tokens: null, durationMs: null },
|
|
1765
|
+
status: draft.read && !chainFailed ? call.status : 'none',
|
|
1766
|
+
cost: seg === 0 ? { tokens, durationMs: call.durationMs, ...(allocated ? { allocated: true } : {}) } : { tokens: null, durationMs: null },
|
|
1735
1767
|
parent: origin?.spawnedAt ?? null,
|
|
1736
1768
|
edges: [],
|
|
1737
1769
|
frameId,
|
|
@@ -3,7 +3,7 @@ import { appendFile, mkdir, readFile } from 'node:fs/promises';
|
|
|
3
3
|
import { paths } from './store.js';
|
|
4
4
|
const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
5
5
|
const eventOf = (value) => {
|
|
6
|
-
if (value === 'session-start' || value === 'pre-tool' || value === 'post-tool' || value === 'stop' || value === 'nightly')
|
|
6
|
+
if (value === 'session-start' || value === 'prompt' || value === 'pre-tool' || value === 'post-tool' || value === 'stop' || value === 'nightly')
|
|
7
7
|
return value;
|
|
8
8
|
return undefined;
|
|
9
9
|
};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { Read } from './read.js';
|
|
2
|
+
import type { Position, RecoveryEdges } from './recall-next.js';
|
|
3
|
+
import type { NextByFrame } from './recall-frames.js';
|
|
2
4
|
import { type RecallRead } from './recall-reads.js';
|
|
3
5
|
import { type Store } from './store.js';
|
|
4
6
|
/** A signature is "recurring" once it has been seen in this many sessions. */
|
|
@@ -33,8 +35,19 @@ export interface HookIndex {
|
|
|
33
35
|
fit?: {
|
|
34
36
|
[readId: string]: RecallRead;
|
|
35
37
|
};
|
|
38
|
+
/**
|
|
39
|
+
* The local pool's recovery edges, by the asker's frame key (`frameKeyOf`: harness and version bucket) and then `action/errorCode`. Every key is kept, one under `RECOVERY_FLOOR` too,
|
|
40
|
+
* so `PostToolUseFailure` can tell a cell too thin to serve (silent) from no cell (the signature line may answer). An index a version before this wrote has none.
|
|
41
|
+
*/
|
|
42
|
+
recoveryByFrame?: {
|
|
43
|
+
[frame: string]: RecoveryEdges;
|
|
44
|
+
};
|
|
45
|
+
/** The local pool's split positions (t782's rule) by the asker's frame key, most observed first. */
|
|
46
|
+
positionsByFrame?: {
|
|
47
|
+
[frame: string]: Position[];
|
|
48
|
+
};
|
|
36
49
|
}
|
|
37
|
-
export declare const buildIndex: (store: Store, redactDomains: readonly string[], at: string, reads?: readonly Read[]) => HookIndex;
|
|
50
|
+
export declare const buildIndex: (store: Store, redactDomains: readonly string[], at: string, reads?: readonly Read[], next?: NextByFrame) => HookIndex;
|
|
38
51
|
export declare const indexPath: (storeDir: string) => string;
|
|
39
52
|
/** The index as written, or undefined when there is none (or it is torn): a hook then says nothing. */
|
|
40
53
|
export declare const readIndex: (storeDir: string) => Promise<HookIndex | undefined>;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { readFile } from 'node:fs/promises';
|
|
5
5
|
import { join, resolve } from 'node:path';
|
|
6
6
|
import { resolveReads } from './recall-pool.js';
|
|
7
|
-
import { aboveBaseRate, fitReads, toolShapeId } from './recall-reads.js';
|
|
7
|
+
import { aboveBaseRate, fitReads, PUSH_FLOORS, toolShapeId } from './recall-reads.js';
|
|
8
8
|
import { expandHome } from './allow.js';
|
|
9
9
|
import { slugOf } from './store.js';
|
|
10
10
|
const INDEX_FILE = 'hook-index.json';
|
|
@@ -33,7 +33,7 @@ const repositoryOf = (projects) => {
|
|
|
33
33
|
return bases.find((candidate) => base === candidate || base.startsWith(`${candidate}-`)) ?? base;
|
|
34
34
|
};
|
|
35
35
|
};
|
|
36
|
-
export const buildIndex = (store, redactDomains, at, reads = []) => {
|
|
36
|
+
export const buildIndex = (store, redactDomains, at, reads = [], next) => {
|
|
37
37
|
const projectOf = new Map(store.sessions.map((session) => [session.session, session.project]));
|
|
38
38
|
// The latest 300 of each repository, not of the whole store: another repository's deposits must not move this one's ranking.
|
|
39
39
|
const family = repositoryOf([...new Set(store.sessions.map((session) => session.project))]);
|
|
@@ -87,7 +87,14 @@ export const buildIndex = (store, redactDomains, at, reads = []) => {
|
|
|
87
87
|
if (rows.length > 0)
|
|
88
88
|
projects[project] = rows;
|
|
89
89
|
}
|
|
90
|
-
return {
|
|
90
|
+
return {
|
|
91
|
+
at,
|
|
92
|
+
redactDomains: [...redactDomains],
|
|
93
|
+
projects,
|
|
94
|
+
signatures,
|
|
95
|
+
fit: Object.fromEntries(resolveReads(aboveBaseRate(store, fitReads(store, PUSH_FLOORS)), reads).map((read) => [toolShapeId(read.tool, read.keys), read])),
|
|
96
|
+
...(next === undefined ? {} : { recoveryByFrame: next.recovery, positionsByFrame: next.positions }),
|
|
97
|
+
};
|
|
91
98
|
};
|
|
92
99
|
export const indexPath = (storeDir) => join(storeDir, INDEX_FILE);
|
|
93
100
|
const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
package/dist/sessions/hooks.d.ts
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
|
+
import { type Emit } from '../telemetry/emit.js';
|
|
1
2
|
import type { HookCall } from './schema.js';
|
|
2
3
|
import { type SessionsRuntime } from './store.js';
|
|
4
|
+
import type { NightlyTelemetry } from '../telemetry/health.js';
|
|
3
5
|
export type HookEvent = HookCall['event'];
|
|
4
6
|
export declare const HOOK_EVENTS: readonly HookEvent[];
|
|
5
7
|
/** The runtime a hook process runs with. Only `nightly` reads the non-Claude stores (a saved `--allow cursor` or `--allow codex`); the Stop path stays free of it. */
|
|
6
8
|
export declare const hookRuntime: (event: HookEvent, env: {
|
|
7
9
|
[name: string]: string | undefined;
|
|
8
10
|
}, platform: NodeJS.Platform, home: string, io: Pick<SessionsRuntime, "stdin" | "stdout">) => SessionsRuntime;
|
|
11
|
+
/** What the nightly run records with, and nothing for any other hook: the telemetry folder sits beside the session store under the config directory, so `oars telemetry` and the nightly run read and write the same one. The version is read only for the nightly run, so the per-tool hooks never touch the manifest. */
|
|
12
|
+
export declare const nightlyTelemetry: (event: HookEvent, env: NodeJS.ProcessEnv, platform: NodeJS.Platform, home: string, version: () => string, now: () => number) => NightlyTelemetry | undefined;
|
|
9
13
|
/**
|
|
10
14
|
* Runs one hook. It never fails or blocks the session it runs in: whatever happens, it resolves (the process exits 0), and the
|
|
11
15
|
* hook-call line records the outcome, with the failure's first line when there was one, so the record is evidence, not a claim.
|
|
12
16
|
*/
|
|
13
|
-
export declare const runHook: (event: HookEvent, stdin: string, runtime: SessionsRuntime, now: () => number) => Promise<string | undefined>;
|
|
17
|
+
export declare const runHook: (event: HookEvent, stdin: string, runtime: SessionsRuntime, now: () => number, telemetry?: NightlyTelemetry, emit?: Emit) => Promise<string | undefined>;
|