feat(observability): opt-in token capture in Stop hook (VOYAGE_TOKEN_METER)

This commit is contained in:
Kjell Tore Guttormsen 2026-06-26 14:42:58 +02:00
commit 46d51f8088
3 changed files with 243 additions and 2 deletions

View file

@ -17,6 +17,7 @@
// - All stderr prefixed with [voyage].
// - EXDEV mitigation: tmp file in same dir as target (do NOT use atomicWriteJson).
import { stdin } from 'node:process';
import { readFileSync, existsSync, writeFileSync, renameSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { transformToPrometheus } from '../../lib/exporters/textfile-format.mjs';
@ -24,6 +25,13 @@ import { transformToOtlpJson } from '../../lib/exporters/otlp-format.mjs';
import { validateTextfilePath } from '../../lib/exporters/path-validator.mjs';
import { validateOtlpEndpoint } from '../../lib/exporters/endpoint-validator.mjs';
import { applyFieldAllowlist } from '../../lib/exporters/field-allowlist.mjs';
import { captureTokenUsage } from '../../lib/stats/token-usage.mjs';
async function readStdin() {
let data = '';
for await (const chunk of stdin) data += chunk;
return data;
}
const VALID_MODES = new Set(['off', 'textfile', 'otlp']);
const TEXTFILE_NAME = 'voyage.prom';
@ -38,6 +46,7 @@ const STATS_FILES = [
{ file: 'trekexecute-stats.jsonl', schema: 'trekexecute' },
{ file: 'trekreview-stats.jsonl', schema: 'trekreview' },
{ file: 'trekcontinue-stats.jsonl', schema: 'trekcontinue' },
{ file: 'token-usage-stats.jsonl', schema: 'token-usage' },
];
function loadAndAllowlist(dataDir) {
@ -134,6 +143,26 @@ async function exportOtlp(records, env) {
(async () => {
try {
const env = process.env;
// SKAL-2: opt-in main-context token/cost capture (default off → zero added
// latency). Rides this existing Stop hook rather than a new hook script —
// avoids a second-Stop-hook race and keeps the README hook-count pin intact.
// Runs BEFORE the export-mode gate so capture is independent of export.
// Fail-open: any error is swallowed; capture must never block Stop.
if (env.VOYAGE_TOKEN_METER) {
try {
const raw = await readStdin();
if (raw.trim()) {
const payload = JSON.parse(raw);
captureTokenUsage({
transcriptPath: payload.transcript_path,
sessionId: payload.session_id,
dataDir: env.CLAUDE_PLUGIN_DATA,
});
}
} catch { /* fail-open: never block Stop, never throw */ }
}
const mode = (env.VOYAGE_EXPORT_MODE || 'off').toLowerCase();
if (mode === 'off') return;