@agentmemory/agentmemory 0.9.21 → 0.9.22
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/README.md +150 -7
- package/dist/cli.d.mts +5 -1
- package/dist/cli.d.mts.map +1 -0
- package/dist/cli.mjs +103 -692
- package/dist/cli.mjs.map +1 -1
- package/dist/connect-BQQXpyDS.mjs +763 -0
- package/dist/connect-BQQXpyDS.mjs.map +1 -0
- package/dist/hooks/post-tool-use.mjs +1 -1
- package/dist/hooks/post-tool-use.mjs.map +1 -1
- package/dist/hooks/stop.mjs +8 -0
- package/dist/hooks/stop.mjs.map +1 -1
- package/dist/{image-refs-R3tin9MR.mjs → image-refs-CJS5B9Gq.mjs} +2 -2
- package/dist/{image-refs-R3tin9MR.mjs.map → image-refs-CJS5B9Gq.mjs.map} +1 -1
- package/dist/{image-store-DyrKZKqZ.mjs → image-store-CdE0amb1.mjs} +1 -1
- package/dist/index.mjs +450 -242
- package/dist/index.mjs.map +1 -1
- package/dist/logger-xlVlvCWX.mjs +43 -0
- package/dist/logger-xlVlvCWX.mjs.map +1 -0
- package/dist/schema-BkALl7Z_.mjs +74 -0
- package/dist/schema-BkALl7Z_.mjs.map +1 -0
- package/dist/{src-D5arboxc.mjs → src-gpTAJuBy.mjs} +428 -243
- package/dist/src-gpTAJuBy.mjs.map +1 -0
- package/dist/{standalone-C7BgzzIN.mjs → standalone-C4i7ktpn.mjs} +18 -6
- package/dist/standalone-C4i7ktpn.mjs.map +1 -0
- package/dist/standalone.d.mts.map +1 -1
- package/dist/standalone.mjs +15 -4
- package/dist/standalone.mjs.map +1 -1
- package/dist/{tools-registry-CRTWUFw9.mjs → tools-registry-B7Y6nJsr.mjs} +36 -11
- package/dist/tools-registry-B7Y6nJsr.mjs.map +1 -0
- package/dist/version-DvQMNbEH.mjs +6 -0
- package/dist/version-DvQMNbEH.mjs.map +1 -0
- package/dist/viewer/index.html +77 -9
- package/package.json +6 -4
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/.mcp.json +3 -2
- package/plugin/opencode/agentmemory-capture.ts +34 -9
- package/plugin/scripts/diagnostics.d.mts +17 -0
- package/plugin/scripts/diagnostics.d.mts.map +1 -0
- package/plugin/scripts/diagnostics.mjs.map +1 -0
- package/plugin/scripts/post-tool-use.mjs +1 -1
- package/plugin/scripts/post-tool-use.mjs.map +1 -1
- package/plugin/scripts/stop.mjs +8 -0
- package/plugin/scripts/stop.mjs.map +1 -1
- package/dist/src-D5arboxc.mjs.map +0 -1
- package/dist/standalone-C7BgzzIN.mjs.map +0 -1
- package/dist/tools-registry-CRTWUFw9.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { TriggerAction, registerWorker } from "iii-sdk";
|
|
4
|
-
import { constants, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { constants, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { basename, dirname, extname, join, resolve, sep } from "node:path";
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
7
|
import Anthropic from "@anthropic-ai/sdk";
|
|
@@ -40,6 +40,7 @@ function safeParseInt(value, fallback) {
|
|
|
40
40
|
}
|
|
41
41
|
const DATA_DIR = join(homedir(), ".agentmemory");
|
|
42
42
|
const ENV_FILE = join(DATA_DIR, ".env");
|
|
43
|
+
let warnPremiumModelShown = false;
|
|
43
44
|
function loadEnvFile() {
|
|
44
45
|
if (!existsSync(ENV_FILE)) return {};
|
|
45
46
|
const content = readFileSync(ENV_FILE, "utf-8");
|
|
@@ -93,11 +94,18 @@ function detectProvider(env) {
|
|
|
93
94
|
maxTokens
|
|
94
95
|
};
|
|
95
96
|
}
|
|
96
|
-
if (hasRealValue(env["OPENROUTER_API_KEY"]))
|
|
97
|
-
|
|
98
|
-
model
|
|
99
|
-
|
|
100
|
-
|
|
97
|
+
if (hasRealValue(env["OPENROUTER_API_KEY"])) {
|
|
98
|
+
const model = env["OPENROUTER_MODEL"] || "anthropic/claude-sonnet-4-20250514";
|
|
99
|
+
if (!warnPremiumModelShown && /sonnet|opus|gpt-4o(?!.*mini)|gpt-4-turbo/i.test(model) && env["AGENTMEMORY_SUPPRESS_COST_WARNING"] !== "1" && env["AGENTMEMORY_SUPPRESS_COST_WARNING"] !== "true") {
|
|
100
|
+
warnPremiumModelShown = true;
|
|
101
|
+
process.stderr.write(`[agentmemory] OPENROUTER_MODEL=${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-pro, deepseek/deepseek-chat, qwen/qwen3-coder. See README "Cost-aware model selection" for the full table. Set AGENTMEMORY_SUPPRESS_COST_WARNING=1 to silence.\n`);
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
provider: "openrouter",
|
|
105
|
+
model,
|
|
106
|
+
maxTokens
|
|
107
|
+
};
|
|
108
|
+
}
|
|
101
109
|
if (!(env["AGENTMEMORY_ALLOW_AGENT_SDK"] === "true")) {
|
|
102
110
|
process.stderr.write("[agentmemory] No LLM provider key found (ANTHROPIC_API_KEY, GEMINI_API_KEY, OPENROUTER_API_KEY, MINIMAX_API_KEY, OPENAI_API_KEY). LLM-backed compression and summarization are DISABLED — using no-op provider. This is the safe default: the agent-sdk fallback used to spawn Claude Agent SDK child sessions which inherit Claude Code's plugin hooks and cause infinite Stop-hook recursion (#149 follow-up). To opt in to the agent-sdk fallback anyway, set both AGENTMEMORY_AUTO_COMPRESS=true AND AGENTMEMORY_ALLOW_AGENT_SDK=true — but be aware it will burn your Claude Pro allocation and may still recurse if you use it from inside Claude Code itself.\n");
|
|
103
111
|
return {
|
|
@@ -175,8 +183,8 @@ function loadClaudeBridgeConfig() {
|
|
|
175
183
|
const lineBudget = safeParseInt(env["CLAUDE_MEMORY_LINE_BUDGET"], 200);
|
|
176
184
|
let memoryFilePath = "";
|
|
177
185
|
if (enabled && projectPath) {
|
|
178
|
-
const safePath = projectPath.replace(/[/\\]/g, "-")
|
|
179
|
-
memoryFilePath = join(homedir(), ".claude", "projects", safePath, "
|
|
186
|
+
const safePath = projectPath.replace(/[/\\]/g, "-");
|
|
187
|
+
memoryFilePath = join(homedir(), ".claude", "projects", safePath, "MEMORY.md");
|
|
180
188
|
}
|
|
181
189
|
return {
|
|
182
190
|
enabled,
|
|
@@ -196,6 +204,23 @@ function loadTeamConfig() {
|
|
|
196
204
|
mode: env["TEAM_MODE"] === "shared" ? "shared" : "private"
|
|
197
205
|
};
|
|
198
206
|
}
|
|
207
|
+
function loadAgentScope() {
|
|
208
|
+
const env = getMergedEnv();
|
|
209
|
+
const raw = env["AGENT_ID"];
|
|
210
|
+
if (!raw) return null;
|
|
211
|
+
const agentId = raw.trim().slice(0, 128);
|
|
212
|
+
if (!agentId) return null;
|
|
213
|
+
return {
|
|
214
|
+
agentId,
|
|
215
|
+
mode: env["AGENTMEMORY_AGENT_SCOPE"] === "isolated" ? "isolated" : "shared"
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
function getAgentId() {
|
|
219
|
+
return loadAgentScope()?.agentId;
|
|
220
|
+
}
|
|
221
|
+
function isAgentScopeIsolated() {
|
|
222
|
+
return loadAgentScope()?.mode === "isolated";
|
|
223
|
+
}
|
|
199
224
|
function loadSnapshotConfig() {
|
|
200
225
|
const env = getMergedEnv();
|
|
201
226
|
return {
|
|
@@ -453,13 +478,25 @@ function v1AzureUrl(baseUrl, path) {
|
|
|
453
478
|
url.pathname = `${url.pathname.replace(/\/?openai(?:\/v1)?\/?$/, "").replace(/\/+$/, "")}/openai/v1/${route}`;
|
|
454
479
|
return url.toString();
|
|
455
480
|
}
|
|
481
|
+
function appendOpenAIRoute(baseUrl, route) {
|
|
482
|
+
const trimmedBase = baseUrl.replace(/\/+$/, "");
|
|
483
|
+
const cleanRoute = route.startsWith("/") ? route : `/${route}`;
|
|
484
|
+
let pathname;
|
|
485
|
+
try {
|
|
486
|
+
pathname = new URL(trimmedBase).pathname.replace(/\/+$/, "");
|
|
487
|
+
} catch {
|
|
488
|
+
return `${trimmedBase}/v1${cleanRoute}`;
|
|
489
|
+
}
|
|
490
|
+
if (pathname === "" || pathname === "/") return `${trimmedBase}/v1${cleanRoute}`;
|
|
491
|
+
return `${trimmedBase}${cleanRoute}`;
|
|
492
|
+
}
|
|
456
493
|
function buildChatUrl(baseUrl, isAzure, azureApiVersion) {
|
|
457
494
|
if (isAzure) return azureStyleOf(baseUrl) === "legacy" ? legacyAzureUrl(baseUrl, "/chat/completions", azureApiVersion) : v1AzureUrl(baseUrl, "/chat/completions");
|
|
458
|
-
return
|
|
495
|
+
return appendOpenAIRoute(baseUrl, "/chat/completions");
|
|
459
496
|
}
|
|
460
497
|
function buildEmbeddingUrl(baseUrl, isAzure, azureApiVersion) {
|
|
461
498
|
if (isAzure) return azureStyleOf(baseUrl) === "legacy" ? legacyAzureUrl(baseUrl, "/embeddings", azureApiVersion) : v1AzureUrl(baseUrl, "/embeddings");
|
|
462
|
-
return
|
|
499
|
+
return appendOpenAIRoute(baseUrl, "/embeddings");
|
|
463
500
|
}
|
|
464
501
|
function buildAuthHeaders(apiKey, isAzure) {
|
|
465
502
|
if (isAzure) return {
|
|
@@ -541,6 +578,7 @@ var OpenAIProvider = class {
|
|
|
541
578
|
const body = {
|
|
542
579
|
model: this.model,
|
|
543
580
|
max_tokens: this.maxTokens,
|
|
581
|
+
stream: false,
|
|
544
582
|
messages: [{
|
|
545
583
|
role: "system",
|
|
546
584
|
content: systemPrompt
|
|
@@ -569,7 +607,7 @@ var OpenAIProvider = class {
|
|
|
569
607
|
const message = data.choices?.[0]?.message;
|
|
570
608
|
const content = message?.content;
|
|
571
609
|
if (content) return content;
|
|
572
|
-
const reasoning = message?.reasoning;
|
|
610
|
+
const reasoning = message?.reasoning ?? message?.reasoning_content;
|
|
573
611
|
if (reasoning) return reasoning;
|
|
574
612
|
throw new Error(`OpenAI returned unexpected response: ${JSON.stringify(data).slice(0, 200)}`);
|
|
575
613
|
}
|
|
@@ -2514,6 +2552,24 @@ var SearchIndex = class SearchIndex {
|
|
|
2514
2552
|
has(id) {
|
|
2515
2553
|
return this.entries.has(id);
|
|
2516
2554
|
}
|
|
2555
|
+
remove(id) {
|
|
2556
|
+
const entry = this.entries.get(id);
|
|
2557
|
+
if (!entry) return;
|
|
2558
|
+
const termFreq = this.docTermCounts.get(id);
|
|
2559
|
+
if (termFreq) {
|
|
2560
|
+
for (const term of termFreq.keys()) {
|
|
2561
|
+
const postingList = this.invertedIndex.get(term);
|
|
2562
|
+
if (postingList) {
|
|
2563
|
+
postingList.delete(id);
|
|
2564
|
+
if (postingList.size === 0) this.invertedIndex.delete(term);
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
this.docTermCounts.delete(id);
|
|
2568
|
+
}
|
|
2569
|
+
this.totalDocLength = Math.max(0, this.totalDocLength - entry.termCount);
|
|
2570
|
+
this.entries.delete(id);
|
|
2571
|
+
this.sortedTerms = null;
|
|
2572
|
+
}
|
|
2517
2573
|
search(query, limit = 20) {
|
|
2518
2574
|
const rawTerms = this.tokenize(query.toLowerCase());
|
|
2519
2575
|
if (rawTerms.length === 0) return [];
|
|
@@ -2866,6 +2922,7 @@ function buildSyntheticCompression(raw) {
|
|
|
2866
2922
|
};
|
|
2867
2923
|
if (raw.modality) result.modality = raw.modality;
|
|
2868
2924
|
if (raw.imageData) result.imageData = raw.imageData;
|
|
2925
|
+
if (raw.agentId) result.agentId = raw.agentId;
|
|
2869
2926
|
return result;
|
|
2870
2927
|
}
|
|
2871
2928
|
|
|
@@ -2955,6 +3012,16 @@ function setVectorIndex(idx) {
|
|
|
2955
3012
|
function setEmbeddingProvider(provider) {
|
|
2956
3013
|
currentEmbeddingProvider = provider;
|
|
2957
3014
|
}
|
|
3015
|
+
function vectorIndexRemove(id) {
|
|
3016
|
+
vectorIndex?.remove(id);
|
|
3017
|
+
}
|
|
3018
|
+
let indexPersistence = null;
|
|
3019
|
+
function setIndexPersistence(p) {
|
|
3020
|
+
indexPersistence = p;
|
|
3021
|
+
}
|
|
3022
|
+
async function flushIndexSave() {
|
|
3023
|
+
await indexPersistence?.save();
|
|
3024
|
+
}
|
|
2958
3025
|
const EMBED_MAX_CHARS = 16e3;
|
|
2959
3026
|
function clipEmbedInput(text) {
|
|
2960
3027
|
if (text.length <= EMBED_MAX_CHARS) return text;
|
|
@@ -3349,6 +3416,9 @@ function registerObserveFunction(sdk, kv, dedupMap, maxObservationsPerSession) {
|
|
|
3349
3416
|
error: `Session observation limit reached (${maxObservationsPerSession})`
|
|
3350
3417
|
};
|
|
3351
3418
|
}
|
|
3419
|
+
const existingSession = await kv.get(KV.sessions, payload.sessionId);
|
|
3420
|
+
const inheritedAgentId = existingSession ? existingSession.agentId : getAgentId();
|
|
3421
|
+
if (inheritedAgentId) raw.agentId = inheritedAgentId;
|
|
3352
3422
|
if (pendingImageData && (pendingImageData.startsWith("data:image/") || pendingImageData.startsWith("iVBORw0KGgo") || pendingImageData.startsWith("/9j/"))) {
|
|
3353
3423
|
const { saveImageToDisk } = await Promise.resolve().then(() => image_store_exports);
|
|
3354
3424
|
const { filePath, bytesWritten } = await saveImageToDisk(pendingImageData);
|
|
@@ -3400,7 +3470,7 @@ function registerObserveFunction(sdk, kv, dedupMap, maxObservationsPerSession) {
|
|
|
3400
3470
|
},
|
|
3401
3471
|
action: TriggerAction.Void()
|
|
3402
3472
|
});
|
|
3403
|
-
const session =
|
|
3473
|
+
const session = existingSession;
|
|
3404
3474
|
if (session) {
|
|
3405
3475
|
const updates = [{
|
|
3406
3476
|
type: "set",
|
|
@@ -3420,6 +3490,20 @@ function registerObserveFunction(sdk, kv, dedupMap, maxObservationsPerSession) {
|
|
|
3420
3490
|
});
|
|
3421
3491
|
}
|
|
3422
3492
|
await kv.update(KV.sessions, payload.sessionId, updates);
|
|
3493
|
+
} else if (typeof payload.project === "string" && payload.project.trim().length > 0 && typeof payload.cwd === "string" && payload.cwd.trim().length > 0) {
|
|
3494
|
+
const trimmedPrompt = typeof raw.userPrompt === "string" ? raw.userPrompt.replace(/\s+/g, " ").trim().slice(0, 200) : void 0;
|
|
3495
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
3496
|
+
await kv.set(KV.sessions, payload.sessionId, {
|
|
3497
|
+
id: payload.sessionId,
|
|
3498
|
+
project: payload.project,
|
|
3499
|
+
cwd: payload.cwd,
|
|
3500
|
+
startedAt: payload.timestamp ?? ts,
|
|
3501
|
+
updatedAt: ts,
|
|
3502
|
+
status: "active",
|
|
3503
|
+
observationCount: 1,
|
|
3504
|
+
...inheritedAgentId ? { agentId: inheritedAgentId } : {},
|
|
3505
|
+
...trimmedPrompt && trimmedPrompt.length > 0 ? { firstPrompt: trimmedPrompt } : {}
|
|
3506
|
+
});
|
|
3423
3507
|
}
|
|
3424
3508
|
if (isAutoCompressEnabled()) await sdk.trigger({
|
|
3425
3509
|
function_id: "mem::compress",
|
|
@@ -4679,7 +4763,8 @@ function registerCompressFunction(sdk, kv, provider, metricsStore) {
|
|
|
4679
4763
|
confidence: qualityScore / 100,
|
|
4680
4764
|
...hasImage ? { modality: data.raw.modality } : {},
|
|
4681
4765
|
...imageDescription ? { imageDescription } : {},
|
|
4682
|
-
...data.raw.imageData ? { imageRef: data.raw.imageData } : {}
|
|
4766
|
+
...data.raw.imageData ? { imageRef: data.raw.imageData } : {},
|
|
4767
|
+
...data.raw.agentId ? { agentId: data.raw.agentId } : {}
|
|
4683
4768
|
};
|
|
4684
4769
|
await kv.set(KV.observations(data.sessionId), data.observationId, compressed);
|
|
4685
4770
|
try {
|
|
@@ -5650,6 +5735,7 @@ function registerRememberFunction(sdk, kv) {
|
|
|
5650
5735
|
break;
|
|
5651
5736
|
}
|
|
5652
5737
|
}
|
|
5738
|
+
const callAgentId = typeof data.agentId === "string" && data.agentId.trim().length > 0 ? data.agentId.trim().slice(0, 128) : getAgentId();
|
|
5653
5739
|
const memory = {
|
|
5654
5740
|
id: generateId("mem"),
|
|
5655
5741
|
createdAt: now,
|
|
@@ -5665,7 +5751,8 @@ function registerRememberFunction(sdk, kv) {
|
|
|
5665
5751
|
parentId: supersededId,
|
|
5666
5752
|
supersedes: supersededId ? [supersededId] : [],
|
|
5667
5753
|
sourceObservationIds: (data.sourceObservationIds || []).filter((id) => typeof id === "string" && id.length > 0),
|
|
5668
|
-
isLatest: true
|
|
5754
|
+
isLatest: true,
|
|
5755
|
+
...callAgentId ? { agentId: callAgentId } : {}
|
|
5669
5756
|
};
|
|
5670
5757
|
if (data.ttlDays && typeof data.ttlDays === "number" && data.ttlDays > 0) memory.forgetAfter = new Date(Date.now() + data.ttlDays * 864e5).toISOString();
|
|
5671
5758
|
if (supersededMemory) {
|
|
@@ -5711,6 +5798,8 @@ function registerRememberFunction(sdk, kv) {
|
|
|
5711
5798
|
await kv.delete(KV.memories, data.memoryId);
|
|
5712
5799
|
if (mem?.imageRef) await decrementImageRef(kv, sdk, mem.imageRef);
|
|
5713
5800
|
await deleteAccessLog(kv, data.memoryId);
|
|
5801
|
+
getSearchIndex().remove(data.memoryId);
|
|
5802
|
+
vectorIndexRemove(data.memoryId);
|
|
5714
5803
|
deletedMemoryIds.push(data.memoryId);
|
|
5715
5804
|
deleted++;
|
|
5716
5805
|
}
|
|
@@ -5719,6 +5808,8 @@ function registerRememberFunction(sdk, kv) {
|
|
|
5719
5808
|
await kv.delete(KV.observations(data.sessionId), obsId);
|
|
5720
5809
|
if (obs?.imageData) await decrementImageRef(kv, sdk, obs.imageData);
|
|
5721
5810
|
if (obs?.imageRef && obs.imageRef !== obs.imageData) await decrementImageRef(kv, sdk, obs.imageRef);
|
|
5811
|
+
getSearchIndex().remove(obsId);
|
|
5812
|
+
vectorIndexRemove(obsId);
|
|
5722
5813
|
deletedObservationIds.push(obsId);
|
|
5723
5814
|
deleted++;
|
|
5724
5815
|
}
|
|
@@ -5728,6 +5819,8 @@ function registerRememberFunction(sdk, kv) {
|
|
|
5728
5819
|
await kv.delete(KV.observations(data.sessionId), obs.id);
|
|
5729
5820
|
if (obs.imageData) await decrementImageRef(kv, sdk, obs.imageData);
|
|
5730
5821
|
if (obs.imageRef && obs.imageRef !== obs.imageData) await decrementImageRef(kv, sdk, obs.imageRef);
|
|
5822
|
+
getSearchIndex().remove(obs.id);
|
|
5823
|
+
vectorIndexRemove(obs.id);
|
|
5731
5824
|
deletedObservationIds.push(obs.id);
|
|
5732
5825
|
deleted++;
|
|
5733
5826
|
}
|
|
@@ -5736,14 +5829,17 @@ function registerRememberFunction(sdk, kv) {
|
|
|
5736
5829
|
deletedSession = true;
|
|
5737
5830
|
deleted += 2;
|
|
5738
5831
|
}
|
|
5739
|
-
if (deleted > 0)
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5832
|
+
if (deleted > 0) {
|
|
5833
|
+
await flushIndexSave();
|
|
5834
|
+
await recordAudit(kv, "forget", "mem::forget", [...deletedMemoryIds, ...deletedObservationIds], {
|
|
5835
|
+
sessionId: data.sessionId,
|
|
5836
|
+
deleted,
|
|
5837
|
+
memoriesDeleted: deletedMemoryIds.length,
|
|
5838
|
+
observationsDeleted: deletedObservationIds.length,
|
|
5839
|
+
sessionDeleted: deletedSession,
|
|
5840
|
+
reason: "user-initiated forget"
|
|
5841
|
+
});
|
|
5842
|
+
}
|
|
5747
5843
|
logger.info("Memory forgotten", { deleted });
|
|
5748
5844
|
return {
|
|
5749
5845
|
success: true,
|
|
@@ -6258,6 +6354,9 @@ async function findByKeyword(kv, keyword, project) {
|
|
|
6258
6354
|
const LESSON_CONTENT_PREVIEW_CHARS = 240;
|
|
6259
6355
|
function registerSmartSearchFunction(sdk, kv, searchFn) {
|
|
6260
6356
|
sdk.registerFunction("mem::smart-search", async (data) => {
|
|
6357
|
+
const isolated = isAgentScopeIsolated();
|
|
6358
|
+
const explicitAgentId = typeof data.agentId === "string" && data.agentId.trim().length > 0 ? data.agentId.trim() : void 0;
|
|
6359
|
+
const filterAgentId = explicitAgentId === "*" ? void 0 : explicitAgentId ?? (isolated ? getAgentId() : void 0);
|
|
6261
6360
|
if (data.expandIds && data.expandIds.length > 0) {
|
|
6262
6361
|
const raw = data.expandIds.slice(0, 20);
|
|
6263
6362
|
const items = raw.map((entry) => {
|
|
@@ -6278,17 +6377,19 @@ function registerSmartSearchFunction(sdk, kv, searchFn) {
|
|
|
6278
6377
|
observation: obs
|
|
6279
6378
|
} : null)));
|
|
6280
6379
|
for (const r of results) if (r) expanded.push(r);
|
|
6281
|
-
|
|
6380
|
+
const scoped = filterAgentId ? expanded.filter((e) => e.observation.agentId === filterAgentId) : expanded;
|
|
6381
|
+
recordAccessBatch(kv, scoped.map((e) => e.observation.id));
|
|
6282
6382
|
const truncated = data.expandIds.length > raw.length;
|
|
6283
6383
|
logger.info("Smart search expanded", {
|
|
6284
6384
|
requested: data.expandIds.length,
|
|
6285
6385
|
attempted: raw.length,
|
|
6286
|
-
returned:
|
|
6386
|
+
returned: scoped.length,
|
|
6387
|
+
filteredOutOfScope: expanded.length - scoped.length,
|
|
6287
6388
|
truncated
|
|
6288
6389
|
});
|
|
6289
6390
|
return {
|
|
6290
6391
|
mode: "expanded",
|
|
6291
|
-
results:
|
|
6392
|
+
results: scoped,
|
|
6292
6393
|
truncated
|
|
6293
6394
|
};
|
|
6294
6395
|
}
|
|
@@ -6300,8 +6401,9 @@ function registerSmartSearchFunction(sdk, kv, searchFn) {
|
|
|
6300
6401
|
const limit = Math.max(1, Math.min(data.limit ?? 20, 100));
|
|
6301
6402
|
const lessonLimit = Math.min(limit, 10);
|
|
6302
6403
|
const includeLessons = data.includeLessons !== false;
|
|
6303
|
-
const
|
|
6304
|
-
const
|
|
6404
|
+
const overFetchLimit = filterAgentId ? Math.min(limit * 3, 300) : limit;
|
|
6405
|
+
const [hybridResults, lessons] = await Promise.all([searchFn(data.query, overFetchLimit), includeLessons ? recallLessons(sdk, data.query, lessonLimit, data.project) : Promise.resolve([])]);
|
|
6406
|
+
const compact = (filterAgentId ? hybridResults.filter((r) => r.observation.agentId === filterAgentId).slice(0, limit) : hybridResults.slice(0, limit)).map((r) => ({
|
|
6305
6407
|
obsId: r.observation.id,
|
|
6306
6408
|
sessionId: r.sessionId,
|
|
6307
6409
|
title: r.observation.title,
|
|
@@ -6480,6 +6582,8 @@ function registerAutoForgetFunction(sdk, kv) {
|
|
|
6480
6582
|
timestamp: mem.forgetAfter
|
|
6481
6583
|
});
|
|
6482
6584
|
await deleteAccessLog(kv, mem.id);
|
|
6585
|
+
getSearchIndex().remove(mem.id);
|
|
6586
|
+
vectorIndexRemove(mem.id);
|
|
6483
6587
|
}
|
|
6484
6588
|
}
|
|
6485
6589
|
}
|
|
@@ -6557,10 +6661,13 @@ function registerAutoForgetFunction(sdk, kv) {
|
|
|
6557
6661
|
sessionId: sessions[i].id,
|
|
6558
6662
|
timestamp: obs.timestamp
|
|
6559
6663
|
});
|
|
6664
|
+
getSearchIndex().remove(obs.id);
|
|
6665
|
+
vectorIndexRemove(obs.id);
|
|
6560
6666
|
}
|
|
6561
6667
|
}
|
|
6562
6668
|
}
|
|
6563
6669
|
}
|
|
6670
|
+
if (!dryRun && (result.ttlExpired.length > 0 || result.lowValueObs.length > 0)) await flushIndexSave();
|
|
6564
6671
|
logger.info("Auto-forget complete", {
|
|
6565
6672
|
ttlExpired: result.ttlExpired.length,
|
|
6566
6673
|
contradictions: result.contradictions.length,
|
|
@@ -6573,7 +6680,7 @@ function registerAutoForgetFunction(sdk, kv) {
|
|
|
6573
6680
|
|
|
6574
6681
|
//#endregion
|
|
6575
6682
|
//#region src/version.ts
|
|
6576
|
-
const VERSION = "0.9.
|
|
6683
|
+
const VERSION = "0.9.22";
|
|
6577
6684
|
|
|
6578
6685
|
//#endregion
|
|
6579
6686
|
//#region src/functions/export-import.ts
|
|
@@ -6712,7 +6819,8 @@ function registerExportImportFunction(sdk, kv) {
|
|
|
6712
6819
|
"0.9.18",
|
|
6713
6820
|
"0.9.19",
|
|
6714
6821
|
"0.9.20",
|
|
6715
|
-
"0.9.21"
|
|
6822
|
+
"0.9.21",
|
|
6823
|
+
"0.9.22"
|
|
6716
6824
|
]).has(importData.version)) return {
|
|
6717
6825
|
success: false,
|
|
6718
6826
|
error: `Unsupported export version: ${importData.version}`
|
|
@@ -7238,12 +7346,12 @@ function parseGraphXml(xml, observationIds) {
|
|
|
7238
7346
|
const nodes = [];
|
|
7239
7347
|
const edges = [];
|
|
7240
7348
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7241
|
-
const entityRegex = /<entity\s+type="([^"]+)"\s+name="([^"]+)"[^>]
|
|
7349
|
+
const entityRegex = /<entity\s+type="([^"]+)"\s+name="([^"]+)"[^>]*?(?:\/>|>([\s\S]*?)<\/entity>)/g;
|
|
7242
7350
|
let match;
|
|
7243
7351
|
while ((match = entityRegex.exec(xml)) !== null) {
|
|
7244
7352
|
const type = match[1];
|
|
7245
7353
|
const name = match[2];
|
|
7246
|
-
const propsBlock = match[3];
|
|
7354
|
+
const propsBlock = match[3] ?? "";
|
|
7247
7355
|
const properties = {};
|
|
7248
7356
|
const propRegex = /<property\s+key="([^"]+)">([^<]*)<\/property>/g;
|
|
7249
7357
|
let propMatch;
|
|
@@ -7771,8 +7879,11 @@ function registerGovernanceFunction(sdk, kv) {
|
|
|
7771
7879
|
for (const id of data.memoryIds) if (await kv.get(KV.memories, id)) {
|
|
7772
7880
|
await kv.delete(KV.memories, id);
|
|
7773
7881
|
await deleteAccessLog(kv, id);
|
|
7882
|
+
getSearchIndex().remove(id);
|
|
7883
|
+
vectorIndexRemove(id);
|
|
7774
7884
|
deleted++;
|
|
7775
7885
|
}
|
|
7886
|
+
if (deleted > 0) await flushIndexSave();
|
|
7776
7887
|
await recordAudit(kv, "delete", "mem::governance-delete", data.memoryIds, {
|
|
7777
7888
|
reason: data.reason || "manual deletion",
|
|
7778
7889
|
deleted
|
|
@@ -7825,6 +7936,8 @@ function registerGovernanceFunction(sdk, kv) {
|
|
|
7825
7936
|
(await Promise.allSettled(batch.map(async (mem) => {
|
|
7826
7937
|
await kv.delete(KV.memories, mem.id);
|
|
7827
7938
|
await deleteAccessLog(kv, mem.id);
|
|
7939
|
+
getSearchIndex().remove(mem.id);
|
|
7940
|
+
vectorIndexRemove(mem.id);
|
|
7828
7941
|
}))).forEach((result, j) => {
|
|
7829
7942
|
const mem = batch[j];
|
|
7830
7943
|
if (result.status === "fulfilled") successfulIds.push(mem.id);
|
|
@@ -7840,6 +7953,7 @@ function registerGovernanceFunction(sdk, kv) {
|
|
|
7840
7953
|
}
|
|
7841
7954
|
});
|
|
7842
7955
|
}
|
|
7956
|
+
if (successfulIds.length > 0) await flushIndexSave();
|
|
7843
7957
|
await safeAudit(kv, "delete", "mem::governance-bulk", successfulIds, {
|
|
7844
7958
|
filter: data,
|
|
7845
7959
|
deleted: successfulIds.length,
|
|
@@ -13239,6 +13353,8 @@ function registerRetentionFunctions(sdk, kv) {
|
|
|
13239
13353
|
await kv.delete(scope, candidate.memoryId);
|
|
13240
13354
|
await kv.delete(KV.retentionScores, candidate.memoryId);
|
|
13241
13355
|
await deleteAccessLog(kv, candidate.memoryId);
|
|
13356
|
+
getSearchIndex().remove(candidate.memoryId);
|
|
13357
|
+
vectorIndexRemove(candidate.memoryId);
|
|
13242
13358
|
evicted++;
|
|
13243
13359
|
evictedIds.push(candidate.memoryId);
|
|
13244
13360
|
if (resolvedSource === "semantic") evictedSemantic++;
|
|
@@ -13246,13 +13362,16 @@ function registerRetentionFunctions(sdk, kv) {
|
|
|
13246
13362
|
} catch {
|
|
13247
13363
|
continue;
|
|
13248
13364
|
}
|
|
13249
|
-
if (evicted > 0)
|
|
13250
|
-
|
|
13251
|
-
|
|
13252
|
-
|
|
13253
|
-
|
|
13254
|
-
|
|
13255
|
-
|
|
13365
|
+
if (evicted > 0) {
|
|
13366
|
+
await flushIndexSave();
|
|
13367
|
+
await recordAudit(kv, "delete", "mem::retention-evict", evictedIds, {
|
|
13368
|
+
threshold,
|
|
13369
|
+
evicted,
|
|
13370
|
+
evictedEpisodic,
|
|
13371
|
+
evictedSemantic,
|
|
13372
|
+
reason: "retention score below threshold"
|
|
13373
|
+
});
|
|
13374
|
+
}
|
|
13256
13375
|
logger.info("Retention-based eviction complete", {
|
|
13257
13376
|
evicted,
|
|
13258
13377
|
evictedEpisodic,
|
|
@@ -14156,6 +14275,221 @@ function renderViewerDocument() {
|
|
|
14156
14275
|
};
|
|
14157
14276
|
}
|
|
14158
14277
|
|
|
14278
|
+
//#endregion
|
|
14279
|
+
//#region src/viewer/server.ts
|
|
14280
|
+
function loadViewerFavicon() {
|
|
14281
|
+
const base = dirname(fileURLToPath(import.meta.url));
|
|
14282
|
+
const candidates = [
|
|
14283
|
+
join(base, "..", "src", "viewer", "favicon.svg"),
|
|
14284
|
+
join(base, "..", "viewer", "favicon.svg"),
|
|
14285
|
+
join(base, "viewer", "favicon.svg")
|
|
14286
|
+
];
|
|
14287
|
+
for (const path of candidates) try {
|
|
14288
|
+
return readFileSync(path);
|
|
14289
|
+
} catch {}
|
|
14290
|
+
return null;
|
|
14291
|
+
}
|
|
14292
|
+
const ALLOWED_ORIGINS = (process.env.VIEWER_ALLOWED_ORIGINS || "http://localhost:3111,http://localhost:3113,http://127.0.0.1:3111,http://127.0.0.1:3113").split(",").map((o) => o.trim());
|
|
14293
|
+
const ALLOWED_HOSTS_OVERRIDE = (process.env.VIEWER_ALLOWED_HOSTS || "").split(",").map((h) => h.trim().toLowerCase()).filter(Boolean);
|
|
14294
|
+
function buildAllowedHosts(origins, listenPort) {
|
|
14295
|
+
const hosts = /* @__PURE__ */ new Set();
|
|
14296
|
+
for (const o of origins) try {
|
|
14297
|
+
const parsed = new URL(o);
|
|
14298
|
+
if (parsed.host) hosts.add(parsed.host.toLowerCase());
|
|
14299
|
+
} catch {}
|
|
14300
|
+
hosts.add(`localhost:${listenPort}`);
|
|
14301
|
+
hosts.add(`127.0.0.1:${listenPort}`);
|
|
14302
|
+
hosts.add(`[::1]:${listenPort}`);
|
|
14303
|
+
for (const h of ALLOWED_HOSTS_OVERRIDE) hosts.add(h);
|
|
14304
|
+
return hosts;
|
|
14305
|
+
}
|
|
14306
|
+
function isHostAllowed(headerHost, allowed) {
|
|
14307
|
+
if (typeof headerHost !== "string") return false;
|
|
14308
|
+
const lower = headerHost.toLowerCase().trim();
|
|
14309
|
+
if (!lower) return false;
|
|
14310
|
+
return allowed.has(lower);
|
|
14311
|
+
}
|
|
14312
|
+
function corsHeaders(req) {
|
|
14313
|
+
const origin = req.headers.origin || "";
|
|
14314
|
+
const allowed = ALLOWED_ORIGINS.includes(origin) ? origin : ALLOWED_ORIGINS[0];
|
|
14315
|
+
return {
|
|
14316
|
+
"Access-Control-Allow-Origin": allowed,
|
|
14317
|
+
"Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS",
|
|
14318
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
14319
|
+
Vary: "Origin"
|
|
14320
|
+
};
|
|
14321
|
+
}
|
|
14322
|
+
function json(res, status, data, req) {
|
|
14323
|
+
const body = JSON.stringify(data);
|
|
14324
|
+
const cors = req ? corsHeaders(req) : {
|
|
14325
|
+
"Access-Control-Allow-Origin": ALLOWED_ORIGINS[0],
|
|
14326
|
+
Vary: "Origin"
|
|
14327
|
+
};
|
|
14328
|
+
res.writeHead(status, {
|
|
14329
|
+
...cors,
|
|
14330
|
+
"Content-Type": "application/json"
|
|
14331
|
+
});
|
|
14332
|
+
res.end(body);
|
|
14333
|
+
}
|
|
14334
|
+
function readBody(req) {
|
|
14335
|
+
return new Promise((resolve, reject) => {
|
|
14336
|
+
let data = "";
|
|
14337
|
+
let size = 0;
|
|
14338
|
+
req.on("data", (chunk) => {
|
|
14339
|
+
size += chunk.length;
|
|
14340
|
+
if (size > 1e6) {
|
|
14341
|
+
req.destroy();
|
|
14342
|
+
reject(/* @__PURE__ */ new Error("too large"));
|
|
14343
|
+
return;
|
|
14344
|
+
}
|
|
14345
|
+
data += chunk.toString();
|
|
14346
|
+
});
|
|
14347
|
+
req.on("end", () => resolve(data));
|
|
14348
|
+
req.on("error", reject);
|
|
14349
|
+
});
|
|
14350
|
+
}
|
|
14351
|
+
const MAX_VIEWER_PORT_RETRIES = 10;
|
|
14352
|
+
let boundViewerPort = null;
|
|
14353
|
+
let viewerSkipped = false;
|
|
14354
|
+
function getBoundViewerPort() {
|
|
14355
|
+
return boundViewerPort;
|
|
14356
|
+
}
|
|
14357
|
+
function getViewerSkipped() {
|
|
14358
|
+
return viewerSkipped;
|
|
14359
|
+
}
|
|
14360
|
+
function startViewerServer(port, _kv, _sdk, secret, restPort) {
|
|
14361
|
+
boundViewerPort = null;
|
|
14362
|
+
viewerSkipped = false;
|
|
14363
|
+
const resolvedRestPort = restPort ?? port - 2;
|
|
14364
|
+
const requestedPort = port;
|
|
14365
|
+
let allowedHosts = null;
|
|
14366
|
+
const server = createServer(async (req, res) => {
|
|
14367
|
+
if (!allowedHosts) {
|
|
14368
|
+
const addr = server.address();
|
|
14369
|
+
allowedHosts = buildAllowedHosts(ALLOWED_ORIGINS, addr && typeof addr === "object" && "port" in addr ? addr.port : port);
|
|
14370
|
+
}
|
|
14371
|
+
if (!isHostAllowed(req.headers.host, allowedHosts)) {
|
|
14372
|
+
res.writeHead(403, { "Content-Type": "text/plain" });
|
|
14373
|
+
res.end("forbidden host");
|
|
14374
|
+
return;
|
|
14375
|
+
}
|
|
14376
|
+
const raw = req.url || "/";
|
|
14377
|
+
const qIdx = raw.indexOf("?");
|
|
14378
|
+
const pathname = qIdx >= 0 ? raw.slice(0, qIdx) : raw;
|
|
14379
|
+
const qs = qIdx >= 0 ? raw.slice(qIdx + 1) : "";
|
|
14380
|
+
const method = req.method || "GET";
|
|
14381
|
+
if (method === "OPTIONS") {
|
|
14382
|
+
res.writeHead(204, {
|
|
14383
|
+
...corsHeaders(req),
|
|
14384
|
+
"Access-Control-Max-Age": "86400"
|
|
14385
|
+
});
|
|
14386
|
+
res.end();
|
|
14387
|
+
return;
|
|
14388
|
+
}
|
|
14389
|
+
if (method === "GET" && (pathname === "/" || pathname === "/viewer" || pathname === "/agentmemory/viewer")) {
|
|
14390
|
+
const rendered = renderViewerDocument();
|
|
14391
|
+
if (rendered.found) {
|
|
14392
|
+
res.writeHead(200, {
|
|
14393
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
14394
|
+
"Content-Security-Policy": rendered.csp,
|
|
14395
|
+
"Cache-Control": "no-cache"
|
|
14396
|
+
});
|
|
14397
|
+
res.end(rendered.html);
|
|
14398
|
+
return;
|
|
14399
|
+
}
|
|
14400
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
14401
|
+
res.end("viewer not found");
|
|
14402
|
+
return;
|
|
14403
|
+
}
|
|
14404
|
+
if (method === "GET" && pathname === "/favicon.svg") {
|
|
14405
|
+
const favicon = loadViewerFavicon();
|
|
14406
|
+
if (favicon) {
|
|
14407
|
+
res.writeHead(200, {
|
|
14408
|
+
"Content-Type": "image/svg+xml",
|
|
14409
|
+
"Cache-Control": "public, max-age=3600"
|
|
14410
|
+
});
|
|
14411
|
+
res.end(favicon);
|
|
14412
|
+
return;
|
|
14413
|
+
}
|
|
14414
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
14415
|
+
res.end("favicon not found");
|
|
14416
|
+
return;
|
|
14417
|
+
}
|
|
14418
|
+
try {
|
|
14419
|
+
await proxyToRestApi(resolvedRestPort, pathname, qs, method, req, res, secret);
|
|
14420
|
+
} catch (err) {
|
|
14421
|
+
console.error(`[viewer] proxy error on ${method} ${pathname}:`, err);
|
|
14422
|
+
json(res, 502, { error: "upstream error" }, req);
|
|
14423
|
+
}
|
|
14424
|
+
});
|
|
14425
|
+
let attempt = 0;
|
|
14426
|
+
let currentPort = requestedPort;
|
|
14427
|
+
const tryListen = () => {
|
|
14428
|
+
server.listen(currentPort, "127.0.0.1");
|
|
14429
|
+
};
|
|
14430
|
+
server.on("listening", () => {
|
|
14431
|
+
const addr = server.address();
|
|
14432
|
+
boundViewerPort = addr && typeof addr === "object" && "port" in addr ? addr.port : currentPort;
|
|
14433
|
+
viewerSkipped = false;
|
|
14434
|
+
if (currentPort === requestedPort) console.log(`[agentmemory] Viewer: http://localhost:${currentPort}`);
|
|
14435
|
+
else console.log(`[agentmemory] Viewer started on http://localhost:${currentPort} (fallback from ${requestedPort})`);
|
|
14436
|
+
});
|
|
14437
|
+
server.on("error", (err) => {
|
|
14438
|
+
if (err.code === "EADDRINUSE" && attempt < MAX_VIEWER_PORT_RETRIES) {
|
|
14439
|
+
attempt++;
|
|
14440
|
+
currentPort = requestedPort + attempt;
|
|
14441
|
+
setImmediate(tryListen);
|
|
14442
|
+
return;
|
|
14443
|
+
}
|
|
14444
|
+
if (err.code === "EADDRINUSE") {
|
|
14445
|
+
boundViewerPort = null;
|
|
14446
|
+
viewerSkipped = true;
|
|
14447
|
+
console.warn(`[agentmemory] Viewer ports ${requestedPort}-${requestedPort + MAX_VIEWER_PORT_RETRIES} all in use, skipping viewer.`);
|
|
14448
|
+
} else {
|
|
14449
|
+
boundViewerPort = null;
|
|
14450
|
+
viewerSkipped = true;
|
|
14451
|
+
console.error(`[agentmemory] Viewer error:`, err.message);
|
|
14452
|
+
}
|
|
14453
|
+
});
|
|
14454
|
+
tryListen();
|
|
14455
|
+
return server;
|
|
14456
|
+
}
|
|
14457
|
+
async function proxyToRestApi(restPort, pathname, qs, method, req, res, secret) {
|
|
14458
|
+
const upstreamUrl = `http://127.0.0.1:${restPort}${pathname.startsWith("/agentmemory/") ? pathname : `/agentmemory${pathname.startsWith("/") ? pathname : "/" + pathname}`}${qs ? "?" + qs : ""}`;
|
|
14459
|
+
const headers = {};
|
|
14460
|
+
if (secret) headers["Authorization"] = `Bearer ${secret}`;
|
|
14461
|
+
const ct = req.headers["content-type"];
|
|
14462
|
+
if (ct) headers["Content-Type"] = ct;
|
|
14463
|
+
let body;
|
|
14464
|
+
if (method === "POST" || method === "PUT" || method === "DELETE" || method === "PATCH") body = await readBody(req);
|
|
14465
|
+
const controller = new AbortController();
|
|
14466
|
+
const fetchTimeout = setTimeout(() => controller.abort(), 1e4);
|
|
14467
|
+
let upstream;
|
|
14468
|
+
try {
|
|
14469
|
+
upstream = await fetch(upstreamUrl, {
|
|
14470
|
+
method,
|
|
14471
|
+
headers,
|
|
14472
|
+
body: body || void 0,
|
|
14473
|
+
signal: controller.signal
|
|
14474
|
+
});
|
|
14475
|
+
clearTimeout(fetchTimeout);
|
|
14476
|
+
} catch (err) {
|
|
14477
|
+
clearTimeout(fetchTimeout);
|
|
14478
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
14479
|
+
json(res, 504, { error: "upstream timeout" }, req);
|
|
14480
|
+
return;
|
|
14481
|
+
}
|
|
14482
|
+
throw err;
|
|
14483
|
+
}
|
|
14484
|
+
const cors = corsHeaders(req);
|
|
14485
|
+
const responseBody = await upstream.text();
|
|
14486
|
+
const responseHeaders = { ...cors };
|
|
14487
|
+
const upstreamCt = upstream.headers.get("content-type");
|
|
14488
|
+
if (upstreamCt) responseHeaders["Content-Type"] = upstreamCt;
|
|
14489
|
+
res.writeHead(upstream.status, responseHeaders);
|
|
14490
|
+
res.end(responseBody);
|
|
14491
|
+
}
|
|
14492
|
+
|
|
14159
14493
|
//#endregion
|
|
14160
14494
|
//#region src/triggers/api.ts
|
|
14161
14495
|
function parseOptionalInt(raw) {
|
|
@@ -14241,7 +14575,9 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
14241
14575
|
status_code: 200,
|
|
14242
14576
|
body: {
|
|
14243
14577
|
status: "ok",
|
|
14244
|
-
service: "agentmemory"
|
|
14578
|
+
service: "agentmemory",
|
|
14579
|
+
viewerPort: getBoundViewerPort(),
|
|
14580
|
+
viewerSkipped: getViewerSkipped()
|
|
14245
14581
|
}
|
|
14246
14582
|
}));
|
|
14247
14583
|
sdk.registerTrigger({
|
|
@@ -14336,7 +14672,9 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
14336
14672
|
version: VERSION,
|
|
14337
14673
|
health: health || null,
|
|
14338
14674
|
functionMetrics,
|
|
14339
|
-
circuitBreaker
|
|
14675
|
+
circuitBreaker,
|
|
14676
|
+
viewerPort: getBoundViewerPort(),
|
|
14677
|
+
viewerSkipped: getViewerSkipped()
|
|
14340
14678
|
}
|
|
14341
14679
|
};
|
|
14342
14680
|
});
|
|
@@ -14590,6 +14928,7 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
14590
14928
|
body: { error: "sessionId, project, and cwd are required non-empty strings" }
|
|
14591
14929
|
};
|
|
14592
14930
|
const title = typeof body.title === "string" ? body.title.trim() : void 0;
|
|
14931
|
+
const agentId = (typeof body.agentId === "string" && body.agentId.trim().length > 0 ? body.agentId.trim().slice(0, 128) : void 0) ?? getAgentId();
|
|
14593
14932
|
const session = {
|
|
14594
14933
|
id: sessionId,
|
|
14595
14934
|
project,
|
|
@@ -14598,7 +14937,8 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
14598
14937
|
status: "active",
|
|
14599
14938
|
observationCount: 0,
|
|
14600
14939
|
...title ? { summary: title.slice(0, 200) } : {},
|
|
14601
|
-
...title ? { firstPrompt: title.slice(0, 200) } : {}
|
|
14940
|
+
...title ? { firstPrompt: title.slice(0, 200) } : {},
|
|
14941
|
+
...agentId ? { agentId } : {}
|
|
14602
14942
|
};
|
|
14603
14943
|
await kv.set(KV.sessions, sessionId, session);
|
|
14604
14944
|
return {
|
|
@@ -14785,9 +15125,13 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
14785
15125
|
sdk.registerFunction("api::sessions", async (req) => {
|
|
14786
15126
|
const authErr = checkAuth(req, secret);
|
|
14787
15127
|
if (authErr) return authErr;
|
|
15128
|
+
const sessions = await kv.list(KV.sessions);
|
|
15129
|
+
const normalizedAgentId = typeof req.query_params?.["agentId"] === "string" ? req.query_params["agentId"].trim() : void 0;
|
|
15130
|
+
const wildcardAgent = normalizedAgentId === "*";
|
|
15131
|
+
const filterAgentId = wildcardAgent ? void 0 : (normalizedAgentId && !wildcardAgent ? normalizedAgentId : void 0) ?? (isAgentScopeIsolated() ? getAgentId() : void 0);
|
|
14788
15132
|
return {
|
|
14789
15133
|
status_code: 200,
|
|
14790
|
-
body: { sessions:
|
|
15134
|
+
body: { sessions: filterAgentId ? sessions.filter((s) => s.agentId === filterAgentId) : sessions }
|
|
14791
15135
|
};
|
|
14792
15136
|
});
|
|
14793
15137
|
sdk.registerTrigger({
|
|
@@ -14806,9 +15150,13 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
14806
15150
|
status_code: 400,
|
|
14807
15151
|
body: { error: "sessionId required" }
|
|
14808
15152
|
};
|
|
15153
|
+
const observations = await kv.list(KV.observations(sessionId));
|
|
15154
|
+
const normalizedAgentId = typeof req.query_params?.["agentId"] === "string" ? req.query_params["agentId"].trim() : void 0;
|
|
15155
|
+
const wildcardAgent = normalizedAgentId === "*";
|
|
15156
|
+
const filterAgentId = wildcardAgent ? void 0 : (normalizedAgentId && !wildcardAgent ? normalizedAgentId : void 0) ?? (isAgentScopeIsolated() ? getAgentId() : void 0);
|
|
14809
15157
|
return {
|
|
14810
15158
|
status_code: 200,
|
|
14811
|
-
body: { observations:
|
|
15159
|
+
body: { observations: filterAgentId ? observations.filter((o) => o.agentId === filterAgentId) : observations }
|
|
14812
15160
|
};
|
|
14813
15161
|
});
|
|
14814
15162
|
sdk.registerTrigger({
|
|
@@ -15084,11 +15432,22 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
15084
15432
|
sdk.registerFunction("api::export", async (req) => {
|
|
15085
15433
|
const authErr = checkAuth(req, secret);
|
|
15086
15434
|
if (authErr) return authErr;
|
|
15435
|
+
const rawMax = req.query_params?.["maxSessions"];
|
|
15436
|
+
const rawOffset = req.query_params?.["offset"];
|
|
15437
|
+
const payload = {};
|
|
15438
|
+
if (typeof rawMax === "string") {
|
|
15439
|
+
const n = Number(rawMax);
|
|
15440
|
+
if (Number.isInteger(n) && n > 0) payload.maxSessions = n;
|
|
15441
|
+
}
|
|
15442
|
+
if (typeof rawOffset === "string") {
|
|
15443
|
+
const n = Number(rawOffset);
|
|
15444
|
+
if (Number.isInteger(n) && n >= 0) payload.offset = n;
|
|
15445
|
+
}
|
|
15087
15446
|
return {
|
|
15088
15447
|
status_code: 200,
|
|
15089
15448
|
body: await sdk.trigger({
|
|
15090
15449
|
function_id: "mem::export",
|
|
15091
|
-
payload
|
|
15450
|
+
payload
|
|
15092
15451
|
})
|
|
15093
15452
|
};
|
|
15094
15453
|
});
|
|
@@ -15574,9 +15933,35 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
15574
15933
|
const authErr = checkAuth(req, secret);
|
|
15575
15934
|
if (authErr) return authErr;
|
|
15576
15935
|
const memories = await kv.list(KV.memories);
|
|
15936
|
+
const latest = req.query_params?.["latest"] === "true";
|
|
15937
|
+
const normalizedAgentId = typeof req.query_params?.["agentId"] === "string" ? req.query_params["agentId"].trim() : void 0;
|
|
15938
|
+
const wildcardAgent = normalizedAgentId === "*";
|
|
15939
|
+
const explicitAgentId = normalizedAgentId && !wildcardAgent ? normalizedAgentId : void 0;
|
|
15940
|
+
const includeOrphans = req.query_params?.["includeOrphans"] === "true";
|
|
15941
|
+
const filterAgentId = wildcardAgent ? void 0 : explicitAgentId ?? (isAgentScopeIsolated() ? getAgentId() : void 0);
|
|
15942
|
+
let filtered = latest ? memories.filter((m) => m.isLatest) : memories;
|
|
15943
|
+
if (filterAgentId) filtered = filtered.filter((m) => m.agentId === filterAgentId || includeOrphans && m.agentId === void 0);
|
|
15944
|
+
if (req.query_params?.["count"] === "true") return {
|
|
15945
|
+
status_code: 200,
|
|
15946
|
+
body: {
|
|
15947
|
+
total: filtered.length,
|
|
15948
|
+
latestCount: filtered.filter((m) => m.isLatest).length
|
|
15949
|
+
}
|
|
15950
|
+
};
|
|
15951
|
+
const rawLimit = req.query_params?.["limit"];
|
|
15952
|
+
const rawOffset = req.query_params?.["offset"];
|
|
15953
|
+
const parsedLimit = typeof rawLimit === "string" ? Number(rawLimit) : NaN;
|
|
15954
|
+
const parsedOffset = typeof rawOffset === "string" ? Number(rawOffset) : NaN;
|
|
15955
|
+
const limit = Number.isInteger(parsedLimit) && parsedLimit > 0 ? Math.min(parsedLimit, 5e3) : void 0;
|
|
15956
|
+
const offset = Number.isInteger(parsedOffset) && parsedOffset >= 0 ? parsedOffset : 0;
|
|
15577
15957
|
return {
|
|
15578
15958
|
status_code: 200,
|
|
15579
|
-
body: {
|
|
15959
|
+
body: {
|
|
15960
|
+
memories: limit !== void 0 ? filtered.slice(offset, offset + limit) : filtered,
|
|
15961
|
+
total: filtered.length,
|
|
15962
|
+
offset,
|
|
15963
|
+
limit: limit ?? null
|
|
15964
|
+
}
|
|
15580
15965
|
};
|
|
15581
15966
|
});
|
|
15582
15967
|
sdk.registerTrigger({
|
|
@@ -18656,8 +19041,8 @@ function getAllTools() {
|
|
|
18656
19041
|
];
|
|
18657
19042
|
}
|
|
18658
19043
|
function getVisibleTools() {
|
|
18659
|
-
if ((process.env["AGENTMEMORY_TOOLS"] || "
|
|
18660
|
-
return getAllTools()
|
|
19044
|
+
if ((process.env["AGENTMEMORY_TOOLS"] || "all") === "core") return getAllTools().filter((t) => ESSENTIAL_TOOLS.has(t.name));
|
|
19045
|
+
return getAllTools();
|
|
18661
19046
|
}
|
|
18662
19047
|
|
|
18663
19048
|
//#endregion
|
|
@@ -20295,201 +20680,6 @@ function registerMcpEndpoints(sdk, kv, secret) {
|
|
|
20295
20680
|
});
|
|
20296
20681
|
}
|
|
20297
20682
|
|
|
20298
|
-
//#endregion
|
|
20299
|
-
//#region src/viewer/server.ts
|
|
20300
|
-
function loadViewerFavicon() {
|
|
20301
|
-
const base = dirname(fileURLToPath(import.meta.url));
|
|
20302
|
-
const candidates = [
|
|
20303
|
-
join(base, "..", "src", "viewer", "favicon.svg"),
|
|
20304
|
-
join(base, "..", "viewer", "favicon.svg"),
|
|
20305
|
-
join(base, "viewer", "favicon.svg")
|
|
20306
|
-
];
|
|
20307
|
-
for (const path of candidates) try {
|
|
20308
|
-
return readFileSync(path);
|
|
20309
|
-
} catch {}
|
|
20310
|
-
return null;
|
|
20311
|
-
}
|
|
20312
|
-
const ALLOWED_ORIGINS = (process.env.VIEWER_ALLOWED_ORIGINS || "http://localhost:3111,http://localhost:3113,http://127.0.0.1:3111,http://127.0.0.1:3113").split(",").map((o) => o.trim());
|
|
20313
|
-
const ALLOWED_HOSTS_OVERRIDE = (process.env.VIEWER_ALLOWED_HOSTS || "").split(",").map((h) => h.trim().toLowerCase()).filter(Boolean);
|
|
20314
|
-
function buildAllowedHosts(origins, listenPort) {
|
|
20315
|
-
const hosts = /* @__PURE__ */ new Set();
|
|
20316
|
-
for (const o of origins) try {
|
|
20317
|
-
const parsed = new URL(o);
|
|
20318
|
-
if (parsed.host) hosts.add(parsed.host.toLowerCase());
|
|
20319
|
-
} catch {}
|
|
20320
|
-
hosts.add(`localhost:${listenPort}`);
|
|
20321
|
-
hosts.add(`127.0.0.1:${listenPort}`);
|
|
20322
|
-
hosts.add(`[::1]:${listenPort}`);
|
|
20323
|
-
for (const h of ALLOWED_HOSTS_OVERRIDE) hosts.add(h);
|
|
20324
|
-
return hosts;
|
|
20325
|
-
}
|
|
20326
|
-
function isHostAllowed(headerHost, allowed) {
|
|
20327
|
-
if (typeof headerHost !== "string") return false;
|
|
20328
|
-
const lower = headerHost.toLowerCase().trim();
|
|
20329
|
-
if (!lower) return false;
|
|
20330
|
-
return allowed.has(lower);
|
|
20331
|
-
}
|
|
20332
|
-
function corsHeaders(req) {
|
|
20333
|
-
const origin = req.headers.origin || "";
|
|
20334
|
-
const allowed = ALLOWED_ORIGINS.includes(origin) ? origin : ALLOWED_ORIGINS[0];
|
|
20335
|
-
return {
|
|
20336
|
-
"Access-Control-Allow-Origin": allowed,
|
|
20337
|
-
"Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS",
|
|
20338
|
-
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
20339
|
-
Vary: "Origin"
|
|
20340
|
-
};
|
|
20341
|
-
}
|
|
20342
|
-
function json(res, status, data, req) {
|
|
20343
|
-
const body = JSON.stringify(data);
|
|
20344
|
-
const cors = req ? corsHeaders(req) : {
|
|
20345
|
-
"Access-Control-Allow-Origin": ALLOWED_ORIGINS[0],
|
|
20346
|
-
Vary: "Origin"
|
|
20347
|
-
};
|
|
20348
|
-
res.writeHead(status, {
|
|
20349
|
-
...cors,
|
|
20350
|
-
"Content-Type": "application/json"
|
|
20351
|
-
});
|
|
20352
|
-
res.end(body);
|
|
20353
|
-
}
|
|
20354
|
-
function readBody(req) {
|
|
20355
|
-
return new Promise((resolve, reject) => {
|
|
20356
|
-
let data = "";
|
|
20357
|
-
let size = 0;
|
|
20358
|
-
req.on("data", (chunk) => {
|
|
20359
|
-
size += chunk.length;
|
|
20360
|
-
if (size > 1e6) {
|
|
20361
|
-
req.destroy();
|
|
20362
|
-
reject(/* @__PURE__ */ new Error("too large"));
|
|
20363
|
-
return;
|
|
20364
|
-
}
|
|
20365
|
-
data += chunk.toString();
|
|
20366
|
-
});
|
|
20367
|
-
req.on("end", () => resolve(data));
|
|
20368
|
-
req.on("error", reject);
|
|
20369
|
-
});
|
|
20370
|
-
}
|
|
20371
|
-
const MAX_VIEWER_PORT_RETRIES = 10;
|
|
20372
|
-
function startViewerServer(port, _kv, _sdk, secret, restPort) {
|
|
20373
|
-
const resolvedRestPort = restPort ?? port - 2;
|
|
20374
|
-
const requestedPort = port;
|
|
20375
|
-
let allowedHosts = null;
|
|
20376
|
-
const server = createServer(async (req, res) => {
|
|
20377
|
-
if (!allowedHosts) {
|
|
20378
|
-
const addr = server.address();
|
|
20379
|
-
allowedHosts = buildAllowedHosts(ALLOWED_ORIGINS, addr && typeof addr === "object" && "port" in addr ? addr.port : port);
|
|
20380
|
-
}
|
|
20381
|
-
if (!isHostAllowed(req.headers.host, allowedHosts)) {
|
|
20382
|
-
res.writeHead(403, { "Content-Type": "text/plain" });
|
|
20383
|
-
res.end("forbidden host");
|
|
20384
|
-
return;
|
|
20385
|
-
}
|
|
20386
|
-
const raw = req.url || "/";
|
|
20387
|
-
const qIdx = raw.indexOf("?");
|
|
20388
|
-
const pathname = qIdx >= 0 ? raw.slice(0, qIdx) : raw;
|
|
20389
|
-
const qs = qIdx >= 0 ? raw.slice(qIdx + 1) : "";
|
|
20390
|
-
const method = req.method || "GET";
|
|
20391
|
-
if (method === "OPTIONS") {
|
|
20392
|
-
res.writeHead(204, {
|
|
20393
|
-
...corsHeaders(req),
|
|
20394
|
-
"Access-Control-Max-Age": "86400"
|
|
20395
|
-
});
|
|
20396
|
-
res.end();
|
|
20397
|
-
return;
|
|
20398
|
-
}
|
|
20399
|
-
if (method === "GET" && (pathname === "/" || pathname === "/viewer" || pathname === "/agentmemory/viewer")) {
|
|
20400
|
-
const rendered = renderViewerDocument();
|
|
20401
|
-
if (rendered.found) {
|
|
20402
|
-
res.writeHead(200, {
|
|
20403
|
-
"Content-Type": "text/html; charset=utf-8",
|
|
20404
|
-
"Content-Security-Policy": rendered.csp,
|
|
20405
|
-
"Cache-Control": "no-cache"
|
|
20406
|
-
});
|
|
20407
|
-
res.end(rendered.html);
|
|
20408
|
-
return;
|
|
20409
|
-
}
|
|
20410
|
-
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
20411
|
-
res.end("viewer not found");
|
|
20412
|
-
return;
|
|
20413
|
-
}
|
|
20414
|
-
if (method === "GET" && pathname === "/favicon.svg") {
|
|
20415
|
-
const favicon = loadViewerFavicon();
|
|
20416
|
-
if (favicon) {
|
|
20417
|
-
res.writeHead(200, {
|
|
20418
|
-
"Content-Type": "image/svg+xml",
|
|
20419
|
-
"Cache-Control": "public, max-age=3600"
|
|
20420
|
-
});
|
|
20421
|
-
res.end(favicon);
|
|
20422
|
-
return;
|
|
20423
|
-
}
|
|
20424
|
-
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
20425
|
-
res.end("favicon not found");
|
|
20426
|
-
return;
|
|
20427
|
-
}
|
|
20428
|
-
try {
|
|
20429
|
-
await proxyToRestApi(resolvedRestPort, pathname, qs, method, req, res, secret);
|
|
20430
|
-
} catch (err) {
|
|
20431
|
-
console.error(`[viewer] proxy error on ${method} ${pathname}:`, err);
|
|
20432
|
-
json(res, 502, { error: "upstream error" }, req);
|
|
20433
|
-
}
|
|
20434
|
-
});
|
|
20435
|
-
let attempt = 0;
|
|
20436
|
-
let currentPort = requestedPort;
|
|
20437
|
-
const tryListen = () => {
|
|
20438
|
-
server.listen(currentPort, "127.0.0.1");
|
|
20439
|
-
};
|
|
20440
|
-
server.on("listening", () => {
|
|
20441
|
-
if (currentPort === requestedPort) console.log(`[agentmemory] Viewer: http://localhost:${currentPort}`);
|
|
20442
|
-
else console.log(`[agentmemory] Viewer started on http://localhost:${currentPort} (fallback from ${requestedPort})`);
|
|
20443
|
-
});
|
|
20444
|
-
server.on("error", (err) => {
|
|
20445
|
-
if (err.code === "EADDRINUSE" && attempt < MAX_VIEWER_PORT_RETRIES) {
|
|
20446
|
-
attempt++;
|
|
20447
|
-
currentPort = requestedPort + attempt;
|
|
20448
|
-
setImmediate(tryListen);
|
|
20449
|
-
return;
|
|
20450
|
-
}
|
|
20451
|
-
if (err.code === "EADDRINUSE") console.warn(`[agentmemory] Viewer ports ${requestedPort}-${requestedPort + MAX_VIEWER_PORT_RETRIES} all in use, skipping viewer.`);
|
|
20452
|
-
else console.error(`[agentmemory] Viewer error:`, err.message);
|
|
20453
|
-
});
|
|
20454
|
-
tryListen();
|
|
20455
|
-
return server;
|
|
20456
|
-
}
|
|
20457
|
-
async function proxyToRestApi(restPort, pathname, qs, method, req, res, secret) {
|
|
20458
|
-
const upstreamUrl = `http://127.0.0.1:${restPort}${pathname.startsWith("/agentmemory/") ? pathname : `/agentmemory${pathname.startsWith("/") ? pathname : "/" + pathname}`}${qs ? "?" + qs : ""}`;
|
|
20459
|
-
const headers = {};
|
|
20460
|
-
if (secret) headers["Authorization"] = `Bearer ${secret}`;
|
|
20461
|
-
const ct = req.headers["content-type"];
|
|
20462
|
-
if (ct) headers["Content-Type"] = ct;
|
|
20463
|
-
let body;
|
|
20464
|
-
if (method === "POST" || method === "PUT" || method === "DELETE" || method === "PATCH") body = await readBody(req);
|
|
20465
|
-
const controller = new AbortController();
|
|
20466
|
-
const fetchTimeout = setTimeout(() => controller.abort(), 1e4);
|
|
20467
|
-
let upstream;
|
|
20468
|
-
try {
|
|
20469
|
-
upstream = await fetch(upstreamUrl, {
|
|
20470
|
-
method,
|
|
20471
|
-
headers,
|
|
20472
|
-
body: body || void 0,
|
|
20473
|
-
signal: controller.signal
|
|
20474
|
-
});
|
|
20475
|
-
clearTimeout(fetchTimeout);
|
|
20476
|
-
} catch (err) {
|
|
20477
|
-
clearTimeout(fetchTimeout);
|
|
20478
|
-
if (err instanceof Error && err.name === "AbortError") {
|
|
20479
|
-
json(res, 504, { error: "upstream timeout" }, req);
|
|
20480
|
-
return;
|
|
20481
|
-
}
|
|
20482
|
-
throw err;
|
|
20483
|
-
}
|
|
20484
|
-
const cors = corsHeaders(req);
|
|
20485
|
-
const responseBody = await upstream.text();
|
|
20486
|
-
const responseHeaders = { ...cors };
|
|
20487
|
-
const upstreamCt = upstream.headers.get("content-type");
|
|
20488
|
-
if (upstreamCt) responseHeaders["Content-Type"] = upstreamCt;
|
|
20489
|
-
res.writeHead(upstream.status, responseHeaders);
|
|
20490
|
-
res.end(responseBody);
|
|
20491
|
-
}
|
|
20492
|
-
|
|
20493
20683
|
//#endregion
|
|
20494
20684
|
//#region src/eval/metrics-store.ts
|
|
20495
20685
|
var MetricsStore = class {
|
|
@@ -20627,6 +20817,21 @@ function initMetrics(getMeter) {
|
|
|
20627
20817
|
|
|
20628
20818
|
//#endregion
|
|
20629
20819
|
//#region src/index.ts
|
|
20820
|
+
function workerPidfilePath() {
|
|
20821
|
+
return join(homedir(), ".agentmemory", "worker.pid");
|
|
20822
|
+
}
|
|
20823
|
+
function writeWorkerPidfile() {
|
|
20824
|
+
try {
|
|
20825
|
+
const p = workerPidfilePath();
|
|
20826
|
+
mkdirSync(dirname(p), { recursive: true });
|
|
20827
|
+
writeFileSync(p, `${process.pid}\n`, { encoding: "utf-8" });
|
|
20828
|
+
} catch {}
|
|
20829
|
+
}
|
|
20830
|
+
function clearWorkerPidfile() {
|
|
20831
|
+
try {
|
|
20832
|
+
unlinkSync(workerPidfilePath());
|
|
20833
|
+
} catch {}
|
|
20834
|
+
}
|
|
20630
20835
|
function hasGetMeter(sdk) {
|
|
20631
20836
|
return typeof sdk === "object" && sdk !== null && "getMeter" in sdk && typeof sdk.getMeter === "function";
|
|
20632
20837
|
}
|
|
@@ -20667,6 +20872,7 @@ async function main() {
|
|
|
20667
20872
|
framework: "iii-sdk"
|
|
20668
20873
|
}
|
|
20669
20874
|
});
|
|
20875
|
+
writeWorkerPidfile();
|
|
20670
20876
|
const kv = new StateKV(sdk);
|
|
20671
20877
|
const secret = getEnvVar("AGENTMEMORY_SECRET");
|
|
20672
20878
|
const metricsStore = new MetricsStore(kv);
|
|
@@ -20762,6 +20968,7 @@ async function main() {
|
|
|
20762
20968
|
registerMcpEndpoints(sdk, kv, secret);
|
|
20763
20969
|
const healthMonitor = registerHealthMonitor(sdk, kv);
|
|
20764
20970
|
const indexPersistence = new IndexPersistence(kv, bm25Index, vectorIndex);
|
|
20971
|
+
setIndexPersistence(indexPersistence);
|
|
20765
20972
|
const loaded = await indexPersistence.load().catch((err) => {
|
|
20766
20973
|
console.warn(`[agentmemory] Failed to load persisted index:`, err);
|
|
20767
20974
|
return null;
|
|
@@ -20879,6 +21086,7 @@ async function main() {
|
|
|
20879
21086
|
console.warn(`[agentmemory] Failed to save index on shutdown:`, err);
|
|
20880
21087
|
});
|
|
20881
21088
|
await sdk.shutdown();
|
|
21089
|
+
clearWorkerPidfile();
|
|
20882
21090
|
process.exit(0);
|
|
20883
21091
|
};
|
|
20884
21092
|
process.on("SIGINT", shutdown);
|