memorix 1.4.6 → 1.5.1

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 (119) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/TEAM.md +86 -86
  3. package/dist/cli/index.js +225 -70
  4. package/dist/cli/memcode.js +0 -0
  5. package/dist/index.js +225 -70
  6. package/dist/maintenance-runner.js +183 -38
  7. package/dist/media-audio-runner.js +183 -38
  8. package/dist/media-video-runner.js +183 -38
  9. package/dist/memcode-runtime/CHANGELOG.md +38 -0
  10. package/dist/sdk.js +224 -69
  11. package/dist/types.d.ts +2 -0
  12. package/dist/vector-backfill-runner.js +183 -38
  13. package/docs/CONFIGURATION.md +12 -0
  14. package/docs/DESIGN_DECISIONS.md +357 -357
  15. package/package.json +1 -1
  16. package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
  17. package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
  18. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  19. package/plugins/copilot/memorix/plugin.json +1 -1
  20. package/plugins/gemini/memorix/gemini-extension.json +1 -1
  21. package/plugins/omp/memorix/package.json +1 -1
  22. package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
  23. package/plugins/pi/memorix/package.json +1 -1
  24. package/server.json +2 -2
  25. package/src/audit/index.ts +156 -156
  26. package/src/cli/commands/formation.ts +48 -48
  27. package/src/cli/commands/git-hook-install.ts +111 -111
  28. package/src/cli/commands/handoff.ts +54 -54
  29. package/src/cli/commands/hooks-status.ts +63 -63
  30. package/src/cli/commands/ingest.ts +44 -44
  31. package/src/cli/commands/integrate-shared.ts +15 -15
  32. package/src/cli/commands/lock.ts +82 -82
  33. package/src/cli/commands/message.ts +104 -104
  34. package/src/cli/commands/poll.ts +58 -58
  35. package/src/cli/commands/reasoning.ts +118 -118
  36. package/src/cli/commands/skills.ts +114 -114
  37. package/src/cli/commands/task.ts +167 -167
  38. package/src/cli/commands/transfer.ts +47 -47
  39. package/src/cli/index.ts +406 -406
  40. package/src/cli/tui/ChatView.tsx +234 -234
  41. package/src/cli/tui/CommandBar.tsx +312 -312
  42. package/src/cli/tui/ContextRail.tsx +118 -118
  43. package/src/cli/tui/HeaderBar.tsx +72 -72
  44. package/src/cli/tui/LogoBanner.tsx +51 -51
  45. package/src/cli/tui/index.ts +41 -41
  46. package/src/cli/version.ts +7 -7
  47. package/src/cli/workbench.ts +1 -1
  48. package/src/config.ts +23 -2
  49. package/src/dashboard/project-classification.ts +64 -64
  50. package/src/embedding/api-provider.ts +105 -22
  51. package/src/embedding/provider.ts +2 -0
  52. package/src/git/hooks-path.ts +85 -85
  53. package/src/hooks/pattern-detector.ts +173 -173
  54. package/src/hooks/significance-filter.ts +250 -250
  55. package/src/llm/memory-manager.ts +328 -328
  56. package/src/llm/provider.ts +17 -8
  57. package/src/llm/quality.ts +248 -248
  58. package/src/memory/attribution-guard.ts +249 -249
  59. package/src/memory/disclosure-policy.ts +135 -135
  60. package/src/memory/entity-extractor.ts +197 -197
  61. package/src/memory/formation/evaluate.ts +217 -217
  62. package/src/memory/formation/extract.ts +6 -3
  63. package/src/memory/formation/index.ts +2 -1
  64. package/src/memory/formation/resolve.ts +10 -6
  65. package/src/memory/formation/types.ts +3 -1
  66. package/src/memory/freshness.ts +122 -122
  67. package/src/memory/graph.ts +197 -197
  68. package/src/memory/long-term.ts +7 -1
  69. package/src/memory/refs.ts +94 -94
  70. package/src/memory/secret-filter.ts +79 -79
  71. package/src/orchestrate/adapters/claude-stream.ts +192 -192
  72. package/src/orchestrate/adapters/claude.ts +111 -111
  73. package/src/orchestrate/adapters/codex-stream.ts +134 -134
  74. package/src/orchestrate/adapters/codex.ts +41 -41
  75. package/src/orchestrate/adapters/gemini-stream.ts +166 -166
  76. package/src/orchestrate/adapters/gemini.ts +42 -42
  77. package/src/orchestrate/adapters/index.ts +73 -73
  78. package/src/orchestrate/adapters/opencode-stream.ts +143 -143
  79. package/src/orchestrate/adapters/opencode.ts +47 -47
  80. package/src/orchestrate/adapters/types.ts +77 -77
  81. package/src/orchestrate/capability-router.ts +284 -284
  82. package/src/orchestrate/cost-tracker.ts +219 -219
  83. package/src/orchestrate/error-recovery.ts +191 -191
  84. package/src/orchestrate/evidence.ts +140 -140
  85. package/src/orchestrate/ledger.ts +110 -110
  86. package/src/orchestrate/memorix-bridge.ts +343 -343
  87. package/src/orchestrate/output-budget.ts +80 -80
  88. package/src/orchestrate/permission.ts +152 -152
  89. package/src/orchestrate/pipeline-trace.ts +131 -131
  90. package/src/orchestrate/prompt-builder.ts +155 -155
  91. package/src/orchestrate/ring-buffer.ts +37 -37
  92. package/src/orchestrate/task-graph.ts +389 -389
  93. package/src/project/aliases.ts +374 -374
  94. package/src/rules/adapters/trae.ts +56 -56
  95. package/src/rules/syncer.ts +235 -235
  96. package/src/search/query-expansion.ts +52 -52
  97. package/src/server/formation-timeout.ts +27 -27
  98. package/src/server.ts +14 -10
  99. package/src/skills/mini-skills.ts +386 -386
  100. package/src/store/chat-store.ts +119 -119
  101. package/src/store/graph-store.ts +249 -249
  102. package/src/store/mini-skill-store.ts +349 -349
  103. package/src/store/orama-store.ts +6 -4
  104. package/src/store/persistence-json.ts +212 -212
  105. package/src/store/persistence.ts +291 -291
  106. package/src/store/project-affinity.ts +195 -195
  107. package/src/team/event-bus.ts +76 -76
  108. package/src/team/handoff.ts +161 -161
  109. package/src/team/poll.ts +132 -132
  110. package/src/timeout.ts +67 -0
  111. package/src/types.ts +2 -0
  112. package/src/workspace/mcp-adapters/codex.ts +191 -191
  113. package/src/workspace/mcp-adapters/copilot.ts +105 -105
  114. package/src/workspace/mcp-adapters/cursor.ts +53 -53
  115. package/src/workspace/mcp-adapters/kiro.ts +64 -64
  116. package/src/workspace/mcp-adapters/opencode.ts +123 -123
  117. package/src/workspace/mcp-adapters/trae.ts +134 -134
  118. package/src/workspace/mcp-adapters/windsurf.ts +91 -91
  119. package/src/workspace/sanitizer.ts +60 -60
