memorix 1.9.2 → 1.9.4

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 (39) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/dist/cli/index.js +358 -146
  5. package/dist/index.js +281 -125
  6. package/dist/maintenance-runner.js +219 -97
  7. package/dist/media-audio-runner.js +219 -97
  8. package/dist/media-video-runner.js +219 -97
  9. package/dist/memcode-runtime/CHANGELOG.md +27 -0
  10. package/dist/sdk.js +280 -124
  11. package/dist/vector-backfill-runner.js +219 -97
  12. package/docs/AGENT_OPERATOR_PLAYBOOK.md +15 -0
  13. package/docs/ARCHITECTURE.md +23 -0
  14. package/docs/DEVELOPMENT.md +1 -1
  15. package/docs/PERFORMANCE.md +50 -0
  16. package/docs/README.md +1 -1
  17. package/package.json +1 -1
  18. package/plugins/antigravity/memorix/plugin.json +1 -1
  19. package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
  20. package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
  21. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  22. package/plugins/copilot/memorix/plugin.json +1 -1
  23. package/plugins/gemini/memorix/gemini-extension.json +1 -1
  24. package/plugins/hermes/memorix/plugin.yaml +1 -1
  25. package/plugins/omp/memorix/package.json +1 -1
  26. package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
  27. package/plugins/pi/memorix/package.json +1 -1
  28. package/server.json +2 -2
  29. package/src/cli/commands/doctor.ts +78 -17
  30. package/src/cli/commands/media.ts +2 -0
  31. package/src/media/minimax.ts +26 -2
  32. package/src/media/video-jobs.ts +9 -1
  33. package/src/server.ts +3 -1
  34. package/src/store/mini-skill-store.ts +31 -22
  35. package/src/store/obs-store.ts +46 -30
  36. package/src/store/session-store.ts +35 -21
  37. package/src/store/sqlite-db.ts +16 -7
  38. package/src/store/sqlite-reliability.ts +87 -0
  39. package/src/store/sqlite-store.ts +4 -1
