@massu/core 2.5.4 → 2.5.5
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/CHANGELOG.md +33 -0
- package/dist/cli.js +436 -365
- package/dist/hooks/auto-learning-pipeline.js +26 -2
- package/dist/hooks/classify-failure.js +26 -2
- package/dist/hooks/cost-tracker.js +26 -2
- package/dist/hooks/fix-detector.js +26 -2
- package/dist/hooks/incident-pipeline.js +26 -2
- package/dist/hooks/intent-suggester.js +26 -2
- package/dist/hooks/memory-recall.js +26 -2
- package/dist/hooks/post-edit-context.js +26 -2
- package/dist/hooks/post-tool-use.js +26 -2
- package/dist/hooks/pre-compact.js +26 -2
- package/dist/hooks/pre-delete-check.js +26 -2
- package/dist/hooks/pre-tool-use-gate.js +26 -2
- package/dist/hooks/quality-event.js +26 -2
- package/dist/hooks/rule-enforcement-pipeline.js +26 -2
- package/dist/hooks/security-gate.js +26 -2
- package/dist/hooks/session-end.js +633 -313
- package/dist/hooks/session-start.js +26 -2
- package/dist/hooks/user-prompt.js +26 -2
- package/package.json +1 -1
|
@@ -292,12 +292,12 @@ function runRebuild(pkgDir, abiFrom, abiTo, start) {
|
|
|
292
292
|
}
|
|
293
293
|
const prebuildBin = resolveLocalBin(pkgDir, "prebuild-install");
|
|
294
294
|
if (prebuildBin) {
|
|
295
|
-
const
|
|
295
|
+
const r2 = spawnSync(process.execPath, [prebuildBin], {
|
|
296
296
|
cwd: pkgDir,
|
|
297
297
|
encoding: "utf-8",
|
|
298
298
|
timeout: 12e4
|
|
299
299
|
});
|
|
300
|
-
if (
|
|
300
|
+
if (r2.status === 0) {
|
|
301
301
|
const res2 = { healed: true, method: "prebuild-install", abiFrom, abiTo, durationMs: Date.now() - start };
|
|
302
302
|
recordHealEvent({ phase: "success", reason: "abi-mismatch", ...res2 });
|
|
303
303
|
return res2;
|
|
@@ -305,12 +305,12 @@ function runRebuild(pkgDir, abiFrom, abiTo, start) {
|
|
|
305
305
|
}
|
|
306
306
|
const gypBin = resolveNodeGypBin();
|
|
307
307
|
if (gypBin && hasCompiler()) {
|
|
308
|
-
const
|
|
308
|
+
const r2 = spawnSync(process.execPath, [gypBin, "rebuild", "--release"], {
|
|
309
309
|
cwd: pkgDir,
|
|
310
310
|
encoding: "utf-8",
|
|
311
311
|
timeout: 3e5
|
|
312
312
|
});
|
|
313
|
-
if (
|
|
313
|
+
if (r2.status === 0) {
|
|
314
314
|
const res2 = { healed: true, method: "node-gyp", abiFrom, abiTo, durationMs: Date.now() - start };
|
|
315
315
|
recordHealEvent({ phase: "success", reason: "abi-mismatch", ...res2 });
|
|
316
316
|
return res2;
|
|
@@ -389,8 +389,8 @@ function resolveNodeGypBin() {
|
|
|
389
389
|
function hasCompiler() {
|
|
390
390
|
for (const cc of ["cc", "clang", "gcc"]) {
|
|
391
391
|
try {
|
|
392
|
-
const
|
|
393
|
-
if (
|
|
392
|
+
const r2 = spawnSync(cc, ["--version"], { encoding: "utf-8", timeout: 5e3 });
|
|
393
|
+
if (r2.status === 0) return true;
|
|
394
394
|
} catch {
|
|
395
395
|
}
|
|
396
396
|
}
|
|
@@ -464,10 +464,10 @@ function openNodeSqlite(dbPath, opts) {
|
|
|
464
464
|
const st = raw.prepare(sql);
|
|
465
465
|
return {
|
|
466
466
|
run: (...p) => {
|
|
467
|
-
const
|
|
467
|
+
const r2 = st.run(...p);
|
|
468
468
|
return {
|
|
469
|
-
changes: typeof
|
|
470
|
-
lastInsertRowid:
|
|
469
|
+
changes: typeof r2.changes === "bigint" ? Number(r2.changes) : r2.changes,
|
|
470
|
+
lastInsertRowid: r2.lastInsertRowid
|
|
471
471
|
};
|
|
472
472
|
},
|
|
473
473
|
get: (...p) => plainRow(st.get(...p)),
|
|
@@ -951,7 +951,8 @@ var init_config = __esm({
|
|
|
951
951
|
input_per_million: z2.number(),
|
|
952
952
|
output_per_million: z2.number(),
|
|
953
953
|
cache_read_per_million: z2.number().optional(),
|
|
954
|
-
cache_write_per_million: z2.number().optional()
|
|
954
|
+
cache_write_per_million: z2.number().optional(),
|
|
955
|
+
cache_write_1h_per_million: z2.number().optional()
|
|
955
956
|
});
|
|
956
957
|
AnalyticsConfigSchema = z2.object({
|
|
957
958
|
quality: z2.object({
|
|
@@ -1557,11 +1558,11 @@ function claimPage(db, table, pk, orderBy, limit, token, nowMs) {
|
|
|
1557
1558
|
const placeholders = ids.map(() => "?").join(",");
|
|
1558
1559
|
db.prepare(
|
|
1559
1560
|
`UPDATE ${table} SET lease_token = ?, leased_at_ms = ? WHERE ${pk} IN (${placeholders})`
|
|
1560
|
-
).run(token, nowMs, ...ids.map((
|
|
1561
|
+
).run(token, nowMs, ...ids.map((r2) => r2.k));
|
|
1561
1562
|
}
|
|
1562
1563
|
function columns(db, table) {
|
|
1563
1564
|
const rows = db.prepare(`PRAGMA table_info(${table})`).all();
|
|
1564
|
-
return new Set(rows.map((
|
|
1565
|
+
return new Set(rows.map((r2) => r2.name));
|
|
1565
1566
|
}
|
|
1566
1567
|
function migrateRuleDelivery(db) {
|
|
1567
1568
|
const ADDITIONS = [
|
|
@@ -1590,29 +1591,29 @@ function leaseLearning(db, nowMs = Date.now()) {
|
|
|
1590
1591
|
content_hash, hardened, review_attestation_json
|
|
1591
1592
|
FROM team_promotion_outbound WHERE lease_token = ?
|
|
1592
1593
|
ORDER BY created_at ASC LIMIT ?`
|
|
1593
|
-
).all(token, PROMOTION_LEASE_LIMIT).map((
|
|
1594
|
-
prompt_hash:
|
|
1595
|
-
destination:
|
|
1596
|
-
draft_text:
|
|
1597
|
-
...
|
|
1598
|
-
signals: safeParseArray(
|
|
1599
|
-
content_hash:
|
|
1600
|
-
...
|
|
1601
|
-
...
|
|
1594
|
+
).all(token, PROMOTION_LEASE_LIMIT).map((r2) => ({
|
|
1595
|
+
prompt_hash: r2.prompt_hash,
|
|
1596
|
+
destination: r2.destination,
|
|
1597
|
+
draft_text: r2.draft_text,
|
|
1598
|
+
...r2.score !== null ? { score: r2.score } : {},
|
|
1599
|
+
signals: safeParseArray(r2.signals_json),
|
|
1600
|
+
content_hash: r2.content_hash,
|
|
1601
|
+
...r2.hardened ? { hardened: true } : {},
|
|
1602
|
+
...r2.review_attestation_json != null ? { review_attestation: safeParseUnknown(r2.review_attestation_json) } : {}
|
|
1602
1603
|
})) : [];
|
|
1603
1604
|
const revocations = tableExists(db, "team_revocation_outbound") ? db.prepare(
|
|
1604
1605
|
`SELECT prompt_hash FROM team_revocation_outbound
|
|
1605
1606
|
WHERE lease_token = ? ORDER BY created_at ASC LIMIT ?`
|
|
1606
|
-
).all(token, PROMOTION_LEASE_LIMIT).map((
|
|
1607
|
+
).all(token, PROMOTION_LEASE_LIMIT).map((r2) => r2.prompt_hash) : [];
|
|
1607
1608
|
const events = tableExists(db, TELEMETRY_OUTBOUND_TABLE) ? db.prepare(
|
|
1608
1609
|
`SELECT prompt_hash, event_type, metadata_json, created_at
|
|
1609
1610
|
FROM ${TELEMETRY_OUTBOUND_TABLE} WHERE lease_token = ?
|
|
1610
1611
|
ORDER BY id ASC LIMIT ?`
|
|
1611
|
-
).all(token, TELEMETRY_LEASE_LIMIT).map((
|
|
1612
|
-
prompt_hash:
|
|
1613
|
-
event_type:
|
|
1614
|
-
created_at:
|
|
1615
|
-
metadata: safeParseUnknown(
|
|
1612
|
+
).all(token, TELEMETRY_LEASE_LIMIT).map((r2) => ({
|
|
1613
|
+
prompt_hash: r2.prompt_hash,
|
|
1614
|
+
event_type: r2.event_type,
|
|
1615
|
+
created_at: r2.created_at,
|
|
1616
|
+
metadata: safeParseUnknown(r2.metadata_json) ?? {}
|
|
1616
1617
|
})) : [];
|
|
1617
1618
|
return { token, promotions, revocations, events };
|
|
1618
1619
|
}
|
|
@@ -1649,8 +1650,8 @@ function nackLearning(db, lease, error) {
|
|
|
1649
1650
|
function undeliveredSnapshot(db) {
|
|
1650
1651
|
const count = (t) => {
|
|
1651
1652
|
if (!tableExists(db, t)) return 0;
|
|
1652
|
-
const
|
|
1653
|
-
return
|
|
1653
|
+
const r2 = db.prepare(`SELECT COUNT(*) AS n FROM ${t}`).get();
|
|
1654
|
+
return r2.n;
|
|
1654
1655
|
};
|
|
1655
1656
|
const promotions = count("team_promotion_outbound");
|
|
1656
1657
|
const revocations = count("team_revocation_outbound");
|
|
@@ -1660,9 +1661,9 @@ function undeliveredSnapshot(db) {
|
|
|
1660
1661
|
for (const t of RULE_OUTBOUND_TABLES) {
|
|
1661
1662
|
if (!tableExists(db, t)) continue;
|
|
1662
1663
|
if (!columns(db, t).has("attempts")) continue;
|
|
1663
|
-
const
|
|
1664
|
-
if (
|
|
1665
|
-
if (
|
|
1664
|
+
const r2 = db.prepare(`SELECT MAX(attempts) AS a, MIN(created_at) AS c FROM ${t}`).get();
|
|
1665
|
+
if (r2.a !== null && r2.a > maxAttempts) maxAttempts = r2.a;
|
|
1666
|
+
if (r2.c !== null && (oldest === null || r2.c < oldest)) oldest = r2.c;
|
|
1666
1667
|
}
|
|
1667
1668
|
return {
|
|
1668
1669
|
promotions,
|
|
@@ -1988,11 +1989,11 @@ function getActiveEmbedModel() {
|
|
|
1988
1989
|
}
|
|
1989
1990
|
function loadEmbedSettings() {
|
|
1990
1991
|
try {
|
|
1991
|
-
const
|
|
1992
|
+
const r2 = getConfig().memory?.recall;
|
|
1992
1993
|
return {
|
|
1993
|
-
enabled:
|
|
1994
|
-
endpoint:
|
|
1995
|
-
model:
|
|
1994
|
+
enabled: r2?.embedEnabled ?? true,
|
|
1995
|
+
endpoint: r2?.embedEndpoint,
|
|
1996
|
+
model: r2?.embedModel
|
|
1996
1997
|
};
|
|
1997
1998
|
} catch {
|
|
1998
1999
|
return { enabled: true };
|
|
@@ -2250,11 +2251,11 @@ async function runEmbedSweep(db, cfg, opts = {}) {
|
|
|
2250
2251
|
const limit = opts.limit ?? Infinity;
|
|
2251
2252
|
const budgetMs = opts.budgetMs;
|
|
2252
2253
|
const start = Date.now();
|
|
2253
|
-
const
|
|
2254
|
+
const cursorKey2 = `embed_sweep_cursor_${cfg.sourceLabel}`;
|
|
2254
2255
|
let embedded = 0;
|
|
2255
2256
|
let scanned = 0;
|
|
2256
2257
|
let active = getActiveEmbedModel();
|
|
2257
|
-
let cursor = readCursor(db, cfg.metaTable,
|
|
2258
|
+
let cursor = readCursor(db, cfg.metaTable, cursorKey2);
|
|
2258
2259
|
try {
|
|
2259
2260
|
while (true) {
|
|
2260
2261
|
if (embedded >= limit) break;
|
|
@@ -2262,17 +2263,17 @@ async function runEmbedSweep(db, cfg, opts = {}) {
|
|
|
2262
2263
|
const remaining = limit === Infinity ? batchSize : Math.min(batchSize, limit - embedded);
|
|
2263
2264
|
const rows = cfg.selectMissing(db, cursor, active, Math.max(1, remaining));
|
|
2264
2265
|
if (rows.length === 0) {
|
|
2265
|
-
writeCursor(db, cfg.metaTable,
|
|
2266
|
+
writeCursor(db, cfg.metaTable, cursorKey2, 0);
|
|
2266
2267
|
break;
|
|
2267
2268
|
}
|
|
2268
2269
|
const units = [];
|
|
2269
|
-
for (const
|
|
2270
|
+
for (const r2 of rows) {
|
|
2270
2271
|
if (cfg.chunked) {
|
|
2271
|
-
const parts = chunkForEmbedding(
|
|
2272
|
+
const parts = chunkForEmbedding(r2.text);
|
|
2272
2273
|
if (parts.length === 0) continue;
|
|
2273
|
-
parts.forEach((text, chunkIx) => units.push({ id:
|
|
2274
|
+
parts.forEach((text, chunkIx) => units.push({ id: r2.id, chunkIx, text }));
|
|
2274
2275
|
} else {
|
|
2275
|
-
units.push({ id:
|
|
2276
|
+
units.push({ id: r2.id, chunkIx: 0, text: r2.text });
|
|
2276
2277
|
}
|
|
2277
2278
|
}
|
|
2278
2279
|
const vecs = await embedBatch(units.map((u) => u.text));
|
|
@@ -2316,7 +2317,7 @@ async function runEmbedSweep(db, cfg, opts = {}) {
|
|
|
2316
2317
|
tx();
|
|
2317
2318
|
scanned += rows.length;
|
|
2318
2319
|
cursor = rows[rows.length - 1].id;
|
|
2319
|
-
writeCursor(db, cfg.metaTable,
|
|
2320
|
+
writeCursor(db, cfg.metaTable, cursorKey2, cursor);
|
|
2320
2321
|
}
|
|
2321
2322
|
} catch {
|
|
2322
2323
|
}
|
|
@@ -2391,6 +2392,7 @@ __export(memory_db_exports, {
|
|
|
2391
2392
|
pruneToolCostEvents: () => pruneToolCostEvents,
|
|
2392
2393
|
recordRecallHits: () => recordRecallHits,
|
|
2393
2394
|
recordTelemetry: () => recordTelemetry,
|
|
2395
|
+
releaseMetaLease: () => releaseMetaLease,
|
|
2394
2396
|
removePendingSync: () => removePendingSync,
|
|
2395
2397
|
sanitizeFts5Query: () => sanitizeFts5Query,
|
|
2396
2398
|
sanitizeFts5QueryOr: () => sanitizeFts5QueryOr,
|
|
@@ -2399,6 +2401,7 @@ __export(memory_db_exports, {
|
|
|
2399
2401
|
searchObservations: () => searchObservations,
|
|
2400
2402
|
setLastProcessedLine: () => setLastProcessedLine,
|
|
2401
2403
|
setMemoryMeta: () => setMemoryMeta,
|
|
2404
|
+
tryAcquireMetaLease: () => tryAcquireMetaLease,
|
|
2402
2405
|
upsertObservationEmbedding: () => upsertObservationEmbedding,
|
|
2403
2406
|
usageWarmupElapsed: () => usageWarmupElapsed
|
|
2404
2407
|
});
|
|
@@ -2904,6 +2907,15 @@ function initMemorySchema(db) {
|
|
|
2904
2907
|
);
|
|
2905
2908
|
CREATE INDEX IF NOT EXISTS idx_sc_session ON session_costs(session_id);
|
|
2906
2909
|
`);
|
|
2910
|
+
const costCols = db.prepare(`PRAGMA table_info(session_costs)`).all();
|
|
2911
|
+
const addCostCol = (name, ddl) => {
|
|
2912
|
+
if (!costCols.some((c) => c.name === name)) db.exec(`ALTER TABLE session_costs ADD COLUMN ${ddl}`);
|
|
2913
|
+
};
|
|
2914
|
+
addCostCol("cost_status", `cost_status TEXT NOT NULL DEFAULT 'unknown'`);
|
|
2915
|
+
addCostCol("unpriced_models", `unpriced_models TEXT NOT NULL DEFAULT ''`);
|
|
2916
|
+
addCostCol("priced_responses", `priced_responses INTEGER NOT NULL DEFAULT 0`);
|
|
2917
|
+
addCostCol("unpriced_responses", `unpriced_responses INTEGER NOT NULL DEFAULT 0`);
|
|
2918
|
+
addCostCol("pricing_version", `pricing_version INTEGER NOT NULL DEFAULT 0`);
|
|
2907
2919
|
db.exec(`
|
|
2908
2920
|
CREATE TABLE IF NOT EXISTS feature_costs (
|
|
2909
2921
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -3333,6 +3345,18 @@ function getMemoryMeta(db, key) {
|
|
|
3333
3345
|
function setMemoryMeta(db, key, value) {
|
|
3334
3346
|
db.prepare("INSERT OR REPLACE INTO memory_meta (key, value) VALUES (?, ?)").run(key, value);
|
|
3335
3347
|
}
|
|
3348
|
+
function tryAcquireMetaLease(db, key, ttlSec, nowSec = Math.floor(Date.now() / 1e3)) {
|
|
3349
|
+
const r2 = db.prepare(
|
|
3350
|
+
`INSERT INTO memory_meta (key, value) VALUES (?, ?)
|
|
3351
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value
|
|
3352
|
+
WHERE memory_meta.value = ''
|
|
3353
|
+
OR CAST(substr(memory_meta.value, instr(memory_meta.value, ':') + 1) AS INTEGER) <= ?`
|
|
3354
|
+
).run(key, `${process.pid}:${nowSec + ttlSec}`, nowSec);
|
|
3355
|
+
return Number(r2.changes) === 1;
|
|
3356
|
+
}
|
|
3357
|
+
function releaseMetaLease(db, key) {
|
|
3358
|
+
db.prepare(`UPDATE memory_meta SET value = '' WHERE key = ? AND value LIKE ?`).run(key, `${process.pid}:%`);
|
|
3359
|
+
}
|
|
3336
3360
|
function upsertObservationEmbedding(db, observationId, vec, modelId, dim) {
|
|
3337
3361
|
db.prepare(
|
|
3338
3362
|
`INSERT OR REPLACE INTO observation_embeddings (observation_id, model_id, dim, vec, created_at)
|
|
@@ -3374,7 +3398,7 @@ async function embedMissingObservations(db, opts = {}) {
|
|
|
3374
3398
|
)
|
|
3375
3399
|
ORDER BY o.id LIMIT ?`
|
|
3376
3400
|
).all(cursor, batchSize);
|
|
3377
|
-
return rows.map((
|
|
3401
|
+
return rows.map((r2) => ({ id: r2.id, text: observationEmbedText(r2.title, r2.detail) }));
|
|
3378
3402
|
}
|
|
3379
3403
|
},
|
|
3380
3404
|
opts
|
|
@@ -3462,9 +3486,9 @@ function rescueLearningFromStalePayloads(db, stale) {
|
|
|
3462
3486
|
rescued++;
|
|
3463
3487
|
}
|
|
3464
3488
|
const revocations = Array.isArray(payload.rule_revocations) ? payload.rule_revocations : [];
|
|
3465
|
-
for (const
|
|
3466
|
-
if (typeof
|
|
3467
|
-
enqueueTeamRevocation(db,
|
|
3489
|
+
for (const r2 of revocations) {
|
|
3490
|
+
if (typeof r2?.prompt_hash === "string") {
|
|
3491
|
+
enqueueTeamRevocation(db, r2.prompt_hash);
|
|
3468
3492
|
rescued++;
|
|
3469
3493
|
}
|
|
3470
3494
|
}
|
|
@@ -4201,7 +4225,7 @@ var init_memory_db = __esm({
|
|
|
4201
4225
|
init_rule_candidate_store();
|
|
4202
4226
|
init_hook_failure_signal();
|
|
4203
4227
|
init_memory_embed_sweep();
|
|
4204
|
-
MEMORY_SCHEMA_VERSION =
|
|
4228
|
+
MEMORY_SCHEMA_VERSION = 2;
|
|
4205
4229
|
TOOL_COST_EVENTS_RETENTION_DAYS = 90;
|
|
4206
4230
|
MAX_DRAFT_TEXT_LEN = 16384;
|
|
4207
4231
|
FUNNEL_EVENT_OUTBOX_CAP = 2e4;
|
|
@@ -4446,6 +4470,11 @@ function parseEntry(raw) {
|
|
|
4446
4470
|
role: msgRaw.role ?? entryType,
|
|
4447
4471
|
content: normalizeContent(msgRaw.content)
|
|
4448
4472
|
};
|
|
4473
|
+
if (typeof msgRaw.id === "string") base.message.id = msgRaw.id;
|
|
4474
|
+
if (typeof msgRaw.model === "string") base.message.model = msgRaw.model;
|
|
4475
|
+
if (msgRaw.usage && typeof msgRaw.usage === "object") {
|
|
4476
|
+
base.message.usage = msgRaw.usage;
|
|
4477
|
+
}
|
|
4449
4478
|
}
|
|
4450
4479
|
}
|
|
4451
4480
|
if (entryType === "progress") {
|
|
@@ -4468,6 +4497,7 @@ function normalizeContent(content) {
|
|
|
4468
4497
|
async function parseTranscriptFrom(filePath, startLine) {
|
|
4469
4498
|
const entries = [];
|
|
4470
4499
|
let lineNumber = 0;
|
|
4500
|
+
let lastUnparseableLine = 0;
|
|
4471
4501
|
const rl = createInterface({
|
|
4472
4502
|
input: createReadStream(filePath, { encoding: "utf-8" }),
|
|
4473
4503
|
crlfDelay: Infinity
|
|
@@ -4484,10 +4514,12 @@ async function parseTranscriptFrom(filePath, startLine) {
|
|
|
4484
4514
|
entries.push(entry);
|
|
4485
4515
|
}
|
|
4486
4516
|
} catch (_e) {
|
|
4517
|
+
lastUnparseableLine = lineNumber;
|
|
4487
4518
|
continue;
|
|
4488
4519
|
}
|
|
4489
4520
|
}
|
|
4490
|
-
|
|
4521
|
+
const totalLines = lastUnparseableLine === lineNumber && lineNumber > startLine ? lineNumber - 1 : lineNumber;
|
|
4522
|
+
return { entries, totalLines };
|
|
4491
4523
|
}
|
|
4492
4524
|
function estimateTokens(text) {
|
|
4493
4525
|
return Math.ceil(text.length / 4);
|
|
@@ -4533,7 +4565,8 @@ var RETRY_DELAYS = [1e3, 2e3, 4e3];
|
|
|
4533
4565
|
var DEFAULT_CLOUD_REQUEST_TIMEOUT_MS = 15e3;
|
|
4534
4566
|
var SYNC_DEADLINE_MS = 2e4;
|
|
4535
4567
|
var MIN_ATTEMPT_BUDGET_MS = 1e3;
|
|
4536
|
-
|
|
4568
|
+
var MIN_SYNC_ATTEMPT_MS = 3e3;
|
|
4569
|
+
async function syncToCloud(db, payload, opts = {}) {
|
|
4537
4570
|
const config = getConfig();
|
|
4538
4571
|
const cloud = config.cloud;
|
|
4539
4572
|
if (!cloud?.enabled) {
|
|
@@ -4616,11 +4649,11 @@ async function syncToCloud(db, payload) {
|
|
|
4616
4649
|
}
|
|
4617
4650
|
let lastError = "";
|
|
4618
4651
|
const configuredTimeoutMs = cloud.requestTimeoutMs ?? DEFAULT_CLOUD_REQUEST_TIMEOUT_MS;
|
|
4619
|
-
const deadlineAt = Date.now() + SYNC_DEADLINE_MS;
|
|
4652
|
+
const deadlineAt = Math.min(Date.now() + SYNC_DEADLINE_MS, opts.deadlineAt ?? Infinity);
|
|
4620
4653
|
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
4621
4654
|
const remainingMs = deadlineAt - Date.now();
|
|
4622
4655
|
if (remainingMs < MIN_ATTEMPT_BUDGET_MS) {
|
|
4623
|
-
lastError = lastError ||
|
|
4656
|
+
lastError = lastError || "sync deadline exceeded";
|
|
4624
4657
|
break;
|
|
4625
4658
|
}
|
|
4626
4659
|
const requestTimeoutMs = Math.min(configuredTimeoutMs, remainingMs);
|
|
@@ -4673,13 +4706,15 @@ async function syncToCloud(db, payload) {
|
|
|
4673
4706
|
}
|
|
4674
4707
|
}
|
|
4675
4708
|
}
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4709
|
+
if (opts.enqueueOnFailure !== false) {
|
|
4710
|
+
try {
|
|
4711
|
+
enqueueSyncPayload(db, JSON.stringify(stripLearningFromPayload(payload)));
|
|
4712
|
+
} catch (err) {
|
|
4713
|
+
process.stderr.write(
|
|
4714
|
+
`[massu] WARNING: could not queue sync payload for retry: ${err instanceof Error ? err.message : String(err)}
|
|
4681
4715
|
`
|
|
4682
|
-
|
|
4716
|
+
);
|
|
4717
|
+
}
|
|
4683
4718
|
}
|
|
4684
4719
|
return {
|
|
4685
4720
|
success: false,
|
|
@@ -4687,14 +4722,22 @@ async function syncToCloud(db, payload) {
|
|
|
4687
4722
|
error: lastError
|
|
4688
4723
|
};
|
|
4689
4724
|
}
|
|
4690
|
-
async function drainSyncQueue(db) {
|
|
4725
|
+
async function drainSyncQueue(db, deadlineAt) {
|
|
4726
|
+
const out = { attempted: 0, deferred: 0 };
|
|
4691
4727
|
const config = getConfig();
|
|
4692
|
-
if (!config.cloud?.enabled || !config.cloud?.apiKey) return;
|
|
4728
|
+
if (!config.cloud?.enabled || !config.cloud?.apiKey) return out;
|
|
4693
4729
|
const pending = dequeuePendingSync(db, 10);
|
|
4694
|
-
for (
|
|
4730
|
+
for (let i = 0; i < pending.length; i++) {
|
|
4731
|
+
const item = pending[i];
|
|
4732
|
+
const remaining = deadlineAt === void 0 ? Infinity : deadlineAt - Date.now();
|
|
4733
|
+
if (remaining < MIN_SYNC_ATTEMPT_MS) {
|
|
4734
|
+
out.deferred = pending.length - i;
|
|
4735
|
+
break;
|
|
4736
|
+
}
|
|
4737
|
+
out.attempted++;
|
|
4695
4738
|
try {
|
|
4696
4739
|
const payload = JSON.parse(item.payload);
|
|
4697
|
-
const result = await syncToCloud(db, payload);
|
|
4740
|
+
const result = await syncToCloud(db, payload, { deadlineAt, enqueueOnFailure: false });
|
|
4698
4741
|
if (result.success) {
|
|
4699
4742
|
removePendingSync(db, item.id);
|
|
4700
4743
|
} else {
|
|
@@ -4704,6 +4747,7 @@ async function drainSyncQueue(db) {
|
|
|
4704
4747
|
incrementRetryCount(db, item.id, err instanceof Error ? err.message : String(err));
|
|
4705
4748
|
}
|
|
4706
4749
|
}
|
|
4750
|
+
return out;
|
|
4707
4751
|
}
|
|
4708
4752
|
function sleep(ms) {
|
|
4709
4753
|
return new Promise((resolve8) => setTimeout(resolve8, ms));
|
|
@@ -5328,8 +5372,8 @@ function countUntrusted(envelope) {
|
|
|
5328
5372
|
}
|
|
5329
5373
|
function isValidWirePromotion(p) {
|
|
5330
5374
|
if (!p || typeof p !== "object") return false;
|
|
5331
|
-
const
|
|
5332
|
-
return typeof
|
|
5375
|
+
const r2 = p;
|
|
5376
|
+
return typeof r2.prompt_hash === "string" && PROMPT_HASH_RE.test(r2.prompt_hash) && typeof r2.destination === "string" && typeof r2.draft_text === "string" && typeof r2.promoted_by === "string" && typeof r2.promoted_at === "string";
|
|
5333
5377
|
}
|
|
5334
5378
|
function sidecarPath(projectRoot, promptHash) {
|
|
5335
5379
|
return join6(projectRoot, ".massu", "rule-candidates", `${promptHash}.json`);
|
|
@@ -5515,12 +5559,12 @@ function readReposRegistry(home = homedir7()) {
|
|
|
5515
5559
|
try {
|
|
5516
5560
|
const parsed = JSON.parse(readFileSync9(p, "utf-8"));
|
|
5517
5561
|
if (!parsed || typeof parsed !== "object") return emptyRegistry();
|
|
5518
|
-
const
|
|
5519
|
-
if (!Array.isArray(
|
|
5520
|
-
const repos =
|
|
5562
|
+
const r2 = parsed;
|
|
5563
|
+
if (!Array.isArray(r2.repos)) return emptyRegistry();
|
|
5564
|
+
const repos = r2.repos.filter(
|
|
5521
5565
|
(e) => !!e && typeof e === "object" && typeof e.repo_id === "string" && typeof e.label === "string"
|
|
5522
5566
|
);
|
|
5523
|
-
return { version: typeof
|
|
5567
|
+
return { version: typeof r2.version === "number" ? r2.version : REPOS_REGISTRY_VERSION, repos };
|
|
5524
5568
|
} catch {
|
|
5525
5569
|
return emptyRegistry();
|
|
5526
5570
|
}
|
|
@@ -5706,8 +5750,8 @@ async function importSharedMemories(db, transport, opts = {}) {
|
|
|
5706
5750
|
const originRepoId = entry.repo_id;
|
|
5707
5751
|
if (ownRepoId && originRepoId === ownRepoId) continue;
|
|
5708
5752
|
const pinned = getSharedPin(db, originRepoId) ?? tofuPinSharedFingerprint(db, originRepoId, entry.pubkey_fingerprint);
|
|
5709
|
-
const
|
|
5710
|
-
const since = parseCursor2(getMemoryMeta(db,
|
|
5753
|
+
const cursorKey2 = SHARED_CURSOR_PREFIX + originRepoId;
|
|
5754
|
+
const since = parseCursor2(getMemoryMeta(db, cursorKey2));
|
|
5711
5755
|
let envelopes;
|
|
5712
5756
|
try {
|
|
5713
5757
|
envelopes = await transport.fetchSince(originRepoId, since);
|
|
@@ -5772,7 +5816,7 @@ async function importSharedMemories(db, transport, opts = {}) {
|
|
|
5772
5816
|
});
|
|
5773
5817
|
}
|
|
5774
5818
|
}
|
|
5775
|
-
if (maxSeq > since) setMemoryMeta(db,
|
|
5819
|
+
if (maxSeq > since) setMemoryMeta(db, cursorKey2, String(maxSeq));
|
|
5776
5820
|
}
|
|
5777
5821
|
return result;
|
|
5778
5822
|
}
|
|
@@ -5802,19 +5846,19 @@ function parseRecords(json) {
|
|
|
5802
5846
|
return [];
|
|
5803
5847
|
}
|
|
5804
5848
|
}
|
|
5805
|
-
function isWellFormedRecord(
|
|
5806
|
-
if (!
|
|
5807
|
-
const o =
|
|
5849
|
+
function isWellFormedRecord(r2) {
|
|
5850
|
+
if (!r2 || typeof r2 !== "object") return false;
|
|
5851
|
+
const o = r2;
|
|
5808
5852
|
return typeof o.record_hash === "string" && typeof o.type === "string" && typeof o.title === "string" && typeof o.detail === "string" && typeof o.importance === "number" && typeof o.created_at_epoch === "number" && (o.superseded_by_hash === null || typeof o.superseded_by_hash === "string");
|
|
5809
5853
|
}
|
|
5810
|
-
function stripHash(
|
|
5854
|
+
function stripHash(r2) {
|
|
5811
5855
|
return {
|
|
5812
|
-
type:
|
|
5813
|
-
title:
|
|
5814
|
-
detail:
|
|
5815
|
-
importance:
|
|
5816
|
-
created_at_epoch:
|
|
5817
|
-
superseded_by_hash:
|
|
5856
|
+
type: r2.type,
|
|
5857
|
+
title: r2.title,
|
|
5858
|
+
detail: r2.detail,
|
|
5859
|
+
importance: r2.importance,
|
|
5860
|
+
created_at_epoch: r2.created_at_epoch,
|
|
5861
|
+
superseded_by_hash: r2.superseded_by_hash
|
|
5818
5862
|
};
|
|
5819
5863
|
}
|
|
5820
5864
|
function pendingExists(db, recordHash) {
|
|
@@ -6099,45 +6143,45 @@ async function exportSharedMemories(db, transport, opts = {}) {
|
|
|
6099
6143
|
const refusals = [];
|
|
6100
6144
|
const records = [];
|
|
6101
6145
|
const outboundInserts = [];
|
|
6102
|
-
for (const
|
|
6103
|
-
if (!isLocalOrigin(
|
|
6104
|
-
refusals.push({ observation_id:
|
|
6105
|
-
auditExportRefused(db,
|
|
6146
|
+
for (const r2 of rows) {
|
|
6147
|
+
if (!isLocalOrigin(r2.origin)) {
|
|
6148
|
+
refusals.push({ observation_id: r2.id, reason: "non_local_origin" });
|
|
6149
|
+
auditExportRefused(db, r2.id, "non_local_origin");
|
|
6106
6150
|
continue;
|
|
6107
6151
|
}
|
|
6108
|
-
const title =
|
|
6109
|
-
const detail =
|
|
6152
|
+
const title = r2.title ?? "";
|
|
6153
|
+
const detail = r2.detail ?? "";
|
|
6110
6154
|
const sTitle = containsSecret(title);
|
|
6111
6155
|
const sDetail = containsSecret(detail);
|
|
6112
6156
|
if (sTitle.matched || sDetail.matched) {
|
|
6113
6157
|
const reason = `secret:${sTitle.patternName ?? sDetail.patternName}`;
|
|
6114
|
-
refusals.push({ observation_id:
|
|
6115
|
-
auditExportRefused(db,
|
|
6158
|
+
refusals.push({ observation_id: r2.id, reason });
|
|
6159
|
+
auditExportRefused(db, r2.id, reason);
|
|
6116
6160
|
continue;
|
|
6117
6161
|
}
|
|
6118
6162
|
if (home && (title.includes(home) || detail.includes(home))) {
|
|
6119
|
-
refusals.push({ observation_id:
|
|
6120
|
-
auditExportRefused(db,
|
|
6163
|
+
refusals.push({ observation_id: r2.id, reason: "home_path" });
|
|
6164
|
+
auditExportRefused(db, r2.id, "home_path");
|
|
6121
6165
|
continue;
|
|
6122
6166
|
}
|
|
6123
6167
|
const base = {
|
|
6124
|
-
type:
|
|
6168
|
+
type: r2.type,
|
|
6125
6169
|
title,
|
|
6126
6170
|
detail,
|
|
6127
|
-
importance:
|
|
6128
|
-
created_at_epoch:
|
|
6171
|
+
importance: r2.importance,
|
|
6172
|
+
created_at_epoch: r2.created_at_epoch,
|
|
6129
6173
|
superseded_by_hash: null
|
|
6130
6174
|
};
|
|
6131
6175
|
const record_hash = hashSharedMemoryRecord(base);
|
|
6132
6176
|
if (wasExported.get(record_hash)) continue;
|
|
6133
6177
|
const recordBytes = Buffer.byteLength(JSON.stringify({ ...base, record_hash }), "utf-8");
|
|
6134
6178
|
if (recordBytes > caps.maxBytesPerRecord) {
|
|
6135
|
-
refusals.push({ observation_id:
|
|
6136
|
-
auditExportRefused(db,
|
|
6179
|
+
refusals.push({ observation_id: r2.id, reason: "record_too_large" });
|
|
6180
|
+
auditExportRefused(db, r2.id, "record_too_large");
|
|
6137
6181
|
continue;
|
|
6138
6182
|
}
|
|
6139
6183
|
records.push({ ...base, record_hash });
|
|
6140
|
-
outboundInserts.push({ hash: record_hash, id:
|
|
6184
|
+
outboundInserts.push({ hash: record_hash, id: r2.id });
|
|
6141
6185
|
}
|
|
6142
6186
|
const revokeRows = db.prepare(
|
|
6143
6187
|
`SELECT o.record_hash AS h
|
|
@@ -6147,7 +6191,7 @@ async function exportSharedMemories(db, transport, opts = {}) {
|
|
|
6147
6191
|
AND obs.expired_at IS NOT NULL
|
|
6148
6192
|
LIMIT ?`
|
|
6149
6193
|
).all(caps.maxRecords);
|
|
6150
|
-
const revokedHashes = revokeRows.map((
|
|
6194
|
+
const revokedHashes = revokeRows.map((r2) => r2.h);
|
|
6151
6195
|
if (records.length === 0 && revokedHashes.length === 0) {
|
|
6152
6196
|
return { ...zero(true), refused: refusals.length, refusals };
|
|
6153
6197
|
}
|
|
@@ -6354,76 +6398,271 @@ function backfillQualityScores(db) {
|
|
|
6354
6398
|
return backfilled;
|
|
6355
6399
|
}
|
|
6356
6400
|
|
|
6401
|
+
// src/cost-reprice.ts
|
|
6402
|
+
init_config();
|
|
6403
|
+
init_memory_db();
|
|
6404
|
+
import { existsSync as existsSync16 } from "fs";
|
|
6405
|
+
import { dirname as dirname13, join as join11 } from "path";
|
|
6406
|
+
|
|
6357
6407
|
// src/cost-tracker.ts
|
|
6358
6408
|
init_config();
|
|
6359
|
-
|
|
6360
|
-
|
|
6361
|
-
|
|
6362
|
-
|
|
6363
|
-
|
|
6364
|
-
|
|
6409
|
+
|
|
6410
|
+
// src/model-pricing.ts
|
|
6411
|
+
var PRICING_VERSION = 1;
|
|
6412
|
+
var r = (input, cacheWrite5m, cacheWrite1h, cacheRead, output) => ({ input, cacheWrite5m, cacheWrite1h, cacheRead, output });
|
|
6413
|
+
var US = 1.1;
|
|
6414
|
+
var BUILT_IN_PRICES = {
|
|
6415
|
+
"claude-fable-5-1": { ...r(10, 12.5, 20, 0.25, 50), usGeoMultiplier: US },
|
|
6416
|
+
"claude-mythos-5-1": { ...r(10, 12.5, 20, 0.25, 50), usGeoMultiplier: US },
|
|
6417
|
+
"claude-fable-5": { ...r(10, 12.5, 20, 1, 50), usGeoMultiplier: US },
|
|
6418
|
+
"claude-mythos-5": { ...r(10, 12.5, 20, 1, 50), usGeoMultiplier: US },
|
|
6419
|
+
"claude-opus-5-5": { ...r(4, 5, 8, 0.2, 20), fast: { input: 8, output: 40 }, usGeoMultiplier: US },
|
|
6420
|
+
"claude-opus-5": { ...r(5, 6.25, 10, 0.5, 25), fast: { input: 10, output: 50 }, usGeoMultiplier: US },
|
|
6421
|
+
"claude-opus-4-8": { ...r(5, 6.25, 10, 0.5, 25), fast: { input: 10, output: 50 }, usGeoMultiplier: US },
|
|
6422
|
+
"claude-opus-4-7": { ...r(5, 6.25, 10, 0.5, 25), usGeoMultiplier: US },
|
|
6423
|
+
"claude-opus-4-6": { ...r(5, 6.25, 10, 0.5, 25), usGeoMultiplier: US },
|
|
6424
|
+
"claude-opus-4-5": r(5, 6.25, 10, 0.5, 25),
|
|
6425
|
+
"claude-opus-4-1": r(15, 18.75, 30, 1.5, 75),
|
|
6426
|
+
"claude-opus-4": r(15, 18.75, 30, 1.5, 75),
|
|
6427
|
+
"claude-sonnet-5-5": { ...r(2, 2.5, 4, 0.1, 10), usGeoMultiplier: US },
|
|
6428
|
+
"claude-sonnet-5": { ...r(2, 2.5, 4, 0.2, 10), usGeoMultiplier: US },
|
|
6429
|
+
"claude-sonnet-4-6": { ...r(3, 3.75, 6, 0.3, 15), usGeoMultiplier: US },
|
|
6430
|
+
"claude-sonnet-4-5": r(3, 3.75, 6, 0.3, 15),
|
|
6431
|
+
"claude-sonnet-4": r(3, 3.75, 6, 0.3, 15),
|
|
6432
|
+
"claude-haiku-5-5": {
|
|
6433
|
+
...r(0.1, 0.125, 0.2, 0.01, 0.5),
|
|
6434
|
+
longPrompt: { overTokens: 1e5, rates: r(0.5, 0.625, 1, 0.05, 2.5) },
|
|
6435
|
+
usGeoMultiplier: US
|
|
6436
|
+
},
|
|
6437
|
+
"claude-haiku-4-5": r(1, 1.25, 2, 0.1, 5)
|
|
6365
6438
|
};
|
|
6366
|
-
function
|
|
6367
|
-
return
|
|
6368
|
-
}
|
|
6369
|
-
function
|
|
6370
|
-
|
|
6371
|
-
|
|
6372
|
-
|
|
6373
|
-
|
|
6374
|
-
|
|
6375
|
-
|
|
6376
|
-
|
|
6377
|
-
|
|
6378
|
-
|
|
6379
|
-
|
|
6380
|
-
|
|
6381
|
-
|
|
6382
|
-
|
|
6383
|
-
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
6390
|
-
|
|
6391
|
-
|
|
6392
|
-
}
|
|
6393
|
-
function calculateCost(usage) {
|
|
6394
|
-
const pricing = getModelPricing();
|
|
6395
|
-
const modelPricing = pricing[usage.model] ?? pricing["default"] ?? pricing["claude-sonnet-4-5"] ?? { input_per_million: 3, output_per_million: 15 };
|
|
6396
|
-
const inputCost = usage.inputTokens / 1e6 * modelPricing.input_per_million;
|
|
6397
|
-
const outputCost = usage.outputTokens / 1e6 * modelPricing.output_per_million;
|
|
6398
|
-
const cacheReadCost = usage.cacheReadTokens / 1e6 * (modelPricing.cache_read_per_million ?? 0);
|
|
6399
|
-
const cacheWriteCost = usage.cacheWriteTokens / 1e6 * (modelPricing.cache_write_per_million ?? 0);
|
|
6439
|
+
function normalizeModelId(model) {
|
|
6440
|
+
return model.trim().replace(/-\d{8}$/, "");
|
|
6441
|
+
}
|
|
6442
|
+
function resolveModelPrice(model, overrides = {}) {
|
|
6443
|
+
if (!model) return null;
|
|
6444
|
+
const id = normalizeModelId(model);
|
|
6445
|
+
const builtIn = BUILT_IN_PRICES[id];
|
|
6446
|
+
const o = overrides[model] ?? overrides[id];
|
|
6447
|
+
if (!o) return builtIn ?? null;
|
|
6448
|
+
const input = o.input_per_million;
|
|
6449
|
+
return {
|
|
6450
|
+
...builtIn,
|
|
6451
|
+
input,
|
|
6452
|
+
output: o.output_per_million,
|
|
6453
|
+
cacheRead: o.cache_read_per_million ?? builtIn?.cacheRead ?? input * 0.1,
|
|
6454
|
+
cacheWrite5m: o.cache_write_per_million ?? builtIn?.cacheWrite5m ?? input * 1.25,
|
|
6455
|
+
cacheWrite1h: o.cache_write_1h_per_million ?? builtIn?.cacheWrite1h ?? input * 2
|
|
6456
|
+
};
|
|
6457
|
+
}
|
|
6458
|
+
function tokenCounts(u) {
|
|
6459
|
+
const n = (v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : 0;
|
|
6460
|
+
const total = n(u.cache_creation_input_tokens);
|
|
6461
|
+
const has5m = typeof u.cache_creation?.ephemeral_5m_input_tokens === "number";
|
|
6462
|
+
const has1h = typeof u.cache_creation?.ephemeral_1h_input_tokens === "number";
|
|
6463
|
+
const w1h = has1h ? n(u.cache_creation?.ephemeral_1h_input_tokens) : 0;
|
|
6464
|
+
const w5m = has5m ? n(u.cache_creation?.ephemeral_5m_input_tokens) : total - w1h;
|
|
6400
6465
|
return {
|
|
6401
|
-
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
|
|
6406
|
-
currency: getCurrency()
|
|
6466
|
+
input: n(u.input_tokens),
|
|
6467
|
+
output: n(u.output_tokens),
|
|
6468
|
+
cacheRead: n(u.cache_read_input_tokens),
|
|
6469
|
+
cacheWrite5m: Math.max(0, w5m),
|
|
6470
|
+
cacheWrite1h: w1h
|
|
6407
6471
|
};
|
|
6408
6472
|
}
|
|
6409
|
-
function
|
|
6410
|
-
|
|
6473
|
+
function priceResponse(price, u) {
|
|
6474
|
+
if (!price) return null;
|
|
6475
|
+
const t = tokenCounts(u);
|
|
6476
|
+
const prompt = t.input + t.cacheRead + t.cacheWrite5m + t.cacheWrite1h;
|
|
6477
|
+
let rates = price.longPrompt && prompt > price.longPrompt.overTokens ? price.longPrompt.rates : price;
|
|
6478
|
+
if (u.speed === "fast" && price.fast) {
|
|
6479
|
+
const k = price.fast.input / rates.input;
|
|
6480
|
+
rates = {
|
|
6481
|
+
input: price.fast.input,
|
|
6482
|
+
output: price.fast.output,
|
|
6483
|
+
cacheRead: rates.cacheRead * k,
|
|
6484
|
+
cacheWrite5m: rates.cacheWrite5m * k,
|
|
6485
|
+
cacheWrite1h: rates.cacheWrite1h * k
|
|
6486
|
+
};
|
|
6487
|
+
}
|
|
6488
|
+
const geo = u.inference_geo === "us" ? price.usGeoMultiplier ?? 1 : 1;
|
|
6489
|
+
const dollars = t.input * rates.input + t.output * rates.output + t.cacheRead * rates.cacheRead + t.cacheWrite5m * rates.cacheWrite5m + t.cacheWrite1h * rates.cacheWrite1h;
|
|
6490
|
+
return dollars / 1e6 * geo;
|
|
6491
|
+
}
|
|
6492
|
+
|
|
6493
|
+
// src/cost-tracker.ts
|
|
6494
|
+
function configPriceOverrides() {
|
|
6495
|
+
return getConfig().analytics?.cost?.models ?? {};
|
|
6496
|
+
}
|
|
6497
|
+
function costTranscriptEntries(entries, opts = {}) {
|
|
6498
|
+
const overrides = configPriceOverrides();
|
|
6499
|
+
const out = {
|
|
6500
|
+
inputTokens: 0,
|
|
6501
|
+
outputTokens: 0,
|
|
6502
|
+
cacheReadTokens: 0,
|
|
6503
|
+
cacheWriteTokens: 0,
|
|
6504
|
+
costUsd: 0,
|
|
6505
|
+
pricedResponses: 0,
|
|
6506
|
+
unpricedResponses: 0,
|
|
6507
|
+
unpricedModels: [],
|
|
6508
|
+
model: "",
|
|
6509
|
+
lastMessageId: null
|
|
6510
|
+
};
|
|
6511
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6512
|
+
if (opts.skipMessageId) seen.add(opts.skipMessageId);
|
|
6513
|
+
const unpriced = /* @__PURE__ */ new Set();
|
|
6514
|
+
for (const entry of entries) {
|
|
6515
|
+
const msg = entry.message;
|
|
6516
|
+
if (entry.type !== "assistant" || !msg?.usage) continue;
|
|
6517
|
+
if (msg.id) {
|
|
6518
|
+
if (seen.has(msg.id)) continue;
|
|
6519
|
+
seen.add(msg.id);
|
|
6520
|
+
out.lastMessageId = msg.id;
|
|
6521
|
+
}
|
|
6522
|
+
const usage = msg.usage;
|
|
6523
|
+
const t = tokenCounts(usage);
|
|
6524
|
+
if (t.input + t.output + t.cacheRead + t.cacheWrite5m + t.cacheWrite1h === 0) continue;
|
|
6525
|
+
out.inputTokens += t.input;
|
|
6526
|
+
out.outputTokens += t.output;
|
|
6527
|
+
out.cacheReadTokens += t.cacheRead;
|
|
6528
|
+
out.cacheWriteTokens += t.cacheWrite5m + t.cacheWrite1h;
|
|
6529
|
+
const dollars = priceResponse(resolveModelPrice(msg.model, overrides), usage);
|
|
6530
|
+
if (dollars === null) {
|
|
6531
|
+
out.unpricedResponses++;
|
|
6532
|
+
unpriced.add(msg.model ?? "unknown");
|
|
6533
|
+
} else {
|
|
6534
|
+
out.costUsd += dollars;
|
|
6535
|
+
out.pricedResponses++;
|
|
6536
|
+
}
|
|
6537
|
+
if (msg.model) out.model = msg.model;
|
|
6538
|
+
}
|
|
6539
|
+
out.unpricedModels = [...unpriced].sort();
|
|
6540
|
+
return out;
|
|
6541
|
+
}
|
|
6542
|
+
function costStatusOf(priced, unpriced) {
|
|
6543
|
+
if (unpriced === 0) return "priced";
|
|
6544
|
+
return priced === 0 ? "unknown" : "partial";
|
|
6545
|
+
}
|
|
6546
|
+
function storeSessionCost(db, sessionId, slice, mode = "replace") {
|
|
6547
|
+
const prev = mode === "add" ? db.prepare(
|
|
6548
|
+
`SELECT input_tokens, output_tokens, cache_read_tokens, cache_write_tokens, estimated_cost_usd,
|
|
6549
|
+
priced_responses, unpriced_responses, unpriced_models, model
|
|
6550
|
+
FROM session_costs WHERE session_id = ?`
|
|
6551
|
+
).get(sessionId) : void 0;
|
|
6552
|
+
const num = (k) => Number(prev?.[k] ?? 0) || 0;
|
|
6553
|
+
const input = num("input_tokens") + slice.inputTokens;
|
|
6554
|
+
const output = num("output_tokens") + slice.outputTokens;
|
|
6555
|
+
const cacheRead = num("cache_read_tokens") + slice.cacheReadTokens;
|
|
6556
|
+
const cacheWrite = num("cache_write_tokens") + slice.cacheWriteTokens;
|
|
6557
|
+
const priced = num("priced_responses") + slice.pricedResponses;
|
|
6558
|
+
const unpricedCount = num("unpriced_responses") + slice.unpricedResponses;
|
|
6559
|
+
const unpricedModels = [.../* @__PURE__ */ new Set([
|
|
6560
|
+
...String(prev?.unpriced_models ?? "").split(",").filter(Boolean),
|
|
6561
|
+
...slice.unpricedModels
|
|
6562
|
+
])].sort().join(",");
|
|
6563
|
+
const model = slice.model || String(prev?.model ?? "") || null;
|
|
6411
6564
|
db.prepare(`
|
|
6412
6565
|
INSERT INTO session_costs
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
|
|
6566
|
+
(session_id, model, input_tokens, output_tokens, cache_read_tokens, cache_write_tokens,
|
|
6567
|
+
total_tokens, estimated_cost_usd, priced_responses, unpriced_responses, unpriced_models,
|
|
6568
|
+
cost_status, pricing_version)
|
|
6569
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
6570
|
+
ON CONFLICT(session_id) DO UPDATE SET
|
|
6571
|
+
model = excluded.model, input_tokens = excluded.input_tokens,
|
|
6572
|
+
output_tokens = excluded.output_tokens, cache_read_tokens = excluded.cache_read_tokens,
|
|
6573
|
+
cache_write_tokens = excluded.cache_write_tokens, total_tokens = excluded.total_tokens,
|
|
6574
|
+
estimated_cost_usd = excluded.estimated_cost_usd,
|
|
6575
|
+
priced_responses = excluded.priced_responses, unpriced_responses = excluded.unpriced_responses,
|
|
6576
|
+
unpriced_models = excluded.unpriced_models, cost_status = excluded.cost_status,
|
|
6577
|
+
pricing_version = excluded.pricing_version
|
|
6416
6578
|
`).run(
|
|
6417
6579
|
sessionId,
|
|
6418
|
-
|
|
6419
|
-
|
|
6420
|
-
|
|
6421
|
-
|
|
6422
|
-
|
|
6423
|
-
|
|
6424
|
-
|
|
6580
|
+
model,
|
|
6581
|
+
input,
|
|
6582
|
+
output,
|
|
6583
|
+
cacheRead,
|
|
6584
|
+
cacheWrite,
|
|
6585
|
+
input + output + cacheRead + cacheWrite,
|
|
6586
|
+
num("estimated_cost_usd") + slice.costUsd,
|
|
6587
|
+
priced,
|
|
6588
|
+
unpricedCount,
|
|
6589
|
+
unpricedModels,
|
|
6590
|
+
costStatusOf(priced, unpricedCount),
|
|
6591
|
+
PRICING_VERSION
|
|
6425
6592
|
);
|
|
6426
6593
|
}
|
|
6594
|
+
function markSessionCostUnknown(db, sessionId) {
|
|
6595
|
+
db.prepare(`
|
|
6596
|
+
INSERT INTO session_costs (session_id, cost_status, pricing_version) VALUES (?, 'unknown', ?)
|
|
6597
|
+
ON CONFLICT(session_id) DO UPDATE SET
|
|
6598
|
+
input_tokens = 0, output_tokens = 0, cache_read_tokens = 0, cache_write_tokens = 0,
|
|
6599
|
+
total_tokens = 0, estimated_cost_usd = 0, priced_responses = 0, unpriced_responses = 0,
|
|
6600
|
+
unpriced_models = '', cost_status = 'unknown', pricing_version = excluded.pricing_version
|
|
6601
|
+
`).run(sessionId, PRICING_VERSION);
|
|
6602
|
+
}
|
|
6603
|
+
|
|
6604
|
+
// src/cost-reprice.ts
|
|
6605
|
+
var cursorKey = (sid) => `cost_processed_line:${sid}`;
|
|
6606
|
+
var lastIdKey = (sid) => `cost_last_message:${sid}`;
|
|
6607
|
+
var sessionLeaseKey = (sid) => `session_end_lease:${sid}`;
|
|
6608
|
+
async function updateSessionCost(db, sessionId, transcriptPath) {
|
|
6609
|
+
const row = db.prepare("SELECT pricing_version FROM session_costs WHERE session_id = ?").get(sessionId);
|
|
6610
|
+
const cursor = Number(getMemoryMeta(db, cursorKey(sessionId)) ?? "0") || 0;
|
|
6611
|
+
const incremental = row?.pricing_version === PRICING_VERSION && cursor > 0;
|
|
6612
|
+
const from = incremental ? cursor : 0;
|
|
6613
|
+
const { entries, totalLines } = await parseTranscriptFrom(transcriptPath, from);
|
|
6614
|
+
const slice = costTranscriptEntries(entries, {
|
|
6615
|
+
skipMessageId: incremental ? getMemoryMeta(db, lastIdKey(sessionId)) : null
|
|
6616
|
+
});
|
|
6617
|
+
storeSessionCost(db, sessionId, slice, incremental ? "add" : "replace");
|
|
6618
|
+
setMemoryMeta(db, cursorKey(sessionId), String(totalLines));
|
|
6619
|
+
if (slice.lastMessageId) setMemoryMeta(db, lastIdKey(sessionId), slice.lastMessageId);
|
|
6620
|
+
}
|
|
6621
|
+
function defaultTranscriptFor(sessionId) {
|
|
6622
|
+
if (!/^[A-Za-z0-9_-]+$/.test(sessionId)) return null;
|
|
6623
|
+
const path = join11(dirname13(getResolvedPaths().memoryDir), `${sessionId}.jsonl`);
|
|
6624
|
+
return existsSync16(path) ? path : null;
|
|
6625
|
+
}
|
|
6626
|
+
async function repriceStoredSessions(db, opts = {}) {
|
|
6627
|
+
const deadlineAt = opts.deadlineAt ?? Infinity;
|
|
6628
|
+
const find = opts.findTranscript ?? defaultTranscriptFor;
|
|
6629
|
+
const out = { repriced: 0, markedUnknown: 0, busy: 0, failed: 0, remaining: 0 };
|
|
6630
|
+
const stale = db.prepare(
|
|
6631
|
+
`SELECT session_id FROM session_costs WHERE pricing_version < ? ORDER BY id LIMIT ?`
|
|
6632
|
+
);
|
|
6633
|
+
const ttlSec = 120;
|
|
6634
|
+
const tried = /* @__PURE__ */ new Set();
|
|
6635
|
+
outer: while (Date.now() < deadlineAt) {
|
|
6636
|
+
const ids = stale.all(PRICING_VERSION, opts.batch ?? 50).map((r2) => r2.session_id).filter((sid) => !tried.has(sid));
|
|
6637
|
+
if (ids.length === 0) break;
|
|
6638
|
+
for (const sid of ids) {
|
|
6639
|
+
if (Date.now() >= deadlineAt) break outer;
|
|
6640
|
+
tried.add(sid);
|
|
6641
|
+
if (!tryAcquireMetaLease(db, sessionLeaseKey(sid), ttlSec)) {
|
|
6642
|
+
out.busy++;
|
|
6643
|
+
continue;
|
|
6644
|
+
}
|
|
6645
|
+
try {
|
|
6646
|
+
const path = find(sid);
|
|
6647
|
+
if (path) {
|
|
6648
|
+
await updateSessionCost(db, sid, path);
|
|
6649
|
+
out.repriced++;
|
|
6650
|
+
} else {
|
|
6651
|
+
markSessionCostUnknown(db, sid);
|
|
6652
|
+
out.markedUnknown++;
|
|
6653
|
+
}
|
|
6654
|
+
} catch (err) {
|
|
6655
|
+
out.failed++;
|
|
6656
|
+
process.stderr.write(`[massu] cost reprice: ${sid}: ${err instanceof Error ? err.message : String(err)}
|
|
6657
|
+
`);
|
|
6658
|
+
} finally {
|
|
6659
|
+
releaseMetaLease(db, sessionLeaseKey(sid));
|
|
6660
|
+
}
|
|
6661
|
+
}
|
|
6662
|
+
}
|
|
6663
|
+
out.remaining = db.prepare("SELECT COUNT(*) n FROM session_costs WHERE pricing_version < ?").get(PRICING_VERSION).n;
|
|
6664
|
+
return out;
|
|
6665
|
+
}
|
|
6427
6666
|
|
|
6428
6667
|
// src/prompt-analyzer.ts
|
|
6429
6668
|
init_config();
|
|
@@ -6589,14 +6828,14 @@ function collectObservations(memDb, queryText, pool, nowMs, loadVec, modelId, di
|
|
|
6589
6828
|
WHERE observations_fts MATCH ?${bm25Temporal ? ` AND ${bm25Temporal}` : ""}
|
|
6590
6829
|
ORDER BY rank LIMIT ?`
|
|
6591
6830
|
).all(sanitizeFts5QueryOr(queryText), pool);
|
|
6592
|
-
rows.forEach((
|
|
6593
|
-
map.set(
|
|
6594
|
-
id:
|
|
6831
|
+
rows.forEach((r2, i) => {
|
|
6832
|
+
map.set(r2.id, {
|
|
6833
|
+
id: r2.id,
|
|
6595
6834
|
source: "observation",
|
|
6596
|
-
title:
|
|
6597
|
-
snippet: snippetOf(
|
|
6598
|
-
importance:
|
|
6599
|
-
ageDays: ageDaysFrom(
|
|
6835
|
+
title: r2.title,
|
|
6836
|
+
snippet: snippetOf(r2.detail ?? r2.title),
|
|
6837
|
+
importance: r2.importance,
|
|
6838
|
+
ageDays: ageDaysFrom(r2.created_at_epoch * 1e3, nowMs),
|
|
6600
6839
|
bm25Order: i,
|
|
6601
6840
|
vecs: []
|
|
6602
6841
|
});
|
|
@@ -6607,15 +6846,15 @@ function collectObservations(memDb, queryText, pool, nowMs, loadVec, modelId, di
|
|
|
6607
6846
|
`SELECT id, title, detail, importance, created_at_epoch
|
|
6608
6847
|
FROM observations${plainTemporal ? ` WHERE ${plainTemporal}` : ""} ORDER BY created_at_epoch DESC LIMIT ?`
|
|
6609
6848
|
).all(pool);
|
|
6610
|
-
for (const
|
|
6611
|
-
if (!map.has(
|
|
6612
|
-
map.set(
|
|
6613
|
-
id:
|
|
6849
|
+
for (const r2 of recent) {
|
|
6850
|
+
if (!map.has(r2.id)) {
|
|
6851
|
+
map.set(r2.id, {
|
|
6852
|
+
id: r2.id,
|
|
6614
6853
|
source: "observation",
|
|
6615
|
-
title:
|
|
6616
|
-
snippet: snippetOf(
|
|
6617
|
-
importance:
|
|
6618
|
-
ageDays: ageDaysFrom(
|
|
6854
|
+
title: r2.title,
|
|
6855
|
+
snippet: snippetOf(r2.detail ?? r2.title),
|
|
6856
|
+
importance: r2.importance,
|
|
6857
|
+
ageDays: ageDaysFrom(r2.created_at_epoch * 1e3, nowMs),
|
|
6619
6858
|
bm25Order: null,
|
|
6620
6859
|
vecs: []
|
|
6621
6860
|
});
|
|
@@ -6655,14 +6894,14 @@ function collectLikeSource(db, source, cfg, queryText, pool, nowMs) {
|
|
|
6655
6894
|
FROM ${cfg.table} WHERE (${likeClause})${cfg.temporalClause ? ` AND ${cfg.temporalClause}` : ""}
|
|
6656
6895
|
ORDER BY ${cfg.createdCol} DESC LIMIT ?`
|
|
6657
6896
|
).all(...params, pool);
|
|
6658
|
-
rows.forEach((
|
|
6659
|
-
map.set(
|
|
6660
|
-
id:
|
|
6897
|
+
rows.forEach((r2, i) => {
|
|
6898
|
+
map.set(r2.id, {
|
|
6899
|
+
id: r2.id,
|
|
6661
6900
|
source,
|
|
6662
|
-
title:
|
|
6663
|
-
snippet: snippetOf(
|
|
6901
|
+
title: r2.title,
|
|
6902
|
+
snippet: snippetOf(r2.snippet ?? r2.title),
|
|
6664
6903
|
importance: 3,
|
|
6665
|
-
ageDays: ageExpr(
|
|
6904
|
+
ageDays: ageExpr(r2.created),
|
|
6666
6905
|
bm25Order: i,
|
|
6667
6906
|
vecs: []
|
|
6668
6907
|
});
|
|
@@ -6675,15 +6914,15 @@ function collectLikeSource(db, source, cfg, queryText, pool, nowMs) {
|
|
|
6675
6914
|
${cfg.snippetCol} AS snippet, ${cfg.createdCol} AS created
|
|
6676
6915
|
FROM ${cfg.table}${cfg.temporalClause ? ` WHERE ${cfg.temporalClause}` : ""} ORDER BY ${cfg.createdCol} DESC LIMIT ?`
|
|
6677
6916
|
).all(pool);
|
|
6678
|
-
for (const
|
|
6679
|
-
if (!map.has(
|
|
6680
|
-
map.set(
|
|
6681
|
-
id:
|
|
6917
|
+
for (const r2 of recent) {
|
|
6918
|
+
if (!map.has(r2.id)) {
|
|
6919
|
+
map.set(r2.id, {
|
|
6920
|
+
id: r2.id,
|
|
6682
6921
|
source,
|
|
6683
|
-
title:
|
|
6684
|
-
snippet: snippetOf(
|
|
6922
|
+
title: r2.title,
|
|
6923
|
+
snippet: snippetOf(r2.snippet ?? r2.title),
|
|
6685
6924
|
importance: 3,
|
|
6686
|
-
ageDays: ageExpr(
|
|
6925
|
+
ageDays: ageExpr(r2.created),
|
|
6687
6926
|
bm25Order: null,
|
|
6688
6927
|
vecs: []
|
|
6689
6928
|
});
|
|
@@ -6703,14 +6942,14 @@ function collectKnowledgeChunks(knowledgeDb, queryText, pool, nowMs, loadVec, mo
|
|
|
6703
6942
|
WHERE knowledge_fts MATCH ?
|
|
6704
6943
|
ORDER BY rank LIMIT ?`
|
|
6705
6944
|
).all(sanitizeFts5QueryOr(queryText), pool);
|
|
6706
|
-
rows.forEach((
|
|
6707
|
-
map.set(
|
|
6708
|
-
id:
|
|
6945
|
+
rows.forEach((r2, i) => {
|
|
6946
|
+
map.set(r2.id, {
|
|
6947
|
+
id: r2.id,
|
|
6709
6948
|
source: "knowledge_chunk",
|
|
6710
|
-
title:
|
|
6711
|
-
snippet: snippetOf(
|
|
6949
|
+
title: r2.heading || snippetOf(r2.content, 60),
|
|
6950
|
+
snippet: snippetOf(r2.content),
|
|
6712
6951
|
importance: 3,
|
|
6713
|
-
ageDays: ageDaysFrom((
|
|
6952
|
+
ageDays: ageDaysFrom((r2.epoch ?? 0) * 1e3, nowMs),
|
|
6714
6953
|
bm25Order: i,
|
|
6715
6954
|
vecs: []
|
|
6716
6955
|
});
|
|
@@ -6742,9 +6981,9 @@ function loadEmbeddings(db, table, idCol, ids, modelId, dim, assign) {
|
|
|
6742
6981
|
WHERE ${idCol} IN (${placeholders}) AND model_id = ? AND dim = ?
|
|
6743
6982
|
LIMIT ?`
|
|
6744
6983
|
).all(...ids, modelId, dim, ids.length);
|
|
6745
|
-
for (const
|
|
6746
|
-
const v = blobToFloat32(
|
|
6747
|
-
if (v && v.length === dim) assign(
|
|
6984
|
+
for (const r2 of rows) {
|
|
6985
|
+
const v = blobToFloat32(r2.vec);
|
|
6986
|
+
if (v && v.length === dim) assign(r2.id, v);
|
|
6748
6987
|
}
|
|
6749
6988
|
} catch {
|
|
6750
6989
|
}
|
|
@@ -6854,17 +7093,17 @@ function hybridSearch(memDb, knowledgeDb, opts) {
|
|
|
6854
7093
|
ageDays: c.ageDays
|
|
6855
7094
|
};
|
|
6856
7095
|
});
|
|
6857
|
-
const top = ranked.filter((
|
|
7096
|
+
const top = ranked.filter((r2) => r2.score >= minScore).sort((a, b) => b.score - a.score || a.ageDays - b.ageDays).slice(0, limit);
|
|
6858
7097
|
if (includeSuperseded && opts.asOf == null) {
|
|
6859
|
-
for (const
|
|
6860
|
-
const table =
|
|
7098
|
+
for (const r2 of top) {
|
|
7099
|
+
const table = r2.source === "observation" ? "observations" : r2.source === "architecture_decision" ? "architecture_decisions" : null;
|
|
6861
7100
|
if (!table) continue;
|
|
6862
7101
|
try {
|
|
6863
|
-
const row = memDb.prepare(`SELECT valid_to, superseded_by, expired_at_epoch FROM ${table} WHERE id = ?`).get(
|
|
7102
|
+
const row = memDb.prepare(`SELECT valid_to, superseded_by, expired_at_epoch FROM ${table} WHERE id = ?`).get(r2.id);
|
|
6864
7103
|
if (row && row.expired_at_epoch != null) {
|
|
6865
7104
|
const when = row.valid_to ? row.valid_to.slice(0, 10) : "an earlier date";
|
|
6866
7105
|
const by = row.superseded_by != null ? ` by #${row.superseded_by}` : "";
|
|
6867
|
-
|
|
7106
|
+
r2.snippet = `(superseded on ${when}${by}) ${r2.snippet}`;
|
|
6868
7107
|
}
|
|
6869
7108
|
} catch {
|
|
6870
7109
|
}
|
|
@@ -6986,11 +7225,11 @@ async function supersedeIfContradicted(memDb, knowledgeDb, args) {
|
|
|
6986
7225
|
candidatePool: 20
|
|
6987
7226
|
});
|
|
6988
7227
|
const scored = [];
|
|
6989
|
-
for (const
|
|
6990
|
-
if (
|
|
7228
|
+
for (const r2 of results) {
|
|
7229
|
+
if (r2.id === args.newId) continue;
|
|
6991
7230
|
if (Date.now() - start > cfg.budgetMs) break;
|
|
6992
|
-
const vec = await candidateVec(memDb, args.source,
|
|
6993
|
-
if (vec) scored.push({ id:
|
|
7231
|
+
const vec = await candidateVec(memDb, args.source, r2.id);
|
|
7232
|
+
if (vec) scored.push({ id: r2.id, source: args.source, cosine: cosineSim(queryVec, vec) });
|
|
6994
7233
|
}
|
|
6995
7234
|
let verdict = null;
|
|
6996
7235
|
if (cfg.judgeEndpoint && Date.now() - start < cfg.budgetMs) {
|
|
@@ -7065,8 +7304,8 @@ ${d.decision}`.trim(),
|
|
|
7065
7304
|
// src/memory-consolidate.ts
|
|
7066
7305
|
init_memory_db();
|
|
7067
7306
|
import { createHash as createHash6 } from "crypto";
|
|
7068
|
-
import { existsSync as
|
|
7069
|
-
import { join as
|
|
7307
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync14, writeFileSync as writeFileSync10 } from "fs";
|
|
7308
|
+
import { join as join12 } from "path";
|
|
7070
7309
|
init_memory_embedder();
|
|
7071
7310
|
init_memory_vector();
|
|
7072
7311
|
var LEASE_KEY = "consolidate_lease";
|
|
@@ -7089,17 +7328,11 @@ function emptyResult() {
|
|
|
7089
7328
|
};
|
|
7090
7329
|
}
|
|
7091
7330
|
function acquireLease(db, now, ttlSec) {
|
|
7092
|
-
|
|
7093
|
-
if (raw) {
|
|
7094
|
-
const expiry = Number(raw.split(":")[1]);
|
|
7095
|
-
if (Number.isFinite(expiry) && expiry > now) return false;
|
|
7096
|
-
}
|
|
7097
|
-
setMemoryMeta(db, LEASE_KEY, `${process.pid}:${now + ttlSec}`);
|
|
7098
|
-
return true;
|
|
7331
|
+
return tryAcquireMetaLease(db, LEASE_KEY, ttlSec, now);
|
|
7099
7332
|
}
|
|
7100
7333
|
function releaseLease(db) {
|
|
7101
7334
|
try {
|
|
7102
|
-
|
|
7335
|
+
releaseMetaLease(db, LEASE_KEY);
|
|
7103
7336
|
} catch {
|
|
7104
7337
|
}
|
|
7105
7338
|
}
|
|
@@ -7118,19 +7351,19 @@ async function stageDedupe(db, cfg, deadline, now) {
|
|
|
7118
7351
|
return 0;
|
|
7119
7352
|
}
|
|
7120
7353
|
let deduped = 0;
|
|
7121
|
-
for (const
|
|
7354
|
+
for (const r2 of rows) {
|
|
7122
7355
|
if (Date.now() > deadline) break;
|
|
7123
|
-
const live = db.prepare(`SELECT expired_at FROM observations WHERE id = ?`).get(
|
|
7356
|
+
const live = db.prepare(`SELECT expired_at FROM observations WHERE id = ?`).get(r2.id);
|
|
7124
7357
|
if (!live || live.expired_at != null) continue;
|
|
7125
7358
|
const res = await supersedeIfContradicted(db, null, {
|
|
7126
|
-
text: `${
|
|
7127
|
-
${
|
|
7359
|
+
text: `${r2.title}
|
|
7360
|
+
${r2.detail ?? ""}`.trim(),
|
|
7128
7361
|
source: "observation",
|
|
7129
|
-
newId:
|
|
7362
|
+
newId: r2.id,
|
|
7130
7363
|
nowEpochSec: now
|
|
7131
7364
|
});
|
|
7132
7365
|
if (res.superseded != null) deduped++;
|
|
7133
|
-
setMemoryMeta(db, DEDUPE_CURSOR, String(
|
|
7366
|
+
setMemoryMeta(db, DEDUPE_CURSOR, String(r2.id));
|
|
7134
7367
|
}
|
|
7135
7368
|
return deduped;
|
|
7136
7369
|
}
|
|
@@ -7208,11 +7441,11 @@ async function stagePromote(db, cfg, projectRoot, deadline, now, dryRun) {
|
|
|
7208
7441
|
).all(MAX_ROWS_PER_STAGE);
|
|
7209
7442
|
if (rows.length === 0) return { promoted: 0, refusedByTier: 0, embedderUnavailable: false };
|
|
7210
7443
|
const vecs = /* @__PURE__ */ new Map();
|
|
7211
|
-
for (const
|
|
7444
|
+
for (const r2 of rows) {
|
|
7212
7445
|
if (Date.now() > deadline) break;
|
|
7213
|
-
const v = await embed(`${
|
|
7214
|
-
${
|
|
7215
|
-
if (v) vecs.set(
|
|
7446
|
+
const v = await embed(`${r2.title}
|
|
7447
|
+
${r2.detail ?? ""}`.trim());
|
|
7448
|
+
if (v) vecs.set(r2.id, v);
|
|
7216
7449
|
}
|
|
7217
7450
|
if (vecs.size === 0) {
|
|
7218
7451
|
return { promoted: 0, refusedByTier: 0, embedderUnavailable: true };
|
|
@@ -7220,20 +7453,20 @@ ${r.detail ?? ""}`.trim());
|
|
|
7220
7453
|
const CLUSTER_THRESHOLD = 0.8;
|
|
7221
7454
|
const used = /* @__PURE__ */ new Set();
|
|
7222
7455
|
const clusters = [];
|
|
7223
|
-
for (const
|
|
7224
|
-
if (used.has(
|
|
7225
|
-
const cluster = [
|
|
7226
|
-
used.add(
|
|
7456
|
+
for (const r2 of rows) {
|
|
7457
|
+
if (used.has(r2.id) || !vecs.has(r2.id)) continue;
|
|
7458
|
+
const cluster = [r2];
|
|
7459
|
+
used.add(r2.id);
|
|
7227
7460
|
for (const other of rows) {
|
|
7228
7461
|
if (used.has(other.id) || !vecs.has(other.id)) continue;
|
|
7229
|
-
if (cosineSim(vecs.get(
|
|
7462
|
+
if (cosineSim(vecs.get(r2.id), vecs.get(other.id)) >= CLUSTER_THRESHOLD) {
|
|
7230
7463
|
cluster.push(other);
|
|
7231
7464
|
used.add(other.id);
|
|
7232
7465
|
}
|
|
7233
7466
|
}
|
|
7234
7467
|
clusters.push(cluster);
|
|
7235
7468
|
}
|
|
7236
|
-
const candidateDir =
|
|
7469
|
+
const candidateDir = join12(projectRoot, ".massu", "rule-candidates");
|
|
7237
7470
|
let promoted = 0;
|
|
7238
7471
|
for (const cluster of clusters) {
|
|
7239
7472
|
const occurrences = cluster.reduce((n, c) => n + (c.recurrence_count || 1), 0);
|
|
@@ -7246,8 +7479,8 @@ ${r.detail ?? ""}`.trim());
|
|
|
7246
7479
|
${representative.detail}` : ""}`;
|
|
7247
7480
|
const clusterKey = cluster.map((c) => c.title.toLowerCase().replace(/\s+/g, " ").trim()).sort().join("|");
|
|
7248
7481
|
const promptHash = createHash6("sha256").update(clusterKey).digest("hex").slice(0, 16);
|
|
7249
|
-
const candidatePath =
|
|
7250
|
-
if (
|
|
7482
|
+
const candidatePath = join12(candidateDir, `${promptHash}.json`);
|
|
7483
|
+
if (existsSync17(candidatePath)) continue;
|
|
7251
7484
|
if (dryRun) {
|
|
7252
7485
|
promoted++;
|
|
7253
7486
|
continue;
|
|
@@ -7325,14 +7558,14 @@ function stageReweight(db, cfg, now) {
|
|
|
7325
7558
|
ON CONFLICT(source, record_id) DO UPDATE SET last_reweight_epoch = excluded.last_reweight_epoch`
|
|
7326
7559
|
);
|
|
7327
7560
|
const tx = db.transaction(() => {
|
|
7328
|
-
for (const
|
|
7329
|
-
bump.run(
|
|
7330
|
-
mark.run(
|
|
7561
|
+
for (const r2 of promote) {
|
|
7562
|
+
bump.run(r2.id);
|
|
7563
|
+
mark.run(r2.id, now);
|
|
7331
7564
|
changed++;
|
|
7332
7565
|
}
|
|
7333
|
-
for (const
|
|
7334
|
-
drop2.run(
|
|
7335
|
-
mark.run(
|
|
7566
|
+
for (const r2 of demote) {
|
|
7567
|
+
drop2.run(r2.id);
|
|
7568
|
+
mark.run(r2.id, now);
|
|
7336
7569
|
changed++;
|
|
7337
7570
|
}
|
|
7338
7571
|
});
|
|
@@ -7423,105 +7656,184 @@ function isDirectInvocation(moduleUrl) {
|
|
|
7423
7656
|
}
|
|
7424
7657
|
|
|
7425
7658
|
// src/hooks/session-end.ts
|
|
7659
|
+
var SESSION_END_BUDGET_ENV = "MASSU_SESSION_END_BUDGET_MS";
|
|
7660
|
+
var DEFAULT_SESSION_END_BUDGET_MS = 2e4;
|
|
7661
|
+
var SESSION_END_WATCHDOG_GRACE_MS = 15e3;
|
|
7662
|
+
var MAX_COALESCED_RUNS = 3;
|
|
7663
|
+
var MIN_PHASE_BUDGET_MS = 500;
|
|
7664
|
+
function resolveSessionEndBudgetMs() {
|
|
7665
|
+
const raw = process.env[SESSION_END_BUDGET_ENV];
|
|
7666
|
+
if (raw === void 0 || raw === "") return DEFAULT_SESSION_END_BUDGET_MS;
|
|
7667
|
+
const n = Number(raw);
|
|
7668
|
+
return Number.isFinite(n) && n > 0 ? Math.floor(n) : DEFAULT_SESSION_END_BUDGET_MS;
|
|
7669
|
+
}
|
|
7670
|
+
var leaseKey = sessionLeaseKey;
|
|
7671
|
+
var rerunKey = (sessionId) => `session_end_rerun:${sessionId}`;
|
|
7672
|
+
async function runCoalesced(db, sessionId, budgetMs, work) {
|
|
7673
|
+
const ttlSec = Math.ceil((budgetMs + SESSION_END_WATCHDOG_GRACE_MS) / 1e3) + 1;
|
|
7674
|
+
let runs = 0;
|
|
7675
|
+
while (runs < MAX_COALESCED_RUNS) {
|
|
7676
|
+
if (!tryAcquireMetaLease(db, leaseKey(sessionId), ttlSec)) {
|
|
7677
|
+
setMemoryMeta(db, rerunKey(sessionId), "1");
|
|
7678
|
+
if (!tryAcquireMetaLease(db, leaseKey(sessionId), ttlSec)) {
|
|
7679
|
+
return runs === 0 ? "handed-off" : "ran";
|
|
7680
|
+
}
|
|
7681
|
+
}
|
|
7682
|
+
runs++;
|
|
7683
|
+
try {
|
|
7684
|
+
setMemoryMeta(db, rerunKey(sessionId), "");
|
|
7685
|
+
await work(Date.now() + budgetMs);
|
|
7686
|
+
} finally {
|
|
7687
|
+
releaseMetaLease(db, leaseKey(sessionId));
|
|
7688
|
+
}
|
|
7689
|
+
if (getMemoryMeta(db, rerunKey(sessionId)) !== "1") return "ran";
|
|
7690
|
+
}
|
|
7691
|
+
return "ran";
|
|
7692
|
+
}
|
|
7693
|
+
function phaseBudget(deadlineAt, phase, cap) {
|
|
7694
|
+
const left = deadlineAt - Date.now();
|
|
7695
|
+
if (left < MIN_PHASE_BUDGET_MS) {
|
|
7696
|
+
process.stderr.write(`[massu] session-end: skipped ${phase} \u2014 ${Math.max(0, left)}ms of budget left
|
|
7697
|
+
`);
|
|
7698
|
+
return null;
|
|
7699
|
+
}
|
|
7700
|
+
return Math.min(cap, left);
|
|
7701
|
+
}
|
|
7426
7702
|
async function main() {
|
|
7427
7703
|
try {
|
|
7428
7704
|
const input = await readStdin();
|
|
7429
7705
|
const hookInput = JSON.parse(input);
|
|
7430
|
-
const
|
|
7706
|
+
const budgetMs = resolveSessionEndBudgetMs();
|
|
7707
|
+
let watchdog;
|
|
7431
7708
|
const db = getMemoryDb();
|
|
7432
7709
|
try {
|
|
7433
|
-
|
|
7434
|
-
|
|
7435
|
-
|
|
7436
|
-
|
|
7437
|
-
|
|
7438
|
-
|
|
7439
|
-
|
|
7440
|
-
|
|
7441
|
-
|
|
7442
|
-
|
|
7443
|
-
await
|
|
7444
|
-
}
|
|
7445
|
-
|
|
7446
|
-
|
|
7447
|
-
|
|
7448
|
-
|
|
7449
|
-
|
|
7450
|
-
|
|
7451
|
-
|
|
7452
|
-
|
|
7453
|
-
|
|
7454
|
-
|
|
7455
|
-
|
|
7456
|
-
|
|
7457
|
-
|
|
7458
|
-
|
|
7459
|
-
|
|
7460
|
-
|
|
7461
|
-
|
|
7462
|
-
|
|
7463
|
-
|
|
7464
|
-
|
|
7465
|
-
|
|
7466
|
-
|
|
7467
|
-
|
|
7468
|
-
|
|
7469
|
-
|
|
7470
|
-
|
|
7471
|
-
}
|
|
7710
|
+
await runCoalesced(db, hookInput.session_id, budgetMs, async (deadlineAt) => {
|
|
7711
|
+
clearTimeout(watchdog);
|
|
7712
|
+
watchdog = setTimeout(() => {
|
|
7713
|
+
recordHookFailure(
|
|
7714
|
+
"session-end:watchdog",
|
|
7715
|
+
new Error(`a run exceeded its ${budgetMs}ms budget by ${SESSION_END_WATCHDOG_GRACE_MS}ms`)
|
|
7716
|
+
);
|
|
7717
|
+
process.exit(0);
|
|
7718
|
+
}, budgetMs + SESSION_END_WATCHDOG_GRACE_MS);
|
|
7719
|
+
watchdog.unref();
|
|
7720
|
+
await runSessionEnd(db, hookInput, deadlineAt);
|
|
7721
|
+
});
|
|
7722
|
+
} finally {
|
|
7723
|
+
clearTimeout(watchdog);
|
|
7724
|
+
db.close();
|
|
7725
|
+
}
|
|
7726
|
+
} catch (err) {
|
|
7727
|
+
recordHookFailure("session-end", err);
|
|
7728
|
+
}
|
|
7729
|
+
process.exit(0);
|
|
7730
|
+
}
|
|
7731
|
+
async function runSessionEnd(db, hookInput, deadlineAt) {
|
|
7732
|
+
const { session_id, cwd } = hookInput;
|
|
7733
|
+
{
|
|
7734
|
+
createSession(db, session_id);
|
|
7735
|
+
const observations = db.prepare(
|
|
7736
|
+
"SELECT * FROM observations WHERE session_id = ? ORDER BY created_at_epoch ASC LIMIT 10000"
|
|
7737
|
+
).all(session_id);
|
|
7738
|
+
const prompts = db.prepare(
|
|
7739
|
+
"SELECT prompt_text FROM user_prompts WHERE session_id = ? ORDER BY prompt_number ASC LIMIT 10000"
|
|
7740
|
+
).all(session_id);
|
|
7741
|
+
const summary = buildSummaryFromObservations(observations, prompts);
|
|
7742
|
+
addSummary(db, session_id, summary);
|
|
7743
|
+
try {
|
|
7744
|
+
await captureConversationData(db, session_id, hookInput.transcript_path);
|
|
7745
|
+
} catch (_captureErr) {
|
|
7746
|
+
}
|
|
7747
|
+
try {
|
|
7748
|
+
const { score, breakdown } = calculateQualityScore(db, session_id);
|
|
7749
|
+
if (score !== 50) {
|
|
7750
|
+
storeQualityScore(db, session_id, score, breakdown);
|
|
7472
7751
|
}
|
|
7473
|
-
|
|
7474
|
-
|
|
7475
|
-
|
|
7752
|
+
backfillQualityScores(db);
|
|
7753
|
+
} catch (_qualityErr) {
|
|
7754
|
+
}
|
|
7755
|
+
try {
|
|
7756
|
+
await updateSessionCost(db, session_id, hookInput.transcript_path);
|
|
7757
|
+
} catch (costErr) {
|
|
7758
|
+
recordHookFailure("session-end:cost", costErr);
|
|
7759
|
+
}
|
|
7760
|
+
try {
|
|
7761
|
+
analyzeSessionPrompts(db, session_id);
|
|
7762
|
+
} catch (_promptErr) {
|
|
7763
|
+
}
|
|
7764
|
+
try {
|
|
7765
|
+
const budget = phaseBudget(deadlineAt, "embed sweep", 3e3);
|
|
7766
|
+
if (budget !== null) await embedMissingObservations(db, { budgetMs: budget });
|
|
7767
|
+
} catch (_embedErr) {
|
|
7768
|
+
}
|
|
7769
|
+
try {
|
|
7770
|
+
const budget = phaseBudget(deadlineAt, "supersede sweep", 4e3);
|
|
7771
|
+
if (budget !== null) await runSessionSupersedeSweep(db, session_id, { budgetMs: budget });
|
|
7772
|
+
} catch (_supersedeErr) {
|
|
7773
|
+
}
|
|
7774
|
+
try {
|
|
7775
|
+
const consolidationCfg = resolveConsolidationConfig();
|
|
7776
|
+
if (consolidationCfg.enabled && consolidationCfg.sessionSweepEnabled) {
|
|
7777
|
+
const budget = phaseBudget(deadlineAt, "consolidation", consolidationCfg.budgetMs);
|
|
7778
|
+
if (budget !== null) {
|
|
7476
7779
|
await runConsolidation(db, {
|
|
7477
7780
|
config: consolidationCfg,
|
|
7478
|
-
budgetMs:
|
|
7781
|
+
budgetMs: budget,
|
|
7479
7782
|
projectRoot: cwd
|
|
7480
7783
|
});
|
|
7481
7784
|
}
|
|
7482
|
-
} catch (_consolidateErr) {
|
|
7483
7785
|
}
|
|
7484
|
-
|
|
7485
|
-
|
|
7486
|
-
|
|
7487
|
-
|
|
7488
|
-
|
|
7489
|
-
|
|
7490
|
-
|
|
7491
|
-
|
|
7492
|
-
|
|
7493
|
-
|
|
7494
|
-
|
|
7495
|
-
|
|
7496
|
-
|
|
7497
|
-
|
|
7498
|
-
|
|
7499
|
-
|
|
7500
|
-
|
|
7786
|
+
} catch (_consolidateErr) {
|
|
7787
|
+
}
|
|
7788
|
+
try {
|
|
7789
|
+
const budget = phaseBudget(deadlineAt, "cost reprice", 3e3);
|
|
7790
|
+
if (budget !== null) await repriceStoredSessions(db, { deadlineAt: Date.now() + budget });
|
|
7791
|
+
} catch (repriceErr) {
|
|
7792
|
+
recordHookFailure("session-end:cost-reprice", repriceErr);
|
|
7793
|
+
}
|
|
7794
|
+
endSession(db, session_id, "completed");
|
|
7795
|
+
archiveAndRegenerate(db, session_id);
|
|
7796
|
+
let openLease = null;
|
|
7797
|
+
try {
|
|
7798
|
+
const drained = await drainSyncQueue(db, deadlineAt);
|
|
7799
|
+
if (drained.deferred > 0) {
|
|
7800
|
+
process.stderr.write(`[massu] session-end: deferred ${drained.deferred} queued sync item(s) to the next run
|
|
7801
|
+
`);
|
|
7802
|
+
}
|
|
7803
|
+
const cloudCfg = getConfig().cloud;
|
|
7804
|
+
const willTransmit = !!cloudCfg?.enabled && !!cloudCfg?.apiKey && !!cloudCfg?.endpoint && cloudCfg?.sync?.memory !== false;
|
|
7805
|
+
const lease = willTransmit ? leaseLearning(db) : null;
|
|
7806
|
+
openLease = lease;
|
|
7807
|
+
const syncPayload = buildSyncPayload(db, session_id, observations, summary, lease);
|
|
7808
|
+
const result = await syncToCloud(db, syncPayload, { deadlineAt });
|
|
7809
|
+
if (lease) {
|
|
7810
|
+
if (result.transmitted === true) {
|
|
7811
|
+
ackLearning(db, lease);
|
|
7812
|
+
} else {
|
|
7813
|
+
nackLearning(db, lease, result.error ?? "not transmitted (sync disabled or unreachable)");
|
|
7501
7814
|
}
|
|
7502
|
-
|
|
7503
|
-
|
|
7815
|
+
}
|
|
7816
|
+
openLease = null;
|
|
7817
|
+
try {
|
|
7818
|
+
if (phaseBudget(deadlineAt, "team promotion pull", Infinity) !== null) {
|
|
7504
7819
|
await pullTeamPromotions(db);
|
|
7505
|
-
} catch (_pullErr) {
|
|
7506
7820
|
}
|
|
7507
|
-
|
|
7821
|
+
} catch (_pullErr) {
|
|
7822
|
+
}
|
|
7823
|
+
try {
|
|
7824
|
+
if (phaseBudget(deadlineAt, "shared-memory exchange", Infinity) !== null) {
|
|
7508
7825
|
const shareTransport = new LocalFsTransport();
|
|
7509
7826
|
await exportSharedMemories(db, shareTransport);
|
|
7510
7827
|
await importSharedMemories(db, shareTransport);
|
|
7511
|
-
} catch (_shareErr) {
|
|
7512
7828
|
}
|
|
7513
|
-
} catch (
|
|
7514
|
-
const msg = syncErr instanceof Error ? syncErr.message : String(syncErr);
|
|
7515
|
-
if (openLease) nackLearning(db, openLease, msg);
|
|
7516
|
-
recordHookFailure("session-end:cloud-sync", syncErr);
|
|
7829
|
+
} catch (_shareErr) {
|
|
7517
7830
|
}
|
|
7518
|
-
}
|
|
7519
|
-
|
|
7831
|
+
} catch (syncErr) {
|
|
7832
|
+
const msg = syncErr instanceof Error ? syncErr.message : String(syncErr);
|
|
7833
|
+
if (openLease) nackLearning(db, openLease, msg);
|
|
7834
|
+
recordHookFailure("session-end:cloud-sync", syncErr);
|
|
7520
7835
|
}
|
|
7521
|
-
} catch (err) {
|
|
7522
|
-
recordHookFailure("session-end", err);
|
|
7523
7836
|
}
|
|
7524
|
-
process.exit(0);
|
|
7525
7837
|
}
|
|
7526
7838
|
function buildSyncPayload(db, sessionId, observations, summary, lease) {
|
|
7527
7839
|
const promotions = lease?.promotions ?? [];
|
|
@@ -7785,3 +8097,11 @@ function readStdin() {
|
|
|
7785
8097
|
if (isDirectInvocation(import.meta.url)) {
|
|
7786
8098
|
main();
|
|
7787
8099
|
}
|
|
8100
|
+
export {
|
|
8101
|
+
DEFAULT_SESSION_END_BUDGET_MS,
|
|
8102
|
+
MAX_COALESCED_RUNS,
|
|
8103
|
+
SESSION_END_BUDGET_ENV,
|
|
8104
|
+
SESSION_END_WATCHDOG_GRACE_MS,
|
|
8105
|
+
resolveSessionEndBudgetMs,
|
|
8106
|
+
runCoalesced
|
|
8107
|
+
};
|