@manyoars/oars 0.4.1 → 0.5.2
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 +79 -29
- package/dist/bin.js +3 -1
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +207 -21
- package/dist/club.d.ts +19 -0
- package/dist/club.js +132 -0
- package/dist/gate/hook-fill-main.js +1 -1
- package/dist/gate/hook-fill.d.ts +1 -1
- package/dist/gate/hook-fill.js +1 -1
- package/dist/gate/hook-main.js +7 -5
- package/dist/gate/hook.d.ts +12 -1
- package/dist/gate/hook.js +38 -7
- package/dist/hook-bin.js +23 -4
- package/dist/main.js +5 -0
- package/dist/node-floor.d.ts +10 -0
- package/dist/node-floor.js +23 -0
- package/dist/oars-hook.d.ts +2 -0
- package/dist/oars-hook.js +7 -0
- package/dist/sessions/allow.d.ts +1 -0
- package/dist/sessions/allow.js +1 -1
- package/dist/sessions/call-shape.d.ts +111 -0
- package/dist/sessions/call-shape.js +464 -0
- package/dist/sessions/candidates.d.ts +65 -0
- package/dist/sessions/candidates.js +236 -0
- package/dist/sessions/card-command.d.ts +24 -0
- package/dist/sessions/card-command.js +71 -0
- package/dist/sessions/card-font.d.ts +6 -0
- package/dist/sessions/card-font.js +36 -0
- package/dist/sessions/card-line.d.ts +30 -0
- package/dist/sessions/card-line.js +105 -0
- package/dist/sessions/card-render.d.ts +15 -0
- package/dist/sessions/card-render.js +146 -0
- package/dist/sessions/card.d.ts +59 -0
- package/dist/sessions/card.js +107 -0
- package/dist/sessions/cell-increments.d.ts +14 -3
- package/dist/sessions/cell-increments.js +79 -12
- package/dist/sessions/cells.d.ts +42 -4
- package/dist/sessions/cells.js +72 -18
- package/dist/sessions/codex-hook-bin.d.ts +1 -0
- package/dist/sessions/codex-hook-bin.js +24 -0
- package/dist/sessions/codex-hook-install.d.ts +21 -0
- package/dist/sessions/codex-hook-install.js +47 -0
- package/dist/sessions/codex-hook-view.d.ts +20 -0
- package/dist/sessions/codex-hook-view.js +20 -0
- package/dist/sessions/codex-hook.d.ts +77 -0
- package/dist/sessions/codex-hook.js +255 -0
- package/dist/sessions/codex.d.ts +9 -0
- package/dist/sessions/codex.js +104 -18
- package/dist/sessions/command.d.ts +17 -2
- package/dist/sessions/command.js +145 -33
- package/dist/sessions/copilot.d.ts +3 -0
- package/dist/sessions/copilot.js +12 -4
- package/dist/sessions/derive.js +2 -1
- package/dist/sessions/empty-state.d.ts +17 -0
- package/dist/sessions/empty-state.js +38 -0
- package/dist/sessions/exclusion.js +48 -3
- package/dist/sessions/extract.d.ts +16 -3
- package/dist/sessions/extract.js +281 -24
- package/dist/sessions/gemini.d.ts +2 -0
- package/dist/sessions/gemini.js +29 -2
- package/dist/sessions/harness-files.d.ts +27 -0
- package/dist/sessions/harness-files.js +109 -0
- package/dist/sessions/head-only.d.ts +7 -0
- package/dist/sessions/head-only.js +21 -0
- package/dist/sessions/hook-calls.js +4 -3
- package/dist/sessions/hooks.d.ts +1 -1
- package/dist/sessions/hooks.js +6 -2
- package/dist/sessions/identity.d.ts +6 -0
- package/dist/sessions/injected.d.ts +13 -0
- package/dist/sessions/injected.js +260 -0
- package/dist/sessions/install.d.ts +31 -20
- package/dist/sessions/install.js +80 -82
- package/dist/sessions/load-status.d.ts +41 -0
- package/dist/sessions/load-status.js +156 -0
- package/dist/sessions/machine-child-deps.d.ts +5 -0
- package/dist/sessions/machine-child-deps.js +83 -0
- package/dist/sessions/machine-child.d.ts +43 -0
- package/dist/sessions/machine-child.js +120 -0
- package/dist/sessions/machine-credential.d.ts +10 -0
- package/dist/sessions/machine-credential.js +20 -0
- package/dist/sessions/machine-enroll.d.ts +47 -0
- package/dist/sessions/machine-enroll.js +230 -0
- package/dist/sessions/machine-hold.d.ts +49 -0
- package/dist/sessions/machine-hold.js +88 -0
- package/dist/sessions/machine-ids.d.ts +6 -0
- package/dist/sessions/machine-ids.js +9 -0
- package/dist/sessions/machine-notice.d.ts +21 -0
- package/dist/sessions/machine-notice.js +89 -0
- package/dist/sessions/machine-pull.d.ts +40 -0
- package/dist/sessions/machine-pull.js +220 -0
- package/dist/sessions/machine-repo.d.ts +7 -0
- package/dist/sessions/machine-repo.js +30 -0
- package/dist/sessions/machine-scan-cache.d.ts +12 -0
- package/dist/sessions/machine-scan-cache.js +47 -0
- package/dist/sessions/machine-send.d.ts +12 -0
- package/dist/sessions/machine-send.js +156 -0
- package/dist/sessions/machine-stop.d.ts +22 -0
- package/dist/sessions/machine-stop.js +53 -0
- package/dist/sessions/machine.d.ts +41 -0
- package/dist/sessions/machine.js +89 -0
- package/dist/sessions/opencode.d.ts +2 -0
- package/dist/sessions/opencode.js +6 -0
- package/dist/sessions/otel.d.ts +21 -0
- package/dist/sessions/otel.js +109 -38
- package/dist/sessions/owner-only.d.ts +7 -0
- package/dist/sessions/owner-only.js +34 -0
- package/dist/sessions/pi.d.ts +13 -1
- package/dist/sessions/pi.js +27 -13
- package/dist/sessions/pool-books.d.ts +1 -1
- package/dist/sessions/pool-books.js +1 -1
- package/dist/sessions/pool-choices.d.ts +4 -2
- package/dist/sessions/pool-choices.js +4 -2
- package/dist/sessions/pool-command.d.ts +46 -6
- package/dist/sessions/pool-command.js +613 -136
- package/dist/sessions/pool-delta.d.ts +7 -0
- package/dist/sessions/pool-delta.js +28 -5
- package/dist/sessions/pool-due.d.ts +43 -0
- package/dist/sessions/pool-due.js +113 -0
- package/dist/sessions/pool-nightly.d.ts +9 -1
- package/dist/sessions/pool-nightly.js +65 -21
- package/dist/sessions/pool-outbox.d.ts +51 -5
- package/dist/sessions/pool-outbox.js +217 -24
- package/dist/sessions/pool-print.d.ts +1 -1
- package/dist/sessions/pool-raw.js +4 -3
- package/dist/sessions/pool-replica.d.ts +4 -4
- package/dist/sessions/pool-replica.js +26 -7
- package/dist/sessions/pool-send-lock.js +1 -1
- package/dist/sessions/pool-sent.d.ts +2 -0
- package/dist/sessions/pool-sent.js +21 -4
- package/dist/sessions/pool-wire.d.ts +176 -15
- package/dist/sessions/pool-wire.js +297 -33
- package/dist/sessions/pool.js +4 -2
- package/dist/sessions/public-vocabulary.d.ts +5 -0
- package/dist/sessions/public-vocabulary.js +16 -0
- package/dist/sessions/read-call-shape.js +12 -6
- package/dist/sessions/read-fit.d.ts +5 -0
- package/dist/sessions/read-fit.js +23 -2
- package/dist/sessions/read-interaction.js +1 -1
- package/dist/sessions/read-lead-lag.js +1 -1
- package/dist/sessions/read-readable.js +1 -1
- package/dist/sessions/read.d.ts +1 -1
- package/dist/sessions/recall-consequence.d.ts +2 -2
- package/dist/sessions/recall-consequence.js +14 -9
- package/dist/sessions/recall-object.d.ts +9 -2
- package/dist/sessions/receipt.d.ts +8 -0
- package/dist/sessions/receipt.js +78 -0
- package/dist/sessions/recovery-calls.d.ts +3 -2
- package/dist/sessions/recovery-calls.js +9 -3
- package/dist/sessions/redact.d.ts +11 -2
- package/dist/sessions/redact.js +201 -23
- package/dist/sessions/repo-root.d.ts +7 -0
- package/dist/sessions/repo-root.js +17 -13
- package/dist/sessions/reversion.d.ts +8 -0
- package/dist/sessions/reversion.js +114 -0
- package/dist/sessions/schema.d.ts +78 -4
- package/dist/sessions/schema.js +31 -1
- package/dist/sessions/session-shape.d.ts +2 -1
- package/dist/sessions/session-shape.js +15 -0
- package/dist/sessions/sources.d.ts +9 -0
- package/dist/sessions/sources.js +16 -5
- package/dist/sessions/steps.js +3 -2
- package/dist/sessions/store.d.ts +41 -2
- package/dist/sessions/store.js +68 -16
- package/dist/sessions/try-demo.d.ts +8 -0
- package/dist/sessions/try-demo.js +90 -0
- package/dist/sessions/try.d.ts +13 -0
- package/dist/sessions/try.js +50 -0
- package/dist/sessions/vscode-copilot.d.ts +4 -0
- package/dist/sessions/vscode-copilot.js +25 -18
- package/dist/telemetry/command.js +12 -6
- package/dist/telemetry/consent.d.ts +9 -3
- package/dist/telemetry/consent.js +25 -9
- package/dist/telemetry/fields.d.ts +11 -0
- package/dist/telemetry/fields.js +56 -0
- package/dist/telemetry/measures.d.ts +13 -0
- package/dist/telemetry/measures.js +30 -0
- package/dist/telemetry/observe.d.ts +15 -2
- package/dist/telemetry/observe.js +34 -7
- package/dist/telemetry/region.d.ts +6 -0
- package/dist/telemetry/region.js +28 -0
- package/dist/telemetry/report.js +1 -1
- package/dist/telemetry/schema.d.ts +3 -0
- package/dist/telemetry/schema.js +11 -1
- package/dist/telemetry/send.d.ts +5 -2
- package/dist/telemetry/send.js +15 -7
- package/dist/telemetry/words.d.ts +6 -0
- package/dist/telemetry/words.js +6 -0
- package/package.json +24 -5
|
@@ -258,6 +258,20 @@ export const HARNESS_TOOLS = new Map(Object.entries({
|
|
|
258
258
|
export const HARNESS_AGENT_TYPES = new Map(Object.entries({
|
|
259
259
|
'claude-code': 'general-purpose Explore Plan statusline-setup output-style-setup claude-code-guide',
|
|
260
260
|
}).map(([harness, types]) => [harness, words(types)]));
|
|
261
|
+
/**
|
|
262
|
+
* The closed values of a public tool's arguments, by tool and key: a string value on this list goes on the wire as itself (`argValues`), one off it goes nowhere. A boolean of any key is closed already and needs no row.
|
|
263
|
+
* The subagent types are the built-ins (`HARNESS_AGENT_TYPES`), so a custom agent's name is off the list.
|
|
264
|
+
*/
|
|
265
|
+
const BUILT_IN_AGENTS = [...(HARNESS_AGENT_TYPES.get('claude-code') ?? new Set())].join(' ');
|
|
266
|
+
const CLOSED_ROWS = {
|
|
267
|
+
Agent: { subagent_type: BUILT_IN_AGENTS, model: 'sonnet opus haiku inherit' },
|
|
268
|
+
Grep: { output_mode: 'content files_with_matches count' },
|
|
269
|
+
NotebookEdit: { cell_type: 'code markdown', edit_mode: 'replace insert delete' },
|
|
270
|
+
Task: { subagent_type: BUILT_IN_AGENTS, model: 'sonnet opus haiku inherit' },
|
|
271
|
+
};
|
|
272
|
+
export const CLOSED_ARG_VALUES = new Map(Object.entries(CLOSED_ROWS).map(([tool, keys]) => [tool, new Map(Object.entries(keys).map(([key, values]) => [key, words(values)]))]));
|
|
273
|
+
/** The keys of `CLOSED_ROWS` whose list is not exhaustive: a custom agent's name or a full model id is off it and has no `enum` cell, so the key's on-list values do not sum to the calls that send it. */
|
|
274
|
+
export const OPEN_LIST_KEYS = words('subagent_type model');
|
|
261
275
|
/** What a subagent type pools as when it is not a built-in: a custom agent's name is private. */
|
|
262
276
|
export const CUSTOM_AGENT = 'custom';
|
|
263
277
|
/** A subagent type as it pools: a built-in of a harness as itself, anything else as `custom`. */
|
|
@@ -293,3 +307,5 @@ export const TOOL_INPUT_KEYS = new Map(Object.entries({
|
|
|
293
307
|
WebSearch: 'query allowed_domains blocked_domains',
|
|
294
308
|
Write: 'file_path content',
|
|
295
309
|
}).map(([tool, keys]) => [tool, words(keys)]));
|
|
310
|
+
/** Every key name a public tool's pinned list holds: a value feature or closed value keyed on one of these names nothing private, so the pool serves its cell at the clear floor. */
|
|
311
|
+
export const PUBLIC_INPUT_KEYS = new Set([...TOOL_INPUT_KEYS.values()].flatMap((keys) => [...keys]));
|
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
// Call shape (the fit family's other method): one tool and its input key names, over the floors a shape needs before a hook may push it. A shape is what a pre-tool hook can
|
|
2
|
-
// compute from the call it is about to run, so this read is what a push cites.
|
|
3
|
-
//
|
|
4
|
-
|
|
2
|
+
// compute from the call it is about to run, so this read is what a push cites. Its evidence is the shape cells that count the action's calls by those key names (`shape:<action>/keys/<names>`, a week
|
|
3
|
+
// cell per version bucket, `shapeClass`), in the weeks the shape failed. Same confounders as `read-fit.ts`: a failure is not a misfit, and prompt class is not adjusted for. A bundle holds no store, so the
|
|
4
|
+
// pool serves the same cells as `fit-shape` (`deriveFitShape`), by every facet of shape and not this one tool's.
|
|
5
|
+
import { lastDayOfCell, parseShapeClass, UNKNOWN } from './cells.js';
|
|
6
|
+
import { OPEN_GATE, wireKeys } from './pool-wire.js';
|
|
5
7
|
import { CALL_SHAPE_METHOD } from './read.js';
|
|
6
8
|
import { actionOfShape, fitReads, toolShapeId } from './recall-reads.js';
|
|
7
9
|
import { body, atGrain, rate, wilson } from './read-support.js';
|
|
8
10
|
export const deriveCallShape = ({ pool, store }) => fitReads(store).flatMap((read) => {
|
|
9
11
|
const action = actionOfShape(read.tool, read.harnesses);
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
const
|
|
12
|
+
// The cell names the keys as the wire sends them (`projectedStore`); a record captured before steps was never projected, so its raw names are the other spelling.
|
|
13
|
+
const names = new Set([wireKeys(read.keys, read.tool, OPEN_GATE).keys.join(','), read.keys.join(',')]);
|
|
14
|
+
const within = (cell) => read.firstDay !== null && read.lastDay !== null && cell.day <= read.lastDay && lastDayOfCell(cell) >= read.firstDay;
|
|
15
|
+
const cells = atGrain(pool, 'week').filter((cell) => {
|
|
16
|
+
const shape = cell.day === UNKNOWN ? null : parseShapeClass(pool.motifs.get(cell.motifId)?.cls ?? '');
|
|
17
|
+
return shape !== null && shape.action === action && shape.facet === 'keys' && names.has(shape.value) && cell.failed > 0 && within(cell);
|
|
18
|
+
});
|
|
13
19
|
// A shape whose failures are in no cell has nothing to cite, so it is not a read: its answers keep their stand-in.
|
|
14
20
|
if (cells.length === 0)
|
|
15
21
|
return [];
|
|
@@ -5,3 +5,8 @@ export declare const deriveFit: DeriveFamily;
|
|
|
5
5
|
* this read is its own, and a read of the version cells does not see these cells (`atGrain`). A shell the frame does not name reads as `unknown`, as a missing model does.
|
|
6
6
|
*/
|
|
7
7
|
export declare const deriveFitEnvironment: DeriveFamily;
|
|
8
|
+
/**
|
|
9
|
+
* Fit by call shape: how often calls of an action fail when they hold one facet of shape (a target class, an argument class, a count of unsent key names, a set of key names, or a value feature, closed value, or change kind of a sent key), by harness version bucket, from the
|
|
10
|
+
* shape cells (`shapeClass`). A cell counts each call once per facet it holds, so the facets of one action are never summed into one rate. Same confounders as the reads above, and a facet is not the failure's cause.
|
|
11
|
+
*/
|
|
12
|
+
export declare const deriveFitShape: DeriveFamily;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Fit (rank 2): how often a tool fails on this frame, by harness version bucket and model. Effort is not in a cell (rule 3), so it is not a slice. Confounders it does not remove: a failure is not a
|
|
2
2
|
// misfit, red tests can be the method, effort, and prompt class is not adjusted for.
|
|
3
|
-
import { bucketFields, environmentFieldsOf } from './cells.js';
|
|
3
|
+
import { bucketFields, environmentFieldsOf, parseShapeClass } from './cells.js';
|
|
4
4
|
import { motifName } from './motif.js';
|
|
5
|
-
import { body, environmentCells, groupBy, rate, settled, total, unitCells, wilson } from './read-support.js';
|
|
5
|
+
import { atGrain, body, environmentCells, groupBy, rate, settled, total, unitCells, wilson } from './read-support.js';
|
|
6
6
|
export const deriveFit = ({ pool }) => [...groupBy(unitCells(pool, 'week'), ({ cell }) => `${cell.motifId}/${cell.frameBucket}`).values()].flatMap((group) => {
|
|
7
7
|
const [first] = group;
|
|
8
8
|
const cells = group.map(({ cell }) => cell);
|
|
@@ -39,3 +39,24 @@ export const deriveFitEnvironment = ({ pool }) => [...groupBy(environmentCells(p
|
|
|
39
39
|
}),
|
|
40
40
|
];
|
|
41
41
|
});
|
|
42
|
+
/**
|
|
43
|
+
* Fit by call shape: how often calls of an action fail when they hold one facet of shape (a target class, an argument class, a count of unsent key names, a set of key names, or a value feature, closed value, or change kind of a sent key), by harness version bucket, from the
|
|
44
|
+
* shape cells (`shapeClass`). A cell counts each call once per facet it holds, so the facets of one action are never summed into one rate. Same confounders as the reads above, and a facet is not the failure's cause.
|
|
45
|
+
*/
|
|
46
|
+
export const deriveFitShape = ({ pool }) => [...groupBy(atGrain(pool, 'week'), (cell) => `${cell.motifId}/${cell.frameBucket}`).values()].flatMap((group) => {
|
|
47
|
+
const [first] = group;
|
|
48
|
+
const shape = first === undefined ? null : parseShapeClass(pool.motifs.get(first.motifId)?.cls ?? '');
|
|
49
|
+
const n = total(group, settled);
|
|
50
|
+
if (first === undefined || shape === null || n === 0)
|
|
51
|
+
return [];
|
|
52
|
+
const failed = total(group, (cell) => cell.failed);
|
|
53
|
+
const { harness, harnessBucket } = bucketFields(first.frameBucket);
|
|
54
|
+
return [
|
|
55
|
+
body('fit', group, {
|
|
56
|
+
slice: { action: shape.action, facet: shape.facet, value: shape.value, harness, harnessBucket },
|
|
57
|
+
value: { failureRate: rate(failed, n), failed, ok: n - failed },
|
|
58
|
+
n,
|
|
59
|
+
interval: wilson(failed, n),
|
|
60
|
+
}),
|
|
61
|
+
];
|
|
62
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Interaction (spec Section 6, order 2): a tool fails more on one tier than another, beyond how those two tiers differ on every other tool. The difference of differences is
|
|
2
2
|
// what makes it an interaction: a tier that fails more on everything has a main effect, usually the harder work it is given. The axis is the tier assigned, not a model version
|
|
3
|
-
// (ruling
|
|
3
|
+
// (ruling owner-08: one version per model on this store), and comparisons hold the version bucket constant. Effort is not in a cell (rule 3), so nothing here holds it. Confounders it does not remove: task assignment by
|
|
4
4
|
// tier, which is the operator's own choice and the largest of them, effort, prompt class, and a failure not being a misfit.
|
|
5
5
|
import { UNKNOWN } from './cells.js';
|
|
6
6
|
import { candidateOf, differenceOf, estimateOf, screen, TRIALS_FLOOR } from './read-order.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Lead-lag (spec Section 6, order 3), the labeled experiment: does a tool's failure rate moving come before its share moving? Chosen over a model release moving tool choice
|
|
2
|
-
// because each model has one version here with no release date (ruling
|
|
2
|
+
// because each model has one version here with no release date (ruling owner-08), and two version boundaries are too few to cut in time, while consecutive days over many tools
|
|
3
3
|
// can be cut. A search over pairs of tools was rejected: it manufactures the false patterns Section 6 warns about, and this is one question asked once.
|
|
4
4
|
//
|
|
5
5
|
// A pair shares no day between its sides. A failure move ending on day d is paired with the share move from d+1 to d+2, not d to d+1: a burst of failing calls on d lifts both the
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// What this store cannot read, stated rather than forced (ruling
|
|
1
|
+
// What this store cannot read, stated rather than forced (ruling owner-08). A family that finds nothing has made no claim about why, so a reader of an empty family could take
|
|
2
2
|
// "no reads" for "nothing moved". These rows separate the two: a question the store cannot ask from one it asked and got no answer to.
|
|
3
3
|
import { interactionCandidates } from './read-interaction.js';
|
|
4
4
|
import { leadLagPairs, PAIRS_FLOOR } from './read-lead-lag.js';
|
package/dist/sessions/read.d.ts
CHANGED
|
@@ -69,7 +69,7 @@ export interface Read {
|
|
|
69
69
|
interval: ReadInterval | null;
|
|
70
70
|
asOf: ReadAsOf;
|
|
71
71
|
basis: ReadBasis;
|
|
72
|
-
/** The floor
|
|
72
|
+
/** The lowest floor among the cells it cites, each cell's being the floor of its class in the read's family (`cellFloor`): 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
73
|
floor?: number;
|
|
74
74
|
}
|
|
75
75
|
/** A read before its inputs are checked and its ID and order are computed. */
|
|
@@ -31,9 +31,9 @@ export interface RecallCountRow {
|
|
|
31
31
|
sessions: number;
|
|
32
32
|
/** The size of what was injected, or for a held recall would have been. */
|
|
33
33
|
tokens: number;
|
|
34
|
-
/** Recalls with a consequence window, and of those how many
|
|
34
|
+
/** Recalls with a consequence window, and of those how many have a call whose action name is one the recall carried. */
|
|
35
35
|
windows: number;
|
|
36
|
-
|
|
36
|
+
nameMatched: number;
|
|
37
37
|
windowCalls: number;
|
|
38
38
|
/** Null when no window in the row measured any tokens. */
|
|
39
39
|
windowTokens: number | null;
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// shown recall touches the graph, because a held one reached nobody: it adds no `recall.*` step and exposes no cell.
|
|
4
4
|
import { motifsOf, stepsOfCall } from './motif.js';
|
|
5
5
|
import { bucketOf, floorOf } from './pool-wire.js';
|
|
6
|
+
import { reversionsOf } from './reversion.js';
|
|
6
7
|
import { harnessOf } from './schema.js';
|
|
7
8
|
/** A consequence window ends at the prompt's end or after this many calls (spec Section 2.7). */
|
|
8
9
|
export const WINDOW_CALLS = 20;
|
|
@@ -35,14 +36,16 @@ const positionOf = (line, at) => {
|
|
|
35
36
|
const next = line.findIndex((tool) => tool.ts !== null && tool.ts > at);
|
|
36
37
|
return next < 0 ? line.length : next;
|
|
37
38
|
};
|
|
38
|
-
|
|
39
|
+
/** The values the prompt first showed, and beside them, never in place of them, the nearest later revert of a commit the prompt made (null when none is tied). */
|
|
40
|
+
const outcomeOf = (prompt, reverted) => ({
|
|
39
41
|
errors: prompt?.errors ?? null,
|
|
40
42
|
verify: prompt?.verify ?? null,
|
|
41
43
|
stopped: prompt?.stopped ?? null,
|
|
42
44
|
commits: prompt?.commits ?? null,
|
|
43
45
|
redo: prompt?.redo ?? null,
|
|
46
|
+
reverted: prompt === undefined ? null : reverted,
|
|
44
47
|
});
|
|
45
|
-
const consequenceOf = (recall, session, line, position, harness, prompts) => {
|
|
48
|
+
const consequenceOf = (recall, session, line, position, harness, prompts, reverted) => {
|
|
46
49
|
const prompt = (line[position - 1] ?? line[position])?.prompt ?? null;
|
|
47
50
|
const calls = [];
|
|
48
51
|
let end = 'session';
|
|
@@ -64,9 +67,10 @@ const consequenceOf = (recall, session, line, position, harness, prompts) => {
|
|
|
64
67
|
window: { from: position, to: position + calls.length, calls: calls.length, end },
|
|
65
68
|
// Shape level only: it is the ID that joins across accounts, and named windows would multiply a row that exists per recall.
|
|
66
69
|
motifs: [...new Set(motifsOf(steps).flatMap(({ motif }) => (motif.level === 'shape' ? [motif.motifId] : [])))],
|
|
67
|
-
|
|
70
|
+
// The action name is the whole basis: a call of the shown action that failed, or did something else with it, still matches. The record holds no stronger evidence yet, so none is implied.
|
|
71
|
+
nameMatched: steps.some((step) => names.has(step.action)),
|
|
68
72
|
cost: { tokens: measured.length === 0 ? null : measured.reduce((sum, tokens) => sum + tokens, 0), durationMs: steps.reduce((sum, step) => sum + step.durationMs, 0) },
|
|
69
|
-
outcome: outcomeOf(prompts.get(promptKey(session, prompt))),
|
|
73
|
+
outcome: outcomeOf(prompts.get(promptKey(session, prompt)), reverted(session, prompt)),
|
|
70
74
|
};
|
|
71
75
|
};
|
|
72
76
|
/** Fills `n` and `consequence`. A recall with no session, or from a session this store does not hold, has nothing to read and comes back as it was. */
|
|
@@ -74,13 +78,14 @@ export const deriveConsequences = (store, recalls) => {
|
|
|
74
78
|
const lines = mainLines(store);
|
|
75
79
|
const prompts = new Map(store.prompts.map((prompt) => [promptKey(prompt.session, prompt.index), prompt]));
|
|
76
80
|
const sessions = new Map(store.sessions.map((session) => [session.session, session]));
|
|
81
|
+
const reverted = reversionsOf(store);
|
|
77
82
|
return recalls.map((recall) => {
|
|
78
83
|
const session = recall.sid === null ? undefined : sessions.get(recall.sid);
|
|
79
84
|
if (session === undefined)
|
|
80
85
|
return recall;
|
|
81
86
|
const line = lines.get(session.session) ?? [];
|
|
82
87
|
const position = positionOf(line, recall.at);
|
|
83
|
-
return { ...recall, n: position, consequence: consequenceOf(recall, session.session, line, position, harnessOf(session), prompts) };
|
|
88
|
+
return { ...recall, n: position, consequence: consequenceOf(recall, session.session, line, position, harnessOf(session), prompts, reverted) };
|
|
84
89
|
});
|
|
85
90
|
};
|
|
86
91
|
export const NO_RECALLS = {
|
|
@@ -150,7 +155,7 @@ export const recallCounts = (recalls, day) => {
|
|
|
150
155
|
if (day !== undefined && !recall.at.startsWith(day))
|
|
151
156
|
continue;
|
|
152
157
|
const key = [recall.mode, recall.arm, recall.answer.kind].join(' ');
|
|
153
|
-
const held = rows.get(key) ?? { row: { mode: recall.mode, arm: recall.arm, kind: recall.answer.kind, recalls: 0, sessions: 0, tokens: 0, windows: 0,
|
|
158
|
+
const held = rows.get(key) ?? { row: { mode: recall.mode, arm: recall.arm, kind: recall.answer.kind, recalls: 0, sessions: 0, tokens: 0, windows: 0, nameMatched: 0, windowCalls: 0, windowTokens: null, overlapped: 0 }, sessions: new Set() };
|
|
154
159
|
rows.set(key, held);
|
|
155
160
|
const { row, sessions } = held;
|
|
156
161
|
row.recalls += 1;
|
|
@@ -162,8 +167,8 @@ export const recallCounts = (recalls, day) => {
|
|
|
162
167
|
if (consequence === undefined)
|
|
163
168
|
continue;
|
|
164
169
|
row.windows += 1;
|
|
165
|
-
if (consequence.
|
|
166
|
-
row.
|
|
170
|
+
if (consequence.nameMatched)
|
|
171
|
+
row.nameMatched += 1;
|
|
167
172
|
row.windowCalls += consequence.window.calls;
|
|
168
173
|
if (consequence.cost.tokens !== null)
|
|
169
174
|
row.windowTokens = (row.windowTokens ?? 0) + consequence.cost.tokens;
|
|
@@ -175,5 +180,5 @@ export const recallCounts = (recalls, day) => {
|
|
|
175
180
|
export const formatRecallCounts = (rows) => rows.length === 0
|
|
176
181
|
? 'No recalls in that span.'
|
|
177
182
|
: rows
|
|
178
|
-
.map((row) => `${row.mode} ${row.arm} ${row.kind} | ${String(row.recalls)} recalls, ${String(row.sessions)} sessions, ${String(row.tokens)} tokens | ${String(row.windows)} windows, ${String(row.
|
|
183
|
+
.map((row) => `${row.mode} ${row.arm} ${row.kind} | ${String(row.recalls)} recalls, ${String(row.sessions)} sessions, ${String(row.tokens)} tokens | ${String(row.windows)} windows, ${String(row.nameMatched)} name-matched, ${String(row.windowCalls)} calls, ${row.windowTokens === null ? 'window tokens unmeasured' : `${String(row.windowTokens)} window tokens`}, ${String(row.overlapped)} overlapped`)
|
|
179
184
|
.join('\n');
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Reversion } from './reversion.js';
|
|
1
2
|
import type { Frame } from './schema.js';
|
|
2
3
|
export declare const recallsPath: (storeDir: string) => string;
|
|
3
4
|
/** The share of pushes held back (spec Section 8, open parameter). */
|
|
@@ -38,7 +39,7 @@ export interface RecallAnswer {
|
|
|
38
39
|
bytes?: number;
|
|
39
40
|
/** What the asker saw, or for a held recall would have seen. It holds structured fields only (`recall-render.ts`). */
|
|
40
41
|
rendered: string;
|
|
41
|
-
/** The closed names a
|
|
42
|
+
/** The closed names a name match can find: canonical actions, in the order shown. */
|
|
42
43
|
names: string[];
|
|
43
44
|
}
|
|
44
45
|
export type DeltaAxis = 'same' | 'differs' | 'unknown';
|
|
@@ -79,6 +80,11 @@ export interface RecallOutcome {
|
|
|
79
80
|
stopped: string | null;
|
|
80
81
|
commits: number | null;
|
|
81
82
|
redo: boolean | null;
|
|
83
|
+
/**
|
|
84
|
+
* Where the nearest later clean `git revert <sha>` of a commit this prompt made ran, beside the values above and never in place of them. The sha is the whole tie, so null says none was found, not that
|
|
85
|
+
* none happened: an edit undone any other way, or a commit with no sha, is not read.
|
|
86
|
+
*/
|
|
87
|
+
reverted: Reversion | null;
|
|
82
88
|
}
|
|
83
89
|
/** Derived from the capture records, never captured live. */
|
|
84
90
|
export interface RecallConsequence {
|
|
@@ -90,7 +96,8 @@ export interface RecallConsequence {
|
|
|
90
96
|
end: 'prompt' | 'calls' | 'session';
|
|
91
97
|
};
|
|
92
98
|
motifs: string[];
|
|
93
|
-
|
|
99
|
+
/** Whether an action name the recall carried appears among the window's actions. A match on the name alone: it does not say the call took the advice, was the same use of that action, or went well. */
|
|
100
|
+
nameMatched: boolean;
|
|
94
101
|
/** `tokens` is null when no call in the window measured any: an older capture has no step costs, and zero would read as a free window. */
|
|
95
102
|
cost: {
|
|
96
103
|
tokens: number | null;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type RecallQuery } from './query.js';
|
|
2
|
+
import type { Store } from './store.js';
|
|
3
|
+
export declare const RECEIPT_FOOTER = "from my local oars record, manyoars.com";
|
|
4
|
+
export declare const RECEIPT_MISS = "No earlier session in the local record hit this error, so there is no receipt.";
|
|
5
|
+
/** ISO 8601 week of a timestamp, as `2026-W45`; the week holds the Thursday, which fixes the year. */
|
|
6
|
+
export declare const isoWeek: (ts: string) => string | undefined;
|
|
7
|
+
/** The receipt for the best-matching signature of an error text, or null when no earlier session hit it. */
|
|
8
|
+
export declare const receiptOf: (store: Store, query: RecallQuery, now: number, redactDomains?: readonly string[], names?: readonly string[]) => string | null;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// `oars sessions recall --error <text> --receipt`: a fenced Markdown block a person pastes into an issue on the failing tool. It states the tool and
|
|
2
|
+
// harness by listed name, the error's normalized signature, how many earlier sessions hit it and in which weeks, and what those sessions did next. It
|
|
3
|
+
// reads the same match recall does and the same redactor, so nothing here is a second path to a stored string.
|
|
4
|
+
import { ACTION_TABLES } from './actions.js';
|
|
5
|
+
import { redactTranscript } from './redact.js';
|
|
6
|
+
import { recall } from './query.js';
|
|
7
|
+
import { LITERAL } from './public-vocabulary.js';
|
|
8
|
+
import { closedName } from './recall-render.js';
|
|
9
|
+
import { harnessOf } from './schema.js';
|
|
10
|
+
export const RECEIPT_FOOTER = 'from my local oars record, manyoars.com';
|
|
11
|
+
export const RECEIPT_MISS = 'No earlier session in the local record hit this error, so there is no receipt.';
|
|
12
|
+
const OTHER = 'other';
|
|
13
|
+
const SIGNATURE_LIMIT = 120;
|
|
14
|
+
const NEXT_SHOWN = 3;
|
|
15
|
+
const MCP_WORD = /mcp__[A-Za-z0-9_-]+/g;
|
|
16
|
+
const DAY_MS = 86_400_000;
|
|
17
|
+
/** A tool as a person may name it in an issue: a listed name as it stands, anything else `other`. */
|
|
18
|
+
const listedTool = (name) => {
|
|
19
|
+
const listed = closedName(name);
|
|
20
|
+
return listed === LITERAL ? OTHER : listed;
|
|
21
|
+
};
|
|
22
|
+
const listedHarness = (name) => (Object.hasOwn(ACTION_TABLES, name) ? name : OTHER);
|
|
23
|
+
/** ISO 8601 week of a timestamp, as `2026-W45`; the week holds the Thursday, which fixes the year. */
|
|
24
|
+
export const isoWeek = (ts) => {
|
|
25
|
+
const at = Date.parse(ts);
|
|
26
|
+
if (Number.isNaN(at))
|
|
27
|
+
return undefined;
|
|
28
|
+
const day = new Date(Math.floor(at / DAY_MS) * DAY_MS);
|
|
29
|
+
const weekday = day.getUTCDay() === 0 ? 7 : day.getUTCDay();
|
|
30
|
+
const thursday = new Date(day.getTime() + (4 - weekday) * DAY_MS);
|
|
31
|
+
const jan1 = Date.UTC(thursday.getUTCFullYear(), 0, 1);
|
|
32
|
+
const week = Math.floor((thursday.getTime() - jan1) / DAY_MS / 7) + 1;
|
|
33
|
+
return `${String(thursday.getUTCFullYear())}-W${String(week).padStart(2, '0')}`;
|
|
34
|
+
};
|
|
35
|
+
const times = (count) => `${String(count)} ${count === 1 ? 'time' : 'times'}`;
|
|
36
|
+
/** The receipt for the best-matching signature of an error text, or null when no earlier session hit it. */
|
|
37
|
+
export const receiptOf = (store, query, now, redactDomains = [], names = []) => {
|
|
38
|
+
const [top] = recall(store, query, now, 1, redactDomains).matches;
|
|
39
|
+
if (top === undefined)
|
|
40
|
+
return null;
|
|
41
|
+
const rows = store.errors.filter((error) => error.sig === top.sig);
|
|
42
|
+
const sessionIds = new Set(rows.map((error) => error.session));
|
|
43
|
+
const harnesses = [...new Set(store.sessions.filter((session) => sessionIds.has(session.session)).map((session) => listedHarness(harnessOf(session))))].sort();
|
|
44
|
+
const weeks = [...new Set(rows.flatMap((error) => { const week = error.ts === null ? undefined : isoWeek(error.ts); return week === undefined ? [] : [week]; }))].sort().reverse();
|
|
45
|
+
// Listed tool names folded together: two unlisted tools are both `other`, once.
|
|
46
|
+
const next = new Map();
|
|
47
|
+
for (const { tool, count } of top.followedBy)
|
|
48
|
+
next.set(listedTool(tool), (next.get(listedTool(tool)) ?? 0) + count);
|
|
49
|
+
const followed = [...next.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, NEXT_SHOWN).map(([tool, count]) => `${tool} x${String(count)}`);
|
|
50
|
+
// The key is `<tool> | <text>`; the tool is stated by its listed name. The text first loses the literals the store records for the matched sessions
|
|
51
|
+
// (branches, project slugs; the transcript rules have no rule for either), then runs through the transcript rules (home paths, encoded folder slugs,
|
|
52
|
+
// local names, secrets), and only the redacted result is cut. Every unlisted `mcp__` word becomes `other`, with backticks out so the fence holds.
|
|
53
|
+
const recorded = store.sessions.filter((entry) => sessionIds.has(entry.session));
|
|
54
|
+
const labels = new Map();
|
|
55
|
+
for (const entry of recorded) {
|
|
56
|
+
for (const branch of entry.branches)
|
|
57
|
+
labels.set(branch, '<branch>');
|
|
58
|
+
labels.set(entry.project, '<project>');
|
|
59
|
+
}
|
|
60
|
+
// A literal goes only where it stands as a whole token, so a branch named `main` leaves `domain` and `./main.js` alone.
|
|
61
|
+
const literals = [...labels.entries()].filter(([literal]) => literal.length > 0).sort((a, b) => b[0].length - a[0].length || a[0].localeCompare(b[0]));
|
|
62
|
+
const scrubbed = literals.reduce((text, [literal, label]) => text.replace(new RegExp(`(?<![A-Za-z0-9_-])${literal.replaceAll(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?![A-Za-z0-9_-])`, 'g'), label), top.sig.slice(top.sig.indexOf(' | ') + 3));
|
|
63
|
+
const text = redactTranscript(scrubbed, redactDomains, names).text.slice(0, SIGNATURE_LIMIT).replace(MCP_WORD, (word) => listedTool(word)).replaceAll('`', "'");
|
|
64
|
+
return [
|
|
65
|
+
'```markdown',
|
|
66
|
+
'**Error receipt**',
|
|
67
|
+
'',
|
|
68
|
+
`- Tool: ${listedTool(top.tool)}`,
|
|
69
|
+
`- Harness: ${harnesses.join(', ') || OTHER}`,
|
|
70
|
+
`- Error: \`${text}\``,
|
|
71
|
+
`- Earlier sessions that hit it: ${String(top.sessions)} (${times(top.count)})`,
|
|
72
|
+
`- Weeks: ${weeks.join(', ') || 'unknown'}`,
|
|
73
|
+
`- What they did next: ${followed.join(', ') || 'nothing recorded'}; an edit followed ${times(top.editFollowed)}`,
|
|
74
|
+
'',
|
|
75
|
+
RECEIPT_FOOTER,
|
|
76
|
+
'```',
|
|
77
|
+
].join('\n');
|
|
78
|
+
};
|
|
@@ -9,7 +9,7 @@ export type Followed = Pick<ToolObservation, 'side' | 'prompt'> & {
|
|
|
9
9
|
};
|
|
10
10
|
/** How many following calls a failed call lists. */
|
|
11
11
|
export declare const RECOVERY_CALLS = 5;
|
|
12
|
-
/** What followed one failed call: the actions of the next calls, and whether any of them wrote a file. */
|
|
12
|
+
/** What followed one failed call: the actions of the next calls, and whether any of them wrote a file without failing. A write that came back as an error stays in `next` and does not set `editFollowed`. */
|
|
13
13
|
export interface RecoveryCalls {
|
|
14
14
|
next: CanonicalAction[];
|
|
15
15
|
editFollowed: boolean;
|
|
@@ -17,5 +17,6 @@ export interface RecoveryCalls {
|
|
|
17
17
|
/**
|
|
18
18
|
* What followed the call at `at` of a transcript's calls in the order they ran: null for a call that did not fail. The calls that followed are the later ones of the same prompt and the same run (a
|
|
19
19
|
* subagent's call is not what the main run did next), up to 5. The scan ends at the first later call of that run that belongs to another prompt.
|
|
20
|
+
* `failedEditsCount` is the rule of the wire formats before 6, where any later edit counted whether or not its own result was an error; the ingest checks a batch of those formats with it, so a client already released is not refused for a failed edit.
|
|
20
21
|
*/
|
|
21
|
-
export declare const recoveryCallsOf: (calls: readonly Followed[], at: number) => RecoveryCalls | null;
|
|
22
|
+
export declare const recoveryCallsOf: (calls: readonly Followed[], at: number, failedEditsCount?: boolean) => RecoveryCalls | null;
|
|
@@ -7,19 +7,25 @@ const actionOf = ({ steps }) => steps?.[0]?.action ?? 'other';
|
|
|
7
7
|
/**
|
|
8
8
|
* What followed the call at `at` of a transcript's calls in the order they ran: null for a call that did not fail. The calls that followed are the later ones of the same prompt and the same run (a
|
|
9
9
|
* subagent's call is not what the main run did next), up to 5. The scan ends at the first later call of that run that belongs to another prompt.
|
|
10
|
+
* `failedEditsCount` is the rule of the wire formats before 6, where any later edit counted whether or not its own result was an error; the ingest checks a batch of those formats with it, so a client already released is not refused for a failed edit.
|
|
10
11
|
*/
|
|
11
|
-
export const recoveryCallsOf = (calls, at) => {
|
|
12
|
+
export const recoveryCallsOf = (calls, at, failedEditsCount = false) => {
|
|
12
13
|
const failed = calls[at];
|
|
13
14
|
if (failed === undefined || failed.status !== 'error')
|
|
14
15
|
return null;
|
|
15
16
|
const next = [];
|
|
17
|
+
let editFollowed = false;
|
|
16
18
|
for (let index = at + 1; index < calls.length && next.length < RECOVERY_CALLS; index += 1) {
|
|
17
19
|
const call = calls[index];
|
|
18
20
|
if (call === undefined || call.side !== failed.side)
|
|
19
21
|
continue;
|
|
20
22
|
if (call.prompt !== failed.prompt)
|
|
21
23
|
break;
|
|
22
|
-
|
|
24
|
+
const action = actionOf(call);
|
|
25
|
+
next.push(action);
|
|
26
|
+
// An edit whose own result is an error wrote nothing, so it is not an edit that followed. A call with no recorded result is not known to have failed, and still counts.
|
|
27
|
+
if (WRITES.has(action) && (failedEditsCount || call.status !== 'error'))
|
|
28
|
+
editFollowed = true;
|
|
23
29
|
}
|
|
24
|
-
return { next, editFollowed
|
|
30
|
+
return { next, editFollowed };
|
|
25
31
|
};
|
|
@@ -8,10 +8,19 @@ export declare const makeRedactor: (domains?: readonly string[]) => Redactor;
|
|
|
8
8
|
export declare const placeholders: (text: string) => number;
|
|
9
9
|
/** The names this machine's person goes by in a transcript: the account's and the home folder's. A name under three characters would mask ordinary words, so it is left to the path rules. */
|
|
10
10
|
export declare const localNames: (home?: string, account?: () => string) => string[];
|
|
11
|
+
/** What only a sample pack asks of the raw-copy rules (t1111). */
|
|
12
|
+
export interface PackRules {
|
|
13
|
+
/** The accounts the operator's repositories live under (a GitHub user or organization): masked as `<owner>` where one stands as a path segment or after a `:` of a remote. */
|
|
14
|
+
handles?: readonly string[];
|
|
15
|
+
/** Rewrites a tool use id (`toolu_...`, `srvtoolu_...`) to the value the pack keys it to, so a call and its result still pair; without it the entropy rule masks each as `<secret>`. */
|
|
16
|
+
toolId?: (id: string) => string;
|
|
17
|
+
/** Keeps a public host the session itself fetched or searched (the URL or `allowed_domains` of a WebFetch or WebSearch input) in the clear in that call's result. */
|
|
18
|
+
fetchedHosts?: boolean;
|
|
19
|
+
}
|
|
11
20
|
/** A whole transcript body redacted for upload, and how many values were masked (placeholders added, a count and nothing else). `names` are the local usernames to mask wherever they stand as a whole word. */
|
|
12
|
-
export declare const redactTranscript: (body: string, domains?: readonly string[], names?: readonly string[]) => {
|
|
21
|
+
export declare const redactTranscript: (body: string, domains?: readonly string[], names?: readonly string[], injectedKey?: string, pack?: PackRules) => {
|
|
13
22
|
text: string;
|
|
14
23
|
masked: number;
|
|
15
24
|
};
|
|
16
25
|
/** The version of the rules above, stated to the pool with every raw part; a change to any rule of `redactTranscript` raises it. */
|
|
17
|
-
export declare const REDACTION_VERSION = "
|
|
26
|
+
export declare const REDACTION_VERSION = "4";
|