@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,48 @@
1
1
  #!/usr/bin/env node
2
2
  import { execSync } from "node:child_process";
3
3
  import { basename } from "node:path";
4
+ import { captureObservation, isDrainChild, runDrainChild, withEventId } from "./_capture.mjs";
5
+ //#region src/hooks/_capture-filter.ts
6
+ const DEFAULT_DENY_PATTERNS = [
7
+ "memory_*",
8
+ "toolsearch",
9
+ "listmcpresources",
10
+ "fetchmcpresource"
11
+ ];
12
+ function parseEnvList(raw) {
13
+ if (!raw?.trim()) return void 0;
14
+ return raw.split(/[,\s]+/).map((part) => part.trim()).filter(Boolean);
15
+ }
16
+ function bareToolName(toolName) {
17
+ const trimmed = toolName.trim();
18
+ if (/^mcp__/i.test(trimmed)) {
19
+ const parts = trimmed.split("__");
20
+ if (parts.length >= 3) return parts[parts.length - 1];
21
+ }
22
+ return trimmed;
23
+ }
24
+ function normalizePattern(pattern) {
25
+ return pattern.trim().toLowerCase();
26
+ }
27
+ function matchesPattern(toolName, pattern) {
28
+ const bare = bareToolName(toolName).toLowerCase();
29
+ const full = toolName.trim().toLowerCase();
30
+ const pat = normalizePattern(pattern);
31
+ if (!pat.includes("*")) return bare === pat || full === pat;
32
+ const escaped = pat.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
33
+ const re = new RegExp(`^${escaped.replace(/\*/g, ".*")}$`);
34
+ return re.test(bare) || re.test(full);
35
+ }
36
+ function matchesAny(toolName, patterns) {
37
+ return patterns.some((pattern) => matchesPattern(toolName, pattern));
38
+ }
39
+ function shouldCaptureTool(toolName) {
40
+ if (typeof toolName !== "string" || !toolName.trim()) return true;
41
+ const allow = parseEnvList(process.env["AGENTMEMORY_CAPTURE_ALLOW"]);
42
+ if (allow) return matchesAny(toolName, allow);
43
+ return !matchesAny(toolName, [...DEFAULT_DENY_PATTERNS, ...parseEnvList(process.env["AGENTMEMORY_CAPTURE_DENY"]) ?? []]);
44
+ }
45
+ //#endregion
4
46
  //#region src/hooks/_project.ts