package/dist/sdk.js CHANGED
@@ -3377,6 +3377,7 @@ var init_resolved_config = __esm({
3377
3377
  // src/config.ts
3378
3378
  var config_exports = {};
3379
3379
  __export(config_exports, {
3380
+ defaultEmbeddingModelFor: () => defaultEmbeddingModelFor,
3380
3381
  getAgentLLMApiKey: () => getAgentLLMApiKey,
3381
3382
  getAgentLLMBaseUrl: () => getAgentLLMBaseUrl,
3382
3383
  getAgentLLMModel: () => getAgentLLMModel,
@@ -3439,7 +3440,19 @@ function getEmbeddingBaseUrl() {
3439
3440
  return getResolvedEmbeddingLane().baseUrl || "https://api.openai.com/v1";
3440
3441
  }
3441
3442
  function getEmbeddingModel() {
3442
- return getResolvedEmbeddingLane().model || "text-embedding-3-small";
3443
+ const lane = getResolvedEmbeddingLane();
3444
+ return defaultEmbeddingModelFor(lane.baseUrl, lane.model);
3445
+ }
3446
+ function defaultEmbeddingModelFor(baseUrl, explicitModel) {
3447
+ if (explicitModel) return explicitModel;
3448
+ try {
3449
+ const url = baseUrl ? new URL(baseUrl) : null;
3450
+ if (url && (url.hostname === "openrouter.ai" || url.hostname.endsWith(".openrouter.ai"))) {
3451
+ return "qwen/qwen3-embedding-8b";
3452
+ }
3453
+ } catch {
3454
+ }
3455
+ return "text-embedding-3-small";
3443
3456
  }
3444
3457
  function getEmbeddingDimensions() {
3445
3458
  return getResolvedEmbeddingLane().dimensions ?? null;
@@ -3978,6 +3991,8 @@ function getPreferredBatchSize(config) {
3978
3991
  }
3979
3992
  function parseBatchLimit(error) {
3980
3993
  if (!(error instanceof Error)) return null;
3994
+ const status = error instanceof EmbeddingAPIError ? error.status : Number(error.message.match(/Embedding API error \((\d{3})\)/i)?.[1] ?? 0);
3995
+ if (status > 0 && status !== 400) return null;
3981
3996
  const explicit = error.message.match(/should not be larger than\s+(\d+)/i);
3982
3997
  if (explicit) return parseInt(explicit[1], 10);
3983
3998
  if (/batch size/i.test(error.message)) {
@@ -3990,37 +4005,99 @@ async function fetchWithRetry(url, apiKey, body, options = {}, attempt = 0, nati
3990
4005
  const controller = new AbortController();
3991
4006
  const timeoutMs = typeof options.timeoutMs === "number" && Number.isFinite(options.timeoutMs) ? Math.max(100, Math.min(Math.floor(options.timeoutMs), 6e4)) : 1e4;
3992
4007
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
4008
+ const signal = options.signal ? AbortSignal.any([options.signal, controller.signal]) : controller.signal;
3993
4009
  let response;
3994
4010
  try {
3995
4011
  response = await fetch(url, {
3996
4012
  method: "POST",
3997
4013
  headers: nativeGemini ? { "Content-Type": "application/json", "x-goog-api-key": apiKey } : { "Content-Type": "application/json", "Authorization": `Bearer ${apiKey}` },
3998
4014
  body: JSON.stringify(body),
3999
- signal: controller.signal
4015
+ signal
4000
4016
  });
4001
4017
  } catch (err) {
4002
4018
  clearTimeout(timeout);
4003
- if (err instanceof Error && err.name === "AbortError") {
4019
+ if (err instanceof Error && (err.name === "AbortError" || controller.signal.aborted || options.signal?.aborted)) {
4004
4020
  throw new Error(`Embedding API timeout after ${timeoutMs}ms: ${url}`);
4005
4021
  }
4006
4022
  throw err;
4007
4023
  }
4008
- clearTimeout(timeout);
4009
- if (response.ok) {
4010
- return response.json();
4024
+ try {
4025
+ if (response.ok) {
4026
+ return await raceWithAbort(
4027
+ response.json(),
4028
+ signal
4029
+ );
4030
+ }
4031
+ if (options.retry !== false && (response.status === 429 || response.status >= 500) && attempt < MAX_RETRIES2) {
4032
+ const delay2 = BASE_DELAY_MS * Math.pow(2, attempt);
4033
+ const retryAfter = response.headers.get("retry-after");
4034
+ const parsedRetryAfter = retryAfter ? parseInt(retryAfter, 10) * 1e3 : NaN;
4035
+ const waitMs = Number.isFinite(parsedRetryAfter) ? parsedRetryAfter : delay2;
4036
+ console.error(`[memorix] Embedding API ${response.status}, retry ${attempt + 1}/${MAX_RETRIES2} in ${waitMs}ms`);
4037
+ await waitForRetry(waitMs, options.signal);
4038
+ return fetchWithRetry(url, apiKey, body, options, attempt + 1, nativeGemini);
4039
+ }
4040
+ const errorText2 = await raceWithAbort(
4041
+ response.text().catch(() => "unknown error"),
4042
+ signal
4043
+ );
4044
+ throw new EmbeddingAPIError(response.status, errorText2);
4045
+ } catch (err) {
4046
+ if (err instanceof Error && (err.name === "AbortError" || controller.signal.aborted || options.signal?.aborted)) {
4047
+ throw new Error(`Embedding API timeout after ${timeoutMs}ms: ${url}`);
4048
+ }
4049
+ throw err;
4050
+ } finally {
4051
+ clearTimeout(timeout);
4011
4052
  }
4012
- if (options.retry !== false && (response.status === 429 || response.status >= 500) && attempt < MAX_RETRIES2) {
4013
- const delay2 = BASE_DELAY_MS * Math.pow(2, attempt);
4014
- const retryAfter = response.headers.get("retry-after");
4015
- const waitMs = retryAfter ? parseInt(retryAfter, 10) * 1e3 : delay2;
4016
- console.error(`[memorix] Embedding API ${response.status}, retry ${attempt + 1}/${MAX_RETRIES2} in ${waitMs}ms`);
4053
+ }
4054
+ function abortError() {
4055
+ const error = new Error("Embedding API request aborted");
4056
+ error.name = "AbortError";
4057
+ return error;
4058
+ }
4059
+ function raceWithAbort(promise, signal) {
4060
+ if (signal.aborted) return Promise.reject(abortError());
4061
+ return new Promise((resolve2, reject) => {
4062
+ const onAbort = () => {
4063
+ signal.removeEventListener("abort", onAbort);
4064
+ reject(abortError());
4065
+ };
4066
+ signal.addEventListener("abort", onAbort, { once: true });
4067
+ promise.then(
4068
+ (value) => {
4069
+ signal.removeEventListener("abort", onAbort);
4070
+ resolve2(value);
4071
+ },
4072
+ (error) => {
4073
+ signal.removeEventListener("abort", onAbort);
4074
+ reject(error);
4075
+ }
4076
+ );
4077
+ });
4078
+ }
4079
+ async function waitForRetry(waitMs, signal) {
4080
+ if (!signal) {
4017
4081
  await new Promise((resolve2) => setTimeout(resolve2, waitMs));
4018
- return fetchWithRetry(url, apiKey, body, options, attempt + 1, nativeGemini);
4082
+ return;
4019
4083
  }
4020
- const errorText2 = await response.text().catch(() => "unknown error");
4021
- throw new Error(`Embedding API error (${response.status}): ${errorText2}`);
4084
+ const abortSignal = signal;
4085
+ if (abortSignal.aborted) throw new Error("Embedding API retry aborted");
4086
+ await new Promise((resolve2, reject) => {
4087
+ const timer = setTimeout(done, waitMs);
4088
+ const onAbort = () => {
4089
+ clearTimeout(timer);
4090
+ abortSignal.removeEventListener("abort", onAbort);
4091
+ reject(new Error("Embedding API retry aborted"));
4092
+ };
4093
+ function done() {
4094
+ abortSignal.removeEventListener("abort", onAbort);
4095
+ resolve2();
4096
+ }
4097
+ abortSignal.addEventListener("abort", onAbort, { once: true });
4098
+ });
4022
4099
  }
4023
- var cache3, MAX_CACHE_SIZE3, diskCacheDirty2, diskSaveTimer, diskCacheLoaded, diskCacheLoadPromise, MAX_INPUT_CHARS, MAX_CONCURRENCY, DISK_SAVE_DEBOUNCE_MS, DEFAULT_MAX_BATCH_SIZE, DASHSCOPE_MAX_BATCH_SIZE, MAX_RETRIES2, BASE_DELAY_MS, APIEmbeddingProvider;
4100
+ var cache3, MAX_CACHE_SIZE3, diskCacheDirty2, diskSaveTimer, diskCacheLoaded, diskCacheLoadPromise, MAX_INPUT_CHARS, MAX_CONCURRENCY, DISK_SAVE_DEBOUNCE_MS, DEFAULT_MAX_BATCH_SIZE, DASHSCOPE_MAX_BATCH_SIZE, MAX_RETRIES2, BASE_DELAY_MS, EmbeddingAPIError, APIEmbeddingProvider;
4024
4101
  var init_api_provider = __esm({
4025
4102
  "src/embedding/api-provider.ts"() {
4026
4103
  "use strict";
@@ -4039,6 +4116,14 @@ var init_api_provider = __esm({
4039
4116
  DASHSCOPE_MAX_BATCH_SIZE = 10;
4040
4117
  MAX_RETRIES2 = 3;
4041
4118
  BASE_DELAY_MS = 500;
4119
+ EmbeddingAPIError = class extends Error {
4120
+ status;
4121
+ constructor(status, detail) {
4122
+ super(`Embedding API error (${status}): ${detail}`);
4123
+ this.name = "EmbeddingAPIError";
4124
+ this.status = status;
4125
+ }
4126
+ };
4042
4127
  APIEmbeddingProvider = class _APIEmbeddingProvider {
4043
4128
  name;
4044
4129
  dimensions;
@@ -4250,15 +4335,14 @@ var init_api_provider = __esm({
4250
4335
  }
4251
4336
  } catch (error) {
4252
4337
  const providerLimit = parseBatchLimit(error);
4253
- const fallbackSize = providerLimit ?? Math.ceil(chunkTexts.length / 2);
4254
- if (chunkTexts.length > 1 && fallbackSize < chunkTexts.length) {
4338
+ if (providerLimit !== null && chunkTexts.length > 1 && providerLimit < chunkTexts.length) {
4255
4339
  console.error(
4256
- `[memorix] Embedding batch too large for provider, retrying in chunks of ${fallbackSize}`
4340
+ `[memorix] Embedding batch too large for provider, retrying in chunks of ${providerLimit}`
4257
4341
  );
4258
- for (let start = 0; start < chunkTexts.length; start += fallbackSize) {
4342
+ for (let start = 0; start < chunkTexts.length; start += providerLimit) {
4259
4343
  await processChunk(
4260
- chunkTexts.slice(start, start + fallbackSize),
4261
- chunkIndices.slice(start, start + fallbackSize)
4344
+ chunkTexts.slice(start, start + providerLimit),
4345
+ chunkIndices.slice(start, start + providerLimit)
4262
4346
  );
4263
4347
  }
4264
4348
  return;
@@ -5122,22 +5206,23 @@ function getLLMConfig() {
5122
5206
  function setLLMConfig(config) {
5123
5207
  currentConfig = config;
5124
5208
  }
5125
- async function callLLM(systemPrompt, userMessage) {
5209
+ async function callLLM(systemPrompt, userMessage, signal) {
5126
5210
  if (!currentConfig) {
5127
5211
  throw new Error("LLM not configured. Set MEMORIX_LLM_API_KEY or OPENAI_API_KEY.");
5128
5212
  }
5129
5213
  if (currentConfig.provider === "anthropic") {
5130
- return callAnthropic(systemPrompt, userMessage);
5214
+ return callAnthropic(systemPrompt, userMessage, signal);
5131
5215
  }
5132
- return callOpenAICompatible(systemPrompt, userMessage);
5216
+ return callOpenAICompatible(systemPrompt, userMessage, signal);
5133
5217
  }
5134
- async function callOpenAICompatible(systemPrompt, userMessage) {
5218
+ async function callOpenAICompatible(systemPrompt, userMessage, signal) {
5135
5219
  const config = currentConfig;
5136
5220
  let base = config.baseUrl.replace(/\/+$/, "");
5137
5221
  if (!base.endsWith("/v1")) base += "/v1";
5138
5222
  const url = `${base}/chat/completions`;
5223
+ const fetchSignal = signal ? AbortSignal.any([signal, AbortSignal.timeout(LLM_CALL_TIMEOUT_MS)]) : AbortSignal.timeout(LLM_CALL_TIMEOUT_MS);
5139
5224
  const response = await fetch(url, {
5140
- signal: AbortSignal.timeout(LLM_CALL_TIMEOUT_MS),
5225
+ signal: fetchSignal,
5141
5226
  method: "POST",
5142
5227
  headers: {
5143
5228
  "Content-Type": "application/json",
@@ -5154,10 +5239,10 @@ async function callOpenAICompatible(systemPrompt, userMessage) {
5154
5239
  })
5155
5240
  });
5156
5241
  if (!response.ok) {
5157
- const error = await readResponseText(response, void 0, 64 * 1024).catch(() => "unknown error");
5242
+ const error = await readResponseText(response, signal, 64 * 1024).catch(() => "unknown error");
5158
5243
  throw new Error(`LLM API error (${response.status}): ${error}`);
5159
5244
  }
5160
- const data = await readResponseJson(response);
5245
+ const data = await readResponseJson(response, signal);
5161
5246
  return {
5162
5247
  content: data.choices[0]?.message?.content ?? "",
5163
5248
  usage: data.usage ? {
@@ -5166,11 +5251,12 @@ async function callOpenAICompatible(systemPrompt, userMessage) {
5166
5251
  } : void 0
5167
5252
  };
5168
5253
  }
5169
- async function callAnthropic(systemPrompt, userMessage) {
5254
+ async function callAnthropic(systemPrompt, userMessage, signal) {
5170
5255
  const config = currentConfig;
5171
5256
  const url = `${config.baseUrl}/messages`;
5257
+ const fetchSignal = signal ? AbortSignal.any([signal, AbortSignal.timeout(LLM_CALL_TIMEOUT_MS)]) : AbortSignal.timeout(LLM_CALL_TIMEOUT_MS);
5172
5258
  const response = await fetch(url, {
5173
- signal: AbortSignal.timeout(LLM_CALL_TIMEOUT_MS),
5259
+ signal: fetchSignal,
5174
5260
  method: "POST",
5175
5261
  headers: {
5176
5262
  "Content-Type": "application/json",
@@ -5188,10 +5274,10 @@ async function callAnthropic(systemPrompt, userMessage) {
5188
5274
  })
5189
5275
  });
5190
5276
  if (!response.ok) {
5191
- const error = await readResponseText(response, void 0, 64 * 1024).catch(() => "unknown error");
5277
+ const error = await readResponseText(response, signal, 64 * 1024).catch(() => "unknown error");
5192
5278
  throw new Error(`Anthropic API error (${response.status}): ${error}`);
5193
5279
  }
5194
- const data = await readResponseJson(response);
5280
+ const data = await readResponseJson(response, signal);
5195
5281
  return {
5196
5282
  content: data.content[0]?.text ?? "",
5197
5283
  usage: data.usage ? {
@@ -5696,6 +5782,57 @@ function withTimeout(promise, ms, label) {
5696
5782
  );
5697
5783
  });
5698
5784
  }
5785
+ function withTimeoutSignal(operation, ms, label, parentSignal) {
5786
+ const controller = new AbortController();
5787
+ const signal = parentSignal ? AbortSignal.any([parentSignal, controller.signal]) : controller.signal;
5788
+ return new Promise((resolve2, reject) => {
5789
+ let settled = false;
5790
+ let timedOut = false;
5791
+ let timer;
5792
+ const cleanup = () => {
5793
+ if (timer !== void 0) clearTimeout(timer);
5794
+ parentSignal?.removeEventListener("abort", onParentAbort);
5795
+ };
5796
+ const onParentAbort = () => {
5797
+ if (settled || timedOut) return;
5798
+ settled = true;
5799
+ cleanup();
5800
+ reject(parentSignal?.reason ?? new Error(`${label} aborted`));
5801
+ };
5802
+ parentSignal?.addEventListener("abort", onParentAbort, { once: true });
5803
+ timer = setTimeout(() => {
5804
+ if (settled) return;
5805
+ timedOut = true;
5806
+ controller.abort(new Error(`${label} timed out after ${ms}ms`));
5807
+ settled = true;
5808
+ cleanup();
5809
+ reject(new Error(`${label} timed out after ${ms}ms`));
5810
+ }, ms);
5811
+ let pending;
5812
+ try {
5813
+ pending = operation(signal);
5814
+ } catch (error) {
5815
+ settled = true;
5816
+ cleanup();
5817
+ reject(error);
5818
+ return;
5819
+ }
5820
+ pending.then(
5821
+ (value) => {
5822
+ if (settled) return;
5823
+ settled = true;
5824
+ cleanup();
5825
+ resolve2(value);
5826
+ },
5827
+ (error) => {
5828
+ if (settled) return;
5829
+ settled = true;
5830
+ cleanup();
5831
+ reject(timedOut ? new Error(`${label} timed out after ${ms}ms`) : error);
5832
+ }
5833
+ );
5834
+ });
5835
+ }
5699
5836
  var init_timeout = __esm({
5700
5837
  "src/timeout.ts"() {
5701
5838
  "use strict";
@@ -6560,16 +6697,18 @@ async function searchObservations(options) {
6560
6697
  );
6561
6698
  } else {
6562
6699
  const EMBEDDING_TIMEOUT_MS = 15e3;
6563
- queryVector = await withTimeout(
6564
- provider2.embedInput ? provider2.embedInput(
6700
+ queryVector = await withTimeoutSignal(
6701
+ (signal) => provider2.embedInput ? provider2.embedInput(
6565
6702
  { modality: "text", text: expandedEmbeddingQuery },
6566
- { intent: "query", timeoutMs: EMBEDDING_TIMEOUT_MS, retry: false }
6703
+ { intent: "query", timeoutMs: EMBEDDING_TIMEOUT_MS, retry: false, signal }
6567
6704
  ) : provider2.embed(expandedEmbeddingQuery, {
6568
6705
  timeoutMs: EMBEDDING_TIMEOUT_MS,
6569
- retry: false
6706
+ retry: false,
6707
+ signal
6570
6708
  }),
6571
6709
  EMBEDDING_TIMEOUT_MS,
6572
- "Embedding"
6710
+ "Embedding",
6711
+ options.signal
6573
6712
  );
6574
6713
  mark("embedding");
6575
6714
  const cjkRatio = (originalQuery.match(/[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/g) || []).length / originalQuery.length;
@@ -144989,7 +145128,13 @@ async function maintainLongTermMemories(input) {
144989
145128
  }
144990
145129
  for (const group of groups.values()) {
144991
145130
  if (group.length < 2) continue;
144992
- group.sort((a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt));
145131
+ group.sort((a, b) => {
145132
+ const byCreatedAt = Date.parse(b.createdAt) - Date.parse(a.createdAt);
145133
+ if (byCreatedAt !== 0) return byCreatedAt;
145134
+ const byUpdatedAt = Date.parse(b.updatedAt) - Date.parse(a.updatedAt);
145135
+ if (byUpdatedAt !== 0) return byUpdatedAt;
145136
+ return b.id.localeCompare(a.id);
145137
+ });
144993
145138
  const newest = group[0];
144994
145139
  for (const older of group.slice(1)) {
144995
145140
  try {
@@ -151475,13 +151620,13 @@ function verifyType(declaredType, narrative, title) {
151475
151620
  }
151476
151621
  return { type: declaredType, corrected: false };
151477
151622
  }
151478
- async function extractFactsWithLLM(narrative, title, existingFacts) {
151623
+ async function extractFactsWithLLM(narrative, title, existingFacts, signal) {
151479
151624
  try {
151480
151625
  const { callLLM: callLLM2 } = await Promise.resolve().then(() => (init_provider2(), provider_exports2));
151481
151626
  const input = `Title: ${title}
151482
151627
  Content: ${narrative}${existingFacts.length > 0 ? `
151483
151628
  Already known facts (don't repeat): ${existingFacts.join("; ")}` : ""}`;
151484
- const response = await callLLM2(LLM_EXTRACT_PROMPT, input);
151629
+ const response = await callLLM2(LLM_EXTRACT_PROMPT, input, signal);
151485
151630
  const text2 = response.content.trim();
151486
151631
  const jsonMatch = text2.match(/\{[\s\S]*\}/);
151487
151632
  if (!jsonMatch) return [];
@@ -151490,15 +151635,16 @@ Already known facts (don't repeat): ${existingFacts.join("; ")}` : ""}`;
151490
151635
  if (!Array.isArray(facts)) return [];
151491
151636
  const existingLower = new Set(existingFacts.map((f3) => f3.toLowerCase().trim()));
151492
151637
  return facts.filter((f3) => typeof f3 === "string" && f3.length >= 5).filter((f3) => !existingLower.has(f3.toLowerCase().trim())).slice(0, 10);
151493
- } catch {
151638
+ } catch (error) {
151639
+ if (signal?.aborted) throw error;
151494
151640
  return [];
151495
151641
  }
151496
151642
  }
151497
- async function runExtract(input, existingEntities, useLLM = false) {
151643
+ async function runExtract(input, existingEntities, useLLM = false, signal) {
151498
151644
  const callerFacts = input.facts ?? [];
151499
151645
  let extractedFacts;
151500
151646
  if (useLLM) {
151501
- extractedFacts = await extractFactsWithLLM(input.narrative, input.title, callerFacts);
151647
+ extractedFacts = await extractFactsWithLLM(input.narrative, input.title, callerFacts, signal);
151502
151648
  if (extractedFacts.length === 0) {
151503
151649
  extractedFacts = extractFacts2(input.narrative, callerFacts);
151504
151650
  }
@@ -151718,7 +151864,7 @@ function mergeFacts2(oldFacts, newFacts) {
151718
151864
  }
151719
151865
  return merged;
151720
151866
  }
151721
- async function resolveWithLLM(extracted, hits, getObservation2) {
151867
+ async function resolveWithLLM(extracted, hits, getObservation2, signal) {
151722
151868
  try {
151723
151869
  const { callLLM: callLLM2 } = await Promise.resolve().then(() => (init_provider2(), provider_exports2));
151724
151870
  const existingMemories = hits.slice(0, 5).map((h2, i2) => ({
@@ -151735,7 +151881,7 @@ Facts: ${extracted.facts.join("; ")}
151735
151881
 
151736
151882
  EXISTING MEMORIES:
151737
151883
  ${existingMemories.map((m2) => `[ID:${m2.id}] ${m2.title} | ${m2.content} | Facts: ${m2.facts}`).join("\n")}`;
151738
- const response = await callLLM2(LLM_RESOLVE_PROMPT, input);
151884
+ const response = await callLLM2(LLM_RESOLVE_PROMPT, input, signal);
151739
151885
  const text2 = response.content.trim();
151740
151886
  const jsonMatch = text2.match(/\{[\s\S]*\}/);
151741
151887
  if (!jsonMatch) return null;
@@ -151772,7 +151918,8 @@ ${existingMemories.map((m2) => `[ID:${m2.id}] ${m2.title} | ${m2.content} | Fact
151772
151918
  };
151773
151919
  }
151774
151920
  return null;
151775
- } catch {
151921
+ } catch (error) {
151922
+ if (signal?.aborted) throw error;
151776
151923
  return null;
151777
151924
  }
151778
151925
  }
@@ -151790,19 +151937,20 @@ function scoreCandidate(extracted, candidate) {
151790
151937
  const contradiction = hasContradiction(candidate.narrative, extracted.narrative);
151791
151938
  return { score, searchSimilarity, entityMatch, richer, contradiction };
151792
151939
  }
151793
- async function runResolve(extracted, projectId, searchMemories, getObservation2, useLLM = false) {
151940
+ async function runResolve(extracted, projectId, searchMemories, getObservation2, useLLM = false, signal) {
151794
151941
  const query = `${extracted.title} ${extracted.narrative.substring(0, 200)}`;
151795
151942
  let hits;
151796
151943
  try {
151797
- hits = await searchMemories(query, 5, projectId);
151798
- } catch {
151944
+ hits = await searchMemories(query, 5, projectId, signal);
151945
+ } catch (error) {
151946
+ if (signal?.aborted) throw error;
151799
151947
  return { action: "new", reason: "Search unavailable, defaulting to new" };
151800
151948
  }
151801
151949
  if (hits.length === 0) {
151802
151950
  return { action: "new", reason: "No similar existing memories found" };
151803
151951
  }
151804
151952
  if (useLLM) {
151805
- const llmResult = await resolveWithLLM(extracted, hits, getObservation2);
151953
+ const llmResult = await resolveWithLLM(extracted, hits, getObservation2, signal);
151806
151954
  if (llmResult) return llmResult;
151807
151955
  }
151808
151956
  const scored = hits.map((hit) => ({
@@ -152224,7 +152372,7 @@ async function runFormation(input, config) {
152224
152372
  const existingEntities = config.getEntityNames();
152225
152373
  const extractStartTime = Date.now();
152226
152374
  emitStageEvent("extract", "start");
152227
- const extraction = await runExtract(input, existingEntities, config.useLLM);
152375
+ const extraction = await runExtract(input, existingEntities, config.useLLM, config.signal);
152228
152376
  stageDurationsMs.extract = Date.now() - extractStartTime;
152229
152377
  emitStageEvent("extract", "success", stageDurationsMs.extract);
152230
152378
  stagesCompleted = 1;
@@ -152244,7 +152392,8 @@ async function runFormation(input, config) {
152244
152392
  input.projectId,
152245
152393
  config.searchMemories,
152246
152394
  config.getObservation,
152247
- config.useLLM
152395
+ config.useLLM,
152396
+ config.signal
152248
152397
  );
152249
152398
  stageDurationsMs.resolve = Date.now() - resolveStartTime;
152250
152399
  emitStageEvent("resolve", "success", stageDurationsMs.resolve);
@@ -165629,7 +165778,7 @@ The path should point to a directory containing a .git folder.`
165629
165778
  };
165630
165779
  const server = existingServer ?? new McpServer({
165631
165780
  name: "memorix",
165632
- version: true ? "1.4.6" : "1.0.1"
165781
+ version: true ? "1.5.1" : "1.0.1"
165633
165782
  });
165634
165783
  const originalRegisterTool = server.registerTool.bind(server);
165635
165784
  server.registerTool = ((name, ...args) => {
@@ -165818,8 +165967,8 @@ The path should point to a directory containing a .git folder.`
165818
165967
  mode: "active",
165819
165968
  useLLM: isLLMEnabled(),
165820
165969
  minValueScore: 0.3,
165821
- searchMemories: async (q, limit2, pid) => {
165822
- const result = await compactSearch({ query: q, limit: limit2, projectId: pid, status: "active", reader });
165970
+ searchMemories: async (q, limit2, pid, signal) => {
165971
+ const result = await compactSearch({ query: q, limit: limit2, projectId: pid, status: "active", reader, signal });
165823
165972
  if (result.entries.length === 0) return [];
165824
165973
  const details = await compactDetail(result.entries.map((e2) => e2.id), { reader });
165825
165974
  return details.documents.map((d, i2) => ({
@@ -165849,16 +165998,19 @@ The path should point to a directory containing a .git folder.`
165849
165998
  getEntityNames: () => graphManager.getEntityNames(),
165850
165999
  onStageEvent: onFormationStageEvent
165851
166000
  };
165852
- formationResult = await withTimeout(
165853
- runFormation({
165854
- entityName,
165855
- type,
165856
- title,
165857
- narrative,
165858
- facts: safeFacts,
165859
- projectId: project.id,
165860
- source: "explicit"
165861
- }, formationConfig),
166001
+ formationResult = await withTimeoutSignal(
166002
+ async (signal) => {
166003
+ formationConfig.signal = signal;
166004
+ return runFormation({
166005
+ entityName,
166006
+ type,
166007
+ title,
166008
+ narrative,
166009
+ facts: safeFacts,
166010
+ projectId: project.id,
166011
+ source: "explicit"
166012
+ }, formationConfig);
166013
+ },
165862
166014
  FORMATION_TIMEOUT_MS,
165863
166015
  "Formation pipeline"
165864
166016
  );
@@ -166208,8 +166360,8 @@ Auto-enriched: ${enrichmentParts.join(", ")}` : "";
166208
166360
  mode: formationMode,
166209
166361
  useLLM: isLLMEnabled(),
166210
166362
  minValueScore: 0.3,
166211
- searchMemories: async (q, limit2, pid) => {
166212
- const result = await compactSearch({ query: q, limit: limit2, projectId: pid, status: "active", reader });
166363
+ searchMemories: async (q, limit2, pid, signal) => {
166364
+ const result = await compactSearch({ query: q, limit: limit2, projectId: pid, status: "active", reader, signal });
166213
166365
  if (result.entries.length === 0) return [];
166214
166366
  const details = await compactDetail(result.entries.map((e2) => e2.id), { reader });
166215
166367
  return details.documents.map((d, i2) => ({
@@ -166230,8 +166382,11 @@ Auto-enriched: ${enrichmentParts.join(", ")}` : "";
166230
166382
  },
166231
166383
  getEntityNames: () => graphManager.getEntityNames()
166232
166384
  };
166233
- const formed = await withTimeout(
166234
- runFormation({ entityName, type, title, narrative, facts: safeFacts, projectId: project.id, source: "explicit", topicKey }, formationConfig),
166385
+ const formed = await withTimeoutSignal(
166386
+ async (signal) => {
166387
+ formationConfig.signal = signal;
166388
+ return runFormation({ entityName, type, title, narrative, facts: safeFacts, projectId: project.id, source: "explicit", topicKey }, formationConfig);
166389
+ },
166235
166390
  FORMATION_TIMEOUT_MS,
166236
166391
  "Shadow formation"
166237
166392
  );
package/dist/types.d.ts CHANGED
@@ -360,6 +360,8 @@ interface SearchOptions {
360
360
  * `thorough` explicitly permits optional LLM query rewrite and reranking.
361
361
  */
362
362
  quality?: RetrievalQuality;
363
+ /** Abort provider-backed retrieval when the owning request is cancelled. */
364
+ signal?: AbortSignal;
363
365
  }
364
366
  /** Topic key family heuristics for suggesting stable topic keys */
365
367
  declare const TOPIC_KEY_FAMILIES: Record<string, string[]>;