@agentmemory/agentmemory 0.9.31 → 0.9.32
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/.env.example +18 -15
- package/AGENTS.md +4 -4
- package/README.md +92 -270
- package/dist/.env.example +18 -15
- package/dist/cli.d.mts.map +1 -1
- package/dist/cli.mjs +530 -75
- package/dist/cli.mjs.map +1 -1
- package/dist/{compression-recovery-CnhLQC6N.mjs → compression-recovery-CZh63Jmz.mjs} +174 -89
- package/dist/compression-recovery-CZh63Jmz.mjs.map +1 -0
- package/dist/{connect-BSLIAMpw.mjs → connect-BX3njQ3Y.mjs} +28 -14
- package/dist/connect-BX3njQ3Y.mjs.map +1 -0
- package/dist/hooks/_capture.d.mts +3 -2
- package/dist/hooks/_capture.d.mts.map +1 -1
- package/dist/hooks/_capture.mjs +87 -8
- package/dist/hooks/_capture.mjs.map +1 -1
- package/dist/hooks/post-commit.mjs +58 -5
- package/dist/hooks/post-commit.mjs.map +1 -1
- package/dist/hooks/post-tool-failure.mjs +7 -2
- package/dist/hooks/post-tool-failure.mjs.map +1 -1
- package/dist/hooks/post-tool-use.mjs +7 -2
- package/dist/hooks/post-tool-use.mjs.map +1 -1
- package/dist/hooks/pre-compact.mjs +67 -12
- package/dist/hooks/pre-compact.mjs.map +1 -1
- package/dist/hooks/pre-tool-use.mjs +58 -5
- package/dist/hooks/pre-tool-use.mjs.map +1 -1
- package/dist/hooks/session-end.mjs +14 -3
- package/dist/hooks/session-end.mjs.map +1 -1
- package/dist/hooks/session-start.mjs +58 -5
- package/dist/hooks/session-start.mjs.map +1 -1
- package/dist/hooks/stop.mjs +2 -1
- package/dist/hooks/stop.mjs.map +1 -1
- package/dist/hooks/subagent-stop.mjs +15 -5
- package/dist/hooks/subagent-stop.mjs.map +1 -1
- package/dist/hooks/task-completed.mjs +8 -1
- package/dist/hooks/task-completed.mjs.map +1 -1
- package/dist/index.mjs +724 -200
- package/dist/index.mjs.map +1 -1
- package/dist/integrations/dsh.mjs +31 -4
- package/dist/integrations/dsh.mjs.map +1 -1
- package/dist/{secret-store-D9GwO0xp.mjs → secret-store-iYXRcrO8.mjs} +197 -55
- package/dist/secret-store-iYXRcrO8.mjs.map +1 -0
- package/dist/{src-DaMtDzt_.mjs → src-C23nMpda.mjs} +359 -62
- package/dist/src-C23nMpda.mjs.map +1 -0
- package/dist/{standalone-BlqshyZ5.mjs → standalone-BYo5USDb.mjs} +169 -10
- package/dist/standalone-BYo5USDb.mjs.map +1 -0
- package/dist/standalone.d.mts.map +1 -1
- package/dist/standalone.mjs +240 -10
- package/dist/standalone.mjs.map +1 -1
- package/dist/viewer/index.html +49 -28
- package/integrations/hermes/README.md +11 -1
- package/integrations/hermes/__init__.py +1 -0
- package/integrations/hermes/plugin.yaml +1 -1
- package/integrations/omp/dist/index.mjs +31 -4
- package/integrations/omp/package.json +1 -1
- package/integrations/omp/skills/agentmemory-agents/REFERENCE.md +1 -1
- package/integrations/omp/skills/agentmemory-architecture/SKILL.md +5 -1
- package/integrations/omp/skills/agentmemory-config/REFERENCE.md +2 -1
- package/integrations/omp/skills/agentmemory-config/SKILL.md +29 -9
- package/integrations/omp/skills/agentmemory-hooks/SKILL.md +17 -6
- package/integrations/omp/skills/agentmemory-mcp-tools/REFERENCE.md +1 -1
- package/integrations/omp/skills/agentmemory-mcp-tools/SKILL.md +10 -0
- package/integrations/omp/skills/agentmemory-rest-api/REFERENCE.md +2 -1
- package/integrations/omp/skills/agentmemory-rest-api/SKILL.md +1 -1
- package/integrations/openclaw/README.md +10 -0
- package/integrations/openclaw/openclaw.plugin.json +1 -1
- package/integrations/openclaw/package.json +1 -1
- package/integrations/openclaw/plugin.yaml +1 -1
- package/integrations/openclaw/skills/agentmemory-agents/REFERENCE.md +1 -1
- package/integrations/openclaw/skills/agentmemory-architecture/SKILL.md +5 -1
- package/integrations/openclaw/skills/agentmemory-config/REFERENCE.md +2 -1
- package/integrations/openclaw/skills/agentmemory-config/SKILL.md +29 -9
- package/integrations/openclaw/skills/agentmemory-hooks/SKILL.md +17 -6
- package/integrations/openclaw/skills/agentmemory-mcp-tools/REFERENCE.md +1 -1
- package/integrations/openclaw/skills/agentmemory-mcp-tools/SKILL.md +10 -0
- package/integrations/openclaw/skills/agentmemory-rest-api/REFERENCE.md +2 -1
- package/integrations/openclaw/skills/agentmemory-rest-api/SKILL.md +1 -1
- package/integrations/pi/README.md +1 -0
- package/integrations/pi/dist/index.mjs +92 -11
- package/integrations/pi/index.ts +56 -6
- package/integrations/pi/package.json +1 -1
- package/integrations/pi/skills/agentmemory-agents/REFERENCE.md +1 -1
- package/integrations/pi/skills/agentmemory-architecture/SKILL.md +5 -1
- package/integrations/pi/skills/agentmemory-config/REFERENCE.md +2 -1
- package/integrations/pi/skills/agentmemory-config/SKILL.md +29 -9
- package/integrations/pi/skills/agentmemory-hooks/SKILL.md +17 -6
- package/integrations/pi/skills/agentmemory-mcp-tools/REFERENCE.md +1 -1
- package/integrations/pi/skills/agentmemory-mcp-tools/SKILL.md +10 -0
- package/integrations/pi/skills/agentmemory-rest-api/REFERENCE.md +2 -1
- package/integrations/pi/skills/agentmemory-rest-api/SKILL.md +1 -1
- package/package.json +4 -3
- package/plugin/.claude-plugin/plugin.json +16 -3
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/.devin-plugin/plugin.json +3 -4
- package/plugin/.mcp.json +5 -1
- package/plugin/README.md +71 -0
- package/plugin/hooks/hooks.json +24 -12
- package/plugin/opencode/plugin.json +1 -1
- package/plugin/plugin.json +1 -1
- package/plugin/scripts/_capture.d.mts +3 -2
- package/plugin/scripts/_capture.d.mts.map +1 -1
- package/plugin/scripts/_capture.mjs +87 -8
- package/plugin/scripts/_capture.mjs.map +1 -1
- package/plugin/scripts/plugin-bridge.mjs +25 -6
- package/plugin/scripts/post-tool-failure.mjs +7 -2
- package/plugin/scripts/post-tool-failure.mjs.map +1 -1
- package/plugin/scripts/post-tool-use.mjs +7 -2
- package/plugin/scripts/post-tool-use.mjs.map +1 -1
- package/plugin/scripts/pre-compact.mjs +67 -12
- package/plugin/scripts/pre-compact.mjs.map +1 -1
- package/plugin/scripts/pre-tool-use.mjs +58 -5
- package/plugin/scripts/pre-tool-use.mjs.map +1 -1
- package/plugin/scripts/session-end.mjs +14 -3
- package/plugin/scripts/session-end.mjs.map +1 -1
- package/plugin/scripts/session-start.mjs +58 -5
- package/plugin/scripts/session-start.mjs.map +1 -1
- package/plugin/scripts/stop.mjs +2 -1
- package/plugin/scripts/stop.mjs.map +1 -1
- package/plugin/scripts/subagent-stop.mjs +15 -5
- package/plugin/scripts/subagent-stop.mjs.map +1 -1
- package/plugin/scripts/task-completed.mjs +8 -1
- package/plugin/scripts/task-completed.mjs.map +1 -1
- package/plugin/skills/agentmemory-agents/REFERENCE.md +1 -1
- package/plugin/skills/agentmemory-architecture/SKILL.md +5 -1
- package/plugin/skills/agentmemory-config/REFERENCE.md +2 -1
- package/plugin/skills/agentmemory-config/SKILL.md +29 -9
- package/plugin/skills/agentmemory-hooks/SKILL.md +17 -6
- package/plugin/skills/agentmemory-mcp-tools/REFERENCE.md +1 -1
- package/plugin/skills/agentmemory-mcp-tools/SKILL.md +10 -0
- package/plugin/skills/agentmemory-rest-api/REFERENCE.md +2 -1
- package/plugin/skills/agentmemory-rest-api/SKILL.md +1 -1
- package/dist/compression-recovery-CnhLQC6N.mjs.map +0 -1
- package/dist/connect-BSLIAMpw.mjs.map +0 -1
- package/dist/secret-store-D9GwO0xp.mjs.map +0 -1
- package/dist/src-DaMtDzt_.mjs.map +0 -1
- package/dist/standalone-BlqshyZ5.mjs.map +0 -1
- package/plugin/scripts/post-commit.d.mts +0 -1
- package/plugin/scripts/post-commit.mjs +0 -204
- package/plugin/scripts/post-commit.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -80,53 +80,120 @@ function warnIfPremiumModel(env, envName, model) {
|
|
|
80
80
|
warnPremiumModelShown = true;
|
|
81
81
|
process.stderr.write(`[agentmemory] ${envName}=${model} is in the premium tier. Background compression on this model can cost $5+/day under active use. Cheaper alternatives with comparable quality for memory compression: deepseek/deepseek-v4-flash-0731, deepseek/deepseek-v4-pro, qwen/qwen3-coder. See README "Cost-aware model selection" for the full table. Set AGENTMEMORY_SUPPRESS_COST_WARNING=1 to silence.\n`);
|
|
82
82
|
}
|
|
83
|
+
const LLM_PROVIDER_NAMES = [
|
|
84
|
+
"anthropic",
|
|
85
|
+
"openai",
|
|
86
|
+
"gemini",
|
|
87
|
+
"openrouter",
|
|
88
|
+
"minimax"
|
|
89
|
+
];
|
|
90
|
+
const LLM_PROVIDER_KEYS = {
|
|
91
|
+
anthropic: ["ANTHROPIC_API_KEY"],
|
|
92
|
+
openai: ["OPENAI_API_KEY"],
|
|
93
|
+
gemini: ["GEMINI_API_KEY", "GOOGLE_API_KEY"],
|
|
94
|
+
openrouter: ["OPENROUTER_API_KEY"],
|
|
95
|
+
minimax: ["MINIMAX_API_KEY"]
|
|
96
|
+
};
|
|
97
|
+
const REMOTE_EMBEDDING_PROVIDER_KEYS = {
|
|
98
|
+
gemini: ["GEMINI_API_KEY"],
|
|
99
|
+
openai: ["OPENAI_API_KEY", "OPENAI_EMBEDDING_API_KEY"],
|
|
100
|
+
voyage: ["VOYAGE_API_KEY"],
|
|
101
|
+
cohere: ["COHERE_API_KEY"],
|
|
102
|
+
openrouter: ["OPENROUTER_API_KEY"]
|
|
103
|
+
};
|
|
104
|
+
function isLlmProviderName(value) {
|
|
105
|
+
return LLM_PROVIDER_NAMES.includes(value);
|
|
106
|
+
}
|
|
107
|
+
function selectedLlmProviderValue(env) {
|
|
108
|
+
return (env["AGENTMEMORY_LLM_PROVIDER"] || "").trim().toLowerCase();
|
|
109
|
+
}
|
|
110
|
+
function llmProviderHasKey(env, name) {
|
|
111
|
+
return LLM_PROVIDER_KEYS[name].some((k) => hasRealValue(env[k]));
|
|
112
|
+
}
|
|
113
|
+
function envFileLabel(path = ENV_FILE) {
|
|
114
|
+
const home = homedir();
|
|
115
|
+
return path.startsWith(`${home}/`) ? `~${path.slice(home.length)}` : path;
|
|
116
|
+
}
|
|
117
|
+
function llmOptInHint(env = getMergedEnv()) {
|
|
118
|
+
const selected = selectedLlmProviderValue(env);
|
|
119
|
+
if (selected) {
|
|
120
|
+
if (!isLlmProviderName(selected)) return `AGENTMEMORY_LLM_PROVIDER=${selected} is not a known provider. Use one of: ${LLM_PROVIDER_NAMES.join(", ")}.`;
|
|
121
|
+
if (!llmProviderHasKey(env, selected)) return `AGENTMEMORY_LLM_PROVIDER=${selected} is set, but ${LLM_PROVIDER_KEYS[selected].join(" or ")} is not. Add the key to ${envFileLabel()}.`;
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
if (env["AGENTMEMORY_ALLOW_AGENT_SDK"] === "true") return null;
|
|
125
|
+
const withKey = LLM_PROVIDER_NAMES.filter((name) => llmProviderHasKey(env, name));
|
|
126
|
+
if (withKey.length === 0) return null;
|
|
127
|
+
return `Found ${withKey.flatMap((name) => LLM_PROVIDER_KEYS[name].filter((k) => hasRealValue(env[k]))).join(", ")}, but agentmemory sends nothing to an LLM provider until you choose one for it. To turn on LLM summaries and compression, add AGENTMEMORY_LLM_PROVIDER=${withKey[0]} to ${envFileLabel()}.`;
|
|
128
|
+
}
|
|
129
|
+
function embeddingOptInHint(env = getMergedEnv()) {
|
|
130
|
+
const chosen = (env["EMBEDDING_PROVIDER"] || "").trim().toLowerCase();
|
|
131
|
+
if (chosen) {
|
|
132
|
+
const keys = REMOTE_EMBEDDING_PROVIDER_KEYS[chosen];
|
|
133
|
+
if (keys && !keys.some((k) => hasRealValue(env[k]))) return `EMBEDDING_PROVIDER=${chosen} is set, but ${keys.join(" or ")} is not. Add the key to ${envFileLabel()}, or set EMBEDDING_PROVIDER=local for on-device embeddings.`;
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
const withKey = Object.keys(REMOTE_EMBEDDING_PROVIDER_KEYS).filter((name) => REMOTE_EMBEDDING_PROVIDER_KEYS[name].some((k) => hasRealValue(env[k])));
|
|
137
|
+
if (withKey.length === 0) return null;
|
|
138
|
+
return `A ${withKey.join("/")} key is present, but cloud embeddings stay off until you choose them. Add EMBEDDING_PROVIDER=${withKey[0]} to ${envFileLabel()}, or EMBEDDING_PROVIDER=local for on-device embeddings.`;
|
|
139
|
+
}
|
|
83
140
|
function detectProvider(env) {
|
|
84
141
|
const maxTokens = parseInt(env["MAX_TOKENS"] || "4096", 10);
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
return {
|
|
89
|
-
provider: "openai",
|
|
90
|
-
model,
|
|
91
|
-
maxTokens,
|
|
92
|
-
baseURL: env["OPENAI_BASE_URL"]
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
if (hasRealValue(env["MINIMAX_API_KEY"])) return {
|
|
96
|
-
provider: "minimax",
|
|
97
|
-
model: env["MINIMAX_MODEL"] || "MiniMax-M3",
|
|
142
|
+
const noop = {
|
|
143
|
+
provider: "noop",
|
|
144
|
+
model: "noop",
|
|
98
145
|
maxTokens
|
|
99
146
|
};
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
147
|
+
const selected = selectedLlmProviderValue(env);
|
|
148
|
+
if (selected) {
|
|
149
|
+
if (!isLlmProviderName(selected) || !llmProviderHasKey(env, selected)) {
|
|
150
|
+
writeProviderNoticeOnce(`[agentmemory] ${llmOptInHint(env)} Running zero-LLM.\n`);
|
|
151
|
+
return noop;
|
|
152
|
+
}
|
|
153
|
+
switch (selected) {
|
|
154
|
+
case "openai": {
|
|
155
|
+
const model = env["OPENAI_MODEL"] || "gpt-5.6-luna";
|
|
156
|
+
warnIfPremiumModel(env, "OPENAI_MODEL", model);
|
|
157
|
+
return {
|
|
158
|
+
provider: "openai",
|
|
159
|
+
model,
|
|
160
|
+
maxTokens,
|
|
161
|
+
baseURL: env["OPENAI_BASE_URL"]
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
case "minimax": return {
|
|
165
|
+
provider: "minimax",
|
|
166
|
+
model: env["MINIMAX_MODEL"] || "MiniMax-M3",
|
|
167
|
+
maxTokens
|
|
168
|
+
};
|
|
169
|
+
case "anthropic": return {
|
|
170
|
+
provider: "anthropic",
|
|
171
|
+
model: env["ANTHROPIC_MODEL"] || "claude-sonnet-5",
|
|
172
|
+
maxTokens,
|
|
173
|
+
baseURL: env["ANTHROPIC_BASE_URL"]
|
|
174
|
+
};
|
|
175
|
+
case "gemini":
|
|
176
|
+
if (!hasRealValue(env["GEMINI_API_KEY"])) process.stderr.write("[agentmemory] GOOGLE_API_KEY detected — treating as GEMINI_API_KEY. Set GEMINI_API_KEY in ~/.agentmemory/.env to silence this warning.\n");
|
|
177
|
+
return {
|
|
178
|
+
provider: "gemini",
|
|
179
|
+
model: env["GEMINI_MODEL"] || "gemini-3.7-flash",
|
|
180
|
+
maxTokens
|
|
181
|
+
};
|
|
182
|
+
case "openrouter": {
|
|
183
|
+
const model = env["OPENROUTER_MODEL"] || "anthropic/claude-sonnet-5";
|
|
184
|
+
warnIfPremiumModel(env, "OPENROUTER_MODEL", model);
|
|
185
|
+
return {
|
|
186
|
+
provider: "openrouter",
|
|
187
|
+
model,
|
|
188
|
+
maxTokens
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
}
|
|
122
192
|
}
|
|
123
193
|
if (!(env["AGENTMEMORY_ALLOW_AGENT_SDK"] === "true")) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
model: "noop",
|
|
128
|
-
maxTokens
|
|
129
|
-
};
|
|
194
|
+
const hint = llmOptInHint(env);
|
|
195
|
+
writeProviderNoticeOnce(hint ? `[agentmemory] ${hint}\n` : pc.dim("[agentmemory] No LLM provider chosen, so agentmemory runs zero-LLM: no LLM compression or summaries, and no session content leaves this machine for an LLM. Search uses BM25 plus any embedding provider you set with EMBEDDING_PROVIDER (EMBEDDING_PROVIDER=local runs on-device). To turn on LLM summaries, set AGENTMEMORY_LLM_PROVIDER (anthropic, openai, gemini, openrouter or minimax) and that provider's key in ~/.agentmemory/.env.\n"));
|
|
196
|
+
return noop;
|
|
130
197
|
}
|
|
131
198
|
writeProviderNoticeOnce("[agentmemory] WARNING: agent-sdk fallback enabled via AGENTMEMORY_ALLOW_AGENT_SDK=true. This spawns @anthropic-ai/claude-agent-sdk child sessions that can trigger the Stop-hook recursion loop. A SDK-child env marker is set to block re-entry, but prefer setting a real API key in ~/.agentmemory/.env instead.\n");
|
|
132
199
|
return {
|
|
@@ -202,7 +269,9 @@ function getAuditMigrateMaxBytes() {
|
|
|
202
269
|
return safeParseInt(getMergedEnv()["AGENTMEMORY_AUDIT_MIGRATE_MAX_BYTES"], 32 * 1024 * 1024);
|
|
203
270
|
}
|
|
204
271
|
function detectLlmProviderKind(env = getMergedEnv()) {
|
|
205
|
-
|
|
272
|
+
const selected = selectedLlmProviderValue(env);
|
|
273
|
+
if (selected && isLlmProviderName(selected) && llmProviderHasKey(env, selected)) return "llm";
|
|
274
|
+
if (!selected && env["AGENTMEMORY_ALLOW_AGENT_SDK"] === "true") return "llm";
|
|
206
275
|
return "noop";
|
|
207
276
|
}
|
|
208
277
|
function describeLlmRoute(provider) {
|
|
@@ -228,15 +297,8 @@ function loadEmbeddingConfig() {
|
|
|
228
297
|
};
|
|
229
298
|
}
|
|
230
299
|
function detectEmbeddingProvider(env) {
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
if (forced) return forced;
|
|
234
|
-
if (source["GEMINI_API_KEY"]) return "gemini";
|
|
235
|
-
if (source["OPENAI_API_KEY"] || source["OPENAI_EMBEDDING_API_KEY"]) return "openai";
|
|
236
|
-
if (source["VOYAGE_API_KEY"]) return "voyage";
|
|
237
|
-
if (source["COHERE_API_KEY"]) return "cohere";
|
|
238
|
-
if (source["OPENROUTER_API_KEY"]) return "openrouter";
|
|
239
|
-
return null;
|
|
300
|
+
const forced = ((env ?? getMergedEnv())["EMBEDDING_PROVIDER"] || "").trim();
|
|
301
|
+
return forced ? forced : null;
|
|
240
302
|
}
|
|
241
303
|
function loadClaudeBridgeConfig() {
|
|
242
304
|
const env = getMergedEnv();
|
|
@@ -754,9 +816,11 @@ var OpenAIProvider = class {
|
|
|
754
816
|
throw new Error(`OpenAI API error (${response.status}): ${text}`);
|
|
755
817
|
}
|
|
756
818
|
const data = await response.json();
|
|
757
|
-
const
|
|
819
|
+
const choice = data.choices?.[0];
|
|
820
|
+
const message = choice?.message;
|
|
758
821
|
const content = message?.content;
|
|
759
|
-
if (content) return content;
|
|
822
|
+
if (content?.trim()) return content;
|
|
823
|
+
if (choice?.finish_reason === "length") throw new Error(`OpenAI response hit max_tokens (${this.maxTokens}) before any content. Raise MAX_TOKENS or lower OPENAI_REASONING_EFFORT.`);
|
|
760
824
|
const reasoning = message?.reasoning ?? message?.reasoning_content;
|
|
761
825
|
if (reasoning) return reasoning;
|
|
762
826
|
throw new Error(`OpenAI returned unexpected response: ${JSON.stringify(data).slice(0, 200)}`);
|
|
@@ -2699,8 +2763,84 @@ var StateKV = class {
|
|
|
2699
2763
|
if (this.backend !== "redis" || !Array.isArray(values)) return values;
|
|
2700
2764
|
return orderLikeInsertion(values);
|
|
2701
2765
|
}
|
|
2766
|
+
async listGroups() {
|
|
2767
|
+
const groups = (await this.sdk.trigger({
|
|
2768
|
+
function_id: "state::list_groups",
|
|
2769
|
+
payload: {}
|
|
2770
|
+
}))?.groups;
|
|
2771
|
+
if (!Array.isArray(groups) || !groups.every((group) => typeof group === "string")) throw new Error("state::list_groups returned an unexpected shape");
|
|
2772
|
+
return groups;
|
|
2773
|
+
}
|
|
2702
2774
|
};
|
|
2703
2775
|
//#endregion
|
|
2776
|
+
//#region src/state/state-readiness.ts
|
|
2777
|
+
const STATE_READY_TIMEOUT_MS = 12e4;
|
|
2778
|
+
const STATE_READY_INTERVAL_MS = 100;
|
|
2779
|
+
const PROBE_TIMEOUT_MS = 1e4;
|
|
2780
|
+
const READINESS_PROBE_KEY = "__agentmemory_state_readiness__";
|
|
2781
|
+
function errorMessage$3(error) {
|
|
2782
|
+
return error instanceof Error ? error.message : String(error);
|
|
2783
|
+
}
|
|
2784
|
+
function isRetryableProbeError(error) {
|
|
2785
|
+
const code = error?.code;
|
|
2786
|
+
if (code === "function_not_found" || code === "invocation_timeout" || code === "timeout") return true;
|
|
2787
|
+
return /Function state::(?:get|list) not found|timed out|timeout|ETIMEDOUT/i.test(errorMessage$3(error));
|
|
2788
|
+
}
|
|
2789
|
+
async function waitForStateReady(sdk, options = {}) {
|
|
2790
|
+
const scope = options.scope ?? KV.config;
|
|
2791
|
+
const timeoutMs = options.timeoutMs ?? STATE_READY_TIMEOUT_MS;
|
|
2792
|
+
const intervalMs = options.intervalMs ?? STATE_READY_INTERVAL_MS;
|
|
2793
|
+
const now = options.now ?? (() => performance.now());
|
|
2794
|
+
const sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
2795
|
+
const startedAt = now();
|
|
2796
|
+
let attempts = 0;
|
|
2797
|
+
let lastError;
|
|
2798
|
+
const probeTimeoutMs = () => Math.min(PROBE_TIMEOUT_MS, Math.max(1, timeoutMs - (now() - startedAt)));
|
|
2799
|
+
for (;;) {
|
|
2800
|
+
attempts += 1;
|
|
2801
|
+
try {
|
|
2802
|
+
await sdk.trigger({
|
|
2803
|
+
function_id: "state::list",
|
|
2804
|
+
payload: { scope },
|
|
2805
|
+
timeoutMs: probeTimeoutMs()
|
|
2806
|
+
});
|
|
2807
|
+
await sdk.trigger({
|
|
2808
|
+
function_id: "state::get",
|
|
2809
|
+
payload: {
|
|
2810
|
+
scope,
|
|
2811
|
+
key: READINESS_PROBE_KEY
|
|
2812
|
+
},
|
|
2813
|
+
timeoutMs: probeTimeoutMs()
|
|
2814
|
+
});
|
|
2815
|
+
return {
|
|
2816
|
+
ready: true,
|
|
2817
|
+
waitedMs: Math.round(now() - startedAt),
|
|
2818
|
+
attempts
|
|
2819
|
+
};
|
|
2820
|
+
} catch (error) {
|
|
2821
|
+
lastError = errorMessage$3(error);
|
|
2822
|
+
if (!isRetryableProbeError(error)) return {
|
|
2823
|
+
ready: false,
|
|
2824
|
+
waitedMs: Math.round(now() - startedAt),
|
|
2825
|
+
attempts,
|
|
2826
|
+
error: lastError
|
|
2827
|
+
};
|
|
2828
|
+
if (attempts === 1) options.onWaiting?.(lastError);
|
|
2829
|
+
}
|
|
2830
|
+
const elapsed = now() - startedAt;
|
|
2831
|
+
if (elapsed >= timeoutMs) return {
|
|
2832
|
+
ready: false,
|
|
2833
|
+
waitedMs: Math.round(elapsed),
|
|
2834
|
+
attempts,
|
|
2835
|
+
error: lastError
|
|
2836
|
+
};
|
|
2837
|
+
await sleep(Math.min(intervalMs, timeoutMs - elapsed));
|
|
2838
|
+
}
|
|
2839
|
+
}
|
|
2840
|
+
function assertStateReady(readiness) {
|
|
2841
|
+
if (!readiness.ready) throw new Error(`State worker did not become ready within ${readiness.waitedMs}ms: ${readiness.error ?? "timeout"}`);
|
|
2842
|
+
}
|
|
2843
|
+
//#endregion
|
|
2704
2844
|
//#region src/state/search-layer.ts
|
|
2705
2845
|
function isSearchLayer(value) {
|
|
2706
2846
|
return value === "all" || value === "memory" || value === "observation";
|
|
@@ -2889,12 +3029,20 @@ var VectorIndex = class VectorIndex {
|
|
|
2889
3029
|
};
|
|
2890
3030
|
//#endregion
|
|
2891
3031
|
//#region src/state/memory-utils.ts
|
|
3032
|
+
const MAX_DATE_MS = 864e13;
|
|
3033
|
+
function supersededAt(previous, now) {
|
|
3034
|
+
const prior = previous ? Date.parse(previous) : NaN;
|
|
3035
|
+
const current = Date.parse(now);
|
|
3036
|
+
if (Number.isNaN(prior) || current > prior) return now;
|
|
3037
|
+
return new Date(Math.min(prior + 1, MAX_DATE_MS)).toISOString();
|
|
3038
|
+
}
|
|
2892
3039
|
function memoryToObservation(memory) {
|
|
2893
3040
|
return {
|
|
2894
3041
|
id: memory.id,
|
|
2895
3042
|
sessionId: memory.sessionIds?.[0] ?? "memory",
|
|
2896
3043
|
timestamp: memory.createdAt,
|
|
2897
3044
|
type: "decision",
|
|
3045
|
+
memoryType: memory.type,
|
|
2898
3046
|
title: memory.title,
|
|
2899
3047
|
facts: [memory.content],
|
|
2900
3048
|
narrative: memory.content,
|
|
@@ -2918,6 +3066,13 @@ function lessonToObservation(l) {
|
|
|
2918
3066
|
importance: l.confidence
|
|
2919
3067
|
};
|
|
2920
3068
|
}
|
|
3069
|
+
function observationEmbedText(record) {
|
|
3070
|
+
return [
|
|
3071
|
+
record.title,
|
|
3072
|
+
...record.concepts || [],
|
|
3073
|
+
record.narrative || ""
|
|
3074
|
+
].filter((part) => part && part.trim()).join(" ").trim();
|
|
3075
|
+
}
|
|
2921
3076
|
//#endregion
|
|
2922
3077
|
//#region src/state/obs-index.ts
|
|
2923
3078
|
const OBS_INDEX_SHARDS = 64;
|
|
@@ -3555,10 +3710,12 @@ function registerQueryExpansionFunction(sdk, provider) {
|
|
|
3555
3710
|
}
|
|
3556
3711
|
});
|
|
3557
3712
|
}
|
|
3713
|
+
const CJK_RUN = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]+/gu;
|
|
3558
3714
|
function extractEntitiesFromQuery(query) {
|
|
3559
3715
|
const entities = [];
|
|
3560
3716
|
const quoted = query.match(/"([^"]+)"/g);
|
|
3561
3717
|
if (quoted) for (const q of quoted) entities.push(q.replace(/"/g, ""));
|
|
3718
|
+
for (const run of query.matchAll(CJK_RUN)) for (const seg of segmentCjk(run[0])) if (seg.trim().length >= 2) entities.push(seg.trim());
|
|
3562
3719
|
const capitalized = query.match(/\b[A-Z][a-zA-Z0-9_.-]+\b/g);
|
|
3563
3720
|
if (capitalized) {
|
|
3564
3721
|
const stopWords = new Set([
|
|
@@ -3701,8 +3858,8 @@ var HybridSearch = class {
|
|
|
3701
3858
|
this.rerankEnabled = rerankEnabled;
|
|
3702
3859
|
this.graphRetrieval = new GraphRetrieval(kv, (id) => this.bm25.layerOf(id), (id) => this.bm25.sessionOf(id));
|
|
3703
3860
|
}
|
|
3704
|
-
async search(query, limit = 20, targetLayer = "all", authorizeGraph) {
|
|
3705
|
-
return this.tripleStreamSearch(query, limit, targetLayer, this.graphRetrieval.createQuery(authorizeGraph));
|
|
3861
|
+
async search(query, limit = 20, targetLayer = "all", authorizeGraph, request) {
|
|
3862
|
+
return this.tripleStreamSearch(query, limit, targetLayer, this.graphRetrieval.createQuery(authorizeGraph), void 0, true, request);
|
|
3706
3863
|
}
|
|
3707
3864
|
async searchWithExpansion(query, limit, expansion, targetLayer = "all", authorizeGraph) {
|
|
3708
3865
|
const allQueries = [
|
|
@@ -3722,13 +3879,25 @@ var HybridSearch = class {
|
|
|
3722
3879
|
const combined = Array.from(merged.values()).sort((a, b) => b.combinedScore - a.combinedScore || (a.observation.id < b.observation.id ? -1 : a.observation.id > b.observation.id ? 1 : 0));
|
|
3723
3880
|
return this.rerankResults(query, combined, limit);
|
|
3724
3881
|
}
|
|
3725
|
-
async tripleStreamSearch(query, limit, targetLayer, graphQuery, entityHints, applyReranking = true) {
|
|
3882
|
+
async tripleStreamSearch(query, limit, targetLayer, graphQuery, entityHints, applyReranking = true, request) {
|
|
3726
3883
|
const retrievalLimit = this.rerankEnabled ? Math.max(limit, 20) : limit;
|
|
3727
3884
|
const bm25Results = this.bm25.search(query, retrievalLimit * 2, targetLayer);
|
|
3728
3885
|
let vectorResults = [];
|
|
3729
3886
|
let queryEmbedding = null;
|
|
3730
3887
|
if (this.vector && this.embeddingProvider && this.vector.size > 0) try {
|
|
3731
|
-
|
|
3888
|
+
if (request) {
|
|
3889
|
+
if (!request.embedding || request.embedding.query !== query) request.embedding = {
|
|
3890
|
+
query,
|
|
3891
|
+
value: this.embeddingProvider.embed(query)
|
|
3892
|
+
};
|
|
3893
|
+
const embedding = request.embedding;
|
|
3894
|
+
try {
|
|
3895
|
+
queryEmbedding = await embedding.value;
|
|
3896
|
+
} catch (error) {
|
|
3897
|
+
if (request.embedding === embedding) request.embedding = void 0;
|
|
3898
|
+
throw error;
|
|
3899
|
+
}
|
|
3900
|
+
} else queryEmbedding = await this.embeddingProvider.embed(query);
|
|
3732
3901
|
vectorResults = this.vector.search(queryEmbedding, retrievalLimit * 2, targetLayer, (id) => this.bm25.layerOf(id));
|
|
3733
3902
|
} catch {}
|
|
3734
3903
|
const entities = entityHints && entityHints.length > 0 ? entityHints : extractEntitiesFromQuery(query);
|
|
@@ -3803,6 +3972,7 @@ var HybridSearch = class {
|
|
|
3803
3972
|
graphContext: s.graphContext,
|
|
3804
3973
|
combinedScore
|
|
3805
3974
|
}));
|
|
3975
|
+
if (request) request.candidateCount = combined.length;
|
|
3806
3976
|
const retrievalDepth = Math.max(limit, 20);
|
|
3807
3977
|
const diversified = this.diversifyBySession(combined, retrievalDepth);
|
|
3808
3978
|
const enriched = await this.enrichResults(diversified, retrievalDepth);
|
|
@@ -4332,6 +4502,10 @@ const PENDING_LOG_SAVE_THRESHOLD = 500;
|
|
|
4332
4502
|
const EARLY_SAVE_MIN_GAP_MS = 5e3;
|
|
4333
4503
|
const BACKFILL_MARKER_KEY = "vectors:backfill";
|
|
4334
4504
|
const MEMORY_ID_PREFIX = "mem_";
|
|
4505
|
+
const LEGACY_SHARD_SCOPE_PREFIXES = [`${KV.bm25Index}:bm25:`, `${KV.bm25Index}:vectors:`];
|
|
4506
|
+
const LEGACY_SHARD_KEY = "data";
|
|
4507
|
+
const LEGACY_SHARD_SUFFIX = /^((?:[0-9a-z]+-[0-9a-f]{32})|(?:idx_[0-9a-z]+_[0-9a-f]{12})):\d{5}$/;
|
|
4508
|
+
const ORPHAN_SWEEP_KEY = "legacy:sweep";
|
|
4335
4509
|
function auditIndexPersistEnabled() {
|
|
4336
4510
|
const raw = process.env.AGENTMEMORY_AUDIT_INDEX_PERSIST;
|
|
4337
4511
|
if (!raw) return false;
|
|
@@ -4366,6 +4540,11 @@ function isValidShardDescriptor(shard) {
|
|
|
4366
4540
|
const candidate = shard;
|
|
4367
4541
|
return typeof candidate.scope === "string" && candidate.scope.length > 0 && typeof candidate.key === "string" && candidate.key.length > 0 && Number.isInteger(candidate.chars) && candidate.chars >= 0;
|
|
4368
4542
|
}
|
|
4543
|
+
function legacyShardGeneration(scope) {
|
|
4544
|
+
const prefix = LEGACY_SHARD_SCOPE_PREFIXES.find((candidate) => scope.startsWith(candidate));
|
|
4545
|
+
if (!prefix) return void 0;
|
|
4546
|
+
return LEGACY_SHARD_SUFFIX.exec(scope.slice(prefix.length))?.[1] ?? null;
|
|
4547
|
+
}
|
|
4369
4548
|
function isPersistedVector(row) {
|
|
4370
4549
|
if (!row || typeof row !== "object") return false;
|
|
4371
4550
|
const candidate = row;
|
|
@@ -4736,7 +4915,7 @@ var IndexPersistence = class {
|
|
|
4736
4915
|
}
|
|
4737
4916
|
async load() {
|
|
4738
4917
|
if (!this.vector) {
|
|
4739
|
-
await this.
|
|
4918
|
+
await this.finishLegacyCleanup();
|
|
4740
4919
|
return {
|
|
4741
4920
|
vector: null,
|
|
4742
4921
|
state: "none",
|
|
@@ -4764,7 +4943,7 @@ var IndexPersistence = class {
|
|
|
4764
4943
|
this.hasVectorCheckpoint = loaded.size > 0;
|
|
4765
4944
|
this.checkpointBackfillSince = meta.backfillSince;
|
|
4766
4945
|
await this.removeLegacyVectorSnapshotIfPresent();
|
|
4767
|
-
await this.
|
|
4946
|
+
await this.finishLegacyCleanup();
|
|
4768
4947
|
const expectedCount = Number.isInteger(meta.count) ? meta.count : void 0;
|
|
4769
4948
|
this.vectorCountShortfall = expectedCount !== void 0 && loaded.size < expectedCount ? {
|
|
4770
4949
|
expected: expectedCount,
|
|
@@ -4950,7 +5129,7 @@ var IndexPersistence = class {
|
|
|
4950
5129
|
savedAt: null
|
|
4951
5130
|
};
|
|
4952
5131
|
if (!(manifest.value != null || typeof legacyKey.value === "string" && legacyKey.value.length > 0)) {
|
|
4953
|
-
await this.
|
|
5132
|
+
await this.finishLegacyCleanup();
|
|
4954
5133
|
return {
|
|
4955
5134
|
vector: null,
|
|
4956
5135
|
state: "none",
|
|
@@ -4983,7 +5162,7 @@ var IndexPersistence = class {
|
|
|
4983
5162
|
};
|
|
4984
5163
|
}
|
|
4985
5164
|
await this.removeLegacyVectorSnapshot(manifest.value);
|
|
4986
|
-
await this.
|
|
5165
|
+
await this.finishLegacyCleanup();
|
|
4987
5166
|
await this.auditIndexPersistence("migrate", [statePath(KV.bm25Index, VECTOR_META_KEY)], {
|
|
4988
5167
|
vectors: legacy.size,
|
|
4989
5168
|
buckets: this.bucketCountInUse()
|
|
@@ -5023,6 +5202,71 @@ var IndexPersistence = class {
|
|
|
5023
5202
|
if (manifest != null) await this.deleteKey(KV.bm25Index, LEGACY_BM25_MANIFEST_KEY, "legacy_bm25_cleanup");
|
|
5024
5203
|
if (legacy != null) await this.deleteKey(KV.bm25Index, LEGACY_BM25_KEY, "legacy_bm25_cleanup");
|
|
5025
5204
|
}
|
|
5205
|
+
async finishLegacyCleanup() {
|
|
5206
|
+
await this.removeLegacyBm25Snapshot();
|
|
5207
|
+
await this.sweepOrphanedGenerations();
|
|
5208
|
+
}
|
|
5209
|
+
async sweepOrphanedGenerations() {
|
|
5210
|
+
try {
|
|
5211
|
+
if (await this.kv.get(KV.bm25Index, ORPHAN_SWEEP_KEY) != null) return;
|
|
5212
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
5213
|
+
for (const scope of await this.kv.listGroups()) {
|
|
5214
|
+
const generation = legacyShardGeneration(scope);
|
|
5215
|
+
if (generation === void 0) continue;
|
|
5216
|
+
if (generation === null) {
|
|
5217
|
+
logger.warn("index persistence: orphan sweep skipped, unrecognized legacy shard scope", { scope });
|
|
5218
|
+
return;
|
|
5219
|
+
}
|
|
5220
|
+
candidates.set(scope, generation);
|
|
5221
|
+
}
|
|
5222
|
+
const named = await this.namedLegacyGenerations();
|
|
5223
|
+
if (!named) return;
|
|
5224
|
+
const orphans = [...candidates].filter(([, generation]) => !named.has(generation));
|
|
5225
|
+
const failures = await inBatches(orphans, WRITE_CONCURRENCY, async ([scope]) => {
|
|
5226
|
+
if (!await this.deleteKey(scope, LEGACY_SHARD_KEY, "orphan_generation_sweep")) throw new Error(scope);
|
|
5227
|
+
});
|
|
5228
|
+
const summary = {
|
|
5229
|
+
generations: new Set(orphans.map(([, generation]) => generation)).size,
|
|
5230
|
+
shards: orphans.length,
|
|
5231
|
+
failed: failures.length
|
|
5232
|
+
};
|
|
5233
|
+
if (failures.length > 0) {
|
|
5234
|
+
logger.warn("index persistence: orphan sweep left shards behind; the next boot retries", summary);
|
|
5235
|
+
return;
|
|
5236
|
+
}
|
|
5237
|
+
if (candidates.size > orphans.length) {
|
|
5238
|
+
if (orphans.length > 0) logger.info("index persistence: swept orphaned legacy index generations", summary);
|
|
5239
|
+
return;
|
|
5240
|
+
}
|
|
5241
|
+
await this.kv.set(KV.bm25Index, ORPHAN_SWEEP_KEY, {
|
|
5242
|
+
v: 1,
|
|
5243
|
+
at: new Date(this.now()).toISOString(),
|
|
5244
|
+
...summary
|
|
5245
|
+
});
|
|
5246
|
+
await this.auditIndexPersistence("sweep", [statePath(KV.bm25Index, ORPHAN_SWEEP_KEY)], summary);
|
|
5247
|
+
logger.info("index persistence: swept orphaned legacy index generations", summary);
|
|
5248
|
+
} catch (err) {
|
|
5249
|
+
logger.warn("index persistence: orphan sweep failed; the next boot retries", { message: errorMessage$2(err) });
|
|
5250
|
+
}
|
|
5251
|
+
}
|
|
5252
|
+
async namedLegacyGenerations() {
|
|
5253
|
+
const named = /* @__PURE__ */ new Set();
|
|
5254
|
+
for (const [key, label] of [[LEGACY_BM25_MANIFEST_KEY, "bm25"], [LEGACY_VECTOR_MANIFEST_KEY, "vector"]]) {
|
|
5255
|
+
const read = await this.readIndexValue(KV.bm25Index, key, label, "manifest");
|
|
5256
|
+
if (!read.ok) return null;
|
|
5257
|
+
const manifest = read.value;
|
|
5258
|
+
if (manifest == null) continue;
|
|
5259
|
+
const generations = typeof manifest === "object" && Array.isArray(manifest.shards) ? manifest.shards.filter((shard) => typeof shard?.scope === "string" && shard.scope.length > 0).map((shard) => legacyShardGeneration(shard.scope)) : [];
|
|
5260
|
+
if (typeof manifest === "object" && typeof manifest.generation === "string") generations.push(manifest.generation);
|
|
5261
|
+
const usable = generations.filter((generation) => !!generation);
|
|
5262
|
+
for (const generation of usable) named.add(generation);
|
|
5263
|
+
if (usable.length === 0) {
|
|
5264
|
+
logger.warn("index persistence: orphan sweep skipped, a legacy manifest names no generation", { key });
|
|
5265
|
+
return null;
|
|
5266
|
+
}
|
|
5267
|
+
}
|
|
5268
|
+
return named;
|
|
5269
|
+
}
|
|
5026
5270
|
logFailure(err) {
|
|
5027
5271
|
const now = this.now();
|
|
5028
5272
|
if (now - this.lastFailureLogAt < FAILURE_LOG_THROTTLE_MS) return;
|
|
@@ -5057,9 +5301,12 @@ var IndexPersistence = class {
|
|
|
5057
5301
|
result,
|
|
5058
5302
|
error
|
|
5059
5303
|
});
|
|
5304
|
+
return error === void 0;
|
|
5060
5305
|
}
|
|
5061
5306
|
async deleteShards(shards, reason) {
|
|
5062
|
-
await inBatches(shards, WRITE_CONCURRENCY, (shard) =>
|
|
5307
|
+
await inBatches(shards, WRITE_CONCURRENCY, async (shard) => {
|
|
5308
|
+
await this.deleteKey(shard.scope, shard.key, reason);
|
|
5309
|
+
});
|
|
5063
5310
|
}
|
|
5064
5311
|
async loadShardedData(legacyKey, manifestKey, label) {
|
|
5065
5312
|
const manifest = await this.readIndexValue(KV.bm25Index, manifestKey, label, "manifest");
|
|
@@ -5157,7 +5404,29 @@ async function settleWithin(work, ms) {
|
|
|
5157
5404
|
}
|
|
5158
5405
|
//#endregion
|
|
5159
5406
|
//#region src/functions/privacy.ts
|
|
5160
|
-
const
|
|
5407
|
+
const PRIVATE_TAG_TOKEN_RE = /<private(?=\s|>)[^>]*>|<\/private\s*>/gi;
|
|
5408
|
+
function redactPrivateBlocks(input) {
|
|
5409
|
+
const tokens = new RegExp(PRIVATE_TAG_TOKEN_RE.source, PRIVATE_TAG_TOKEN_RE.flags);
|
|
5410
|
+
let out = "";
|
|
5411
|
+
let cursor = 0;
|
|
5412
|
+
let depth = 0;
|
|
5413
|
+
for (let match = tokens.exec(input); match; match = tokens.exec(input)) {
|
|
5414
|
+
const closing = match[0].startsWith("</");
|
|
5415
|
+
if (depth === 0) {
|
|
5416
|
+
if (closing) continue;
|
|
5417
|
+
out += input.slice(cursor, match.index);
|
|
5418
|
+
depth = 1;
|
|
5419
|
+
} else if (closing) {
|
|
5420
|
+
depth -= 1;
|
|
5421
|
+
if (depth === 0) {
|
|
5422
|
+
out += "[REDACTED]";
|
|
5423
|
+
cursor = match.index + match[0].length;
|
|
5424
|
+
}
|
|
5425
|
+
} else depth += 1;
|
|
5426
|
+
}
|
|
5427
|
+
if (depth > 0) return `${out}[REDACTED]`;
|
|
5428
|
+
return out + input.slice(cursor);
|
|
5429
|
+
}
|
|
5161
5430
|
const SECRET_PATTERN_SOURCES = [
|
|
5162
5431
|
/(?:api[_-]?key|secret|token|password|credential|auth)[\s]*[=:]\s*["']?[A-Za-z0-9_\-/.+]{20,}["']?/gi,
|
|
5163
5432
|
/Bearer\s+[A-Za-z0-9._\-+/=]{20,}/gi,
|
|
@@ -5177,7 +5446,7 @@ const SECRET_PATTERN_SOURCES = [
|
|
|
5177
5446
|
const PRIVATE_KEY_BLOCK_RE = /-----BEGIN [A-Z0-9 ]*PRIVATE KEY(?: BLOCK)?-----[\s\S]*?(?:-----END [A-Z0-9 ]*PRIVATE KEY(?: BLOCK)?-----|$)/g;
|
|
5178
5447
|
const URL_CREDENTIALS_RE = /\b([a-z][a-z0-9+.-]*:\/\/)[^\s/?#@:"'<>]*:[^\s/?#@"'<>]+@/gi;
|
|
5179
5448
|
function stripPrivateData(input) {
|
|
5180
|
-
let result = input
|
|
5449
|
+
let result = redactPrivateBlocks(input);
|
|
5181
5450
|
result = result.replace(new RegExp(PRIVATE_KEY_BLOCK_RE.source, PRIVATE_KEY_BLOCK_RE.flags), "[REDACTED_SECRET]");
|
|
5182
5451
|
result = result.replace(new RegExp(URL_CREDENTIALS_RE.source, URL_CREDENTIALS_RE.flags), "$1[REDACTED_SECRET]@");
|
|
5183
5452
|
for (const source of SECRET_PATTERN_SOURCES) {
|
|
@@ -6151,6 +6420,9 @@ function getSynonyms(stemmedTerm) {
|
|
|
6151
6420
|
}
|
|
6152
6421
|
//#endregion
|
|
6153
6422
|
//#region src/state/search-index.ts
|
|
6423
|
+
const USER_PROMPT_TERM_CAP = 2e3;
|
|
6424
|
+
const ASSISTANT_RESPONSE_TERM_CAP = 1e3;
|
|
6425
|
+
const TOOL_OUTPUT_TERM_CAP = 1e3;
|
|
6154
6426
|
var SearchIndex = class SearchIndex {
|
|
6155
6427
|
entries = /* @__PURE__ */ new Map();
|
|
6156
6428
|
invertedIndex = /* @__PURE__ */ new Map();
|
|
@@ -6409,6 +6681,9 @@ var SearchIndex = class SearchIndex {
|
|
|
6409
6681
|
...obs.files,
|
|
6410
6682
|
obs.type
|
|
6411
6683
|
];
|
|
6684
|
+
if (obs.source?.userPrompt) parts.push(obs.source.userPrompt.slice(0, USER_PROMPT_TERM_CAP));
|
|
6685
|
+
if (obs.source?.assistantResponse) parts.push(obs.source.assistantResponse.slice(0, ASSISTANT_RESPONSE_TERM_CAP));
|
|
6686
|
+
if (obs.source?.toolName === "conversation" && typeof obs.source.toolOutput === "string") parts.push(obs.source.toolOutput.slice(0, TOOL_OUTPUT_TERM_CAP));
|
|
6412
6687
|
return this.tokenize(parts.join(" ").toLowerCase());
|
|
6413
6688
|
}
|
|
6414
6689
|
tokenize(text) {
|
|
@@ -6465,6 +6740,7 @@ function formatResult(result, format) {
|
|
|
6465
6740
|
sessionId: result.sessionId,
|
|
6466
6741
|
title: observation.title,
|
|
6467
6742
|
type: observation.type,
|
|
6743
|
+
...observation.memoryType !== void 0 && { memoryType: observation.memoryType },
|
|
6468
6744
|
score: result.score,
|
|
6469
6745
|
timestamp: observation.timestamp,
|
|
6470
6746
|
...result.layer !== void 0 && { layer: result.layer }
|
|
@@ -6485,6 +6761,7 @@ function clipResult(result, format, budget) {
|
|
|
6485
6761
|
sessionId: result.sessionId,
|
|
6486
6762
|
title: "",
|
|
6487
6763
|
type: observation.type,
|
|
6764
|
+
...observation.memoryType !== void 0 && { memoryType: observation.memoryType },
|
|
6488
6765
|
score: result.score,
|
|
6489
6766
|
timestamp: observation.timestamp,
|
|
6490
6767
|
...layer,
|
|
@@ -6506,6 +6783,7 @@ function clipResult(result, format, budget) {
|
|
|
6506
6783
|
sessionId: result.sessionId,
|
|
6507
6784
|
title,
|
|
6508
6785
|
narrative,
|
|
6786
|
+
...observation.memoryType !== void 0 && { memoryType: observation.memoryType },
|
|
6509
6787
|
score: result.score,
|
|
6510
6788
|
timestamp: observation.timestamp,
|
|
6511
6789
|
...layer,
|
|
@@ -6517,6 +6795,7 @@ function clipResult(result, format, budget) {
|
|
|
6517
6795
|
sessionId: observation.sessionId,
|
|
6518
6796
|
timestamp: observation.timestamp,
|
|
6519
6797
|
type: observation.type,
|
|
6798
|
+
...observation.memoryType !== void 0 && { memoryType: observation.memoryType },
|
|
6520
6799
|
title,
|
|
6521
6800
|
facts: [],
|
|
6522
6801
|
narrative,
|
|
@@ -6967,7 +7246,11 @@ async function indexRecords(observations, memories) {
|
|
|
6967
7246
|
id: memory.id,
|
|
6968
7247
|
revision,
|
|
6969
7248
|
sessionId: memory.sessionIds?.[0] ?? "memory",
|
|
6970
|
-
text:
|
|
7249
|
+
text: observationEmbedText({
|
|
7250
|
+
title: memory.title,
|
|
7251
|
+
narrative: memory.content,
|
|
7252
|
+
concepts: memory.concepts
|
|
7253
|
+
}),
|
|
6971
7254
|
context: {
|
|
6972
7255
|
kind: "memory",
|
|
6973
7256
|
logId: memory.id
|
|
@@ -6981,7 +7264,7 @@ async function indexRecords(observations, memories) {
|
|
|
6981
7264
|
id: obs.id,
|
|
6982
7265
|
revision,
|
|
6983
7266
|
sessionId: obs.sessionId,
|
|
6984
|
-
text: obs
|
|
7267
|
+
text: observationEmbedText(obs),
|
|
6985
7268
|
context: {
|
|
6986
7269
|
kind: "observation",
|
|
6987
7270
|
logId: obs.id
|
|
@@ -7014,21 +7297,36 @@ async function findUnindexedObservations(kv) {
|
|
|
7014
7297
|
missing
|
|
7015
7298
|
};
|
|
7016
7299
|
}
|
|
7017
|
-
const
|
|
7018
|
-
const
|
|
7300
|
+
const LIST_RETRY_ATTEMPTS = 8;
|
|
7301
|
+
const LIST_RETRY_BASE_MS = 2500;
|
|
7302
|
+
const LIST_RETRY_BUDGET_MS = 7e4;
|
|
7019
7303
|
function delay(ms) {
|
|
7020
7304
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
7021
7305
|
}
|
|
7022
|
-
|
|
7306
|
+
function isStateNotReadyError(err) {
|
|
7307
|
+
const code = err?.code;
|
|
7308
|
+
if (code === "function_not_found" || code === "invocation_timeout" || code === "timeout" || code === "ECONNRESET" || code === "ECONNREFUSED" || code === "EPIPE") return true;
|
|
7309
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
7310
|
+
return /function_not_found|Function state::\w+ not found|timed out|timeout|ETIMEDOUT|ECONNRESET|ECONNREFUSED|EPIPE|socket hang up|connection (?:closed|reset)/i.test(message);
|
|
7311
|
+
}
|
|
7312
|
+
async function listWithRetry(kv, scope, label, deadline) {
|
|
7023
7313
|
let lastErr;
|
|
7024
|
-
|
|
7025
|
-
|
|
7026
|
-
|
|
7027
|
-
|
|
7028
|
-
|
|
7314
|
+
let attempts = 0;
|
|
7315
|
+
while (attempts < LIST_RETRY_ATTEMPTS) {
|
|
7316
|
+
attempts++;
|
|
7317
|
+
try {
|
|
7318
|
+
return await kv.list(scope);
|
|
7319
|
+
} catch (err) {
|
|
7320
|
+
lastErr = err;
|
|
7321
|
+
if (!isStateNotReadyError(err)) break;
|
|
7322
|
+
const remaining = deadline - performance.now();
|
|
7323
|
+
const wait = LIST_RETRY_BASE_MS * attempts;
|
|
7324
|
+
if (attempts >= LIST_RETRY_ATTEMPTS || wait >= remaining) break;
|
|
7325
|
+
await delay(wait);
|
|
7326
|
+
}
|
|
7029
7327
|
}
|
|
7030
|
-
logger.warn(
|
|
7031
|
-
attempts
|
|
7328
|
+
logger.warn(`${label}: failed to load`, {
|
|
7329
|
+
attempts,
|
|
7032
7330
|
error: lastErr instanceof Error ? lastErr.message : String(lastErr)
|
|
7033
7331
|
});
|
|
7034
7332
|
return null;
|
|
@@ -7078,20 +7376,24 @@ async function runKeywordRebuild(kv, vectorBackfillSince) {
|
|
|
7078
7376
|
};
|
|
7079
7377
|
let documents = 0;
|
|
7080
7378
|
let memoriesLoaded = false;
|
|
7081
|
-
|
|
7082
|
-
|
|
7379
|
+
const listDeadline = performance.now() + LIST_RETRY_BUDGET_MS;
|
|
7380
|
+
const memories = await listWithRetry(kv, KV.memories, "rebuildKeywordIndex: memories", listDeadline);
|
|
7381
|
+
const sessions = await listWithRetry(kv, KV.sessions, "rebuildKeywordIndex: sessions", listDeadline);
|
|
7382
|
+
if (memories === null) bm25RebuildIncomplete = true;
|
|
7383
|
+
else {
|
|
7083
7384
|
memoriesLoaded = true;
|
|
7084
7385
|
for (const memory of memories) {
|
|
7085
7386
|
if (memory.isLatest === false) continue;
|
|
7086
7387
|
if (!memory.title || !memory.content) continue;
|
|
7087
7388
|
idx.add(memoryToObservation(memory), "memory");
|
|
7088
|
-
consider(memory.id, memory.sessionIds?.[0] ?? "memory",
|
|
7389
|
+
consider(memory.id, memory.sessionIds?.[0] ?? "memory", observationEmbedText({
|
|
7390
|
+
title: memory.title,
|
|
7391
|
+
narrative: memory.content,
|
|
7392
|
+
concepts: memory.concepts
|
|
7393
|
+
}), memory.createdAt, "memory");
|
|
7089
7394
|
documents++;
|
|
7090
7395
|
}
|
|
7091
|
-
} catch (err) {
|
|
7092
|
-
logger.warn("rebuildKeywordIndex: failed to load memories", { error: err instanceof Error ? err.message : String(err) });
|
|
7093
7396
|
}
|
|
7094
|
-
const sessions = await listSessionsWithRetry(kv);
|
|
7095
7397
|
if (sessions === null) {
|
|
7096
7398
|
bm25RebuildIncomplete = true;
|
|
7097
7399
|
if (memoriesLoaded) memoryIndexReady = true;
|
|
@@ -7115,7 +7417,7 @@ async function runKeywordRebuild(kv, vectorBackfillSince) {
|
|
|
7115
7417
|
for (const obs of results.flat()) {
|
|
7116
7418
|
if (!obs.title || !obs.narrative) continue;
|
|
7117
7419
|
idx.add(obs);
|
|
7118
|
-
consider(obs.id, obs.sessionId, obs
|
|
7420
|
+
consider(obs.id, obs.sessionId, observationEmbedText(obs), obs.timestamp, "observation");
|
|
7119
7421
|
documents++;
|
|
7120
7422
|
}
|
|
7121
7423
|
}
|
|
@@ -7222,6 +7524,7 @@ function registerSearchFunction(sdk, kv) {
|
|
|
7222
7524
|
if (data.targetLayer !== void 0 && !isSearchLayer(data.targetLayer)) throw new Error("mem::search: targetLayer must be one of 'all', 'memory', or 'observation'");
|
|
7223
7525
|
const targetLayer = data.targetLayer ?? "all";
|
|
7224
7526
|
const MAX_LIMIT = 100;
|
|
7527
|
+
const MAX_CANDIDATES = 1e3;
|
|
7225
7528
|
let effectiveLimit = 20;
|
|
7226
7529
|
if (data.limit !== void 0) {
|
|
7227
7530
|
if (!Number.isInteger(data.limit) || data.limit < 1) throw new Error("mem::search: limit must be a positive integer");
|
|
@@ -7259,7 +7562,7 @@ function registerSearchFunction(sdk, kv) {
|
|
|
7259
7562
|
await rebuildPromise;
|
|
7260
7563
|
}
|
|
7261
7564
|
const filtering = !!(projectFilter || cwdFilter || filterAgentId);
|
|
7262
|
-
|
|
7565
|
+
let fetchLimit = filtering ? Math.max(effectiveLimit * 10, 100) : effectiveLimit;
|
|
7263
7566
|
let results;
|
|
7264
7567
|
const authorizeGraph = async (record, sessionId, read) => {
|
|
7265
7568
|
if (filterAgentId !== void 0 && record.agentId !== filterAgentId) return false;
|
|
@@ -7273,25 +7576,12 @@ function registerSearchFunction(sdk, kv) {
|
|
|
7273
7576
|
}
|
|
7274
7577
|
return true;
|
|
7275
7578
|
};
|
|
7276
|
-
if (hybridRanker) try {
|
|
7277
|
-
results = (await hybridRanker(query, fetchLimit, targetLayer, authorizeGraph)).map((r) => ({
|
|
7278
|
-
obsId: r.observation.id,
|
|
7279
|
-
sessionId: r.sessionId,
|
|
7280
|
-
score: r.combinedScore,
|
|
7281
|
-
observation: r.observation,
|
|
7282
|
-
layer: r.layer
|
|
7283
|
-
}));
|
|
7284
|
-
} catch (err) {
|
|
7285
|
-
logger.warn("hybrid ranking failed, falling back to keyword search", { error: err instanceof Error ? err.message : String(err) });
|
|
7286
|
-
results = idx.search(query, fetchLimit, targetLayer);
|
|
7287
|
-
}
|
|
7288
|
-
else results = idx.search(query, fetchLimit, targetLayer);
|
|
7289
7579
|
const sessionCache = /* @__PURE__ */ new Map();
|
|
7290
7580
|
const loadSession = async (sessionId) => {
|
|
7291
7581
|
if (sessionCache.has(sessionId)) return sessionCache.get(sessionId);
|
|
7292
|
-
const
|
|
7293
|
-
sessionCache.set(sessionId,
|
|
7294
|
-
return
|
|
7582
|
+
const pending = kv.get(KV.sessions, sessionId).then((session) => session ?? null);
|
|
7583
|
+
sessionCache.set(sessionId, pending);
|
|
7584
|
+
return pending;
|
|
7295
7585
|
};
|
|
7296
7586
|
const memoryProjectCache = /* @__PURE__ */ new Map();
|
|
7297
7587
|
const loadMemoryProject = async (obsId) => {
|
|
@@ -7300,53 +7590,88 @@ function registerSearchFunction(sdk, kv) {
|
|
|
7300
7590
|
memoryProjectCache.set(obsId, proj);
|
|
7301
7591
|
return proj;
|
|
7302
7592
|
};
|
|
7303
|
-
const
|
|
7304
|
-
const
|
|
7305
|
-
|
|
7306
|
-
|
|
7307
|
-
|
|
7308
|
-
|
|
7309
|
-
if (
|
|
7310
|
-
|
|
7311
|
-
|
|
7312
|
-
|
|
7313
|
-
|
|
7314
|
-
|
|
7315
|
-
|
|
7316
|
-
}
|
|
7317
|
-
}
|
|
7318
|
-
|
|
7319
|
-
|
|
7320
|
-
|
|
7321
|
-
|
|
7322
|
-
|
|
7323
|
-
|
|
7324
|
-
|
|
7325
|
-
|
|
7326
|
-
|
|
7327
|
-
|
|
7328
|
-
|
|
7329
|
-
|
|
7330
|
-
|
|
7331
|
-
|
|
7332
|
-
|
|
7333
|
-
|
|
7334
|
-
|
|
7335
|
-
|
|
7336
|
-
|
|
7337
|
-
|
|
7338
|
-
|
|
7339
|
-
|
|
7340
|
-
|
|
7341
|
-
|
|
7342
|
-
|
|
7343
|
-
|
|
7344
|
-
|
|
7345
|
-
|
|
7346
|
-
|
|
7347
|
-
|
|
7348
|
-
|
|
7349
|
-
|
|
7593
|
+
const observationCache = /* @__PURE__ */ new Map();
|
|
7594
|
+
const hybridRequest = {};
|
|
7595
|
+
let enriched = [];
|
|
7596
|
+
let useHybridRanker = hybridRanker !== null;
|
|
7597
|
+
while (true) {
|
|
7598
|
+
hybridRequest.candidateCount = void 0;
|
|
7599
|
+
if (useHybridRanker && hybridRanker) try {
|
|
7600
|
+
results = (await hybridRanker(query, fetchLimit, targetLayer, authorizeGraph, hybridRequest)).map((r) => ({
|
|
7601
|
+
obsId: r.observation.id,
|
|
7602
|
+
sessionId: r.sessionId,
|
|
7603
|
+
score: r.combinedScore,
|
|
7604
|
+
observation: r.observation,
|
|
7605
|
+
layer: r.layer
|
|
7606
|
+
}));
|
|
7607
|
+
} catch (err) {
|
|
7608
|
+
hybridRequest.candidateCount = void 0;
|
|
7609
|
+
logger.warn("hybrid ranking failed", { error: err instanceof Error ? err.message : String(err) });
|
|
7610
|
+
if (enriched.length > 0) break;
|
|
7611
|
+
useHybridRanker = false;
|
|
7612
|
+
results = idx.search(query, fetchLimit, targetLayer);
|
|
7613
|
+
}
|
|
7614
|
+
else results = idx.search(query, fetchLimit, targetLayer);
|
|
7615
|
+
enriched = [];
|
|
7616
|
+
let batchSize = Math.min(10, effectiveLimit);
|
|
7617
|
+
for (let offset = 0; offset < Math.min(results.length, fetchLimit) && enriched.length < effectiveLimit;) {
|
|
7618
|
+
const batch = results.slice(offset, Math.min(offset + batchSize, fetchLimit));
|
|
7619
|
+
offset += batch.length;
|
|
7620
|
+
const obsResults = await Promise.all(batch.map(async (r) => {
|
|
7621
|
+
const layer = r.layer ?? idx.layerOf(r.obsId) ?? getSearchResultLayer(r.obsId, r.sessionId);
|
|
7622
|
+
if (!matchesSearchLayer(r.obsId, r.sessionId, targetLayer, layer)) return null;
|
|
7623
|
+
if (filtering) {
|
|
7624
|
+
const memoryProject = projectFilter && layer === "memory" ? await loadMemoryProject(r.obsId) : null;
|
|
7625
|
+
if (projectFilter && memoryProject !== null && memoryProject !== projectFilter) return null;
|
|
7626
|
+
const session = projectFilter || cwdFilter ? await loadSession(r.sessionId) : null;
|
|
7627
|
+
if (projectFilter && memoryProject === null && !session) return null;
|
|
7628
|
+
if (cwdFilter && layer === "observation" && !session) return null;
|
|
7629
|
+
if (session) {
|
|
7630
|
+
if (projectFilter && memoryProject === null && session.project !== projectFilter) return null;
|
|
7631
|
+
if (cwdFilter && session.cwd !== cwdFilter) return null;
|
|
7632
|
+
}
|
|
7633
|
+
}
|
|
7634
|
+
const key = JSON.stringify([r.obsId, r.sessionId]);
|
|
7635
|
+
let pending = observationCache.get(key);
|
|
7636
|
+
if (!pending) {
|
|
7637
|
+
pending = (async () => {
|
|
7638
|
+
if (r.observation) return {
|
|
7639
|
+
observation: r.observation,
|
|
7640
|
+
layer: r.layer ?? idx.layerOf(r.obsId) ?? getSearchResultLayer(r.obsId, r.sessionId)
|
|
7641
|
+
};
|
|
7642
|
+
const obs = await kv.get(KV.observations(r.sessionId), r.obsId).catch(() => null);
|
|
7643
|
+
if (obs) return {
|
|
7644
|
+
observation: obs,
|
|
7645
|
+
layer: "observation"
|
|
7646
|
+
};
|
|
7647
|
+
const mem = await kv.get(KV.memories, r.obsId).catch(() => null);
|
|
7648
|
+
return mem ? {
|
|
7649
|
+
observation: memoryToObservation(mem),
|
|
7650
|
+
layer: "memory"
|
|
7651
|
+
} : null;
|
|
7652
|
+
})();
|
|
7653
|
+
observationCache.set(key, pending);
|
|
7654
|
+
}
|
|
7655
|
+
return pending;
|
|
7656
|
+
}));
|
|
7657
|
+
for (let i = 0; i < batch.length; i++) {
|
|
7658
|
+
const resolved = obsResults[i];
|
|
7659
|
+
if (!resolved) continue;
|
|
7660
|
+
const obs = resolved.observation;
|
|
7661
|
+
if (!matchesSearchLayer(obs.id, obs.sessionId, targetLayer, resolved.layer)) continue;
|
|
7662
|
+
if (filterAgentId !== void 0 && obs.agentId !== filterAgentId) continue;
|
|
7663
|
+
if (enriched.length >= effectiveLimit) break;
|
|
7664
|
+
enriched.push({
|
|
7665
|
+
observation: withoutObservationSource(obs),
|
|
7666
|
+
layer: resolved.layer,
|
|
7667
|
+
score: batch[i].score,
|
|
7668
|
+
sessionId: batch[i].sessionId
|
|
7669
|
+
});
|
|
7670
|
+
}
|
|
7671
|
+
batchSize = Math.min(10, Math.max(effectiveLimit - enriched.length, batchSize * 2));
|
|
7672
|
+
}
|
|
7673
|
+
if (!filtering || enriched.length >= effectiveLimit || (hybridRequest.candidateCount ?? results.length) < fetchLimit || fetchLimit >= MAX_CANDIDATES) break;
|
|
7674
|
+
fetchLimit = Math.min(fetchLimit * 2, MAX_CANDIDATES);
|
|
7350
7675
|
}
|
|
7351
7676
|
const response = buildRecallResponse(enriched, format, tokenBudget);
|
|
7352
7677
|
recordAccessBatch(kv, enriched.slice(0, response.results.length).map((result) => result.observation.id));
|
|
@@ -7747,7 +8072,7 @@ async function restoreIndexEntries(obs) {
|
|
|
7747
8072
|
scheduleIndexSave();
|
|
7748
8073
|
}
|
|
7749
8074
|
const vectors = getVectorIndex();
|
|
7750
|
-
if (vectors && !vectors.has(obs.id)) await vectorIndexAddGuarded(obs.id, obs.sessionId,
|
|
8075
|
+
if (vectors && !vectors.has(obs.id)) await vectorIndexAddGuarded(obs.id, obs.sessionId, observationEmbedText(obs), {
|
|
7751
8076
|
kind: "synthetic",
|
|
7752
8077
|
logId: obs.id
|
|
7753
8078
|
});
|
|
@@ -7969,7 +8294,7 @@ function registerObserveFunction(sdk, kv, dedupMap, maxObservationsPerSession) {
|
|
|
7969
8294
|
}
|
|
7970
8295
|
getSearchIndex().add(synthetic);
|
|
7971
8296
|
scheduleIndexSave();
|
|
7972
|
-
await vectorIndexAddGuarded(synthetic.id, synthetic.sessionId,
|
|
8297
|
+
await vectorIndexAddGuarded(synthetic.id, synthetic.sessionId, observationEmbedText(synthetic), {
|
|
7973
8298
|
kind: "synthetic",
|
|
7974
8299
|
logId: synthetic.id
|
|
7975
8300
|
});
|
|
@@ -9848,7 +10173,7 @@ Rules:
|
|
|
9848
10173
|
- Be concise but preserve ALL technically relevant details
|
|
9849
10174
|
- File paths must be exact
|
|
9850
10175
|
- Importance: 1-3 for routine reads, 4-6 for edits/commands, 7-9 for architectural decisions, 10 for breaking changes
|
|
9851
|
-
- Concepts should be reusable search terms (e.g., "React hooks", "SQL migration", "auth middleware")
|
|
10176
|
+
- Concepts should be reusable search terms (e.g., "React hooks", "SQL migration", "auth middleware"); keep them in the user's language: when the source content is not English, include at least one concept in that original language, and when the observation mixes languages include both the original-language and English terms so search works in either
|
|
9852
10177
|
- Strip any secrets, tokens, or credentials from the output`;
|
|
9853
10178
|
function buildCompressionPrompt(observation) {
|
|
9854
10179
|
const parts = [`Timestamp: ${observation.timestamp}`, `Hook: ${observation.hookType}`];
|
|
@@ -10572,7 +10897,7 @@ function registerCompressFunction(sdk, kv, provider, metricsStore) {
|
|
|
10572
10897
|
retryable: false
|
|
10573
10898
|
};
|
|
10574
10899
|
const committedRevision = observationRevision(compressed);
|
|
10575
|
-
await bounded(() => vectorIndexAddGuarded(observationId, sessionId, compressed
|
|
10900
|
+
await bounded(() => vectorIndexAddGuarded(observationId, sessionId, observationEmbedText(compressed), {
|
|
10576
10901
|
kind: "observation",
|
|
10577
10902
|
logId: observationId
|
|
10578
10903
|
}, (embedding, isCurrent) => withKeyedLock(`obs:${sessionId}`, async () => {
|
|
@@ -11192,7 +11517,7 @@ function registerSummarizeFunction(sdk, kv, provider, metricsStore) {
|
|
|
11192
11517
|
return {
|
|
11193
11518
|
success: false,
|
|
11194
11519
|
error: "no_provider",
|
|
11195
|
-
reason: "No LLM provider
|
|
11520
|
+
reason: "No LLM provider chosen; Summarize is a no-op. Set AGENTMEMORY_LLM_PROVIDER and that provider's key in ~/.agentmemory/.env to enable."
|
|
11196
11521
|
};
|
|
11197
11522
|
}
|
|
11198
11523
|
const boundedProvider = {
|
|
@@ -11803,6 +12128,7 @@ function registerConsolidateFunction(sdk, kv, provider) {
|
|
|
11803
12128
|
const existingMatch = existingMemories.find((m) => m.title.toLowerCase() === parsed.title.toLowerCase() && (!scopedProject || !m.project || m.project === scopedProject));
|
|
11804
12129
|
if (existingMatch) {
|
|
11805
12130
|
existingMatch.isLatest = false;
|
|
12131
|
+
existingMatch.updatedAt = supersededAt(existingMatch.updatedAt, now);
|
|
11806
12132
|
await kv.set(KV.memories, existingMatch.id, existingMatch);
|
|
11807
12133
|
await recordAudit(kv, "evolve", "mem::consolidate", [existingMatch.id], {
|
|
11808
12134
|
action: "mark_non_latest",
|
|
@@ -12001,25 +12327,52 @@ function registerPatternsFunction(sdk, kv) {
|
|
|
12001
12327
|
}
|
|
12002
12328
|
//#endregion
|
|
12003
12329
|
//#region src/functions/facets.ts
|
|
12004
|
-
async function
|
|
12330
|
+
async function targetLiveness(kv, id) {
|
|
12331
|
+
let observation = false;
|
|
12332
|
+
if (id.startsWith("obs_")) {
|
|
12333
|
+
const sessionId = await lookupObservationSession(kv, id);
|
|
12334
|
+
observation = !!sessionId && !!await kv.get(KV.observations(sessionId), id);
|
|
12335
|
+
}
|
|
12336
|
+
if (id.startsWith("lsn_") || id.startsWith("lesson_")) {
|
|
12337
|
+
const lesson = await kv.get(KV.lessons, id);
|
|
12338
|
+
if (lesson && !lesson.deleted) return {
|
|
12339
|
+
observation,
|
|
12340
|
+
record: true
|
|
12341
|
+
};
|
|
12342
|
+
}
|
|
12343
|
+
const [mem, sem] = await Promise.all([kv.get(KV.memories, id), kv.get(KV.semantic, id)]);
|
|
12344
|
+
return {
|
|
12345
|
+
observation,
|
|
12346
|
+
record: !!(mem || sem)
|
|
12347
|
+
};
|
|
12348
|
+
}
|
|
12349
|
+
function facetTargetLive(facet, liveness) {
|
|
12350
|
+
if (!liveness) return false;
|
|
12351
|
+
if (facet.targetType === "observation") return liveness.observation;
|
|
12352
|
+
return liveness.record || liveness.observation;
|
|
12353
|
+
}
|
|
12354
|
+
async function deleteFacetsForTargets(kv, targetIds, options) {
|
|
12005
12355
|
const ids = new Set(targetIds);
|
|
12006
12356
|
ids.delete("");
|
|
12007
12357
|
if (ids.size === 0) return;
|
|
12358
|
+
const targetTypes = new Set(options?.targetTypes ?? ["memory"]);
|
|
12008
12359
|
try {
|
|
12009
|
-
const live = /* @__PURE__ */ new
|
|
12360
|
+
const live = /* @__PURE__ */ new Map();
|
|
12010
12361
|
await Promise.all([...ids].map(async (id) => {
|
|
12011
12362
|
for (let attempt = 0; attempt < 2; attempt++) try {
|
|
12012
|
-
|
|
12013
|
-
if (mem || sem) live.add(id);
|
|
12363
|
+
live.set(id, await targetLiveness(kv, id));
|
|
12014
12364
|
return;
|
|
12015
12365
|
} catch {
|
|
12016
12366
|
if (attempt === 1) {
|
|
12017
|
-
live.
|
|
12367
|
+
live.set(id, {
|
|
12368
|
+
observation: true,
|
|
12369
|
+
record: true
|
|
12370
|
+
});
|
|
12018
12371
|
logger.warn("Facet cleanup skipped unverifiable target", { targetId: id });
|
|
12019
12372
|
}
|
|
12020
12373
|
}
|
|
12021
12374
|
}));
|
|
12022
|
-
const matches = (await kv.list(KV.facets)).filter((f) => ids.has(f.targetId) &&
|
|
12375
|
+
const matches = (await kv.list(KV.facets)).filter((f) => ids.has(f.targetId) && targetTypes.has(f.targetType) && !facetTargetLive(f, live.get(f.targetId)));
|
|
12023
12376
|
await Promise.all(matches.map((f) => kv.delete(KV.facets, f.id)));
|
|
12024
12377
|
} catch (err) {
|
|
12025
12378
|
logger.warn("Facet cleanup failed", {
|
|
@@ -12295,6 +12648,7 @@ function registerRememberFunction(sdk, kv) {
|
|
|
12295
12648
|
if (data.ttlDays && typeof data.ttlDays === "number" && data.ttlDays > 0) memory.forgetAfter = new Date(Date.now() + data.ttlDays * 864e5).toISOString();
|
|
12296
12649
|
if (supersededMemory) {
|
|
12297
12650
|
supersededMemory.isLatest = false;
|
|
12651
|
+
supersededMemory.updatedAt = supersededAt(supersededMemory.updatedAt, now);
|
|
12298
12652
|
await kv.set(KV.memories, supersededMemory.id, supersededMemory);
|
|
12299
12653
|
try {
|
|
12300
12654
|
getSearchIndex().remove(supersededMemory.id);
|
|
@@ -12317,7 +12671,11 @@ function registerRememberFunction(sdk, kv) {
|
|
|
12317
12671
|
nearMatch
|
|
12318
12672
|
};
|
|
12319
12673
|
});
|
|
12320
|
-
await vectorIndexAddGuarded(memory.id, memory.sessionIds?.[0] ?? "memory",
|
|
12674
|
+
await vectorIndexAddGuarded(memory.id, memory.sessionIds?.[0] ?? "memory", observationEmbedText({
|
|
12675
|
+
title: memory.title,
|
|
12676
|
+
narrative: memory.content,
|
|
12677
|
+
concepts: memory.concepts
|
|
12678
|
+
}), {
|
|
12321
12679
|
kind: "memory",
|
|
12322
12680
|
logId: memory.id
|
|
12323
12681
|
}, (embedding, isCurrent) => withKeyedLock("mem:remember", async () => {
|
|
@@ -12459,6 +12817,7 @@ function registerRememberFunction(sdk, kv) {
|
|
|
12459
12817
|
}
|
|
12460
12818
|
});
|
|
12461
12819
|
}
|
|
12820
|
+
if (deletedObservationIds.length > 0) await deleteFacetsForTargets(kv, deletedObservationIds, { targetTypes: ["memory", "observation"] });
|
|
12462
12821
|
if (deleted > 0 || indexCleaned) await flushIndexSave();
|
|
12463
12822
|
if (deleted > 0 || failures.length > 0) await recordAudit(kv, "forget", "mem::forget", [...deletedMemoryIds, ...deletedObservationIds], {
|
|
12464
12823
|
sessionId: data.sessionId,
|
|
@@ -12899,6 +13258,7 @@ function registerRelationsFunction(sdk, kv) {
|
|
|
12899
13258
|
isLatest: true
|
|
12900
13259
|
};
|
|
12901
13260
|
existing.isLatest = false;
|
|
13261
|
+
existing.updatedAt = supersededAt(existing.updatedAt, now);
|
|
12902
13262
|
await kv.set(KV.memories, existing.id, existing);
|
|
12903
13263
|
try {
|
|
12904
13264
|
getSearchIndex().remove(existing.id);
|
|
@@ -12919,7 +13279,11 @@ function registerRelationsFunction(sdk, kv) {
|
|
|
12919
13279
|
error: err instanceof Error ? err.message : String(err)
|
|
12920
13280
|
});
|
|
12921
13281
|
}
|
|
12922
|
-
await vectorIndexAddGuarded(evolved.id, evolved.sessionIds?.[0] ?? "memory",
|
|
13282
|
+
await vectorIndexAddGuarded(evolved.id, evolved.sessionIds?.[0] ?? "memory", observationEmbedText({
|
|
13283
|
+
title: evolved.title,
|
|
13284
|
+
narrative: evolved.content,
|
|
13285
|
+
concepts: evolved.concepts
|
|
13286
|
+
}), {
|
|
12923
13287
|
kind: "memory",
|
|
12924
13288
|
logId: evolved.id
|
|
12925
13289
|
});
|
|
@@ -13094,7 +13458,7 @@ async function findByKeyword(kv, keyword, project) {
|
|
|
13094
13458
|
}
|
|
13095
13459
|
//#endregion
|
|
13096
13460
|
//#region src/version.ts
|
|
13097
|
-
const VERSION = "0.9.
|
|
13461
|
+
const VERSION = "0.9.32";
|
|
13098
13462
|
const III_PINNED_VERSION = "0.25.0";
|
|
13099
13463
|
//#endregion
|
|
13100
13464
|
//#region src/telemetry/setup.ts
|
|
@@ -13166,6 +13530,7 @@ function getFollowupStats() {
|
|
|
13166
13530
|
};
|
|
13167
13531
|
}
|
|
13168
13532
|
const LESSON_CONTENT_PREVIEW_CHARS = 240;
|
|
13533
|
+
const SCOPED_RETRY_CANDIDATES = 1e3;
|
|
13169
13534
|
function registerSmartSearchFunction(sdk, kv, searchFn) {
|
|
13170
13535
|
sdk.registerFunction("mem::smart-search", async (data) => {
|
|
13171
13536
|
if (data.targetLayer !== void 0 && !isSearchLayer(data.targetLayer)) throw new Error("mem::smart-search: targetLayer must be one of 'all', 'memory', or 'observation'");
|
|
@@ -13242,16 +13607,32 @@ function registerSmartSearchFunction(sdk, kv, searchFn) {
|
|
|
13242
13607
|
return (await read(KV.sessions, sessionId))?.project === project;
|
|
13243
13608
|
};
|
|
13244
13609
|
const [hybridResults, lessons] = await Promise.all([searchFn(data.query, overFetchLimit, targetLayer, authorizeGraph), includeLessons ? recallLessons(sdk, data.query, lessonLimit, project) : Promise.resolve([])]);
|
|
13245
|
-
const
|
|
13246
|
-
|
|
13247
|
-
|
|
13248
|
-
|
|
13249
|
-
|
|
13250
|
-
|
|
13251
|
-
|
|
13252
|
-
|
|
13253
|
-
|
|
13254
|
-
|
|
13610
|
+
const scopeResults = async (results) => {
|
|
13611
|
+
const layerResults = results.filter((result) => matchesSearchLayer(result.observation.id, result.sessionId, targetLayer, result.layer ?? getSearchIndex().layerOf(result.observation.id)));
|
|
13612
|
+
const projectResults = matchesProject ? await filterProjectResults(layerResults, (result) => matchesProject(result.observation.id, result.sessionId)) : layerResults;
|
|
13613
|
+
return filterAgentId ? projectResults.filter((r) => r.observation.agentId === filterAgentId).slice(0, limit) : projectResults.slice(0, limit);
|
|
13614
|
+
};
|
|
13615
|
+
let filteredHybrid = await scopeResults(hybridResults);
|
|
13616
|
+
if ((filterAgentId || project) && filteredHybrid.length < limit && getSearchIndex().size > overFetchLimit) try {
|
|
13617
|
+
const deeper = await scopeResults(await searchFn(data.query, SCOPED_RETRY_CANDIDATES, targetLayer, authorizeGraph));
|
|
13618
|
+
const seen = new Set(deeper.map((result) => result.observation.id));
|
|
13619
|
+
filteredHybrid = [...deeper, ...filteredHybrid.filter((result) => !seen.has(result.observation.id))].slice(0, limit);
|
|
13620
|
+
} catch (err) {
|
|
13621
|
+
logger.warn("Smart search deeper scoped ranking failed; keeping first-pass results", { error: err instanceof Error ? err.message : String(err) });
|
|
13622
|
+
}
|
|
13623
|
+
const compact = await Promise.all(filteredHybrid.map(async (r) => {
|
|
13624
|
+
const layer = r.layer ?? getSearchIndex().layerOf(r.observation.id) ?? getSearchResultLayer(r.observation.id, r.sessionId);
|
|
13625
|
+
const memoryType = layer === "memory" ? (await kv.get(KV.memories, r.observation.id).catch(() => null))?.type ?? r.observation.memoryType : void 0;
|
|
13626
|
+
return {
|
|
13627
|
+
obsId: r.observation.id,
|
|
13628
|
+
layer,
|
|
13629
|
+
sessionId: r.sessionId,
|
|
13630
|
+
title: r.observation.title,
|
|
13631
|
+
type: r.observation.type,
|
|
13632
|
+
...memoryType !== void 0 && { memoryType },
|
|
13633
|
+
score: r.combinedScore,
|
|
13634
|
+
timestamp: r.observation.timestamp
|
|
13635
|
+
};
|
|
13255
13636
|
}));
|
|
13256
13637
|
recordAccessBatch(kv, compact.map((r) => r.obsId));
|
|
13257
13638
|
if (data.sessionId && typeof data.sessionId === "string" && data.source !== "viewer" && compact.length > 0) {
|
|
@@ -13452,6 +13833,7 @@ function registerRecentSearchesSweepFunction(sdk, kv) {
|
|
|
13452
13833
|
const HOUR_MS = 3600 * 1e3;
|
|
13453
13834
|
function isStaleActive(session, cutoff) {
|
|
13454
13835
|
if (!session || session.status !== "active") return false;
|
|
13836
|
+
if (typeof session.id !== "string" || session.id.length === 0) return false;
|
|
13455
13837
|
const freshness = session.updatedAt ?? session.startedAt;
|
|
13456
13838
|
if (!freshness) return false;
|
|
13457
13839
|
const ts = new Date(freshness).getTime();
|
|
@@ -13747,6 +14129,7 @@ function registerAutoForgetFunction(sdk, kv) {
|
|
|
13747
14129
|
if (!dryRun) {
|
|
13748
14130
|
const older = new Date(memA.createdAt).getTime() < new Date(memB.createdAt).getTime() ? memA : memB;
|
|
13749
14131
|
older.isLatest = false;
|
|
14132
|
+
older.updatedAt = supersededAt(older.updatedAt, (/* @__PURE__ */ new Date()).toISOString());
|
|
13750
14133
|
await kv.set(KV.memories, older.id, older);
|
|
13751
14134
|
await recordAudit(kv, "forget", "mem::auto-forget", [older.id], {
|
|
13752
14135
|
resource: "memory",
|
|
@@ -14269,6 +14652,7 @@ function registerLessonsFunctions(sdk, kv) {
|
|
|
14269
14652
|
lessonRecords.delete(lesson.id);
|
|
14270
14653
|
if (lessonIndex) lessonIndex.remove(lesson.id);
|
|
14271
14654
|
noteLessonMutation();
|
|
14655
|
+
await deleteFacetsForTargets(kv, [lesson.id]);
|
|
14272
14656
|
try {
|
|
14273
14657
|
await recordAudit(kv, "lesson_delete", "mem::lesson-delete", [lesson.id]);
|
|
14274
14658
|
} catch {}
|
|
@@ -14314,11 +14698,14 @@ function registerLessonsFunctions(sdk, kv) {
|
|
|
14314
14698
|
}
|
|
14315
14699
|
}
|
|
14316
14700
|
await Promise.all(dirty.map((l) => kv.set(KV.lessons, l.id, l)));
|
|
14701
|
+
const softDeletedIds = [];
|
|
14317
14702
|
for (const l of dirty) if (l.deleted) {
|
|
14703
|
+
softDeletedIds.push(l.id);
|
|
14318
14704
|
lessonRecords.delete(l.id);
|
|
14319
14705
|
if (lessonIndex) lessonIndex.remove(l.id);
|
|
14320
14706
|
} else lessonRecords.set(l.id, l);
|
|
14321
14707
|
if (dirty.length > 0) noteLessonMutation();
|
|
14708
|
+
if (softDeletedIds.length > 0) await deleteFacetsForTargets(kv, softDeletedIds);
|
|
14322
14709
|
await Promise.all(auditEvents.map((event) => recordAudit(kv, "lesson_strengthen", "mem::lesson-decay-sweep", [event.id], {
|
|
14323
14710
|
action: event.action,
|
|
14324
14711
|
actor: "system",
|
|
@@ -14567,7 +14954,8 @@ function registerExportImportFunction(sdk, state) {
|
|
|
14567
14954
|
"0.9.28",
|
|
14568
14955
|
"0.9.29",
|
|
14569
14956
|
"0.9.30",
|
|
14570
|
-
"0.9.31"
|
|
14957
|
+
"0.9.31",
|
|
14958
|
+
"0.9.32"
|
|
14571
14959
|
]).has(importData.version)) return {
|
|
14572
14960
|
success: false,
|
|
14573
14961
|
error: `Unsupported export version: ${importData.version}`
|
|
@@ -17034,7 +17422,7 @@ function registerConsolidationPipelineFunction(sdk, kv, provider) {
|
|
|
17034
17422
|
if (!data?.force && !isConsolidationEnabled()) return {
|
|
17035
17423
|
success: false,
|
|
17036
17424
|
skipped: true,
|
|
17037
|
-
reason: "Consolidation disabled: set CONSOLIDATION_ENABLED=true or
|
|
17425
|
+
reason: "Consolidation disabled: set CONSOLIDATION_ENABLED=true or choose an LLM provider (AGENTMEMORY_LLM_PROVIDER=anthropic, openai, gemini, openrouter or minimax with that provider's key, or AGENTMEMORY_ALLOW_AGENT_SDK=true). A provider key alone does not turn on LLM use."
|
|
17038
17426
|
};
|
|
17039
17427
|
const tier = data?.tier || "all";
|
|
17040
17428
|
const decayDays = getConsolidationDecayDays();
|
|
@@ -24106,7 +24494,7 @@ const FUNCTION_FIXES = {
|
|
|
24106
24494
|
"mem::compress": "Compression calls your LLM provider. Check the provider key and model, or set AGENTMEMORY_AUTO_COMPRESS=false to use zero-LLM compression.",
|
|
24107
24495
|
"mem::graph-extract": "Graph extraction calls your LLM provider. Check the provider key and model, or set GRAPH_EXTRACTION_ENABLED=false."
|
|
24108
24496
|
};
|
|
24109
|
-
const LLM_KEY_FIX = "
|
|
24497
|
+
const LLM_KEY_FIX = "Set AGENTMEMORY_LLM_PROVIDER and that provider's key (for example AGENTMEMORY_LLM_PROVIDER=anthropic and ANTHROPIC_API_KEY) in ~/.agentmemory/.env and restart.";
|
|
24110
24498
|
const OFF_WITHOUT_LLM = [
|
|
24111
24499
|
"session summaries",
|
|
24112
24500
|
"consolidation into semantic facts and procedures",
|
|
@@ -24215,8 +24603,17 @@ function evaluateStatus(input) {
|
|
|
24215
24603
|
level: "info",
|
|
24216
24604
|
code: "no-llm-provider",
|
|
24217
24605
|
message: "No LLM provider is configured, so summaries, consolidation and graph extraction stay off. Search still works.",
|
|
24218
|
-
fix:
|
|
24606
|
+
fix: input.providerHint || "Set AGENTMEMORY_LLM_PROVIDER and that provider's key (for example AGENTMEMORY_LLM_PROVIDER=anthropic and ANTHROPIC_API_KEY) in ~/.agentmemory/.env and restart."
|
|
24219
24607
|
});
|
|
24608
|
+
if (input.embeddingHint) {
|
|
24609
|
+
const keyMissing = input.embeddingHint.startsWith("EMBEDDING_PROVIDER=");
|
|
24610
|
+
problems.push({
|
|
24611
|
+
level: "info",
|
|
24612
|
+
code: keyMissing ? "embedding-provider-key-missing" : "embedding-provider-not-chosen",
|
|
24613
|
+
message: keyMissing ? "EMBEDDING_PROVIDER names a cloud provider whose key is missing, so search is keyword and graph only." : "A cloud embedding key is present, but no embedding provider is chosen, so search is keyword and graph only.",
|
|
24614
|
+
fix: input.embeddingHint
|
|
24615
|
+
});
|
|
24616
|
+
}
|
|
24220
24617
|
const noLlm = input.provider === "noop";
|
|
24221
24618
|
const functions = markLlmFunctions(input.functionMetrics, input.provider).map((m) => ({
|
|
24222
24619
|
...m,
|
|
@@ -24397,7 +24794,9 @@ function evaluateStatus(input) {
|
|
|
24397
24794
|
provider: {
|
|
24398
24795
|
llm: input.provider,
|
|
24399
24796
|
route: input.provider === "noop" ? null : input.llmRoute ?? null,
|
|
24797
|
+
hint: input.providerHint ?? null,
|
|
24400
24798
|
embeddings: input.embeddingProvider,
|
|
24799
|
+
embeddingHint: input.embeddingHint ?? null,
|
|
24401
24800
|
circuitBreaker: input.circuitBreaker,
|
|
24402
24801
|
offWithoutLlm: noLlm ? OFF_WITHOUT_LLM : []
|
|
24403
24802
|
},
|
|
@@ -25694,7 +26093,9 @@ function createStatusReporter(sdk, kv, deps) {
|
|
|
25694
26093
|
llmRoute: describeLlmRoute(config.provider),
|
|
25695
26094
|
functionMetrics: functionMetrics ?? [],
|
|
25696
26095
|
provider: detectLlmProviderKind(),
|
|
26096
|
+
providerHint: llmOptInHint(),
|
|
25697
26097
|
embeddingProvider: describeEmbeddingProvider(),
|
|
26098
|
+
embeddingHint: embeddingOptInHint(),
|
|
25698
26099
|
flags: buildConfigFlags(),
|
|
25699
26100
|
index: {
|
|
25700
26101
|
bm25Documents: idx.size,
|
|
@@ -25839,13 +26240,18 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
25839
26240
|
sdk.registerFunction("api::config-flags", async (req) => {
|
|
25840
26241
|
const authErr = checkAuth(req, secret);
|
|
25841
26242
|
if (authErr) return authErr;
|
|
26243
|
+
const providerKind = detectLlmProviderKind();
|
|
26244
|
+
const embeddingProvider = describeEmbeddingProvider();
|
|
26245
|
+
const flags = buildConfigFlags();
|
|
25842
26246
|
return {
|
|
25843
26247
|
status_code: 200,
|
|
25844
26248
|
body: {
|
|
25845
26249
|
version: VERSION,
|
|
25846
|
-
provider:
|
|
25847
|
-
|
|
25848
|
-
|
|
26250
|
+
provider: providerKind,
|
|
26251
|
+
providerHint: llmOptInHint(),
|
|
26252
|
+
embeddingProvider,
|
|
26253
|
+
embeddingHint: embeddingOptInHint(),
|
|
26254
|
+
flags
|
|
25849
26255
|
}
|
|
25850
26256
|
};
|
|
25851
26257
|
});
|
|
@@ -29696,6 +30102,45 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
29696
30102
|
http_method: "POST"
|
|
29697
30103
|
}
|
|
29698
30104
|
});
|
|
30105
|
+
sdk.registerFunction("api::lesson-get", async (req) => {
|
|
30106
|
+
const denied = checkAuth(req, secret);
|
|
30107
|
+
if (denied) return denied;
|
|
30108
|
+
const body = req.body;
|
|
30109
|
+
const lessonId = typeof body?.lessonId === "string" ? body.lessonId.trim() : "";
|
|
30110
|
+
if (!lessonId) return {
|
|
30111
|
+
status_code: 400,
|
|
30112
|
+
body: { error: "lessonId is required" }
|
|
30113
|
+
};
|
|
30114
|
+
if (body.project !== void 0 && typeof body.project !== "string") return {
|
|
30115
|
+
status_code: 400,
|
|
30116
|
+
body: { error: "project must be a string" }
|
|
30117
|
+
};
|
|
30118
|
+
const project = typeof body.project === "string" ? body.project.trim() : "";
|
|
30119
|
+
const lesson = await kv.get(KV.lessons, lessonId);
|
|
30120
|
+
if (!lesson || lesson.deleted) return {
|
|
30121
|
+
status_code: 404,
|
|
30122
|
+
body: { error: "lesson not found" }
|
|
30123
|
+
};
|
|
30124
|
+
if (lesson.project !== void 0 && lesson.project !== project) return {
|
|
30125
|
+
status_code: 404,
|
|
30126
|
+
body: { error: "lesson not found" }
|
|
30127
|
+
};
|
|
30128
|
+
return {
|
|
30129
|
+
status_code: 200,
|
|
30130
|
+
body: {
|
|
30131
|
+
success: true,
|
|
30132
|
+
lesson
|
|
30133
|
+
}
|
|
30134
|
+
};
|
|
30135
|
+
});
|
|
30136
|
+
sdk.registerTrigger({
|
|
30137
|
+
type: "http",
|
|
30138
|
+
function_id: "api::lesson-get",
|
|
30139
|
+
config: {
|
|
30140
|
+
api_path: "/agentmemory/lessons/get",
|
|
30141
|
+
http_method: "POST"
|
|
30142
|
+
}
|
|
30143
|
+
});
|
|
29699
30144
|
sdk.registerFunction("api::lesson-strengthen", async (req) => {
|
|
29700
30145
|
const denied = checkAuth(req, secret);
|
|
29701
30146
|
if (denied) return denied;
|
|
@@ -30461,6 +30906,59 @@ function registerViewerSse(sdk) {
|
|
|
30461
30906
|
}
|
|
30462
30907
|
//#endregion
|
|
30463
30908
|
//#region src/mcp/tools-registry.ts
|
|
30909
|
+
const READ_ONLY_TOOLS = new Set([
|
|
30910
|
+
"memory_recall",
|
|
30911
|
+
"memory_file_history",
|
|
30912
|
+
"memory_patterns",
|
|
30913
|
+
"memory_sessions",
|
|
30914
|
+
"memory_smart_search",
|
|
30915
|
+
"memory_vision_search",
|
|
30916
|
+
"memory_timeline",
|
|
30917
|
+
"memory_export",
|
|
30918
|
+
"memory_relations",
|
|
30919
|
+
"memory_commit_lookup",
|
|
30920
|
+
"memory_commits",
|
|
30921
|
+
"memory_graph_query",
|
|
30922
|
+
"memory_team_feed",
|
|
30923
|
+
"memory_audit",
|
|
30924
|
+
"memory_frontier",
|
|
30925
|
+
"memory_next",
|
|
30926
|
+
"memory_diagnose",
|
|
30927
|
+
"memory_facet_query",
|
|
30928
|
+
"memory_verify",
|
|
30929
|
+
"memory_lesson_recall",
|
|
30930
|
+
"memory_insight_list",
|
|
30931
|
+
"memory_slot_list",
|
|
30932
|
+
"memory_slot_get"
|
|
30933
|
+
]);
|
|
30934
|
+
const DESTRUCTIVE_TOOLS = new Set([
|
|
30935
|
+
"memory_action_update",
|
|
30936
|
+
"memory_compress_file",
|
|
30937
|
+
"memory_claude_bridge_sync",
|
|
30938
|
+
"memory_governance_delete",
|
|
30939
|
+
"memory_heal",
|
|
30940
|
+
"memory_lesson_delete",
|
|
30941
|
+
"memory_obsidian_export",
|
|
30942
|
+
"memory_slot_replace",
|
|
30943
|
+
"memory_slot_delete"
|
|
30944
|
+
]);
|
|
30945
|
+
const IDEMPOTENT_WRITE_TOOLS = new Set([
|
|
30946
|
+
"memory_governance_delete",
|
|
30947
|
+
"memory_lesson_delete",
|
|
30948
|
+
"memory_slot_replace",
|
|
30949
|
+
"memory_slot_delete",
|
|
30950
|
+
"memory_claude_bridge_sync"
|
|
30951
|
+
]);
|
|
30952
|
+
const OPEN_WORLD_TOOLS = new Set(["memory_mesh_sync"]);
|
|
30953
|
+
function toolAnnotations(name) {
|
|
30954
|
+
const readOnly = READ_ONLY_TOOLS.has(name);
|
|
30955
|
+
return {
|
|
30956
|
+
readOnlyHint: readOnly,
|
|
30957
|
+
destructiveHint: !readOnly && DESTRUCTIVE_TOOLS.has(name),
|
|
30958
|
+
idempotentHint: readOnly || IDEMPOTENT_WRITE_TOOLS.has(name),
|
|
30959
|
+
openWorldHint: OPEN_WORLD_TOOLS.has(name)
|
|
30960
|
+
};
|
|
30961
|
+
}
|
|
30464
30962
|
const CORE_TOOLS = [
|
|
30465
30963
|
{
|
|
30466
30964
|
name: "memory_recall",
|
|
@@ -31441,6 +31939,11 @@ const V070_TOOLS = [
|
|
|
31441
31939
|
tags: {
|
|
31442
31940
|
type: "string",
|
|
31443
31941
|
description: "Comma-separated tags"
|
|
31942
|
+
},
|
|
31943
|
+
sourceIds: {
|
|
31944
|
+
type: "array",
|
|
31945
|
+
items: { type: "string" },
|
|
31946
|
+
description: "Ids of the session, memory, observation or crystal this lesson came from (max 50)"
|
|
31444
31947
|
}
|
|
31445
31948
|
},
|
|
31446
31949
|
required: ["content"]
|
|
@@ -31663,7 +32166,10 @@ function getAllTools() {
|
|
|
31663
32166
|
...V070_TOOLS,
|
|
31664
32167
|
...V073_TOOLS,
|
|
31665
32168
|
...V010_SLOTS_TOOLS
|
|
31666
|
-
]
|
|
32169
|
+
].map((tool) => ({
|
|
32170
|
+
...tool,
|
|
32171
|
+
annotations: toolAnnotations(tool.name)
|
|
32172
|
+
}));
|
|
31667
32173
|
}
|
|
31668
32174
|
function getVisibleTools() {
|
|
31669
32175
|
if ((process.env["AGENTMEMORY_TOOLS"] || "all") === "core") return getAllTools().filter((t) => ESSENTIAL_TOOLS.has(t.name));
|
|
@@ -31719,13 +32225,18 @@ function parseCsvList(value) {
|
|
|
31719
32225
|
if (Array.isArray(value)) return value.map((v) => typeof v === "string" ? v.trim() : "").filter(Boolean);
|
|
31720
32226
|
return [];
|
|
31721
32227
|
}
|
|
32228
|
+
const SLOT_TOOL_NAMES = new Set(getAllTools().filter((t) => t.name.startsWith("memory_slot_")).map((t) => t.name));
|
|
32229
|
+
function isSlotTool(name) {
|
|
32230
|
+
return SLOT_TOOL_NAMES.has(name);
|
|
32231
|
+
}
|
|
31722
32232
|
function registerMcpEndpoints(sdk, kv, secret) {
|
|
32233
|
+
const slotsEnabled = isSlotsEnabled();
|
|
31723
32234
|
sdk.registerFunction("mcp::tools::list", async (req) => {
|
|
31724
32235
|
const authErr = checkAuth(req, secret);
|
|
31725
32236
|
if (authErr) return authErr;
|
|
31726
32237
|
return {
|
|
31727
32238
|
status_code: 200,
|
|
31728
|
-
body: { tools: getVisibleTools() }
|
|
32239
|
+
body: { tools: getVisibleTools().filter((t) => slotsEnabled || !isSlotTool(t.name)) }
|
|
31729
32240
|
};
|
|
31730
32241
|
});
|
|
31731
32242
|
sdk.registerTrigger({
|
|
@@ -31744,6 +32255,7 @@ function registerMcpEndpoints(sdk, kv, secret) {
|
|
|
31744
32255
|
body: { error: "name is required" }
|
|
31745
32256
|
};
|
|
31746
32257
|
const { name, arguments: args = {} } = req.body;
|
|
32258
|
+
if (isSlotTool(name) && !slotsEnabled) return slotsDisabledResponse();
|
|
31747
32259
|
try {
|
|
31748
32260
|
switch (name) {
|
|
31749
32261
|
case "memory_recall": {
|
|
@@ -32765,6 +33277,11 @@ function registerMcpEndpoints(sdk, kv, secret) {
|
|
|
32765
33277
|
body: { error: "content is required" }
|
|
32766
33278
|
};
|
|
32767
33279
|
const lessonTags = typeof args.tags === "string" && args.tags.trim() ? args.tags.split(",").map((t) => t.trim()).filter(Boolean) : [];
|
|
33280
|
+
const lessonSourceIds = normalizeLessonSourceIds(args.sourceIds);
|
|
33281
|
+
if (!lessonSourceIds) return {
|
|
33282
|
+
status_code: 400,
|
|
33283
|
+
body: { error: `sourceIds must be an array of at most 50 ids (session, memory, observation or crystal ids) without spaces` }
|
|
33284
|
+
};
|
|
32768
33285
|
const lessonSaveResult = await sdk.trigger({
|
|
32769
33286
|
function_id: "mem::lesson-save",
|
|
32770
33287
|
payload: {
|
|
@@ -32773,7 +33290,8 @@ function registerMcpEndpoints(sdk, kv, secret) {
|
|
|
32773
33290
|
confidence: args.confidence,
|
|
32774
33291
|
project: args.project,
|
|
32775
33292
|
tags: lessonTags,
|
|
32776
|
-
source: "manual"
|
|
33293
|
+
source: "manual",
|
|
33294
|
+
sourceIds: lessonSourceIds
|
|
32777
33295
|
}
|
|
32778
33296
|
});
|
|
32779
33297
|
return {
|
|
@@ -33195,24 +33713,16 @@ function registerMcpEndpoints(sdk, kv, secret) {
|
|
|
33195
33713
|
};
|
|
33196
33714
|
}
|
|
33197
33715
|
if (uri === "agentmemory://graph/stats") try {
|
|
33198
|
-
const
|
|
33199
|
-
|
|
33200
|
-
|
|
33201
|
-
|
|
33202
|
-
for (const n of nodes) nodesByType[n.type] = (nodesByType[n.type] || 0) + 1;
|
|
33203
|
-
const edgesByType = {};
|
|
33204
|
-
for (const e of edges) edgesByType[e.type] = (edgesByType[e.type] || 0) + 1;
|
|
33716
|
+
const stats = await sdk.trigger({
|
|
33717
|
+
function_id: "mem::graph-stats",
|
|
33718
|
+
payload: {}
|
|
33719
|
+
});
|
|
33205
33720
|
return {
|
|
33206
33721
|
status_code: 200,
|
|
33207
33722
|
body: { contents: [{
|
|
33208
33723
|
uri,
|
|
33209
33724
|
mimeType: "application/json",
|
|
33210
|
-
text: JSON.stringify(
|
|
33211
|
-
totalNodes: nodes.length,
|
|
33212
|
-
totalEdges: edges.length,
|
|
33213
|
-
nodesByType,
|
|
33214
|
-
edgesByType
|
|
33215
|
-
})
|
|
33725
|
+
text: JSON.stringify(stats)
|
|
33216
33726
|
}] }
|
|
33217
33727
|
};
|
|
33218
33728
|
} catch {
|
|
@@ -33222,8 +33732,15 @@ function registerMcpEndpoints(sdk, kv, secret) {
|
|
|
33222
33732
|
uri,
|
|
33223
33733
|
mimeType: "application/json",
|
|
33224
33734
|
text: JSON.stringify({
|
|
33225
|
-
totalNodes:
|
|
33226
|
-
totalEdges:
|
|
33735
|
+
totalNodes: null,
|
|
33736
|
+
totalEdges: null,
|
|
33737
|
+
nodesByType: {},
|
|
33738
|
+
edgesByType: {},
|
|
33739
|
+
fromSnapshot: false,
|
|
33740
|
+
fromIndex: false,
|
|
33741
|
+
available: false,
|
|
33742
|
+
coverage: "unknown",
|
|
33743
|
+
warning: "Graph stats are unavailable. Check agentmemory graph-index status."
|
|
33227
33744
|
})
|
|
33228
33745
|
}] }
|
|
33229
33746
|
};
|
|
@@ -33652,14 +34169,18 @@ async function main() {
|
|
|
33652
34169
|
const config = loadConfig();
|
|
33653
34170
|
const embeddingConfig = loadEmbeddingConfig();
|
|
33654
34171
|
const fallbackConfig = loadFallbackConfig();
|
|
33655
|
-
const provider = fallbackConfig.providers.length > 0 ? createFallbackProvider(config.provider, fallbackConfig) : createProvider(config.provider);
|
|
34172
|
+
const provider = fallbackConfig.providers.length > 0 && config.provider.provider !== "noop" ? createFallbackProvider(config.provider, fallbackConfig) : createProvider(config.provider);
|
|
33656
34173
|
const embeddingProvider = createEmbeddingProvider();
|
|
33657
34174
|
const imageEmbeddingProvider = createImageEmbeddingProvider();
|
|
33658
34175
|
bootLog(`Starting worker v${VERSION}...`);
|
|
33659
34176
|
bootLog(`Engine: ${config.engineUrl}`);
|
|
33660
34177
|
bootLog(`Provider: ${config.provider.provider} (${config.provider.model})`);
|
|
33661
34178
|
if (embeddingProvider) bootLog(`Embedding provider: ${embeddingProvider.name} (${embeddingProvider.dimensions} dims)`);
|
|
33662
|
-
else
|
|
34179
|
+
else {
|
|
34180
|
+
bootLog(`Embedding provider: none (BM25-only mode)`);
|
|
34181
|
+
const embeddingHint = embeddingOptInHint();
|
|
34182
|
+
if (embeddingHint) bootLog(embeddingHint);
|
|
34183
|
+
}
|
|
33663
34184
|
if (imageEmbeddingProvider) bootLog(`Image embedding provider: ${imageEmbeddingProvider.name} (${imageEmbeddingProvider.dimensions} dims) — vision-search active`);
|
|
33664
34185
|
bootLog(`REST API: http://localhost:${config.restPort}/agentmemory/*`);
|
|
33665
34186
|
const sdk = withWriteScrubbing(registerWorker(config.engineUrl, {
|
|
@@ -33682,6 +34203,9 @@ async function main() {
|
|
|
33682
34203
|
stateBackend = getStateBackend();
|
|
33683
34204
|
} catch {}
|
|
33684
34205
|
const kv = new StateKV(sdk, { backend: stateBackend });
|
|
34206
|
+
const stateReadiness = await waitForStateReady(sdk, { onWaiting: (error) => bootWarn(`Waiting for the state worker to register state::get and state::list (${error})`) });
|
|
34207
|
+
assertStateReady(stateReadiness);
|
|
34208
|
+
if (stateReadiness.attempts > 1) bootLog(`State worker ready after ${stateReadiness.waitedMs}ms (${stateReadiness.attempts} attempts)`);
|
|
33685
34209
|
const secret = resolveWorkerSecret();
|
|
33686
34210
|
const metricsStore = new MetricsStore(kv);
|
|
33687
34211
|
const dedupMap = new DedupMap();
|
|
@@ -33786,7 +34310,7 @@ async function main() {
|
|
|
33786
34310
|
const bm25Index = getSearchIndex();
|
|
33787
34311
|
const graphWeight = parseFloat(getEnvVar("AGENTMEMORY_GRAPH_WEIGHT") || "0.3");
|
|
33788
34312
|
const hybridSearch = new HybridSearch(bm25Index, vectorIndex, embeddingProvider, kv, embeddingConfig.bm25Weight, embeddingConfig.vectorWeight, graphWeight);
|
|
33789
|
-
const hybridRanker = (query, limit, targetLayer, authorizeGraph) => hybridSearch.search(query, limit, targetLayer, authorizeGraph);
|
|
34313
|
+
const hybridRanker = (query, limit, targetLayer, authorizeGraph, request) => hybridSearch.search(query, limit, targetLayer, authorizeGraph, request);
|
|
33790
34314
|
registerSmartSearchFunction(sdk, kv, hybridRanker);
|
|
33791
34315
|
setHybridRanker(hybridRanker);
|
|
33792
34316
|
registerRecentSearchesSweepFunction(sdk, kv);
|
|
@@ -33884,7 +34408,7 @@ async function main() {
|
|
|
33884
34408
|
console.warn(`[agentmemory] Failed to rebuild the BM25 index:`, err);
|
|
33885
34409
|
}
|
|
33886
34410
|
bootLog(`Ready. ${embeddingProvider ? "Triple-stream (BM25+Vector+Graph)" : "BM25+Graph"} search active.`);
|
|
33887
|
-
bootLog(`REST API:
|
|
34411
|
+
bootLog(`REST API: 147 endpoints at http://localhost:${config.restPort}/agentmemory/*`);
|
|
33888
34412
|
bootLog(`MCP surface (opt-in via \`npx @agentmemory/mcp\`): ${getAllTools().length} tools · 6 resources · 3 prompts`);
|
|
33889
34413
|
(async () => {
|
|
33890
34414
|
try {
|