memorix 1.5.0 → 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 (117) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/TEAM.md +86 -86
  3. package/dist/cli/index.js +211 -69
  4. package/dist/cli/memcode.js +0 -0
  5. package/dist/index.js +211 -69
  6. package/dist/maintenance-runner.js +169 -37
  7. package/dist/media-audio-runner.js +169 -37
  8. package/dist/media-video-runner.js +169 -37
  9. package/dist/memcode-runtime/CHANGELOG.md +14 -0
  10. package/dist/sdk.js +210 -68
  11. package/dist/types.d.ts +2 -0
  12. package/dist/vector-backfill-runner.js +169 -37
  13. package/docs/DESIGN_DECISIONS.md +357 -357
  14. package/package.json +1 -1
  15. package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
  16. package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
  17. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  18. package/plugins/copilot/memorix/plugin.json +1 -1
  19. package/plugins/gemini/memorix/gemini-extension.json +1 -1
  20. package/plugins/omp/memorix/package.json +1 -1
  21. package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
  22. package/plugins/pi/memorix/package.json +1 -1
  23. package/server.json +2 -2
  24. package/src/audit/index.ts +156 -156
  25. package/src/cli/commands/formation.ts +48 -48
  26. package/src/cli/commands/git-hook-install.ts +111 -111
  27. package/src/cli/commands/handoff.ts +54 -54
  28. package/src/cli/commands/hooks-status.ts +63 -63
  29. package/src/cli/commands/ingest.ts +44 -44
  30. package/src/cli/commands/integrate-shared.ts +15 -15
  31. package/src/cli/commands/lock.ts +82 -82
  32. package/src/cli/commands/message.ts +104 -104
  33. package/src/cli/commands/poll.ts +58 -58
  34. package/src/cli/commands/reasoning.ts +118 -118
  35. package/src/cli/commands/skills.ts +114 -114
  36. package/src/cli/commands/task.ts +167 -167
  37. package/src/cli/commands/transfer.ts +47 -47
  38. package/src/cli/index.ts +406 -406
  39. package/src/cli/tui/ChatView.tsx +234 -234
  40. package/src/cli/tui/CommandBar.tsx +312 -312
  41. package/src/cli/tui/ContextRail.tsx +118 -118
  42. package/src/cli/tui/HeaderBar.tsx +72 -72
  43. package/src/cli/tui/LogoBanner.tsx +51 -51
  44. package/src/cli/tui/index.ts +41 -41
  45. package/src/cli/version.ts +7 -7
  46. package/src/cli/workbench.ts +1 -1
  47. package/src/dashboard/project-classification.ts +64 -64
  48. package/src/embedding/api-provider.ts +105 -22
  49. package/src/embedding/provider.ts +2 -0
  50. package/src/git/hooks-path.ts +85 -85
  51. package/src/hooks/pattern-detector.ts +173 -173
  52. package/src/hooks/significance-filter.ts +250 -250
  53. package/src/llm/memory-manager.ts +328 -328
  54. package/src/llm/provider.ts +17 -8
  55. package/src/llm/quality.ts +248 -248
  56. package/src/memory/attribution-guard.ts +249 -249
  57. package/src/memory/disclosure-policy.ts +135 -135
  58. package/src/memory/entity-extractor.ts +197 -197
  59. package/src/memory/formation/evaluate.ts +217 -217
  60. package/src/memory/formation/extract.ts +6 -3
  61. package/src/memory/formation/index.ts +2 -1
  62. package/src/memory/formation/resolve.ts +10 -6
  63. package/src/memory/formation/types.ts +3 -1
  64. package/src/memory/freshness.ts +122 -122
  65. package/src/memory/graph.ts +197 -197
  66. package/src/memory/long-term.ts +7 -1
  67. package/src/memory/refs.ts +94 -94
  68. package/src/memory/secret-filter.ts +79 -79
  69. package/src/orchestrate/adapters/claude-stream.ts +192 -192
  70. package/src/orchestrate/adapters/claude.ts +111 -111
  71. package/src/orchestrate/adapters/codex-stream.ts +134 -134
  72. package/src/orchestrate/adapters/codex.ts +41 -41
  73. package/src/orchestrate/adapters/gemini-stream.ts +166 -166
  74. package/src/orchestrate/adapters/gemini.ts +42 -42
  75. package/src/orchestrate/adapters/index.ts +73 -73
  76. package/src/orchestrate/adapters/opencode-stream.ts +143 -143
  77. package/src/orchestrate/adapters/opencode.ts +47 -47
  78. package/src/orchestrate/adapters/types.ts +77 -77
  79. package/src/orchestrate/capability-router.ts +284 -284
  80. package/src/orchestrate/cost-tracker.ts +219 -219
  81. package/src/orchestrate/error-recovery.ts +191 -191
  82. package/src/orchestrate/evidence.ts +140 -140
  83. package/src/orchestrate/ledger.ts +110 -110
  84. package/src/orchestrate/memorix-bridge.ts +343 -343
  85. package/src/orchestrate/output-budget.ts +80 -80
  86. package/src/orchestrate/permission.ts +152 -152
  87. package/src/orchestrate/pipeline-trace.ts +131 -131
  88. package/src/orchestrate/prompt-builder.ts +155 -155
  89. package/src/orchestrate/ring-buffer.ts +37 -37
  90. package/src/orchestrate/task-graph.ts +389 -389
  91. package/src/project/aliases.ts +374 -374
  92. package/src/rules/adapters/trae.ts +56 -56
  93. package/src/rules/syncer.ts +235 -235
  94. package/src/search/query-expansion.ts +52 -52
  95. package/src/server/formation-timeout.ts +27 -27
  96. package/src/server.ts +14 -10
  97. package/src/skills/mini-skills.ts +386 -386
  98. package/src/store/chat-store.ts +119 -119
  99. package/src/store/graph-store.ts +249 -249
  100. package/src/store/mini-skill-store.ts +349 -349
  101. package/src/store/orama-store.ts +6 -4
  102. package/src/store/persistence-json.ts +212 -212
  103. package/src/store/persistence.ts +291 -291
  104. package/src/store/project-affinity.ts +195 -195
  105. package/src/team/event-bus.ts +76 -76
  106. package/src/team/handoff.ts +161 -161
  107. package/src/team/poll.ts +132 -132
  108. package/src/timeout.ts +67 -0
  109. package/src/types.ts +2 -0
  110. package/src/workspace/mcp-adapters/codex.ts +191 -191
  111. package/src/workspace/mcp-adapters/copilot.ts +105 -105
  112. package/src/workspace/mcp-adapters/cursor.ts +53 -53
  113. package/src/workspace/mcp-adapters/kiro.ts +64 -64
  114. package/src/workspace/mcp-adapters/opencode.ts +123 -123
  115. package/src/workspace/mcp-adapters/trae.ts +134 -134
  116. package/src/workspace/mcp-adapters/windsurf.ts +91 -91
  117. package/src/workspace/sanitizer.ts +60 -60
