memorix 1.5.0 → 1.6.0

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 (49) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +3 -3
  3. package/README.zh-CN.md +3 -3
  4. package/dist/cli/index.js +452 -109
  5. package/dist/index.js +376 -87
  6. package/dist/maintenance-runner.js +174 -41
  7. package/dist/media-audio-runner.js +174 -41
  8. package/dist/media-video-runner.js +174 -41
  9. package/dist/memcode-runtime/CHANGELOG.md +37 -0
  10. package/dist/sdk.js +375 -86
  11. package/dist/types.d.ts +2 -0
  12. package/dist/vector-backfill-runner.js +174 -41
  13. package/docs/1.2.4-PERSISTENT-MEMORY-DELIVERY.md +1 -1
  14. package/docs/API_REFERENCE.md +1 -1
  15. package/docs/DEVELOPMENT.md +5 -3
  16. package/package.json +1 -1
  17. package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
  18. package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
  19. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  20. package/plugins/copilot/memorix/plugin.json +1 -1
  21. package/plugins/gemini/memorix/gemini-extension.json +1 -1
  22. package/plugins/omp/memorix/package.json +1 -1
  23. package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
  24. package/plugins/pi/memorix/package.json +1 -1
  25. package/server.json +2 -2
  26. package/src/cli/commands/codegraph.ts +48 -5
  27. package/src/cli/commands/context.ts +20 -4
  28. package/src/cli/commands/resume.ts +4 -0
  29. package/src/codegraph/auto-context.ts +4 -1
  30. package/src/codegraph/context-pack.ts +3 -1
  31. package/src/codegraph/external-provider.ts +93 -0
  32. package/src/embedding/api-provider.ts +105 -22
  33. package/src/embedding/provider.ts +2 -0
  34. package/src/hooks/installers/index.ts +1 -1
  35. package/src/hooks/official-skills.ts +3 -3
  36. package/src/hooks/rules/memorix-agent-rules.md +1 -1
  37. package/src/knowledge/context-receipt.ts +41 -0
  38. package/src/knowledge/workflows.ts +6 -3
  39. package/src/knowledge/workset.ts +56 -2
  40. package/src/llm/provider.ts +17 -8
  41. package/src/memory/formation/extract.ts +6 -3
  42. package/src/memory/formation/index.ts +2 -1
  43. package/src/memory/formation/resolve.ts +10 -6
  44. package/src/memory/formation/types.ts +3 -1
  45. package/src/memory/long-term.ts +7 -1
  46. package/src/server.ts +29 -13
  47. package/src/store/orama-store.ts +6 -4
  48. package/src/timeout.ts +67 -0
  49. package/src/types.ts +2 -0
