@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
|
@@ -23,8 +23,6 @@ export declare const PUBLIC_PROGRAMS: ReadonlySet<string>;
|
|
|
23
23
|
export declare const PUBLIC_SUBCOMMANDS: ReadonlyMap<string, ReadonlySet<string>>;
|
|
24
24
|
/** Flag names of common public programs. A flag name on another program can be a private script's, so an unlisted name reads as the literal. */
|
|
25
25
|
export declare const PUBLIC_FLAGS: ReadonlySet<string>;
|
|
26
|
-
/** Hosts that say nothing about who asked. Matched whole: `acme.github.io` names a customer even though `github.io` is public. */
|
|
27
|
-
export declare const PUBLIC_HOSTS: ReadonlySet<string>;
|
|
28
26
|
/** MCP servers published for anyone to install. A server someone named themselves can name their company, so it reads as the literal. */
|
|
29
27
|
export declare const PUBLIC_MCP_SERVERS: ReadonlySet<string>;
|
|
30
28
|
/**
|
|
@@ -68,6 +66,7 @@ export declare const CUSTOM_AGENT = "custom";
|
|
|
68
66
|
export declare const projectedAgentType: (agentType: string) => string;
|
|
69
67
|
/**
|
|
70
68
|
* The input keys each built-in tool's published schema lists, by the tool's name. Curated from the harness's tool reference. A key outside the tool's list, and every
|
|
71
|
-
* key of an MCP tool (no pinned registry lists their schemas), is counted and never named (spec Section 1.3).
|
|
69
|
+
* key of an MCP tool (no pinned registry lists their schemas), is counted and never named (spec Section 1.3). That is the local projection; the wire (t870) sends the names that pass
|
|
70
|
+
* the name gate and counts the rest in `keysDropped`.
|
|
72
71
|
*/
|
|
73
72
|
export declare const TOOL_INPUT_KEYS: ReadonlyMap<string, ReadonlySet<string>>;
|
|
@@ -187,14 +187,6 @@ export const PUBLIC_FLAGS = words(`
|
|
|
187
187
|
-NoNewline -Property -Seconds -SimpleMatch -Skip -TotalCount -Unique -Value -Wait
|
|
188
188
|
-Command -Depth -ErrorAction -ExecutionPolicy -Filter -Force -Include -LiteralPath -NoLogo -NoProfile -NonInteractive -Path -Pattern -Raw -Recurse -Tail
|
|
189
189
|
`);
|
|
190
|
-
/** Hosts that say nothing about who asked. Matched whole: `acme.github.io` names a customer even though `github.io` is public. */
|
|
191
|
-
export const PUBLIC_HOSTS = words(`
|
|
192
|
-
github.com raw.githubusercontent.com gist.github.com docs.github.com api.github.com gitlab.com bitbucket.org stackoverflow.com superuser.com serverfault.com
|
|
193
|
-
en.wikipedia.org developer.mozilla.org npmjs.com www.npmjs.com registry.npmjs.org pypi.org docs.python.org crates.io docs.rs pkg.go.dev go.dev rubygems.org
|
|
194
|
-
docs.oracle.com spring.io docs.spring.io maven.apache.org kubernetes.io docs.docker.com hub.docker.com nodejs.org react.dev vite.dev vitejs.dev typescriptlang.org
|
|
195
|
-
www.typescriptlang.org docs.astro.build developers.cloudflare.com docs.stripe.com stripe.com docs.anthropic.com docs.claude.com code.claude.com learn.microsoft.com
|
|
196
|
-
docs.aws.amazon.com cloud.google.com platform.openai.com arxiv.org medium.com dev.to news.ycombinator.com reddit.com www.reddit.com youtube.com www.youtube.com
|
|
197
|
-
`);
|
|
198
190
|
/** MCP servers published for anyone to install. A server someone named themselves can name their company, so it reads as the literal. */
|
|
199
191
|
export const PUBLIC_MCP_SERVERS = words(`
|
|
200
192
|
claude-in-chrome playwright puppeteer github gitlab filesystem fetch memory sequential-thinking context7 sentry
|
|
@@ -272,7 +264,8 @@ export const CUSTOM_AGENT = 'custom';
|
|
|
272
264
|
export const projectedAgentType = (agentType) => ([...HARNESS_AGENT_TYPES.values()].some((types) => types.has(agentType)) ? agentType : CUSTOM_AGENT);
|
|
273
265
|
/**
|
|
274
266
|
* The input keys each built-in tool's published schema lists, by the tool's name. Curated from the harness's tool reference. A key outside the tool's list, and every
|
|
275
|
-
* key of an MCP tool (no pinned registry lists their schemas), is counted and never named (spec Section 1.3).
|
|
267
|
+
* key of an MCP tool (no pinned registry lists their schemas), is counted and never named (spec Section 1.3). That is the local projection; the wire (t870) sends the names that pass
|
|
268
|
+
* the name gate and counts the rest in `keysDropped`.
|
|
276
269
|
*/
|
|
277
270
|
export const TOOL_INPUT_KEYS = new Map(Object.entries({
|
|
278
271
|
Agent: 'description prompt subagent_type model run_in_background',
|
package/dist/sessions/query.d.ts
CHANGED
|
@@ -37,13 +37,23 @@ export interface BooksShare {
|
|
|
37
37
|
of: number;
|
|
38
38
|
share: number | null;
|
|
39
39
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
/** The usage columns of a row. Each sums only the prompts that recorded the category, and is null when none did: absent, never a measured 0. */
|
|
41
|
+
export declare const BOOKS_USAGE_FIELDS: readonly ["cacheRead", "cacheWrite", "output", "thinking"];
|
|
42
|
+
export interface BooksUsageCoverage {
|
|
43
43
|
cacheRead: number;
|
|
44
44
|
cacheWrite: number;
|
|
45
45
|
output: number;
|
|
46
46
|
thinking: number;
|
|
47
|
+
}
|
|
48
|
+
export interface BooksRow {
|
|
49
|
+
key: string;
|
|
50
|
+
prompts: number;
|
|
51
|
+
cacheRead: number | null;
|
|
52
|
+
cacheWrite: number | null;
|
|
53
|
+
output: number | null;
|
|
54
|
+
thinking: number | null;
|
|
55
|
+
/** How many of `prompts` recorded each usage category; below `prompts` the column is a partial sum. */
|
|
56
|
+
usageRecorded: BooksUsageCoverage;
|
|
47
57
|
wallSec: number;
|
|
48
58
|
turns: number;
|
|
49
59
|
errors: number;
|
|
@@ -78,8 +88,10 @@ export interface Signal {
|
|
|
78
88
|
recurringPer100: number | null;
|
|
79
89
|
/** Distinct signatures among those recurring errors in the window. */
|
|
80
90
|
recurringSignatures: number | null;
|
|
81
|
-
/** Mean cache-write tokens of
|
|
91
|
+
/** Mean cache-write tokens of the window's sessions that recorded them; null when none did. */
|
|
82
92
|
cacheWritePerSession: number | null;
|
|
93
|
+
/** How many of `sessions` recorded cache-write tokens, the sessions `cacheWritePerSession` is a mean over. */
|
|
94
|
+
cacheWriteSessions: number;
|
|
83
95
|
/** MCP recall calls made during the window, per session. */
|
|
84
96
|
recallPerSession: number | null;
|
|
85
97
|
/** Hook lookups (SessionStart injection and PostToolUse recall) made in the window's sessions, per session. */
|
package/dist/sessions/query.js
CHANGED
|
@@ -3,7 +3,7 @@ import { firstCopies } from './derive.js';
|
|
|
3
3
|
import { errorKey } from './extract.js';
|
|
4
4
|
import { makeRedactor } from './redact.js';
|
|
5
5
|
import { SOURCES } from './sources.js';
|
|
6
|
-
import { harnessOf } from './schema.js';
|
|
6
|
+
import { harnessOf, observesUsage } from './schema.js';
|
|
7
7
|
import { signatureRecord } from './store.js';
|
|
8
8
|
const DAY_MS = 86_400_000;
|
|
9
9
|
const sinceCutoff = (now, days) => (days === undefined ? undefined : new Date(now - days * DAY_MS).toISOString());
|
|
@@ -71,6 +71,8 @@ export const formatRecall = (result) => {
|
|
|
71
71
|
};
|
|
72
72
|
/** Below this many in its denominator a share is noise, so a cell has none and prints as too few. The counts stay in `--json`. */
|
|
73
73
|
export const BOOKS_MIN_PROMPTS = 30;
|
|
74
|
+
/** The usage columns of a row. Each sums only the prompts that recorded the category, and is null when none did: absent, never a measured 0. */
|
|
75
|
+
export const BOOKS_USAGE_FIELDS = ['cacheRead', 'cacheWrite', 'output', 'thinking'];
|
|
74
76
|
/** Each subagent's tokens, keyed by the parent prompt that spawned it (`<session>#<index>`). */
|
|
75
77
|
const delegatedByPrompt = (store) => {
|
|
76
78
|
const promptOf = new Map();
|
|
@@ -117,6 +119,9 @@ const callsByPrompt = (store) => {
|
|
|
117
119
|
}
|
|
118
120
|
return calls;
|
|
119
121
|
};
|
|
122
|
+
const recordedBy = (list, field) => list.filter((prompt) => observesUsage(prompt, field)).length;
|
|
123
|
+
/** The sum of one usage category over the prompts that recorded it, or null when none did. */
|
|
124
|
+
const usageSum = (list, field) => recordedBy(list, field) === 0 ? null : list.reduce((total, prompt) => total + (observesUsage(prompt, field) ? prompt.usage[field] : 0), 0);
|
|
120
125
|
export const books = (store, by, now, sinceDays, survival = new Map()) => {
|
|
121
126
|
const cutoff = sinceCutoff(now, sinceDays);
|
|
122
127
|
const groups = new Map();
|
|
@@ -152,10 +157,11 @@ export const books = (store, by, now, sinceDays, survival = new Map()) => {
|
|
|
152
157
|
return {
|
|
153
158
|
key,
|
|
154
159
|
prompts: list.length,
|
|
155
|
-
cacheRead:
|
|
156
|
-
cacheWrite:
|
|
157
|
-
output:
|
|
158
|
-
thinking:
|
|
160
|
+
cacheRead: usageSum(list, 'cacheRead'),
|
|
161
|
+
cacheWrite: usageSum(list, 'cacheWrite'),
|
|
162
|
+
output: usageSum(list, 'output'),
|
|
163
|
+
thinking: usageSum(list, 'thinking'),
|
|
164
|
+
usageRecorded: { cacheRead: recordedBy(list, 'cacheRead'), cacheWrite: recordedBy(list, 'cacheWrite'), output: recordedBy(list, 'output'), thinking: recordedBy(list, 'thinking') },
|
|
159
165
|
wallSec: Math.round(sum(list, (prompt) => prompt.wallSec ?? 0)),
|
|
160
166
|
turns: sum(list, (prompt) => prompt.turns),
|
|
161
167
|
errors: sum(list, (prompt) => prompt.errors),
|
|
@@ -174,14 +180,18 @@ export const books = (store, by, now, sinceDays, survival = new Map()) => {
|
|
|
174
180
|
/** The closing line of `books` text: what the columns can and cannot be read as. */
|
|
175
181
|
export const BOOKS_CAVEAT = "These are associations in one operator's logs, not measured lift.";
|
|
176
182
|
const shareCell = (cell) => (cell.share === null ? 'too few' : `${String(Math.round(cell.share * 100))}%`);
|
|
183
|
+
const USAGE_LABELS = { cacheRead: 'cache read', cacheWrite: 'cache write', output: 'output', thinking: 'thinking' };
|
|
177
184
|
export const formatBooks = (rows, by) => {
|
|
178
185
|
if (rows.length === 0)
|
|
179
186
|
return by === 'pull' ? 'No pulls in the store.' : 'No prompts in the store.';
|
|
180
187
|
// A pull name, unlike a class, has no length it stays under.
|
|
181
188
|
const width = Math.max(14, ...rows.map((row) => row.key.length));
|
|
189
|
+
const usage = (row, field) => (row[field] === null ? 'n/a' : String(row[field]));
|
|
182
190
|
const header = `${by.padEnd(width)} ${'prompts'.padStart(8)} ${'cache read'.padStart(14)} ${'cache write'.padStart(13)} ${'output'.padStart(11)} ${'thinking'.padStart(10)} ${'wall s'.padStart(9)} ${'turns'.padStart(7)} ${'errors'.padStart(7)} ${'verify'.padStart(7)} ${'delegated'.padStart(13)} ${'passed'.padStart(8)} ${'stop'.padStart(8)} ${'redo'.padStart(8)} ${'commits'.padStart(8)} ${'settled'.padStart(8)} ${'survived'.padStart(8)}`;
|
|
183
|
-
const lines = rows.map((row) => `${row.key.padEnd(width)} ${String(row.prompts).padStart(8)} ${
|
|
184
|
-
|
|
191
|
+
const lines = rows.map((row) => `${row.key.padEnd(width)} ${String(row.prompts).padStart(8)} ${usage(row, 'cacheRead').padStart(14)} ${usage(row, 'cacheWrite').padStart(13)} ${usage(row, 'output').padStart(11)} ${usage(row, 'thinking').padStart(10)} ${String(row.wallSec).padStart(9)} ${String(row.turns).padStart(7)} ${String(row.errors).padStart(7)} ${(row.verifyAfterEdit === null ? '-' : `${String(Math.round(row.verifyAfterEdit * 100))}%`).padStart(7)} ${String(row.delegated).padStart(13)} ${shareCell(row.verifyPassed).padStart(8)} ${shareCell(row.stopped).padStart(8)} ${shareCell(row.redone).padStart(8)} ${String(row.commits).padStart(8)} ${String(row.settled).padStart(8)} ${shareCell(row.survived).padStart(8)}`);
|
|
192
|
+
// A column some prompts did not record is a partial sum: say how many did in a line under the table (a column no prompt recorded prints n/a and needs no line), so the table's columns stay aligned.
|
|
193
|
+
const coverage = rows.flatMap((row) => BOOKS_USAGE_FIELDS.filter((field) => row.usageRecorded[field] > 0 && row.usageRecorded[field] < row.prompts).map((field) => `${row.key} ${USAGE_LABELS[field]}: ${String(row.usageRecorded[field])} of ${String(row.prompts)} prompts recorded`));
|
|
194
|
+
return [header, ...lines, ...coverage].join('\n');
|
|
185
195
|
};
|
|
186
196
|
const order = (a, b) => (a.tsFirst ?? '').localeCompare(b.tsFirst ?? '') || a.session.localeCompare(b.session);
|
|
187
197
|
/**
|
|
@@ -200,7 +210,7 @@ export const measure = (store, calls, hookCalls, window, since) => {
|
|
|
200
210
|
}
|
|
201
211
|
const signal = (slice) => {
|
|
202
212
|
if (slice.length === 0)
|
|
203
|
-
return { sessions: 0, cursorSessions: 0, recurringPer100: null, recurringSignatures: null, cacheWritePerSession: null, recallPerSession: null, hookRecallPerSession: null, hookHitsPerSession: null, hookDepositsPerSession: null };
|
|
213
|
+
return { sessions: 0, cursorSessions: 0, recurringPer100: null, recurringSignatures: null, cacheWritePerSession: null, cacheWriteSessions: 0, recallPerSession: null, hookRecallPerSession: null, hookHitsPerSession: null, hookDepositsPerSession: null };
|
|
204
214
|
const names = new Set(slice.map((session) => session.session));
|
|
205
215
|
const recurringErrors = store.errors.filter((error) => names.has(error.session) && (index.get(error.session) ?? 0) > (firstSeen.get(error.sig) ?? 0));
|
|
206
216
|
const recurring = recurringErrors.length;
|
|
@@ -208,6 +218,8 @@ export const measure = (store, calls, hookCalls, window, since) => {
|
|
|
208
218
|
const to = slice.map((session) => session.tsLast).filter((ts) => ts !== null).sort().reverse()[0];
|
|
209
219
|
const inWindow = from === undefined || to === undefined ? 0 : calls.filter((call) => call.at >= from && call.at <= to).length;
|
|
210
220
|
const round = (value) => Math.round(value * 100) / 100;
|
|
221
|
+
// A session that never recorded cache writes holds a placeholder zero there, which would pull the mean down.
|
|
222
|
+
const recorded = slice.filter((session) => observesUsage(session, 'cacheWrite'));
|
|
211
223
|
const lookups = hookCalls.filter((call) => call.session !== null && names.has(call.session) && (call.event === 'session-start' || call.event === 'post-tool'));
|
|
212
224
|
return {
|
|
213
225
|
sessions: slice.length,
|
|
@@ -215,7 +227,8 @@ export const measure = (store, calls, hookCalls, window, since) => {
|
|
|
215
227
|
otherSessions: Object.fromEntries(SOURCES.filter((source) => source.harness !== 'cursor').map((source) => [source.harness, slice.filter((session) => harnessOf(session) === source.harness).length])),
|
|
216
228
|
recurringPer100: round((recurring * 100) / slice.length),
|
|
217
229
|
recurringSignatures: new Set(recurringErrors.map((error) => error.sig)).size,
|
|
218
|
-
cacheWritePerSession: Math.round(
|
|
230
|
+
cacheWritePerSession: recorded.length === 0 ? null : Math.round(recorded.reduce((total, session) => total + session.usage.cacheWrite, 0) / recorded.length),
|
|
231
|
+
cacheWriteSessions: recorded.length,
|
|
219
232
|
recallPerSession: round(inWindow / slice.length),
|
|
220
233
|
hookRecallPerSession: round(lookups.length / slice.length),
|
|
221
234
|
hookHitsPerSession: round(lookups.filter((call) => call.hit).length / slice.length),
|
|
@@ -229,7 +242,14 @@ export const measure = (store, calls, hookCalls, window, since) => {
|
|
|
229
242
|
};
|
|
230
243
|
export const formatMeasure = (result) => {
|
|
231
244
|
const cell = (value) => (value === null ? '-' : String(value));
|
|
245
|
+
const cacheWrite = (signal) => {
|
|
246
|
+
if (signal.cacheWritePerSession === null)
|
|
247
|
+
return signal.sessions === 0 ? '-' : 'n/a';
|
|
248
|
+
return String(signal.cacheWritePerSession);
|
|
249
|
+
};
|
|
232
250
|
const row = (label, pick) => `${label.padEnd(36)} ${cell(pick(result.last)).padStart(12)} ${cell(pick(result.before)).padStart(12)}`;
|
|
251
|
+
// A mean over only some of a window's sessions says so on its own line under the table, as `books` does, so a five-digit mean cannot push the `before` column out of line.
|
|
252
|
+
const coverage = ['last', 'before'].filter((name) => result[name].cacheWriteSessions > 0 && result[name].cacheWriteSessions < result[name].sessions).map((name) => `cache-write tokens per session, ${name}: ${String(result[name].cacheWriteSessions)} of ${String(result[name].sessions)} sessions recorded`);
|
|
233
253
|
return [
|
|
234
254
|
`${(result.since === null ? `G1 signals, last ${String(result.window)} sessions and the ${String(result.window)} before` : `G1 signals, ${String(result.window)} sessions from ${result.since} and the ${String(result.window)} before`).padEnd(36)} ${(result.since === null ? 'last' : 'from').padStart(12)} ${'before'.padStart(12)}`,
|
|
235
255
|
row('sessions in the window', (signal) => signal.sessions),
|
|
@@ -237,10 +257,12 @@ export const formatMeasure = (result) => {
|
|
|
237
257
|
...SOURCES.filter((source) => source.harness !== 'cursor' && ((result.last.otherSessions?.[source.harness] ?? 0) > 0 || (result.before.otherSessions?.[source.harness] ?? 0) > 0)).map((source) => row(` of which ${source.label}`, (signal) => signal.otherSessions?.[source.harness] ?? 0)),
|
|
238
258
|
row('recurring errors per 100 sessions', (signal) => signal.recurringPer100),
|
|
239
259
|
row('distinct recurring signatures', (signal) => signal.recurringSignatures),
|
|
240
|
-
|
|
260
|
+
// `-` is an empty window; `n/a` is sessions in it that never recorded cache writes; a partial mean is named in a line under the table.
|
|
261
|
+
`${'cache-write tokens per session'.padEnd(36)} ${cacheWrite(result.last).padStart(12)} ${cacheWrite(result.before).padStart(12)}`,
|
|
241
262
|
row('recall calls per session', (signal) => signal.recallPerSession),
|
|
242
263
|
row('hook recall calls per session', (signal) => signal.hookRecallPerSession),
|
|
243
264
|
row('hook hits per session', (signal) => signal.hookHitsPerSession),
|
|
244
265
|
row('hook deposits per session', (signal) => signal.hookDepositsPerSession),
|
|
266
|
+
...coverage,
|
|
245
267
|
].join('\n');
|
|
246
268
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Cost (rank 3): tokens and wall time per prompt, by prompt class, model, and ISO week. Confounders: class drift, subagent work outside the prompt's own usage, and
|
|
2
2
|
// task size, which nothing here can see. Tokens are plain counts, not priced. A prompt with no wall time adds no seconds and stays in the denominator, so seconds per prompt understates.
|
|
3
|
-
import { bucketFields,
|
|
3
|
+
import { bucketFields, isPromptClass, UNKNOWN } from './cells.js';
|
|
4
4
|
import { motifName } from './motif.js';
|
|
5
5
|
import { body, atGrain, groupBy, rate, settled, total } from './read-support.js';
|
|
6
6
|
const DAY_MS = 86_400_000;
|
|
@@ -14,10 +14,19 @@ export const isoWeek = (day) => {
|
|
|
14
14
|
return `${String(year)}-W${String(week).padStart(2, '0')}`;
|
|
15
15
|
};
|
|
16
16
|
const weekOf = (cell) => (cell.day === UNKNOWN ? UNKNOWN : isoWeek(cell.day));
|
|
17
|
+
const promptsWithTokens = (cells) => {
|
|
18
|
+
let sum = 0;
|
|
19
|
+
for (const cell of cells) {
|
|
20
|
+
if (cell.tokenPrompts === undefined)
|
|
21
|
+
return undefined;
|
|
22
|
+
sum += cell.tokenPrompts;
|
|
23
|
+
}
|
|
24
|
+
return sum;
|
|
25
|
+
};
|
|
17
26
|
export const deriveCost = ({ pool }) => {
|
|
18
27
|
const classed = atGrain(pool, 'week').flatMap((cell) => {
|
|
19
28
|
const motif = pool.motifs.get(cell.motifId);
|
|
20
|
-
return motif?.level === 'class' &&
|
|
29
|
+
return motif?.level === 'class' && isPromptClass(motif.cls) ? [{ cell, motif, model: bucketFields(cell.frameBucket).model }] : [];
|
|
21
30
|
});
|
|
22
31
|
return [...groupBy(classed, ({ cell, model }) => `${cell.motifId}/${model}/${weekOf(cell)}`).values()].flatMap((group) => {
|
|
23
32
|
const [first] = group;
|
|
@@ -25,10 +34,12 @@ export const deriveCost = ({ pool }) => {
|
|
|
25
34
|
return [];
|
|
26
35
|
const cells = group.map(({ cell }) => cell);
|
|
27
36
|
const prompts = total(cells, settled);
|
|
37
|
+
// Tokens hold only the prompts that recorded input, so they are divided by those prompts. A cell read back from a pool row written before coverage was stored says no count, and the group's coverage is then unknown: no figure, never a guess at full coverage.
|
|
38
|
+
const covered = promptsWithTokens(cells);
|
|
28
39
|
return [
|
|
29
40
|
body('cost', cells, {
|
|
30
41
|
slice: { class: motifName(first.motif), model: first.model, week: weekOf(first.cell) },
|
|
31
|
-
value: { tokensPerPrompt: Math.round(total(cells, (cell) => cell.tokens) /
|
|
42
|
+
value: { tokensPerPrompt: covered === undefined || covered === 0 ? null : Math.round(total(cells, (cell) => cell.tokens ?? 0) / covered), ...(covered === undefined ? { tokenCoverage: 'unknown' } : {}), secondsPerPrompt: Math.round(total(cells, (cell) => cell.durationMs) / prompts / 1000), errorShare: rate(total(cells, (cell) => cell.failed), prompts) },
|
|
32
43
|
n: prompts,
|
|
33
44
|
interval: null,
|
|
34
45
|
}),
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Recovery from cells (rank 1, t868): what others did next after a failed call, by (failed action, `errorCode`, next action). One read for each edge, citing its own week cell only, so a count under the floor in
|
|
2
|
+
// another edge is not lifted by this one. It states counts, never a fix: there is no fix field, `none` means nothing followed in that prompt, and an edit that followed is not that the failure was resolved.
|
|
3
|
+
// Confounders: the work the failure interrupted, the user's refusals and policy denials (their own codes), and that a call's next action is one of many a prompt may take.
|
|
4
|
+
import { parseRecoveryClass } from './cells.js';
|
|
5
|
+
import { RECOVERY_EDGE_METHOD } from './read.js';
|
|
6
|
+
import { atGrain, body, groupBy, rate, total, wilson } from './read-support.js';
|
|
7
|
+
export const deriveRecoveryEdges = ({ pool }) => {
|
|
8
|
+
const edges = atGrain(pool, 'week').flatMap((cell) => {
|
|
9
|
+
const cls = pool.motifs.get(cell.motifId)?.cls;
|
|
10
|
+
const parts = cls === undefined || cls === null ? null : parseRecoveryClass(cls);
|
|
11
|
+
return parts === null ? [] : [{ cell, ...parts }];
|
|
12
|
+
});
|
|
13
|
+
// The denominator is the failures of the same action and code in the same frame bucket and week, over the cells this pool holds. A bundle's pool holds served cells only (`bundleOf`), so no `of` sums a cell under the floor and subtraction reveals none; the local read has no floor and sums what it holds.
|
|
14
|
+
const keyOf = ({ cell, code, failed }) => `${cell.frameBucket}/${cell.day}/${code}/${failed}`;
|
|
15
|
+
const failures = new Map([...groupBy(edges, keyOf)].map(([key, group]) => [key, total(group.map(({ cell }) => cell), (cell) => cell.failed)]));
|
|
16
|
+
return edges.flatMap((edge) => {
|
|
17
|
+
const { cell, code, failed, next } = edge;
|
|
18
|
+
const followed = cell.failed;
|
|
19
|
+
const of = failures.get(keyOf(edge)) ?? 0;
|
|
20
|
+
return followed === 0
|
|
21
|
+
? []
|
|
22
|
+
: [
|
|
23
|
+
body('recovery', [cell], { slice: { action: failed, errorCode: code, next, week: cell.day }, value: { followed, of, share: rate(followed, of) }, n: followed, interval: wilson(followed, of) }, RECOVERY_EDGE_METHOD),
|
|
24
|
+
];
|
|
25
|
+
});
|
|
26
|
+
};
|
package/dist/sessions/read.d.ts
CHANGED
|
@@ -16,6 +16,11 @@ export declare const METHODS: {
|
|
|
16
16
|
readonly version: string;
|
|
17
17
|
};
|
|
18
18
|
};
|
|
19
|
+
/** The recovery family's other method (`read-recovery-edge.ts`): what others did next after a failed call, from cells alone. The pool's version hashes it beside the table. */
|
|
20
|
+
export declare const RECOVERY_EDGE_METHOD: {
|
|
21
|
+
readonly name: "next-action-after-failure";
|
|
22
|
+
readonly version: "1";
|
|
23
|
+
};
|
|
19
24
|
/** The fit family's other method (`read-call-shape.ts`). The pool's version hashes it beside the table, for the same reason. */
|
|
20
25
|
export declare const CALL_SHAPE_METHOD: {
|
|
21
26
|
readonly name: "call-shape";
|
|
@@ -27,7 +32,7 @@ export type ReadBasis = 'all' | 'unexposed';
|
|
|
27
32
|
export interface ReadSlice {
|
|
28
33
|
[axis: string]: string;
|
|
29
34
|
}
|
|
30
|
-
/** What a read says.
|
|
35
|
+
/** What a read says. Names and numbers only: a read never holds a path, a prompt, or an error's text. A local read can name a long-tail program or native tool name (taken from the call's segments, through the open gate, which keeps the name shape but not your redaction or exclusion terms), and a pool read can also name a long-tail server or skill, which the pool serves only at the floor. */
|
|
31
36
|
export interface ReadValue {
|
|
32
37
|
[metric: string]: number | string | null | {
|
|
33
38
|
[name: string]: number;
|
|
@@ -64,6 +69,8 @@ export interface Read {
|
|
|
64
69
|
interval: ReadInterval | null;
|
|
65
70
|
asOf: ReadAsOf;
|
|
66
71
|
basis: ReadBasis;
|
|
72
|
+
/** The floor this read cleared (its family's, which is the public floor unless `floors.families` names the family): every cell it counts had at least this many clusters. Stated by a pool that serves the read, and absent from a read computed on one operator's own store, which applies no public floor. */
|
|
73
|
+
floor?: number;
|
|
67
74
|
}
|
|
68
75
|
/** A read before its inputs are checked and its ID and order are computed. */
|
|
69
76
|
export type ReadDraft = Omit<Read, 'readId' | 'order'>;
|
package/dist/sessions/read.js
CHANGED
|
@@ -26,7 +26,7 @@ export const FAMILIES = [...FIRST_ORDER_FAMILIES, 'version-boundary', 'substitut
|
|
|
26
26
|
export const METHODS = {
|
|
27
27
|
recovery: { name: 'signature-record', version: '2' },
|
|
28
28
|
fit: { name: 'failure-rate', version: '2' },
|
|
29
|
-
cost: { name: 'per-prompt-by-class', version: '
|
|
29
|
+
cost: { name: 'per-prompt-by-class', version: '2' },
|
|
30
30
|
change: { name: 'share-in-version-bucket', version: '2' },
|
|
31
31
|
live: { name: 'day-against-trailing-week', version: '2' },
|
|
32
32
|
adoption: { name: 'share-and-span', version: '2' },
|
|
@@ -39,6 +39,8 @@ export const METHODS = {
|
|
|
39
39
|
'day-move': { name: 'move-to-next-day', version: '2' },
|
|
40
40
|
'lead-lag': { name: 'failure-move-leads-share-move', version: '1' },
|
|
41
41
|
};
|
|
42
|
+
/** The recovery family's other method (`read-recovery-edge.ts`): what others did next after a failed call, from cells alone. The pool's version hashes it beside the table. */
|
|
43
|
+
export const RECOVERY_EDGE_METHOD = { name: 'next-action-after-failure', version: '1' };
|
|
42
44
|
/** The fit family's other method (`read-call-shape.ts`). The pool's version hashes it beside the table, for the same reason. */
|
|
43
45
|
export const CALL_SHAPE_METHOD = { name: 'call-shape', version: '2' };
|
|
44
46
|
const ORDER_OF_CELL = 0;
|
|
@@ -51,8 +53,9 @@ export const orderResolver = (reads) => {
|
|
|
51
53
|
return (input) => (input.startsWith(CELL_PREFIX) ? ORDER_OF_CELL : orders.get(input));
|
|
52
54
|
};
|
|
53
55
|
export const readIdOf = (read) => {
|
|
54
|
-
const { family, method, methodVersion, inputs, order, slice, value, n, clusters, interval, asOf, basis } = read;
|
|
55
|
-
|
|
56
|
+
const { family, method, methodVersion, inputs, order, slice, value, n, clusters, interval, asOf, basis, floor } = read;
|
|
57
|
+
// The floor joins the hash only when stated, so a read with none keeps the ID it always had.
|
|
58
|
+
return `${READ_PREFIX}${contentHash({ family, method, methodVersion, inputs, order, slice, value, n, clusters, interval, asOf, basis, ...(floor === undefined ? {} : { floor }) })}`;
|
|
56
59
|
};
|
|
57
60
|
const deepFreeze = (value) => {
|
|
58
61
|
if (typeof value !== 'object' || value === null)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type Setup } from './pool-replica.js';
|
|
2
|
+
import type { Bundle } from './pool-wire.js';
|
|
3
|
+
import type { CurrentPool } from './recall-pool.js';
|
|
4
|
+
import type { Store } from './store.js';
|
|
5
|
+
/** The setup of the store's newest call that states a version: what a pull made outside a hook (the CLI, the MCP tool) is asked from. Undefined for a store with none. */
|
|
6
|
+
export declare const storeSetup: (store: Pick<Store, "sessions" | "tools">) => Setup | undefined;
|
|
7
|
+
/** A verified bundle as a pool: its served cells, motifs, and reads, as of the pool snapshot it was signed from. */
|
|
8
|
+
export declare const bundlePool: (bundle: Bundle) => CurrentPool;
|
|
9
|
+
/** The bundle's pool first, then the local one; either alone when the other is absent. */
|
|
10
|
+
export declare const withBundle: (local: CurrentPool | undefined, bundle: Bundle | undefined) => CurrentPool | undefined;
|
|
11
|
+
/** The pool a pull answers from: the local one `served` gives, with the kept bundle for the store's own setup ahead of it. */
|
|
12
|
+
export declare const recallPool: (storeDir: string, store: Store, served: (storeDir: string, store: Store) => Promise<CurrentPool | undefined>) => Promise<CurrentPool | undefined>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { sessionBundle } from './pool-replica.js';
|
|
2
|
+
import { harnessOf } from './schema.js';
|
|
3
|
+
/** The setup of the store's newest call that states a version: what a pull made outside a hook (the CLI, the MCP tool) is asked from. Undefined for a store with none. */
|
|
4
|
+
export const storeSetup = (store) => {
|
|
5
|
+
const harnesses = new Map(store.sessions.map((session) => [session.session, harnessOf(session)]));
|
|
6
|
+
let newest;
|
|
7
|
+
for (const call of store.tools) {
|
|
8
|
+
if (call.harnessVersion === null || call.ts === null || (newest !== undefined && call.ts <= newest.ts))
|
|
9
|
+
continue;
|
|
10
|
+
newest = { ts: call.ts, setup: { harness: harnesses.get(call.session) ?? 'claude-code', harnessVersion: call.harnessVersion } };
|
|
11
|
+
}
|
|
12
|
+
return newest?.setup;
|
|
13
|
+
};
|
|
14
|
+
/** A verified bundle as a pool: its served cells, motifs, and reads, as of the pool snapshot it was signed from. */
|
|
15
|
+
export const bundlePool = (bundle) => ({
|
|
16
|
+
pool: { cells: bundle.cells, motifs: new Map(bundle.motifs.map((motif) => [motif.motifId, motif])), version: bundle.asOf.pool, lastDay: bundle.asOf.day },
|
|
17
|
+
reads: bundle.reads,
|
|
18
|
+
});
|
|
19
|
+
/** The bundle's pool first, then the local one; either alone when the other is absent. */
|
|
20
|
+
export const withBundle = (local, bundle) => {
|
|
21
|
+
if (bundle === undefined)
|
|
22
|
+
return local;
|
|
23
|
+
const kept = bundlePool(bundle);
|
|
24
|
+
if (local === undefined)
|
|
25
|
+
return kept;
|
|
26
|
+
const motifs = new Map([...local.pool.motifs, ...kept.pool.motifs]);
|
|
27
|
+
const cells = [...kept.pool.cells, ...local.pool.cells];
|
|
28
|
+
const pool = { cells, motifs, version: local.pool.version, lastDay: local.pool.lastDay };
|
|
29
|
+
const reads = [...kept.reads, ...local.reads];
|
|
30
|
+
return { pool, reads };
|
|
31
|
+
};
|
|
32
|
+
/** The pool a pull answers from: the local one `served` gives, with the kept bundle for the store's own setup ahead of it. */
|
|
33
|
+
export const recallPool = async (storeDir, store, served) => withBundle(await served(storeDir, store), await sessionBundle(storeDir, storeSetup(store)));
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type Pool } from './cells.js';
|
|
2
|
+
import { type Position, type RecoveryEdges } from './recall-next.js';
|
|
3
|
+
import type { Store } from './store.js';
|
|
4
|
+
/** The positions one frame keeps: a table prints at most eight, so twelve leaves room for the bundle's to rank ahead without carrying every position a store has. */
|
|
5
|
+
export declare const PER_FRAME_POSITIONS = 12;
|
|
6
|
+
export interface NextByFrame {
|
|
7
|
+
recovery: {
|
|
8
|
+
[frame: string]: RecoveryEdges;
|
|
9
|
+
};
|
|
10
|
+
positions: {
|
|
11
|
+
[frame: string]: Position[];
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The recovery edges and split positions for each harness (key `<harness>/*`, for a session whose version is not stated) and each harness version bucket (`<harness>/<bucket>`).
|
|
16
|
+
* Every recovery key is kept, a below-floor one included, so a hook can tell "a cell, too thin to serve" from "no cell".
|
|
17
|
+
*/
|
|
18
|
+
export declare const nextByFrame: (pool: Pool, store: Store) => NextByFrame;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// The local half of a next-node read, in the asker's frame (t926 Item 9). The pool's next-step and recovery reads sum every harness and version a store holds, so a table built from
|
|
2
|
+
// them would tell a Codex session what Claude Code sessions did. This derives the two families again over the cells of each harness and version bucket, once per deposit, and the
|
|
3
|
+
// hook index keeps the result by frame key; a hook then reads the one entry for the session it serves. It is its own module because the families it imports are the pool's,
|
|
4
|
+
// which a lookup hook must not load (the index step imports this one lazily, as it does `pool.js`).
|
|
5
|
+
import { bucketFields } from './cells.js';
|
|
6
|
+
import { makeRead, orderResolver } from './read.js';
|
|
7
|
+
import { deriveNextStep } from './read-next-step.js';
|
|
8
|
+
import { deriveRecoveryEdges } from './read-recovery-edge.js';
|
|
9
|
+
import { positionsOf, recoveryEdgesOf } from './recall-next.js';
|
|
10
|
+
/** The positions one frame keeps: a table prints at most eight, so twelve leaves room for the bundle's to rank ahead without carrying every position a store has. */
|
|
11
|
+
export const PER_FRAME_POSITIONS = 12;
|
|
12
|
+
const readsOf = (pool, store) => {
|
|
13
|
+
const asOf = { pool: pool.version, day: pool.lastDay };
|
|
14
|
+
const orderOf = orderResolver([]);
|
|
15
|
+
return [...deriveNextStep({ pool, store }), ...deriveRecoveryEdges({ pool, store })].map((body) => makeRead({ ...body, asOf }, orderOf));
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* The recovery edges and split positions for each harness (key `<harness>/*`, for a session whose version is not stated) and each harness version bucket (`<harness>/<bucket>`).
|
|
19
|
+
* Every recovery key is kept, a below-floor one included, so a hook can tell "a cell, too thin to serve" from "no cell".
|
|
20
|
+
*/
|
|
21
|
+
export const nextByFrame = (pool, store) => {
|
|
22
|
+
const frames = new Map();
|
|
23
|
+
for (const cell of pool.cells) {
|
|
24
|
+
if (cell.frameBucket.startsWith('env:'))
|
|
25
|
+
continue;
|
|
26
|
+
const { harness, harnessBucket } = bucketFields(cell.frameBucket);
|
|
27
|
+
for (const key of [`${harness}/*`, `${harness}/${harnessBucket}`]) {
|
|
28
|
+
const buckets = frames.get(key) ?? new Set();
|
|
29
|
+
frames.set(key, buckets);
|
|
30
|
+
buckets.add(cell.frameBucket);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const out = { recovery: {}, positions: {} };
|
|
34
|
+
for (const [key, buckets] of frames) {
|
|
35
|
+
const reads = readsOf({ ...pool, cells: pool.cells.filter((cell) => buckets.has(cell.frameBucket)) }, store);
|
|
36
|
+
const recovery = recoveryEdgesOf(reads);
|
|
37
|
+
const positions = positionsOf(reads).slice(0, PER_FRAME_POSITIONS);
|
|
38
|
+
if (Object.keys(recovery).length > 0)
|
|
39
|
+
out.recovery[key] = recovery;
|
|
40
|
+
if (positions.length > 0)
|
|
41
|
+
out.positions[key] = positions;
|
|
42
|
+
}
|
|
43
|
+
return out;
|
|
44
|
+
};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import type { Read } from './read.js';
|
|
2
|
+
import { type Recall } from './recall-object.js';
|
|
3
|
+
import { type RecallRead } from './recall-reads.js';
|
|
4
|
+
import type { Frame, Harness } from './schema.js';
|
|
5
|
+
/**
|
|
6
|
+
* The count a recovery edge needs before it is served from the local pool. The pool's own served floor is `K_CELL` (5 accounts, `packages/pool/src/derive.ts`); a bundle's read has
|
|
7
|
+
* cleared it already, so only a local edge is held to this, counted in followed steps because one operator's store is one account.
|
|
8
|
+
*/
|
|
9
|
+
export declare const RECOVERY_FLOOR = 5;
|
|
10
|
+
/** The steps a position needs behind it before it can be called split: t782's qualifying cell is a cell topped up to 20 sessions. */
|
|
11
|
+
export declare const POSITION_MIN_N = 20;
|
|
12
|
+
/** The band of t782 plan 1.7: the leading option's share, 20 to 80 percent inclusive. A settled position (above) and a diffuse one (below) are not listed. */
|
|
13
|
+
export declare const POSITION_BAND: {
|
|
14
|
+
readonly low: 0.2;
|
|
15
|
+
readonly high: 0.8;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* The bytes a table may add to a turn's context. Eight lines of about 70 bytes: a session start or a prompt carries it every time it is sent, and a table the agent has to
|
|
19
|
+
* read past is one it stops reading. Bundle positions come first, then local ones, each by observations; the cap cuts from the end, so a local position goes before a bundle one.
|
|
20
|
+
*/
|
|
21
|
+
export declare const TABLE_CAP_BYTES = 600;
|
|
22
|
+
export interface RecoveryEdge {
|
|
23
|
+
next: string;
|
|
24
|
+
followed: number;
|
|
25
|
+
/** The failures of this action and code over every week, the one denominator of every edge's share. */
|
|
26
|
+
of: number;
|
|
27
|
+
clusters: number;
|
|
28
|
+
/** The heaviest week's read, the one the row cites. */
|
|
29
|
+
readId: string;
|
|
30
|
+
day: string | null;
|
|
31
|
+
}
|
|
32
|
+
export interface RecoveryEdges {
|
|
33
|
+
[actionAndCode: string]: RecoveryEdge[];
|
|
34
|
+
}
|
|
35
|
+
export interface Position {
|
|
36
|
+
prefix: string;
|
|
37
|
+
n: number;
|
|
38
|
+
/** The three most common next steps with their counts. */
|
|
39
|
+
next: Array<[string, number]>;
|
|
40
|
+
clusters: number;
|
|
41
|
+
readId: string;
|
|
42
|
+
day: string | null;
|
|
43
|
+
}
|
|
44
|
+
/** The reads one source (the kept bundle, or the local pool) gives the two hooks. */
|
|
45
|
+
export interface NextReads {
|
|
46
|
+
poolVersion: string;
|
|
47
|
+
recovery: RecoveryEdges;
|
|
48
|
+
positions: Position[];
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The recovery edges of a set of reads, by `action/errorCode`, each next step summed over its weeks. A next step that is not a canonical action (or `none`) is left out, so the
|
|
52
|
+
* names a hook shows are the closed ones whatever a store holds.
|
|
53
|
+
*/
|
|
54
|
+
export declare const recoveryEdgesOf: (reads: readonly Read[], keep?: (followed: number) => boolean) => RecoveryEdges;
|
|
55
|
+
/** The positions of a set of reads where the next step is split by t782's rule: enough observations, and the leading step's share inside the band. */
|
|
56
|
+
export declare const positionsOf: (reads: readonly Read[]) => Position[];
|
|
57
|
+
export type RecoveryDecision = {
|
|
58
|
+
kind: 'serve';
|
|
59
|
+
source: 'bundle' | 'local';
|
|
60
|
+
edges: RecoveryEdge[];
|
|
61
|
+
poolVersion: string;
|
|
62
|
+
}
|
|
63
|
+
/** The top next step is the failed action again: nothing to add to what the agent does anyway. */
|
|
64
|
+
| {
|
|
65
|
+
kind: 'retry';
|
|
66
|
+
}
|
|
67
|
+
/** A local edge under the floor. */
|
|
68
|
+
| {
|
|
69
|
+
kind: 'floor';
|
|
70
|
+
}
|
|
71
|
+
/** A cell covers this action and error class and its top next step is that nothing followed: the failure is covered, so no older line answers it either. */
|
|
72
|
+
| {
|
|
73
|
+
kind: 'ended';
|
|
74
|
+
}
|
|
75
|
+
/** No cell for this action and error class. */
|
|
76
|
+
| {
|
|
77
|
+
kind: 'none';
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* What a failure is answered with: the bundle's edges for this action and error class if it holds any (they cleared the pool's floor), else the local pool's. Silent when the top
|
|
81
|
+
* next step is a same-step retry (direction 13c), when a local edge is under `RECOVERY_FLOOR`, or when the top is that nothing followed (`ended`).
|
|
82
|
+
*/
|
|
83
|
+
export declare const recoveryDecision: (bundle: NextReads | undefined, local: {
|
|
84
|
+
recovery: RecoveryEdges;
|
|
85
|
+
poolVersion: string;
|
|
86
|
+
} | undefined, action: string, code: string) => RecoveryDecision;
|
|
87
|
+
/** The recovery read as text: names (closed) and numbers. */
|
|
88
|
+
export declare const renderRecovery: (action: string, code: string, edges: readonly RecoveryEdge[]) => {
|
|
89
|
+
rendered: string;
|
|
90
|
+
names: string[];
|
|
91
|
+
};
|
|
92
|
+
/** The positions table, with every position that fits under the cap and nothing when none does. */
|
|
93
|
+
export declare const renderPositions: (positions: readonly Position[]) => {
|
|
94
|
+
rendered: string;
|
|
95
|
+
listed: Position[];
|
|
96
|
+
names: string[];
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* The key a hook index files the local pool's reads under for a session: its harness and the bucket of the version it runs (`recall-frames.ts`). A session whose version is not
|
|
100
|
+
* stated is read across every version of its harness, which is the filter that can be applied honestly.
|
|
101
|
+
*/
|
|
102
|
+
export declare const frameKeyOf: (asker: Asker) => string;
|
|
103
|
+
/** The bundle's and the local pool's positions as one list, a prefix once: the bundle's positions by observations, then the local ones it lacks by observations. */
|
|
104
|
+
export declare const mergePositions: (bundle: readonly Position[], local: readonly Position[]) => Position[];
|
|
105
|
+
export interface Asker {
|
|
106
|
+
harness: Harness;
|
|
107
|
+
harnessVersion: string | null;
|
|
108
|
+
}
|
|
109
|
+
export interface NextInput {
|
|
110
|
+
storeDir: string;
|
|
111
|
+
sid: string;
|
|
112
|
+
/** The tail of the transcript the hook was given, which states the version and model; empty when it gave none. */
|
|
113
|
+
tail: string;
|
|
114
|
+
platform: string;
|
|
115
|
+
at: string;
|
|
116
|
+
}
|
|
117
|
+
/** The asker's frame, from the tail of its transcript. */
|
|
118
|
+
export declare const frameFor: (input: Pick<NextInput, "tail" | "platform" | "at">) => Frame;
|
|
119
|
+
/** The reads of the kept bundle for the asker's harness and version; undefined with no version stated, no bundle, or a bundle that does not verify (never an error). */
|
|
120
|
+
export declare const bundleNext: (storeDir: string, asker: Asker) => Promise<NextReads | undefined>;
|
|
121
|
+
/**
|
|
122
|
+
* A served or held read: the row is written before anything is injected, and the arm is the session's for this query against this pool. The row is the recall's exposure record
|
|
123
|
+
* (ids and counts, and the rendered text a held read would have shown); an arm that kept it back is returned so the hook injects nothing.
|
|
124
|
+
*/
|
|
125
|
+
export declare const recordRead: (input: NextInput, frame: Frame, query: Recall["query"], armPrefix: readonly string[], poolVersion: string, rendered: string, names: readonly string[], reads: readonly RecallRead[]) => Promise<Recall>;
|
|
126
|
+
/** The recovery read for one failure, as a recall row. */
|
|
127
|
+
export declare const recoveryRecall: (input: NextInput, setup: Asker, action: string, code: string, decision: Extract<RecoveryDecision, {
|
|
128
|
+
kind: "serve";
|
|
129
|
+
}>) => Promise<Recall>;
|
|
130
|
+
/** The old signature line's recall: the local signature index, held to the same retry rule and floor. */
|
|
131
|
+
export declare const signatureRecall: (input: NextInput, setup: Asker, tool: string, sig: string, found: {
|
|
132
|
+
sessions: number;
|
|
133
|
+
followed: Array<[string, number]>;
|
|
134
|
+
}, poolVersion: string) => Promise<Recall>;
|
|
135
|
+
/**
|
|
136
|
+
* The signature line is a same-step retry when the tool that failed is the one that most often followed, strictly: a tie with another tool is not a retry-only cell. It keeps its
|
|
137
|
+
* own floor, the index's two sessions: it states how many sessions saw the error, so a thin count is printed as thin, which a cell's share is not.
|
|
138
|
+
*/
|
|
139
|
+
export declare const isSignatureRetry: (tool: string, found: {
|
|
140
|
+
followed: Array<[string, number]>;
|
|
141
|
+
}) => boolean;
|
|
142
|
+
/** The positions table for a session, as a recall row; undefined when no position qualifies or the table is the one `previous` already sent. */
|
|
143
|
+
export declare const positionsRecall: (input: NextInput, setup: Asker, positions: readonly Position[], poolVersion: string, previous?: string) => Promise<Recall | undefined>;
|
|
144
|
+
/** The text the session was last sent a table with, from `recalls.jsonl`; undefined when it was never sent one. */
|
|
145
|
+
export declare const lastTable: (storeDir: string, sid: string) => Promise<string | undefined>;
|