memorix 1.9.9 → 1.9.11

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 (54) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +2 -0
  3. package/README.zh-CN.md +2 -0
  4. package/dist/cli/index.js +855 -231
  5. package/dist/index.js +284 -77
  6. package/dist/maintenance-runner.js +74 -20
  7. package/dist/media-audio-runner.js +74 -20
  8. package/dist/media-video-runner.js +74 -20
  9. package/dist/memcode/index.js +1532 -1002
  10. package/dist/memcode-runtime/CHANGELOG.md +42 -0
  11. package/dist/memcode-runtime/package.json +4 -4
  12. package/dist/sdk.js +283 -76
  13. package/dist/vector-backfill-runner.js +74 -20
  14. package/docs/CONFIGURATION.md +26 -0
  15. package/docs/DEVELOPMENT.md +3 -2
  16. package/docs/INTEGRATIONS.md +3 -1
  17. package/docs/README.md +2 -1
  18. package/docs/SETUP.md +1 -1
  19. package/docs/hooks-architecture.md +5 -0
  20. package/package.json +18 -11
  21. package/plugins/antigravity/memorix/plugin.json +1 -1
  22. package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
  23. package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
  24. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  25. package/plugins/copilot/memorix/plugin.json +1 -1
  26. package/plugins/gemini/memorix/gemini-extension.json +1 -1
  27. package/plugins/hermes/memorix/plugin.yaml +1 -1
  28. package/plugins/omp/memorix/package.json +1 -1
  29. package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
  30. package/plugins/pi/memorix/package.json +1 -1
  31. package/server.json +2 -2
  32. package/src/cli/commands/hooks-install.ts +2 -0
  33. package/src/cli/commands/init.ts +6 -2
  34. package/src/cli/commands/integrate.ts +2 -0
  35. package/src/cli/commands/setup.ts +1 -1
  36. package/src/cli/index.ts +2 -2
  37. package/src/cli/tui/ConfigureView.tsx +2 -0
  38. package/src/config/dotenv-loader.ts +1 -0
  39. package/src/config/resolved-config.ts +7 -3
  40. package/src/config/yaml-loader.ts +2 -2
  41. package/src/dashboard/server.ts +3 -0
  42. package/src/hooks/installers/index.ts +219 -31
  43. package/src/hooks/lifecycle.ts +4 -9
  44. package/src/hooks/normalizer.ts +6 -1
  45. package/src/hooks/types.ts +3 -1
  46. package/src/llm/provider.ts +2 -1
  47. package/src/memory/observations.ts +11 -0
  48. package/src/memory/session.ts +4 -13
  49. package/src/memory/visibility.ts +8 -2
  50. package/src/multimodal/image-loader.ts +6 -5
  51. package/src/server.ts +8 -4
  52. package/src/store/obs-store.ts +3 -0
  53. package/src/store/orama-store.ts +43 -6
  54. package/src/store/sqlite-store.ts +15 -0
@@ -2111,15 +2111,16 @@ function getResolvedConfig(options = {}) {
2111
2111
  const openRouterMemoryLlmApiKey = isOpenRouterMemoryLane(memoryLlmProvider, memoryLlmBaseUrl) ? process.env.OPENROUTER_API_KEY : void 0;
2112
2112
  const isAtlasCloud = memoryLlmProvider === "atlascloud";
2113
2113
  const isRequesty = memoryLlmProvider === "requesty";
2114
- const usesPresetKeyOnly = isAtlasCloud || isRequesty;
2114
+ const isApiRoute = memoryLlmProvider === "apiroute";
2115
+ const usesPresetKeyOnly = isAtlasCloud || isRequesty || isApiRoute;
2115
2116
  const memoryLlmApiKey = first(
2116
2117
  process.env.MEMORIX_LLM_API_KEY,
2117
2118
  process.env.MEMORIX_API_KEY,
2118
2119
  toml.memory?.llm?.api_key,
2119
2120
  yaml.llm?.apiKey,
2120
2121
  legacy.llm?.apiKey,
2121
- // Never borrow another provider's credentials for the Atlas Cloud or Requesty presets.
2122
- isAtlasCloud ? process.env.ATLASCLOUD_API_KEY : isRequesty ? process.env.REQUESTY_API_KEY : process.env.OPENAI_API_KEY,
2122
+ // Never borrow another provider's credentials for these gateway presets.
2123
+ isAtlasCloud ? process.env.ATLASCLOUD_API_KEY : isRequesty ? process.env.REQUESTY_API_KEY : isApiRoute ? process.env.API_ROUTE_API_KEY : process.env.OPENAI_API_KEY,
2123
2124
  usesPresetKeyOnly ? void 0 : process.env.ANTHROPIC_API_KEY,
2124
2125
  usesPresetKeyOnly ? void 0 : openRouterMemoryLlmApiKey
2125
2126
  );
@@ -2302,6 +2303,7 @@ function getEnvSourceNames() {
2302
2303
  "MEMORIX_CODEGRAPH_EXTERNAL_TIMEOUT_MS",
2303
2304
  "OPENROUTER_API_KEY",
2304
2305
  "REQUESTY_API_KEY",
2306
+ "API_ROUTE_API_KEY",
2305
2307
  "ATLASCLOUD_API_KEY"
2306
2308
  ].filter((name) => process.env[name]);
2307
2309
  }