@@ -580,70 +580,79 @@ function getDatabase(dataDir) {
580
580
  loadBetterSqlite3();
581
581
  fs.mkdirSync(dataDir, { recursive: true });
582
582
  const dbPath = path3.join(dataDir, "memorix.db");
583
- const db2 = createDatabase(dbPath);
584
- db2.pragma("journal_mode = WAL");
585
- db2.pragma("busy_timeout = 5000");
586
- db2.pragma("foreign_keys = ON");
587
- db2.exec(CREATE_OBSERVATIONS_TABLE);
588
- db2.exec(CREATE_MINI_SKILLS_TABLE);
589
- db2.exec(CREATE_SESSIONS_TABLE);
590
- db2.exec(CREATE_META_TABLE);
591
- db2.exec(CREATE_TEAM_AGENTS_TABLE);
592
- db2.exec(CREATE_TEAM_TASKS_TABLE);
593
- db2.exec(CREATE_TEAM_TASK_DEPS_TABLE);
594
- db2.exec(CREATE_TEAM_MESSAGES_TABLE);
595
- db2.exec(CREATE_TEAM_LOCKS_TABLE);
596
- db2.exec(CREATE_TEAM_ROLES_TABLE);
597
- db2.exec(CREATE_GRAPH_ENTITIES_TABLE);
598
- db2.exec(CREATE_GRAPH_RELATIONS_TABLE);
599
- db2.exec(CREATE_CHAT_TRANSCRIPT_TABLE);
600
- db2.exec(CREATE_CODE_FILES_TABLE);
601
- db2.exec(CREATE_CODE_SYMBOLS_TABLE);
602
- db2.exec(CREATE_CODE_EDGES_TABLE);
603
- db2.exec(CREATE_OBSERVATION_CODE_REFS_TABLE);
604
- db2.exec(CREATE_MAINTENANCE_JOBS_TABLE);
605
- db2.exec(CREATE_MAINTENANCE_TARGETS_TABLE);
606
- db2.exec(CREATE_COMPACTION_CHECKPOINTS_TABLE);
583
+ let db2;
607
584
  try {
608
- db2.exec(`ALTER TABLE mini_skills ADD COLUMN sourceSnapshot TEXT NOT NULL DEFAULT ''`);
609
- } catch {
610
- }
611
- try {
612
- db2.exec(`ALTER TABLE mini_skills ADD COLUMN updatedAt TEXT`);
613
- } catch {
614
- }
615
- try {
616
- db2.exec(`ALTER TABLE observations ADD COLUMN createdByAgentId TEXT`);
617
- } catch {
618
- }
619
- try {
620
- db2.exec(`ALTER TABLE observations ADD COLUMN writeGeneration INTEGER DEFAULT 0`);
621
- } catch {
622
- }
623
- try {
624
- db2.exec(`ALTER TABLE team_tasks ADD COLUMN required_role TEXT`);
625
- } catch {
626
- }
627
- try {
628
- db2.exec(`ALTER TABLE team_tasks ADD COLUMN preferred_role TEXT`);
629
- } catch {
630
- }
631
- try {
632
- db2.exec(`ALTER TABLE team_messages ADD COLUMN to_role TEXT`);
633
- } catch {
634
- }
635
- try {
636
- db2.exec(`ALTER TABLE team_messages ADD COLUMN handoff_status TEXT`);
637
- } catch {
585
+ db2 = createDatabase(dbPath);
586
+ db2.pragma("busy_timeout = 5000");
587
+ db2.pragma("journal_mode = WAL");
588
+ db2.pragma("foreign_keys = ON");
589
+ db2.exec(CREATE_OBSERVATIONS_TABLE);
590
+ db2.exec(CREATE_MINI_SKILLS_TABLE);
591
+ db2.exec(CREATE_SESSIONS_TABLE);
592
+ db2.exec(CREATE_META_TABLE);
593
+ db2.exec(CREATE_TEAM_AGENTS_TABLE);
594
+ db2.exec(CREATE_TEAM_TASKS_TABLE);
595
+ db2.exec(CREATE_TEAM_TASK_DEPS_TABLE);
596
+ db2.exec(CREATE_TEAM_MESSAGES_TABLE);
597
+ db2.exec(CREATE_TEAM_LOCKS_TABLE);
598
+ db2.exec(CREATE_TEAM_ROLES_TABLE);
599
+ db2.exec(CREATE_GRAPH_ENTITIES_TABLE);
600
+ db2.exec(CREATE_GRAPH_RELATIONS_TABLE);
601
+ db2.exec(CREATE_CHAT_TRANSCRIPT_TABLE);
602
+ db2.exec(CREATE_CODE_FILES_TABLE);
603
+ db2.exec(CREATE_CODE_SYMBOLS_TABLE);
604
+ db2.exec(CREATE_CODE_EDGES_TABLE);
605
+ db2.exec(CREATE_OBSERVATION_CODE_REFS_TABLE);
606
+ db2.exec(CREATE_MAINTENANCE_JOBS_TABLE);
607
+ db2.exec(CREATE_MAINTENANCE_TARGETS_TABLE);
608
+ db2.exec(CREATE_COMPACTION_CHECKPOINTS_TABLE);
609
+ try {
610
+ db2.exec(`ALTER TABLE mini_skills ADD COLUMN sourceSnapshot TEXT NOT NULL DEFAULT ''`);
611
+ } catch {
612
+ }
613
+ try {
614
+ db2.exec(`ALTER TABLE mini_skills ADD COLUMN updatedAt TEXT`);
615
+ } catch {
616
+ }
617
+ try {
618
+ db2.exec(`ALTER TABLE observations ADD COLUMN createdByAgentId TEXT`);
619
+ } catch {
620
+ }
621
+ try {
622
+ db2.exec(`ALTER TABLE observations ADD COLUMN writeGeneration INTEGER DEFAULT 0`);
623
+ } catch {
624
+ }
625
+ try {
626
+ db2.exec(`ALTER TABLE team_tasks ADD COLUMN required_role TEXT`);
627
+ } catch {
628
+ }
629
+ try {
630
+ db2.exec(`ALTER TABLE team_tasks ADD COLUMN preferred_role TEXT`);
631
+ } catch {
632
+ }
633
+ try {
634
+ db2.exec(`ALTER TABLE team_messages ADD COLUMN to_role TEXT`);
635
+ } catch {
636
+ }
637
+ try {
638
+ db2.exec(`ALTER TABLE team_messages ADD COLUMN handoff_status TEXT`);
639
+ } catch {
640
+ }
641
+ applySchemaMigrations(db2);
642
+ db2.exec(CREATE_INDEXES);
643
+ initializeObservationLexicalIndex(db2);
644
+ db2.prepare(`INSERT OR IGNORE INTO meta (key, value) VALUES ('storage_generation', '0')`).run();
645
+ db2.prepare(`INSERT OR IGNORE INTO meta (key, value) VALUES ('next_id', '1')`).run();
646
+ db2.prepare(`INSERT OR IGNORE INTO meta (key, value) VALUES ('mini_skills_generation', '0')`).run();
647
+ _dbCache.set(normalized, db2);
648
+ return db2;
649
+ } catch (error) {
650
+ try {
651
+ db2?.close();
652
+ } catch {
653
+ }
654
+ throw error;
638
655
  }
639
- applySchemaMigrations(db2);
640
- db2.exec(CREATE_INDEXES);
641
- initializeObservationLexicalIndex(db2);
642
- db2.prepare(`INSERT OR IGNORE INTO meta (key, value) VALUES ('storage_generation', '0')`).run();
643
- db2.prepare(`INSERT OR IGNORE INTO meta (key, value) VALUES ('next_id', '1')`).run();
644
- db2.prepare(`INSERT OR IGNORE INTO meta (key, value) VALUES ('mini_skills_generation', '0')`).run();
645
- _dbCache.set(normalized, db2);
646
- return db2;
647
656
  }
