@manyoars/oars 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +147 -28
- package/dist/api.js +1 -1
- package/dist/bin.js +6 -29
- package/dist/cli.d.ts +10 -4
- package/dist/cli.js +440 -91
- package/dist/commands/keys.d.ts +31 -0
- package/dist/commands/keys.js +65 -0
- package/dist/commands/login.d.ts +1 -1
- package/dist/commands/login.js +3 -3
- package/dist/errors.d.ts +13 -13
- package/dist/errors.js +28 -11
- package/dist/gate/checks.d.ts +68 -0
- package/dist/gate/checks.js +584 -0
- package/dist/gate/command.d.ts +14 -0
- package/dist/gate/command.js +61 -0
- package/dist/gate/hook-fill-main.d.ts +1 -0
- package/dist/gate/hook-fill-main.js +16 -0
- package/dist/gate/hook-fill.d.ts +38 -0
- package/dist/gate/hook-fill.js +94 -0
- package/dist/gate/hook-main.d.ts +1 -0
- package/dist/gate/hook-main.js +47 -0
- package/dist/gate/hook-process.d.ts +24 -0
- package/dist/gate/hook-process.js +112 -0
- package/dist/gate/hook-record.d.ts +3 -0
- package/dist/gate/hook-record.js +8 -0
- package/dist/gate/hook.d.ts +48 -0
- package/dist/gate/hook.js +259 -0
- package/dist/gate/install.d.ts +25 -0
- package/dist/gate/install.js +126 -0
- package/dist/gate/local-bin.d.ts +8 -0
- package/dist/gate/local-bin.js +36 -0
- package/dist/gate/notes.d.ts +19 -0
- package/dist/gate/notes.js +27 -0
- package/dist/gate/novelty.d.ts +35 -0
- package/dist/gate/novelty.js +99 -0
- package/dist/gate/policy.d.ts +25 -0
- package/dist/gate/policy.js +73 -0
- package/dist/gate/record.d.ts +120 -0
- package/dist/gate/record.js +125 -0
- package/dist/gate/registry-state.d.ts +10 -0
- package/dist/gate/registry-state.js +30 -0
- package/dist/gate/registry.d.ts +56 -0
- package/dist/gate/registry.js +94 -0
- package/dist/gate/replay.d.ts +20 -0
- package/dist/gate/replay.js +254 -0
- package/dist/gate/report.d.ts +50 -0
- package/dist/gate/report.js +89 -0
- package/dist/gate/scope.d.ts +7 -0
- package/dist/gate/scope.js +27 -0
- package/dist/gate/transcripts.d.ts +68 -0
- package/dist/gate/transcripts.js +232 -0
- package/dist/hook-bin.d.ts +1 -0
- package/dist/hook-bin.js +50 -0
- package/dist/issuer.d.ts +10 -6
- package/dist/issuer.js +19 -24
- package/dist/main.d.ts +1 -0
- package/dist/main.js +67 -0
- package/dist/oidc.js +1 -1
- package/dist/sessions/actions.d.ts +35 -0
- package/dist/sessions/actions.js +429 -0
- package/dist/sessions/allow.d.ts +23 -0
- package/dist/sessions/allow.js +71 -0
- package/dist/sessions/archive.d.ts +11 -0
- package/dist/sessions/archive.js +38 -0
- package/dist/sessions/audit.d.ts +42 -0
- package/dist/sessions/audit.js +104 -0
- package/dist/sessions/baseline.d.ts +62 -0
- package/dist/sessions/baseline.js +190 -0
- package/dist/sessions/call-shape.d.ts +19 -0
- package/dist/sessions/call-shape.js +85 -0
- package/dist/sessions/capture-runtime.d.ts +10 -0
- package/dist/sessions/capture-runtime.js +12 -0
- package/dist/sessions/cell-increments.d.ts +47 -0
- package/dist/sessions/cell-increments.js +243 -0
- package/dist/sessions/cells.d.ts +109 -0
- package/dist/sessions/cells.js +261 -0
- package/dist/sessions/codex.d.ts +53 -0
- package/dist/sessions/codex.js +745 -0
- package/dist/sessions/command.d.ts +127 -0
- package/dist/sessions/command.js +419 -0
- package/dist/sessions/copilot.d.ts +40 -0
- package/dist/sessions/copilot.js +432 -0
- package/dist/sessions/coverage.d.ts +19 -0
- package/dist/sessions/coverage.js +56 -0
- package/dist/sessions/cursor.d.ts +24 -0
- package/dist/sessions/cursor.js +324 -0
- package/dist/sessions/derive.d.ts +57 -0
- package/dist/sessions/derive.js +386 -0
- package/dist/sessions/exclusion.d.ts +87 -0
- package/dist/sessions/exclusion.js +361 -0
- package/dist/sessions/extract.d.ts +181 -0
- package/dist/sessions/extract.js +1806 -0
- package/dist/sessions/fold.d.ts +17 -0
- package/dist/sessions/fold.js +71 -0
- package/dist/sessions/frame.d.ts +24 -0
- package/dist/sessions/frame.js +95 -0
- package/dist/sessions/gemini.d.ts +37 -0
- package/dist/sessions/gemini.js +613 -0
- package/dist/sessions/gold-arms.d.ts +67 -0
- package/dist/sessions/gold-arms.js +172 -0
- package/dist/sessions/gold.d.ts +197 -0
- package/dist/sessions/gold.js +463 -0
- package/dist/sessions/graph.d.ts +12 -0
- package/dist/sessions/graph.js +34 -0
- package/dist/sessions/grok.d.ts +36 -0
- package/dist/sessions/grok.js +421 -0
- package/dist/sessions/hook-calls.d.ts +3 -0
- package/dist/sessions/hook-calls.js +47 -0
- package/dist/sessions/hook-index.d.ts +66 -0
- package/dist/sessions/hook-index.js +164 -0
- package/dist/sessions/hooks.d.ts +17 -0
- package/dist/sessions/hooks.js +281 -0
- package/dist/sessions/identity.d.ts +27 -0
- package/dist/sessions/identity.js +131 -0
- package/dist/sessions/install.d.ts +70 -0
- package/dist/sessions/install.js +246 -0
- package/dist/sessions/lineage.d.ts +19 -0
- package/dist/sessions/lineage.js +118 -0
- package/dist/sessions/mcp.d.ts +7 -0
- package/dist/sessions/mcp.js +157 -0
- package/dist/sessions/merkle.d.ts +7 -0
- package/dist/sessions/merkle.js +28 -0
- package/dist/sessions/motif.d.ts +44 -0
- package/dist/sessions/motif.js +59 -0
- 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 +32 -0
- package/dist/sessions/package-resolve.js +185 -0
- package/dist/sessions/pending.d.ts +34 -0
- package/dist/sessions/pending.js +142 -0
- 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 +115 -0
- package/dist/sessions/pool-command.js +669 -0
- package/dist/sessions/pool-delta.d.ts +54 -0
- package/dist/sessions/pool-delta.js +190 -0
- 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 +76 -0
- package/dist/sessions/pool-outbox.js +313 -0
- package/dist/sessions/pool-print.d.ts +26 -0
- package/dist/sessions/pool-print.js +68 -0
- 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 +435 -0
- package/dist/sessions/pool-wire.js +1100 -0
- package/dist/sessions/pool.d.ts +32 -0
- package/dist/sessions/pool.js +164 -0
- package/dist/sessions/projection.d.ts +75 -0
- package/dist/sessions/projection.js +165 -0
- package/dist/sessions/public-vocabulary.d.ts +72 -0
- package/dist/sessions/public-vocabulary.js +295 -0
- package/dist/sessions/query.d.ts +123 -0
- package/dist/sessions/query.js +268 -0
- package/dist/sessions/read-adoption.d.ts +2 -0
- package/dist/sessions/read-adoption.js +27 -0
- package/dist/sessions/read-call-shape.d.ts +2 -0
- package/dist/sessions/read-call-shape.js +25 -0
- package/dist/sessions/read-change.d.ts +2 -0
- package/dist/sessions/read-change.js +30 -0
- package/dist/sessions/read-cost.d.ts +4 -0
- package/dist/sessions/read-cost.js +48 -0
- package/dist/sessions/read-day-move.d.ts +3 -0
- package/dist/sessions/read-day-move.js +65 -0
- package/dist/sessions/read-fit.d.ts +7 -0
- package/dist/sessions/read-fit.js +41 -0
- package/dist/sessions/read-interaction.d.ts +5 -0
- package/dist/sessions/read-interaction.js +62 -0
- package/dist/sessions/read-lead-lag.d.ts +15 -0
- package/dist/sessions/read-lead-lag.js +81 -0
- package/dist/sessions/read-live.d.ts +6 -0
- package/dist/sessions/read-live.js +38 -0
- package/dist/sessions/read-method.d.ts +2 -0
- package/dist/sessions/read-method.js +29 -0
- package/dist/sessions/read-next-step.d.ts +2 -0
- package/dist/sessions/read-next-step.js +35 -0
- package/dist/sessions/read-order.d.ts +54 -0
- package/dist/sessions/read-order.js +62 -0
- package/dist/sessions/read-readable.d.ts +8 -0
- package/dist/sessions/read-readable.js +24 -0
- package/dist/sessions/read-recovery-edge.d.ts +2 -0
- package/dist/sessions/read-recovery-edge.js +26 -0
- package/dist/sessions/read-recovery.d.ts +20 -0
- package/dist/sessions/read-recovery.js +97 -0
- package/dist/sessions/read-rows.d.ts +13 -0
- package/dist/sessions/read-rows.js +47 -0
- package/dist/sessions/read-substitution.d.ts +5 -0
- package/dist/sessions/read-substitution.js +51 -0
- package/dist/sessions/read-support.d.ts +48 -0
- package/dist/sessions/read-support.js +73 -0
- package/dist/sessions/read-tool-day.d.ts +2 -0
- package/dist/sessions/read-tool-day.js +26 -0
- package/dist/sessions/read-version-boundary.d.ts +31 -0
- package/dist/sessions/read-version-boundary.js +95 -0
- package/dist/sessions/read.d.ts +86 -0
- package/dist/sessions/read.js +87 -0
- package/dist/sessions/recall-bundle.d.ts +12 -0
- package/dist/sessions/recall-bundle.js +33 -0
- package/dist/sessions/recall-consequence.d.ts +45 -0
- package/dist/sessions/recall-consequence.js +179 -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 +139 -0
- package/dist/sessions/recall-object.js +78 -0
- package/dist/sessions/recall-pool.d.ts +19 -0
- package/dist/sessions/recall-pool.js +96 -0
- package/dist/sessions/recall-push.d.ts +26 -0
- package/dist/sessions/recall-push.js +47 -0
- package/dist/sessions/recall-reads.d.ts +124 -0
- package/dist/sessions/recall-reads.js +417 -0
- package/dist/sessions/recall-render.d.ts +19 -0
- package/dist/sessions/recall-render.js +168 -0
- package/dist/sessions/recovery-calls.d.ts +21 -0
- package/dist/sessions/recovery-calls.js +25 -0
- package/dist/sessions/redact.d.ts +17 -0
- package/dist/sessions/redact.js +113 -0
- package/dist/sessions/repo-root.d.ts +10 -0
- package/dist/sessions/repo-root.js +101 -0
- package/dist/sessions/schema.d.ts +694 -0
- package/dist/sessions/schema.js +167 -0
- package/dist/sessions/session-shape.d.ts +76 -0
- package/dist/sessions/session-shape.js +355 -0
- package/dist/sessions/sources.d.ts +50 -0
- package/dist/sessions/sources.js +233 -0
- package/dist/sessions/steps.d.ts +105 -0
- package/dist/sessions/steps.js +250 -0
- package/dist/sessions/store.d.ts +309 -0
- package/dist/sessions/store.js +1498 -0
- package/dist/sessions/strata.d.ts +26 -0
- package/dist/sessions/strata.js +77 -0
- package/dist/sessions/survival.d.ts +27 -0
- package/dist/sessions/survival.js +230 -0
- package/dist/sessions/transcript-tail.d.ts +14 -0
- package/dist/sessions/transcript-tail.js +64 -0
- package/dist/sessions/verify.d.ts +53 -0
- package/dist/sessions/verify.js +286 -0
- package/dist/sessions/vocab-load.d.ts +26 -0
- package/dist/sessions/vocab-load.js +61 -0
- package/dist/sessions/vscode-copilot.d.ts +38 -0
- package/dist/sessions/vscode-copilot.js +494 -0
- 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 +4 -5
- package/vocab/crates.json +1 -0
- package/vocab/go.json +1 -0
- package/vocab/harness.json +1 -0
- package/vocab/homebrew.json +1 -0
- package/vocab/manifest.json +1 -0
- package/vocab/maven.json +1 -0
- package/vocab/models.json +1 -0
- package/vocab/npm.json +1 -0
- package/vocab/programs.json +1 -0
- package/vocab/pypi.json +1 -0
- package/vocab/rubygems.json +1 -0
- package/dist/commands/deploy-input.d.ts +0 -38
- package/dist/commands/deploy-input.js +0 -150
- package/dist/commands/deploy.d.ts +0 -22
- package/dist/commands/deploy.js +0 -124
- package/dist/commands/status.d.ts +0 -7
- package/dist/commands/status.js +0 -18
- package/dist/commands/templates.d.ts +0 -11
- package/dist/commands/templates.js +0 -32
- package/dist/commands/test-charge.d.ts +0 -11
- package/dist/commands/test-charge.js +0 -34
- package/dist/journal.d.ts +0 -26
- package/dist/journal.js +0 -58
- package/parity/deploy-plans/paid-api.json +0 -34
- package/parity/deploy-plans/per-task-agent.json +0 -34
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
// The cell computation a deposit and a local read share (pool vocabulary spec Section 2, "the deposit form", and Section 4, rules 1 to 6). The pool's ingest turns a batch's skeleton into
|
|
2
|
+
// increments with `incrementsOf`, and a local read derives its cells with `cellsOf` over the store it holds, projected the way a deposit projects it (`projectedStore`). Both end in the
|
|
3
|
+
// one `cellsOf`, so a session gives the same cells at the pool and on the machine. This file reads no file system: the pool's Worker bundles it.
|
|
4
|
+
import { ARG_CLASSES } from './call-shape.js';
|
|
5
|
+
import { bucketFields, deriveCells, ENVIRONMENT_BUCKET, isEnvironmentCell } from './cells.js';
|
|
6
|
+
import { bucketOf, floorOf, OTHER, projectedSteps } from './pool-wire.js';
|
|
7
|
+
import { NO_RECALLS } from './recall-consequence.js';
|
|
8
|
+
import { USAGE_FIELDS, harnessOf } from './schema.js';
|
|
9
|
+
const floorTokens = (value) => floorOf('tokens', bucketOf('tokens', value));
|
|
10
|
+
const floorUsage = ({ input, cacheWrite, cacheRead, output, thinking }) => ({
|
|
11
|
+
input: floorTokens(input),
|
|
12
|
+
cacheWrite: floorTokens(cacheWrite),
|
|
13
|
+
cacheRead: floorTokens(cacheRead),
|
|
14
|
+
output: floorTokens(output),
|
|
15
|
+
thinking: floorTokens(thinking),
|
|
16
|
+
});
|
|
17
|
+
const floorPrompt = (prompt) => ({
|
|
18
|
+
...prompt,
|
|
19
|
+
errors: floorOf('count', bucketOf('count', prompt.errors)),
|
|
20
|
+
wallSec: prompt.wallSec === null ? null : floorOf('seconds', bucketOf('seconds', prompt.wallSec)),
|
|
21
|
+
usage: floorUsage(prompt.usage),
|
|
22
|
+
});
|
|
23
|
+
const floorTool = (tool) => ({
|
|
24
|
+
...tool,
|
|
25
|
+
durationMs: tool.durationMs === null ? null : floorOf('ms', bucketOf('ms', tool.durationMs)),
|
|
26
|
+
steps: tool.steps?.map((step) => ({
|
|
27
|
+
...step,
|
|
28
|
+
cost: { tokens: step.cost.tokens === null ? null : floorTokens(step.cost.tokens), durationMs: step.cost.durationMs === null ? null : floorOf('ms', bucketOf('ms', step.cost.durationMs)) },
|
|
29
|
+
})),
|
|
30
|
+
});
|
|
31
|
+
const flooredStore = (store) => ({ ...store, prompts: store.prompts.map(floorPrompt), tools: store.tools.map(floorTool) });
|
|
32
|
+
/** A step's shell identity without the two lists it carries: a named motif keys on the program and its subcommand, and each flag and each pull is a cell of its own (rule 1). */
|
|
33
|
+
const withoutLists = (step) => (step.identity !== null && 'program' in step.identity ? { ...step, identity: { program: step.identity.program, sub: step.identity.sub, flags: [] } } : step);
|
|
34
|
+
/** One step per flag and one per pull of each shell step, each holding that one name and nothing else (rule 2: no cell has two set-valued dimensions). */
|
|
35
|
+
const marginalSteps = (step) => {
|
|
36
|
+
if (step.identity === null || !('program' in step.identity))
|
|
37
|
+
return [];
|
|
38
|
+
const { program, sub, flags, pulls } = step.identity;
|
|
39
|
+
return [...flags.map((flag) => ({ ...step, identity: { program, sub, flags: [flag] } })), ...(pulls ?? []).map((pull) => ({ ...step, identity: { program, sub, flags: [], pulls: [pull] } }))];
|
|
40
|
+
};
|
|
41
|
+
/** The tools of a store as one-step calls, one per flag and per pull: read at window length 1, each is one marginal cell. */
|
|
42
|
+
const marginalStore = (store) => ({ ...store, tools: store.tools.flatMap((tool) => (tool.steps ?? []).flatMap(marginalSteps).map((marginal) => ({ ...tool, steps: [marginal] }))) });
|
|
43
|
+
/**
|
|
44
|
+
* Rule 5: exact tokens and durations never reach `index`. A cell counts a bucketed value as the bucket's lowest value, so a derivation reads only what a deposit carries, and a
|
|
45
|
+
* summed window is a sum of those. A value already at a bucket's floor stays there, which is why a skeleton's store and a local store of the same session derive the same cells.
|
|
46
|
+
* Rules 1 and 2: a window names a shell step by its program and subcommand alone, and each flag and each pull of it adds a one-step cell of its own.
|
|
47
|
+
*/
|
|
48
|
+
export const cellsOf = (store, graph = NO_RECALLS) => {
|
|
49
|
+
const floored = flooredStore(store);
|
|
50
|
+
const whole = deriveCells({ ...floored, tools: floored.tools.map((tool) => (tool.steps === undefined ? tool : { ...tool, steps: tool.steps.map(withoutLists) })) }, graph);
|
|
51
|
+
// Only the one-step named motifs of the marginal pass that hold a shell identity (a recall step the graph places there has none, and `whole` already counts it): its prompts, its shape motifs, and its longer windows repeat or splice what `whole` holds.
|
|
52
|
+
const marginal = deriveCells(marginalStore(floored), graph);
|
|
53
|
+
const named = new Set(marginal.motifs.filter(({ level, steps }) => level === 'named' && steps.length === 1 && steps[0]?.identity !== null).map(({ motifId }) => motifId));
|
|
54
|
+
const motifs = new Map([...whole.motifs, ...marginal.motifs.filter(({ motifId }) => named.has(motifId))].map((motif) => [motif.motifId, motif]));
|
|
55
|
+
const order = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
56
|
+
const cells = [...whole.cells, ...marginal.cells.filter(({ motifId }) => named.has(motifId))].sort((a, b) => order(a.motifId, b.motifId) || order(a.frameBucket, b.frameBucket) || order(a.grain, b.grain) || order(a.day, b.day) || Number(a.exposed) - Number(b.exposed));
|
|
57
|
+
return { cells, motifs: [...motifs.values()].sort((a, b) => order(a.motifId, b.motifId)) };
|
|
58
|
+
};
|
|
59
|
+
/** A local store with each shell step read as a deposit reads it (`projectedSteps`), so its named motifs are the ones the pool holds. It uses the open gate, which keeps the name shape and length but applies no redactor or exclusion term: a local read sends nothing. */
|
|
60
|
+
export const projectedStore = (store, read) => {
|
|
61
|
+
const harnesses = new Map([...store.sessions, ...store.subagents].map((session) => [session.session, harnessOf(session)]));
|
|
62
|
+
return { ...store, tools: store.tools.map((tool) => (tool.steps === undefined ? tool : { ...tool, steps: projectedSteps(tool, read, harnesses.get(tool.session) ?? 'claude-code') })) };
|
|
63
|
+
};
|
|
64
|
+
const NO_USAGE = { input: 0, cacheWrite: 0, cacheRead: 0, output: 0, thinking: 0 };
|
|
65
|
+
const isKnown = (transcript) => transcript.harness !== OTHER;
|
|
66
|
+
/** A frame whose own harness is `other` takes its transcript's: the two are the same harness, and a frame cannot name one the transcript does not. A repo size this reader does not know is a size the frame does not name. */
|
|
67
|
+
const frameOf = (frame, harness) => ({
|
|
68
|
+
...frame,
|
|
69
|
+
harness: frame.harness === OTHER ? harness : frame.harness,
|
|
70
|
+
stack: { ...frame.stack, size: frame.stack.size === OTHER ? null : frame.stack.size },
|
|
71
|
+
});
|
|
72
|
+
/** A class this reader does not know counts in `mixed`, the class a prompt that fits no other takes. */
|
|
73
|
+
const classOf = (cls) => (cls === OTHER ? 'mixed' : cls);
|
|
74
|
+
/** A status this reader does not know is a segment the call did not report. */
|
|
75
|
+
const statusOf = (status) => (status === OTHER ? 'none' : status);
|
|
76
|
+
/** A skeleton's transcript as the record `deriveCells` reads. Everything a skeleton leaves out stays empty, so no derivation here can come to depend on it unnoticed. */
|
|
77
|
+
const sessionRecord = (id, parent, spawnedIn, { harness, frame }) => ({
|
|
78
|
+
kind: 'session',
|
|
79
|
+
cv: '',
|
|
80
|
+
sid: '',
|
|
81
|
+
session: id,
|
|
82
|
+
project: '',
|
|
83
|
+
parent,
|
|
84
|
+
agentType: null,
|
|
85
|
+
spawnedBy: null,
|
|
86
|
+
spawnedIn,
|
|
87
|
+
rev: 1,
|
|
88
|
+
at: '',
|
|
89
|
+
bytes: 0,
|
|
90
|
+
lines: 0,
|
|
91
|
+
badLines: 0,
|
|
92
|
+
userTurns: 0,
|
|
93
|
+
assistantTurns: 0,
|
|
94
|
+
tsFirst: null,
|
|
95
|
+
tsLast: null,
|
|
96
|
+
versions: [],
|
|
97
|
+
models: {},
|
|
98
|
+
branches: [],
|
|
99
|
+
tools: {},
|
|
100
|
+
files: [],
|
|
101
|
+
fileHashes: [],
|
|
102
|
+
// The store's salted key, so a read that groups by repo works on pooled data from one store (one machine). It joins no cell.
|
|
103
|
+
repo: frame.repoKey ?? null,
|
|
104
|
+
urls: [],
|
|
105
|
+
searches: [],
|
|
106
|
+
bashCmds: 0,
|
|
107
|
+
tldrs: [],
|
|
108
|
+
confidences: [],
|
|
109
|
+
compactSummaries: 0,
|
|
110
|
+
promptFingerprint: null,
|
|
111
|
+
env: { harness: frame.harnessVersion ?? '', oars: '', node: '', os: frame.platform },
|
|
112
|
+
costUsd: null,
|
|
113
|
+
usage: NO_USAGE,
|
|
114
|
+
harness,
|
|
115
|
+
frame: frameOf(frame, harness),
|
|
116
|
+
});
|
|
117
|
+
const promptRecords = (id, { harness, prompts }) => prompts.map(({ cls, errors, wallSec, usage, usageResponses, explore, frame }, index) => ({
|
|
118
|
+
kind: 'prompt',
|
|
119
|
+
v: '',
|
|
120
|
+
session: id,
|
|
121
|
+
rev: 1,
|
|
122
|
+
index,
|
|
123
|
+
ts: null,
|
|
124
|
+
wallSec: wallSec === null ? null : floorOf('seconds', wallSec),
|
|
125
|
+
head: '',
|
|
126
|
+
cls: classOf(cls),
|
|
127
|
+
tools: [],
|
|
128
|
+
shape: '',
|
|
129
|
+
errors: floorOf('count', errors),
|
|
130
|
+
thinking: 0,
|
|
131
|
+
sideTools: 0,
|
|
132
|
+
turns: 0,
|
|
133
|
+
model: frame.model ?? '',
|
|
134
|
+
efforts: frame.effort === null ? [] : [frame.effort],
|
|
135
|
+
skills: [],
|
|
136
|
+
// A category the wire sent as null was never recorded: it reads as a placeholder zero here and is left out of `usageObserved`.
|
|
137
|
+
usage: {
|
|
138
|
+
input: floorOf('tokens', usage.input ?? 0),
|
|
139
|
+
cacheWrite: floorOf('tokens', usage.cacheWrite ?? 0),
|
|
140
|
+
cacheRead: floorOf('tokens', usage.cacheRead ?? 0),
|
|
141
|
+
output: floorOf('tokens', usage.output ?? 0),
|
|
142
|
+
thinking: floorOf('tokens', usage.thinking ?? 0),
|
|
143
|
+
},
|
|
144
|
+
usageObserved: USAGE_FIELDS.filter((field) => usage[field] !== null),
|
|
145
|
+
...(usageResponses === undefined ? {} : { usageResponses: floorOf('count', usageResponses) }),
|
|
146
|
+
recovery: [],
|
|
147
|
+
// The lowest value of the bucket, as every other bucketed value counts; a prompt that sent none counts none.
|
|
148
|
+
explore: explore === null ? 0 : floorOf('calls', explore),
|
|
149
|
+
act: 0,
|
|
150
|
+
bigrams: {},
|
|
151
|
+
contextFingerprint: null,
|
|
152
|
+
frame: frameOf(frame, harness),
|
|
153
|
+
}));
|
|
154
|
+
/** A step's place as the store names it: its call's position and its segment. A transcript's steps are numbered across its calls in order, which is what an edge's target counts. */
|
|
155
|
+
const stepPlaces = ({ tools }) => tools.flatMap(({ steps }, n) => steps.map((_, seg) => ({ n, seg })));
|
|
156
|
+
const toolRecords = (id, main, transcript, spawner) => {
|
|
157
|
+
const own = stepPlaces(transcript);
|
|
158
|
+
const spawning = spawner === null ? [] : stepPlaces(spawner);
|
|
159
|
+
return transcript.tools.map(({ side, prompt, status, durationMs, errorCode, keys, targetClass, argClass, steps }, n) => ({
|
|
160
|
+
kind: 'tool',
|
|
161
|
+
cv: '',
|
|
162
|
+
sid: '',
|
|
163
|
+
session: id,
|
|
164
|
+
rev: 1,
|
|
165
|
+
parent: main,
|
|
166
|
+
agentType: null,
|
|
167
|
+
n,
|
|
168
|
+
ts: null,
|
|
169
|
+
tool: steps[0]?.native ?? '',
|
|
170
|
+
status: statusOf(status),
|
|
171
|
+
target: null,
|
|
172
|
+
targetBase: null,
|
|
173
|
+
// The names the wire carried, which are the only ones a skeleton-derived store ever holds; `keysDropped` is a count of names not sent and has no stored form.
|
|
174
|
+
keys: keys ?? [],
|
|
175
|
+
...(targetClass === undefined ? {} : { targetClass }),
|
|
176
|
+
...(argClass === undefined ? {} : { argClass: ARG_CLASSES.filter((kind) => argClass.includes(kind)).sort() }),
|
|
177
|
+
durationMs,
|
|
178
|
+
toolUseId: '',
|
|
179
|
+
harnessVersion: null,
|
|
180
|
+
side,
|
|
181
|
+
prompt,
|
|
182
|
+
...(errorCode === null ? {} : { errorCode }),
|
|
183
|
+
steps: steps.map(({ action, native, identity, status: reported, parent, tokens, ms, edges }, seg) => ({
|
|
184
|
+
action,
|
|
185
|
+
native,
|
|
186
|
+
identity,
|
|
187
|
+
status: statusOf(reported),
|
|
188
|
+
parent,
|
|
189
|
+
seg,
|
|
190
|
+
// A target is read back as the call position and segment it names, in this transcript or, for a `spawned` edge, in the spawning one. A target the transcript does not hold names nothing and is left out, and so does a kind this reader does not know (`other`).
|
|
191
|
+
edges: (edges ?? []).flatMap(({ kind, target }) => {
|
|
192
|
+
const place = (kind === 'spawned' ? spawning : own)[target];
|
|
193
|
+
return place === undefined || kind === OTHER ? [] : [{ kind, n: place.n, seg: place.seg }];
|
|
194
|
+
}),
|
|
195
|
+
cost: { tokens: tokens === null ? null : floorOf('tokens', tokens), durationMs: ms === null ? null : floorOf('ms', ms) },
|
|
196
|
+
n,
|
|
197
|
+
frameId: '',
|
|
198
|
+
})),
|
|
199
|
+
}));
|
|
200
|
+
};
|
|
201
|
+
/** One account's deposits as a store. A session's id is its skeleton's, which the client hashes per account, so it joins nothing across accounts. */
|
|
202
|
+
export const storeOf = (skeletons) => {
|
|
203
|
+
const store = { sessions: [], prompts: [], errors: [], subagents: [], tools: [], newerCaptures: 0 };
|
|
204
|
+
for (const { id, transcripts } of skeletons) {
|
|
205
|
+
const nameOf = (index) => `${id}#${String(index)}`;
|
|
206
|
+
const main = nameOf(0);
|
|
207
|
+
transcripts.forEach((transcript, index) => {
|
|
208
|
+
if (!isKnown(transcript))
|
|
209
|
+
return;
|
|
210
|
+
const session = nameOf(index);
|
|
211
|
+
const spawner = transcript.spawnedIn === null ? null : (transcripts[transcript.spawnedIn] ?? null);
|
|
212
|
+
(index === 0 ? store.sessions : store.subagents).push(sessionRecord(session, index === 0 ? null : main, transcript.spawnedIn === null ? null : nameOf(transcript.spawnedIn), transcript));
|
|
213
|
+
store.prompts.push(...promptRecords(session, transcript));
|
|
214
|
+
store.tools.push(...toolRecords(session, main, transcript, spawner));
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
return store;
|
|
218
|
+
};
|
|
219
|
+
/** Whole cells and strata, summed over a batch's own sessions: the same cells a store of that batch alone derives. */
|
|
220
|
+
export const incrementsOf = (skeleton) => {
|
|
221
|
+
const derived = cellsOf(storeOf([skeleton]));
|
|
222
|
+
const cells = new Map();
|
|
223
|
+
for (const cell of derived.cells) {
|
|
224
|
+
const exposed = cell.exposed ? 1 : 0;
|
|
225
|
+
const key = `${cell.motifId}/${cell.frameBucket}/${cell.grain}/${cell.day}/${String(exposed)}`;
|
|
226
|
+
// The served column is a running sum, so a cell no session of this batch recorded tokens for adds nothing to it; the sum itself holds only what was observed, and `tokenSessions` and `tokenPrompts` say how much of the cell it covers.
|
|
227
|
+
cells.set(key, { key, harnessBucket: isEnvironmentCell(cell) ? ENVIRONMENT_BUCKET : bucketFields(cell.frameBucket).harnessBucket, motifId: cell.motifId, frameBucket: cell.frameBucket, grain: cell.grain, day: cell.day, exposed, sessions: cell.sessions, ok: cell.ok, failed: cell.failed, tokens: cell.tokens ?? 0, tokenSessions: cell.tokenSessions ?? 0, tokenPrompts: cell.tokenPrompts ?? 0, durationMs: cell.durationMs });
|
|
228
|
+
}
|
|
229
|
+
// A session counts once per value, so each value is one session of this batch.
|
|
230
|
+
const strata = new Map();
|
|
231
|
+
const frames = skeleton.transcripts.flatMap(({ frame, prompts }) => [frame, ...prompts.map((prompt) => prompt.frame)]);
|
|
232
|
+
const named = [
|
|
233
|
+
...frames.flatMap(({ stack }) => stack.languages.map((value) => ['language', value])),
|
|
234
|
+
...frames.flatMap(({ stack }) => stack.frameworks.map((value) => ['framework', value])),
|
|
235
|
+
...frames.flatMap(({ stack }) => (stack.size === null ? [] : [['repoFiles', stack.size]])),
|
|
236
|
+
// The two pairs (rule 3): each is a stratum of its own, never a cell and never keyed by the stack's other values. A language with no share, and a prompt with no count, make none.
|
|
237
|
+
...frames.flatMap(({ stack }) => stack.languageShares.map(({ language, languageShare }) => ['languageShare', `${language}/${languageShare}`])),
|
|
238
|
+
...skeleton.transcripts.flatMap(({ prompts }) => prompts.flatMap(({ explore, frame }) => (explore === null || frame.stack.size === null ? [] : [['repoFilesExplore', `${frame.stack.size}/${String(explore)}`]]))),
|
|
239
|
+
];
|
|
240
|
+
for (const [dimension, value] of named)
|
|
241
|
+
strata.set(`${dimension}/${value}`, { key: `${dimension}/${value}`, dimension, value, sessions: 1 });
|
|
242
|
+
return { cells, motifs: derived.motifs, strata };
|
|
243
|
+
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { type Motif } from './motif.js';
|
|
2
|
+
import { type RecallGraph } from './recall-consequence.js';
|
|
3
|
+
import { type ErrorObservation, type Frame } from './schema.js';
|
|
4
|
+
import type { Store } from './store.js';
|
|
5
|
+
/** Rule 6: an `index` cell is weekly, except a live cell, which stays at the UTC day. */
|
|
6
|
+
export type Grain = 'day' | 'week';
|
|
7
|
+
export interface Cell {
|
|
8
|
+
motifId: string;
|
|
9
|
+
/** `harness/versionBucket/model`, public names (`frameBucketOf`), or `env:<platform>:<shell>` for a (name, `platform`, `shell`) cell, which names no harness, version, or model (`ENVIRONMENT_BUCKET`). Effort is not in it (rule 3). */
|
|
10
|
+
frameBucket: string;
|
|
11
|
+
/** What `day` names: a UTC day, or a UTC week. A read takes the grain it answers from and never the other. */
|
|
12
|
+
grain: Grain;
|
|
13
|
+
/** The UTC day of the step's frame for a day cell, the Monday of its UTC week for a week cell, or `unknown`. */
|
|
14
|
+
day: string;
|
|
15
|
+
/** Distinct main sessions: a subagent's steps count under the session that spawned it. */
|
|
16
|
+
sessions: number;
|
|
17
|
+
/** Distinct accounts. One store is one operator, so 1 here (spec Section 6: one operator stays labeled). */
|
|
18
|
+
clusters: number;
|
|
19
|
+
/** Windows whose last step came back clean; a `class` cell: prompts with no error. */
|
|
20
|
+
ok: number;
|
|
21
|
+
/** Windows whose last step failed; a `class` cell: prompts with an error. A step with no result is in neither. */
|
|
22
|
+
failed: number;
|
|
23
|
+
/**
|
|
24
|
+
* Summed over each window's steps; a `class` cell: the whole usage of the prompts that recorded input, counting only the categories each recorded (a placeholder zero beside
|
|
25
|
+
* `usageObserved` is not a measurement). Null when no session recorded any: absent, never a measured 0.
|
|
26
|
+
*/
|
|
27
|
+
tokens: number | null;
|
|
28
|
+
/** How many of `sessions` recorded the cost `tokens` sums (a class cell: input; a step cell: a step cost), which is the coverage the `tokens` figure has (a session that recorded input but not every category still counts as covered, so the figure can be partial by category); less than `sessions` makes it a partial sum. Absent on a cell read back from a stored sum written before the pool held coverage. */
|
|
29
|
+
tokenSessions?: number;
|
|
30
|
+
/** How many of the cell's prompts the `tokens` figure covers (a step cell counts steps): the divisor of a per-prompt mean, since `tokens` holds only prompts that recorded input. Absent like `tokenSessions`. */
|
|
31
|
+
tokenPrompts?: number;
|
|
32
|
+
durationMs: number;
|
|
33
|
+
/** Whether these counts sit inside a shown recall's consequence window, wholly or in part. It is part of the cell's key, so a motif's exposed and unexposed counts stay apart. */
|
|
34
|
+
exposed: boolean;
|
|
35
|
+
}
|
|
36
|
+
export declare const UNKNOWN = "unknown";
|
|
37
|
+
/** The class name an effort cell carries: a class motif with no prompt class in it, so a read of prompt classes skips it by this prefix. */
|
|
38
|
+
export declare const EFFORT_CLASS = "effort:";
|
|
39
|
+
/**
|
|
40
|
+
* Rule 3: effort is not in a cell's key. The one cell that holds it pairs it with the bucket of the prompt's thinking tokens, which is the fact an effort level explains, and it
|
|
41
|
+
* carries `week` and `exposure` but no frame axis: rule 3 bars joining `effort` to `model` or `harnessVersion`, and the pair would be four dimensions over `q_dims`.
|
|
42
|
+
*/
|
|
43
|
+
export declare const effortClass: (effort: string, thinking: number) => string;
|
|
44
|
+
/**
|
|
45
|
+
* The two day-cell families of a failed call by its `errorCode` (t868): `error:<code>/<name>` keeps the model and no version beside the code, and `error-version:<code>/<name>` keeps the harness version bucket and no model
|
|
46
|
+
* (`versionBucketOf`). Each joins three dimensions: name, code, and either model or version. A class motif with no prompt class in it, so a read of prompt classes skips both by prefix (`isPromptClass`).
|
|
47
|
+
*/
|
|
48
|
+
export declare const ERROR_CLASS = "error:";
|
|
49
|
+
export declare const ERROR_VERSION_CLASS = "error-version:";
|
|
50
|
+
/**
|
|
51
|
+
* The week cell of what followed a failed call (t868): `recovery:<code>/<failed action>><next action>`, where the next action is that of the call right after it in the same prompt, or `none` when nothing
|
|
52
|
+
* followed. It keeps the harness version bucket and no model, so it joins three dimensions: the failed-to-next edge, code, and version. A class motif with no prompt class in it, like the others.
|
|
53
|
+
*/
|
|
54
|
+
export declare const RECOVERY_CLASS = "recovery:";
|
|
55
|
+
export declare const NO_NEXT = "none";
|
|
56
|
+
export declare const recoveryClass: (code: string, failed: string, next: string) => string;
|
|
57
|
+
/** The three parts of a recovery class name, or null for a name that is not one. An action name holds no `>` or `/`, and a code holds no `/`. */
|
|
58
|
+
export declare const parseRecoveryClass: (cls: string) => {
|
|
59
|
+
code: string;
|
|
60
|
+
failed: string;
|
|
61
|
+
next: string;
|
|
62
|
+
} | null;
|
|
63
|
+
/** Whether a class motif names a prompt class, as opposed to one of the side cells (effort, or a failed call's code or what followed it). */
|
|
64
|
+
export declare const isPromptClass: (cls: string | null) => boolean;
|
|
65
|
+
/** The frame bucket of a cell that joins no axis. */
|
|
66
|
+
export declare const NO_AXES = "-";
|
|
67
|
+
/**
|
|
68
|
+
* A harness version's bucket: its patch by tens. 70.5 percent of one operator's later prompts ran a version not yet seen, so an
|
|
69
|
+
* exact-version cell goes stale in days; a reader compares buckets by distance instead. A version with no patch stands for itself.
|
|
70
|
+
*/
|
|
71
|
+
export declare const harnessBucket: (version: string | null) => string;
|
|
72
|
+
/** Only what most comparisons cross: platform, shell, and stack are on the frame and in the records, so a later extractor can add them without new capture. */
|
|
73
|
+
export declare const frameBucketOf: (frame: Frame) => string;
|
|
74
|
+
/** The frame bucket of a cell that keeps the version and drops the model: `harness/versionBucket/-`. */
|
|
75
|
+
export declare const versionBucketOf: (frame: Frame) => string;
|
|
76
|
+
export declare const bucketFields: (bucket: string) => {
|
|
77
|
+
harness: string;
|
|
78
|
+
harnessBucket: string;
|
|
79
|
+
model: string;
|
|
80
|
+
};
|
|
81
|
+
/** The frame bucket of a (name, `platform`, `shell`) cell: fit's third pool form. Rule 3 lets the two join each other and a name within `q_dims`, but not a version or a model beside them, so the cell is version-free and model-free. */
|
|
82
|
+
export declare const ENVIRONMENT_BUCKET = "env:";
|
|
83
|
+
export declare const environmentBucketOf: (frame: Frame) => string;
|
|
84
|
+
export declare const isEnvironmentCell: (cell: Pick<Cell, "frameBucket">) => boolean;
|
|
85
|
+
export declare const environmentFieldsOf: (bucket: string) => {
|
|
86
|
+
platform: string;
|
|
87
|
+
shell: string;
|
|
88
|
+
};
|
|
89
|
+
/** The Monday of the UTC week a UTC day falls in, as a day: the week's name in a cell. An unknown day is an unknown week. */
|
|
90
|
+
export declare const weekStartOf: (day: string) => string;
|
|
91
|
+
/** The last UTC day a cell can hold: its day, or the Sunday that closes its week. A check that evidence predates a day reads this, since a week's cell may hold any of its seven days. */
|
|
92
|
+
export declare const lastDayOfCell: (cell: Cell) => string;
|
|
93
|
+
/** `cell:<contentHash>`: a cell that counts differently is a different cell, so a read's inputs pin what it read. */
|
|
94
|
+
export declare const cellIdOf: (cell: Cell) => string;
|
|
95
|
+
export interface CellSet {
|
|
96
|
+
cells: Cell[];
|
|
97
|
+
motifs: Motif[];
|
|
98
|
+
}
|
|
99
|
+
export declare const deriveCells: (store: Store, graph?: RecallGraph) => CellSet;
|
|
100
|
+
export declare const serializeCells: (cells: readonly Cell[]) => string;
|
|
101
|
+
/** What every read of one derive pass reads, and the snapshot it answers from. */
|
|
102
|
+
export interface Pool {
|
|
103
|
+
cells: readonly Cell[];
|
|
104
|
+
motifs: ReadonlyMap<string, Motif>;
|
|
105
|
+
/** The hash of the extractor version, the family methods, every cell, and the error rows recovery reads: new data or a new rule is a new pool, and every read of it a new read. */
|
|
106
|
+
version: string;
|
|
107
|
+
lastDay: string | null;
|
|
108
|
+
}
|
|
109
|
+
export declare const poolOf: (set: CellSet, errors: readonly ErrorObservation[]) => Pool;
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
// The cell (recall spec Section 2.5): counts for one motif in one frame bucket in one UTC week (or, for a live cell, one UTC day), order 0. Cells are computed from the capture records, which
|
|
2
|
+
// stay the primary record, so a cell file can be dropped and rebuilt and any later derivation can run over old data. Nothing here reads raw.
|
|
3
|
+
import { closedFrame, utcDay } from './frame.js';
|
|
4
|
+
import { classMotif, motifName, motifsOf, stepsOfCall } from './motif.js';
|
|
5
|
+
import { bucketOf } from './pool-wire.js';
|
|
6
|
+
import { CALL_SHAPE_METHOD, cellRef, contentHash, METHODS, RECOVERY_EDGE_METHOD } from './read.js';
|
|
7
|
+
import { recoveryCallsOf } from './recovery-calls.js';
|
|
8
|
+
import { NO_RECALLS } from './recall-consequence.js';
|
|
9
|
+
import { ERROR_CODES, EXTRACTOR_VERSION, harnessOf, observesUsage } from './schema.js';
|
|
10
|
+
export const UNKNOWN = 'unknown';
|
|
11
|
+
/** The class name an effort cell carries: a class motif with no prompt class in it, so a read of prompt classes skips it by this prefix. */
|
|
12
|
+
export const EFFORT_CLASS = 'effort:';
|
|
13
|
+
/**
|
|
14
|
+
* Rule 3: effort is not in a cell's key. The one cell that holds it pairs it with the bucket of the prompt's thinking tokens, which is the fact an effort level explains, and it
|
|
15
|
+
* carries `week` and `exposure` but no frame axis: rule 3 bars joining `effort` to `model` or `harnessVersion`, and the pair would be four dimensions over `q_dims`.
|
|
16
|
+
*/
|
|
17
|
+
export const effortClass = (effort, thinking) => `${EFFORT_CLASS}${effort}/thinking:${String(bucketOf('tokens', thinking))}`;
|
|
18
|
+
/**
|
|
19
|
+
* The two day-cell families of a failed call by its `errorCode` (t868): `error:<code>/<name>` keeps the model and no version beside the code, and `error-version:<code>/<name>` keeps the harness version bucket and no model
|
|
20
|
+
* (`versionBucketOf`). Each joins three dimensions: name, code, and either model or version. A class motif with no prompt class in it, so a read of prompt classes skips both by prefix (`isPromptClass`).
|
|
21
|
+
*/
|
|
22
|
+
export const ERROR_CLASS = 'error:';
|
|
23
|
+
export const ERROR_VERSION_CLASS = 'error-version:';
|
|
24
|
+
/**
|
|
25
|
+
* The week cell of what followed a failed call (t868): `recovery:<code>/<failed action>><next action>`, where the next action is that of the call right after it in the same prompt, or `none` when nothing
|
|
26
|
+
* followed. It keeps the harness version bucket and no model, so it joins three dimensions: the failed-to-next edge, code, and version. A class motif with no prompt class in it, like the others.
|
|
27
|
+
*/
|
|
28
|
+
export const RECOVERY_CLASS = 'recovery:';
|
|
29
|
+
export const NO_NEXT = 'none';
|
|
30
|
+
export const recoveryClass = (code, failed, next) => `${RECOVERY_CLASS}${code}/${failed}>${next}`;
|
|
31
|
+
/** The three parts of a recovery class name, or null for a name that is not one. An action name holds no `>` or `/`, and a code holds no `/`. */
|
|
32
|
+
export const parseRecoveryClass = (cls) => {
|
|
33
|
+
const found = /^recovery:([^/]+)\/([^>/]+)>([^>/]+)$/.exec(cls);
|
|
34
|
+
return found === null ? null : { code: found[1] ?? '', failed: found[2] ?? '', next: found[3] ?? '' };
|
|
35
|
+
};
|
|
36
|
+
/** Whether a class motif names a prompt class, as opposed to one of the side cells (effort, or a failed call's code or what followed it). */
|
|
37
|
+
export const isPromptClass = (cls) => cls !== null && ![EFFORT_CLASS, ERROR_CLASS, ERROR_VERSION_CLASS, RECOVERY_CLASS].some((prefix) => cls.startsWith(prefix));
|
|
38
|
+
/** The frame bucket of a cell that joins no axis. */
|
|
39
|
+
export const NO_AXES = '-';
|
|
40
|
+
const ONE_OPERATOR = 1;
|
|
41
|
+
/**
|
|
42
|
+
* A harness version's bucket: its patch by tens. 70.5 percent of one operator's later prompts ran a version not yet seen, so an
|
|
43
|
+
* exact-version cell goes stale in days; a reader compares buckets by distance instead. A version with no patch stands for itself.
|
|
44
|
+
*/
|
|
45
|
+
export const harnessBucket = (version) => {
|
|
46
|
+
if (version === null)
|
|
47
|
+
return UNKNOWN;
|
|
48
|
+
const found = /^(\d+\.\d+)\.(\d+)/.exec(version);
|
|
49
|
+
return found === null ? version : `${found[1] ?? ''}.${String(Math.floor(Number(found[2]) / 10))}x`;
|
|
50
|
+
};
|
|
51
|
+
/** Only what most comparisons cross: platform, shell, and stack are on the frame and in the records, so a later extractor can add them without new capture. */
|
|
52
|
+
export const frameBucketOf = (frame) => [frame.harness, harnessBucket(frame.harnessVersion), frame.model ?? UNKNOWN].join('/');
|
|
53
|
+
/** The frame bucket of a cell that keeps the version and drops the model: `harness/versionBucket/-`. */
|
|
54
|
+
export const versionBucketOf = (frame) => [frame.harness, harnessBucket(frame.harnessVersion), NO_AXES].join('/');
|
|
55
|
+
export const bucketFields = (bucket) => {
|
|
56
|
+
const [harness = UNKNOWN, version = UNKNOWN, model = UNKNOWN] = bucket.split('/');
|
|
57
|
+
return { harness, harnessBucket: version, model };
|
|
58
|
+
};
|
|
59
|
+
/** The frame bucket of a (name, `platform`, `shell`) cell: fit's third pool form. Rule 3 lets the two join each other and a name within `q_dims`, but not a version or a model beside them, so the cell is version-free and model-free. */
|
|
60
|
+
export const ENVIRONMENT_BUCKET = 'env:';
|
|
61
|
+
export const environmentBucketOf = (frame) => `${ENVIRONMENT_BUCKET}${frame.platform}:${frame.shell ?? UNKNOWN}`;
|
|
62
|
+
export const isEnvironmentCell = (cell) => cell.frameBucket.startsWith(ENVIRONMENT_BUCKET);
|
|
63
|
+
export const environmentFieldsOf = (bucket) => {
|
|
64
|
+
const held = bucket.slice(ENVIRONMENT_BUCKET.length);
|
|
65
|
+
const at = held.indexOf(':');
|
|
66
|
+
return { platform: held.slice(0, at), shell: held.slice(at + 1) };
|
|
67
|
+
};
|
|
68
|
+
/** A one-step named call with no flag and no pull: the name that is one dimension. A call with no identity (a file read, an edit) is its action alone, so it counts. A flag or a pull is a second dimension of its own, so its cell would be four with the pair. */
|
|
69
|
+
const isPlainCall = (motif) => {
|
|
70
|
+
const [only] = motif.steps;
|
|
71
|
+
if (motif.level !== 'named' || motif.steps.length !== 1 || only === undefined)
|
|
72
|
+
return false;
|
|
73
|
+
return only.identity === null || !('program' in only.identity) || (only.identity.flags.length === 0 && (only.identity.pulls?.length ?? 0) === 0);
|
|
74
|
+
};
|
|
75
|
+
const DAY_MS = 86_400_000;
|
|
76
|
+
/** The Monday of the UTC week a UTC day falls in, as a day: the week's name in a cell. An unknown day is an unknown week. */
|
|
77
|
+
export const weekStartOf = (day) => {
|
|
78
|
+
if (day === UNKNOWN)
|
|
79
|
+
return UNKNOWN;
|
|
80
|
+
const millis = Date.parse(day);
|
|
81
|
+
return new Date(millis - ((new Date(millis).getUTCDay() + 6) % 7) * DAY_MS).toISOString().slice(0, 10);
|
|
82
|
+
};
|
|
83
|
+
/** The last UTC day a cell can hold: its day, or the Sunday that closes its week. A check that evidence predates a day reads this, since a week's cell may hold any of its seven days. */
|
|
84
|
+
export const lastDayOfCell = (cell) => (cell.grain === 'week' && cell.day !== UNKNOWN ? new Date(Date.parse(cell.day) + 6 * DAY_MS).toISOString().slice(0, 10) : cell.day);
|
|
85
|
+
const cellIds = new WeakMap();
|
|
86
|
+
/** `cell:<contentHash>`: a cell that counts differently is a different cell, so a read's inputs pin what it read. */
|
|
87
|
+
export const cellIdOf = (cell) => {
|
|
88
|
+
const known = cellIds.get(cell);
|
|
89
|
+
if (known !== undefined)
|
|
90
|
+
return known;
|
|
91
|
+
const id = cellRef(contentHash(cell));
|
|
92
|
+
cellIds.set(cell, id);
|
|
93
|
+
return id;
|
|
94
|
+
};
|
|
95
|
+
/** The usage categories a prompt's tokens add up: thinking is part of the output already billed, so it is not summed. */
|
|
96
|
+
const TOKEN_FIELDS = ['input', 'cacheWrite', 'cacheRead', 'output'];
|
|
97
|
+
const newTally = (motifId, frameBucket, grain, day, exposed) => ({ motifId, frameBucket, grain, day, sessions: new Set(), ok: 0, failed: 0, tokens: 0, recorded: new Set(), recordedUnits: 0, durationMs: 0, exposed });
|
|
98
|
+
const compare = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
99
|
+
/** A frame for a record captured before frames existed, from the fields it does hold, held to the same public vocabulary. */
|
|
100
|
+
const oldFrame = (parts) => closedFrame({
|
|
101
|
+
harness: parts.harness,
|
|
102
|
+
harnessVersion: parts.version,
|
|
103
|
+
model: parts.model === '' ? null : parts.model,
|
|
104
|
+
effort: parts.effort,
|
|
105
|
+
day: utcDay(parts.ts),
|
|
106
|
+
platform: parts.platform,
|
|
107
|
+
shell: null,
|
|
108
|
+
stack: { languages: [], frameworks: [], size: null },
|
|
109
|
+
});
|
|
110
|
+
const mostUsedModel = (models) => [...Object.entries(models)].sort(([a, one], [b, two]) => two - one || compare(a, b))[0]?.[0] ?? null;
|
|
111
|
+
const framedPrompt = (prompt, harness, version, ts, platform) => prompt.frame ?? oldFrame({ harness, version, model: prompt.model, effort: prompt.efforts[0] ?? null, ts: prompt.ts ?? ts, platform });
|
|
112
|
+
/** One line of work (spec Section 2.3): a window never crosses a prompt, a sidechain, or a transcript. */
|
|
113
|
+
const linesOf = (tools) => {
|
|
114
|
+
const lines = new Map();
|
|
115
|
+
for (const tool of tools) {
|
|
116
|
+
const key = `${tool.session}\n${String(tool.side)}\n${String(tool.prompt)}`;
|
|
117
|
+
const line = lines.get(key);
|
|
118
|
+
if (line === undefined)
|
|
119
|
+
lines.set(key, [tool]);
|
|
120
|
+
else
|
|
121
|
+
line.push(tool);
|
|
122
|
+
}
|
|
123
|
+
return [...lines.values()].map((line) => line.sort((a, b) => a.n - b.n));
|
|
124
|
+
};
|
|
125
|
+
export const deriveCells = (store, graph = NO_RECALLS) => {
|
|
126
|
+
const tallies = new Map();
|
|
127
|
+
const motifs = new Map();
|
|
128
|
+
const tallyAt = (motif, frameBucket, grain, day, exposed) => {
|
|
129
|
+
const key = `${motif.motifId}/${frameBucket}/${grain}/${day}/${String(exposed)}`;
|
|
130
|
+
const found = tallies.get(key);
|
|
131
|
+
if (found !== undefined)
|
|
132
|
+
return found;
|
|
133
|
+
const fresh = newTally(motif.motifId, frameBucket, grain, day, exposed);
|
|
134
|
+
tallies.set(key, fresh);
|
|
135
|
+
return fresh;
|
|
136
|
+
};
|
|
137
|
+
/** The tallies one occurrence counts into: its week cell, and for a one-step shape motif with a known day its live cell too (rule 6). A window of two or more steps, a named motif, and a class motif have no day cell. */
|
|
138
|
+
const talliesOf = (motif, frame, exposed, frameBucket = frameBucketOf(frame)) => {
|
|
139
|
+
motifs.set(motif.motifId, motif);
|
|
140
|
+
const day = frame.day ?? UNKNOWN;
|
|
141
|
+
const weekly = tallyAt(motif, frameBucket, 'week', weekStartOf(day), exposed);
|
|
142
|
+
const tallies = motif.level === 'shape' && motif.steps.length === 1 && day !== UNKNOWN ? [weekly, tallyAt(motif, frameBucket, 'day', day, exposed)] : [weekly];
|
|
143
|
+
// Fit's (name, platform, shell) cell: a week cell beside the versioned one, counting the same call.
|
|
144
|
+
return isPlainCall(motif) ? [...tallies, tallyAt(motif, environmentBucketOf(frame), 'week', weekStartOf(day), exposed)] : tallies;
|
|
145
|
+
};
|
|
146
|
+
const sessions = new Map([...store.sessions, ...store.subagents].map((session) => [session.session, session]));
|
|
147
|
+
const prompts = new Map(store.prompts.map((prompt) => [`${prompt.session}#${String(prompt.index)}`, prompt]));
|
|
148
|
+
for (const line of linesOf(store.tools)) {
|
|
149
|
+
const [first] = line;
|
|
150
|
+
const session = first === undefined ? undefined : sessions.get(first.session);
|
|
151
|
+
if (first === undefined || session === undefined)
|
|
152
|
+
continue;
|
|
153
|
+
const harness = harnessOf(session);
|
|
154
|
+
const version = first.harnessVersion ?? session.env.harness;
|
|
155
|
+
const prompt = prompts.get(`${first.session}#${String(first.prompt)}`);
|
|
156
|
+
const frame = prompt === undefined
|
|
157
|
+
? (session.frame ?? oldFrame({ harness, version, model: mostUsedModel(session.models), effort: null, ts: first.ts ?? session.tsFirst, platform: session.env.os }))
|
|
158
|
+
: framedPrompt(prompt, harness, version, first.ts, session.env.os);
|
|
159
|
+
const placed = line.flatMap((tool) => graph.place(tool, stepsOfCall(tool, harness)));
|
|
160
|
+
const steps = placed.map(({ step }) => step);
|
|
161
|
+
for (const { motif, start } of motifsOf(steps)) {
|
|
162
|
+
const window = steps.slice(start, start + motif.steps.length);
|
|
163
|
+
const status = window.at(-1)?.status;
|
|
164
|
+
for (const tally of talliesOf(motif, frame, placed.slice(start, start + motif.steps.length).some(({ exposed }) => exposed))) {
|
|
165
|
+
tally.sessions.add(first.parent);
|
|
166
|
+
if (status === 'ok')
|
|
167
|
+
tally.ok += 1;
|
|
168
|
+
if (status === 'error')
|
|
169
|
+
tally.failed += 1;
|
|
170
|
+
for (const step of window) {
|
|
171
|
+
// A step with no recorded cost has no tokens to count, so it joins neither the sum nor the coverage (its duration is still counted).
|
|
172
|
+
if (step.tokens !== null) {
|
|
173
|
+
tally.tokens += step.tokens;
|
|
174
|
+
tally.recorded.add(first.parent);
|
|
175
|
+
tally.recordedUnits += 1;
|
|
176
|
+
}
|
|
177
|
+
tally.durationMs += step.durationMs;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// What followed each failed call that has a code (t868): a week cell per (code, failed action, next action).
|
|
182
|
+
// A call is read by its first step's action, as a skeleton's is; a record captured before steps has the action its native name maps to.
|
|
183
|
+
const calls = line.map((call) => ({ side: call.side, prompt: call.prompt, status: call.status, steps: stepsOfCall(call, harness) }));
|
|
184
|
+
for (const [at, tool] of line.entries()) {
|
|
185
|
+
const followed = recoveryCallsOf(calls, at);
|
|
186
|
+
if (followed === null || tool.errorCode === undefined)
|
|
187
|
+
continue;
|
|
188
|
+
const code = ERROR_CODES.has(tool.errorCode) ? tool.errorCode : 'other';
|
|
189
|
+
const motif = classMotif(recoveryClass(code, calls[at]?.steps[0]?.action ?? 'other', followed.next[0] ?? NO_NEXT));
|
|
190
|
+
motifs.set(motif.motifId, motif);
|
|
191
|
+
const tally = tallyAt(motif, versionBucketOf(frame), 'week', weekStartOf(frame.day ?? UNKNOWN), false);
|
|
192
|
+
tally.sessions.add(first.parent);
|
|
193
|
+
tally.failed += 1;
|
|
194
|
+
}
|
|
195
|
+
// The failed calls by their `errorCode` (t868). Rule 6: a day cell exists for a known day only.
|
|
196
|
+
const failedDay = frame.day ?? UNKNOWN;
|
|
197
|
+
for (const tool of failedDay === UNKNOWN ? [] : line) {
|
|
198
|
+
if (tool.status !== 'error' || tool.errorCode === undefined)
|
|
199
|
+
continue;
|
|
200
|
+
const code = ERROR_CODES.has(tool.errorCode) ? tool.errorCode : 'other';
|
|
201
|
+
for (const counted of stepsOfCall(tool, harness)) {
|
|
202
|
+
if (counted.status !== 'error')
|
|
203
|
+
continue;
|
|
204
|
+
// The call's name at both levels: its action alone, and a named call (a flag or a pull would be a second dimension, so those steps are named by program and subcommand alone upstream).
|
|
205
|
+
for (const { motif: named } of motifsOf([counted])) {
|
|
206
|
+
if (named.level === 'named' && counted.identity === null)
|
|
207
|
+
continue;
|
|
208
|
+
const name = motifName(named);
|
|
209
|
+
for (const [prefix, bucket] of [[ERROR_CLASS, `${frame.harness}/${NO_AXES}/${frame.model ?? UNKNOWN}`], [ERROR_VERSION_CLASS, versionBucketOf(frame)]]) {
|
|
210
|
+
const motif = classMotif(`${prefix}${code}/${name}`);
|
|
211
|
+
motifs.set(motif.motifId, motif);
|
|
212
|
+
const tally = tallyAt(motif, bucket, 'day', failedDay, false);
|
|
213
|
+
tally.sessions.add(first.parent);
|
|
214
|
+
tally.failed += 1;
|
|
215
|
+
if (counted.tokens !== null) {
|
|
216
|
+
tally.tokens += counted.tokens;
|
|
217
|
+
tally.recorded.add(first.parent);
|
|
218
|
+
tally.recordedUnits += 1;
|
|
219
|
+
}
|
|
220
|
+
tally.durationMs += counted.durationMs;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
for (const prompt of store.prompts) {
|
|
227
|
+
const session = sessions.get(prompt.session);
|
|
228
|
+
const frame = framedPrompt(prompt, harnessOf(session ?? {}), session?.env.harness ?? null, null, session?.env.os ?? UNKNOWN);
|
|
229
|
+
const exposed = graph.exposedPrompt(prompt.session, prompt.index);
|
|
230
|
+
const effort = frame.effort === null ? [] : talliesOf(classMotif(effortClass(frame.effort, prompt.usage.thinking)), frame, exposed, NO_AXES);
|
|
231
|
+
for (const tally of [...talliesOf(classMotif(prompt.cls), frame, exposed), ...effort]) {
|
|
232
|
+
tally.sessions.add(prompt.session);
|
|
233
|
+
if (prompt.errors > 0)
|
|
234
|
+
tally.failed += 1;
|
|
235
|
+
else
|
|
236
|
+
tally.ok += 1;
|
|
237
|
+
// A prompt that recorded no input has no whole usage to count, and a category it never recorded is a placeholder zero, so neither enters the sum.
|
|
238
|
+
if (observesUsage(prompt, 'input')) {
|
|
239
|
+
tally.recorded.add(prompt.session);
|
|
240
|
+
tally.recordedUnits += 1;
|
|
241
|
+
for (const field of TOKEN_FIELDS)
|
|
242
|
+
if (observesUsage(prompt, field))
|
|
243
|
+
tally.tokens += prompt.usage[field];
|
|
244
|
+
}
|
|
245
|
+
tally.durationMs += (prompt.wallSec ?? 0) * 1000;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
const cells = [...tallies.values()]
|
|
249
|
+
.sort((a, b) => compare(a.motifId, b.motifId) || compare(a.frameBucket, b.frameBucket) || compare(a.grain, b.grain) || compare(a.day, b.day) || Number(a.exposed) - Number(b.exposed))
|
|
250
|
+
.map((tally) => ({ motifId: tally.motifId, frameBucket: tally.frameBucket, grain: tally.grain, day: tally.day, sessions: tally.sessions.size, clusters: ONE_OPERATOR, ok: tally.ok, failed: tally.failed, tokens: tally.recorded.size === 0 ? null : tally.tokens, tokenSessions: tally.recorded.size, tokenPrompts: tally.recordedUnits, durationMs: tally.durationMs, exposed: tally.exposed }));
|
|
251
|
+
return { cells, motifs: [...motifs.values()].sort((a, b) => compare(a.motifId, b.motifId)) };
|
|
252
|
+
};
|
|
253
|
+
export const serializeCells = (cells) => cells.map((cell) => `${JSON.stringify(cell)}\n`).join('');
|
|
254
|
+
/** The fields of an error row a recovery read depends on, as text: only those, so a change elsewhere is not a new pool, and sorted, so record order is not one either. */
|
|
255
|
+
const errorRowsRead = (errors) => errors.map(({ session, agent, toolUseId, prompt, tool, sig, ts, next, editFollowed }) => JSON.stringify([session, agent ?? null, toolUseId ?? null, prompt, tool, sig, ts, next, editFollowed])).sort();
|
|
256
|
+
export const poolOf = (set, errors) => ({
|
|
257
|
+
cells: set.cells,
|
|
258
|
+
motifs: new Map(set.motifs.map((motif) => [motif.motifId, motif])),
|
|
259
|
+
version: contentHash([EXTRACTOR_VERSION, METHODS, CALL_SHAPE_METHOD, RECOVERY_EDGE_METHOD, set.cells.map(cellIdOf), errorRowsRead(errors)]),
|
|
260
|
+
lastDay: set.cells.reduce((last, cell) => (cell.day !== UNKNOWN && (last === null || cell.day > last) ? cell.day : last), null),
|
|
261
|
+
});
|