@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.
@@ -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
- "SELECT id, payload, retry_count FROM pending_sync ORDER BY created_at ASC LIMIT ?"
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
- for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
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
- lastError = `HTTP ${response.status}: ${response.statusText}`;
4675
- if (response.status >= 400 && response.status < 500) {
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 < MAX_RETRIES - 1) {
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 < MAX_RETRIES - 1) {
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
- return moduleUrl === pathToFileURL(entry).href;
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
- "SELECT id, payload, retry_count FROM pending_sync ORDER BY created_at ASC LIMIT ?"
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
- import { pathToFileURL } from "url";
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
- return moduleUrl === pathToFileURL(entry).href;
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 = estimateTokens("=== Massu Memory: Previous Session Context ===\n\n=== END Massu Memory ===\n");
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 = estimateTokens(section.text);
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 estimateTokens(text) {
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: