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
@@ -835,18 +835,21 @@ CREATE TABLE IF NOT EXISTS chat_transcript (
835
835
  `;
836
836
  CREATE_GRAPH_ENTITIES_TABLE = `
837
837
  CREATE TABLE IF NOT EXISTS graph_entities (
838
- name TEXT PRIMARY KEY,
838
+ projectId TEXT NOT NULL,
839
+ name TEXT NOT NULL,
839
840
  entityType TEXT NOT NULL DEFAULT '',
840
- observations TEXT NOT NULL DEFAULT '[]'
841
+ observations TEXT NOT NULL DEFAULT '[]',
842
+ PRIMARY KEY (projectId, name)
841
843
  );
842
844
  `;
843
845
  CREATE_GRAPH_RELATIONS_TABLE = `
844
846
  CREATE TABLE IF NOT EXISTS graph_relations (
845
847
  id INTEGER PRIMARY KEY AUTOINCREMENT,
848
+ projectId TEXT NOT NULL,
846
849
  from_entity TEXT NOT NULL,
847
850
  to_entity TEXT NOT NULL,
848
851
  relationType TEXT NOT NULL DEFAULT '',
849
- UNIQUE(from_entity, to_entity, relationType)
852
+ UNIQUE(projectId, from_entity, to_entity, relationType)
850
853
  );
851
854
  `;
852
855
  CREATE_CODE_FILES_TABLE = `
@@ -1714,6 +1717,21 @@ var init_sqlite_reliability = __esm({
1714
1717
  import { promises as fs2 } from "fs";
1715
1718
  import { randomUUID } from "crypto";
1716
1719
  import path4 from "path";
1720
+ function canReclaimStaleLock(raw) {
1721
+ try {
1722
+ const parsed = JSON.parse(raw);
1723
+ const pid = Number(parsed.pid);
1724
+ if (!Number.isSafeInteger(pid) || pid < 1) return true;
1725
+ try {
1726
+ process.kill(pid, 0);
1727
+ return false;
1728
+ } catch (error) {
1729
+ return error.code !== "EPERM";
1730
+ }
1731
+ } catch {
1732
+ return true;
1733
+ }
1734
+ }
1717
1735
  async function acquireLock(lockPath) {
1718
1736
  for (let i2 = 0; i2 < MAX_RETRIES; i2++) {
1719
1737
  try {
@@ -1727,9 +1745,12 @@ async function acquireLock(lockPath) {
1727
1745
  try {
1728
1746
  const stat5 = await fs2.stat(lockPath);
1729
1747
  if (Date.now() - stat5.mtimeMs > LOCK_STALE_MS) {
1730
- await fs2.unlink(lockPath).catch(() => {
1731
- });
1732
- continue;
1748
+ const raw2 = await fs2.readFile(lockPath, "utf8").catch(() => "");
1749
+ if (canReclaimStaleLock(raw2)) {
1750
+ await fs2.unlink(lockPath).catch(() => {
1751
+ });
1752
+ continue;
1753
+ }
1733
1754
  }
1734
1755
  } catch {
1735
1756
  continue;
@@ -1740,6 +1761,10 @@ async function acquireLock(lockPath) {
1740
1761
  }
1741
1762
  }
1742
1763
  }
1764
+ const raw = await fs2.readFile(lockPath, "utf8").catch(() => "");
1765
+ if (!canReclaimStaleLock(raw)) {
1766
+ throw new Error(`Failed to acquire lock: ${lockPath} (owner is still running)`);
1767
+ }
1743
1768
  await fs2.unlink(lockPath).catch(() => {
1744
1769
  });
1745
1770
  try {
@@ -5670,6 +5695,7 @@ var init_sqlite_store = __esm({
5670
5695
  init_sqlite_db();
5671
5696
  init_sqlite_reliability();
5672
5697
  init_sqlite_fts();
5698
+ init_file_lock();
5673
5699
  SqliteBackend = class {
5674
5700
  db = null;
5675
5701
  dataDir = "";
@@ -5833,38 +5859,43 @@ var init_sqlite_store = __esm({
5833
5859
  }
5834
5860
  // ── Migration ────────────────────────────────────────────────────
5835
5861
  async migrateFromJsonIfNeeded() {
5836
- const count2 = this.db.prepare(`SELECT COUNT(*) AS cnt FROM observations`).get();
5837
- if (count2.cnt > 0) return;
5838
5862
  const jsonPath = path7.join(this.dataDir, "observations.json");
5839
5863
  if (!fs4.existsSync(jsonPath)) return;
5840
- try {
5864
+ await withFileLock(this.dataDir, async () => {
5865
+ const count2 = this.db.prepare(`SELECT COUNT(*) AS cnt FROM observations`).get();
5866
+ if (count2.cnt > 0) return;
5841
5867
  const raw = fs4.readFileSync(jsonPath, "utf-8");
5842
- const observations2 = JSON.parse(raw);
5868
+ const parsed = JSON.parse(raw);
5869
+ if (!Array.isArray(parsed)) {
5870
+ throw new Error("[memorix] observations.json migration refused: expected a JSON array");
5871
+ }
5872
+ const observations2 = parsed;
5843
5873
  if (observations2.length === 0) return;
5874
+ if (observations2.some((obs) => !obs || !Number.isSafeInteger(obs.id) || obs.id < 1 || typeof obs.projectId !== "string")) {
5875
+ throw new Error("[memorix] observations.json migration refused: invalid observation record");
5876
+ }
5844
5877
  console.error(`[memorix] Migrating ${observations2.length} observations from JSON to SQLite...`);
5845
5878
  const insertMany = this.db.transaction((obsList) => {
5846
5879
  for (const obs of obsList) {
5847
5880
  this.stmtInsert.run(obsToRow(obs));
5848
5881
  }
5849
- });
5850
- insertMany(observations2);
5851
- const counterPath = path7.join(this.dataDir, "counter.json");
5852
- if (fs4.existsSync(counterPath)) {
5853
- try {
5882
+ const counterPath = path7.join(this.dataDir, "counter.json");
5883
+ let counterNextId;
5884
+ if (fs4.existsSync(counterPath)) {
5854
5885
  const counterData = JSON.parse(fs4.readFileSync(counterPath, "utf-8"));
5855
- const nextId2 = counterData.nextId ?? counterData.next_id ?? Math.max(...observations2.map((o) => o.id)) + 1;
5856
- this.rawSaveIdCounter(nextId2);
5857
- } catch {
5858
- this.rawSaveIdCounter(Math.max(...observations2.map((o) => o.id)) + 1);
5886
+ const candidate = counterData.nextId ?? counterData.next_id;
5887
+ if (candidate !== void 0 && (!Number.isSafeInteger(candidate) || Number(candidate) < 1)) {
5888
+ throw new Error("[memorix] counter.json migration refused: nextId must be a positive integer");
5889
+ }
5890
+ counterNextId = candidate === void 0 ? void 0 : Number(candidate);
5859
5891
  }
5860
- } else {
5861
- this.rawSaveIdCounter(Math.max(...observations2.map((o) => o.id)) + 1);
5862
- }
5863
- this.bumpGeneration();
5892
+ const maxId = Math.max(...obsList.map((obs) => obs.id));
5893
+ this.rawSaveIdCounter(Math.max(counterNextId ?? 1, maxId + 1));
5894
+ this.bumpGeneration();
5895
+ });
5896
+ insertMany(observations2);
5864
5897
  console.error(`[memorix] Migration complete. ${observations2.length} observations now in SQLite.`);
5865
- } catch (err) {
5866
- console.error(`[memorix] JSON\u2192SQLite migration failed (non-fatal, data preserved in JSON): ${err}`);
5867
- }
5898
+ });
5868
5899
  }
5869
5900
  // ── Public read ──────────────────────────────────────────────────
5870
5901
  async loadAll() {
@@ -143454,7 +143485,20 @@ function normalizeMiniMaxVideoRequest(input) {
143454
143485
  if (ratio !== "adaptive" && ratio !== "16:9" && ratio !== "9:16" && ratio !== "1:1") {
143455
143486
  throw new Error("MiniMax video ratio must be adaptive, 16:9, 9:16, or 1:1");
143456
143487
  }
143457
- return { region, model, prompt, resolution, duration, ratio };
143488
+ let firstFrameImageUrl;
143489
+ if (input.firstFrameImageUrl !== void 0) {
143490
+ firstFrameImageUrl = input.firstFrameImageUrl.trim();
143491
+ let parsed;
143492
+ try {
143493
+ parsed = new URL(firstFrameImageUrl);
143494
+ } catch {
143495
+ throw new Error("MiniMax first-frame image must be a public HTTPS URL");
143496
+ }
143497
+ if (parsed.protocol !== "https:" || parsed.username || parsed.password || sanitizeCredentials(firstFrameImageUrl) !== firstFrameImageUrl) {
143498
+ throw new Error("MiniMax first-frame image must be an HTTPS URL without credentials");
143499
+ }
143500
+ }
143501
+ return { region, model, prompt, resolution, duration, ratio, ...firstFrameImageUrl ? { firstFrameImageUrl } : {} };
143458
143502
  }
143459
143503
  function videoRequestConfig(input) {
143460
143504
  const normalized = normalizeMiniMaxVideoRequest(input);
@@ -143531,7 +143575,14 @@ async function createMiniMaxVideoTask(input, dependencies = {}) {
143531
143575
  headers: videoHeaders(config.apiKey),
143532
143576
  body: JSON.stringify({
143533
143577
  model: config.model,
143534
- content: [{ type: "text", text: config.prompt }],
143578
+ content: [
143579
+ { type: "text", text: config.prompt },
143580
+ ...config.firstFrameImageUrl ? [{
143581
+ type: "image_url",
143582
+ image_url: { url: config.firstFrameImageUrl },
143583
+ role: "first_frame"
143584
+ }] : []
143585
+ ],
143535
143586
  resolution: config.resolution,
143536
143587
  duration: config.duration,
143537
143588
  ratio: config.ratio
@@ -143692,10 +143743,19 @@ function parseStoredRequest(value) {
143692
143743
  const duration = value.duration;
143693
143744
  const ratio = value.ratio;
143694
143745
  const maxBytes = value.maxBytes;
143695
- if (provider2 !== "minimax" && provider2 !== "minimax-cn" || region !== "global" && region !== "cn" || model !== "MiniMax-H3" || typeof prompt !== "string" || resolution !== "2K" || duration !== 5 && duration !== 10 || ratio !== "adaptive" && ratio !== "16:9" && ratio !== "9:16" && ratio !== "1:1" || typeof maxBytes !== "number" || !Number.isSafeInteger(maxBytes) || maxBytes < 1) {
143746
+ if (provider2 !== "minimax" && provider2 !== "minimax-cn" || region !== "global" && region !== "cn" || model !== "MiniMax-H3" || typeof prompt !== "string" || value.firstFrameImageUrl !== void 0 && typeof value.firstFrameImageUrl !== "string" || resolution !== "2K" || duration !== 5 && duration !== 10 || ratio !== "adaptive" && ratio !== "16:9" && ratio !== "9:16" && ratio !== "1:1" || typeof maxBytes !== "number" || !Number.isSafeInteger(maxBytes) || maxBytes < 1) {
143696
143747
  throw new Error("Media video job has an invalid request payload");
143697
143748
  }
143698
- return { provider: provider2, region, model, prompt, resolution, duration, ratio, maxBytes };
143749
+ const normalized = normalizeMiniMaxVideoRequest({
143750
+ region,
143751
+ model,
143752
+ prompt,
143753
+ resolution,
143754
+ duration,
143755
+ ratio,
143756
+ firstFrameImageUrl: value.firstFrameImageUrl
143757
+ });
143758
+ return { provider: provider2, ...normalized, maxBytes };
143699
143759
  }
143700
143760
  function mediaJobIdFromMaintenance(job) {
143701
143761
  const mediaJobId = job.payload.mediaJobId;
@@ -143707,6 +143767,7 @@ function mediaJobIdFromMaintenance(job) {
143707
143767
  function toVideoRequest(request) {
143708
143768
  return {
143709
143769
  prompt: request.prompt,
143770
+ firstFrameImageUrl: request.firstFrameImageUrl,
143710
143771
  model: request.model,
143711
143772
  region: request.region,
143712
143773
  resolution: request.resolution,
@@ -143798,6 +143859,7 @@ function queueMiniMaxVideoGeneration(input) {
143798
143859
  region: normalized.region,
143799
143860
  model: normalized.model,
143800
143861
  prompt: normalized.prompt,
143862
+ ...normalized.firstFrameImageUrl ? { firstFrameImageUrl: normalized.firstFrameImageUrl } : {},
143801
143863
  resolution: normalized.resolution,
143802
143864
  duration: normalized.duration,
143803
143865
  ratio: normalized.ratio,
@@ -2,7 +2,55 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
- ## [Unreleased]
5
+ ## [1.9.5] - 2026-09-20
6
+
7
+ ### Added
8
+ - **Setup HTTP MCP** -- `memorix setup --mcp http` honors Grok (writes a
9
+ `[mcp_servers.memorix]` URL in Grok `config.toml`) and rewrites installed
10
+ plugin/extension bundled MCP manifests from `memorix serve` to
11
+ `http://localhost:3211/mcp`. Default Grok MCP stays host-owned. Docs include
12
+ generic systemd user unit and LaunchAgent KeepAlive examples for
13
+ `memorix serve-http`.
14
+ - **User-global store sync** -- `memorix sync store push|pull|status --scope user`
15
+ replicates every local project's eligible observations through the existing
16
+ opt-in relay. Default `--scope project` is unchanged. Privacy filters still
17
+ drop personal, agent-targeted, candidate, and ephemeral rows. The live
18
+ SQLite file is never copied.
19
+
20
+ ### Fixed
21
+ - **HTTP control-plane safety** -- JSON request bodies are bounded, non-loopback
22
+ binds require an explicit Bearer token or unsafe opt-in, listen failures are
23
+ reported synchronously, background startup removes unverified state, and
24
+ shutdown waits for HTTP and SQLite resources to close.
25
+ - **Project-scoped graph storage** -- graph entities and relations now carry a
26
+ project key, legacy JSONL migration recovers ownership from observation
27
+ references, and Dashboard/MCP graph access no longer crosses projects.
28
+ - **Migration and lifecycle reliability** -- legacy observation/subdirectory
29
+ migrations use locks and atomic writes, malformed observation JSON fails
30
+ closed, stale file locks cannot evict a live writer, stateless MCP bindings
31
+ expire and are cleaned up, and SDK clients share resources safely.
32
+ - **User-scope sync correctness** -- imported observation IDs are remapped on
33
+ collision, tombstones validate their source project, and user namespaces
34
+ must be explicit per-user values.
35
+ - **Release contract** -- publish workflow validates tag/version/commit
36
+ identity, waits for npm visibility before MCP Registry publication, and
37
+ keeps prepublish checks non-mutating.
38
+
39
+ ## [1.9.4] - 2026-09-18
40
+
41
+ ### Added
42
+ - **MiniMax first-frame video input** -- image-to-video generation now accepts
43
+ a public HTTPS first-frame image URL through the CLI and MCP surfaces,
44
+ preserves it in durable media jobs, and submits it with the provider's
45
+ `first_frame` image role.
46
+
47
+ ### Fixed
48
+ - **First-frame URL safety** -- rejects non-HTTPS URLs and embedded credentials
49
+ before a media request is submitted or persisted.
50
+
51
+ ### Changed
52
+ - **Grok Build documentation** -- the README comparison now uses the official
53
+ Grok Build logo assets.
6
54
 
7
55
  ## [1.9.3] - 2026-09-14
8
56
 
package/dist/sdk.d.ts CHANGED
@@ -120,6 +120,7 @@ declare class MemoryClient {
120
120
  private _oramaStore;
121
121
  private _obsStore;
122
122
  private _freshness;
123
+ private _sdkRefRegistered;
123
124
  /** @internal — use createMemoryClient() instead */
124
125
  constructor(projectId: string, projectRoot: string, dataDir: string);
125
126
  /** The canonical project ID (derived from Git remote or local path) */