session-orchestrator 5.3.0 → 5.5.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/.agents/skills/claude-md-drift-check/SKILL.md +1 -1
- package/.agents/skills/test-audit/SKILL.md +21 -0
- package/.agents/skills/test-audit/agents/openai.yaml +5 -0
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.codex-plugin/skills/claude-md-drift-check/SKILL.md +1 -1
- package/.codex-plugin/skills/test-audit/SKILL.md +21 -0
- package/.codex-plugin/skills/test-audit/agents/openai.yaml +5 -0
- package/.cursor/commands/test-audit.md +14 -0
- package/.cursor/rules/060-evolve.mdc +28 -13
- package/.cursor/skills/claude-md-drift-check/SKILL.md +1 -1
- package/.cursor/skills/test-audit/SKILL.md +12 -0
- package/.cursor-plugin/plugin.json +1 -1
- package/.orchestrator/policy/blocked-commands.json +1 -1
- package/AGENTS.md +9 -31
- package/CHANGELOG.md +162 -0
- package/README.md +14 -10
- package/docs/agent-authoring.md +2 -2
- package/docs/baseline.md +10 -1
- package/docs/ci-setup.md +154 -4
- package/docs/components.md +11 -11
- package/docs/events-schema.md +25 -19
- package/docs/install.md +14 -2
- package/docs/migration-v4.md +24 -17
- package/docs/persona-panel.md +5 -4
- package/docs/plugin-architecture-v3.md +1 -1
- package/docs/rule-authoring.md +27 -12
- package/docs/scope-collision-guard.md +40 -16
- package/docs/session-config-reference.md +61 -12
- package/docs/session-config-template.md +11 -2
- package/docs/vault-docs-architecture.md +8 -4
- package/hooks/_lib/hook-import-set.json +42 -29
- package/hooks/_lib/lock-bootstrap.mjs +118 -26
- package/hooks/config-protection.mjs +1 -1
- package/hooks/enforce-commands.mjs +28 -13
- package/hooks/enforce-scope.mjs +201 -23
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks-cursor.json +5 -0
- package/hooks/hooks-pi.json +5 -0
- package/hooks/hooks.json +6 -1
- package/hooks/on-session-start.mjs +57 -2
- package/hooks/on-stop.mjs +70 -168
- package/hooks/post-bash-write-verify.mjs +98 -17
- package/hooks/post-edit-validate.mjs +15 -5
- package/hooks/post-tool-batch-wave-signal.mjs +32 -152
- package/hooks/pre-bash-memory-propose-audit.mjs +71 -10
- package/hooks/pre-bash-sessions-ledger-guard.mjs +28 -2
- package/hooks/pre-bash-staging-fence.mjs +29 -8
- package/hooks/pre-edit-sessions-ledger-guard.mjs +162 -0
- package/hooks/pre-task-scope-disjoint.mjs +1143 -279
- package/hooks/subagent-telemetry.mjs +96 -19
- package/hooks/wave-scope-commit-guard.mjs +168 -72
- package/package.json +2 -6
- package/pi/prompts/test-audit.md +12 -0
- package/rules/README.md +6 -1
- package/rules/_index.md +3 -3
- package/rules/always-on/bash-harness-pitfalls.md +7 -1
- package/rules/opt-in-domain/prompt-caching.md +5 -1
- package/rules/opt-in-stack/backend-data.md +9 -1
- package/rules/opt-in-stack/backend.md +7 -1
- package/rules/opt-in-stack/frontend.md +6 -1
- package/rules/opt-in-stack/security-web.md +7 -1
- package/rules/opt-in-stack/swift.md +5 -1
- package/scripts/apply-session-learnings.mjs +513 -0
- package/scripts/archive-closed-prds.mjs +40 -12
- package/scripts/autopilot.mjs +57 -54
- package/scripts/backfill-abandoned-sessions.mjs +81 -6
- package/scripts/backfill-learnings-from-vault.mjs +49 -22
- package/scripts/baseline-archetypes.mjs +1 -1
- package/scripts/check-package-manager.mjs +62 -5
- package/scripts/codex-install.mjs +3 -0
- package/scripts/cursor-install.mjs +47 -6
- package/scripts/emit-session.mjs +298 -2
- package/scripts/export-hw-learnings.mjs +40 -10
- package/scripts/fleet-checkin.mjs +170 -0
- package/scripts/lib/agent-frontmatter.mjs +3 -3
- package/scripts/lib/auto-dream.mjs +7 -6
- package/scripts/lib/autopilot/flags.mjs +6 -1
- package/scripts/lib/autopilot/loop.mjs +32 -13
- package/scripts/lib/autopilot/stall-sampler.mjs +13 -9
- package/scripts/lib/baseline-archetypes.mjs +41 -22
- package/scripts/lib/blocked-commands-policy.mjs +40 -0
- package/scripts/lib/common.mjs +41 -0
- package/scripts/lib/config/host-paths.mjs +79 -0
- package/scripts/lib/config/persona-gate-wave.mjs +3 -3
- package/scripts/lib/config/reaper.mjs +7 -6
- package/scripts/lib/config/vault-integration.mjs +151 -16
- package/scripts/lib/config-schema.mjs +13 -0
- package/scripts/lib/config.mjs +75 -10
- package/scripts/lib/eval/engine.mjs +13 -0
- package/scripts/lib/events-retention-banner.mjs +393 -0
- package/scripts/lib/events-rotation.mjs +21 -6
- package/scripts/lib/events.mjs +72 -5
- package/scripts/lib/fleet-protocol.mjs +288 -0
- package/scripts/lib/gates/gate-baseline.mjs +13 -3
- package/scripts/lib/gates/gate-full.mjs +7 -2
- package/scripts/lib/gates/gate-helpers.mjs +93 -2
- package/scripts/lib/gates/gate-incremental.mjs +23 -22
- package/scripts/lib/gates/gate-per-file.mjs +16 -7
- package/scripts/lib/hardening.mjs +1 -0
- package/scripts/lib/harness-audit/categories/category4.mjs +8 -6
- package/scripts/lib/instruction-budget-guard.mjs +77 -11
- package/scripts/lib/learnings/expiry-sweep.mjs +420 -22
- package/scripts/lib/learnings/io.mjs +274 -37
- package/scripts/lib/learnings/schema.mjs +163 -15
- package/scripts/lib/learnings/sizing-subject.mjs +2 -2
- package/scripts/lib/lock-reaper.mjs +38 -14
- package/scripts/lib/maintenance-due-banner.mjs +23 -8
- package/scripts/lib/memory-proposals/schema.mjs +11 -5
- package/scripts/lib/named-baseline-resolver.mjs +53 -28
- package/scripts/lib/named-vault-resolver.mjs +147 -5
- package/scripts/lib/orphan-reaper/defaults.mjs +41 -0
- package/scripts/lib/orphan-reaper/no-follow-append.mjs +87 -0
- package/scripts/lib/orphan-reaper/ps-snapshot.mjs +181 -0
- package/scripts/lib/orphan-reaper/reaper-audit.mjs +323 -0
- package/scripts/lib/orphan-reaper/reaper-decide.mjs +395 -0
- package/scripts/lib/orphan-reaper/scan-throttle.mjs +144 -0
- package/scripts/lib/orphan-reaper/scan.mjs +510 -0
- package/scripts/lib/orphan-reaper/trigger.mjs +173 -0
- package/scripts/lib/orphan-reaper.mjs +47 -1315
- package/scripts/lib/owner-yaml.mjs +104 -6
- package/scripts/lib/persona-panel/catalog-loader.mjs +1 -1
- package/scripts/lib/platform.mjs +206 -2
- package/scripts/lib/plugin-update-banner.mjs +3 -1
- package/scripts/lib/process-group.mjs +134 -22
- package/scripts/lib/project-hygiene.mjs +21 -1
- package/scripts/lib/quality-gate.mjs +45 -5
- package/scripts/lib/resource-probe.mjs +1 -1
- package/scripts/lib/rule-loader.mjs +100 -62
- package/scripts/lib/rules-sync.mjs +27 -12
- package/scripts/lib/scope-baseline.mjs +119 -29
- package/scripts/lib/scope-echo.mjs +7 -0
- package/scripts/lib/scope-gate.mjs +194 -27
- package/scripts/lib/session-close-backfill.mjs +129 -4
- package/scripts/lib/session-end/tail-runner.mjs +10 -5
- package/scripts/lib/session-lock-shape.mjs +6 -3
- package/scripts/lib/session-lock.mjs +320 -67
- package/scripts/lib/session-record-repair.mjs +183 -26
- package/scripts/lib/session-registry.mjs +67 -17
- package/scripts/lib/session-schema/constants.mjs +30 -4
- package/scripts/lib/session-schema/validator.mjs +28 -1
- package/scripts/lib/session-start-probes.mjs +242 -68
- package/scripts/lib/session-token-rollup.mjs +252 -54
- package/scripts/lib/sessions-canonical.mjs +100 -9
- package/scripts/lib/sessions-staleness-banner.mjs +3 -1
- package/scripts/lib/sh-quote.mjs +29 -0
- package/scripts/lib/subagents-schema.mjs +30 -3
- package/scripts/lib/tail-window.mjs +27 -3
- package/scripts/lib/telemetry/pricing.mjs +121 -71
- package/scripts/lib/telemetry/sync.mjs +17 -0
- package/scripts/lib/tests-src-ratio.mjs +215 -23
- package/scripts/lib/tmux-layout/layouts.mjs +1 -11
- package/scripts/lib/tmux-layout/telemetry-stats.mjs +12 -0
- package/scripts/lib/tmux-layout/tmux-shell.mjs +2 -11
- package/scripts/lib/ux-grill/reconcile.mjs +1 -1
- package/scripts/lib/validate/check-agents.mjs +1 -1
- package/scripts/lib/validate/check-hooks-symmetry.mjs +5 -0
- package/scripts/lib/validate/check-learnings-shell-writes.mjs +214 -0
- package/scripts/lib/validate/check-test-value-bans.mjs +388 -27
- package/scripts/lib/validate/check-unicode-safety.mjs +1 -0
- package/scripts/lib/validate/check-unwired-features.mjs +1 -1
- package/scripts/lib/validate/check-vcs-repo-flag.mjs +1 -1
- package/scripts/lib/validate-vendored-rules.mjs +206 -81
- package/scripts/lib/vault-mirror/auto-commit.mjs +5 -3
- package/scripts/lib/vault-mirror/namespace.mjs +47 -26
- package/scripts/lib/vault-mirror/process.mjs +350 -62
- package/scripts/lib/vault-mirror/pseudonym-map.mjs +14 -5
- package/scripts/lib/vault-mirror/render-learnings.mjs +66 -3
- package/scripts/lib/vault-mirror/render-sessions.mjs +28 -4
- package/scripts/lib/vault-mirror/telemetry.mjs +25 -11
- package/scripts/lib/vault-status/board-writer.mjs +53 -12
- package/scripts/lib/vault-status/narrative-mirror.mjs +40 -9
- package/scripts/lib/vcs-repo-spec.mjs +8 -0
- package/scripts/lib/wave-executor/dispatch-common.mjs +10 -18
- package/scripts/lib/wave-executor/foreign-dispatch.mjs +1 -1
- package/scripts/lib/wave-executor/remote-dispatch.mjs +1 -1
- package/scripts/lib/wave-resource-gate.mjs +34 -8
- package/scripts/materialize-wave-scope.mjs +7 -4
- package/scripts/memory-propose.mjs +18 -5
- package/scripts/release-session-lock.mjs +2 -1
- package/scripts/release.mjs +11 -3
- package/scripts/repair-invalid-sessions.mjs +37 -4
- package/scripts/run-migrate-v2-cross-repo.mjs +171 -31
- package/scripts/run-quality-gate.mjs +331 -307
- package/scripts/self-update.mjs +229 -0
- package/scripts/site-numbers.mjs +163 -5
- package/scripts/sweep-expired-learnings.mjs +603 -20
- package/scripts/sweep-expired-rules.mjs +2 -1
- package/scripts/validate-plugin.mjs +7 -0
- package/scripts/validate-wave-scope.mjs +21 -2
- package/scripts/vault-backfill.mjs +50 -37
- package/scripts/vault-integration-watcher.mjs +2 -1
- package/scripts/vault-mirror.mjs +238 -75
- package/scripts/wave-scope-binding.mjs +2 -1
- package/skills/_shared/config-reading.md +3 -3
- package/skills/_shared/fleet-protocol.md +134 -0
- package/skills/_shared/state-ownership.md +41 -12
- package/skills/autopilot/SKILL.md +25 -11
- package/skills/bootstrap/SKILL.md +6 -4
- package/skills/bootstrap/_shared-template.md +97 -2
- package/skills/bootstrap/fast-template.md +4 -1
- package/skills/bootstrap/private-contract.md +2 -1
- package/skills/bootstrap/public-fallback.md +6 -3
- package/skills/bootstrap/references/bootstrap-refresh-lock-flow.md +5 -3
- package/skills/bootstrap/references/bootstrap-retroactive-flow.md +2 -1
- package/skills/bootstrap/standard-template.md +2 -0
- package/skills/claude-md-drift-check/SKILL.md +10 -10
- package/skills/claude-md-drift-check/checker.mjs +26 -18
- package/skills/discovery/probes/ssot-code-diff.mjs +12 -6
- package/skills/discovery/probes/vault-narrative-staleness.mjs +6 -2
- package/skills/discovery/probes/vault-staleness.mjs +9 -3
- package/skills/docs-orchestrator/SKILL.md +12 -5
- package/skills/docs-orchestrator/audience-mapping.md +5 -3
- package/skills/evolve/SKILL.md +13 -4
- package/skills/evolve/references/evolve-analyze-mode.md +42 -18
- package/skills/memory-cleanup/SKILL.md +1 -1
- package/skills/persona-panel/SKILL.md +2 -2
- package/skills/persona-panel/persona-format.md +2 -2
- package/skills/persona-panel/presets/designer-lens.md +1 -1
- package/skills/persona-panel/presets/engineer-lens.md +1 -1
- package/skills/persona-panel/presets/pm-lens.md +1 -1
- package/skills/plan/mode-retro.md +19 -4
- package/skills/quality-gates/SKILL.md +6 -4
- package/skills/remote-offload/SKILL.md +4 -2
- package/skills/session-end/learning-patterns.md +23 -13
- package/skills/session-end/metrics-collection.md +6 -7
- package/skills/session-end/phase-3-2-docs-verification.md +7 -6
- package/skills/session-end/plan-verification.md +25 -9
- package/skills/session-end/references/phase-3-documentation-updates.md +13 -3
- package/skills/session-end/references/phase-4a-worktree-cleanup.md +2 -0
- package/skills/session-end/references/session-summary-template.md +1 -1
- package/skills/session-end/session-metrics-write.md +65 -68
- package/skills/session-plan/SKILL.md +0 -3
- package/skills/session-start/SKILL.md +8 -2
- package/skills/session-start/references/operations-contract.md +6 -0
- package/skills/session-start/references/phase-1-2-session-lock.md +32 -7
- package/skills/session-start/references/phase-1-7-vault-status-board.md +1 -1
- package/skills/session-start/references/phase-4-ssot-environment-check.md +10 -3
- package/skills/test-audit/SKILL.md +152 -0
- package/skills/test-audit/references/campaign.md +111 -0
- package/skills/test-audit/references/mutate.sh +50 -0
- package/skills/vault-mirror/SKILL.md +21 -11
- package/skills/wave-executor/SKILL.md +2 -2
- package/skills/wave-executor/circuit-breaker.md +1 -1
- package/skills/wave-executor/references/wave-executor-state-init.md +3 -0
- package/skills/wave-executor/references/wave-loop-dispatch.md +11 -1
- package/skills/wave-executor/references/wave-loop-review.md +4 -6
- package/skills/wave-executor/references/wave-loop-scope-manifest.md +13 -5
- package/templates/personas/README.md +5 -5
- package/templates/personas/accounting-compliance.v1.md +1 -1
- package/templates/personas/accounting-tax-advisor.v1.md +1 -1
- package/templates/personas/buyer-p1-cto.v1.md +1 -1
- package/templates/personas/buyer-p2-kanzlei.v1.md +1 -1
- package/templates/personas/buyer-p3-build.v1.md +1 -1
- package/templates/personas/buyer-p4-tech-veto.v1.md +1 -1
- package/templates/personas/buyer-p5-solo.v1.md +1 -1
- package/templates/personas/buyer-p6-ld.v1.md +1 -1
- package/templates/personas/klima-ai-expert.v1.md +1 -1
- package/templates/personas/klima-physicist.v1.md +1 -1
|
@@ -42,8 +42,16 @@
|
|
|
42
42
|
* token_cache_creation integer | null — `usage.cache_creation_input_tokens` (v2+)
|
|
43
43
|
* token_output integer | null — completion token count for this subagent
|
|
44
44
|
* model string | null — model id from the transcript (v2+); null when
|
|
45
|
-
* the transcript exposes none
|
|
46
|
-
*
|
|
45
|
+
* the transcript exposes none, or when its token-
|
|
46
|
+
* bearing turns used two or more models or one turn
|
|
47
|
+
* named none (#1470). Required to price the record —
|
|
48
|
+
* see scripts/lib/telemetry/pricing.mjs.
|
|
49
|
+
* models_usage array | absent — per-model breakdown of a record whose token-
|
|
50
|
+
* bearing turns used two or more models, every
|
|
51
|
+
* one named (#1470): [{ model, token_input_uncached,
|
|
52
|
+
* token_cache_read, token_cache_creation,
|
|
53
|
+
* token_output }], one entry per model. The session
|
|
54
|
+
* rollup prices each part; absent on every other record.
|
|
47
55
|
* total_cost_usd number | null — native total cost in USD (#624, fractional,
|
|
48
56
|
* best-effort: null when the harness does not
|
|
49
57
|
* expose it; no rate table is applied)
|
|
@@ -115,7 +123,8 @@ export class ValidationError extends Error {
|
|
|
115
123
|
*
|
|
116
124
|
* Required fields: timestamp, event, agent_id, schema_version.
|
|
117
125
|
* Additional requirement when event='stop': duration_ms (integer or null).
|
|
118
|
-
* Optional: agent_type, parent_session_id, token_input, token_output
|
|
126
|
+
* Optional: agent_type, parent_session_id, token_input, token_output, the cache
|
|
127
|
+
* buckets, model, models_usage, total_cost_usd and the OTel aliases.
|
|
119
128
|
*
|
|
120
129
|
* @param {object} entry
|
|
121
130
|
* @param {object} [options]
|
|
@@ -233,6 +242,24 @@ export function validateSubagent(entry, options = {}) {
|
|
|
233
242
|
throw new ValidationError('model must be a string or null', 'model');
|
|
234
243
|
}
|
|
235
244
|
|
|
245
|
+
// models_usage (optional, #1470) — per-model parts of a mixed-model record.
|
|
246
|
+
// Each part is priced on its own, so each needs a model and four buckets.
|
|
247
|
+
if (entry.models_usage !== undefined && entry.models_usage !== null) {
|
|
248
|
+
if (!Array.isArray(entry.models_usage)) {
|
|
249
|
+
throw new ValidationError('models_usage must be an array or null', 'models_usage');
|
|
250
|
+
}
|
|
251
|
+
for (const part of entry.models_usage) {
|
|
252
|
+
const bucketsValid = ['token_input_uncached', 'token_cache_read', 'token_cache_creation', 'token_output']
|
|
253
|
+
.every((field) => Number.isInteger(part?.[field]) && part[field] >= 0);
|
|
254
|
+
if (typeof part?.model !== 'string' || part.model.length === 0 || !bucketsValid) {
|
|
255
|
+
throw new ValidationError(
|
|
256
|
+
'models_usage entries must carry a model string and four non-negative integer token buckets',
|
|
257
|
+
'models_usage'
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
236
263
|
// total_cost_usd (optional, #624) — fractional number (not integer), best-effort.
|
|
237
264
|
if (entry.total_cost_usd !== undefined && entry.total_cost_usd !== null) {
|
|
238
265
|
if (typeof entry.total_cost_usd !== 'number' || !Number.isFinite(entry.total_cost_usd) || entry.total_cost_usd < 0) {
|
|
@@ -16,6 +16,18 @@
|
|
|
16
16
|
|
|
17
17
|
import fs from 'node:fs';
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* `open(2)` flags of a `noFollow` read. `O_NOFOLLOW` refuses a symlink as the
|
|
21
|
+
* last path component (ELOOP) in the open itself; `O_NONBLOCK` makes the
|
|
22
|
+
* read-side open of a FIFO return at once instead of blocking until a writer
|
|
23
|
+
* appears (a plain `'r'` open of a planted FIFO blocked a detached scan child
|
|
24
|
+
* for good — reproduced 2026-10-02, #1487). On a regular file `O_NONBLOCK`
|
|
25
|
+
* changes nothing. Both are POSIX-only; Node leaves them undefined on Windows,
|
|
26
|
+
* where the read degrades to a plain open.
|
|
27
|
+
*/
|
|
28
|
+
const NO_FOLLOW_READ_FLAGS = fs.constants.O_RDONLY
|
|
29
|
+
| (fs.constants.O_NOFOLLOW ?? 0) | (fs.constants.O_NONBLOCK ?? 0);
|
|
30
|
+
|
|
19
31
|
/**
|
|
20
32
|
* Read the last `maxBytes` of `file` as UTF-8.
|
|
21
33
|
*
|
|
@@ -28,14 +40,26 @@ import fs from 'node:fs';
|
|
|
28
40
|
* THROWS on any fs error (ENOENT, EACCES, …) — mapping the failure is the
|
|
29
41
|
* caller's decision.
|
|
30
42
|
*
|
|
43
|
+
* `opts.noFollow` is for a file a hostile local writer may have replaced: it
|
|
44
|
+
* opens with {@link NO_FOLLOW_READ_FLAGS} and then refuses anything the
|
|
45
|
+
* descriptor's `fstat` does not call a regular file (FIFO, directory, device),
|
|
46
|
+
* so a symlink throws ELOOP and every other non-regular file throws
|
|
47
|
+
* `ERR_NOT_REGULAR_FILE` — never a blocked open, never a read of a link
|
|
48
|
+
* target. The default (`false`) is the plain `'r'` open every other caller uses.
|
|
49
|
+
*
|
|
31
50
|
* @param {string} file
|
|
32
51
|
* @param {number} maxBytes window size in bytes (> 0)
|
|
52
|
+
* @param {{noFollow?: boolean}} [opts]
|
|
33
53
|
* @returns {{ text: string, cut: boolean, size: number }} `size` is the file size at open time
|
|
34
54
|
*/
|
|
35
|
-
export function readTailWindow(file, maxBytes) {
|
|
36
|
-
const fd = fs.openSync(file, 'r');
|
|
55
|
+
export function readTailWindow(file, maxBytes, { noFollow = false } = {}) {
|
|
56
|
+
const fd = fs.openSync(file, noFollow ? NO_FOLLOW_READ_FLAGS : 'r');
|
|
37
57
|
try {
|
|
38
|
-
const
|
|
58
|
+
const stats = fs.fstatSync(fd);
|
|
59
|
+
if (noFollow && !stats.isFile()) {
|
|
60
|
+
throw Object.assign(new Error('not a regular file'), { code: 'ERR_NOT_REGULAR_FILE' });
|
|
61
|
+
}
|
|
62
|
+
const size = stats.size;
|
|
39
63
|
const want = Math.min(size, maxBytes);
|
|
40
64
|
const start = size - want;
|
|
41
65
|
const buf = Buffer.allocUnsafe(want);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* pricing.mjs — USD price table for token-bucket cost estimation (#1244).
|
|
2
|
+
* pricing.mjs — USD price table for token-bucket cost estimation (#1244, #1470).
|
|
3
3
|
*
|
|
4
4
|
* The subagent ledger records four token buckets per stop record
|
|
5
5
|
* (`token_input_uncached`, `token_cache_read`, `token_cache_creation`,
|
|
@@ -8,23 +8,39 @@
|
|
|
8
8
|
* an order of magnitude on a cache-heavy run — which is the whole reason this
|
|
9
9
|
* table exists rather than a single `$/token` constant.
|
|
10
10
|
*
|
|
11
|
-
* ##
|
|
11
|
+
* ## Every rate is a documented figure
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* carries
|
|
16
|
-
* in this prose.
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* multiplied out here.
|
|
13
|
+
* All four rates of every row are first-party Anthropic API list prices, read
|
|
14
|
+
* off the price page each row names in `source_url` on the date
|
|
15
|
+
* `PRICING_TABLE_DATE` carries — that constant is the SSOT, never a second date
|
|
16
|
+
* repeated in this prose. Nothing here is multiplied out. `cache_read` in
|
|
17
|
+
* particular is never derived from `input`: the ratio is not a constant (Opus
|
|
18
|
+
* 5.5 reads at 0.05× its input rate, Fable 5.1 at 0.025×, Fable 5 at 0.1×), so
|
|
19
|
+
* a multiplier would misprice exactly the cache-heavy runs this table exists
|
|
20
|
+
* for. Each row carries its own provenance fields, so a consumer quoting a
|
|
21
|
+
* dollar figure can cite where it came from without reading this file.
|
|
23
22
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
23
|
+
* `verified: false` would mark a PLACEHOLDER row — a best-effort stand-in,
|
|
24
|
+
* never a quoted price. No current row carries it; a consumer publishing a
|
|
25
|
+
* dollar figure must still surface the flag.
|
|
26
|
+
*
|
|
27
|
+
* ## Only exact ids resolve
|
|
28
|
+
*
|
|
29
|
+
* `priceFor()` accepts an exact table key or an explicit `MODEL_ALIASES` entry
|
|
30
|
+
* and nothing else. Until #1470 it fell back to the longest table key an id
|
|
31
|
+
* started with, which handed an unlisted generation the rates of its nearest
|
|
32
|
+
* neighbour, marked verified.
|
|
33
|
+
*
|
|
34
|
+
* ## Series break (#1470, 2026-09-29)
|
|
35
|
+
*
|
|
36
|
+
* Session reports written before this fix priced records through that
|
|
37
|
+
* fallback: `claude-opus-5-5` at `claude-opus-5` rates, `claude-sonnet-5-5` at
|
|
38
|
+
* `claude-sonnet-5` rates (identical numbers, by coincidence rather than
|
|
39
|
+
* evidence), and `claude-fable-5` / `claude-opus-4-8` not at all. Nothing
|
|
40
|
+
* rewrites that history — the metrics ledgers are append-only. Measured
|
|
41
|
+
* 2026-09-29: 0 of the 9 priced sessions.jsonl reports in this repo contained
|
|
42
|
+
* such records (the first one reached subagents.jsonl after the last priced
|
|
43
|
+
* report was written).
|
|
28
44
|
*
|
|
29
45
|
* ## Null means UNKNOWN MODEL, never zero
|
|
30
46
|
*
|
|
@@ -39,68 +55,96 @@
|
|
|
39
55
|
*/
|
|
40
56
|
|
|
41
57
|
/**
|
|
42
|
-
* The date the
|
|
43
|
-
* Any consumer quoting a dollar figure should quote this
|
|
44
|
-
* price without its measurement date ages silently.
|
|
58
|
+
* The date the rates in PRICING_TABLE were read off the price page. Every row's
|
|
59
|
+
* `checked_at` equals it. Any consumer quoting a dollar figure should quote this
|
|
60
|
+
* date beside it — a price without its measurement date ages silently.
|
|
45
61
|
* @type {string}
|
|
46
62
|
*/
|
|
47
|
-
export const PRICING_TABLE_DATE = '2026-09-
|
|
63
|
+
export const PRICING_TABLE_DATE = '2026-09-29';
|
|
48
64
|
|
|
49
65
|
/**
|
|
50
66
|
* @typedef {Object} PricingRow
|
|
51
67
|
* @property {number} input - USD per 1M uncached prompt tokens.
|
|
52
68
|
* @property {number} cache_read - USD per 1M tokens served from the prompt cache.
|
|
53
|
-
* @property {number} cache_creation - USD per 1M tokens written to the prompt cache
|
|
69
|
+
* @property {number} cache_creation - USD per 1M tokens written to the prompt cache
|
|
70
|
+
* (the `cache_write_ttl` rate).
|
|
54
71
|
* @property {number} output - USD per 1M completion tokens.
|
|
55
72
|
* @property {boolean} verified - false ⇒ the row is a labelled placeholder, not a quoted price.
|
|
56
73
|
* @property {'documented'|'derived-multiplier'} cache_source - provenance of the two cache rates.
|
|
74
|
+
* Every current row is 'documented'; 'derived-multiplier' is kept for a future row whose cache
|
|
75
|
+
* rates are multiplied out and must say so.
|
|
76
|
+
* @property {string} source_url - the page all four rates were read from.
|
|
77
|
+
* @property {string} checked_at - ISO date the rates were read; equals PRICING_TABLE_DATE.
|
|
78
|
+
* @property {'anthropic'} provider
|
|
79
|
+
* @property {'5m'} cache_write_ttl - which cache-write rate `cache_creation` is.
|
|
80
|
+
* @property {string} unit - 'USD per 1M tokens'.
|
|
81
|
+
* @property {string} surface - the billing surface and speed the rates apply to.
|
|
82
|
+
* @property {'standard'} tier - the service tier the rates apply to.
|
|
83
|
+
*/
|
|
84
|
+
|
|
85
|
+
/** Provenance shared by every current row — all read off one page on one date. */
|
|
86
|
+
const PROVENANCE = Object.freeze({
|
|
87
|
+
verified: true,
|
|
88
|
+
cache_source: 'documented',
|
|
89
|
+
source_url: 'https://platform.claude.com/docs/en/about-claude/pricing',
|
|
90
|
+
checked_at: PRICING_TABLE_DATE,
|
|
91
|
+
provider: 'anthropic',
|
|
92
|
+
cache_write_ttl: '5m',
|
|
93
|
+
unit: 'USD per 1M tokens',
|
|
94
|
+
surface: 'Claude API (first-party), standard speed',
|
|
95
|
+
tier: 'standard',
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* One frozen documented row: four USD rates plus the shared provenance. The
|
|
100
|
+
* rates are spread last, so provenance can never overwrite a price.
|
|
101
|
+
* @param {{input: number, cache_read: number, cache_creation: number, output: number}} rates
|
|
102
|
+
* @returns {PricingRow}
|
|
57
103
|
*/
|
|
104
|
+
function usd(rates) {
|
|
105
|
+
return Object.freeze({ ...PROVENANCE, ...rates });
|
|
106
|
+
}
|
|
58
107
|
|
|
59
108
|
/**
|
|
60
109
|
* Per-model USD-per-million-token rates.
|
|
61
110
|
* @type {Readonly<Record<string, PricingRow>>}
|
|
62
111
|
*/
|
|
63
112
|
export const PRICING_TABLE = Object.freeze({
|
|
64
|
-
//
|
|
65
|
-
// (
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}),
|
|
90
|
-
'claude-
|
|
91
|
-
|
|
92
|
-
cache_read: 0.1,
|
|
93
|
-
cache_creation: 1.25,
|
|
94
|
-
output: 5.0,
|
|
95
|
-
verified: true,
|
|
96
|
-
cache_source: 'derived-multiplier',
|
|
97
|
-
}),
|
|
113
|
+
// Ceiling: only the 5-minute cache-write rate is priced; a 1-hour write costs 2× input
|
|
114
|
+
// (Opus 5.5: $8/MTok) and would be under-priced here. Measured 2026-09-29 over 803,788
|
|
115
|
+
// assistant usage blocks in 10,745 subagent transcripts: `usage.cache_creation
|
|
116
|
+
// .ephemeral_1h_input_tokens > 0` on 0 blocks. Revisit when the first such block appears.
|
|
117
|
+
//
|
|
118
|
+
// Ceiling: list price only — fast mode, `inference_geo: "us"` (1.1×) and batch discounts are
|
|
119
|
+
// not modelled. Measured 2026-09-29: `usage.speed` = "fast" on 0 of the 374,331 blocks that
|
|
120
|
+
// carry the field; `inference_geo` = "not_available" on 803,479 and no other value seen.
|
|
121
|
+
// Revisit when either field shows another value.
|
|
122
|
+
//
|
|
123
|
+
// Ceiling: a model absent from this table prices as null, never at a neighbour's rate, so the
|
|
124
|
+
// rollup reports `total_cost_usd: null` and `cost_records_priced < cost_records_total` — the
|
|
125
|
+
// intended way missing evidence shows up. That ratio also drops below 1 for agents no row can
|
|
126
|
+
// fix: a token-less record (transcript over the hook's size cap, without usage turns, or
|
|
127
|
+
// unreadable), a record with a token-bearing turn that names no model (#1475), a started agent
|
|
128
|
+
// whose stop found no transcript of its own, and an agent seen only in schema_version 1 records
|
|
129
|
+
// (#1487 Pkt 1). The one complete list is the `session-token-rollup.mjs` module doc — the code
|
|
130
|
+
// that decides; this comment and `emit-session.mjs` only point at it (#1498). Revisit when
|
|
131
|
+
// an unpriced record names a model id absent from this table: add that model as its own
|
|
132
|
+
// evidenced row, never as a family prefix or an alias to another generation.
|
|
133
|
+
'claude-fable-5-1': usd({ input: 10.0, cache_read: 0.25, cache_creation: 12.5, output: 50.0 }),
|
|
134
|
+
'claude-fable-5': usd({ input: 10.0, cache_read: 1.0, cache_creation: 12.5, output: 50.0 }),
|
|
135
|
+
'claude-opus-5-5': usd({ input: 4.0, cache_read: 0.2, cache_creation: 5.0, output: 20.0 }),
|
|
136
|
+
'claude-opus-5': usd({ input: 5.0, cache_read: 0.5, cache_creation: 6.25, output: 25.0 }),
|
|
137
|
+
'claude-opus-4-8': usd({ input: 5.0, cache_read: 0.5, cache_creation: 6.25, output: 25.0 }),
|
|
138
|
+
'claude-sonnet-5-5': usd({ input: 2.0, cache_read: 0.2, cache_creation: 2.5, output: 10.0 }),
|
|
139
|
+
'claude-sonnet-5': usd({ input: 2.0, cache_read: 0.2, cache_creation: 2.5, output: 10.0 }),
|
|
140
|
+
'claude-haiku-4-5': usd({ input: 1.0, cache_read: 0.1, cache_creation: 1.25, output: 5.0 }),
|
|
98
141
|
});
|
|
99
142
|
|
|
100
143
|
/**
|
|
101
144
|
* Explicit aliases for model ids that appear in transcripts but are not the
|
|
102
|
-
* canonical table key — dated
|
|
103
|
-
* the harness stamps on long-context sessions.
|
|
145
|
+
* canonical table key — a dated snapshot of the SAME model and the `[1m]`
|
|
146
|
+
* context-window suffix the harness stamps on long-context sessions. An alias
|
|
147
|
+
* never points at another generation.
|
|
104
148
|
* @type {Readonly<Record<string, string>>}
|
|
105
149
|
*/
|
|
106
150
|
export const MODEL_ALIASES = Object.freeze({
|
|
@@ -113,10 +157,19 @@ export const MODEL_ALIASES = Object.freeze({
|
|
|
113
157
|
/**
|
|
114
158
|
* Look up the rate row for a model id.
|
|
115
159
|
*
|
|
116
|
-
* Resolution
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
* `claude-opus-5-
|
|
160
|
+
* Resolution: exact `PRICING_TABLE` key → explicit `MODEL_ALIASES` entry → `null`,
|
|
161
|
+
* after trimming surrounding whitespace.
|
|
162
|
+
* Nothing else resolves — no prefix, no pattern, no family fallback — so
|
|
163
|
+
* `claude-opus-5-20260401`, `claude-opus-5-99` and `claude-haiku-4-5-20251002` are
|
|
164
|
+
* all UNKNOWN even though each starts with a table key.
|
|
165
|
+
*
|
|
166
|
+
* Why no prefix: from the 4.6 generation on, Anthropic ships dateless pinned
|
|
167
|
+
* model ids (https://platform.claude.com/docs/en/about-claude/models/model-ids-and-versions,
|
|
168
|
+
* retrieved 2026-09-29). The ids that extend a current table key are therefore
|
|
169
|
+
* other generations, not snapshots of it — `claude-opus-5-5` extends
|
|
170
|
+
* `claude-opus-5` — and a prefix match priced them at another generation's
|
|
171
|
+
* rates, marked verified (#1470). A future generation gets its own evidenced
|
|
172
|
+
* row, never a family prefix.
|
|
120
173
|
*
|
|
121
174
|
* @param {string|null|undefined} modelId
|
|
122
175
|
* @returns {PricingRow|null} the row, or null when the model is UNKNOWN
|
|
@@ -129,14 +182,10 @@ export function priceFor(modelId) {
|
|
|
129
182
|
|
|
130
183
|
if (Object.hasOwn(PRICING_TABLE, id)) return PRICING_TABLE[id];
|
|
131
184
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
let best = null;
|
|
136
|
-
for (const key of Object.keys(PRICING_TABLE)) {
|
|
137
|
-
if (id.startsWith(key) && (best === null || key.length > best.length)) best = key;
|
|
185
|
+
if (Object.hasOwn(MODEL_ALIASES, id) && Object.hasOwn(PRICING_TABLE, MODEL_ALIASES[id])) {
|
|
186
|
+
return PRICING_TABLE[MODEL_ALIASES[id]];
|
|
138
187
|
}
|
|
139
|
-
return
|
|
188
|
+
return null;
|
|
140
189
|
}
|
|
141
190
|
|
|
142
191
|
/**
|
|
@@ -161,9 +210,10 @@ function num(n) {
|
|
|
161
210
|
/**
|
|
162
211
|
* Estimate the USD cost of one record's four token buckets.
|
|
163
212
|
*
|
|
164
|
-
* Each bucket is multiplied by ITS OWN rate —
|
|
165
|
-
*
|
|
166
|
-
* blended rate is not an approximation of
|
|
213
|
+
* Each bucket is multiplied by ITS OWN rate — depending on the row, a cache
|
|
214
|
+
* read costs a tenth to a fortieth of an uncached prompt token and a 5-minute
|
|
215
|
+
* cache write 1.25× as much, so a blended rate is not an approximation of
|
|
216
|
+
* this, it is a different number.
|
|
167
217
|
*
|
|
168
218
|
* @param {object} opts
|
|
169
219
|
* @param {string|null|undefined} opts.model - model id from the transcript
|
|
@@ -312,6 +312,23 @@ export function readSessionProfileForMetricsDir(metricsDir) {
|
|
|
312
312
|
}
|
|
313
313
|
}
|
|
314
314
|
|
|
315
|
+
/**
|
|
316
|
+
* Events window this reader needs retained (#1401 part 3), in days — a CHOSEN
|
|
317
|
+
* ceiling, not a derived value. `deriveSessionFromEvents` loads the whole
|
|
318
|
+
* rotated ledger (no time filter) and keeps the LATEST
|
|
319
|
+
* `orchestrator.session.started`, so what it needs retained is that one
|
|
320
|
+
* record; 1 day is the assumed maximum age of that record when a flush reads
|
|
321
|
+
* it. Nothing in this module bounds that age: `buildBatch`'s
|
|
322
|
+
* `[now - DAILY_FLUSH_MS, now]` cutoff filters `skill-invocations.jsonl`, not
|
|
323
|
+
* the events ledger. CEILING (BV-004): a latest `session.started` older than a
|
|
324
|
+
* day at flush time (a session running longer than that) is found only if
|
|
325
|
+
* retention happens to reach it; revisit if that becomes common. Read by
|
|
326
|
+
* `scripts/lib/events-retention-banner.mjs`.
|
|
327
|
+
*
|
|
328
|
+
* @type {number}
|
|
329
|
+
*/
|
|
330
|
+
export const REQUIRED_EVENTS_WINDOW_DAYS = 1;
|
|
331
|
+
|
|
315
332
|
/**
|
|
316
333
|
* Reconstruct the session facts a ping needs (`session_type`, `started_at`,
|
|
317
334
|
* `completed_at`) from `<metricsDir>/events.jsonl` when `sessions.jsonl` has no
|