memorix 1.9.3 → 1.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/CHANGELOG.md +49 -1
  2. package/README.md +7 -6
  3. package/README.zh-CN.md +7 -6
  4. package/dist/cli/index.js +930 -330
  5. package/dist/index.d.ts +3 -1
  6. package/dist/index.js +289 -109
  7. package/dist/maintenance-runner.js +179 -117
  8. package/dist/media-audio-runner.js +92 -30
  9. package/dist/media-video-runner.js +92 -30
  10. package/dist/memcode-runtime/CHANGELOG.md +49 -1
  11. package/dist/sdk.d.ts +1 -0
  12. package/dist/sdk.js +1050 -847
  13. package/dist/types.d.ts +1 -1
  14. package/dist/vector-backfill-runner.js +92 -30
  15. package/docs/AGENT_OPERATOR_PLAYBOOK.md +47 -3
  16. package/docs/API_REFERENCE.md +3 -1
  17. package/docs/CONFIGURATION.md +15 -1
  18. package/docs/DEVELOPMENT.md +1 -1
  19. package/docs/DOCKER.md +23 -3
  20. package/docs/INTEGRATIONS.md +1 -1
  21. package/docs/KNOWN_ISSUES_AND_ROADMAP.md +26 -1
  22. package/docs/MULTI-DEVICE-SYNC-RESEARCH.md +4 -0
  23. package/docs/README.md +1 -1
  24. package/docs/SETUP.md +55 -0
  25. package/package.json +2 -2
  26. package/plugins/antigravity/memorix/plugin.json +1 -1
  27. package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
  28. package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
  29. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  30. package/plugins/copilot/memorix/plugin.json +1 -1
  31. package/plugins/gemini/memorix/gemini-extension.json +1 -1
  32. package/plugins/hermes/memorix/plugin.yaml +1 -1
  33. package/plugins/omp/memorix/package.json +1 -1
  34. package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
  35. package/plugins/pi/memorix/package.json +1 -1
  36. package/server.json +2 -2
  37. package/src/cli/command-guide.ts +2 -2
  38. package/src/cli/commands/agent-integrations.ts +57 -0
  39. package/src/cli/commands/background.ts +11 -2
  40. package/src/cli/commands/media.ts +2 -0
  41. package/src/cli/commands/serve-http.ts +193 -42
  42. package/src/cli/commands/setup.ts +77 -10
  43. package/src/cli/commands/sync-store.ts +47 -12
  44. package/src/cli/commands/sync.ts +26 -0
  45. package/src/dashboard/server.ts +10 -12
  46. package/src/integrations/registry.ts +1 -1
  47. package/src/media/minimax.ts +26 -2
  48. package/src/media/video-jobs.ts +9 -1
  49. package/src/memory/graph.ts +10 -8
  50. package/src/memory/session.ts +1 -1
  51. package/src/sdk.ts +24 -0
  52. package/src/server/mcp-binding-store.ts +14 -3
  53. package/src/server.ts +5 -3
  54. package/src/store/file-lock.ts +28 -3
  55. package/src/store/graph-store.ts +178 -46
  56. package/src/store/persistence.ts +14 -9
  57. package/src/store/sqlite-db.ts +6 -3
  58. package/src/store/sqlite-store.ts +32 -27
  59. package/src/sync/engine.ts +9 -7
  60. package/src/sync/namespace.ts +23 -0
  61. package/src/sync/policy.ts +19 -4
  62. package/src/sync/store-port.ts +43 -13
  63. package/src/sync/types.ts +2 -0
  64. package/src/types.ts +1 -1
  65. package/src/workspace/mcp-adapters/grok.ts +32 -0
package/dist/cli/index.js CHANGED
@@ -3232,7 +3232,7 @@ ${import_picocolors.default.gray(m3)} ${s4}
3232
3232
 
3233
3233
  // src/cli/version.ts
3234
3234
  function getCliVersion() {
3235
- return true ? "1.9.3" : pkg.version;
3235
+ return true ? "1.9.5" : pkg.version;
3236
3236
  }
3237
3237
  var init_version = __esm({
3238
3238
  "src/cli/version.ts"() {
@@ -9473,6 +9473,14 @@ END;
9473
9473
  });
9474
9474
 
9475
9475
  // src/store/sqlite-db.ts
9476
+ var sqlite_db_exports = {};
9477
+ __export(sqlite_db_exports, {
9478
+ closeAllDatabases: () => closeAllDatabases,
9479
+ closeDatabase: () => closeDatabase,
9480
+ getDatabase: () => getDatabase,
9481
+ isSqliteAvailable: () => isSqliteAvailable,
9482
+ loadBetterSqlite3: () => loadBetterSqlite3
9483
+ });
9476
9484
  import "module";
9477
9485
  import path3 from "path";
9478
9486
  import fs from "fs";
@@ -9589,6 +9597,17 @@ function getDatabase(dataDir) {
9589
9597
  throw error40;
9590
9598
  }
9591
9599
  }