@@ -2083,22 +2083,23 @@ function getLLMConfig() {
2083
2083
  function setLLMConfig(config) {
2084
2084
  currentConfig = config;
2085
2085
  }
2086
- async function callLLM(systemPrompt, userMessage) {
2086
+ async function callLLM(systemPrompt, userMessage, signal) {
2087
2087
  if (!currentConfig) {
2088
2088
  throw new Error("LLM not configured. Set MEMORIX_LLM_API_KEY or OPENAI_API_KEY.");
2089
2089
  }
2090
2090
  if (currentConfig.provider === "anthropic") {
2091
- return callAnthropic(systemPrompt, userMessage);
2091
+ return callAnthropic(systemPrompt, userMessage, signal);
2092
2092
  }
2093
- return callOpenAICompatible(systemPrompt, userMessage);
2093
+ return callOpenAICompatible(systemPrompt, userMessage, signal);
2094
2094
  }
2095
- async function callOpenAICompatible(systemPrompt, userMessage) {
2095
+ async function callOpenAICompatible(systemPrompt, userMessage, signal) {
2096
2096
  const config = currentConfig;
2097
2097
  let base = config.baseUrl.replace(/\/+$/, "");
2098
2098
  if (!base.endsWith("/v1")) base += "/v1";
2099
2099
  const url = `${base}/chat/completions`;
2100
+ const fetchSignal = signal ? AbortSignal.any([signal, AbortSignal.timeout(LLM_CALL_TIMEOUT_MS)]) : AbortSignal.timeout(LLM_CALL_TIMEOUT_MS);
2100
2101
  const response = await fetch(url, {
2101
- signal: AbortSignal.timeout(LLM_CALL_TIMEOUT_MS),
2102
+ signal: fetchSignal,
2102
2103
  method: "POST",
2103
2104
  headers: {
2104
2105
  "Content-Type": "application/json",
@@ -2115,10 +2116,10 @@ async function callOpenAICompatible(systemPrompt, userMessage) {
2115
2116
  })
2116
2117
  });
2117
2118
  if (!response.ok) {
2118
- const error = await readResponseText(response, void 0, 64 * 1024).catch(() => "unknown error");
2119
+ const error = await readResponseText(response, signal, 64 * 1024).catch(() => "unknown error");
2119
2120
  throw new Error(`LLM API error (${response.status}): ${error}`);
2120
2121
  }
2121
- const data = await readResponseJson(response);
2122
+ const data = await readResponseJson(response, signal);
2122
2123
  return {
2123
2124
  content: data.choices[0]?.message?.content ?? "",
2124
2125
  usage: data.usage ? {
@@ -2127,11 +2128,12 @@ async function callOpenAICompatible(systemPrompt, userMessage) {
2127
2128
  } : void 0
2128
2129
  };
2129
2130
  }
2130
- async function callAnthropic(systemPrompt, userMessage) {
2131
+ async function callAnthropic(systemPrompt, userMessage, signal) {
2131
2132
  const config = currentConfig;
2132
2133
  const url = `${config.baseUrl}/messages`;
2134
+ const fetchSignal = signal ? AbortSignal.any([signal, AbortSignal.timeout(LLM_CALL_TIMEOUT_MS)]) : AbortSignal.timeout(LLM_CALL_TIMEOUT_MS);
2133
2135
  const response = await fetch(url, {
2134
- signal: AbortSignal.timeout(LLM_CALL_TIMEOUT_MS),
2136
+ signal: fetchSignal,
2135
2137
  method: "POST",
2136
2138
  headers: {
2137
2139
  "Content-Type": "application/json",
@@ -2149,10 +2151,10 @@ async function callAnthropic(systemPrompt, userMessage) {
2149
2151
  })
2150
2152
  });
2151
2153
  if (!response.ok) {
2152
- const error = await readResponseText(response, void 0, 64 * 1024).catch(() => "unknown error");
2154
+ const error = await readResponseText(response, signal, 64 * 1024).catch(() => "unknown error");
2153
2155
  throw new Error(`Anthropic API error (${response.status}): ${error}`);
2154
2156
  }
2155
- const data = await readResponseJson(response);
2157
+ const data = await readResponseJson(response, signal);
2156
2158
  return {
2157
2159
  content: data.content[0]?.text ?? "",
2158
2160
  usage: data.usage ? {
@@ -3855,6 +3857,8 @@ function getPreferredBatchSize(config) {
3855
3857
  }
3856
3858
  function parseBatchLimit(error) {
3857
3859
  if (!(error instanceof Error)) return null;
3860
+ const status = error instanceof EmbeddingAPIError ? error.status : Number(error.message.match(/Embedding API error \((\d{3})\)/i)?.[1] ?? 0);
3861
+ if (status > 0 && status !== 400) return null;
3858
3862
  const explicit = error.message.match(/should not be larger than\s+(\d+)/i);
3859
3863
  if (explicit) return parseInt(explicit[1], 10);
3860
3864
  if (/batch size/i.test(error.message)) {
@@ -3867,37 +3871,99 @@ async function fetchWithRetry(url, apiKey, body, options = {}, attempt = 0, nati
3867
3871
  const controller = new AbortController();
3868
3872
  const timeoutMs = typeof options.timeoutMs === "number" && Number.isFinite(options.timeoutMs) ? Math.max(100, Math.min(Math.floor(options.timeoutMs), 6e4)) : 1e4;
3869
3873
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
3874
+ const signal = options.signal ? AbortSignal.any([options.signal, controller.signal]) : controller.signal;
3870
3875
  let response;
3871
3876
  try {
3872
3877
  response = await fetch(url, {
3873
3878
  method: "POST",
3874
3879
  headers: nativeGemini ? { "Content-Type": "application/json", "x-goog-api-key": apiKey } : { "Content-Type": "application/json", "Authorization": `Bearer ${apiKey}` },
3875
3880
  body: JSON.stringify(body),
3876
- signal: controller.signal
3881
+ signal
3877
3882
  });
3878
3883
  } catch (err) {
3879
3884
  clearTimeout(timeout);
3880
- if (err instanceof Error && err.name === "AbortError") {
3885
+ if (err instanceof Error && (err.name === "AbortError" || controller.signal.aborted || options.signal?.aborted)) {
3881
3886
  throw new Error(`Embedding API timeout after ${timeoutMs}ms: ${url}`);
3882
3887
  }
3883
3888
  throw err;
3884
3889
  }
3885
- clearTimeout(timeout);
3886
- if (response.ok) {
3887
- return response.json();
3890
+ try {
3891
+ if (response.ok) {
3892
+ return await raceWithAbort(
3893
+ response.json(),
3894
+ signal
3895
+ );
3896
+ }
3897
+ if (options.retry !== false && (response.status === 429 || response.status >= 500) && attempt < MAX_RETRIES2) {
3898
+ const delay2 = BASE_DELAY_MS * Math.pow(2, attempt);
3899
+ const retryAfter = response.headers.get("retry-after");
3900
+ const parsedRetryAfter = retryAfter ? parseInt(retryAfter, 10) * 1e3 : NaN;
3901
+ const waitMs = Number.isFinite(parsedRetryAfter) ? parsedRetryAfter : delay2;
3902
+ console.error(`[memorix] Embedding API ${response.status}, retry ${attempt + 1}/${MAX_RETRIES2} in ${waitMs}ms`);
3903
+ await waitForRetry(waitMs, options.signal);
3904
+ return fetchWithRetry(url, apiKey, body, options, attempt + 1, nativeGemini);
3905
+ }
3906
+ const errorText2 = await raceWithAbort(
3907
+ response.text().catch(() => "unknown error"),
3908
+ signal
3909
+ );
3910
+ throw new EmbeddingAPIError(response.status, errorText2);
3911
+ } catch (err) {
3912
+ if (err instanceof Error && (err.name === "AbortError" || controller.signal.aborted || options.signal?.aborted)) {
3913
+ throw new Error(`Embedding API timeout after ${timeoutMs}ms: ${url}`);
3914
+ }
3915
+ throw err;
3916
+ } finally {
3917
+ clearTimeout(timeout);
3888
3918
  }
3889
- if (options.retry !== false && (response.status === 429 || response.status >= 500) && attempt < MAX_RETRIES2) {
3890
- const delay2 = BASE_DELAY_MS * Math.pow(2, attempt);
3891
- const retryAfter = response.headers.get("retry-after");
3892
- const waitMs = retryAfter ? parseInt(retryAfter, 10) * 1e3 : delay2;
3893
- console.error(`[memorix] Embedding API ${response.status}, retry ${attempt + 1}/${MAX_RETRIES2} in ${waitMs}ms`);
3919
+ }
3920
+ function abortError() {
3921
+ const error = new Error("Embedding API request aborted");
3922
+ error.name = "AbortError";
3923
+ return error;
3924
+ }
3925
+ function raceWithAbort(promise, signal) {
3926
+ if (signal.aborted) return Promise.reject(abortError());
3927
+ return new Promise((resolve, reject) => {
3928
+ const onAbort = () => {
3929
+ signal.removeEventListener("abort", onAbort);
3930
+ reject(abortError());
3931
+ };
3932
+ signal.addEventListener("abort", onAbort, { once: true });
3933
+ promise.then(
3934
+ (value) => {
3935
+ signal.removeEventListener("abort", onAbort);
3936
+ resolve(value);
3937
+ },
3938
+ (error) => {
3939
+ signal.removeEventListener("abort", onAbort);
3940
+ reject(error);
3941
+ }
3942
+ );
3943
+ });
3944
+ }
3945
+ async function waitForRetry(waitMs, signal) {
3946
+ if (!signal) {
3894
3947
  await new Promise((resolve) => setTimeout(resolve, waitMs));
3895
- return fetchWithRetry(url, apiKey, body, options, attempt + 1, nativeGemini);
3948
+ return;
3896
3949
  }
3897
- const errorText2 = await response.text().catch(() => "unknown error");
3898
- throw new Error(`Embedding API error (${response.status}): ${errorText2}`);
3950
+ const abortSignal = signal;
3951
+ if (abortSignal.aborted) throw new Error("Embedding API retry aborted");
3952
+ await new Promise((resolve, reject) => {
3953
+ const timer = setTimeout(done, waitMs);
3954
+ const onAbort = () => {
3955
+ clearTimeout(timer);
3956
+ abortSignal.removeEventListener("abort", onAbort);
3957
+ reject(new Error("Embedding API retry aborted"));
3958
+ };
3959
+ function done() {
3960
+ abortSignal.removeEventListener("abort", onAbort);
3961
+ resolve();
3962
+ }
3963
+ abortSignal.addEventListener("abort", onAbort, { once: true });
3964
+ });
3899
3965
  }
3900
- 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;
3966
+ 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;
3901
3967
  var init_api_provider = __esm({
3902
3968
  "src/embedding/api-provider.ts"() {
3903
3969
  "use strict";
@@ -3916,6 +3982,14 @@ var init_api_provider = __esm({
3916
3982
  DASHSCOPE_MAX_BATCH_SIZE = 10;
3917
3983
  MAX_RETRIES2 = 3;
3918
3984
  BASE_DELAY_MS = 500;
3985
+ EmbeddingAPIError = class extends Error {
3986
+ status;
3987
+ constructor(status, detail) {
3988
+ super(`Embedding API error (${status}): ${detail}`);
3989
+ this.name = "EmbeddingAPIError";
3990
+ this.status = status;
3991
+ }
3992
+ };
3919
3993
  APIEmbeddingProvider = class _APIEmbeddingProvider {
3920
3994
  name;
3921
3995
  dimensions;
@@ -4127,15 +4201,14 @@ var init_api_provider = __esm({
4127
4201
  }
4128
4202
  } catch (error) {
4129
4203
  const providerLimit = parseBatchLimit(error);
4130
- const fallbackSize = providerLimit ?? Math.ceil(chunkTexts.length / 2);
4131
- if (chunkTexts.length > 1 && fallbackSize < chunkTexts.length) {
4204
+ if (providerLimit !== null && chunkTexts.length > 1 && providerLimit < chunkTexts.length) {
4132
4205
  console.error(
4133
- `[memorix] Embedding batch too large for provider, retrying in chunks of ${fallbackSize}`
4206
+ `[memorix] Embedding batch too large for provider, retrying in chunks of ${providerLimit}`
4134
4207
  );
4135
- for (let start = 0; start < chunkTexts.length; start += fallbackSize) {
4208
+ for (let start = 0; start < chunkTexts.length; start += providerLimit) {
4136
4209
  await processChunk(
4137
- chunkTexts.slice(start, start + fallbackSize),
4138
- chunkIndices.slice(start, start + fallbackSize)
4210
+ chunkTexts.slice(start, start + providerLimit),
4211
+ chunkIndices.slice(start, start + providerLimit)
4139
4212
  );
4140
4213
  }
4141
4214
  return;
@@ -4959,6 +5032,57 @@ function withTimeout(promise, ms, label) {
4959
5032
  );
4960
5033
  });
4961
5034
  }
5035
+ function withTimeoutSignal(operation, ms, label, parentSignal) {
5036
+ const controller = new AbortController();
5037
+ const signal = parentSignal ? AbortSignal.any([parentSignal, controller.signal]) : controller.signal;
5038
+ return new Promise((resolve, reject) => {
5039
+ let settled = false;
5040
+ let timedOut = false;
5041
+ let timer;
5042
+ const cleanup = () => {
5043
+ if (timer !== void 0) clearTimeout(timer);
5044
+ parentSignal?.removeEventListener("abort", onParentAbort);
5045
+ };
5046
+ const onParentAbort = () => {
5047
+ if (settled || timedOut) return;
5048
+ settled = true;
5049
+ cleanup();
5050
+ reject(parentSignal?.reason ?? new Error(`${label} aborted`));
5051
+ };
5052
+ parentSignal?.addEventListener("abort", onParentAbort, { once: true });
5053
+ timer = setTimeout(() => {
5054
+ if (settled) return;
5055
+ timedOut = true;
5056
+ controller.abort(new Error(`${label} timed out after ${ms}ms`));
5057
+ settled = true;
5058
+ cleanup();
5059
+ reject(new Error(`${label} timed out after ${ms}ms`));
5060
+ }, ms);
5061
+ let pending;
5062
+ try {
5063
+ pending = operation(signal);
5064
+ } catch (error) {
5065
+ settled = true;
5066
+ cleanup();
5067
+ reject(error);
5068
+ return;
5069
+ }
5070
+ pending.then(
5071
+ (value) => {
5072
+ if (settled) return;
5073
+ settled = true;
5074
+ cleanup();
5075
+ resolve(value);
5076
+ },
5077
+ (error) => {
5078
+ if (settled) return;
5079
+ settled = true;
5080
+ cleanup();
5081
+ reject(timedOut ? new Error(`${label} timed out after ${ms}ms`) : error);
5082
+ }
5083
+ );
5084
+ });
5085
+ }
4962
5086
  var init_timeout = __esm({
4963
5087
  "src/timeout.ts"() {
4964
5088
  "use strict";
@@ -5823,16 +5947,18 @@ async function searchObservations(options) {
5823
5947
  );
5824
5948
  } else {
5825
5949
  const EMBEDDING_TIMEOUT_MS = 15e3;
5826
- queryVector = await withTimeout(
5827
- provider2.embedInput ? provider2.embedInput(
5950
+ queryVector = await withTimeoutSignal(
5951
+ (signal) => provider2.embedInput ? provider2.embedInput(
5828
5952
  { modality: "text", text: expandedEmbeddingQuery },
5829
- { intent: "query", timeoutMs: EMBEDDING_TIMEOUT_MS, retry: false }
5953
+ { intent: "query", timeoutMs: EMBEDDING_TIMEOUT_MS, retry: false, signal }
5830
5954
  ) : provider2.embed(expandedEmbeddingQuery, {
5831
5955
  timeoutMs: EMBEDDING_TIMEOUT_MS,
5832
- retry: false
5956
+ retry: false,
5957
+ signal
5833
5958
  }),
5834
5959
  EMBEDDING_TIMEOUT_MS,
5835
- "Embedding"
5960
+ "Embedding",
5961
+ options.signal
5836
5962
  );
5837
5963
  mark("embedding");
5838
5964
  const cjkRatio = (originalQuery.match(/[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/g) || []).length / originalQuery.length;
@@ -143295,7 +143421,13 @@ async function maintainLongTermMemories(input) {
143295
143421
  }
143296
143422
  for (const group of groups.values()) {
143297
143423
  if (group.length < 2) continue;
143298
- group.sort((a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt));
143424
+ group.sort((a, b) => {
143425
+ const byCreatedAt = Date.parse(b.createdAt) - Date.parse(a.createdAt);
143426
+ if (byCreatedAt !== 0) return byCreatedAt;
143427
+ const byUpdatedAt = Date.parse(b.updatedAt) - Date.parse(a.updatedAt);
143428
+ if (byUpdatedAt !== 0) return byUpdatedAt;
143429
+ return b.id.localeCompare(a.id);
143430
+ });
143299
143431
  const newest = group[0];
143300
143432
  for (const older of group.slice(1)) {
143301
143433
  try {
@@ -146687,6 +146819,7 @@ var init_task_lens = __esm({
146687
146819
  var workflows_exports = {};
146688
146820
  __export(workflows_exports, {
146689
146821
  WORKFLOW_ADAPTER_TARGETS: () => WORKFLOW_ADAPTER_TARGETS,
146822
+ WORKFLOW_AGENT_TARGETS: () => WORKFLOW_AGENT_TARGETS,
146690
146823
  applyWorkflowAdapter: () => applyWorkflowAdapter,
146691
146824
  importWindsurfWorkflows: () => importWindsurfWorkflows,
146692
146825
  parseWorkflowMarkdown: () => parseWorkflowMarkdown,
@@ -146752,7 +146885,7 @@ function optionalStatus(data) {
146752
146885
  return value;
146753
146886
  }
146754
146887
  function normalizeAgents(values) {
146755
- const unknown3 = values.filter((value) => !KNOWN_AGENTS.includes(value));
146888
+ const unknown3 = values.filter((value) => !WORKFLOW_AGENT_TARGETS.includes(value));
146756
146889
  if (unknown3.length > 0) {
146757
146890
  throw new Error("workflow frontmatter has unsupported agent: " + unknown3[0]);
146758
146891
  }
@@ -147028,7 +147161,7 @@ function taskScore(workflow, task) {
147028
147161
  return { score, reasons: [...new Set(reasons)] };
147029
147162
  }
147030
147163
  function selectWorkflows(input) {
147031
- const selected = input.workflows.filter((workflow) => workflow.status === "active").map((workflow) => {
147164
+ const selected = input.workflows.filter((workflow) => workflow.status === "active" && (!input.agent || workflow.compatibleAgents.includes(input.agent))).map((workflow) => {
147032
147165
  const match2 = taskScore(workflow, input.task);
147033
147166
  const cautions = input.store && input.projectId ? input.store.recentFailureCautions(input.projectId, workflow.id) : [];
147034
147167
  return {
@@ -147308,7 +147441,7 @@ async function selectWorkspaceWorkflows(input) {
147308
147441
  errors: synced.errors
147309
147442
  };
147310
147443
  }
147311
- var KNOWN_AGENTS, WORKFLOW_ADAPTER_TARGETS, STATUS_VALUES, LENS_TERMS, GENERIC_WORKFLOW_LENSES, GENERIC_WORKFLOW_TERMS;
147444
+ var WORKFLOW_AGENT_TARGETS, WORKFLOW_ADAPTER_TARGETS, STATUS_VALUES, LENS_TERMS, GENERIC_WORKFLOW_LENSES, GENERIC_WORKFLOW_TERMS;
147312
147445
  var init_workflows2 = __esm({
147313
147446
  "src/knowledge/workflows.ts"() {
147314
147447
  "use strict";
@@ -147320,7 +147453,7 @@ var init_workflows2 = __esm({
147320
147453
  init_workflow_store();
147321
147454
  init_outcome_store();
147322
147455
  init_task_lens();
147323
- KNOWN_AGENTS = [
147456
+ WORKFLOW_AGENT_TARGETS = [
147324
147457
  "windsurf",
147325
147458
  "cursor",
147326
147459
  "claude-code",