@@ -3146,6 +3148,7 @@ var init_provider = __esm({
3146
3148
  anthropic: { baseUrl: "https://api.anthropic.com/v1", model: "claude-3-5-haiku-latest" },
3147
3149
  openrouter: { baseUrl: "https://openrouter.ai/api/v1", model: "openai/gpt-4.1-nano" },
3148
3150
  requesty: { baseUrl: "https://router.requesty.ai/v1", model: "openai/gpt-4.1-nano" },
3151
+ apiroute: { baseUrl: "https://global.api-route.com/v1", model: "deepseek-v4.1-flash" },
3149
3152
  atlascloud: { baseUrl: "https://api.atlascloud.ai/v1", model: "deepseek-ai/deepseek-v3.2" },
3150
3153
  custom: { baseUrl: "http://localhost:11434/v1", model: "llama3" }
3151
3154
  };
@@ -3583,6 +3586,19 @@ var init_sqlite_store = __esm({
3583
3586
  async listProjectIds() {
3584
3587
  return this.db.prepare("SELECT DISTINCT projectId FROM observations ORDER BY projectId").all().map((row) => row.projectId).filter((projectId) => Boolean(projectId));
3585
3588
  }
3589
+ async migrateProjectIds(aliasIds, canonicalId) {
3590
+ const sourceIds = [...new Set(aliasIds.filter((projectId) => projectId && projectId !== canonicalId))];
3591
+ if (sourceIds.length === 0) return 0;
3592
+ return retrySqliteBusy(async () => {
3593
+ const placeholders = sourceIds.map(() => "?").join(", ");
3594
+ const result = this.db.prepare(
3595
+ `UPDATE observations SET projectId = ? WHERE projectId IN (${placeholders})`
3596
+ ).run(canonicalId, ...sourceIds);
3597
+ const migrated = Number(result.changes ?? 0);
3598
+ if (migrated > 0) this.bumpGeneration();
3599
+ return migrated;
3600
+ }, "Migrate observation project IDs");
3601
+ }
3586
3602
  async searchLexical(options) {
3587
3603
  return searchObservationLexically(this.db, options).map((hit) => ({
3588
3604
  observation: rowToObs(hit.row),
@@ -4012,9 +4028,9 @@ function sharedAgentIds(record80) {
4012
4028
  return [];
4013
4029
  }
4014
4030
  }
4015
- function canReadObservation(record80, reader) {
4031
+ function canReadObservation(record80, reader, allowedProjectIds) {
4016
4032
  if (!reader) return true;
4017
- const sameProject = reader.projectId === record80.projectId;
4033
+ const sameProject = allowedProjectIds ? allowedProjectIds.includes(record80.projectId) : reader.projectId === record80.projectId;
4018
4034
  const visibility = resolveObservationVisibility(record80);
4019
4035
  if (visibility === "project") return !reader.projectId || sameProject;
4020
4036
  if (!sameProject || !reader.agentId) return false;
@@ -7129,6 +7145,7 @@ var orama_store_exports = {};
7129
7145
  __export(orama_store_exports, {
7130
7146
  _classifyQueryTier: () => classifyQueryTier,
7131
7147
  batchGenerateEmbeddings: () => batchGenerateEmbeddings,
7148
+ createSearchTokenizer: () => createSearchTokenizer,
7132
7149
  generateEmbedding: () => generateEmbedding,
7133
7150
  generateEmbeddingInput: () => generateEmbeddingInput,
7134
7151
  getActiveSemanticIndexProfile: () => getActiveSemanticIndexProfile,
@@ -7151,7 +7168,7 @@ __export(orama_store_exports, {
7151
7168
  searchObservations: () => searchObservations,
7152
7169
  updateObservationMetadata: () => updateObservationMetadata
7153
7170
  });
7154
- import { create, insert as insert2, insertMultiple, search, remove as remove2, update, count, getByID } from "@orama/orama";
7171
+ import { components, create, insert as insert2, insertMultiple, search, remove as remove2, update, count, getByID } from "@orama/orama";
7155
7172
  function rememberSearchMode(key, value) {
7156
7173
  if (!lastSearchModeByProject.has(key) && lastSearchModeByProject.size >= MAX_SEARCH_MODE_KEYS) {
7157
7174
  const oldest = lastSearchModeByProject.keys().next().value;
@@ -7170,6 +7187,26 @@ function getOptionalObservationStore() {
7170
7187
  return null;
7171
7188
  }
7172
7189
  }
7190
+ function createSearchTokenizer() {
7191
+ const defaultTokenizer = components.tokenizer.createTokenizer({ language: "english" });
7192
+ return {
7193
+ language: defaultTokenizer.language,
7194
+ normalizationCache: defaultTokenizer.normalizationCache,
7195
+ tokenize(raw, language, prop, withCache = true) {
7196
+ const defaultTokens = defaultTokenizer.tokenize(raw, language, prop, withCache);
7197
+ const cjkTokens = [];
7198
+ const normalized = raw.normalize("NFKC").toLowerCase();
7199
+ for (const match2 of normalized.matchAll(CJK_RUN)) {
7200
+ const characters = Array.from(match2[0]);
7201
+ cjkTokens.push(...characters);
7202
+ for (let index = 0; index + 1 < characters.length; index++) {
7203
+ cjkTokens.push(`${characters[index]}${characters[index + 1]}`);
7204
+ }
7205
+ }
7206
+ return [.../* @__PURE__ */ new Set([...defaultTokens, ...cjkTokens])];
7207
+ }
7208
+ };
7209
+ }
7173
7210
  async function shouldUsePersistentSearch(projectIds, projectId) {
7174
7211
  const store = getOptionalObservationStore();
7175
7212
  if (!store?.hasLexicalIndex?.()) return false;
@@ -7270,6 +7307,7 @@ async function searchPersistentSemantically(options, projectIds, limit2, queryVe
7270
7307
  const lexical = await searchPersistentLexically(options, projectIds, limit2);
7271
7308
  const store = getObservationStore();
7272
7309
  const documents = /* @__PURE__ */ new Map();
7310
+ const vectorDocumentKeys = /* @__PURE__ */ new Map();
7273
7311
  const addObservation = (observation) => {
7274
7312
  const document2 = observationToSearchDocument(observation);
7275
7313
  documents.set(makeEntryKey(document2.projectId, document2.observationId), document2);
@@ -7285,6 +7323,7 @@ async function searchPersistentSemantically(options, projectIds, limit2, queryVe
7285
7323
  if (options.type && observation.type !== options.type) return null;
7286
7324
  if (options.source && observation.source !== options.source) return null;
7287
7325
  addObservation(observation);
7326
+ vectorDocumentKeys.set(hit.observationId, makeEntryKey(observation.projectId, observation.id));
7288
7327
  return observation;
7289
7328
  }));
7290
7329
  const RRF_K = 60;
@@ -7294,7 +7333,7 @@ async function searchPersistentSemantically(options, projectIds, limit2, queryVe
7294
7333
  scores.set(key, (scores.get(key) ?? 0) + 0.6 / (RRF_K + index + 1));
7295
7334
  }
7296
7335
  for (const [index, hit] of vectorHits.entries()) {
7297
- const key = makeEntryKey(hit.projectId, hit.observationId);
7336
+ const key = vectorDocumentKeys.get(hit.observationId) ?? makeEntryKey(hit.projectId, hit.observationId);
7298
7337
  if (!documents.has(key)) continue;
7299
7338
  scores.set(key, (scores.get(key) ?? 0) + 0.4 / (RRF_K + index + 1));
7300
7339
  }
@@ -7307,12 +7346,13 @@ async function searchPersistentSemantically(options, projectIds, limit2, queryVe
7307
7346
  return {
7308
7347
  count: vectorHits.length,
7309
7348
  hits: vectorHits.map((hit, index) => {
7310
- const key = makeEntryKey(hit.projectId, hit.observationId);
7349
+ const key = vectorDocumentKeys.get(hit.observationId) ?? makeEntryKey(hit.projectId, hit.observationId);
7311
7350
  const document2 = documents.get(key);
7312
7351
  return document2 ? { id: document2.id, score: 0.4 / (RRF_K + index + 1), document: document2 } : null;
7313
7352
  }).filter((hit) => hit !== null).slice(0, limit2)
7314
7353
  };
7315
7354
  }
7355
+ if (hits.length === 0) return null;
7316
7356
  return { count: hits.length, hits };
7317
7357
  } catch {
7318
7358
  return null;
@@ -7387,7 +7427,10 @@ async function initializeDb(options, generation) {
7387
7427
  };
7388
7428
  const dims = nextEmbeddingDimensions ?? 384;
7389
7429
  const schema = nextEmbeddingEnabled ? { ...baseSchema, embedding: `vector[${dims}]` } : baseSchema;
7390
- const nextDb = await create({ schema });
7430
+ const nextDb = await create({
7431
+ schema,
7432
+ components: { tokenizer: createSearchTokenizer() }
7433
+ });
7391
7434
  if (generation !== dbGeneration) {
7392
7435
  return getDb(options);
7393
7436
  }
@@ -7821,7 +7864,7 @@ async function searchObservations(options) {
7821
7864
  if (!projectIds) return true;
7822
7865
  const doc = hit.document;
7823
7866
  return projectIds.includes(doc.projectId);
7824
- }).filter((hit) => canReadObservation(hit.document, options.reader)).filter((hit) => {
7867
+ }).filter((hit) => canReadObservation(hit.document, options.reader, projectIds ?? void 0)).filter((hit) => {
7825
7868
  if (statusFilter === "all") return true;
7826
7869
  const doc = hit.document;
7827
7870
  return (doc.status || "active") === statusFilter;
@@ -8087,7 +8130,7 @@ async function searchObservations(options) {
8087
8130
  let entries = intermediate.map(({ rawTime: _, _isCommandLog: _c, ...rest }) => rest);
8088
8131
  for (const hit of results.hits) {
8089
8132
  const doc = hit.document;
8090
- if (canReadObservation(doc, options.reader)) rememberObservationDoc(doc);
8133
+ if (canReadObservation(doc, options.reader, projectIds ?? void 0)) rememberObservationDoc(doc);
8091
8134
  }
8092
8135
  if (hasQuery && originalQuery) {
8093
8136
  const queryLower = originalQuery.toLowerCase();
@@ -8245,7 +8288,7 @@ function formatTime(isoDate) {
8245
8288
  return isoDate;
8246
8289
  }
8247
8290
  }
8248
- var db, dbInitPromise, dbGeneration, embeddingEnabled, embeddingDimensions, indexEmbeddingProvider, deferredCachedVectorHydration, NON_CJK_HYBRID_SIMILARITY, lastSearchModeByProject, SEARCH_MODE_DEFAULT_KEY, MAX_SEARCH_MODE_KEYS, COMMAND_LOG_TITLE, COMMAND_STYLE_TITLE, COMMAND_LIKE_QUERY2, COMMAND_INTENT_QUERY, NATURAL_LANGUAGE_MARKERS;
8291
+ var db, dbInitPromise, dbGeneration, embeddingEnabled, embeddingDimensions, indexEmbeddingProvider, deferredCachedVectorHydration, NON_CJK_HYBRID_SIMILARITY, CJK_RUN, lastSearchModeByProject, SEARCH_MODE_DEFAULT_KEY, MAX_SEARCH_MODE_KEYS, COMMAND_LOG_TITLE, COMMAND_STYLE_TITLE, COMMAND_LIKE_QUERY2, COMMAND_INTENT_QUERY, NATURAL_LANGUAGE_MARKERS;
8249
8292
  var init_orama_store = __esm({
8250
8293
  "src/store/orama-store.ts"() {
8251
8294
  "use strict";
@@ -8269,6 +8312,7 @@ var init_orama_store = __esm({
8269
8312
  indexEmbeddingProvider = null;
8270
8313
  deferredCachedVectorHydration = null;
8271
8314
  NON_CJK_HYBRID_SIMILARITY = 0.45;
8315
+ CJK_RUN = /[\u3400-\u4dbf\u4e00-\u9fff\u3040-\u30ff\uac00-\ud7af]+/gu;
8272
8316
  lastSearchModeByProject = /* @__PURE__ */ new Map();
8273
8317
  SEARCH_MODE_DEFAULT_KEY = "__global__";
8274
8318
  MAX_SEARCH_MODE_KEYS = 4096;
@@ -71849,16 +71893,16 @@ var require_pluggable_auth_handler = __commonJS({
71849
71893
  * spaces are between quotation marks.
71850
71894
  */
71851
71895
  static parseCommand(command) {
71852
- const components = command.match(/(?:[^\s"]+|"[^"]*")+/g);
71853
- if (!components) {
71896
+ const components2 = command.match(/(?:[^\s"]+|"[^"]*")+/g);
71897
+ if (!components2) {
71854
71898
  throw new Error(`Provided command: "${command}" could not be parsed.`);
71855
71899
  }
71856
- for (let i2 = 0; i2 < components.length; i2++) {
71857
- if (components[i2][0] === '"' && components[i2].slice(-1) === '"') {
71858
- components[i2] = components[i2].slice(1, -1);
71900
+ for (let i2 = 0; i2 < components2.length; i2++) {
71901
+ if (components2[i2][0] === '"' && components2[i2].slice(-1) === '"') {
71902
+ components2[i2] = components2[i2].slice(1, -1);
71859
71903
  }
71860
71904
  }
71861
- return components;
71905
+ return components2;
71862
71906
  }
71863
71907
  };
71864
71908
  exports.PluggableAuthHandler = PluggableAuthHandler;
@@ -151984,6 +152028,16 @@ function getProjectObservations(projectId) {
151984
152028
  async function migrateProjectIds(aliasIds, canonicalId) {
151985
152029
  const nonCanonical = new Set(aliasIds.filter((id) => id !== canonicalId));
151986
152030
  if (nonCanonical.size === 0) return 0;
152031
+ const store = projectDir ? getObservationStore() : null;
152032
+ if (store?.migrateProjectIds) {
152033
+ const migrated2 = await store.migrateProjectIds(aliasIds, canonicalId);
152034
+ if (migrated2 > 0 && corpusLoaded) {
152035
+ for (const observation of observations) {
152036
+ if (nonCanonical.has(observation.projectId)) observation.projectId = canonicalId;
152037
+ }
152038
+ }
152039
+ return migrated2;
152040
+ }
151987
152041
  let migrated = 0;
151988
152042
  const changed = [];
151989
152043
  for (const obs of observations) {
@@ -151994,8 +152048,8 @@ async function migrateProjectIds(aliasIds, canonicalId) {
151994
152048
  }
151995
152049
  }
151996
152050
  if (migrated > 0 && projectDir) {
151997
- const store = getObservationStore();
151998
- await store.atomic(async (tx) => {
152051
+ const store2 = getObservationStore();
152052
+ await store2.atomic(async (tx) => {
151999
152053
  await Promise.all(changed.map((observation) => tx.update(observation)));
152000
152054
  });
152001
152055
  }
@@ -3026,9 +3026,9 @@ function sharedAgentIds(record80) {
3026
3026
  return [];
3027
3027
  }
3028
3028
  }
3029
- function canReadObservation(record80, reader) {
3029
+ function canReadObservation(record80, reader, allowedProjectIds) {
3030
3030
  if (!reader) return true;
3031
- const sameProject = reader.projectId === record80.projectId;
3031
+ const sameProject = allowedProjectIds ? allowedProjectIds.includes(record80.projectId) : reader.projectId === record80.projectId;
3032
3032
  const visibility = resolveObservationVisibility(record80);
3033
3033
  if (visibility === "project") return !reader.projectId || sameProject;
3034
3034
  if (!sameProject || !reader.agentId) return false;
@@ -3394,15 +3394,16 @@ function getResolvedConfig(options = {}) {
3394
3394
  const openRouterMemoryLlmApiKey = isOpenRouterMemoryLane(memoryLlmProvider, memoryLlmBaseUrl) ? process.env.OPENROUTER_API_KEY : void 0;
3395
3395
  const isAtlasCloud = memoryLlmProvider === "atlascloud";
3396
3396
  const isRequesty = memoryLlmProvider === "requesty";
3397
- const usesPresetKeyOnly = isAtlasCloud || isRequesty;
3397
+ const isApiRoute = memoryLlmProvider === "apiroute";
3398
+ const usesPresetKeyOnly = isAtlasCloud || isRequesty || isApiRoute;
3398
3399
  const memoryLlmApiKey = first(
3399
3400
  process.env.MEMORIX_LLM_API_KEY,
3400
3401
  process.env.MEMORIX_API_KEY,
3401
3402
  toml.memory?.llm?.api_key,
3402
3403
  yaml.llm?.apiKey,
3403
3404
  legacy.llm?.apiKey,
3404
- // Never borrow another provider's credentials for the Atlas Cloud or Requesty presets.
3405
- isAtlasCloud ? process.env.ATLASCLOUD_API_KEY : isRequesty ? process.env.REQUESTY_API_KEY : process.env.OPENAI_API_KEY,
3405
+ // Never borrow another provider's credentials for these gateway presets.
3406
+ isAtlasCloud ? process.env.ATLASCLOUD_API_KEY : isRequesty ? process.env.REQUESTY_API_KEY : isApiRoute ? process.env.API_ROUTE_API_KEY : process.env.OPENAI_API_KEY,
3406
3407
  usesPresetKeyOnly ? void 0 : process.env.ANTHROPIC_API_KEY,
3407
3408
  usesPresetKeyOnly ? void 0 : openRouterMemoryLlmApiKey
3408
3409
  );
@@ -3585,6 +3586,7 @@ function getEnvSourceNames() {
3585
3586
  "MEMORIX_CODEGRAPH_EXTERNAL_TIMEOUT_MS",
3586
3587
  "OPENROUTER_API_KEY",
3587
3588
  "REQUESTY_API_KEY",
3589
+ "API_ROUTE_API_KEY",
3588
3590
  "ATLASCLOUD_API_KEY"
3589
3591
  ].filter((name) => process.env[name]);
3590
3592
  }
@@ -6578,6 +6580,7 @@ var init_provider2 = __esm({
6578
6580
  anthropic: { baseUrl: "https://api.anthropic.com/v1", model: "claude-3-5-haiku-latest" },
6579
6581
  openrouter: { baseUrl: "https://openrouter.ai/api/v1", model: "openai/gpt-4.1-nano" },
6580
6582
  requesty: { baseUrl: "https://router.requesty.ai/v1", model: "openai/gpt-4.1-nano" },
6583
+ apiroute: { baseUrl: "https://global.api-route.com/v1", model: "deepseek-v4.1-flash" },
6581
6584
  atlascloud: { baseUrl: "https://api.atlascloud.ai/v1", model: "deepseek-ai/deepseek-v3.2" },
6582
6585
  custom: { baseUrl: "http://localhost:11434/v1", model: "llama3" }
6583
6586
  };
@@ -7072,6 +7075,19 @@ var init_sqlite_store = __esm({
7072
7075
  async listProjectIds() {
7073
7076
  return this.db.prepare("SELECT DISTINCT projectId FROM observations ORDER BY projectId").all().map((row) => row.projectId).filter((projectId) => Boolean(projectId));
7074
7077
  }
7078
+ async migrateProjectIds(aliasIds, canonicalId) {
7079
+ const sourceIds = [...new Set(aliasIds.filter((projectId) => projectId && projectId !== canonicalId))];
7080
+ if (sourceIds.length === 0) return 0;
7081
+ return retrySqliteBusy(async () => {
7082
+ const placeholders = sourceIds.map(() => "?").join(", ");
7083
+ const result = this.db.prepare(
7084
+ `UPDATE observations SET projectId = ? WHERE projectId IN (${placeholders})`
7085
+ ).run(canonicalId, ...sourceIds);
7086
+ const migrated = Number(result.changes ?? 0);
7087
+ if (migrated > 0) this.bumpGeneration();
7088
+ return migrated;
7089
+ }, "Migrate observation project IDs");
7090
+ }
7075
7091
  async searchLexical(options) {
7076
7092
  return searchObservationLexically(this.db, options).map((hit) => ({
7077
7093
  observation: rowToObs(hit.row),
@@ -8117,6 +8133,7 @@ var orama_store_exports = {};
8117
8133
  __export(orama_store_exports, {
8118
8134
  _classifyQueryTier: () => classifyQueryTier,
8119
8135
  batchGenerateEmbeddings: () => batchGenerateEmbeddings,
8136
+ createSearchTokenizer: () => createSearchTokenizer,
8120
8137
  generateEmbedding: () => generateEmbedding,
8121
8138
  generateEmbeddingInput: () => generateEmbeddingInput,
8122
8139
  getActiveSemanticIndexProfile: () => getActiveSemanticIndexProfile,
@@ -8139,7 +8156,7 @@ __export(orama_store_exports, {
8139
8156
  searchObservations: () => searchObservations,
8140
8157
  updateObservationMetadata: () => updateObservationMetadata
8141
8158
  });
8142
- import { create, insert as insert2, insertMultiple, search, remove as remove2, update, count, getByID } from "@orama/orama";
8159
+ import { components, create, insert as insert2, insertMultiple, search, remove as remove2, update, count, getByID } from "@orama/orama";
8143
8160
  function rememberSearchMode(key, value) {
8144
8161
  if (!lastSearchModeByProject.has(key) && lastSearchModeByProject.size >= MAX_SEARCH_MODE_KEYS) {
8145
8162
  const oldest = lastSearchModeByProject.keys().next().value;
@@ -8158,6 +8175,26 @@ function getOptionalObservationStore() {
8158
8175
  return null;
8159
8176
  }
8160
8177
  }
8178
+ function createSearchTokenizer() {
8179
+ const defaultTokenizer = components.tokenizer.createTokenizer({ language: "english" });
8180
+ return {
8181
+ language: defaultTokenizer.language,
8182
+ normalizationCache: defaultTokenizer.normalizationCache,
8183
+ tokenize(raw, language, prop, withCache = true) {
8184
+ const defaultTokens = defaultTokenizer.tokenize(raw, language, prop, withCache);
8185
+ const cjkTokens = [];
8186
+ const normalized = raw.normalize("NFKC").toLowerCase();
8187
+ for (const match2 of normalized.matchAll(CJK_RUN)) {
8188
+ const characters = Array.from(match2[0]);
8189
+ cjkTokens.push(...characters);
8190
+ for (let index = 0; index + 1 < characters.length; index++) {
8191
+ cjkTokens.push(`${characters[index]}${characters[index + 1]}`);
8192
+ }
8193
+ }
8194
+ return [.../* @__PURE__ */ new Set([...defaultTokens, ...cjkTokens])];
8195
+ }
8196
+ };
8197
+ }
8161
8198
  async function shouldUsePersistentSearch(projectIds, projectId) {
8162
8199
  const store = getOptionalObservationStore();
8163
8200
  if (!store?.hasLexicalIndex?.()) return false;
@@ -8258,6 +8295,7 @@ async function searchPersistentSemantically(options, projectIds, limit2, queryVe
8258
8295
  const lexical = await searchPersistentLexically(options, projectIds, limit2);
8259
8296
  const store = getObservationStore();
8260
8297
  const documents = /* @__PURE__ */ new Map();
8298
+ const vectorDocumentKeys = /* @__PURE__ */ new Map();
8261
8299
  const addObservation = (observation) => {
8262
8300
  const document2 = observationToSearchDocument(observation);
8263
8301
  documents.set(makeEntryKey(document2.projectId, document2.observationId), document2);
@@ -8273,6 +8311,7 @@ async function searchPersistentSemantically(options, projectIds, limit2, queryVe
8273
8311
  if (options.type && observation.type !== options.type) return null;
8274
8312
  if (options.source && observation.source !== options.source) return null;
8275
8313
  addObservation(observation);
8314
+ vectorDocumentKeys.set(hit.observationId, makeEntryKey(observation.projectId, observation.id));
8276
8315
  return observation;
8277
8316
  }));
8278
8317
  const RRF_K = 60;
@@ -8282,7 +8321,7 @@ async function searchPersistentSemantically(options, projectIds, limit2, queryVe
8282
8321
  scores.set(key, (scores.get(key) ?? 0) + 0.6 / (RRF_K + index + 1));
8283
8322
  }
8284
8323
  for (const [index, hit] of vectorHits.entries()) {
8285
- const key = makeEntryKey(hit.projectId, hit.observationId);
8324
+ const key = vectorDocumentKeys.get(hit.observationId) ?? makeEntryKey(hit.projectId, hit.observationId);
8286
8325
  if (!documents.has(key)) continue;
8287
8326
  scores.set(key, (scores.get(key) ?? 0) + 0.4 / (RRF_K + index + 1));
8288
8327
  }
@@ -8295,12 +8334,13 @@ async function searchPersistentSemantically(options, projectIds, limit2, queryVe
8295
8334
  return {
8296
8335
  count: vectorHits.length,
8297
8336
  hits: vectorHits.map((hit, index) => {
8298
- const key = makeEntryKey(hit.projectId, hit.observationId);
8337
+ const key = vectorDocumentKeys.get(hit.observationId) ?? makeEntryKey(hit.projectId, hit.observationId);
8299
8338
  const document2 = documents.get(key);
8300
8339
  return document2 ? { id: document2.id, score: 0.4 / (RRF_K + index + 1), document: document2 } : null;
8301
8340
  }).filter((hit) => hit !== null).slice(0, limit2)
8302
8341
  };
8303
8342
  }
8343
+ if (hits.length === 0) return null;
8304
8344
  return { count: hits.length, hits };
8305
8345
  } catch {
8306
8346
  return null;
@@ -8375,7 +8415,10 @@ async function initializeDb(options, generation) {
8375
8415
  };
8376
8416
  const dims = nextEmbeddingDimensions ?? 384;
8377
8417
  const schema = nextEmbeddingEnabled ? { ...baseSchema, embedding: `vector[${dims}]` } : baseSchema;
8378
- const nextDb = await create({ schema });
8418
+ const nextDb = await create({
8419
+ schema,
8420
+ components: { tokenizer: createSearchTokenizer() }
8421
+ });
8379
8422
  if (generation !== dbGeneration) {
8380
8423
  return getDb(options);
8381
8424
  }
@@ -8809,7 +8852,7 @@ async function searchObservations(options) {
8809
8852
  if (!projectIds) return true;
8810
8853
  const doc = hit.document;
8811
8854
  return projectIds.includes(doc.projectId);
8812
- }).filter((hit) => canReadObservation(hit.document, options.reader)).filter((hit) => {
8855
+ }).filter((hit) => canReadObservation(hit.document, options.reader, projectIds ?? void 0)).filter((hit) => {
8813
8856
  if (statusFilter === "all") return true;
8814
8857
  const doc = hit.document;
8815
8858
  return (doc.status || "active") === statusFilter;
@@ -9075,7 +9118,7 @@ async function searchObservations(options) {
9075
9118
  let entries = intermediate.map(({ rawTime: _, _isCommandLog: _c, ...rest }) => rest);
9076
9119
  for (const hit of results.hits) {
9077
9120
  const doc = hit.document;
9078
- if (canReadObservation(doc, options.reader)) rememberObservationDoc(doc);
9121
+ if (canReadObservation(doc, options.reader, projectIds ?? void 0)) rememberObservationDoc(doc);
9079
9122
  }
9080
9123
  if (hasQuery && originalQuery) {
9081
9124
  const queryLower = originalQuery.toLowerCase();
@@ -9233,7 +9276,7 @@ function formatTime(isoDate) {
9233
9276
  return isoDate;
9234
9277
  }
9235
9278
  }
9236
- var db, dbInitPromise, dbGeneration, embeddingEnabled, embeddingDimensions, indexEmbeddingProvider, deferredCachedVectorHydration, NON_CJK_HYBRID_SIMILARITY, lastSearchModeByProject, SEARCH_MODE_DEFAULT_KEY, MAX_SEARCH_MODE_KEYS, COMMAND_LOG_TITLE, COMMAND_STYLE_TITLE, COMMAND_LIKE_QUERY2, COMMAND_INTENT_QUERY, NATURAL_LANGUAGE_MARKERS;
9279
+ var db, dbInitPromise, dbGeneration, embeddingEnabled, embeddingDimensions, indexEmbeddingProvider, deferredCachedVectorHydration, NON_CJK_HYBRID_SIMILARITY, CJK_RUN, lastSearchModeByProject, SEARCH_MODE_DEFAULT_KEY, MAX_SEARCH_MODE_KEYS, COMMAND_LOG_TITLE, COMMAND_STYLE_TITLE, COMMAND_LIKE_QUERY2, COMMAND_INTENT_QUERY, NATURAL_LANGUAGE_MARKERS;
9237
9280
  var init_orama_store = __esm({
9238
9281
  "src/store/orama-store.ts"() {
9239
9282
  "use strict";
@@ -9257,6 +9300,7 @@ var init_orama_store = __esm({
9257
9300
  indexEmbeddingProvider = null;
9258
9301
  deferredCachedVectorHydration = null;
9259
9302
  NON_CJK_HYBRID_SIMILARITY = 0.45;
9303
+ CJK_RUN = /[\u3400-\u4dbf\u4e00-\u9fff\u3040-\u30ff\uac00-\ud7af]+/gu;
9260
9304
  lastSearchModeByProject = /* @__PURE__ */ new Map();
9261
9305
  SEARCH_MODE_DEFAULT_KEY = "__global__";
9262
9306
  MAX_SEARCH_MODE_KEYS = 4096;
@@ -71774,16 +71818,16 @@ var require_pluggable_auth_handler = __commonJS({
71774
71818
  * spaces are between quotation marks.
71775
71819
  */
71776
71820
  static parseCommand(command) {
71777
- const components = command.match(/(?:[^\s"]+|"[^"]*")+/g);
71778
- if (!components) {
71821
+ const components2 = command.match(/(?:[^\s"]+|"[^"]*")+/g);
71822
+ if (!components2) {
71779
71823
  throw new Error(`Provided command: "${command}" could not be parsed.`);
71780
71824
  }
71781
- for (let i2 = 0; i2 < components.length; i2++) {
71782
- if (components[i2][0] === '"' && components[i2].slice(-1) === '"') {
71783
- components[i2] = components[i2].slice(1, -1);
71825
+ for (let i2 = 0; i2 < components2.length; i2++) {
71826
+ if (components2[i2][0] === '"' && components2[i2].slice(-1) === '"') {
71827
+ components2[i2] = components2[i2].slice(1, -1);
71784
71828
  }
71785
71829
  }
71786
- return components;
71830
+ return components2;
71787
71831
  }
71788
71832
  };
71789
71833
  exports.PluggableAuthHandler = PluggableAuthHandler;
@@ -151615,6 +151659,16 @@ function getProjectObservations(projectId) {
151615
151659
  async function migrateProjectIds(aliasIds, canonicalId) {
151616
151660
  const nonCanonical = new Set(aliasIds.filter((id) => id !== canonicalId));
151617
151661
  if (nonCanonical.size === 0) return 0;
151662
+ const store = projectDir ? getObservationStore() : null;
151663
+ if (store?.migrateProjectIds) {
151664
+ const migrated2 = await store.migrateProjectIds(aliasIds, canonicalId);
151665
+ if (migrated2 > 0 && corpusLoaded) {
151666
+ for (const observation of observations) {
151667
+ if (nonCanonical.has(observation.projectId)) observation.projectId = canonicalId;
151668
+ }
151669
+ }
151670
+ return migrated2;
151671
+ }
151618
151672
  let migrated = 0;
151619
151673
  const changed = [];
151620
151674
  for (const obs of observations) {
@@ -151625,8 +151679,8 @@ async function migrateProjectIds(aliasIds, canonicalId) {
151625
151679
  }
151626
151680
  }
151627
151681
  if (migrated > 0 && projectDir) {
151628
- const store = getObservationStore();
151629
- await store.atomic(async (tx) => {
151682
+ const store2 = getObservationStore();
151683
+ await store2.atomic(async (tx) => {
151630
151684
  await Promise.all(changed.map((observation) => tx.update(observation)));
151631
151685
  });
151632
151686
  }