9600
+ function closeDatabase(dataDir) {
9601
+ const normalized = path3.resolve(dataDir);
9602
+ const db2 = _dbCache.get(normalized);
9603
+ if (db2) {
9604
+ try {
9605
+ db2.close();
9606
+ } catch {
9607
+ }
9608
+ _dbCache.delete(normalized);
9609
+ }
9610
+ }
9592
9611
  function closeAllDatabases() {
9593
9612
  for (const [key, db2] of _dbCache) {
9594
9613
  try {
@@ -9598,6 +9617,14 @@ function closeAllDatabases() {
9598
9617
  _dbCache.delete(key);
9599
9618
  }
9600
9619
  }
9620
+ function isSqliteAvailable() {
9621
+ try {
9622
+ loadBetterSqlite3();
9623
+ return true;
9624
+ } catch {
9625
+ return false;
9626
+ }
9627
+ }
9601
9628
  var BetterSqlite3, CREATE_OBSERVATIONS_TABLE, CREATE_MINI_SKILLS_TABLE, CREATE_SESSIONS_TABLE, CREATE_META_TABLE, CREATE_TEAM_AGENTS_TABLE, CREATE_TEAM_MESSAGES_TABLE, CREATE_TEAM_TASKS_TABLE, CREATE_TEAM_TASK_DEPS_TABLE, CREATE_TEAM_LOCKS_TABLE, CREATE_TEAM_ROLES_TABLE, CREATE_CHAT_TRANSCRIPT_TABLE, CREATE_GRAPH_ENTITIES_TABLE, CREATE_GRAPH_RELATIONS_TABLE, CREATE_CODE_FILES_TABLE, CREATE_CODE_SYMBOLS_TABLE, CREATE_CODE_EDGES_TABLE, CREATE_OBSERVATION_CODE_REFS_TABLE, CREATE_SCHEMA_MIGRATIONS_TABLE, CREATE_CODE_STATE_SNAPSHOTS_TABLE, CREATE_CODE_STATE_SNAPSHOT_FILES_TABLE, CREATE_KNOWLEDGE_CLAIMS_TABLE, CREATE_KNOWLEDGE_CLAIM_EVIDENCE_TABLE, CREATE_KNOWLEDGE_CLAIM_EVENTS_TABLE, CREATE_KNOWLEDGE_WORKSPACES_TABLE, CREATE_KNOWLEDGE_PAGES_TABLE, CREATE_KNOWLEDGE_PAGE_CLAIMS_TABLE, CREATE_KNOWLEDGE_PAGE_LINKS_TABLE, CREATE_KNOWLEDGE_PROPOSALS_TABLE, CREATE_KNOWLEDGE_WORKFLOWS_TABLE, CREATE_KNOWLEDGE_WORKFLOW_RUNS_TABLE, CREATE_LONG_TERM_MEMORIES_TABLE, CREATE_LONG_TERM_MEMORY_EVIDENCE_TABLE, CREATE_LONG_TERM_MEMORY_EVENTS_TABLE, CREATE_MEMORY_OUTCOME_SIGNALS_TABLE, CREATE_MEDIA_ASSETS_TABLE, CREATE_MEDIA_ASSET_LINKS_TABLE, CREATE_MEDIA_DERIVATIONS_TABLE, CREATE_MEDIA_EMBEDDING_PROFILES_TABLE, CREATE_MEDIA_EMBEDDINGS_TABLE, CREATE_MEDIA_JOBS_TABLE, CREATE_MAINTENANCE_JOBS_TABLE, CREATE_MAINTENANCE_TARGETS_TABLE, CREATE_COMPACTION_CHECKPOINTS_TABLE, CREATE_EVIDENCE_CARDS_TABLE, CREATE_EVIDENCE_CARD_EVENTS_TABLE, CREATE_MEMORY_FEEDBACK_STATES_TABLE, CREATE_MEMORY_FEEDBACK_EVENTS_TABLE, CREATE_MCP_BINDINGS_TABLE, CREATE_INDEXES, SCHEMA_MIGRATIONS, _dbCache;
9602
9629
  var init_sqlite_db = __esm({
9603
9630
  "src/store/sqlite-db.ts"() {
@@ -9770,18 +9797,21 @@ CREATE TABLE IF NOT EXISTS chat_transcript (
9770
9797
  `;
9771
9798
  CREATE_GRAPH_ENTITIES_TABLE = `
9772
9799
  CREATE TABLE IF NOT EXISTS graph_entities (
9773
- name TEXT PRIMARY KEY,
9800
+ projectId TEXT NOT NULL,
9801
+ name TEXT NOT NULL,
9774
9802
  entityType TEXT NOT NULL DEFAULT '',
9775
- observations TEXT NOT NULL DEFAULT '[]'
9803
+ observations TEXT NOT NULL DEFAULT '[]',
9804
+ PRIMARY KEY (projectId, name)
9776
9805
  );
9777
9806
  `;
9778
9807
  CREATE_GRAPH_RELATIONS_TABLE = `
9779
9808
  CREATE TABLE IF NOT EXISTS graph_relations (
9780
9809
  id INTEGER PRIMARY KEY AUTOINCREMENT,
9810
+ projectId TEXT NOT NULL,
9781
9811
  from_entity TEXT NOT NULL,
9782
9812
  to_entity TEXT NOT NULL,
9783
9813
  relationType TEXT NOT NULL DEFAULT '',
9784
- UNIQUE(from_entity, to_entity, relationType)
9814
+ UNIQUE(projectId, from_entity, to_entity, relationType)
9785
9815
  );
9786
9816
  `;
9787
9817
  CREATE_CODE_FILES_TABLE = `
@@ -10649,6 +10679,21 @@ var init_sqlite_reliability = __esm({
10649
10679
  import { promises as fs2 } from "fs";
10650
10680
  import { randomUUID } from "crypto";
10651
10681
  import path4 from "path";
10682
+ function canReclaimStaleLock(raw) {
10683
+ try {
10684
+ const parsed = JSON.parse(raw);
10685
+ const pid = Number(parsed.pid);
10686
+ if (!Number.isSafeInteger(pid) || pid < 1) return true;
10687
+ try {
10688
+ process.kill(pid, 0);
10689
+ return false;
10690
+ } catch (error40) {
10691
+ return error40.code !== "EPERM";
10692
+ }
10693
+ } catch {
10694
+ return true;
10695
+ }
10696
+ }
10652
10697
  async function acquireLock(lockPath) {
10653
10698
  for (let i4 = 0; i4 < MAX_RETRIES; i4++) {
10654
10699
  try {
@@ -10662,9 +10707,12 @@ async function acquireLock(lockPath) {
10662
10707
  try {
10663
10708
  const stat6 = await fs2.stat(lockPath);
10664
10709
  if (Date.now() - stat6.mtimeMs > LOCK_STALE_MS) {
10665
- await fs2.unlink(lockPath).catch(() => {
10666
- });
10667
- continue;
10710
+ const raw2 = await fs2.readFile(lockPath, "utf8").catch(() => "");
10711
+ if (canReclaimStaleLock(raw2)) {
10712
+ await fs2.unlink(lockPath).catch(() => {
10713
+ });
10714
+ continue;
10715
+ }
10668
10716
  }
10669
10717
  } catch {
10670
10718
  continue;
@@ -10675,6 +10723,10 @@ async function acquireLock(lockPath) {
10675
10723
  }
10676
10724
  }
10677
10725
  }
10726
+ const raw = await fs2.readFile(lockPath, "utf8").catch(() => "");
10727
+ if (!canReclaimStaleLock(raw)) {
10728
+ throw new Error(`Failed to acquire lock: ${lockPath} (owner is still running)`);
10729
+ }
10678
10730
  await fs2.unlink(lockPath).catch(() => {
10679
10731
  });
10680
10732
  try {
@@ -10913,6 +10965,11 @@ async function listProjectDirs(baseDir) {
10913
10965
  }
10914
10966
  }
10915
10967
  async function migrateSubdirsToFlat(baseDir) {
10968
+ const base = baseDir ?? resolveDefaultDataDir();
10969
+ await fs4.mkdir(base, { recursive: true });
10970
+ return withFileLock(base, () => migrateSubdirsToFlatUnlocked(base));
10971
+ }
10972
+ async function migrateSubdirsToFlatUnlocked(baseDir) {
10916
10973
  const base = baseDir ?? resolveDefaultDataDir();
10917
10974
  await fs4.mkdir(base, { recursive: true });
10918
10975
  let entries;
@@ -10969,7 +11026,7 @@ async function migrateSubdirsToFlat(baseDir) {
10969
11026
  for (const { obs } of subdirData) {
10970
11027
  for (const o4 of obs) {
10971
11028
  const isDuplicate = allObs.some(
10972
- (existing) => existing.title === o4.title && existing.createdAt === o4.createdAt
11029
+ (existing) => existing.title === o4.title && existing.createdAt === o4.createdAt && existing.projectId === o4.projectId
10973
11030
  );
10974
11031
  if (!isDuplicate) {
10975
11032
  allObs.push(o4);
@@ -11002,18 +11059,14 @@ async function migrateSubdirsToFlat(baseDir) {
11002
11059
  mergedRelations.push(rel);
11003
11060
  }
11004
11061
  }
11005
- await fs4.writeFile(path6.join(base, "observations.json"), JSON.stringify(allObs, null, 2), "utf-8");
11006
- await fs4.writeFile(
11007
- path6.join(base, "counter.json"),
11008
- JSON.stringify({ nextId: allObs.length + 1 }),
11009
- "utf-8"
11010
- );
11062
+ await atomicWriteFile(path6.join(base, "observations.json"), JSON.stringify(allObs, null, 2));
11063
+ await atomicWriteFile(path6.join(base, "counter.json"), JSON.stringify({ nextId: allObs.length + 1 }));
11011
11064
  const graphLines = [
11012
11065
  ...[...entityMap.values()].map((e5) => JSON.stringify({ type: "entity", name: e5.name, entityType: e5.entityType, observations: e5.observations })),
11013
11066
  ...mergedRelations.map((r6) => JSON.stringify({ type: "relation", from: r6.from, to: r6.to, relationType: r6.relationType }))
11014
11067
  ];
11015
11068
  if (graphLines.length > 0) {
11016
- await fs4.writeFile(path6.join(base, "graph.jsonl"), graphLines.join("\n"), "utf-8");
11069
+ await atomicWriteFile(path6.join(base, "graph.jsonl"), graphLines.join("\n"));
11017
11070
  }
11018
11071
  let allSessions = [];
11019
11072
  try {
@@ -11031,7 +11084,7 @@ async function migrateSubdirsToFlat(baseDir) {
11031
11084
  }
11032
11085
  }
11033
11086
  if (allSessions.length > 0) {
11034
- await fs4.writeFile(path6.join(base, "sessions.json"), JSON.stringify(allSessions, null, 2), "utf-8");
11087
+ await atomicWriteFile(path6.join(base, "sessions.json"), JSON.stringify(allSessions, null, 2));
11035
11088
  }
11036
11089
  const backupDir = path6.join(base, ".migrated-subdirs");
11037
11090
  await fs4.mkdir(backupDir, { recursive: true });
@@ -11067,6 +11120,7 @@ var init_persistence = __esm({
11067
11120
  "src/store/persistence.ts"() {
11068
11121
  "use strict";
11069
11122
  init_esm_shims();
11123
+ init_file_lock();
11070
11124
  init_persistence_json();
11071
11125
  }
11072
11126
  });
@@ -25338,6 +25392,7 @@ var init_sqlite_store = __esm({
25338
25392
  init_sqlite_db();
25339
25393
  init_sqlite_reliability();
25340
25394
  init_sqlite_fts();
25395
+ init_file_lock();
25341
25396
  SqliteBackend = class {
25342
25397
  db = null;
25343
25398
  dataDir = "";
@@ -25501,38 +25556,43 @@ var init_sqlite_store = __esm({
25501
25556
  }
25502
25557
  // ── Migration ────────────────────────────────────────────────────
25503
25558
  async migrateFromJsonIfNeeded() {
25504
- const count3 = this.db.prepare(`SELECT COUNT(*) AS cnt FROM observations`).get();
25505
- if (count3.cnt > 0) return;
25506
25559
  const jsonPath = path9.join(this.dataDir, "observations.json");
25507
25560
  if (!fs6.existsSync(jsonPath)) return;
25508
- try {
25561
+ await withFileLock(this.dataDir, async () => {
25562
+ const count3 = this.db.prepare(`SELECT COUNT(*) AS cnt FROM observations`).get();
25563
+ if (count3.cnt > 0) return;
25509
25564
  const raw = fs6.readFileSync(jsonPath, "utf-8");
25510
- const observations2 = JSON.parse(raw);
25565
+ const parsed = JSON.parse(raw);
25566
+ if (!Array.isArray(parsed)) {
25567
+ throw new Error("[memorix] observations.json migration refused: expected a JSON array");
25568
+ }
25569
+ const observations2 = parsed;
25511
25570
  if (observations2.length === 0) return;
25571
+ if (observations2.some((obs) => !obs || !Number.isSafeInteger(obs.id) || obs.id < 1 || typeof obs.projectId !== "string")) {
25572
+ throw new Error("[memorix] observations.json migration refused: invalid observation record");
25573
+ }
25512
25574
  console.error(`[memorix] Migrating ${observations2.length} observations from JSON to SQLite...`);
25513
25575
  const insertMany = this.db.transaction((obsList) => {
25514
25576
  for (const obs of obsList) {
25515
25577
  this.stmtInsert.run(obsToRow(obs));
25516
25578
  }
25517
- });
25518
- insertMany(observations2);
25519
- const counterPath = path9.join(this.dataDir, "counter.json");
25520
- if (fs6.existsSync(counterPath)) {
25521
- try {
25579
+ const counterPath = path9.join(this.dataDir, "counter.json");
25580
+ let counterNextId;
25581
+ if (fs6.existsSync(counterPath)) {
25522
25582
  const counterData = JSON.parse(fs6.readFileSync(counterPath, "utf-8"));
25523
- const nextId2 = counterData.nextId ?? counterData.next_id ?? Math.max(...observations2.map((o4) => o4.id)) + 1;
25524
- this.rawSaveIdCounter(nextId2);
25525
- } catch {
25526
- this.rawSaveIdCounter(Math.max(...observations2.map((o4) => o4.id)) + 1);
25583
+ const candidate2 = counterData.nextId ?? counterData.next_id;
25584
+ if (candidate2 !== void 0 && (!Number.isSafeInteger(candidate2) || Number(candidate2) < 1)) {
25585
+ throw new Error("[memorix] counter.json migration refused: nextId must be a positive integer");
25586
+ }
25587
+ counterNextId = candidate2 === void 0 ? void 0 : Number(candidate2);
25527
25588
  }
25528
- } else {
25529
- this.rawSaveIdCounter(Math.max(...observations2.map((o4) => o4.id)) + 1);
25530
- }
25531
- this.bumpGeneration();
25589
+ const maxId = Math.max(...obsList.map((obs) => obs.id));
25590
+ this.rawSaveIdCounter(Math.max(counterNextId ?? 1, maxId + 1));
25591
+ this.bumpGeneration();
25592
+ });
25593
+ insertMany(observations2);
25532
25594
  console.error(`[memorix] Migration complete. ${observations2.length} observations now in SQLite.`);
25533
- } catch (err) {
25534
- console.error(`[memorix] JSON\u2192SQLite migration failed (non-fatal, data preserved in JSON): ${err}`);
25535
- }
25595
+ });
25536
25596
  }
25537
25597
  // ── Public read ──────────────────────────────────────────────────
25538
25598
  async loadAll() {
@@ -86686,12 +86746,12 @@ var require_jwa = __commonJS({
86686
86746
  };
86687
86747
  }
86688
86748
  var bufferEqual;
86689
- var timingSafeEqual2 = "timingSafeEqual" in crypto2 ? function timingSafeEqual3(a4, b4) {
86749
+ var timingSafeEqual3 = "timingSafeEqual" in crypto2 ? function timingSafeEqual4(a4, b4) {
86690
86750
  if (a4.byteLength !== b4.byteLength) {
86691
86751
  return false;
86692
86752
  }
86693
86753
  return crypto2.timingSafeEqual(a4, b4);
86694
- } : function timingSafeEqual3(a4, b4) {
86754
+ } : function timingSafeEqual4(a4, b4) {
86695
86755
  if (!bufferEqual) {
86696
86756
  bufferEqual = require_buffer_equal_constant_time();
86697
86757
  }
@@ -86700,7 +86760,7 @@ var require_jwa = __commonJS({
86700
86760
  function createHmacVerifier(bits) {
86701
86761
  return function verify2(thing, signature, secret) {
86702
86762
  var computedSig = createHmacSigner(bits)(thing, secret);
86703
- return timingSafeEqual2(Buffer5.from(signature), Buffer5.from(computedSig));
86763
+ return timingSafeEqual3(Buffer5.from(signature), Buffer5.from(computedSig));
86704
86764
  };
86705
86765
  }
86706
86766
  function createKeySigner(bits) {
@@ -177667,7 +177727,20 @@ function normalizeMiniMaxVideoRequest(input) {
177667
177727
  if (ratio !== "adaptive" && ratio !== "16:9" && ratio !== "9:16" && ratio !== "1:1") {
177668
177728
  throw new Error("MiniMax video ratio must be adaptive, 16:9, 9:16, or 1:1");
177669
177729
  }
177670
- return { region, model, prompt: prompt2, resolution, duration: duration3, ratio };
177730
+ let firstFrameImageUrl;
177731
+ if (input.firstFrameImageUrl !== void 0) {
177732
+ firstFrameImageUrl = input.firstFrameImageUrl.trim();
177733
+ let parsed;
177734
+ try {
177735
+ parsed = new URL(firstFrameImageUrl);
177736
+ } catch {
177737
+ throw new Error("MiniMax first-frame image must be a public HTTPS URL");
177738
+ }
177739
+ if (parsed.protocol !== "https:" || parsed.username || parsed.password || sanitizeCredentials(firstFrameImageUrl) !== firstFrameImageUrl) {
177740
+ throw new Error("MiniMax first-frame image must be an HTTPS URL without credentials");
177741
+ }
177742
+ }
177743
+ return { region, model, prompt: prompt2, resolution, duration: duration3, ratio, ...firstFrameImageUrl ? { firstFrameImageUrl } : {} };
177671
177744
  }
177672
177745
  function videoRequestConfig(input) {
177673
177746
  const normalized = normalizeMiniMaxVideoRequest(input);
@@ -177744,7 +177817,14 @@ async function createMiniMaxVideoTask(input, dependencies = {}) {
177744
177817
  headers: videoHeaders(config2.apiKey),
177745
177818
  body: JSON.stringify({
177746
177819
  model: config2.model,
177747
- content: [{ type: "text", text: config2.prompt }],
177820
+ content: [
177821
+ { type: "text", text: config2.prompt },
177822
+ ...config2.firstFrameImageUrl ? [{
177823
+ type: "image_url",
177824
+ image_url: { url: config2.firstFrameImageUrl },
177825
+ role: "first_frame"
177826
+ }] : []
177827
+ ],
177748
177828
  resolution: config2.resolution,
177749
177829
  duration: config2.duration,
177750
177830
  ratio: config2.ratio
@@ -177992,10 +178072,19 @@ function parseStoredRequest(value) {
177992
178072
  const duration3 = value.duration;
177993
178073
  const ratio = value.ratio;
177994
178074
  const maxBytes = value.maxBytes;
177995
- if (provider2 !== "minimax" && provider2 !== "minimax-cn" || region !== "global" && region !== "cn" || model !== "MiniMax-H3" || typeof prompt2 !== "string" || resolution !== "2K" || duration3 !== 5 && duration3 !== 10 || ratio !== "adaptive" && ratio !== "16:9" && ratio !== "9:16" && ratio !== "1:1" || typeof maxBytes !== "number" || !Number.isSafeInteger(maxBytes) || maxBytes < 1) {
178075
+ if (provider2 !== "minimax" && provider2 !== "minimax-cn" || region !== "global" && region !== "cn" || model !== "MiniMax-H3" || typeof prompt2 !== "string" || value.firstFrameImageUrl !== void 0 && typeof value.firstFrameImageUrl !== "string" || resolution !== "2K" || duration3 !== 5 && duration3 !== 10 || ratio !== "adaptive" && ratio !== "16:9" && ratio !== "9:16" && ratio !== "1:1" || typeof maxBytes !== "number" || !Number.isSafeInteger(maxBytes) || maxBytes < 1) {
177996
178076
  throw new Error("Media video job has an invalid request payload");
177997
178077
  }
177998
- return { provider: provider2, region, model, prompt: prompt2, resolution, duration: duration3, ratio, maxBytes };
178078
+ const normalized = normalizeMiniMaxVideoRequest({
178079
+ region,
178080
+ model,
178081
+ prompt: prompt2,
178082
+ resolution,
178083
+ duration: duration3,
178084
+ ratio,
178085
+ firstFrameImageUrl: value.firstFrameImageUrl
178086
+ });
178087
+ return { provider: provider2, ...normalized, maxBytes };
177999
178088
  }
178000
178089
  function mediaJobIdFromMaintenance(job) {
178001
178090
  const mediaJobId = job.payload.mediaJobId;
@@ -178007,6 +178096,7 @@ function mediaJobIdFromMaintenance(job) {
178007
178096
  function toVideoRequest(request) {
178008
178097
  return {
178009
178098
  prompt: request.prompt,
178099
+ firstFrameImageUrl: request.firstFrameImageUrl,
178010
178100
  model: request.model,
178011
178101
  region: request.region,
178012
178102
  resolution: request.resolution,
@@ -178098,6 +178188,7 @@ function queueMiniMaxVideoGeneration(input) {
178098
178188
  region: normalized.region,
178099
178189
  model: normalized.model,
178100
178190
  prompt: normalized.prompt,
178191
+ ...normalized.firstFrameImageUrl ? { firstFrameImageUrl: normalized.firstFrameImageUrl } : {},
178101
178192
  resolution: normalized.resolution,
178102
178193
  duration: normalized.duration,
178103
178194
  ratio: normalized.ratio,
@@ -194246,6 +194337,32 @@ var init_codebuddy = __esm({
194246
194337
  }
194247
194338
  });
194248
194339
 
194340
+ // src/workspace/mcp-adapters/grok.ts
194341
+ import { homedir as homedir25 } from "os";
194342
+ import { join as join22 } from "path";
194343
+ var GrokMCPAdapter;
194344
+ var init_grok = __esm({
194345
+ "src/workspace/mcp-adapters/grok.ts"() {
194346
+ "use strict";
194347
+ init_esm_shims();
194348
+ init_codex();
194349
+ GrokMCPAdapter = class {
194350
+ source = "grok";
194351
+ toml = new CodexMCPAdapter();
194352
+ parse(content) {
194353
+ return this.toml.parse(content);
194354
+ }
194355
+ generate(servers) {
194356
+ return this.toml.generate(servers);
194357
+ }
194358
+ getConfigPath(_projectRoot) {
194359
+ const grokHome = process.env.GROK_HOME?.trim() || join22(homedir25(), ".grok");
194360
+ return join22(grokHome, "config.toml");
194361
+ }
194362
+ };
194363
+ }
194364
+ });
194365
+
194249
194366
  // src/integrations/registry.ts
194250
194367
  function getSetupIntegrationRows() {
194251
194368
  return MEMORIX_INTEGRATIONS.filter((item) => item.status === "ready" && item.agent !== "memcode" && item.agent !== "any-mcp").map((item) => ({
@@ -194303,7 +194420,7 @@ var init_registry2 = __esm({
194303
194420
  status: "ready",
194304
194421
  install: "memorix setup --agent grok --global",
194305
194422
  surfaces: ["hooks", "AGENTS.md"],
194306
- note: "Writes ~/.grok/hooks/memorix.json and leaves Grok MCP host-owned; setup does not write config.toml."
194423
+ note: "Writes ~/.grok/hooks/memorix.json and leaves Grok MCP host-owned by default. Pass --mcp http to write [mcp_servers.memorix] in Grok config.toml."
194307
194424
  },
194308
194425
  {
194309
194426
  agent: "cursor",
@@ -194708,9 +194825,9 @@ __export(audit_exports, {
194708
194825
  });
194709
194826
  import * as fs18 from "fs/promises";
194710
194827
  import * as path30 from "path";
194711
- import { homedir as homedir25 } from "os";
194828
+ import { homedir as homedir26 } from "os";
194712
194829
  function getAuditFilePath() {
194713
- return process.env.MEMORIX_AUDIT_FILE || path30.join(homedir25(), ".memorix", "audit.json");
194830
+ return process.env.MEMORIX_AUDIT_FILE || path30.join(homedir26(), ".memorix", "audit.json");
194714
194831
  }
194715
194832
  async function loadAudit() {
194716
194833
  try {
@@ -196164,6 +196281,7 @@ var init_installers = __esm({
196164
196281
  // src/cli/commands/setup.ts
196165
196282
  var setup_exports = {};
196166
196283
  __export(setup_exports, {
196284
+ MEMORIX_HTTP_MCP_URL: () => MEMORIX_HTTP_MCP_URL,
196167
196285
  buildMemorixServer: () => buildMemorixServer,
196168
196286
  buildSetupPlan: () => buildSetupPlan,
196169
196287
  default: () => setup_default,
@@ -196183,19 +196301,20 @@ __export(setup_exports, {
196183
196301
  migrateLegacyCodexIntegration: () => migrateLegacyCodexIntegration,
196184
196302
  parseCodexPluginState: () => parseCodexPluginState,
196185
196303
  removeLegacyCodexMemorixMcpConfig: () => removeLegacyCodexMemorixMcpConfig,
196304
+ rewriteBundledMemorixMcpToHttp: () => rewriteBundledMemorixMcpToHttp,
196186
196305
  tryInstallCodexPlugin: () => tryInstallCodexPlugin
196187
196306
  });
196188
196307
  import { spawnSync } from "child_process";
196189
196308
  import { existsSync as existsSync17 } from "fs";
196190
196309
  import { copyFile as copyFile2, mkdir as mkdir6, readFile as readFile6, readdir as readdir3, rm as rm2, writeFile as writeFile7 } from "fs/promises";
196191
- import { homedir as homedir27 } from "os";
196310
+ import { homedir as homedir28 } from "os";
196192
196311
  import path32 from "path";
196193
196312
  import { fileURLToPath as fileURLToPath7 } from "url";
196194
196313
  function getSetupAgentTargets() {
196195
196314
  return [...SUPPORTED_SETUP_AGENTS];
196196
196315
  }
196197
196316
  function buildSetupPlan(options) {
196198
- const mcp = options.agent === "grok" ? "none" : options.mcp ?? "stdio";
196317
+ const mcp = options.agent === "grok" ? options.mcp === "http" ? "http" : "none" : options.mcp ?? "stdio";
196199
196318
  const actions = [];
196200
196319
  const isPackageOwnedAgent = options.agent !== "all" && PACKAGE_OWNED_INTEGRATION_AGENTS.has(options.agent);
196201
196320
  const includeHooks = options.hooks ?? true;
@@ -196351,7 +196470,8 @@ function getMcpAdapter(agent) {
196351
196470
  opencode: new OpenCodeMCPAdapter(),
196352
196471
  trae: new TraeMCPAdapter(),
196353
196472
  dsh: new DshMCPAdapter(),
196354
- workbuddy: new WorkbuddyMCPAdapter()
196473
+ workbuddy: new WorkbuddyMCPAdapter(),
196474
+ grok: new GrokMCPAdapter()
196355
196475
  };
196356
196476
  return adapters[agent];
196357
196477
  }
@@ -196361,7 +196481,7 @@ function buildMemorixServer(mcp) {
196361
196481
  name: "memorix",
196362
196482
  command: "",
196363
196483
  args: [],
196364
- url: "http://localhost:3211/mcp"
196484
+ url: MEMORIX_HTTP_MCP_URL
196365
196485
  };
196366
196486
  }
196367
196487
  return {
@@ -196372,6 +196492,36 @@ function buildMemorixServer(mcp) {
196372
196492
  args: ["serve", "--mode", "lite"]
196373
196493
  };
196374
196494
  }
196495
+ function toHttpMemorixServer(existing) {
196496
+ const next = { url: MEMORIX_HTTP_MCP_URL };
196497
+ if (existing.alwaysLoad === true) next.alwaysLoad = true;
196498
+ if (typeof existing.type === "string") next.type = "http";
196499
+ return next;
196500
+ }
196501
+ async function rewriteBundledMemorixMcpToHttp(root) {
196502
+ const candidates = [".mcp.json", "mcp_config.json", "gemini-extension.json"].map((name) => path32.join(root, name));
196503
+ const rewritten = [];
196504
+ for (const filePath of candidates) {
196505
+ if (!existsSync17(filePath)) continue;
196506
+ let doc;
196507
+ try {
196508
+ doc = JSON.parse(await readFile6(filePath, "utf-8"));
196509
+ } catch {
196510
+ continue;
196511
+ }
196512
+ if (!doc || typeof doc !== "object" || Array.isArray(doc)) continue;
196513
+ const record2 = doc;
196514
+ const servers = record2.mcpServers;
196515
+ if (!servers || typeof servers !== "object" || Array.isArray(servers)) continue;
196516
+ const serverMap = servers;
196517
+ const existing = serverMap.memorix;
196518
+ if (!existing || typeof existing !== "object" || Array.isArray(existing)) continue;
196519
+ serverMap.memorix = toHttpMemorixServer(existing);
196520
+ await writeFile7(filePath, JSON.stringify(record2, null, 2) + "\n", "utf-8");
196521
+ rewritten.push(filePath);
196522
+ }
196523
+ return rewritten;
196524
+ }
196375
196525
  function mergeJsonMcpConfig(existingContent, generatedContent) {
196376
196526
  let generated;
196377
196527
  try {
@@ -196473,7 +196623,7 @@ function isOwnedLegacyCodexMcp(content) {
196473
196623
  const server = new CodexMCPAdapter().parse(content).find((entry) => entry.name === "memorix");
196474
196624
  return Boolean(server && isLegacyCodexMemorixNodeServer(server));
196475
196625
  }
196476
- async function removeLegacyCodexMemorixMcpConfig(configPath = path32.join(homedir27(), ".codex", "config.toml")) {
196626
+ async function removeLegacyCodexMemorixMcpConfig(configPath = path32.join(homedir28(), ".codex", "config.toml")) {
196477
196627
  let existingContent;
196478
196628
  try {
196479
196629
  existingContent = await readFile6(configPath, "utf-8");
@@ -196530,7 +196680,7 @@ async function removeOwnedLegacyCodexPluginFolder(homeDir) {
196530
196680
  }
196531
196681
  }
196532
196682
  async function migrateLegacyCodexIntegration(options = {}) {
196533
- const homeDir = options.homeDir ?? homedir27();
196683
+ const homeDir = options.homeDir ?? homedir28();
196534
196684
  const projectRoot = options.projectRoot ?? process.cwd();
196535
196685
  const configPath = path32.join(homeDir, ".codex", "config.toml");
196536
196686
  let mcp = "missing";
@@ -196640,7 +196790,7 @@ async function installMcpConfig(options) {
196640
196790
  existingContent = null;
196641
196791
  }
196642
196792
  const generated = adapter.generate([server]);
196643
- const content = options.agent === "codex" ? mergeTomlMcpConfig(existingContent, generated) : options.agent === "hermes" ? mergeYamlMcpConfig(existingContent, generated) : options.agent === "dsh" ? mergeDshPatchConfig(existingContent, generated) : mergeJsonMcpConfig(existingContent, generated);
196793
+ const content = options.agent === "codex" || options.agent === "grok" ? mergeTomlMcpConfig(existingContent, generated) : options.agent === "hermes" ? mergeYamlMcpConfig(existingContent, generated) : options.agent === "dsh" ? mergeDshPatchConfig(existingContent, generated) : mergeJsonMcpConfig(existingContent, generated);
196644
196794
  await mkdir6(path32.dirname(configPath), { recursive: true });
196645
196795
  await writeFile7(configPath, content, "utf-8");
196646
196796
  return { agent: options.agent, configPath, transport: options.mcp };
@@ -196731,7 +196881,7 @@ async function stripCodeBuddyHookCapture(pluginPath) {
196731
196881
  await writeFile7(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
196732
196882
  }
196733
196883
  async function installCodeBuddyPluginPackage(options = {}) {
196734
- const home = options.homeDir ?? homedir27();
196884
+ const home = options.homeDir ?? homedir28();
196735
196885
  const packageRoot = findPackageRoot();
196736
196886
  const source = path32.join(packageRoot, "plugins", "codebuddy", "memorix");
196737
196887
  if (!existsSync17(source)) throw new Error(`CodeBuddy plugin template not found: ${source}`);
@@ -196751,7 +196901,7 @@ async function installCodeBuddyPluginPackage(options = {}) {
196751
196901
  };
196752
196902
  }
196753
196903
  async function installPluginPackage(options) {
196754
- const home = options.homeDir ?? homedir27();
196904
+ const home = options.homeDir ?? homedir28();
196755
196905
  const packageRoot = findPackageRoot();
196756
196906
  const source = path32.join(packageRoot, "plugins", options.agent, "memorix");
196757
196907
  const includeHooks = options.includeHooks ?? true;
@@ -196800,7 +196950,7 @@ async function installPluginPackage(options) {
196800
196950
  };
196801
196951
  }
196802
196952
  async function installGeminiExtensionPackage(options = {}) {
196803
- const home = options.homeDir ?? homedir27();
196953
+ const home = options.homeDir ?? homedir28();
196804
196954
  const packageRoot = findPackageRoot();
196805
196955
  const source = path32.join(packageRoot, "plugins", "gemini", "memorix");
196806
196956
  if (!existsSync17(source)) {
@@ -196820,7 +196970,7 @@ async function installAntigravityPluginPackage(options = {}) {
196820
196970
  if (!existsSync17(source)) {
196821
196971
  throw new Error(`Antigravity plugin template not found: ${source}`);
196822
196972
  }
196823
- const pluginPath = options.global === false ? path32.join(options.projectRoot ?? process.cwd(), ".agents", "plugins", "memorix") : path32.join(options.homeDir ?? homedir27(), ".gemini", "config", "plugins", "memorix");
196973
+ const pluginPath = options.global === false ? path32.join(options.projectRoot ?? process.cwd(), ".agents", "plugins", "memorix") : path32.join(options.homeDir ?? homedir28(), ".gemini", "config", "plugins", "memorix");
196824
196974
  await copyDir(source, pluginPath);
196825
196975
  if (options.includeHooks === false) {
196826
196976
  await rm2(path32.join(pluginPath, "hooks.json"), { force: true });
@@ -196838,7 +196988,7 @@ async function installPiPackage(options = {}) {
196838
196988
  if (!existsSync17(source)) {
196839
196989
  throw new Error(`Pi package template not found: ${source}`);
196840
196990
  }
196841
- const defaultRoot = options.global ? path32.join(options.homeDir ?? homedir27(), ".pi", "agent", "packages") : path32.join(process.cwd(), ".pi", "packages");
196991
+ const defaultRoot = options.global ? path32.join(options.homeDir ?? homedir28(), ".pi", "agent", "packages") : path32.join(process.cwd(), ".pi", "packages");
196842
196992
  const packagePath = options.packageDir ?? path32.join(defaultRoot, "memorix");
196843
196993
  await copyDir(source, packagePath);
196844
196994
  await writeOfficialSkills(path32.join(packagePath, "skills"));
@@ -196850,7 +197000,7 @@ async function installPiPackage(options = {}) {
196850
197000
  };
196851
197001
  }
196852
197002
  async function installOpenClawBundlePackage(options = {}) {
196853
- const home = options.homeDir ?? homedir27();
197003
+ const home = options.homeDir ?? homedir28();
196854
197004
  const packageRoot = findPackageRoot();
196855
197005
  const source = path32.join(packageRoot, "plugins", "openclaw", "memorix");
196856
197006
  if (!existsSync17(source)) {
@@ -196899,7 +197049,7 @@ async function installOmpPackage(options = {}) {
196899
197049
  if (!existsSync17(source)) {
196900
197050
  throw new Error(`Oh-my-Pi package template not found: ${source}`);
196901
197051
  }
196902
- const defaultRoot = options.global ? path32.join(options.homeDir ?? homedir27(), ".omp", "agent", "packages") : path32.join(process.cwd(), ".omp", "packages");
197052
+ const defaultRoot = options.global ? path32.join(options.homeDir ?? homedir28(), ".omp", "agent", "packages") : path32.join(process.cwd(), ".omp", "packages");
196903
197053
  const packagePath = options.packageDir ?? path32.join(defaultRoot, "memorix");
196904
197054
  await copyDir(source, packagePath);
196905
197055
  await writeOfficialSkills(path32.join(packagePath, "skills"));
@@ -197173,7 +197323,7 @@ ${result.stdout || ""}`.toLowerCase();
197173
197323
  }
197174
197324
  async function installAgentSetup(agent, plan, global2) {
197175
197325
  const { installAgentGuidance: installAgentGuidance2, installHooks: installHooks2 } = await Promise.resolve().then(() => (init_installers(), installers_exports));
197176
- const targetRoot = global2 ? homedir27() : process.cwd();
197326
+ const targetRoot = global2 ? homedir28() : process.cwd();
197177
197327
  const hasPluginPackage = plan.actions.includes("plugin-package") && PLUGIN_PACKAGE_AGENTS.has(agent);
197178
197328
  const hasExtensionPackage = plan.actions.includes("extension-package") && agent === "gemini-cli";
197179
197329
  const hasPiPackage = plan.actions.includes("pi-package") && agent === "pi";
@@ -197182,7 +197332,8 @@ async function installAgentSetup(agent, plan, global2) {
197182
197332
  const hasHermesPlugin = plan.actions.includes("hermes-plugin") && agent === "hermes";
197183
197333
  const hasOmpPackage = plan.actions.includes("omp-package") && agent === "omp";
197184
197334
  const hasCodeBuddyPlugin = plan.actions.includes("codebuddy-plugin") && agent === "codebuddy";
197185
- const wantsMcpConfig = plan.mcp !== "none" && agent !== "pi" && agent !== "grok" && !(agent === "codex" && !global2);
197335
+ const wantsMcpConfig = plan.mcp !== "none" && agent !== "pi" && !(agent === "codex" && !global2) && !(agent === "grok" && plan.mcp !== "http");
197336
+ const bundledMcpRoots = [];
197186
197337
  if (PLUGIN_PACKAGE_AGENTS.has(agent) && plan.includePlugin && !global2) {
197187
197338
  v3.info(`${agent}: project setup leaves user-level plugins untouched. Run \`memorix setup --agent ${agent} --global\` to install its plugin, skills, and lifecycle hooks.`);
197188
197339
  }
@@ -197191,6 +197342,7 @@ async function installAgentSetup(agent, plan, global2) {
197191
197342
  agent,
197192
197343
  includeHooks: plan.includeHooks
197193
197344
  });
197345
+ bundledMcpRoots.push(result.pluginPath);
197194
197346
  v3.success(`${agent}: plugin package -> ${result.pluginPath}`);
197195
197347
  if (result.marketplacePath) {
197196
197348
  v3.info(`${agent}: marketplace -> ${result.marketplacePath}`);
@@ -197221,6 +197373,7 @@ async function installAgentSetup(agent, plan, global2) {
197221
197373
  }
197222
197374
  if (hasCodeBuddyPlugin) {
197223
197375
  const result = await installCodeBuddyPluginPackage({ includeHooks: plan.includeHooks });
197376
+ bundledMcpRoots.push(result.pluginPath);
197224
197377
  v3.success(`${agent}: plugin -> ${result.pluginPath}`);
197225
197378
  v3.info(`${agent}: marketplace -> ${result.marketplacePath}`);
197226
197379
  const install = tryInstallCodeBuddyPlugin(result.marketplaceRoot);
@@ -197231,6 +197384,7 @@ async function installAgentSetup(agent, plan, global2) {
197231
197384
  }
197232
197385
  if (hasExtensionPackage) {
197233
197386
  const result = await installGeminiExtensionPackage();
197387
+ bundledMcpRoots.push(result.extensionPath);
197234
197388
  v3.success(`${agent}: extension package -> ${result.extensionPath}`);
197235
197389
  v3.info(result.installHint);
197236
197390
  }
@@ -197244,6 +197398,7 @@ async function installAgentSetup(agent, plan, global2) {
197244
197398
  }
197245
197399
  if (hasOpenClawBundle) {
197246
197400
  const result = await installOpenClawBundlePackage({ includeHooks: plan.includeHooks });
197401
+ bundledMcpRoots.push(result.bundlePath);
197247
197402
  v3.success(`${agent}: compatible bundle -> ${result.bundlePath}`);
197248
197403
  if (plan.includeHooks) {
197249
197404
  const install = tryInstallOpenClawBundle(result.bundlePath);
@@ -197256,6 +197411,7 @@ async function installAgentSetup(agent, plan, global2) {
197256
197411
  }
197257
197412
  if (hasAntigravityPlugin) {
197258
197413
  const result = await installAntigravityPluginPackage({ global: global2, projectRoot: targetRoot, includeHooks: plan.includeHooks });
197414
+ bundledMcpRoots.push(result.pluginPath);
197259
197415
  v3.success(`${agent}: plugin -> ${result.pluginPath}`);
197260
197416
  if (!plan.includeHooks) {
197261
197417
  v3.info(`${agent}: hooks.json skipped because --noHooks was selected`);
@@ -197286,9 +197442,17 @@ async function installAgentSetup(agent, plan, global2) {
197286
197442
  v3.info(result.installHint);
197287
197443
  }
197288
197444
  if (wantsMcpConfig) {
197289
- if ((hasPluginPackage || hasCodeBuddyPlugin || hasExtensionPackage || hasOpenClawBundle || hasAntigravityPlugin) && plan.mcp === "stdio") {
197445
+ const mcpOwnedByPackage = hasPluginPackage || hasCodeBuddyPlugin || hasExtensionPackage || hasOpenClawBundle || hasAntigravityPlugin;
197446
+ if (mcpOwnedByPackage && plan.mcp === "stdio") {
197290
197447
  const packageLabel = hasExtensionPackage ? "extension package" : hasCodeBuddyPlugin ? "CodeBuddy plugin" : hasOpenClawBundle ? "OpenClaw bundle" : hasAntigravityPlugin ? "Antigravity plugin" : "plugin package";
197291
197448
  v3.info(`${agent}: stdio MCP is bundled in the ${packageLabel}`);
197449
+ } else if (mcpOwnedByPackage && plan.mcp === "http") {
197450
+ for (const root of bundledMcpRoots) {
197451
+ const rewritten = await rewriteBundledMemorixMcpToHttp(root);
197452
+ for (const filePath of rewritten) {
197453
+ v3.success(`${agent}: plugin MCP -> HTTP ${filePath}`);
197454
+ }
197455
+ }
197292
197456
  } else {
197293
197457
  const mcp = plan.mcp === "http" ? "http" : "stdio";
197294
197458
  const result = await installMcpConfig({ agent, projectRoot: targetRoot, global: global2, mcp });
@@ -197342,7 +197506,11 @@ async function installAgentSetup(agent, plan, global2) {
197342
197506
  if (agent === "pi") {
197343
197507
  v3.info("pi: Pi has no MCP config lane in the current CLI; use the installed package extension and skill.");
197344
197508
  } else if (agent === "grok") {
197345
- v3.info("grok: lifecycle hooks write ~/.grok/hooks/memorix.json. MCP stays host-owned (do not write config.toml).");
197509
+ if (plan.mcp === "http") {
197510
+ v3.info("grok: wrote [mcp_servers.memorix] HTTP URL in Grok config.toml. Start the control plane with `memorix background start`.");
197511
+ } else {
197512
+ v3.info("grok: lifecycle hooks write ~/.grok/hooks/memorix.json. MCP stays host-owned unless you pass `--mcp http`.");
197513
+ }
197346
197514
  if (!global2) v3.info("grok: project hooks require `/hooks-trust` or launching Grok with `--trust`.");
197347
197515
  } else if (plan.mcp === "stdio") {
197348
197516
  v3.info(`${agent}: MCP server command is \`memorix serve\``);
@@ -197369,7 +197537,7 @@ function printSetupIntegrationRows() {
197369
197537
  ].join("\n");
197370
197538
  ye2(table, "Memorix integrations");
197371
197539
  }
197372
- var import_yaml5, PLUGIN_PACKAGE_AGENTS, EXTENSION_PACKAGE_AGENTS, PI_PACKAGE_AGENTS, OPENCLAW_BUNDLE_AGENTS, ANTIGRAVITY_PLUGIN_AGENTS, HERMES_PLUGIN_AGENTS, OMP_PACKAGE_AGENTS, CODEBUDDY_PLUGIN_AGENTS, PACKAGE_OWNED_INTEGRATION_AGENTS, SUPPORTED_SETUP_AGENTS, getSetupIntegrationRows2, setup_default;
197540
+ var import_yaml5, PLUGIN_PACKAGE_AGENTS, EXTENSION_PACKAGE_AGENTS, PI_PACKAGE_AGENTS, OPENCLAW_BUNDLE_AGENTS, ANTIGRAVITY_PLUGIN_AGENTS, HERMES_PLUGIN_AGENTS, OMP_PACKAGE_AGENTS, CODEBUDDY_PLUGIN_AGENTS, PACKAGE_OWNED_INTEGRATION_AGENTS, SUPPORTED_SETUP_AGENTS, getSetupIntegrationRows2, MEMORIX_HTTP_MCP_URL, setup_default;
197373
197541
  var init_setup = __esm({
197374
197542
  "src/cli/commands/setup.ts"() {
197375
197543
  "use strict";
@@ -197393,6 +197561,7 @@ var init_setup = __esm({
197393
197561
  init_dsh();
197394
197562
  init_workbuddy();
197395
197563
  init_codebuddy();
197564
+ init_grok();
197396
197565
  init_registry2();
197397
197566
  init_official_skills();
197398
197567
  init_version();
@@ -197426,6 +197595,7 @@ var init_setup = __esm({
197426
197595
  "grok"
197427
197596
  ];
197428
197597
  getSetupIntegrationRows2 = getSetupIntegrationRows;
197598
+ MEMORIX_HTTP_MCP_URL = "http://localhost:3211/mcp";
197429
197599
  setup_default = defineCommand({
197430
197600
  meta: {
197431
197601
  name: "setup",
@@ -197501,7 +197671,7 @@ var init_setup = __esm({
197501
197671
  { value: "codex", label: "Codex", hint: "plugin package + marketplace + MCP" },
197502
197672
  { value: "copilot", label: "GitHub Copilot CLI", hint: "plugin package + hooks + MCP" },
197503
197673
  { value: "cursor", label: "Cursor", hint: "MCP + rules + skills" },
197504
- { value: "grok", label: "Grok Build", hint: "hooks file; MCP stays host-owned" },
197674
+ { value: "grok", label: "Grok Build", hint: "hooks file; MCP host-owned unless --mcp http" },
197505
197675
  { value: "gemini-cli", label: "Gemini CLI", hint: "extension + MCP" },
197506
197676
  { value: "openclaw", label: "OpenClaw", hint: "bundle + hook pack + skills + MCP" },
197507
197677
  { value: "hermes", label: "Hermes Agent", hint: "plugin + hooks + commands + skills + MCP" },
@@ -197567,7 +197737,7 @@ var config_path_exports = {};
197567
197737
  __export(config_path_exports, {
197568
197738
  default: () => config_path_default
197569
197739
  });
197570
- import { homedir as homedir28 } from "os";
197740
+ import { homedir as homedir29 } from "os";
197571
197741
  var config_path_default;
197572
197742
  var init_config_path = __esm({
197573
197743
  "src/cli/commands/config-path.ts"() {
@@ -197585,7 +197755,7 @@ var init_config_path = __esm({
197585
197755
  run: async () => {
197586
197756
  const project = detectProject(process.cwd());
197587
197757
  ye2([
197588
- `Global: ${getGlobalConfigTomlPath(homedir28())}`,
197758
+ `Global: ${getGlobalConfigTomlPath(homedir29())}`,
197589
197759
  `Project: ${project ? getProjectConfigTomlPath(project.rootPath) : "not inside a git project"}`
197590
197760
  ].join("\n"), "Memorix config");
197591
197761
  }
@@ -197661,7 +197831,7 @@ __export(config_migrate_exports, {
197661
197831
  serializeResolvedConfigToToml: () => serializeResolvedConfigToToml
197662
197832
  });
197663
197833
  import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
197664
- import { homedir as homedir29 } from "os";
197834
+ import { homedir as homedir30 } from "os";
197665
197835
  import { dirname as dirname4 } from "path";
197666
197836
  function serializeResolvedConfigToToml(config2, options = {}) {
197667
197837
  const includeSecrets = options.includeSecrets === true;
@@ -197760,7 +197930,7 @@ var init_config_migrate = __esm({
197760
197930
  v3.error("Not inside a git project. Use `memorix config migrate --global` for global config.");
197761
197931
  return;
197762
197932
  }
197763
- const target = isGlobal ? getGlobalConfigTomlPath(homedir29()) : getProjectConfigTomlPath(project.rootPath);
197933
+ const target = isGlobal ? getGlobalConfigTomlPath(homedir30()) : getProjectConfigTomlPath(project.rootPath);
197764
197934
  const resolved = getResolvedConfig({ projectRoot: project?.rootPath ?? null });
197765
197935
  const content = serializeResolvedConfigToToml(resolved, { includeSecrets: isGlobal });
197766
197936
  mkdirSync4(dirname4(target), { recursive: true });
@@ -197772,8 +197942,8 @@ var init_config_migrate = __esm({
197772
197942
  });
197773
197943
 
197774
197944
  // src/cli/commands/integrate-shared.ts
197775
- import { homedir as homedir30 } from "os";
197776
- function getIntegrationTargetRoot(useGlobalDefaults, cwd, homeDir = homedir30()) {
197945
+ import { homedir as homedir31 } from "os";
197946
+ function getIntegrationTargetRoot(useGlobalDefaults, cwd, homeDir = homedir31()) {
197777
197947
  return useGlobalDefaults ? homeDir : cwd;
197778
197948
  }
197779
197949
  function getIntegrationScopeLabel(useGlobalDefaults) {
@@ -199035,7 +199205,7 @@ __export(external_provider_exports, {
199035
199205
  });
199036
199206
  import { spawn as spawn5 } from "child_process";
199037
199207
  import { existsSync as existsSync19, readFileSync as readFileSync12 } from "fs";
199038
- import { delimiter, dirname as dirname5, extname, isAbsolute as isAbsolute4, join as join24, relative as relative4, resolve as resolve6, sep as sep2 } from "path";
199208
+ import { delimiter, dirname as dirname5, extname, isAbsolute as isAbsolute4, join as join25, relative as relative4, resolve as resolve6, sep as sep2 } from "path";
199039
199209
  function boundedTimeout(value) {
199040
199210
  if (!Number.isFinite(value)) return DEFAULT_EXTERNAL_CODEGRAPH_TIMEOUT_MS;
199041
199211
  return Math.max(100, Math.min(Math.floor(value), MAX_EXTERNAL_CODEGRAPH_TIMEOUT_MS));
@@ -199064,10 +199234,10 @@ function resolveCommandCandidates(command) {
199064
199234
  }
199065
199235
  const names = extname(requested) ? [requested] : process.platform === "win32" ? [requested + ".exe", requested + ".cmd", requested + ".js", requested] : [requested];
199066
199236
  const directories = [
199067
- ...process.platform === "win32" && process.env.LOCALAPPDATA ? [join24(process.env.LOCALAPPDATA, "codegraph", "current", "bin")] : [],
199237
+ ...process.platform === "win32" && process.env.LOCALAPPDATA ? [join25(process.env.LOCALAPPDATA, "codegraph", "current", "bin")] : [],
199068
199238
  ...(process.env.PATH ?? "").split(delimiter).filter(Boolean)
199069
199239
  ];
199070
- return [...new Set(directories.flatMap((directory) => names.map((name) => join24(directory, name))))];
199240
+ return [...new Set(directories.flatMap((directory) => names.map((name) => join25(directory, name))))];
199071
199241
  }
199072
199242
  function resolveLaunch(command) {
199073
199243
  for (const candidate2 of resolveCommandCandidates(command)) {
@@ -199075,8 +199245,8 @@ function resolveLaunch(command) {
199075
199245
  const extension3 = extname(candidate2).toLowerCase();
199076
199246
  if (extension3 === ".cmd") {
199077
199247
  const bundleRoot = resolve6(dirname5(candidate2), "..");
199078
- const node = join24(bundleRoot, "node.exe");
199079
- const entry = join24(bundleRoot, "lib", "dist", "bin", "codegraph.js");
199248
+ const node = join25(bundleRoot, "node.exe");
199249
+ const entry = join25(bundleRoot, "lib", "dist", "bin", "codegraph.js");
199080
199250
  if (existsSync19(node) && existsSync19(entry)) {
199081
199251
  return { executable: node, prefixArgs: ["--liftoff-only", entry] };
199082
199252
  }
@@ -199234,7 +199404,7 @@ async function inspectExternalCodeGraph(input) {
199234
199404
  const health2 = { state: "disabled", reason: "External CodeGraph is disabled by configuration." };
199235
199405
  return { health: health2, quality: providerQuality({ mode, health: health2 }) };
199236
199406
  }
199237
- if (!existsSync19(join24(input.projectRoot, ".codegraph"))) {
199407
+ if (!existsSync19(join25(input.projectRoot, ".codegraph"))) {
199238
199408
  const health2 = { state: "not-detected", reason: "No local .codegraph index is present for this project." };
199239
199409
  return { health: health2, quality: providerQuality({ mode, health: health2 }) };
199240
199410
  }
@@ -199269,7 +199439,7 @@ async function runExternalCodeGraphLifecycle(input) {
199269
199439
  quality: providerQuality({ mode, health })
199270
199440
  };
199271
199441
  }
199272
- const indexPath = join24(input.projectRoot, ".codegraph");
199442
+ const indexPath = join25(input.projectRoot, ".codegraph");
199273
199443
  if (input.action === "sync" && !existsSync19(indexPath)) {
199274
199444
  const health = { state: "not-detected", reason: "No local .codegraph index is present for this project." };
199275
199445
  return {
@@ -200095,6 +200265,7 @@ var init_project_context = __esm({
200095
200265
  // src/store/graph-store.ts
200096
200266
  var graph_store_exports = {};
200097
200267
  __export(graph_store_exports, {
200268
+ DEFAULT_GRAPH_PROJECT_ID: () => DEFAULT_GRAPH_PROJECT_ID,
200098
200269
  GraphSqliteStore: () => GraphSqliteStore,
200099
200270
  getGraphStore: () => getGraphStore,
200100
200271
  initGraphStore: () => initGraphStore,
@@ -200124,37 +200295,106 @@ function entityToRow(entity) {
200124
200295
  observations: JSON.stringify(entity.observations ?? [])
200125
200296
  };
200126
200297
  }
200127
- async function initGraphStore(dataDir) {
200128
- if (_graphStore && _graphDataDir === dataDir) return _graphStore;
200129
- _graphStore = new GraphSqliteStore();
200130
- await _graphStore.init(dataDir);
200131
- _graphDataDir = dataDir;
200132
- return _graphStore;
200298
+ function ensureScopedGraphSchema(db2) {
200299
+ const entityColumns = db2.prepare("PRAGMA table_info(graph_entities)").all();
200300
+ if (!entityColumns.some((column) => column.name === "projectId")) {
200301
+ const migrate = db2.transaction(() => {
200302
+ db2.exec("ALTER TABLE graph_entities RENAME TO graph_entities_legacy_unscoped");
200303
+ db2.exec(CREATE_SCOPED_GRAPH_ENTITIES);
200304
+ db2.exec(`INSERT INTO graph_entities (projectId, name, entityType, observations)
200305
+ SELECT '${LEGACY_GRAPH_PROJECT_ID}', name, entityType, observations
200306
+ FROM graph_entities_legacy_unscoped`);
200307
+ db2.exec("DROP TABLE graph_entities_legacy_unscoped");
200308
+ });
200309
+ migrate();
200310
+ } else {
200311
+ db2.exec(CREATE_SCOPED_GRAPH_ENTITIES);
200312
+ }
200313
+ const relationColumns = db2.prepare("PRAGMA table_info(graph_relations)").all();
200314
+ if (!relationColumns.some((column) => column.name === "projectId")) {
200315
+ const migrate = db2.transaction(() => {
200316
+ db2.exec("ALTER TABLE graph_relations RENAME TO graph_relations_legacy_unscoped");
200317
+ db2.exec(CREATE_SCOPED_GRAPH_RELATIONS);
200318
+ db2.exec(`INSERT INTO graph_relations (projectId, from_entity, to_entity, relationType)
200319
+ SELECT '${LEGACY_GRAPH_PROJECT_ID}', from_entity, to_entity, relationType
200320
+ FROM graph_relations_legacy_unscoped`);
200321
+ db2.exec("DROP TABLE graph_relations_legacy_unscoped");
200322
+ });
200323
+ migrate();
200324
+ } else {
200325
+ db2.exec(CREATE_SCOPED_GRAPH_RELATIONS);
200326
+ }
200327
+ }
200328
+ function graphStoreKey(dataDir, projectId) {
200329
+ return `${path34.resolve(dataDir)}\0${projectId}`;
200133
200330
  }
200134
- function getGraphStore() {
200135
- if (!_graphStore) throw new Error("[memorix] GraphStore not initialized \u2014 call initGraphStore() first");
200136
- return _graphStore;
200331
+ async function initGraphStore(dataDir, projectId = DEFAULT_GRAPH_PROJECT_ID) {
200332
+ const key = graphStoreKey(dataDir, projectId);
200333
+ const existing = _graphStores.get(key);
200334
+ if (existing) {
200335
+ _graphStores.delete(key);
200336
+ _graphStores.set(key, existing);
200337
+ _activeGraphKey = key;
200338
+ return existing;
200339
+ }
200340
+ const store2 = new GraphSqliteStore();
200341
+ await store2.init(dataDir, projectId);
200342
+ _graphStores.set(key, store2);
200343
+ while (_graphStores.size > MAX_GRAPH_STORE_CACHE) {
200344
+ const oldest = _graphStores.keys().next().value;
200345
+ if (!oldest) break;
200346
+ _graphStores.delete(oldest);
200347
+ }
200348
+ _activeGraphKey = key;
200349
+ return store2;
200350
+ }
200351
+ function getGraphStore(dataDir, projectId) {
200352
+ const key = dataDir && projectId ? graphStoreKey(dataDir, projectId) : _activeGraphKey;
200353
+ const store2 = key ? _graphStores.get(key) : void 0;
200354
+ if (!store2) throw new Error("[memorix] GraphStore not initialized \u2014 call initGraphStore() first");
200355
+ return store2;
200137
200356
  }
200138
200357
  function resetGraphStore() {
200139
- if (_graphStore) {
200358
+ for (const store2 of _graphStores.values()) {
200140
200359
  try {
200141
- _graphStore.close();
200360
+ store2.close();
200142
200361
  } catch {
200143
200362
  }
200144
200363
  }
200145
- _graphStore = null;
200146
- _graphDataDir = null;
200364
+ _graphStores.clear();
200365
+ _activeGraphKey = null;
200147
200366
  }
200148
- var GraphSqliteStore, _graphStore, _graphDataDir;
200367
+ var DEFAULT_GRAPH_PROJECT_ID, LEGACY_GRAPH_PROJECT_ID, MAX_GRAPH_STORE_CACHE, CREATE_SCOPED_GRAPH_ENTITIES, CREATE_SCOPED_GRAPH_RELATIONS, GraphSqliteStore, _graphStores, _activeGraphKey;
200149
200368
  var init_graph_store = __esm({
200150
200369
  "src/store/graph-store.ts"() {
200151
200370
  "use strict";
200152
200371
  init_esm_shims();
200153
200372
  init_sqlite_db();
200154
200373
  init_persistence();
200374
+ DEFAULT_GRAPH_PROJECT_ID = "__default__";
200375
+ LEGACY_GRAPH_PROJECT_ID = "__legacy__";
200376
+ MAX_GRAPH_STORE_CACHE = 32;
200377
+ CREATE_SCOPED_GRAPH_ENTITIES = `
200378
+ CREATE TABLE IF NOT EXISTS graph_entities (
200379
+ projectId TEXT NOT NULL,
200380
+ name TEXT NOT NULL,
200381
+ entityType TEXT NOT NULL DEFAULT '',
200382
+ observations TEXT NOT NULL DEFAULT '[]',
200383
+ PRIMARY KEY (projectId, name)
200384
+ );`;
200385
+ CREATE_SCOPED_GRAPH_RELATIONS = `
200386
+ CREATE TABLE IF NOT EXISTS graph_relations (
200387
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
200388
+ projectId TEXT NOT NULL,
200389
+ from_entity TEXT NOT NULL,
200390
+ to_entity TEXT NOT NULL,
200391
+ relationType TEXT NOT NULL DEFAULT '',
200392
+ UNIQUE(projectId, from_entity, to_entity, relationType)
200393
+ );`;
200155
200394
  GraphSqliteStore = class {
200156
200395
  db = null;
200157
200396
  dataDir = "";
200397
+ projectId = DEFAULT_GRAPH_PROJECT_ID;
200158
200398
  stmtInsertEntity = null;
200159
200399
  stmtUpdateEntityObs = null;
200160
200400
  stmtDeleteEntity = null;
@@ -200164,45 +200404,89 @@ var init_graph_store = __esm({
200164
200404
  stmtDeleteRelation = null;
200165
200405
  stmtDeleteRelationsByEntity = null;
200166
200406
  stmtSelectAllRelations = null;
200167
- async init(dataDir) {
200407
+ async init(dataDir, projectId = DEFAULT_GRAPH_PROJECT_ID) {
200168
200408
  this.dataDir = dataDir;
200409
+ this.projectId = projectId;
200169
200410
  this.db = getDatabase(dataDir);
200411
+ ensureScopedGraphSchema(this.db);
200170
200412
  this.stmtInsertEntity = this.db.prepare(
200171
- `INSERT OR REPLACE INTO graph_entities (name, entityType, observations) VALUES (@name, @entityType, @observations)`
200413
+ `INSERT OR REPLACE INTO graph_entities (projectId, name, entityType, observations) VALUES (@projectId, @name, @entityType, @observations)`
200172
200414
  );
200173
200415
  this.stmtUpdateEntityObs = this.db.prepare(
200174
- `UPDATE graph_entities SET observations = @observations WHERE name = @name`
200416
+ `UPDATE graph_entities SET observations = @observations WHERE projectId = @projectId AND name = @name`
200175
200417
  );
200176
- this.stmtDeleteEntity = this.db.prepare(`DELETE FROM graph_entities WHERE name = ?`);
200177
- this.stmtSelectAllEntities = this.db.prepare(`SELECT * FROM graph_entities`);
200178
- this.stmtSelectEntityByName = this.db.prepare(`SELECT * FROM graph_entities WHERE name = ?`);
200418
+ this.stmtDeleteEntity = this.db.prepare(`DELETE FROM graph_entities WHERE projectId = ? AND name = ?`);
200419
+ this.stmtSelectAllEntities = this.db.prepare(`SELECT * FROM graph_entities WHERE projectId = ? ORDER BY name`);
200420
+ this.stmtSelectEntityByName = this.db.prepare(`SELECT * FROM graph_entities WHERE projectId = ? AND name = ?`);
200179
200421
  this.stmtInsertRelation = this.db.prepare(
200180
- `INSERT OR IGNORE INTO graph_relations (from_entity, to_entity, relationType) VALUES (@from, @to, @relationType)`
200422
+ `INSERT OR IGNORE INTO graph_relations (projectId, from_entity, to_entity, relationType) VALUES (@projectId, @from, @to, @relationType)`
200181
200423
  );
200182
200424
  this.stmtDeleteRelation = this.db.prepare(
200183
- `DELETE FROM graph_relations WHERE from_entity = ? AND to_entity = ? AND relationType = ?`
200425
+ `DELETE FROM graph_relations WHERE projectId = ? AND from_entity = ? AND to_entity = ? AND relationType = ?`
200184
200426
  );
200185
200427
  this.stmtDeleteRelationsByEntity = this.db.prepare(
200186
- `DELETE FROM graph_relations WHERE from_entity = ? OR to_entity = ?`
200428
+ `DELETE FROM graph_relations WHERE projectId = ? AND (from_entity = ? OR to_entity = ?)`
200187
200429
  );
200188
- this.stmtSelectAllRelations = this.db.prepare(`SELECT * FROM graph_relations`);
200430
+ this.stmtSelectAllRelations = this.db.prepare(`SELECT * FROM graph_relations WHERE projectId = ? ORDER BY id`);
200189
200431
  await this.migrateFromJsonlIfNeeded();
200190
200432
  }
200191
200433
  async migrateFromJsonlIfNeeded() {
200192
- const count3 = this.db.prepare(`SELECT COUNT(*) AS cnt FROM graph_entities`).get();
200434
+ const count3 = this.db.prepare(`SELECT COUNT(*) AS cnt FROM graph_entities WHERE projectId = ?`).get(this.projectId);
200435
+ const totalCount = this.db.prepare(`SELECT COUNT(*) AS cnt FROM graph_entities`).get();
200193
200436
  if (count3.cnt > 0) return;
200437
+ if (totalCount.cnt > 0) return;
200194
200438
  const jsonlPath = path34.join(this.dataDir, "graph.jsonl");
200195
200439
  if (!fs20.existsSync(jsonlPath)) return;
200196
200440
  try {
200197
200441
  const data = await loadGraphJsonl(this.dataDir);
200198
200442
  if (data.entities.length === 0 && data.relations.length === 0) return;
200199
200443
  console.error(`[memorix] Migrating graph from JSONL to SQLite (${data.entities.length} entities, ${data.relations.length} relations)...`);
200444
+ const observations2 = this.db.prepare("SELECT id, projectId FROM observations").all();
200445
+ const projectByObservationId = new Map(observations2.map((row) => [row.id, row.projectId]));
200446
+ const projectsByEntity = /* @__PURE__ */ new Map();
200447
+ for (const entity of data.entities) {
200448
+ const projects = /* @__PURE__ */ new Set();
200449
+ for (const content of entity.observations ?? []) {
200450
+ const match2 = /^\[#(\d+)\]/.exec(content);
200451
+ const projectId = match2 ? projectByObservationId.get(Number(match2[1])) : void 0;
200452
+ if (projectId) projects.add(projectId);
200453
+ }
200454
+ projectsByEntity.set(entity.name, projects);
200455
+ }
200456
+ let changed = true;
200457
+ while (changed) {
200458
+ changed = false;
200459
+ for (const relation of data.relations) {
200460
+ const from = projectsByEntity.get(relation.from) ?? /* @__PURE__ */ new Set();
200461
+ const to2 = projectsByEntity.get(relation.to) ?? /* @__PURE__ */ new Set();
200462
+ if (from.size > 0 && to2.size === 0) {
200463
+ projectsByEntity.set(relation.to, new Set(from));
200464
+ changed = true;
200465
+ } else if (to2.size > 0 && from.size === 0) {
200466
+ projectsByEntity.set(relation.from, new Set(to2));
200467
+ changed = true;
200468
+ }
200469
+ }
200470
+ }
200471
+ const entityRows = data.entities.flatMap((entity) => {
200472
+ const projects = projectsByEntity.get(entity.name) ?? /* @__PURE__ */ new Set();
200473
+ const owners = projects.size > 0 ? [...projects] : [this.projectId];
200474
+ return owners.map((projectId) => ({ ...entityToRow(entity), projectId }));
200475
+ });
200476
+ const projectSet = new Set(entityRows.map((row) => String(row.projectId)));
200477
+ const relationRows = data.relations.flatMap((relation) => {
200478
+ const from = projectsByEntity.get(relation.from) ?? /* @__PURE__ */ new Set();
200479
+ const to2 = projectsByEntity.get(relation.to) ?? /* @__PURE__ */ new Set();
200480
+ const shared = [...from].filter((projectId) => to2.has(projectId));
200481
+ const owners = shared.length > 0 ? shared : from.size === 0 && to2.size === 0 ? [this.projectId] : [];
200482
+ return owners.filter((projectId) => projectSet.has(projectId)).map((projectId) => ({ projectId, from: relation.from, to: relation.to, relationType: relation.relationType }));
200483
+ });
200200
200484
  const insertAll = this.db.transaction(() => {
200201
- for (const entity of data.entities) {
200202
- this.stmtInsertEntity.run(entityToRow(entity));
200485
+ for (const entity of entityRows) {
200486
+ this.stmtInsertEntity.run(entity);
200203
200487
  }
200204
- for (const rel of data.relations) {
200205
- this.stmtInsertRelation.run({ from: rel.from, to: rel.to, relationType: rel.relationType });
200488
+ for (const relation of relationRows) {
200489
+ this.stmtInsertRelation.run(relation);
200206
200490
  }
200207
200491
  });
200208
200492
  insertAll();
@@ -200212,10 +200496,10 @@ var init_graph_store = __esm({
200212
200496
  }
200213
200497
  }
200214
200498
  loadEntities() {
200215
- return this.stmtSelectAllEntities.all().map(rowToEntity);
200499
+ return this.stmtSelectAllEntities.all(this.projectId).map(rowToEntity);
200216
200500
  }
200217
200501
  loadRelations() {
200218
- return this.stmtSelectAllRelations.all().map((row) => ({
200502
+ return this.stmtSelectAllRelations.all(this.projectId).map((row) => ({
200219
200503
  from: row.from_entity,
200220
200504
  to: row.to_entity,
200221
200505
  relationType: row.relationType
@@ -200224,7 +200508,7 @@ var init_graph_store = __esm({
200224
200508
  insertEntities(entities) {
200225
200509
  const insertAll = this.db.transaction(() => {
200226
200510
  for (const entity of entities) {
200227
- this.stmtInsertEntity.run(entityToRow(entity));
200511
+ this.stmtInsertEntity.run({ ...entityToRow(entity), projectId: this.projectId });
200228
200512
  }
200229
200513
  });
200230
200514
  insertAll();
@@ -200232,7 +200516,7 @@ var init_graph_store = __esm({
200232
200516
  insertRelations(relations) {
200233
200517
  const insertAll = this.db.transaction(() => {
200234
200518
  for (const rel of relations) {
200235
- this.stmtInsertRelation.run({ from: rel.from, to: rel.to, relationType: rel.relationType });
200519
+ this.stmtInsertRelation.run({ projectId: this.projectId, from: rel.from, to: rel.to, relationType: rel.relationType });
200236
200520
  }
200237
200521
  });
200238
200522
  insertAll();
@@ -200240,8 +200524,8 @@ var init_graph_store = __esm({
200240
200524
  deleteEntities(names) {
200241
200525
  const deleteAll = this.db.transaction(() => {
200242
200526
  for (const name of names) {
200243
- this.stmtDeleteRelationsByEntity.run(name, name);
200244
- this.stmtDeleteEntity.run(name);
200527
+ this.stmtDeleteRelationsByEntity.run(this.projectId, name, name);
200528
+ this.stmtDeleteEntity.run(this.projectId, name);
200245
200529
  }
200246
200530
  });
200247
200531
  deleteAll();
@@ -200249,7 +200533,7 @@ var init_graph_store = __esm({
200249
200533
  deleteRelations(relations) {
200250
200534
  const deleteAll = this.db.transaction(() => {
200251
200535
  for (const rel of relations) {
200252
- this.stmtDeleteRelation.run(rel.from, rel.to, rel.relationType);
200536
+ this.stmtDeleteRelation.run(this.projectId, rel.from, rel.to, rel.relationType);
200253
200537
  }
200254
200538
  });
200255
200539
  deleteAll();
@@ -200257,13 +200541,13 @@ var init_graph_store = __esm({
200257
200541
  addObservations(updates) {
200258
200542
  const updateAll = this.db.transaction(() => {
200259
200543
  for (const u5 of updates) {
200260
- const row = this.stmtSelectEntityByName.get(u5.entityName);
200544
+ const row = this.stmtSelectEntityByName.get(this.projectId, u5.entityName);
200261
200545
  if (!row) continue;
200262
200546
  const existing = safeJsonParse3(row.observations, []);
200263
200547
  const newObs = u5.contents.filter((c6) => !existing.includes(c6));
200264
200548
  if (newObs.length > 0) {
200265
200549
  existing.push(...newObs);
200266
- this.stmtUpdateEntityObs.run({ name: u5.entityName, observations: JSON.stringify(existing) });
200550
+ this.stmtUpdateEntityObs.run({ projectId: this.projectId, name: u5.entityName, observations: JSON.stringify(existing) });
200267
200551
  }
200268
200552
  }
200269
200553
  });
@@ -200272,11 +200556,11 @@ var init_graph_store = __esm({
200272
200556
  deleteObservations(deletions) {
200273
200557
  const deleteAll = this.db.transaction(() => {
200274
200558
  for (const d4 of deletions) {
200275
- const row = this.stmtSelectEntityByName.get(d4.entityName);
200559
+ const row = this.stmtSelectEntityByName.get(this.projectId, d4.entityName);
200276
200560
  if (!row) continue;
200277
200561
  const existing = safeJsonParse3(row.observations, []);
200278
200562
  const filtered = existing.filter((o4) => !d4.observations.includes(o4));
200279
- this.stmtUpdateEntityObs.run({ name: d4.entityName, observations: JSON.stringify(filtered) });
200563
+ this.stmtUpdateEntityObs.run({ projectId: this.projectId, name: d4.entityName, observations: JSON.stringify(filtered) });
200280
200564
  }
200281
200565
  });
200282
200566
  deleteAll();
@@ -200287,13 +200571,13 @@ var init_graph_store = __esm({
200287
200571
  */
200288
200572
  replaceAll(entities, relations) {
200289
200573
  const replaceAll = this.db.transaction(() => {
200290
- this.db.prepare("DELETE FROM graph_relations").run();
200291
- this.db.prepare("DELETE FROM graph_entities").run();
200574
+ this.db.prepare("DELETE FROM graph_relations WHERE projectId = ?").run(this.projectId);
200575
+ this.db.prepare("DELETE FROM graph_entities WHERE projectId = ?").run(this.projectId);
200292
200576
  for (const entity of entities) {
200293
- this.stmtInsertEntity.run(entityToRow(entity));
200577
+ this.stmtInsertEntity.run({ ...entityToRow(entity), projectId: this.projectId });
200294
200578
  }
200295
200579
  for (const rel of relations) {
200296
- this.stmtInsertRelation.run({ from: rel.from, to: rel.to, relationType: rel.relationType });
200580
+ this.stmtInsertRelation.run({ projectId: this.projectId, from: rel.from, to: rel.to, relationType: rel.relationType });
200297
200581
  }
200298
200582
  });
200299
200583
  replaceAll();
@@ -200301,8 +200585,8 @@ var init_graph_store = __esm({
200301
200585
  close() {
200302
200586
  }
200303
200587
  };
200304
- _graphStore = null;
200305
- _graphDataDir = null;
200588
+ _graphStores = /* @__PURE__ */ new Map();
200589
+ _activeGraphKey = null;
200306
200590
  }
200307
200591
  });
200308
200592
 
@@ -200317,11 +200601,14 @@ var init_graph = __esm({
200317
200601
  entities = [];
200318
200602
  relations = [];
200319
200603
  projectDir;
200604
+ projectId;
200605
+ store = null;
200320
200606
  initialized = false;
200321
200607
  /** Index: lowercase entity name → Entity for O(1) lookups */
200322
200608
  entityIndex = /* @__PURE__ */ new Map();
200323
- constructor(projectDir2) {
200609
+ constructor(projectDir2, projectId = "__default__") {
200324
200610
  this.projectDir = projectDir2;
200611
+ this.projectId = projectId;
200325
200612
  }
200326
200613
  /** Rebuild the entity name index */
200327
200614
  rebuildIndex() {
@@ -200333,10 +200620,9 @@ var init_graph = __esm({
200333
200620
  /** Load graph from SQLite on first access */
200334
200621
  async init() {
200335
200622
  if (this.initialized) return;
200336
- await initGraphStore(this.projectDir);
200337
- const store2 = getGraphStore();
200338
- this.entities = store2.loadEntities();
200339
- this.relations = store2.loadRelations();
200623
+ this.store = await initGraphStore(this.projectDir, this.projectId);
200624
+ this.entities = this.store.loadEntities();
200625
+ this.relations = this.store.loadRelations();
200340
200626
  this.rebuildIndex();
200341
200627
  this.initialized = true;
200342
200628
  }
@@ -200350,8 +200636,8 @@ var init_graph = __esm({
200350
200636
  }
200351
200637
  /** Persist current state to SQLite */
200352
200638
  async save() {
200353
- const store2 = getGraphStore();
200354
- store2.replaceAll(this.entities, this.relations);
200639
+ if (!this.store) throw new Error("[memorix] KnowledgeGraphManager not initialized");
200640
+ this.store.replaceAll(this.entities, this.relations);
200355
200641
  }
200356
200642
  /** Create new entities (skip duplicates by name) */
200357
200643
  async createEntities(entities) {
@@ -200727,7 +201013,7 @@ async function getSessionContext(projectDir2, projectId, limit2 = 3, reader2) {
200727
201013
  let graphNeighbors = [];
200728
201014
  if (!reader2 && activeEntities.length > 0) {
200729
201015
  try {
200730
- const graphMgr = new KnowledgeGraphManager(projectDir2);
201016
+ const graphMgr = new KnowledgeGraphManager(projectDir2, projectId);
200731
201017
  await graphMgr.init();
200732
201018
  const { relations } = await graphMgr.readGraph();
200733
201019
  const activeSet = new Set(activeEntities.map((n3) => n3.toLowerCase()));
@@ -205235,8 +205521,8 @@ __export(engine_exports2, {
205235
205521
  SkillsEngine: () => SkillsEngine
205236
205522
  });
205237
205523
  import { existsSync as existsSync21, readFileSync as readFileSync13, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, readdirSync as readdirSync3 } from "fs";
205238
- import { join as join25 } from "path";
205239
- import { homedir as homedir31 } from "os";
205524
+ import { join as join26 } from "path";
205525
+ import { homedir as homedir32 } from "os";
205240
205526
  var SKILLS_DIRS, SKILL_WORTHY_TYPES, MIN_OBS_FOR_SKILL, MIN_SCORE_FOR_SKILL, LOW_SIGNAL_TITLE_PATTERNS, LOW_SIGNAL_ENTITY_PATTERNS, COMMAND_TRACE_PATTERNS2, SkillsEngine;
205241
205527
  var init_engine4 = __esm({
205242
205528
  "src/skills/engine.ts"() {
@@ -205307,12 +205593,12 @@ var init_engine4 = __esm({
205307
205593
  listSkills() {
205308
205594
  const skills = [];
205309
205595
  const seen = /* @__PURE__ */ new Set();
205310
- const home = homedir31();
205596
+ const home = homedir32();
205311
205597
  for (const [agent, dirs] of Object.entries(SKILLS_DIRS)) {
205312
205598
  for (const dir of dirs) {
205313
- const paths = [join25(this.projectRoot, dir)];
205599
+ const paths = [join26(this.projectRoot, dir)];
205314
205600
  if (!this.skipGlobal) {
205315
- paths.push(join25(home, dir));
205601
+ paths.push(join26(home, dir));
205316
205602
  }
205317
205603
  for (const skillsRoot of paths) {
205318
205604
  if (!existsSync21(skillsRoot)) continue;
@@ -205322,7 +205608,7 @@ var init_engine4 = __esm({
205322
205608
  if (!entry.isDirectory()) continue;
205323
205609
  const name = entry.name;
205324
205610
  if (seen.has(name)) continue;
205325
- const skillMd = join25(skillsRoot, name, "SKILL.md");
205611
+ const skillMd = join26(skillsRoot, name, "SKILL.md");
205326
205612
  if (!existsSync21(skillMd)) continue;
205327
205613
  try {
205328
205614
  const content = readFileSync13(skillMd, "utf-8");
@@ -205330,7 +205616,7 @@ var init_engine4 = __esm({
205330
205616
  skills.push({
205331
205617
  name,
205332
205618
  description,
205333
- sourcePath: join25(skillsRoot, name),
205619
+ sourcePath: join26(skillsRoot, name),
205334
205620
  sourceAgent: agent,
205335
205621
  content,
205336
205622
  generated: false
@@ -205373,11 +205659,11 @@ var init_engine4 = __esm({
205373
205659
  writeSkill(skill2, target) {
205374
205660
  const dirs = SKILLS_DIRS[target];
205375
205661
  if (!dirs || dirs.length === 0) return null;
205376
- const targetDir = join25(this.projectRoot, dirs[0], skill2.name);
205662
+ const targetDir = join26(this.projectRoot, dirs[0], skill2.name);
205377
205663
  try {
205378
205664
  mkdirSync5(targetDir, { recursive: true });
205379
- writeFileSync5(join25(targetDir, "SKILL.md"), skill2.content, "utf-8");
205380
- return join25(dirs[0], skill2.name, "SKILL.md");
205665
+ writeFileSync5(join26(targetDir, "SKILL.md"), skill2.content, "utf-8");
205666
+ return join26(dirs[0], skill2.name, "SKILL.md");
205381
205667
  } catch {
205382
205668
  return null;
205383
205669
  }
@@ -206546,7 +206832,7 @@ var init_task_graph = __esm({
206546
206832
  // src/orchestrate/context-collector.ts
206547
206833
  import { execFileSync as execFileSync2 } from "child_process";
206548
206834
  import { readFileSync as readFileSync14, existsSync as existsSync22 } from "fs";
206549
- import { join as join26 } from "path";
206835
+ import { join as join27 } from "path";
206550
206836
  function normalizeEntryLimit(value, fallback) {
206551
206837
  if (!Number.isSafeInteger(value) || value < 1) return fallback;
206552
206838
  return Math.min(value, 500);
@@ -206621,7 +206907,7 @@ function collectFileTree(projectDir2, maxEntries) {
206621
206907
  }
206622
206908
  function collectDependencies(projectDir2) {
206623
206909
  try {
206624
- const pkgPath = join26(projectDir2, "package.json");
206910
+ const pkgPath = join27(projectDir2, "package.json");
206625
206911
  if (!existsSync22(pkgPath)) return "";
206626
206912
  const pkg = JSON.parse(readFileSync14(pkgPath, "utf-8"));
206627
206913
  const parts = [];
@@ -207958,7 +208244,7 @@ var init_tool_profile = __esm({
207958
208244
  function getMcpServerInfo() {
207959
208245
  return {
207960
208246
  name: "memorix",
207961
- version: true ? "1.9.3" : "1.0.1"
208247
+ version: true ? "1.9.5" : "1.0.1"
207962
208248
  };
207963
208249
  }
207964
208250
  function createMcpDiscoverResult(capabilities, serverInfo) {
@@ -221764,8 +222050,8 @@ __export(engine_exports3, {
221764
222050
  WorkspaceSyncEngine: () => WorkspaceSyncEngine
221765
222051
  });
221766
222052
  import { readFileSync as readFileSync16, readdirSync as readdirSync4, existsSync as existsSync25, cpSync, mkdirSync as mkdirSync7 } from "fs";
221767
- import { join as join28 } from "path";
221768
- import { homedir as homedir32 } from "os";
222053
+ import { join as join29 } from "path";
222054
+ import { homedir as homedir33 } from "os";
221769
222055
  var WorkspaceSyncEngine;
221770
222056
  var init_engine5 = __esm({
221771
222057
  "src/workspace/engine.ts"() {
@@ -221837,9 +222123,9 @@ var init_engine5 = __esm({
221837
222123
  const globalPath = adapter.getConfigPath();
221838
222124
  const pathsToCheck = [configPath, globalPath];
221839
222125
  if (target === "antigravity") {
221840
- pathsToCheck.push(join28(this.projectRoot, ".gemini", "settings.json"));
221841
- pathsToCheck.push(join28(homedir32(), ".gemini", "settings.json"));
221842
- pathsToCheck.push(join28(homedir32(), ".gemini", "antigravity", "mcp_config.json"));
222126
+ pathsToCheck.push(join29(this.projectRoot, ".gemini", "settings.json"));
222127
+ pathsToCheck.push(join29(homedir33(), ".gemini", "settings.json"));
222128
+ pathsToCheck.push(join29(homedir33(), ".gemini", "antigravity", "mcp_config.json"));
221843
222129
  }
221844
222130
  const merged = /* @__PURE__ */ new Map();
221845
222131
  for (const path50 of pathsToCheck) {
@@ -221956,7 +222242,7 @@ var init_engine5 = __esm({
221956
222242
  getTargetSkillsDir(target) {
221957
222243
  const dirs = _WorkspaceSyncEngine.SKILLS_DIRS[target];
221958
222244
  if (!dirs || dirs.length === 0) return null;
221959
- return join28(this.projectRoot, dirs[0]);
222245
+ return join29(this.projectRoot, dirs[0]);
221960
222246
  }
221961
222247
  /**
221962
222248
  * Scan all agent skills directories and collect unique skills.
@@ -221965,12 +222251,12 @@ var init_engine5 = __esm({
221965
222251
  const skills = [];
221966
222252
  const conflicts = [];
221967
222253
  const seen = /* @__PURE__ */ new Map();
221968
- const home = homedir32();
222254
+ const home = homedir33();
221969
222255
  for (const [agent, dirs] of Object.entries(_WorkspaceSyncEngine.SKILLS_DIRS)) {
221970
222256
  for (const dir of dirs) {
221971
222257
  const paths = [
221972
- join28(this.projectRoot, dir),
221973
- join28(home, dir)
222258
+ join29(this.projectRoot, dir),
222259
+ join29(home, dir)
221974
222260
  ];
221975
222261
  for (const skillsRoot of paths) {
221976
222262
  if (!existsSync25(skillsRoot)) continue;
@@ -221978,7 +222264,7 @@ var init_engine5 = __esm({
221978
222264
  const entries = readdirSync4(skillsRoot, { withFileTypes: true });
221979
222265
  for (const entry of entries) {
221980
222266
  if (!entry.isDirectory()) continue;
221981
- const skillMd = join28(skillsRoot, entry.name, "SKILL.md");
222267
+ const skillMd = join29(skillsRoot, entry.name, "SKILL.md");
221982
222268
  if (!existsSync25(skillMd)) continue;
221983
222269
  let description = "";
221984
222270
  try {
@@ -221990,7 +222276,7 @@ var init_engine5 = __esm({
221990
222276
  const newEntry = {
221991
222277
  name: entry.name,
221992
222278
  description,
221993
- sourcePath: join28(skillsRoot, entry.name),
222279
+ sourcePath: join29(skillsRoot, entry.name),
221994
222280
  sourceAgent: agent
221995
222281
  };
221996
222282
  const existing = seen.get(entry.name);
@@ -222027,7 +222313,7 @@ var init_engine5 = __esm({
222027
222313
  }
222028
222314
  for (const skill2 of skills) {
222029
222315
  if (skill2.sourceAgent === target) continue;
222030
- const dest = join28(targetDir, skill2.name);
222316
+ const dest = join29(targetDir, skill2.name);
222031
222317
  if (existsSync25(dest)) {
222032
222318
  skipped.push(`${skill2.name} (already exists in ${target})`);
222033
222319
  continue;
@@ -222043,13 +222329,13 @@ var init_engine5 = __esm({
222043
222329
  }
222044
222330
  scanWorkflows() {
222045
222331
  const workflows = [];
222046
- const wfDir = join28(this.projectRoot, ".windsurf", "workflows");
222332
+ const wfDir = join29(this.projectRoot, ".windsurf", "workflows");
222047
222333
  if (!existsSync25(wfDir)) return workflows;
222048
222334
  try {
222049
222335
  const files = readdirSync4(wfDir).filter((f7) => f7.endsWith(".md"));
222050
222336
  for (const file2 of files) {
222051
222337
  try {
222052
- const content = readFileSync16(join28(wfDir, file2), "utf-8");
222338
+ const content = readFileSync16(join29(wfDir, file2), "utf-8");
222053
222339
  workflows.push(this.workflowSyncer.parseWindsurfWorkflow(file2, content));
222054
222340
  } catch {
222055
222341
  }
@@ -222324,7 +222610,7 @@ __export(behavior_exports, {
222324
222610
  resetBehaviorConfigCache: () => resetBehaviorConfigCache
222325
222611
  });
222326
222612
  import { existsSync as existsSync26, readFileSync as readFileSync17 } from "fs";
222327
- import { homedir as homedir33 } from "os";
222613
+ import { homedir as homedir34 } from "os";
222328
222614
  function isSessionInject(value) {
222329
222615
  return value === "full" || value === "minimal" || value === "silent";
222330
222616
  }
@@ -222342,7 +222628,7 @@ function legacyBehavior(homeDir) {
222342
222628
  }
222343
222629
  }
222344
222630
  function getBehaviorConfig(options = {}) {
222345
- const homeDir = options.homeDir ?? homedir33();
222631
+ const homeDir = options.homeDir ?? homedir34();
222346
222632
  const projectRoot = options.projectRoot === void 0 ? detectProject()?.rootPath ?? null : options.projectRoot;
222347
222633
  const cacheKey2 = `${homeDir}\0${projectRoot ?? ""}`;
222348
222634
  const cached2 = cache4.get(cacheKey2);
@@ -223580,8 +223866,7 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
223580
223866
  break;
223581
223867
  }
223582
223868
  case "/graph": {
223583
- await initGraphStore(effectiveDataDir);
223584
- const gStore = getGraphStore();
223869
+ const gStore = await initGraphStore(effectiveDataDir, effectiveProjectId);
223585
223870
  const graph = { entities: gStore.loadEntities(), relations: gStore.loadRelations() };
223586
223871
  const graphObs = filterDashboardObservations(
223587
223872
  await getObservationStore().loadByProject(effectiveProjectId, { status: "active" }),
@@ -223625,8 +223910,8 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
223625
223910
  break;
223626
223911
  }
223627
223912
  case "/stats": {
223628
- await initGraphStore(effectiveDataDir);
223629
- const graph = { entities: getGraphStore().loadEntities(), relations: getGraphStore().loadRelations() };
223913
+ const gStore = await initGraphStore(effectiveDataDir, effectiveProjectId);
223914
+ const graph = { entities: gStore.loadEntities(), relations: gStore.loadRelations() };
223630
223915
  const observations2 = filterDashboardObservations(
223631
223916
  await getObservationStore().loadByProject(effectiveProjectId, { status: "active" }),
223632
223917
  effectiveProjectId
@@ -223819,15 +224104,15 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
223819
224104
  case "/knowledge-graph": {
223820
224105
  const { generateKnowledgeGraph: generateKnowledgeGraph2 } = await Promise.resolve().then(() => (init_knowledge_graph(), knowledge_graph_exports));
223821
224106
  const { initMiniSkillStore: initMiniSkillStore2, getMiniSkillStore: getMiniSkillStore2 } = await Promise.resolve().then(() => (init_mini_skill_store(), mini_skill_store_exports));
223822
- const { initGraphStore: initGraphStore2, getGraphStore: getGraphStore2 } = await Promise.resolve().then(() => (init_graph_store(), graph_store_exports));
224107
+ const { initGraphStore: initGraphStore2 } = await Promise.resolve().then(() => (init_graph_store(), graph_store_exports));
223823
224108
  await initMiniSkillStore2(effectiveDataDir);
223824
- await initGraphStore2(effectiveDataDir);
224109
+ const gStore = await initGraphStore2(effectiveDataDir, effectiveProjectId);
223825
224110
  const allObs = filterDashboardObservations(
223826
224111
  await getObservationStore().loadByProject(effectiveProjectId, { status: "active" }),
223827
224112
  effectiveProjectId
223828
224113
  );
223829
224114
  const skills = await getMiniSkillStore2().loadByProject(effectiveProjectId);
223830
- const fullGraph = { entities: getGraphStore2().loadEntities(), relations: getGraphStore2().loadRelations() };
224115
+ const fullGraph = { entities: gStore.loadEntities(), relations: gStore.loadRelations() };
223831
224116
  const scoped = scopeKnowledgeGraphToProject(fullGraph, allObs);
223832
224117
  const graph = generateKnowledgeGraph2({
223833
224118
  projectId: effectiveProjectId,
@@ -223842,7 +224127,7 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
223842
224127
  case "/config": {
223843
224128
  const os10 = await import("os");
223844
224129
  const { existsSync: existsSync36 } = await import("fs");
223845
- const { join: join35 } = await import("path");
224130
+ const { join: join36 } = await import("path");
223846
224131
  const configProjectRoot = effectiveProjectRoot;
223847
224132
  const resolved = getResolvedConfig({ projectRoot: configProjectRoot });
223848
224133
  const files = {
@@ -223857,13 +224142,13 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
223857
224142
  try {
223858
224143
  const home = os10.homedir();
223859
224144
  const paths = {
223860
- "project memorix.yml": configProjectRoot ? join35(configProjectRoot, "memorix.yml") : null,
223861
- "user memorix.yml": join35(home, ".memorix", "memorix.yml"),
223862
- "project memorix.toml": configProjectRoot ? join35(configProjectRoot, "memorix.toml") : null,
223863
- "user config.toml": join35(home, ".memorix", "config.toml"),
223864
- "project .env": configProjectRoot ? join35(configProjectRoot, ".env") : null,
223865
- "user .env": join35(home, ".memorix", ".env"),
223866
- "legacy config.json": join35(home, ".memorix", "config.json")
224145
+ "project memorix.yml": configProjectRoot ? join36(configProjectRoot, "memorix.yml") : null,
224146
+ "user memorix.yml": join36(home, ".memorix", "memorix.yml"),
224147
+ "project memorix.toml": configProjectRoot ? join36(configProjectRoot, "memorix.toml") : null,
224148
+ "user config.toml": join36(home, ".memorix", "config.toml"),
224149
+ "project .env": configProjectRoot ? join36(configProjectRoot, ".env") : null,
224150
+ "user .env": join36(home, ".memorix", ".env"),
224151
+ "legacy config.json": join36(home, ".memorix", "config.json")
223867
224152
  };
223868
224153
  for (const [key, fpath] of Object.entries(paths)) {
223869
224154
  if (fpath === null) {
@@ -224024,8 +224309,7 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
224024
224309
  } else {
224025
224310
  await obsStore.remove(obsId);
224026
224311
  try {
224027
- await initGraphStore(effectiveDataDir);
224028
- const gStore = getGraphStore();
224312
+ const gStore = await initGraphStore(effectiveDataDir, effectiveProjectId);
224029
224313
  const prefix = `[#${obsId}] `;
224030
224314
  const deletions = [];
224031
224315
  for (const entity of gStore.loadEntities()) {
@@ -224040,8 +224324,8 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
224040
224324
  break;
224041
224325
  }
224042
224326
  if (apiPath === "/export") {
224043
- await initGraphStore(effectiveDataDir);
224044
- const fullGraph = { entities: getGraphStore().loadEntities(), relations: getGraphStore().loadRelations() };
224327
+ const gStore = await initGraphStore(effectiveDataDir, effectiveProjectId);
224328
+ const fullGraph = { entities: gStore.loadEntities(), relations: gStore.loadRelations() };
224045
224329
  const observations2 = filterDashboardObservations(
224046
224330
  await getObservationStore().loadByProject(effectiveProjectId, { status: "active" }),
224047
224331
  effectiveProjectId
@@ -264565,7 +264849,7 @@ async function createMemorixServer(cwd, existingServer, sharedTeam, options = {}
264565
264849
  const store2 = getObservationStore();
264566
264850
  console.error(`[memorix] ObservationStore backend: ${store2.getBackendName()}, generation: ${store2.getGeneration()}`);
264567
264851
  }
264568
- let graphManager = new KnowledgeGraphManager(projectDir2);
264852
+ let graphManager = new KnowledgeGraphManager(projectDir2, project.id);
264569
264853
  if (!deferProjectRuntimeInit) {
264570
264854
  await graphManager.init();
264571
264855
  await initObservations(projectDir2);
@@ -264577,7 +264861,7 @@ async function createMemorixServer(cwd, existingServer, sharedTeam, options = {}
264577
264861
  await initObservationStore(projectDir2);
264578
264862
  await initMiniSkillStore(projectDir2);
264579
264863
  await initSessionStore(projectDir2);
264580
- graphManager = new KnowledgeGraphManager(projectDir2);
264864
+ graphManager = new KnowledgeGraphManager(projectDir2, project.id);
264581
264865
  await graphManager.init();
264582
264866
  await initObservations(projectDir2);
264583
264867
  const indexed = await prepareSearchIndex();
@@ -266789,10 +267073,10 @@ ${Math.max(0, managedIds.size - result.resolved.length)} visible writable observ
266789
267073
  );
266790
267074
  let enableKG = isToolInProfile("create_entities", toolProfile);
266791
267075
  try {
266792
- const { homedir: homedir37 } = await import("os");
266793
- const { join: join35 } = await import("path");
267076
+ const { homedir: homedir38 } = await import("os");
267077
+ const { join: join36 } = await import("path");
266794
267078
  const { readFile: readFile11 } = await import("fs/promises");
266795
- const raw = await readFile11(join35(homedir37(), ".memorix", "settings.json"), "utf-8");
267079
+ const raw = await readFile11(join36(homedir38(), ".memorix", "settings.json"), "utf-8");
266796
267080
  const s4 = JSON.parse(raw);
266797
267081
  if (s4.knowledgeGraph === true) enableKG = true;
266798
267082
  } catch {
@@ -268636,6 +268920,7 @@ ${lines.join("\n")}` }] };
268636
268920
  } : {},
268637
268921
  ...mediaGenerationEnabled ? {
268638
268922
  image: external_exports2.string().optional().describe("Base64-encoded reference image for image-to-image generation."),
268923
+ firstFrameImageUrl: external_exports2.string().optional().describe("Public HTTPS first-frame image URL for MiniMax video generation; stored with the queued job."),
268639
268924
  region: external_exports2.enum(["global", "cn"]).optional().describe("MiniMax deployment region."),
268640
268925
  n: external_exports2.number().int().min(1).max(4).optional().describe("Image output count."),
268641
268926
  ratio: external_exports2.enum(["adaptive", "1:1", "16:9", "4:3", "3:2", "2:3", "3:4", "9:16", "21:9"]).optional().describe("Image or video aspect ratio."),
@@ -268645,7 +268930,7 @@ ${lines.join("\n")}` }] };
268645
268930
  } : {}
268646
268931
  }
268647
268932
  },
268648
- async ({ action, path: assetPath, assetId, jobId, kind, limit: limit2, title, narrative, prompt: prompt2, image, model, provider: provider2, language, region, n: n3, ratio, width, height, duration: duration3, maxPages, maxChars, attach }) => {
268933
+ async ({ action, path: assetPath, assetId, jobId, kind, limit: limit2, title, narrative, prompt: prompt2, image, firstFrameImageUrl, model, provider: provider2, language, region, n: n3, ratio, width, height, duration: duration3, maxPages, maxChars, attach }) => {
268649
268934
  const unresolved = requireResolvedProject("manage controlled media");
268650
268935
  if (unresolved) return unresolved;
268651
268936
  const safeError4 = (error40) => sanitizeCredentials(
@@ -268815,6 +269100,7 @@ ${lines.join("\n")}` }] };
268815
269100
  dataDir: projectDir2,
268816
269101
  projectId: project.id,
268817
269102
  prompt: prompt2,
269103
+ firstFrameImageUrl,
268818
269104
  ...model === "MiniMax-H3" ? { model } : {},
268819
269105
  ...region ? { region } : {},
268820
269106
  ...videoRatio ? { ratio: videoRatio } : {},
@@ -269365,8 +269651,8 @@ __export(update_checker_exports, {
269365
269651
  });
269366
269652
  import { execFile as execFile3 } from "child_process";
269367
269653
  import { readFile as readFile7, writeFile as writeFile8, mkdir as mkdir7 } from "fs/promises";
269368
- import { join as join29 } from "path";
269369
- import { homedir as homedir34 } from "os";
269654
+ import { join as join30 } from "path";
269655
+ import { homedir as homedir35 } from "os";
269370
269656
  function getCurrentVersion() {
269371
269657
  try {
269372
269658
  return getCliVersion();
@@ -269545,8 +269831,8 @@ var init_update_checker = __esm({
269545
269831
  MIN_AUTO_UPDATE_TIMEOUT_MS = 5e3;
269546
269832
  MAX_AUTO_UPDATE_TIMEOUT_MS = 30 * 60 * 1e3;
269547
269833
  REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
269548
- CACHE_DIR2 = join29(homedir34(), ".memorix");
269549
- CACHE_FILE2 = join29(CACHE_DIR2, "update-check.json");
269834
+ CACHE_DIR2 = join30(homedir35(), ".memorix");
269835
+ CACHE_FILE2 = join30(CACHE_DIR2, "update-check.json");
269550
269836
  _testing = {
269551
269837
  CACHE_FILE: CACHE_FILE2,
269552
269838
  CHECK_INTERVAL_MS,
@@ -269604,7 +269890,7 @@ var init_serve = __esm({
269604
269890
  const { serveStdio, StdioServerTransport } = await import("@modelcontextprotocol/server/stdio");
269605
269891
  const { createModernMcpBridge: createModernMcpBridge2 } = await Promise.resolve().then(() => (init_modern_mcp_bridge(), modern_mcp_bridge_exports));
269606
269892
  const { detectProject: detectProject2, findGitInSubdirs: findGitInSubdirs2, isSystemDirectory: isSystemDirectory2 } = await Promise.resolve().then(() => (init_detector(), detector_exports));
269607
- const { homedir: homedir37 } = await import("os");
269893
+ const { homedir: homedir38 } = await import("os");
269608
269894
  const { writeLastProjectRoot: writeLastProjectRoot2 } = await Promise.resolve().then(() => (init_launch_root(), launch_root_exports));
269609
269895
  const { resolveServeProject: resolveServeProject2 } = await Promise.resolve().then(() => (init_serve_shared(), serve_shared_exports));
269610
269896
  let stdioHandle;
@@ -269633,7 +269919,7 @@ var init_serve = __esm({
269633
269919
  try {
269634
269920
  safeCwd = process.cwd();
269635
269921
  } catch {
269636
- safeCwd = homedir37();
269922
+ safeCwd = homedir38();
269637
269923
  }
269638
269924
  const resolution = resolveServeProject2(
269639
269925
  {
@@ -269641,7 +269927,7 @@ var init_serve = __esm({
269641
269927
  envProjectRoot: process.env.MEMORIX_PROJECT_ROOT,
269642
269928
  initCwd: process.env.INIT_CWD,
269643
269929
  processCwd: safeCwd,
269644
- homeDir: homedir37()
269930
+ homeDir: homedir38()
269645
269931
  },
269646
269932
  { detectProject: detectProject2, findGitInSubdirs: findGitInSubdirs2, isSystemDirectory: isSystemDirectory2 }
269647
269933
  );
@@ -271862,6 +272148,7 @@ var init_streamableHttp = __esm({
271862
272148
  // src/server/mcp-binding-store.ts
271863
272149
  var mcp_binding_store_exports = {};
271864
272150
  __export(mcp_binding_store_exports, {
272151
+ DEFAULT_MCP_BINDING_TTL_MS: () => DEFAULT_MCP_BINDING_TTL_MS,
271865
272152
  McpBindingStore: () => McpBindingStore
271866
272153
  });
271867
272154
  import { randomUUID as randomUUID19 } from "crypto";
@@ -271876,12 +272163,13 @@ function rowToBinding(row) {
271876
272163
  ...row.expires_at ? { expiresAt: String(row.expires_at) } : {}
271877
272164
  };
271878
272165
  }
271879
- var McpBindingStore;
272166
+ var DEFAULT_MCP_BINDING_TTL_MS, McpBindingStore;
271880
272167
  var init_mcp_binding_store = __esm({
271881
272168
  "src/server/mcp-binding-store.ts"() {
271882
272169
  "use strict";
271883
272170
  init_esm_shims();
271884
272171
  init_sqlite_db();
272172
+ DEFAULT_MCP_BINDING_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
271885
272173
  McpBindingStore = class {
271886
272174
  db = null;
271887
272175
  async init(dataDir) {
@@ -271893,6 +272181,7 @@ var init_mcp_binding_store = __esm({
271893
272181
  }
271894
272182
  create(input) {
271895
272183
  const now3 = /* @__PURE__ */ new Date();
272184
+ const ttlMs = input.ttlMs ?? DEFAULT_MCP_BINDING_TTL_MS;
271896
272185
  const binding = {
271897
272186
  handleId: `mxh_${randomUUID19().replace(/-/g, "")}`,
271898
272187
  projectId: input.projectId,
@@ -271900,7 +272189,7 @@ var init_mcp_binding_store = __esm({
271900
272189
  dataDir: input.dataDir,
271901
272190
  createdAt: now3.toISOString(),
271902
272191
  lastUsedAt: now3.toISOString(),
271903
- ...input.ttlMs && input.ttlMs > 0 ? { expiresAt: new Date(now3.getTime() + input.ttlMs).toISOString() } : {}
272192
+ ...ttlMs > 0 ? { expiresAt: new Date(now3.getTime() + ttlMs).toISOString() } : {}
271904
272193
  };
271905
272194
  this.requireDb().prepare(`
271906
272195
  INSERT INTO mcp_bindings (handle_id, project_id, project_root, data_dir, created_at, last_used_at, expires_at)
@@ -271922,8 +272211,15 @@ var init_mcp_binding_store = __esm({
271922
272211
  const binding = this.get(handleId);
271923
272212
  if (!binding) return void 0;
271924
272213
  const lastUsedAt = (/* @__PURE__ */ new Date()).toISOString();
271925
- this.requireDb().prepare(`UPDATE mcp_bindings SET last_used_at = ? WHERE handle_id = ?`).run(lastUsedAt, handleId);
271926
- return { ...binding, lastUsedAt };
272214
+ const expiresAt = new Date(Date.now() + DEFAULT_MCP_BINDING_TTL_MS).toISOString();
272215
+ this.requireDb().prepare(`UPDATE mcp_bindings SET last_used_at = ?, expires_at = ? WHERE handle_id = ?`).run(lastUsedAt, expiresAt, handleId);
272216
+ return { ...binding, lastUsedAt, expiresAt };
272217
+ }
272218
+ cleanupExpired(now3 = Date.now()) {
272219
+ const result = this.requireDb().prepare(
272220
+ `DELETE FROM mcp_bindings WHERE expires_at IS NOT NULL AND expires_at <= ?`
272221
+ ).run(new Date(now3).toISOString());
272222
+ return Number(result.changes ?? 0);
271927
272223
  }
271928
272224
  /** Reuse the most recent valid extension handle for a verified project root. */
271929
272225
  findByProjectRoot(projectRoot) {
@@ -272013,16 +272309,25 @@ var init_control_plane_maintenance = __esm({
272013
272309
  // src/cli/commands/serve-http.ts
272014
272310
  var serve_http_exports = {};
272015
272311
  __export(serve_http_exports, {
272312
+ DEFAULT_HTTP_BODY_LIMIT_BYTES: () => DEFAULT_HTTP_BODY_LIMIT_BYTES,
272016
272313
  DEFAULT_SESSION_TIMEOUT_MS: () => DEFAULT_SESSION_TIMEOUT_MS,
272017
272314
  EXPIRED_SESSION_TTL_MS: () => EXPIRED_SESSION_TTL_MS,
272315
+ HttpPayloadTooLargeError: () => HttpPayloadTooLargeError,
272316
+ MAX_HTTP_BODY_LIMIT_BYTES: () => MAX_HTTP_BODY_LIMIT_BYTES,
272018
272317
  MCP_SDK_COMPAT_PROTOCOL_VERSION: () => MCP_SDK_COMPAT_PROTOCOL_VERSION,
272318
+ MIN_HTTP_BODY_LIMIT_BYTES: () => MIN_HTTP_BODY_LIMIT_BYTES,
272019
272319
  _testing: () => _testing2,
272020
272320
  default: () => serve_http_default,
272321
+ isLoopbackHost: () => isLoopbackHost,
272021
272322
  isServerDiscoverRequest: () => isServerDiscoverRequest,
272323
+ parseHttpBodyLimit: () => parseHttpBodyLimit,
272324
+ parseJsonBody: () => parseJsonBody,
272022
272325
  parseSessionTimeoutMs: () => parseSessionTimeoutMs,
272023
- resolveServeHttpDefaults: () => resolveServeHttpDefaults
272326
+ resolveServeHttpDefaults: () => resolveServeHttpDefaults,
272327
+ validateHttpBindSecurity: () => validateHttpBindSecurity
272024
272328
  });
272025
272329
  import { getHeapStatistics } from "v8";
272330
+ import { timingSafeEqual as timingSafeEqual2 } from "crypto";
272026
272331
  function isServerDiscoverRequest(body) {
272027
272332
  return Boolean(body && typeof body === "object" && !Array.isArray(body) && body.method === "server/discover");
272028
272333
  }
@@ -272033,6 +272338,78 @@ function parseSessionTimeoutMs(raw) {
272033
272338
  if (!Number.isFinite(parsed) || parsed < 0) return DEFAULT_SESSION_TIMEOUT_MS;
272034
272339
  return Math.floor(parsed);
272035
272340
  }
272341
+ function parseHttpBodyLimit(raw) {
272342
+ const value = raw?.trim();
272343
+ if (!value) return DEFAULT_HTTP_BODY_LIMIT_BYTES;
272344
+ const parsed = Number(value);
272345
+ if (!Number.isSafeInteger(parsed) || parsed < MIN_HTTP_BODY_LIMIT_BYTES) {
272346
+ return DEFAULT_HTTP_BODY_LIMIT_BYTES;
272347
+ }
272348
+ return Math.min(parsed, MAX_HTTP_BODY_LIMIT_BYTES);
272349
+ }
272350
+ function isLoopbackHost(host) {
272351
+ const normalized = host.trim().toLowerCase();
272352
+ return normalized === "localhost" || normalized === "127.0.0.1" || normalized === "::1" || normalized === "[::1]";
272353
+ }
272354
+ function validateHttpBindSecurity(host, env3 = process.env) {
272355
+ const hasToken = Boolean(env3.MEMORIX_HTTP_AUTH_TOKEN?.trim());
272356
+ const explicitlyUnauthenticated = env3.MEMORIX_HTTP_ALLOW_UNAUTHENTICATED_BIND === "1";
272357
+ if (!isLoopbackHost(host) && !hasToken && !explicitlyUnauthenticated) {
272358
+ throw new Error(
272359
+ `[memorix] Refusing unauthenticated non-loopback HTTP bind on ${host}. Set MEMORIX_HTTP_AUTH_TOKEN or explicitly set MEMORIX_HTTP_ALLOW_UNAUTHENTICATED_BIND=1.`
272360
+ );
272361
+ }
272362
+ return { requiresAuth: hasToken, explicitlyUnauthenticated };
272363
+ }
272364
+ function isAuthorizedHttpRequest(req, token) {
272365
+ const header = req.headers.authorization;
272366
+ if (!header) return false;
272367
+ const match2 = /^Bearer\s+(.+)$/iu.exec(header);
272368
+ if (!match2) return false;
272369
+ const provided = Buffer.from(match2[1], "utf8");
272370
+ const expected = Buffer.from(token, "utf8");
272371
+ return provided.length === expected.length && timingSafeEqual2(provided, expected);
272372
+ }
272373
+ function parseJsonBody(req, limitBytes) {
272374
+ return new Promise((resolve9, reject) => {
272375
+ const contentLength = Number(req.headers["content-length"]);
272376
+ if (Number.isSafeInteger(contentLength) && contentLength > limitBytes) {
272377
+ req.resume();
272378
+ reject(new HttpPayloadTooLargeError(limitBytes));
272379
+ return;
272380
+ }
272381
+ const chunks = [];
272382
+ let size = 0;
272383
+ let settled = false;
272384
+ const fail2 = (error40) => {
272385
+ if (settled) return;
272386
+ settled = true;
272387
+ reject(error40);
272388
+ };
272389
+ req.on("data", (chunk) => {
272390
+ if (settled) return;
272391
+ size += chunk.length;
272392
+ if (size > limitBytes) {
272393
+ req.removeAllListeners("data");
272394
+ req.resume();
272395
+ fail2(new HttpPayloadTooLargeError(limitBytes));
272396
+ return;
272397
+ }
272398
+ chunks.push(chunk);
272399
+ });
272400
+ req.on("end", () => {
272401
+ if (settled) return;
272402
+ settled = true;
272403
+ try {
272404
+ const body = Buffer.concat(chunks).toString("utf-8");
272405
+ resolve9(body ? JSON.parse(body) : void 0);
272406
+ } catch (error40) {
272407
+ reject(error40);
272408
+ }
272409
+ });
272410
+ req.on("error", fail2);
272411
+ });
272412
+ }
272036
272413
  function resolveServeHttpDefaults(argsPort, configured) {
272037
272414
  const configuredPort = typeof configured.port === "number" && Number.isFinite(configured.port) ? configured.port : void 0;
272038
272415
  const port = parseTcpPortOrReport(argsPort, configuredPort ?? 3211);
@@ -272042,7 +272419,7 @@ function resolveServeHttpDefaults(argsPort, configured) {
272042
272419
  const transportNotice = configured.transport === "stdio" ? '[memorix] config server.transport = "stdio"; serve-http continues on HTTP (the explicit command wins).' : void 0;
272043
272420
  return { port, transportNotice };
272044
272421
  }
272045
- var DEFAULT_SESSION_TIMEOUT_MS, EXPIRED_SESSION_TTL_MS, MCP_SDK_COMPAT_PROTOCOL_VERSION, serve_http_default, _testing2;
272422
+ var DEFAULT_SESSION_TIMEOUT_MS, EXPIRED_SESSION_TTL_MS, DEFAULT_HTTP_BODY_LIMIT_BYTES, MAX_HTTP_BODY_LIMIT_BYTES, MIN_HTTP_BODY_LIMIT_BYTES, MCP_SDK_COMPAT_PROTOCOL_VERSION, HttpPayloadTooLargeError, serve_http_default, _testing2;
272046
272423
  var init_serve_http = __esm({
272047
272424
  "src/cli/commands/serve-http.ts"() {
272048
272425
  "use strict";
@@ -272058,7 +272435,17 @@ var init_serve_http = __esm({
272058
272435
  init_mcp_root_path();
272059
272436
  DEFAULT_SESSION_TIMEOUT_MS = 12 * 60 * 60 * 1e3;
272060
272437
  EXPIRED_SESSION_TTL_MS = 10 * 60 * 1e3;
272438
+ DEFAULT_HTTP_BODY_LIMIT_BYTES = 10 * 1024 * 1024;
272439
+ MAX_HTTP_BODY_LIMIT_BYTES = 64 * 1024 * 1024;
272440
+ MIN_HTTP_BODY_LIMIT_BYTES = 64 * 1024;
272061
272441
  MCP_SDK_COMPAT_PROTOCOL_VERSION = "2025-11-25";
272442
+ HttpPayloadTooLargeError = class extends Error {
272443
+ statusCode = 413;
272444
+ constructor(limitBytes) {
272445
+ super(`Request body exceeds the ${limitBytes} byte limit.`);
272446
+ this.name = "HttpPayloadTooLargeError";
272447
+ }
272448
+ };
272062
272449
  serve_http_default = defineCommand({
272063
272450
  meta: {
272064
272451
  name: "serve-http",
@@ -272095,7 +272482,7 @@ var init_serve_http = __esm({
272095
272482
  const { createProjectBindingController: createProjectBindingController2 } = await Promise.resolve().then(() => (init_request_context(), request_context_exports));
272096
272483
  const { findGitInSubdirs: findGitInSubdirs2 } = await Promise.resolve().then(() => (init_detector(), detector_exports));
272097
272484
  const { writeFileSync: writeFileSync13, mkdirSync: mkdirSync13 } = await import("fs");
272098
- const { homedir: homedir37 } = await import("os");
272485
+ const { homedir: homedir38 } = await import("os");
272099
272486
  const { McpBindingStore: McpBindingStore2 } = await Promise.resolve().then(() => (init_mcp_binding_store(), mcp_binding_store_exports));
272100
272487
  const { MemoryFeedbackStore: MemoryFeedbackStore2 } = await Promise.resolve().then(() => (init_feedback(), feedback_exports));
272101
272488
  const { createModernMcpBridge: createModernMcpBridge2 } = await Promise.resolve().then(() => (init_modern_mcp_bridge(), modern_mcp_bridge_exports));
@@ -272111,12 +272498,20 @@ var init_serve_http = __esm({
272111
272498
  if (resolved.transportNotice) console.error(resolved.transportNotice);
272112
272499
  const port = resolved.port;
272113
272500
  const host = args.host || "127.0.0.1";
272501
+ const bindSecurity = validateHttpBindSecurity(host);
272502
+ const httpAuthToken = process.env.MEMORIX_HTTP_AUTH_TOKEN?.trim() || "";
272503
+ if (bindSecurity.explicitlyUnauthenticated && !isLoopbackHost(host)) {
272504
+ console.error(`[memorix] WARNING: HTTP control plane is unauthenticated on ${host}; keep it behind a private network or authenticated proxy.`);
272505
+ }
272506
+ if (bindSecurity.requiresAuth) {
272507
+ console.error("[memorix] HTTP control plane authentication is enabled (Bearer token).");
272508
+ }
272114
272509
  const toolProfile = resolveToolProfile({ explicit: args.mode, envValue: process.env.MEMORIX_MODE, fallback: "team" });
272115
272510
  let safeCwd;
272116
272511
  try {
272117
272512
  safeCwd = process.cwd();
272118
272513
  } catch {
272119
- safeCwd = homedir37();
272514
+ safeCwd = homedir38();
272120
272515
  }
272121
272516
  const { resolveServeProject: resolveServeProject2 } = await Promise.resolve().then(() => (init_serve_shared(), serve_shared_exports));
272122
272517
  const { writeLastProjectRoot: writeLastProjectRoot2 } = await Promise.resolve().then(() => (init_launch_root(), launch_root_exports));
@@ -272127,7 +272522,7 @@ var init_serve_http = __esm({
272127
272522
  cwdArg: args.cwd,
272128
272523
  envProjectRoot: process.env.MEMORIX_PROJECT_ROOT,
272129
272524
  processCwd: safeCwd,
272130
- homeDir: homedir37()
272525
+ homeDir: homedir38()
272131
272526
  },
272132
272527
  { detectProject: detectorMod.detectProject, findGitInSubdirs: detectorMod.findGitInSubdirs, isSystemDirectory: detectorMod.isSystemDirectory }
272133
272528
  );
@@ -272141,6 +272536,7 @@ var init_serve_http = __esm({
272141
272536
  console.error(`[memorix] HTTP transport starting on ${host}:${port}`);
272142
272537
  console.error(`[memorix] Project root: ${projectRoot}`);
272143
272538
  const sessionTimeoutMs = parseSessionTimeoutMs(process.env.MEMORIX_SESSION_TIMEOUT_MS);
272539
+ const httpBodyLimitBytes = parseHttpBodyLimit(process.env.MEMORIX_HTTP_MAX_BODY_BYTES);
272144
272540
  console.error(
272145
272541
  sessionTimeoutMs === 0 ? "[memorix] HTTP session idle timeout: disabled" : `[memorix] HTTP session idle timeout: ${Math.round(sessionTimeoutMs / 6e4)}min (${sessionTimeoutMs}ms)`
272146
272542
  );
@@ -272211,21 +272607,6 @@ var init_serve_http = __esm({
272211
272607
  res.writeHead(404, { "Content-Type": "text/plain" });
272212
272608
  res.end(message);
272213
272609
  }
272214
- function parseBody(req) {
272215
- return new Promise((resolve9, reject) => {
272216
- const chunks = [];
272217
- req.on("data", (chunk) => chunks.push(chunk));
272218
- req.on("end", () => {
272219
- try {
272220
- const body = Buffer.concat(chunks).toString("utf-8");
272221
- resolve9(body ? JSON.parse(body) : void 0);
272222
- } catch (err) {
272223
- reject(err);
272224
- }
272225
- });
272226
- req.on("error", reject);
272227
- });
272228
- }
272229
272610
  function patchMcpResponse(res) {
272230
272611
  const origWriteHead = res.writeHead;
272231
272612
  const origWrite = res.write;
@@ -272315,7 +272696,7 @@ var init_serve_http = __esm({
272315
272696
  res.setHeader("Vary", "Origin");
272316
272697
  }
272317
272698
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
272318
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept, Mcp-Session-Id, Mcp-Project-Handle, Mcp-Project-Root, Mcp-Stateless, Last-Event-Id");
272699
+ res.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type, Accept, Mcp-Session-Id, Mcp-Project-Handle, Mcp-Project-Root, Mcp-Stateless, Last-Event-Id");
272319
272700
  res.setHeader("Access-Control-Expose-Headers", "Mcp-Session-Id, Mcp-Project-Handle, Mcp-Protocol-Version, Mcp-Method, Mcp-Name");
272320
272701
  res.setHeader("Mcp-Protocol-Version", MCP_SDK_COMPAT_PROTOCOL_VERSION);
272321
272702
  res.setHeader("Mcp-Method", req.method || "UNKNOWN");
@@ -272323,7 +272704,7 @@ var init_serve_http = __esm({
272323
272704
  }
272324
272705
  async function handlePost(req, res) {
272325
272706
  const sessionId = req.headers["mcp-session-id"];
272326
- const body = await parseBody(req);
272707
+ const body = await parseJsonBody(req, httpBodyLimitBytes);
272327
272708
  const params = body && typeof body === "object" && !Array.isArray(body) ? body.params : void 0;
272328
272709
  const claimlessDiscovery = !sessionId && isServerDiscoverRequest(body) && !req.headers["mcp-protocol-version"] && !req.headers["mcp-method"] && req.headers["mcp-stateless"] !== "true" && (params === void 0 || typeof params === "object" && params !== null && !Array.isArray(params) && !Object.prototype.hasOwnProperty.call(params, "_meta"));
272329
272710
  if (claimlessDiscovery) {
@@ -272627,7 +273008,7 @@ var init_serve_http = __esm({
272627
273008
  sendJson2({ error: "Maintenance actions require POST." }, 405);
272628
273009
  return;
272629
273010
  }
272630
- const bodyValue = await parseBody(req);
273011
+ const bodyValue = await parseJsonBody(req, Math.min(httpBodyLimitBytes, 1024 * 1024));
272631
273012
  const body = bodyValue && typeof bodyValue === "object" && !Array.isArray(bodyValue) ? bodyValue : {};
272632
273013
  const { projectId: maintenanceProjectId, dataDir: maintenanceDataDir } = await resolveRequestProject(url2);
272633
273014
  const maintenanceStore = await getDashboardObservationStore(maintenanceDataDir);
@@ -272762,9 +273143,9 @@ var init_serve_http = __esm({
272762
273143
  }
272763
273144
  if (apiPath === "/stats") {
272764
273145
  const { projectId: statsProjectId, dataDir: statsDataDir } = await resolveRequestProject(url2);
272765
- const { initGraphStore: initGraphStore2, getGraphStore: getGraphStore2 } = await Promise.resolve().then(() => (init_graph_store(), graph_store_exports));
272766
- await initGraphStore2(statsDataDir);
272767
- const graph = { entities: getGraphStore2().loadEntities(), relations: getGraphStore2().loadRelations() };
273146
+ const { initGraphStore: initGraphStore2 } = await Promise.resolve().then(() => (init_graph_store(), graph_store_exports));
273147
+ const gStore = await initGraphStore2(statsDataDir, statsProjectId);
273148
+ const graph = { entities: gStore.loadEntities(), relations: gStore.loadRelations() };
272768
273149
  const observations2 = await loadDashboardProjectObservations(statsDataDir, statsProjectId, "active");
272769
273150
  const feedback = await loadProjectFeedback(statsDataDir, statsProjectId);
272770
273151
  const statsStore = await getDashboardObservationStore(statsDataDir);
@@ -272926,9 +273307,9 @@ var init_serve_http = __esm({
272926
273307
  }
272927
273308
  if (apiPath === "/graph") {
272928
273309
  const { projectId: graphProjectId, dataDir: graphDataDir } = await resolveRequestProject(url2);
272929
- const { initGraphStore: initGraphStore2, getGraphStore: getGraphStore2 } = await Promise.resolve().then(() => (init_graph_store(), graph_store_exports));
272930
- await initGraphStore2(graphDataDir);
272931
- const fullGraph = { entities: getGraphStore2().loadEntities(), relations: getGraphStore2().loadRelations() };
273310
+ const { initGraphStore: initGraphStore2 } = await Promise.resolve().then(() => (init_graph_store(), graph_store_exports));
273311
+ const gStore = await initGraphStore2(graphDataDir, graphProjectId);
273312
+ const fullGraph = { entities: gStore.loadEntities(), relations: gStore.loadRelations() };
272932
273313
  const allObs = await loadDashboardProjectObservations(graphDataDir, graphProjectId, "active");
272933
273314
  const scoped = scopeKnowledgeGraphToProject(fullGraph, allObs);
272934
273315
  sendJson2({ entities: scoped.entities, relations: scoped.relations });
@@ -273001,12 +273382,12 @@ var init_serve_http = __esm({
273001
273382
  const { projectId: kgProjectId, dataDir: kgDataDir } = await resolveRequestProject(url2);
273002
273383
  const { generateKnowledgeGraph: generateKnowledgeGraph2 } = await Promise.resolve().then(() => (init_knowledge_graph(), knowledge_graph_exports));
273003
273384
  const { initMiniSkillStore: initMiniSkillStore2, getMiniSkillStore: getMiniSkillStore2 } = await Promise.resolve().then(() => (init_mini_skill_store(), mini_skill_store_exports));
273004
- const { initGraphStore: initGraphStore2, getGraphStore: getGraphStore2 } = await Promise.resolve().then(() => (init_graph_store(), graph_store_exports));
273385
+ const { initGraphStore: initGraphStore2 } = await Promise.resolve().then(() => (init_graph_store(), graph_store_exports));
273005
273386
  await initMiniSkillStore2(kgDataDir);
273006
- await initGraphStore2(kgDataDir);
273387
+ const gStore = await initGraphStore2(kgDataDir, kgProjectId);
273007
273388
  const allObs = await loadDashboardProjectObservations(kgDataDir, kgProjectId, "active");
273008
273389
  const skills = await getMiniSkillStore2().loadByProject(kgProjectId);
273009
- const fullGraph = { entities: getGraphStore2().loadEntities(), relations: getGraphStore2().loadRelations() };
273390
+ const fullGraph = { entities: gStore.loadEntities(), relations: gStore.loadRelations() };
273010
273391
  const scoped = scopeKnowledgeGraphToProject(fullGraph, allObs);
273011
273392
  const graph = generateKnowledgeGraph2({
273012
273393
  projectId: kgProjectId,
@@ -273022,7 +273403,7 @@ var init_serve_http = __esm({
273022
273403
  const { projectId: configProjectId } = await resolveRequestProject(url2);
273023
273404
  const os10 = await import("os");
273024
273405
  const { existsSync: existsSync36 } = await import("fs");
273025
- const { join: join35 } = await import("path");
273406
+ const { join: join36 } = await import("path");
273026
273407
  const { loadYamlConfig: loadYamlConfig2 } = await Promise.resolve().then(() => (init_yaml_loader(), yaml_loader_exports));
273027
273408
  const { loadFileConfig: loadFileConfig2, loadDotenv: loadDotenv2, getLoadedEnvFiles: getLoadedEnvFiles2 } = await Promise.resolve().then(() => (init_config(), config_exports));
273028
273409
  const isStartupProject = configProjectId === defaultProject.id;
@@ -273036,11 +273417,11 @@ var init_serve_http = __esm({
273036
273417
  const home = os10.homedir();
273037
273418
  if (effectiveRoot) {
273038
273419
  const paths = {
273039
- "project memorix.yml": join35(effectiveRoot, "memorix.yml"),
273040
- "user memorix.yml": join35(home, ".memorix", "memorix.yml"),
273041
- "project .env": join35(effectiveRoot, ".env"),
273042
- "user .env": join35(home, ".memorix", ".env"),
273043
- "legacy config.json": join35(home, ".memorix", "config.json")
273420
+ "project memorix.yml": join36(effectiveRoot, "memorix.yml"),
273421
+ "user memorix.yml": join36(home, ".memorix", "memorix.yml"),
273422
+ "project .env": join36(effectiveRoot, ".env"),
273423
+ "user .env": join36(home, ".memorix", ".env"),
273424
+ "legacy config.json": join36(home, ".memorix", "config.json")
273044
273425
  };
273045
273426
  for (const [key, fpath] of Object.entries(paths)) {
273046
273427
  files[key] = { exists: existsSync36(fpath), path: fpath };
@@ -273049,9 +273430,9 @@ var init_serve_http = __esm({
273049
273430
  files["project memorix.yml"] = { exists: false, path: "unavailable", unavailable: true };
273050
273431
  files["project .env"] = { exists: false, path: "unavailable", unavailable: true };
273051
273432
  const userPaths = {
273052
- "user memorix.yml": join35(home, ".memorix", "memorix.yml"),
273053
- "user .env": join35(home, ".memorix", ".env"),
273054
- "legacy config.json": join35(home, ".memorix", "config.json")
273433
+ "user memorix.yml": join36(home, ".memorix", "memorix.yml"),
273434
+ "user .env": join36(home, ".memorix", ".env"),
273435
+ "legacy config.json": join36(home, ".memorix", "config.json")
273055
273436
  };
273056
273437
  for (const [key, fpath] of Object.entries(userPaths)) {
273057
273438
  files[key] = { exists: existsSync36(fpath), path: fpath };
@@ -273267,9 +273648,8 @@ var init_serve_http = __esm({
273267
273648
  } else {
273268
273649
  await store2.remove(obsId);
273269
273650
  try {
273270
- const { initGraphStore: initGraphStore2, getGraphStore: getGraphStore2 } = await Promise.resolve().then(() => (init_graph_store(), graph_store_exports));
273271
- await initGraphStore2(delDataDir);
273272
- const gStore = getGraphStore2();
273651
+ const { initGraphStore: initGraphStore2 } = await Promise.resolve().then(() => (init_graph_store(), graph_store_exports));
273652
+ const gStore = await initGraphStore2(delDataDir, delProjectId);
273273
273653
  const prefix = `[#${obsId}] `;
273274
273654
  const deletions = [];
273275
273655
  for (const entity of gStore.loadEntities()) {
@@ -273285,9 +273665,9 @@ var init_serve_http = __esm({
273285
273665
  }
273286
273666
  if (apiPath === "/export") {
273287
273667
  const { projectId: expProjectId, projectName: expProjectName, dataDir: expDataDir } = await resolveRequestProject(url2);
273288
- const { initGraphStore: initGraphStore2, getGraphStore: getGraphStore2 } = await Promise.resolve().then(() => (init_graph_store(), graph_store_exports));
273289
- await initGraphStore2(expDataDir);
273290
- const fullGraph = { entities: getGraphStore2().loadEntities(), relations: getGraphStore2().loadRelations() };
273668
+ const { initGraphStore: initGraphStore2 } = await Promise.resolve().then(() => (init_graph_store(), graph_store_exports));
273669
+ const gStore = await initGraphStore2(expDataDir, expProjectId);
273670
+ const fullGraph = { entities: gStore.loadEntities(), relations: gStore.loadRelations() };
273291
273671
  const observations2 = await loadDashboardProjectObservations(expDataDir, expProjectId, "active");
273292
273672
  const expStore = await getDashboardObservationStore(expDataDir);
273293
273673
  const nextId2 = await expStore.loadIdCounter();
@@ -273325,7 +273705,8 @@ var init_serve_http = __esm({
273325
273705
  }, 503);
273326
273706
  return;
273327
273707
  }
273328
- sendJson2({ error: err instanceof Error ? err.message : "Unknown error" }, 500);
273708
+ const status = err instanceof HttpPayloadTooLargeError ? err.statusCode : 500;
273709
+ sendJson2({ error: err instanceof Error ? err.message : "Unknown error" }, status);
273329
273710
  }
273330
273711
  }
273331
273712
  let httpReadyAt = null;
@@ -273337,6 +273718,14 @@ var init_serve_http = __esm({
273337
273718
  return;
273338
273719
  }
273339
273720
  const url2 = new URL(req.url || "/", `http://localhost:${port}`);
273721
+ if (bindSecurity.requiresAuth && url2.pathname !== "/health" && !isAuthorizedHttpRequest(req, httpAuthToken)) {
273722
+ res.writeHead(401, {
273723
+ "Content-Type": "application/json",
273724
+ "WWW-Authenticate": "Bearer"
273725
+ });
273726
+ res.end(JSON.stringify({ error: "Authentication required" }));
273727
+ return;
273728
+ }
273340
273729
  if (url2.pathname === "/health") {
273341
273730
  const { getEmbeddingRuntimeHealth: getEmbeddingRuntimeHealth2 } = await Promise.resolve().then(() => (init_provider(), provider_exports));
273342
273731
  const memory = process.memoryUsage();
@@ -273407,10 +273796,15 @@ var init_serve_http = __esm({
273407
273796
  } catch (err) {
273408
273797
  console.error("[memorix] HTTP handler error:", err);
273409
273798
  if (!res.headersSent) {
273410
- res.writeHead(500, { "Content-Type": "application/json" });
273799
+ const status = err instanceof HttpPayloadTooLargeError ? err.statusCode : 500;
273800
+ const message = err instanceof Error ? err.message : "Internal server error";
273801
+ res.writeHead(status, { "Content-Type": "application/json" });
273411
273802
  res.end(JSON.stringify({
273412
273803
  jsonrpc: "2.0",
273413
- error: { code: -32603, message: "Internal server error" },
273804
+ error: {
273805
+ code: status === 413 ? -32013 : -32603,
273806
+ message: status === 413 ? message : "Internal server error"
273807
+ },
273414
273808
  id: null
273415
273809
  }));
273416
273810
  }
@@ -273425,31 +273819,62 @@ var init_serve_http = __esm({
273425
273819
  });
273426
273820
  httpServer.keepAliveTimeout = 6e4;
273427
273821
  httpServer.headersTimeout = 65e3;
273428
- httpServer.listen(port, host, () => {
273429
- httpReadyAt = (/* @__PURE__ */ new Date()).toISOString();
273430
- try {
273431
- const memorixDir = (process.env.HOME || process.env.USERPROFILE || "").replace(/\\/g, "/") + "/.memorix";
273432
- mkdirSync13(memorixDir, { recursive: true });
273433
- writeFileSync13(memorixDir + "/background.ready", JSON.stringify({ pid: process.pid, port, readyAt: Date.now() }));
273434
- } catch {
273822
+ const listenPromise = new Promise((resolve9, reject) => {
273823
+ const onError = (error40) => {
273824
+ if (typeof httpServer.off === "function") httpServer.off("listening", onListening);
273825
+ reject(error40);
273826
+ };
273827
+ const onListening = () => {
273828
+ if (typeof httpServer.off === "function") httpServer.off("error", onError);
273829
+ httpReadyAt = (/* @__PURE__ */ new Date()).toISOString();
273830
+ try {
273831
+ const memorixDir = (process.env.HOME || process.env.USERPROFILE || "").replace(/\\/g, "/") + "/.memorix";
273832
+ mkdirSync13(memorixDir, { recursive: true });
273833
+ writeFileSync13(memorixDir + "/background.ready", JSON.stringify({ pid: process.pid, port, readyAt: Date.now() }));
273834
+ } catch {
273835
+ }
273836
+ console.error(`[memorix] \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
273837
+ console.error(`[memorix] Mode: Control Plane (HTTP MCP + multi-session live dashboard)`);
273838
+ console.error(`[memorix] MCP Server listening on http://${host}:${port}/mcp`);
273839
+ console.error(`[memorix] Dashboard: http://${host}:${port}/`);
273840
+ console.error(`[memorix] Coordination: http://${host}:${port}/api/team`);
273841
+ console.error(`[memorix] Port: ${port}`);
273842
+ console.error(`[memorix] \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
273843
+ console.error(`[memorix] Sessions at startup: ${sessions.size} (live count available at /api/team)`);
273844
+ console.error(`[memorix] Agents can connect via: { "transport": "http", "url": "http://localhost:${port}/mcp" }`);
273845
+ Promise.resolve().then(() => (init_update_checker(), update_checker_exports)).then((m6) => m6.checkForUpdates()).catch(() => {
273846
+ });
273847
+ resolve9();
273848
+ };
273849
+ if (typeof httpServer.once === "function") {
273850
+ httpServer.once("error", onError);
273851
+ httpServer.once("listening", onListening);
273852
+ httpServer.listen(port, host);
273853
+ } else {
273854
+ httpServer.listen(port, host, onListening);
273435
273855
  }
273436
- console.error(`[memorix] \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
273437
- console.error(`[memorix] Mode: Control Plane (HTTP MCP + multi-session live dashboard)`);
273438
- console.error(`[memorix] MCP Server listening on http://${host}:${port}/mcp`);
273439
- console.error(`[memorix] Dashboard: http://${host}:${port}/`);
273440
- console.error(`[memorix] Coordination: http://${host}:${port}/api/team`);
273441
- console.error(`[memorix] Port: ${port}`);
273442
- console.error(`[memorix] \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
273443
- console.error(`[memorix] Sessions at startup: ${sessions.size} (live count available at /api/team)`);
273444
- console.error(`[memorix] Agents can connect via: { "transport": "http", "url": "http://localhost:${port}/mcp" }`);
273445
- Promise.resolve().then(() => (init_update_checker(), update_checker_exports)).then((m6) => m6.checkForUpdates()).catch(() => {
273446
- });
273447
273856
  });
273857
+ try {
273858
+ await listenPromise;
273859
+ } catch (error40) {
273860
+ console.error(`[memorix] HTTP server failed to listen on ${host}:${port}: ${error40 instanceof Error ? error40.message : String(error40)}`);
273861
+ for (const store2 of dashboardObservationStores.values()) {
273862
+ try {
273863
+ store2.close();
273864
+ } catch {
273865
+ }
273866
+ }
273867
+ dashboardObservationStores.clear();
273868
+ const { closeAllDatabases: closeAllDatabases2 } = await Promise.resolve().then(() => (init_sqlite_db(), sqlite_db_exports));
273869
+ closeAllDatabases2();
273870
+ throw error40;
273871
+ }
273448
273872
  const SESSION_TIMEOUT_MS = sessionTimeoutMs;
273449
273873
  const gcInterval = setInterval(() => {
273450
273874
  maybeProbeSummary();
273451
273875
  const now3 = Date.now();
273452
273876
  forgetExpiredSessions(now3);
273877
+ statelessBindingStore.cleanupExpired(now3);
273453
273878
  if (SESSION_TIMEOUT_MS === 0) return;
273454
273879
  for (const [sid, state] of sessions) {
273455
273880
  const lastActive = sessionLastActivity.get(sid) ?? 0;
@@ -273464,7 +273889,10 @@ var init_serve_http = __esm({
273464
273889
  }
273465
273890
  }, 6e4);
273466
273891
  gcInterval.unref();
273892
+ let shutdownStarted = false;
273467
273893
  const shutdown = async () => {
273894
+ if (shutdownStarted) return;
273895
+ shutdownStarted = true;
273468
273896
  if (suppressedProbeCount > 0) {
273469
273897
  console.error(`[memorix] ${suppressedProbeCount} probe connection(s) suppressed during this session`);
273470
273898
  }
@@ -273498,7 +273926,17 @@ var init_serve_http = __esm({
273498
273926
  console.error(`[memorix] Error closing session ${sid}:`, err);
273499
273927
  }
273500
273928
  }
273501
- httpServer.close();
273929
+ clearInterval(gcInterval);
273930
+ clearInterval(heartbeatInterval);
273931
+ await new Promise((resolve9) => {
273932
+ if (!httpServer.listening) {
273933
+ resolve9();
273934
+ return;
273935
+ }
273936
+ httpServer.close(() => resolve9());
273937
+ });
273938
+ const { closeAllDatabases: closeAllDatabases2 } = await Promise.resolve().then(() => (init_sqlite_db(), sqlite_db_exports));
273939
+ closeAllDatabases2();
273502
273940
  process.exit(0);
273503
273941
  };
273504
273942
  process.on("SIGINT", shutdown);
@@ -273537,7 +273975,13 @@ var init_serve_http = __esm({
273537
273975
  });
273538
273976
  _testing2 = {
273539
273977
  DEFAULT_SESSION_TIMEOUT_MS,
273978
+ DEFAULT_HTTP_BODY_LIMIT_BYTES,
273979
+ MAX_HTTP_BODY_LIMIT_BYTES,
273980
+ MIN_HTTP_BODY_LIMIT_BYTES,
273540
273981
  parseSessionTimeoutMs,
273982
+ parseHttpBodyLimit,
273983
+ isLoopbackHost,
273984
+ validateHttpBindSecurity,
273541
273985
  parseTcpPortOrReport
273542
273986
  };
273543
273987
  }
@@ -273988,8 +274432,10 @@ var init_journal = __esm({
273988
274432
  });
273989
274433
 
273990
274434
  // src/sync/policy.ts
273991
- function syncEligibility(observation, projectId) {
273992
- if (observation.projectId !== projectId) return { eligible: false, reason: "other-project" };
274435
+ function syncEligibility(observation, scopeId, scope = "project") {
274436
+ if (scope === "project" && observation.projectId !== scopeId) {
274437
+ return { eligible: false, reason: "other-project" };
274438
+ }
273993
274439
  if ((observation.visibility ?? "project") !== "project") return { eligible: false, reason: "non-project-visibility" };
273994
274440
  if ((observation.sharedWithAgentIds?.length ?? 0) > 0) {
273995
274441
  return { eligible: false, reason: "agent-targeted" };
@@ -274000,11 +274446,11 @@ function syncEligibility(observation, projectId) {
274000
274446
  if (observation.valueCategory === "ephemeral") return { eligible: false, reason: "ephemeral-value" };
274001
274447
  return { eligible: true };
274002
274448
  }
274003
- function eligibleObservations(observations2, projectId) {
274449
+ function eligibleObservations(observations2, scopeId, scope = "project") {
274004
274450
  const eligible = [];
274005
274451
  let excluded = 0;
274006
274452
  for (const observation of observations2) {
274007
- if (syncEligibility(observation, projectId).eligible) eligible.push(observation);
274453
+ if (syncEligibility(observation, scopeId, scope).eligible) eligible.push(observation);
274008
274454
  else excluded++;
274009
274455
  }
274010
274456
  return { eligible, excluded };
@@ -274021,13 +274467,26 @@ import { createHash as createHash22 } from "crypto";
274021
274467
  function syncNamespace(projectId) {
274022
274468
  return `project-${createHash22("sha256").update(projectId).digest("hex").slice(0, 32)}`;
274023
274469
  }
274470
+ function userSyncNamespace(env3 = process.env) {
274471
+ const override = env3.MEMORIX_SYNC_USER_NAMESPACE?.trim();
274472
+ if (!override) {
274473
+ throw new Error("[memorix] MEMORIX_SYNC_USER_NAMESPACE is required for --scope user; use one stable per-user namespace across devices");
274474
+ }
274475
+ if (!isSafeSyncDeviceId(override)) {
274476
+ throw new Error("[memorix] MEMORIX_SYNC_USER_NAMESPACE must match [A-Za-z0-9][A-Za-z0-9._-]{0,127}");
274477
+ }
274478
+ return `${USER_SYNC_NAMESPACE}${override}`;
274479
+ }
274024
274480
  function isSafeSyncDeviceId(value) {
274025
274481
  return /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(value);
274026
274482
  }
274483
+ var USER_SYNC_SCOPE_ID, USER_SYNC_NAMESPACE;
274027
274484
  var init_namespace = __esm({
274028
274485
  "src/sync/namespace.ts"() {
274029
274486
  "use strict";
274030
274487
  init_esm_shims();
274488
+ USER_SYNC_SCOPE_ID = "__user__";
274489
+ USER_SYNC_NAMESPACE = "user-";
274031
274490
  }
274032
274491
  });
274033
274492
 
@@ -274038,6 +274497,7 @@ async function runSync(store2, remote, opts) {
274038
274497
  remote: remote.kind,
274039
274498
  namespace: remote.namespace,
274040
274499
  projectId: store2.projectId(),
274500
+ scope: store2.scope(),
274041
274501
  deviceId: opts.deviceId,
274042
274502
  pushed: 0,
274043
274503
  pulledBatches: 0,
@@ -274065,7 +274525,7 @@ async function runSync(store2, remote, opts) {
274065
274525
  report.pending = pending.length;
274066
274526
  if (!opts.dryRun) {
274067
274527
  for (const pendingBatch of pending) {
274068
- validateBatches([pendingBatch.batch], store2.projectId(), remote.namespace);
274528
+ validateBatches([pendingBatch.batch], store2.projectId(), remote.namespace, store2.scope());
274069
274529
  if (pendingBatch.batch.deviceId !== opts.deviceId) {
274070
274530
  throw new Error("[memorix] sync outbox rejected: batch belongs to another device");
274071
274531
  }
@@ -274077,7 +274537,7 @@ async function runSync(store2, remote, opts) {
274077
274537
  report.pending = 0;
274078
274538
  }
274079
274539
  const current = await store2.loadAll();
274080
- const filtered = eligibleObservations(current, store2.projectId());
274540
+ const filtered = eligibleObservations(current, store2.projectId(), store2.scope());
274081
274541
  report.eligible = filtered.eligible.length;
274082
274542
  report.excluded = filtered.excluded;
274083
274543
  const state = await store2.loadState();
@@ -274100,7 +274560,7 @@ async function runSync(store2, remote, opts) {
274100
274560
  producedAt: nowIso(),
274101
274561
  entries: chunk.map((c6) => c6.entry)
274102
274562
  };
274103
- if (!opts.dryRun) validateBatches([batch], store2.projectId(), remote.namespace);
274563
+ if (!opts.dryRun) validateBatches([batch], store2.projectId(), remote.namespace, store2.scope());
274104
274564
  report.pushed += chunk.length;
274105
274565
  if (!opts.dryRun) {
274106
274566
  const states = chunk.map((c6) => ({ ...c6.nextState, shippedSeq: sequence }));
@@ -274121,7 +274581,7 @@ async function runSync(store2, remote, opts) {
274121
274581
  while (hasMore) {
274122
274582
  const page = await remote.pull(pullSince, PULL_PAGE_SIZE, pageToken);
274123
274583
  report.pulledBatches += page.batches.length;
274124
- validateBatches(page.batches, store2.projectId(), remote.namespace);
274584
+ validateBatches(page.batches, store2.projectId(), remote.namespace, store2.scope());
274125
274585
  for (const batch of page.batches) {
274126
274586
  if (batch.deviceId === opts.deviceId) {
274127
274587
  cursor.applied[batch.deviceId] = Math.max(cursor.applied[batch.deviceId] ?? 0, batch.sequence);
@@ -274191,7 +274651,7 @@ async function runSync(store2, remote, opts) {
274191
274651
  await remote.close();
274192
274652
  }
274193
274653
  }
274194
- function validateBatches(batches, projectId, namespace) {
274654
+ function validateBatches(batches, projectId, namespace, scope = "project") {
274195
274655
  for (const batch of batches) {
274196
274656
  if (batch.formatVersion !== 3 || batch.projectId !== projectId || batch.namespace !== namespace) {
274197
274657
  throw new Error("[memorix] sync batch rejected: wrong format, project, or remote namespace");
@@ -274203,7 +274663,7 @@ function validateBatches(batches, projectId, namespace) {
274203
274663
  if (!entry.syncKey || entry.kind !== "upsert" && entry.kind !== "tombstone" || !entry.version || !Number.isSafeInteger(entry.version.revision) || entry.version.revision < 1 || !entry.version.writer || typeof entry.contentHash !== "string" || entry.kind === "tombstone" && (entry.row !== void 0 || entry.contentHash !== "") || entry.kind === "upsert" && (!/^[a-f0-9]{64}$/.test(entry.contentHash) || entry.row === void 0)) {
274204
274664
  throw new Error("[memorix] sync batch rejected: malformed change entry");
274205
274665
  }
274206
- if (entry.kind === "upsert" && (!Number.isSafeInteger(entry.row.id) || entry.row.id < 1 || !eligibleObservations([entry.row], projectId).eligible.length)) {
274666
+ if (entry.kind === "upsert" && (!Number.isSafeInteger(entry.row.id) || entry.row.id < 1 || !eligibleObservations([entry.row], projectId, scope).eligible.length)) {
274207
274667
  throw new Error("[memorix] sync batch rejected: ineligible observation payload");
274208
274668
  }
274209
274669
  const incomingHash = entry.row ? contentHash(entry.row) : "";
@@ -274275,9 +274735,11 @@ var init_engine6 = __esm({
274275
274735
  // src/sync/store-port.ts
274276
274736
  import os7 from "os";
274277
274737
  import { randomUUID as randomUUID20 } from "crypto";
274278
- function createSqliteSyncStore(dataDir, store2, projectId) {
274738
+ function createSqliteSyncStore(dataDir, store2, projectId, options = {}) {
274279
274739
  const db2 = getDatabase(dataDir);
274280
- ensureSyncSchema(db2, projectId);
274740
+ const scope = options.scope ?? "project";
274741
+ const scopeId = scope === "user" ? USER_SYNC_SCOPE_ID : projectId;
274742
+ ensureSyncSchema(db2, scope === "user" ? LEGACY_PROJECT_ID : projectId);
274281
274743
  const getMeta = db2.prepare("SELECT value FROM sync_meta WHERE key = ?");
274282
274744
  const setMeta = db2.prepare("INSERT OR REPLACE INTO sync_meta (key, value) VALUES (?, ?)");
274283
274745
  const selState = db2.prepare("SELECT projectId, syncKey, obsId, revision, writer, kind, contentHash, shippedSeq FROM sync_row_state WHERE projectId = ?");
@@ -274317,7 +274779,7 @@ function createSqliteSyncStore(dataDir, store2, projectId) {
274317
274779
  SET deviceId = ?, batchJson = ?
274318
274780
  WHERE projectId = ? AND namespace = ? AND deviceId = ? AND sequence = ?`
274319
274781
  );
274320
- const namespace = syncNamespace(projectId);
274782
+ const namespace = scope === "user" ? userSyncNamespace() : syncNamespace(projectId);
274321
274783
  const deviceFingerprint = process.env.MEMORIX_SYNC_DEVICE_FINGERPRINT?.trim() || `${process.platform}|${os7.hostname()}|${os7.homedir()}`;
274322
274784
  function ensureDevice() {
274323
274785
  const row = getMeta.get(DEVICE_KEY);
@@ -274345,7 +274807,8 @@ function createSqliteSyncStore(dataDir, store2, projectId) {
274345
274807
  return `cursor:${namespace}`;
274346
274808
  }
274347
274809
  return {
274348
- projectId: () => projectId,
274810
+ projectId: () => scopeId,
274811
+ scope: () => scope,
274349
274812
  namespace: () => namespace,
274350
274813
  deviceId: ensureDevice,
274351
274814
  assertCanPublish,
@@ -274385,14 +274848,14 @@ function createSqliteSyncStore(dataDir, store2, projectId) {
274385
274848
  setMeta.run(cursorKey(), JSON.stringify(cursor));
274386
274849
  },
274387
274850
  async loadPendingBatches() {
274388
- return selPending.all(projectId, namespace, ensureDevice()).map((row) => ({
274851
+ return selPending.all(scopeId, namespace, ensureDevice()).map((row) => ({
274389
274852
  batch: JSON.parse(row.batchJson),
274390
274853
  states: JSON.parse(row.statesJson)
274391
274854
  }));
274392
274855
  },
274393
274856
  async enqueueBatch(pending) {
274394
274857
  insertPending.run({
274395
- projectId,
274858
+ projectId: scopeId,
274396
274859
  namespace,
274397
274860
  deviceId: pending.batch.deviceId,
274398
274861
  sequence: pending.batch.sequence,
@@ -274402,13 +274865,13 @@ function createSqliteSyncStore(dataDir, store2, projectId) {
274402
274865
  });
274403
274866
  },
274404
274867
  async markBatchShipped(sequence) {
274405
- deletePending.run(projectId, namespace, ensureDevice(), sequence);
274868
+ deletePending.run(scopeId, namespace, ensureDevice(), sequence);
274406
274869
  },
274407
274870
  async loadAll() {
274408
- return store2.loadByProject(projectId);
274871
+ return scope === "user" ? store2.loadAll() : store2.loadByProject(projectId);
274409
274872
  },
274410
274873
  async loadState() {
274411
- const rows = selState.all(projectId);
274874
+ const rows = selState.all(scopeId);
274412
274875
  const map2 = /* @__PURE__ */ new Map();
274413
274876
  for (const r6 of rows) map2.set(r6.syncKey, r6);
274414
274877
  return map2;
@@ -274422,8 +274885,10 @@ function createSqliteSyncStore(dataDir, store2, projectId) {
274422
274885
  async allocateId() {
274423
274886
  return store2.atomic(async (tx) => {
274424
274887
  const nextId2 = await tx.loadIdCounter();
274425
- await tx.saveIdCounter(nextId2 + 1);
274426
- return nextId2;
274888
+ const maxId = Number(db2.prepare("SELECT COALESCE(MAX(id), 0) AS maxId FROM observations").get()?.maxId ?? 0);
274889
+ const allocated = Math.max(nextId2, maxId + 1);
274890
+ await tx.saveIdCounter(allocated + 1);
274891
+ return allocated;
274427
274892
  });
274428
274893
  },
274429
274894
  async applyInsert(row, state) {
@@ -274453,7 +274918,11 @@ function createSqliteSyncStore(dataDir, store2, projectId) {
274453
274918
  async applyRemove(id, state) {
274454
274919
  await store2.atomic(async (tx) => {
274455
274920
  const existing = await tx.getById(id);
274456
- if (existing && existing.projectId !== state.projectId) {
274921
+ const expectedProjectId = scope === "user" ? sourceProjectIdFromSyncKey(state.syncKey) : state.projectId;
274922
+ if (!expectedProjectId) {
274923
+ throw new Error("[memorix] sync refused to remove an observation without a project-scoped sync key");
274924
+ }
274925
+ if (existing && existing.projectId !== expectedProjectId) {
274457
274926
  throw new Error("[memorix] sync refused to remove an observation from another project");
274458
274927
  }
274459
274928
  await tx.remove(id);
@@ -274482,6 +274951,15 @@ function createSqliteSyncStore(dataDir, store2, projectId) {
274482
274951
  }
274483
274952
  };
274484
274953
  }
274954
+ function sourceProjectIdFromSyncKey(syncKey) {
274955
+ const match2 = /^p:([^:]+):/.exec(syncKey);
274956
+ if (!match2) return void 0;
274957
+ try {
274958
+ return decodeURIComponent(match2[1]);
274959
+ } catch {
274960
+ return void 0;
274961
+ }
274962
+ }
274485
274963
  function ensureSyncSchema(db2, projectId) {
274486
274964
  db2.exec(CREATE_SYNC_TABLES);
274487
274965
  const columns = db2.prepare("PRAGMA table_info(sync_row_state)").all();
@@ -275392,9 +275870,30 @@ var sync_store_exports = {};
275392
275870
  __export(sync_store_exports, {
275393
275871
  default: () => sync_store_default
275394
275872
  });
275873
+ function parseScope(raw) {
275874
+ const value = String(raw || "project").toLowerCase();
275875
+ if (value === "project" || value === "user") return value;
275876
+ throw new Error("--scope must be one of: project, user");
275877
+ }
275878
+ async function openSyncStore(scope) {
275879
+ if (scope === "user") {
275880
+ const dataDir2 = await getProjectDataDir(USER_SYNC_SCOPE_ID);
275881
+ await initObservationStore(dataDir2);
275882
+ return {
275883
+ projectId: USER_SYNC_SCOPE_ID,
275884
+ syncStore: createSqliteSyncStore(dataDir2, getObservationStore(), USER_SYNC_SCOPE_ID, { scope: "user" })
275885
+ };
275886
+ }
275887
+ const { project, dataDir } = await getCliProjectContext();
275888
+ await initObservationStore(dataDir);
275889
+ return {
275890
+ projectId: project.id,
275891
+ syncStore: createSqliteSyncStore(dataDir, getObservationStore(), project.id)
275892
+ };
275893
+ }
275395
275894
  function renderReport(report) {
275396
275895
  const lines = [
275397
- `Store sync (${report.remote}) \u2014 device ${report.deviceId}${report.dryRun ? " [dry-run]" : ""}`,
275896
+ `Store sync (${report.remote}, scope ${report.scope}) \u2014 device ${report.deviceId}${report.dryRun ? " [dry-run]" : ""}`,
275398
275897
  `- Pushed changes: ${report.pushed}`,
275399
275898
  `- Pulled batches: ${report.pulledBatches}`,
275400
275899
  `- Applied upserts: ${report.applied}`,
@@ -275420,9 +275919,11 @@ var init_sync_store = __esm({
275420
275919
  init_dist2();
275421
275920
  init_operator_shared();
275422
275921
  init_obs_store();
275922
+ init_persistence();
275423
275923
  init_engine6();
275424
275924
  init_store_port();
275425
275925
  init_remote_factory();
275926
+ init_namespace();
275426
275927
  sync_store_default = defineCommand({
275427
275928
  meta: {
275428
275929
  name: "sync-store",
@@ -275434,23 +275935,34 @@ var init_sync_store = __esm({
275434
275935
  "push-only": { type: "boolean", description: "Only push local changes", default: false },
275435
275936
  "pull-only": { type: "boolean", description: "Only pull remote changes", default: false },
275436
275937
  through: { type: "string", description: "Compaction cutoff: device=sequence,device=sequence" },
275437
- yes: { type: "boolean", description: "Confirm destructive remote compaction", default: false }
275938
+ yes: { type: "boolean", description: "Confirm destructive remote compaction", default: false },
275939
+ scope: {
275940
+ type: "string",
275941
+ description: "project (default: current Git project) or user (all local projects)",
275942
+ default: "project"
275943
+ }
275438
275944
  },
275439
275945
  run: async ({ args }) => {
275440
275946
  const asJson = Boolean(args.json);
275441
275947
  const action = args._?.[0] || "status";
275948
+ let scope;
275949
+ try {
275950
+ scope = parseScope(args.scope);
275951
+ } catch (err) {
275952
+ emitError(err.message, asJson);
275953
+ process.exitCode = 2;
275954
+ return;
275955
+ }
275442
275956
  if (action === "device") {
275443
- const { project: project2, dataDir: dataDir2 } = await getCliProjectContext();
275444
- await initObservationStore(dataDir2);
275445
- const syncStore2 = createSqliteSyncStore(dataDir2, getObservationStore(), project2.id);
275446
275957
  const subaction = args._?.[1] || "";
275447
275958
  if (subaction !== "rotate") {
275448
275959
  emitError('expected "memorix sync store device rotate"', asJson);
275449
275960
  process.exitCode = 2;
275450
275961
  return;
275451
275962
  }
275963
+ const { projectId: projectId2, syncStore: syncStore2 } = await openSyncStore("user");
275452
275964
  const deviceId = syncStore2.rotateDevice();
275453
- emitResult({ project: project2.id, deviceId, rotated: true }, `Sync device identity rotated: ${deviceId}`, asJson);
275965
+ emitResult({ project: projectId2, scope: "user", deviceId, rotated: true }, `Sync device identity rotated: ${deviceId}`, asJson);
275454
275966
  return;
275455
275967
  }
275456
275968
  if (action !== "push" && action !== "pull" && action !== "status" && action !== "compact") {
@@ -275474,10 +275986,7 @@ var init_sync_store = __esm({
275474
275986
  );
275475
275987
  return;
275476
275988
  }
275477
- const { project, dataDir } = await getCliProjectContext();
275478
- await initObservationStore(dataDir);
275479
- const obsStore = getObservationStore();
275480
- const syncStore = createSqliteSyncStore(dataDir, obsStore, project.id);
275989
+ const { projectId, syncStore } = await openSyncStore(scope);
275481
275990
  if (action === "compact") {
275482
275991
  if (!args.yes && !args.dry) {
275483
275992
  emitError("remote compaction deletes relay events; repeat with --yes after specifying --through device=sequence", asJson);
@@ -275512,7 +276021,7 @@ var init_sync_store = __esm({
275512
276021
  try {
275513
276022
  await remote2.init({ create: false });
275514
276023
  const result = await remote2.compact(through, { dryRun: Boolean(args.dry) });
275515
- emitResult({ project: project.id, through, ...result, dryRun: Boolean(args.dry) }, `Compaction candidates: ${result.candidates}; deleted: ${result.deleted}`, asJson);
276024
+ emitResult({ project: projectId, scope, through, ...result, dryRun: Boolean(args.dry) }, `Compaction candidates: ${result.candidates}; deleted: ${result.deleted}`, asJson);
275516
276025
  } catch (err) {
275517
276026
  emitError(err.message, asJson);
275518
276027
  process.exitCode = 1;
@@ -275537,7 +276046,7 @@ var init_sync_store = __esm({
275537
276046
  push: true,
275538
276047
  pull: true
275539
276048
  });
275540
- emitResult({ project: project.id, ...report }, renderReport(report), asJson);
276049
+ emitResult({ project: projectId, ...report }, renderReport(report), asJson);
275541
276050
  } catch (err) {
275542
276051
  emitError(err.message, asJson);
275543
276052
  process.exitCode = 1;
@@ -275561,7 +276070,7 @@ var init_sync_store = __esm({
275561
276070
  push: doPush,
275562
276071
  pull: doPull
275563
276072
  });
275564
- emitResult({ project: project.id, ...report }, renderReport(report), asJson);
276073
+ emitResult({ project: projectId, ...report }, renderReport(report), asJson);
275565
276074
  } catch (err) {
275566
276075
  emitError(err.message, asJson);
275567
276076
  process.exitCode = 1;
@@ -275623,6 +276132,32 @@ var init_sync = __esm({
275623
276132
  type: "boolean",
275624
276133
  description: "Emit machine-readable JSON output",
275625
276134
  default: false
276135
+ },
276136
+ scope: {
276137
+ type: "string",
276138
+ description: "Store sync scope: project (default) or user",
276139
+ required: false,
276140
+ default: "project"
276141
+ },
276142
+ "push-only": {
276143
+ type: "boolean",
276144
+ description: "Store sync: only push local changes",
276145
+ required: false
276146
+ },
276147
+ "pull-only": {
276148
+ type: "boolean",
276149
+ description: "Store sync: only pull remote changes",
276150
+ required: false
276151
+ },
276152
+ through: {
276153
+ type: "string",
276154
+ description: "Store sync compaction cutoff: device=sequence[,device=sequence]",
276155
+ required: false
276156
+ },
276157
+ yes: {
276158
+ type: "boolean",
276159
+ description: "Store sync: confirm destructive remote compaction",
276160
+ required: false
275626
276161
  }
275627
276162
  },
275628
276163
  run: async ({ args }) => {
@@ -279290,6 +279825,7 @@ var init_media = __esm({
279290
279825
  concepts: { type: "string", description: "Comma-separated concepts when attaching" },
279291
279826
  visibility: { type: "string", description: "Observation visibility when attaching" },
279292
279827
  prompt: { type: "string", description: "Generation prompt" },
279828
+ "first-frame-image-url": { type: "string", description: "Public HTTPS first-frame image URL for MiniMax video generation; stored with the queued job" },
279293
279829
  image: { type: "string", description: "Path to a reference image for image-to-image generation" },
279294
279830
  model: { type: "string", description: "Provider model" },
279295
279831
  provider: { type: "string", description: "Audio transcription provider: openai or groq" },
@@ -279340,6 +279876,7 @@ var init_media = __esm({
279340
279876
  dataDir,
279341
279877
  projectId: project.id,
279342
279878
  prompt: prompt2,
279879
+ firstFrameImageUrl: args["first-frame-image-url"],
279343
279880
  model: parseMiniMaxVideoModel(args.model),
279344
279881
  region: parseMiniMaxRegion(args.region),
279345
279882
  duration: parseMiniMaxVideoDuration(args.duration),
@@ -280238,8 +280775,20 @@ async function doStartUnlocked(port) {
280238
280775
  if (healthy) {
280239
280776
  process.stderr.write("[OK] Control plane is running and healthy.\n");
280240
280777
  } else {
280241
- process.stderr.write("[WARN] Health check timed out \u2014 service may still be initializing.\n");
280242
- process.stderr.write(" Check later: memorix background status\n");
280778
+ process.stderr.write("[ERROR] Health check timed out; refusing to leave an unverified background state.\n");
280779
+ if (isProcessRunning(pid)) {
280780
+ killProcess(pid);
280781
+ for (let i4 = 0; i4 < 10 && isProcessRunning(pid); i4++) {
280782
+ await new Promise((r6) => setTimeout(r6, 250));
280783
+ }
280784
+ }
280785
+ clearState();
280786
+ try {
280787
+ fs26.unlinkSync(getMemorixDir() + "/background.ready");
280788
+ } catch {
280789
+ }
280790
+ process.exitCode = 1;
280791
+ return;
280243
280792
  }
280244
280793
  const footer = [
280245
280794
  "",
@@ -280627,7 +281176,7 @@ import { spawnSync as spawnSync2 } from "child_process";
280627
281176
  import { existsSync as existsSync31, realpathSync as realpathSync2 } from "fs";
280628
281177
  import { mkdir as mkdir9, readFile as readFile10, writeFile as writeFile10 } from "fs/promises";
280629
281178
  import { basename as basename5, dirname as dirname8, resolve as resolve7 } from "path";
280630
- import { homedir as homedir35 } from "os";
281179
+ import { homedir as homedir36 } from "os";
280631
281180
  function requestedAgents(agent) {
280632
281181
  const all = getSetupAgentTargets();
280633
281182
  if (!agent || agent === "all") return all;
@@ -280705,16 +281254,16 @@ function asRecordArray(value) {
280705
281254
  return Array.isArray(value) ? value.map(asRecord3).filter((entry) => entry !== null) : [];
280706
281255
  }
280707
281256
  function codexPluginPath() {
280708
- return `${homedir35()}/plugins/${CODEX_PLUGIN_NAME}`;
281257
+ return `${homedir36()}/plugins/${CODEX_PLUGIN_NAME}`;
280709
281258
  }
280710
281259
  function codexPluginMcpPath() {
280711
281260
  return `${codexPluginPath()}/.mcp.json`;
280712
281261
  }
280713
281262
  function codexMarketplacePath() {
280714
- return `${homedir35()}/.agents/plugins/marketplace.json`;
281263
+ return `${homedir36()}/.agents/plugins/marketplace.json`;
280715
281264
  }
280716
281265
  function codexConfigPath() {
280717
- return `${homedir35()}/.codex/config.toml`;
281266
+ return `${homedir36()}/.codex/config.toml`;
280718
281267
  }
280719
281268
  function normalizeMarketplacePath(value) {
280720
281269
  return value.replace(/\\/g, "/").replace(/^\.\//, "");
@@ -281029,7 +281578,7 @@ function canonicalProjectPath(projectRoot) {
281029
281578
  return canonical.replace(/\\/g, "/");
281030
281579
  }
281031
281580
  function getClaudeLocalConfigPath() {
281032
- return `${homedir35()}/.claude.json`;
281581
+ return `${homedir36()}/.claude.json`;
281033
281582
  }
281034
281583
  function coerceMcpServer(name, value) {
281035
281584
  const entry = asRecord3(value);
@@ -281191,7 +281740,58 @@ async function inspectCodexPluginMcp() {
281191
281740
  };
281192
281741
  }
281193
281742
  }
281743
+ async function inspectOptionalGrokMcp(scope) {
281744
+ if (scope === "project" || scope === "local") {
281745
+ return { status: "skipped", issues: ["mcp-host-owned"], checks: [] };
281746
+ }
281747
+ const adapter = getMcpAdapter("grok");
281748
+ const configPath = adapter.getConfigPath();
281749
+ if (!existsSync31(configPath)) {
281750
+ return {
281751
+ status: "skipped",
281752
+ issues: ["mcp-host-owned"],
281753
+ checks: [{
281754
+ scope: "global",
281755
+ path: configPath,
281756
+ exists: false,
281757
+ status: "skipped",
281758
+ issues: ["mcp-host-owned"]
281759
+ }]
281760
+ };
281761
+ }
281762
+ const content = await readFile10(configPath, "utf-8");
281763
+ const server = adapter.parse(content).find((entry) => entry.name === "memorix");
281764
+ if (!server) {
281765
+ return {
281766
+ status: "skipped",
281767
+ issues: ["mcp-host-owned"],
281768
+ checks: [{
281769
+ scope: "global",
281770
+ path: configPath,
281771
+ exists: true,
281772
+ status: "skipped",
281773
+ issues: ["mcp-host-owned"]
281774
+ }]
281775
+ };
281776
+ }
281777
+ const issues = [];
281778
+ if (!server.url && looksLikeStaleMemorixCommand(server)) issues.push("stale-command-path");
281779
+ if (!server.url && !isRecommendedStdioServer(server)) issues.push("nonstandard-mcp-command");
281780
+ return {
281781
+ status: issues.length > 0 ? "repairable" : "ok",
281782
+ issues,
281783
+ checks: [{
281784
+ scope: "global",
281785
+ path: configPath,
281786
+ exists: true,
281787
+ status: issues.length > 0 ? "repairable" : "ok",
281788
+ issues,
281789
+ server: sanitizeServer(server)
281790
+ }]
281791
+ };
281792
+ }
281194
281793
  async function inspectMcp(agent, projectRoot, scope) {
281794
+ if (agent === "grok") return inspectOptionalGrokMcp(scope);
281195
281795
  if (!isMcpConfigAgent(agent)) {
281196
281796
  return { status: "skipped", issues: ["mcp-managed-by-package"], checks: [] };
281197
281797
  }
@@ -281288,7 +281888,7 @@ async function inspectGuidance(agent, projectRoot, scope) {
281288
281888
  }
281289
281889
  const checks = [];
281290
281890
  for (const targetScope of requestedGuidanceScopes(scope)) {
281291
- const root = targetScope === "project" ? projectRoot : homedir35();
281891
+ const root = targetScope === "project" ? projectRoot : homedir36();
281292
281892
  const rulesPath = getAgentRulesPath(agent, root, targetScope === "global");
281293
281893
  if (!existsSync31(rulesPath)) {
281294
281894
  checks.push({
@@ -281569,8 +282169,8 @@ var init_doctor = __esm({
281569
282169
  return;
281570
282170
  }
281571
282171
  const { existsSync: existsSync36, readFileSync: readFileSync25 } = await import("fs");
281572
- const { join: join35, basename: basename7 } = await import("path");
281573
- const { homedir: homedir37 } = await import("os");
282172
+ const { join: join36, basename: basename7 } = await import("path");
282173
+ const { homedir: homedir38 } = await import("os");
281574
282174
  const { execSync: execSync5 } = await import("child_process");
281575
282175
  const report = {};
281576
282176
  const issues = [];
@@ -281632,8 +282232,8 @@ var init_doctor = __esm({
281632
282232
  }
281633
282233
  lines.push("");
281634
282234
  lines.push("\u250C\u2500 Runtime Mode \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
281635
- const memorixDir = join35(homedir37(), ".memorix");
281636
- const bgStatePath = join35(memorixDir, "background.json");
282235
+ const memorixDir = join36(homedir38(), ".memorix");
282236
+ const bgStatePath = join36(memorixDir, "background.json");
281637
282237
  let bgRunning = false;
281638
282238
  let bgPort = 0;
281639
282239
  let bgPid = 0;
@@ -281753,7 +282353,7 @@ var init_doctor = __esm({
281753
282353
  }
281754
282354
  }
281755
282355
  try {
281756
- const cachePath = join35(memorixDir, "embedding-cache.json");
282356
+ const cachePath = join36(memorixDir, "embedding-cache.json");
281757
282357
  if (existsSync36(cachePath)) {
281758
282358
  const cache5 = JSON.parse(readFileSync25(cachePath, "utf-8"));
281759
282359
  const cacheCount = Object.keys(cache5).length;
@@ -282007,7 +282607,7 @@ var init_doctor = __esm({
282007
282607
  lines.push("\u250C\u2500 Conflict Check \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
282008
282608
  let conflictsFound = false;
282009
282609
  if (dataDir) {
282010
- const lockFile = join35(dataDir, ".memorix.lock");
282610
+ const lockFile = join36(dataDir, ".memorix.lock");
282011
282611
  if (existsSync36(lockFile)) {
282012
282612
  try {
282013
282613
  const lockData = JSON.parse(readFileSync25(lockFile, "utf-8"));
@@ -284219,7 +284819,7 @@ var init_pipeline_trace = __esm({
284219
284819
  // src/orchestrate/worktree.ts
284220
284820
  import { execFileSync as execFileSync5 } from "child_process";
284221
284821
  import { existsSync as existsSync33, rmSync as rmSync2 } from "fs";
284222
- import { join as join33, basename as basename6 } from "path";
284822
+ import { join as join34, basename as basename6 } from "path";
284223
284823
  function runGit4(cwd, args, timeout = 5e3) {
284224
284824
  return String(execFileSync5("git", args, {
284225
284825
  cwd,
@@ -284258,7 +284858,7 @@ function isManagedWorktreePath(worktreePath, worktreeBase) {
284258
284858
  function createWorktree(projectDir2, taskId, pipelineId) {
284259
284859
  const shortId2 = taskId.slice(0, 8);
284260
284860
  const shortPipeline = pipelineId.slice(0, 8);
284261
- const worktreeBase = join33(projectDir2, WORKTREE_DIR, `task-${shortId2}`);
284861
+ const worktreeBase = join34(projectDir2, WORKTREE_DIR, `task-${shortId2}`);
284262
284862
  const branchBase = `pipeline/${shortPipeline}/task-${shortId2}`;
284263
284863
  for (let attempt = 1; attempt <= 100; attempt++) {
284264
284864
  const suffix = attempt === 1 ? "" : `-${attempt}`;
@@ -284348,7 +284948,7 @@ function listWorktrees(projectDir2) {
284348
284948
  }
284349
284949
  }
284350
284950
  if (current) worktrees.push(current);
284351
- const worktreeBase = join33(projectDir2, WORKTREE_DIR);
284951
+ const worktreeBase = join34(projectDir2, WORKTREE_DIR);
284352
284952
  return worktrees.filter((w4) => isManagedWorktreePath(w4.path, worktreeBase));
284353
284953
  } catch {
284354
284954
  return [];
@@ -284770,16 +285370,16 @@ var init_cost_tracker = __esm({
284770
285370
 
284771
285371
  // src/orchestrate/evidence.ts
284772
285372
  import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync11 } from "fs";
284773
- import { join as join34 } from "path";
285373
+ import { join as join35 } from "path";
284774
285374
  function writeTaskEvidence(projectDir2, pipelineId, evidence) {
284775
285375
  try {
284776
- const dir = join34(projectDir2, ".pipeline", pipelineId, "evidence", evidence.taskId.slice(0, 12));
285376
+ const dir = join35(projectDir2, ".pipeline", pipelineId, "evidence", evidence.taskId.slice(0, 12));
284777
285377
  mkdirSync11(dir, { recursive: true });
284778
285378
  if (evidence.prompt) {
284779
- safeWrite(join34(dir, "prompt.md"), evidence.prompt);
285379
+ safeWrite(join35(dir, "prompt.md"), evidence.prompt);
284780
285380
  }
284781
285381
  if (evidence.tailOutput) {
284782
- safeWrite(join34(dir, "output.txt"), evidence.tailOutput);
285382
+ safeWrite(join35(dir, "output.txt"), evidence.tailOutput);
284783
285383
  }
284784
285384
  if (evidence.gateResults) {
284785
285385
  for (const g4 of evidence.gateResults) {
@@ -284788,7 +285388,7 @@ Status: ${g4.passed ? "PASS" : "FAIL"}
284788
285388
  Duration: ${(g4.durationMs / 1e3).toFixed(1)}s
284789
285389
 
284790
285390
  ${g4.output}`;
284791
- safeWrite(join34(dir, `${g4.gate}.txt`), content);
285391
+ safeWrite(join35(dir, `${g4.gate}.txt`), content);
284792
285392
  }
284793
285393
  }
284794
285394
  const resultJson = {
@@ -284802,7 +285402,7 @@ ${g4.output}`;
284802
285402
  tokenUsage: evidence.tokenUsage,
284803
285403
  costUSD: evidence.costUSD
284804
285404
  };
284805
- safeWrite(join34(dir, "result.json"), JSON.stringify(resultJson, null, 2));
285405
+ safeWrite(join35(dir, "result.json"), JSON.stringify(resultJson, null, 2));
284806
285406
  return dir;
284807
285407
  } catch {
284808
285408
  return null;
@@ -284810,7 +285410,7 @@ ${g4.output}`;
284810
285410
  }
284811
285411
  function writePipelineSummary(projectDir2, summary) {
284812
285412
  try {
284813
- const dir = join34(projectDir2, ".pipeline", summary.pipelineId);
285413
+ const dir = join35(projectDir2, ".pipeline", summary.pipelineId);
284814
285414
  mkdirSync11(dir, { recursive: true });
284815
285415
  const elapsed = (summary.elapsedMs / 1e3).toFixed(0);
284816
285416
  const lines = [
@@ -284831,8 +285431,8 @@ function writePipelineSummary(projectDir2, summary) {
284831
285431
  lines.push(`- [${status}] ${t4.taskDescription.slice(0, 80)} \u2014 ${t4.agentName}, ${dur}s${fixes}`);
284832
285432
  }
284833
285433
  const content = lines.join("\n") + "\n";
284834
- safeWrite(join34(dir, "summary.md"), content);
284835
- return join34(dir, "summary.md");
285434
+ safeWrite(join35(dir, "summary.md"), content);
285435
+ return join35(dir, "summary.md");
284836
285436
  } catch {
284837
285437
  return null;
284838
285438
  }
@@ -291211,11 +291811,11 @@ var GUIDES = {
291211
291811
  "memorix sync rules --action status",
291212
291812
  "memorix sync workspace --action scan",
291213
291813
  "memorix sync store status --json",
291214
- "memorix sync store push|pull [--dry] [--json]",
291814
+ "memorix sync store push|pull [--dry] [--json] [--scope project|user]",
291215
291815
  "memorix sync store compact --dry --through device=sequence",
291216
291816
  "memorix sync store device rotate"
291217
291817
  ],
291218
- notes: ["Store sync is opt-in, project-scoped, and never copies the live SQLite database."]
291818
+ notes: ["Store sync is opt-in and never copies the live SQLite database. Default scope is the current Git project; `--scope user` relays every local project."]
291219
291819
  },
291220
291820
  ingest: {
291221
291821
  summary: "Ingest Git and image evidence into project memory.",