648
657
  function closeAllDatabases() {
649
658
  for (const [key, db2] of _dbCache) {
@@ -1633,6 +1642,74 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_maintenance_jobs_active_dedupe
1633
1642
  }
1634
1643
  });
1635
1644
 
1645
+ // src/store/sqlite-reliability.ts
1646
+ function isTransientSqliteError(error) {
1647
+ const code = error?.code;
1648
+ if (typeof code === "string") {
1649
+ return /^SQLITE_BUSY(?:_|$)/i.test(code);
1650
+ }
1651
+ return error instanceof Error && /\bdatabase is locked\b|\bdatabase is busy\b/i.test(error.message);
1652
+ }
1653
+ function isSqliteUnavailableError(error) {
1654
+ return error instanceof SqliteUnavailableError || error instanceof Error && /SQLite is not available|better-sqlite3.*failed|node:sqlite.*failed|bun:sqlite.*failed/i.test(error.message);
1655
+ }
1656
+ function degradedReadError(storeName) {
1657
+ return new Error(`[memorix] Cannot read ${storeName}: SQLite backend unavailable (memory is not loaded)`);
1658
+ }
1659
+ function degradedWriteError(storeName) {
1660
+ return new Error(`[memorix] Cannot write ${storeName}: SQLite backend unavailable (degraded mode; read-only)`);
1661
+ }
1662
+ function formatSqliteFailure(error) {
1663
+ return error instanceof Error && error.message ? error.message : String(error);
1664
+ }
1665
+ function delay(ms) {
1666
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
1667
+ }
1668
+ async function retrySqliteBusy(operation, label) {
1669
+ for (let attempt = 0; ; attempt++) {
1670
+ try {
1671
+ return await operation();
1672
+ } catch (error) {
1673
+ if (!isTransientSqliteError(error) || attempt >= SQLITE_BUSY_RETRY_DELAYS_MS.length) throw error;
1674
+ const waitMs = SQLITE_BUSY_RETRY_DELAYS_MS[attempt];
1675
+ console.error(
1676
+ `[memorix] ${label} SQLite busy (attempt ${attempt + 1}/${SQLITE_BUSY_RETRY_DELAYS_MS.length + 1}), retrying in ${waitMs}ms`
1677
+ );
1678
+ await delay(waitMs);
1679
+ }
1680
+ }
1681
+ }
1682
+ async function initializeSqliteStore(factory, dataDir, label) {
1683
+ try {
1684
+ loadBetterSqlite3();
1685
+ } catch (error) {
1686
+ throw new SqliteUnavailableError(error);
1687
+ }
1688
+ return retrySqliteBusy(async () => {
1689
+ const store = factory();
1690
+ await store.init(dataDir);
1691
+ return store;
1692
+ }, label);
1693
+ }
1694
+ var SQLITE_BUSY_RETRY_DELAYS_MS, SqliteUnavailableError;
1695
+ var init_sqlite_reliability = __esm({
1696
+ "src/store/sqlite-reliability.ts"() {
1697
+ "use strict";
1698
+ init_esm_shims();
1699
+ init_sqlite_db();
1700
+ SQLITE_BUSY_RETRY_DELAYS_MS = [50, 150, 400, 1e3, 2500];
1701
+ SqliteUnavailableError = class extends Error {
1702
+ code = "MEMORIX_SQLITE_UNAVAILABLE";
1703
+ constructor(cause) {
1704
+ super(
1705
+ cause instanceof Error && cause.message ? cause.message : "[memorix] SQLite is unavailable (install a supported SQLite runtime)"
1706
+ );
1707
+ this.name = "SqliteUnavailableError";
1708
+ }
1709
+ };
1710
+ }
1711
+ });
1712
+
1636
1713
  // src/store/file-lock.ts
