@agentmemory/agentmemory 0.9.29 → 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.
Files changed (140) hide show
  1. package/.env.example +83 -22
  2. package/AGENTS.md +17 -24
  3. package/README.md +447 -170
  4. package/dist/.env.example +83 -22
  5. package/dist/cli.d.mts.map +1 -1
  6. package/dist/cli.mjs +1221 -335
  7. package/dist/cli.mjs.map +1 -1
  8. package/dist/{connect-Qaeq15TQ.mjs → connect-B4_ca6BD.mjs} +30 -39
  9. package/dist/connect-B4_ca6BD.mjs.map +1 -0
  10. package/dist/docker-compose.yml +40 -14
  11. package/dist/hooks/_capture.d.mts +22 -0
  12. package/dist/hooks/_capture.d.mts.map +1 -0
  13. package/dist/hooks/_capture.mjs +908 -0
  14. package/dist/hooks/_capture.mjs.map +1 -0
  15. package/dist/hooks/antigravity-bridge.d.mts +1 -1
  16. package/dist/hooks/antigravity-bridge.d.mts.map +1 -1
  17. package/dist/hooks/antigravity-bridge.mjs +24 -15
  18. package/dist/hooks/antigravity-bridge.mjs.map +1 -1
  19. package/dist/hooks/notification.mjs +17 -25
  20. package/dist/hooks/notification.mjs.map +1 -1
  21. package/dist/hooks/post-commit.mjs +71 -1
  22. package/dist/hooks/post-commit.mjs.map +1 -1
  23. package/dist/hooks/post-tool-failure.mjs +59 -25
  24. package/dist/hooks/post-tool-failure.mjs.map +1 -1
  25. package/dist/hooks/post-tool-use.mjs +87 -35
  26. package/dist/hooks/post-tool-use.mjs.map +1 -1
  27. package/dist/hooks/pre-compact.mjs +83 -3
  28. package/dist/hooks/pre-compact.mjs.map +1 -1
  29. package/dist/hooks/pre-tool-use.mjs +71 -1
  30. package/dist/hooks/pre-tool-use.mjs.map +1 -1
  31. package/dist/hooks/prompt-submit.mjs +13 -21
  32. package/dist/hooks/prompt-submit.mjs.map +1 -1
  33. package/dist/hooks/session-end.mjs +25 -20
  34. package/dist/hooks/session-end.mjs.map +1 -1
  35. package/dist/hooks/session-start.mjs +80 -3
  36. package/dist/hooks/session-start.mjs.map +1 -1
  37. package/dist/hooks/stop.mjs +71 -1
  38. package/dist/hooks/stop.mjs.map +1 -1
  39. package/dist/hooks/subagent-start.mjs +16 -25
  40. package/dist/hooks/subagent-start.mjs.map +1 -1
  41. package/dist/hooks/subagent-stop.mjs +17 -25
  42. package/dist/hooks/subagent-stop.mjs.map +1 -1
  43. package/dist/hooks/task-completed.mjs +19 -27
  44. package/dist/hooks/task-completed.mjs.map +1 -1
  45. package/dist/iii-config.docker.yaml +10 -10
  46. package/dist/iii-config.yaml +3 -1
  47. package/dist/{image-refs-BKLmp_sD.mjs → image-refs-BaT-9oMU.mjs} +2 -2
  48. package/dist/image-refs-BaT-9oMU.mjs.map +1 -0
  49. package/dist/{image-refs-CJgZmb9X.mjs → image-refs-T_urbwHM.mjs} +3 -3
  50. package/dist/image-refs-T_urbwHM.mjs.map +1 -0
  51. package/dist/{keyed-mutex-DTHtcZrt.mjs → image-store-BsN4vyjr.mjs} +2 -14
  52. package/dist/image-store-BsN4vyjr.mjs.map +1 -0
  53. package/dist/index.mjs +12943 -6218
  54. package/dist/index.mjs.map +1 -1
  55. package/dist/{schema-BFZhNTF7.mjs → keyed-mutex-DchXsyfP.mjs} +28 -4
  56. package/dist/keyed-mutex-DchXsyfP.mjs.map +1 -0
  57. package/dist/secret-store-BnY3A50C.mjs +2348 -0
  58. package/dist/secret-store-BnY3A50C.mjs.map +1 -0
  59. package/dist/{src-G7yt8gGm.mjs → src-Jsq9LCeH.mjs} +5941 -2180
  60. package/dist/src-Jsq9LCeH.mjs.map +1 -0
  61. package/dist/{standalone-BqNJDJ04.mjs → standalone-DKCdrf1d.mjs} +39 -12
  62. package/dist/standalone-DKCdrf1d.mjs.map +1 -0
  63. package/dist/standalone.d.mts.map +1 -1
  64. package/dist/standalone.mjs +249 -14
  65. package/dist/standalone.mjs.map +1 -1
  66. package/dist/status-IRz7ta8-.mjs +2344 -0
  67. package/dist/status-IRz7ta8-.mjs.map +1 -0
  68. package/dist/viewer/index.html +7224 -2794
  69. package/docker-compose.yml +40 -14
  70. package/iii-config.docker.yaml +10 -10
  71. package/iii-config.yaml +3 -1
  72. package/integrations/pi/index.ts +2 -3
  73. package/integrations/pi/security.ts +52 -0
  74. package/package.json +13 -4
  75. package/plugin/.claude-plugin/plugin.json +2 -2
  76. package/plugin/.codex-plugin/plugin.json +26 -4
  77. package/plugin/.devin-plugin/plugin.json +1 -1
  78. package/plugin/.mcp.codex.json +12 -0
  79. package/plugin/.mcp.copilot.json +2 -7
  80. package/plugin/.mcp.json +2 -7
  81. package/plugin/assets/icon.svg +35 -0
  82. package/plugin/hooks/hooks.copilot.json +11 -11
  83. package/plugin/opencode/README.md +266 -30
  84. package/plugin/opencode/agentmemory-capture.ts +572 -3
  85. package/plugin/opencode/plugin.json +1 -1
  86. package/plugin/plugin.json +1 -1
  87. package/plugin/scripts/_capture.d.mts +22 -0
  88. package/plugin/scripts/_capture.d.mts.map +1 -0
  89. package/plugin/scripts/_capture.mjs +908 -0
  90. package/plugin/scripts/_capture.mjs.map +1 -0
  91. package/plugin/scripts/antigravity-bridge.d.mts +1 -1
  92. package/plugin/scripts/antigravity-bridge.d.mts.map +1 -1
  93. package/plugin/scripts/antigravity-bridge.mjs +24 -15
  94. package/plugin/scripts/antigravity-bridge.mjs.map +1 -1
  95. package/plugin/scripts/notification.mjs +17 -25
  96. package/plugin/scripts/notification.mjs.map +1 -1
  97. package/plugin/scripts/plugin-bridge.mjs +378 -0
  98. package/plugin/scripts/post-commit.mjs +71 -1
  99. package/plugin/scripts/post-commit.mjs.map +1 -1
  100. package/plugin/scripts/post-tool-failure.mjs +59 -25
  101. package/plugin/scripts/post-tool-failure.mjs.map +1 -1
  102. package/plugin/scripts/post-tool-use.mjs +87 -35
  103. package/plugin/scripts/post-tool-use.mjs.map +1 -1
  104. package/plugin/scripts/pre-compact.mjs +83 -3
  105. package/plugin/scripts/pre-compact.mjs.map +1 -1
  106. package/plugin/scripts/pre-tool-use.mjs +71 -1
  107. package/plugin/scripts/pre-tool-use.mjs.map +1 -1
  108. package/plugin/scripts/prompt-submit.mjs +13 -21
  109. package/plugin/scripts/prompt-submit.mjs.map +1 -1
  110. package/plugin/scripts/session-end.mjs +25 -20
  111. package/plugin/scripts/session-end.mjs.map +1 -1
  112. package/plugin/scripts/session-start.mjs +80 -3
  113. package/plugin/scripts/session-start.mjs.map +1 -1
  114. package/plugin/scripts/stop.mjs +71 -1
  115. package/plugin/scripts/stop.mjs.map +1 -1
  116. package/plugin/scripts/subagent-start.mjs +16 -25
  117. package/plugin/scripts/subagent-start.mjs.map +1 -1
  118. package/plugin/scripts/subagent-stop.mjs +17 -25
  119. package/plugin/scripts/subagent-stop.mjs.map +1 -1
  120. package/plugin/scripts/task-completed.mjs +19 -27
  121. package/plugin/scripts/task-completed.mjs.map +1 -1
  122. package/plugin/skills/_shared/TROUBLESHOOTING.md +22 -6
  123. package/plugin/skills/agentmemory-agents/REFERENCE.md +3 -3
  124. package/plugin/skills/agentmemory-config/REFERENCE.md +49 -1
  125. package/plugin/skills/agentmemory-config/SKILL.md +44 -7
  126. package/plugin/skills/agentmemory-mcp-tools/REFERENCE.md +1 -1
  127. package/plugin/skills/agentmemory-rest-api/REFERENCE.md +10 -2
  128. package/plugin/skills/handoff/SKILL.md +4 -2
  129. package/plugin/skills/memory-discipline/SKILL.md +13 -5
  130. package/dist/connect-Qaeq15TQ.mjs.map +0 -1
  131. package/dist/image-refs-BKLmp_sD.mjs.map +0 -1
  132. package/dist/image-refs-CJgZmb9X.mjs.map +0 -1
  133. package/dist/keyed-mutex-DTHtcZrt.mjs.map +0 -1
  134. package/dist/logger-BEB7pCI9.mjs +0 -43
  135. package/dist/logger-BEB7pCI9.mjs.map +0 -1
  136. package/dist/schema-BFZhNTF7.mjs.map +0 -1
  137. package/dist/src-G7yt8gGm.mjs.map +0 -1
  138. package/dist/standalone-BqNJDJ04.mjs.map +0 -1
  139. package/dist/tools-registry-BV_2bu7z.mjs +0 -1405
  140. package/dist/tools-registry-BV_2bu7z.mjs.map +0 -1
