@agentmemory/agentmemory 0.9.28 → 0.9.30
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 +98 -28
- package/AGENTS.md +19 -26
- package/README.md +619 -274
- package/dist/.env.example +98 -28
- package/dist/cli.d.mts.map +1 -1
- package/dist/cli.mjs +1328 -244
- package/dist/cli.mjs.map +1 -1
- package/dist/{connect-JnsJXc68.mjs → connect-B4_ca6BD.mjs} +619 -70
- package/dist/connect-B4_ca6BD.mjs.map +1 -0
- package/dist/docker-compose.yml +42 -16
- package/dist/hooks/_capture.d.mts +22 -0
- package/dist/hooks/_capture.d.mts.map +1 -0
- package/dist/hooks/_capture.mjs +908 -0
- package/dist/hooks/_capture.mjs.map +1 -0
- package/dist/hooks/antigravity-bridge.d.mts +8 -0
- package/dist/hooks/antigravity-bridge.d.mts.map +1 -0
- package/dist/hooks/antigravity-bridge.mjs +123 -0
- package/dist/hooks/antigravity-bridge.mjs.map +1 -0
- package/dist/hooks/notification.mjs +34 -27
- package/dist/hooks/notification.mjs.map +1 -1
- package/dist/hooks/post-commit.mjs +84 -2
- package/dist/hooks/post-commit.mjs.map +1 -1
- package/dist/hooks/post-tool-failure.mjs +71 -26
- package/dist/hooks/post-tool-failure.mjs.map +1 -1
- package/dist/hooks/post-tool-use.mjs +99 -36
- package/dist/hooks/post-tool-use.mjs.map +1 -1
- package/dist/hooks/pre-compact.mjs +95 -5
- package/dist/hooks/pre-compact.mjs.map +1 -1
- package/dist/hooks/pre-tool-use.mjs +72 -2
- package/dist/hooks/pre-tool-use.mjs.map +1 -1
- package/dist/hooks/prompt-submit.mjs +25 -22
- package/dist/hooks/prompt-submit.mjs.map +1 -1
- package/dist/hooks/session-end.mjs +93 -24
- package/dist/hooks/session-end.mjs.map +1 -1
- package/dist/hooks/session-start.mjs +101 -6
- package/dist/hooks/session-start.mjs.map +1 -1
- package/dist/hooks/stop.mjs +72 -8
- package/dist/hooks/stop.mjs.map +1 -1
- package/dist/hooks/subagent-start.mjs +28 -26
- package/dist/hooks/subagent-start.mjs.map +1 -1
- package/dist/hooks/subagent-stop.mjs +29 -26
- package/dist/hooks/subagent-stop.mjs.map +1 -1
- package/dist/hooks/task-completed.mjs +31 -28
- package/dist/hooks/task-completed.mjs.map +1 -1
- package/dist/iii-config.docker.yaml +10 -10
- package/dist/iii-config.yaml +3 -1
- package/dist/{image-refs-BKLmp_sD.mjs → image-refs-BaT-9oMU.mjs} +2 -2
- package/dist/image-refs-BaT-9oMU.mjs.map +1 -0
- package/dist/{image-refs-C2YU7BIm.mjs → image-refs-T_urbwHM.mjs} +3 -3
- package/dist/image-refs-T_urbwHM.mjs.map +1 -0
- package/dist/{keyed-mutex-DTHtcZrt.mjs → image-store-BsN4vyjr.mjs} +2 -14
- package/dist/image-store-BsN4vyjr.mjs.map +1 -0
- package/dist/index.mjs +14021 -6494
- package/dist/index.mjs.map +1 -1
- package/dist/keyed-mutex-DchXsyfP.mjs +221 -0
- package/dist/keyed-mutex-DchXsyfP.mjs.map +1 -0
- package/dist/secret-store-BnY3A50C.mjs +2348 -0
- package/dist/secret-store-BnY3A50C.mjs.map +1 -0
- package/dist/{src-CzgoepGU.mjs → src-Jsq9LCeH.mjs} +7349 -2940
- package/dist/src-Jsq9LCeH.mjs.map +1 -0
- package/dist/{standalone-YsZFFQBM.mjs → standalone-DKCdrf1d.mjs} +62 -24
- package/dist/standalone-DKCdrf1d.mjs.map +1 -0
- package/dist/standalone.d.mts.map +1 -1
- package/dist/standalone.mjs +298 -27
- package/dist/standalone.mjs.map +1 -1
- package/dist/status-IRz7ta8-.mjs +2344 -0
- package/dist/status-IRz7ta8-.mjs.map +1 -0
- package/dist/viewer/favicon.svg +35 -1
- package/dist/viewer/index.html +7428 -2608
- package/docker-compose.yml +42 -16
- package/iii-config.docker.yaml +10 -10
- package/iii-config.yaml +3 -1
- package/integrations/pi/README.md +77 -0
- package/integrations/pi/index.ts +402 -0
- package/integrations/pi/package.json +32 -0
- package/integrations/pi/security.ts +87 -0
- package/package.json +15 -7
- package/plugin/.claude-plugin/plugin.json +2 -2
- package/plugin/.codex-plugin/plugin.json +26 -4
- package/plugin/.devin-plugin/plugin.json +32 -0
- package/plugin/.mcp.codex.json +12 -0
- package/plugin/.mcp.copilot.json +2 -7
- package/plugin/.mcp.json +2 -7
- package/plugin/assets/icon.svg +35 -0
- package/plugin/cursor/hooks.json +41 -0
- package/plugin/cursor/mcp.json +12 -0
- package/plugin/hooks/hooks.antigravity.json +43 -0
- package/plugin/hooks/hooks.copilot.json +11 -11
- package/plugin/hooks/hooks.devin.json +67 -0
- package/plugin/hooks/hooks.droid.json +55 -0
- package/plugin/opencode/README.md +267 -31
- package/plugin/opencode/agentmemory-capture.ts +644 -15
- package/plugin/opencode/plugin.json +1 -1
- package/plugin/plugin.json +2 -2
- package/plugin/scripts/_capture.d.mts +22 -0
- package/plugin/scripts/_capture.d.mts.map +1 -0
- package/plugin/scripts/_capture.mjs +908 -0
- package/plugin/scripts/_capture.mjs.map +1 -0
- package/plugin/scripts/antigravity-bridge.d.mts +8 -0
- package/plugin/scripts/antigravity-bridge.d.mts.map +1 -0
- package/plugin/scripts/antigravity-bridge.mjs +123 -0
- package/plugin/scripts/antigravity-bridge.mjs.map +1 -0
- package/plugin/scripts/notification.mjs +34 -27
- package/plugin/scripts/notification.mjs.map +1 -1
- package/plugin/scripts/plugin-bridge.mjs +378 -0
- package/plugin/scripts/post-commit.mjs +84 -2
- package/plugin/scripts/post-commit.mjs.map +1 -1
- package/plugin/scripts/post-tool-failure.mjs +71 -26
- package/plugin/scripts/post-tool-failure.mjs.map +1 -1
- package/plugin/scripts/post-tool-use.mjs +99 -36
- package/plugin/scripts/post-tool-use.mjs.map +1 -1
- package/plugin/scripts/pre-compact.mjs +95 -5
- package/plugin/scripts/pre-compact.mjs.map +1 -1
- package/plugin/scripts/pre-tool-use.mjs +72 -2
- package/plugin/scripts/pre-tool-use.mjs.map +1 -1
- package/plugin/scripts/prompt-submit.mjs +25 -22
- package/plugin/scripts/prompt-submit.mjs.map +1 -1
- package/plugin/scripts/session-end.mjs +93 -24
- package/plugin/scripts/session-end.mjs.map +1 -1
- package/plugin/scripts/session-start.mjs +101 -6
- package/plugin/scripts/session-start.mjs.map +1 -1
- package/plugin/scripts/stop.mjs +72 -8
- package/plugin/scripts/stop.mjs.map +1 -1
- package/plugin/scripts/subagent-start.mjs +28 -26
- package/plugin/scripts/subagent-start.mjs.map +1 -1
- package/plugin/scripts/subagent-stop.mjs +29 -26
- package/plugin/scripts/subagent-stop.mjs.map +1 -1
- package/plugin/scripts/task-completed.mjs +31 -28
- package/plugin/scripts/task-completed.mjs.map +1 -1
- package/plugin/skills/_shared/TROUBLESHOOTING.md +22 -6
- package/plugin/skills/agentmemory-agents/REFERENCE.md +8 -5
- package/plugin/skills/agentmemory-config/REFERENCE.md +52 -1
- package/plugin/skills/agentmemory-config/SKILL.md +44 -7
- package/plugin/skills/agentmemory-mcp-tools/REFERENCE.md +4 -3
- package/plugin/skills/agentmemory-rest-api/REFERENCE.md +23 -2
- package/plugin/skills/forget/SKILL.md +4 -1
- package/plugin/skills/handoff/SKILL.md +4 -2
- package/plugin/skills/lesson/SKILL.md +64 -0
- package/plugin/skills/memory-discipline/SKILL.md +65 -0
- package/plugin/skills/recall/SKILL.md +4 -1
- package/plugin/skills/remember/SKILL.md +6 -1
- package/dist/config-ChC9EtgV.mjs +0 -259
- package/dist/config-ChC9EtgV.mjs.map +0 -1
- package/dist/connect-JnsJXc68.mjs.map +0 -1
- package/dist/image-refs-BKLmp_sD.mjs.map +0 -1
- package/dist/image-refs-C2YU7BIm.mjs.map +0 -1
- package/dist/keyed-mutex-DTHtcZrt.mjs.map +0 -1
- package/dist/logger-BEB7pCI9.mjs +0 -43
- package/dist/logger-BEB7pCI9.mjs.map +0 -1
- package/dist/schema-CNiCWzV9.mjs +0 -78
- package/dist/schema-CNiCWzV9.mjs.map +0 -1
- package/dist/src-CzgoepGU.mjs.map +0 -1
- package/dist/standalone-YsZFFQBM.mjs.map +0 -1
- package/dist/tools-registry-D3NEoe5m.mjs +0 -1115
- package/dist/tools-registry-D3NEoe5m.mjs.map +0 -1
- package/plugin/scripts/diagnostics.d.mts +0 -17
- package/plugin/scripts/diagnostics.d.mts.map +0 -1
- package/plugin/scripts/diagnostics.mjs.map +0 -1
|
@@ -1,12 +1,67 @@
|
|
|
1
1
|
import type { Plugin } from "@opencode-ai/plugin";
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { basename, join } from "node:path";
|
|
2
6
|
|
|
3
7
|
const API = process.env.AGENTMEMORY_URL || "http://localhost:3111";
|
|
4
|
-
|
|
8
|
+
// OpenCode reports tool names in lowercase ("read", "edit", ...); matching is
|
|
9
|
+
// case-insensitive at the call site so a future casing change cannot silently
|
|
10
|
+
// kill file enrichment again.
|
|
11
|
+
const FILE_TOOLS = new Set(["read", "write", "edit", "glob", "grep"]);
|
|
5
12
|
const FILE_KEYS = ["filePath", "file_path", "path", "file", "pattern"];
|
|
6
13
|
const MAX_STASHED_FILES = 20;
|
|
7
14
|
|
|
8
15
|
const DEBUG = process.env.OPENCODE_AGENTMEMORY_DEBUG === "1";
|
|
9
|
-
|
|
16
|
+
function usableSecret(value: unknown): string {
|
|
17
|
+
const trimmed = typeof value === "string" ? value.trim() : "";
|
|
18
|
+
if (!trimmed || (trimmed.startsWith("${") && trimmed.endsWith("}"))) return "";
|
|
19
|
+
return trimmed;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function readAgentmemoryFile(name: string): string {
|
|
23
|
+
try {
|
|
24
|
+
return readFileSync(join(homedir(), ".agentmemory", name), "utf-8");
|
|
25
|
+
} catch {
|
|
26
|
+
return "";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function envFileSecret(): string {
|
|
31
|
+
let found = "";
|
|
32
|
+
for (const line of readAgentmemoryFile(".env").split("\n")) {
|
|
33
|
+
const trimmed = line.trim();
|
|
34
|
+
if (trimmed.startsWith("#")) continue;
|
|
35
|
+
const eq = trimmed.indexOf("=");
|
|
36
|
+
if (eq === -1) continue;
|
|
37
|
+
if (trimmed.slice(0, eq).replace(/^export\s+/, "").trim() !== "AGENTMEMORY_SECRET") continue;
|
|
38
|
+
let value = trimmed.slice(eq + 1).trim();
|
|
39
|
+
const quote = value[0];
|
|
40
|
+
const close = quote === '"' || quote === "'" ? value.indexOf(quote, 1) : -1;
|
|
41
|
+
if (close > 0) value = value.slice(1, close);
|
|
42
|
+
else if (value.includes(" #")) value = value.slice(0, value.indexOf(" #"));
|
|
43
|
+
found = usableSecret(value);
|
|
44
|
+
}
|
|
45
|
+
return found;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function isLoopbackUrl(url: string): boolean {
|
|
49
|
+
try {
|
|
50
|
+
const host = new URL(url).hostname.replace(/^\[|\]$/g, "").toLowerCase();
|
|
51
|
+
return host === "localhost" || host === "::1" || /^127(?:\.\d{1,3}){3}$/.test(host);
|
|
52
|
+
} catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function resolveSecret(url: string, explicit: string | undefined): string {
|
|
58
|
+
const configured = usableSecret(explicit);
|
|
59
|
+
if (configured) return configured;
|
|
60
|
+
if (!isLoopbackUrl(url)) return "";
|
|
61
|
+
return envFileSecret() || usableSecret(readAgentmemoryFile("secret"));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const SECRET = resolveSecret(API, process.env.AGENTMEMORY_SECRET);
|
|
10
65
|
|
|
11
66
|
function authHeaders(): Record<string, string> {
|
|
12
67
|
const headers: Record<string, string> = { "Content-Type": "application/json" };
|
|
@@ -47,11 +102,12 @@ async function observe(
|
|
|
47
102
|
hookType: string,
|
|
48
103
|
data: Record<string, unknown>,
|
|
49
104
|
): Promise<void> {
|
|
105
|
+
const proj = projectFor(sessionId);
|
|
50
106
|
await post("/observe", {
|
|
51
107
|
hookType,
|
|
52
108
|
sessionId,
|
|
53
|
-
project:
|
|
54
|
-
cwd:
|
|
109
|
+
project: proj.name,
|
|
110
|
+
cwd: proj.cwd,
|
|
55
111
|
timestamp: new Date().toISOString(),
|
|
56
112
|
data,
|
|
57
113
|
});
|
|
@@ -59,7 +115,46 @@ async function observe(
|
|
|
59
115
|
|
|
60
116
|
let activeSessionId: string | null = null;
|
|
61
117
|
let pendingConfig: Record<string, unknown> | null = null;
|
|
62
|
-
|
|
118
|
+
// Default scope resolved at plugin init (same resolution order as the hooks'
|
|
119
|
+
// resolveProject: env override, git toplevel basename, cwd basename). In a
|
|
120
|
+
// long-lived OpenCode process serving multiple directories these defaults are
|
|
121
|
+
// only a fallback — attribution is per-session via sessionProjects, resolved
|
|
122
|
+
// from each session's own directory at session.created. Module-level-only
|
|
123
|
+
// state recorded home-directory sessions under whatever repo loaded first.
|
|
124
|
+
let defaultProjectName: string | null = null;
|
|
125
|
+
let defaultProjectCwd: string | null = null;
|
|
126
|
+
const sessionProjects = new Map<string, { name: string; cwd: string }>();
|
|
127
|
+
|
|
128
|
+
function projectFor(sessionId: string): { name: string | null; cwd: string | null } {
|
|
129
|
+
const p = sessionProjects.get(sessionId);
|
|
130
|
+
return p ?? { name: defaultProjectName, cwd: defaultProjectCwd };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const projectNameCache = new Map<string, string>();
|
|
134
|
+
|
|
135
|
+
function resolveProjectName(dir: string): string {
|
|
136
|
+
const explicit = process.env.AGENTMEMORY_PROJECT_NAME?.trim();
|
|
137
|
+
if (explicit) return explicit;
|
|
138
|
+
const cached = projectNameCache.get(dir);
|
|
139
|
+
if (cached !== undefined) return cached;
|
|
140
|
+
try {
|
|
141
|
+
const top = execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
142
|
+
cwd: dir,
|
|
143
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
144
|
+
encoding: "utf8",
|
|
145
|
+
}).trim();
|
|
146
|
+
if (top) {
|
|
147
|
+
const name = basename(top);
|
|
148
|
+
projectNameCache.set(dir, name);
|
|
149
|
+
return name;
|
|
150
|
+
}
|
|
151
|
+
} catch {
|
|
152
|
+
// not a git repo, fall through
|
|
153
|
+
}
|
|
154
|
+
const fallback = basename(dir) || dir;
|
|
155
|
+
projectNameCache.set(dir, fallback);
|
|
156
|
+
return fallback;
|
|
157
|
+
}
|
|
63
158
|
const stashedFiles = new Map<string, Set<string>>();
|
|
64
159
|
const seenSubtaskIds = new Map<string, Set<string>>();
|
|
65
160
|
const seenToolCallIds = new Map<string, Set<string>>();
|
|
@@ -93,6 +188,7 @@ function pruneSessionMaps(sid: string): void {
|
|
|
93
188
|
stashedFiles.delete(sid);
|
|
94
189
|
seenSubtaskIds.delete(sid);
|
|
95
190
|
seenToolCallIds.delete(sid);
|
|
191
|
+
sessionProjects.delete(sid);
|
|
96
192
|
}
|
|
97
193
|
|
|
98
194
|
function safeSlice(v: unknown, max: number): string {
|
|
@@ -167,8 +263,9 @@ function extractErrorMessage(err: unknown): string {
|
|
|
167
263
|
return String(err ?? "");
|
|
168
264
|
}
|
|
169
265
|
|
|
170
|
-
|
|
171
|
-
|
|
266
|
+
const v1Hooks: Plugin = async (ctx) => {
|
|
267
|
+
defaultProjectCwd = ctx.worktree || ctx.project?.id || process.cwd();
|
|
268
|
+
defaultProjectName = resolveProjectName(defaultProjectCwd);
|
|
172
269
|
|
|
173
270
|
return {
|
|
174
271
|
event: async ({ event }) => {
|
|
@@ -188,13 +285,28 @@ export const AgentmemoryCapturePlugin: Plugin = async (ctx) => {
|
|
|
188
285
|
// and another `session.created` event during the await could
|
|
189
286
|
// rebind it, causing context to be cached against the wrong key.
|
|
190
287
|
const sessionId = activeSessionId;
|
|
288
|
+
// Attribute this session to its own directory when the event
|
|
289
|
+
// carries one; a multi-directory OpenCode process otherwise
|
|
290
|
+
// records every session under whichever repo loaded the plugin.
|
|
291
|
+
const sessionDir =
|
|
292
|
+
typeof info?.directory === "string" && info.directory
|
|
293
|
+
? info.directory
|
|
294
|
+
: defaultProjectCwd;
|
|
295
|
+
let proj: { name: string | null; cwd: string | null };
|
|
296
|
+
if (sessionDir) {
|
|
297
|
+
const entry = { cwd: sessionDir, name: resolveProjectName(sessionDir) };
|
|
298
|
+
sessionProjects.set(sessionId, entry);
|
|
299
|
+
proj = entry;
|
|
300
|
+
} else {
|
|
301
|
+
proj = projectFor(sessionId);
|
|
302
|
+
}
|
|
191
303
|
const startResult = await postJson("/session/start", {
|
|
192
304
|
sessionId,
|
|
193
305
|
title: info?.title ?? null,
|
|
194
306
|
parentID: info?.parentID ?? null,
|
|
195
307
|
version: info?.version ?? null,
|
|
196
|
-
project:
|
|
197
|
-
cwd:
|
|
308
|
+
project: proj.name,
|
|
309
|
+
cwd: proj.cwd,
|
|
198
310
|
});
|
|
199
311
|
// cache the context returned at session/start so the
|
|
200
312
|
// chat.system.transform hook injects it without a second fetch.
|
|
@@ -272,10 +384,8 @@ export const AgentmemoryCapturePlugin: Plugin = async (ctx) => {
|
|
|
272
384
|
post("/crystals/auto", { olderThanDays: 7 }, 30000);
|
|
273
385
|
post("/consolidate-pipeline", { tier: "all", force: true }, 30000);
|
|
274
386
|
if (sid === activeSessionId) activeSessionId = null;
|
|
275
|
-
|
|
387
|
+
pruneSessionMaps(sid);
|
|
276
388
|
startContextCache.delete(sid);
|
|
277
|
-
seenSubtaskIds.delete(sid);
|
|
278
|
-
seenToolCallIds.delete(sid);
|
|
279
389
|
contextInjectedSessions.delete(sid);
|
|
280
390
|
}
|
|
281
391
|
|
|
@@ -581,7 +691,7 @@ export const AgentmemoryCapturePlugin: Plugin = async (ctx) => {
|
|
|
581
691
|
|
|
582
692
|
// ── tool.execute.before ──
|
|
583
693
|
"tool.execute.before": async (input, output) => {
|
|
584
|
-
if (!FILE_TOOLS.has(input.tool)) return;
|
|
694
|
+
if (!FILE_TOOLS.has(String(input.tool ?? "").toLowerCase())) return;
|
|
585
695
|
const sid = input.sessionID || activeSessionId;
|
|
586
696
|
if (!sid) return;
|
|
587
697
|
const args = output.args as Record<string, unknown> | undefined;
|
|
@@ -612,7 +722,7 @@ export const AgentmemoryCapturePlugin: Plugin = async (ctx) => {
|
|
|
612
722
|
if (typeof ctx !== "string" || ctx.length === 0) {
|
|
613
723
|
const result = await postJson("/context", {
|
|
614
724
|
sessionId: sid,
|
|
615
|
-
project:
|
|
725
|
+
project: projectFor(sid).name,
|
|
616
726
|
});
|
|
617
727
|
ctx = (result as any)?.context;
|
|
618
728
|
} else {
|
|
@@ -650,7 +760,7 @@ export const AgentmemoryCapturePlugin: Plugin = async (ctx) => {
|
|
|
650
760
|
|
|
651
761
|
const result = await postJson("/context", {
|
|
652
762
|
sessionId: sid,
|
|
653
|
-
project:
|
|
763
|
+
project: projectFor(sid).name,
|
|
654
764
|
});
|
|
655
765
|
const ctx = (result as any)?.context;
|
|
656
766
|
if (typeof ctx === "string" && ctx.length > 0) {
|
|
@@ -685,3 +795,522 @@ export const AgentmemoryCapturePlugin: Plugin = async (ctx) => {
|
|
|
685
795
|
},
|
|
686
796
|
};
|
|
687
797
|
};
|
|
798
|
+
|
|
799
|
+
async function v2Setup(ctx: any) {
|
|
800
|
+
if (typeof ctx?.tool?.hook !== "function" || typeof ctx?.session?.hook !== "function" || typeof ctx?.event?.subscribe !== "function") {
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
const location = ctx.location;
|
|
804
|
+
defaultProjectCwd = location?.directory ?? location?.project?.directory ?? process.cwd();
|
|
805
|
+
defaultProjectName = resolveProjectName(defaultProjectCwd);
|
|
806
|
+
|
|
807
|
+
async function observeV2(sessionId: string, hookType: string, data: Record<string, unknown>): Promise<void> {
|
|
808
|
+
await observe(sessionId, hookType, data);
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
const shellSessions = new Map<string, string>();
|
|
812
|
+
const turnContextCache = new Map<string, string>();
|
|
813
|
+
|
|
814
|
+
function stashAdd(sid: string, paths: Iterable<string>): void {
|
|
815
|
+
const stash = stashFor(sid);
|
|
816
|
+
for (const p of paths) stash.add(p);
|
|
817
|
+
if (stash.size > MAX_STASHED_FILES) {
|
|
818
|
+
const keep = [...stash].slice(-MAX_STASHED_FILES);
|
|
819
|
+
stash.clear();
|
|
820
|
+
for (const k of keep) stash.add(k);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
const toolNames = new Map<string, string>();
|
|
825
|
+
const toolCallInputs = new Map<string, Record<string, unknown>>();
|
|
826
|
+
const reportedToolCalls = new Set<string>();
|
|
827
|
+
const registeredSessions = new Set<string>();
|
|
828
|
+
|
|
829
|
+
function namesFrom(result: unknown): string[] {
|
|
830
|
+
const arr = Array.isArray(result) ? result : (result as any)?.data;
|
|
831
|
+
if (!Array.isArray(arr)) return [];
|
|
832
|
+
return arr.map((x: any) => (typeof x === "string" ? x : x?.id ?? x?.name)).filter(Boolean);
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
let lastConfigSnapshot: string | null = null;
|
|
836
|
+
|
|
837
|
+
async function snapshotConfig(): Promise<void> {
|
|
838
|
+
const [agents, providers, mcp, modelDefault] = await Promise.all([
|
|
839
|
+
ctx.agent?.list?.().catch(() => null),
|
|
840
|
+
ctx.provider?.list?.().catch(() => null),
|
|
841
|
+
ctx.mcp?.list?.().catch(() => null),
|
|
842
|
+
Promise.resolve(ctx.model?.default?.()).catch(() => null),
|
|
843
|
+
]);
|
|
844
|
+
const model = Array.isArray(modelDefault) ? modelDefault[0] : (modelDefault as any)?.data;
|
|
845
|
+
const payload = {
|
|
846
|
+
agents: namesFrom(agents),
|
|
847
|
+
providers: namesFrom(providers),
|
|
848
|
+
mcp_servers: namesFrom(mcp),
|
|
849
|
+
model: model ? `${model.providerID ?? ""}/${model.id ?? model.modelID ?? ""}` : null,
|
|
850
|
+
model_limits: model?.limit ?? null,
|
|
851
|
+
location: defaultProjectCwd,
|
|
852
|
+
};
|
|
853
|
+
const snapshot = JSON.stringify(payload);
|
|
854
|
+
if (snapshot === lastConfigSnapshot) return;
|
|
855
|
+
lastConfigSnapshot = snapshot;
|
|
856
|
+
if (activeSessionId) await observeV2(activeSessionId, "config_loaded", payload);
|
|
857
|
+
else pendingConfig = payload;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
void snapshotConfig().catch((e) => {
|
|
861
|
+
if (DEBUG) console.error("[agentmemory] config snapshot failed:", (e as Error).message);
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
await ctx.tool.hook("execute.before", (event: any) => {
|
|
865
|
+
if (!FILE_TOOLS.has(String(event?.tool ?? "").toLowerCase())) return;
|
|
866
|
+
const sid = event?.sessionID || activeSessionId;
|
|
867
|
+
if (!sid) return;
|
|
868
|
+
const args = event?.input as Record<string, unknown> | undefined;
|
|
869
|
+
if (!args) return;
|
|
870
|
+
stashAdd(sid, extractFilePaths(args));
|
|
871
|
+
});
|
|
872
|
+
|
|
873
|
+
await ctx.tool.hook("execute.after", async (event: any) => {
|
|
874
|
+
const sid = event?.sessionID || activeSessionId;
|
|
875
|
+
if (!sid) return;
|
|
876
|
+
const tool = String(event?.tool ?? "");
|
|
877
|
+
const callId = (event?.id as string) || (event?.messageID as string) || null;
|
|
878
|
+
const status = String(event?.status ?? "");
|
|
879
|
+
const result = event?.result ?? {};
|
|
880
|
+
const metadata = (result?.metadata ?? {}) as Record<string, unknown>;
|
|
881
|
+
const output = result?.output as Record<string, unknown> | undefined;
|
|
882
|
+
const raw = output?.output ?? result?.content;
|
|
883
|
+
const text = Array.isArray(raw)
|
|
884
|
+
? raw.map((p: any) => (typeof p === "string" ? p : (p?.text ?? ""))).join("\n")
|
|
885
|
+
: typeof raw === "string"
|
|
886
|
+
? raw
|
|
887
|
+
: "";
|
|
888
|
+
const startMs = typeof metadata?.started === "number" ? metadata.started : null;
|
|
889
|
+
const endMs = typeof metadata?.ended === "number" ? metadata.ended : null;
|
|
890
|
+
const duration = startMs != null && endMs != null ? endMs - startMs : null;
|
|
891
|
+
|
|
892
|
+
if (status === "error") {
|
|
893
|
+
await observeV2(sid, "post_tool_failure", {
|
|
894
|
+
tool_name: tool,
|
|
895
|
+
call_id: callId,
|
|
896
|
+
tool_input: safeSlice(event?.input, 4000),
|
|
897
|
+
tool_output: safeSlice(text || extractErrorMessage(event?.error) || extractErrorMessage(metadata?.error), 8000),
|
|
898
|
+
duration_ms: duration,
|
|
899
|
+
});
|
|
900
|
+
if (callId) reportedToolCalls.add(`${sid}:${callId}`);
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
await observeV2(sid, "post_tool_use", {
|
|
905
|
+
tool_name: tool,
|
|
906
|
+
call_id: callId,
|
|
907
|
+
tool_input: safeSlice(event?.input, 4000),
|
|
908
|
+
tool_output: safeSlice(text, 8000),
|
|
909
|
+
title: (output?.title as string) ?? null,
|
|
910
|
+
metadata: metadata,
|
|
911
|
+
duration_ms: duration,
|
|
912
|
+
attachments: Array.isArray(output?.attachments)
|
|
913
|
+
? (output?.attachments as Array<Record<string, unknown>>).map((a) => a.filename || a.url)
|
|
914
|
+
: [],
|
|
915
|
+
});
|
|
916
|
+
if (callId) reportedToolCalls.add(`${sid}:${callId}`);
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
await ctx.session.hook("prompt", async (event: any) => {
|
|
920
|
+
const sid = event?.sessionID || activeSessionId;
|
|
921
|
+
if (!sid) return;
|
|
922
|
+
const files = (event?.prompt?.files ?? [])
|
|
923
|
+
.map((f: any) => (typeof f === "string" ? f : f?.uri ?? f?.filename ?? f?.url))
|
|
924
|
+
.filter(Boolean) as string[];
|
|
925
|
+
stashAdd(sid, files);
|
|
926
|
+
turnContextCache.delete(sid);
|
|
927
|
+
await observeV2(sid, "prompt_submit", {
|
|
928
|
+
prompt: (event?.prompt?.text ?? "").slice(0, 8000),
|
|
929
|
+
files: files.slice(0, 20),
|
|
930
|
+
agents: event?.prompt?.agents ?? [],
|
|
931
|
+
skills: event?.prompt?.skills ?? [],
|
|
932
|
+
delivery: event?.delivery ?? null,
|
|
933
|
+
});
|
|
934
|
+
});
|
|
935
|
+
|
|
936
|
+
await ctx.session.hook("context", async (event: any) => {
|
|
937
|
+
const sid = event?.sessionID || activeSessionId;
|
|
938
|
+
if (!sid) return;
|
|
939
|
+
if (!Array.isArray(event.system)) return;
|
|
940
|
+
|
|
941
|
+
if (!contextInjectedSessions.has(sid)) {
|
|
942
|
+
event.system.push({ type: "text", text: AGENTMEMORY_INSTRUCTIONS });
|
|
943
|
+
contextInjectedSessions.add(sid);
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
let ctxText = startContextCache.get(sid) ?? turnContextCache.get(sid);
|
|
947
|
+
if (typeof ctxText !== "string" || ctxText.length === 0) {
|
|
948
|
+
const result = await postJson("/context", { sessionId: sid, project: projectFor(sid).name });
|
|
949
|
+
ctxText = (result as any)?.context;
|
|
950
|
+
}
|
|
951
|
+
if (typeof ctxText === "string" && ctxText.length > 0) {
|
|
952
|
+
turnContextCache.set(sid, ctxText);
|
|
953
|
+
startContextCache.delete(sid);
|
|
954
|
+
event.system.push({ type: "text", text: ctxText });
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
const stash = stashFor(sid);
|
|
958
|
+
if (stash.size === 0) return;
|
|
959
|
+
const files = [...stash].slice(0, 10);
|
|
960
|
+
const enrichResult = await postJson("/enrich", { sessionId: sid, files, toolName: "enrich_inject" });
|
|
961
|
+
const enrichCtx = (enrichResult as any)?.context;
|
|
962
|
+
if (typeof enrichCtx === "string" && enrichCtx.length > 0) {
|
|
963
|
+
event.system.push({ type: "text", text: enrichCtx });
|
|
964
|
+
for (const f of files) stash.delete(f);
|
|
965
|
+
}
|
|
966
|
+
});
|
|
967
|
+
|
|
968
|
+
await ctx.session.hook("compaction", async (event: any) => {
|
|
969
|
+
const sid = event?.sessionID || activeSessionId;
|
|
970
|
+
if (!sid) return;
|
|
971
|
+
if (!Array.isArray(event?.system)) return;
|
|
972
|
+
let ctxText = turnContextCache.get(sid);
|
|
973
|
+
if (typeof ctxText !== "string" || ctxText.length === 0) {
|
|
974
|
+
const result = await postJson("/context", { sessionId: sid, project: projectFor(sid).name });
|
|
975
|
+
ctxText = (result as any)?.context;
|
|
976
|
+
if (typeof ctxText === "string" && ctxText.length > 0) turnContextCache.set(sid, ctxText);
|
|
977
|
+
}
|
|
978
|
+
if (typeof ctxText === "string" && ctxText.length > 0) {
|
|
979
|
+
event.system.push({ type: "text", text: ctxText });
|
|
980
|
+
}
|
|
981
|
+
});
|
|
982
|
+
|
|
983
|
+
const controller = new AbortController();
|
|
984
|
+
|
|
985
|
+
const handleEvent = async (event: any): Promise<void> => {
|
|
986
|
+
const type = String(event?.type ?? "");
|
|
987
|
+
const data: any = event?.data ?? {};
|
|
988
|
+
const eventSid = typeof data.sessionID === "string" && data.sessionID ? data.sessionID : null;
|
|
989
|
+
const sid0 = eventSid || activeSessionId;
|
|
990
|
+
|
|
991
|
+
if (eventSid && !registeredSessions.has(eventSid)) {
|
|
992
|
+
if (!activeSessionId) activeSessionId = eventSid;
|
|
993
|
+
stashedFiles.set(sid0, new Set());
|
|
994
|
+
seenSubtaskIds.delete(sid0);
|
|
995
|
+
seenToolCallIds.delete(sid0);
|
|
996
|
+
contextInjectedSessions.delete(sid0);
|
|
997
|
+
const dir = (data.location?.directory as string) || (event?.location?.directory as string) || null;
|
|
998
|
+
if (dir) {
|
|
999
|
+
sessionProjects.set(sid0, { cwd: dir, name: resolveProjectName(dir) });
|
|
1000
|
+
}
|
|
1001
|
+
const proj = projectFor(sid0);
|
|
1002
|
+
const startResult = await postJson("/session/start", {
|
|
1003
|
+
sessionId: sid0,
|
|
1004
|
+
title: (data.title as string) ?? null,
|
|
1005
|
+
parentID: (data.parentID as string) ?? null,
|
|
1006
|
+
project: proj.name,
|
|
1007
|
+
cwd: proj.cwd,
|
|
1008
|
+
});
|
|
1009
|
+
|
|
1010
|
+
if (startResult === null) {
|
|
1011
|
+
stashedFiles.delete(sid0);
|
|
1012
|
+
contextInjectedSessions.delete(sid0);
|
|
1013
|
+
if (activeSessionId === eventSid) activeSessionId = null;
|
|
1014
|
+
if (DEBUG) {
|
|
1015
|
+
console.error("[agentmemory] /session/start failed, will retry on the next event for", eventSid);
|
|
1016
|
+
}
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
registeredSessions.add(eventSid);
|
|
1020
|
+
|
|
1021
|
+
const startCtx = (startResult as any)?.context;
|
|
1022
|
+
if (typeof startCtx === "string" && startCtx.length > 0) startContextCache.set(sid0, startCtx);
|
|
1023
|
+
await observeV2(sid0, "session_started", {});
|
|
1024
|
+
if (pendingConfig) {
|
|
1025
|
+
await observeV2(sid0, "config_loaded", pendingConfig);
|
|
1026
|
+
pendingConfig = null;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
switch (type) {
|
|
1031
|
+
|
|
1032
|
+
case "session.execution.failed": {
|
|
1033
|
+
if (!sid0) return;
|
|
1034
|
+
await observeV2(sid0, "post_tool_failure", {
|
|
1035
|
+
tool_name: "session.execution",
|
|
1036
|
+
tool_input: "",
|
|
1037
|
+
tool_output: safeSlice(extractErrorMessage(data.error ?? data), 8000),
|
|
1038
|
+
});
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
case "session.compaction.started": {
|
|
1043
|
+
if (!sid0) return;
|
|
1044
|
+
await observeV2(sid0, "compaction_event", {
|
|
1045
|
+
state: "started",
|
|
1046
|
+
reason: (data.reason as string) ?? null,
|
|
1047
|
+
inputID: (data.inputID as string) ?? null,
|
|
1048
|
+
recent: safeSlice(data.recent, 8000),
|
|
1049
|
+
});
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
case "session.compaction.failed": {
|
|
1054
|
+
if (!sid0) return;
|
|
1055
|
+
await observeV2(sid0, "compaction_event", {
|
|
1056
|
+
state: "failed",
|
|
1057
|
+
reason: (data.reason as string) ?? null,
|
|
1058
|
+
inputID: (data.inputID as string) ?? null,
|
|
1059
|
+
tool_output: safeSlice(extractErrorMessage(data.error ?? data.reason), 8000),
|
|
1060
|
+
});
|
|
1061
|
+
return;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
case "session.step.started": {
|
|
1065
|
+
if (!sid0) return;
|
|
1066
|
+
await observeV2(sid0, "step_start", {
|
|
1067
|
+
messageID: (data.assistantMessageID as string) ?? null,
|
|
1068
|
+
agent: (data.agent as string) ?? null,
|
|
1069
|
+
model: data.model ? `${data.model.providerID ?? ""}/${data.model.id ?? ""}` : null,
|
|
1070
|
+
});
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
case "session.step.ended": {
|
|
1075
|
+
if (!sid0) return;
|
|
1076
|
+
const tokens = (data.tokens ?? {}) as Record<string, any>;
|
|
1077
|
+
await observeV2(sid0, "step_finish", {
|
|
1078
|
+
messageID: (data.assistantMessageID as string) ?? null,
|
|
1079
|
+
reason: (data.rawFinish as string) ?? (data.finish as string) ?? null,
|
|
1080
|
+
cost: data.cost ?? 0,
|
|
1081
|
+
input_tokens: tokens.input ?? 0,
|
|
1082
|
+
output_tokens: tokens.output ?? 0,
|
|
1083
|
+
reasoning_tokens: tokens.reasoning ?? 0,
|
|
1084
|
+
cache_read: tokens.cache?.read ?? 0,
|
|
1085
|
+
cache_write: tokens.cache?.write ?? 0,
|
|
1086
|
+
});
|
|
1087
|
+
return;
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
case "session.agent.selected": {
|
|
1091
|
+
if (!sid0) return;
|
|
1092
|
+
await observeV2(sid0, "agent_selected", {
|
|
1093
|
+
name: (data.agent as string) ?? null,
|
|
1094
|
+
previous: (data.previous as string) ?? null,
|
|
1095
|
+
});
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
case "session.reasoning.ended": {
|
|
1100
|
+
if (!sid0) return;
|
|
1101
|
+
await observeV2(sid0, "reasoning", {
|
|
1102
|
+
messageID: (data.assistantMessageID as string) ?? null,
|
|
1103
|
+
text: safeSlice(data.text, 4000),
|
|
1104
|
+
});
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
case "session.instructions.updated": {
|
|
1109
|
+
if (!sid0) return;
|
|
1110
|
+
const sources = data.delta && typeof data.delta === "object" ? Object.keys(data.delta) : [];
|
|
1111
|
+
if (sources.length === 0) return;
|
|
1112
|
+
await observeV2(sid0, "notification", {
|
|
1113
|
+
notification_type: "instructions_updated",
|
|
1114
|
+
sources: sources.slice(0, 50),
|
|
1115
|
+
});
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
case "session.text.ended": {
|
|
1120
|
+
if (!sid0) return;
|
|
1121
|
+
const text = typeof data.text === "string" ? data.text : "";
|
|
1122
|
+
if (!text) return;
|
|
1123
|
+
await observeV2(sid0, "assistant_message", {
|
|
1124
|
+
messageID: (data.assistantMessageID as string) ?? null,
|
|
1125
|
+
text: text.slice(0, 8000),
|
|
1126
|
+
});
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
case "session.execution.succeeded": {
|
|
1131
|
+
if (!sid0) return;
|
|
1132
|
+
await post("/summarize", { sessionId: sid0 });
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
case "shell.created": {
|
|
1137
|
+
const info = (data.info ?? {}) as Record<string, any>;
|
|
1138
|
+
const shellSid = (info.metadata?.sessionID as string) || sid0;
|
|
1139
|
+
if (!shellSid) return;
|
|
1140
|
+
if (info.id) shellSessions.set(String(info.id), shellSid);
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
case "shell.exited": {
|
|
1145
|
+
const shellId = String(data.id ?? "");
|
|
1146
|
+
const shellSid = shellSessions.get(shellId) || activeSessionId;
|
|
1147
|
+
if (!shellSid) return;
|
|
1148
|
+
const exit = Number(data.exit ?? 0);
|
|
1149
|
+
if (exit !== 0) {
|
|
1150
|
+
await observeV2(shellSid, "post_tool_failure", {
|
|
1151
|
+
tool_name: "shell",
|
|
1152
|
+
call_id: shellId,
|
|
1153
|
+
tool_input: null,
|
|
1154
|
+
tool_output: safeSlice(data.status, 4000),
|
|
1155
|
+
duration_ms: null,
|
|
1156
|
+
});
|
|
1157
|
+
}
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
case "shell.deleted": {
|
|
1162
|
+
const shellId = String(data.id ?? "");
|
|
1163
|
+
shellSessions.delete(shellId);
|
|
1164
|
+
return;
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
case "file.watcher.updated":
|
|
1168
|
+
case "filesystem.changed":
|
|
1169
|
+
case "vcs.branch.updated": {
|
|
1170
|
+
const sid = sid0 || activeSessionId;
|
|
1171
|
+
if (!sid) return;
|
|
1172
|
+
const file = (data.file as string) ?? (data.path as string) ?? null;
|
|
1173
|
+
if (file) stashAdd(sid, [file]);
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
case "session.tool.input.started": {
|
|
1178
|
+
if (!sid0) return;
|
|
1179
|
+
if (data.id) toolNames.set(String(data.id), String(data.name ?? ""));
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
case "session.tool.input.ended": {
|
|
1184
|
+
if (!sid0) return;
|
|
1185
|
+
if (data.id && typeof data.text === "string" && !toolNames.has(String(data.id))) {
|
|
1186
|
+
try {
|
|
1187
|
+
const parsed = JSON.parse(data.text);
|
|
1188
|
+
if (typeof parsed?.name === "string") toolNames.set(String(data.id), parsed.name);
|
|
1189
|
+
} catch {
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
return;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
case "session.tool.called": {
|
|
1196
|
+
if (!sid0) return;
|
|
1197
|
+
if (data.id) toolCallInputs.set(String(data.id), (data.input ?? {}) as Record<string, unknown>);
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
case "session.tool.progress": {
|
|
1202
|
+
if (!sid0) return;
|
|
1203
|
+
const callId = String(data.id ?? "");
|
|
1204
|
+
if (!callId || !FILE_TOOLS.has((toolNames.get(callId) ?? "").toLowerCase())) return;
|
|
1205
|
+
const input = toolCallInputs.get(callId);
|
|
1206
|
+
if (!input) return;
|
|
1207
|
+
stashAdd(sid0, extractFilePaths(input));
|
|
1208
|
+
return;
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
case "session.tool.success": {
|
|
1212
|
+
const callId = String(data.id ?? "");
|
|
1213
|
+
toolNames.delete(callId);
|
|
1214
|
+
toolCallInputs.delete(callId);
|
|
1215
|
+
if (sid0) reportedToolCalls.delete(`${sid0}:${callId}`);
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
case "session.tool.failed": {
|
|
1220
|
+
if (!sid0) return;
|
|
1221
|
+
const callId = String(data.id ?? "");
|
|
1222
|
+
if (callId && reportedToolCalls.has(`${sid0}:${callId}`)) return;
|
|
1223
|
+
await observeV2(sid0, "post_tool_failure", {
|
|
1224
|
+
tool_name: toolNames.get(callId) || null,
|
|
1225
|
+
call_id: callId || null,
|
|
1226
|
+
tool_input: safeSlice(toolCallInputs.get(callId), 4000),
|
|
1227
|
+
tool_output: safeSlice(extractErrorMessage(data.error), 8000),
|
|
1228
|
+
duration_ms: null,
|
|
1229
|
+
});
|
|
1230
|
+
if (callId) {
|
|
1231
|
+
toolNames.delete(callId);
|
|
1232
|
+
toolCallInputs.delete(callId);
|
|
1233
|
+
}
|
|
1234
|
+
return;
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
case "permission.asked": {
|
|
1238
|
+
if (!sid0) return;
|
|
1239
|
+
const resources = (data.resources ?? data.patterns ?? []) as unknown;
|
|
1240
|
+
await observeV2(sid0, "notification", {
|
|
1241
|
+
notification_type: "permission_prompt",
|
|
1242
|
+
permission: (data.action as string) ?? (data.permission as string) ?? "unknown",
|
|
1243
|
+
pattern: Array.isArray(resources) ? resources.join(", ") : String(resources ?? ""),
|
|
1244
|
+
tool_call_id: (data.tool?.callID as string) ?? (data.callID as string) ?? null,
|
|
1245
|
+
title: (data.action as string) ?? (data.permission as string) ?? "",
|
|
1246
|
+
metadata: data.metadata ?? {},
|
|
1247
|
+
});
|
|
1248
|
+
return;
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
case "permission.replied": {
|
|
1252
|
+
if (!sid0) return;
|
|
1253
|
+
await observeV2(sid0, "permission_replied", {
|
|
1254
|
+
permission_id: (data.requestID as string) ?? (data.permissionID as string) ?? "",
|
|
1255
|
+
response: (data.reply as string) ?? (data.response as string) ?? "",
|
|
1256
|
+
});
|
|
1257
|
+
return;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
case "session.deleted": {
|
|
1261
|
+
const sid = (data.sessionID as string) || activeSessionId;
|
|
1262
|
+
if (!sid) return;
|
|
1263
|
+
await post("/session/end", { sessionId: sid });
|
|
1264
|
+
void post("/crystals/auto", { olderThanDays: 7 }, 30000);
|
|
1265
|
+
void post("/consolidate-pipeline", { tier: "all", force: true }, 30000);
|
|
1266
|
+
if (sid === activeSessionId) activeSessionId = null;
|
|
1267
|
+
registeredSessions.delete(sid);
|
|
1268
|
+
pruneSessionMaps(sid);
|
|
1269
|
+
startContextCache.delete(sid);
|
|
1270
|
+
contextInjectedSessions.delete(sid);
|
|
1271
|
+
for (const key of reportedToolCalls) {
|
|
1272
|
+
if (key.startsWith(`${sid}:`)) reportedToolCalls.delete(key);
|
|
1273
|
+
}
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
case "config.updated":
|
|
1278
|
+
case "agent.updated":
|
|
1279
|
+
case "provider.updated":
|
|
1280
|
+
case "model.updated":
|
|
1281
|
+
case "mcp.status.changed": {
|
|
1282
|
+
if (DEBUG) console.error("[agentmemory] config changed, re-snapshotting");
|
|
1283
|
+
void snapshotConfig().catch(() => {});
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
};
|
|
1288
|
+
|
|
1289
|
+
void (async () => {
|
|
1290
|
+
try {
|
|
1291
|
+
for await (const event of ctx.event.subscribe({ signal: controller.signal })) {
|
|
1292
|
+
try {
|
|
1293
|
+
await handleEvent(event);
|
|
1294
|
+
} catch (e) {
|
|
1295
|
+
if (DEBUG) console.error("[agentmemory] event handler failed:", (e as Error).message);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
} catch (e) {
|
|
1299
|
+
if (DEBUG) console.error("[agentmemory] event stream failed:", (e as Error).message);
|
|
1300
|
+
}
|
|
1301
|
+
})();
|
|
1302
|
+
|
|
1303
|
+
return () => {
|
|
1304
|
+
controller.abort();
|
|
1305
|
+
startContextCache.clear();
|
|
1306
|
+
contextInjectedSessions.clear();
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
export default {
|
|
1311
|
+
id: "agentmemory-capture",
|
|
1312
|
+
setup: v2Setup,
|
|
1313
|
+
server: v1Hooks,
|
|
1314
|
+
};
|
|
1315
|
+
|
|
1316
|
+
export const AgentmemoryCapturePlugin: Plugin = v1Hooks;
|