memorix 1.4.6 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/TEAM.md +86 -86
- package/dist/cli/index.js +225 -70
- package/dist/cli/memcode.js +0 -0
- package/dist/index.js +225 -70
- package/dist/maintenance-runner.js +183 -38
- package/dist/media-audio-runner.js +183 -38
- package/dist/media-video-runner.js +183 -38
- package/dist/memcode-runtime/CHANGELOG.md +38 -0
- package/dist/sdk.js +224 -69
- package/dist/types.d.ts +2 -0
- package/dist/vector-backfill-runner.js +183 -38
- package/docs/CONFIGURATION.md +12 -0
- package/docs/DESIGN_DECISIONS.md +357 -357
- package/package.json +1 -1
- package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
- package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
- package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
- package/plugins/copilot/memorix/plugin.json +1 -1
- package/plugins/gemini/memorix/gemini-extension.json +1 -1
- package/plugins/omp/memorix/package.json +1 -1
- package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
- package/plugins/pi/memorix/package.json +1 -1
- package/server.json +2 -2
- package/src/audit/index.ts +156 -156
- package/src/cli/commands/formation.ts +48 -48
- package/src/cli/commands/git-hook-install.ts +111 -111
- package/src/cli/commands/handoff.ts +54 -54
- package/src/cli/commands/hooks-status.ts +63 -63
- package/src/cli/commands/ingest.ts +44 -44
- package/src/cli/commands/integrate-shared.ts +15 -15
- package/src/cli/commands/lock.ts +82 -82
- package/src/cli/commands/message.ts +104 -104
- package/src/cli/commands/poll.ts +58 -58
- package/src/cli/commands/reasoning.ts +118 -118
- package/src/cli/commands/skills.ts +114 -114
- package/src/cli/commands/task.ts +167 -167
- package/src/cli/commands/transfer.ts +47 -47
- package/src/cli/index.ts +406 -406
- package/src/cli/tui/ChatView.tsx +234 -234
- package/src/cli/tui/CommandBar.tsx +312 -312
- package/src/cli/tui/ContextRail.tsx +118 -118
- package/src/cli/tui/HeaderBar.tsx +72 -72
- package/src/cli/tui/LogoBanner.tsx +51 -51
- package/src/cli/tui/index.ts +41 -41
- package/src/cli/version.ts +7 -7
- package/src/cli/workbench.ts +1 -1
- package/src/config.ts +23 -2
- package/src/dashboard/project-classification.ts +64 -64
- package/src/embedding/api-provider.ts +105 -22
- package/src/embedding/provider.ts +2 -0
- package/src/git/hooks-path.ts +85 -85
- package/src/hooks/pattern-detector.ts +173 -173
- package/src/hooks/significance-filter.ts +250 -250
- package/src/llm/memory-manager.ts +328 -328
- package/src/llm/provider.ts +17 -8
- package/src/llm/quality.ts +248 -248
- package/src/memory/attribution-guard.ts +249 -249
- package/src/memory/disclosure-policy.ts +135 -135
- package/src/memory/entity-extractor.ts +197 -197
- package/src/memory/formation/evaluate.ts +217 -217
- package/src/memory/formation/extract.ts +6 -3
- package/src/memory/formation/index.ts +2 -1
- package/src/memory/formation/resolve.ts +10 -6
- package/src/memory/formation/types.ts +3 -1
- package/src/memory/freshness.ts +122 -122
- package/src/memory/graph.ts +197 -197
- package/src/memory/long-term.ts +7 -1
- package/src/memory/refs.ts +94 -94
- package/src/memory/secret-filter.ts +79 -79
- package/src/orchestrate/adapters/claude-stream.ts +192 -192
- package/src/orchestrate/adapters/claude.ts +111 -111
- package/src/orchestrate/adapters/codex-stream.ts +134 -134
- package/src/orchestrate/adapters/codex.ts +41 -41
- package/src/orchestrate/adapters/gemini-stream.ts +166 -166
- package/src/orchestrate/adapters/gemini.ts +42 -42
- package/src/orchestrate/adapters/index.ts +73 -73
- package/src/orchestrate/adapters/opencode-stream.ts +143 -143
- package/src/orchestrate/adapters/opencode.ts +47 -47
- package/src/orchestrate/adapters/types.ts +77 -77
- package/src/orchestrate/capability-router.ts +284 -284
- package/src/orchestrate/cost-tracker.ts +219 -219
- package/src/orchestrate/error-recovery.ts +191 -191
- package/src/orchestrate/evidence.ts +140 -140
- package/src/orchestrate/ledger.ts +110 -110
- package/src/orchestrate/memorix-bridge.ts +343 -343
- package/src/orchestrate/output-budget.ts +80 -80
- package/src/orchestrate/permission.ts +152 -152
- package/src/orchestrate/pipeline-trace.ts +131 -131
- package/src/orchestrate/prompt-builder.ts +155 -155
- package/src/orchestrate/ring-buffer.ts +37 -37
- package/src/orchestrate/task-graph.ts +389 -389
- package/src/project/aliases.ts +374 -374
- package/src/rules/adapters/trae.ts +56 -56
- package/src/rules/syncer.ts +235 -235
- package/src/search/query-expansion.ts +52 -52
- package/src/server/formation-timeout.ts +27 -27
- package/src/server.ts +14 -10
- package/src/skills/mini-skills.ts +386 -386
- package/src/store/chat-store.ts +119 -119
- package/src/store/graph-store.ts +249 -249
- package/src/store/mini-skill-store.ts +349 -349
- package/src/store/orama-store.ts +6 -4
- package/src/store/persistence-json.ts +212 -212
- package/src/store/persistence.ts +291 -291
- package/src/store/project-affinity.ts +195 -195
- package/src/team/event-bus.ts +76 -76
- package/src/team/handoff.ts +161 -161
- package/src/team/poll.ts +132 -132
- package/src/timeout.ts +67 -0
- package/src/types.ts +2 -0
- package/src/workspace/mcp-adapters/codex.ts +191 -191
- package/src/workspace/mcp-adapters/copilot.ts +105 -105
- package/src/workspace/mcp-adapters/cursor.ts +53 -53
- package/src/workspace/mcp-adapters/kiro.ts +64 -64
- package/src/workspace/mcp-adapters/opencode.ts +123 -123
- package/src/workspace/mcp-adapters/trae.ts +134 -134
- package/src/workspace/mcp-adapters/windsurf.ts +91 -91
- package/src/workspace/sanitizer.ts +60 -60
|
@@ -2141,6 +2141,7 @@ var init_resolved_config = __esm({
|
|
|
2141
2141
|
// src/config.ts
|
|
2142
2142
|
var config_exports = {};
|
|
2143
2143
|
__export(config_exports, {
|
|
2144
|
+
defaultEmbeddingModelFor: () => defaultEmbeddingModelFor,
|
|
2144
2145
|
getAgentLLMApiKey: () => getAgentLLMApiKey,
|
|
2145
2146
|
getAgentLLMBaseUrl: () => getAgentLLMBaseUrl,
|
|
2146
2147
|
getAgentLLMModel: () => getAgentLLMModel,
|
|
@@ -2203,7 +2204,19 @@ function getEmbeddingBaseUrl() {
|
|
|
2203
2204
|
return getResolvedEmbeddingLane().baseUrl || "https://api.openai.com/v1";
|
|
2204
2205
|
}
|
|
2205
2206
|
function getEmbeddingModel() {
|
|
2206
|
-
|
|
2207
|
+
const lane = getResolvedEmbeddingLane();
|
|
2208
|
+
return defaultEmbeddingModelFor(lane.baseUrl, lane.model);
|
|
2209
|
+
}
|
|
2210
|
+
function defaultEmbeddingModelFor(baseUrl, explicitModel) {
|
|
2211
|
+
if (explicitModel) return explicitModel;
|
|
2212
|
+
try {
|
|
2213
|
+
const url = baseUrl ? new URL(baseUrl) : null;
|
|
2214
|
+
if (url && (url.hostname === "openrouter.ai" || url.hostname.endsWith(".openrouter.ai"))) {
|
|
2215
|
+
return "qwen/qwen3-embedding-8b";
|
|
2216
|
+
}
|
|
2217
|
+
} catch {
|
|
2218
|
+
}
|
|
2219
|
+
return "text-embedding-3-small";
|
|
2207
2220
|
}
|
|
2208
2221
|
function getEmbeddingDimensions() {
|
|
2209
2222
|
return getResolvedEmbeddingLane().dimensions ?? null;
|
|
@@ -2742,6 +2755,8 @@ function getPreferredBatchSize(config) {
|
|
|
2742
2755
|
}
|
|
2743
2756
|
function parseBatchLimit(error) {
|
|
2744
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;
|
|
2745
2760
|
const explicit = error.message.match(/should not be larger than\s+(\d+)/i);
|
|
2746
2761
|
if (explicit) return parseInt(explicit[1], 10);
|
|
2747
2762
|
if (/batch size/i.test(error.message)) {
|
|
@@ -2754,37 +2769,99 @@ async function fetchWithRetry(url, apiKey, body, options = {}, attempt = 0, nati
|
|
|
2754
2769
|
const controller = new AbortController();
|
|
2755
2770
|
const timeoutMs = typeof options.timeoutMs === "number" && Number.isFinite(options.timeoutMs) ? Math.max(100, Math.min(Math.floor(options.timeoutMs), 6e4)) : 1e4;
|
|
2756
2771
|
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
2772
|
+
const signal = options.signal ? AbortSignal.any([options.signal, controller.signal]) : controller.signal;
|
|
2757
2773
|
let response;
|
|
2758
2774
|
try {
|
|
2759
2775
|
response = await fetch(url, {
|
|
2760
2776
|
method: "POST",
|
|
2761
2777
|
headers: nativeGemini ? { "Content-Type": "application/json", "x-goog-api-key": apiKey } : { "Content-Type": "application/json", "Authorization": `Bearer ${apiKey}` },
|
|
2762
2778
|
body: JSON.stringify(body),
|
|
2763
|
-
signal
|
|
2779
|
+
signal
|
|
2764
2780
|
});
|
|
2765
2781
|
} catch (err) {
|
|
2766
2782
|
clearTimeout(timeout);
|
|
2767
|
-
if (err instanceof Error && err.name === "AbortError") {
|
|
2783
|
+
if (err instanceof Error && (err.name === "AbortError" || controller.signal.aborted || options.signal?.aborted)) {
|
|
2768
2784
|
throw new Error(`Embedding API timeout after ${timeoutMs}ms: ${url}`);
|
|
2769
2785
|
}
|
|
2770
2786
|
throw err;
|
|
2771
2787
|
}
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
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);
|
|
2775
2816
|
}
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
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) {
|
|
2781
2845
|
await new Promise((resolve) => setTimeout(resolve, waitMs));
|
|
2782
|
-
return
|
|
2846
|
+
return;
|
|
2783
2847
|
}
|
|
2784
|
-
const
|
|
2785
|
-
throw new Error(
|
|
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
|
+
});
|
|
2786
2863
|
}
|
|
2787
|
-
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;
|
|
2788
2865
|
var init_api_provider = __esm({
|
|
2789
2866
|
"src/embedding/api-provider.ts"() {
|
|
2790
2867
|
"use strict";
|
|
@@ -2803,6 +2880,14 @@ var init_api_provider = __esm({
|
|
|
2803
2880
|
DASHSCOPE_MAX_BATCH_SIZE = 10;
|
|
2804
2881
|
MAX_RETRIES2 = 3;
|
|
2805
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
|
+
};
|
|
2806
2891
|
APIEmbeddingProvider = class _APIEmbeddingProvider {
|
|
2807
2892
|
name;
|
|
2808
2893
|
dimensions;
|
|
@@ -3014,15 +3099,14 @@ var init_api_provider = __esm({
|
|
|
3014
3099
|
}
|
|
3015
3100
|
} catch (error) {
|
|
3016
3101
|
const providerLimit = parseBatchLimit(error);
|
|
3017
|
-
|
|
3018
|
-
if (chunkTexts.length > 1 && fallbackSize < chunkTexts.length) {
|
|
3102
|
+
if (providerLimit !== null && chunkTexts.length > 1 && providerLimit < chunkTexts.length) {
|
|
3019
3103
|
console.error(
|
|
3020
|
-
`[memorix] Embedding batch too large for provider, retrying in chunks of ${
|
|
3104
|
+
`[memorix] Embedding batch too large for provider, retrying in chunks of ${providerLimit}`
|
|
3021
3105
|
);
|
|
3022
|
-
for (let start = 0; start < chunkTexts.length; start +=
|
|
3106
|
+
for (let start = 0; start < chunkTexts.length; start += providerLimit) {
|
|
3023
3107
|
await processChunk(
|
|
3024
|
-
chunkTexts.slice(start, start +
|
|
3025
|
-
chunkIndices.slice(start, start +
|
|
3108
|
+
chunkTexts.slice(start, start + providerLimit),
|
|
3109
|
+
chunkIndices.slice(start, start + providerLimit)
|
|
3026
3110
|
);
|
|
3027
3111
|
}
|
|
3028
3112
|
return;
|
|
@@ -3886,22 +3970,23 @@ function getLLMConfig() {
|
|
|
3886
3970
|
function setLLMConfig(config) {
|
|
3887
3971
|
currentConfig = config;
|
|
3888
3972
|
}
|
|
3889
|
-
async function callLLM(systemPrompt, userMessage) {
|
|
3973
|
+
async function callLLM(systemPrompt, userMessage, signal) {
|
|
3890
3974
|
if (!currentConfig) {
|
|
3891
3975
|
throw new Error("LLM not configured. Set MEMORIX_LLM_API_KEY or OPENAI_API_KEY.");
|
|
3892
3976
|
}
|
|
3893
3977
|
if (currentConfig.provider === "anthropic") {
|
|
3894
|
-
return callAnthropic(systemPrompt, userMessage);
|
|
3978
|
+
return callAnthropic(systemPrompt, userMessage, signal);
|
|
3895
3979
|
}
|
|
3896
|
-
return callOpenAICompatible(systemPrompt, userMessage);
|
|
3980
|
+
return callOpenAICompatible(systemPrompt, userMessage, signal);
|
|
3897
3981
|
}
|
|
3898
|
-
async function callOpenAICompatible(systemPrompt, userMessage) {
|
|
3982
|
+
async function callOpenAICompatible(systemPrompt, userMessage, signal) {
|
|
3899
3983
|
const config = currentConfig;
|
|
3900
3984
|
let base = config.baseUrl.replace(/\/+$/, "");
|
|
3901
3985
|
if (!base.endsWith("/v1")) base += "/v1";
|
|
3902
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);
|
|
3903
3988
|
const response = await fetch(url, {
|
|
3904
|
-
signal:
|
|
3989
|
+
signal: fetchSignal,
|
|
3905
3990
|
method: "POST",
|
|
3906
3991
|
headers: {
|
|
3907
3992
|
"Content-Type": "application/json",
|
|
@@ -3918,10 +4003,10 @@ async function callOpenAICompatible(systemPrompt, userMessage) {
|
|
|
3918
4003
|
})
|
|
3919
4004
|
});
|
|
3920
4005
|
if (!response.ok) {
|
|
3921
|
-
const error = await readResponseText(response,
|
|
4006
|
+
const error = await readResponseText(response, signal, 64 * 1024).catch(() => "unknown error");
|
|
3922
4007
|
throw new Error(`LLM API error (${response.status}): ${error}`);
|
|
3923
4008
|
}
|
|
3924
|
-
const data = await readResponseJson(response);
|
|
4009
|
+
const data = await readResponseJson(response, signal);
|
|
3925
4010
|
return {
|
|
3926
4011
|
content: data.choices[0]?.message?.content ?? "",
|
|
3927
4012
|
usage: data.usage ? {
|
|
@@ -3930,11 +4015,12 @@ async function callOpenAICompatible(systemPrompt, userMessage) {
|
|
|
3930
4015
|
} : void 0
|
|
3931
4016
|
};
|
|
3932
4017
|
}
|
|
3933
|
-
async function callAnthropic(systemPrompt, userMessage) {
|
|
4018
|
+
async function callAnthropic(systemPrompt, userMessage, signal) {
|
|
3934
4019
|
const config = currentConfig;
|
|
3935
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);
|
|
3936
4022
|
const response = await fetch(url, {
|
|
3937
|
-
signal:
|
|
4023
|
+
signal: fetchSignal,
|
|
3938
4024
|
method: "POST",
|
|
3939
4025
|
headers: {
|
|
3940
4026
|
"Content-Type": "application/json",
|
|
@@ -3952,10 +4038,10 @@ async function callAnthropic(systemPrompt, userMessage) {
|
|
|
3952
4038
|
})
|
|
3953
4039
|
});
|
|
3954
4040
|
if (!response.ok) {
|
|
3955
|
-
const error = await readResponseText(response,
|
|
4041
|
+
const error = await readResponseText(response, signal, 64 * 1024).catch(() => "unknown error");
|
|
3956
4042
|
throw new Error(`Anthropic API error (${response.status}): ${error}`);
|
|
3957
4043
|
}
|
|
3958
|
-
const data = await readResponseJson(response);
|
|
4044
|
+
const data = await readResponseJson(response, signal);
|
|
3959
4045
|
return {
|
|
3960
4046
|
content: data.content[0]?.text ?? "",
|
|
3961
4047
|
usage: data.usage ? {
|
|
@@ -4460,6 +4546,57 @@ function withTimeout(promise, ms, label) {
|
|
|
4460
4546
|
);
|
|
4461
4547
|
});
|
|
4462
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
|
+
}
|
|
4463
4600
|
var init_timeout = __esm({
|
|
4464
4601
|
"src/timeout.ts"() {
|
|
4465
4602
|
"use strict";
|
|
@@ -5324,16 +5461,18 @@ async function searchObservations(options) {
|
|
|
5324
5461
|
);
|
|
5325
5462
|
} else {
|
|
5326
5463
|
const EMBEDDING_TIMEOUT_MS = 15e3;
|
|
5327
|
-
queryVector = await
|
|
5328
|
-
provider2.embedInput ? provider2.embedInput(
|
|
5464
|
+
queryVector = await withTimeoutSignal(
|
|
5465
|
+
(signal) => provider2.embedInput ? provider2.embedInput(
|
|
5329
5466
|
{ modality: "text", text: expandedEmbeddingQuery },
|
|
5330
|
-
{ intent: "query", timeoutMs: EMBEDDING_TIMEOUT_MS, retry: false }
|
|
5467
|
+
{ intent: "query", timeoutMs: EMBEDDING_TIMEOUT_MS, retry: false, signal }
|
|
5331
5468
|
) : provider2.embed(expandedEmbeddingQuery, {
|
|
5332
5469
|
timeoutMs: EMBEDDING_TIMEOUT_MS,
|
|
5333
|
-
retry: false
|
|
5470
|
+
retry: false,
|
|
5471
|
+
signal
|
|
5334
5472
|
}),
|
|
5335
5473
|
EMBEDDING_TIMEOUT_MS,
|
|
5336
|
-
"Embedding"
|
|
5474
|
+
"Embedding",
|
|
5475
|
+
options.signal
|
|
5337
5476
|
);
|
|
5338
5477
|
mark("embedding");
|
|
5339
5478
|
const cjkRatio = (originalQuery.match(/[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/g) || []).length / originalQuery.length;
|
|
@@ -143253,7 +143392,13 @@ async function maintainLongTermMemories(input) {
|
|
|
143253
143392
|
}
|
|
143254
143393
|
for (const group of groups.values()) {
|
|
143255
143394
|
if (group.length < 2) continue;
|
|
143256
|
-
group.sort((a, b) =>
|
|
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
|
+
});
|
|
143257
143402
|
const newest = group[0];
|
|
143258
143403
|
for (const older of group.slice(1)) {
|
|
143259
143404
|
try {
|
|
@@ -2,6 +2,44 @@
|
|
|
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
|
+
|
|
19
|
+
## [1.5.0] - 2026-08-14
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
- **Stability stress suite** -- Five deterministic, offline stress exams now
|
|
23
|
+
run in the normal test suite: a 2,000-observation corpus (planted needles,
|
|
24
|
+
ranked determinism, project isolation), 60-way parallel writes plus
|
|
25
|
+
20-way same-topic upserts, session churn across projects with parallel
|
|
26
|
+
starts, 300-record long-term maintenance exactness, and repeated CLI
|
|
27
|
+
invocations. Live embedding stress exams are env-gated
|
|
28
|
+
(`MEMORIX_RUN_LIVE_EMBEDDING_TESTS=1`) and never run in CI.
|
|
29
|
+
- **Provider-aware embedding default** -- When the embedding base URL
|
|
30
|
+
points at OpenRouter and no model is configured, the default is now
|
|
31
|
+
`qwen/qwen3-embedding-8b` (4096 dimensions) instead of the OpenAI-only
|
|
32
|
+
default. An explicitly configured model always wins.
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
- **Verified OpenRouter embedding lane** -- The `api` embedding provider
|
|
36
|
+
was exercised live against `qwen/qwen3-embedding-8b` on OpenRouter:
|
|
37
|
+
a 100-text batch, cache round-trip, and provider-singleton stability all
|
|
38
|
+
pass. Docs cover the env-var form and borrowing `OPENROUTER_API_KEY`.
|
|
39
|
+
- **Multimodal vision option documented** -- Image analysis can run on the
|
|
40
|
+
LLM lane through an OpenAI-compatible vision endpoint, including
|
|
41
|
+
OpenRouter models such as `qwen/qwen3-vl-8b-instruct`.
|
|
42
|
+
|
|
5
43
|
## [1.4.6] - 2026-08-14
|
|
6
44
|
|
|
7
45
|
### Added
|