sandoichi 0.4.2 → 0.6.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 +5 -1
- package/index.mjs +18 -1
- package/package.json +1 -1
- package/src/accounting-cli.mjs +1 -1
- package/src/artifact-lifecycle.mjs +67 -0
- package/src/artifact-recovery.mjs +5 -0
- package/src/artifact-store.mjs +2 -1
- package/src/cache-attribution.mjs +13 -3
- package/src/context-transform.mjs +169 -17
- package/src/core.mjs +283 -34
- package/src/history-archive.mjs +80 -0
- package/src/hook-cli.mjs +17 -1
- package/src/lazy-mcp-gateway.mjs +10 -6
- package/src/mcp-server.mjs +47 -12
- package/src/metrics.mjs +4 -3
- package/src/provider-usage.mjs +103 -23
- package/src/proxy.mjs +83 -7
- package/src/result-disclosure.mjs +8 -2
- package/src/semantic-gate.mjs +69 -0
- package/src/semantic-judge.mjs +261 -0
- package/src/slice.mjs +419 -0
- package/src/statusline.mjs +5 -9
- package/src/telemetry.mjs +101 -19
package/src/statusline.mjs
CHANGED
|
@@ -33,19 +33,14 @@ function readMetricsSnapshot(metricsPath, { host, sessionId, model } = {}) {
|
|
|
33
33
|
const records = scopedRecords(state.records, { host, sessionId });
|
|
34
34
|
if (!records.length) return undefined;
|
|
35
35
|
const report = buildMetricsReport({ ...state, records }, { sessionId });
|
|
36
|
-
const
|
|
37
|
-
const hasEstimate = records.some((item) => item.providerReportedSavingsTokens === null);
|
|
38
|
-
const providerSavings = report.cumulative.providerReportedSavingsTokens;
|
|
39
|
-
const source = hasProvider && !hasEstimate && providerSavings !== null
|
|
40
|
-
? 'provider-reported' : 'estimate';
|
|
36
|
+
const source = 'estimate';
|
|
41
37
|
const estimatedInputTokens = records.reduce((total, item) => total + item.estimatedInputTokens, 0);
|
|
42
38
|
const latest = [...records].sort((left, right) => left.at.localeCompare(right.at)).at(-1);
|
|
43
39
|
return {
|
|
44
40
|
updatedAt: latestAt(records), source,
|
|
45
41
|
model: model ?? latest?.model,
|
|
46
42
|
estimatedInputTokens,
|
|
47
|
-
savedTokens:
|
|
48
|
-
? providerSavings : report.cumulative.estimatedTransformSavingsTokens,
|
|
43
|
+
savedTokens: report.cumulative.estimatedTransformSavingsTokens,
|
|
49
44
|
};
|
|
50
45
|
} catch {
|
|
51
46
|
return undefined;
|
|
@@ -85,7 +80,8 @@ function compactTokens(value) {
|
|
|
85
80
|
export function renderStatusLine({ metrics } = {}) {
|
|
86
81
|
if (!Number.isSafeInteger(metrics?.savedTokens) || metrics.savedTokens < 0
|
|
87
82
|
|| !Number.isSafeInteger(metrics?.estimatedInputTokens) || metrics.estimatedInputTokens <= 0) return '🥪 —';
|
|
88
|
-
const percentage = Math.round(metrics.savedTokens / metrics.estimatedInputTokens * 100);
|
|
89
83
|
const estimate = metrics.source === 'estimate' ? '~' : '';
|
|
90
|
-
|
|
84
|
+
const percentage = metrics.source === 'estimate'
|
|
85
|
+
? ` (${Math.round(metrics.savedTokens / metrics.estimatedInputTokens * 100)}%)` : '';
|
|
86
|
+
return `🥪 saved ${estimate}${compactTokens(metrics.savedTokens)} ctx tok${percentage}`;
|
|
91
87
|
}
|
package/src/telemetry.mjs
CHANGED
|
@@ -29,6 +29,20 @@ const F1_STATUSES = ['complete', 'partial', 'unavailable'];
|
|
|
29
29
|
const F1_RATIO_BUCKETS = ['zero', 'lt_1pct', '1_to_10pct', 'gt_10pct', 'unavailable'];
|
|
30
30
|
const F1_SIZE_BUCKETS = [...BYTE_BUCKETS, 'unavailable'];
|
|
31
31
|
const F1_INPUT_BUCKETS = [...COUNT_BUCKETS, 'unavailable'];
|
|
32
|
+
// Reduction without coverage reads as better than it is: a day that bounds heavily on the 3% of
|
|
33
|
+
// commands it recognises looks identical to one that bounds everything. These are the reasons the
|
|
34
|
+
// shell classifier already emits, so they are a closed set and carry nothing free-form.
|
|
35
|
+
// Counts alone cannot answer "how much did it reach": 1,276 routed and 43,945 bypassed both land
|
|
36
|
+
// in `gt_100`, and so would the reverse. The ratio is the field that carries the answer.
|
|
37
|
+
export const COVERAGE_RATIO_BUCKETS = ['zero', 'lt_1pct', '1_to_10pct', '10_to_50pct', '50_to_90pct', 'gt_90pct'];
|
|
38
|
+
|
|
39
|
+
export const COVERAGE_REASONS = [
|
|
40
|
+
'ambiguous-shell', 'compound-feeds-pipeline', 'compound-has-redirect', 'compound-segment-ambiguous',
|
|
41
|
+
'grep-shape', 'head-shape', 'invalid-input', 'read-shape', 'routing-disabled', 'sed-shape',
|
|
42
|
+
'tail-unbounded-from-end', 'unsafe-cwd', 'unsafe-grep-pattern', 'unsafe-grep-target',
|
|
43
|
+
'unsafe-read-target', 'unsupported-shell', 'unsupported-tool', 'other',
|
|
44
|
+
];
|
|
45
|
+
|
|
32
46
|
export const FAILURE_STAGES = [
|
|
33
47
|
'policy', 'input', 'redaction', 'optimization', 'artifact', 'output', 'upstream', 'response',
|
|
34
48
|
];
|
|
@@ -37,7 +51,7 @@ const SHARED_FIELDS = {
|
|
|
37
51
|
schema_version: (value) => value === SCHEMA_VERSION,
|
|
38
52
|
event: (value) => [
|
|
39
53
|
'hook_summary', 'proxy_summary', 'active_day', 'hook_failure_summary', 'proxy_failure_summary',
|
|
40
|
-
'f1_footprint', 'f4_gateway',
|
|
54
|
+
'f1_footprint', 'f4_gateway', 'coverage_summary',
|
|
41
55
|
].includes(value),
|
|
42
56
|
day_utc: (value) => typeof value === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(value),
|
|
43
57
|
plugin_version: (value) => typeof value === 'string' && /^\d+\.\d+(?:\.\d+)?$/.test(value) && value.length <= MAX_STRING_LENGTH,
|
|
@@ -69,6 +83,14 @@ const PROXY_FAILURE_FIELDS = {
|
|
|
69
83
|
provider: (value) => PROVIDERS.includes(value),
|
|
70
84
|
failure_stage: (value) => FAILURE_STAGES.includes(value),
|
|
71
85
|
};
|
|
86
|
+
const COVERAGE_FIELDS = {
|
|
87
|
+
host: (value) => HOSTS.includes(value),
|
|
88
|
+
routed_bucket: (value) => COUNT_BUCKETS.includes(value),
|
|
89
|
+
bypassed_bucket: (value) => COUNT_BUCKETS.includes(value),
|
|
90
|
+
coverage_ratio_bucket: (value) => COVERAGE_RATIO_BUCKETS.includes(value),
|
|
91
|
+
top_bypass_reason: (value) => COVERAGE_REASONS.includes(value),
|
|
92
|
+
};
|
|
93
|
+
|
|
72
94
|
const F4_FIELDS = {
|
|
73
95
|
f4_host: (value) => F4_HOSTS.includes(value),
|
|
74
96
|
f4_operation: (value) => F4_OPERATIONS.includes(value),
|
|
@@ -91,6 +113,7 @@ function fieldsForEvent(eventType) {
|
|
|
91
113
|
if (eventType === 'active_day') return ACTIVE_DAY_FIELDS;
|
|
92
114
|
if (eventType === 'hook_failure_summary') return HOOK_FAILURE_FIELDS;
|
|
93
115
|
if (eventType === 'f4_gateway') return F4_FIELDS;
|
|
116
|
+
if (eventType === 'coverage_summary') return COVERAGE_FIELDS;
|
|
94
117
|
return PROXY_FAILURE_FIELDS;
|
|
95
118
|
}
|
|
96
119
|
|
|
@@ -259,11 +282,11 @@ const LEASE_MS = 5 * 60 * 1000;
|
|
|
259
282
|
const RETRY_DELAYS_MS = [60_000, 300_000, 1_800_000, 7_200_000, 21_600_000];
|
|
260
283
|
const CHILD_ENV_KEYS = ['HTTPS_PROXY', 'HTTP_PROXY', 'NO_PROXY', 'NODE_EXTRA_CA_CERTS', 'SSL_CERT_FILE'];
|
|
261
284
|
|
|
262
|
-
function emptyCounters() { return { schema_version: TELEMETRY_CONFIG_VERSION, counters: {}, active_days: {} }; }
|
|
285
|
+
function emptyCounters() { return { schema_version: TELEMETRY_CONFIG_VERSION, counters: {}, active_days: {}, coverage_days: {} }; }
|
|
263
286
|
function readCounters(countersPath) {
|
|
264
287
|
if (!fs.existsSync(countersPath)) return emptyCounters();
|
|
265
288
|
const state = JSON.parse(fs.readFileSync(countersPath, 'utf8'));
|
|
266
|
-
return { ...state, counters: state.counters ?? {}, active_days: state.active_days ?? {} };
|
|
289
|
+
return { ...state, counters: state.counters ?? {}, active_days: state.active_days ?? {}, coverage_days: state.coverage_days ?? {} };
|
|
267
290
|
}
|
|
268
291
|
|
|
269
292
|
function readQueueRows(queuePath) {
|
|
@@ -320,9 +343,10 @@ function publicRow(row) {
|
|
|
320
343
|
}
|
|
321
344
|
|
|
322
345
|
function bucketEntry(entry, pluginVersion) {
|
|
346
|
+
const recordedVersion = entry.pluginVersion ?? pluginVersion;
|
|
323
347
|
if (entry.event === 'hook_summary') {
|
|
324
348
|
return {
|
|
325
|
-
schema_version: SCHEMA_VERSION, event: 'hook_summary', day_utc: entry.day, plugin_version:
|
|
349
|
+
schema_version: SCHEMA_VERSION, event: 'hook_summary', day_utc: entry.day, plugin_version: recordedVersion,
|
|
326
350
|
host: entry.host, mode: entry.mode,
|
|
327
351
|
tool_calls_bucket: countBucket(entry.toolCalls ?? 0),
|
|
328
352
|
capped_outputs_bucket: countBucket(entry.cappedOutputs ?? 0),
|
|
@@ -331,41 +355,70 @@ function bucketEntry(entry, pluginVersion) {
|
|
|
331
355
|
};
|
|
332
356
|
}
|
|
333
357
|
if (entry.event === 'proxy_summary') return {
|
|
334
|
-
schema_version: SCHEMA_VERSION, event: 'proxy_summary', day_utc: entry.day, plugin_version:
|
|
358
|
+
schema_version: SCHEMA_VERSION, event: 'proxy_summary', day_utc: entry.day, plugin_version: recordedVersion,
|
|
335
359
|
provider: entry.provider ?? 'unknown', mode: entry.mode ?? 'enforce',
|
|
336
360
|
rewrites_applied_bucket: countBucket(entry.rewritesApplied ?? 0),
|
|
337
361
|
rewrites_skipped_cache_bucket: countBucket(entry.rewritesSkippedCache ?? 0),
|
|
338
362
|
input_tokens_saved_bucket: byteBucket(entry.inputTokensSaved ?? 0),
|
|
339
363
|
};
|
|
364
|
+
if (entry.event === 'coverage_summary') {
|
|
365
|
+
const reasons = Object.entries(entry)
|
|
366
|
+
.filter(([field, count]) => field.startsWith(REASON_PREFIX) && Number.isInteger(count) && count > 0)
|
|
367
|
+
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
|
|
368
|
+
const leading = reasons[0]?.[0].slice(REASON_PREFIX.length);
|
|
369
|
+
const routed = entry.routed ?? 0;
|
|
370
|
+
const bypassed = entry.bypassed ?? 0;
|
|
371
|
+
return {
|
|
372
|
+
schema_version: SCHEMA_VERSION, event: 'coverage_summary', day_utc: entry.day, plugin_version: recordedVersion,
|
|
373
|
+
host: entry.host,
|
|
374
|
+
routed_bucket: countBucket(routed),
|
|
375
|
+
bypassed_bucket: countBucket(bypassed),
|
|
376
|
+
coverage_ratio_bucket: coverageRatioBucket(routed, routed + bypassed),
|
|
377
|
+
// A reason this build does not know travels as `other`, never as free text.
|
|
378
|
+
top_bypass_reason: COVERAGE_REASONS.includes(leading) ? leading : 'other',
|
|
379
|
+
};
|
|
380
|
+
}
|
|
340
381
|
if (entry.event === 'hook_failure_summary') return {
|
|
341
|
-
schema_version: SCHEMA_VERSION, event: 'hook_failure_summary', day_utc: entry.day, plugin_version:
|
|
382
|
+
schema_version: SCHEMA_VERSION, event: 'hook_failure_summary', day_utc: entry.day, plugin_version: recordedVersion,
|
|
342
383
|
host: entry.host, failure_stage: entry.failureStage,
|
|
343
384
|
};
|
|
344
385
|
return {
|
|
345
|
-
schema_version: SCHEMA_VERSION, event: 'proxy_failure_summary', day_utc: entry.day, plugin_version:
|
|
386
|
+
schema_version: SCHEMA_VERSION, event: 'proxy_failure_summary', day_utc: entry.day, plugin_version: recordedVersion,
|
|
346
387
|
provider: entry.provider, failure_stage: entry.failureStage,
|
|
347
388
|
};
|
|
348
389
|
}
|
|
349
390
|
|
|
350
391
|
/** Accumulates raw per-day counts in memory/on disk; values are only bucketed (and thus
|
|
351
392
|
* only ever leave the machine) once `closeDay` closes a finished UTC day. */
|
|
352
|
-
export function incrementCounter({ statePaths, day, event, host, provider, mode, failureStage, deltas = {} }) {
|
|
353
|
-
if (!['hook_summary', 'proxy_summary', 'hook_failure_summary', 'proxy_failure_summary'].includes(event)) {
|
|
393
|
+
export function incrementCounter({ statePaths, day, pluginVersion = PLUGIN_VERSION, event, host, provider, mode, failureStage, deltas = {} }) {
|
|
394
|
+
if (!['hook_summary', 'proxy_summary', 'hook_failure_summary', 'proxy_failure_summary', 'coverage_summary'].includes(event)) {
|
|
354
395
|
throw new Error('incrementCounter: invalid event');
|
|
355
396
|
}
|
|
356
397
|
const isProxy = event.startsWith('proxy_');
|
|
357
398
|
const dimension = isProxy ? provider : host;
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
399
|
+
if (!SHARED_FIELDS.plugin_version(pluginVersion)) throw new Error('incrementCounter: invalid plugin version');
|
|
400
|
+
const suffix = event.includes('failure') ? failureStage ?? '' : mode ?? '';
|
|
401
|
+
const key = [day, pluginVersion, event, dimension, suffix].join('|');
|
|
402
|
+
const legacyKey = [day, event, dimension, suffix].join('|');
|
|
361
403
|
ensureDirectory(path.dirname(statePaths.counters));
|
|
362
404
|
withLock(`${statePaths.counters}.lock`, () => {
|
|
363
405
|
const state = readCounters(statePaths.counters);
|
|
364
|
-
const
|
|
365
|
-
|
|
406
|
+
const legacy = state.counters[legacyKey];
|
|
407
|
+
let existing = state.counters[key];
|
|
408
|
+
if (legacy && !legacy.pluginVersion) {
|
|
409
|
+
if (existing) {
|
|
410
|
+
for (const [field, value] of Object.entries(legacy)) {
|
|
411
|
+
if (Number.isInteger(value) && value >= 0) existing[field] = (existing[field] ?? 0) + value;
|
|
412
|
+
}
|
|
413
|
+
} else existing = legacy;
|
|
414
|
+
delete state.counters[legacyKey];
|
|
415
|
+
}
|
|
416
|
+
existing ??= {
|
|
417
|
+
day, pluginVersion, event, ...(isProxy ? { provider: dimension } : { host: dimension }),
|
|
366
418
|
...(event.endsWith('_summary') && !event.includes('failure') ? { mode: mode ?? null } : {}),
|
|
367
419
|
...(event.includes('failure') ? { failureStage } : {}),
|
|
368
420
|
};
|
|
421
|
+
existing.pluginVersion = pluginVersion;
|
|
369
422
|
for (const [field, value] of Object.entries(deltas)) {
|
|
370
423
|
if (!Number.isInteger(value) || value < 0) throw new Error(`incrementCounter: invalid delta ${field}`);
|
|
371
424
|
existing[field] = (existing[field] ?? 0) + value;
|
|
@@ -375,23 +428,52 @@ export function incrementCounter({ statePaths, day, event, host, provider, mode,
|
|
|
375
428
|
});
|
|
376
429
|
}
|
|
377
430
|
|
|
378
|
-
export function recordFailure({ statePaths, day, event, host, provider, failureStage }) {
|
|
431
|
+
export function recordFailure({ statePaths, day, pluginVersion = PLUGIN_VERSION, event, host, provider, failureStage }) {
|
|
379
432
|
incrementCounter({
|
|
380
|
-
statePaths, day, event, host, provider, failureStage, deltas: { count: 1 },
|
|
433
|
+
statePaths, day, pluginVersion, event, host, provider, failureStage, deltas: { count: 1 },
|
|
381
434
|
});
|
|
382
435
|
}
|
|
383
436
|
|
|
384
437
|
/** Queues a single non-aggregate activity marker for this UTC day and host. */
|
|
385
|
-
export
|
|
438
|
+
export const REASON_PREFIX = 'reason:';
|
|
439
|
+
|
|
440
|
+
export function coverageRatioBucket(routed, total) {
|
|
441
|
+
if (!Number.isInteger(routed) || !Number.isInteger(total) || routed < 0 || total < routed) {
|
|
442
|
+
throw new Error('coverageRatioBucket: invalid counts');
|
|
443
|
+
}
|
|
444
|
+
if (total === 0 || routed === 0) return 'zero';
|
|
445
|
+
const ratio = routed / total;
|
|
446
|
+
if (ratio < 0.01) return 'lt_1pct';
|
|
447
|
+
if (ratio < 0.1) return '1_to_10pct';
|
|
448
|
+
if (ratio < 0.5) return '10_to_50pct';
|
|
449
|
+
if (ratio < 0.9) return '50_to_90pct';
|
|
450
|
+
return 'gt_90pct';
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/** One call per classified shell command. Counters close into a single row per day, next to the
|
|
454
|
+
* reduction they qualify: a large saving on a small share of commands should not read the same as
|
|
455
|
+
* a large saving on all of them. */
|
|
456
|
+
export function recordCoverage({ statePaths, day, pluginVersion = PLUGIN_VERSION, host, routed, reason }) {
|
|
457
|
+
const deltas = routed ? { routed: 1 } : { bypassed: 1 };
|
|
458
|
+
if (!routed) {
|
|
459
|
+
const label = COVERAGE_REASONS.includes(reason) ? reason : 'other';
|
|
460
|
+
deltas[`${REASON_PREFIX}${label}`] = 1;
|
|
461
|
+
}
|
|
462
|
+
incrementCounter({ statePaths, day, pluginVersion, event: 'coverage_summary', host, deltas });
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
export function recordActiveDay({ statePaths, day, pluginVersion = PLUGIN_VERSION, host }) {
|
|
386
466
|
const marker = {
|
|
387
|
-
schema_version: SCHEMA_VERSION, event: 'active_day', day_utc: day, plugin_version:
|
|
467
|
+
schema_version: SCHEMA_VERSION, event: 'active_day', day_utc: day, plugin_version: pluginVersion, host,
|
|
388
468
|
};
|
|
389
469
|
const validatedMarker = validateEvent(marker);
|
|
390
|
-
const activeDayKey = `${day}|${host}`;
|
|
470
|
+
const activeDayKey = `${day}|${pluginVersion}|${host}`;
|
|
471
|
+
const legacyActiveDayKey = `${day}|${host}`;
|
|
391
472
|
ensureDirectory(path.dirname(statePaths.counters));
|
|
392
473
|
withLock(`${statePaths.counters}.lock`, () => {
|
|
393
474
|
const state = readCounters(statePaths.counters);
|
|
394
475
|
const activeDays = state.active_days;
|
|
476
|
+
if (Object.hasOwn(activeDays, legacyActiveDayKey)) delete activeDays[legacyActiveDayKey];
|
|
395
477
|
const cutoff = Date.parse(`${day}T00:00:00Z`) - (ACTIVE_DAY_RETENTION_DAYS - 1) * 86_400_000;
|
|
396
478
|
for (const [key, recordedDay] of Object.entries(activeDays)) {
|
|
397
479
|
if (Date.parse(`${recordedDay}T00:00:00Z`) < cutoff) delete activeDays[key];
|