1637
1714
  import { promises as fs2 } from "fs";
1638
1715
  import { randomUUID } from "crypto";
@@ -3556,10 +3633,10 @@ async function fetchWithRetry(url, apiKey, body, options = {}, attempt = 0, nati
3556
3633
  );
3557
3634
  }
3558
3635
  if (options.retry !== false && (response.status === 429 || response.status >= 500) && attempt < MAX_RETRIES2) {
3559
- const delay2 = BASE_DELAY_MS * Math.pow(2, attempt);
3636
+ const delay3 = BASE_DELAY_MS * Math.pow(2, attempt);
3560
3637
  const retryAfter = response.headers.get("retry-after");
3561
3638
  const parsedRetryAfter = retryAfter ? parseInt(retryAfter, 10) * 1e3 : NaN;
3562
- const waitMs = Number.isFinite(parsedRetryAfter) ? parsedRetryAfter : delay2;
3639
+ const waitMs = Number.isFinite(parsedRetryAfter) ? parsedRetryAfter : delay3;
3563
3640
  console.error(`[memorix] Embedding API ${response.status}, retry ${attempt + 1}/${MAX_RETRIES2} in ${waitMs}ms`);
3564
3641
  await waitForRetry(waitMs, options.signal);
3565
3642
  return fetchWithRetry(url, apiKey, body, options, attempt + 1, nativeGemini);
@@ -5591,6 +5668,7 @@ var init_sqlite_store = __esm({
5591
5668
  "use strict";
5592
5669
  init_esm_shims();
5593
5670
  init_sqlite_db();
5671
+ init_sqlite_reliability();
5594
5672
  init_sqlite_fts();
5595
5673
  SqliteBackend = class {
5596
5674
  db = null;
@@ -5868,7 +5946,9 @@ var init_sqlite_store = __esm({
5868
5946
  // ── Compound atomic operation ────────────────────────────────────
5869
5947
  async atomic(fn) {
5870
5948
  const run = this._atomicQueue.catch(() => void 0).then(async () => {
5871
- this.db.prepare("BEGIN IMMEDIATE").run();
5949
+ await retrySqliteBusy(async () => {
5950
+ this.db.prepare("BEGIN IMMEDIATE").run();
5951
+ }, "Observation atomic write");
5872
5952
  try {
5873
5953
  const tx = {
5874
5954
  loadAll: async () => this.rawLoadAll(),
@@ -5967,15 +6047,15 @@ function resetObservationStore() {
5967
6047
  async function createObservationStore(dataDir) {
5968
6048
  try {
5969
6049
  const { SqliteBackend: SqliteBackend2 } = await Promise.resolve().then(() => (init_sqlite_store(), sqlite_store_exports));
5970
- const store2 = new SqliteBackend2();
5971
- await store2.init(dataDir);
5972
- return store2;
6050
+ return await initializeSqliteStore(() => new SqliteBackend2(), dataDir, "Observation store");
5973
6051
  } catch (err) {
5974
- console.error(`[memorix] SQLite backend unavailable \u2014 degraded mode (read-only): ${err instanceof Error ? err.message : err}`);
6052
+ if (!isSqliteUnavailableError(err)) throw err;
6053
+ const reason = formatSqliteFailure(err);
6054
+ console.error(`[memorix] SQLite module unavailable \u2014 degraded mode (reads/writes disabled): ${reason}`);
6055
+ const degraded = new DegradedBackend(reason);
6056
+ await degraded.init(dataDir);
6057
+ return degraded;
5975
6058
  }
5976
- const store = new DegradedBackend();
5977
- await store.init(dataDir);
5978
- return store;
5979
6059
  }
5980
6060
  async function initObservationStore(dataDir) {
5981
6061
  if (_store2 && _storeDataDir === dataDir) {
@@ -5999,36 +6079,44 @@ var init_obs_store = __esm({
5999
6079
  "src/store/obs-store.ts"() {
6000
6080
  "use strict";
6001
6081
  init_esm_shims();
6082
+ init_sqlite_reliability();
6002
6083
  _store2 = null;
6003
6084
  _storeDataDir = null;
6004
6085
  DegradedBackend = class {
6086
+ constructor(reason = "SQLite runtime unavailable") {
6087
+ this.reason = reason;
6088
+ }
6089
+ reason;
6005
6090
  dataDir = "";
6091
+ unavailableRead() {
6092
+ return degradedReadError("observations");
6093
+ }
6006
6094
  async init(dataDir) {
6007
6095
  this.dataDir = dataDir;
6008
6096
  }
6009
6097
  async loadAll() {
6010
- return [];
6098
+ throw this.unavailableRead();
6011
6099
  }
6012
6100
  async loadByProject(_projectId, _options) {
6013
- return [];
6101
+ throw this.unavailableRead();
6014
6102
  }
6015
6103
  async countByProject(_projectId, _options) {
6016
- return 0;
6104
+ throw this.unavailableRead();
6017
6105
  }
6018
6106
  async getById(_id) {
6019
- return void 0;
6107
+ throw this.unavailableRead();
6020
6108
  }
6021
6109
  async loadIdCounter() {
6022
- return 1;
6110
+ throw this.unavailableRead();
6023
6111
  }
6024
6112
  async countAll() {
6025
- return 0;
6113
+ throw this.unavailableRead();
6026
6114
  }
6027
6115
  async listProjectIds() {
6028
- return [];
6116
+ throw this.unavailableRead();
6029
6117
  }
6030
6118
  async searchLexical(_options) {
6031
- return [];
6119
+ throw this.unavailableRead();
6032
6120
  }
6033
6121
  hasLexicalIndex() {
6034
6122
  return false;
@@ -6037,25 +6125,25 @@ var init_obs_store = __esm({
6037
6125
  return false;
6038
6126
  }
6039
6127
  async insert(_obs) {
6040
- throw new Error("[memorix] Cannot write observations: SQLite backend unavailable (degraded mode)");
6128
+ throw degradedWriteError("observations");
6041
6129
  }
6042
6130
  async update(_obs) {
6043
- throw new Error("[memorix] Cannot write observations: SQLite backend unavailable (degraded mode)");
6131
+ throw degradedWriteError("observations");
6044
6132
  }
6045
6133
  async remove(_id) {
6046
- throw new Error("[memorix] Cannot write observations: SQLite backend unavailable (degraded mode)");
6134
+ throw degradedWriteError("observations");
6047
6135
  }
6048
6136
  async bulkReplace(_obs) {
6049
- throw new Error("[memorix] Cannot write observations: SQLite backend unavailable (degraded mode)");
6137
+ throw degradedWriteError("observations");
6050
6138
  }
6051
6139
  async bulkRemoveByIds(_ids) {
6052
- throw new Error("[memorix] Cannot write observations: SQLite backend unavailable (degraded mode)");
6140
+ throw degradedWriteError("observations");
6053
6141
  }
6054
6142
  async saveIdCounter(_nextId) {
6055
- throw new Error("[memorix] Cannot write observations: SQLite backend unavailable (degraded mode)");
6143
+ throw degradedWriteError("observations");
6056
6144
  }
6057
6145
  async atomic(_fn) {
6058
- throw new Error("[memorix] Cannot write observations: SQLite backend unavailable (degraded mode)");
6146
+ throw degradedWriteError("observations");
6059
6147
  }
6060
6148
  async ensureFresh() {
6061
6149
  return false;
@@ -6068,6 +6156,9 @@ var init_obs_store = __esm({
6068
6156
  getBackendName() {
6069
6157
  return "degraded";
6070
6158
  }
6159
+ getBackendReason() {
6160
+ return this.reason;
6161
+ }
6071
6162
  };
6072
6163
  }
6073
6164
  });
@@ -57603,10 +57694,10 @@ var require_retry3 = __commonJS({
57603
57694
  if (!await shouldRetryFn(err)) {
57604
57695
  return { shouldRetry: false, config: err.config };
57605
57696
  }
57606
- const delay2 = getNextRetryDelay(config);
57697
+ const delay3 = getNextRetryDelay(config);
57607
57698
  err.config.retryConfig.currentRetryAttempt += 1;
57608
- const backoff = config.retryBackoff ? config.retryBackoff(err, delay2) : new Promise((resolve2) => {
57609
- setTimeout(resolve2, delay2);
57699
+ const backoff = config.retryBackoff ? config.retryBackoff(err, delay3) : new Promise((resolve2) => {
57700
+ setTimeout(resolve2, delay3);
57610
57701
  });
57611
57702
  if (config.onRetryAttempt) {
57612
57703
  await config.onRetryAttempt(err);
@@ -99392,7 +99483,7 @@ async function retryBackoff(fn, strategy) {
99392
99483
  retryInterval = initialInterval * Math.pow(x2, exponent) + Math.random() * 1e3;
99393
99484
  }
99394
99485
  const d = Math.min(retryInterval, maxInterval);
99395
- await delay(d);
99486
+ await delay2(d);
99396
99487
  x2++;
99397
99488
  }
99398
99489
  }
@@ -99413,8 +99504,8 @@ function retryIntervalFromResponse(res) {
99413
99504
  }
99414
99505
  return 0;
99415
99506
  }
99416
- async function delay(delay2) {
99417
- return new Promise((resolve2) => setTimeout(resolve2, delay2));
99507
+ async function delay2(delay3) {
99508
+ return new Promise((resolve2) => setTimeout(resolve2, delay3));
99418
99509
  }
99419
99510
  var defaultBackoff, PermanentError, TemporaryError, codeRangeRE2;
99420
99511
  var init_retries = __esm({
@@ -145691,7 +145782,20 @@ function normalizeMiniMaxVideoRequest(input) {
145691
145782
  if (ratio !== "adaptive" && ratio !== "16:9" && ratio !== "9:16" && ratio !== "1:1") {
145692
145783
  throw new Error("MiniMax video ratio must be adaptive, 16:9, 9:16, or 1:1");
145693
145784
  }
145694
- return { region, model, prompt, resolution, duration, ratio };
145785
+ let firstFrameImageUrl;
145786
+ if (input.firstFrameImageUrl !== void 0) {
145787
+ firstFrameImageUrl = input.firstFrameImageUrl.trim();
145788
+ let parsed;
145789
+ try {
145790
+ parsed = new URL(firstFrameImageUrl);
145791
+ } catch {
145792
+ throw new Error("MiniMax first-frame image must be a public HTTPS URL");
145793
+ }
145794
+ if (parsed.protocol !== "https:" || parsed.username || parsed.password || sanitizeCredentials(firstFrameImageUrl) !== firstFrameImageUrl) {
145795
+ throw new Error("MiniMax first-frame image must be an HTTPS URL without credentials");
145796
+ }
145797
+ }
145798
+ return { region, model, prompt, resolution, duration, ratio, ...firstFrameImageUrl ? { firstFrameImageUrl } : {} };
145695
145799
  }
145696
145800
  function videoRequestConfig(input) {
145697
145801
  const normalized = normalizeMiniMaxVideoRequest(input);
@@ -145768,7 +145872,14 @@ async function createMiniMaxVideoTask(input, dependencies = {}) {
145768
145872
  headers: videoHeaders(config.apiKey),
145769
145873
  body: JSON.stringify({
145770
145874
  model: config.model,
145771
- content: [{ type: "text", text: config.prompt }],
145875
+ content: [
145876
+ { type: "text", text: config.prompt },
145877
+ ...config.firstFrameImageUrl ? [{
145878
+ type: "image_url",
145879
+ image_url: { url: config.firstFrameImageUrl },
145880
+ role: "first_frame"
145881
+ }] : []
145882
+ ],
145772
145883
  resolution: config.resolution,
145773
145884
  duration: config.duration,
145774
145885
  ratio: config.ratio
@@ -145929,10 +146040,19 @@ function parseStoredRequest(value) {
145929
146040
  const duration = value.duration;
145930
146041
  const ratio = value.ratio;
145931
146042
  const maxBytes = value.maxBytes;
145932
- 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) {
146043
+ 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) {
145933
146044
  throw new Error("Media video job has an invalid request payload");
145934
146045
  }
145935
- return { provider: provider2, region, model, prompt, resolution, duration, ratio, maxBytes };
146046
+ const normalized = normalizeMiniMaxVideoRequest({
146047
+ region,
146048
+ model,
146049
+ prompt,
146050
+ resolution,
146051
+ duration,
146052
+ ratio,
146053
+ firstFrameImageUrl: value.firstFrameImageUrl
146054
+ });
146055
+ return { provider: provider2, ...normalized, maxBytes };
145936
146056
  }
145937
146057
  function mediaJobIdFromMaintenance(job) {
145938
146058
  const mediaJobId = job.payload.mediaJobId;
@@ -145944,6 +146064,7 @@ function mediaJobIdFromMaintenance(job) {
145944
146064
  function toVideoRequest(request) {
145945
146065
  return {
145946
146066
  prompt: request.prompt,
146067
+ firstFrameImageUrl: request.firstFrameImageUrl,
145947
146068
  model: request.model,
145948
146069
  region: request.region,
145949
146070
  resolution: request.resolution,
@@ -146035,6 +146156,7 @@ function queueMiniMaxVideoGeneration(input) {
146035
146156
  region: normalized.region,
146036
146157
  model: normalized.model,
146037
146158
  prompt: normalized.prompt,
146159
+ ...normalized.firstFrameImageUrl ? { firstFrameImageUrl: normalized.firstFrameImageUrl } : {},
146038
146160
  resolution: normalized.resolution,
146039
146161
  duration: normalized.duration,
146040
146162
  ratio: normalized.ratio,
@@ -792,6 +792,21 @@ not a memory-loss condition. If Memorix instead reports that SQLite is
792
792
  unavailable, confirm the installed Node version satisfies the package engine
793
793
  and include the exact error when filing an issue.
794
794
 
795
+ ## 10. Does `doctor` report degraded or unavailable storage?
796
+
797
+ Treat this as a real storage failure, not as an empty project. The active
798
+ SQLite backend is the source of truth for observations, sessions, and
799
+ mini-skills; derived search indexes may fall back, but they must not hide a
800
+ database failure.
801
+
802
+ Run `memorix doctor --json` and keep the reported error. A transient
803
+ cross-process `SQLITE_BUSY` is retried automatically. A persistent runtime
804
+ error is surfaced instead of returning empty memory. Stop or pause a long
805
+ maintenance operation and retry after the database is writable. For several
806
+ agent harnesses sharing one local store, prefer one `memorix background start`
807
+ control plane so writes are serialized by one owner; WAL does not allow
808
+ multiple simultaneous writers.
809
+
795
810
  ---
796
811
 
797
812
  ## 11. What Not to Do
@@ -150,6 +150,29 @@ Responsibilities:
150
150
  - curate long-lived episodes, facts, and procedures with source evidence,
151
151
  qualification, approval, archival, and supersession
152
152
 
153
+ ### Persistence Contract
154
+
155
+ SQLite is the canonical local record for observations, sessions, mini-skills,
156
+ knowledge, coordination, and CodeGraph metadata. Search indexes and in-memory
157
+ structures are derived views and may be rebuilt; they must never decide that a
158
+ failed database is an empty project.
159
+
160
+ All SQLite-backed stores share the same startup semantics:
161
+
162
+ 1. A missing SQLite runtime is reported as an explicit degraded backend.
163
+ 2. Cross-process `SQLITE_BUSY` contention is retried with bounded backoff.
164
+ 3. Persistent runtime failures are thrown and surfaced to the operator.
165
+ 4. Degraded business reads and writes fail clearly instead of returning empty
166
+ data or a fake success.
167
+ 5. A connection that fails during schema/index initialization is closed before
168
+ a retry, so a failed startup cannot retain a file lock.
169
+
170
+ WAL improves reader/writer overlap but still permits one writer per database
171
+ file. Long maintenance work is therefore isolated and paged, while write
172
+ transactions stay short. Multi-agent installations should prefer one local
173
+ HTTP control plane as the database owner; stdio remains useful for a single
174
+ agent or a deliberately isolated project store.
175
+
153
176
  ### Intelligence and Quality Layer
154
177
 
155
178
  This layer improves memory quality and retrieval quality.
@@ -13,7 +13,7 @@ Memorix is a TypeScript project built around:
13
13
 
14
14
  ## Current Development Baseline
15
15
 
16
- The current release line is **1.9.1** and package metadata is kept synchronized
16
+ The current release line is **1.9.4** and package metadata is kept synchronized
17
17
  with the root package and MCP Registry manifest.
18
18
 
19
19
  Contributors should assume the following areas are part of the current release line:
@@ -43,6 +43,34 @@ user is allowed to remember.
43
43
  memory, and the V8 heap limit so a real saturation report can distinguish
44
44
  JavaScript retention from native/model memory.
45
45
 
46
+ ## SQLite Storage Reliability Contract
47
+
48
+ SQLite is the durable source of truth. FTS5, Orama, vector indexes, and other
49
+ search structures are derived working indexes; an index failure must never turn
50
+ the durable store into an empty project.
51
+
52
+ - A missing SQLite runtime may use the explicit read-only degraded backend.
53
+ - A runtime lock (`SQLITE_BUSY`) is retried with bounded backoff. A persistent
54
+ runtime error is surfaced to the caller; it is never converted into a fake
55
+ successful read or write.
56
+ - Observations, sessions, and mini-skills follow the same initialization rule.
57
+ Degraded writes fail loudly so an agent cannot believe a session or promotion
58
+ was saved when it was not.
59
+ - SQLite connections configure `busy_timeout` before WAL setup. A connection
60
+ that fails during schema or index initialization is closed before retrying,
61
+ so a failed startup cannot leave an orphan lock behind.
62
+ - WAL permits concurrent readers but still has one writer. Maintenance and
63
+ migration work must use short, bounded transactions and must not perform
64
+ network or LLM work while holding a write transaction. For several agent
65
+ harnesses, prefer one local `serve-http` control plane so writes are queued
66
+ by one owner instead of many stdio processes competing for one file.
67
+ - `memorix doctor` reports the active backend. It reports read-only or
68
+ unavailable storage as an error and never presents the backend's synthetic
69
+ zero count as an empty project.
70
+
71
+ This is an integrity boundary, not a corpus quota. The product does not lower
72
+ the number of durable memories to hide lock contention or resource problems.
73
+
46
74
  ## Retrieval Profiles
47
75
 
48
76
  | Profile | Default? | Network work | Best fit |
@@ -163,6 +191,28 @@ Results are machine-specific and should be compared on the same OS and Node
163
191
  version. The release gate is an integrity and regression check, not a public
164
192
  latency guarantee.
165
193
 
194
+ ### 1.9.3 Release Evidence (2026-09-14)
195
+
196
+ The 40,000-record gate passed on Windows with Node `v22.22.3`, SQLite-backed
197
+ storage, and embeddings disabled:
198
+
199
+ | Measurement | Result |
200
+ | --- | ---: |
201
+ | Seed and index | 48.9 s |
202
+ | Steady-state write p50 / p95 | 0.78 / 1.49 ms |
203
+ | SDK lexical search | 22.8 ms |
204
+ | HTTP ready | 1.87 s |
205
+ | MCP initialize / bind | 116 / 229 ms |
206
+ | MCP context / search / store | 605 / 322 / 595 ms |
207
+ | Hook write | 3.36 s |
208
+ | SDK reopen | 134 ms |
209
+ | Service peak RSS | 412.7 MiB |
210
+ | Durable records after MCP and hook writes | 40,002 |
211
+
212
+ The planted record was searchable, the MCP and hook writes were durable, and
213
+ the reopened SDK observed the project-visible records. These values are local
214
+ acceptance evidence, not cross-machine performance promises.
215
+
166
216
  ## 1.9.1 Large-Scale Retrieval Contract
167
217
 
168
218
  The 1.9.1 line improves retrieval latency without shrinking the user's durable
package/docs/README.md CHANGED
@@ -125,7 +125,7 @@ Historical/deep-reference documents may describe older designs. If they conflict
125
125
 
126
126
  ## Current Product Line
127
127
 
128
- The current product line is **1.9.1**. Multi-device store sync is opt-in and
128
+ The current product line is **1.9.4**. Multi-device store sync is opt-in and
129
129
  keeps local SQLite canonical; existing users do not upload anything until they
130
130
  configure a relay provider explicitly.
131
131
  The authoritative acceptance contract for the current implementation is
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "memorix",
3
3
  "mcpName": "io.github.AVIDS2/memorix",
4
- "version": "1.9.2",
4
+ "version": "1.9.4",
5
5
  "description": "Local-first shared memory layer for AI coding agents across MCP clients, Git history, reasoning context, and project sessions.",
6
6
  "type": "module",
7
7
  "sideEffects": false,
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "name": "memorix",
3
- "version": "1.9.2"
3
+ "version": "1.9.4"
4
4
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://anthropic.com/claude-code/plugin.schema.json",
3
3
  "name": "memorix",
4
- "version": "1.9.2",
4
+ "version": "1.9.4",
5
5
  "description": "Shared workspace memory for Claude Code and other AI coding agents.",
6
6
  "author": {
7
7
  "name": "AVIDS2",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix",
3
- "version": "1.9.2",
3
+ "version": "1.9.4",
4
4
  "description": "Local-first project memory for CodeBuddy Code and other coding agents.",
5
5
  "author": {
6
6
  "name": "AVIDS2",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix",
3
- "version": "1.9.2",
3
+ "version": "1.9.4",
4
4
  "description": "Shared workspace memory for Codex and other AI coding agents.",
5
5
  "author": {
6
6
  "name": "AVIDS2",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix",
3
- "version": "1.9.2",
3
+ "version": "1.9.4",
4
4
  "description": "Shared local workspace memory for GitHub Copilot CLI and other AI coding agents.",
5
5
  "author": {
6
6
  "name": "AVIDS2",