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
@@ -2755,6 +2755,8 @@ function getPreferredBatchSize(config) {
2755
2755
  }
2756
2756
  function parseBatchLimit(error) {
2757
2757
  if (!(error instanceof Error)) return null;
2758
+ const status = error instanceof EmbeddingAPIError ? error.status : Number(error.message.match(/Embedding API error \((\d{3})\)/i)?.[1] ?? 0);
2759
+ if (status > 0 && status !== 400) return null;
2758
2760
  const explicit = error.message.match(/should not be larger than\s+(\d+)/i);
2759
2761
  if (explicit) return parseInt(explicit[1], 10);
2760
2762
  if (/batch size/i.test(error.message)) {
@@ -2767,37 +2769,99 @@ async function fetchWithRetry(url, apiKey, body, options = {}, attempt = 0, nati
2767
2769
  const controller = new AbortController();
2768
2770
  const timeoutMs = typeof options.timeoutMs === "number" && Number.isFinite(options.timeoutMs) ? Math.max(100, Math.min(Math.floor(options.timeoutMs), 6e4)) : 1e4;
2769
2771
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
2772
+ const signal = options.signal ? AbortSignal.any([options.signal, controller.signal]) : controller.signal;
2770
2773
  let response;
2771
2774
  try {
2772
2775
  response = await fetch(url, {
2773
2776
  method: "POST",
2774
2777
  headers: nativeGemini ? { "Content-Type": "application/json", "x-goog-api-key": apiKey } : { "Content-Type": "application/json", "Authorization": `Bearer ${apiKey}` },
2775
2778
  body: JSON.stringify(body),
2776
- signal: controller.signal
2779
+ signal
2777
2780
  });
2778
2781
  } catch (err) {
2779
2782
  clearTimeout(timeout);
2780
- if (err instanceof Error && err.name === "AbortError") {
2783
+ if (err instanceof Error && (err.name === "AbortError" || controller.signal.aborted || options.signal?.aborted)) {
2781
2784
  throw new Error(`Embedding API timeout after ${timeoutMs}ms: ${url}`);
2782
2785
  }
2783
2786
  throw err;
2784
2787
  }
2785
- clearTimeout(timeout);
2786
- if (response.ok) {
2787
- return response.json();
2788
+ try {
2789
+ if (response.ok) {
2790
+ return await raceWithAbort(
2791
+ response.json(),
2792
+ signal
2793
+ );
2794
+ }
2795
+ if (options.retry !== false && (response.status === 429 || response.status >= 500) && attempt < MAX_RETRIES2) {
2796
+ const delay2 = BASE_DELAY_MS * Math.pow(2, attempt);
2797
+ const retryAfter = response.headers.get("retry-after");
2798
+ const parsedRetryAfter = retryAfter ? parseInt(retryAfter, 10) * 1e3 : NaN;
2799
+ const waitMs = Number.isFinite(parsedRetryAfter) ? parsedRetryAfter : delay2;
2800
+ console.error(`[memorix] Embedding API ${response.status}, retry ${attempt + 1}/${MAX_RETRIES2} in ${waitMs}ms`);
2801
+ await waitForRetry(waitMs, options.signal);
2802
+ return fetchWithRetry(url, apiKey, body, options, attempt + 1, nativeGemini);
2803
+ }
2804
+ const errorText2 = await raceWithAbort(
2805
+ response.text().catch(() => "unknown error"),
2806
+ signal
2807
+ );
2808
+ throw new EmbeddingAPIError(response.status, errorText2);
2809
+ } catch (err) {
2810
+ if (err instanceof Error && (err.name === "AbortError" || controller.signal.aborted || options.signal?.aborted)) {
2811
+ throw new Error(`Embedding API timeout after ${timeoutMs}ms: ${url}`);
2812
+ }
2813
+ throw err;
2814
+ } finally {
2815
+ clearTimeout(timeout);
2788
2816
  }
2789
- if (options.retry !== false && (response.status === 429 || response.status >= 500) && attempt < MAX_RETRIES2) {
2790
- const delay2 = BASE_DELAY_MS * Math.pow(2, attempt);
2791
- const retryAfter = response.headers.get("retry-after");
2792
- const waitMs = retryAfter ? parseInt(retryAfter, 10) * 1e3 : delay2;
2793
- console.error(`[memorix] Embedding API ${response.status}, retry ${attempt + 1}/${MAX_RETRIES2} in ${waitMs}ms`);
2817
+ }
2818
+ function abortError() {
2819
+ const error = new Error("Embedding API request aborted");
2820
+ error.name = "AbortError";
2821
+ return error;
2822
+ }
2823
+ function raceWithAbort(promise, signal) {
2824
+ if (signal.aborted) return Promise.reject(abortError());
2825
+ return new Promise((resolve, reject) => {
2826
+ const onAbort = () => {
2827
+ signal.removeEventListener("abort", onAbort);
2828
+ reject(abortError());
2829
+ };
2830
+ signal.addEventListener("abort", onAbort, { once: true });
2831
+ promise.then(
2832
+ (value) => {
2833
+ signal.removeEventListener("abort", onAbort);
2834
+ resolve(value);
2835
+ },
2836
+ (error) => {
2837
+ signal.removeEventListener("abort", onAbort);
2838
+ reject(error);
2839
+ }
2840
+ );
2841
+ });
2842
+ }
2843
+ async function waitForRetry(waitMs, signal) {
2844
+ if (!signal) {
2794
2845
  await new Promise((resolve) => setTimeout(resolve, waitMs));
2795
- return fetchWithRetry(url, apiKey, body, options, attempt + 1, nativeGemini);
2846
+ return;
2796
2847
  }
2797
- const errorText2 = await response.text().catch(() => "unknown error");
2798
- throw new Error(`Embedding API error (${response.status}): ${errorText2}`);
2848
+ const abortSignal = signal;
2849
+ if (abortSignal.aborted) throw new Error("Embedding API retry aborted");
2850
+ await new Promise((resolve, reject) => {
2851
+ const timer = setTimeout(done, waitMs);
2852
+ const onAbort = () => {
2853
+ clearTimeout(timer);
2854
+ abortSignal.removeEventListener("abort", onAbort);
2855
+ reject(new Error("Embedding API retry aborted"));
2856
+ };
2857
+ function done() {
2858
+ abortSignal.removeEventListener("abort", onAbort);
2859
+ resolve();
2860
+ }
2861
+ abortSignal.addEventListener("abort", onAbort, { once: true });
2862
+ });
2799
2863
  }
2800
- 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;
2864
+ 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;
2801
2865
  var init_api_provider = __esm({
2802
2866
  "src/embedding/api-provider.ts"() {
2803
2867
  "use strict";
@@ -2816,6 +2880,14 @@ var init_api_provider = __esm({
2816
2880
  DASHSCOPE_MAX_BATCH_SIZE = 10;
2817
2881
  MAX_RETRIES2 = 3;
2818
2882
  BASE_DELAY_MS = 500;
2883
+ EmbeddingAPIError = class extends Error {
2884
+ status;
2885
+ constructor(status, detail) {
2886
+ super(`Embedding API error (${status}): ${detail}`);
2887
+ this.name = "EmbeddingAPIError";
2888
+ this.status = status;
2889
+ }
2890
+ };
2819
2891
  APIEmbeddingProvider = class _APIEmbeddingProvider {
2820
2892
  name;
2821
2893
  dimensions;
@@ -3027,15 +3099,14 @@ var init_api_provider = __esm({
3027
3099
  }
3028
3100
  } catch (error) {
3029
3101
  const providerLimit = parseBatchLimit(error);
3030
- const fallbackSize = providerLimit ?? Math.ceil(chunkTexts.length / 2);
3031
- if (chunkTexts.length > 1 && fallbackSize < chunkTexts.length) {
3102
+ if (providerLimit !== null && chunkTexts.length > 1 && providerLimit < chunkTexts.length) {
3032
3103
  console.error(
3033
- `[memorix] Embedding batch too large for provider, retrying in chunks of ${fallbackSize}`
3104
+ `[memorix] Embedding batch too large for provider, retrying in chunks of ${providerLimit}`
3034
3105
  );
3035
- for (let start = 0; start < chunkTexts.length; start += fallbackSize) {
3106
+ for (let start = 0; start < chunkTexts.length; start += providerLimit) {
3036
3107
  await processChunk(
3037
- chunkTexts.slice(start, start + fallbackSize),
3038
- chunkIndices.slice(start, start + fallbackSize)
3108
+ chunkTexts.slice(start, start + providerLimit),
3109
+ chunkIndices.slice(start, start + providerLimit)
3039
3110
  );
3040
3111
  }
3041
3112
  return;
@@ -3899,22 +3970,23 @@ function getLLMConfig() {
3899
3970
  function setLLMConfig(config) {
3900
3971
  currentConfig = config;
3901
3972
  }
3902
- async function callLLM(systemPrompt, userMessage) {
3973
+ async function callLLM(systemPrompt, userMessage, signal) {
3903
3974
  if (!currentConfig) {
3904
3975
  throw new Error("LLM not configured. Set MEMORIX_LLM_API_KEY or OPENAI_API_KEY.");
3905
3976
  }
3906
3977
  if (currentConfig.provider === "anthropic") {
3907
- return callAnthropic(systemPrompt, userMessage);
3978
+ return callAnthropic(systemPrompt, userMessage, signal);
3908
3979
  }
3909
- return callOpenAICompatible(systemPrompt, userMessage);
3980
+ return callOpenAICompatible(systemPrompt, userMessage, signal);
3910
3981
  }
3911
- async function callOpenAICompatible(systemPrompt, userMessage) {
3982
+ async function callOpenAICompatible(systemPrompt, userMessage, signal) {
3912
3983
  const config = currentConfig;
3913
3984
  let base = config.baseUrl.replace(/\/+$/, "");
3914
3985
  if (!base.endsWith("/v1")) base += "/v1";
3915
3986
  const url = `${base}/chat/completions`;
3987
+ const fetchSignal = signal ? AbortSignal.any([signal, AbortSignal.timeout(LLM_CALL_TIMEOUT_MS)]) : AbortSignal.timeout(LLM_CALL_TIMEOUT_MS);
3916
3988
  const response = await fetch(url, {
3917
- signal: AbortSignal.timeout(LLM_CALL_TIMEOUT_MS),
3989
+ signal: fetchSignal,
3918
3990
  method: "POST",
3919
3991
  headers: {
3920
3992
  "Content-Type": "application/json",
@@ -3931,10 +4003,10 @@ async function callOpenAICompatible(systemPrompt, userMessage) {
3931
4003
  })
3932
4004
  });
3933
4005
  if (!response.ok) {
3934
- const error = await readResponseText(response, void 0, 64 * 1024).catch(() => "unknown error");
4006
+ const error = await readResponseText(response, signal, 64 * 1024).catch(() => "unknown error");
3935
4007
  throw new Error(`LLM API error (${response.status}): ${error}`);
3936
4008
  }
3937
- const data = await readResponseJson(response);
4009
+ const data = await readResponseJson(response, signal);
3938
4010
  return {
3939
4011
  content: data.choices[0]?.message?.content ?? "",
3940
4012
  usage: data.usage ? {
@@ -3943,11 +4015,12 @@ async function callOpenAICompatible(systemPrompt, userMessage) {
3943
4015
  } : void 0
3944
4016
  };
3945
4017
  }
3946
- async function callAnthropic(systemPrompt, userMessage) {
4018
+ async function callAnthropic(systemPrompt, userMessage, signal) {
3947
4019
  const config = currentConfig;
3948
4020
  const url = `${config.baseUrl}/messages`;
4021
+ const fetchSignal = signal ? AbortSignal.any([signal, AbortSignal.timeout(LLM_CALL_TIMEOUT_MS)]) : AbortSignal.timeout(LLM_CALL_TIMEOUT_MS);
3949
4022
  const response = await fetch(url, {
3950
- signal: AbortSignal.timeout(LLM_CALL_TIMEOUT_MS),
4023
+ signal: fetchSignal,
3951
4024
  method: "POST",
3952
4025
  headers: {
3953
4026
  "Content-Type": "application/json",
@@ -3965,10 +4038,10 @@ async function callAnthropic(systemPrompt, userMessage) {
3965
4038
  })
3966
4039
  });
3967
4040
  if (!response.ok) {
3968
- const error = await readResponseText(response, void 0, 64 * 1024).catch(() => "unknown error");
4041
+ const error = await readResponseText(response, signal, 64 * 1024).catch(() => "unknown error");
3969
4042
  throw new Error(`Anthropic API error (${response.status}): ${error}`);
3970
4043
  }
3971
- const data = await readResponseJson(response);
4044
+ const data = await readResponseJson(response, signal);
3972
4045
  return {
3973
4046
  content: data.content[0]?.text ?? "",
3974
4047
  usage: data.usage ? {
@@ -4473,6 +4546,57 @@ function withTimeout(promise, ms, label) {
4473
4546
  );
4474
4547
  });
4475
4548
  }
4549
+ function withTimeoutSignal(operation, ms, label, parentSignal) {
4550
+ const controller = new AbortController();
4551
+ const signal = parentSignal ? AbortSignal.any([parentSignal, controller.signal]) : controller.signal;
4552
+ return new Promise((resolve, reject) => {
4553
+ let settled = false;
4554
+ let timedOut = false;
4555
+ let timer;
4556
+ const cleanup = () => {
4557
+ if (timer !== void 0) clearTimeout(timer);
4558
+ parentSignal?.removeEventListener("abort", onParentAbort);
4559
+ };
4560
+ const onParentAbort = () => {
4561
+ if (settled || timedOut) return;
4562
+ settled = true;
4563
+ cleanup();
4564
+ reject(parentSignal?.reason ?? new Error(`${label} aborted`));
4565
+ };
4566
+ parentSignal?.addEventListener("abort", onParentAbort, { once: true });
4567
+ timer = setTimeout(() => {
4568
+ if (settled) return;
4569
+ timedOut = true;
4570
+ controller.abort(new Error(`${label} timed out after ${ms}ms`));
4571
+ settled = true;
4572
+ cleanup();
4573
+ reject(new Error(`${label} timed out after ${ms}ms`));
4574
+ }, ms);
4575
+ let pending;
4576
+ try {
4577
+ pending = operation(signal);
4578
+ } catch (error) {
4579
+ settled = true;
4580
+ cleanup();
4581
+ reject(error);
4582
+ return;
4583
+ }
4584
+ pending.then(
4585
+ (value) => {
4586
+ if (settled) return;
4587
+ settled = true;
4588
+ cleanup();
4589
+ resolve(value);
4590
+ },
4591
+ (error) => {
4592
+ if (settled) return;
4593
+ settled = true;
4594
+ cleanup();
4595
+ reject(timedOut ? new Error(`${label} timed out after ${ms}ms`) : error);
4596
+ }
4597
+ );
4598
+ });
4599
+ }
4476
4600
  var init_timeout = __esm({
4477
4601
  "src/timeout.ts"() {
4478
4602
  "use strict";
@@ -5337,16 +5461,18 @@ async function searchObservations(options) {
5337
5461
  );
5338
5462
  } else {
5339
5463
  const EMBEDDING_TIMEOUT_MS = 15e3;
5340
- queryVector = await withTimeout(
5341
- provider2.embedInput ? provider2.embedInput(
5464
+ queryVector = await withTimeoutSignal(
5465
+ (signal) => provider2.embedInput ? provider2.embedInput(
5342
5466
  { modality: "text", text: expandedEmbeddingQuery },
5343
- { intent: "query", timeoutMs: EMBEDDING_TIMEOUT_MS, retry: false }
5467
+ { intent: "query", timeoutMs: EMBEDDING_TIMEOUT_MS, retry: false, signal }
5344
5468
  ) : provider2.embed(expandedEmbeddingQuery, {
5345
5469
  timeoutMs: EMBEDDING_TIMEOUT_MS,
5346
- retry: false
5470
+ retry: false,
5471
+ signal
5347
5472
  }),
5348
5473
  EMBEDDING_TIMEOUT_MS,
5349
- "Embedding"
5474
+ "Embedding",
5475
+ options.signal
5350
5476
  );
5351
5477
  mark("embedding");
5352
5478
  const cjkRatio = (originalQuery.match(/[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/g) || []).length / originalQuery.length;
@@ -143266,7 +143392,13 @@ async function maintainLongTermMemories(input) {
143266
143392
  }
143267
143393
  for (const group of groups.values()) {
143268
143394
  if (group.length < 2) continue;
143269
- group.sort((a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt));
143395
+ group.sort((a, b) => {
143396
+ const byCreatedAt = Date.parse(b.createdAt) - Date.parse(a.createdAt);
143397
+ if (byCreatedAt !== 0) return byCreatedAt;
143398
+ const byUpdatedAt = Date.parse(b.updatedAt) - Date.parse(a.updatedAt);
143399
+ if (byUpdatedAt !== 0) return byUpdatedAt;
143400
+ return b.id.localeCompare(a.id);
143401
+ });
143270
143402
  const newest = group[0];
143271
143403
  for (const older of group.slice(1)) {
143272
143404
  try {
@@ -2,6 +2,20 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [Unreleased]
6
+
7
+ ## [1.5.1] - 2026-08-16
8
+
9
+ ### Fixed
10
+ - **Bounded formation cancellation** -- Formation deadlines now abort the
11
+ LLM and vector-search work they started, including response-body reads and
12
+ retry waits. A provider that ignores cancellation still cannot hold the
13
+ caller past its deadline.
14
+ - **Embedding error classification** -- Only an explicit HTTP 400 batch-shape
15
+ error may trigger batch splitting. Authentication, billing, quota, and
16
+ upstream errors fail once instead of multiplying latency through recursive
17
+ retries.
18
+
5
19
  ## [1.5.0] - 2026-08-14
6
20
 
7
21
  ### Added