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/index.js CHANGED
@@ -3987,6 +3987,8 @@ function getPreferredBatchSize(config) {
3987
3987
  }
3988
3988
  function parseBatchLimit(error) {
3989
3989
  if (!(error instanceof Error)) return null;
3990
+ const status = error instanceof EmbeddingAPIError ? error.status : Number(error.message.match(/Embedding API error \((\d{3})\)/i)?.[1] ?? 0);
3991
+ if (status > 0 && status !== 400) return null;
3990
3992
  const explicit = error.message.match(/should not be larger than\s+(\d+)/i);
3991
3993
  if (explicit) return parseInt(explicit[1], 10);
3992
3994
  if (/batch size/i.test(error.message)) {
@@ -3999,37 +4001,99 @@ async function fetchWithRetry(url, apiKey, body, options = {}, attempt = 0, nati
3999
4001
  const controller = new AbortController();
4000
4002
  const timeoutMs = typeof options.timeoutMs === "number" && Number.isFinite(options.timeoutMs) ? Math.max(100, Math.min(Math.floor(options.timeoutMs), 6e4)) : 1e4;
4001
4003
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
4004
+ const signal = options.signal ? AbortSignal.any([options.signal, controller.signal]) : controller.signal;
4002
4005
  let response;
4003
4006
  try {
4004
4007
  response = await fetch(url, {
4005
4008
  method: "POST",
4006
4009
  headers: nativeGemini ? { "Content-Type": "application/json", "x-goog-api-key": apiKey } : { "Content-Type": "application/json", "Authorization": `Bearer ${apiKey}` },
4007
4010
  body: JSON.stringify(body),
4008
- signal: controller.signal
4011
+ signal
4009
4012
  });
4010
4013
  } catch (err) {
4011
4014
  clearTimeout(timeout);
4012
- if (err instanceof Error && err.name === "AbortError") {
4015
+ if (err instanceof Error && (err.name === "AbortError" || controller.signal.aborted || options.signal?.aborted)) {
4013
4016
  throw new Error(`Embedding API timeout after ${timeoutMs}ms: ${url}`);
4014
4017
  }
4015
4018
  throw err;
4016
4019
  }
4017
- clearTimeout(timeout);
4018
- if (response.ok) {
4019
- return response.json();
4020
+ try {
4021
+ if (response.ok) {
4022
+ return await raceWithAbort(
4023
+ response.json(),
4024
+ signal
4025
+ );
4026
+ }
4027
+ if (options.retry !== false && (response.status === 429 || response.status >= 500) && attempt < MAX_RETRIES2) {
4028
+ const delay2 = BASE_DELAY_MS * Math.pow(2, attempt);
4029
+ const retryAfter = response.headers.get("retry-after");
4030
+ const parsedRetryAfter = retryAfter ? parseInt(retryAfter, 10) * 1e3 : NaN;
4031
+ const waitMs = Number.isFinite(parsedRetryAfter) ? parsedRetryAfter : delay2;
4032
+ console.error(`[memorix] Embedding API ${response.status}, retry ${attempt + 1}/${MAX_RETRIES2} in ${waitMs}ms`);
4033
+ await waitForRetry(waitMs, options.signal);
4034
+ return fetchWithRetry(url, apiKey, body, options, attempt + 1, nativeGemini);
4035
+ }
4036
+ const errorText2 = await raceWithAbort(
4037
+ response.text().catch(() => "unknown error"),
4038
+ signal
4039
+ );
4040
+ throw new EmbeddingAPIError(response.status, errorText2);
4041
+ } catch (err) {
4042
+ if (err instanceof Error && (err.name === "AbortError" || controller.signal.aborted || options.signal?.aborted)) {
4043
+ throw new Error(`Embedding API timeout after ${timeoutMs}ms: ${url}`);
4044
+ }
4045
+ throw err;
4046
+ } finally {
4047
+ clearTimeout(timeout);
4020
4048
  }
4021
- if (options.retry !== false && (response.status === 429 || response.status >= 500) && attempt < MAX_RETRIES2) {
4022
- const delay2 = BASE_DELAY_MS * Math.pow(2, attempt);
4023
- const retryAfter = response.headers.get("retry-after");
4024
- const waitMs = retryAfter ? parseInt(retryAfter, 10) * 1e3 : delay2;
4025
- console.error(`[memorix] Embedding API ${response.status}, retry ${attempt + 1}/${MAX_RETRIES2} in ${waitMs}ms`);
4049
+ }
4050
+ function abortError() {
4051
+ const error = new Error("Embedding API request aborted");
4052
+ error.name = "AbortError";
4053
+ return error;
4054
+ }
4055
+ function raceWithAbort(promise, signal) {
4056
+ if (signal.aborted) return Promise.reject(abortError());
4057
+ return new Promise((resolve2, reject) => {
4058
+ const onAbort = () => {
4059
+ signal.removeEventListener("abort", onAbort);
4060
+ reject(abortError());
4061
+ };
4062
+ signal.addEventListener("abort", onAbort, { once: true });
4063
+ promise.then(
4064
+ (value) => {
4065
+ signal.removeEventListener("abort", onAbort);
4066
+ resolve2(value);
4067
+ },
4068
+ (error) => {
4069
+ signal.removeEventListener("abort", onAbort);
4070
+ reject(error);
4071
+ }
4072
+ );
4073
+ });
4074
+ }
4075
+ async function waitForRetry(waitMs, signal) {
4076
+ if (!signal) {
4026
4077
  await new Promise((resolve2) => setTimeout(resolve2, waitMs));
4027
- return fetchWithRetry(url, apiKey, body, options, attempt + 1, nativeGemini);
4078
+ return;
4028
4079
  }
4029
- const errorText2 = await response.text().catch(() => "unknown error");
4030
- throw new Error(`Embedding API error (${response.status}): ${errorText2}`);
4080
+ const abortSignal = signal;
4081
+ if (abortSignal.aborted) throw new Error("Embedding API retry aborted");
4082
+ await new Promise((resolve2, reject) => {
4083
+ const timer = setTimeout(done, waitMs);
4084
+ const onAbort = () => {
4085
+ clearTimeout(timer);
4086
+ abortSignal.removeEventListener("abort", onAbort);
4087
+ reject(new Error("Embedding API retry aborted"));
4088
+ };
4089
+ function done() {
4090
+ abortSignal.removeEventListener("abort", onAbort);
4091
+ resolve2();
4092
+ }
4093
+ abortSignal.addEventListener("abort", onAbort, { once: true });
4094
+ });
4031
4095
  }
4032
- 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;
4096
+ 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;
4033
4097
  var init_api_provider = __esm({
4034
4098
  "src/embedding/api-provider.ts"() {
4035
4099
  "use strict";
@@ -4048,6 +4112,14 @@ var init_api_provider = __esm({
4048
4112
  DASHSCOPE_MAX_BATCH_SIZE = 10;
4049
4113
  MAX_RETRIES2 = 3;
4050
4114
  BASE_DELAY_MS = 500;
4115
+ EmbeddingAPIError = class extends Error {
4116
+ status;
4117
+ constructor(status, detail) {
4118
+ super(`Embedding API error (${status}): ${detail}`);
4119
+ this.name = "EmbeddingAPIError";
4120
+ this.status = status;
4121
+ }
4122
+ };
4051
4123
  APIEmbeddingProvider = class _APIEmbeddingProvider {
4052
4124
  name;
4053
4125
  dimensions;
@@ -4259,15 +4331,14 @@ var init_api_provider = __esm({
4259
4331
  }
4260
4332
  } catch (error) {
4261
4333
  const providerLimit = parseBatchLimit(error);
4262
- const fallbackSize = providerLimit ?? Math.ceil(chunkTexts.length / 2);
4263
- if (chunkTexts.length > 1 && fallbackSize < chunkTexts.length) {
4334
+ if (providerLimit !== null && chunkTexts.length > 1 && providerLimit < chunkTexts.length) {
4264
4335
  console.error(
4265
- `[memorix] Embedding batch too large for provider, retrying in chunks of ${fallbackSize}`
4336
+ `[memorix] Embedding batch too large for provider, retrying in chunks of ${providerLimit}`
4266
4337
  );
4267
- for (let start = 0; start < chunkTexts.length; start += fallbackSize) {
4338
+ for (let start = 0; start < chunkTexts.length; start += providerLimit) {
4268
4339
  await processChunk(
4269
- chunkTexts.slice(start, start + fallbackSize),
4270
- chunkIndices.slice(start, start + fallbackSize)
4340
+ chunkTexts.slice(start, start + providerLimit),
4341
+ chunkIndices.slice(start, start + providerLimit)
4271
4342
  );
4272
4343
  }
4273
4344
  return;
@@ -5131,22 +5202,23 @@ function getLLMConfig() {
5131
5202
  function setLLMConfig(config) {
5132
5203
  currentConfig = config;
5133
5204
  }
5134
- async function callLLM(systemPrompt, userMessage) {
5205
+ async function callLLM(systemPrompt, userMessage, signal) {
5135
5206
  if (!currentConfig) {
5136
5207
  throw new Error("LLM not configured. Set MEMORIX_LLM_API_KEY or OPENAI_API_KEY.");
5137
5208
  }
5138
5209
  if (currentConfig.provider === "anthropic") {
5139
- return callAnthropic(systemPrompt, userMessage);
5210
+ return callAnthropic(systemPrompt, userMessage, signal);
5140
5211
  }
5141
- return callOpenAICompatible(systemPrompt, userMessage);
5212
+ return callOpenAICompatible(systemPrompt, userMessage, signal);
5142
5213
  }
5143
- async function callOpenAICompatible(systemPrompt, userMessage) {
5214
+ async function callOpenAICompatible(systemPrompt, userMessage, signal) {
5144
5215
  const config = currentConfig;
5145
5216
  let base = config.baseUrl.replace(/\/+$/, "");
5146
5217
  if (!base.endsWith("/v1")) base += "/v1";
5147
5218
  const url = `${base}/chat/completions`;
5219
+ const fetchSignal = signal ? AbortSignal.any([signal, AbortSignal.timeout(LLM_CALL_TIMEOUT_MS)]) : AbortSignal.timeout(LLM_CALL_TIMEOUT_MS);
5148
5220
  const response = await fetch(url, {
5149
- signal: AbortSignal.timeout(LLM_CALL_TIMEOUT_MS),
5221
+ signal: fetchSignal,
5150
5222
  method: "POST",
5151
5223
  headers: {
5152
5224
  "Content-Type": "application/json",
@@ -5163,10 +5235,10 @@ async function callOpenAICompatible(systemPrompt, userMessage) {
5163
5235
  })
5164
5236
  });
5165
5237
  if (!response.ok) {
5166
- const error = await readResponseText(response, void 0, 64 * 1024).catch(() => "unknown error");
5238
+ const error = await readResponseText(response, signal, 64 * 1024).catch(() => "unknown error");
5167
5239
  throw new Error(`LLM API error (${response.status}): ${error}`);
5168
5240
  }
5169
- const data = await readResponseJson(response);
5241
+ const data = await readResponseJson(response, signal);
5170
5242
  return {
5171
5243
  content: data.choices[0]?.message?.content ?? "",
5172
5244
  usage: data.usage ? {
@@ -5175,11 +5247,12 @@ async function callOpenAICompatible(systemPrompt, userMessage) {
5175
5247
  } : void 0
5176
5248
  };
5177
5249
  }
5178
- async function callAnthropic(systemPrompt, userMessage) {
5250
+ async function callAnthropic(systemPrompt, userMessage, signal) {
5179
5251
  const config = currentConfig;
5180
5252
  const url = `${config.baseUrl}/messages`;
5253
+ const fetchSignal = signal ? AbortSignal.any([signal, AbortSignal.timeout(LLM_CALL_TIMEOUT_MS)]) : AbortSignal.timeout(LLM_CALL_TIMEOUT_MS);
5181
5254
  const response = await fetch(url, {
5182
- signal: AbortSignal.timeout(LLM_CALL_TIMEOUT_MS),
5255
+ signal: fetchSignal,
5183
5256
  method: "POST",
5184
5257
  headers: {
5185
5258
  "Content-Type": "application/json",
@@ -5197,10 +5270,10 @@ async function callAnthropic(systemPrompt, userMessage) {
5197
5270
  })
5198
5271
  });
5199
5272
  if (!response.ok) {
5200
- const error = await readResponseText(response, void 0, 64 * 1024).catch(() => "unknown error");
5273
+ const error = await readResponseText(response, signal, 64 * 1024).catch(() => "unknown error");
5201
5274
  throw new Error(`Anthropic API error (${response.status}): ${error}`);
5202
5275
  }
5203
- const data = await readResponseJson(response);
5276
+ const data = await readResponseJson(response, signal);
5204
5277
  return {
5205
5278
  content: data.content[0]?.text ?? "",
5206
5279
  usage: data.usage ? {
@@ -5705,6 +5778,57 @@ function withTimeout(promise, ms, label) {
5705
5778
  );
5706
5779
  });
5707
5780
  }
5781
+ function withTimeoutSignal(operation, ms, label, parentSignal) {
5782
+ const controller = new AbortController();
5783
+ const signal = parentSignal ? AbortSignal.any([parentSignal, controller.signal]) : controller.signal;
5784
+ return new Promise((resolve2, reject) => {
5785
+ let settled = false;
5786
+ let timedOut = false;
5787
+ let timer;
5788
+ const cleanup = () => {
5789
+ if (timer !== void 0) clearTimeout(timer);
5790
+ parentSignal?.removeEventListener("abort", onParentAbort);
5791
+ };
5792
+ const onParentAbort = () => {
5793
+ if (settled || timedOut) return;
5794
+ settled = true;
5795
+ cleanup();
5796
+ reject(parentSignal?.reason ?? new Error(`${label} aborted`));
5797
+ };
5798
+ parentSignal?.addEventListener("abort", onParentAbort, { once: true });
5799
+ timer = setTimeout(() => {
5800
+ if (settled) return;
5801
+ timedOut = true;
5802
+ controller.abort(new Error(`${label} timed out after ${ms}ms`));
5803
+ settled = true;
5804
+ cleanup();
5805
+ reject(new Error(`${label} timed out after ${ms}ms`));
5806
+ }, ms);
5807
+ let pending;
5808
+ try {
5809
+ pending = operation(signal);
5810
+ } catch (error) {
5811
+ settled = true;
5812
+ cleanup();
5813
+ reject(error);
5814
+ return;
5815
+ }
5816
+ pending.then(
5817
+ (value) => {
5818
+ if (settled) return;
5819
+ settled = true;
5820
+ cleanup();
5821
+ resolve2(value);
5822
+ },
5823
+ (error) => {
5824
+ if (settled) return;
5825
+ settled = true;
5826
+ cleanup();
5827
+ reject(timedOut ? new Error(`${label} timed out after ${ms}ms`) : error);
5828
+ }
5829
+ );
5830
+ });
5831
+ }
5708
5832
  var init_timeout = __esm({
5709
5833
  "src/timeout.ts"() {
5710
5834
  "use strict";
@@ -6569,16 +6693,18 @@ async function searchObservations(options) {
6569
6693
  );
6570
6694
  } else {
6571
6695
  const EMBEDDING_TIMEOUT_MS = 15e3;
6572
- queryVector = await withTimeout(
6573
- provider2.embedInput ? provider2.embedInput(
6696
+ queryVector = await withTimeoutSignal(
6697
+ (signal) => provider2.embedInput ? provider2.embedInput(
6574
6698
  { modality: "text", text: expandedEmbeddingQuery },
6575
- { intent: "query", timeoutMs: EMBEDDING_TIMEOUT_MS, retry: false }
6699
+ { intent: "query", timeoutMs: EMBEDDING_TIMEOUT_MS, retry: false, signal }
6576
6700
  ) : provider2.embed(expandedEmbeddingQuery, {
6577
6701
  timeoutMs: EMBEDDING_TIMEOUT_MS,
6578
- retry: false
6702
+ retry: false,
6703
+ signal
6579
6704
  }),
6580
6705
  EMBEDDING_TIMEOUT_MS,
6581
- "Embedding"
6706
+ "Embedding",
6707
+ options.signal
6582
6708
  );
6583
6709
  mark("embedding");
6584
6710
  const cjkRatio = (originalQuery.match(/[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/g) || []).length / originalQuery.length;
@@ -144998,7 +145124,13 @@ async function maintainLongTermMemories(input) {
144998
145124
  }
144999
145125
  for (const group of groups.values()) {
145000
145126
  if (group.length < 2) continue;
145001
- group.sort((a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt));
145127
+ group.sort((a, b) => {
145128
+ const byCreatedAt = Date.parse(b.createdAt) - Date.parse(a.createdAt);
145129
+ if (byCreatedAt !== 0) return byCreatedAt;
145130
+ const byUpdatedAt = Date.parse(b.updatedAt) - Date.parse(a.updatedAt);
145131
+ if (byUpdatedAt !== 0) return byUpdatedAt;
145132
+ return b.id.localeCompare(a.id);
145133
+ });
145002
145134
  const newest = group[0];
145003
145135
  for (const older of group.slice(1)) {
145004
145136
  try {
@@ -155333,13 +155465,13 @@ function verifyType(declaredType, narrative, title) {
155333
155465
  }
155334
155466
  return { type: declaredType, corrected: false };
155335
155467
  }
155336
- async function extractFactsWithLLM(narrative, title, existingFacts) {
155468
+ async function extractFactsWithLLM(narrative, title, existingFacts, signal) {
155337
155469
  try {
155338
155470
  const { callLLM: callLLM2 } = await Promise.resolve().then(() => (init_provider2(), provider_exports2));
155339
155471
  const input = `Title: ${title}
155340
155472
  Content: ${narrative}${existingFacts.length > 0 ? `
155341
155473
  Already known facts (don't repeat): ${existingFacts.join("; ")}` : ""}`;
155342
- const response = await callLLM2(LLM_EXTRACT_PROMPT, input);
155474
+ const response = await callLLM2(LLM_EXTRACT_PROMPT, input, signal);
155343
155475
  const text2 = response.content.trim();
155344
155476
  const jsonMatch = text2.match(/\{[\s\S]*\}/);
155345
155477
  if (!jsonMatch) return [];
@@ -155348,15 +155480,16 @@ Already known facts (don't repeat): ${existingFacts.join("; ")}` : ""}`;
155348
155480
  if (!Array.isArray(facts)) return [];
155349
155481
  const existingLower = new Set(existingFacts.map((f3) => f3.toLowerCase().trim()));
155350
155482
  return facts.filter((f3) => typeof f3 === "string" && f3.length >= 5).filter((f3) => !existingLower.has(f3.toLowerCase().trim())).slice(0, 10);
155351
- } catch {
155483
+ } catch (error) {
155484
+ if (signal?.aborted) throw error;
155352
155485
  return [];
155353
155486
  }
155354
155487
  }
155355
- async function runExtract(input, existingEntities, useLLM = false) {
155488
+ async function runExtract(input, existingEntities, useLLM = false, signal) {
155356
155489
  const callerFacts = input.facts ?? [];
155357
155490
  let extractedFacts;
155358
155491
  if (useLLM) {
155359
- extractedFacts = await extractFactsWithLLM(input.narrative, input.title, callerFacts);
155492
+ extractedFacts = await extractFactsWithLLM(input.narrative, input.title, callerFacts, signal);
155360
155493
  if (extractedFacts.length === 0) {
155361
155494
  extractedFacts = extractFacts2(input.narrative, callerFacts);
155362
155495
  }
@@ -155576,7 +155709,7 @@ function mergeFacts2(oldFacts, newFacts) {
155576
155709
  }
155577
155710
  return merged;
155578
155711
  }
155579
- async function resolveWithLLM(extracted, hits, getObservation2) {
155712
+ async function resolveWithLLM(extracted, hits, getObservation2, signal) {
155580
155713
  try {
155581
155714
  const { callLLM: callLLM2 } = await Promise.resolve().then(() => (init_provider2(), provider_exports2));
155582
155715
  const existingMemories = hits.slice(0, 5).map((h2, i2) => ({
@@ -155593,7 +155726,7 @@ Facts: ${extracted.facts.join("; ")}
155593
155726
 
155594
155727
  EXISTING MEMORIES:
155595
155728
  ${existingMemories.map((m2) => `[ID:${m2.id}] ${m2.title} | ${m2.content} | Facts: ${m2.facts}`).join("\n")}`;
155596
- const response = await callLLM2(LLM_RESOLVE_PROMPT, input);
155729
+ const response = await callLLM2(LLM_RESOLVE_PROMPT, input, signal);
155597
155730
  const text2 = response.content.trim();
155598
155731
  const jsonMatch = text2.match(/\{[\s\S]*\}/);
155599
155732
  if (!jsonMatch) return null;
@@ -155630,7 +155763,8 @@ ${existingMemories.map((m2) => `[ID:${m2.id}] ${m2.title} | ${m2.content} | Fact
155630
155763
  };
155631
155764
  }
155632
155765
  return null;
155633
- } catch {
155766
+ } catch (error) {
155767
+ if (signal?.aborted) throw error;
155634
155768
  return null;
155635
155769
  }
155636
155770
  }
@@ -155648,19 +155782,20 @@ function scoreCandidate(extracted, candidate) {
155648
155782
  const contradiction = hasContradiction(candidate.narrative, extracted.narrative);
155649
155783
  return { score, searchSimilarity, entityMatch, richer, contradiction };
155650
155784
  }
155651
- async function runResolve(extracted, projectId, searchMemories, getObservation2, useLLM = false) {
155785
+ async function runResolve(extracted, projectId, searchMemories, getObservation2, useLLM = false, signal) {
155652
155786
  const query = `${extracted.title} ${extracted.narrative.substring(0, 200)}`;
155653
155787
  let hits;
155654
155788
  try {
155655
- hits = await searchMemories(query, 5, projectId);
155656
- } catch {
155789
+ hits = await searchMemories(query, 5, projectId, signal);
155790
+ } catch (error) {
155791
+ if (signal?.aborted) throw error;
155657
155792
  return { action: "new", reason: "Search unavailable, defaulting to new" };
155658
155793
  }
155659
155794
  if (hits.length === 0) {
155660
155795
  return { action: "new", reason: "No similar existing memories found" };
155661
155796
  }
155662
155797
  if (useLLM) {
155663
- const llmResult = await resolveWithLLM(extracted, hits, getObservation2);
155798
+ const llmResult = await resolveWithLLM(extracted, hits, getObservation2, signal);
155664
155799
  if (llmResult) return llmResult;
155665
155800
  }
155666
155801
  const scored = hits.map((hit) => ({
@@ -156082,7 +156217,7 @@ async function runFormation(input, config) {
156082
156217
  const existingEntities = config.getEntityNames();
156083
156218
  const extractStartTime = Date.now();
156084
156219
  emitStageEvent("extract", "start");
156085
- const extraction = await runExtract(input, existingEntities, config.useLLM);
156220
+ const extraction = await runExtract(input, existingEntities, config.useLLM, config.signal);
156086
156221
  stageDurationsMs.extract = Date.now() - extractStartTime;
156087
156222
  emitStageEvent("extract", "success", stageDurationsMs.extract);
156088
156223
  stagesCompleted = 1;
@@ -156102,7 +156237,8 @@ async function runFormation(input, config) {
156102
156237
  input.projectId,
156103
156238
  config.searchMemories,
156104
156239
  config.getObservation,
156105
- config.useLLM
156240
+ config.useLLM,
156241
+ config.signal
156106
156242
  );
156107
156243
  stageDurationsMs.resolve = Date.now() - resolveStartTime;
156108
156244
  emitStageEvent("resolve", "success", stageDurationsMs.resolve);
@@ -165834,7 +165970,7 @@ The path should point to a directory containing a .git folder.`
165834
165970
  };
165835
165971
  const server = existingServer ?? new McpServer({
165836
165972
  name: "memorix",
165837
- version: true ? "1.5.0" : "1.0.1"
165973
+ version: true ? "1.5.1" : "1.0.1"
165838
165974
  });
165839
165975
  const originalRegisterTool = server.registerTool.bind(server);
165840
165976
  server.registerTool = ((name, ...args) => {
@@ -166023,8 +166159,8 @@ The path should point to a directory containing a .git folder.`
166023
166159
  mode: "active",
166024
166160
  useLLM: isLLMEnabled(),
166025
166161
  minValueScore: 0.3,
166026
- searchMemories: async (q, limit2, pid) => {
166027
- const result = await compactSearch({ query: q, limit: limit2, projectId: pid, status: "active", reader });
166162
+ searchMemories: async (q, limit2, pid, signal) => {
166163
+ const result = await compactSearch({ query: q, limit: limit2, projectId: pid, status: "active", reader, signal });
166028
166164
  if (result.entries.length === 0) return [];
166029
166165
  const details = await compactDetail(result.entries.map((e2) => e2.id), { reader });
166030
166166
  return details.documents.map((d, i2) => ({
@@ -166054,16 +166190,19 @@ The path should point to a directory containing a .git folder.`
166054
166190
  getEntityNames: () => graphManager.getEntityNames(),
166055
166191
  onStageEvent: onFormationStageEvent
166056
166192
  };
166057
- formationResult = await withTimeout(
166058
- runFormation({
166059
- entityName,
166060
- type,
166061
- title,
166062
- narrative,
166063
- facts: safeFacts,
166064
- projectId: project.id,
166065
- source: "explicit"
166066
- }, formationConfig),
166193
+ formationResult = await withTimeoutSignal(
166194
+ async (signal) => {
166195
+ formationConfig.signal = signal;
166196
+ return runFormation({
166197
+ entityName,
166198
+ type,
166199
+ title,
166200
+ narrative,
166201
+ facts: safeFacts,
166202
+ projectId: project.id,
166203
+ source: "explicit"
166204
+ }, formationConfig);
166205
+ },
166067
166206
  FORMATION_TIMEOUT_MS,
166068
166207
  "Formation pipeline"
166069
166208
  );
@@ -166413,8 +166552,8 @@ Auto-enriched: ${enrichmentParts.join(", ")}` : "";
166413
166552
  mode: formationMode,
166414
166553
  useLLM: isLLMEnabled(),
166415
166554
  minValueScore: 0.3,
166416
- searchMemories: async (q, limit2, pid) => {
166417
- const result = await compactSearch({ query: q, limit: limit2, projectId: pid, status: "active", reader });
166555
+ searchMemories: async (q, limit2, pid, signal) => {
166556
+ const result = await compactSearch({ query: q, limit: limit2, projectId: pid, status: "active", reader, signal });
166418
166557
  if (result.entries.length === 0) return [];
166419
166558
  const details = await compactDetail(result.entries.map((e2) => e2.id), { reader });
166420
166559
  return details.documents.map((d, i2) => ({
@@ -166435,8 +166574,11 @@ Auto-enriched: ${enrichmentParts.join(", ")}` : "";
166435
166574
  },
166436
166575
  getEntityNames: () => graphManager.getEntityNames()
166437
166576
  };
166438
- const formed = await withTimeout(
166439
- runFormation({ entityName, type, title, narrative, facts: safeFacts, projectId: project.id, source: "explicit", topicKey }, formationConfig),
166577
+ const formed = await withTimeoutSignal(
166578
+ async (signal) => {
166579
+ formationConfig.signal = signal;
166580
+ return runFormation({ entityName, type, title, narrative, facts: safeFacts, projectId: project.id, source: "explicit", topicKey }, formationConfig);
166581
+ },
166440
166582
  FORMATION_TIMEOUT_MS,
166441
166583
  "Shadow formation"
166442
166584
  );
@@ -170213,7 +170355,7 @@ var init_serve_shared = __esm({
170213
170355
 
170214
170356
  // src/cli/version.ts
170215
170357
  function getCliVersion() {
170216
- return true ? "1.5.0" : pkg.version;
170358
+ return true ? "1.5.1" : pkg.version;
170217
170359
  }
170218
170360
  var init_version3 = __esm({
170219
170361
  "src/cli/version.ts"() {