5
47
  function resolveProject(cwd) {
6
48
  const explicit = process.env["AGENTMEMORY_PROJECT_NAME"];
@@ -37,14 +79,10 @@ function isSdkChildContext(payload) {
37
79
  if (!payload || typeof payload !== "object") return false;
38
80
  return payload.entrypoint === "sdk-ts";
39
81
  }
40
- const REST_URL = process.env["AGENTMEMORY_URL"] || "http://localhost:3111";
41
- const SECRET = process.env["AGENTMEMORY_SECRET"] || "";
42
- function authHeaders() {
43
- const h = { "Content-Type": "application/json" };
44
- if (SECRET) h["Authorization"] = `Bearer ${SECRET}`;
45
- return h;
46
- }
82
+ const OBSERVE_TIMEOUT_MS = 3e3;
83
+ const EXIT_CAP_MS = 3500;
47
84
  async function main() {
85
+ if (isDrainChild()) return runDrainChild();
48
86
  let input = "";
49
87
  for await (const chunk of process.stdin) input += chunk;
50
88
  let data;
@@ -58,27 +96,23 @@ async function main() {
58
96
  if (data.is_interrupt || data.isInterrupt) return;
59
97
  const sessionId = data.session_id || data.sessionId || data.conversation_id || "unknown";
60
98
  const toolName = data.tool_name ?? data.toolName;
99
+ if (!shouldCaptureTool(toolName)) return;
61
100
  const toolInput = data.tool_input ?? data.toolArgs;
62
101
  const error = data.error ?? data.errorMessage;
63
102
  const cwd = hookCwd(data) || process.cwd();
64
- fetch(`${REST_URL}/agentmemory/observe`, {
65
- method: "POST",
66
- headers: authHeaders(),
67
- body: JSON.stringify({
68
- hookType: "post_tool_failure",
69
- sessionId,
70
- project: resolveProject(cwd),
71
- cwd,
72
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
73
- data: {
74
- tool_name: toolName,
75
- tool_input: typeof toolInput === "string" ? toolInput.slice(0, 4e3) : JSON.stringify(toolInput ?? "").slice(0, 4e3),
76
- error: typeof error === "string" ? error.slice(0, 4e3) : JSON.stringify(error ?? "").slice(0, 4e3)
77
- }
78
- }),
79
- signal: AbortSignal.timeout(3e3)
80
- }).catch(() => {});
81
- setTimeout(() => process.exit(0), 500).unref();
103
+ captureObservation(withEventId({
104
+ hookType: "post_tool_failure",
105
+ sessionId,
106
+ project: resolveProject(cwd),
107
+ cwd,
108
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
109
+ data: {
110
+ tool_name: toolName,
111
+ tool_input: typeof toolInput === "string" ? toolInput.slice(0, 4e3) : JSON.stringify(toolInput ?? "").slice(0, 4e3),
112
+ error: typeof error === "string" ? error.slice(0, 4e3) : JSON.stringify(error ?? "").slice(0, 4e3)
113
+ }
114
+ }, data), OBSERVE_TIMEOUT_MS);
115
+ setTimeout(() => process.exit(0), EXIT_CAP_MS).unref();
82
116
  }
83
117
  main().catch(() => process.exit(0));
84
118
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"post-tool-failure.mjs","names":[],"sources":["../../src/hooks/_project.ts","../../src/hooks/post-tool-failure.ts"],"sourcesContent":["import { execSync } from \"node:child_process\";\nimport { basename } from \"node:path\";\n\n// Resolution order: AGENTMEMORY_PROJECT_NAME env → git toplevel basename → cwd basename.\nexport function resolveProject(cwd?: string): string {\n const explicit = process.env[\"AGENTMEMORY_PROJECT_NAME\"];\n if (explicit && explicit.trim()) return explicit.trim();\n const dir = cwd && cwd.trim() ? cwd : process.cwd();\n try {\n const top = execSync(\"git rev-parse --show-toplevel\", {\n cwd: dir,\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n timeout: 500,\n })\n .toString()\n .trim();\n if (top) return basename(top);\n } catch {}\n return basename(dir);\n}\n\nexport function hookCwd(data: Record<string, unknown> | null | undefined): string | undefined {\n if (!data || typeof data !== \"object\") return undefined;\n if (typeof data.cwd === \"string\" && data.cwd.trim()) return data.cwd;\n const roots = data.workspace_roots;\n if (Array.isArray(roots)) {\n for (const root of roots) {\n if (typeof root === \"string\" && root.trim()) return root;\n }\n }\n const projectDir =\n process.env[\"DEVIN_PROJECT_DIR\"] || process.env[\"CLAUDE_PROJECT_DIR\"];\n if (projectDir && projectDir.trim()) return projectDir;\n return undefined;\n}\n","#!/usr/bin/env node\nimport { resolveProject, hookCwd } from \"./_project.js\";\n\nfunction isSdkChildContext(payload: unknown): boolean {\n if (process.env[\"AGENTMEMORY_SDK_CHILD\"] === \"1\") return true;\n if (!payload || typeof payload !== \"object\") return false;\n return (payload as { entrypoint?: unknown }).entrypoint === \"sdk-ts\";\n}\n\nconst REST_URL = process.env[\"AGENTMEMORY_URL\"] || \"http://localhost:3111\";\nconst SECRET = process.env[\"AGENTMEMORY_SECRET\"] || \"\";\n\nfunction authHeaders(): Record<string, string> {\n const h: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (SECRET) h[\"Authorization\"] = `Bearer ${SECRET}`;\n return h;\n}\n\nasync function main() {\n let input = \"\";\n for await (const chunk of process.stdin) {\n input += chunk;\n }\n\n let data: Record<string, unknown>;\n try {\n data = JSON.parse(input);\n } catch {\n return;\n }\n\n if (!data || typeof data !== \"object\") return;\n if (isSdkChildContext(data)) return;\n if (data.is_interrupt || data.isInterrupt) return;\n\n const sessionId = ((data.session_id || data.sessionId || data.conversation_id) as string) || \"unknown\";\n const toolName = data.tool_name ?? data.toolName;\n const toolInput = data.tool_input ?? data.toolArgs;\n const error = data.error ?? data.errorMessage;\n\n const cwd = hookCwd(data) || process.cwd();\n\n fetch(`${REST_URL}/agentmemory/observe`, {\n method: \"POST\",\n headers: authHeaders(),\n body: JSON.stringify({\n hookType: \"post_tool_failure\",\n sessionId,\n project: resolveProject(cwd),\n cwd,\n timestamp: new Date().toISOString(),\n data: {\n tool_name: toolName,\n tool_input:\n typeof toolInput === \"string\"\n ? toolInput.slice(0, 4000)\n : JSON.stringify(toolInput ?? \"\").slice(0, 4000),\n error:\n typeof error === \"string\"\n ? error.slice(0, 4000)\n : JSON.stringify(error ?? \"\").slice(0, 4000),\n },\n }),\n signal: AbortSignal.timeout(3000),\n }).catch(() => {});\n setTimeout(() => process.exit(0), 500).unref();\n}\n\nmain().catch(() => process.exit(0));\n"],"mappings":";;;;AAIA,SAAgB,eAAe,KAAsB;CACnD,MAAM,WAAW,QAAQ,IAAI;AAC7B,KAAI,YAAY,SAAS,MAAM,CAAE,QAAO,SAAS,MAAM;CACvD,MAAM,MAAM,OAAO,IAAI,MAAM,GAAG,MAAM,QAAQ,KAAK;AACnD,KAAI;EACF,MAAM,MAAM,SAAS,iCAAiC;GACpD,KAAK;GACL,OAAO;IAAC;IAAU;IAAQ;IAAS;GACnC,SAAS;GACV,CAAC,CACC,UAAU,CACV,MAAM;AACT,MAAI,IAAK,QAAO,SAAS,IAAI;SACvB;AACR,QAAO,SAAS,IAAI;;AAGtB,SAAgB,QAAQ,MAAsE;AAC5F,KAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO,KAAA;AAC9C,KAAI,OAAO,KAAK,QAAQ,YAAY,KAAK,IAAI,MAAM,CAAE,QAAO,KAAK;CACjE,MAAM,QAAQ,KAAK;AACnB,KAAI,MAAM,QAAQ,MAAM;OACjB,MAAM,QAAQ,MACjB,KAAI,OAAO,SAAS,YAAY,KAAK,MAAM,CAAE,QAAO;;CAGxD,MAAM,aACJ,QAAQ,IAAI,wBAAwB,QAAQ,IAAI;AAClD,KAAI,cAAc,WAAW,MAAM,CAAE,QAAO;;;;AC7B9C,SAAS,kBAAkB,SAA2B;AACpD,KAAI,QAAQ,IAAI,6BAA6B,IAAK,QAAO;AACzD,KAAI,CAAC,WAAW,OAAO,YAAY,SAAU,QAAO;AACpD,QAAQ,QAAqC,eAAe;;AAG9D,MAAM,WAAW,QAAQ,IAAI,sBAAsB;AACnD,MAAM,SAAS,QAAQ,IAAI,yBAAyB;AAEpD,SAAS,cAAsC;CAC7C,MAAM,IAA4B,EAAE,gBAAgB,oBAAoB;AACxE,KAAI,OAAQ,GAAE,mBAAmB,UAAU;AAC3C,QAAO;;AAGT,eAAe,OAAO;CACpB,IAAI,QAAQ;AACZ,YAAW,MAAM,SAAS,QAAQ,MAChC,UAAS;CAGX,IAAI;AACJ,KAAI;AACF,SAAO,KAAK,MAAM,MAAM;SAClB;AACN;;AAGF,KAAI,CAAC,QAAQ,OAAO,SAAS,SAAU;AACvC,KAAI,kBAAkB,KAAK,CAAE;AAC7B,KAAI,KAAK,gBAAgB,KAAK,YAAa;CAE3C,MAAM,YAAc,KAAK,cAAc,KAAK,aAAa,KAAK,mBAA+B;CAC7F,MAAM,WAAW,KAAK,aAAa,KAAK;CACxC,MAAM,YAAY,KAAK,cAAc,KAAK;CAC1C,MAAM,QAAQ,KAAK,SAAS,KAAK;CAEjC,MAAM,MAAM,QAAQ,KAAK,IAAI,QAAQ,KAAK;AAE1C,OAAM,GAAG,SAAS,uBAAuB;EACvC,QAAQ;EACR,SAAS,aAAa;EACtB,MAAM,KAAK,UAAU;GACnB,UAAU;GACV;GACA,SAAS,eAAe,IAAI;GAC5B;GACA,4BAAW,IAAI,MAAM,EAAC,aAAa;GACnC,MAAM;IACJ,WAAW;IACX,YACE,OAAO,cAAc,WACjB,UAAU,MAAM,GAAG,IAAK,GACxB,KAAK,UAAU,aAAa,GAAG,CAAC,MAAM,GAAG,IAAK;IACpD,OACE,OAAO,UAAU,WACb,MAAM,MAAM,GAAG,IAAK,GACpB,KAAK,UAAU,SAAS,GAAG,CAAC,MAAM,GAAG,IAAK;IACjD;GACF,CAAC;EACF,QAAQ,YAAY,QAAQ,IAAK;EAClC,CAAC,CAAC,YAAY,GAAG;AAClB,kBAAiB,QAAQ,KAAK,EAAE,EAAE,IAAI,CAAC,OAAO;;AAGhD,MAAM,CAAC,YAAY,QAAQ,KAAK,EAAE,CAAC"}
1
+ {"version":3,"file":"post-tool-failure.mjs","names":[],"sources":["../../src/hooks/_capture-filter.ts","../../src/hooks/_project.ts","../../src/hooks/post-tool-failure.ts"],"sourcesContent":["const DEFAULT_DENY_PATTERNS = [\n \"memory_*\",\n \"toolsearch\",\n \"listmcpresources\",\n \"fetchmcpresource\",\n];\n\nfunction parseEnvList(raw: string | undefined): string[] | undefined {\n if (!raw?.trim()) return undefined;\n return raw\n .split(/[,\\s]+/)\n .map((part) => part.trim())\n .filter(Boolean);\n}\n\nexport function bareToolName(toolName: string): string {\n const trimmed = toolName.trim();\n if (/^mcp__/i.test(trimmed)) {\n const parts = trimmed.split(\"__\");\n if (parts.length >= 3) return parts[parts.length - 1]!;\n }\n return trimmed;\n}\n\nfunction normalizePattern(pattern: string): string {\n return pattern.trim().toLowerCase();\n}\n\nfunction matchesPattern(toolName: string, pattern: string): boolean {\n const bare = bareToolName(toolName).toLowerCase();\n const full = toolName.trim().toLowerCase();\n const pat = normalizePattern(pattern);\n if (!pat.includes(\"*\")) return bare === pat || full === pat;\n const escaped = pat.replace(/[.+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const re = new RegExp(`^${escaped.replace(/\\*/g, \".*\")}$`);\n return re.test(bare) || re.test(full);\n}\n\nfunction matchesAny(toolName: string, patterns: string[]): boolean {\n return patterns.some((pattern) => matchesPattern(toolName, pattern));\n}\n\nexport function shouldCaptureTool(toolName: unknown): boolean {\n if (typeof toolName !== \"string\" || !toolName.trim()) return true;\n\n const allow = parseEnvList(process.env[\"AGENTMEMORY_CAPTURE_ALLOW\"]);\n if (allow) return matchesAny(toolName, allow);\n\n const deny = [\n ...DEFAULT_DENY_PATTERNS,\n ...(parseEnvList(process.env[\"AGENTMEMORY_CAPTURE_DENY\"]) ?? []),\n ];\n return !matchesAny(toolName, deny);\n}\n\nexport function captureOutputMax(): number {\n const raw = process.env[\"AGENTMEMORY_CAPTURE_OUTPUT_MAX\"];\n if (!raw?.trim()) return 8000;\n const parsed = Number.parseInt(raw, 10);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : 8000;\n}\n\nexport function truncateCaptureOutput(value: unknown, max: number): unknown {\n if (typeof value === \"string\" && value.length > max) {\n const suffix = \"\\n[...truncated]\";\n return max <= suffix.length\n ? suffix.slice(0, max)\n : value.slice(0, max - suffix.length) + suffix;\n }\n if (typeof value === \"object\" && value !== null) {\n const str = JSON.stringify(value);\n if (str.length > max) {\n const suffix = \"...[truncated]\";\n return max <= suffix.length\n ? suffix.slice(0, max)\n : str.slice(0, max - suffix.length) + suffix;\n }\n return value;\n }\n return value;\n}\n\nexport function preCompactBudget(): number {\n const raw = process.env[\"AGENTMEMORY_PRE_COMPACT_BUDGET\"];\n if (raw?.trim() === \"0\") return 0;\n if (!raw?.trim()) return 1500;\n const parsed = Number.parseInt(raw, 10);\n return Number.isFinite(parsed) && parsed >= 0 ? parsed : 1500;\n}\n","import { execSync } from \"node:child_process\";\nimport { basename } from \"node:path\";\n\n// Resolution order: AGENTMEMORY_PROJECT_NAME env → git toplevel basename → cwd basename.\nexport function resolveProject(cwd?: string): string {\n const explicit = process.env[\"AGENTMEMORY_PROJECT_NAME\"];\n if (explicit && explicit.trim()) return explicit.trim();\n const dir = cwd && cwd.trim() ? cwd : process.cwd();\n try {\n const top = execSync(\"git rev-parse --show-toplevel\", {\n cwd: dir,\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n timeout: 500,\n })\n .toString()\n .trim();\n if (top) return basename(top);\n } catch {}\n return basename(dir);\n}\n\nexport function hookCwd(data: Record<string, unknown> | null | undefined): string | undefined {\n if (!data || typeof data !== \"object\") return undefined;\n if (typeof data.cwd === \"string\" && data.cwd.trim()) return data.cwd;\n const roots = data.workspace_roots;\n if (Array.isArray(roots)) {\n for (const root of roots) {\n if (typeof root === \"string\" && root.trim()) return root;\n }\n }\n const projectDir =\n process.env[\"DEVIN_PROJECT_DIR\"] || process.env[\"CLAUDE_PROJECT_DIR\"];\n if (projectDir && projectDir.trim()) return projectDir;\n return undefined;\n}\n","#!/usr/bin/env node\nimport { shouldCaptureTool } from \"./_capture-filter.js\";\nimport { resolveProject, hookCwd } from \"./_project.js\";\nimport { captureObservation, isDrainChild, runDrainChild, withEventId } from \"./_capture.js\";\n\nfunction isSdkChildContext(payload: unknown): boolean {\n if (process.env[\"AGENTMEMORY_SDK_CHILD\"] === \"1\") return true;\n if (!payload || typeof payload !== \"object\") return false;\n return (payload as { entrypoint?: unknown }).entrypoint === \"sdk-ts\";\n}\n\nconst OBSERVE_TIMEOUT_MS = 3000;\nconst EXIT_CAP_MS = 3500;\n\nasync function main() {\n if (isDrainChild()) return runDrainChild();\n let input = \"\";\n for await (const chunk of process.stdin) {\n input += chunk;\n }\n\n let data: Record<string, unknown>;\n try {\n data = JSON.parse(input);\n } catch {\n return;\n }\n\n if (!data || typeof data !== \"object\") return;\n if (isSdkChildContext(data)) return;\n if (data.is_interrupt || data.isInterrupt) return;\n\n const sessionId = ((data.session_id || data.sessionId || data.conversation_id) as string) || \"unknown\";\n const toolName = data.tool_name ?? data.toolName;\n if (!shouldCaptureTool(toolName)) return;\n\n const toolInput = data.tool_input ?? data.toolArgs;\n const error = data.error ?? data.errorMessage;\n\n const cwd = hookCwd(data) || process.cwd();\n\n void captureObservation(\n withEventId(\n {\n hookType: \"post_tool_failure\",\n sessionId,\n project: resolveProject(cwd),\n cwd,\n timestamp: new Date().toISOString(),\n data: {\n tool_name: toolName,\n tool_input:\n typeof toolInput === \"string\"\n ? toolInput.slice(0, 4000)\n : JSON.stringify(toolInput ?? \"\").slice(0, 4000),\n error:\n typeof error === \"string\"\n ? error.slice(0, 4000)\n : JSON.stringify(error ?? \"\").slice(0, 4000),\n },\n },\n data,\n ),\n OBSERVE_TIMEOUT_MS,\n );\n setTimeout(() => process.exit(0), EXIT_CAP_MS).unref();\n}\n\nmain().catch(() => process.exit(0));\n"],"mappings":"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"}
@@ -1,6 +1,69 @@
1
1
  #!/usr/bin/env node
2
2
  import { execSync } from "node:child_process";
3
3
  import { basename } from "node:path";
4
+ import { captureObservation, isDrainChild, runDrainChild, withEventId } from "./_capture.mjs";
5
+ //#region src/hooks/_capture-filter.ts
6
+ const DEFAULT_DENY_PATTERNS = [
7
+ "memory_*",
8
+ "toolsearch",
9
+ "listmcpresources",
10
+ "fetchmcpresource"
11
+ ];
12
+ function parseEnvList(raw) {
13
+ if (!raw?.trim()) return void 0;
14
+ return raw.split(/[,\s]+/).map((part) => part.trim()).filter(Boolean);
15
+ }
16
+ function bareToolName(toolName) {
17
+ const trimmed = toolName.trim();
18
+ if (/^mcp__/i.test(trimmed)) {
19
+ const parts = trimmed.split("__");
20
+ if (parts.length >= 3) return parts[parts.length - 1];
21
+ }
22
+ return trimmed;
23
+ }
24
+ function normalizePattern(pattern) {
25
+ return pattern.trim().toLowerCase();
26
+ }
27
+ function matchesPattern(toolName, pattern) {
28
+ const bare = bareToolName(toolName).toLowerCase();
29
+ const full = toolName.trim().toLowerCase();
30
+ const pat = normalizePattern(pattern);
31
+ if (!pat.includes("*")) return bare === pat || full === pat;
32
+ const escaped = pat.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
33
+ const re = new RegExp(`^${escaped.replace(/\*/g, ".*")}$`);
34
+ return re.test(bare) || re.test(full);
35
+ }
36
+ function matchesAny(toolName, patterns) {
37
+ return patterns.some((pattern) => matchesPattern(toolName, pattern));
38
+ }
39
+ function shouldCaptureTool(toolName) {
40
+ if (typeof toolName !== "string" || !toolName.trim()) return true;
41
+ const allow = parseEnvList(process.env["AGENTMEMORY_CAPTURE_ALLOW"]);
42
+ if (allow) return matchesAny(toolName, allow);
43
+ return !matchesAny(toolName, [...DEFAULT_DENY_PATTERNS, ...parseEnvList(process.env["AGENTMEMORY_CAPTURE_DENY"]) ?? []]);
44
+ }
45
+ function captureOutputMax() {
46
+ const raw = process.env["AGENTMEMORY_CAPTURE_OUTPUT_MAX"];
47
+ if (!raw?.trim()) return 8e3;
48
+ const parsed = Number.parseInt(raw, 10);
49
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 8e3;
50
+ }
51
+ function truncateCaptureOutput(value, max) {
52
+ if (typeof value === "string" && value.length > max) {
53
+ const suffix = "\n[...truncated]";
54
+ return max <= 15 ? suffix.slice(0, max) : value.slice(0, max - 15) + suffix;
55
+ }
56
+ if (typeof value === "object" && value !== null) {
57
+ const str = JSON.stringify(value);
58
+ if (str.length > max) {
59
+ const suffix = "...[truncated]";
60
+ return max <= 14 ? suffix.slice(0, max) : str.slice(0, max - 14) + suffix;
61
+ }
62
+ return value;
63
+ }
64
+ return value;
65
+ }
66
+ //#endregion
4
67
  //#region src/hooks/_project.ts
5
68
  function resolveProject(cwd) {
6
69
  const explicit = process.env["AGENTMEMORY_PROJECT_NAME"];
@@ -37,14 +100,10 @@ function isSdkChildContext(payload) {
37
100
  if (!payload || typeof payload !== "object") return false;
38
101
  return payload.entrypoint === "sdk-ts";
39
102
  }
40
- const REST_URL = process.env["AGENTMEMORY_URL"] || "http://localhost:3111";
41
- const SECRET = process.env["AGENTMEMORY_SECRET"] || "";
42
- function authHeaders() {
43
- const h = { "Content-Type": "application/json" };
44
- if (SECRET) h["Authorization"] = `Bearer ${SECRET}`;
45
- return h;
46
- }
103
+ const OBSERVE_TIMEOUT_MS = 3e3;
104
+ const EXIT_CAP_MS = 3500;
47
105
  async function main() {
106
+ if (isDrainChild()) return runDrainChild();
48
107
  let input = "";
49
108
  for await (const chunk of process.stdin) input += chunk;
50
109
  let data;
@@ -57,28 +116,30 @@ async function main() {
57
116
  if (isSdkChildContext(data)) return;
58
117
  const sessionId = data.session_id || data.sessionId || data.conversation_id || "unknown";
59
118
  const toolName = data.tool_name ?? data.toolName;
119
+ if (!shouldCaptureTool(toolName)) return;
60
120
  const toolInput = data.tool_input ?? data.toolArgs;
61
121
  const { imageData, cleanOutput } = extractImageData(toolOutput(data));
62
122
  const cwd = hookCwd(data) || process.cwd();
63
- fetch(`${REST_URL}/agentmemory/observe`, {
64
- method: "POST",
65
- headers: authHeaders(),
66
- body: JSON.stringify({
67
- hookType: "post_tool_use",
68
- sessionId,
69
- project: resolveProject(cwd),
70
- cwd,
71
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
72
- data: {
73
- tool_name: toolName,
74
- tool_input: toolInput,
75
- tool_output: truncate(cleanOutput, 8e3),
76
- ...imageData ? { image_data: imageData } : {}
77
- }
78
- }),
79
- signal: AbortSignal.timeout(3e3)
80
- }).catch(() => {});
81
- setTimeout(() => process.exit(0), 500).unref();
123
+ const toolOutputText = truncateCaptureOutput(cleanOutput, captureOutputMax());
124
+ captureObservation(withEventId({
125
+ hookType: "post_tool_use",
126
+ sessionId,
127
+ project: resolveProject(cwd),
128
+ cwd,
129
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
130
+ data: {
131
+ tool_name: toolName,
132
+ tool_input: toolInput,
133
+ tool_output: toolOutputText,
134
+ ...imageData ? { image_data: imageData } : {}
135
+ }
136
+ }, data, {
137
+ tool_name: toolName,
138
+ tool_input: toolInput,
139
+ tool_output: toolOutputText,
140
+ image_data: imageData
141
+ }), OBSERVE_TIMEOUT_MS);
142
+ setTimeout(() => process.exit(0), EXIT_CAP_MS).unref();
82
143
  }
83
144
  function toolOutput(data) {
84
145
  if (data.tool_response !== void 0) return data.tool_response;
@@ -116,15 +177,6 @@ function extractImageData(output) {
116
177
  cleanOutput: output
117
178
  };
118
179
  }
119
- function truncate(value, max) {
120
- if (typeof value === "string" && value.length > max) return value.slice(0, max) + "\n[...truncated]";
121
- if (typeof value === "object" && value !== null) {
122
- const str = JSON.stringify(value);
123
- if (str.length > max) return str.slice(0, max) + "...[truncated]";
124
- return value;
125
- }
126
- return value;
127
- }
128
180
  main().catch(() => process.exit(0));
129
181
  //#endregion
130
182
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"post-tool-use.mjs","names":[],"sources":["../../src/hooks/_project.ts","../../src/hooks/post-tool-use.ts"],"sourcesContent":["import { execSync } from \"node:child_process\";\nimport { basename } from \"node:path\";\n\n// Resolution order: AGENTMEMORY_PROJECT_NAME env → git toplevel basename → cwd basename.\nexport function resolveProject(cwd?: string): string {\n const explicit = process.env[\"AGENTMEMORY_PROJECT_NAME\"];\n if (explicit && explicit.trim()) return explicit.trim();\n const dir = cwd && cwd.trim() ? cwd : process.cwd();\n try {\n const top = execSync(\"git rev-parse --show-toplevel\", {\n cwd: dir,\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n timeout: 500,\n })\n .toString()\n .trim();\n if (top) return basename(top);\n } catch {}\n return basename(dir);\n}\n\nexport function hookCwd(data: Record<string, unknown> | null | undefined): string | undefined {\n if (!data || typeof data !== \"object\") return undefined;\n if (typeof data.cwd === \"string\" && data.cwd.trim()) return data.cwd;\n const roots = data.workspace_roots;\n if (Array.isArray(roots)) {\n for (const root of roots) {\n if (typeof root === \"string\" && root.trim()) return root;\n }\n }\n const projectDir =\n process.env[\"DEVIN_PROJECT_DIR\"] || process.env[\"CLAUDE_PROJECT_DIR\"];\n if (projectDir && projectDir.trim()) return projectDir;\n return undefined;\n}\n","#!/usr/bin/env node\nimport { resolveProject, hookCwd } from \"./_project.js\";\n\nfunction isSdkChildContext(payload: unknown): boolean {\n if (process.env[\"AGENTMEMORY_SDK_CHILD\"] === \"1\") return true;\n if (!payload || typeof payload !== \"object\") return false;\n return (payload as { entrypoint?: unknown }).entrypoint === \"sdk-ts\";\n}\n\nconst REST_URL = process.env[\"AGENTMEMORY_URL\"] || \"http://localhost:3111\";\nconst SECRET = process.env[\"AGENTMEMORY_SECRET\"] || \"\";\n\nfunction authHeaders(): Record<string, string> {\n const h: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (SECRET) h[\"Authorization\"] = `Bearer ${SECRET}`;\n return h;\n}\n\nasync function main() {\n let input = \"\";\n for await (const chunk of process.stdin) {\n input += chunk;\n }\n\n let data: Record<string, unknown>;\n try {\n data = JSON.parse(input);\n } catch {\n return;\n }\n\n if (!data || typeof data !== \"object\") return;\n if (isSdkChildContext(data)) return;\n\n const sessionId = ((data.session_id || data.sessionId || data.conversation_id) as string) || \"unknown\";\n const toolName = data.tool_name ?? data.toolName;\n const toolInput = data.tool_input ?? data.toolArgs;\n\n const { imageData, cleanOutput } = extractImageData(toolOutput(data));\n const cwd = hookCwd(data) || process.cwd();\n\n fetch(`${REST_URL}/agentmemory/observe`, {\n method: \"POST\",\n headers: authHeaders(),\n body: JSON.stringify({\n hookType: \"post_tool_use\",\n sessionId,\n project: resolveProject(cwd),\n cwd,\n timestamp: new Date().toISOString(),\n data: {\n tool_name: toolName,\n tool_input: toolInput,\n tool_output: truncate(cleanOutput, 8000),\n ...(imageData ? { image_data: imageData } : {}),\n },\n }),\n signal: AbortSignal.timeout(3000),\n }).catch(() => {});\n setTimeout(() => process.exit(0), 500).unref();\n}\n\nfunction toolOutput(data: Record<string, unknown>): unknown {\n if (data.tool_response !== undefined) return data.tool_response;\n if (data.tool_output !== undefined) return data.tool_output;\n const result = data.tool_result ?? data.toolResult;\n if (typeof result === \"object\" && result !== null) {\n const obj = result as Record<string, unknown>;\n return obj.text_result_for_llm ?? obj.textResultForLlm ?? result;\n }\n return result;\n}\n\nfunction isBase64Image(val: unknown): val is string {\n return typeof val === \"string\" && (\n val.startsWith(\"data:image/\") ||\n val.startsWith(\"iVBORw0KGgo\") ||\n val.startsWith(\"/9j/\")\n );\n}\n\nfunction extractImageData(output: unknown): { imageData: string | undefined; cleanOutput: unknown } {\n if (isBase64Image(output)) {\n return { imageData: output, cleanOutput: \"[image data extracted]\" };\n }\n\n if (typeof output === \"object\" && output !== null && !Array.isArray(output)) {\n const obj = output as Record<string, unknown>;\n let imageData: string | undefined;\n const clean: Record<string, unknown> = {};\n\n for (const [key, val] of Object.entries(obj)) {\n if (!imageData && isBase64Image(val)) {\n imageData = val;\n clean[key] = \"[image data extracted]\";\n } else {\n clean[key] = val;\n }\n }\n\n return { imageData, cleanOutput: clean };\n }\n\n return { imageData: undefined, cleanOutput: output };\n}\n\nfunction truncate(value: unknown, max: number): unknown {\n if (typeof value === \"string\" && value.length > max) {\n return value.slice(0, max) + \"\\n[...truncated]\";\n }\n if (typeof value === \"object\" && value !== null) {\n const str = JSON.stringify(value);\n if (str.length > max) return str.slice(0, max) + \"...[truncated]\";\n return value;\n }\n return value;\n}\n\nmain().catch(() => process.exit(0));\n"],"mappings":"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"}
1
+ {"version":3,"file":"post-tool-use.mjs","names":[],"sources":["../../src/hooks/_capture-filter.ts","../../src/hooks/_project.ts","../../src/hooks/post-tool-use.ts"],"sourcesContent":["const DEFAULT_DENY_PATTERNS = [\n \"memory_*\",\n \"toolsearch\",\n \"listmcpresources\",\n \"fetchmcpresource\",\n];\n\nfunction parseEnvList(raw: string | undefined): string[] | undefined {\n if (!raw?.trim()) return undefined;\n return raw\n .split(/[,\\s]+/)\n .map((part) => part.trim())\n .filter(Boolean);\n}\n\nexport function bareToolName(toolName: string): string {\n const trimmed = toolName.trim();\n if (/^mcp__/i.test(trimmed)) {\n const parts = trimmed.split(\"__\");\n if (parts.length >= 3) return parts[parts.length - 1]!;\n }\n return trimmed;\n}\n\nfunction normalizePattern(pattern: string): string {\n return pattern.trim().toLowerCase();\n}\n\nfunction matchesPattern(toolName: string, pattern: string): boolean {\n const bare = bareToolName(toolName).toLowerCase();\n const full = toolName.trim().toLowerCase();\n const pat = normalizePattern(pattern);\n if (!pat.includes(\"*\")) return bare === pat || full === pat;\n const escaped = pat.replace(/[.+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const re = new RegExp(`^${escaped.replace(/\\*/g, \".*\")}$`);\n return re.test(bare) || re.test(full);\n}\n\nfunction matchesAny(toolName: string, patterns: string[]): boolean {\n return patterns.some((pattern) => matchesPattern(toolName, pattern));\n}\n\nexport function shouldCaptureTool(toolName: unknown): boolean {\n if (typeof toolName !== \"string\" || !toolName.trim()) return true;\n\n const allow = parseEnvList(process.env[\"AGENTMEMORY_CAPTURE_ALLOW\"]);\n if (allow) return matchesAny(toolName, allow);\n\n const deny = [\n ...DEFAULT_DENY_PATTERNS,\n ...(parseEnvList(process.env[\"AGENTMEMORY_CAPTURE_DENY\"]) ?? []),\n ];\n return !matchesAny(toolName, deny);\n}\n\nexport function captureOutputMax(): number {\n const raw = process.env[\"AGENTMEMORY_CAPTURE_OUTPUT_MAX\"];\n if (!raw?.trim()) return 8000;\n const parsed = Number.parseInt(raw, 10);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : 8000;\n}\n\nexport function truncateCaptureOutput(value: unknown, max: number): unknown {\n if (typeof value === \"string\" && value.length > max) {\n const suffix = \"\\n[...truncated]\";\n return max <= suffix.length\n ? suffix.slice(0, max)\n : value.slice(0, max - suffix.length) + suffix;\n }\n if (typeof value === \"object\" && value !== null) {\n const str = JSON.stringify(value);\n if (str.length > max) {\n const suffix = \"...[truncated]\";\n return max <= suffix.length\n ? suffix.slice(0, max)\n : str.slice(0, max - suffix.length) + suffix;\n }\n return value;\n }\n return value;\n}\n\nexport function preCompactBudget(): number {\n const raw = process.env[\"AGENTMEMORY_PRE_COMPACT_BUDGET\"];\n if (raw?.trim() === \"0\") return 0;\n if (!raw?.trim()) return 1500;\n const parsed = Number.parseInt(raw, 10);\n return Number.isFinite(parsed) && parsed >= 0 ? parsed : 1500;\n}\n","import { execSync } from \"node:child_process\";\nimport { basename } from \"node:path\";\n\n// Resolution order: AGENTMEMORY_PROJECT_NAME env → git toplevel basename → cwd basename.\nexport function resolveProject(cwd?: string): string {\n const explicit = process.env[\"AGENTMEMORY_PROJECT_NAME\"];\n if (explicit && explicit.trim()) return explicit.trim();\n const dir = cwd && cwd.trim() ? cwd : process.cwd();\n try {\n const top = execSync(\"git rev-parse --show-toplevel\", {\n cwd: dir,\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n timeout: 500,\n })\n .toString()\n .trim();\n if (top) return basename(top);\n } catch {}\n return basename(dir);\n}\n\nexport function hookCwd(data: Record<string, unknown> | null | undefined): string | undefined {\n if (!data || typeof data !== \"object\") return undefined;\n if (typeof data.cwd === \"string\" && data.cwd.trim()) return data.cwd;\n const roots = data.workspace_roots;\n if (Array.isArray(roots)) {\n for (const root of roots) {\n if (typeof root === \"string\" && root.trim()) return root;\n }\n }\n const projectDir =\n process.env[\"DEVIN_PROJECT_DIR\"] || process.env[\"CLAUDE_PROJECT_DIR\"];\n if (projectDir && projectDir.trim()) return projectDir;\n return undefined;\n}\n","#!/usr/bin/env node\nimport {\n captureOutputMax,\n shouldCaptureTool,\n truncateCaptureOutput,\n} from \"./_capture-filter.js\";\nimport { resolveProject, hookCwd } from \"./_project.js\";\nimport { captureObservation, isDrainChild, runDrainChild, withEventId } from \"./_capture.js\";\n\nfunction isSdkChildContext(payload: unknown): boolean {\n if (process.env[\"AGENTMEMORY_SDK_CHILD\"] === \"1\") return true;\n if (!payload || typeof payload !== \"object\") return false;\n return (payload as { entrypoint?: unknown }).entrypoint === \"sdk-ts\";\n}\n\nconst OBSERVE_TIMEOUT_MS = 3000;\nconst EXIT_CAP_MS = 3500;\n\nasync function main() {\n if (isDrainChild()) return runDrainChild();\n let input = \"\";\n for await (const chunk of process.stdin) {\n input += chunk;\n }\n\n let data: Record<string, unknown>;\n try {\n data = JSON.parse(input);\n } catch {\n return;\n }\n\n if (!data || typeof data !== \"object\") return;\n if (isSdkChildContext(data)) return;\n\n const sessionId = ((data.session_id || data.sessionId || data.conversation_id) as string) || \"unknown\";\n const toolName = data.tool_name ?? data.toolName;\n if (!shouldCaptureTool(toolName)) return;\n\n const toolInput = data.tool_input ?? data.toolArgs;\n\n const { imageData, cleanOutput } = extractImageData(toolOutput(data));\n const cwd = hookCwd(data) || process.cwd();\n const outputMax = captureOutputMax();\n\n const toolOutputText = truncateCaptureOutput(cleanOutput, outputMax);\n const body = withEventId(\n {\n hookType: \"post_tool_use\",\n sessionId,\n project: resolveProject(cwd),\n cwd,\n timestamp: new Date().toISOString(),\n data: {\n tool_name: toolName,\n tool_input: toolInput,\n tool_output: toolOutputText,\n ...(imageData ? { image_data: imageData } : {}),\n },\n },\n data,\n { tool_name: toolName, tool_input: toolInput, tool_output: toolOutputText, image_data: imageData },\n );\n void captureObservation(body, OBSERVE_TIMEOUT_MS);\n setTimeout(() => process.exit(0), EXIT_CAP_MS).unref();\n}\n\nfunction toolOutput(data: Record<string, unknown>): unknown {\n if (data.tool_response !== undefined) return data.tool_response;\n if (data.tool_output !== undefined) return data.tool_output;\n const result = data.tool_result ?? data.toolResult;\n if (typeof result === \"object\" && result !== null) {\n const obj = result as Record<string, unknown>;\n return obj.text_result_for_llm ?? obj.textResultForLlm ?? result;\n }\n return result;\n}\n\nfunction isBase64Image(val: unknown): val is string {\n return typeof val === \"string\" && (\n val.startsWith(\"data:image/\") ||\n val.startsWith(\"iVBORw0KGgo\") ||\n val.startsWith(\"/9j/\")\n );\n}\n\nfunction extractImageData(output: unknown): { imageData: string | undefined; cleanOutput: unknown } {\n if (isBase64Image(output)) {\n return { imageData: output, cleanOutput: \"[image data extracted]\" };\n }\n\n if (typeof output === \"object\" && output !== null && !Array.isArray(output)) {\n const obj = output as Record<string, unknown>;\n let imageData: string | undefined;\n const clean: Record<string, unknown> = {};\n\n for (const [key, val] of Object.entries(obj)) {\n if (!imageData && isBase64Image(val)) {\n imageData = val;\n clean[key] = \"[image data extracted]\";\n } else {\n clean[key] = val;\n }\n }\n\n return { imageData, cleanOutput: clean };\n }\n\n return { imageData: undefined, cleanOutput: output };\n}\n\nmain().catch(() => process.exit(0));\n"],"mappings":"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"}
@@ -1,6 +1,84 @@
1
1
  #!/usr/bin/env node
2
+ import { readFileSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { basename, join } from "node:path";
2
5
  import { execSync } from "node:child_process";
3
- import { basename } from "node:path";
6
+ //#region src/secret-store.ts
7
+ const SECRET_KEY = "AGENTMEMORY_SECRET";
8
+ function agentmemoryHomeDir() {
9
+ return join(homedir(), ".agentmemory");
10
+ }
11
+ function secretFilePath() {
12
+ return join(agentmemoryHomeDir(), "secret");
13
+ }
14
+ function usable(value) {
15
+ if (typeof value !== "string") return "";
16
+ const trimmed = value.trim();
17
+ if (!trimmed) return "";
18
+ if (trimmed.startsWith("${") && trimmed.endsWith("}")) return "";
19
+ return trimmed;
20
+ }
21
+ function unquote(value) {
22
+ const quote = value[0];
23
+ if ((quote === "\"" || quote === "'") && value.length > 1) {
24
+ const close = value.indexOf(quote, 1);
25
+ if (close !== -1) return value.slice(1, close);
26
+ }
27
+ const hash = value.indexOf(" #");
28
+ return hash === -1 ? value : value.slice(0, hash).trim();
29
+ }
30
+ function readEnvFileSecret() {
31
+ let content;
32
+ try {
33
+ content = readFileSync(join(agentmemoryHomeDir(), ".env"), "utf-8");
34
+ } catch {
35
+ return "";
36
+ }
37
+ if (typeof content !== "string") return "";
38
+ let found = "";
39
+ for (const line of content.split("\n")) {
40
+ const trimmed = line.trim();
41
+ if (trimmed.startsWith("#")) continue;
42
+ const eq = trimmed.indexOf("=");
43
+ if (eq === -1) continue;
44
+ if (trimmed.slice(0, eq).replace(/^export\s+/, "").trim() !== SECRET_KEY) continue;
45
+ found = usable(unquote(trimmed.slice(eq + 1).trim()));
46
+ }
47
+ return found;
48
+ }
49
+ function readStoredSecret() {
50
+ try {
51
+ return usable(readFileSync(secretFilePath(), "utf-8"));
52
+ } catch {
53
+ return "";
54
+ }
55
+ }
56
+ function isLoopbackUrl(url) {
57
+ let hostname;
58
+ try {
59
+ hostname = new URL(url).hostname.toLowerCase();
60
+ } catch {
61
+ return false;
62
+ }
63
+ const bare = hostname.replace(/^\[|\]$/g, "");
64
+ return bare === "localhost" || bare === "::1" || /^127(?:\.\d{1,3}){3}$/.test(bare);
65
+ }
66
+ function resolveClientSecret(baseUrl, env = process.env) {
67
+ const fromEnv = usable(env[SECRET_KEY]);
68
+ if (fromEnv) return fromEnv;
69
+ if (!isLoopbackUrl(baseUrl)) return "";
70
+ return readEnvFileSecret() || readStoredSecret();
71
+ }
72
+ //#endregion
73
+ //#region src/hooks/_capture-filter.ts
74
+ function preCompactBudget() {
75
+ const raw = process.env["AGENTMEMORY_PRE_COMPACT_BUDGET"];
76
+ if (raw?.trim() === "0") return 0;
77
+ if (!raw?.trim()) return 1500;
78
+ const parsed = Number.parseInt(raw, 10);
79
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : 1500;
80
+ }
81
+ //#endregion
4
82
  //#region src/hooks/_project.ts
5
83
  function resolveProject(cwd) {
6
84
  const explicit = process.env["AGENTMEMORY_PROJECT_NAME"];
@@ -38,7 +116,7 @@ function isSdkChildContext(payload) {
38
116
  return payload.entrypoint === "sdk-ts";
39
117
  }
40
118
  const REST_URL = process.env["AGENTMEMORY_URL"] || "http://localhost:3111";
41
- const SECRET = process.env["AGENTMEMORY_SECRET"] || "";
119
+ const SECRET = resolveClientSecret(REST_URL);
42
120
  function authHeaders() {
43
121
  const h = { "Content-Type": "application/json" };
44
122
  if (SECRET) h["Authorization"] = `Bearer ${SECRET}`;
@@ -65,6 +143,8 @@ async function main() {
65
143
  signal: AbortSignal.timeout(5e3)
66
144
  });
67
145
  } catch {}
146
+ const budget = preCompactBudget();
147
+ if (budget === 0) return;
68
148
  try {
69
149
  const res = await fetch(`${REST_URL}/agentmemory/context`, {
70
150
  method: "POST",
@@ -72,7 +152,7 @@ async function main() {
72
152
  body: JSON.stringify({
73
153
  sessionId,
74
154
  project,
75
- budget: 1500
155
+ budget
76
156
  }),
77
157
  signal: AbortSignal.timeout(5e3)
78
158
  });
@@ -1 +1 @@
1
- {"version":3,"file":"pre-compact.mjs","names":[],"sources":["../../src/hooks/_project.ts","../../src/hooks/pre-compact.ts"],"sourcesContent":["import { execSync } from \"node:child_process\";\nimport { basename } from \"node:path\";\n\n// Resolution order: AGENTMEMORY_PROJECT_NAME env → git toplevel basename → cwd basename.\nexport function resolveProject(cwd?: string): string {\n const explicit = process.env[\"AGENTMEMORY_PROJECT_NAME\"];\n if (explicit && explicit.trim()) return explicit.trim();\n const dir = cwd && cwd.trim() ? cwd : process.cwd();\n try {\n const top = execSync(\"git rev-parse --show-toplevel\", {\n cwd: dir,\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n timeout: 500,\n })\n .toString()\n .trim();\n if (top) return basename(top);\n } catch {}\n return basename(dir);\n}\n\nexport function hookCwd(data: Record<string, unknown> | null | undefined): string | undefined {\n if (!data || typeof data !== \"object\") return undefined;\n if (typeof data.cwd === \"string\" && data.cwd.trim()) return data.cwd;\n const roots = data.workspace_roots;\n if (Array.isArray(roots)) {\n for (const root of roots) {\n if (typeof root === \"string\" && root.trim()) return root;\n }\n }\n const projectDir =\n process.env[\"DEVIN_PROJECT_DIR\"] || process.env[\"CLAUDE_PROJECT_DIR\"];\n if (projectDir && projectDir.trim()) return projectDir;\n return undefined;\n}\n","#!/usr/bin/env node\nimport { resolveProject, hookCwd } from \"./_project.js\";\n\nfunction isSdkChildContext(payload: unknown): boolean {\n if (process.env[\"AGENTMEMORY_SDK_CHILD\"] === \"1\") return true;\n if (!payload || typeof payload !== \"object\") return false;\n return (payload as { entrypoint?: unknown }).entrypoint === \"sdk-ts\";\n}\n\nconst REST_URL = process.env[\"AGENTMEMORY_URL\"] || \"http://localhost:3111\";\nconst SECRET = process.env[\"AGENTMEMORY_SECRET\"] || \"\";\n\nfunction authHeaders(): Record<string, string> {\n const h: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (SECRET) h[\"Authorization\"] = `Bearer ${SECRET}`;\n return h;\n}\n\nasync function main() {\n let input = \"\";\n for await (const chunk of process.stdin) {\n input += chunk;\n }\n\n let data: Record<string, unknown>;\n try {\n data = JSON.parse(input);\n } catch {\n return;\n }\n\n if (!data || typeof data !== \"object\") return;\n if (isSdkChildContext(data)) return;\n\n const sessionId = ((data.session_id || data.sessionId || data.conversation_id) as string) || \"unknown\";\n const project = resolveProject(hookCwd(data));\n\n if (process.env[\"CLAUDE_MEMORY_BRIDGE\"] === \"true\") {\n try {\n await fetch(`${REST_URL}/agentmemory/claude-bridge/sync`, {\n method: \"POST\",\n headers: authHeaders(),\n body: JSON.stringify({}),\n signal: AbortSignal.timeout(5000),\n });\n } catch {\n // best-effort\n }\n }\n\n try {\n const res = await fetch(`${REST_URL}/agentmemory/context`, {\n method: \"POST\",\n headers: authHeaders(),\n body: JSON.stringify({ sessionId, project, budget: 1500 }),\n signal: AbortSignal.timeout(5000),\n });\n\n if (res.ok) {\n const result = (await res.json()) as { context?: string };\n if (result.context) {\n process.stdout.write(result.context);\n }\n }\n } catch {\n // best effort -- don't block compaction\n }\n}\n\nmain().catch(() => process.exit(0));\n"],"mappings":";;;;AAIA,SAAgB,eAAe,KAAsB;CACnD,MAAM,WAAW,QAAQ,IAAI;AAC7B,KAAI,YAAY,SAAS,MAAM,CAAE,QAAO,SAAS,MAAM;CACvD,MAAM,MAAM,OAAO,IAAI,MAAM,GAAG,MAAM,QAAQ,KAAK;AACnD,KAAI;EACF,MAAM,MAAM,SAAS,iCAAiC;GACpD,KAAK;GACL,OAAO;IAAC;IAAU;IAAQ;IAAS;GACnC,SAAS;GACV,CAAC,CACC,UAAU,CACV,MAAM;AACT,MAAI,IAAK,QAAO,SAAS,IAAI;SACvB;AACR,QAAO,SAAS,IAAI;;AAGtB,SAAgB,QAAQ,MAAsE;AAC5F,KAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO,KAAA;AAC9C,KAAI,OAAO,KAAK,QAAQ,YAAY,KAAK,IAAI,MAAM,CAAE,QAAO,KAAK;CACjE,MAAM,QAAQ,KAAK;AACnB,KAAI,MAAM,QAAQ,MAAM;OACjB,MAAM,QAAQ,MACjB,KAAI,OAAO,SAAS,YAAY,KAAK,MAAM,CAAE,QAAO;;CAGxD,MAAM,aACJ,QAAQ,IAAI,wBAAwB,QAAQ,IAAI;AAClD,KAAI,cAAc,WAAW,MAAM,CAAE,QAAO;;;;AC7B9C,SAAS,kBAAkB,SAA2B;AACpD,KAAI,QAAQ,IAAI,6BAA6B,IAAK,QAAO;AACzD,KAAI,CAAC,WAAW,OAAO,YAAY,SAAU,QAAO;AACpD,QAAQ,QAAqC,eAAe;;AAG9D,MAAM,WAAW,QAAQ,IAAI,sBAAsB;AACnD,MAAM,SAAS,QAAQ,IAAI,yBAAyB;AAEpD,SAAS,cAAsC;CAC7C,MAAM,IAA4B,EAAE,gBAAgB,oBAAoB;AACxE,KAAI,OAAQ,GAAE,mBAAmB,UAAU;AAC3C,QAAO;;AAGT,eAAe,OAAO;CACpB,IAAI,QAAQ;AACZ,YAAW,MAAM,SAAS,QAAQ,MAChC,UAAS;CAGX,IAAI;AACJ,KAAI;AACF,SAAO,KAAK,MAAM,MAAM;SAClB;AACN;;AAGF,KAAI,CAAC,QAAQ,OAAO,SAAS,SAAU;AACvC,KAAI,kBAAkB,KAAK,CAAE;CAE7B,MAAM,YAAc,KAAK,cAAc,KAAK,aAAa,KAAK,mBAA+B;CAC7F,MAAM,UAAU,eAAe,QAAQ,KAAK,CAAC;AAE7C,KAAI,QAAQ,IAAI,4BAA4B,OAC1C,KAAI;AACF,QAAM,MAAM,GAAG,SAAS,kCAAkC;GACxD,QAAQ;GACR,SAAS,aAAa;GACtB,MAAM,KAAK,UAAU,EAAE,CAAC;GACxB,QAAQ,YAAY,QAAQ,IAAK;GAClC,CAAC;SACI;AAKV,KAAI;EACF,MAAM,MAAM,MAAM,MAAM,GAAG,SAAS,uBAAuB;GACzD,QAAQ;GACR,SAAS,aAAa;GACtB,MAAM,KAAK,UAAU;IAAE;IAAW;IAAS,QAAQ;IAAM,CAAC;GAC1D,QAAQ,YAAY,QAAQ,IAAK;GAClC,CAAC;AAEF,MAAI,IAAI,IAAI;GACV,MAAM,SAAU,MAAM,IAAI,MAAM;AAChC,OAAI,OAAO,QACT,SAAQ,OAAO,MAAM,OAAO,QAAQ;;SAGlC;;AAKV,MAAM,CAAC,YAAY,QAAQ,KAAK,EAAE,CAAC"}
1
+ {"version":3,"file":"pre-compact.mjs","names":[],"sources":["../../src/secret-store.ts","../../src/hooks/_capture-filter.ts","../../src/hooks/_project.ts","../../src/hooks/pre-compact.ts"],"sourcesContent":["import {\n chmodSync,\n closeSync,\n linkSync,\n mkdirSync,\n openSync,\n readFileSync,\n unlinkSync,\n writeSync,\n} from \"node:fs\";\nimport { randomBytes } from \"node:crypto\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nconst SECRET_KEY = \"AGENTMEMORY_SECRET\";\n\nexport function agentmemoryHomeDir(): string {\n return join(homedir(), \".agentmemory\");\n}\n\nexport function secretFilePath(): string {\n return join(agentmemoryHomeDir(), \"secret\");\n}\n\nfunction usable(value: string | undefined): string {\n if (typeof value !== \"string\") return \"\";\n const trimmed = value.trim();\n if (!trimmed) return \"\";\n if (trimmed.startsWith(\"${\") && trimmed.endsWith(\"}\")) return \"\";\n return trimmed;\n}\n\nfunction unquote(value: string): string {\n const quote = value[0];\n if ((quote === '\"' || quote === \"'\") && value.length > 1) {\n const close = value.indexOf(quote, 1);\n if (close !== -1) return value.slice(1, close);\n }\n const hash = value.indexOf(\" #\");\n return hash === -1 ? value : value.slice(0, hash).trim();\n}\n\nexport function readEnvFileSecret(): string {\n let content: unknown;\n try {\n content = readFileSync(join(agentmemoryHomeDir(), \".env\"), \"utf-8\");\n } catch {\n return \"\";\n }\n if (typeof content !== \"string\") return \"\";\n let found = \"\";\n for (const line of content.split(\"\\n\")) {\n const trimmed = line.trim();\n if (trimmed.startsWith(\"#\")) continue;\n const eq = trimmed.indexOf(\"=\");\n if (eq === -1) continue;\n const key = trimmed.slice(0, eq).replace(/^export\\s+/, \"\").trim();\n if (key !== SECRET_KEY) continue;\n found = usable(unquote(trimmed.slice(eq + 1).trim()));\n }\n return found;\n}\n\nexport function readStoredSecret(): string {\n try {\n return usable(readFileSync(secretFilePath(), \"utf-8\"));\n } catch {\n return \"\";\n }\n}\n\nexport function explicitSecret(env: NodeJS.ProcessEnv = process.env): string {\n return usable(env[SECRET_KEY]) || readEnvFileSecret();\n}\n\nexport function isLoopbackUrl(url: string): boolean {\n let hostname: string;\n try {\n hostname = new URL(url).hostname.toLowerCase();\n } catch {\n return false;\n }\n const bare = hostname.replace(/^\\[|\\]$/g, \"\");\n return bare === \"localhost\" || bare === \"::1\" || /^127(?:\\.\\d{1,3}){3}$/.test(bare);\n}\n\nexport function resolveClientSecret(\n baseUrl: string,\n env: NodeJS.ProcessEnv = process.env,\n): string {\n const fromEnv = usable(env[SECRET_KEY]);\n if (fromEnv) return fromEnv;\n if (!isLoopbackUrl(baseUrl)) return \"\";\n return readEnvFileSecret() || readStoredSecret();\n}\n\nexport function ensureServerSecret(env: NodeJS.ProcessEnv = process.env): {\n secret: string;\n source: \"env\" | \"file\" | \"generated\";\n} {\n const configured = explicitSecret(env);\n if (configured) return { secret: configured, source: \"env\" };\n const stored = readStoredSecret();\n if (stored) return { secret: stored, source: \"file\" };\n const generated = randomBytes(32).toString(\"hex\");\n const path = secretFilePath();\n mkdirSync(agentmemoryHomeDir(), { recursive: true, mode: 0o700 });\n const staging = `${path}.${process.pid}.${randomBytes(4).toString(\"hex\")}.tmp`;\n const fd = openSync(staging, \"wx\", 0o600);\n try {\n writeSync(fd, `${generated}\\n`);\n } finally {\n closeSync(fd);\n }\n try {\n chmodSync(staging, 0o600);\n linkSync(staging, path);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"EEXIST\") throw err;\n const raced = readStoredSecret();\n if (!raced) throw err;\n return { secret: raced, source: \"file\" };\n } finally {\n try {\n unlinkSync(staging);\n } catch {}\n }\n return { secret: generated, source: \"generated\" };\n}\n\nexport function bearerHeaders(\n baseUrl: string,\n env: NodeJS.ProcessEnv = process.env,\n): Record<string, string> {\n const secret = resolveClientSecret(baseUrl, env);\n return secret ? { Authorization: `Bearer ${secret}` } : {};\n}\n","const DEFAULT_DENY_PATTERNS = [\n \"memory_*\",\n \"toolsearch\",\n \"listmcpresources\",\n \"fetchmcpresource\",\n];\n\nfunction parseEnvList(raw: string | undefined): string[] | undefined {\n if (!raw?.trim()) return undefined;\n return raw\n .split(/[,\\s]+/)\n .map((part) => part.trim())\n .filter(Boolean);\n}\n\nexport function bareToolName(toolName: string): string {\n const trimmed = toolName.trim();\n if (/^mcp__/i.test(trimmed)) {\n const parts = trimmed.split(\"__\");\n if (parts.length >= 3) return parts[parts.length - 1]!;\n }\n return trimmed;\n}\n\nfunction normalizePattern(pattern: string): string {\n return pattern.trim().toLowerCase();\n}\n\nfunction matchesPattern(toolName: string, pattern: string): boolean {\n const bare = bareToolName(toolName).toLowerCase();\n const full = toolName.trim().toLowerCase();\n const pat = normalizePattern(pattern);\n if (!pat.includes(\"*\")) return bare === pat || full === pat;\n const escaped = pat.replace(/[.+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const re = new RegExp(`^${escaped.replace(/\\*/g, \".*\")}$`);\n return re.test(bare) || re.test(full);\n}\n\nfunction matchesAny(toolName: string, patterns: string[]): boolean {\n return patterns.some((pattern) => matchesPattern(toolName, pattern));\n}\n\nexport function shouldCaptureTool(toolName: unknown): boolean {\n if (typeof toolName !== \"string\" || !toolName.trim()) return true;\n\n const allow = parseEnvList(process.env[\"AGENTMEMORY_CAPTURE_ALLOW\"]);\n if (allow) return matchesAny(toolName, allow);\n\n const deny = [\n ...DEFAULT_DENY_PATTERNS,\n ...(parseEnvList(process.env[\"AGENTMEMORY_CAPTURE_DENY\"]) ?? []),\n ];\n return !matchesAny(toolName, deny);\n}\n\nexport function captureOutputMax(): number {\n const raw = process.env[\"AGENTMEMORY_CAPTURE_OUTPUT_MAX\"];\n if (!raw?.trim()) return 8000;\n const parsed = Number.parseInt(raw, 10);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : 8000;\n}\n\nexport function truncateCaptureOutput(value: unknown, max: number): unknown {\n if (typeof value === \"string\" && value.length > max) {\n const suffix = \"\\n[...truncated]\";\n return max <= suffix.length\n ? suffix.slice(0, max)\n : value.slice(0, max - suffix.length) + suffix;\n }\n if (typeof value === \"object\" && value !== null) {\n const str = JSON.stringify(value);\n if (str.length > max) {\n const suffix = \"...[truncated]\";\n return max <= suffix.length\n ? suffix.slice(0, max)\n : str.slice(0, max - suffix.length) + suffix;\n }\n return value;\n }\n return value;\n}\n\nexport function preCompactBudget(): number {\n const raw = process.env[\"AGENTMEMORY_PRE_COMPACT_BUDGET\"];\n if (raw?.trim() === \"0\") return 0;\n if (!raw?.trim()) return 1500;\n const parsed = Number.parseInt(raw, 10);\n return Number.isFinite(parsed) && parsed >= 0 ? parsed : 1500;\n}\n","import { execSync } from \"node:child_process\";\nimport { basename } from \"node:path\";\n\n// Resolution order: AGENTMEMORY_PROJECT_NAME env → git toplevel basename → cwd basename.\nexport function resolveProject(cwd?: string): string {\n const explicit = process.env[\"AGENTMEMORY_PROJECT_NAME\"];\n if (explicit && explicit.trim()) return explicit.trim();\n const dir = cwd && cwd.trim() ? cwd : process.cwd();\n try {\n const top = execSync(\"git rev-parse --show-toplevel\", {\n cwd: dir,\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n timeout: 500,\n })\n .toString()\n .trim();\n if (top) return basename(top);\n } catch {}\n return basename(dir);\n}\n\nexport function hookCwd(data: Record<string, unknown> | null | undefined): string | undefined {\n if (!data || typeof data !== \"object\") return undefined;\n if (typeof data.cwd === \"string\" && data.cwd.trim()) return data.cwd;\n const roots = data.workspace_roots;\n if (Array.isArray(roots)) {\n for (const root of roots) {\n if (typeof root === \"string\" && root.trim()) return root;\n }\n }\n const projectDir =\n process.env[\"DEVIN_PROJECT_DIR\"] || process.env[\"CLAUDE_PROJECT_DIR\"];\n if (projectDir && projectDir.trim()) return projectDir;\n return undefined;\n}\n","#!/usr/bin/env node\nimport { resolveClientSecret } from \"../secret-store.js\";\nimport { preCompactBudget } from \"./_capture-filter.js\";\nimport { resolveProject, hookCwd } from \"./_project.js\";\n\nfunction isSdkChildContext(payload: unknown): boolean {\n if (process.env[\"AGENTMEMORY_SDK_CHILD\"] === \"1\") return true;\n if (!payload || typeof payload !== \"object\") return false;\n return (payload as { entrypoint?: unknown }).entrypoint === \"sdk-ts\";\n}\n\nconst REST_URL = process.env[\"AGENTMEMORY_URL\"] || \"http://localhost:3111\";\nconst SECRET = resolveClientSecret(REST_URL);\n\nfunction authHeaders(): Record<string, string> {\n const h: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (SECRET) h[\"Authorization\"] = `Bearer ${SECRET}`;\n return h;\n}\n\nasync function main() {\n let input = \"\";\n for await (const chunk of process.stdin) {\n input += chunk;\n }\n\n let data: Record<string, unknown>;\n try {\n data = JSON.parse(input);\n } catch {\n return;\n }\n\n if (!data || typeof data !== \"object\") return;\n if (isSdkChildContext(data)) return;\n\n const sessionId = ((data.session_id || data.sessionId || data.conversation_id) as string) || \"unknown\";\n const project = resolveProject(hookCwd(data));\n\n if (process.env[\"CLAUDE_MEMORY_BRIDGE\"] === \"true\") {\n try {\n await fetch(`${REST_URL}/agentmemory/claude-bridge/sync`, {\n method: \"POST\",\n headers: authHeaders(),\n body: JSON.stringify({}),\n signal: AbortSignal.timeout(5000),\n });\n } catch {\n // best-effort\n }\n }\n\n const budget = preCompactBudget();\n if (budget === 0) return;\n\n try {\n const res = await fetch(`${REST_URL}/agentmemory/context`, {\n method: \"POST\",\n headers: authHeaders(),\n body: JSON.stringify({ sessionId, project, budget }),\n signal: AbortSignal.timeout(5000),\n });\n\n if (res.ok) {\n const result = (await res.json()) as { context?: string };\n if (result.context) {\n process.stdout.write(result.context);\n }\n }\n } catch {\n // best effort -- don't block compaction\n }\n}\n\nmain().catch(() => process.exit(0));\n"],"mappings":"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"}
@@ -1,4 +1,74 @@
1
1
  #!/usr/bin/env node
2
+ import { readFileSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { join } from "node:path";
5
+ //#region src/secret-store.ts
6
+ const SECRET_KEY = "AGENTMEMORY_SECRET";
7
+ function agentmemoryHomeDir() {
8
+ return join(homedir(), ".agentmemory");
9
+ }
10
+ function secretFilePath() {
11
+ return join(agentmemoryHomeDir(), "secret");
12
+ }
13
+ function usable(value) {
14
+ if (typeof value !== "string") return "";
15
+ const trimmed = value.trim();
16
+ if (!trimmed) return "";
17
+ if (trimmed.startsWith("${") && trimmed.endsWith("}")) return "";
18
+ return trimmed;
19
+ }
20
+ function unquote(value) {
21
+ const quote = value[0];
22
+ if ((quote === "\"" || quote === "'") && value.length > 1) {
23
+ const close = value.indexOf(quote, 1);
24
+ if (close !== -1) return value.slice(1, close);
25
+ }
26
+ const hash = value.indexOf(" #");
27
+ return hash === -1 ? value : value.slice(0, hash).trim();
28
+ }
29
+ function readEnvFileSecret() {
30
+ let content;
31
+ try {
32
+ content = readFileSync(join(agentmemoryHomeDir(), ".env"), "utf-8");
33
+ } catch {
34
+ return "";
35
+ }
36
+ if (typeof content !== "string") return "";
37
+ let found = "";
38
+ for (const line of content.split("\n")) {
39
+ const trimmed = line.trim();
40
+ if (trimmed.startsWith("#")) continue;
41
+ const eq = trimmed.indexOf("=");
42
+ if (eq === -1) continue;
43
+ if (trimmed.slice(0, eq).replace(/^export\s+/, "").trim() !== SECRET_KEY) continue;
44
+ found = usable(unquote(trimmed.slice(eq + 1).trim()));
45
+ }
46
+ return found;
47
+ }
48
+ function readStoredSecret() {
49
+ try {
50
+ return usable(readFileSync(secretFilePath(), "utf-8"));
51
+ } catch {
52
+ return "";
53
+ }
54
+ }
55
+ function isLoopbackUrl(url) {
56
+ let hostname;
57
+ try {
58
+ hostname = new URL(url).hostname.toLowerCase();
59
+ } catch {
60
+ return false;
61
+ }
62
+ const bare = hostname.replace(/^\[|\]$/g, "");
63
+ return bare === "localhost" || bare === "::1" || /^127(?:\.\d{1,3}){3}$/.test(bare);
64
+ }
65
+ function resolveClientSecret(baseUrl, env = process.env) {
66
+ const fromEnv = usable(env[SECRET_KEY]);
67
+ if (fromEnv) return fromEnv;
68
+ if (!isLoopbackUrl(baseUrl)) return "";
69
+ return readEnvFileSecret() || readStoredSecret();
70
+ }
71
+ //#endregion
2
72
  //#region src/hooks/pre-tool-use.ts
3
73
  function isSdkChildContext(payload) {
4
74
  if (process.env["AGENTMEMORY_SDK_CHILD"] === "1") return true;
@@ -7,7 +77,7 @@ function isSdkChildContext(payload) {
7
77
  }
8
78
  const INJECT_CONTEXT = process.env["AGENTMEMORY_INJECT_CONTEXT"] === "true";
9
79
  const REST_URL = process.env["AGENTMEMORY_URL"] || "http://localhost:3111";
10
- const SECRET = process.env["AGENTMEMORY_SECRET"] || "";
80
+ const SECRET = resolveClientSecret(REST_URL);
11
81
  function authHeaders() {
12
82
  const h = { "Content-Type": "application/json" };
13
83
  if (SECRET) h["Authorization"] = `Bearer ${SECRET}`;
@@ -1 +1 @@
1
- {"version":3,"file":"pre-tool-use.mjs","names":[],"sources":["../../src/hooks/pre-tool-use.ts"],"sourcesContent":["#!/usr/bin/env node\n\nfunction isSdkChildContext(payload: unknown): boolean {\n if (process.env[\"AGENTMEMORY_SDK_CHILD\"] === \"1\") return true;\n if (!payload || typeof payload !== \"object\") return false;\n return (payload as { entrypoint?: unknown }).entrypoint === \"sdk-ts\";\n}\n\n// Pre-tool-use enrichment hook.\n//\n// THIS HOOK IS A NO-OP BY DEFAULT AS OF 0.8.10 (#143). Previously it\n// fired /agentmemory/enrich on every Edit/Write/Read/Glob/Grep tool call\n// and wrote up to 4000 chars of context to stdout. Claude Code reads\n// PreToolUse stdout and prepends it to the model's next turn, which meant\n// agentmemory was silently injecting ~1000 tokens into every tool turn\n// via the user's Claude Code session. On Claude Pro that burned entire\n// allocations in a handful of messages (@adrianricardo, #143).\n//\n// Users who explicitly want pre-tool enrichment opt in with:\n// AGENTMEMORY_INJECT_CONTEXT=true in ~/.agentmemory/.env\n// and restart Claude Code. Expect your session input token count to grow\n// proportionally with the number of file-touching tool calls per turn.\nconst INJECT_CONTEXT = process.env[\"AGENTMEMORY_INJECT_CONTEXT\"] === \"true\";\n\nconst REST_URL = process.env[\"AGENTMEMORY_URL\"] || \"http://localhost:3111\";\nconst SECRET = process.env[\"AGENTMEMORY_SECRET\"] || \"\";\n\nfunction authHeaders(): Record<string, string> {\n const h: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (SECRET) h[\"Authorization\"] = `Bearer ${SECRET}`;\n return h;\n}\n\nasync function main() {\n // Default off: exit immediately so we don't even open stdin. This keeps\n // Claude Code's tool-call hot path as cheap as possible.\n if (!INJECT_CONTEXT) return;\n\n let input = \"\";\n for await (const chunk of process.stdin) {\n input += chunk;\n }\n\n let data: Record<string, unknown>;\n try {\n data = JSON.parse(input);\n } catch {\n return;\n }\n\n if (!data || typeof data !== \"object\") return;\n if (isSdkChildContext(data)) return;\n\n const toolName =\n typeof data.tool_name === \"string\"\n ? data.tool_name\n : typeof data.toolName === \"string\"\n ? data.toolName\n : undefined;\n if (!toolName) return;\n\n const normalizedToolName = toolName.toLowerCase();\n const fileTools = [\"edit\", \"write\", \"create\", \"read\", \"view\", \"glob\", \"grep\"];\n if (!fileTools.includes(normalizedToolName)) return;\n\n const rawToolInput = data.tool_input ?? data.toolArgs;\n const toolInput =\n typeof rawToolInput === \"object\" &&\n rawToolInput !== null &&\n !Array.isArray(rawToolInput)\n ? (rawToolInput as Record<string, unknown>)\n : {};\n const files: string[] = [];\n const fileKeys =\n normalizedToolName === \"grep\"\n ? [\"path\", \"file\"]\n : [\"file_path\", \"path\", \"file\", \"pattern\"];\n for (const key of fileKeys) {\n const val = toolInput[key];\n if (typeof val === \"string\" && val.length > 0) files.push(val);\n }\n if (files.length === 0) return;\n\n const terms: string[] = [];\n if (normalizedToolName === \"grep\" || normalizedToolName === \"glob\") {\n const pattern = toolInput[\"pattern\"];\n if (typeof pattern === \"string\" && pattern.length > 0) {\n terms.push(pattern);\n }\n }\n\n const rawSessionId = data.session_id || data.sessionId || data.conversation_id;\n const sessionId =\n typeof rawSessionId === \"string\" && rawSessionId.length > 0\n ? rawSessionId\n : \"unknown\";\n const project =\n typeof data.project === \"string\" && data.project.trim().length > 0\n ? data.project.trim()\n : undefined;\n\n try {\n const res = await fetch(`${REST_URL}/agentmemory/enrich`, {\n method: \"POST\",\n headers: authHeaders(),\n body: JSON.stringify({\n sessionId,\n files,\n terms,\n toolName,\n ...(project !== undefined && { project }),\n }),\n signal: AbortSignal.timeout(2000),\n });\n\n if (res.ok) {\n const result = (await res.json()) as { context?: string };\n if (result.context) {\n process.stdout.write(result.context);\n }\n }\n } catch {\n // don't block tool execution\n }\n}\n\nmain().catch(() => process.exit(0));\n"],"mappings":";;AAEA,SAAS,kBAAkB,SAA2B;AACpD,KAAI,QAAQ,IAAI,6BAA6B,IAAK,QAAO;AACzD,KAAI,CAAC,WAAW,OAAO,YAAY,SAAU,QAAO;AACpD,QAAQ,QAAqC,eAAe;;AAiB9D,MAAM,iBAAiB,QAAQ,IAAI,kCAAkC;AAErE,MAAM,WAAW,QAAQ,IAAI,sBAAsB;AACnD,MAAM,SAAS,QAAQ,IAAI,yBAAyB;AAEpD,SAAS,cAAsC;CAC7C,MAAM,IAA4B,EAAE,gBAAgB,oBAAoB;AACxE,KAAI,OAAQ,GAAE,mBAAmB,UAAU;AAC3C,QAAO;;AAGT,eAAe,OAAO;AAGpB,KAAI,CAAC,eAAgB;CAErB,IAAI,QAAQ;AACZ,YAAW,MAAM,SAAS,QAAQ,MAChC,UAAS;CAGX,IAAI;AACJ,KAAI;AACF,SAAO,KAAK,MAAM,MAAM;SAClB;AACN;;AAGF,KAAI,CAAC,QAAQ,OAAO,SAAS,SAAU;AACvC,KAAI,kBAAkB,KAAK,CAAE;CAE7B,MAAM,WACJ,OAAO,KAAK,cAAc,WACtB,KAAK,YACL,OAAO,KAAK,aAAa,WACvB,KAAK,WACL,KAAA;AACR,KAAI,CAAC,SAAU;CAEf,MAAM,qBAAqB,SAAS,aAAa;AAEjD,KAAI,CAAC;EADc;EAAQ;EAAS;EAAU;EAAQ;EAAQ;EAAQ;EACxD,CAAC,SAAS,mBAAmB,CAAE;CAE7C,MAAM,eAAe,KAAK,cAAc,KAAK;CAC7C,MAAM,YACJ,OAAO,iBAAiB,YACxB,iBAAiB,QACjB,CAAC,MAAM,QAAQ,aAAa,GACvB,eACD,EAAE;CACR,MAAM,QAAkB,EAAE;CAC1B,MAAM,WACJ,uBAAuB,SACnB,CAAC,QAAQ,OAAO,GAChB;EAAC;EAAa;EAAQ;EAAQ;EAAU;AAC9C,MAAK,MAAM,OAAO,UAAU;EAC1B,MAAM,MAAM,UAAU;AACtB,MAAI,OAAO,QAAQ,YAAY,IAAI,SAAS,EAAG,OAAM,KAAK,IAAI;;AAEhE,KAAI,MAAM,WAAW,EAAG;CAExB,MAAM,QAAkB,EAAE;AAC1B,KAAI,uBAAuB,UAAU,uBAAuB,QAAQ;EAClE,MAAM,UAAU,UAAU;AAC1B,MAAI,OAAO,YAAY,YAAY,QAAQ,SAAS,EAClD,OAAM,KAAK,QAAQ;;CAIvB,MAAM,eAAe,KAAK,cAAc,KAAK,aAAa,KAAK;CAC/D,MAAM,YACJ,OAAO,iBAAiB,YAAY,aAAa,SAAS,IACtD,eACA;CACN,MAAM,UACJ,OAAO,KAAK,YAAY,YAAY,KAAK,QAAQ,MAAM,CAAC,SAAS,IAC7D,KAAK,QAAQ,MAAM,GACnB,KAAA;AAEN,KAAI;EACF,MAAM,MAAM,MAAM,MAAM,GAAG,SAAS,sBAAsB;GACxD,QAAQ;GACR,SAAS,aAAa;GACtB,MAAM,KAAK,UAAU;IACnB;IACA;IACA;IACA;IACA,GAAI,YAAY,KAAA,KAAa,EAAE,SAAS;IACzC,CAAC;GACF,QAAQ,YAAY,QAAQ,IAAK;GAClC,CAAC;AAEF,MAAI,IAAI,IAAI;GACV,MAAM,SAAU,MAAM,IAAI,MAAM;AAChC,OAAI,OAAO,QACT,SAAQ,OAAO,MAAM,OAAO,QAAQ;;SAGlC;;AAKV,MAAM,CAAC,YAAY,QAAQ,KAAK,EAAE,CAAC"}
1
+ {"version":3,"file":"pre-tool-use.mjs","names":[],"sources":["../../src/secret-store.ts","../../src/hooks/pre-tool-use.ts"],"sourcesContent":["import {\n chmodSync,\n closeSync,\n linkSync,\n mkdirSync,\n openSync,\n readFileSync,\n unlinkSync,\n writeSync,\n} from \"node:fs\";\nimport { randomBytes } from \"node:crypto\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nconst SECRET_KEY = \"AGENTMEMORY_SECRET\";\n\nexport function agentmemoryHomeDir(): string {\n return join(homedir(), \".agentmemory\");\n}\n\nexport function secretFilePath(): string {\n return join(agentmemoryHomeDir(), \"secret\");\n}\n\nfunction usable(value: string | undefined): string {\n if (typeof value !== \"string\") return \"\";\n const trimmed = value.trim();\n if (!trimmed) return \"\";\n if (trimmed.startsWith(\"${\") && trimmed.endsWith(\"}\")) return \"\";\n return trimmed;\n}\n\nfunction unquote(value: string): string {\n const quote = value[0];\n if ((quote === '\"' || quote === \"'\") && value.length > 1) {\n const close = value.indexOf(quote, 1);\n if (close !== -1) return value.slice(1, close);\n }\n const hash = value.indexOf(\" #\");\n return hash === -1 ? value : value.slice(0, hash).trim();\n}\n\nexport function readEnvFileSecret(): string {\n let content: unknown;\n try {\n content = readFileSync(join(agentmemoryHomeDir(), \".env\"), \"utf-8\");\n } catch {\n return \"\";\n }\n if (typeof content !== \"string\") return \"\";\n let found = \"\";\n for (const line of content.split(\"\\n\")) {\n const trimmed = line.trim();\n if (trimmed.startsWith(\"#\")) continue;\n const eq = trimmed.indexOf(\"=\");\n if (eq === -1) continue;\n const key = trimmed.slice(0, eq).replace(/^export\\s+/, \"\").trim();\n if (key !== SECRET_KEY) continue;\n found = usable(unquote(trimmed.slice(eq + 1).trim()));\n }\n return found;\n}\n\nexport function readStoredSecret(): string {\n try {\n return usable(readFileSync(secretFilePath(), \"utf-8\"));\n } catch {\n return \"\";\n }\n}\n\nexport function explicitSecret(env: NodeJS.ProcessEnv = process.env): string {\n return usable(env[SECRET_KEY]) || readEnvFileSecret();\n}\n\nexport function isLoopbackUrl(url: string): boolean {\n let hostname: string;\n try {\n hostname = new URL(url).hostname.toLowerCase();\n } catch {\n return false;\n }\n const bare = hostname.replace(/^\\[|\\]$/g, \"\");\n return bare === \"localhost\" || bare === \"::1\" || /^127(?:\\.\\d{1,3}){3}$/.test(bare);\n}\n\nexport function resolveClientSecret(\n baseUrl: string,\n env: NodeJS.ProcessEnv = process.env,\n): string {\n const fromEnv = usable(env[SECRET_KEY]);\n if (fromEnv) return fromEnv;\n if (!isLoopbackUrl(baseUrl)) return \"\";\n return readEnvFileSecret() || readStoredSecret();\n}\n\nexport function ensureServerSecret(env: NodeJS.ProcessEnv = process.env): {\n secret: string;\n source: \"env\" | \"file\" | \"generated\";\n} {\n const configured = explicitSecret(env);\n if (configured) return { secret: configured, source: \"env\" };\n const stored = readStoredSecret();\n if (stored) return { secret: stored, source: \"file\" };\n const generated = randomBytes(32).toString(\"hex\");\n const path = secretFilePath();\n mkdirSync(agentmemoryHomeDir(), { recursive: true, mode: 0o700 });\n const staging = `${path}.${process.pid}.${randomBytes(4).toString(\"hex\")}.tmp`;\n const fd = openSync(staging, \"wx\", 0o600);\n try {\n writeSync(fd, `${generated}\\n`);\n } finally {\n closeSync(fd);\n }\n try {\n chmodSync(staging, 0o600);\n linkSync(staging, path);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"EEXIST\") throw err;\n const raced = readStoredSecret();\n if (!raced) throw err;\n return { secret: raced, source: \"file\" };\n } finally {\n try {\n unlinkSync(staging);\n } catch {}\n }\n return { secret: generated, source: \"generated\" };\n}\n\nexport function bearerHeaders(\n baseUrl: string,\n env: NodeJS.ProcessEnv = process.env,\n): Record<string, string> {\n const secret = resolveClientSecret(baseUrl, env);\n return secret ? { Authorization: `Bearer ${secret}` } : {};\n}\n","#!/usr/bin/env node\nimport { resolveClientSecret } from \"../secret-store.js\";\n\nfunction isSdkChildContext(payload: unknown): boolean {\n if (process.env[\"AGENTMEMORY_SDK_CHILD\"] === \"1\") return true;\n if (!payload || typeof payload !== \"object\") return false;\n return (payload as { entrypoint?: unknown }).entrypoint === \"sdk-ts\";\n}\n\nconst INJECT_CONTEXT = process.env[\"AGENTMEMORY_INJECT_CONTEXT\"] === \"true\";\n\nconst REST_URL = process.env[\"AGENTMEMORY_URL\"] || \"http://localhost:3111\";\nconst SECRET = resolveClientSecret(REST_URL);\n\nfunction authHeaders(): Record<string, string> {\n const h: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (SECRET) h[\"Authorization\"] = `Bearer ${SECRET}`;\n return h;\n}\n\nasync function main() {\n // Default off: exit immediately so we don't even open stdin. This keeps\n // Claude Code's tool-call hot path as cheap as possible.\n if (!INJECT_CONTEXT) return;\n\n let input = \"\";\n for await (const chunk of process.stdin) {\n input += chunk;\n }\n\n let data: Record<string, unknown>;\n try {\n data = JSON.parse(input);\n } catch {\n return;\n }\n\n if (!data || typeof data !== \"object\") return;\n if (isSdkChildContext(data)) return;\n\n const toolName =\n typeof data.tool_name === \"string\"\n ? data.tool_name\n : typeof data.toolName === \"string\"\n ? data.toolName\n : undefined;\n if (!toolName) return;\n\n const normalizedToolName = toolName.toLowerCase();\n const fileTools = [\"edit\", \"write\", \"create\", \"read\", \"view\", \"glob\", \"grep\"];\n if (!fileTools.includes(normalizedToolName)) return;\n\n const rawToolInput = data.tool_input ?? data.toolArgs;\n const toolInput =\n typeof rawToolInput === \"object\" &&\n rawToolInput !== null &&\n !Array.isArray(rawToolInput)\n ? (rawToolInput as Record<string, unknown>)\n : {};\n const files: string[] = [];\n const fileKeys =\n normalizedToolName === \"grep\"\n ? [\"path\", \"file\"]\n : [\"file_path\", \"path\", \"file\", \"pattern\"];\n for (const key of fileKeys) {\n const val = toolInput[key];\n if (typeof val === \"string\" && val.length > 0) files.push(val);\n }\n if (files.length === 0) return;\n\n const terms: string[] = [];\n if (normalizedToolName === \"grep\" || normalizedToolName === \"glob\") {\n const pattern = toolInput[\"pattern\"];\n if (typeof pattern === \"string\" && pattern.length > 0) {\n terms.push(pattern);\n }\n }\n\n const rawSessionId = data.session_id || data.sessionId || data.conversation_id;\n const sessionId =\n typeof rawSessionId === \"string\" && rawSessionId.length > 0\n ? rawSessionId\n : \"unknown\";\n const project =\n typeof data.project === \"string\" && data.project.trim().length > 0\n ? data.project.trim()\n : undefined;\n\n try {\n const res = await fetch(`${REST_URL}/agentmemory/enrich`, {\n method: \"POST\",\n headers: authHeaders(),\n body: JSON.stringify({\n sessionId,\n files,\n terms,\n toolName,\n ...(project !== undefined && { project }),\n }),\n signal: AbortSignal.timeout(2000),\n });\n\n if (res.ok) {\n const result = (await res.json()) as { context?: string };\n if (result.context) {\n process.stdout.write(result.context);\n }\n }\n } catch {\n // don't block tool execution\n }\n}\n\nmain().catch(() => process.exit(0));\n"],"mappings":"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"}