@massu/core 2.5.5 → 2.5.7
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 +63 -0
- package/dist/cli.js +167 -46
- package/dist/hooks/auto-learning-pipeline.js +81 -16
- package/dist/hooks/classify-failure.js +90 -652
- package/dist/hooks/cost-tracker.js +69 -4
- package/dist/hooks/fix-detector.js +79 -12
- package/dist/hooks/incident-pipeline.js +72 -7
- package/dist/hooks/intent-suggester.js +69 -4
- package/dist/hooks/memory-recall.js +89 -6
- package/dist/hooks/post-edit-context.js +69 -4
- package/dist/hooks/post-tool-use.js +76 -11
- package/dist/hooks/pre-compact.js +69 -4
- package/dist/hooks/pre-delete-check.js +446 -71
- package/dist/hooks/pre-tool-use-gate.js +237 -223
- package/dist/hooks/quality-event.js +69 -4
- package/dist/hooks/rule-enforcement-pipeline.js +72 -7
- package/dist/hooks/security-gate.js +611 -181
- package/dist/hooks/session-end.js +607 -73
- package/dist/hooks/session-start.js +112 -8
- package/dist/hooks/user-prompt.js +83 -18
- package/package.json +1 -1
|
@@ -1648,9 +1648,9 @@ function nackLearning(db, lease, error) {
|
|
|
1648
1648
|
return snap;
|
|
1649
1649
|
}
|
|
1650
1650
|
function undeliveredSnapshot(db) {
|
|
1651
|
-
const count = (
|
|
1652
|
-
if (!tableExists(db,
|
|
1653
|
-
const r2 = db.prepare(`SELECT COUNT(*) AS n FROM ${
|
|
1651
|
+
const count = (t2) => {
|
|
1652
|
+
if (!tableExists(db, t2)) return 0;
|
|
1653
|
+
const r2 = db.prepare(`SELECT COUNT(*) AS n FROM ${t2}`).get();
|
|
1654
1654
|
return r2.n;
|
|
1655
1655
|
};
|
|
1656
1656
|
const promotions = count("team_promotion_outbound");
|
|
@@ -1658,10 +1658,10 @@ function undeliveredSnapshot(db) {
|
|
|
1658
1658
|
const events = count(TELEMETRY_OUTBOUND_TABLE);
|
|
1659
1659
|
let maxAttempts = 0;
|
|
1660
1660
|
let oldest = null;
|
|
1661
|
-
for (const
|
|
1662
|
-
if (!tableExists(db,
|
|
1663
|
-
if (!columns(db,
|
|
1664
|
-
const r2 = db.prepare(`SELECT MAX(attempts) AS a, MIN(created_at) AS c FROM ${
|
|
1661
|
+
for (const t2 of RULE_OUTBOUND_TABLES) {
|
|
1662
|
+
if (!tableExists(db, t2)) continue;
|
|
1663
|
+
if (!columns(db, t2).has("attempts")) continue;
|
|
1664
|
+
const r2 = db.prepare(`SELECT MAX(attempts) AS a, MIN(created_at) AS c FROM ${t2}`).get();
|
|
1665
1665
|
if (r2.a !== null && r2.a > maxAttempts) maxAttempts = r2.a;
|
|
1666
1666
|
if (r2.c !== null && (oldest === null || r2.c < oldest)) oldest = r2.c;
|
|
1667
1667
|
}
|
|
@@ -1958,13 +1958,13 @@ function encode(text, vocab, opts = {}) {
|
|
|
1958
1958
|
const maxLen = opts.maxLen ?? 256;
|
|
1959
1959
|
const basic = basicTokenize(text);
|
|
1960
1960
|
const wpTokens = [];
|
|
1961
|
-
for (const
|
|
1962
|
-
for (const sub of wordpieceTokenize(
|
|
1961
|
+
for (const t2 of basic) {
|
|
1962
|
+
for (const sub of wordpieceTokenize(t2, vocab)) wpTokens.push(sub);
|
|
1963
1963
|
}
|
|
1964
1964
|
const truncated = wpTokens.slice(0, maxLen - 2);
|
|
1965
1965
|
const tokens = [CLS, ...truncated, SEP];
|
|
1966
1966
|
const unkId = vocab.get(UNK) ?? 100;
|
|
1967
|
-
const input_ids = tokens.map((
|
|
1967
|
+
const input_ids = tokens.map((t2) => vocab.get(t2) ?? unkId);
|
|
1968
1968
|
const attention_mask = tokens.map(() => 1);
|
|
1969
1969
|
const token_type_ids = tokens.map(() => 0);
|
|
1970
1970
|
return { tokens, input_ids, attention_mask, token_type_ids };
|
|
@@ -2066,11 +2066,11 @@ async function getSession() {
|
|
|
2066
2066
|
function meanPool(hidden, mask, seqLen, hiddenSize) {
|
|
2067
2067
|
const out = new Float32Array(hiddenSize);
|
|
2068
2068
|
let maskSum = 0;
|
|
2069
|
-
for (let
|
|
2070
|
-
const m = mask[
|
|
2069
|
+
for (let t2 = 0; t2 < seqLen; t2++) {
|
|
2070
|
+
const m = mask[t2];
|
|
2071
2071
|
if (m === 0) continue;
|
|
2072
2072
|
maskSum += m;
|
|
2073
|
-
const base =
|
|
2073
|
+
const base = t2 * hiddenSize;
|
|
2074
2074
|
for (let h = 0; h < hiddenSize; h++) out[h] += hidden[base + h] * m;
|
|
2075
2075
|
}
|
|
2076
2076
|
const denom = Math.max(maskSum, 1e-9);
|
|
@@ -2078,16 +2078,16 @@ function meanPool(hidden, mask, seqLen, hiddenSize) {
|
|
|
2078
2078
|
return out;
|
|
2079
2079
|
}
|
|
2080
2080
|
function chunkForEmbedding(text) {
|
|
2081
|
-
const
|
|
2082
|
-
if (!
|
|
2083
|
-
if (
|
|
2081
|
+
const t2 = text.trim();
|
|
2082
|
+
if (!t2) return [];
|
|
2083
|
+
if (t2.length <= CHUNK_TARGET_CHARS) return [t2];
|
|
2084
2084
|
const chunks = [];
|
|
2085
2085
|
let start = 0;
|
|
2086
|
-
while (start <
|
|
2087
|
-
let end = Math.min(start + CHUNK_TARGET_CHARS,
|
|
2088
|
-
if (end <
|
|
2086
|
+
while (start < t2.length && chunks.length < MAX_CHUNKS_PER_RECORD) {
|
|
2087
|
+
let end = Math.min(start + CHUNK_TARGET_CHARS, t2.length);
|
|
2088
|
+
if (end < t2.length) {
|
|
2089
2089
|
const floor = start + Math.floor(CHUNK_TARGET_CHARS / 2);
|
|
2090
|
-
const slice =
|
|
2090
|
+
const slice = t2.slice(start, end);
|
|
2091
2091
|
const para = slice.lastIndexOf("\n\n");
|
|
2092
2092
|
const sent = Math.max(slice.lastIndexOf(". "), slice.lastIndexOf(".\n"));
|
|
2093
2093
|
const space = slice.lastIndexOf(" ");
|
|
@@ -2099,9 +2099,9 @@ function chunkForEmbedding(text) {
|
|
|
2099
2099
|
}
|
|
2100
2100
|
}
|
|
2101
2101
|
}
|
|
2102
|
-
const chunk =
|
|
2102
|
+
const chunk = t2.slice(start, end).trim();
|
|
2103
2103
|
if (chunk) chunks.push(chunk);
|
|
2104
|
-
if (end >=
|
|
2104
|
+
if (end >= t2.length) break;
|
|
2105
2105
|
start = Math.max(end - CHUNK_OVERLAP_CHARS, start + 1);
|
|
2106
2106
|
}
|
|
2107
2107
|
return chunks;
|
|
@@ -2186,17 +2186,17 @@ async function embedBatch(texts) {
|
|
|
2186
2186
|
const settings = loadEmbedSettings();
|
|
2187
2187
|
if (!settings.enabled) return texts.map(() => null);
|
|
2188
2188
|
const results = [];
|
|
2189
|
-
for (const
|
|
2190
|
-
if (!
|
|
2189
|
+
for (const t2 of texts) {
|
|
2190
|
+
if (!t2 || !t2.trim()) {
|
|
2191
2191
|
results.push(null);
|
|
2192
2192
|
continue;
|
|
2193
2193
|
}
|
|
2194
2194
|
try {
|
|
2195
2195
|
let vec = null;
|
|
2196
2196
|
if (settings.endpoint) {
|
|
2197
|
-
vec = await embedTier0(
|
|
2197
|
+
vec = await embedTier0(t2, settings);
|
|
2198
2198
|
}
|
|
2199
|
-
if (!vec) vec = await embedTier1(
|
|
2199
|
+
if (!vec) vec = await embedTier1(t2);
|
|
2200
2200
|
results.push(vec);
|
|
2201
2201
|
} catch {
|
|
2202
2202
|
results.push(null);
|
|
@@ -2340,6 +2340,8 @@ __export(memory_db_exports, {
|
|
|
2340
2340
|
MEMORY_FILE_TITLE_LIKE: () => MEMORY_FILE_TITLE_LIKE,
|
|
2341
2341
|
MEMORY_FILE_TITLE_PREFIX: () => MEMORY_FILE_TITLE_PREFIX,
|
|
2342
2342
|
MEMORY_SCHEMA_VERSION: () => MEMORY_SCHEMA_VERSION,
|
|
2343
|
+
PENDING_SYNC_MAX_AGE_DAYS: () => PENDING_SYNC_MAX_AGE_DAYS,
|
|
2344
|
+
PENDING_SYNC_MAX_ITEMS: () => PENDING_SYNC_MAX_ITEMS,
|
|
2343
2345
|
TOOL_COST_EVENTS_RETENTION_DAYS: () => TOOL_COST_EVENTS_RETENTION_DAYS,
|
|
2344
2346
|
USAGE_COUNTER_ARMED_KEY: () => USAGE_COUNTER_ARMED_KEY,
|
|
2345
2347
|
addConversationTurn: () => addConversationTurn,
|
|
@@ -2348,6 +2350,7 @@ __export(memory_db_exports, {
|
|
|
2348
2350
|
addSummary: () => addSummary,
|
|
2349
2351
|
addToolCallDetail: () => addToolCallDetail,
|
|
2350
2352
|
addUserPrompt: () => addUserPrompt,
|
|
2353
|
+
allPendingSync: () => allPendingSync,
|
|
2351
2354
|
appendIncidentToFailureClass: () => appendIncidentToFailureClass,
|
|
2352
2355
|
armUsageCounter: () => armUsageCounter,
|
|
2353
2356
|
assignImportance: () => assignImportance,
|
|
@@ -2355,6 +2358,7 @@ __export(memory_db_exports, {
|
|
|
2355
2358
|
createSession: () => createSession,
|
|
2356
2359
|
deduplicateFailedAttempt: () => deduplicateFailedAttempt,
|
|
2357
2360
|
dequeuePendingSync: () => dequeuePendingSync,
|
|
2361
|
+
discardPendingSync: () => discardPendingSync,
|
|
2358
2362
|
embedMissingObservations: () => embedMissingObservations,
|
|
2359
2363
|
endSession: () => endSession,
|
|
2360
2364
|
enqueueRulePromotionEvent: () => enqueueRulePromotionEvent,
|
|
@@ -2411,14 +2415,14 @@ function sanitizeFts5Query(raw) {
|
|
|
2411
2415
|
const trimmed = raw.trim();
|
|
2412
2416
|
if (!trimmed) return '""';
|
|
2413
2417
|
const tokens = trimmed.replace(/"/g, "").split(/\s+/).filter(Boolean);
|
|
2414
|
-
return tokens.map((
|
|
2418
|
+
return tokens.map((t2) => `"${t2}"`).join(" ");
|
|
2415
2419
|
}
|
|
2416
2420
|
function sanitizeFts5QueryOr(raw) {
|
|
2417
2421
|
const trimmed = raw.trim();
|
|
2418
2422
|
if (!trimmed) return '""';
|
|
2419
|
-
const tokens = trimmed.replace(/"/g, "").split(/\s+/).filter((
|
|
2423
|
+
const tokens = trimmed.replace(/"/g, "").split(/\s+/).filter((t2) => t2.replace(/[^a-zA-Z0-9]/g, "").length >= 3);
|
|
2420
2424
|
if (tokens.length === 0) return '""';
|
|
2421
|
-
return tokens.map((
|
|
2425
|
+
return tokens.map((t2) => `"${t2}"`).join(" OR ");
|
|
2422
2426
|
}
|
|
2423
2427
|
function getMemoryDb() {
|
|
2424
2428
|
const dbPath = getResolvedPaths().memoryDbPath;
|
|
@@ -3337,6 +3341,35 @@ function initMemorySchema(db) {
|
|
|
3337
3341
|
}
|
|
3338
3342
|
function enqueueSyncPayload(db, payload) {
|
|
3339
3343
|
db.prepare("INSERT INTO pending_sync (payload) VALUES (?)").run(payload);
|
|
3344
|
+
const overflow = db.prepare(
|
|
3345
|
+
`SELECT id, retry_count, last_error, payload FROM pending_sync
|
|
3346
|
+
WHERE created_at < datetime('now', ?)
|
|
3347
|
+
OR id NOT IN (SELECT id FROM pending_sync ORDER BY created_at DESC, id DESC LIMIT ?)
|
|
3348
|
+
LIMIT 10000`
|
|
3349
|
+
).all(`-${PENDING_SYNC_MAX_AGE_DAYS} days`, PENDING_SYNC_MAX_ITEMS);
|
|
3350
|
+
if (overflow.length > 0) {
|
|
3351
|
+
discardPendingSync(db, overflow, `over the queue limit (${PENDING_SYNC_MAX_ITEMS} items / ${PENDING_SYNC_MAX_AGE_DAYS} days)`);
|
|
3352
|
+
}
|
|
3353
|
+
}
|
|
3354
|
+
function discardPendingSync(db, rows, why) {
|
|
3355
|
+
if (rows.length === 0) return 0;
|
|
3356
|
+
const rescued = rescueLearningFromStalePayloads(db, rows);
|
|
3357
|
+
for (let i = 0; i < rows.length; i += 500) {
|
|
3358
|
+
const ids = rows.slice(i, i + 500).map((r2) => r2.id);
|
|
3359
|
+
db.prepare(`DELETE FROM pending_sync WHERE id IN (${ids.map(() => "?").join(",")})`).run(...ids);
|
|
3360
|
+
}
|
|
3361
|
+
if (rescued > 0) {
|
|
3362
|
+
process.stderr.write(
|
|
3363
|
+
`[massu] RESCUED ${rescued} learned rule(s) from ${rows.length} discarded payload(s) \u2014 they were re-queued for delivery, NOT discarded.
|
|
3364
|
+
`
|
|
3365
|
+
);
|
|
3366
|
+
}
|
|
3367
|
+
process.stderr.write(`[massu] cloud sync: discarded ${rows.length} queued upload(s): ${why}.
|
|
3368
|
+
`);
|
|
3369
|
+
return rows.length;
|
|
3370
|
+
}
|
|
3371
|
+
function allPendingSync(db) {
|
|
3372
|
+
return db.prepare("SELECT id, retry_count, last_error, payload FROM pending_sync").all();
|
|
3340
3373
|
}
|
|
3341
3374
|
function getMemoryMeta(db, key) {
|
|
3342
3375
|
const row = db.prepare("SELECT value FROM memory_meta WHERE key = ?").get(key);
|
|
@@ -3364,9 +3397,9 @@ function upsertObservationEmbedding(db, observationId, vec, modelId, dim) {
|
|
|
3364
3397
|
).run(observationId, modelId, dim, float32ToBlob(vec));
|
|
3365
3398
|
}
|
|
3366
3399
|
function observationEmbedText(title, detail) {
|
|
3367
|
-
const
|
|
3400
|
+
const t2 = (title ?? "").trim();
|
|
3368
3401
|
const d = (detail ?? "").trim();
|
|
3369
|
-
return d ? `${
|
|
3402
|
+
return d ? `${t2}. ${d}` : t2;
|
|
3370
3403
|
}
|
|
3371
3404
|
async function embedMissingObservations(db, opts = {}) {
|
|
3372
3405
|
return runEmbedSweep(
|
|
@@ -3539,7 +3572,9 @@ function dequeuePendingSync(db, limit = 10) {
|
|
|
3539
3572
|
}
|
|
3540
3573
|
}
|
|
3541
3574
|
return db.prepare(
|
|
3542
|
-
|
|
3575
|
+
// Least-retried first: oldest-first retried the same 10 items until they were discarded
|
|
3576
|
+
// while every newer item waited at retry 0 (2026-11-09: 43,492 never attempted).
|
|
3577
|
+
"SELECT id, payload, retry_count FROM pending_sync ORDER BY retry_count ASC, created_at ASC LIMIT ?"
|
|
3543
3578
|
).all(limit);
|
|
3544
3579
|
}
|
|
3545
3580
|
function removePendingSync(db, id) {
|
|
@@ -4212,7 +4247,7 @@ function scoreFailureClasses(db, matchText, filePath, promptContext, weights) {
|
|
|
4212
4247
|
}
|
|
4213
4248
|
return bestMatch;
|
|
4214
4249
|
}
|
|
4215
|
-
var MEMORY_SCHEMA_VERSION, TOOL_COST_EVENTS_RETENTION_DAYS, MAX_DRAFT_TEXT_LEN, FUNNEL_EVENT_OUTBOX_CAP, _temporalColCache, CONSOLIDATION_LESSON_EVIDENCE, MEMORY_FILE_TITLE_PREFIX, MEMORY_FILE_TITLE_LIKE, USAGE_COUNTER_ARMED_KEY;
|
|
4250
|
+
var MEMORY_SCHEMA_VERSION, TOOL_COST_EVENTS_RETENTION_DAYS, PENDING_SYNC_MAX_ITEMS, PENDING_SYNC_MAX_AGE_DAYS, MAX_DRAFT_TEXT_LEN, FUNNEL_EVENT_OUTBOX_CAP, _temporalColCache, CONSOLIDATION_LESSON_EVIDENCE, MEMORY_FILE_TITLE_PREFIX, MEMORY_FILE_TITLE_LIKE, USAGE_COUNTER_ARMED_KEY;
|
|
4216
4251
|
var init_memory_db = __esm({
|
|
4217
4252
|
"src/memory-db.ts"() {
|
|
4218
4253
|
"use strict";
|
|
@@ -4227,6 +4262,8 @@ var init_memory_db = __esm({
|
|
|
4227
4262
|
init_memory_embed_sweep();
|
|
4228
4263
|
MEMORY_SCHEMA_VERSION = 2;
|
|
4229
4264
|
TOOL_COST_EVENTS_RETENTION_DAYS = 90;
|
|
4265
|
+
PENDING_SYNC_MAX_ITEMS = 500;
|
|
4266
|
+
PENDING_SYNC_MAX_AGE_DAYS = 30;
|
|
4230
4267
|
MAX_DRAFT_TEXT_LEN = 16384;
|
|
4231
4268
|
FUNNEL_EVENT_OUTBOX_CAP = 2e4;
|
|
4232
4269
|
_temporalColCache = /* @__PURE__ */ new WeakMap();
|
|
@@ -4560,6 +4597,21 @@ function classifyVisibility(title, detail) {
|
|
|
4560
4597
|
}
|
|
4561
4598
|
|
|
4562
4599
|
// src/cloud-sync.ts
|
|
4600
|
+
var CLOUD_SYNC_REFUSAL_KEY = "cloud_sync_refused";
|
|
4601
|
+
var CLOUD_SYNC_RECHECK_MS = 24 * 60 * 60 * 1e3;
|
|
4602
|
+
function isPermanentSyncStatus(status) {
|
|
4603
|
+
return status >= 400 && status < 500 && status !== 408 && status !== 429;
|
|
4604
|
+
}
|
|
4605
|
+
function readCloudSyncRefusal(db) {
|
|
4606
|
+
try {
|
|
4607
|
+
const raw = getMemoryMeta(db, CLOUD_SYNC_REFUSAL_KEY);
|
|
4608
|
+
if (!raw) return null;
|
|
4609
|
+
const v = JSON.parse(raw);
|
|
4610
|
+
return v && typeof v.status === "number" ? v : null;
|
|
4611
|
+
} catch {
|
|
4612
|
+
return null;
|
|
4613
|
+
}
|
|
4614
|
+
}
|
|
4563
4615
|
var MAX_RETRIES = 3;
|
|
4564
4616
|
var RETRY_DELAYS = [1e3, 2e3, 4e3];
|
|
4565
4617
|
var DEFAULT_CLOUD_REQUEST_TIMEOUT_MS = 15e3;
|
|
@@ -4579,6 +4631,15 @@ async function syncToCloud(db, payload, opts = {}) {
|
|
|
4579
4631
|
if (!endpoint) {
|
|
4580
4632
|
return { success: false, synced: { sessions: 0, observations: 0, analytics: 0, audit: 0 }, error: "No sync endpoint configured" };
|
|
4581
4633
|
}
|
|
4634
|
+
const refusal = readCloudSyncRefusal(db);
|
|
4635
|
+
if (refusal && Date.now() - Date.parse(refusal.at) < CLOUD_SYNC_RECHECK_MS) {
|
|
4636
|
+
return {
|
|
4637
|
+
success: false,
|
|
4638
|
+
paused: true,
|
|
4639
|
+
synced: { sessions: 0, observations: 0, analytics: 0, audit: 0 },
|
|
4640
|
+
error: `cloud sync paused: HTTP ${refusal.status}: ${refusal.reason}`
|
|
4641
|
+
};
|
|
4642
|
+
}
|
|
4582
4643
|
const filteredPayload = {};
|
|
4583
4644
|
if (cloud.sync?.memory !== false) {
|
|
4584
4645
|
filteredPayload.sessions = payload.sessions;
|
|
@@ -4648,9 +4709,12 @@ async function syncToCloud(db, payload, opts = {}) {
|
|
|
4648
4709
|
}
|
|
4649
4710
|
}
|
|
4650
4711
|
let lastError = "";
|
|
4712
|
+
let permanentStatus = null;
|
|
4713
|
+
let permanentReason = "";
|
|
4651
4714
|
const configuredTimeoutMs = cloud.requestTimeoutMs ?? DEFAULT_CLOUD_REQUEST_TIMEOUT_MS;
|
|
4652
4715
|
const deadlineAt = Math.min(Date.now() + SYNC_DEADLINE_MS, opts.deadlineAt ?? Infinity);
|
|
4653
|
-
|
|
4716
|
+
const maxAttempts = Math.max(1, Math.min(opts.maxAttempts ?? MAX_RETRIES, MAX_RETRIES));
|
|
4717
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
4654
4718
|
const remainingMs = deadlineAt - Date.now();
|
|
4655
4719
|
if (remainingMs < MIN_ATTEMPT_BUDGET_MS) {
|
|
4656
4720
|
lastError = lastError || "sync deadline exceeded";
|
|
@@ -4671,16 +4735,25 @@ async function syncToCloud(db, payload, opts = {}) {
|
|
|
4671
4735
|
signal: AbortSignal.timeout(requestTimeoutMs)
|
|
4672
4736
|
});
|
|
4673
4737
|
if (!response.ok) {
|
|
4674
|
-
|
|
4675
|
-
|
|
4738
|
+
const reason = await readErrorReason(response);
|
|
4739
|
+
lastError = `HTTP ${response.status}: ${reason}`;
|
|
4740
|
+
if (isPermanentSyncStatus(response.status)) {
|
|
4741
|
+
permanentStatus = response.status;
|
|
4742
|
+
permanentReason = reason;
|
|
4676
4743
|
break;
|
|
4677
4744
|
}
|
|
4678
|
-
if (attempt <
|
|
4745
|
+
if (attempt < maxAttempts - 1) {
|
|
4679
4746
|
await sleep(RETRY_DELAYS[attempt]);
|
|
4680
4747
|
continue;
|
|
4681
4748
|
}
|
|
4682
4749
|
break;
|
|
4683
4750
|
}
|
|
4751
|
+
if (refusal) {
|
|
4752
|
+
try {
|
|
4753
|
+
setMemoryMeta(db, CLOUD_SYNC_REFUSAL_KEY, "");
|
|
4754
|
+
} catch {
|
|
4755
|
+
}
|
|
4756
|
+
}
|
|
4684
4757
|
const result = await response.json();
|
|
4685
4758
|
return {
|
|
4686
4759
|
success: true,
|
|
@@ -4700,12 +4773,33 @@ async function syncToCloud(db, payload, opts = {}) {
|
|
|
4700
4773
|
if (err instanceof Error && (err.name === "AbortError" || err.name === "TimeoutError")) {
|
|
4701
4774
|
break;
|
|
4702
4775
|
}
|
|
4703
|
-
if (attempt <
|
|
4776
|
+
if (attempt < maxAttempts - 1) {
|
|
4704
4777
|
await sleep(RETRY_DELAYS[attempt]);
|
|
4705
4778
|
continue;
|
|
4706
4779
|
}
|
|
4707
4780
|
}
|
|
4708
4781
|
}
|
|
4782
|
+
if (permanentStatus !== null) {
|
|
4783
|
+
if (permanentStatus === 401 || permanentStatus === 403) {
|
|
4784
|
+
try {
|
|
4785
|
+
setMemoryMeta(db, CLOUD_SYNC_REFUSAL_KEY, JSON.stringify({
|
|
4786
|
+
status: permanentStatus,
|
|
4787
|
+
reason: permanentReason,
|
|
4788
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
4789
|
+
}));
|
|
4790
|
+
discardPendingSync(db, allPendingSync(db), `the cloud refused this account (HTTP ${permanentStatus}: ${permanentReason})`);
|
|
4791
|
+
} catch (err) {
|
|
4792
|
+
process.stderr.write(`[massu] WARNING: could not record the cloud-sync refusal: ${err instanceof Error ? err.message : String(err)}
|
|
4793
|
+
`);
|
|
4794
|
+
}
|
|
4795
|
+
}
|
|
4796
|
+
return {
|
|
4797
|
+
success: false,
|
|
4798
|
+
permanent: true,
|
|
4799
|
+
synced: { sessions: 0, observations: 0, analytics: 0, audit: 0 },
|
|
4800
|
+
error: lastError
|
|
4801
|
+
};
|
|
4802
|
+
}
|
|
4709
4803
|
if (opts.enqueueOnFailure !== false) {
|
|
4710
4804
|
try {
|
|
4711
4805
|
enqueueSyncPayload(db, JSON.stringify(stripLearningFromPayload(payload)));
|
|
@@ -4726,6 +4820,8 @@ async function drainSyncQueue(db, deadlineAt) {
|
|
|
4726
4820
|
const out = { attempted: 0, deferred: 0 };
|
|
4727
4821
|
const config = getConfig();
|
|
4728
4822
|
if (!config.cloud?.enabled || !config.cloud?.apiKey) return out;
|
|
4823
|
+
const refusal = readCloudSyncRefusal(db);
|
|
4824
|
+
if (refusal && Date.now() - Date.parse(refusal.at) < CLOUD_SYNC_RECHECK_MS) return out;
|
|
4729
4825
|
const pending = dequeuePendingSync(db, 10);
|
|
4730
4826
|
for (let i = 0; i < pending.length; i++) {
|
|
4731
4827
|
const item = pending[i];
|
|
@@ -4737,9 +4833,14 @@ async function drainSyncQueue(db, deadlineAt) {
|
|
|
4737
4833
|
out.attempted++;
|
|
4738
4834
|
try {
|
|
4739
4835
|
const payload = JSON.parse(item.payload);
|
|
4740
|
-
const result = await syncToCloud(db, payload, { deadlineAt, enqueueOnFailure: false });
|
|
4836
|
+
const result = await syncToCloud(db, payload, { deadlineAt, enqueueOnFailure: false, maxAttempts: 1 });
|
|
4741
4837
|
if (result.success) {
|
|
4742
4838
|
removePendingSync(db, item.id);
|
|
4839
|
+
} else if (result.paused) {
|
|
4840
|
+
break;
|
|
4841
|
+
} else if (result.permanent) {
|
|
4842
|
+
discardPendingSync(db, [{ id: item.id, retry_count: item.retry_count, last_error: result.error ?? null, payload: item.payload }], result.error ?? "permanent refusal");
|
|
4843
|
+
if (readCloudSyncRefusal(db)) break;
|
|
4743
4844
|
} else {
|
|
4744
4845
|
incrementRetryCount(db, item.id, result.error ?? "Unknown error");
|
|
4745
4846
|
}
|
|
@@ -4750,7 +4851,21 @@ async function drainSyncQueue(db, deadlineAt) {
|
|
|
4750
4851
|
return out;
|
|
4751
4852
|
}
|
|
4752
4853
|
function sleep(ms) {
|
|
4753
|
-
return new Promise((
|
|
4854
|
+
return new Promise((resolve9) => setTimeout(resolve9, ms));
|
|
4855
|
+
}
|
|
4856
|
+
async function readErrorReason(response) {
|
|
4857
|
+
try {
|
|
4858
|
+
const text = (await response.text()).slice(0, 300);
|
|
4859
|
+
try {
|
|
4860
|
+
const j = JSON.parse(text);
|
|
4861
|
+
const r2 = typeof j.error === "string" ? j.error : typeof j.message === "string" ? j.message : "";
|
|
4862
|
+
if (r2) return r2;
|
|
4863
|
+
} catch {
|
|
4864
|
+
}
|
|
4865
|
+
return text.trim() || response.statusText;
|
|
4866
|
+
} catch {
|
|
4867
|
+
return response.statusText;
|
|
4868
|
+
}
|
|
4754
4869
|
}
|
|
4755
4870
|
|
|
4756
4871
|
// src/team-rule-sync.ts
|
|
@@ -5131,6 +5246,13 @@ function assertContainedIn(rootDir, candidate, opts = {}) {
|
|
|
5131
5246
|
}
|
|
5132
5247
|
return abs;
|
|
5133
5248
|
}
|
|
5249
|
+
function isSameRealPath(a, b) {
|
|
5250
|
+
try {
|
|
5251
|
+
return realpathSync(a) === realpathSync(b);
|
|
5252
|
+
} catch {
|
|
5253
|
+
return false;
|
|
5254
|
+
}
|
|
5255
|
+
}
|
|
5134
5256
|
function isContainedIn(rootDir, candidate, opts = {}) {
|
|
5135
5257
|
if (candidate.includes("\0")) return false;
|
|
5136
5258
|
const root = resolve7(rootDir);
|
|
@@ -5953,9 +6075,9 @@ var NOISE_PATTERNS = [
|
|
|
5953
6075
|
/system-reminder/i
|
|
5954
6076
|
];
|
|
5955
6077
|
function isSummarizableSignal(text) {
|
|
5956
|
-
const
|
|
5957
|
-
if (
|
|
5958
|
-
return !NOISE_PATTERNS.some((re) => re.test(
|
|
6078
|
+
const t2 = text.trim();
|
|
6079
|
+
if (t2.length < 25) return false;
|
|
6080
|
+
return !NOISE_PATTERNS.some((re) => re.test(t2));
|
|
5959
6081
|
}
|
|
5960
6082
|
var CREDENTIAL_LABELS = ["api[_-]?key", "secret", "password", "token"];
|
|
5961
6083
|
var LABELLED_CREDENTIAL = new RegExp(
|
|
@@ -6400,9 +6522,403 @@ function backfillQualityScores(db) {
|
|
|
6400
6522
|
|
|
6401
6523
|
// src/cost-reprice.ts
|
|
6402
6524
|
init_config();
|
|
6403
|
-
|
|
6404
|
-
import {
|
|
6525
|
+
import { existsSync as existsSync17 } from "fs";
|
|
6526
|
+
import { dirname as dirname14, join as join12 } from "path";
|
|
6527
|
+
|
|
6528
|
+
// src/db.ts
|
|
6529
|
+
init_db_driver();
|
|
6405
6530
|
import { dirname as dirname13, join as join11 } from "path";
|
|
6531
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync14, readdirSync as readdirSync3, statSync as statSync4 } from "fs";
|
|
6532
|
+
|
|
6533
|
+
// src/preflight.ts
|
|
6534
|
+
init_db_driver();
|
|
6535
|
+
init_config();
|
|
6536
|
+
|
|
6537
|
+
// src/db.ts
|
|
6538
|
+
init_config();
|
|
6539
|
+
|
|
6540
|
+
// src/lib/sql-table-names.ts
|
|
6541
|
+
init_config();
|
|
6542
|
+
function t(suffix) {
|
|
6543
|
+
return `${getConfig().toolPrefix}_${suffix}`;
|
|
6544
|
+
}
|
|
6545
|
+
|
|
6546
|
+
// src/db.ts
|
|
6547
|
+
function getDataDb() {
|
|
6548
|
+
const dbPath = getResolvedPaths().dataDbPath;
|
|
6549
|
+
const dir = dirname13(dbPath);
|
|
6550
|
+
if (!existsSync16(dir)) {
|
|
6551
|
+
mkdirSync14(dir, { recursive: true });
|
|
6552
|
+
}
|
|
6553
|
+
const db = openDatabase2(dbPath);
|
|
6554
|
+
db.pragma("journal_mode = WAL");
|
|
6555
|
+
db.pragma("foreign_keys = ON");
|
|
6556
|
+
initDataSchema(db);
|
|
6557
|
+
return db;
|
|
6558
|
+
}
|
|
6559
|
+
function initDataSchema(db) {
|
|
6560
|
+
db.exec(`
|
|
6561
|
+
CREATE TABLE IF NOT EXISTS ${t("imports")} (
|
|
6562
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6563
|
+
source_file TEXT NOT NULL,
|
|
6564
|
+
target_file TEXT NOT NULL,
|
|
6565
|
+
import_type TEXT NOT NULL CHECK(import_type IN ('named', 'default', 'namespace', 'side_effect', 'dynamic')),
|
|
6566
|
+
imported_names TEXT NOT NULL DEFAULT '[]',
|
|
6567
|
+
line INTEGER NOT NULL DEFAULT 0
|
|
6568
|
+
);
|
|
6569
|
+
|
|
6570
|
+
CREATE INDEX IF NOT EXISTS idx_massu_imports_source ON ${t("imports")}(source_file);
|
|
6571
|
+
CREATE INDEX IF NOT EXISTS idx_massu_imports_target ON ${t("imports")}(target_file);
|
|
6572
|
+
|
|
6573
|
+
CREATE TABLE IF NOT EXISTS ${t("trpc_procedures")} (
|
|
6574
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6575
|
+
router_file TEXT NOT NULL,
|
|
6576
|
+
router_name TEXT NOT NULL,
|
|
6577
|
+
procedure_name TEXT NOT NULL,
|
|
6578
|
+
procedure_type TEXT NOT NULL CHECK(procedure_type IN ('query', 'mutation')),
|
|
6579
|
+
has_ui_caller INTEGER NOT NULL DEFAULT 0
|
|
6580
|
+
);
|
|
6581
|
+
|
|
6582
|
+
CREATE INDEX IF NOT EXISTS idx_massu_trpc_router ON ${t("trpc_procedures")}(router_name);
|
|
6583
|
+
|
|
6584
|
+
CREATE TABLE IF NOT EXISTS ${t("trpc_call_sites")} (
|
|
6585
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6586
|
+
procedure_id INTEGER NOT NULL,
|
|
6587
|
+
file TEXT NOT NULL,
|
|
6588
|
+
line INTEGER NOT NULL DEFAULT 0,
|
|
6589
|
+
call_pattern TEXT NOT NULL,
|
|
6590
|
+
FOREIGN KEY (procedure_id) REFERENCES ${t("trpc_procedures")}(id)
|
|
6591
|
+
);
|
|
6592
|
+
|
|
6593
|
+
CREATE INDEX IF NOT EXISTS idx_massu_call_sites_proc ON ${t("trpc_call_sites")}(procedure_id);
|
|
6594
|
+
|
|
6595
|
+
CREATE TABLE IF NOT EXISTS ${t("page_deps")} (
|
|
6596
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6597
|
+
page_file TEXT NOT NULL,
|
|
6598
|
+
route TEXT NOT NULL,
|
|
6599
|
+
portal TEXT NOT NULL DEFAULT 'unknown',
|
|
6600
|
+
components TEXT NOT NULL DEFAULT '[]',
|
|
6601
|
+
hooks TEXT NOT NULL DEFAULT '[]',
|
|
6602
|
+
routers TEXT NOT NULL DEFAULT '[]',
|
|
6603
|
+
tables_touched TEXT NOT NULL DEFAULT '[]'
|
|
6604
|
+
);
|
|
6605
|
+
|
|
6606
|
+
CREATE INDEX IF NOT EXISTS idx_massu_page_deps_page ON ${t("page_deps")}(page_file);
|
|
6607
|
+
|
|
6608
|
+
CREATE TABLE IF NOT EXISTS ${t("middleware_tree")} (
|
|
6609
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6610
|
+
file TEXT NOT NULL UNIQUE
|
|
6611
|
+
);
|
|
6612
|
+
|
|
6613
|
+
CREATE TABLE IF NOT EXISTS ${t("meta")} (
|
|
6614
|
+
key TEXT PRIMARY KEY,
|
|
6615
|
+
value TEXT NOT NULL
|
|
6616
|
+
);
|
|
6617
|
+
|
|
6618
|
+
-- ============================================================
|
|
6619
|
+
-- Python Code Intelligence Tables
|
|
6620
|
+
-- ============================================================
|
|
6621
|
+
|
|
6622
|
+
CREATE TABLE IF NOT EXISTS ${t("py_imports")} (
|
|
6623
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6624
|
+
source_file TEXT NOT NULL,
|
|
6625
|
+
target_file TEXT NOT NULL,
|
|
6626
|
+
import_type TEXT NOT NULL CHECK(import_type IN ('absolute', 'relative', 'from_absolute', 'from_relative')),
|
|
6627
|
+
imported_names TEXT NOT NULL DEFAULT '[]',
|
|
6628
|
+
line INTEGER NOT NULL DEFAULT 0
|
|
6629
|
+
);
|
|
6630
|
+
CREATE INDEX IF NOT EXISTS idx_massu_py_imports_source ON ${t("py_imports")}(source_file);
|
|
6631
|
+
CREATE INDEX IF NOT EXISTS idx_massu_py_imports_target ON ${t("py_imports")}(target_file);
|
|
6632
|
+
|
|
6633
|
+
CREATE TABLE IF NOT EXISTS ${t("py_meta")} (
|
|
6634
|
+
key TEXT PRIMARY KEY,
|
|
6635
|
+
value TEXT NOT NULL
|
|
6636
|
+
);
|
|
6637
|
+
|
|
6638
|
+
CREATE TABLE IF NOT EXISTS ${t("py_routes")} (
|
|
6639
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6640
|
+
file TEXT NOT NULL,
|
|
6641
|
+
method TEXT NOT NULL,
|
|
6642
|
+
path TEXT NOT NULL,
|
|
6643
|
+
function_name TEXT NOT NULL,
|
|
6644
|
+
dependencies TEXT NOT NULL DEFAULT '[]',
|
|
6645
|
+
request_model TEXT,
|
|
6646
|
+
response_model TEXT,
|
|
6647
|
+
is_authenticated INTEGER NOT NULL DEFAULT 0,
|
|
6648
|
+
line INTEGER NOT NULL DEFAULT 0
|
|
6649
|
+
);
|
|
6650
|
+
CREATE INDEX IF NOT EXISTS idx_massu_py_routes_path ON ${t("py_routes")}(path);
|
|
6651
|
+
CREATE INDEX IF NOT EXISTS idx_massu_py_routes_file ON ${t("py_routes")}(file);
|
|
6652
|
+
|
|
6653
|
+
CREATE TABLE IF NOT EXISTS ${t("py_route_callers")} (
|
|
6654
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6655
|
+
route_id INTEGER NOT NULL REFERENCES ${t("py_routes")}(id) ON DELETE CASCADE,
|
|
6656
|
+
frontend_file TEXT NOT NULL,
|
|
6657
|
+
line INTEGER NOT NULL DEFAULT 0,
|
|
6658
|
+
call_pattern TEXT NOT NULL
|
|
6659
|
+
);
|
|
6660
|
+
CREATE INDEX IF NOT EXISTS idx_massu_py_route_callers_route ON ${t("py_route_callers")}(route_id);
|
|
6661
|
+
|
|
6662
|
+
CREATE TABLE IF NOT EXISTS ${t("py_models")} (
|
|
6663
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6664
|
+
class_name TEXT NOT NULL,
|
|
6665
|
+
table_name TEXT,
|
|
6666
|
+
file TEXT NOT NULL,
|
|
6667
|
+
line INTEGER NOT NULL DEFAULT 0,
|
|
6668
|
+
columns TEXT NOT NULL DEFAULT '[]',
|
|
6669
|
+
relationships TEXT NOT NULL DEFAULT '[]',
|
|
6670
|
+
foreign_keys TEXT NOT NULL DEFAULT '[]'
|
|
6671
|
+
);
|
|
6672
|
+
CREATE INDEX IF NOT EXISTS idx_massu_py_models_file ON ${t("py_models")}(file);
|
|
6673
|
+
CREATE INDEX IF NOT EXISTS idx_massu_py_models_table ON ${t("py_models")}(table_name);
|
|
6674
|
+
|
|
6675
|
+
CREATE TABLE IF NOT EXISTS ${t("py_fk_edges")} (
|
|
6676
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6677
|
+
source_table TEXT NOT NULL,
|
|
6678
|
+
source_column TEXT NOT NULL,
|
|
6679
|
+
target_table TEXT NOT NULL,
|
|
6680
|
+
target_column TEXT NOT NULL
|
|
6681
|
+
);
|
|
6682
|
+
CREATE INDEX IF NOT EXISTS idx_massu_py_fk_source ON ${t("py_fk_edges")}(source_table);
|
|
6683
|
+
CREATE INDEX IF NOT EXISTS idx_massu_py_fk_target ON ${t("py_fk_edges")}(target_table);
|
|
6684
|
+
|
|
6685
|
+
CREATE TABLE IF NOT EXISTS ${t("py_migrations")} (
|
|
6686
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6687
|
+
revision TEXT NOT NULL UNIQUE,
|
|
6688
|
+
down_revision TEXT,
|
|
6689
|
+
file TEXT NOT NULL,
|
|
6690
|
+
description TEXT,
|
|
6691
|
+
operations TEXT NOT NULL DEFAULT '[]',
|
|
6692
|
+
is_head INTEGER NOT NULL DEFAULT 0
|
|
6693
|
+
);
|
|
6694
|
+
CREATE INDEX IF NOT EXISTS idx_massu_py_migrations_rev ON ${t("py_migrations")}(revision);
|
|
6695
|
+
|
|
6696
|
+
-- ============================================================
|
|
6697
|
+
-- Sentinel: Feature Registry Tables
|
|
6698
|
+
-- ============================================================
|
|
6699
|
+
|
|
6700
|
+
-- Core feature definition
|
|
6701
|
+
CREATE TABLE IF NOT EXISTS ${t("sentinel")} (
|
|
6702
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6703
|
+
feature_key TEXT UNIQUE NOT NULL,
|
|
6704
|
+
domain TEXT NOT NULL,
|
|
6705
|
+
subdomain TEXT,
|
|
6706
|
+
title TEXT NOT NULL,
|
|
6707
|
+
description TEXT,
|
|
6708
|
+
status TEXT NOT NULL DEFAULT 'active'
|
|
6709
|
+
CHECK(status IN ('planned', 'active', 'deprecated', 'removed')),
|
|
6710
|
+
priority TEXT DEFAULT 'standard'
|
|
6711
|
+
CHECK(priority IN ('critical', 'standard', 'nice-to-have')),
|
|
6712
|
+
portal_scope TEXT DEFAULT '[]',
|
|
6713
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
6714
|
+
updated_at TEXT DEFAULT (datetime('now')),
|
|
6715
|
+
removed_at TEXT,
|
|
6716
|
+
removed_reason TEXT
|
|
6717
|
+
);
|
|
6718
|
+
|
|
6719
|
+
CREATE INDEX IF NOT EXISTS idx_sentinel_domain ON ${t("sentinel")}(domain);
|
|
6720
|
+
CREATE INDEX IF NOT EXISTS idx_sentinel_status ON ${t("sentinel")}(status);
|
|
6721
|
+
CREATE INDEX IF NOT EXISTS idx_sentinel_key ON ${t("sentinel")}(feature_key);
|
|
6722
|
+
|
|
6723
|
+
-- Feature-to-component mapping (many-to-many)
|
|
6724
|
+
CREATE TABLE IF NOT EXISTS ${t("sentinel_components")} (
|
|
6725
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6726
|
+
feature_id INTEGER NOT NULL REFERENCES ${t("sentinel")}(id) ON DELETE CASCADE,
|
|
6727
|
+
component_file TEXT NOT NULL,
|
|
6728
|
+
component_name TEXT,
|
|
6729
|
+
role TEXT DEFAULT 'implementation'
|
|
6730
|
+
CHECK(role IN ('implementation', 'ui', 'data', 'utility')),
|
|
6731
|
+
is_primary BOOLEAN DEFAULT 0,
|
|
6732
|
+
UNIQUE(feature_id, component_file, component_name)
|
|
6733
|
+
);
|
|
6734
|
+
|
|
6735
|
+
-- Feature-to-procedure mapping (many-to-many)
|
|
6736
|
+
CREATE TABLE IF NOT EXISTS ${t("sentinel_procedures")} (
|
|
6737
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6738
|
+
feature_id INTEGER NOT NULL REFERENCES ${t("sentinel")}(id) ON DELETE CASCADE,
|
|
6739
|
+
router_name TEXT NOT NULL,
|
|
6740
|
+
procedure_name TEXT NOT NULL,
|
|
6741
|
+
procedure_type TEXT,
|
|
6742
|
+
UNIQUE(feature_id, router_name, procedure_name)
|
|
6743
|
+
);
|
|
6744
|
+
|
|
6745
|
+
-- Feature-to-page mapping (where feature is accessible)
|
|
6746
|
+
CREATE TABLE IF NOT EXISTS ${t("sentinel_pages")} (
|
|
6747
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6748
|
+
feature_id INTEGER NOT NULL REFERENCES ${t("sentinel")}(id) ON DELETE CASCADE,
|
|
6749
|
+
page_route TEXT NOT NULL,
|
|
6750
|
+
portal TEXT,
|
|
6751
|
+
UNIQUE(feature_id, page_route, portal)
|
|
6752
|
+
);
|
|
6753
|
+
|
|
6754
|
+
-- Feature dependency graph
|
|
6755
|
+
CREATE TABLE IF NOT EXISTS ${t("sentinel_deps")} (
|
|
6756
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6757
|
+
feature_id INTEGER NOT NULL REFERENCES ${t("sentinel")}(id) ON DELETE CASCADE,
|
|
6758
|
+
depends_on_feature_id INTEGER NOT NULL REFERENCES ${t("sentinel")}(id) ON DELETE CASCADE,
|
|
6759
|
+
dependency_type TEXT DEFAULT 'requires'
|
|
6760
|
+
CHECK(dependency_type IN ('requires', 'enhances', 'replaces')),
|
|
6761
|
+
UNIQUE(feature_id, depends_on_feature_id)
|
|
6762
|
+
);
|
|
6763
|
+
|
|
6764
|
+
-- Feature change log (audit trail)
|
|
6765
|
+
CREATE TABLE IF NOT EXISTS ${t("sentinel_changelog")} (
|
|
6766
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6767
|
+
feature_id INTEGER NOT NULL REFERENCES ${t("sentinel")}(id) ON DELETE CASCADE,
|
|
6768
|
+
change_type TEXT NOT NULL,
|
|
6769
|
+
changed_by TEXT,
|
|
6770
|
+
change_detail TEXT,
|
|
6771
|
+
commit_hash TEXT,
|
|
6772
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
6773
|
+
);
|
|
6774
|
+
|
|
6775
|
+
CREATE INDEX IF NOT EXISTS idx_sentinel_components_file ON ${t("sentinel_components")}(component_file);
|
|
6776
|
+
CREATE INDEX IF NOT EXISTS idx_sentinel_procedures_router ON ${t("sentinel_procedures")}(router_name);
|
|
6777
|
+
CREATE INDEX IF NOT EXISTS idx_sentinel_pages_route ON ${t("sentinel_pages")}(page_route);
|
|
6778
|
+
CREATE INDEX IF NOT EXISTS idx_sentinel_changelog_feature ON ${t("sentinel_changelog")}(feature_id);
|
|
6779
|
+
`);
|
|
6780
|
+
db.exec(`
|
|
6781
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS ${t("sentinel_fts")} USING fts5(
|
|
6782
|
+
feature_key, title, description, domain, subdomain,
|
|
6783
|
+
content=${t("sentinel")}, content_rowid=id
|
|
6784
|
+
);
|
|
6785
|
+
`);
|
|
6786
|
+
db.exec(`
|
|
6787
|
+
CREATE TRIGGER IF NOT EXISTS massu_sentinel_ai AFTER INSERT ON ${t("sentinel")} BEGIN
|
|
6788
|
+
INSERT INTO ${t("sentinel_fts")}(rowid, feature_key, title, description, domain, subdomain)
|
|
6789
|
+
VALUES (new.id, new.feature_key, new.title, new.description, new.domain, new.subdomain);
|
|
6790
|
+
END;
|
|
6791
|
+
|
|
6792
|
+
CREATE TRIGGER IF NOT EXISTS massu_sentinel_ad AFTER DELETE ON ${t("sentinel")} BEGIN
|
|
6793
|
+
INSERT INTO ${t("sentinel_fts")}(${t("sentinel_fts")}, rowid, feature_key, title, description, domain, subdomain)
|
|
6794
|
+
VALUES ('delete', old.id, old.feature_key, old.title, old.description, old.domain, old.subdomain);
|
|
6795
|
+
END;
|
|
6796
|
+
|
|
6797
|
+
CREATE TRIGGER IF NOT EXISTS massu_sentinel_au AFTER UPDATE ON ${t("sentinel")} BEGIN
|
|
6798
|
+
INSERT INTO ${t("sentinel_fts")}(${t("sentinel_fts")}, rowid, feature_key, title, description, domain, subdomain)
|
|
6799
|
+
VALUES ('delete', old.id, old.feature_key, old.title, old.description, old.domain, old.subdomain);
|
|
6800
|
+
INSERT INTO ${t("sentinel_fts")}(rowid, feature_key, title, description, domain, subdomain)
|
|
6801
|
+
VALUES (new.id, new.feature_key, new.title, new.description, new.domain, new.subdomain);
|
|
6802
|
+
END;
|
|
6803
|
+
`);
|
|
6804
|
+
}
|
|
6805
|
+
|
|
6806
|
+
// src/feature-cost-attribution.ts
|
|
6807
|
+
import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve8 } from "path";
|
|
6808
|
+
|
|
6809
|
+
// src/sentinel-db.ts
|
|
6810
|
+
init_config();
|
|
6811
|
+
init_memory_db();
|
|
6812
|
+
var PROJECT_ROOT = getProjectRoot();
|
|
6813
|
+
function parsePortalScope(raw) {
|
|
6814
|
+
if (!raw) return [];
|
|
6815
|
+
try {
|
|
6816
|
+
return JSON.parse(raw);
|
|
6817
|
+
} catch {
|
|
6818
|
+
return [];
|
|
6819
|
+
}
|
|
6820
|
+
}
|
|
6821
|
+
function toFeature(row) {
|
|
6822
|
+
return {
|
|
6823
|
+
id: row.id,
|
|
6824
|
+
feature_key: row.feature_key,
|
|
6825
|
+
domain: row.domain,
|
|
6826
|
+
subdomain: row.subdomain || null,
|
|
6827
|
+
title: row.title,
|
|
6828
|
+
description: row.description || null,
|
|
6829
|
+
status: row.status,
|
|
6830
|
+
priority: row.priority,
|
|
6831
|
+
portal_scope: parsePortalScope(row.portal_scope),
|
|
6832
|
+
created_at: row.created_at,
|
|
6833
|
+
updated_at: row.updated_at,
|
|
6834
|
+
removed_at: row.removed_at || null,
|
|
6835
|
+
removed_reason: row.removed_reason || null
|
|
6836
|
+
};
|
|
6837
|
+
}
|
|
6838
|
+
function getFeaturesByFile(db, filePath) {
|
|
6839
|
+
const rows = db.prepare(`
|
|
6840
|
+
SELECT DISTINCT s.* FROM ${t("sentinel")} s
|
|
6841
|
+
JOIN ${t("sentinel_components")} c ON c.feature_id = s.id
|
|
6842
|
+
WHERE c.component_file = ?
|
|
6843
|
+
ORDER BY s.feature_key
|
|
6844
|
+
LIMIT 1000
|
|
6845
|
+
`).all(filePath);
|
|
6846
|
+
return rows.map(toFeature);
|
|
6847
|
+
}
|
|
6848
|
+
|
|
6849
|
+
// src/feature-cost-attribution.ts
|
|
6850
|
+
var UNATTRIBUTED_FEATURE = "(no registered feature)";
|
|
6851
|
+
var MAX_SESSION_FILES = 2e4;
|
|
6852
|
+
function sessionFileTouches(memDb, sessionId, projectRoot) {
|
|
6853
|
+
const rows = memDb.prepare(
|
|
6854
|
+
`SELECT j.value AS file, COUNT(*) AS n
|
|
6855
|
+
FROM tool_call_details t, json_each(t.files_involved) j
|
|
6856
|
+
WHERE t.session_id = ? AND json_valid(t.files_involved)
|
|
6857
|
+
AND json_type(t.files_involved) = 'array' AND j.type = 'text' AND j.value <> ''
|
|
6858
|
+
GROUP BY j.value
|
|
6859
|
+
LIMIT ?`
|
|
6860
|
+
).all(sessionId, MAX_SESSION_FILES + 1);
|
|
6861
|
+
if (rows.length > MAX_SESSION_FILES) {
|
|
6862
|
+
throw new Error(`session ${sessionId} touched more than ${MAX_SESSION_FILES} distinct files; not allocating a truncated list`);
|
|
6863
|
+
}
|
|
6864
|
+
const touches = /* @__PURE__ */ new Map();
|
|
6865
|
+
for (const { file, n } of rows) {
|
|
6866
|
+
const abs = isAbsolute2(file) ? file : resolve8(projectRoot, file);
|
|
6867
|
+
const rel = relative2(projectRoot, abs);
|
|
6868
|
+
const key = rel.startsWith("..") || isAbsolute2(rel) ? abs : rel;
|
|
6869
|
+
touches.set(key, (touches.get(key) ?? 0) + Number(n));
|
|
6870
|
+
}
|
|
6871
|
+
return touches;
|
|
6872
|
+
}
|
|
6873
|
+
function allocateSessionCost(touches, featuresOf, totals) {
|
|
6874
|
+
const units = /* @__PURE__ */ new Map();
|
|
6875
|
+
let all = 0;
|
|
6876
|
+
for (const [file, n] of touches) {
|
|
6877
|
+
all += n;
|
|
6878
|
+
const features = [...new Set(featuresOf(file))];
|
|
6879
|
+
if (features.length === 0) {
|
|
6880
|
+
units.set(UNATTRIBUTED_FEATURE, (units.get(UNATTRIBUTED_FEATURE) ?? 0) + n);
|
|
6881
|
+
} else {
|
|
6882
|
+
for (const k of features) units.set(k, (units.get(k) ?? 0) + n / features.length);
|
|
6883
|
+
}
|
|
6884
|
+
}
|
|
6885
|
+
if (all === 0) return [{ featureKey: UNATTRIBUTED_FEATURE, share: 1, tokens: totals.tokens, costUsd: totals.costUsd }];
|
|
6886
|
+
return [...units].map(([featureKey, u]) => ({
|
|
6887
|
+
featureKey,
|
|
6888
|
+
share: u / all,
|
|
6889
|
+
tokens: Math.round(totals.tokens * u / all),
|
|
6890
|
+
costUsd: totals.costUsd * u / all
|
|
6891
|
+
})).sort((a, b) => b.costUsd - a.costUsd || a.featureKey.localeCompare(b.featureKey));
|
|
6892
|
+
}
|
|
6893
|
+
function attributeSessionCost(memDb, dataDb, sessionId, projectRoot) {
|
|
6894
|
+
const cost = memDb.prepare(
|
|
6895
|
+
`SELECT total_tokens, estimated_cost_usd, cost_status FROM session_costs WHERE session_id = ?`
|
|
6896
|
+
).get(sessionId);
|
|
6897
|
+
const replace = memDb.transaction((rows2) => {
|
|
6898
|
+
memDb.prepare("DELETE FROM feature_costs WHERE session_id = ?").run(sessionId);
|
|
6899
|
+
const ins = memDb.prepare(
|
|
6900
|
+
"INSERT INTO feature_costs (feature_key, session_id, tokens_used, estimated_cost_usd) VALUES (?, ?, ?, ?)"
|
|
6901
|
+
);
|
|
6902
|
+
for (const r2 of rows2) ins.run(r2.featureKey, sessionId, r2.tokens, r2.costUsd);
|
|
6903
|
+
});
|
|
6904
|
+
if (!cost || cost.cost_status === "unknown") {
|
|
6905
|
+
replace([]);
|
|
6906
|
+
return [];
|
|
6907
|
+
}
|
|
6908
|
+
const touches = sessionFileTouches(memDb, sessionId, projectRoot);
|
|
6909
|
+
const featuresOf = (file) => {
|
|
6910
|
+
const keys = getFeaturesByFile(dataDb, file).map((f) => f.feature_key);
|
|
6911
|
+
if (!isAbsolute2(file)) keys.push(...getFeaturesByFile(dataDb, resolve8(projectRoot, file)).map((f) => f.feature_key));
|
|
6912
|
+
return keys;
|
|
6913
|
+
};
|
|
6914
|
+
const rows = allocateSessionCost(touches, featuresOf, { tokens: cost.total_tokens, costUsd: cost.estimated_cost_usd });
|
|
6915
|
+
replace(rows);
|
|
6916
|
+
return rows;
|
|
6917
|
+
}
|
|
6918
|
+
|
|
6919
|
+
// src/cost-reprice.ts
|
|
6920
|
+
init_hook_failure_signal();
|
|
6921
|
+
init_memory_db();
|
|
6406
6922
|
|
|
6407
6923
|
// src/cost-tracker.ts
|
|
6408
6924
|
init_config();
|
|
@@ -6472,8 +6988,8 @@ function tokenCounts(u) {
|
|
|
6472
6988
|
}
|
|
6473
6989
|
function priceResponse(price, u) {
|
|
6474
6990
|
if (!price) return null;
|
|
6475
|
-
const
|
|
6476
|
-
const prompt =
|
|
6991
|
+
const t2 = tokenCounts(u);
|
|
6992
|
+
const prompt = t2.input + t2.cacheRead + t2.cacheWrite5m + t2.cacheWrite1h;
|
|
6477
6993
|
let rates = price.longPrompt && prompt > price.longPrompt.overTokens ? price.longPrompt.rates : price;
|
|
6478
6994
|
if (u.speed === "fast" && price.fast) {
|
|
6479
6995
|
const k = price.fast.input / rates.input;
|
|
@@ -6486,7 +7002,7 @@ function priceResponse(price, u) {
|
|
|
6486
7002
|
};
|
|
6487
7003
|
}
|
|
6488
7004
|
const geo = u.inference_geo === "us" ? price.usGeoMultiplier ?? 1 : 1;
|
|
6489
|
-
const dollars =
|
|
7005
|
+
const dollars = t2.input * rates.input + t2.output * rates.output + t2.cacheRead * rates.cacheRead + t2.cacheWrite5m * rates.cacheWrite5m + t2.cacheWrite1h * rates.cacheWrite1h;
|
|
6490
7006
|
return dollars / 1e6 * geo;
|
|
6491
7007
|
}
|
|
6492
7008
|
|
|
@@ -6520,12 +7036,12 @@ function costTranscriptEntries(entries, opts = {}) {
|
|
|
6520
7036
|
out.lastMessageId = msg.id;
|
|
6521
7037
|
}
|
|
6522
7038
|
const usage = msg.usage;
|
|
6523
|
-
const
|
|
6524
|
-
if (
|
|
6525
|
-
out.inputTokens +=
|
|
6526
|
-
out.outputTokens +=
|
|
6527
|
-
out.cacheReadTokens +=
|
|
6528
|
-
out.cacheWriteTokens +=
|
|
7039
|
+
const t2 = tokenCounts(usage);
|
|
7040
|
+
if (t2.input + t2.output + t2.cacheRead + t2.cacheWrite5m + t2.cacheWrite1h === 0) continue;
|
|
7041
|
+
out.inputTokens += t2.input;
|
|
7042
|
+
out.outputTokens += t2.output;
|
|
7043
|
+
out.cacheReadTokens += t2.cacheRead;
|
|
7044
|
+
out.cacheWriteTokens += t2.cacheWrite5m + t2.cacheWrite1h;
|
|
6529
7045
|
const dollars = priceResponse(resolveModelPrice(msg.model, overrides), usage);
|
|
6530
7046
|
if (dollars === null) {
|
|
6531
7047
|
out.unpricedResponses++;
|
|
@@ -6605,7 +7121,7 @@ function markSessionCostUnknown(db, sessionId) {
|
|
|
6605
7121
|
var cursorKey = (sid) => `cost_processed_line:${sid}`;
|
|
6606
7122
|
var lastIdKey = (sid) => `cost_last_message:${sid}`;
|
|
6607
7123
|
var sessionLeaseKey = (sid) => `session_end_lease:${sid}`;
|
|
6608
|
-
async function updateSessionCost(db, sessionId, transcriptPath) {
|
|
7124
|
+
async function updateSessionCost(db, sessionId, transcriptPath, attribute = attributeFeatureCosts) {
|
|
6609
7125
|
const row = db.prepare("SELECT pricing_version FROM session_costs WHERE session_id = ?").get(sessionId);
|
|
6610
7126
|
const cursor = Number(getMemoryMeta(db, cursorKey(sessionId)) ?? "0") || 0;
|
|
6611
7127
|
const incremental = row?.pricing_version === PRICING_VERSION && cursor > 0;
|
|
@@ -6617,11 +7133,23 @@ async function updateSessionCost(db, sessionId, transcriptPath) {
|
|
|
6617
7133
|
storeSessionCost(db, sessionId, slice, incremental ? "add" : "replace");
|
|
6618
7134
|
setMemoryMeta(db, cursorKey(sessionId), String(totalLines));
|
|
6619
7135
|
if (slice.lastMessageId) setMemoryMeta(db, lastIdKey(sessionId), slice.lastMessageId);
|
|
7136
|
+
attribute(db, sessionId);
|
|
6620
7137
|
}
|
|
7138
|
+
var attributeFeatureCosts = (db, sessionId) => {
|
|
7139
|
+
let dataDb = null;
|
|
7140
|
+
try {
|
|
7141
|
+
dataDb = getDataDb();
|
|
7142
|
+
attributeSessionCost(db, dataDb, sessionId, getProjectRoot());
|
|
7143
|
+
} catch (err) {
|
|
7144
|
+
recordHookFailure("cost:feature-attribution", err, { sessionId });
|
|
7145
|
+
} finally {
|
|
7146
|
+
dataDb?.close();
|
|
7147
|
+
}
|
|
7148
|
+
};
|
|
6621
7149
|
function defaultTranscriptFor(sessionId) {
|
|
6622
7150
|
if (!/^[A-Za-z0-9_-]+$/.test(sessionId)) return null;
|
|
6623
|
-
const path =
|
|
6624
|
-
return
|
|
7151
|
+
const path = join12(dirname14(getResolvedPaths().memoryDir), `${sessionId}.jsonl`);
|
|
7152
|
+
return existsSync17(path) ? path : null;
|
|
6625
7153
|
}
|
|
6626
7154
|
async function repriceStoredSessions(db, opts = {}) {
|
|
6627
7155
|
const deadlineAt = opts.deadlineAt ?? Infinity;
|
|
@@ -6645,10 +7173,11 @@ async function repriceStoredSessions(db, opts = {}) {
|
|
|
6645
7173
|
try {
|
|
6646
7174
|
const path = find(sid);
|
|
6647
7175
|
if (path) {
|
|
6648
|
-
await updateSessionCost(db, sid, path);
|
|
7176
|
+
await updateSessionCost(db, sid, path, opts.attribute);
|
|
6649
7177
|
out.repriced++;
|
|
6650
7178
|
} else {
|
|
6651
7179
|
markSessionCostUnknown(db, sid);
|
|
7180
|
+
(opts.attribute ?? attributeFeatureCosts)(db, sid);
|
|
6652
7181
|
out.markedUnknown++;
|
|
6653
7182
|
}
|
|
6654
7183
|
} catch (err) {
|
|
@@ -6781,8 +7310,8 @@ init_memory_vector();
|
|
|
6781
7310
|
function temporalPredicate(asOf, includeSuperseded, prefix) {
|
|
6782
7311
|
const c = (name) => `${prefix}${name}`;
|
|
6783
7312
|
if (asOf != null && Number.isFinite(asOf)) {
|
|
6784
|
-
const
|
|
6785
|
-
return `${c("ingested_at_epoch")} <= ${
|
|
7313
|
+
const t2 = Math.floor(Number(asOf) / 1e3);
|
|
7314
|
+
return `${c("ingested_at_epoch")} <= ${t2} AND (${c("expired_at_epoch")} IS NULL OR ${c("expired_at_epoch")} > ${t2}) AND ${c("valid_from_epoch")} <= ${t2} AND (${c("valid_to_epoch")} IS NULL OR ${c("valid_to_epoch")} > ${t2})`;
|
|
6786
7315
|
}
|
|
6787
7316
|
if (!includeSuperseded) {
|
|
6788
7317
|
return `${c("expired_at_epoch")} IS NULL`;
|
|
@@ -6878,7 +7407,7 @@ function collectObservations(memDb, queryText, pool, nowMs, loadVec, modelId, di
|
|
|
6878
7407
|
}
|
|
6879
7408
|
function collectLikeSource(db, source, cfg, queryText, pool, nowMs) {
|
|
6880
7409
|
const map = /* @__PURE__ */ new Map();
|
|
6881
|
-
const tokens = queryText.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((
|
|
7410
|
+
const tokens = queryText.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((t2) => t2.length >= 3).slice(0, 8);
|
|
6882
7411
|
const ageExpr = (created) => {
|
|
6883
7412
|
const ms = cfg.createdIsEpoch ? Number(created) * 1e3 : Date.parse(String(created));
|
|
6884
7413
|
return Number.isFinite(ms) ? ageDaysFrom(ms, nowMs) : 0;
|
|
@@ -6886,7 +7415,7 @@ function collectLikeSource(db, source, cfg, queryText, pool, nowMs) {
|
|
|
6886
7415
|
if (tokens.length > 0) {
|
|
6887
7416
|
const likeClause = cfg.searchCols.map((col) => tokens.map(() => `${col} LIKE ?`).join(" OR ")).join(" OR ");
|
|
6888
7417
|
const params = [];
|
|
6889
|
-
for (const _col of cfg.searchCols) for (const
|
|
7418
|
+
for (const _col of cfg.searchCols) for (const t2 of tokens) params.push(`%${t2}%`);
|
|
6890
7419
|
try {
|
|
6891
7420
|
const rows = db.prepare(
|
|
6892
7421
|
`SELECT ${cfg.idCol} AS id, ${cfg.titleExpr} AS title,
|
|
@@ -7304,8 +7833,8 @@ ${d.decision}`.trim(),
|
|
|
7304
7833
|
// src/memory-consolidate.ts
|
|
7305
7834
|
init_memory_db();
|
|
7306
7835
|
import { createHash as createHash6 } from "crypto";
|
|
7307
|
-
import { existsSync as
|
|
7308
|
-
import { join as
|
|
7836
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync15, writeFileSync as writeFileSync10 } from "fs";
|
|
7837
|
+
import { join as join13 } from "path";
|
|
7309
7838
|
init_memory_embedder();
|
|
7310
7839
|
init_memory_vector();
|
|
7311
7840
|
var LEASE_KEY = "consolidate_lease";
|
|
@@ -7466,7 +7995,7 @@ ${r2.detail ?? ""}`.trim());
|
|
|
7466
7995
|
}
|
|
7467
7996
|
clusters.push(cluster);
|
|
7468
7997
|
}
|
|
7469
|
-
const candidateDir =
|
|
7998
|
+
const candidateDir = join13(projectRoot, ".massu", "rule-candidates");
|
|
7470
7999
|
let promoted = 0;
|
|
7471
8000
|
for (const cluster of clusters) {
|
|
7472
8001
|
const occurrences = cluster.reduce((n, c) => n + (c.recurrence_count || 1), 0);
|
|
@@ -7479,13 +8008,13 @@ ${r2.detail ?? ""}`.trim());
|
|
|
7479
8008
|
${representative.detail}` : ""}`;
|
|
7480
8009
|
const clusterKey = cluster.map((c) => c.title.toLowerCase().replace(/\s+/g, " ").trim()).sort().join("|");
|
|
7481
8010
|
const promptHash = createHash6("sha256").update(clusterKey).digest("hex").slice(0, 16);
|
|
7482
|
-
const candidatePath =
|
|
7483
|
-
if (
|
|
8011
|
+
const candidatePath = join13(candidateDir, `${promptHash}.json`);
|
|
8012
|
+
if (existsSync18(candidatePath)) continue;
|
|
7484
8013
|
if (dryRun) {
|
|
7485
8014
|
promoted++;
|
|
7486
8015
|
continue;
|
|
7487
8016
|
}
|
|
7488
|
-
|
|
8017
|
+
mkdirSync15(candidateDir, { recursive: true });
|
|
7489
8018
|
writeFileSync10(
|
|
7490
8019
|
candidatePath,
|
|
7491
8020
|
JSON.stringify(
|
|
@@ -7644,12 +8173,17 @@ async function runConsolidation(db, opts = {}) {
|
|
|
7644
8173
|
init_hook_failure_signal();
|
|
7645
8174
|
|
|
7646
8175
|
// src/hooks/lib/is-direct-invocation.ts
|
|
7647
|
-
import { pathToFileURL } from "url";
|
|
8176
|
+
import { fileURLToPath as fileURLToPath2, pathToFileURL } from "url";
|
|
7648
8177
|
function isDirectInvocation(moduleUrl) {
|
|
7649
8178
|
const entry = process.argv[1];
|
|
7650
8179
|
if (!entry) return false;
|
|
7651
8180
|
try {
|
|
7652
|
-
|
|
8181
|
+
if (moduleUrl === pathToFileURL(entry).href) return true;
|
|
8182
|
+
} catch {
|
|
8183
|
+
return false;
|
|
8184
|
+
}
|
|
8185
|
+
try {
|
|
8186
|
+
return isSameRealPath(fileURLToPath2(moduleUrl), entry);
|
|
7653
8187
|
} catch {
|
|
7654
8188
|
return false;
|
|
7655
8189
|
}
|
|
@@ -8084,14 +8618,14 @@ function extractFilesFromToolCall(toolName, input) {
|
|
|
8084
8618
|
return [];
|
|
8085
8619
|
}
|
|
8086
8620
|
function readStdin() {
|
|
8087
|
-
return new Promise((
|
|
8621
|
+
return new Promise((resolve9) => {
|
|
8088
8622
|
let data = "";
|
|
8089
8623
|
process.stdin.setEncoding("utf-8");
|
|
8090
8624
|
process.stdin.on("data", (chunk) => {
|
|
8091
8625
|
data += chunk;
|
|
8092
8626
|
});
|
|
8093
|
-
process.stdin.on("end", () =>
|
|
8094
|
-
setTimeout(() =>
|
|
8627
|
+
process.stdin.on("end", () => resolve9(data));
|
|
8628
|
+
setTimeout(() => resolve9(data), 5e3);
|
|
8095
8629
|
});
|
|
8096
8630
|
}
|
|
8097
8631
|
if (isDirectInvocation(import.meta.url)) {
|