package/dist/sdk.js CHANGED
@@ -3991,6 +3991,8 @@ function getPreferredBatchSize(config) {
3991
3991
  }
3992
3992
  function parseBatchLimit(error) {
3993
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;
3994
3996
  const explicit = error.message.match(/should not be larger than\s+(\d+)/i);
3995
3997
  if (explicit) return parseInt(explicit[1], 10);
3996
3998
  if (/batch size/i.test(error.message)) {
@@ -4003,37 +4005,99 @@ async function fetchWithRetry(url, apiKey, body, options = {}, attempt = 0, nati
4003
4005
  const controller = new AbortController();
4004
4006
  const timeoutMs = typeof options.timeoutMs === "number" && Number.isFinite(options.timeoutMs) ? Math.max(100, Math.min(Math.floor(options.timeoutMs), 6e4)) : 1e4;
4005
4007
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
4008
+ const signal = options.signal ? AbortSignal.any([options.signal, controller.signal]) : controller.signal;
4006
4009
  let response;
4007
4010
  try {
4008
4011
  response = await fetch(url, {
4009
4012
  method: "POST",
4010
4013
  headers: nativeGemini ? { "Content-Type": "application/json", "x-goog-api-key": apiKey } : { "Content-Type": "application/json", "Authorization": `Bearer ${apiKey}` },
4011
4014
  body: JSON.stringify(body),
4012
- signal: controller.signal
4015
+ signal
4013
4016
  });
4014
4017
  } catch (err) {
4015
4018
  clearTimeout(timeout);
4016
- if (err instanceof Error && err.name === "AbortError") {
4019
+ if (err instanceof Error && (err.name === "AbortError" || controller.signal.aborted || options.signal?.aborted)) {
4017
4020
  throw new Error(`Embedding API timeout after ${timeoutMs}ms: ${url}`);
4018
4021
  }
4019
4022
  throw err;
4020
4023
  }
4021
- clearTimeout(timeout);
4022
- if (response.ok) {
4023
- 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);
4024
4052
  }
4025
- if (options.retry !== false && (response.status === 429 || response.status >= 500) && attempt < MAX_RETRIES2) {
4026
- const delay2 = BASE_DELAY_MS * Math.pow(2, attempt);
4027
- const retryAfter = response.headers.get("retry-after");
4028
- const waitMs = retryAfter ? parseInt(retryAfter, 10) * 1e3 : delay2;
4029
- 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) {
4030
4081
  await new Promise((resolve2) => setTimeout(resolve2, waitMs));
4031
- return fetchWithRetry(url, apiKey, body, options, attempt + 1, nativeGemini);
4082
+ return;
4032
4083
  }
4033
- const errorText2 = await response.text().catch(() => "unknown error");
4034
- 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
+ });
4035
4099
  }