@@ -1,6 +1,8 @@
1
1
  import type { Plugin } from "@opencode-ai/plugin";
2
2
  import { execFileSync } from "node:child_process";
3
- import { basename } from "node:path";
3
+ import { readFileSync } from "node:fs";
4
+ import { homedir } from "node:os";
5
+ import { basename, join } from "node:path";
4
6
 
5
7
  const API = process.env.AGENTMEMORY_URL || "http://localhost:3111";
6
8
  // OpenCode reports tool names in lowercase ("read", "edit", ...); matching is
@@ -11,7 +13,55 @@ const FILE_KEYS = ["filePath", "file_path", "path", "file", "pattern"];
11
13
  const MAX_STASHED_FILES = 20;
12
14
 
13
15
  const DEBUG = process.env.OPENCODE_AGENTMEMORY_DEBUG === "1";
14
- const SECRET = process.env.AGENTMEMORY_SECRET || "";
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);
15
65
 
16
66
  function authHeaders(): Record<string, string> {
17
67
  const headers: Record<string, string> = { "Content-Type": "application/json" };
@@ -213,7 +263,7 @@ function extractErrorMessage(err: unknown): string {
213
263
  return String(err ?? "");
214
264
  }
215
265
 
216
- export const AgentmemoryCapturePlugin: Plugin = async (ctx) => {
266
+ const v1Hooks: Plugin = async (ctx) => {
217
267
  defaultProjectCwd = ctx.worktree || ctx.project?.id || process.cwd();
218
268
  defaultProjectName = resolveProjectName(defaultProjectCwd);
219
269
 
@@ -745,3 +795,522 @@ export const AgentmemoryCapturePlugin: Plugin = async (ctx) => {
745
795
  },
746
796
  };
747
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;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentmemory-capture",
3
- "version": "0.9.29",
3
+ "version": "0.9.30",
4
4
  "description": "OpenCode plugin for agentmemory — full Claude Code hook parity: session lifecycle (create/idle/status/compacted/update/diff/delete/error), messages & prompts (chat.message, message.updated user+assistant, message.removed), tool lifecycle (ToolPart states with timing), part tracking (subtask, step-finish, reasoning, file, patch, compaction, agent, retry), file enrichment pipeline, permissions, task tracking (w/ priority), commands, config & model tracking. 22 hooks, 2 slash commands.",
5
5
  "author": {
6
6
  "name": "Rohit Ghumare",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentmemory",
3
- "version": "0.9.29",
3
+ "version": "0.9.30",
4
4
  "description": "Persistent memory for AI coding agents -- captures tool usage, compresses via LLM, injects context into future sessions. 12 hooks, 54 MCP tools, 17 skills, real-time viewer.",
5
5
  "author": {
6
6
  "name": "Rohit Ghumare",
@@ -0,0 +1,22 @@
1
+ //#region src/hooks/_capture.d.ts
2
+ declare const REST_URL: string;
3
+ type CaptureOutcome = "delivered" | "duplicate" | "rejected" | "spooled" | "dropped";
4
+ interface ObserveBody {
5
+ hookType: string;
6
+ sessionId: string;
7
+ project: string;
8
+ cwd: string;
9
+ timestamp: string;
10
+ data: unknown;
11
+ eventId?: string;
12
+ }
13
+ declare function authHeaders(): Record<string, string>;
14
+ declare function withEventId(body: ObserveBody, host: Record<string, unknown>, content?: unknown, options?: {
15
+ stable?: boolean;
16
+ }): ObserveBody;
17
+ declare function captureObservation(body: ObserveBody, timeoutMs: number): Promise<CaptureOutcome>;
18
+ declare function isDrainChild(): boolean;
19
+ declare function runDrainChild(): Promise<void>;
20
+ //#endregion
21
+ export { CaptureOutcome, ObserveBody, REST_URL, authHeaders, captureObservation, isDrainChild, runDrainChild, withEventId };
22
+ //# sourceMappingURL=_capture.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_capture.d.mts","names":[],"sources":["../../src/hooks/_capture.ts"],"mappings":";cAgBa,QAAA;AAAA,KAOD,cAAA;AAAA,UAEK,WAAA;EACf,QAAA;EACA,SAAA;EACA,OAAA;EACA,GAAA;EACA,SAAA;EACA,IAAA;EACA,OAAA;AAAA;AAAA,iBAGc,WAAA,CAAA,GAAe,MAAA;AAAA,iBAMf,WAAA,CACd,IAAA,EAAM,WAAA,EACN,IAAA,EAAM,MAAA,mBACN,OAAA,YACA,OAAA;EAAW,MAAA;AAAA,IACV,WAAA;AAAA,iBAoDmB,kBAAA,CAAmB,IAAA,EAAM,WAAA,EAAa,SAAA,WAAoB,OAAA,CAAQ,cAAA;AAAA,iBAmBxE,YAAA,CAAA;AAAA,iBAIM,aAAA,CAAA,GAAiB,OAAA"}