@massu/core 2.5.6 → 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 +30 -0
- package/dist/cli.js +98 -4
- package/dist/hooks/auto-learning-pipeline.js +81 -16
- package/dist/hooks/classify-failure.js +73 -8
- package/dist/hooks/cost-tracker.js +69 -4
- package/dist/hooks/fix-detector.js +75 -10
- 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 +69 -4
- package/dist/hooks/pre-tool-use-gate.js +69 -4
- package/dist/hooks/quality-event.js +69 -4
- package/dist/hooks/rule-enforcement-pipeline.js +72 -7
- package/dist/hooks/security-gate.js +69 -4
- package/dist/hooks/session-end.js +137 -10
- package/dist/hooks/session-start.js +112 -8
- package/dist/hooks/user-prompt.js +83 -18
- package/package.json +1 -1
|
@@ -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,
|
|
@@ -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);
|
|
@@ -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
|
}
|
|
@@ -4752,6 +4853,20 @@ async function drainSyncQueue(db, deadlineAt) {
|
|
|
4752
4853
|
function sleep(ms) {
|
|
4753
4854
|
return new Promise((resolve9) => setTimeout(resolve9, ms));
|
|
4754
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
|
+
}
|
|
4869
|
+
}
|
|
4755
4870
|
|
|
4756
4871
|
// src/team-rule-sync.ts
|
|
4757
4872
|
import { existsSync as existsSync12, writeFileSync as writeFileSync6, unlinkSync as unlinkSync3, mkdirSync as mkdirSync9 } from "fs";
|
|
@@ -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);
|
|
@@ -8051,12 +8173,17 @@ async function runConsolidation(db, opts = {}) {
|
|
|
8051
8173
|
init_hook_failure_signal();
|
|
8052
8174
|
|
|
8053
8175
|
// src/hooks/lib/is-direct-invocation.ts
|
|
8054
|
-
import { pathToFileURL } from "url";
|
|
8176
|
+
import { fileURLToPath as fileURLToPath2, pathToFileURL } from "url";
|
|
8055
8177
|
function isDirectInvocation(moduleUrl) {
|
|
8056
8178
|
const entry = process.argv[1];
|
|
8057
8179
|
if (!entry) return false;
|
|
8058
8180
|
try {
|
|
8059
|
-
|
|
8181
|
+
if (moduleUrl === pathToFileURL(entry).href) return true;
|
|
8182
|
+
} catch {
|
|
8183
|
+
return false;
|
|
8184
|
+
}
|
|
8185
|
+
try {
|
|
8186
|
+
return isSameRealPath(fileURLToPath2(moduleUrl), entry);
|
|
8060
8187
|
} catch {
|
|
8061
8188
|
return false;
|
|
8062
8189
|
}
|
|
@@ -2205,6 +2205,8 @@ __export(memory_db_exports, {
|
|
|
2205
2205
|
MEMORY_FILE_TITLE_LIKE: () => MEMORY_FILE_TITLE_LIKE,
|
|
2206
2206
|
MEMORY_FILE_TITLE_PREFIX: () => MEMORY_FILE_TITLE_PREFIX,
|
|
2207
2207
|
MEMORY_SCHEMA_VERSION: () => MEMORY_SCHEMA_VERSION,
|
|
2208
|
+
PENDING_SYNC_MAX_AGE_DAYS: () => PENDING_SYNC_MAX_AGE_DAYS,
|
|
2209
|
+
PENDING_SYNC_MAX_ITEMS: () => PENDING_SYNC_MAX_ITEMS,
|
|
2208
2210
|
TOOL_COST_EVENTS_RETENTION_DAYS: () => TOOL_COST_EVENTS_RETENTION_DAYS,
|
|
2209
2211
|
USAGE_COUNTER_ARMED_KEY: () => USAGE_COUNTER_ARMED_KEY,
|
|
2210
2212
|
addConversationTurn: () => addConversationTurn,
|
|
@@ -2213,6 +2215,7 @@ __export(memory_db_exports, {
|
|
|
2213
2215
|
addSummary: () => addSummary,
|
|
2214
2216
|
addToolCallDetail: () => addToolCallDetail,
|
|
2215
2217
|
addUserPrompt: () => addUserPrompt,
|
|
2218
|
+
allPendingSync: () => allPendingSync,
|
|
2216
2219
|
appendIncidentToFailureClass: () => appendIncidentToFailureClass,
|
|
2217
2220
|
armUsageCounter: () => armUsageCounter,
|
|
2218
2221
|
assignImportance: () => assignImportance,
|
|
@@ -2220,6 +2223,7 @@ __export(memory_db_exports, {
|
|
|
2220
2223
|
createSession: () => createSession,
|
|
2221
2224
|
deduplicateFailedAttempt: () => deduplicateFailedAttempt,
|
|
2222
2225
|
dequeuePendingSync: () => dequeuePendingSync,
|
|
2226
|
+
discardPendingSync: () => discardPendingSync,
|
|
2223
2227
|
embedMissingObservations: () => embedMissingObservations,
|
|
2224
2228
|
endSession: () => endSession,
|
|
2225
2229
|
enqueueRulePromotionEvent: () => enqueueRulePromotionEvent,
|
|
@@ -3202,6 +3206,35 @@ function initMemorySchema(db) {
|
|
|
3202
3206
|
}
|
|
3203
3207
|
function enqueueSyncPayload(db, payload) {
|
|
3204
3208
|
db.prepare("INSERT INTO pending_sync (payload) VALUES (?)").run(payload);
|
|
3209
|
+
const overflow = db.prepare(
|
|
3210
|
+
`SELECT id, retry_count, last_error, payload FROM pending_sync
|
|
3211
|
+
WHERE created_at < datetime('now', ?)
|
|
3212
|
+
OR id NOT IN (SELECT id FROM pending_sync ORDER BY created_at DESC, id DESC LIMIT ?)
|
|
3213
|
+
LIMIT 10000`
|
|
3214
|
+
).all(`-${PENDING_SYNC_MAX_AGE_DAYS} days`, PENDING_SYNC_MAX_ITEMS);
|
|
3215
|
+
if (overflow.length > 0) {
|
|
3216
|
+
discardPendingSync(db, overflow, `over the queue limit (${PENDING_SYNC_MAX_ITEMS} items / ${PENDING_SYNC_MAX_AGE_DAYS} days)`);
|
|
3217
|
+
}
|
|
3218
|
+
}
|
|
3219
|
+
function discardPendingSync(db, rows, why) {
|
|
3220
|
+
if (rows.length === 0) return 0;
|
|
3221
|
+
const rescued = rescueLearningFromStalePayloads(db, rows);
|
|
3222
|
+
for (let i = 0; i < rows.length; i += 500) {
|
|
3223
|
+
const ids = rows.slice(i, i + 500).map((r) => r.id);
|
|
3224
|
+
db.prepare(`DELETE FROM pending_sync WHERE id IN (${ids.map(() => "?").join(",")})`).run(...ids);
|
|
3225
|
+
}
|
|
3226
|
+
if (rescued > 0) {
|
|
3227
|
+
process.stderr.write(
|
|
3228
|
+
`[massu] RESCUED ${rescued} learned rule(s) from ${rows.length} discarded payload(s) \u2014 they were re-queued for delivery, NOT discarded.
|
|
3229
|
+
`
|
|
3230
|
+
);
|
|
3231
|
+
}
|
|
3232
|
+
process.stderr.write(`[massu] cloud sync: discarded ${rows.length} queued upload(s): ${why}.
|
|
3233
|
+
`);
|
|
3234
|
+
return rows.length;
|
|
3235
|
+
}
|
|
3236
|
+
function allPendingSync(db) {
|
|
3237
|
+
return db.prepare("SELECT id, retry_count, last_error, payload FROM pending_sync").all();
|
|
3205
3238
|
}
|
|
3206
3239
|
function getMemoryMeta(db, key) {
|
|
3207
3240
|
const row = db.prepare("SELECT value FROM memory_meta WHERE key = ?").get(key);
|
|
@@ -3404,7 +3437,9 @@ function dequeuePendingSync(db, limit = 10) {
|
|
|
3404
3437
|
}
|
|
3405
3438
|
}
|
|
3406
3439
|
return db.prepare(
|
|
3407
|
-
|
|
3440
|
+
// Least-retried first: oldest-first retried the same 10 items until they were discarded
|
|
3441
|
+
// while every newer item waited at retry 0 (2026-11-09: 43,492 never attempted).
|
|
3442
|
+
"SELECT id, payload, retry_count FROM pending_sync ORDER BY retry_count ASC, created_at ASC LIMIT ?"
|
|
3408
3443
|
).all(limit);
|
|
3409
3444
|
}
|
|
3410
3445
|
function removePendingSync(db, id) {
|
|
@@ -4077,7 +4112,7 @@ function scoreFailureClasses(db, matchText, filePath, promptContext, weights) {
|
|
|
4077
4112
|
}
|
|
4078
4113
|
return bestMatch;
|
|
4079
4114
|
}
|
|
4080
|
-
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;
|
|
4115
|
+
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;
|
|
4081
4116
|
var init_memory_db = __esm({
|
|
4082
4117
|
"src/memory-db.ts"() {
|
|
4083
4118
|
"use strict";
|
|
@@ -4092,6 +4127,8 @@ var init_memory_db = __esm({
|
|
|
4092
4127
|
init_memory_embed_sweep();
|
|
4093
4128
|
MEMORY_SCHEMA_VERSION = 2;
|
|
4094
4129
|
TOOL_COST_EVENTS_RETENTION_DAYS = 90;
|
|
4130
|
+
PENDING_SYNC_MAX_ITEMS = 500;
|
|
4131
|
+
PENDING_SYNC_MAX_AGE_DAYS = 30;
|
|
4095
4132
|
MAX_DRAFT_TEXT_LEN = 16384;
|
|
4096
4133
|
FUNNEL_EVENT_OUTBOX_CAP = 2e4;
|
|
4097
4134
|
_temporalColCache = /* @__PURE__ */ new WeakMap();
|
|
@@ -4219,6 +4256,13 @@ function assertContainedIn(rootDir, candidate, opts = {}) {
|
|
|
4219
4256
|
}
|
|
4220
4257
|
return abs;
|
|
4221
4258
|
}
|
|
4259
|
+
function isSameRealPath(a, b) {
|
|
4260
|
+
try {
|
|
4261
|
+
return realpathSync(a) === realpathSync(b);
|
|
4262
|
+
} catch {
|
|
4263
|
+
return false;
|
|
4264
|
+
}
|
|
4265
|
+
}
|
|
4222
4266
|
function isContainedIn(rootDir, candidate, opts = {}) {
|
|
4223
4267
|
if (candidate.includes("\0")) return false;
|
|
4224
4268
|
const root = resolve6(rootDir);
|
|
@@ -13984,7 +14028,7 @@ init_config();
|
|
|
13984
14028
|
init_corrections_md();
|
|
13985
14029
|
init_db_driver();
|
|
13986
14030
|
import { readFileSync as readFileSync19, existsSync as existsSync24 } from "fs";
|
|
13987
|
-
import { resolve as resolve9 } from "path";
|
|
14031
|
+
import { dirname as dirname13, join as join18, resolve as resolve9 } from "path";
|
|
13988
14032
|
import { parse as parseYaml4 } from "yaml";
|
|
13989
14033
|
|
|
13990
14034
|
// src/capability-advisor.ts
|
|
@@ -14276,18 +14320,69 @@ function checkWindows(pid) {
|
|
|
14276
14320
|
init_hook_failure_signal();
|
|
14277
14321
|
|
|
14278
14322
|
// src/hooks/lib/is-direct-invocation.ts
|
|
14279
|
-
|
|
14323
|
+
init_safe_write();
|
|
14324
|
+
import { fileURLToPath as fileURLToPath2, pathToFileURL } from "url";
|
|
14280
14325
|
function isDirectInvocation(moduleUrl) {
|
|
14281
14326
|
const entry = process.argv[1];
|
|
14282
14327
|
if (!entry) return false;
|
|
14283
14328
|
try {
|
|
14284
|
-
|
|
14329
|
+
if (moduleUrl === pathToFileURL(entry).href) return true;
|
|
14285
14330
|
} catch {
|
|
14286
14331
|
return false;
|
|
14287
14332
|
}
|
|
14333
|
+
try {
|
|
14334
|
+
return isSameRealPath(fileURLToPath2(moduleUrl), entry);
|
|
14335
|
+
} catch {
|
|
14336
|
+
return false;
|
|
14337
|
+
}
|
|
14338
|
+
}
|
|
14339
|
+
|
|
14340
|
+
// src/cloud-sync.ts
|
|
14341
|
+
init_config();
|
|
14342
|
+
init_memory_db();
|
|
14343
|
+
init_rule_delivery();
|
|
14344
|
+
|
|
14345
|
+
// src/observation-extractor.ts
|
|
14346
|
+
init_memory_db();
|
|
14347
|
+
init_config();
|
|
14348
|
+
|
|
14349
|
+
// src/cloud-sync.ts
|
|
14350
|
+
var CLOUD_SYNC_REFUSAL_KEY = "cloud_sync_refused";
|
|
14351
|
+
var CLOUD_SYNC_RECHECK_MS = 24 * 60 * 60 * 1e3;
|
|
14352
|
+
function readCloudSyncRefusal(db) {
|
|
14353
|
+
try {
|
|
14354
|
+
const raw = getMemoryMeta(db, CLOUD_SYNC_REFUSAL_KEY);
|
|
14355
|
+
if (!raw) return null;
|
|
14356
|
+
const v = JSON.parse(raw);
|
|
14357
|
+
return v && typeof v.status === "number" ? v : null;
|
|
14358
|
+
} catch {
|
|
14359
|
+
return null;
|
|
14360
|
+
}
|
|
14361
|
+
}
|
|
14362
|
+
function cloudSyncRefusalBanner(db) {
|
|
14363
|
+
const r = readCloudSyncRefusal(db);
|
|
14364
|
+
if (!r) return null;
|
|
14365
|
+
return `=== MASSU CLOUD SYNC PAUSED ===
|
|
14366
|
+
The massu cloud refused uploads from this project (HTTP ${r.status}: ${r.reason || "no reason given"}) on ${r.at}. Nothing is being uploaded or queued; local memory is unaffected. It re-checks once a day and resumes on its own when the account allows it.
|
|
14367
|
+
=== END ===
|
|
14368
|
+
|
|
14369
|
+
`;
|
|
14288
14370
|
}
|
|
14289
14371
|
|
|
14290
14372
|
// src/hooks/session-start.ts
|
|
14373
|
+
import { spawn } from "child_process";
|
|
14374
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
14375
|
+
function startRecallWarmUp() {
|
|
14376
|
+
if (process.env.MASSU_RECALL_WARMUP === "0") return false;
|
|
14377
|
+
try {
|
|
14378
|
+
const recall = join18(dirname13(fileURLToPath3(import.meta.url)), "memory-recall.js");
|
|
14379
|
+
if (!existsSync24(recall)) return false;
|
|
14380
|
+
spawn(process.execPath, [recall, "--warm-cache"], { detached: true, stdio: "ignore" }).unref();
|
|
14381
|
+
return true;
|
|
14382
|
+
} catch {
|
|
14383
|
+
return false;
|
|
14384
|
+
}
|
|
14385
|
+
}
|
|
14291
14386
|
async function main() {
|
|
14292
14387
|
try {
|
|
14293
14388
|
const input = await readStdin();
|
|
@@ -14347,6 +14442,12 @@ Session memory, code intelligence, and governance are now active.
|
|
|
14347
14442
|
const watcherBanner = buildWatcherBanner();
|
|
14348
14443
|
if (watcherBanner) process.stdout.write(watcherBanner);
|
|
14349
14444
|
}
|
|
14445
|
+
try {
|
|
14446
|
+
const syncBanner = cloudSyncRefusalBanner(db);
|
|
14447
|
+
if (syncBanner) process.stdout.write(syncBanner);
|
|
14448
|
+
} catch {
|
|
14449
|
+
}
|
|
14450
|
+
startRecallWarmUp();
|
|
14350
14451
|
try {
|
|
14351
14452
|
const consolidationCfg = resolveConsolidationConfig();
|
|
14352
14453
|
const advisorBlock = await runAdvisors([localModelAdvisor, crossRepoShareAdvisor], {
|
|
@@ -14499,11 +14600,11 @@ Index has ${stats.doc_count} documents, last indexed ${ageHours}h ago. Consider
|
|
|
14499
14600
|
}
|
|
14500
14601
|
sections.sort((a, b) => b.importance - a.importance);
|
|
14501
14602
|
let usedTokens = 0;
|
|
14502
|
-
const headerTokens =
|
|
14603
|
+
const headerTokens = estimateTokens2("=== Massu Memory: Previous Session Context ===\n\n=== END Massu Memory ===\n");
|
|
14503
14604
|
usedTokens += headerTokens;
|
|
14504
14605
|
const includedSections = [];
|
|
14505
14606
|
for (const section of sections) {
|
|
14506
|
-
const sectionTokens =
|
|
14607
|
+
const sectionTokens = estimateTokens2(section.text);
|
|
14507
14608
|
if (usedTokens + sectionTokens <= tokenBudget) {
|
|
14508
14609
|
includedSections.push(section.text);
|
|
14509
14610
|
usedTokens += sectionTokens;
|
|
@@ -14516,7 +14617,7 @@ ${includedSections.join("\n")}
|
|
|
14516
14617
|
=== END Massu Memory ===
|
|
14517
14618
|
`;
|
|
14518
14619
|
}
|
|
14519
|
-
function
|
|
14620
|
+
function estimateTokens2(text) {
|
|
14520
14621
|
return Math.ceil(text.length / 4);
|
|
14521
14622
|
}
|
|
14522
14623
|
async function getGitBranch() {
|
|
@@ -14649,6 +14750,9 @@ function formatAge(ms) {
|
|
|
14649
14750
|
if (isDirectInvocation(import.meta.url)) {
|
|
14650
14751
|
main();
|
|
14651
14752
|
}
|
|
14753
|
+
export {
|
|
14754
|
+
startRecallWarmUp
|
|
14755
|
+
};
|
|
14652
14756
|
/*! Bundled license information:
|
|
14653
14757
|
|
|
14654
14758
|
is-extglob/index.js:
|