4036
- 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;
4037
4101
  var init_api_provider = __esm({
4038
4102
  "src/embedding/api-provider.ts"() {
4039
4103
  "use strict";
@@ -4052,6 +4116,14 @@ var init_api_provider = __esm({
4052
4116
  DASHSCOPE_MAX_BATCH_SIZE = 10;
4053
4117
  MAX_RETRIES2 = 3;
4054
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
+ };
4055
4127
  APIEmbeddingProvider = class _APIEmbeddingProvider {
4056
4128
  name;
4057
4129
  dimensions;
@@ -4263,15 +4335,14 @@ var init_api_provider = __esm({
4263
4335
  }
4264
4336
  } catch (error) {
4265
4337
  const providerLimit = parseBatchLimit(error);
4266
- const fallbackSize = providerLimit ?? Math.ceil(chunkTexts.length / 2);
4267
- if (chunkTexts.length > 1 && fallbackSize < chunkTexts.length) {
4338
+ if (providerLimit !== null && chunkTexts.length > 1 && providerLimit < chunkTexts.length) {
4268
4339
  console.error(
4269
- `[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}`
4270
4341
  );
4271
- for (let start = 0; start < chunkTexts.length; start += fallbackSize) {
4342
+ for (let start = 0; start < chunkTexts.length; start += providerLimit) {
4272
4343
  await processChunk(
4273
- chunkTexts.slice(start, start + fallbackSize),
4274
- chunkIndices.slice(start, start + fallbackSize)
4344
+ chunkTexts.slice(start, start + providerLimit),
4345
+ chunkIndices.slice(start, start + providerLimit)
4275
4346
  );
4276
4347
  }
4277
4348
  return;
@@ -5135,22 +5206,23 @@ function getLLMConfig() {
5135
5206
  function setLLMConfig(config) {
5136
5207
  currentConfig = config;
5137
5208
  }
5138
- async function callLLM(systemPrompt, userMessage) {
5209
+ async function callLLM(systemPrompt, userMessage, signal) {
5139
5210
  if (!currentConfig) {
5140
5211
  throw new Error("LLM not configured. Set MEMORIX_LLM_API_KEY or OPENAI_API_KEY.");
5141
5212
  }
5142
5213
  if (currentConfig.provider === "anthropic") {
5143
- return callAnthropic(systemPrompt, userMessage);
5214
+ return callAnthropic(systemPrompt, userMessage, signal);
5144
5215
  }
5145
- return callOpenAICompatible(systemPrompt, userMessage);
5216
+ return callOpenAICompatible(systemPrompt, userMessage, signal);
5146
5217
  }
5147
- async function callOpenAICompatible(systemPrompt, userMessage) {
5218
+ async function callOpenAICompatible(systemPrompt, userMessage, signal) {
5148
5219
  const config = currentConfig;
5149
5220
  let base = config.baseUrl.replace(/\/+$/, "");
5150
5221
  if (!base.endsWith("/v1")) base += "/v1";
5151
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);
5152
5224
  const response = await fetch(url, {
5153
- signal: AbortSignal.timeout(LLM_CALL_TIMEOUT_MS),
5225
+ signal: fetchSignal,
5154
5226
  method: "POST",
5155
5227
  headers: {
5156
5228
  "Content-Type": "application/json",
@@ -5167,10 +5239,10 @@ async function callOpenAICompatible(systemPrompt, userMessage) {
5167
5239
  })
5168
5240
  });
5169
5241
  if (!response.ok) {
5170
- const error = await readResponseText(response, void 0, 64 * 1024).catch(() => "unknown error");
5242
+ const error = await readResponseText(response, signal, 64 * 1024).catch(() => "unknown error");
5171
5243
  throw new Error(`LLM API error (${response.status}): ${error}`);
5172
5244
  }
5173
- const data = await readResponseJson(response);
5245
+ const data = await readResponseJson(response, signal);
5174
5246
  return {
5175
5247
  content: data.choices[0]?.message?.content ?? "",
5176
5248
  usage: data.usage ? {
@@ -5179,11 +5251,12 @@ async function callOpenAICompatible(systemPrompt, userMessage) {
5179
5251
  } : void 0
5180
5252
  };
5181
5253
  }
5182
- async function callAnthropic(systemPrompt, userMessage) {
5254
+ async function callAnthropic(systemPrompt, userMessage, signal) {
5183
5255
  const config = currentConfig;
5184
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);
5185
5258
  const response = await fetch(url, {
5186
- signal: AbortSignal.timeout(LLM_CALL_TIMEOUT_MS),
5259
+ signal: fetchSignal,
5187
5260
  method: "POST",
5188
5261
  headers: {
5189
5262
  "Content-Type": "application/json",
@@ -5201,10 +5274,10 @@ async function callAnthropic(systemPrompt, userMessage) {
5201
5274
  })
5202
5275
  });
5203
5276
  if (!response.ok) {
5204
- const error = await readResponseText(response, void 0, 64 * 1024).catch(() => "unknown error");
5277
+ const error = await readResponseText(response, signal, 64 * 1024).catch(() => "unknown error");
5205
5278
  throw new Error(`Anthropic API error (${response.status}): ${error}`);
5206
5279
  }
5207
- const data = await readResponseJson(response);
5280
+ const data = await readResponseJson(response, signal);
5208
5281
  return {
5209
5282
  content: data.content[0]?.text ?? "",
5210
5283
  usage: data.usage ? {
@@ -5709,6 +5782,57 @@ function withTimeout(promise, ms, label) {
5709
5782
  );
5710
5783
  });
5711
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
+ }
5712
5836
  var init_timeout = __esm({
5713
5837
  "src/timeout.ts"() {
5714
5838
  "use strict";
@@ -6573,16 +6697,18 @@ async function searchObservations(options) {
6573
6697
  );
6574
6698
  } else {
6575
6699
  const EMBEDDING_TIMEOUT_MS = 15e3;
6576
- queryVector = await withTimeout(
6577
- provider2.embedInput ? provider2.embedInput(
6700
+ queryVector = await withTimeoutSignal(
6701
+ (signal) => provider2.embedInput ? provider2.embedInput(
6578
6702
  { modality: "text", text: expandedEmbeddingQuery },
6579
- { intent: "query", timeoutMs: EMBEDDING_TIMEOUT_MS, retry: false }
6703
+ { intent: "query", timeoutMs: EMBEDDING_TIMEOUT_MS, retry: false, signal }
6580
6704
  ) : provider2.embed(expandedEmbeddingQuery, {
6581
6705
  timeoutMs: EMBEDDING_TIMEOUT_MS,
6582
- retry: false
6706
+ retry: false,
6707
+ signal
6583
6708
  }),
6584
6709
  EMBEDDING_TIMEOUT_MS,
6585
- "Embedding"
6710
+ "Embedding",
6711
+ options.signal
6586
6712
  );
6587
6713
  mark("embedding");
6588
6714
  const cjkRatio = (originalQuery.match(/[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/g) || []).length / originalQuery.length;
@@ -145002,7 +145128,13 @@ async function maintainLongTermMemories(input) {
145002
145128
  }
145003
145129
  for (const group of groups.values()) {
145004
145130
  if (group.length < 2) continue;
145005
- 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
+ });
145006
145138
  const newest = group[0];
145007
145139
  for (const older of group.slice(1)) {
145008
145140
  try {
@@ -151488,13 +151620,13 @@ function verifyType(declaredType, narrative, title) {
151488
151620
  }
151489
151621
  return { type: declaredType, corrected: false };
151490
151622
  }
151491
- async function extractFactsWithLLM(narrative, title, existingFacts) {
151623
+ async function extractFactsWithLLM(narrative, title, existingFacts, signal) {
151492
151624
  try {
151493
151625
  const { callLLM: callLLM2 } = await Promise.resolve().then(() => (init_provider2(), provider_exports2));
151494
151626
  const input = `Title: ${title}
151495
151627
  Content: ${narrative}${existingFacts.length > 0 ? `
151496
151628
  Already known facts (don't repeat): ${existingFacts.join("; ")}` : ""}`;
151497
- const response = await callLLM2(LLM_EXTRACT_PROMPT, input);
151629
+ const response = await callLLM2(LLM_EXTRACT_PROMPT, input, signal);
151498
151630
  const text2 = response.content.trim();
151499
151631
  const jsonMatch = text2.match(/\{[\s\S]*\}/);
151500
151632
  if (!jsonMatch) return [];
@@ -151503,15 +151635,16 @@ Already known facts (don't repeat): ${existingFacts.join("; ")}` : ""}`;
151503
151635
  if (!Array.isArray(facts)) return [];
151504
151636
  const existingLower = new Set(existingFacts.map((f3) => f3.toLowerCase().trim()));
151505
151637
  return facts.filter((f3) => typeof f3 === "string" && f3.length >= 5).filter((f3) => !existingLower.has(f3.toLowerCase().trim())).slice(0, 10);
151506
- } catch {
151638
+ } catch (error) {
151639
+ if (signal?.aborted) throw error;
151507
151640
  return [];
151508
151641
  }
151509
151642
  }
151510
- async function runExtract(input, existingEntities, useLLM = false) {
151643
+ async function runExtract(input, existingEntities, useLLM = false, signal) {
151511
151644
  const callerFacts = input.facts ?? [];
151512
151645
  let extractedFacts;
151513
151646
  if (useLLM) {
151514
- extractedFacts = await extractFactsWithLLM(input.narrative, input.title, callerFacts);
151647
+ extractedFacts = await extractFactsWithLLM(input.narrative, input.title, callerFacts, signal);
151515
151648
  if (extractedFacts.length === 0) {
151516
151649
  extractedFacts = extractFacts2(input.narrative, callerFacts);
151517
151650
  }
@@ -151731,7 +151864,7 @@ function mergeFacts2(oldFacts, newFacts) {
151731
151864
  }
151732
151865
  return merged;
151733
151866
  }
151734
- async function resolveWithLLM(extracted, hits, getObservation2) {
151867
+ async function resolveWithLLM(extracted, hits, getObservation2, signal) {
151735
151868
  try {
151736
151869
  const { callLLM: callLLM2 } = await Promise.resolve().then(() => (init_provider2(), provider_exports2));
151737
151870
  const existingMemories = hits.slice(0, 5).map((h2, i2) => ({
@@ -151748,7 +151881,7 @@ Facts: ${extracted.facts.join("; ")}
151748
151881
 
151749
151882
  EXISTING MEMORIES:
151750
151883
  ${existingMemories.map((m2) => `[ID:${m2.id}] ${m2.title} | ${m2.content} | Facts: ${m2.facts}`).join("\n")}`;
151751
- const response = await callLLM2(LLM_RESOLVE_PROMPT, input);
151884
+ const response = await callLLM2(LLM_RESOLVE_PROMPT, input, signal);
151752
151885
  const text2 = response.content.trim();
151753
151886
  const jsonMatch = text2.match(/\{[\s\S]*\}/);
151754
151887
  if (!jsonMatch) return null;
@@ -151785,7 +151918,8 @@ ${existingMemories.map((m2) => `[ID:${m2.id}] ${m2.title} | ${m2.content} | Fact
151785
151918
  };
151786
151919
  }
151787
151920
  return null;
151788
- } catch {
151921
+ } catch (error) {
151922
+ if (signal?.aborted) throw error;
151789
151923
  return null;
151790
151924
  }
151791
151925
  }
@@ -151803,19 +151937,20 @@ function scoreCandidate(extracted, candidate) {
151803
151937
  const contradiction = hasContradiction(candidate.narrative, extracted.narrative);
151804
151938
  return { score, searchSimilarity, entityMatch, richer, contradiction };
151805
151939
  }
151806
- async function runResolve(extracted, projectId, searchMemories, getObservation2, useLLM = false) {
151940
+ async function runResolve(extracted, projectId, searchMemories, getObservation2, useLLM = false, signal) {
151807
151941
  const query = `${extracted.title} ${extracted.narrative.substring(0, 200)}`;
151808
151942
  let hits;
151809
151943
  try {
151810
- hits = await searchMemories(query, 5, projectId);
151811
- } catch {
151944
+ hits = await searchMemories(query, 5, projectId, signal);
151945
+ } catch (error) {
151946
+ if (signal?.aborted) throw error;
151812
151947
  return { action: "new", reason: "Search unavailable, defaulting to new" };
151813
151948
  }
151814
151949
  if (hits.length === 0) {
151815
151950
  return { action: "new", reason: "No similar existing memories found" };
151816
151951
  }
151817
151952
  if (useLLM) {
151818
- const llmResult = await resolveWithLLM(extracted, hits, getObservation2);
151953
+ const llmResult = await resolveWithLLM(extracted, hits, getObservation2, signal);
151819
151954
  if (llmResult) return llmResult;
151820
151955
  }
151821
151956
  const scored = hits.map((hit) => ({
@@ -152237,7 +152372,7 @@ async function runFormation(input, config) {
152237
152372
  const existingEntities = config.getEntityNames();
152238
152373
  const extractStartTime = Date.now();
152239
152374
  emitStageEvent("extract", "start");
152240
- const extraction = await runExtract(input, existingEntities, config.useLLM);
152375
+ const extraction = await runExtract(input, existingEntities, config.useLLM, config.signal);
152241
152376
  stageDurationsMs.extract = Date.now() - extractStartTime;
152242
152377
  emitStageEvent("extract", "success", stageDurationsMs.extract);
152243
152378
  stagesCompleted = 1;
@@ -152257,7 +152392,8 @@ async function runFormation(input, config) {
152257
152392
  input.projectId,
152258
152393
  config.searchMemories,
152259
152394
  config.getObservation,
152260
- config.useLLM
152395
+ config.useLLM,
152396
+ config.signal
152261
152397
  );
152262
152398
  stageDurationsMs.resolve = Date.now() - resolveStartTime;
152263
152399
  emitStageEvent("resolve", "success", stageDurationsMs.resolve);
@@ -165642,7 +165778,7 @@ The path should point to a directory containing a .git folder.`
165642
165778
  };
165643
165779
  const server = existingServer ?? new McpServer({
165644
165780
  name: "memorix",
165645
- version: true ? "1.5.0" : "1.0.1"
165781
+ version: true ? "1.5.1" : "1.0.1"
165646
165782
  });
165647
165783
  const originalRegisterTool = server.registerTool.bind(server);
165648
165784
  server.registerTool = ((name, ...args) => {
@@ -165831,8 +165967,8 @@ The path should point to a directory containing a .git folder.`
165831
165967
  mode: "active",
165832
165968
  useLLM: isLLMEnabled(),
165833
165969
  minValueScore: 0.3,
165834
- searchMemories: async (q, limit2, pid) => {
165835
- 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 });
165836
165972
  if (result.entries.length === 0) return [];
165837
165973
  const details = await compactDetail(result.entries.map((e2) => e2.id), { reader });
165838
165974
  return details.documents.map((d, i2) => ({
@@ -165862,16 +165998,19 @@ The path should point to a directory containing a .git folder.`
165862
165998
  getEntityNames: () => graphManager.getEntityNames(),
165863
165999
  onStageEvent: onFormationStageEvent
165864
166000
  };
165865
- formationResult = await withTimeout(
165866
- runFormation({
165867
- entityName,
165868
- type,
165869
- title,
165870
- narrative,
165871
- facts: safeFacts,
165872
- projectId: project.id,
165873
- source: "explicit"
165874
- }, 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
+ },
165875
166014
  FORMATION_TIMEOUT_MS,
165876
166015
  "Formation pipeline"
165877
166016
  );
@@ -166221,8 +166360,8 @@ Auto-enriched: ${enrichmentParts.join(", ")}` : "";
166221
166360
  mode: formationMode,
166222
166361
  useLLM: isLLMEnabled(),
166223
166362
  minValueScore: 0.3,
166224
- searchMemories: async (q, limit2, pid) => {
166225
- 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 });
166226
166365
  if (result.entries.length === 0) return [];
166227
166366
  const details = await compactDetail(result.entries.map((e2) => e2.id), { reader });
166228
166367
  return details.documents.map((d, i2) => ({
@@ -166243,8 +166382,11 @@ Auto-enriched: ${enrichmentParts.join(", ")}` : "";
166243
166382
  },
166244
166383
  getEntityNames: () => graphManager.getEntityNames()
166245
166384
  };
166246
- const formed = await withTimeout(
166247
- 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
+ },
166248
166390
  FORMATION_TIMEOUT_MS,
166249
166391
  "Shadow formation"
166250
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[]>;