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/sdk.js
CHANGED
|
@@ -86,36 +86,111 @@ var init_visibility = __esm({
|
|
|
86
86
|
|
|
87
87
|
// src/store/bun-sqlite-compat.ts
|
|
88
88
|
import { createRequire } from "module";
|
|
89
|
+
function loadNodeSqlite() {
|
|
90
|
+
const nodeSqlite = requireFromHere("node:sqlite");
|
|
91
|
+
return nodeSqlite.DatabaseSync;
|
|
92
|
+
}
|
|
93
|
+
function isNativeBindingFailure(error) {
|
|
94
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
95
|
+
return /could not locate the bindings file|better_sqlite3\.node|module did not self-register/i.test(message);
|
|
96
|
+
}
|
|
97
|
+
function addCompatibility(db2) {
|
|
98
|
+
if (!db2.pragma) {
|
|
99
|
+
db2.pragma = function(pragma, options) {
|
|
100
|
+
const statement = db2.prepare(`PRAGMA ${pragma}`);
|
|
101
|
+
if (options?.simple) {
|
|
102
|
+
const result = statement.get();
|
|
103
|
+
return result ? Object.values(result)[0] : void 0;
|
|
104
|
+
}
|
|
105
|
+
return statement.all();
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
if (!db2.transaction) {
|
|
109
|
+
let depth = 0;
|
|
110
|
+
let sequence = 0;
|
|
111
|
+
db2.transaction = function(fn) {
|
|
112
|
+
return (...args) => {
|
|
113
|
+
const outermost = depth === 0;
|
|
114
|
+
const savepoint = `memorix_tx_${++sequence}`;
|
|
115
|
+
if (outermost) {
|
|
116
|
+
db2.exec("BEGIN IMMEDIATE");
|
|
117
|
+
} else {
|
|
118
|
+
db2.exec(`SAVEPOINT ${savepoint}`);
|
|
119
|
+
}
|
|
120
|
+
depth += 1;
|
|
121
|
+
try {
|
|
122
|
+
const result = fn(...args);
|
|
123
|
+
if (result && typeof result.then === "function") {
|
|
124
|
+
throw new Error("[memorix] SQLite transactions must be synchronous");
|
|
125
|
+
}
|
|
126
|
+
depth -= 1;
|
|
127
|
+
if (outermost) {
|
|
128
|
+
db2.exec("COMMIT");
|
|
129
|
+
} else {
|
|
130
|
+
db2.exec(`RELEASE SAVEPOINT ${savepoint}`);
|
|
131
|
+
}
|
|
132
|
+
return result;
|
|
133
|
+
} catch (error) {
|
|
134
|
+
depth -= 1;
|
|
135
|
+
try {
|
|
136
|
+
if (outermost) {
|
|
137
|
+
db2.exec("ROLLBACK");
|
|
138
|
+
} else {
|
|
139
|
+
db2.exec(`ROLLBACK TO SAVEPOINT ${savepoint}`);
|
|
140
|
+
db2.exec(`RELEASE SAVEPOINT ${savepoint}`);
|
|
141
|
+
}
|
|
142
|
+
} catch {
|
|
143
|
+
}
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
return db2;
|
|
150
|
+
}
|
|
151
|
+
function instantiateDatabase(Sqlite, filePath, options) {
|
|
152
|
+
return driver === "node:sqlite" && options === void 0 ? new Sqlite(filePath) : new Sqlite(filePath, options);
|
|
153
|
+
}
|
|
89
154
|
function loadSqlite() {
|
|
90
155
|
if (Database) return Database;
|
|
156
|
+
if (process.env.MEMORIX_SQLITE_DRIVER === "node") {
|
|
157
|
+
Database = loadNodeSqlite();
|
|
158
|
+
driver = "node:sqlite";
|
|
159
|
+
return Database;
|
|
160
|
+
}
|
|
91
161
|
try {
|
|
92
162
|
Database = requireFromHere("better-sqlite3");
|
|
163
|
+
driver = "better-sqlite3";
|
|
164
|
+
return Database;
|
|
165
|
+
} catch {
|
|
166
|
+
}
|
|
167
|
+
try {
|
|
168
|
+
Database = loadNodeSqlite();
|
|
169
|
+
driver = "node:sqlite";
|
|
93
170
|
return Database;
|
|
94
171
|
} catch {
|
|
95
172
|
}
|
|
96
173
|
try {
|
|
97
174
|
const bunSqlite = requireFromHere("bun:sqlite");
|
|
98
175
|
Database = bunSqlite.Database;
|
|
176
|
+
driver = "bun:sqlite";
|
|
99
177
|
return Database;
|
|
100
178
|
} catch {
|
|
101
|
-
throw new Error("[memorix]
|
|
179
|
+
throw new Error("[memorix] SQLite is unavailable (better-sqlite3, node:sqlite, and bun:sqlite failed)");
|
|
102
180
|
}
|
|
103
181
|
}
|
|
104
182
|
function createDatabase(path25, options) {
|
|
105
183
|
const Sqlite = loadSqlite();
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
return db2.prepare(`PRAGMA ${pragma}`).all();
|
|
114
|
-
};
|
|
184
|
+
try {
|
|
185
|
+
return addCompatibility(instantiateDatabase(Sqlite, path25, options));
|
|
186
|
+
} catch (error) {
|
|
187
|
+
if (driver !== "better-sqlite3" || !isNativeBindingFailure(error)) throw error;
|
|
188
|
+
Database = loadNodeSqlite();
|
|
189
|
+
driver = "node:sqlite";
|
|
190
|
+
return addCompatibility(instantiateDatabase(Database, path25, options));
|
|
115
191
|
}
|
|
116
|
-
return db2;
|
|
117
192
|
}
|
|
118
|
-
var Database, requireFromHere;
|
|
193
|
+
var Database, driver, requireFromHere;
|
|
119
194
|
var init_bun_sqlite_compat = __esm({
|
|
120
195
|
"src/store/bun-sqlite-compat.ts"() {
|
|
121
196
|
"use strict";
|
|
@@ -134,7 +209,7 @@ function loadBetterSqlite3() {
|
|
|
134
209
|
BetterSqlite3 = loadSqlite();
|
|
135
210
|
return BetterSqlite3;
|
|
136
211
|
} catch {
|
|
137
|
-
throw new Error("[memorix] SQLite is not available (
|
|
212
|
+
throw new Error("[memorix] SQLite is not available (better-sqlite3, node:sqlite, and bun:sqlite all failed)");
|
|
138
213
|
}
|
|
139
214
|
}
|
|
140
215
|
function hasColumn(db2, table, column) {
|
|
@@ -1942,7 +2017,16 @@ function truncateToTokenBudget(text, budget) {
|
|
|
1942
2017
|
result = result.slice(0, Math.floor(result.length * 0.9));
|
|
1943
2018
|
}
|
|
1944
2019
|
if (result.length < text.length) {
|
|
1945
|
-
|
|
2020
|
+
const nextCharacter = text.charAt(result.length);
|
|
2021
|
+
if (nextCharacter && !/[\s.,;:!?)}\]]/.test(nextCharacter)) {
|
|
2022
|
+
const boundary = result.search(/\s+\S*$/);
|
|
2023
|
+
result = boundary > 0 ? result.slice(0, boundary).trimEnd() : "";
|
|
2024
|
+
}
|
|
2025
|
+
while (result && fitsInBudget(result + "...", budget) === false) {
|
|
2026
|
+
const boundary = result.lastIndexOf(" ");
|
|
2027
|
+
result = boundary > 0 ? result.slice(0, boundary).trimEnd() : "";
|
|
2028
|
+
}
|
|
2029
|
+
result = result ? result + "..." : "...";
|
|
1946
2030
|
}
|
|
1947
2031
|
}
|
|
1948
2032
|
return result;
|
|
@@ -2595,6 +2679,9 @@ function parseTomlValue(raw, filePath, line) {
|
|
|
2595
2679
|
if (raw.startsWith('"') && raw.endsWith('"')) {
|
|
2596
2680
|
return raw.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
2597
2681
|
}
|
|
2682
|
+
if (raw.startsWith("'") && raw.endsWith("'")) {
|
|
2683
|
+
return raw.slice(1, -1);
|
|
2684
|
+
}
|
|
2598
2685
|
if (raw === "true") return true;
|
|
2599
2686
|
if (raw === "false") return false;
|
|
2600
2687
|
if (/^-?\d+$/.test(raw)) return Number.parseInt(raw, 10);
|
|
@@ -2607,7 +2694,7 @@ function parseTomlValue(raw, filePath, line) {
|
|
|
2607
2694
|
throw new Error(`Unsupported TOML value in ${filePath}:${line}`);
|
|
2608
2695
|
}
|
|
2609
2696
|
function stripComment(line) {
|
|
2610
|
-
let
|
|
2697
|
+
let quote = null;
|
|
2611
2698
|
let escaped = false;
|
|
2612
2699
|
for (let i = 0; i < line.length; i++) {
|
|
2613
2700
|
const char = line[i];
|
|
@@ -2615,15 +2702,16 @@ function stripComment(line) {
|
|
|
2615
2702
|
escaped = false;
|
|
2616
2703
|
continue;
|
|
2617
2704
|
}
|
|
2618
|
-
if (char === "\\" &&
|
|
2705
|
+
if (char === "\\" && quote === '"') {
|
|
2619
2706
|
escaped = true;
|
|
2620
2707
|
continue;
|
|
2621
2708
|
}
|
|
2622
|
-
if (char === '"') {
|
|
2623
|
-
|
|
2709
|
+
if (char === '"' || char === "'") {
|
|
2710
|
+
if (quote === char) quote = null;
|
|
2711
|
+
else if (quote === null) quote = char;
|
|
2624
2712
|
continue;
|
|
2625
2713
|
}
|
|
2626
|
-
if (char === "#" &&
|
|
2714
|
+
if (char === "#" && quote === null) {
|
|
2627
2715
|
return line.slice(0, i);
|
|
2628
2716
|
}
|
|
2629
2717
|
}
|
|
@@ -2719,6 +2807,25 @@ function getResolvedConfig(options = {}) {
|
|
|
2719
2807
|
const legacy = loadFileConfig();
|
|
2720
2808
|
const embeddingBaseUrl = first(process.env.MEMORIX_EMBEDDING_BASE_URL, toml.embedding?.base_url, yaml2.embedding?.baseUrl, legacy.embeddingApi?.baseUrl);
|
|
2721
2809
|
const openRouterEmbeddingApiKey = isOpenRouterUrl(embeddingBaseUrl) ? process.env.OPENROUTER_API_KEY : void 0;
|
|
2810
|
+
const memoryLlmProvider = first(
|
|
2811
|
+
process.env.MEMORIX_LLM_PROVIDER,
|
|
2812
|
+
toml.memory?.llm?.provider,
|
|
2813
|
+
yaml2.llm?.provider,
|
|
2814
|
+
legacy.llm?.provider
|
|
2815
|
+
);
|
|
2816
|
+
const memoryLlmModel = first(
|
|
2817
|
+
process.env.MEMORIX_LLM_MODEL,
|
|
2818
|
+
toml.memory?.llm?.model,
|
|
2819
|
+
yaml2.llm?.model,
|
|
2820
|
+
legacy.llm?.model
|
|
2821
|
+
);
|
|
2822
|
+
const memoryLlmBaseUrl = first(
|
|
2823
|
+
process.env.MEMORIX_LLM_BASE_URL,
|
|
2824
|
+
toml.memory?.llm?.base_url,
|
|
2825
|
+
yaml2.llm?.baseUrl,
|
|
2826
|
+
legacy.llm?.baseUrl
|
|
2827
|
+
);
|
|
2828
|
+
const openRouterMemoryLlmApiKey = isOpenRouterMemoryLane(memoryLlmProvider, memoryLlmBaseUrl) ? process.env.OPENROUTER_API_KEY : void 0;
|
|
2722
2829
|
const resolved = {
|
|
2723
2830
|
agent: {
|
|
2724
2831
|
provider: first(
|
|
@@ -2756,9 +2863,9 @@ function getResolvedConfig(options = {}) {
|
|
|
2756
2863
|
autoCleanup: firstBool(toml.memory?.auto_cleanup, yaml2.behavior?.autoCleanup),
|
|
2757
2864
|
syncAdvisory: firstBool(toml.memory?.sync_advisory, yaml2.behavior?.syncAdvisory),
|
|
2758
2865
|
llm: {
|
|
2759
|
-
provider:
|
|
2760
|
-
model:
|
|
2761
|
-
baseUrl:
|
|
2866
|
+
provider: memoryLlmProvider,
|
|
2867
|
+
model: memoryLlmModel,
|
|
2868
|
+
baseUrl: memoryLlmBaseUrl,
|
|
2762
2869
|
apiKey: first(
|
|
2763
2870
|
process.env.MEMORIX_LLM_API_KEY,
|
|
2764
2871
|
process.env.MEMORIX_API_KEY,
|
|
@@ -2767,7 +2874,7 @@ function getResolvedConfig(options = {}) {
|
|
|
2767
2874
|
legacy.llm?.apiKey,
|
|
2768
2875
|
process.env.OPENAI_API_KEY,
|
|
2769
2876
|
process.env.ANTHROPIC_API_KEY,
|
|
2770
|
-
|
|
2877
|
+
openRouterMemoryLlmApiKey
|
|
2771
2878
|
)
|
|
2772
2879
|
}
|
|
2773
2880
|
},
|
|
@@ -2904,6 +3011,9 @@ function isOpenRouterUrl(value) {
|
|
|
2904
3011
|
return /(^|\.)openrouter\.ai(?::|\/|$)/i.test(value);
|
|
2905
3012
|
}
|
|
2906
3013
|
}
|
|
3014
|
+
function isOpenRouterMemoryLane(provider2, baseUrl) {
|
|
3015
|
+
return provider2?.trim().toLowerCase() === "openrouter" || isOpenRouterUrl(baseUrl);
|
|
3016
|
+
}
|
|
2907
3017
|
function normalizeExternalContext(value) {
|
|
2908
3018
|
const normalized = value?.trim().toLowerCase();
|
|
2909
3019
|
if (normalized === "auto" || normalized === "off") return normalized;
|
|
@@ -4951,6 +5061,29 @@ Rules:
|
|
|
4951
5061
|
}
|
|
4952
5062
|
});
|
|
4953
5063
|
|
|
5064
|
+
// src/timeout.ts
|
|
5065
|
+
function withTimeout(promise, ms, label) {
|
|
5066
|
+
return new Promise((resolve2, reject) => {
|
|
5067
|
+
const timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
|
|
5068
|
+
promise.then(
|
|
5069
|
+
(value) => {
|
|
5070
|
+
clearTimeout(timer);
|
|
5071
|
+
resolve2(value);
|
|
5072
|
+
},
|
|
5073
|
+
(error) => {
|
|
5074
|
+
clearTimeout(timer);
|
|
5075
|
+
reject(error);
|
|
5076
|
+
}
|
|
5077
|
+
);
|
|
5078
|
+
});
|
|
5079
|
+
}
|
|
5080
|
+
var init_timeout = __esm({
|
|
5081
|
+
"src/timeout.ts"() {
|
|
5082
|
+
"use strict";
|
|
5083
|
+
init_esm_shims();
|
|
5084
|
+
}
|
|
5085
|
+
});
|
|
5086
|
+
|
|
4954
5087
|
// src/project/aliases.ts
|
|
4955
5088
|
var aliases_exports = {};
|
|
4956
5089
|
__export(aliases_exports, {
|
|
@@ -5716,8 +5849,12 @@ async function searchObservations(options) {
|
|
|
5716
5849
|
}
|
|
5717
5850
|
};
|
|
5718
5851
|
const modeKey = options.projectId ?? SEARCH_MODE_DEFAULT_KEY;
|
|
5719
|
-
|
|
5852
|
+
const quality = options.quality ?? "balanced";
|
|
5720
5853
|
const database = await getDb();
|
|
5854
|
+
lastSearchModeByProject.set(
|
|
5855
|
+
modeKey,
|
|
5856
|
+
quality === "fast" ? "fulltext (fast profile)" : embeddingEnabled ? "hybrid" : "fulltext"
|
|
5857
|
+
);
|
|
5721
5858
|
let projectIds = null;
|
|
5722
5859
|
if (options.projectId) {
|
|
5723
5860
|
try {
|
|
@@ -5739,11 +5876,15 @@ async function searchObservations(options) {
|
|
|
5739
5876
|
}
|
|
5740
5877
|
const hasQuery = options.query && options.query.trim().length > 0;
|
|
5741
5878
|
const originalQuery = options.query;
|
|
5742
|
-
const tier = hasQuery ? classifyQueryTier(originalQuery) : "fast";
|
|
5879
|
+
const tier = quality === "fast" ? "fast" : hasQuery ? classifyQueryTier(originalQuery) : "fast";
|
|
5743
5880
|
mark(`tier=${tier}`);
|
|
5744
|
-
|
|
5881
|
+
if (quality === "thorough" && hasQuery) {
|
|
5882
|
+
const { initLLM: initLLM2, isLLMEnabled: isLLMEnabled2 } = await Promise.resolve().then(() => (init_provider2(), provider_exports2));
|
|
5883
|
+
if (!isLLMEnabled2()) initLLM2();
|
|
5884
|
+
}
|
|
5885
|
+
const expandedEmbeddingQuery = quality === "thorough" && tier === "heavy" ? await maybeExpandSearchQuery(options.query) : options.query;
|
|
5745
5886
|
mark("queryExpansion");
|
|
5746
|
-
const intentResult = hasQuery ? detectQueryIntent(originalQuery)
|
|
5887
|
+
const intentResult = quality === "fast" || !hasQuery ? null : detectQueryIntent(originalQuery);
|
|
5747
5888
|
const requestLimit = projectIds ? (options.limit ?? 20) * 3 : options.limit ?? 20;
|
|
5748
5889
|
const defaultBoost = {
|
|
5749
5890
|
title: 3,
|
|
@@ -5757,17 +5898,21 @@ async function searchObservations(options) {
|
|
|
5757
5898
|
let searchParams = {
|
|
5758
5899
|
term: originalQuery,
|
|
5759
5900
|
limit: requestLimit,
|
|
5760
|
-
|
|
5901
|
+
// Fast search never uses vectors, so returning them only adds work and
|
|
5902
|
+
// payload pressure to an explicitly latency-sensitive path.
|
|
5903
|
+
includeVectors: quality !== "fast",
|
|
5761
5904
|
...Object.keys(filters).length > 0 ? { where: filters } : {},
|
|
5762
5905
|
// Search specific fields (not tokens, accessCount, etc.)
|
|
5763
5906
|
properties: ["title", "entityName", "narrative", "facts", "concepts", "filesModified"],
|
|
5764
5907
|
// Field boosting: intent-aware or default
|
|
5765
5908
|
boost: fieldBoost,
|
|
5766
5909
|
// Fuzzy tolerance: allow 1-char typos for short queries, 2 for longer
|
|
5767
|
-
|
|
5910
|
+
// Fuzzy matching is useful for ordinary recall, but grows sharply with
|
|
5911
|
+
// corpus size. The fast profile deliberately uses exact lexical matching.
|
|
5912
|
+
...hasQuery && quality !== "fast" ? { tolerance: originalQuery.length > 6 ? 2 : 1 } : {}
|
|
5768
5913
|
};
|
|
5769
5914
|
let queryVector = null;
|
|
5770
|
-
if (embeddingEnabled && hasQuery && tier !== "fast") {
|
|
5915
|
+
if (quality !== "fast" && embeddingEnabled && hasQuery && tier !== "fast") {
|
|
5771
5916
|
try {
|
|
5772
5917
|
const provider2 = await getEmbeddingProvider();
|
|
5773
5918
|
if (provider2) {
|
|
@@ -5782,11 +5927,11 @@ async function searchObservations(options) {
|
|
|
5782
5927
|
);
|
|
5783
5928
|
} else {
|
|
5784
5929
|
const EMBEDDING_TIMEOUT_MS = 15e3;
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5930
|
+
queryVector = await withTimeout(
|
|
5931
|
+
provider2.embed(expandedEmbeddingQuery),
|
|
5932
|
+
EMBEDDING_TIMEOUT_MS,
|
|
5933
|
+
"Embedding"
|
|
5788
5934
|
);
|
|
5789
|
-
queryVector = await Promise.race([embedPromise, timeoutPromise]);
|
|
5790
5935
|
mark("embedding");
|
|
5791
5936
|
const cjkRatio = (originalQuery.match(/[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/g) || []).length / originalQuery.length;
|
|
5792
5937
|
const isCJKHeavy = cjkRatio > 0.3;
|
|
@@ -6027,7 +6172,7 @@ async function searchObservations(options) {
|
|
|
6027
6172
|
}
|
|
6028
6173
|
}
|
|
6029
6174
|
}
|
|
6030
|
-
const shouldRerank = tier === "heavy" && hasQuery && intermediate.length > 2 && (() => {
|
|
6175
|
+
const shouldRerank = quality === "thorough" && tier === "heavy" && hasQuery && intermediate.length > 2 && (() => {
|
|
6031
6176
|
const top = intermediate[0]?.score ?? 0;
|
|
6032
6177
|
const second = intermediate[1]?.score ?? 0;
|
|
6033
6178
|
return top > 0 && second / top > 0.7;
|
|
@@ -6051,11 +6196,11 @@ async function searchObservations(options) {
|
|
|
6051
6196
|
}));
|
|
6052
6197
|
const _parsedRerank = parseInt(process.env.MEMORIX_RERANK_TIMEOUT_MS || "", 10);
|
|
6053
6198
|
const RERANK_TIMEOUT_MS = Number.isFinite(_parsedRerank) && _parsedRerank > 0 ? _parsedRerank : 5e3;
|
|
6054
|
-
const
|
|
6055
|
-
|
|
6056
|
-
|
|
6199
|
+
const { reranked, usedLLM } = await withTimeout(
|
|
6200
|
+
rerankResults2(originalQuery, candidates),
|
|
6201
|
+
RERANK_TIMEOUT_MS,
|
|
6202
|
+
"LLM rerank"
|
|
6057
6203
|
);
|
|
6058
|
-
const { reranked, usedLLM } = await Promise.race([rerankPromise, timeoutPromise]);
|
|
6059
6204
|
mark(`rerank(usedLLM=${usedLLM})`);
|
|
6060
6205
|
if (usedLLM) {
|
|
6061
6206
|
lastSearchModeByProject.set(modeKey, (lastSearchModeByProject.get(modeKey) ?? "fulltext") + " + LLM rerank");
|
|
@@ -6247,6 +6392,7 @@ var init_orama_store = __esm({
|
|
|
6247
6392
|
init_project_affinity();
|
|
6248
6393
|
init_intent_detector();
|
|
6249
6394
|
init_query_expansion();
|
|
6395
|
+
init_timeout();
|
|
6250
6396
|
db = null;
|
|
6251
6397
|
dbInitPromise = null;
|
|
6252
6398
|
dbGeneration = 0;
|
|
@@ -13695,6 +13841,7 @@ var init_workflow_store = __esm({
|
|
|
13695
13841
|
var task_lens_exports = {};
|
|
13696
13842
|
__export(task_lens_exports, {
|
|
13697
13843
|
containsTaskKeyword: () => containsTaskKeyword,
|
|
13844
|
+
isContinuationTask: () => isContinuationTask,
|
|
13698
13845
|
lensPathCandidates: () => lensPathCandidates,
|
|
13699
13846
|
lensVerificationHints: () => lensVerificationHints,
|
|
13700
13847
|
rankLensPaths: () => rankLensPaths,
|
|
@@ -13760,6 +13907,12 @@ function resolveTaskLens(task) {
|
|
|
13760
13907
|
}
|
|
13761
13908
|
return best.score > 0 ? LENSES[best.id] : LENSES.general;
|
|
13762
13909
|
}
|
|
13910
|
+
function isContinuationTask(task) {
|
|
13911
|
+
const normalized = (task ?? "").trim().toLowerCase();
|
|
13912
|
+
return normalized.length > 0 && CONTINUATION_KEYWORDS.some(
|
|
13913
|
+
(keyword) => containsTaskKeyword(normalized, keyword)
|
|
13914
|
+
);
|
|
13915
|
+
}
|
|
13763
13916
|
function pathKindScore(path25, lens) {
|
|
13764
13917
|
const normalized = normalizePath2(path25).toLowerCase();
|
|
13765
13918
|
const name = normalized.split("/").pop() ?? normalized;
|
|
@@ -13903,7 +14056,7 @@ function shouldShowLensSource(source, lens, task) {
|
|
|
13903
14056
|
if (lens.id === "onboarding" || lens.id === "release" || lens.id === "docs") return score >= 40;
|
|
13904
14057
|
return score > 0;
|
|
13905
14058
|
}
|
|
13906
|
-
var STOP_WORDS, LENSES, KEYWORDS, LENS_PRIORITY;
|
|
14059
|
+
var STOP_WORDS, LENSES, KEYWORDS, CONTINUATION_KEYWORDS, LENS_PRIORITY;
|
|
13907
14060
|
var init_task_lens = __esm({
|
|
13908
14061
|
"src/codegraph/task-lens.ts"() {
|
|
13909
14062
|
"use strict";
|
|
@@ -14006,6 +14159,7 @@ var init_task_lens = __esm({
|
|
|
14006
14159
|
"fail",
|
|
14007
14160
|
"failing",
|
|
14008
14161
|
"fix",
|
|
14162
|
+
"fixing",
|
|
14009
14163
|
"issue",
|
|
14010
14164
|
"regression",
|
|
14011
14165
|
"repro",
|
|
@@ -14023,6 +14177,18 @@ var init_task_lens = __esm({
|
|
|
14023
14177
|
docs: ["doc", "docs", "readme", "\u6587\u6863", "\u8BF4\u660E"],
|
|
14024
14178
|
test: ["coverage", "fixture", "smoke", "spec", "test", "tests", "testing", "vitest", "\u6D4B\u8BD5"]
|
|
14025
14179
|
};
|
|
14180
|
+
CONTINUATION_KEYWORDS = [
|
|
14181
|
+
"continue",
|
|
14182
|
+
"resume",
|
|
14183
|
+
"pick up",
|
|
14184
|
+
"carry on",
|
|
14185
|
+
"previous session",
|
|
14186
|
+
"\u7EE7\u7EED",
|
|
14187
|
+
"\u63A5\u624B",
|
|
14188
|
+
"\u6062\u590D",
|
|
14189
|
+
"\u5EF6\u7EED",
|
|
14190
|
+
"\u4E0A\u6B21\u4F1A\u8BDD"
|
|
14191
|
+
];
|
|
14026
14192
|
LENS_PRIORITY = [
|
|
14027
14193
|
"bugfix",
|
|
14028
14194
|
"release",
|
|
@@ -14751,21 +14917,6 @@ async function loadWorkspaceForMaintenance(projectId, projectDir2, mode) {
|
|
|
14751
14917
|
]);
|
|
14752
14918
|
return versioned ?? local;
|
|
14753
14919
|
}
|
|
14754
|
-
function withTimeout(promise, ms, label) {
|
|
14755
|
-
return new Promise((resolve2, reject) => {
|
|
14756
|
-
const timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
|
|
14757
|
-
promise.then(
|
|
14758
|
-
(value) => {
|
|
14759
|
-
clearTimeout(timer);
|
|
14760
|
-
resolve2(value);
|
|
14761
|
-
},
|
|
14762
|
-
(error) => {
|
|
14763
|
-
clearTimeout(timer);
|
|
14764
|
-
reject(error);
|
|
14765
|
-
}
|
|
14766
|
-
);
|
|
14767
|
-
});
|
|
14768
|
-
}
|
|
14769
14920
|
async function runAutomaticConsolidation(projectId, projectDir2, options) {
|
|
14770
14921
|
const { isLLMEnabled: isLLMEnabled2 } = await Promise.resolve().then(() => (init_provider2(), provider_exports2));
|
|
14771
14922
|
if (!isLLMEnabled2()) {
|
|
@@ -15150,6 +15301,7 @@ var init_project_maintenance = __esm({
|
|
|
15150
15301
|
init_esm_shims();
|
|
15151
15302
|
init_isolated_maintenance();
|
|
15152
15303
|
init_lifecycle();
|
|
15304
|
+
init_timeout();
|
|
15153
15305
|
DEFAULT_VECTOR_BATCH_SIZE = 12;
|
|
15154
15306
|
DEFAULT_RETENTION_BATCH_SIZE = 100;
|
|
15155
15307
|
DEFAULT_CONSOLIDATION_BATCH_SIZE = 200;
|
|
@@ -15233,6 +15385,7 @@ __export(session_exports, {
|
|
|
15233
15385
|
endSession: () => endSession,
|
|
15234
15386
|
getActiveSession: () => getActiveSession,
|
|
15235
15387
|
getSessionContext: () => getSessionContext,
|
|
15388
|
+
getSessionResumeBrief: () => getSessionResumeBrief,
|
|
15236
15389
|
listSessions: () => listSessions,
|
|
15237
15390
|
scoreObservationForSessionContext: () => scoreObservationForSessionContext,
|
|
15238
15391
|
startSession: () => startSession
|
|
@@ -15300,6 +15453,62 @@ function isSystemSelfObservation(obs) {
|
|
|
15300
15453
|
const text = stringifyObservation(obs, false);
|
|
15301
15454
|
return SYSTEM_SELF_PATTERNS.some((pattern) => pattern.test(text));
|
|
15302
15455
|
}
|
|
15456
|
+
function readerForAlias(reader, aliases, observation) {
|
|
15457
|
+
if (!reader) return void 0;
|
|
15458
|
+
return reader.projectId && aliases.has(observation.projectId) ? { ...reader, projectId: observation.projectId } : reader;
|
|
15459
|
+
}
|
|
15460
|
+
function readableAliasObservations(observations2, aliases, reader) {
|
|
15461
|
+
return reader ? observations2.filter((observation) => canReadObservation(
|
|
15462
|
+
observation,
|
|
15463
|
+
readerForAlias(reader, aliases, observation)
|
|
15464
|
+
)) : observations2;
|
|
15465
|
+
}
|
|
15466
|
+
function isUsefulSessionSummary(summary) {
|
|
15467
|
+
return Boolean(summary) && !NOISE_PATTERNS2.some((pattern) => pattern.test(summary)) && !SYSTEM_SELF_PATTERNS.some((pattern) => pattern.test(summary));
|
|
15468
|
+
}
|
|
15469
|
+
function continuationTaskTokens(task) {
|
|
15470
|
+
return [...new Set(
|
|
15471
|
+
(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))
|
|
15472
|
+
)].slice(0, 8);
|
|
15473
|
+
}
|
|
15474
|
+
function continuationScore(observation, projectTokens, task) {
|
|
15475
|
+
let score = scoreObservationForSessionContext(observation, projectTokens);
|
|
15476
|
+
if (observation.type === "session-request" || observation.type === "what-changed") score += 1;
|
|
15477
|
+
const text = stringifyObservation(observation);
|
|
15478
|
+
const matches = continuationTaskTokens(task).filter((token) => text.includes(token)).length;
|
|
15479
|
+
score += Math.min(matches, 2) * 2;
|
|
15480
|
+
return score;
|
|
15481
|
+
}
|
|
15482
|
+
async function getSessionResumeBrief(projectId, task, reader) {
|
|
15483
|
+
const aliasSet = await resolveProjectIds(projectId);
|
|
15484
|
+
const [sessions, allObs] = await Promise.all([
|
|
15485
|
+
loadAliasSessions(aliasSet),
|
|
15486
|
+
loadAliasActiveObservations(aliasSet)
|
|
15487
|
+
]);
|
|
15488
|
+
const readableObs = readableAliasObservations(allObs, aliasSet, reader);
|
|
15489
|
+
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));
|
|
15490
|
+
const projectTokens = tokenizeProjectId(projectId);
|
|
15491
|
+
const memories = readableObs.filter((observation) => RESUME_TYPES.has(observation.type)).filter((observation) => classifyLayer(observation) === "L2").filter((observation) => !isNoiseObservation(observation) && !isSystemSelfObservation(observation)).map((observation) => ({
|
|
15492
|
+
observation,
|
|
15493
|
+
score: continuationScore(observation, projectTokens, task)
|
|
15494
|
+
})).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 }) => ({
|
|
15495
|
+
id: observation.id,
|
|
15496
|
+
title: sanitizeCredentials(observation.title),
|
|
15497
|
+
type: observation.type,
|
|
15498
|
+
...observation.facts?.[0] ? { detail: sanitizeCredentials(observation.facts[0]) } : observation.narrative ? { detail: sanitizeCredentials(observation.narrative) } : {}
|
|
15499
|
+
}));
|
|
15500
|
+
return {
|
|
15501
|
+
...previous?.summary ? {
|
|
15502
|
+
previousSession: {
|
|
15503
|
+
id: previous.id,
|
|
15504
|
+
...previous.agent ? { agent: previous.agent } : {},
|
|
15505
|
+
...previous.endedAt ? { endedAt: previous.endedAt } : {},
|
|
15506
|
+
summary: sanitizeCredentials(previous.summary)
|
|
15507
|
+
}
|
|
15508
|
+
} : {},
|
|
15509
|
+
memories
|
|
15510
|
+
};
|
|
15511
|
+
}
|
|
15303
15512
|
function scoreObservationForSessionContext(obs, projectTokens, now3 = Date.now()) {
|
|
15304
15513
|
let score = TYPE_WEIGHTS2[obs.type] ?? 1;
|
|
15305
15514
|
const text = stringifyObservation(obs);
|
|
@@ -15370,10 +15579,7 @@ async function getSessionContext(projectDir2, projectId, limit = 3, reader) {
|
|
|
15370
15579
|
loadAliasSessions(aliasSet),
|
|
15371
15580
|
loadAliasActiveObservations(aliasSet)
|
|
15372
15581
|
]);
|
|
15373
|
-
const readableObs =
|
|
15374
|
-
const observationReader = reader.projectId && aliasSet.has(observation.projectId) ? { ...reader, projectId: observation.projectId } : reader;
|
|
15375
|
-
return canReadObservation(observation, observationReader);
|
|
15376
|
-
}) : allObs;
|
|
15582
|
+
const readableObs = readableAliasObservations(allObs, aliasSet, reader);
|
|
15377
15583
|
const isNoisySummary = (summary) => {
|
|
15378
15584
|
if (!summary) return false;
|
|
15379
15585
|
return NOISE_PATTERNS2.some((p) => p.test(summary)) || SYSTEM_SELF_PATTERNS.some((p) => p.test(summary));
|
|
@@ -15524,7 +15730,7 @@ async function getActiveSession(projectDir2, projectId) {
|
|
|
15524
15730
|
const sessions = await loadAliasActiveSessions(aliasSet);
|
|
15525
15731
|
return sessions.sort((a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime())[0] ?? null;
|
|
15526
15732
|
}
|
|
15527
|
-
var PRIORITY_TYPES, TYPE_EMOJI, TYPE_WEIGHTS2, NOISE_PATTERNS2, COMMAND_TRACE_PATTERNS, SYSTEM_SELF_PATTERNS;
|
|
15733
|
+
var PRIORITY_TYPES, RESUME_TYPES, TYPE_EMOJI, TYPE_WEIGHTS2, NOISE_PATTERNS2, COMMAND_TRACE_PATTERNS, SYSTEM_SELF_PATTERNS;
|
|
15528
15734
|
var init_session = __esm({
|
|
15529
15735
|
"src/memory/session.ts"() {
|
|
15530
15736
|
"use strict";
|
|
@@ -15538,6 +15744,17 @@ var init_session = __esm({
|
|
|
15538
15744
|
init_secret_filter();
|
|
15539
15745
|
init_visibility();
|
|
15540
15746
|
PRIORITY_TYPES = /* @__PURE__ */ new Set(["gotcha", "decision", "problem-solution", "trade-off", "discovery"]);
|
|
15747
|
+
RESUME_TYPES = /* @__PURE__ */ new Set([
|
|
15748
|
+
"gotcha",
|
|
15749
|
+
"decision",
|
|
15750
|
+
"problem-solution",
|
|
15751
|
+
"trade-off",
|
|
15752
|
+
"discovery",
|
|
15753
|
+
"how-it-works",
|
|
15754
|
+
"what-changed",
|
|
15755
|
+
"reasoning",
|
|
15756
|
+
"session-request"
|
|
15757
|
+
]);
|
|
15541
15758
|
TYPE_EMOJI = {
|
|
15542
15759
|
"gotcha": "[DISCOVERY]",
|
|
15543
15760
|
"decision": "[WHY]",
|
|
@@ -15698,6 +15915,7 @@ function freshnessForMemory(status) {
|
|
|
15698
15915
|
return "unknown";
|
|
15699
15916
|
}
|
|
15700
15917
|
function receiptOmissionKind(raw) {
|
|
15918
|
+
if (raw.includes("continuation")) return "continuation";
|
|
15701
15919
|
if (raw.includes("task")) return "task";
|
|
15702
15920
|
if (raw.includes("fact")) return "current-fact";
|
|
15703
15921
|
if (raw.includes("state")) return "code-state";
|
|
@@ -15747,6 +15965,48 @@ function renderTaskWorksetPrompt(input) {
|
|
|
15747
15965
|
trust: "source-backed"
|
|
15748
15966
|
});
|
|
15749
15967
|
appendLine(lines, "Task lens: " + input.lens, maxTokens, omitted, "lens");
|
|
15968
|
+
const hasContinuation = Boolean(
|
|
15969
|
+
input.continuation?.previousSession || (input.continuation?.memories.length ?? 0) > 0
|
|
15970
|
+
);
|
|
15971
|
+
if (hasContinuation && input.continuation) {
|
|
15972
|
+
appendLine(lines, "", maxTokens, omitted, "continuation-heading");
|
|
15973
|
+
appendLine(lines, "Resume from prior work", maxTokens, omitted, "continuation-heading");
|
|
15974
|
+
if (input.continuation.previousSession) {
|
|
15975
|
+
const session = input.continuation.previousSession;
|
|
15976
|
+
const source = [session.agent, session.endedAt ? session.endedAt.slice(0, 10) : void 0].filter(Boolean).join(", ");
|
|
15977
|
+
appendLine(
|
|
15978
|
+
lines,
|
|
15979
|
+
"- Previous session" + (source ? ` (${source})` : "") + ": " + short(session.summary, 44),
|
|
15980
|
+
maxTokens,
|
|
15981
|
+
omitted,
|
|
15982
|
+
"continuation-session",
|
|
15983
|
+
selected,
|
|
15984
|
+
{
|
|
15985
|
+
kind: "continuation",
|
|
15986
|
+
id: "session:" + session.id,
|
|
15987
|
+
reason: "latest meaningful project session summary",
|
|
15988
|
+
trust: "historical"
|
|
15989
|
+
}
|
|
15990
|
+
);
|
|
15991
|
+
}
|
|
15992
|
+
for (const memory of input.continuation.memories.slice(0, 3)) {
|
|
15993
|
+
const detail = memory.detail ? ": " + short(memory.detail, CONTINUATION_DETAIL_TOKEN_BUDGET) : "";
|
|
15994
|
+
appendLine(
|
|
15995
|
+
lines,
|
|
15996
|
+
"- #" + memory.id + " " + memory.type + ": " + short(memory.title, 18) + detail,
|
|
15997
|
+
maxTokens,
|
|
15998
|
+
omitted,
|
|
15999
|
+
"continuation-memory",
|
|
16000
|
+
selected,
|
|
16001
|
+
{
|
|
16002
|
+
kind: "continuation",
|
|
16003
|
+
id: "memory:" + memory.id,
|
|
16004
|
+
reason: "durable prior-work memory",
|
|
16005
|
+
trust: "historical"
|
|
16006
|
+
}
|
|
16007
|
+
);
|
|
16008
|
+
}
|
|
16009
|
+
}
|
|
15750
16010
|
if (input.cautions.length > 0 || input.cautionMemory.length > 0) {
|
|
15751
16011
|
appendLine(lines, "", maxTokens, omitted, "caution-heading");
|
|
15752
16012
|
appendLine(lines, "Cautions", maxTokens, omitted, "caution-heading");
|
|
@@ -16015,11 +16275,25 @@ async function buildTaskWorkset(input) {
|
|
|
16015
16275
|
...input.verificationHints
|
|
16016
16276
|
]).slice(0, 4);
|
|
16017
16277
|
const normalizedCautions = unique(cautions.map((caution) => caution.kind)).map((kind) => cautions.find((caution) => caution.kind === kind)).slice(0, 6);
|
|
16278
|
+
const continuation = input.continuation && (input.continuation.previousSession || input.continuation.memories.length > 0) ? {
|
|
16279
|
+
...input.continuation.previousSession ? {
|
|
16280
|
+
previousSession: {
|
|
16281
|
+
...input.continuation.previousSession,
|
|
16282
|
+
summary: short(input.continuation.previousSession.summary, 52)
|
|
16283
|
+
}
|
|
16284
|
+
} : {},
|
|
16285
|
+
memories: input.continuation.memories.slice(0, 3).map((memory) => ({
|
|
16286
|
+
...memory,
|
|
16287
|
+
title: short(memory.title, 20),
|
|
16288
|
+
...memory.detail ? { detail: short(memory.detail, CONTINUATION_DETAIL_TOKEN_BUDGET) } : {}
|
|
16289
|
+
}))
|
|
16290
|
+
} : void 0;
|
|
16018
16291
|
const base = {
|
|
16019
16292
|
version: "1.2",
|
|
16020
16293
|
task,
|
|
16021
16294
|
lens: input.lens,
|
|
16022
16295
|
currentFacts: input.currentFacts?.map((fact) => fact.startsWith("Historical note:") ? short(fact, 48) : short(fact, 28)).slice(0, 4) ?? [],
|
|
16296
|
+
...continuation ? { continuation } : {},
|
|
16023
16297
|
...input.codeState ? { codeState: short(input.codeState, 28) } : {},
|
|
16024
16298
|
startHere: unique(input.startHere).slice(0, 5),
|
|
16025
16299
|
...input.semanticCode ? { semanticCode: input.semanticCode } : {},
|
|
@@ -16044,7 +16318,7 @@ async function buildTaskWorkset(input) {
|
|
|
16044
16318
|
budget: { maxTokens }
|
|
16045
16319
|
});
|
|
16046
16320
|
const receipt = {
|
|
16047
|
-
version: "1.2.
|
|
16321
|
+
version: "1.2.4",
|
|
16048
16322
|
target: input.deliveryTarget ?? "project-context",
|
|
16049
16323
|
elapsedMs: Math.max(0, Date.now() - startedAt),
|
|
16050
16324
|
budget: {
|
|
@@ -16066,6 +16340,7 @@ async function buildTaskWorkset(input) {
|
|
|
16066
16340
|
prompt: rendered.prompt
|
|
16067
16341
|
};
|
|
16068
16342
|
}
|
|
16343
|
+
var CONTINUATION_DETAIL_TOKEN_BUDGET;
|
|
16069
16344
|
var init_workset = __esm({
|
|
16070
16345
|
"src/knowledge/workset.ts"() {
|
|
16071
16346
|
"use strict";
|
|
@@ -16078,6 +16353,7 @@ var init_workset = __esm({
|
|
|
16078
16353
|
init_workspace();
|
|
16079
16354
|
init_workflow_store();
|
|
16080
16355
|
init_workflows();
|
|
16356
|
+
CONTINUATION_DETAIL_TOKEN_BUDGET = 20;
|
|
16081
16357
|
}
|
|
16082
16358
|
});
|
|
16083
16359
|
|
|
@@ -16870,6 +17146,7 @@ async function buildAutoProjectContext(input) {
|
|
|
16870
17146
|
const now3 = input.now ?? /* @__PURE__ */ new Date();
|
|
16871
17147
|
const task = input.task?.trim();
|
|
16872
17148
|
const lens = resolveTaskLens(task);
|
|
17149
|
+
const continuationRequested = input.continuation === "always" || input.continuation !== "never" && isContinuationTask(task);
|
|
16873
17150
|
const codegraphConfig = getResolvedConfig({ projectRoot: input.project.rootPath }).codegraph;
|
|
16874
17151
|
const exclude = input.exclude ?? codegraphConfig.excludePatterns;
|
|
16875
17152
|
const maxFileBytes = input.maxFileBytes ?? codegraphConfig.maxFileBytes;
|
|
@@ -17006,6 +17283,11 @@ async function buildAutoProjectContext(input) {
|
|
|
17006
17283
|
if (externalCaution) {
|
|
17007
17284
|
runtimeCautions.push({ kind: "external-codegraph-fallback", message: externalCaution });
|
|
17008
17285
|
}
|
|
17286
|
+
let continuation;
|
|
17287
|
+
if (continuationRequested) {
|
|
17288
|
+
await initSessionStore(input.dataDir);
|
|
17289
|
+
continuation = await getSessionResumeBrief(input.project.id, task, input.reader);
|
|
17290
|
+
}
|
|
17009
17291
|
const workset = await buildTaskWorkset({
|
|
17010
17292
|
projectId: input.project.id,
|
|
17011
17293
|
dataDir: input.dataDir,
|
|
@@ -17015,6 +17297,7 @@ async function buildAutoProjectContext(input) {
|
|
|
17015
17297
|
...externalOutline ? { semanticCode: externalOutline } : {},
|
|
17016
17298
|
providerQuality: providerQuality2,
|
|
17017
17299
|
currentFacts: worksetFactLines(currentFacts),
|
|
17300
|
+
...continuation ? { continuation } : {},
|
|
17018
17301
|
codeState: codeStateLine(overview),
|
|
17019
17302
|
reliableMemory: sourceSets.reliableSources.slice(0, lens.sourceLimit).map((source) => ({
|
|
17020
17303
|
id: source.observationId,
|
|
@@ -17059,12 +17342,19 @@ async function buildAutoProjectContext(input) {
|
|
|
17059
17342
|
explain,
|
|
17060
17343
|
refresh,
|
|
17061
17344
|
providerQuality: providerQuality2,
|
|
17345
|
+
...continuationRequested && workset.continuation ? { continuation: workset.continuation } : {},
|
|
17062
17346
|
workset
|
|
17063
17347
|
};
|
|
17064
17348
|
}
|
|
17065
17349
|
function formatLanguages(overview) {
|
|
17066
17350
|
return overview.code.languages.length > 0 ? overview.code.languages.map((item) => `${item.language} ${item.files}`).join(", ") : "none indexed yet";
|
|
17067
17351
|
}
|
|
17352
|
+
function compactContinuationText(text, budget) {
|
|
17353
|
+
return truncateToTokenBudget(
|
|
17354
|
+
sanitizeCredentials(text).replace(/\s+/g, " ").trim(),
|
|
17355
|
+
budget
|
|
17356
|
+
);
|
|
17357
|
+
}
|
|
17068
17358
|
function codeStateLine(overview) {
|
|
17069
17359
|
const snapshot = overview.code.latestSnapshot;
|
|
17070
17360
|
if (!snapshot) return "- Code state: no completed snapshot yet";
|
|
@@ -17204,6 +17494,23 @@ function formatAutoProjectContextSummary(context) {
|
|
|
17204
17494
|
"Reliable memory",
|
|
17205
17495
|
reliableSources.length > 0 ? `- ${reliableSources.length} current code-bound memory link(s)` : "- none yet"
|
|
17206
17496
|
);
|
|
17497
|
+
const continuation = context.workset.continuation;
|
|
17498
|
+
if (continuation?.previousSession || continuation?.memories.length) {
|
|
17499
|
+
lines.push("", "Resume from prior work");
|
|
17500
|
+
if (continuation.previousSession) {
|
|
17501
|
+
const session = continuation.previousSession;
|
|
17502
|
+
const source = [session.agent, session.endedAt ? session.endedAt.slice(0, 10) : void 0].filter(Boolean).join(", ");
|
|
17503
|
+
lines.push(
|
|
17504
|
+
"- Previous session" + (source ? ` (${source})` : "") + ": " + compactContinuationText(session.summary, 44)
|
|
17505
|
+
);
|
|
17506
|
+
}
|
|
17507
|
+
for (const memory of continuation.memories.slice(0, 3)) {
|
|
17508
|
+
const detail = memory.detail ? ": " + compactContinuationText(memory.detail, 20) : "";
|
|
17509
|
+
lines.push(
|
|
17510
|
+
"- #" + memory.id + " " + memory.type + ": " + compactContinuationText(memory.title, 18) + detail
|
|
17511
|
+
);
|
|
17512
|
+
}
|
|
17513
|
+
}
|
|
17207
17514
|
return lines.join("\n");
|
|
17208
17515
|
}
|
|
17209
17516
|
function formatAutoProjectContextPrompt(context) {
|
|
@@ -17274,8 +17581,10 @@ var init_auto_context = __esm({
|
|
|
17274
17581
|
"src/codegraph/auto-context.ts"() {
|
|
17275
17582
|
"use strict";
|
|
17276
17583
|
init_esm_shims();
|
|
17584
|
+
init_token_budget();
|
|
17277
17585
|
init_resolved_config();
|
|
17278
17586
|
init_workset();
|
|
17587
|
+
init_secret_filter();
|
|
17279
17588
|
init_binder();
|
|
17280
17589
|
init_current_facts();
|
|
17281
17590
|
init_lite_provider();
|
|
@@ -17283,6 +17592,8 @@ var init_auto_context = __esm({
|
|
|
17283
17592
|
init_project_context();
|
|
17284
17593
|
init_store();
|
|
17285
17594
|
init_admission();
|
|
17595
|
+
init_session();
|
|
17596
|
+
init_session_store();
|
|
17286
17597
|
init_task_lens();
|
|
17287
17598
|
DEFAULT_MAX_AGE_MS = 10 * 60 * 1e3;
|
|
17288
17599
|
}
|
|
@@ -21477,7 +21788,8 @@ var init_official_skills = __esm({
|
|
|
21477
21788
|
"",
|
|
21478
21789
|
"| Situation | Prefer | CLI fallback |",
|
|
21479
21790
|
"|---|---|---|",
|
|
21480
|
-
'| Starting
|
|
21791
|
+
'| Starting a coding task and needing the best task-lensed project map | `memorix_project_context` with the user\'s task | `memorix context "<task>" --json` |',
|
|
21792
|
+
'| Continuing prior project work | `memorix_project_context` with the user\'s task | `memorix resume "<task>" --json` |',
|
|
21481
21793
|
'| Need structured refs/freshness for code-bound memories | `memorix_context_pack` | `memorix codegraph context-pack --task "<topic>"` |',
|
|
21482
21794
|
'| Explicit memory graph question | `memorix_graph_context` | `memorix memory graph-context --query "<topic>"` |',
|
|
21483
21795
|
'| Specific past decision, bug, file, or change | `memorix_search` | `memorix memory search --query "<topic>"` |',
|
|
@@ -21490,9 +21802,10 @@ var init_official_skills = __esm({
|
|
|
21490
21802
|
"",
|
|
21491
21803
|
"- 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.",
|
|
21492
21804
|
"- 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.",
|
|
21493
|
-
'-
|
|
21494
|
-
"-
|
|
21495
|
-
"-
|
|
21805
|
+
'- 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.',
|
|
21806
|
+
"- 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.",
|
|
21807
|
+
"- 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.",
|
|
21808
|
+
"- 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.",
|
|
21496
21809
|
"- 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.",
|
|
21497
21810
|
"- Skip memory lookup for greetings, tiny one-off edits, or questions fully answered by the current file.",
|
|
21498
21811
|
"- If a fresh project has no memories, proceed normally and do not repeat the same empty search in the same turn.",
|
|
@@ -22713,17 +23026,18 @@ alwaysApply: true
|
|
|
22713
23026
|
"",
|
|
22714
23027
|
"## Start with Memory Autopilot",
|
|
22715
23028
|
"",
|
|
22716
|
-
`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.`,
|
|
23029
|
+
`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.`,
|
|
22717
23030
|
"",
|
|
22718
|
-
'If the MCP tool is not visible yet but the client supports tool discovery or dynamic loading, search/select `memorix_project_context` first.
|
|
23031
|
+
'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.',
|
|
22719
23032
|
"",
|
|
22720
|
-
"Use `memorix_context_pack` when
|
|
23033
|
+
"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.",
|
|
23034
|
+
"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.",
|
|
22721
23035
|
"",
|
|
22722
23036
|
"## When to search memory",
|
|
22723
23037
|
"",
|
|
22724
23038
|
"Use `memorix_graph_context` for explicit memory graph questions or broad graph overview after the autopilot brief is not enough.",
|
|
22725
23039
|
"",
|
|
22726
|
-
`Use \`memorix_search\` when prior ${contextNoun} context would help \u2014 for example:`,
|
|
23040
|
+
`Use \`memorix_search\` when prior ${contextNoun} context would help and the Autopilot brief did not already answer the question \u2014 for example:`,
|
|
22727
23041
|
"- The user asks about a past decision, bug, or change",
|
|
22728
23042
|
"- You need to understand why something was designed a certain way",
|
|
22729
23043
|
"- You're continuing work that started in a previous session",
|
|
@@ -26535,18 +26849,10 @@ function parseFormationTimeoutMs(raw) {
|
|
|
26535
26849
|
}
|
|
26536
26850
|
|
|
26537
26851
|
// src/server.ts
|
|
26852
|
+
init_timeout();
|
|
26538
26853
|
var FORMATION_TIMEOUT_MS = parseFormationTimeoutMs(process.env.MEMORIX_FORMATION_TIMEOUT_MS);
|
|
26539
26854
|
var COMPACT_ON_WRITE_TIMEOUT_MS = 12e3;
|
|
26540
26855
|
var COMPRESSION_TIMEOUT_MS = 5e3;
|
|
26541
|
-
function withTimeout2(promise, ms, label) {
|
|
26542
|
-
let timer;
|
|
26543
|
-
return Promise.race([
|
|
26544
|
-
promise,
|
|
26545
|
-
new Promise((_, reject) => {
|
|
26546
|
-
timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
|
|
26547
|
-
})
|
|
26548
|
-
]).finally(() => clearTimeout(timer));
|
|
26549
|
-
}
|
|
26550
26856
|
function formatFormationStageDurations(stageDurationsMs) {
|
|
26551
26857
|
const orderedStages = ["extract", "resolve", "evaluate"];
|
|
26552
26858
|
const parts = orderedStages.filter((stage) => stageDurationsMs[stage] !== void 0).map((stage) => `${stage}=${stageDurationsMs[stage]}ms`);
|
|
@@ -26650,6 +26956,8 @@ var BOOTSTRAP_SAFE_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
|
26650
26956
|
"memorix_graph_context",
|
|
26651
26957
|
"memorix_context_pack"
|
|
26652
26958
|
]);
|
|
26959
|
+
var AUTOPILOT_RETRIEVAL_BOUNDARY_TTL_MS = 2 * 60 * 1e3;
|
|
26960
|
+
var READ_ONLY_TASK_PATTERN = /\b(?:do not|don't|never)\s+(?:modify|edit|change|write)\b|\bread[- ]only\b|(?:不要|不准|勿|禁止).{0,6}(?:修改|编辑|写入)|只读/i;
|
|
26653
26961
|
function shouldAwaitProjectRuntime(toolName) {
|
|
26654
26962
|
return !BOOTSTRAP_SAFE_TOOL_NAMES.has(toolName);
|
|
26655
26963
|
}
|
|
@@ -26920,7 +27228,7 @@ The path should point to a directory containing a .git folder.`
|
|
|
26920
27228
|
};
|
|
26921
27229
|
const server = existingServer ?? new McpServer({
|
|
26922
27230
|
name: "memorix",
|
|
26923
|
-
version: true ? "1.2.
|
|
27231
|
+
version: true ? "1.2.5" : "1.0.1"
|
|
26924
27232
|
});
|
|
26925
27233
|
const originalRegisterTool = server.registerTool.bind(server);
|
|
26926
27234
|
server.registerTool = ((name, ...args) => {
|
|
@@ -26955,11 +27263,52 @@ The path should point to a directory containing a .git folder.`
|
|
|
26955
27263
|
isTeamMember
|
|
26956
27264
|
};
|
|
26957
27265
|
};
|
|
27266
|
+
let autopilotRetrievalBoundary = null;
|
|
27267
|
+
const getActiveAutopilotRetrievalBoundary = () => {
|
|
27268
|
+
const boundary = autopilotRetrievalBoundary;
|
|
27269
|
+
if (!boundary) return null;
|
|
27270
|
+
if (boundary.projectId === project.id && Date.now() - boundary.issuedAt <= AUTOPILOT_RETRIEVAL_BOUNDARY_TTL_MS) {
|
|
27271
|
+
return boundary;
|
|
27272
|
+
}
|
|
27273
|
+
autopilotRetrievalBoundary = null;
|
|
27274
|
+
return null;
|
|
27275
|
+
};
|
|
27276
|
+
const requireExplicitAutopilotExpansion = (toolName, purpose) => {
|
|
27277
|
+
if (!getActiveAutopilotRetrievalBoundary() || purpose?.trim()) return null;
|
|
27278
|
+
return {
|
|
27279
|
+
content: [{
|
|
27280
|
+
type: "text",
|
|
27281
|
+
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."
|
|
27282
|
+
}]
|
|
27283
|
+
};
|
|
27284
|
+
};
|
|
27285
|
+
const blockCoveredAutopilotEvidence = (toolName, observationIds, force) => {
|
|
27286
|
+
const boundary = getActiveAutopilotRetrievalBoundary();
|
|
27287
|
+
if (!boundary || force || observationIds.length === 0 || !observationIds.every((id) => boundary.coveredObservationIds.has(id))) {
|
|
27288
|
+
return null;
|
|
27289
|
+
}
|
|
27290
|
+
return {
|
|
27291
|
+
content: [{
|
|
27292
|
+
type: "text",
|
|
27293
|
+
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."
|
|
27294
|
+
}]
|
|
27295
|
+
};
|
|
27296
|
+
};
|
|
27297
|
+
const blockReadOnlyAutopilotWrite = (overrideReadOnly) => {
|
|
27298
|
+
const boundary = getActiveAutopilotRetrievalBoundary();
|
|
27299
|
+
if (!boundary?.readOnly || overrideReadOnly) return null;
|
|
27300
|
+
return {
|
|
27301
|
+
content: [{
|
|
27302
|
+
type: "text",
|
|
27303
|
+
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`."
|
|
27304
|
+
}]
|
|
27305
|
+
};
|
|
27306
|
+
};
|
|
26958
27307
|
server.registerTool(
|
|
26959
27308
|
"memorix_store",
|
|
26960
27309
|
{
|
|
26961
27310
|
title: "Store Memory",
|
|
26962
|
-
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.",
|
|
27311
|
+
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.",
|
|
26963
27312
|
inputSchema: {
|
|
26964
27313
|
entityName: z2.string().describe('The entity this observation belongs to (e.g., "auth-module", "port-config")'),
|
|
26965
27314
|
type: z2.enum(OBSERVATION_TYPES).describe("Observation type for classification"),
|
|
@@ -26980,12 +27329,17 @@ The path should point to a directory containing a .git folder.`
|
|
|
26980
27329
|
relatedEntities: z2.array(z2.string()).optional().describe("Other entity names this memory cross-references"),
|
|
26981
27330
|
visibility: z2.enum(["personal", "project", "team"]).optional().describe(
|
|
26982
27331
|
"Retrieval scope. Project is the normal shared default; personal/team require memorix_session_start with joinTeam=true."
|
|
27332
|
+
),
|
|
27333
|
+
overrideReadOnly: z2.boolean().optional().describe(
|
|
27334
|
+
"Use only when the user explicitly asks to save memory during a read-only or no-modification task."
|
|
26983
27335
|
)
|
|
26984
27336
|
}
|
|
26985
27337
|
},
|
|
26986
|
-
async ({ entityName: rawEntityName, type: rawType, title: rawTitle, narrative, facts, filesModified, concepts, topicKey, progress, relatedCommits, relatedEntities, visibility }) => {
|
|
27338
|
+
async ({ entityName: rawEntityName, type: rawType, title: rawTitle, narrative, facts, filesModified, concepts, topicKey, progress, relatedCommits, relatedEntities, visibility, overrideReadOnly }) => {
|
|
26987
27339
|
const unresolved = requireResolvedProject("store memory in the current project");
|
|
26988
27340
|
if (unresolved) return unresolved;
|
|
27341
|
+
const readOnlyBoundary = blockReadOnlyAutopilotWrite(overrideReadOnly);
|
|
27342
|
+
if (readOnlyBoundary) return readOnlyBoundary;
|
|
26989
27343
|
const requestedVisibility = visibility ?? "project";
|
|
26990
27344
|
const reader = getObservationReader();
|
|
26991
27345
|
if (requestedVisibility !== "project" && !currentAgentId) {
|
|
@@ -27071,7 +27425,7 @@ The path should point to a directory containing a .git folder.`
|
|
|
27071
27425
|
getEntityNames: () => graphManager.getEntityNames(),
|
|
27072
27426
|
onStageEvent: onFormationStageEvent
|
|
27073
27427
|
};
|
|
27074
|
-
formationResult = await
|
|
27428
|
+
formationResult = await withTimeout(
|
|
27075
27429
|
runFormation({
|
|
27076
27430
|
entityName,
|
|
27077
27431
|
type,
|
|
@@ -27192,7 +27546,7 @@ ${modeIcon} Formation[active]: ${formationResult.evaluation.category} (${formati
|
|
|
27192
27546
|
facts: d.facts,
|
|
27193
27547
|
score: similarEntries[i]?.score ?? 0
|
|
27194
27548
|
}));
|
|
27195
|
-
const decision = await
|
|
27549
|
+
const decision = await withTimeout(
|
|
27196
27550
|
compactOnWrite(
|
|
27197
27551
|
{ title, narrative, facts: safeFacts ?? [] },
|
|
27198
27552
|
existingMemories
|
|
@@ -27288,7 +27642,7 @@ Mode: ${decision.usedLLM ? "LLM" : "heuristic"}`
|
|
|
27288
27642
|
let compressionNote = "";
|
|
27289
27643
|
try {
|
|
27290
27644
|
const { compressNarrative: compressNarrative2 } = await Promise.resolve().then(() => (init_quality(), quality_exports));
|
|
27291
|
-
const { compressed, saved, usedLLM } = await
|
|
27645
|
+
const { compressed, saved, usedLLM } = await withTimeout(
|
|
27292
27646
|
compressNarrative2(narrative, safeFacts, type),
|
|
27293
27647
|
COMPRESSION_TIMEOUT_MS,
|
|
27294
27648
|
"Narrative compression"
|
|
@@ -27410,7 +27764,7 @@ Auto-enriched: ${enrichmentParts.join(", ")}` : "";
|
|
|
27410
27764
|
},
|
|
27411
27765
|
getEntityNames: () => graphManager.getEntityNames()
|
|
27412
27766
|
};
|
|
27413
|
-
const formed = await
|
|
27767
|
+
const formed = await withTimeout(
|
|
27414
27768
|
runFormation({ entityName, type, title, narrative, facts: safeFacts, projectId: project.id, source: "explicit", topicKey }, formationConfig),
|
|
27415
27769
|
FORMATION_TIMEOUT_MS,
|
|
27416
27770
|
"Shadow formation"
|
|
@@ -27484,7 +27838,7 @@ Use this as the \`topicKey\` parameter in \`memorix_store\` to enable upsert beh
|
|
|
27484
27838
|
"memorix_search",
|
|
27485
27839
|
{
|
|
27486
27840
|
title: "Search Memory",
|
|
27487
|
-
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.",
|
|
27841
|
+
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.",
|
|
27488
27842
|
inputSchema: {
|
|
27489
27843
|
query: z2.string().describe("Search query (natural language or keywords)"),
|
|
27490
27844
|
limit: z2.number().optional().describe("Max results (default: 20)"),
|
|
@@ -27500,14 +27854,27 @@ Use this as the \`topicKey\` parameter in \`memorix_store\` to enable upsert beh
|
|
|
27500
27854
|
),
|
|
27501
27855
|
source: z2.enum(["agent", "git", "manual"]).optional().describe(
|
|
27502
27856
|
'Filter by memory source. "git" returns only commit-derived ground truth memories. Omit for all sources.'
|
|
27857
|
+
),
|
|
27858
|
+
quality: z2.enum(["fast", "balanced", "thorough"]).optional().default("balanced").describe(
|
|
27859
|
+
"Retrieval profile: fast stays local, balanced uses configured embeddings, thorough explicitly permits optional LLM refinement."
|
|
27860
|
+
),
|
|
27861
|
+
purpose: z2.string().optional().describe(
|
|
27862
|
+
"Why this must expand beyond the latest Autopilot brief. Name the missing fact or the user's explicit request."
|
|
27863
|
+
),
|
|
27864
|
+
force: z2.boolean().optional().describe(
|
|
27865
|
+
"Use only when the user explicitly asks to read a record already represented in the latest Autopilot brief."
|
|
27503
27866
|
)
|
|
27504
27867
|
}
|
|
27505
27868
|
},
|
|
27506
|
-
async ({ query, limit, type, maxTokens, scope, since, until, status, source }) => {
|
|
27869
|
+
async ({ query, limit, type, maxTokens, scope, since, until, status, source, quality, purpose, force }) => {
|
|
27507
27870
|
if (scope !== "global") {
|
|
27508
27871
|
const unresolved = requireResolvedProject("search the current project");
|
|
27509
27872
|
if (unresolved) return unresolved;
|
|
27510
27873
|
}
|
|
27874
|
+
if (scope !== "global") {
|
|
27875
|
+
const boundary = requireExplicitAutopilotExpansion("memorix_search", purpose);
|
|
27876
|
+
if (boundary) return boundary;
|
|
27877
|
+
}
|
|
27511
27878
|
return withFreshIndex(async () => {
|
|
27512
27879
|
const safeLimit = limit != null ? coerceNumber(limit, 20) : void 0;
|
|
27513
27880
|
const safeMaxTokens = maxTokens != null ? coerceNumber(maxTokens, 0) : void 0;
|
|
@@ -27524,16 +27891,14 @@ Use this as the \`topicKey\` parameter in \`memorix_store\` to enable upsert beh
|
|
|
27524
27891
|
projectId: scope === "global" ? void 0 : project.id,
|
|
27525
27892
|
status: status ?? "active",
|
|
27526
27893
|
source,
|
|
27894
|
+
quality,
|
|
27527
27895
|
reader: getObservationReader(scope === "global" ? "global" : "project")
|
|
27528
27896
|
});
|
|
27529
|
-
const timeoutPromise = new Promise(
|
|
27530
|
-
(_, reject) => setTimeout(() => reject(new Error(`Search timeout after ${TIMEOUT_MS}ms`)), TIMEOUT_MS)
|
|
27531
|
-
);
|
|
27532
27897
|
let result;
|
|
27533
27898
|
try {
|
|
27534
|
-
result = await
|
|
27899
|
+
result = await withTimeout(searchPromise, TIMEOUT_MS, "Search");
|
|
27535
27900
|
} catch (error) {
|
|
27536
|
-
if (error instanceof Error && error.message
|
|
27901
|
+
if (error instanceof Error && /\btimeout\b|timed out/i.test(error.message)) {
|
|
27537
27902
|
return {
|
|
27538
27903
|
content: [
|
|
27539
27904
|
{
|
|
@@ -27546,6 +27911,11 @@ Use this as the \`topicKey\` parameter in \`memorix_store\` to enable upsert beh
|
|
|
27546
27911
|
}
|
|
27547
27912
|
throw error;
|
|
27548
27913
|
}
|
|
27914
|
+
const activeBoundary = getActiveAutopilotRetrievalBoundary();
|
|
27915
|
+
if (scope !== "global" && activeBoundary && !force && result.entries.length > 0 && result.entries.every((entry) => activeBoundary.coveredObservationIds.has(entry.id))) {
|
|
27916
|
+
const duplicate = blockCoveredAutopilotEvidence("memorix_search", result.entries.map((entry) => entry.id), force);
|
|
27917
|
+
if (duplicate) return duplicate;
|
|
27918
|
+
}
|
|
27549
27919
|
let text = result.formatted;
|
|
27550
27920
|
try {
|
|
27551
27921
|
const { getLastSearchMode: getLastSearchMode2 } = await Promise.resolve().then(() => (init_orama_store(), orama_store_exports));
|
|
@@ -27647,6 +28017,7 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
|
|
|
27647
28017
|
observations: observations2,
|
|
27648
28018
|
task,
|
|
27649
28019
|
refresh: refresh ?? "auto",
|
|
28020
|
+
reader: getObservationReader(),
|
|
27650
28021
|
enqueueRefresh: () => {
|
|
27651
28022
|
enqueueCodegraphRefresh2({
|
|
27652
28023
|
dataDir: projectDir2,
|
|
@@ -27658,6 +28029,16 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
|
|
|
27658
28029
|
}
|
|
27659
28030
|
});
|
|
27660
28031
|
const text = format === "json" ? JSON.stringify({ ...context, brief: buildAutoProjectBrief2(context) }, null, 2) : format === "summary" ? formatAutoProjectContextSummary2(context) : formatAutoProjectContextPrompt2(context);
|
|
28032
|
+
autopilotRetrievalBoundary = {
|
|
28033
|
+
projectId: project.id,
|
|
28034
|
+
issuedAt: Date.now(),
|
|
28035
|
+
coveredObservationIds: /* @__PURE__ */ new Set([
|
|
28036
|
+
...context.workset.continuation?.memories.map((memory) => memory.id) ?? [],
|
|
28037
|
+
...context.workset.reliableMemory.map((memory) => memory.id),
|
|
28038
|
+
...context.workset.cautionMemory.map((memory) => memory.id)
|
|
28039
|
+
]),
|
|
28040
|
+
readOnly: READ_ONLY_TASK_PATTERN.test(task ?? "")
|
|
28041
|
+
};
|
|
27661
28042
|
return {
|
|
27662
28043
|
content: [{ type: "text", text }]
|
|
27663
28044
|
};
|
|
@@ -27697,18 +28078,23 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
|
|
|
27697
28078
|
"memorix_context_pack",
|
|
27698
28079
|
{
|
|
27699
28080
|
title: "Context Pack",
|
|
27700
|
-
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.",
|
|
28081
|
+
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.",
|
|
27701
28082
|
inputSchema: {
|
|
27702
28083
|
task: z2.string().describe("Current coding task or question"),
|
|
27703
28084
|
limit: z2.preprocess(
|
|
27704
28085
|
(value) => typeof value === "string" && value.trim() !== "" ? Number(value) : value,
|
|
27705
28086
|
z2.number().int().positive().max(100)
|
|
27706
|
-
).optional().describe("Max active memories to inspect before code-ref filtering (default: 20)")
|
|
28087
|
+
).optional().describe("Max active memories to inspect before code-ref filtering (default: 20)"),
|
|
28088
|
+
purpose: z2.string().optional().describe(
|
|
28089
|
+
"Why this must expand beyond the latest Autopilot brief. Name the missing fact or the user's explicit request."
|
|
28090
|
+
)
|
|
27707
28091
|
}
|
|
27708
28092
|
},
|
|
27709
|
-
async ({ task, limit }) => {
|
|
28093
|
+
async ({ task, limit, purpose }) => {
|
|
27710
28094
|
const unresolved = requireResolvedProject("build a context pack for the current project");
|
|
27711
28095
|
if (unresolved) return unresolved;
|
|
28096
|
+
const boundary = requireExplicitAutopilotExpansion("memorix_context_pack", purpose);
|
|
28097
|
+
if (boundary) return boundary;
|
|
27712
28098
|
const [
|
|
27713
28099
|
{ CodeGraphStore: CodeGraphStore2 },
|
|
27714
28100
|
{ assembleContextPackForTask: assembleContextPackForTask2, attachTaskWorkset: attachTaskWorkset2, buildContextPackPrompt: buildContextPackPrompt2 },
|
|
@@ -28453,7 +28839,7 @@ ${actions.join("\n")}`
|
|
|
28453
28839
|
"memorix_detail",
|
|
28454
28840
|
{
|
|
28455
28841
|
title: "Memory Details",
|
|
28456
|
-
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.',
|
|
28842
|
+
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.',
|
|
28457
28843
|
inputSchema: {
|
|
28458
28844
|
ids: z2.array(z2.number()).optional().describe("Observation IDs to fetch (legacy, from memorix_search results)"),
|
|
28459
28845
|
refs: z2.array(
|
|
@@ -28462,16 +28848,36 @@ ${actions.join("\n")}`
|
|
|
28462
28848
|
projectId: z2.string().optional().describe("Project ID for global-search disambiguation")
|
|
28463
28849
|
})
|
|
28464
28850
|
).optional().describe("Explicit observation refs. Prefer this for global search results."),
|
|
28465
|
-
typedRefs: z2.array(z2.string()).optional().describe('Typed memory refs from search results, e.g. "obs:42", "skill:3", "obs:42@org/proj"')
|
|
28851
|
+
typedRefs: z2.array(z2.string()).optional().describe('Typed memory refs from search results, e.g. "obs:42", "skill:3", "obs:42@org/proj"'),
|
|
28852
|
+
purpose: z2.string().optional().describe(
|
|
28853
|
+
"Why this must expand beyond the latest Autopilot brief. Name the missing fact or the user's explicit request."
|
|
28854
|
+
),
|
|
28855
|
+
force: z2.boolean().optional().describe(
|
|
28856
|
+
"Use only when the user explicitly asks to read a record already represented in the latest Autopilot brief."
|
|
28857
|
+
)
|
|
28466
28858
|
}
|
|
28467
28859
|
},
|
|
28468
|
-
async ({ ids, refs, typedRefs }) => {
|
|
28860
|
+
async ({ ids, refs, typedRefs, purpose, force }) => {
|
|
28469
28861
|
const safeIds = coerceNumberArray(ids);
|
|
28470
28862
|
const safeRefs = coerceObservationRefs(refs);
|
|
28471
28863
|
const safeTypedRefs = coerceStringArray(typedRefs);
|
|
28864
|
+
const hasCrossProjectRef = safeRefs.some((ref) => ref.projectId && ref.projectId !== project.id) || safeTypedRefs.some((ref) => ref.includes("@") && !ref.endsWith(`@${project.id}`));
|
|
28865
|
+
if (!hasCrossProjectRef) {
|
|
28866
|
+
const boundary = requireExplicitAutopilotExpansion("memorix_detail", purpose);
|
|
28867
|
+
if (boundary) return boundary;
|
|
28868
|
+
const requestedObservationIds = [
|
|
28869
|
+
...safeIds,
|
|
28870
|
+
...safeRefs.map((ref) => ref.id),
|
|
28871
|
+
...safeTypedRefs.flatMap((ref) => {
|
|
28872
|
+
const match = /^obs:(\d+)(?:@.+)?$/i.exec(ref.trim());
|
|
28873
|
+
return match ? [Number(match[1])] : [];
|
|
28874
|
+
})
|
|
28875
|
+
];
|
|
28876
|
+
const duplicate = blockCoveredAutopilotEvidence("memorix_detail", requestedObservationIds, force);
|
|
28877
|
+
if (duplicate) return duplicate;
|
|
28878
|
+
}
|
|
28472
28879
|
let result;
|
|
28473
28880
|
try {
|
|
28474
|
-
const hasCrossProjectRef = safeRefs.some((ref) => ref.projectId && ref.projectId !== project.id) || safeTypedRefs.some((ref) => ref.includes("@") && !ref.endsWith(`@${project.id}`));
|
|
28475
28881
|
const reader = getObservationReader(hasCrossProjectRef ? "global" : "project");
|
|
28476
28882
|
if (safeTypedRefs.length > 0) {
|
|
28477
28883
|
result = await compactDetail(safeTypedRefs, { reader });
|
|
@@ -30497,6 +30903,7 @@ fi
|
|
|
30497
30903
|
if (newCanonicalId === project.id && projectResolved) return false;
|
|
30498
30904
|
console.error(`[memorix] Switching project: ${project.id} \u2192 ${newCanonicalId}`);
|
|
30499
30905
|
currentAgentId = void 0;
|
|
30906
|
+
autopilotRetrievalBoundary = null;
|
|
30500
30907
|
const canonicalProjectDir = newCanonicalId !== newDetected.id ? await getProjectDataDir(newCanonicalId) : newProjectDir;
|
|
30501
30908
|
projectResolved = true;
|
|
30502
30909
|
projectResolutionError = null;
|
|
@@ -30660,6 +31067,7 @@ var MemoryClient = class {
|
|
|
30660
31067
|
type: options.type,
|
|
30661
31068
|
source: options.source,
|
|
30662
31069
|
status: options.status === "all" ? void 0 : options.status ?? "active",
|
|
31070
|
+
quality: options.quality,
|
|
30663
31071
|
reader: this._reader
|
|
30664
31072
|
};
|
|
30665
31073
|
return this._oramaStore.searchObservations(searchOpts);
|