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.
- package/CHANGELOG.md +37 -0
- package/README.md +3 -3
- package/README.zh-CN.md +3 -3
- package/dist/cli/index.js +452 -109
- package/dist/index.js +376 -87
- package/dist/maintenance-runner.js +174 -41
- package/dist/media-audio-runner.js +174 -41
- package/dist/media-video-runner.js +174 -41
- package/dist/memcode-runtime/CHANGELOG.md +37 -0
- package/dist/sdk.js +375 -86
- package/dist/types.d.ts +2 -0
- package/dist/vector-backfill-runner.js +174 -41
- package/docs/1.2.4-PERSISTENT-MEMORY-DELIVERY.md +1 -1
- package/docs/API_REFERENCE.md +1 -1
- package/docs/DEVELOPMENT.md +5 -3
- 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/cli/commands/codegraph.ts +48 -5
- package/src/cli/commands/context.ts +20 -4
- package/src/cli/commands/resume.ts +4 -0
- package/src/codegraph/auto-context.ts +4 -1
- package/src/codegraph/context-pack.ts +3 -1
- package/src/codegraph/external-provider.ts +93 -0
- package/src/embedding/api-provider.ts +105 -22
- package/src/embedding/provider.ts +2 -0
- package/src/hooks/installers/index.ts +1 -1
- package/src/hooks/official-skills.ts +3 -3
- package/src/hooks/rules/memorix-agent-rules.md +1 -1
- package/src/knowledge/context-receipt.ts +41 -0
- package/src/knowledge/workflows.ts +6 -3
- package/src/knowledge/workset.ts +56 -2
- package/src/llm/provider.ts +17 -8
- 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/long-term.ts +7 -1
- package/src/server.ts +29 -13
- package/src/store/orama-store.ts +6 -4
- package/src/timeout.ts +67 -0
- package/src/types.ts +2 -0
package/dist/types.d.ts
CHANGED
|
@@ -360,6 +360,8 @@ interface SearchOptions {
|
|
|
360
360
|
* `thorough` explicitly permits optional LLM query rewrite and reranking.
|
|
361
361
|
*/
|
|
362
362
|
quality?: RetrievalQuality;
|
|
363
|
+
/** Abort provider-backed retrieval when the owning request is cancelled. */
|
|
364
|
+
signal?: AbortSignal;
|
|
363
365
|
}
|
|
364
366
|
/** Topic key family heuristics for suggesting stable topic keys */
|
|
365
367
|
declare const TOPIC_KEY_FAMILIES: Record<string, string[]>;
|
|
@@ -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
|
|
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
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
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
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
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
|
|
2846
|
+
return;
|
|
2796
2847
|
}
|
|
2797
|
-
const
|
|
2798
|
-
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
|
+
});
|
|
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
|
-
|
|
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 ${
|
|
3104
|
+
`[memorix] Embedding batch too large for provider, retrying in chunks of ${providerLimit}`
|
|
3034
3105
|
);
|
|
3035
|
-
for (let start = 0; start < chunkTexts.length; start +=
|
|
3106
|
+
for (let start = 0; start < chunkTexts.length; start += providerLimit) {
|
|
3036
3107
|
await processChunk(
|
|
3037
|
-
chunkTexts.slice(start, start +
|
|
3038
|
-
chunkIndices.slice(start, start +
|
|
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:
|
|
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,
|
|
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:
|
|
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,
|
|
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
|
|
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;
|
|
@@ -145244,7 +145370,13 @@ async function maintainLongTermMemories(input) {
|
|
|
145244
145370
|
}
|
|
145245
145371
|
for (const group of groups.values()) {
|
|
145246
145372
|
if (group.length < 2) continue;
|
|
145247
|
-
group.sort((a, b) =>
|
|
145373
|
+
group.sort((a, b) => {
|
|
145374
|
+
const byCreatedAt = Date.parse(b.createdAt) - Date.parse(a.createdAt);
|
|
145375
|
+
if (byCreatedAt !== 0) return byCreatedAt;
|
|
145376
|
+
const byUpdatedAt = Date.parse(b.updatedAt) - Date.parse(a.updatedAt);
|
|
145377
|
+
if (byUpdatedAt !== 0) return byUpdatedAt;
|
|
145378
|
+
return b.id.localeCompare(a.id);
|
|
145379
|
+
});
|
|
145248
145380
|
const newest = group[0];
|
|
145249
145381
|
for (const older of group.slice(1)) {
|
|
145250
145382
|
try {
|
|
@@ -147648,6 +147780,7 @@ var init_task_lens = __esm({
|
|
|
147648
147780
|
var workflows_exports = {};
|
|
147649
147781
|
__export(workflows_exports, {
|
|
147650
147782
|
WORKFLOW_ADAPTER_TARGETS: () => WORKFLOW_ADAPTER_TARGETS,
|
|
147783
|
+
WORKFLOW_AGENT_TARGETS: () => WORKFLOW_AGENT_TARGETS,
|
|
147651
147784
|
applyWorkflowAdapter: () => applyWorkflowAdapter,
|
|
147652
147785
|
importWindsurfWorkflows: () => importWindsurfWorkflows,
|
|
147653
147786
|
parseWorkflowMarkdown: () => parseWorkflowMarkdown,
|
|
@@ -147713,7 +147846,7 @@ function optionalStatus(data) {
|
|
|
147713
147846
|
return value;
|
|
147714
147847
|
}
|
|
147715
147848
|
function normalizeAgents(values) {
|
|
147716
|
-
const unknown3 = values.filter((value) => !
|
|
147849
|
+
const unknown3 = values.filter((value) => !WORKFLOW_AGENT_TARGETS.includes(value));
|
|
147717
147850
|
if (unknown3.length > 0) {
|
|
147718
147851
|
throw new Error("workflow frontmatter has unsupported agent: " + unknown3[0]);
|
|
147719
147852
|
}
|
|
@@ -147989,7 +148122,7 @@ function taskScore(workflow, task) {
|
|
|
147989
148122
|
return { score, reasons: [...new Set(reasons)] };
|
|
147990
148123
|
}
|
|
147991
148124
|
function selectWorkflows(input) {
|
|
147992
|
-
const selected = input.workflows.filter((workflow) => workflow.status === "active").map((workflow) => {
|
|
148125
|
+
const selected = input.workflows.filter((workflow) => workflow.status === "active" && (!input.agent || workflow.compatibleAgents.includes(input.agent))).map((workflow) => {
|
|
147993
148126
|
const match2 = taskScore(workflow, input.task);
|
|
147994
148127
|
const cautions = input.store && input.projectId ? input.store.recentFailureCautions(input.projectId, workflow.id) : [];
|
|
147995
148128
|
return {
|
|
@@ -148269,7 +148402,7 @@ async function selectWorkspaceWorkflows(input) {
|
|
|
148269
148402
|
errors: synced.errors
|
|
148270
148403
|
};
|
|
148271
148404
|
}
|
|
148272
|
-
var
|
|
148405
|
+
var WORKFLOW_AGENT_TARGETS, WORKFLOW_ADAPTER_TARGETS, STATUS_VALUES, LENS_TERMS, GENERIC_WORKFLOW_LENSES, GENERIC_WORKFLOW_TERMS;
|
|
148273
148406
|
var init_workflows2 = __esm({
|
|
148274
148407
|
"src/knowledge/workflows.ts"() {
|
|
148275
148408
|
"use strict";
|
|
@@ -148281,7 +148414,7 @@ var init_workflows2 = __esm({
|
|
|
148281
148414
|
init_workflow_store();
|
|
148282
148415
|
init_outcome_store();
|
|
148283
148416
|
init_task_lens();
|
|
148284
|
-
|
|
148417
|
+
WORKFLOW_AGENT_TARGETS = [
|
|
148285
148418
|
"windsurf",
|
|
148286
148419
|
"cursor",
|
|
148287
148420
|
"claude-code",
|
|
@@ -26,7 +26,7 @@ database, a transcript archive, or another default MCP tool.
|
|
|
26
26
|
| Start a new, unrelated task | A bounded task Workset. No automatic prior-session text. |
|
|
27
27
|
| Continue or take over prior work | The same Workset plus a bounded prior-work projection. |
|
|
28
28
|
| MCP is available | Use the existing `memorix_project_context` tool with the real task. |
|
|
29
|
-
| MCP is unavailable | Make one CLI call: `memorix resume "<task>" --json` for continuation or `memorix context "<task>" --json` for a new task. |
|
|
29
|
+
| MCP is unavailable | Make one CLI call: `memorix resume "<task>" --brief-json` for continuation or `memorix context "<task>" --brief-json` for a new task. `--json` remains the detailed diagnostic form. |
|
|
30
30
|
| Claude Code hook is installed | An explicit continuation prompt receives the same bounded prior-work Workset through the official `UserPromptSubmit` context channel, unless `memory.inject = "silent"`. |
|
|
31
31
|
| Read-only assessment | The brief may be read, but Memorix does not persist a new observation unless the user explicitly asks to save one. |
|
|
32
32
|
| Need a deliberate session handoff | Keep using the explicit session/handoff surfaces; this projection does not replace them. |
|
package/docs/API_REFERENCE.md
CHANGED
|
@@ -115,7 +115,7 @@ MCP:
|
|
|
115
115
|
- `memorix_context_pack` builds a task-specific packet with reliable current memories, lower-trust unbound memories, current code facts, freshness warnings, suggested reads, and suggested verification.
|
|
116
116
|
- `memorix_graph_context` is an advanced compact graph overview. It starts from a small task-matched baseline and can add only directly related, evidence-governed memories. Its `Graph evidence` section includes relation provenance rather than recursively dumping graph neighbors.
|
|
117
117
|
|
|
118
|
-
`memorix context` defaults to `--refresh auto`, so first use can seed Code State without a separate manual `memorix codegraph refresh`. Its brief puts live package/changelog/Git facts before memory hints and flags an old `ACTIVE_WORK.md`, legacy `progress.txt`, or legacy dev-log note as historical when it predates the latest changelog, so agents should treat current facts as the source of truth when files disagree. Task lenses keep the packet shaped to the work: bugfix briefs prefer failing tests and repros, release briefs prefer metadata/changelog/package checks, and onboarding briefs prefer docs and entry points while hiding unrelated suspect details. Continuation delivery is separate from the task lens: continuation language in `memorix_project_context` enables a bounded prior-work projection, and `memorix resume "..."` makes that choice explicit. It includes only the latest useful session summary, up to three readable durable memories, and at most one recent source-labelled compact checkpoint. Checkpoints are host lifecycle evidence, not durable memory or transcript backups; ordinary new tasks do not receive historical-session context. A completed MCP brief is the default retrieval boundary: search, detail, or Context Pack should expand it only for a named missing fact or an explicit request for deeper history. Use `--refresh never` for read-only inspection and `--refresh always` when you want to force a fresh scan.
|
|
118
|
+
`memorix context` defaults to `--refresh auto`, so first use can seed Code State without a separate manual `memorix codegraph refresh`. Its brief puts live package/changelog/Git facts before memory hints and flags an old `ACTIVE_WORK.md`, legacy `progress.txt`, or legacy dev-log note as historical when it predates the latest changelog, so agents should treat current facts as the source of truth when files disagree. Use `--brief-json` for the same bounded brief plus a receipt of selected and omitted evidence; `--json` remains the detailed legacy diagnostic form. Task lenses keep the packet shaped to the work: bugfix briefs prefer failing tests and repros, release briefs prefer metadata/changelog/package checks, and onboarding briefs prefer docs and entry points while hiding unrelated suspect details. Continuation delivery is separate from the task lens: continuation language in `memorix_project_context` enables a bounded prior-work projection, and `memorix resume "..."` makes that choice explicit. It includes only the latest useful session summary, up to three readable durable memories, and at most one recent source-labelled compact checkpoint. Checkpoints are host lifecycle evidence, not durable memory or transcript backups; ordinary new tasks do not receive historical-session context. A completed MCP brief is the default retrieval boundary: search, detail, or Context Pack should expand it only for a named missing fact or an explicit request for deeper history. Use `--refresh never` for read-only inspection and `--refresh always` when you want to force a fresh scan.
|
|
119
119
|
|
|
120
120
|
Project-specific generated, vendored, or cache paths can be excluded from Code State with `[codegraph].exclude_patterns` in `memorix.toml` or `~/.memorix/config.toml` (`codegraph.excludePatterns` in legacy YAML). User patterns extend the built-in excludes and are applied to indexing, Project Context suggested reads, and Context Pack suggested reads.
|
|
121
121
|
|
package/docs/DEVELOPMENT.md
CHANGED
|
@@ -326,9 +326,11 @@ Notes:
|
|
|
326
326
|
- `server.json` is the official MCP Registry record. Keep it synchronized with
|
|
327
327
|
`package.json` by running `npm run sync:mcp-registry`; verify it with
|
|
328
328
|
`npm run check:mcp-registry` before a release.
|
|
329
|
-
- The GitHub publish workflow
|
|
330
|
-
publish the same verified version to the official MCP Registry.
|
|
331
|
-
|
|
329
|
+
- The GitHub publish workflow can publish npm first, then uses GitHub OIDC to
|
|
330
|
+
publish the same verified version to the official MCP Registry. When npm was
|
|
331
|
+
already published manually, run the workflow with `publish_npm=false` to
|
|
332
|
+
publish only the Registry record. It does not require a Registry token or
|
|
333
|
+
private key in repository secrets.
|
|
332
334
|
- `@memorix/ai`, `@memorix/agent-core`, `@memorix/tui`, and `@memorix/memcode` are internal workspaces. Their code is bundled into the root `memorix` distribution and they must stay `private` unless the project deliberately establishes an owned npm scope and a separate package-support contract.
|
|
333
335
|
- npm publish is usually manual, especially when 2FA is enabled
|
|
334
336
|
- GitHub release automation should not be treated as a substitute for manual runtime validation
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memorix",
|
|
3
3
|
"mcpName": "io.github.AVIDS2/memorix",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.6.0",
|
|
5
5
|
"description": "Local-first shared memory layer for AI coding agents across MCP clients, Git history, reasoning context, and project sessions.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"sideEffects": false,
|
package/server.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"url": "https://github.com/AVIDS2/memorix",
|
|
8
8
|
"source": "github"
|
|
9
9
|
},
|
|
10
|
-
"version": "1.
|
|
10
|
+
"version": "1.6.0",
|
|
11
11
|
"websiteUrl": "https://github.com/AVIDS2/memorix#readme",
|
|
12
12
|
"icons": [
|
|
13
13
|
{
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
{
|
|
23
23
|
"registryType": "npm",
|
|
24
24
|
"identifier": "memorix",
|
|
25
|
-
"version": "1.
|
|
25
|
+
"version": "1.6.0",
|
|
26
26
|
"runtimeHint": "npx",
|
|
27
27
|
"packageArguments": [
|
|
28
28
|
{
|
|
@@ -5,11 +5,14 @@ import { assembleContextPackForTask, attachTaskWorkset, buildContextPackPrompt }
|
|
|
5
5
|
import { backfillMissingObservationCodeRefs } from '../../codegraph/binder.js';
|
|
6
6
|
import { collectCurrentProjectFacts, formatGitFact } from '../../codegraph/current-facts.js';
|
|
7
7
|
import { resolveTaskLens } from '../../codegraph/task-lens.js';
|
|
8
|
-
import { getExternalCodeGraphContext, inspectExternalCodeGraph } from '../../codegraph/external-provider.js';
|
|
8
|
+
import { getExternalCodeGraphContext, inspectExternalCodeGraph, runExternalCodeGraphLifecycle } from '../../codegraph/external-provider.js';
|
|
9
9
|
import type { CodeGraphProviderQuality } from '../../codegraph/types.js';
|
|
10
10
|
import { getResolvedConfig } from '../../config/resolved-config.js';
|
|
11
|
+
import { buildBoundedContextReceipt } from '../../knowledge/context-receipt.js';
|
|
12
|
+
import { WORKFLOW_AGENT_TARGETS } from '../../knowledge/workflows.js';
|
|
11
13
|
import { getAllObservations } from '../../memory/observations.js';
|
|
12
14
|
import { filterReadableObservations } from '../../memory/visibility.js';
|
|
15
|
+
import type { AgentTarget } from '../../types.js';
|
|
13
16
|
import { emitError, emitResult, getCliProjectContext, parsePositiveInt } from './operator-shared.js';
|
|
14
17
|
|
|
15
18
|
function formatSnapshotStatus(status: ReturnType<CodeGraphStore['status']>): string[] {
|
|
@@ -48,6 +51,7 @@ function formatStatus(status: ReturnType<CodeGraphStore['status']>, quality?: Co
|
|
|
48
51
|
`- Persistent provider: ${status.provider} (heuristic local index)`,
|
|
49
52
|
`- External semantic CodeGraph: ${quality.external.state}`
|
|
50
53
|
+ (quality.external.reason ? ` (${quality.external.reason})` : ''),
|
|
54
|
+
`- Task-scoped provider: ${quality.selected} (${quality.selectedQuality})`,
|
|
51
55
|
]
|
|
52
56
|
: []),
|
|
53
57
|
].join('\n');
|
|
@@ -57,6 +61,8 @@ function formatUsageHint(): string {
|
|
|
57
61
|
return [
|
|
58
62
|
'Usage:',
|
|
59
63
|
' memorix codegraph refresh',
|
|
64
|
+
' memorix codegraph init # explicit local CodeGraph initialization',
|
|
65
|
+
' memorix codegraph sync # explicit local CodeGraph synchronization',
|
|
60
66
|
' memorix codegraph status --json',
|
|
61
67
|
' memorix codegraph diff [--from <snapshot>] [--to <snapshot>]',
|
|
62
68
|
' memorix codegraph impact --path <relative-source-path>',
|
|
@@ -116,25 +122,34 @@ function compactFacts(project: { rootPath: string }): { facts: string[]; dirty:
|
|
|
116
122
|
return { facts, dirty: current.git.dirty };
|
|
117
123
|
}
|
|
118
124
|
|
|
125
|
+
function coerceAgentTarget(input?: string): AgentTarget | undefined {
|
|
126
|
+
const value = input?.trim().toLowerCase();
|
|
127
|
+
if (!value) return undefined;
|
|
128
|
+
if (WORKFLOW_AGENT_TARGETS.includes(value as AgentTarget)) return value as AgentTarget;
|
|
129
|
+
throw new Error('agent must be one of: ' + WORKFLOW_AGENT_TARGETS.join(', '));
|
|
130
|
+
}
|
|
131
|
+
|
|
119
132
|
export default defineCommand({
|
|
120
133
|
meta: {
|
|
121
134
|
name: 'codegraph',
|
|
122
135
|
description: 'Inspect and refresh CodeGraph Memory for the current project',
|
|
123
136
|
},
|
|
124
137
|
args: {
|
|
125
|
-
action: { type: 'string', description: 'Action: status, refresh, diff, impact, or context-pack' },
|
|
138
|
+
action: { type: 'string', description: 'Action: status, refresh, diff, impact, init, sync, or context-pack' },
|
|
126
139
|
task: { type: 'string', description: 'Task text for context-pack' },
|
|
127
140
|
path: { type: 'string', description: 'Indexed relative source path for impact' },
|
|
128
141
|
limit: { type: 'string', description: 'Max active memories to inspect for context-pack' },
|
|
129
142
|
from: { type: 'string', description: 'Earlier CodeGraph snapshot id for diff' },
|
|
130
143
|
to: { type: 'string', description: 'Later CodeGraph snapshot id for diff' },
|
|
131
144
|
impactLimit: { type: 'string', description: 'Maximum graph relations to inspect for diff impact' },
|
|
145
|
+
agent: { type: 'string', description: 'Optional target agent for compatible workflow selection' },
|
|
132
146
|
json: { type: 'boolean', description: 'Emit machine-readable JSON output' },
|
|
147
|
+
briefJson: { type: 'boolean', description: 'Emit only the bounded agent brief and receipt JSON for context-pack' },
|
|
133
148
|
},
|
|
134
149
|
run: async ({ args }) => {
|
|
135
150
|
const positional = (args._ as string[]) ?? [];
|
|
136
151
|
const action = (positional[0] || (args.action as string | undefined) || 'status').toLowerCase();
|
|
137
|
-
const asJson = !!args.json;
|
|
152
|
+
const asJson = !!args.json || !!args.briefJson;
|
|
138
153
|
|
|
139
154
|
try {
|
|
140
155
|
const { project, dataDir, reader } = await getCliProjectContext();
|
|
@@ -206,6 +221,27 @@ export default defineCommand({
|
|
|
206
221
|
return;
|
|
207
222
|
}
|
|
208
223
|
|
|
224
|
+
case 'init':
|
|
225
|
+
case 'sync': {
|
|
226
|
+
const lifecycle = await runExternalCodeGraphLifecycle({
|
|
227
|
+
action,
|
|
228
|
+
projectRoot: project.rootPath,
|
|
229
|
+
mode: codegraphConfig.externalContext,
|
|
230
|
+
command: codegraphConfig.externalCommand,
|
|
231
|
+
timeoutMs: codegraphConfig.externalTimeoutMs,
|
|
232
|
+
});
|
|
233
|
+
emitResult(
|
|
234
|
+
{ project, lifecycle, providerQuality: lifecycle.quality },
|
|
235
|
+
[
|
|
236
|
+
lifecycle.message,
|
|
237
|
+
`- External semantic CodeGraph: ${lifecycle.health.state}`,
|
|
238
|
+
`- Task-scoped provider: ${lifecycle.quality.selected} (${lifecycle.quality.selectedQuality})`,
|
|
239
|
+
].join('\n'),
|
|
240
|
+
asJson,
|
|
241
|
+
);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
209
245
|
case 'diff': {
|
|
210
246
|
const snapshots = store.listSnapshots(project.id, 2);
|
|
211
247
|
const fromSnapshotId = (args.from as string | undefined)?.trim() || snapshots[1]?.id;
|
|
@@ -299,13 +335,20 @@ export default defineCommand({
|
|
|
299
335
|
? { runtimeCautions: [{ kind: 'external-codegraph-fallback' as const, message: external.caution }] }
|
|
300
336
|
: {}),
|
|
301
337
|
reader,
|
|
338
|
+
agent: coerceAgentTarget(args.agent as string | undefined),
|
|
302
339
|
});
|
|
303
|
-
emitResult(
|
|
340
|
+
emitResult(
|
|
341
|
+
args.briefJson && pack.workset
|
|
342
|
+
? buildBoundedContextReceipt({ workset: pack.workset, providerQuality: external.quality })
|
|
343
|
+
: { project, pack, providerQuality: external.quality },
|
|
344
|
+
buildContextPackPrompt(pack),
|
|
345
|
+
asJson,
|
|
346
|
+
);
|
|
304
347
|
return;
|
|
305
348
|
}
|
|
306
349
|
|
|
307
350
|
default:
|
|
308
|
-
emitError(`unknown codegraph action "${action}". Use "status", "refresh", "diff", "impact", or "context-pack".`, asJson);
|
|
351
|
+
emitError(`unknown codegraph action "${action}". Use "status", "refresh", "init", "sync", "diff", "impact", or "context-pack".`, asJson);
|
|
309
352
|
}
|
|
310
353
|
} catch (error) {
|
|
311
354
|
emitError(error instanceof Error ? error.message : String(error), asJson);
|