memorix 1.2.3 → 1.2.5
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 +33 -0
- package/README.md +3 -3
- package/README.zh-CN.md +3 -3
- package/dist/cli/index.js +5273 -4730
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +506 -99
- package/dist/index.js.map +1 -1
- package/dist/maintenance-runner.js +176 -53
- package/dist/maintenance-runner.js.map +1 -1
- package/dist/memcode-runtime/CHANGELOG.md +33 -0
- package/dist/sdk.d.ts +4 -2
- package/dist/sdk.js +507 -99
- package/dist/sdk.js.map +1 -1
- package/dist/types.d.ts +8 -1
- package/dist/types.js.map +1 -1
- package/docs/1.2.4-PERSISTENT-MEMORY-DELIVERY.md +86 -0
- package/docs/1.2.5-RETRIEVAL-PERFORMANCE.md +85 -0
- package/docs/AGENT_OPERATOR_PLAYBOOK.md +13 -1
- package/docs/API_REFERENCE.md +13 -3
- package/docs/PERFORMANCE.md +22 -1
- package/docs/dev-log/progress.txt +73 -7
- package/package.json +2 -1
- package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
- package/src/cli/capability-map.ts +1 -1
- package/src/cli/command-guide.ts +4 -1
- package/src/cli/commands/agent-integrations.ts +5 -1
- package/src/cli/commands/codegraph.ts +1 -1
- package/src/cli/commands/context.ts +9 -1
- package/src/cli/commands/memory.ts +12 -3
- package/src/cli/commands/operator-shared.ts +74 -2
- package/src/cli/commands/resume.ts +31 -0
- package/src/cli/index.ts +5 -1
- package/src/codegraph/auto-context.ts +54 -1
- package/src/codegraph/task-lens.ts +29 -0
- package/src/compact/token-budget.ts +16 -1
- package/src/config/resolved-config.ts +29 -4
- package/src/config/toml-loader.ts +9 -5
- package/src/hooks/handler.ts +127 -66
- package/src/hooks/installers/index.ts +5 -4
- package/src/hooks/official-skills.ts +6 -4
- package/src/hooks/rules/memorix-agent-rules.md +9 -7
- package/src/knowledge/context-assembly.ts +4 -1
- package/src/knowledge/workset.ts +89 -1
- package/src/memory/session.ts +144 -10
- package/src/runtime/project-maintenance.ts +1 -14
- package/src/sdk.ts +4 -0
- package/src/server.ts +144 -25
- package/src/store/bun-sqlite-compat.ts +118 -15
- package/src/store/orama-store.ts +39 -18
- package/src/store/sqlite-db.ts +3 -3
- package/src/timeout.ts +23 -0
- package/src/types.ts +8 -0
package/dist/index.js
CHANGED
|
@@ -35,36 +35,111 @@ var init_esm_shims = __esm({
|
|
|
35
35
|
|
|
36
36
|
// src/store/bun-sqlite-compat.ts
|
|
37
37
|
import { createRequire } from "module";
|
|
38
|
+
function loadNodeSqlite() {
|
|
39
|
+
const nodeSqlite = requireFromHere("node:sqlite");
|
|
40
|
+
return nodeSqlite.DatabaseSync;
|
|
41
|
+
}
|
|
42
|
+
function isNativeBindingFailure(error) {
|
|
43
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
44
|
+
return /could not locate the bindings file|better_sqlite3\.node|module did not self-register/i.test(message);
|
|
45
|
+
}
|
|
46
|
+
function addCompatibility(db2) {
|
|
47
|
+
if (!db2.pragma) {
|
|
48
|
+
db2.pragma = function(pragma, options) {
|
|
49
|
+
const statement = db2.prepare(`PRAGMA ${pragma}`);
|
|
50
|
+
if (options?.simple) {
|
|
51
|
+
const result = statement.get();
|
|
52
|
+
return result ? Object.values(result)[0] : void 0;
|
|
53
|
+
}
|
|
54
|
+
return statement.all();
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
if (!db2.transaction) {
|
|
58
|
+
let depth = 0;
|
|
59
|
+
let sequence = 0;
|
|
60
|
+
db2.transaction = function(fn) {
|
|
61
|
+
return (...args) => {
|
|
62
|
+
const outermost = depth === 0;
|
|
63
|
+
const savepoint = `memorix_tx_${++sequence}`;
|
|
64
|
+
if (outermost) {
|
|
65
|
+
db2.exec("BEGIN IMMEDIATE");
|
|
66
|
+
} else {
|
|
67
|
+
db2.exec(`SAVEPOINT ${savepoint}`);
|
|
68
|
+
}
|
|
69
|
+
depth += 1;
|
|
70
|
+
try {
|
|
71
|
+
const result = fn(...args);
|
|
72
|
+
if (result && typeof result.then === "function") {
|
|
73
|
+
throw new Error("[memorix] SQLite transactions must be synchronous");
|
|
74
|
+
}
|
|
75
|
+
depth -= 1;
|
|
76
|
+
if (outermost) {
|
|
77
|
+
db2.exec("COMMIT");
|
|
78
|
+
} else {
|
|
79
|
+
db2.exec(`RELEASE SAVEPOINT ${savepoint}`);
|
|
80
|
+
}
|
|
81
|
+
return result;
|
|
82
|
+
} catch (error) {
|
|
83
|
+
depth -= 1;
|
|
84
|
+
try {
|
|
85
|
+
if (outermost) {
|
|
86
|
+
db2.exec("ROLLBACK");
|
|
87
|
+
} else {
|
|
88
|
+
db2.exec(`ROLLBACK TO SAVEPOINT ${savepoint}`);
|
|
89
|
+
db2.exec(`RELEASE SAVEPOINT ${savepoint}`);
|
|
90
|
+
}
|
|
91
|
+
} catch {
|
|
92
|
+
}
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
return db2;
|
|
99
|
+
}
|
|
100
|
+
function instantiateDatabase(Sqlite, filePath, options) {
|
|
101
|
+
return driver === "node:sqlite" && options === void 0 ? new Sqlite(filePath) : new Sqlite(filePath, options);
|
|
102
|
+
}
|
|
38
103
|
function loadSqlite() {
|
|
39
104
|
if (Database) return Database;
|
|
105
|
+
if (process.env.MEMORIX_SQLITE_DRIVER === "node") {
|
|
106
|
+
Database = loadNodeSqlite();
|
|
107
|
+
driver = "node:sqlite";
|
|
108
|
+
return Database;
|
|
109
|
+
}
|
|
40
110
|
try {
|
|
41
111
|
Database = requireFromHere("better-sqlite3");
|
|
112
|
+
driver = "better-sqlite3";
|
|
113
|
+
return Database;
|
|
114
|
+
} catch {
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
Database = loadNodeSqlite();
|
|
118
|
+
driver = "node:sqlite";
|
|
42
119
|
return Database;
|
|
43
120
|
} catch {
|
|
44
121
|
}
|
|
45
122
|
try {
|
|
46
123
|
const bunSqlite = requireFromHere("bun:sqlite");
|
|
47
124
|
Database = bunSqlite.Database;
|
|
125
|
+
driver = "bun:sqlite";
|
|
48
126
|
return Database;
|
|
49
127
|
} catch {
|
|
50
|
-
throw new Error("[memorix]
|
|
128
|
+
throw new Error("[memorix] SQLite is unavailable (better-sqlite3, node:sqlite, and bun:sqlite failed)");
|
|
51
129
|
}
|
|
52
130
|
}
|
|
53
131
|
function createDatabase(path25, options) {
|
|
54
132
|
const Sqlite = loadSqlite();
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return db2.prepare(`PRAGMA ${pragma}`).all();
|
|
63
|
-
};
|
|
133
|
+
try {
|
|
134
|
+
return addCompatibility(instantiateDatabase(Sqlite, path25, options));
|
|
135
|
+
} catch (error) {
|
|
136
|
+
if (driver !== "better-sqlite3" || !isNativeBindingFailure(error)) throw error;
|
|
137
|
+
Database = loadNodeSqlite();
|
|
138
|
+
driver = "node:sqlite";
|
|
139
|
+
return addCompatibility(instantiateDatabase(Database, path25, options));
|
|
64
140
|
}
|
|
65
|
-
return db2;
|
|
66
141
|
}
|
|
67
|
-
var Database, requireFromHere;
|
|
142
|
+
var Database, driver, requireFromHere;
|
|
68
143
|
var init_bun_sqlite_compat = __esm({
|
|
69
144
|
"src/store/bun-sqlite-compat.ts"() {
|
|
70
145
|
"use strict";
|
|
@@ -83,7 +158,7 @@ function loadBetterSqlite3() {
|
|
|
83
158
|
BetterSqlite3 = loadSqlite();
|
|
84
159
|
return BetterSqlite3;
|
|
85
160
|
} catch {
|
|
86
|
-
throw new Error("[memorix] SQLite is not available (
|
|
161
|
+
throw new Error("[memorix] SQLite is not available (better-sqlite3, node:sqlite, and bun:sqlite all failed)");
|
|
87
162
|
}
|
|
88
163
|
}
|
|
89
164
|
function hasColumn(db2, table, column) {
|
|
@@ -1886,7 +1961,16 @@ function truncateToTokenBudget(text, budget) {
|
|
|
1886
1961
|
result = result.slice(0, Math.floor(result.length * 0.9));
|
|
1887
1962
|
}
|
|
1888
1963
|
if (result.length < text.length) {
|
|
1889
|
-
|
|
1964
|
+
const nextCharacter = text.charAt(result.length);
|
|
1965
|
+
if (nextCharacter && !/[\s.,;:!?)}\]]/.test(nextCharacter)) {
|
|
1966
|
+
const boundary = result.search(/\s+\S*$/);
|
|
1967
|
+
result = boundary > 0 ? result.slice(0, boundary).trimEnd() : "";
|
|
1968
|
+
}
|
|
1969
|
+
while (result && fitsInBudget(result + "...", budget) === false) {
|
|
1970
|
+
const boundary = result.lastIndexOf(" ");
|
|
1971
|
+
result = boundary > 0 ? result.slice(0, boundary).trimEnd() : "";
|
|
1972
|
+
}
|
|
1973
|
+
result = result ? result + "..." : "...";
|
|
1890
1974
|
}
|
|
1891
1975
|
}
|
|
1892
1976
|
return result;
|
|
@@ -2591,6 +2675,9 @@ function parseTomlValue(raw, filePath, line) {
|
|
|
2591
2675
|
if (raw.startsWith('"') && raw.endsWith('"')) {
|
|
2592
2676
|
return raw.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
2593
2677
|
}
|
|
2678
|
+
if (raw.startsWith("'") && raw.endsWith("'")) {
|
|
2679
|
+
return raw.slice(1, -1);
|
|
2680
|
+
}
|
|
2594
2681
|
if (raw === "true") return true;
|
|
2595
2682
|
if (raw === "false") return false;
|
|
2596
2683
|
if (/^-?\d+$/.test(raw)) return Number.parseInt(raw, 10);
|
|
@@ -2603,7 +2690,7 @@ function parseTomlValue(raw, filePath, line) {
|
|
|
2603
2690
|
throw new Error(`Unsupported TOML value in ${filePath}:${line}`);
|
|
2604
2691
|
}
|
|
2605
2692
|
function stripComment(line) {
|
|
2606
|
-
let
|
|
2693
|
+
let quote = null;
|
|
2607
2694
|
let escaped = false;
|
|
2608
2695
|
for (let i = 0; i < line.length; i++) {
|
|
2609
2696
|
const char = line[i];
|
|
@@ -2611,15 +2698,16 @@ function stripComment(line) {
|
|
|
2611
2698
|
escaped = false;
|
|
2612
2699
|
continue;
|
|
2613
2700
|
}
|
|
2614
|
-
if (char === "\\" &&
|
|
2701
|
+
if (char === "\\" && quote === '"') {
|
|
2615
2702
|
escaped = true;
|
|
2616
2703
|
continue;
|
|
2617
2704
|
}
|
|
2618
|
-
if (char === '"') {
|
|
2619
|
-
|
|
2705
|
+
if (char === '"' || char === "'") {
|
|
2706
|
+
if (quote === char) quote = null;
|
|
2707
|
+
else if (quote === null) quote = char;
|
|
2620
2708
|
continue;
|
|
2621
2709
|
}
|
|
2622
|
-
if (char === "#" &&
|
|
2710
|
+
if (char === "#" && quote === null) {
|
|
2623
2711
|
return line.slice(0, i);
|
|
2624
2712
|
}
|
|
2625
2713
|
}
|
|
@@ -2715,6 +2803,25 @@ function getResolvedConfig(options = {}) {
|
|
|
2715
2803
|
const legacy = loadFileConfig();
|
|
2716
2804
|
const embeddingBaseUrl = first(process.env.MEMORIX_EMBEDDING_BASE_URL, toml.embedding?.base_url, yaml2.embedding?.baseUrl, legacy.embeddingApi?.baseUrl);
|
|
2717
2805
|
const openRouterEmbeddingApiKey = isOpenRouterUrl(embeddingBaseUrl) ? process.env.OPENROUTER_API_KEY : void 0;
|
|
2806
|
+
const memoryLlmProvider = first(
|
|
2807
|
+
process.env.MEMORIX_LLM_PROVIDER,
|
|
2808
|
+
toml.memory?.llm?.provider,
|
|
2809
|
+
yaml2.llm?.provider,
|
|
2810
|
+
legacy.llm?.provider
|
|
2811
|
+
);
|
|
2812
|
+
const memoryLlmModel = first(
|
|
2813
|
+
process.env.MEMORIX_LLM_MODEL,
|
|
2814
|
+
toml.memory?.llm?.model,
|
|
2815
|
+
yaml2.llm?.model,
|
|
2816
|
+
legacy.llm?.model
|
|
2817
|
+
);
|
|
2818
|
+
const memoryLlmBaseUrl = first(
|
|
2819
|
+
process.env.MEMORIX_LLM_BASE_URL,
|
|
2820
|
+
toml.memory?.llm?.base_url,
|
|
2821
|
+
yaml2.llm?.baseUrl,
|
|
2822
|
+
legacy.llm?.baseUrl
|
|
2823
|
+
);
|
|
2824
|
+
const openRouterMemoryLlmApiKey = isOpenRouterMemoryLane(memoryLlmProvider, memoryLlmBaseUrl) ? process.env.OPENROUTER_API_KEY : void 0;
|
|
2718
2825
|
const resolved = {
|
|
2719
2826
|
agent: {
|
|
2720
2827
|
provider: first(
|
|
@@ -2752,9 +2859,9 @@ function getResolvedConfig(options = {}) {
|
|
|
2752
2859
|
autoCleanup: firstBool(toml.memory?.auto_cleanup, yaml2.behavior?.autoCleanup),
|
|
2753
2860
|
syncAdvisory: firstBool(toml.memory?.sync_advisory, yaml2.behavior?.syncAdvisory),
|
|
2754
2861
|
llm: {
|
|
2755
|
-
provider:
|
|
2756
|
-
model:
|
|
2757
|
-
baseUrl:
|
|
2862
|
+
provider: memoryLlmProvider,
|
|
2863
|
+
model: memoryLlmModel,
|
|
2864
|
+
baseUrl: memoryLlmBaseUrl,
|
|
2758
2865
|
apiKey: first(
|
|
2759
2866
|
process.env.MEMORIX_LLM_API_KEY,
|
|
2760
2867
|
process.env.MEMORIX_API_KEY,
|
|
@@ -2763,7 +2870,7 @@ function getResolvedConfig(options = {}) {
|
|
|
2763
2870
|
legacy.llm?.apiKey,
|
|
2764
2871
|
process.env.OPENAI_API_KEY,
|
|
2765
2872
|
process.env.ANTHROPIC_API_KEY,
|
|
2766
|
-
|
|
2873
|
+
openRouterMemoryLlmApiKey
|
|
2767
2874
|
)
|
|
2768
2875
|
}
|
|
2769
2876
|
},
|
|
@@ -2900,6 +3007,9 @@ function isOpenRouterUrl(value) {
|
|
|
2900
3007
|
return /(^|\.)openrouter\.ai(?::|\/|$)/i.test(value);
|
|
2901
3008
|
}
|
|
2902
3009
|
}
|
|
3010
|
+
function isOpenRouterMemoryLane(provider2, baseUrl) {
|
|
3011
|
+
return provider2?.trim().toLowerCase() === "openrouter" || isOpenRouterUrl(baseUrl);
|
|
3012
|
+
}
|
|
2903
3013
|
function normalizeExternalContext(value) {
|
|
2904
3014
|
const normalized = value?.trim().toLowerCase();
|
|
2905
3015
|
if (normalized === "auto" || normalized === "off") return normalized;
|
|
@@ -4947,6 +5057,29 @@ Rules:
|
|
|
4947
5057
|
}
|
|
4948
5058
|
});
|
|
4949
5059
|
|
|
5060
|
+
// src/timeout.ts
|
|
5061
|
+
function withTimeout(promise, ms, label) {
|
|
5062
|
+
return new Promise((resolve2, reject) => {
|
|
5063
|
+
const timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
|
|
5064
|
+
promise.then(
|
|
5065
|
+
(value) => {
|
|
5066
|
+
clearTimeout(timer);
|
|
5067
|
+
resolve2(value);
|
|
5068
|
+
},
|
|
5069
|
+
(error) => {
|
|
5070
|
+
clearTimeout(timer);
|
|
5071
|
+
reject(error);
|
|
5072
|
+
}
|
|
5073
|
+
);
|
|
5074
|
+
});
|
|
5075
|
+
}
|
|
5076
|
+
var init_timeout = __esm({
|
|
5077
|
+
"src/timeout.ts"() {
|
|
5078
|
+
"use strict";
|
|
5079
|
+
init_esm_shims();
|
|
5080
|
+
}
|
|
5081
|
+
});
|
|
5082
|
+
|
|
4950
5083
|
// src/project/aliases.ts
|
|
4951
5084
|
var aliases_exports = {};
|
|
4952
5085
|
__export(aliases_exports, {
|
|
@@ -5712,8 +5845,12 @@ async function searchObservations(options) {
|
|
|
5712
5845
|
}
|
|
5713
5846
|
};
|
|
5714
5847
|
const modeKey = options.projectId ?? SEARCH_MODE_DEFAULT_KEY;
|
|
5715
|
-
|
|
5848
|
+
const quality = options.quality ?? "balanced";
|
|
5716
5849
|
const database = await getDb();
|
|
5850
|
+
lastSearchModeByProject.set(
|
|
5851
|
+
modeKey,
|
|
5852
|
+
quality === "fast" ? "fulltext (fast profile)" : embeddingEnabled ? "hybrid" : "fulltext"
|
|
5853
|
+
);
|
|
5717
5854
|
let projectIds = null;
|
|
5718
5855
|
if (options.projectId) {
|
|
5719
5856
|
try {
|
|
@@ -5735,11 +5872,15 @@ async function searchObservations(options) {
|
|
|
5735
5872
|
}
|
|
5736
5873
|
const hasQuery = options.query && options.query.trim().length > 0;
|
|
5737
5874
|
const originalQuery = options.query;
|
|
5738
|
-
const tier = hasQuery ? classifyQueryTier(originalQuery) : "fast";
|
|
5875
|
+
const tier = quality === "fast" ? "fast" : hasQuery ? classifyQueryTier(originalQuery) : "fast";
|
|
5739
5876
|
mark(`tier=${tier}`);
|
|
5740
|
-
|
|
5877
|
+
if (quality === "thorough" && hasQuery) {
|
|
5878
|
+
const { initLLM: initLLM2, isLLMEnabled: isLLMEnabled2 } = await Promise.resolve().then(() => (init_provider2(), provider_exports2));
|
|
5879
|
+
if (!isLLMEnabled2()) initLLM2();
|
|
5880
|
+
}
|
|
5881
|
+
const expandedEmbeddingQuery = quality === "thorough" && tier === "heavy" ? await maybeExpandSearchQuery(options.query) : options.query;
|
|
5741
5882
|
mark("queryExpansion");
|
|
5742
|
-
const intentResult = hasQuery ? detectQueryIntent(originalQuery)
|
|
5883
|
+
const intentResult = quality === "fast" || !hasQuery ? null : detectQueryIntent(originalQuery);
|
|
5743
5884
|
const requestLimit = projectIds ? (options.limit ?? 20) * 3 : options.limit ?? 20;
|
|
5744
5885
|
const defaultBoost = {
|
|
5745
5886
|
title: 3,
|
|
@@ -5753,17 +5894,21 @@ async function searchObservations(options) {
|
|
|
5753
5894
|
let searchParams = {
|
|
5754
5895
|
term: originalQuery,
|
|
5755
5896
|
limit: requestLimit,
|
|
5756
|
-
|
|
5897
|
+
// Fast search never uses vectors, so returning them only adds work and
|
|
5898
|
+
// payload pressure to an explicitly latency-sensitive path.
|
|
5899
|
+
includeVectors: quality !== "fast",
|
|
5757
5900
|
...Object.keys(filters).length > 0 ? { where: filters } : {},
|
|
5758
5901
|
// Search specific fields (not tokens, accessCount, etc.)
|
|
5759
5902
|
properties: ["title", "entityName", "narrative", "facts", "concepts", "filesModified"],
|
|
5760
5903
|
// Field boosting: intent-aware or default
|
|
5761
5904
|
boost: fieldBoost,
|
|
5762
5905
|
// Fuzzy tolerance: allow 1-char typos for short queries, 2 for longer
|
|
5763
|
-
|
|
5906
|
+
// Fuzzy matching is useful for ordinary recall, but grows sharply with
|
|
5907
|
+
// corpus size. The fast profile deliberately uses exact lexical matching.
|
|
5908
|
+
...hasQuery && quality !== "fast" ? { tolerance: originalQuery.length > 6 ? 2 : 1 } : {}
|
|
5764
5909
|
};
|
|
5765
5910
|
let queryVector = null;
|
|
5766
|
-
if (embeddingEnabled && hasQuery && tier !== "fast") {
|
|
5911
|
+
if (quality !== "fast" && embeddingEnabled && hasQuery && tier !== "fast") {
|
|
5767
5912
|
try {
|
|
5768
5913
|
const provider2 = await getEmbeddingProvider();
|
|
5769
5914
|
if (provider2) {
|
|
@@ -5778,11 +5923,11 @@ async function searchObservations(options) {
|
|
|
5778
5923
|
);
|
|
5779
5924
|
} else {
|
|
5780
5925
|
const EMBEDDING_TIMEOUT_MS = 15e3;
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5926
|
+
queryVector = await withTimeout(
|
|
5927
|
+
provider2.embed(expandedEmbeddingQuery),
|
|
5928
|
+
EMBEDDING_TIMEOUT_MS,
|
|
5929
|
+
"Embedding"
|
|
5784
5930
|
);
|
|
5785
|
-
queryVector = await Promise.race([embedPromise, timeoutPromise]);
|
|
5786
5931
|
mark("embedding");
|
|
5787
5932
|
const cjkRatio = (originalQuery.match(/[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/g) || []).length / originalQuery.length;
|
|
5788
5933
|
const isCJKHeavy = cjkRatio > 0.3;
|
|
@@ -6023,7 +6168,7 @@ async function searchObservations(options) {
|
|
|
6023
6168
|
}
|
|
6024
6169
|
}
|
|
6025
6170
|
}
|
|
6026
|
-
const shouldRerank = tier === "heavy" && hasQuery && intermediate.length > 2 && (() => {
|
|
6171
|
+
const shouldRerank = quality === "thorough" && tier === "heavy" && hasQuery && intermediate.length > 2 && (() => {
|
|
6027
6172
|
const top = intermediate[0]?.score ?? 0;
|
|
6028
6173
|
const second = intermediate[1]?.score ?? 0;
|
|
6029
6174
|
return top > 0 && second / top > 0.7;
|
|
@@ -6047,11 +6192,11 @@ async function searchObservations(options) {
|
|
|
6047
6192
|
}));
|
|
6048
6193
|
const _parsedRerank = parseInt(process.env.MEMORIX_RERANK_TIMEOUT_MS || "", 10);
|
|
6049
6194
|
const RERANK_TIMEOUT_MS = Number.isFinite(_parsedRerank) && _parsedRerank > 0 ? _parsedRerank : 5e3;
|
|
6050
|
-
const
|
|
6051
|
-
|
|
6052
|
-
|
|
6195
|
+
const { reranked, usedLLM } = await withTimeout(
|
|
6196
|
+
rerankResults2(originalQuery, candidates),
|
|
6197
|
+
RERANK_TIMEOUT_MS,
|
|
6198
|
+
"LLM rerank"
|
|
6053
6199
|
);
|
|
6054
|
-
const { reranked, usedLLM } = await Promise.race([rerankPromise, timeoutPromise]);
|
|
6055
6200
|
mark(`rerank(usedLLM=${usedLLM})`);
|
|
6056
6201
|
if (usedLLM) {
|
|
6057
6202
|
lastSearchModeByProject.set(modeKey, (lastSearchModeByProject.get(modeKey) ?? "fulltext") + " + LLM rerank");
|
|
@@ -6243,6 +6388,7 @@ var init_orama_store = __esm({
|
|
|
6243
6388
|
init_project_affinity();
|
|
6244
6389
|
init_intent_detector();
|
|
6245
6390
|
init_query_expansion();
|
|
6391
|
+
init_timeout();
|
|
6246
6392
|
db = null;
|
|
6247
6393
|
dbInitPromise = null;
|
|
6248
6394
|
dbGeneration = 0;
|
|
@@ -13691,6 +13837,7 @@ var init_workflow_store = __esm({
|
|
|
13691
13837
|
var task_lens_exports = {};
|
|
13692
13838
|
__export(task_lens_exports, {
|
|
13693
13839
|
containsTaskKeyword: () => containsTaskKeyword,
|
|
13840
|
+
isContinuationTask: () => isContinuationTask,
|
|
13694
13841
|
lensPathCandidates: () => lensPathCandidates,
|
|
13695
13842
|
lensVerificationHints: () => lensVerificationHints,
|
|
13696
13843
|
rankLensPaths: () => rankLensPaths,
|
|
@@ -13756,6 +13903,12 @@ function resolveTaskLens(task) {
|
|
|
13756
13903
|
}
|
|
13757
13904
|
return best.score > 0 ? LENSES[best.id] : LENSES.general;
|
|
13758
13905
|
}
|
|
13906
|
+
function isContinuationTask(task) {
|
|
13907
|
+
const normalized = (task ?? "").trim().toLowerCase();
|
|
13908
|
+
return normalized.length > 0 && CONTINUATION_KEYWORDS.some(
|
|
13909
|
+
(keyword) => containsTaskKeyword(normalized, keyword)
|
|
13910
|
+
);
|
|
13911
|
+
}
|
|
13759
13912
|
function pathKindScore(path25, lens) {
|
|
13760
13913
|
const normalized = normalizePath2(path25).toLowerCase();
|
|
13761
13914
|
const name = normalized.split("/").pop() ?? normalized;
|
|
@@ -13899,7 +14052,7 @@ function shouldShowLensSource(source, lens, task) {
|
|
|
13899
14052
|
if (lens.id === "onboarding" || lens.id === "release" || lens.id === "docs") return score >= 40;
|
|
13900
14053
|
return score > 0;
|
|
13901
14054
|
}
|
|
13902
|
-
var STOP_WORDS, LENSES, KEYWORDS, LENS_PRIORITY;
|
|
14055
|
+
var STOP_WORDS, LENSES, KEYWORDS, CONTINUATION_KEYWORDS, LENS_PRIORITY;
|
|
13903
14056
|
var init_task_lens = __esm({
|
|
13904
14057
|
"src/codegraph/task-lens.ts"() {
|
|
13905
14058
|
"use strict";
|
|
@@ -14002,6 +14155,7 @@ var init_task_lens = __esm({
|
|
|
14002
14155
|
"fail",
|
|
14003
14156
|
"failing",
|
|
14004
14157
|
"fix",
|
|
14158
|
+
"fixing",
|
|
14005
14159
|
"issue",
|
|
14006
14160
|
"regression",
|
|
14007
14161
|
"repro",
|
|
@@ -14019,6 +14173,18 @@ var init_task_lens = __esm({
|
|
|
14019
14173
|
docs: ["doc", "docs", "readme", "\u6587\u6863", "\u8BF4\u660E"],
|
|
14020
14174
|
test: ["coverage", "fixture", "smoke", "spec", "test", "tests", "testing", "vitest", "\u6D4B\u8BD5"]
|
|
14021
14175
|
};
|
|
14176
|
+
CONTINUATION_KEYWORDS = [
|
|
14177
|
+
"continue",
|
|
14178
|
+
"resume",
|
|
14179
|
+
"pick up",
|
|
14180
|
+
"carry on",
|
|
14181
|
+
"previous session",
|
|
14182
|
+
"\u7EE7\u7EED",
|
|
14183
|
+
"\u63A5\u624B",
|
|
14184
|
+
"\u6062\u590D",
|
|
14185
|
+
"\u5EF6\u7EED",
|
|
14186
|
+
"\u4E0A\u6B21\u4F1A\u8BDD"
|
|
14187
|
+
];
|
|
14022
14188
|
LENS_PRIORITY = [
|
|
14023
14189
|
"bugfix",
|
|
14024
14190
|
"release",
|
|
@@ -14747,21 +14913,6 @@ async function loadWorkspaceForMaintenance(projectId, projectDir2, mode) {
|
|
|
14747
14913
|
]);
|
|
14748
14914
|
return versioned ?? local;
|
|
14749
14915
|
}
|
|
14750
|
-
function withTimeout(promise, ms, label) {
|
|
14751
|
-
return new Promise((resolve2, reject) => {
|
|
14752
|
-
const timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
|
|
14753
|
-
promise.then(
|
|
14754
|
-
(value) => {
|
|
14755
|
-
clearTimeout(timer);
|
|
14756
|
-
resolve2(value);
|
|
14757
|
-
},
|
|
14758
|
-
(error) => {
|
|
14759
|
-
clearTimeout(timer);
|
|
14760
|
-
reject(error);
|
|
14761
|
-
}
|
|
14762
|
-
);
|
|
14763
|
-
});
|
|
14764
|
-
}
|
|
14765
14916
|
async function runAutomaticConsolidation(projectId, projectDir2, options) {
|
|
14766
14917
|
const { isLLMEnabled: isLLMEnabled2 } = await Promise.resolve().then(() => (init_provider2(), provider_exports2));
|
|
14767
14918
|
if (!isLLMEnabled2()) {
|
|
@@ -15146,6 +15297,7 @@ var init_project_maintenance = __esm({
|
|
|
15146
15297
|
init_esm_shims();
|
|
15147
15298
|
init_isolated_maintenance();
|
|
15148
15299
|
init_lifecycle();
|
|
15300
|
+
init_timeout();
|
|
15149
15301
|
DEFAULT_VECTOR_BATCH_SIZE = 12;
|
|
15150
15302
|
DEFAULT_RETENTION_BATCH_SIZE = 100;
|
|
15151
15303
|
DEFAULT_CONSOLIDATION_BATCH_SIZE = 200;
|
|
@@ -15229,6 +15381,7 @@ __export(session_exports, {
|
|
|
15229
15381
|
endSession: () => endSession,
|
|
15230
15382
|
getActiveSession: () => getActiveSession,
|
|
15231
15383
|
getSessionContext: () => getSessionContext,
|
|
15384
|
+
getSessionResumeBrief: () => getSessionResumeBrief,
|
|
15232
15385
|
listSessions: () => listSessions,
|
|
15233
15386
|
scoreObservationForSessionContext: () => scoreObservationForSessionContext,
|
|
15234
15387
|
startSession: () => startSession
|
|
@@ -15296,6 +15449,62 @@ function isSystemSelfObservation(obs) {
|
|
|
15296
15449
|
const text = stringifyObservation(obs, false);
|
|
15297
15450
|
return SYSTEM_SELF_PATTERNS.some((pattern) => pattern.test(text));
|
|
15298
15451
|
}
|
|
15452
|
+
function readerForAlias(reader, aliases, observation) {
|
|
15453
|
+
if (!reader) return void 0;
|
|
15454
|
+
return reader.projectId && aliases.has(observation.projectId) ? { ...reader, projectId: observation.projectId } : reader;
|
|
15455
|
+
}
|
|
15456
|
+
function readableAliasObservations(observations2, aliases, reader) {
|
|
15457
|
+
return reader ? observations2.filter((observation) => canReadObservation(
|
|
15458
|
+
observation,
|
|
15459
|
+
readerForAlias(reader, aliases, observation)
|
|
15460
|
+
)) : observations2;
|
|
15461
|
+
}
|
|
15462
|
+
function isUsefulSessionSummary(summary) {
|
|
15463
|
+
return Boolean(summary) && !NOISE_PATTERNS2.some((pattern) => pattern.test(summary)) && !SYSTEM_SELF_PATTERNS.some((pattern) => pattern.test(summary));
|
|
15464
|
+
}
|
|
15465
|
+
function continuationTaskTokens(task) {
|
|
15466
|
+
return [...new Set(
|
|
15467
|
+
(task?.toLowerCase().match(/[a-z0-9_./-]+|[\u4e00-\u9fff]+/g) ?? []).map((token) => token.trim()).filter((token) => token.length > 1 && !["continue", "resume", "\u7EE7\u7EED", "\u63A5\u624B", "\u6062\u590D"].includes(token))
|
|
15468
|
+
)].slice(0, 8);
|
|
15469
|
+
}
|
|
15470
|
+
function continuationScore(observation, projectTokens, task) {
|
|
15471
|
+
let score = scoreObservationForSessionContext(observation, projectTokens);
|
|
15472
|
+
if (observation.type === "session-request" || observation.type === "what-changed") score += 1;
|
|
15473
|
+
const text = stringifyObservation(observation);
|
|
15474
|
+
const matches = continuationTaskTokens(task).filter((token) => text.includes(token)).length;
|
|
15475
|
+
score += Math.min(matches, 2) * 2;
|
|
15476
|
+
return score;
|
|
15477
|
+
}
|
|
15478
|
+
async function getSessionResumeBrief(projectId, task, reader) {
|
|
15479
|
+
const aliasSet = await resolveProjectIds(projectId);
|
|
15480
|
+
const [sessions, allObs] = await Promise.all([
|
|
15481
|
+
loadAliasSessions(aliasSet),
|
|
15482
|
+
loadAliasActiveObservations(aliasSet)
|
|
15483
|
+
]);
|
|
15484
|
+
const readableObs = readableAliasObservations(allObs, aliasSet, reader);
|
|
15485
|
+
const previous = sessions.filter((session) => session.status === "completed").sort((a, b) => new Date(b.endedAt || b.startedAt).getTime() - new Date(a.endedAt || a.startedAt).getTime()).find((session) => session.summary && session.summary !== "(session ended implicitly by new session start)" && isUsefulSessionSummary(session.summary));
|
|
15486
|
+
const projectTokens = tokenizeProjectId(projectId);
|
|
15487
|
+
const memories = readableObs.filter((observation) => RESUME_TYPES.has(observation.type)).filter((observation) => classifyLayer(observation) === "L2").filter((observation) => !isNoiseObservation(observation) && !isSystemSelfObservation(observation)).map((observation) => ({
|
|
15488
|
+
observation,
|
|
15489
|
+
score: continuationScore(observation, projectTokens, task)
|
|
15490
|
+
})).sort((a, b) => b.score - a.score || new Date(b.observation.createdAt).getTime() - new Date(a.observation.createdAt).getTime() || a.observation.id - b.observation.id).slice(0, 3).map(({ observation }) => ({
|
|
15491
|
+
id: observation.id,
|
|
15492
|
+
title: sanitizeCredentials(observation.title),
|
|
15493
|
+
type: observation.type,
|
|
15494
|
+
...observation.facts?.[0] ? { detail: sanitizeCredentials(observation.facts[0]) } : observation.narrative ? { detail: sanitizeCredentials(observation.narrative) } : {}
|
|
15495
|
+
}));
|
|
15496
|
+
return {
|
|
15497
|
+
...previous?.summary ? {
|
|
15498
|
+
previousSession: {
|
|
15499
|
+
id: previous.id,
|
|
15500
|
+
...previous.agent ? { agent: previous.agent } : {},
|
|
15501
|
+
...previous.endedAt ? { endedAt: previous.endedAt } : {},
|
|
15502
|
+
summary: sanitizeCredentials(previous.summary)
|
|
15503
|
+
}
|
|
15504
|
+
} : {},
|
|
15505
|
+
memories
|
|
15506
|
+
};
|
|
15507
|
+
}
|
|
15299
15508
|
function scoreObservationForSessionContext(obs, projectTokens, now3 = Date.now()) {
|
|
15300
15509
|
let score = TYPE_WEIGHTS2[obs.type] ?? 1;
|
|
15301
15510
|
const text = stringifyObservation(obs);
|
|
@@ -15366,10 +15575,7 @@ async function getSessionContext(projectDir2, projectId, limit = 3, reader) {
|
|
|
15366
15575
|
loadAliasSessions(aliasSet),
|
|
15367
15576
|
loadAliasActiveObservations(aliasSet)
|
|
15368
15577
|
]);
|
|
15369
|
-
const readableObs =
|
|
15370
|
-
const observationReader = reader.projectId && aliasSet.has(observation.projectId) ? { ...reader, projectId: observation.projectId } : reader;
|
|
15371
|
-
return canReadObservation(observation, observationReader);
|
|
15372
|
-
}) : allObs;
|
|
15578
|
+
const readableObs = readableAliasObservations(allObs, aliasSet, reader);
|
|
15373
15579
|
const isNoisySummary = (summary) => {
|
|
15374
15580
|
if (!summary) return false;
|
|
15375
15581
|
return NOISE_PATTERNS2.some((p) => p.test(summary)) || SYSTEM_SELF_PATTERNS.some((p) => p.test(summary));
|
|
@@ -15520,7 +15726,7 @@ async function getActiveSession(projectDir2, projectId) {
|
|
|
15520
15726
|
const sessions = await loadAliasActiveSessions(aliasSet);
|
|
15521
15727
|
return sessions.sort((a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime())[0] ?? null;
|
|
15522
15728
|
}
|
|
15523
|
-
var PRIORITY_TYPES, TYPE_EMOJI, TYPE_WEIGHTS2, NOISE_PATTERNS2, COMMAND_TRACE_PATTERNS, SYSTEM_SELF_PATTERNS;
|
|
15729
|
+
var PRIORITY_TYPES, RESUME_TYPES, TYPE_EMOJI, TYPE_WEIGHTS2, NOISE_PATTERNS2, COMMAND_TRACE_PATTERNS, SYSTEM_SELF_PATTERNS;
|
|
15524
15730
|
var init_session = __esm({
|
|
15525
15731
|
"src/memory/session.ts"() {
|
|
15526
15732
|
"use strict";
|
|
@@ -15534,6 +15740,17 @@ var init_session = __esm({
|
|
|
15534
15740
|
init_secret_filter();
|
|
15535
15741
|
init_visibility();
|
|
15536
15742
|
PRIORITY_TYPES = /* @__PURE__ */ new Set(["gotcha", "decision", "problem-solution", "trade-off", "discovery"]);
|
|
15743
|
+
RESUME_TYPES = /* @__PURE__ */ new Set([
|
|
15744
|
+
"gotcha",
|
|
15745
|
+
"decision",
|
|
15746
|
+
"problem-solution",
|
|
15747
|
+
"trade-off",
|
|
15748
|
+
"discovery",
|
|
15749
|
+
"how-it-works",
|
|
15750
|
+
"what-changed",
|
|
15751
|
+
"reasoning",
|
|
15752
|
+
"session-request"
|
|
15753
|
+
]);
|
|
15537
15754
|
TYPE_EMOJI = {
|
|
15538
15755
|
"gotcha": "[DISCOVERY]",
|
|
15539
15756
|
"decision": "[WHY]",
|
|
@@ -15694,6 +15911,7 @@ function freshnessForMemory(status) {
|
|
|
15694
15911
|
return "unknown";
|
|
15695
15912
|
}
|
|
15696
15913
|
function receiptOmissionKind(raw) {
|
|
15914
|
+
if (raw.includes("continuation")) return "continuation";
|
|
15697
15915
|
if (raw.includes("task")) return "task";
|
|
15698
15916
|
if (raw.includes("fact")) return "current-fact";
|
|
15699
15917
|
if (raw.includes("state")) return "code-state";
|
|
@@ -15743,6 +15961,48 @@ function renderTaskWorksetPrompt(input) {
|
|
|
15743
15961
|
trust: "source-backed"
|
|
15744
15962
|
});
|
|
15745
15963
|
appendLine(lines, "Task lens: " + input.lens, maxTokens, omitted, "lens");
|
|
15964
|
+
const hasContinuation = Boolean(
|
|
15965
|
+
input.continuation?.previousSession || (input.continuation?.memories.length ?? 0) > 0
|
|
15966
|
+
);
|
|
15967
|
+
if (hasContinuation && input.continuation) {
|
|
15968
|
+
appendLine(lines, "", maxTokens, omitted, "continuation-heading");
|
|
15969
|
+
appendLine(lines, "Resume from prior work", maxTokens, omitted, "continuation-heading");
|
|
15970
|
+
if (input.continuation.previousSession) {
|
|
15971
|
+
const session = input.continuation.previousSession;
|
|
15972
|
+
const source = [session.agent, session.endedAt ? session.endedAt.slice(0, 10) : void 0].filter(Boolean).join(", ");
|
|
15973
|
+
appendLine(
|
|
15974
|
+
lines,
|
|
15975
|
+
"- Previous session" + (source ? ` (${source})` : "") + ": " + short(session.summary, 44),
|
|
15976
|
+
maxTokens,
|
|
15977
|
+
omitted,
|
|
15978
|
+
"continuation-session",
|
|
15979
|
+
selected,
|
|
15980
|
+
{
|
|
15981
|
+
kind: "continuation",
|
|
15982
|
+
id: "session:" + session.id,
|
|
15983
|
+
reason: "latest meaningful project session summary",
|
|
15984
|
+
trust: "historical"
|
|
15985
|
+
}
|
|
15986
|
+
);
|
|
15987
|
+
}
|
|
15988
|
+
for (const memory of input.continuation.memories.slice(0, 3)) {
|
|
15989
|
+
const detail = memory.detail ? ": " + short(memory.detail, CONTINUATION_DETAIL_TOKEN_BUDGET) : "";
|
|
15990
|
+
appendLine(
|
|
15991
|
+
lines,
|
|
15992
|
+
"- #" + memory.id + " " + memory.type + ": " + short(memory.title, 18) + detail,
|
|
15993
|
+
maxTokens,
|
|
15994
|
+
omitted,
|
|
15995
|
+
"continuation-memory",
|
|
15996
|
+
selected,
|
|
15997
|
+
{
|
|
15998
|
+
kind: "continuation",
|
|
15999
|
+
id: "memory:" + memory.id,
|
|
16000
|
+
reason: "durable prior-work memory",
|
|
16001
|
+
trust: "historical"
|
|
16002
|
+
}
|
|
16003
|
+
);
|
|
16004
|
+
}
|
|
16005
|
+
}
|
|
15746
16006
|
if (input.cautions.length > 0 || input.cautionMemory.length > 0) {
|
|
15747
16007
|
appendLine(lines, "", maxTokens, omitted, "caution-heading");
|
|
15748
16008
|
appendLine(lines, "Cautions", maxTokens, omitted, "caution-heading");
|
|
@@ -16011,11 +16271,25 @@ async function buildTaskWorkset(input) {
|
|
|
16011
16271
|
...input.verificationHints
|
|
16012
16272
|
]).slice(0, 4);
|
|
16013
16273
|
const normalizedCautions = unique(cautions.map((caution) => caution.kind)).map((kind) => cautions.find((caution) => caution.kind === kind)).slice(0, 6);
|
|
16274
|
+
const continuation = input.continuation && (input.continuation.previousSession || input.continuation.memories.length > 0) ? {
|
|
16275
|
+
...input.continuation.previousSession ? {
|
|
16276
|
+
previousSession: {
|
|
16277
|
+
...input.continuation.previousSession,
|
|
16278
|
+
summary: short(input.continuation.previousSession.summary, 52)
|
|
16279
|
+
}
|
|
16280
|
+
} : {},
|
|
16281
|
+
memories: input.continuation.memories.slice(0, 3).map((memory) => ({
|
|
16282
|
+
...memory,
|
|
16283
|
+
title: short(memory.title, 20),
|
|
16284
|
+
...memory.detail ? { detail: short(memory.detail, CONTINUATION_DETAIL_TOKEN_BUDGET) } : {}
|
|
16285
|
+
}))
|
|
16286
|
+
} : void 0;
|
|
16014
16287
|
const base = {
|
|
16015
16288
|
version: "1.2",
|
|
16016
16289
|
task,
|
|
16017
16290
|
lens: input.lens,
|
|
16018
16291
|
currentFacts: input.currentFacts?.map((fact) => fact.startsWith("Historical note:") ? short(fact, 48) : short(fact, 28)).slice(0, 4) ?? [],
|
|
16292
|
+
...continuation ? { continuation } : {},
|
|
16019
16293
|
...input.codeState ? { codeState: short(input.codeState, 28) } : {},
|
|
16020
16294
|
startHere: unique(input.startHere).slice(0, 5),
|
|
16021
16295
|
...input.semanticCode ? { semanticCode: input.semanticCode } : {},
|
|
@@ -16040,7 +16314,7 @@ async function buildTaskWorkset(input) {
|
|
|
16040
16314
|
budget: { maxTokens }
|
|
16041
16315
|
});
|
|
16042
16316
|
const receipt = {
|
|
16043
|
-
version: "1.2.
|
|
16317
|
+
version: "1.2.4",
|
|
16044
16318
|
target: input.deliveryTarget ?? "project-context",
|
|
16045
16319
|
elapsedMs: Math.max(0, Date.now() - startedAt),
|
|
16046
16320
|
budget: {
|
|
@@ -16062,6 +16336,7 @@ async function buildTaskWorkset(input) {
|
|
|
16062
16336
|
prompt: rendered.prompt
|
|
16063
16337
|
};
|
|
16064
16338
|
}
|
|
16339
|
+
var CONTINUATION_DETAIL_TOKEN_BUDGET;
|
|
16065
16340
|
var init_workset = __esm({
|
|
16066
16341
|
"src/knowledge/workset.ts"() {
|
|
16067
16342
|
"use strict";
|
|
@@ -16074,6 +16349,7 @@ var init_workset = __esm({
|
|
|
16074
16349
|
init_workspace();
|
|
16075
16350
|
init_workflow_store();
|
|
16076
16351
|
init_workflows();
|
|
16352
|
+
CONTINUATION_DETAIL_TOKEN_BUDGET = 20;
|
|
16077
16353
|
}
|
|
16078
16354
|
});
|
|
16079
16355
|
|
|
@@ -16866,6 +17142,7 @@ async function buildAutoProjectContext(input) {
|
|
|
16866
17142
|
const now3 = input.now ?? /* @__PURE__ */ new Date();
|
|
16867
17143
|
const task = input.task?.trim();
|
|
16868
17144
|
const lens = resolveTaskLens(task);
|
|
17145
|
+
const continuationRequested = input.continuation === "always" || input.continuation !== "never" && isContinuationTask(task);
|
|
16869
17146
|
const codegraphConfig = getResolvedConfig({ projectRoot: input.project.rootPath }).codegraph;
|
|
16870
17147
|
const exclude = input.exclude ?? codegraphConfig.excludePatterns;
|
|
16871
17148
|
const maxFileBytes = input.maxFileBytes ?? codegraphConfig.maxFileBytes;
|
|
@@ -17002,6 +17279,11 @@ async function buildAutoProjectContext(input) {
|
|
|
17002
17279
|
if (externalCaution) {
|
|
17003
17280
|
runtimeCautions.push({ kind: "external-codegraph-fallback", message: externalCaution });
|
|
17004
17281
|
}
|
|
17282
|
+
let continuation;
|
|
17283
|
+
if (continuationRequested) {
|
|
17284
|
+
await initSessionStore(input.dataDir);
|
|
17285
|
+
continuation = await getSessionResumeBrief(input.project.id, task, input.reader);
|
|
17286
|
+
}
|
|
17005
17287
|
const workset = await buildTaskWorkset({
|
|
17006
17288
|
projectId: input.project.id,
|
|
17007
17289
|
dataDir: input.dataDir,
|
|
@@ -17011,6 +17293,7 @@ async function buildAutoProjectContext(input) {
|
|
|
17011
17293
|
...externalOutline ? { semanticCode: externalOutline } : {},
|
|
17012
17294
|
providerQuality: providerQuality2,
|
|
17013
17295
|
currentFacts: worksetFactLines(currentFacts),
|
|
17296
|
+
...continuation ? { continuation } : {},
|
|
17014
17297
|
codeState: codeStateLine(overview),
|
|
17015
17298
|
reliableMemory: sourceSets.reliableSources.slice(0, lens.sourceLimit).map((source) => ({
|
|
17016
17299
|
id: source.observationId,
|
|
@@ -17055,12 +17338,19 @@ async function buildAutoProjectContext(input) {
|
|
|
17055
17338
|
explain,
|
|
17056
17339
|
refresh,
|
|
17057
17340
|
providerQuality: providerQuality2,
|
|
17341
|
+
...continuationRequested && workset.continuation ? { continuation: workset.continuation } : {},
|
|
17058
17342
|
workset
|
|
17059
17343
|
};
|
|
17060
17344
|
}
|
|
17061
17345
|
function formatLanguages(overview) {
|
|
17062
17346
|
return overview.code.languages.length > 0 ? overview.code.languages.map((item) => `${item.language} ${item.files}`).join(", ") : "none indexed yet";
|
|
17063
17347
|
}
|
|
17348
|
+
function compactContinuationText(text, budget) {
|
|
17349
|
+
return truncateToTokenBudget(
|
|
17350
|
+
sanitizeCredentials(text).replace(/\s+/g, " ").trim(),
|
|
17351
|
+
budget
|
|
17352
|
+
);
|
|
17353
|
+
}
|
|
17064
17354
|
function codeStateLine(overview) {
|
|
17065
17355
|
const snapshot = overview.code.latestSnapshot;
|
|
17066
17356
|
if (!snapshot) return "- Code state: no completed snapshot yet";
|
|
@@ -17200,6 +17490,23 @@ function formatAutoProjectContextSummary(context) {
|
|
|
17200
17490
|
"Reliable memory",
|
|
17201
17491
|
reliableSources.length > 0 ? `- ${reliableSources.length} current code-bound memory link(s)` : "- none yet"
|
|
17202
17492
|
);
|
|
17493
|
+
const continuation = context.workset.continuation;
|
|
17494
|
+
if (continuation?.previousSession || continuation?.memories.length) {
|
|
17495
|
+
lines.push("", "Resume from prior work");
|
|
17496
|
+
if (continuation.previousSession) {
|
|
17497
|
+
const session = continuation.previousSession;
|
|
17498
|
+
const source = [session.agent, session.endedAt ? session.endedAt.slice(0, 10) : void 0].filter(Boolean).join(", ");
|
|
17499
|
+
lines.push(
|
|
17500
|
+
"- Previous session" + (source ? ` (${source})` : "") + ": " + compactContinuationText(session.summary, 44)
|
|
17501
|
+
);
|
|
17502
|
+
}
|
|
17503
|
+
for (const memory of continuation.memories.slice(0, 3)) {
|
|
17504
|
+
const detail = memory.detail ? ": " + compactContinuationText(memory.detail, 20) : "";
|
|
17505
|
+
lines.push(
|
|
17506
|
+
"- #" + memory.id + " " + memory.type + ": " + compactContinuationText(memory.title, 18) + detail
|
|
17507
|
+
);
|
|
17508
|
+
}
|
|
17509
|
+
}
|
|
17203
17510
|
return lines.join("\n");
|
|
17204
17511
|
}
|
|
17205
17512
|
function formatAutoProjectContextPrompt(context) {
|
|
@@ -17270,8 +17577,10 @@ var init_auto_context = __esm({
|
|
|
17270
17577
|
"src/codegraph/auto-context.ts"() {
|
|
17271
17578
|
"use strict";
|
|
17272
17579
|
init_esm_shims();
|
|
17580
|
+
init_token_budget();
|
|
17273
17581
|
init_resolved_config();
|
|
17274
17582
|
init_workset();
|
|
17583
|
+
init_secret_filter();
|
|
17275
17584
|
init_binder();
|
|
17276
17585
|
init_current_facts();
|
|
17277
17586
|
init_lite_provider();
|
|
@@ -17279,6 +17588,8 @@ var init_auto_context = __esm({
|
|
|
17279
17588
|
init_project_context();
|
|
17280
17589
|
init_store();
|
|
17281
17590
|
init_admission();
|
|
17591
|
+
init_session();
|
|
17592
|
+
init_session_store();
|
|
17282
17593
|
init_task_lens();
|
|
17283
17594
|
DEFAULT_MAX_AGE_MS = 10 * 60 * 1e3;
|
|
17284
17595
|
}
|
|
@@ -21473,7 +21784,8 @@ var init_official_skills = __esm({
|
|
|
21473
21784
|
"",
|
|
21474
21785
|
"| Situation | Prefer | CLI fallback |",
|
|
21475
21786
|
"|---|---|---|",
|
|
21476
|
-
'| Starting
|
|
21787
|
+
'| Starting a coding task and needing the best task-lensed project map | `memorix_project_context` with the user\'s task | `memorix context "<task>" --json` |',
|
|
21788
|
+
'| Continuing prior project work | `memorix_project_context` with the user\'s task | `memorix resume "<task>" --json` |',
|
|
21477
21789
|
'| Need structured refs/freshness for code-bound memories | `memorix_context_pack` | `memorix codegraph context-pack --task "<topic>"` |',
|
|
21478
21790
|
'| Explicit memory graph question | `memorix_graph_context` | `memorix memory graph-context --query "<topic>"` |',
|
|
21479
21791
|
'| Specific past decision, bug, file, or change | `memorix_search` | `memorix memory search --query "<topic>"` |',
|
|
@@ -21486,9 +21798,10 @@ var init_official_skills = __esm({
|
|
|
21486
21798
|
"",
|
|
21487
21799
|
"- Default first step for non-trivial coding work: call `memorix_project_context` before progress files, dev-log reads, ad-hoc file reads, or git archaeology. Pass the user's actual task so Memorix can choose a bugfix, feature, release, onboarding, refactor, docs, test, or general brief. Start by reading its suggested files.",
|
|
21488
21800
|
"- In Claude Code headless/print-mode, an initial MCP `pending` status is not a failure by itself. Prefer MCP first: use the client's tool discovery/dynamic loading to find `memorix_project_context` when it is not in the first visible tool list.",
|
|
21489
|
-
'-
|
|
21490
|
-
"-
|
|
21491
|
-
"-
|
|
21801
|
+
'- Continuation fallback is mandatory: when the user asks to continue, resume, take over, or explain prior work and MCP cannot be called in this turn, run `memorix resume "<task>" --json` exactly once before inspecting files, Git history, progress notes, or guessing. The absence of `.memorix` or visible memory files never proves project memory is empty. For a new task, use `memorix context "<task>" --json` instead. If that one command fails, report it and proceed normally; do not probe help, enumerate commands, or chain broad searches.',
|
|
21802
|
+
"- After a successful `memorix_project_context`, the brief is the default retrieval boundary. Do not call more Memorix retrieval tools after a complete brief. Use Context Pack, search, or detail only when the brief lacks a specific reference, freshness field, or fact needed for the task, or when the user explicitly asks for deeper history. In MCP, name that missing fact in `purpose` when intentionally expanding beyond the brief. Do not retrieve the same decision twice just to confirm an already-complete brief.",
|
|
21803
|
+
"- If the user asks for read-only work or says not to modify files, do not call `memorix_store` just to record an assessment. Store only when the user explicitly asks to preserve it.",
|
|
21804
|
+
"- Use `memorix_search` for a specific decision, bug, file, or prior change that the project context did not answer. Fetch detail only for a result you still need after the brief.",
|
|
21492
21805
|
"- Treat current code-bound memory as a map. Treat stale, suspect, or unbound memory as a lead that must be verified against the current code.",
|
|
21493
21806
|
"- Skip memory lookup for greetings, tiny one-off edits, or questions fully answered by the current file.",
|
|
21494
21807
|
"- If a fresh project has no memories, proceed normally and do not repeat the same empty search in the same turn.",
|
|
@@ -22709,17 +23022,18 @@ alwaysApply: true
|
|
|
22709
23022
|
"",
|
|
22710
23023
|
"## Start with Memory Autopilot",
|
|
22711
23024
|
"",
|
|
22712
|
-
`Default first step for non-trivial coding work: call \`memorix_project_context\` with the user's actual task before progress files, dev-log reads, ad-hoc file reads, or git archaeology. Memorix will choose a task-lensed brief (bugfix, feature, release, onboarding, refactor, docs, test, or general). Treat its "Start here" files as the first ${contextNoun} files to inspect.`,
|
|
23025
|
+
`Default first step for non-trivial coding work: call \`memorix_project_context\` with the user's actual task before progress files, dev-log reads, ad-hoc file reads, or git archaeology. Memorix will choose a task-lensed brief (bugfix, feature, release, onboarding, refactor, docs, test, or general). When the task is continuing prior work, the same brief also includes a bounded prior-work projection. Treat its "Start here" files as the first ${contextNoun} files to inspect.`,
|
|
22713
23026
|
"",
|
|
22714
|
-
'If the MCP tool is not visible yet but the client supports tool discovery or dynamic loading, search/select `memorix_project_context` first.
|
|
23027
|
+
'If the MCP tool is not visible yet but the client supports tool discovery or dynamic loading, search/select `memorix_project_context` first. Continuation fallback is mandatory: when the user asks to continue, resume, take over, or explain prior work and MCP cannot be called in this turn, run exactly one CLI brief with the user\'s real task before inspecting files, Git history, progress notes, or guessing: `memorix resume "<task>" --json`. For a new task, use `memorix context "<task>" --json` instead. The absence of `.memorix` or visible memory files never proves project memory is empty. If that one command fails, report it and proceed normally. Do not probe help, enumerate commands, chain broad searches, wait indefinitely on MCP startup, or hand-write tool-call syntax.',
|
|
22715
23028
|
"",
|
|
22716
|
-
"Use `memorix_context_pack` when
|
|
23029
|
+
"After a successful `memorix_project_context` result, the brief is the default retrieval boundary. Do not call more Memorix retrieval tools after a complete brief. Use `memorix_context_pack`, `memorix_search`, or `memorix_detail` only when the brief lacks a specific reference, freshness field, or fact needed for the task, or when the user explicitly asks for deeper history. In MCP, name that missing fact in `purpose` when intentionally expanding beyond the brief. Do not retrieve the same decision twice just to confirm an already-complete brief.",
|
|
23030
|
+
"If the user asks for read-only work or says not to modify files, do not call `memorix_store` just to record an assessment. Store only when the user explicitly asks to preserve it.",
|
|
22717
23031
|
"",
|
|
22718
23032
|
"## When to search memory",
|
|
22719
23033
|
"",
|
|
22720
23034
|
"Use `memorix_graph_context` for explicit memory graph questions or broad graph overview after the autopilot brief is not enough.",
|
|
22721
23035
|
"",
|
|
22722
|
-
`Use \`memorix_search\` when prior ${contextNoun} context would help \u2014 for example:`,
|
|
23036
|
+
`Use \`memorix_search\` when prior ${contextNoun} context would help and the Autopilot brief did not already answer the question \u2014 for example:`,
|
|
22723
23037
|
"- The user asks about a past decision, bug, or change",
|
|
22724
23038
|
"- You need to understand why something was designed a certain way",
|
|
22725
23039
|
"- You're continuing work that started in a previous session",
|
|
@@ -26619,18 +26933,10 @@ function parseFormationTimeoutMs(raw) {
|
|
|
26619
26933
|
}
|
|
26620
26934
|
|
|
26621
26935
|
// src/server.ts
|
|
26936
|
+
init_timeout();
|
|
26622
26937
|
var FORMATION_TIMEOUT_MS = parseFormationTimeoutMs(process.env.MEMORIX_FORMATION_TIMEOUT_MS);
|
|
26623
26938
|
var COMPACT_ON_WRITE_TIMEOUT_MS = 12e3;
|
|
26624
26939
|
var COMPRESSION_TIMEOUT_MS = 5e3;
|
|
26625
|
-
function withTimeout2(promise, ms, label) {
|
|
26626
|
-
let timer;
|
|
26627
|
-
return Promise.race([
|
|
26628
|
-
promise,
|
|
26629
|
-
new Promise((_, reject) => {
|
|
26630
|
-
timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
|
|
26631
|
-
})
|
|
26632
|
-
]).finally(() => clearTimeout(timer));
|
|
26633
|
-
}
|
|
26634
26940
|
function formatFormationStageDurations(stageDurationsMs) {
|
|
26635
26941
|
const orderedStages = ["extract", "resolve", "evaluate"];
|
|
26636
26942
|
const parts = orderedStages.filter((stage) => stageDurationsMs[stage] !== void 0).map((stage) => `${stage}=${stageDurationsMs[stage]}ms`);
|
|
@@ -26734,6 +27040,8 @@ var BOOTSTRAP_SAFE_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
|
26734
27040
|
"memorix_graph_context",
|
|
26735
27041
|
"memorix_context_pack"
|
|
26736
27042
|
]);
|
|
27043
|
+
var AUTOPILOT_RETRIEVAL_BOUNDARY_TTL_MS = 2 * 60 * 1e3;
|
|
27044
|
+
var READ_ONLY_TASK_PATTERN = /\b(?:do not|don't|never)\s+(?:modify|edit|change|write)\b|\bread[- ]only\b|(?:不要|不准|勿|禁止).{0,6}(?:修改|编辑|写入)|只读/i;
|
|
26737
27045
|
function shouldAwaitProjectRuntime(toolName) {
|
|
26738
27046
|
return !BOOTSTRAP_SAFE_TOOL_NAMES.has(toolName);
|
|
26739
27047
|
}
|
|
@@ -27004,7 +27312,7 @@ The path should point to a directory containing a .git folder.`
|
|
|
27004
27312
|
};
|
|
27005
27313
|
const server = existingServer ?? new McpServer({
|
|
27006
27314
|
name: "memorix",
|
|
27007
|
-
version: true ? "1.2.
|
|
27315
|
+
version: true ? "1.2.5" : "1.0.1"
|
|
27008
27316
|
});
|
|
27009
27317
|
const originalRegisterTool = server.registerTool.bind(server);
|
|
27010
27318
|
server.registerTool = ((name, ...args) => {
|
|
@@ -27039,11 +27347,52 @@ The path should point to a directory containing a .git folder.`
|
|
|
27039
27347
|
isTeamMember
|
|
27040
27348
|
};
|
|
27041
27349
|
};
|
|
27350
|
+
let autopilotRetrievalBoundary = null;
|
|
27351
|
+
const getActiveAutopilotRetrievalBoundary = () => {
|
|
27352
|
+
const boundary = autopilotRetrievalBoundary;
|
|
27353
|
+
if (!boundary) return null;
|
|
27354
|
+
if (boundary.projectId === project.id && Date.now() - boundary.issuedAt <= AUTOPILOT_RETRIEVAL_BOUNDARY_TTL_MS) {
|
|
27355
|
+
return boundary;
|
|
27356
|
+
}
|
|
27357
|
+
autopilotRetrievalBoundary = null;
|
|
27358
|
+
return null;
|
|
27359
|
+
};
|
|
27360
|
+
const requireExplicitAutopilotExpansion = (toolName, purpose) => {
|
|
27361
|
+
if (!getActiveAutopilotRetrievalBoundary() || purpose?.trim()) return null;
|
|
27362
|
+
return {
|
|
27363
|
+
content: [{
|
|
27364
|
+
type: "text",
|
|
27365
|
+
text: "Memorix Autopilot retrieval boundary: the latest `memorix_project_context` already supplied the default bounded brief for this coding turn, so no additional memory was retrieved. Verify the current project first. To intentionally expand beyond that brief, call `" + toolName + "` again with `purpose` naming the specific missing fact or the user's explicit request."
|
|
27366
|
+
}]
|
|
27367
|
+
};
|
|
27368
|
+
};
|
|
27369
|
+
const blockCoveredAutopilotEvidence = (toolName, observationIds, force) => {
|
|
27370
|
+
const boundary = getActiveAutopilotRetrievalBoundary();
|
|
27371
|
+
if (!boundary || force || observationIds.length === 0 || !observationIds.every((id) => boundary.coveredObservationIds.has(id))) {
|
|
27372
|
+
return null;
|
|
27373
|
+
}
|
|
27374
|
+
return {
|
|
27375
|
+
content: [{
|
|
27376
|
+
type: "text",
|
|
27377
|
+
text: "Memorix Autopilot retrieval boundary: every requested memory is already represented in the latest project brief, so no duplicate detail was retrieved. Inspect the current project or seek a new source. Retry with `force: true` only when the user explicitly asks to read the underlying record in full."
|
|
27378
|
+
}]
|
|
27379
|
+
};
|
|
27380
|
+
};
|
|
27381
|
+
const blockReadOnlyAutopilotWrite = (overrideReadOnly) => {
|
|
27382
|
+
const boundary = getActiveAutopilotRetrievalBoundary();
|
|
27383
|
+
if (!boundary?.readOnly || overrideReadOnly) return null;
|
|
27384
|
+
return {
|
|
27385
|
+
content: [{
|
|
27386
|
+
type: "text",
|
|
27387
|
+
text: "Memorix write boundary: the latest task is read-only or asks not to modify files, so no memory was stored. Persist a record only when the user explicitly asks to save it, then retry with `overrideReadOnly: true`."
|
|
27388
|
+
}]
|
|
27389
|
+
};
|
|
27390
|
+
};
|
|
27042
27391
|
server.registerTool(
|
|
27043
27392
|
"memorix_store",
|
|
27044
27393
|
{
|
|
27045
27394
|
title: "Store Memory",
|
|
27046
|
-
description: "Store a new observation/memory. Automatically indexed for search. Use type to classify: gotcha ([GOTCHA] critical pitfall), decision ([DECISION] architecture choice), problem-solution ([FIX] bug fix), how-it-works ([INFO] explanation), what-changed ([CHANGE] change), discovery ([DISCOVERY] insight), why-it-exists ([WHY] rationale), trade-off ([TRADEOFF] compromise), session-request ([SESSION] original goal). Project visibility is the default. Personal or team visibility requires an explicitly joined coordination identity.",
|
|
27395
|
+
description: "Store a new observation/memory. Automatically indexed for search. Use type to classify: gotcha ([GOTCHA] critical pitfall), decision ([DECISION] architecture choice), problem-solution ([FIX] bug fix), how-it-works ([INFO] explanation), what-changed ([CHANGE] change), discovery ([DISCOVERY] insight), why-it-exists ([WHY] rationale), trade-off ([TRADEOFF] compromise), session-request ([SESSION] original goal). Project visibility is the default. Personal or team visibility requires an explicitly joined coordination identity. For a read-only task, do not store unless the user explicitly asks to save a record.",
|
|
27047
27396
|
inputSchema: {
|
|
27048
27397
|
entityName: z2.string().describe('The entity this observation belongs to (e.g., "auth-module", "port-config")'),
|
|
27049
27398
|
type: z2.enum(OBSERVATION_TYPES).describe("Observation type for classification"),
|
|
@@ -27064,12 +27413,17 @@ The path should point to a directory containing a .git folder.`
|
|
|
27064
27413
|
relatedEntities: z2.array(z2.string()).optional().describe("Other entity names this memory cross-references"),
|
|
27065
27414
|
visibility: z2.enum(["personal", "project", "team"]).optional().describe(
|
|
27066
27415
|
"Retrieval scope. Project is the normal shared default; personal/team require memorix_session_start with joinTeam=true."
|
|
27416
|
+
),
|
|
27417
|
+
overrideReadOnly: z2.boolean().optional().describe(
|
|
27418
|
+
"Use only when the user explicitly asks to save memory during a read-only or no-modification task."
|
|
27067
27419
|
)
|
|
27068
27420
|
}
|
|
27069
27421
|
},
|
|
27070
|
-
async ({ entityName: rawEntityName, type: rawType, title: rawTitle, narrative, facts, filesModified, concepts, topicKey, progress, relatedCommits, relatedEntities, visibility }) => {
|
|
27422
|
+
async ({ entityName: rawEntityName, type: rawType, title: rawTitle, narrative, facts, filesModified, concepts, topicKey, progress, relatedCommits, relatedEntities, visibility, overrideReadOnly }) => {
|
|
27071
27423
|
const unresolved = requireResolvedProject("store memory in the current project");
|
|
27072
27424
|
if (unresolved) return unresolved;
|
|
27425
|
+
const readOnlyBoundary = blockReadOnlyAutopilotWrite(overrideReadOnly);
|
|
27426
|
+
if (readOnlyBoundary) return readOnlyBoundary;
|
|
27073
27427
|
const requestedVisibility = visibility ?? "project";
|
|
27074
27428
|
const reader = getObservationReader();
|
|
27075
27429
|
if (requestedVisibility !== "project" && !currentAgentId) {
|
|
@@ -27155,7 +27509,7 @@ The path should point to a directory containing a .git folder.`
|
|
|
27155
27509
|
getEntityNames: () => graphManager.getEntityNames(),
|
|
27156
27510
|
onStageEvent: onFormationStageEvent
|
|
27157
27511
|
};
|
|
27158
|
-
formationResult = await
|
|
27512
|
+
formationResult = await withTimeout(
|
|
27159
27513
|
runFormation({
|
|
27160
27514
|
entityName,
|
|
27161
27515
|
type,
|
|
@@ -27276,7 +27630,7 @@ ${modeIcon} Formation[active]: ${formationResult.evaluation.category} (${formati
|
|
|
27276
27630
|
facts: d.facts,
|
|
27277
27631
|
score: similarEntries[i]?.score ?? 0
|
|
27278
27632
|
}));
|
|
27279
|
-
const decision = await
|
|
27633
|
+
const decision = await withTimeout(
|
|
27280
27634
|
compactOnWrite(
|
|
27281
27635
|
{ title, narrative, facts: safeFacts ?? [] },
|
|
27282
27636
|
existingMemories
|
|
@@ -27372,7 +27726,7 @@ Mode: ${decision.usedLLM ? "LLM" : "heuristic"}`
|
|
|
27372
27726
|
let compressionNote = "";
|
|
27373
27727
|
try {
|
|
27374
27728
|
const { compressNarrative: compressNarrative2 } = await Promise.resolve().then(() => (init_quality(), quality_exports));
|
|
27375
|
-
const { compressed, saved, usedLLM } = await
|
|
27729
|
+
const { compressed, saved, usedLLM } = await withTimeout(
|
|
27376
27730
|
compressNarrative2(narrative, safeFacts, type),
|
|
27377
27731
|
COMPRESSION_TIMEOUT_MS,
|
|
27378
27732
|
"Narrative compression"
|
|
@@ -27494,7 +27848,7 @@ Auto-enriched: ${enrichmentParts.join(", ")}` : "";
|
|
|
27494
27848
|
},
|
|
27495
27849
|
getEntityNames: () => graphManager.getEntityNames()
|
|
27496
27850
|
};
|
|
27497
|
-
const formed = await
|
|
27851
|
+
const formed = await withTimeout(
|
|
27498
27852
|
runFormation({ entityName, type, title, narrative, facts: safeFacts, projectId: project.id, source: "explicit", topicKey }, formationConfig),
|
|
27499
27853
|
FORMATION_TIMEOUT_MS,
|
|
27500
27854
|
"Shadow formation"
|
|
@@ -27568,7 +27922,7 @@ Use this as the \`topicKey\` parameter in \`memorix_store\` to enable upsert beh
|
|
|
27568
27922
|
"memorix_search",
|
|
27569
27923
|
{
|
|
27570
27924
|
title: "Search Memory",
|
|
27571
|
-
description: "Search project memory. Returns a compact index (~50-100 tokens/result). Use memorix_detail to fetch full content for specific IDs. Use memorix_timeline to see chronological context. Searches across all observations stored from any IDE session \u2014 enabling cross-session and cross-agent context retrieval.",
|
|
27925
|
+
description: "Search project memory. Returns a compact index (~50-100 tokens/result). Do not use as a follow-up to a complete memorix_project_context brief unless a specific fact is still missing or the user asks for deeper history; provide purpose when intentionally expanding. Use memorix_detail to fetch full content for specific IDs. Use memorix_timeline to see chronological context. Searches across all observations stored from any IDE session \u2014 enabling cross-session and cross-agent context retrieval.",
|
|
27572
27926
|
inputSchema: {
|
|
27573
27927
|
query: z2.string().describe("Search query (natural language or keywords)"),
|
|
27574
27928
|
limit: z2.number().optional().describe("Max results (default: 20)"),
|
|
@@ -27584,14 +27938,27 @@ Use this as the \`topicKey\` parameter in \`memorix_store\` to enable upsert beh
|
|
|
27584
27938
|
),
|
|
27585
27939
|
source: z2.enum(["agent", "git", "manual"]).optional().describe(
|
|
27586
27940
|
'Filter by memory source. "git" returns only commit-derived ground truth memories. Omit for all sources.'
|
|
27941
|
+
),
|
|
27942
|
+
quality: z2.enum(["fast", "balanced", "thorough"]).optional().default("balanced").describe(
|
|
27943
|
+
"Retrieval profile: fast stays local, balanced uses configured embeddings, thorough explicitly permits optional LLM refinement."
|
|
27944
|
+
),
|
|
27945
|
+
purpose: z2.string().optional().describe(
|
|
27946
|
+
"Why this must expand beyond the latest Autopilot brief. Name the missing fact or the user's explicit request."
|
|
27947
|
+
),
|
|
27948
|
+
force: z2.boolean().optional().describe(
|
|
27949
|
+
"Use only when the user explicitly asks to read a record already represented in the latest Autopilot brief."
|
|
27587
27950
|
)
|
|
27588
27951
|
}
|
|
27589
27952
|
},
|
|
27590
|
-
async ({ query, limit, type, maxTokens, scope, since, until, status, source }) => {
|
|
27953
|
+
async ({ query, limit, type, maxTokens, scope, since, until, status, source, quality, purpose, force }) => {
|
|
27591
27954
|
if (scope !== "global") {
|
|
27592
27955
|
const unresolved = requireResolvedProject("search the current project");
|
|
27593
27956
|
if (unresolved) return unresolved;
|
|
27594
27957
|
}
|
|
27958
|
+
if (scope !== "global") {
|
|
27959
|
+
const boundary = requireExplicitAutopilotExpansion("memorix_search", purpose);
|
|
27960
|
+
if (boundary) return boundary;
|
|
27961
|
+
}
|
|
27595
27962
|
return withFreshIndex(async () => {
|
|
27596
27963
|
const safeLimit = limit != null ? coerceNumber(limit, 20) : void 0;
|
|
27597
27964
|
const safeMaxTokens = maxTokens != null ? coerceNumber(maxTokens, 0) : void 0;
|
|
@@ -27608,16 +27975,14 @@ Use this as the \`topicKey\` parameter in \`memorix_store\` to enable upsert beh
|
|
|
27608
27975
|
projectId: scope === "global" ? void 0 : project.id,
|
|
27609
27976
|
status: status ?? "active",
|
|
27610
27977
|
source,
|
|
27978
|
+
quality,
|
|
27611
27979
|
reader: getObservationReader(scope === "global" ? "global" : "project")
|
|
27612
27980
|
});
|
|
27613
|
-
const timeoutPromise = new Promise(
|
|
27614
|
-
(_, reject) => setTimeout(() => reject(new Error(`Search timeout after ${TIMEOUT_MS}ms`)), TIMEOUT_MS)
|
|
27615
|
-
);
|
|
27616
27981
|
let result;
|
|
27617
27982
|
try {
|
|
27618
|
-
result = await
|
|
27983
|
+
result = await withTimeout(searchPromise, TIMEOUT_MS, "Search");
|
|
27619
27984
|
} catch (error) {
|
|
27620
|
-
if (error instanceof Error && error.message
|
|
27985
|
+
if (error instanceof Error && /\btimeout\b|timed out/i.test(error.message)) {
|
|
27621
27986
|
return {
|
|
27622
27987
|
content: [
|
|
27623
27988
|
{
|
|
@@ -27630,6 +27995,11 @@ Use this as the \`topicKey\` parameter in \`memorix_store\` to enable upsert beh
|
|
|
27630
27995
|
}
|
|
27631
27996
|
throw error;
|
|
27632
27997
|
}
|
|
27998
|
+
const activeBoundary = getActiveAutopilotRetrievalBoundary();
|
|
27999
|
+
if (scope !== "global" && activeBoundary && !force && result.entries.length > 0 && result.entries.every((entry) => activeBoundary.coveredObservationIds.has(entry.id))) {
|
|
28000
|
+
const duplicate = blockCoveredAutopilotEvidence("memorix_search", result.entries.map((entry) => entry.id), force);
|
|
28001
|
+
if (duplicate) return duplicate;
|
|
28002
|
+
}
|
|
27633
28003
|
let text = result.formatted;
|
|
27634
28004
|
try {
|
|
27635
28005
|
const { getLastSearchMode: getLastSearchMode2 } = await Promise.resolve().then(() => (init_orama_store(), orama_store_exports));
|
|
@@ -27731,6 +28101,7 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
|
|
|
27731
28101
|
observations: observations2,
|
|
27732
28102
|
task,
|
|
27733
28103
|
refresh: refresh ?? "auto",
|
|
28104
|
+
reader: getObservationReader(),
|
|
27734
28105
|
enqueueRefresh: () => {
|
|
27735
28106
|
enqueueCodegraphRefresh2({
|
|
27736
28107
|
dataDir: projectDir2,
|
|
@@ -27742,6 +28113,16 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
|
|
|
27742
28113
|
}
|
|
27743
28114
|
});
|
|
27744
28115
|
const text = format === "json" ? JSON.stringify({ ...context, brief: buildAutoProjectBrief2(context) }, null, 2) : format === "summary" ? formatAutoProjectContextSummary2(context) : formatAutoProjectContextPrompt2(context);
|
|
28116
|
+
autopilotRetrievalBoundary = {
|
|
28117
|
+
projectId: project.id,
|
|
28118
|
+
issuedAt: Date.now(),
|
|
28119
|
+
coveredObservationIds: /* @__PURE__ */ new Set([
|
|
28120
|
+
...context.workset.continuation?.memories.map((memory) => memory.id) ?? [],
|
|
28121
|
+
...context.workset.reliableMemory.map((memory) => memory.id),
|
|
28122
|
+
...context.workset.cautionMemory.map((memory) => memory.id)
|
|
28123
|
+
]),
|
|
28124
|
+
readOnly: READ_ONLY_TASK_PATTERN.test(task ?? "")
|
|
28125
|
+
};
|
|
27745
28126
|
return {
|
|
27746
28127
|
content: [{ type: "text", text }]
|
|
27747
28128
|
};
|
|
@@ -27781,18 +28162,23 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
|
|
|
27781
28162
|
"memorix_context_pack",
|
|
27782
28163
|
{
|
|
27783
28164
|
title: "Context Pack",
|
|
27784
|
-
description: "Build a prompt-ready working context pack for a coding task. Combines relevant memories, CodeGraph Memory facts, freshness warnings, suggested reads, and verification hints.",
|
|
28165
|
+
description: "Build a prompt-ready working context pack for a coding task. Combines relevant memories, CodeGraph Memory facts, freshness warnings, suggested reads, and verification hints. After a complete memorix_project_context brief, provide purpose only when deliberately expanding beyond it.",
|
|
27785
28166
|
inputSchema: {
|
|
27786
28167
|
task: z2.string().describe("Current coding task or question"),
|
|
27787
28168
|
limit: z2.preprocess(
|
|
27788
28169
|
(value) => typeof value === "string" && value.trim() !== "" ? Number(value) : value,
|
|
27789
28170
|
z2.number().int().positive().max(100)
|
|
27790
|
-
).optional().describe("Max active memories to inspect before code-ref filtering (default: 20)")
|
|
28171
|
+
).optional().describe("Max active memories to inspect before code-ref filtering (default: 20)"),
|
|
28172
|
+
purpose: z2.string().optional().describe(
|
|
28173
|
+
"Why this must expand beyond the latest Autopilot brief. Name the missing fact or the user's explicit request."
|
|
28174
|
+
)
|
|
27791
28175
|
}
|
|
27792
28176
|
},
|
|
27793
|
-
async ({ task, limit }) => {
|
|
28177
|
+
async ({ task, limit, purpose }) => {
|
|
27794
28178
|
const unresolved = requireResolvedProject("build a context pack for the current project");
|
|
27795
28179
|
if (unresolved) return unresolved;
|
|
28180
|
+
const boundary = requireExplicitAutopilotExpansion("memorix_context_pack", purpose);
|
|
28181
|
+
if (boundary) return boundary;
|
|
27796
28182
|
const [
|
|
27797
28183
|
{ CodeGraphStore: CodeGraphStore2 },
|
|
27798
28184
|
{ assembleContextPackForTask: assembleContextPackForTask2, attachTaskWorkset: attachTaskWorkset2, buildContextPackPrompt: buildContextPackPrompt2 },
|
|
@@ -28537,7 +28923,7 @@ ${actions.join("\n")}`
|
|
|
28537
28923
|
"memorix_detail",
|
|
28538
28924
|
{
|
|
28539
28925
|
title: "Memory Details",
|
|
28540
|
-
description: 'Fetch full observation or mini-skill details \u2014 includes source kind (explicit memory / hook trace / git evidence), value category, and cross-references (~500-1000 tokens each). Always use memorix_search first to find relevant IDs, then fetch only what you need. Accepts typed refs from search results (e.g. "obs:42", "skill:3") via the typedRefs field, or legacy numeric ids / object refs for backward compatibility.',
|
|
28926
|
+
description: 'Fetch full observation or mini-skill details \u2014 includes source kind (explicit memory / hook trace / git evidence), value category, and cross-references (~500-1000 tokens each). Do not re-fetch content already covered by a complete memorix_project_context brief unless a specific fact is still missing or the user asks for deeper history; provide purpose when intentionally expanding. Always use memorix_search first to find relevant IDs, then fetch only what you need. Accepts typed refs from search results (e.g. "obs:42", "skill:3") via the typedRefs field, or legacy numeric ids / object refs for backward compatibility.',
|
|
28541
28927
|
inputSchema: {
|
|
28542
28928
|
ids: z2.array(z2.number()).optional().describe("Observation IDs to fetch (legacy, from memorix_search results)"),
|
|
28543
28929
|
refs: z2.array(
|
|
@@ -28546,16 +28932,36 @@ ${actions.join("\n")}`
|
|
|
28546
28932
|
projectId: z2.string().optional().describe("Project ID for global-search disambiguation")
|
|
28547
28933
|
})
|
|
28548
28934
|
).optional().describe("Explicit observation refs. Prefer this for global search results."),
|
|
28549
|
-
typedRefs: z2.array(z2.string()).optional().describe('Typed memory refs from search results, e.g. "obs:42", "skill:3", "obs:42@org/proj"')
|
|
28935
|
+
typedRefs: z2.array(z2.string()).optional().describe('Typed memory refs from search results, e.g. "obs:42", "skill:3", "obs:42@org/proj"'),
|
|
28936
|
+
purpose: z2.string().optional().describe(
|
|
28937
|
+
"Why this must expand beyond the latest Autopilot brief. Name the missing fact or the user's explicit request."
|
|
28938
|
+
),
|
|
28939
|
+
force: z2.boolean().optional().describe(
|
|
28940
|
+
"Use only when the user explicitly asks to read a record already represented in the latest Autopilot brief."
|
|
28941
|
+
)
|
|
28550
28942
|
}
|
|
28551
28943
|
},
|
|
28552
|
-
async ({ ids, refs, typedRefs }) => {
|
|
28944
|
+
async ({ ids, refs, typedRefs, purpose, force }) => {
|
|
28553
28945
|
const safeIds = coerceNumberArray(ids);
|
|
28554
28946
|
const safeRefs = coerceObservationRefs(refs);
|
|
28555
28947
|
const safeTypedRefs = coerceStringArray(typedRefs);
|
|
28948
|
+
const hasCrossProjectRef = safeRefs.some((ref) => ref.projectId && ref.projectId !== project.id) || safeTypedRefs.some((ref) => ref.includes("@") && !ref.endsWith(`@${project.id}`));
|
|
28949
|
+
if (!hasCrossProjectRef) {
|
|
28950
|
+
const boundary = requireExplicitAutopilotExpansion("memorix_detail", purpose);
|
|
28951
|
+
if (boundary) return boundary;
|
|
28952
|
+
const requestedObservationIds = [
|
|
28953
|
+
...safeIds,
|
|
28954
|
+
...safeRefs.map((ref) => ref.id),
|
|
28955
|
+
...safeTypedRefs.flatMap((ref) => {
|
|
28956
|
+
const match = /^obs:(\d+)(?:@.+)?$/i.exec(ref.trim());
|
|
28957
|
+
return match ? [Number(match[1])] : [];
|
|
28958
|
+
})
|
|
28959
|
+
];
|
|
28960
|
+
const duplicate = blockCoveredAutopilotEvidence("memorix_detail", requestedObservationIds, force);
|
|
28961
|
+
if (duplicate) return duplicate;
|
|
28962
|
+
}
|
|
28556
28963
|
let result;
|
|
28557
28964
|
try {
|
|
28558
|
-
const hasCrossProjectRef = safeRefs.some((ref) => ref.projectId && ref.projectId !== project.id) || safeTypedRefs.some((ref) => ref.includes("@") && !ref.endsWith(`@${project.id}`));
|
|
28559
28965
|
const reader = getObservationReader(hasCrossProjectRef ? "global" : "project");
|
|
28560
28966
|
if (safeTypedRefs.length > 0) {
|
|
28561
28967
|
result = await compactDetail(safeTypedRefs, { reader });
|
|
@@ -30581,6 +30987,7 @@ fi
|
|
|
30581
30987
|
if (newCanonicalId === project.id && projectResolved) return false;
|
|
30582
30988
|
console.error(`[memorix] Switching project: ${project.id} \u2192 ${newCanonicalId}`);
|
|
30583
30989
|
currentAgentId = void 0;
|
|
30990
|
+
autopilotRetrievalBoundary = null;
|
|
30584
30991
|
const canonicalProjectDir = newCanonicalId !== newDetected.id ? await getProjectDataDir(newCanonicalId) : newProjectDir;
|
|
30585
30992
|
projectResolved = true;
|
|
30586
30993
|
projectResolutionError = null;
|