@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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_capture.mjs","names":[],"sources":["../../src/secret-store.ts","../../src/capture/event-id.ts","../../src/cli-data-dir.ts","../../src/functions/privacy.ts","../../src/capture/spool.ts","../../src/hooks/_capture.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","import { createHash } from \"node:crypto\";\n\nexport const EVENT_ID_RE = /^[A-Za-z0-9][A-Za-z0-9_.:-]{7,127}$/;\n\nconst HOST_ID_FIELDS = [\n \"event_id\",\n \"eventId\",\n \"tool_use_id\",\n \"toolUseId\",\n \"tool_call_id\",\n \"toolCallId\",\n \"call_id\",\n \"callId\",\n \"prompt_id\",\n \"promptId\",\n \"message_id\",\n \"messageId\",\n];\n\nconst HOST_TIME_FIELDS = [\"timestamp\", \"ts\", \"created_at\", \"createdAt\", \"event_time\"];\n\nexport function isValidEventId(value: unknown): value is string {\n return typeof value === \"string\" && EVENT_ID_RE.test(value);\n}\n\nexport function stableStringify(value: unknown): string {\n if (value === undefined) return \"null\";\n if (value === null || typeof value !== \"object\") {\n const encoded = JSON.stringify(value);\n return encoded === undefined ? \"null\" : encoded;\n }\n if (Array.isArray(value)) return `[${value.map(stableStringify).join(\",\")}]`;\n const obj = value as Record<string, unknown>;\n const keys = Object.keys(obj)\n .filter((k) => obj[k] !== undefined)\n .sort();\n return `{${keys.map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`).join(\",\")}}`;\n}\n\nfunction hostScalar(host: Record<string, unknown>, fields: string[]): string | undefined {\n for (const field of fields) {\n const v = host[field];\n if (typeof v === \"string\" && v.trim()) return v.trim();\n if (typeof v === \"number\" && Number.isFinite(v)) return String(v);\n }\n return undefined;\n}\n\nfunction digest(parts: string[]): string {\n return createHash(\"sha256\").update(parts.join(\"\\u0000\")).digest(\"hex\").slice(0, 32);\n}\n\nexport function hasHostIdentity(host: Record<string, unknown> | null | undefined): boolean {\n const source = host && typeof host === \"object\" ? host : {};\n return hostScalar(source, HOST_ID_FIELDS) !== undefined || hostScalar(source, HOST_TIME_FIELDS) !== undefined;\n}\n\nexport function deriveEventId(\n hookType: string,\n sessionId: string,\n host: Record<string, unknown> | null | undefined,\n content: unknown,\n): string {\n const source = host && typeof host === \"object\" ? host : {};\n const hostId = hostScalar(source, HOST_ID_FIELDS);\n if (hostId) return `evh_${digest([\"host\", sessionId, hookType, hostId])}`;\n const hostTime = hostScalar(source, HOST_TIME_FIELDS) ?? \"\";\n return `evc_${digest([\"content\", sessionId, hookType, hostTime, stableStringify(content)])}`;\n}\n","import { existsSync } from \"node:fs\";\nimport { dirname, isAbsolute, join, relative, resolve, sep } from \"node:path\";\nimport { homedir, platform } from \"node:os\";\n\nexport type DataDirSource = \"flag\" | \"env\" | \"default\";\n\nexport type ResolvedDataDir = {\n dataDir: string;\n source: DataDirSource;\n relocatedFrom?: string;\n};\n\ntype ResolveDataDirOptions = {\n args?: string[];\n env?: NodeJS.ProcessEnv;\n cwd?: string;\n home?: string;\n platform?: NodeJS.Platform;\n};\n\nfunction argValue(args: string[], name: string): string | undefined {\n const equals = `${name}=`;\n const inline = args.find((arg) => arg.startsWith(equals));\n if (inline) return inline.slice(equals.length);\n\n const idx = args.indexOf(name);\n if (idx !== -1) return args[idx + 1];\n return undefined;\n}\n\nfunction expandHome(pathValue: string, home: string): string {\n if (pathValue === \"~\") return home;\n if (pathValue.startsWith(\"~/\") || pathValue.startsWith(\"~\\\\\")) {\n return join(home, pathValue.slice(2));\n }\n return pathValue;\n}\n\nfunction absoluteDataDir(pathValue: string, cwd: string, home: string): string {\n const expanded = expandHome(pathValue, home);\n return isAbsolute(expanded) ? expanded : resolve(cwd, expanded);\n}\n\nfunction platformDefaultDataDir(\n env: NodeJS.ProcessEnv,\n home: string,\n nodePlatform: NodeJS.Platform,\n useXdg = true,\n): string {\n if (nodePlatform === \"darwin\") {\n return join(home, \"Library\", \"Application Support\", \"agentmemory\");\n }\n if (nodePlatform === \"win32\") {\n const appData = env[\"APPDATA\"];\n return appData\n ? join(appData, \"agentmemory\")\n : join(home, \".agentmemory\");\n }\n const xdgDataHome = env[\"XDG_DATA_HOME\"];\n if (useXdg && xdgDataHome && isAbsolute(xdgDataHome)) {\n return join(xdgDataHome, \"agentmemory\");\n }\n return join(home, \".local\", \"share\", \"agentmemory\");\n}\n\nfunction nearestGitParent(pathValue: string): string | undefined {\n let current = pathValue;\n while (true) {\n if (existsSync(join(current, \".git\"))) return current;\n const parent = dirname(current);\n if (parent === current) return undefined;\n current = parent;\n }\n}\n\nfunction isWithin(parent: string, child: string): boolean {\n const pathFromParent = relative(parent, child);\n return (\n pathFromParent === \"\" ||\n (pathFromParent !== \"..\" &&\n !pathFromParent.startsWith(`..${sep}`) &&\n !isAbsolute(pathFromParent))\n );\n}\n\nexport function resolveDataDir(options: ResolveDataDirOptions = {}): ResolvedDataDir {\n const args = options.args ?? process.argv.slice(2);\n const env = options.env ?? process.env;\n const cwd = options.cwd ?? process.cwd();\n const home = options.home ?? homedir();\n const nodePlatform = options.platform ?? platform();\n const parsedInstance = parseInt(argValue(args, \"--instance\") ?? \"0\", 10);\n const instance =\n parsedInstance > 0 && parsedInstance <= 50 ? parsedInstance : 0;\n const forInstance = (dataDir: string) =>\n instance ? join(dataDir, `instance-${instance}`) : dataDir;\n\n const flagValue = argValue(args, \"--data-dir\");\n if (flagValue) {\n return {\n dataDir: forInstance(absoluteDataDir(flagValue, cwd, home)),\n source: \"flag\",\n };\n }\n\n const envValue = env[\"AGENTMEMORY_DATA_DIR\"];\n if (envValue) {\n return {\n dataDir: forInstance(absoluteDataDir(envValue, cwd, home)),\n source: \"env\",\n };\n }\n\n const legacyDir = resolve(cwd, \"data\");\n const hasLegacyStore =\n existsSync(join(legacyDir, \"state_store.db\")) ||\n existsSync(join(legacyDir, \"iii-config.yaml\"));\n if (instance === 0 && hasLegacyStore) {\n return { dataDir: legacyDir, source: \"default\" };\n }\n\n const defaultDir = platformDefaultDataDir(env, home, nodePlatform);\n const gitParent = nearestGitParent(cwd);\n if (gitParent && isWithin(gitParent, defaultDir)) {\n const relocated = platformDefaultDataDir(env, home, nodePlatform, false);\n if (relocated !== defaultDir) {\n return {\n dataDir: forInstance(relocated),\n source: \"default\",\n relocatedFrom: defaultDir,\n };\n }\n }\n\n return {\n dataDir: forInstance(defaultDir),\n source: \"default\",\n };\n}\n","import type { IIIClient } from \"iii-sdk\";\n\nconst PRIVATE_TAG_RE = /<private>[\\s\\S]*?<\\/private>/gi;\n\nconst SECRET_PATTERN_SOURCES = [\n /(?:api[_-]?key|secret|token|password|credential|auth)[\\s]*[=:]\\s*[\"']?[A-Za-z0-9_\\-/.+]{20,}[\"']?/gi,\n /Bearer\\s+[A-Za-z0-9._\\-+/=]{20,}/gi,\n /sk-proj-[A-Za-z0-9\\-_]{20,}/g,\n /(?:sk|pk|rk|ak)-[A-Za-z0-9][A-Za-z0-9\\-_]{19,}/g,\n /sk-ant-[A-Za-z0-9\\-_]{20,}/g,\n /gh[pus]_[A-Za-z0-9]{36,}/g,\n /github_pat_[A-Za-z0-9_]{22,}/g,\n /xoxb-[A-Za-z0-9\\-]+/g,\n /AKIA[0-9A-Z]{16}/g,\n /AIza[A-Za-z0-9\\-_]{35}/g,\n /eyJ[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]{10,}/g,\n /npm_[A-Za-z0-9]{36}/g,\n /glpat-[A-Za-z0-9\\-_]{20,}/g,\n /dop_v1_[A-Za-z0-9]{64}/g,\n];\n\nconst PRIVATE_KEY_BLOCK_RE =\n /-----BEGIN [A-Z0-9 ]*PRIVATE KEY(?: BLOCK)?-----[\\s\\S]*?(?:-----END [A-Z0-9 ]*PRIVATE KEY(?: BLOCK)?-----|$)/g;\n\nconst URL_CREDENTIALS_RE =\n /\\b([a-z][a-z0-9+.-]*:\\/\\/)[^\\s/?#@:\"'<>]*:[^\\s/?#@\"'<>]+@/gi;\n\nexport function stripPrivateData(input: string): string {\n let result = input.replace(PRIVATE_TAG_RE, \"[REDACTED]\");\n result = result.replace(new RegExp(PRIVATE_KEY_BLOCK_RE.source, PRIVATE_KEY_BLOCK_RE.flags), \"[REDACTED_SECRET]\");\n result = result.replace(new RegExp(URL_CREDENTIALS_RE.source, URL_CREDENTIALS_RE.flags), \"$1[REDACTED_SECRET]@\");\n for (const source of SECRET_PATTERN_SOURCES) {\n const pattern = new RegExp(source.source, source.flags);\n result = result.replace(pattern, \"[REDACTED_SECRET]\");\n }\n return result;\n}\n\nexport function scrubRecord<T>(value: T): T {\n if (typeof value === \"string\") return stripPrivateData(value) as T;\n if (Array.isArray(value)) return value.map((item) => scrubRecord(item)) as T;\n if (value !== null && typeof value === \"object\") {\n const proto = Object.getPrototypeOf(value);\n if (proto !== Object.prototype && proto !== null) return value;\n const out: Record<string, unknown> = {};\n for (const [key, item] of Object.entries(value as Record<string, unknown>)) {\n out[key] = scrubRecord(item);\n }\n return out as T;\n }\n return value;\n}\n\nexport const SCRUBBED_WRITE_FUNCTIONS: ReadonlySet<string> = new Set([\n \"mem::remember\",\n \"mem::evolve\",\n \"mem::slot-create\",\n \"mem::slot-append\",\n \"mem::slot-replace\",\n \"mem::lesson-save\",\n \"mem::action-create\",\n \"mem::action-update\",\n \"mem::sketch-create\",\n \"mem::sketch-add\",\n \"mem::import\",\n \"mem::mesh-receive\",\n \"mem::team-share\",\n \"mem::signal-send\",\n \"mem::routine-create\",\n \"mem::checkpoint-create\",\n \"mem::checkpoint-resolve\",\n \"mem::sentinel-create\",\n \"mem::sentinel-trigger\",\n \"mem::core-add\",\n \"mem::facet-tag\",\n]);\n\ntype RegisterFunction = (id: string, handler: unknown, ...rest: unknown[]) => unknown;\n\nexport function withWriteScrubbing<T extends object>(sdk: T): T {\n return new Proxy(sdk, {\n get(target, prop) {\n const value = Reflect.get(target, prop, target);\n if (typeof value !== \"function\") return value;\n if (prop !== \"registerFunction\") return value.bind(target);\n const register = value as RegisterFunction;\n return (id: string, handler: unknown, ...rest: unknown[]) => {\n if (!SCRUBBED_WRITE_FUNCTIONS.has(id) || typeof handler !== \"function\") {\n return register.call(target, id, handler, ...rest);\n }\n const original = handler as (data: unknown, ...args: unknown[]) => unknown;\n const scrubbed = (data: unknown, ...args: unknown[]) => original(scrubRecord(data), ...args);\n return register.call(target, id, scrubbed, ...rest);\n };\n },\n });\n}\n\nexport function registerPrivacyFunction(sdk: IIIClient): void {\n sdk.registerFunction(\"mem::privacy\", \n async (data: { input?: unknown } | undefined) => {\n if (!data || typeof data.input !== \"string\") {\n return { output: \"\", error: \"invalid input: expected string field 'input'\" };\n }\n return { output: stripPrivateData(data.input) };\n },\n );\n}\n","import {\n chmodSync,\n closeSync,\n existsSync,\n fsyncSync,\n mkdirSync,\n openSync,\n readFileSync,\n readdirSync,\n renameSync,\n statSync,\n unlinkSync,\n utimesSync,\n writeFileSync,\n writeSync,\n} from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport { resolveDataDir } from \"../cli-data-dir.js\";\nimport { stripPrivateData } from \"../functions/privacy.js\";\n\nexport interface SpoolRecord {\n v: 1;\n eventId: string;\n spooledAt: string;\n reason: string;\n attempts: number;\n body: Record<string, unknown>;\n bootId?: string;\n}\n\nexport interface SentMark {\n bootId: string;\n durableAfterMs: number;\n}\n\nexport interface SentReconcile {\n released: number;\n requeued: number;\n}\n\nexport interface SpoolPolicy {\n enabled: boolean;\n maxBytes: number;\n maxAgeMs: number;\n maxRecordBytes: number;\n}\n\nexport interface SpoolStats {\n spooled: number;\n dropped: number;\n droppedBytes: number;\n expired: number;\n delivered: number;\n duplicates: number;\n rejected: number;\n lastSpooledAt?: string;\n lastSpoolReason?: string;\n lastDropAt?: string;\n lastDropReason?: string;\n lastDrainAt?: string;\n lastDrainDelivered?: number;\n lastDrainDuplicates?: number;\n lastDrainRejected?: number;\n lastDrainRemaining?: number;\n lastDrainError?: string;\n}\n\nexport interface SpoolPaths {\n dir: string;\n name: string;\n file: string;\n lock: string;\n drainLock: string;\n stats: string;\n}\n\nexport interface SpoolSummary {\n enabled: boolean;\n path: string;\n records: number;\n bytes: number;\n oldestAt: string | null;\n retained: number;\n retainedBytes: number;\n maxBytes: number;\n maxAgeHours: number;\n stats: SpoolStats;\n}\n\nexport type SendOutcome = \"delivered\" | \"duplicate\" | \"rejected\" | \"retry\";\n\nexport interface DrainResult {\n claimed: number;\n delivered: number;\n duplicates: number;\n rejected: number;\n expired: number;\n corrupt: number;\n remaining: number;\n error?: string;\n skipped?: \"locked\" | \"empty\" | \"disabled\";\n}\n\nconst DEFAULT_MAX_BYTES = 5 * 1024 * 1024;\nconst MIN_MAX_BYTES = 64 * 1024;\nconst DEFAULT_MAX_AGE_HOURS = 168;\nconst LOCK_STALE_MS = 10_000;\nconst DRAIN_LOCK_STALE_MS = 120_000;\nconst ORPHAN_CLAIM_MS = 120_000;\nconst LOCK_WAIT_MS = 500;\nconst IMAGE_PLACEHOLDER = \"[image dropped from capture spool]\";\nconst BOOT_ID_RE = /^[A-Za-z0-9]{8,64}$/;\nconst SENT_FILE_RE = /^([A-Za-z0-9]{8,64})-(\\d+)\\.jsonl$/;\nconst SENT_BUCKET_MS = 1000;\nconst MAX_DURABLE_AFTER_MS = 10 * 60_000;\n\nfunction emptyStats(): SpoolStats {\n return { spooled: 0, dropped: 0, droppedBytes: 0, expired: 0, delivered: 0, duplicates: 0, rejected: 0 };\n}\n\nfunction positiveInt(raw: string | undefined, fallback: number): number {\n if (!raw || !/^\\d+$/.test(raw.trim())) return fallback;\n const n = parseInt(raw.trim(), 10);\n return n > 0 ? n : fallback;\n}\n\nexport function spoolPolicy(env: NodeJS.ProcessEnv = process.env): SpoolPolicy {\n const maxBytes = Math.max(MIN_MAX_BYTES, positiveInt(env[\"AGENTMEMORY_CAPTURE_SPOOL_MAX_BYTES\"], DEFAULT_MAX_BYTES));\n const maxAgeHours = positiveInt(env[\"AGENTMEMORY_CAPTURE_SPOOL_MAX_AGE_HOURS\"], DEFAULT_MAX_AGE_HOURS);\n return {\n enabled: env[\"AGENTMEMORY_CAPTURE_SPOOL\"] !== \"false\",\n maxBytes,\n maxAgeMs: maxAgeHours * 3600_000,\n maxRecordBytes: Math.min(256 * 1024, Math.floor(maxBytes / 4)),\n };\n}\n\nexport function spoolDir(env: NodeJS.ProcessEnv = process.env): string {\n const explicit = env[\"AGENTMEMORY_CAPTURE_SPOOL_DIR\"];\n if (explicit && explicit.trim()) return resolve(explicit.trim());\n return join(resolveDataDir({ args: [], env }).dataDir, \"capture-spool\");\n}\n\nconst LOOPBACK = new Set([\"localhost\", \"127.0.0.1\", \"::1\", \"[::1]\", \"0.0.0.0\"]);\n\nexport function spoolTargetName(url: string): string {\n try {\n const u = new URL(url);\n const host = LOOPBACK.has(u.hostname) ? \"local\" : u.hostname.toLowerCase().replace(/[^a-z0-9.-]/g, \"_\");\n const port = u.port || (u.protocol === \"https:\" ? \"443\" : \"80\");\n return `${host}-${port}`;\n } catch {\n return \"local-3111\";\n }\n}\n\nexport function spoolPaths(url: string, dir: string = spoolDir()): SpoolPaths {\n const name = spoolTargetName(url);\n return {\n dir,\n name,\n file: join(dir, `${name}.jsonl`),\n lock: join(dir, `${name}.lock`),\n drainLock: join(dir, `${name}.drain.lock`),\n stats: join(dir, `${name}.stats.json`),\n };\n}\n\nfunction ensureDir(dir: string): void {\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n try {\n chmodSync(dir, 0o700);\n } catch {}\n}\n\nfunction sleepSync(ms: number): void {\n Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);\n}\n\nfunction tryLock(path: string, staleMs: number): boolean {\n try {\n const fd = openSync(path, \"wx\", 0o600);\n writeSync(fd, `${process.pid}\\n`);\n closeSync(fd);\n return true;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"EEXIST\") return false;\n try {\n if (Date.now() - statSync(path).mtimeMs > staleMs) unlinkSync(path);\n } catch {}\n return false;\n }\n}\n\nfunction releaseLock(path: string): void {\n try {\n unlinkSync(path);\n } catch {}\n}\n\nfunction withLock<T>(path: string, fn: (locked: boolean) => T, waitMs = LOCK_WAIT_MS): T {\n const deadline = Date.now() + waitMs;\n let locked = tryLock(path, LOCK_STALE_MS);\n while (!locked && Date.now() < deadline) {\n sleepSync(5);\n locked = tryLock(path, LOCK_STALE_MS);\n }\n try {\n return fn(locked);\n } finally {\n if (locked) releaseLock(path);\n }\n}\n\nexport function readSpoolStats(paths: SpoolPaths): SpoolStats {\n try {\n const parsed = JSON.parse(readFileSync(paths.stats, \"utf-8\")) as Partial<SpoolStats>;\n return { ...emptyStats(), ...parsed };\n } catch {\n return emptyStats();\n }\n}\n\nfunction writeStats(paths: SpoolPaths, patch: (stats: SpoolStats) => void): void {\n try {\n const stats = readSpoolStats(paths);\n patch(stats);\n const tmp = `${paths.stats}.${process.pid}.tmp`;\n writeFileSync(tmp, JSON.stringify(stats), { mode: 0o600 });\n renameSync(tmp, paths.stats);\n } catch {}\n}\n\nfunction fileSize(path: string): number {\n try {\n return statSync(path).size;\n } catch {\n return 0;\n }\n}\n\nfunction sanitizeBody(body: Record<string, unknown>): Record<string, unknown> {\n try {\n return JSON.parse(stripPrivateData(JSON.stringify(body))) as Record<string, unknown>;\n } catch {\n return body;\n }\n}\n\nfunction withoutImage(body: Record<string, unknown>): Record<string, unknown> {\n const data = body[\"data\"];\n if (!data || typeof data !== \"object\" || Array.isArray(data)) return body;\n const d = data as Record<string, unknown>;\n if (d[\"image_data\"] === undefined) return body;\n return { ...body, data: { ...d, image_data: IMAGE_PLACEHOLDER } };\n}\n\nfunction parseLines(text: string): { records: SpoolRecord[]; corrupt: number } {\n const records: SpoolRecord[] = [];\n let corrupt = 0;\n for (const line of text.split(\"\\n\")) {\n if (!line.trim()) continue;\n try {\n const rec = JSON.parse(line) as SpoolRecord;\n if (rec && typeof rec.eventId === \"string\" && rec.body && typeof rec.body === \"object\") records.push(rec);\n else corrupt++;\n } catch {\n corrupt++;\n }\n }\n return { records, corrupt };\n}\n\nfunction isExpired(rec: SpoolRecord, policy: SpoolPolicy, now: number): boolean {\n const at = Date.parse(rec.spooledAt);\n return Number.isFinite(at) && now - at > policy.maxAgeMs;\n}\n\nfunction serialize(records: SpoolRecord[]): string {\n return records.map((r) => JSON.stringify(r)).join(\"\\n\") + (records.length ? \"\\n\" : \"\");\n}\n\nfunction rewriteLocked(paths: SpoolPaths, records: SpoolRecord[]): void {\n const tmp = `${paths.file}.${process.pid}.tmp`;\n const fd = openSync(tmp, \"w\", 0o600);\n try {\n writeSync(fd, serialize(records));\n fsyncSync(fd);\n } finally {\n closeSync(fd);\n }\n renameSync(tmp, paths.file);\n}\n\nfunction pruneExpiredLocked(paths: SpoolPaths, policy: SpoolPolicy): number {\n if (!existsSync(paths.file)) return 0;\n const { records } = parseLines(readFileSync(paths.file, \"utf-8\"));\n const now = Date.now();\n const kept = records.filter((r) => !isExpired(r, policy, now));\n const expired = records.length - kept.length;\n if (expired > 0) rewriteLocked(paths, kept);\n return expired;\n}\n\nexport interface AppendResult {\n spooled: boolean;\n dropped?: \"disabled\" | \"too-large\" | \"full\" | \"error\";\n}\n\nexport function appendSpool(\n url: string,\n eventId: string,\n body: Record<string, unknown>,\n reason: string,\n options: { policy?: SpoolPolicy; dir?: string; attempts?: number } = {},\n): AppendResult {\n const policy = options.policy ?? spoolPolicy();\n if (!policy.enabled) return { spooled: false, dropped: \"disabled\" };\n const paths = spoolPaths(url, options.dir);\n const note = (dropReason: NonNullable<AppendResult[\"dropped\"]>, bytes: number) => {\n writeStats(paths, (s) => {\n s.dropped++;\n s.droppedBytes += bytes;\n s.lastDropAt = new Date().toISOString();\n s.lastDropReason = dropReason;\n });\n return { spooled: false, dropped: dropReason } as AppendResult;\n };\n try {\n ensureDir(paths.dir);\n const record: SpoolRecord = {\n v: 1,\n eventId,\n spooledAt: new Date().toISOString(),\n reason,\n attempts: options.attempts ?? 0,\n body: sanitizeBody(body),\n };\n let line = JSON.stringify(record) + \"\\n\";\n if (Buffer.byteLength(line) > policy.maxRecordBytes) {\n record.body = withoutImage(record.body);\n line = JSON.stringify(record) + \"\\n\";\n }\n const bytes = Buffer.byteLength(line);\n if (bytes > policy.maxRecordBytes) return note(\"too-large\", bytes);\n return withLock(paths.lock, (locked) => {\n let expired = 0;\n if (locked && fileSize(paths.file) + bytes > policy.maxBytes) expired = pruneExpiredLocked(paths, policy);\n const over = fileSize(paths.file) + sentBytes(paths) + bytes - policy.maxBytes;\n if (locked && over > 0) evictSent(paths, over);\n if (fileSize(paths.file) + sentBytes(paths) + bytes > policy.maxBytes) {\n if (expired) writeStats(paths, (s) => void (s.expired += expired));\n return note(\"full\", bytes);\n }\n const fd = openSync(paths.file, \"a\", 0o600);\n try {\n writeSync(fd, line);\n fsyncSync(fd);\n } finally {\n closeSync(fd);\n }\n writeStats(paths, (s) => {\n s.spooled++;\n s.expired += expired;\n s.lastSpooledAt = record.spooledAt;\n s.lastSpoolReason = reason;\n });\n return { spooled: true } as AppendResult;\n });\n } catch {\n return { spooled: false, dropped: \"error\" };\n }\n}\n\ninterface SentSegment {\n file: string;\n bootId: string;\n dueAt: number;\n bytes: number;\n}\n\nfunction listSent(paths: SpoolPaths): SentSegment[] {\n const segments: SentSegment[] = [];\n let entries: string[];\n try {\n entries = readdirSync(paths.dir);\n } catch {\n return segments;\n }\n const prefix = `${paths.name}.sent-`;\n for (const entry of entries) {\n if (!entry.startsWith(prefix)) continue;\n const match = entry.slice(prefix.length).match(SENT_FILE_RE);\n if (!match) continue;\n const file = join(paths.dir, entry);\n segments.push({ file, bootId: match[1]!, dueAt: Number(match[2]), bytes: fileSize(file) });\n }\n return segments;\n}\n\nfunction sentBytes(paths: SpoolPaths): number {\n return listSent(paths).reduce((n, s) => n + s.bytes, 0);\n}\n\nfunction evictSent(paths: SpoolPaths, need: number): void {\n let freed = 0;\n for (const seg of listSent(paths).sort((a, b) => a.dueAt - b.dueAt)) {\n if (freed >= need) return;\n try {\n unlinkSync(seg.file);\n freed += seg.bytes;\n } catch {}\n }\n}\n\nexport function parseSentMark(body: unknown): SentMark | null {\n if (!body || typeof body !== \"object\" || Array.isArray(body)) return null;\n const bootId = (body as Record<string, unknown>)[\"bootId\"];\n const after = (body as Record<string, unknown>)[\"durableAfterMs\"];\n if (typeof bootId !== \"string\" || !BOOT_ID_RE.test(bootId)) return null;\n if (typeof after !== \"number\" || !Number.isFinite(after) || after < 0) return null;\n return { bootId, durableAfterMs: Math.min(Math.ceil(after), MAX_DURABLE_AFTER_MS) };\n}\n\nexport function retainSent(\n url: string,\n eventId: string,\n body: Record<string, unknown>,\n mark: SentMark,\n options: { policy?: SpoolPolicy; dir?: string; now?: number } = {},\n): boolean {\n const policy = options.policy ?? spoolPolicy();\n if (!policy.enabled || !BOOT_ID_RE.test(mark.bootId)) return false;\n const paths = spoolPaths(url, options.dir);\n const now = options.now ?? Date.now();\n try {\n ensureDir(paths.dir);\n const record: SpoolRecord = {\n v: 1,\n eventId,\n spooledAt: new Date(now).toISOString(),\n reason: \"sent\",\n attempts: 0,\n body: sanitizeBody(body),\n bootId: mark.bootId,\n };\n let line = JSON.stringify(record) + \"\\n\";\n if (Buffer.byteLength(line) > policy.maxRecordBytes) {\n record.body = withoutImage(record.body);\n line = JSON.stringify(record) + \"\\n\";\n }\n const bytes = Buffer.byteLength(line);\n if (bytes > policy.maxRecordBytes) return false;\n const dueAt = Math.ceil((now + mark.durableAfterMs) / SENT_BUCKET_MS) * SENT_BUCKET_MS;\n const file = join(paths.dir, `${paths.name}.sent-${mark.bootId}-${dueAt}.jsonl`);\n return withLock(paths.lock, () => {\n if (fileSize(paths.file) + sentBytes(paths) + bytes > policy.maxBytes) return false;\n const fd = openSync(file, \"a\", 0o600);\n try {\n writeSync(fd, line);\n } finally {\n closeSync(fd);\n }\n return true;\n });\n } catch {\n return false;\n }\n}\n\nexport function reconcileSent(\n url: string,\n bootId: string,\n options: { policy?: SpoolPolicy; dir?: string; now?: number } = {},\n): SentReconcile {\n const result: SentReconcile = { released: 0, requeued: 0 };\n const policy = options.policy ?? spoolPolicy();\n if (!policy.enabled) return result;\n const paths = spoolPaths(url, options.dir);\n const now = options.now ?? Date.now();\n for (const seg of listSent(paths)) {\n if (seg.bootId === bootId) {\n if (seg.dueAt > now) continue;\n try {\n unlinkSync(seg.file);\n result.released++;\n } catch {}\n continue;\n }\n const target = join(paths.dir, `${paths.name}.draining-${process.pid}-${now}-${result.requeued}.jsonl`);\n try {\n renameSync(seg.file, target);\n } catch {\n continue;\n }\n try {\n utimesSync(target, 0, 0);\n } catch {}\n result.requeued++;\n }\n return result;\n}\n\nfunction claimFiles(paths: SpoolPaths): string[] {\n const claimed: string[] = [];\n try {\n const prefix = `${paths.name}.draining-`;\n for (const entry of readdirSync(paths.dir)) {\n if (!entry.startsWith(prefix) || !entry.endsWith(\".jsonl\")) continue;\n const full = join(paths.dir, entry);\n try {\n if (Date.now() - statSync(full).mtimeMs > ORPHAN_CLAIM_MS) claimed.push(full);\n } catch {}\n }\n } catch {}\n if (fileSize(paths.file) > 0) {\n const target = join(paths.dir, `${paths.name}.draining-${process.pid}-${Date.now()}.jsonl`);\n withLock(paths.lock, (locked) => {\n if (!locked) return;\n try {\n renameSync(paths.file, target);\n claimed.push(target);\n } catch {}\n });\n }\n return claimed;\n}\n\nfunction requeueLocked(paths: SpoolPaths, leftover: SpoolRecord[]): void {\n if (leftover.length === 0) return;\n const existing = existsSync(paths.file) ? parseLines(readFileSync(paths.file, \"utf-8\")).records : [];\n rewriteLocked(paths, [...leftover, ...existing]);\n}\n\nfunction requeueUnlocked(paths: SpoolPaths, leftover: SpoolRecord[]): void {\n if (leftover.length === 0) return;\n const target = join(paths.dir, `${paths.name}.draining-${process.pid}-${Date.now()}-requeue.jsonl`);\n const fd = openSync(target, \"wx\", 0o600);\n try {\n writeSync(fd, serialize(leftover));\n fsyncSync(fd);\n } finally {\n closeSync(fd);\n }\n try {\n utimesSync(target, 0, 0);\n } catch {}\n}\n\nexport function spoolHasRecords(url: string, dir?: string): boolean {\n return fileSize(spoolPaths(url, dir).file) > 0;\n}\n\nexport function drainInProgress(url: string, dir?: string): boolean {\n const paths = spoolPaths(url, dir);\n try {\n return Date.now() - statSync(paths.drainLock).mtimeMs < DRAIN_LOCK_STALE_MS;\n } catch {\n return false;\n }\n}\n\nexport async function drainSpool(\n url: string,\n send: (record: SpoolRecord) => Promise<SendOutcome>,\n options: { policy?: SpoolPolicy; dir?: string; maxRecords?: number; deadlineMs?: number } = {},\n): Promise<DrainResult> {\n const policy = options.policy ?? spoolPolicy();\n const result: DrainResult = { claimed: 0, delivered: 0, duplicates: 0, rejected: 0, expired: 0, corrupt: 0, remaining: 0 };\n const paths = spoolPaths(url, options.dir);\n if (!existsSync(paths.dir)) return { ...result, skipped: \"empty\" };\n try {\n ensureDir(paths.dir);\n } catch {}\n if (!tryLock(paths.drainLock, DRAIN_LOCK_STALE_MS)) return { ...result, skipped: \"locked\" };\n try {\n const files = claimFiles(paths);\n if (files.length === 0) return { ...result, skipped: \"empty\" };\n const records: SpoolRecord[] = [];\n for (const file of files) {\n try {\n const parsed = parseLines(readFileSync(file, \"utf-8\"));\n records.push(...parsed.records);\n result.corrupt += parsed.corrupt;\n } catch {}\n }\n result.claimed = records.length;\n const seen = new Set<string>();\n const now = Date.now();\n const deadline = options.deadlineMs ? now + options.deadlineMs : Number.POSITIVE_INFINITY;\n const maxRecords = options.maxRecords ?? Number.POSITIVE_INFINITY;\n const leftover: SpoolRecord[] = [];\n let stopped = false;\n let processed = 0;\n for (const rec of records) {\n if (seen.has(rec.eventId)) {\n result.duplicates++;\n continue;\n }\n seen.add(rec.eventId);\n if (isExpired(rec, policy, now)) {\n result.expired++;\n continue;\n }\n if (stopped || processed >= maxRecords || Date.now() > deadline) {\n leftover.push(rec);\n continue;\n }\n processed++;\n let outcome: SendOutcome;\n try {\n outcome = await send(rec);\n } catch (err) {\n outcome = \"retry\";\n result.error = err instanceof Error ? err.message : String(err);\n }\n if (outcome === \"delivered\") result.delivered++;\n else if (outcome === \"duplicate\") result.duplicates++;\n else if (outcome === \"rejected\") result.rejected++;\n else {\n stopped = true;\n leftover.push({ ...rec, attempts: (rec.attempts ?? 0) + 1 });\n }\n }\n result.remaining = leftover.length;\n withLock(paths.lock, (locked) => (locked ? requeueLocked(paths, leftover) : requeueUnlocked(paths, leftover)), 2000);\n for (const file of files) {\n try {\n unlinkSync(file);\n } catch {}\n }\n writeStats(paths, (s) => {\n s.delivered += result.delivered;\n s.duplicates += result.duplicates;\n s.rejected += result.rejected;\n s.expired += result.expired;\n s.lastDrainAt = new Date().toISOString();\n s.lastDrainDelivered = result.delivered;\n s.lastDrainDuplicates = result.duplicates;\n s.lastDrainRejected = result.rejected;\n s.lastDrainRemaining = result.remaining;\n if (result.error) s.lastDrainError = result.error;\n else delete s.lastDrainError;\n });\n return result;\n } finally {\n releaseLock(paths.drainLock);\n }\n}\n\nexport function spoolSummary(url: string, options: { policy?: SpoolPolicy; dir?: string } = {}): SpoolSummary {\n const policy = options.policy ?? spoolPolicy();\n const paths = spoolPaths(url, options.dir);\n let records = 0;\n let bytes = 0;\n let oldestAt: string | null = null;\n const files: string[] = [];\n if (existsSync(paths.file)) files.push(paths.file);\n try {\n for (const entry of readdirSync(paths.dir)) {\n if (entry.startsWith(`${paths.name}.draining-`) && entry.endsWith(\".jsonl\")) files.push(join(paths.dir, entry));\n }\n } catch {}\n for (const file of files) {\n try {\n const text = readFileSync(file, \"utf-8\");\n bytes += Buffer.byteLength(text);\n for (const rec of parseLines(text).records) {\n records++;\n if (!oldestAt || rec.spooledAt < oldestAt) oldestAt = rec.spooledAt;\n }\n } catch {}\n }\n let retained = 0;\n let retainedBytes = 0;\n for (const seg of listSent(paths)) {\n try {\n const text = readFileSync(seg.file, \"utf-8\");\n retainedBytes += Buffer.byteLength(text);\n retained += parseLines(text).records.length;\n } catch {}\n }\n return {\n enabled: policy.enabled,\n path: paths.file,\n records,\n bytes,\n oldestAt,\n retained,\n retainedBytes,\n maxBytes: policy.maxBytes,\n maxAgeHours: Math.round(policy.maxAgeMs / 3600_000),\n stats: readSpoolStats(paths),\n };\n}\n","import { spawn } from \"node:child_process\";\nimport { resolveClientSecret } from \"../secret-store.js\";\nimport { deriveEventId, hasHostIdentity } from \"../capture/event-id.js\";\nimport {\n appendSpool,\n drainInProgress,\n drainSpool,\n parseSentMark,\n reconcileSent,\n retainSent,\n spoolHasRecords,\n type SendOutcome,\n type SentMark,\n type SpoolRecord,\n} from \"../capture/spool.js\";\n\nexport const REST_URL = process.env[\"AGENTMEMORY_URL\"] || \"http://localhost:3111\";\nconst SECRET = resolveClientSecret(REST_URL);\nconst DRAIN_CHILD_ENV = \"AGENTMEMORY_CAPTURE_DRAIN_CHILD\";\nconst DRAIN_MAX_RECORDS = 500;\nconst DRAIN_DEADLINE_MS = 20_000;\nconst DRAIN_REQUEST_TIMEOUT_MS = 5000;\n\nexport type CaptureOutcome = \"delivered\" | \"duplicate\" | \"rejected\" | \"spooled\" | \"dropped\";\n\nexport interface ObserveBody {\n hookType: string;\n sessionId: string;\n project: string;\n cwd: string;\n timestamp: string;\n data: unknown;\n eventId?: string;\n}\n\nexport function 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\nexport function withEventId(\n body: ObserveBody,\n host: Record<string, unknown>,\n content?: unknown,\n options: { stable?: boolean } = {},\n): ObserveBody {\n const source = options.stable || hasHostIdentity(host) ? host : { ...host, timestamp: body.timestamp };\n return {\n ...body,\n eventId: deriveEventId(body.hookType, body.sessionId, source, content ?? body.data),\n };\n}\n\nfunction classify(status: number): SendOutcome {\n if (status >= 200 && status < 300) return status === 200 ? \"duplicate\" : \"delivered\";\n if (status === 408 || status === 429 || status >= 500) return \"retry\";\n return \"rejected\";\n}\n\nasync function post(body: Record<string, unknown>, timeoutMs: number): Promise<{ outcome: SendOutcome; mark: SentMark | null }> {\n const res = await fetch(`${REST_URL}/agentmemory/observe`, {\n method: \"POST\",\n headers: authHeaders(),\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(timeoutMs),\n });\n const text = await res.text().catch(() => \"\");\n const outcome = classify(res.status);\n if (outcome !== \"delivered\" && outcome !== \"duplicate\") return { outcome, mark: null };\n try {\n return { outcome, mark: parseSentMark(JSON.parse(text)) };\n } catch {\n return { outcome, mark: null };\n }\n}\n\nfunction keepUntilDurable(eventId: string, body: Record<string, unknown>, mark: SentMark): number {\n const { requeued } = reconcileSent(REST_URL, mark.bootId);\n retainSent(REST_URL, eventId, body, mark);\n return requeued;\n}\n\nfunction startDrainChild(): void {\n if (process.env[DRAIN_CHILD_ENV] === \"1\") return;\n if (!process.argv[1] || drainInProgress(REST_URL)) return;\n try {\n const child = spawn(process.execPath, [process.argv[1]], {\n detached: true,\n stdio: \"ignore\",\n windowsHide: true,\n env: { ...process.env, [DRAIN_CHILD_ENV]: \"1\" },\n });\n child.on(\"error\", () => {});\n child.unref();\n } catch {}\n}\n\nexport async function captureObservation(body: ObserveBody, timeoutMs: number): Promise<CaptureOutcome> {\n const eventId = body.eventId ?? deriveEventId(body.hookType, body.sessionId, null, body.data);\n const payload = { ...body, eventId } as unknown as Record<string, unknown>;\n let reason: string;\n try {\n const { outcome, mark } = await post(payload, timeoutMs);\n if (outcome !== \"retry\") {\n const requeued = mark ? keepUntilDurable(eventId, payload, mark) : 0;\n if (requeued > 0 || spoolHasRecords(REST_URL)) startDrainChild();\n return outcome;\n }\n reason = \"server-error\";\n } catch (err) {\n const name = err instanceof Error ? err.name : \"\";\n reason = name === \"TimeoutError\" || name === \"AbortError\" ? \"timeout\" : \"unreachable\";\n }\n return appendSpool(REST_URL, eventId, payload, reason).spooled ? \"spooled\" : \"dropped\";\n}\n\nexport function isDrainChild(): boolean {\n return process.env[DRAIN_CHILD_ENV] === \"1\";\n}\n\nexport async function runDrainChild(): Promise<void> {\n await drainSpool(\n REST_URL,\n async (record: SpoolRecord) => {\n const { outcome, mark } = await post({ ...record.body, eventId: record.eventId }, DRAIN_REQUEST_TIMEOUT_MS);\n if (mark) retainSent(REST_URL, record.eventId, record.body, mark);\n return outcome;\n },\n { maxRecords: DRAIN_MAX_RECORDS, deadlineMs: DRAIN_DEADLINE_MS },\n ).catch(() => undefined);\n}\n"],"mappings":";;;;;;AAcA,MAAM,aAAa;AAEnB,SAAgB,qBAA6B;AAC3C,QAAO,KAAK,SAAS,EAAE,eAAe;;AAGxC,SAAgB,iBAAyB;AACvC,QAAO,KAAK,oBAAoB,EAAE,SAAS;;AAG7C,SAAS,OAAO,OAAmC;AACjD,KAAI,OAAO,UAAU,SAAU,QAAO;CACtC,MAAM,UAAU,MAAM,MAAM;AAC5B,KAAI,CAAC,QAAS,QAAO;AACrB,KAAI,QAAQ,WAAW,KAAK,IAAI,QAAQ,SAAS,IAAI,CAAE,QAAO;AAC9D,QAAO;;AAGT,SAAS,QAAQ,OAAuB;CACtC,MAAM,QAAQ,MAAM;AACpB,MAAK,UAAU,QAAO,UAAU,QAAQ,MAAM,SAAS,GAAG;EACxD,MAAM,QAAQ,MAAM,QAAQ,OAAO,EAAE;AACrC,MAAI,UAAU,GAAI,QAAO,MAAM,MAAM,GAAG,MAAM;;CAEhD,MAAM,OAAO,MAAM,QAAQ,KAAK;AAChC,QAAO,SAAS,KAAK,QAAQ,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM;;AAG1D,SAAgB,oBAA4B;CAC1C,IAAI;AACJ,KAAI;AACF,YAAU,aAAa,KAAK,oBAAoB,EAAE,OAAO,EAAE,QAAQ;SAC7D;AACN,SAAO;;AAET,KAAI,OAAO,YAAY,SAAU,QAAO;CACxC,IAAI,QAAQ;AACZ,MAAK,MAAM,QAAQ,QAAQ,MAAM,KAAK,EAAE;EACtC,MAAM,UAAU,KAAK,MAAM;AAC3B,MAAI,QAAQ,WAAW,IAAI,CAAE;EAC7B,MAAM,KAAK,QAAQ,QAAQ,IAAI;AAC/B,MAAI,OAAO,GAAI;AAEf,MADY,QAAQ,MAAM,GAAG,GAAG,CAAC,QAAQ,cAAc,GAAG,CAAC,MACpD,KAAK,WAAY;AACxB,UAAQ,OAAO,QAAQ,QAAQ,MAAM,KAAK,EAAE,CAAC,MAAM,CAAC,CAAC;;AAEvD,QAAO;;AAGT,SAAgB,mBAA2B;AACzC,KAAI;AACF,SAAO,OAAO,aAAa,gBAAgB,EAAE,QAAQ,CAAC;SAChD;AACN,SAAO;;;AAQX,SAAgB,cAAc,KAAsB;CAClD,IAAI;AACJ,KAAI;AACF,aAAW,IAAI,IAAI,IAAI,CAAC,SAAS,aAAa;SACxC;AACN,SAAO;;CAET,MAAM,OAAO,SAAS,QAAQ,YAAY,GAAG;AAC7C,QAAO,SAAS,eAAe,SAAS,SAAS,wBAAwB,KAAK,KAAK;;AAGrF,SAAgB,oBACd,SACA,MAAyB,QAAQ,KACzB;CACR,MAAM,UAAU,OAAO,IAAI,YAAY;AACvC,KAAI,QAAS,QAAO;AACpB,KAAI,CAAC,cAAc,QAAQ,CAAE,QAAO;AACpC,QAAO,mBAAmB,IAAI,kBAAkB;;;;ACzFlD,MAAM,iBAAiB;CACrB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AAED,MAAM,mBAAmB;CAAC;CAAa;CAAM;CAAc;CAAa;CAAa;AAMrF,SAAgB,gBAAgB,OAAwB;AACtD,KAAI,UAAU,KAAA,EAAW,QAAO;AAChC,KAAI,UAAU,QAAQ,OAAO,UAAU,UAAU;EAC/C,MAAM,UAAU,KAAK,UAAU,MAAM;AACrC,SAAO,YAAY,KAAA,IAAY,SAAS;;AAE1C,KAAI,MAAM,QAAQ,MAAM,CAAE,QAAO,IAAI,MAAM,IAAI,gBAAgB,CAAC,KAAK,IAAI,CAAC;CAC1E,MAAM,MAAM;AAIZ,QAAO,IAHM,OAAO,KAAK,IAAI,CAC1B,QAAQ,MAAM,IAAI,OAAO,KAAA,EAAU,CACnC,MACY,CAAC,KAAK,MAAM,GAAG,KAAK,UAAU,EAAE,CAAC,GAAG,gBAAgB,IAAI,GAAG,GAAG,CAAC,KAAK,IAAI,CAAC;;AAG1F,SAAS,WAAW,MAA+B,QAAsC;AACvF,MAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,IAAI,KAAK;AACf,MAAI,OAAO,MAAM,YAAY,EAAE,MAAM,CAAE,QAAO,EAAE,MAAM;AACtD,MAAI,OAAO,MAAM,YAAY,OAAO,SAAS,EAAE,CAAE,QAAO,OAAO,EAAE;;;AAKrE,SAAS,OAAO,OAAyB;AACvC,QAAO,WAAW,SAAS,CAAC,OAAO,MAAM,KAAK,KAAS,CAAC,CAAC,OAAO,MAAM,CAAC,MAAM,GAAG,GAAG;;AAGrF,SAAgB,gBAAgB,MAA2D;CACzF,MAAM,SAAS,QAAQ,OAAO,SAAS,WAAW,OAAO,EAAE;AAC3D,QAAO,WAAW,QAAQ,eAAe,KAAK,KAAA,KAAa,WAAW,QAAQ,iBAAiB,KAAK,KAAA;;AAGtG,SAAgB,cACd,UACA,WACA,MACA,SACQ;CACR,MAAM,SAAS,QAAQ,OAAO,SAAS,WAAW,OAAO,EAAE;CAC3D,MAAM,SAAS,WAAW,QAAQ,eAAe;AACjD,KAAI,OAAQ,QAAO,OAAO,OAAO;EAAC;EAAQ;EAAW;EAAU;EAAO,CAAC;AAEvE,QAAO,OAAO,OAAO;EAAC;EAAW;EAAW;EAD3B,WAAW,QAAQ,iBAAiB,IAAI;EACO,gBAAgB,QAAQ;EAAC,CAAC;;;;AC/C5F,SAAS,SAAS,MAAgB,MAAkC;CAClE,MAAM,SAAS,GAAG,KAAK;CACvB,MAAM,SAAS,KAAK,MAAM,QAAQ,IAAI,WAAW,OAAO,CAAC;AACzD,KAAI,OAAQ,QAAO,OAAO,MAAM,OAAO,OAAO;CAE9C,MAAM,MAAM,KAAK,QAAQ,KAAK;AAC9B,KAAI,QAAQ,GAAI,QAAO,KAAK,MAAM;;AAIpC,SAAS,WAAW,WAAmB,MAAsB;AAC3D,KAAI,cAAc,IAAK,QAAO;AAC9B,KAAI,UAAU,WAAW,KAAK,IAAI,UAAU,WAAW,MAAM,CAC3D,QAAO,KAAK,MAAM,UAAU,MAAM,EAAE,CAAC;AAEvC,QAAO;;AAGT,SAAS,gBAAgB,WAAmB,KAAa,MAAsB;CAC7E,MAAM,WAAW,WAAW,WAAW,KAAK;AAC5C,QAAO,WAAW,SAAS,GAAG,WAAW,QAAQ,KAAK,SAAS;;AAGjE,SAAS,uBACP,KACA,MACA,cACA,SAAS,MACD;AACR,KAAI,iBAAiB,SACnB,QAAO,KAAK,MAAM,WAAW,uBAAuB,cAAc;AAEpE,KAAI,iBAAiB,SAAS;EAC5B,MAAM,UAAU,IAAI;AACpB,SAAO,UACH,KAAK,SAAS,cAAc,GAC5B,KAAK,MAAM,eAAe;;CAEhC,MAAM,cAAc,IAAI;AACxB,KAAI,UAAU,eAAe,WAAW,YAAY,CAClD,QAAO,KAAK,aAAa,cAAc;AAEzC,QAAO,KAAK,MAAM,UAAU,SAAS,cAAc;;AAGrD,SAAS,iBAAiB,WAAuC;CAC/D,IAAI,UAAU;AACd,QAAO,MAAM;AACX,MAAI,WAAW,KAAK,SAAS,OAAO,CAAC,CAAE,QAAO;EAC9C,MAAM,SAAS,QAAQ,QAAQ;AAC/B,MAAI,WAAW,QAAS,QAAO,KAAA;AAC/B,YAAU;;;AAId,SAAS,SAAS,QAAgB,OAAwB;CACxD,MAAM,iBAAiB,SAAS,QAAQ,MAAM;AAC9C,QACE,mBAAmB,MAClB,mBAAmB,QAClB,CAAC,eAAe,WAAW,KAAK,MAAM,IACtC,CAAC,WAAW,eAAe;;AAIjC,SAAgB,eAAe,UAAiC,EAAE,EAAmB;CACnF,MAAM,OAAO,QAAQ,QAAQ,QAAQ,KAAK,MAAM,EAAE;CAClD,MAAM,MAAM,QAAQ,OAAO,QAAQ;CACnC,MAAM,MAAM,QAAQ,OAAO,QAAQ,KAAK;CACxC,MAAM,OAAO,QAAQ,QAAQ,SAAS;CACtC,MAAM,eAAe,QAAQ,YAAY,UAAU;CACnD,MAAM,iBAAiB,SAAS,SAAS,MAAM,aAAa,IAAI,KAAK,GAAG;CACxE,MAAM,WACJ,iBAAiB,KAAK,kBAAkB,KAAK,iBAAiB;CAChE,MAAM,eAAe,YACnB,WAAW,KAAK,SAAS,YAAY,WAAW,GAAG;CAErD,MAAM,YAAY,SAAS,MAAM,aAAa;AAC9C,KAAI,UACF,QAAO;EACL,SAAS,YAAY,gBAAgB,WAAW,KAAK,KAAK,CAAC;EAC3D,QAAQ;EACT;CAGH,MAAM,WAAW,IAAI;AACrB,KAAI,SACF,QAAO;EACL,SAAS,YAAY,gBAAgB,UAAU,KAAK,KAAK,CAAC;EAC1D,QAAQ;EACT;CAGH,MAAM,YAAY,QAAQ,KAAK,OAAO;CACtC,MAAM,iBACJ,WAAW,KAAK,WAAW,iBAAiB,CAAC,IAC7C,WAAW,KAAK,WAAW,kBAAkB,CAAC;AAChD,KAAI,aAAa,KAAK,eACpB,QAAO;EAAE,SAAS;EAAW,QAAQ;EAAW;CAGlD,MAAM,aAAa,uBAAuB,KAAK,MAAM,aAAa;CAClE,MAAM,YAAY,iBAAiB,IAAI;AACvC,KAAI,aAAa,SAAS,WAAW,WAAW,EAAE;EAChD,MAAM,YAAY,uBAAuB,KAAK,MAAM,cAAc,MAAM;AACxE,MAAI,cAAc,WAChB,QAAO;GACL,SAAS,YAAY,UAAU;GAC/B,QAAQ;GACR,eAAe;GAChB;;AAIL,QAAO;EACL,SAAS,YAAY,WAAW;EAChC,QAAQ;EACT;;;;ACvIH,MAAM,iBAAiB;AAEvB,MAAM,yBAAyB;CAC7B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AAED,MAAM,uBACJ;AAEF,MAAM,qBACJ;AAEF,SAAgB,iBAAiB,OAAuB;CACtD,IAAI,SAAS,MAAM,QAAQ,gBAAgB,aAAa;AACxD,UAAS,OAAO,QAAQ,IAAI,OAAO,qBAAqB,QAAQ,qBAAqB,MAAM,EAAE,oBAAoB;AACjH,UAAS,OAAO,QAAQ,IAAI,OAAO,mBAAmB,QAAQ,mBAAmB,MAAM,EAAE,uBAAuB;AAChH,MAAK,MAAM,UAAU,wBAAwB;EAC3C,MAAM,UAAU,IAAI,OAAO,OAAO,QAAQ,OAAO,MAAM;AACvD,WAAS,OAAO,QAAQ,SAAS,oBAAoB;;AAEvD,QAAO;;;;ACoET,MAAM,oBAAoB,IAAI,OAAO;AACrC,MAAM,gBAAgB,KAAK;AAC3B,MAAM,wBAAwB;AAC9B,MAAM,gBAAgB;AACtB,MAAM,sBAAsB;AAC5B,MAAM,kBAAkB;AACxB,MAAM,eAAe;AACrB,MAAM,oBAAoB;AAC1B,MAAM,aAAa;AACnB,MAAM,eAAe;AACrB,MAAM,iBAAiB;AACvB,MAAM,uBAAuB,KAAK;AAElC,SAAS,aAAyB;AAChC,QAAO;EAAE,SAAS;EAAG,SAAS;EAAG,cAAc;EAAG,SAAS;EAAG,WAAW;EAAG,YAAY;EAAG,UAAU;EAAG;;AAG1G,SAAS,YAAY,KAAyB,UAA0B;AACtE,KAAI,CAAC,OAAO,CAAC,QAAQ,KAAK,IAAI,MAAM,CAAC,CAAE,QAAO;CAC9C,MAAM,IAAI,SAAS,IAAI,MAAM,EAAE,GAAG;AAClC,QAAO,IAAI,IAAI,IAAI;;AAGrB,SAAgB,YAAY,MAAyB,QAAQ,KAAkB;CAC7E,MAAM,WAAW,KAAK,IAAI,eAAe,YAAY,IAAI,wCAAwC,kBAAkB,CAAC;CACpH,MAAM,cAAc,YAAY,IAAI,4CAA4C,sBAAsB;AACtG,QAAO;EACL,SAAS,IAAI,iCAAiC;EAC9C;EACA,UAAU,cAAc;EACxB,gBAAgB,KAAK,IAAI,MAAM,MAAM,KAAK,MAAM,WAAW,EAAE,CAAC;EAC/D;;AAGH,SAAgB,SAAS,MAAyB,QAAQ,KAAa;CACrE,MAAM,WAAW,IAAI;AACrB,KAAI,YAAY,SAAS,MAAM,CAAE,QAAO,QAAQ,SAAS,MAAM,CAAC;AAChE,QAAO,KAAK,eAAe;EAAE,MAAM,EAAE;EAAE;EAAK,CAAC,CAAC,SAAS,gBAAgB;;AAGzE,MAAM,WAAW,IAAI,IAAI;CAAC;CAAa;CAAa;CAAO;CAAS;CAAU,CAAC;AAE/E,SAAgB,gBAAgB,KAAqB;AACnD,KAAI;EACF,MAAM,IAAI,IAAI,IAAI,IAAI;AAGtB,SAAO,GAFM,SAAS,IAAI,EAAE,SAAS,GAAG,UAAU,EAAE,SAAS,aAAa,CAAC,QAAQ,gBAAgB,IAAI,CAExF,GADF,EAAE,SAAS,EAAE,aAAa,WAAW,QAAQ;SAEpD;AACN,SAAO;;;AAIX,SAAgB,WAAW,KAAa,MAAc,UAAU,EAAc;CAC5E,MAAM,OAAO,gBAAgB,IAAI;AACjC,QAAO;EACL;EACA;EACA,MAAM,KAAK,KAAK,GAAG,KAAK,QAAQ;EAChC,MAAM,KAAK,KAAK,GAAG,KAAK,OAAO;EAC/B,WAAW,KAAK,KAAK,GAAG,KAAK,aAAa;EAC1C,OAAO,KAAK,KAAK,GAAG,KAAK,aAAa;EACvC;;AAGH,SAAS,UAAU,KAAmB;AACpC,WAAU,KAAK;EAAE,WAAW;EAAM,MAAM;EAAO,CAAC;AAChD,KAAI;AACF,YAAU,KAAK,IAAM;SACf;;AAGV,SAAS,UAAU,IAAkB;AACnC,SAAQ,KAAK,IAAI,WAAW,IAAI,kBAAkB,EAAE,CAAC,EAAE,GAAG,GAAG,GAAG;;AAGlE,SAAS,QAAQ,MAAc,SAA0B;AACvD,KAAI;EACF,MAAM,KAAK,SAAS,MAAM,MAAM,IAAM;AACtC,YAAU,IAAI,GAAG,QAAQ,IAAI,IAAI;AACjC,YAAU,GAAG;AACb,SAAO;UACA,KAAK;AACZ,MAAK,IAA8B,SAAS,SAAU,QAAO;AAC7D,MAAI;AACF,OAAI,KAAK,KAAK,GAAG,SAAS,KAAK,CAAC,UAAU,QAAS,YAAW,KAAK;UAC7D;AACR,SAAO;;;AAIX,SAAS,YAAY,MAAoB;AACvC,KAAI;AACF,aAAW,KAAK;SACV;;AAGV,SAAS,SAAY,MAAc,IAA4B,SAAS,cAAiB;CACvF,MAAM,WAAW,KAAK,KAAK,GAAG;CAC9B,IAAI,SAAS,QAAQ,MAAM,cAAc;AACzC,QAAO,CAAC,UAAU,KAAK,KAAK,GAAG,UAAU;AACvC,YAAU,EAAE;AACZ,WAAS,QAAQ,MAAM,cAAc;;AAEvC,KAAI;AACF,SAAO,GAAG,OAAO;WACT;AACR,MAAI,OAAQ,aAAY,KAAK;;;AAIjC,SAAgB,eAAe,OAA+B;AAC5D,KAAI;EACF,MAAM,SAAS,KAAK,MAAM,aAAa,MAAM,OAAO,QAAQ,CAAC;AAC7D,SAAO;GAAE,GAAG,YAAY;GAAE,GAAG;GAAQ;SAC/B;AACN,SAAO,YAAY;;;AAIvB,SAAS,WAAW,OAAmB,OAA0C;AAC/E,KAAI;EACF,MAAM,QAAQ,eAAe,MAAM;AACnC,QAAM,MAAM;EACZ,MAAM,MAAM,GAAG,MAAM,MAAM,GAAG,QAAQ,IAAI;AAC1C,gBAAc,KAAK,KAAK,UAAU,MAAM,EAAE,EAAE,MAAM,KAAO,CAAC;AAC1D,aAAW,KAAK,MAAM,MAAM;SACtB;;AAGV,SAAS,SAAS,MAAsB;AACtC,KAAI;AACF,SAAO,SAAS,KAAK,CAAC;SAChB;AACN,SAAO;;;AAIX,SAAS,aAAa,MAAwD;AAC5E,KAAI;AACF,SAAO,KAAK,MAAM,iBAAiB,KAAK,UAAU,KAAK,CAAC,CAAC;SACnD;AACN,SAAO;;;AAIX,SAAS,aAAa,MAAwD;CAC5E,MAAM,OAAO,KAAK;AAClB,KAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,KAAK,CAAE,QAAO;CACrE,MAAM,IAAI;AACV,KAAI,EAAE,kBAAkB,KAAA,EAAW,QAAO;AAC1C,QAAO;EAAE,GAAG;EAAM,MAAM;GAAE,GAAG;GAAG,YAAY;GAAmB;EAAE;;AAGnE,SAAS,WAAW,MAA2D;CAC7E,MAAM,UAAyB,EAAE;CACjC,IAAI,UAAU;AACd,MAAK,MAAM,QAAQ,KAAK,MAAM,KAAK,EAAE;AACnC,MAAI,CAAC,KAAK,MAAM,CAAE;AAClB,MAAI;GACF,MAAM,MAAM,KAAK,MAAM,KAAK;AAC5B,OAAI,OAAO,OAAO,IAAI,YAAY,YAAY,IAAI,QAAQ,OAAO,IAAI,SAAS,SAAU,SAAQ,KAAK,IAAI;OACpG;UACC;AACN;;;AAGJ,QAAO;EAAE;EAAS;EAAS;;AAG7B,SAAS,UAAU,KAAkB,QAAqB,KAAsB;CAC9E,MAAM,KAAK,KAAK,MAAM,IAAI,UAAU;AACpC,QAAO,OAAO,SAAS,GAAG,IAAI,MAAM,KAAK,OAAO;;AAGlD,SAAS,UAAU,SAAgC;AACjD,QAAO,QAAQ,KAAK,MAAM,KAAK,UAAU,EAAE,CAAC,CAAC,KAAK,KAAK,IAAI,QAAQ,SAAS,OAAO;;AAGrF,SAAS,cAAc,OAAmB,SAA8B;CACtE,MAAM,MAAM,GAAG,MAAM,KAAK,GAAG,QAAQ,IAAI;CACzC,MAAM,KAAK,SAAS,KAAK,KAAK,IAAM;AACpC,KAAI;AACF,YAAU,IAAI,UAAU,QAAQ,CAAC;AACjC,YAAU,GAAG;WACL;AACR,YAAU,GAAG;;AAEf,YAAW,KAAK,MAAM,KAAK;;AAG7B,SAAS,mBAAmB,OAAmB,QAA6B;AAC1E,KAAI,CAAC,WAAW,MAAM,KAAK,CAAE,QAAO;CACpC,MAAM,EAAE,YAAY,WAAW,aAAa,MAAM,MAAM,QAAQ,CAAC;CACjE,MAAM,MAAM,KAAK,KAAK;CACtB,MAAM,OAAO,QAAQ,QAAQ,MAAM,CAAC,UAAU,GAAG,QAAQ,IAAI,CAAC;CAC9D,MAAM,UAAU,QAAQ,SAAS,KAAK;AACtC,KAAI,UAAU,EAAG,eAAc,OAAO,KAAK;AAC3C,QAAO;;AAQT,SAAgB,YACd,KACA,SACA,MACA,QACA,UAAqE,EAAE,EACzD;CACd,MAAM,SAAS,QAAQ,UAAU,aAAa;AAC9C,KAAI,CAAC,OAAO,QAAS,QAAO;EAAE,SAAS;EAAO,SAAS;EAAY;CACnE,MAAM,QAAQ,WAAW,KAAK,QAAQ,IAAI;CAC1C,MAAM,QAAQ,YAAkD,UAAkB;AAChF,aAAW,QAAQ,MAAM;AACvB,KAAE;AACF,KAAE,gBAAgB;AAClB,KAAE,8BAAa,IAAI,MAAM,EAAC,aAAa;AACvC,KAAE,iBAAiB;IACnB;AACF,SAAO;GAAE,SAAS;GAAO,SAAS;GAAY;;AAEhD,KAAI;AACF,YAAU,MAAM,IAAI;EACpB,MAAM,SAAsB;GAC1B,GAAG;GACH;GACA,4BAAW,IAAI,MAAM,EAAC,aAAa;GACnC;GACA,UAAU,QAAQ,YAAY;GAC9B,MAAM,aAAa,KAAK;GACzB;EACD,IAAI,OAAO,KAAK,UAAU,OAAO,GAAG;AACpC,MAAI,OAAO,WAAW,KAAK,GAAG,OAAO,gBAAgB;AACnD,UAAO,OAAO,aAAa,OAAO,KAAK;AACvC,UAAO,KAAK,UAAU,OAAO,GAAG;;EAElC,MAAM,QAAQ,OAAO,WAAW,KAAK;AACrC,MAAI,QAAQ,OAAO,eAAgB,QAAO,KAAK,aAAa,MAAM;AAClE,SAAO,SAAS,MAAM,OAAO,WAAW;GACtC,IAAI,UAAU;AACd,OAAI,UAAU,SAAS,MAAM,KAAK,GAAG,QAAQ,OAAO,SAAU,WAAU,mBAAmB,OAAO,OAAO;GACzG,MAAM,OAAO,SAAS,MAAM,KAAK,GAAG,UAAU,MAAM,GAAG,QAAQ,OAAO;AACtE,OAAI,UAAU,OAAO,EAAG,WAAU,OAAO,KAAK;AAC9C,OAAI,SAAS,MAAM,KAAK,GAAG,UAAU,MAAM,GAAG,QAAQ,OAAO,UAAU;AACrE,QAAI,QAAS,YAAW,QAAQ,MAAM,MAAM,EAAE,WAAW,SAAS;AAClE,WAAO,KAAK,QAAQ,MAAM;;GAE5B,MAAM,KAAK,SAAS,MAAM,MAAM,KAAK,IAAM;AAC3C,OAAI;AACF,cAAU,IAAI,KAAK;AACnB,cAAU,GAAG;aACL;AACR,cAAU,GAAG;;AAEf,cAAW,QAAQ,MAAM;AACvB,MAAE;AACF,MAAE,WAAW;AACb,MAAE,gBAAgB,OAAO;AACzB,MAAE,kBAAkB;KACpB;AACF,UAAO,EAAE,SAAS,MAAM;IACxB;SACI;AACN,SAAO;GAAE,SAAS;GAAO,SAAS;GAAS;;;AAW/C,SAAS,SAAS,OAAkC;CAClD,MAAM,WAA0B,EAAE;CAClC,IAAI;AACJ,KAAI;AACF,YAAU,YAAY,MAAM,IAAI;SAC1B;AACN,SAAO;;CAET,MAAM,SAAS,GAAG,MAAM,KAAK;AAC7B,MAAK,MAAM,SAAS,SAAS;AAC3B,MAAI,CAAC,MAAM,WAAW,OAAO,CAAE;EAC/B,MAAM,QAAQ,MAAM,MAAM,OAAO,OAAO,CAAC,MAAM,aAAa;AAC5D,MAAI,CAAC,MAAO;EACZ,MAAM,OAAO,KAAK,MAAM,KAAK,MAAM;AACnC,WAAS,KAAK;GAAE;GAAM,QAAQ,MAAM;GAAK,OAAO,OAAO,MAAM,GAAG;GAAE,OAAO,SAAS,KAAK;GAAE,CAAC;;AAE5F,QAAO;;AAGT,SAAS,UAAU,OAA2B;AAC5C,QAAO,SAAS,MAAM,CAAC,QAAQ,GAAG,MAAM,IAAI,EAAE,OAAO,EAAE;;AAGzD,SAAS,UAAU,OAAmB,MAAoB;CACxD,IAAI,QAAQ;AACZ,MAAK,MAAM,OAAO,SAAS,MAAM,CAAC,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE;AACnE,MAAI,SAAS,KAAM;AACnB,MAAI;AACF,cAAW,IAAI,KAAK;AACpB,YAAS,IAAI;UACP;;;AAIZ,SAAgB,cAAc,MAAgC;AAC5D,KAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,KAAK,CAAE,QAAO;CACrE,MAAM,SAAU,KAAiC;CACjD,MAAM,QAAS,KAAiC;AAChD,KAAI,OAAO,WAAW,YAAY,CAAC,WAAW,KAAK,OAAO,CAAE,QAAO;AACnE,KAAI,OAAO,UAAU,YAAY,CAAC,OAAO,SAAS,MAAM,IAAI,QAAQ,EAAG,QAAO;AAC9E,QAAO;EAAE;EAAQ,gBAAgB,KAAK,IAAI,KAAK,KAAK,MAAM,EAAE,qBAAqB;EAAE;;AAGrF,SAAgB,WACd,KACA,SACA,MACA,MACA,UAAgE,EAAE,EACzD;CACT,MAAM,SAAS,QAAQ,UAAU,aAAa;AAC9C,KAAI,CAAC,OAAO,WAAW,CAAC,WAAW,KAAK,KAAK,OAAO,CAAE,QAAO;CAC7D,MAAM,QAAQ,WAAW,KAAK,QAAQ,IAAI;CAC1C,MAAM,MAAM,QAAQ,OAAO,KAAK,KAAK;AACrC,KAAI;AACF,YAAU,MAAM,IAAI;EACpB,MAAM,SAAsB;GAC1B,GAAG;GACH;GACA,WAAW,IAAI,KAAK,IAAI,CAAC,aAAa;GACtC,QAAQ;GACR,UAAU;GACV,MAAM,aAAa,KAAK;GACxB,QAAQ,KAAK;GACd;EACD,IAAI,OAAO,KAAK,UAAU,OAAO,GAAG;AACpC,MAAI,OAAO,WAAW,KAAK,GAAG,OAAO,gBAAgB;AACnD,UAAO,OAAO,aAAa,OAAO,KAAK;AACvC,UAAO,KAAK,UAAU,OAAO,GAAG;;EAElC,MAAM,QAAQ,OAAO,WAAW,KAAK;AACrC,MAAI,QAAQ,OAAO,eAAgB,QAAO;EAC1C,MAAM,QAAQ,KAAK,MAAM,MAAM,KAAK,kBAAkB,eAAe,GAAG;EACxE,MAAM,OAAO,KAAK,MAAM,KAAK,GAAG,MAAM,KAAK,QAAQ,KAAK,OAAO,GAAG,MAAM,QAAQ;AAChF,SAAO,SAAS,MAAM,YAAY;AAChC,OAAI,SAAS,MAAM,KAAK,GAAG,UAAU,MAAM,GAAG,QAAQ,OAAO,SAAU,QAAO;GAC9E,MAAM,KAAK,SAAS,MAAM,KAAK,IAAM;AACrC,OAAI;AACF,cAAU,IAAI,KAAK;aACX;AACR,cAAU,GAAG;;AAEf,UAAO;IACP;SACI;AACN,SAAO;;;AAIX,SAAgB,cACd,KACA,QACA,UAAgE,EAAE,EACnD;CACf,MAAM,SAAwB;EAAE,UAAU;EAAG,UAAU;EAAG;AAE1D,KAAI,EADW,QAAQ,UAAU,aAAa,EAClC,QAAS,QAAO;CAC5B,MAAM,QAAQ,WAAW,KAAK,QAAQ,IAAI;CAC1C,MAAM,MAAM,QAAQ,OAAO,KAAK,KAAK;AACrC,MAAK,MAAM,OAAO,SAAS,MAAM,EAAE;AACjC,MAAI,IAAI,WAAW,QAAQ;AACzB,OAAI,IAAI,QAAQ,IAAK;AACrB,OAAI;AACF,eAAW,IAAI,KAAK;AACpB,WAAO;WACD;AACR;;EAEF,MAAM,SAAS,KAAK,MAAM,KAAK,GAAG,MAAM,KAAK,YAAY,QAAQ,IAAI,GAAG,IAAI,GAAG,OAAO,SAAS,QAAQ;AACvG,MAAI;AACF,cAAW,IAAI,MAAM,OAAO;UACtB;AACN;;AAEF,MAAI;AACF,cAAW,QAAQ,GAAG,EAAE;UAClB;AACR,SAAO;;AAET,QAAO;;AAGT,SAAS,WAAW,OAA6B;CAC/C,MAAM,UAAoB,EAAE;AAC5B,KAAI;EACF,MAAM,SAAS,GAAG,MAAM,KAAK;AAC7B,OAAK,MAAM,SAAS,YAAY,MAAM,IAAI,EAAE;AAC1C,OAAI,CAAC,MAAM,WAAW,OAAO,IAAI,CAAC,MAAM,SAAS,SAAS,CAAE;GAC5D,MAAM,OAAO,KAAK,MAAM,KAAK,MAAM;AACnC,OAAI;AACF,QAAI,KAAK,KAAK,GAAG,SAAS,KAAK,CAAC,UAAU,gBAAiB,SAAQ,KAAK,KAAK;WACvE;;SAEJ;AACR,KAAI,SAAS,MAAM,KAAK,GAAG,GAAG;EAC5B,MAAM,SAAS,KAAK,MAAM,KAAK,GAAG,MAAM,KAAK,YAAY,QAAQ,IAAI,GAAG,KAAK,KAAK,CAAC,QAAQ;AAC3F,WAAS,MAAM,OAAO,WAAW;AAC/B,OAAI,CAAC,OAAQ;AACb,OAAI;AACF,eAAW,MAAM,MAAM,OAAO;AAC9B,YAAQ,KAAK,OAAO;WACd;IACR;;AAEJ,QAAO;;AAGT,SAAS,cAAc,OAAmB,UAA+B;AACvE,KAAI,SAAS,WAAW,EAAG;CAC3B,MAAM,WAAW,WAAW,MAAM,KAAK,GAAG,WAAW,aAAa,MAAM,MAAM,QAAQ,CAAC,CAAC,UAAU,EAAE;AACpG,eAAc,OAAO,CAAC,GAAG,UAAU,GAAG,SAAS,CAAC;;AAGlD,SAAS,gBAAgB,OAAmB,UAA+B;AACzE,KAAI,SAAS,WAAW,EAAG;CAC3B,MAAM,SAAS,KAAK,MAAM,KAAK,GAAG,MAAM,KAAK,YAAY,QAAQ,IAAI,GAAG,KAAK,KAAK,CAAC,gBAAgB;CACnG,MAAM,KAAK,SAAS,QAAQ,MAAM,IAAM;AACxC,KAAI;AACF,YAAU,IAAI,UAAU,SAAS,CAAC;AAClC,YAAU,GAAG;WACL;AACR,YAAU,GAAG;;AAEf,KAAI;AACF,aAAW,QAAQ,GAAG,EAAE;SAClB;;AAGV,SAAgB,gBAAgB,KAAa,KAAuB;AAClE,QAAO,SAAS,WAAW,KAAK,IAAI,CAAC,KAAK,GAAG;;AAG/C,SAAgB,gBAAgB,KAAa,KAAuB;CAClE,MAAM,QAAQ,WAAW,KAAK,IAAI;AAClC,KAAI;AACF,SAAO,KAAK,KAAK,GAAG,SAAS,MAAM,UAAU,CAAC,UAAU;SAClD;AACN,SAAO;;;AAIX,eAAsB,WACpB,KACA,MACA,UAA4F,EAAE,EACxE;CACtB,MAAM,SAAS,QAAQ,UAAU,aAAa;CAC9C,MAAM,SAAsB;EAAE,SAAS;EAAG,WAAW;EAAG,YAAY;EAAG,UAAU;EAAG,SAAS;EAAG,SAAS;EAAG,WAAW;EAAG;CAC1H,MAAM,QAAQ,WAAW,KAAK,QAAQ,IAAI;AAC1C,KAAI,CAAC,WAAW,MAAM,IAAI,CAAE,QAAO;EAAE,GAAG;EAAQ,SAAS;EAAS;AAClE,KAAI;AACF,YAAU,MAAM,IAAI;SACd;AACR,KAAI,CAAC,QAAQ,MAAM,WAAW,oBAAoB,CAAE,QAAO;EAAE,GAAG;EAAQ,SAAS;EAAU;AAC3F,KAAI;EACF,MAAM,QAAQ,WAAW,MAAM;AAC/B,MAAI,MAAM,WAAW,EAAG,QAAO;GAAE,GAAG;GAAQ,SAAS;GAAS;EAC9D,MAAM,UAAyB,EAAE;AACjC,OAAK,MAAM,QAAQ,MACjB,KAAI;GACF,MAAM,SAAS,WAAW,aAAa,MAAM,QAAQ,CAAC;AACtD,WAAQ,KAAK,GAAG,OAAO,QAAQ;AAC/B,UAAO,WAAW,OAAO;UACnB;AAEV,SAAO,UAAU,QAAQ;EACzB,MAAM,uBAAO,IAAI,KAAa;EAC9B,MAAM,MAAM,KAAK,KAAK;EACtB,MAAM,WAAW,QAAQ,aAAa,MAAM,QAAQ,aAAa,OAAO;EACxE,MAAM,aAAa,QAAQ,cAAc,OAAO;EAChD,MAAM,WAA0B,EAAE;EAClC,IAAI,UAAU;EACd,IAAI,YAAY;AAChB,OAAK,MAAM,OAAO,SAAS;AACzB,OAAI,KAAK,IAAI,IAAI,QAAQ,EAAE;AACzB,WAAO;AACP;;AAEF,QAAK,IAAI,IAAI,QAAQ;AACrB,OAAI,UAAU,KAAK,QAAQ,IAAI,EAAE;AAC/B,WAAO;AACP;;AAEF,OAAI,WAAW,aAAa,cAAc,KAAK,KAAK,GAAG,UAAU;AAC/D,aAAS,KAAK,IAAI;AAClB;;AAEF;GACA,IAAI;AACJ,OAAI;AACF,cAAU,MAAM,KAAK,IAAI;YAClB,KAAK;AACZ,cAAU;AACV,WAAO,QAAQ,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;;AAEjE,OAAI,YAAY,YAAa,QAAO;YAC3B,YAAY,YAAa,QAAO;YAChC,YAAY,WAAY,QAAO;QACnC;AACH,cAAU;AACV,aAAS,KAAK;KAAE,GAAG;KAAK,WAAW,IAAI,YAAY,KAAK;KAAG,CAAC;;;AAGhE,SAAO,YAAY,SAAS;AAC5B,WAAS,MAAM,OAAO,WAAY,SAAS,cAAc,OAAO,SAAS,GAAG,gBAAgB,OAAO,SAAS,EAAG,IAAK;AACpH,OAAK,MAAM,QAAQ,MACjB,KAAI;AACF,cAAW,KAAK;UACV;AAEV,aAAW,QAAQ,MAAM;AACvB,KAAE,aAAa,OAAO;AACtB,KAAE,cAAc,OAAO;AACvB,KAAE,YAAY,OAAO;AACrB,KAAE,WAAW,OAAO;AACpB,KAAE,+BAAc,IAAI,MAAM,EAAC,aAAa;AACxC,KAAE,qBAAqB,OAAO;AAC9B,KAAE,sBAAsB,OAAO;AAC/B,KAAE,oBAAoB,OAAO;AAC7B,KAAE,qBAAqB,OAAO;AAC9B,OAAI,OAAO,MAAO,GAAE,iBAAiB,OAAO;OACvC,QAAO,EAAE;IACd;AACF,SAAO;WACC;AACR,cAAY,MAAM,UAAU;;;;;ACtnBhC,MAAa,WAAW,QAAQ,IAAI,sBAAsB;AAC1D,MAAM,SAAS,oBAAoB,SAAS;AAC5C,MAAM,kBAAkB;AACxB,MAAM,oBAAoB;AAC1B,MAAM,oBAAoB;AAC1B,MAAM,2BAA2B;AAcjC,SAAgB,cAAsC;CACpD,MAAM,IAA4B,EAAE,gBAAgB,oBAAoB;AACxE,KAAI,OAAQ,GAAE,mBAAmB,UAAU;AAC3C,QAAO;;AAGT,SAAgB,YACd,MACA,MACA,SACA,UAAgC,EAAE,EACrB;CACb,MAAM,SAAS,QAAQ,UAAU,gBAAgB,KAAK,GAAG,OAAO;EAAE,GAAG;EAAM,WAAW,KAAK;EAAW;AACtG,QAAO;EACL,GAAG;EACH,SAAS,cAAc,KAAK,UAAU,KAAK,WAAW,QAAQ,WAAW,KAAK,KAAK;EACpF;;AAGH,SAAS,SAAS,QAA6B;AAC7C,KAAI,UAAU,OAAO,SAAS,IAAK,QAAO,WAAW,MAAM,cAAc;AACzE,KAAI,WAAW,OAAO,WAAW,OAAO,UAAU,IAAK,QAAO;AAC9D,QAAO;;AAGT,eAAe,KAAK,MAA+B,WAA6E;CAC9H,MAAM,MAAM,MAAM,MAAM,GAAG,SAAS,uBAAuB;EACzD,QAAQ;EACR,SAAS,aAAa;EACtB,MAAM,KAAK,UAAU,KAAK;EAC1B,QAAQ,YAAY,QAAQ,UAAU;EACvC,CAAC;CACF,MAAM,OAAO,MAAM,IAAI,MAAM,CAAC,YAAY,GAAG;CAC7C,MAAM,UAAU,SAAS,IAAI,OAAO;AACpC,KAAI,YAAY,eAAe,YAAY,YAAa,QAAO;EAAE;EAAS,MAAM;EAAM;AACtF,KAAI;AACF,SAAO;GAAE;GAAS,MAAM,cAAc,KAAK,MAAM,KAAK,CAAC;GAAE;SACnD;AACN,SAAO;GAAE;GAAS,MAAM;GAAM;;;AAIlC,SAAS,iBAAiB,SAAiB,MAA+B,MAAwB;CAChG,MAAM,EAAE,aAAa,cAAc,UAAU,KAAK,OAAO;AACzD,YAAW,UAAU,SAAS,MAAM,KAAK;AACzC,QAAO;;AAGT,SAAS,kBAAwB;AAC/B,KAAI,QAAQ,IAAI,qBAAqB,IAAK;AAC1C,KAAI,CAAC,QAAQ,KAAK,MAAM,gBAAgB,SAAS,CAAE;AACnD,KAAI;EACF,MAAM,QAAQ,MAAM,QAAQ,UAAU,CAAC,QAAQ,KAAK,GAAG,EAAE;GACvD,UAAU;GACV,OAAO;GACP,aAAa;GACb,KAAK;IAAE,GAAG,QAAQ;KAAM,kBAAkB;IAAK;GAChD,CAAC;AACF,QAAM,GAAG,eAAe,GAAG;AAC3B,QAAM,OAAO;SACP;;AAGV,eAAsB,mBAAmB,MAAmB,WAA4C;CACtG,MAAM,UAAU,KAAK,WAAW,cAAc,KAAK,UAAU,KAAK,WAAW,MAAM,KAAK,KAAK;CAC7F,MAAM,UAAU;EAAE,GAAG;EAAM;EAAS;CACpC,IAAI;AACJ,KAAI;EACF,MAAM,EAAE,SAAS,SAAS,MAAM,KAAK,SAAS,UAAU;AACxD,MAAI,YAAY,SAAS;AAEvB,QADiB,OAAO,iBAAiB,SAAS,SAAS,KAAK,GAAG,KACpD,KAAK,gBAAgB,SAAS,CAAE,kBAAiB;AAChE,UAAO;;AAET,WAAS;UACF,KAAK;EACZ,MAAM,OAAO,eAAe,QAAQ,IAAI,OAAO;AAC/C,WAAS,SAAS,kBAAkB,SAAS,eAAe,YAAY;;AAE1E,QAAO,YAAY,UAAU,SAAS,SAAS,OAAO,CAAC,UAAU,YAAY;;AAG/E,SAAgB,eAAwB;AACtC,QAAO,QAAQ,IAAI,qBAAqB;;AAG1C,eAAsB,gBAA+B;AACnD,OAAM,WACJ,UACA,OAAO,WAAwB;EAC7B,MAAM,EAAE,SAAS,SAAS,MAAM,KAAK;GAAE,GAAG,OAAO;GAAM,SAAS,OAAO;GAAS,EAAE,yBAAyB;AAC3G,MAAI,KAAM,YAAW,UAAU,OAAO,SAAS,OAAO,MAAM,KAAK;AACjE,SAAO;IAET;EAAE,YAAY;EAAmB,YAAY;EAAmB,CACjE,CAAC,YAAY,KAAA,EAAU"}
@@ -2,7 +2,7 @@
2
2
  type Json = Record<string, unknown>;
3
3
  declare function normalizePayload(event: string, raw: Json): Json;
4
4
  declare function targetsFor(event: string, raw: Json): string[];
5
- declare function responseFor(event: string): string;
5
+ declare function responseFor(event: string, context?: string): string;
6
6
  //#endregion
7
7
  export { normalizePayload, responseFor, targetsFor };
8
8
  //# sourceMappingURL=antigravity-bridge.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"antigravity-bridge.d.mts","names":[],"sources":["../../src/hooks/antigravity-bridge.ts"],"mappings":";KAkDK,IAAA,GAAO,MAAA;AAAA,iBA2BI,gBAAA,CAAiB,KAAA,UAAe,GAAA,EAAK,IAAA,GAAO,IAAA;AAAA,iBAsD5C,UAAA,CAAW,KAAA,UAAe,GAAA,EAAK,IAAA;AAAA,iBAwB/B,WAAA,CAAY,KAAA"}
1
+ {"version":3,"file":"antigravity-bridge.d.mts","names":[],"sources":["../../src/hooks/antigravity-bridge.ts"],"mappings":";KAmDK,IAAA,GAAO,MAAA;AAAA,iBA2BI,gBAAA,CAAiB,KAAA,UAAe,GAAA,EAAK,IAAA,GAAO,IAAA;AAAA,iBAuD5C,UAAA,CAAW,KAAA,UAAe,GAAA,EAAK,IAAA;AAAA,iBAqB/B,WAAA,CAAY,KAAA,UAAe,OAAA"}
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawnSync } from "node:child_process";
3
- import { dirname, join, resolve } from "node:path";
3
+ import { realpathSync } from "node:fs";
4
+ import { dirname, join } from "node:path";
4
5
  import { fileURLToPath } from "node:url";
5
6
  //#region src/hooks/antigravity-bridge.ts
6
7
  const SCRIPTS_DIR = dirname(fileURLToPath(import.meta.url));
@@ -62,7 +63,8 @@ function normalizePayload(event, raw) {
62
63
  out["native_tool_name"] = rawName;
63
64
  }
64
65
  out["tool_input"] = args;
65
- const result = toolCall["result"] ?? raw["toolResult"] ?? raw["result"];
66
+ if (out["tool_use_id"] === void 0 && Number.isInteger(raw["stepIdx"]) && raw["stepIdx"] >= 0) out["tool_use_id"] = `step:${raw["stepIdx"]}`;
67
+ const result = toolCall["result"] ?? raw["toolResult"] ?? raw["result"] ?? firstString(raw["error"]);
66
68
  if (result !== void 0) out["tool_result"] = result;
67
69
  }
68
70
  return out;
@@ -71,15 +73,16 @@ function targetsFor(event, raw) {
71
73
  switch (event) {
72
74
  case "PreInvocation": {
73
75
  const n = raw["invocationNum"];
74
- return typeof n !== "number" || n <= 1 ? ["session-start.mjs", "prompt-submit.mjs"] : ["prompt-submit.mjs"];
76
+ return typeof n !== "number" || n === 0 ? ["session-start.mjs"] : [];
75
77
  }
76
78
  case "PreToolUse": return ["pre-tool-use.mjs"];
77
79
  case "PostToolUse": return ["post-tool-use.mjs"];
78
- case "Stop": return ["stop.mjs", "session-end.mjs"];
80
+ case "Stop": return raw["fullyIdle"] === false ? [] : ["session-end.mjs"];
79
81
  default: return [];
80
82
  }
81
83
  }
82
- function responseFor(event) {
84
+ function responseFor(event, context = "") {
85
+ if (event === "PreInvocation" && context) return JSON.stringify({ injectSteps: [{ ephemeralMessage: context }] });
83
86
  return event === "PreToolUse" ? "{\"decision\":\"allow\"}" : "{}";
84
87
  }
85
88
  async function main() {
@@ -95,17 +98,23 @@ async function main() {
95
98
  }
96
99
  if (!raw || typeof raw !== "object") return;
97
100
  const payload = JSON.stringify(normalizePayload(event, raw));
98
- for (const script of targetsFor(event, raw)) spawnSync(process.execPath, [join(SCRIPTS_DIR, script)], {
99
- input: payload,
100
- stdio: [
101
- "pipe",
102
- "ignore",
103
- "ignore"
104
- ]
105
- });
101
+ let context = "";
102
+ for (const script of targetsFor(event, raw)) {
103
+ const child = spawnSync(process.execPath, [join(SCRIPTS_DIR, script)], {
104
+ input: payload,
105
+ encoding: "utf8",
106
+ stdio: [
107
+ "pipe",
108
+ "pipe",
109
+ "ignore"
110
+ ]
111
+ });
112
+ if (event === "PreInvocation" && script === "session-start.mjs" && child.status === 0) context = child.stdout?.trim() ?? "";
113
+ }
114
+ return context;
106
115
  }
107
- if (process.argv[1] !== void 0 && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) main().catch(() => {}).finally(() => {
108
- process.stdout.write(responseFor(process.argv[2] ?? ""));
116
+ if (process.argv[1] !== void 0 && realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)) main().catch(() => void 0).then((context) => {
117
+ process.stdout.write(responseFor(process.argv[2] ?? "", context));
109
118
  process.exit(0);
110
119
  });
111
120
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"antigravity-bridge.mjs","names":[],"sources":["../../src/hooks/antigravity-bridge.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { spawnSync } from \"node:child_process\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\n// Antigravity CLI (`agy`) bridge — sits in front of the canonical hooks\n// because three parts of agy's contract make them unusable as direct\n// `command` targets: only five events exist (no SessionStart/SessionEnd/\n// UserPromptSubmit, so the lifecycle is synthesized from PreInvocation and\n// Stop); the payload is camelCase and nests tool calls under `toolCall`; and\n// stdout must be a JSON object, which `pre-tool-use.mjs` breaks when\n// AGENTMEMORY_INJECT_CONTEXT=true makes it write raw context text.\n//\n// Invoked as: node antigravity-bridge.mjs <PreInvocation|PreToolUse|PostToolUse|Stop>\n// Sources: antigravity.google/docs/hooks, antigravity.google/docs/cli/using\n\nconst SCRIPTS_DIR = dirname(fileURLToPath(import.meta.url));\n\n// Antigravity inherits Cascade-style tool names. Map them onto the tool\n// vocabulary pre-tool-use.ts / post-tool-use.ts already understand so the\n// existing file-activity heuristics keep working unchanged.\nconst TOOL_NAME_MAP: Record<string, string> = {\n view_file: \"read\",\n view_line_range: \"read\",\n view_code_item: \"read\",\n read_file: \"read\",\n read_url_content: \"read\",\n edit_file: \"edit\",\n replace_file_content: \"edit\",\n propose_code: \"edit\",\n write_to_file: \"write\",\n create_file: \"write\",\n grep_search: \"grep\",\n codebase_search: \"grep\",\n find_by_name: \"glob\",\n list_dir: \"glob\",\n};\n\n// Antigravity tool args are PascalCase; the canonical hooks look for\n// snake_case keys. Only the keys those hooks actually read are mapped.\nconst ARG_KEY_MAP: Record<string, string> = {\n AbsolutePath: \"file_path\",\n TargetFile: \"file_path\",\n DirectoryPath: \"path\",\n SearchDirectory: \"path\",\n Pattern: \"pattern\",\n Query: \"pattern\",\n CommandLine: \"command\",\n};\n\ntype Json = Record<string, unknown>;\n\nfunction asObject(value: unknown): Json | undefined {\n return typeof value === \"object\" && value !== null && !Array.isArray(value)\n ? (value as Json)\n : undefined;\n}\n\nfunction firstString(...values: unknown[]): string | undefined {\n for (const v of values) {\n if (typeof v === \"string\" && v.length > 0) return v;\n }\n return undefined;\n}\n\nfunction normalizeToolArgs(args: Json | undefined): Json {\n if (!args) return {};\n const out: Json = { ...args };\n for (const [from, to] of Object.entries(ARG_KEY_MAP)) {\n if (out[to] === undefined && args[from] !== undefined) out[to] = args[from];\n }\n return out;\n}\n\n// Translate one Antigravity hook payload into the flat, snake_case shape the\n// bundled hooks consume. Unknown fields pass through so future Antigravity\n// additions stay visible to the capture pipeline.\nexport function normalizePayload(event: string, raw: Json): Json {\n const toolCall = asObject(raw[\"toolCall\"]);\n const workspacePaths = Array.isArray(raw[\"workspacePaths\"])\n ? (raw[\"workspacePaths\"] as unknown[])\n : [];\n\n const sessionId =\n firstString(raw[\"conversationId\"], raw[\"session_id\"], raw[\"sessionId\"]) ??\n \"unknown\";\n const cwd =\n firstString(raw[\"cwd\"], workspacePaths[0]) ?? process.cwd();\n\n const out: Json = {\n ...raw,\n session_id: sessionId,\n cwd,\n hook_event_name: event,\n };\n\n const transcriptPath = firstString(\n raw[\"transcript_path\"],\n raw[\"transcriptPath\"],\n );\n if (transcriptPath) out[\"transcript_path\"] = transcriptPath;\n\n if (toolCall) {\n const args = normalizeToolArgs(\n asObject(toolCall[\"args\"]) ?? asObject(toolCall[\"toolArgs\"]),\n );\n const rawName = firstString(\n toolCall[\"name\"],\n toolCall[\"toolName\"],\n args[\"ToolName\"],\n args[\"toolName\"],\n );\n if (rawName) {\n out[\"tool_name\"] = TOOL_NAME_MAP[rawName] ?? rawName;\n // Keep the host-native name so captured observations stay traceable\n // back to the tool Antigravity actually ran.\n out[\"native_tool_name\"] = rawName;\n }\n out[\"tool_input\"] = args;\n const result = toolCall[\"result\"] ?? raw[\"toolResult\"] ?? raw[\"result\"];\n if (result !== undefined) out[\"tool_result\"] = result;\n }\n\n return out;\n}\n\n// Map an Antigravity event to the bundled scripts it should drive.\n// PreInvocation stands in for both SessionStart and UserPromptSubmit: the\n// first invocation of a conversation opens the session, every later one is a\n// fresh user turn. PostInvocation is deliberately unmapped — PostToolUse\n// already captures the work, and firing again would double-record it.\nexport function targetsFor(event: string, raw: Json): string[] {\n switch (event) {\n case \"PreInvocation\": {\n const n = raw[\"invocationNum\"];\n const isFirst = typeof n !== \"number\" || n <= 1;\n return isFirst\n ? [\"session-start.mjs\", \"prompt-submit.mjs\"]\n : [\"prompt-submit.mjs\"];\n }\n case \"PreToolUse\":\n return [\"pre-tool-use.mjs\"];\n case \"PostToolUse\":\n return [\"post-tool-use.mjs\"];\n case \"Stop\":\n return [\"stop.mjs\", \"session-end.mjs\"];\n default:\n return [];\n }\n}\n\n// The stdout contract, per event. agy treats a PreToolUse response without\n// `decision` as a denial, so a bare `{}` there makes it refuse every matched\n// tool call (verified on 1.0.15). No other event carries a permission\n// decision, so they stay on `{}` — sending one would override user settings.\nexport function responseFor(event: string): string {\n return event === \"PreToolUse\" ? '{\"decision\":\"allow\"}' : \"{}\";\n}\n\nasync function main() {\n const event = process.argv[2];\n if (!event) return;\n\n let input = \"\";\n for await (const chunk of process.stdin) {\n input += chunk;\n }\n\n let raw: Json;\n try {\n raw = JSON.parse(input) as Json;\n } catch {\n return;\n }\n if (!raw || typeof raw !== \"object\") return;\n\n const payload = JSON.stringify(normalizePayload(event, raw));\n\n for (const script of targetsFor(event, raw)) {\n // Synchronous so the hook process does not exit before the capture\n // POSTs are issued. Each bundled hook already caps its own fetch\n // timeout, so the worst case here is bounded by those.\n spawnSync(process.execPath, [join(SCRIPTS_DIR, script)], {\n input: payload,\n // Child stdout is discarded on purpose: Antigravity parses this\n // process's stdout as the hook response, and the bundled scripts\n // emit prose when context injection is enabled.\n stdio: [\"pipe\", \"ignore\", \"ignore\"],\n });\n }\n}\n\n// Guarded so the pure helpers above stay importable from tests without the\n// module blocking on stdin. Every other bundled hook is a leaf script and\n// needs no such guard.\nconst invokedDirectly =\n process.argv[1] !== undefined &&\n resolve(process.argv[1]) === fileURLToPath(import.meta.url);\n\nif (invokedDirectly) {\n main()\n .catch(() => {})\n .finally(() => {\n // Always a well-formed, non-blocking response, emitted even when the\n // capture above threw or the payload was unparseable — a hook that\n // writes nothing is as fatal to PreToolUse as one that writes `{}`.\n process.stdout.write(responseFor(process.argv[2] ?? \"\"));\n process.exit(0);\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"antigravity-bridge.mjs","names":[],"sources":["../../src/hooks/antigravity-bridge.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { spawnSync } from \"node:child_process\";\nimport { realpathSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\n// Antigravity CLI (`agy`) bridge — sits in front of the canonical hooks\n// because three parts of agy's contract make them unusable as direct\n// `command` targets: only five events exist (no SessionStart/SessionEnd/\n// UserPromptSubmit, so session boundaries use PreInvocation and Stop and\n// prompts come from the transcript); tool calls are nested under `toolCall`; and\n// stdout must be a JSON object, which `pre-tool-use.mjs` breaks when\n// AGENTMEMORY_INJECT_CONTEXT=true makes it write raw context text.\n//\n// Invoked as: node antigravity-bridge.mjs <PreInvocation|PreToolUse|PostToolUse|Stop>\n// Sources: antigravity.google/docs/hooks, antigravity.google/docs/cli/using\n\nconst SCRIPTS_DIR = dirname(fileURLToPath(import.meta.url));\n\n// Antigravity inherits Cascade-style tool names. Map them onto the tool\n// vocabulary pre-tool-use.ts / post-tool-use.ts already understand so the\n// existing file-activity heuristics keep working unchanged.\nconst TOOL_NAME_MAP: Record<string, string> = {\n view_file: \"read\",\n view_line_range: \"read\",\n view_code_item: \"read\",\n read_file: \"read\",\n read_url_content: \"read\",\n edit_file: \"edit\",\n replace_file_content: \"edit\",\n propose_code: \"edit\",\n write_to_file: \"write\",\n create_file: \"write\",\n grep_search: \"grep\",\n codebase_search: \"grep\",\n find_by_name: \"glob\",\n list_dir: \"glob\",\n};\n\n// Antigravity tool args are PascalCase; the canonical hooks look for\n// snake_case keys. Only the keys those hooks actually read are mapped.\nconst ARG_KEY_MAP: Record<string, string> = {\n AbsolutePath: \"file_path\",\n TargetFile: \"file_path\",\n DirectoryPath: \"path\",\n SearchDirectory: \"path\",\n Pattern: \"pattern\",\n Query: \"pattern\",\n CommandLine: \"command\",\n};\n\ntype Json = Record<string, unknown>;\n\nfunction asObject(value: unknown): Json | undefined {\n return typeof value === \"object\" && value !== null && !Array.isArray(value)\n ? (value as Json)\n : undefined;\n}\n\nfunction firstString(...values: unknown[]): string | undefined {\n for (const v of values) {\n if (typeof v === \"string\" && v.length > 0) return v;\n }\n return undefined;\n}\n\nfunction normalizeToolArgs(args: Json | undefined): Json {\n if (!args) return {};\n const out: Json = { ...args };\n for (const [from, to] of Object.entries(ARG_KEY_MAP)) {\n if (out[to] === undefined && args[from] !== undefined) out[to] = args[from];\n }\n return out;\n}\n\n// Translate one Antigravity hook payload into the flat, snake_case shape the\n// bundled hooks consume. Unknown fields pass through so future Antigravity\n// additions stay visible to the capture pipeline.\nexport function normalizePayload(event: string, raw: Json): Json {\n const toolCall = asObject(raw[\"toolCall\"]);\n const workspacePaths = Array.isArray(raw[\"workspacePaths\"])\n ? (raw[\"workspacePaths\"] as unknown[])\n : [];\n\n const sessionId =\n firstString(raw[\"conversationId\"], raw[\"session_id\"], raw[\"sessionId\"]) ??\n \"unknown\";\n const cwd =\n firstString(raw[\"cwd\"], workspacePaths[0]) ?? process.cwd();\n\n const out: Json = {\n ...raw,\n session_id: sessionId,\n cwd,\n hook_event_name: event,\n };\n\n const transcriptPath = firstString(\n raw[\"transcript_path\"],\n raw[\"transcriptPath\"],\n );\n if (transcriptPath) out[\"transcript_path\"] = transcriptPath;\n\n if (toolCall) {\n const args = normalizeToolArgs(\n asObject(toolCall[\"args\"]) ?? asObject(toolCall[\"toolArgs\"]),\n );\n const rawName = firstString(\n toolCall[\"name\"],\n toolCall[\"toolName\"],\n args[\"ToolName\"],\n args[\"toolName\"],\n );\n if (rawName) {\n out[\"tool_name\"] = TOOL_NAME_MAP[rawName] ?? rawName;\n // Keep the host-native name so captured observations stay traceable\n // back to the tool Antigravity actually ran.\n out[\"native_tool_name\"] = rawName;\n }\n out[\"tool_input\"] = args;\n if (out[\"tool_use_id\"] === undefined && Number.isInteger(raw[\"stepIdx\"]) && (raw[\"stepIdx\"] as number) >= 0) {\n out[\"tool_use_id\"] = `step:${raw[\"stepIdx\"]}`;\n }\n const result = toolCall[\"result\"] ?? raw[\"toolResult\"] ?? raw[\"result\"] ?? firstString(raw[\"error\"]);\n if (result !== undefined) out[\"tool_result\"] = result;\n }\n\n return out;\n}\n\n// Map an Antigravity event to the bundled scripts it should drive.\n// PreInvocation fires per model call, not per user prompt. Antigravity omits\n// prompt text from that payload; session-end backfills it from the transcript.\nexport function targetsFor(event: string, raw: Json): string[] {\n switch (event) {\n case \"PreInvocation\": {\n const n = raw[\"invocationNum\"];\n return typeof n !== \"number\" || n === 0 ? [\"session-start.mjs\"] : [];\n }\n case \"PreToolUse\":\n return [\"pre-tool-use.mjs\"];\n case \"PostToolUse\":\n return [\"post-tool-use.mjs\"];\n case \"Stop\":\n return raw[\"fullyIdle\"] === false ? [] : [\"session-end.mjs\"];\n default:\n return [];\n }\n}\n\n// The stdout contract, per event. agy treats a PreToolUse response without\n// `decision` as a denial, so a bare `{}` there makes it refuse every matched\n// tool call (verified on 1.0.15). No other event carries a permission\n// decision. PreInvocation can inject recalled context as an ephemeral step.\nexport function responseFor(event: string, context = \"\"): string {\n if (event === \"PreInvocation\" && context) {\n return JSON.stringify({ injectSteps: [{ ephemeralMessage: context }] });\n }\n return event === \"PreToolUse\" ? '{\"decision\":\"allow\"}' : \"{}\";\n}\n\nasync function main() {\n const event = process.argv[2];\n if (!event) return;\n\n let input = \"\";\n for await (const chunk of process.stdin) {\n input += chunk;\n }\n\n let raw: Json;\n try {\n raw = JSON.parse(input) as Json;\n } catch {\n return;\n }\n if (!raw || typeof raw !== \"object\") return;\n\n const payload = JSON.stringify(normalizePayload(event, raw));\n let context = \"\";\n\n for (const script of targetsFor(event, raw)) {\n // Synchronous so the hook process does not exit before the capture\n // POSTs are issued. Each bundled hook already caps its own fetch\n // timeout, so the worst case here is bounded by those.\n const child = spawnSync(process.execPath, [join(SCRIPTS_DIR, script)], {\n input: payload,\n encoding: \"utf8\",\n stdio: [\"pipe\", \"pipe\", \"ignore\"],\n });\n if (event === \"PreInvocation\" && script === \"session-start.mjs\" && child.status === 0) {\n context = child.stdout?.trim() ?? \"\";\n }\n }\n return context;\n}\n\n// Guarded so the pure helpers above stay importable from tests without the\n// module blocking on stdin. Every other bundled hook is a leaf script and\n// needs no such guard.\nconst invokedDirectly =\n process.argv[1] !== undefined &&\n realpathSync(process.argv[1]) === fileURLToPath(import.meta.url);\n\nif (invokedDirectly) {\n main()\n .catch(() => undefined)\n .then((context) => {\n // Always a well-formed, non-blocking response, emitted even when the\n // capture above threw or the payload was unparseable — a hook that\n // writes nothing is as fatal to PreToolUse as one that writes `{}`.\n process.stdout.write(responseFor(process.argv[2] ?? \"\", context));\n process.exit(0);\n });\n}\n"],"mappings":";;;;;;AAiBA,MAAM,cAAc,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;AAK3D,MAAM,gBAAwC;CAC5C,WAAW;CACX,iBAAiB;CACjB,gBAAgB;CAChB,WAAW;CACX,kBAAkB;CAClB,WAAW;CACX,sBAAsB;CACtB,cAAc;CACd,eAAe;CACf,aAAa;CACb,aAAa;CACb,iBAAiB;CACjB,cAAc;CACd,UAAU;CACX;AAID,MAAM,cAAsC;CAC1C,cAAc;CACd,YAAY;CACZ,eAAe;CACf,iBAAiB;CACjB,SAAS;CACT,OAAO;CACP,aAAa;CACd;AAID,SAAS,SAAS,OAAkC;AAClD,QAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,MAAM,GACtE,QACD,KAAA;;AAGN,SAAS,YAAY,GAAG,QAAuC;AAC7D,MAAK,MAAM,KAAK,OACd,KAAI,OAAO,MAAM,YAAY,EAAE,SAAS,EAAG,QAAO;;AAKtD,SAAS,kBAAkB,MAA8B;AACvD,KAAI,CAAC,KAAM,QAAO,EAAE;CACpB,MAAM,MAAY,EAAE,GAAG,MAAM;AAC7B,MAAK,MAAM,CAAC,MAAM,OAAO,OAAO,QAAQ,YAAY,CAClD,KAAI,IAAI,QAAQ,KAAA,KAAa,KAAK,UAAU,KAAA,EAAW,KAAI,MAAM,KAAK;AAExE,QAAO;;AAMT,SAAgB,iBAAiB,OAAe,KAAiB;CAC/D,MAAM,WAAW,SAAS,IAAI,YAAY;CAC1C,MAAM,iBAAiB,MAAM,QAAQ,IAAI,kBAAkB,GACtD,IAAI,oBACL,EAAE;CAEN,MAAM,YACJ,YAAY,IAAI,mBAAmB,IAAI,eAAe,IAAI,aAAa,IACvE;CACF,MAAM,MACJ,YAAY,IAAI,QAAQ,eAAe,GAAG,IAAI,QAAQ,KAAK;CAE7D,MAAM,MAAY;EAChB,GAAG;EACH,YAAY;EACZ;EACA,iBAAiB;EAClB;CAED,MAAM,iBAAiB,YACrB,IAAI,oBACJ,IAAI,kBACL;AACD,KAAI,eAAgB,KAAI,qBAAqB;AAE7C,KAAI,UAAU;EACZ,MAAM,OAAO,kBACX,SAAS,SAAS,QAAQ,IAAI,SAAS,SAAS,YAAY,CAC7D;EACD,MAAM,UAAU,YACd,SAAS,SACT,SAAS,aACT,KAAK,aACL,KAAK,YACN;AACD,MAAI,SAAS;AACX,OAAI,eAAe,cAAc,YAAY;AAG7C,OAAI,sBAAsB;;AAE5B,MAAI,gBAAgB;AACpB,MAAI,IAAI,mBAAmB,KAAA,KAAa,OAAO,UAAU,IAAI,WAAW,IAAK,IAAI,cAAyB,EACxG,KAAI,iBAAiB,QAAQ,IAAI;EAEnC,MAAM,SAAS,SAAS,aAAa,IAAI,iBAAiB,IAAI,aAAa,YAAY,IAAI,SAAS;AACpG,MAAI,WAAW,KAAA,EAAW,KAAI,iBAAiB;;AAGjD,QAAO;;AAMT,SAAgB,WAAW,OAAe,KAAqB;AAC7D,SAAQ,OAAR;EACE,KAAK,iBAAiB;GACpB,MAAM,IAAI,IAAI;AACd,UAAO,OAAO,MAAM,YAAY,MAAM,IAAI,CAAC,oBAAoB,GAAG,EAAE;;EAEtE,KAAK,aACH,QAAO,CAAC,mBAAmB;EAC7B,KAAK,cACH,QAAO,CAAC,oBAAoB;EAC9B,KAAK,OACH,QAAO,IAAI,iBAAiB,QAAQ,EAAE,GAAG,CAAC,kBAAkB;EAC9D,QACE,QAAO,EAAE;;;AAQf,SAAgB,YAAY,OAAe,UAAU,IAAY;AAC/D,KAAI,UAAU,mBAAmB,QAC/B,QAAO,KAAK,UAAU,EAAE,aAAa,CAAC,EAAE,kBAAkB,SAAS,CAAC,EAAE,CAAC;AAEzE,QAAO,UAAU,eAAe,6BAAyB;;AAG3D,eAAe,OAAO;CACpB,MAAM,QAAQ,QAAQ,KAAK;AAC3B,KAAI,CAAC,MAAO;CAEZ,IAAI,QAAQ;AACZ,YAAW,MAAM,SAAS,QAAQ,MAChC,UAAS;CAGX,IAAI;AACJ,KAAI;AACF,QAAM,KAAK,MAAM,MAAM;SACjB;AACN;;AAEF,KAAI,CAAC,OAAO,OAAO,QAAQ,SAAU;CAErC,MAAM,UAAU,KAAK,UAAU,iBAAiB,OAAO,IAAI,CAAC;CAC5D,IAAI,UAAU;AAEd,MAAK,MAAM,UAAU,WAAW,OAAO,IAAI,EAAE;EAI3C,MAAM,QAAQ,UAAU,QAAQ,UAAU,CAAC,KAAK,aAAa,OAAO,CAAC,EAAE;GACrE,OAAO;GACP,UAAU;GACV,OAAO;IAAC;IAAQ;IAAQ;IAAS;GAClC,CAAC;AACF,MAAI,UAAU,mBAAmB,WAAW,uBAAuB,MAAM,WAAW,EAClF,WAAU,MAAM,QAAQ,MAAM,IAAI;;AAGtC,QAAO;;AAUT,IAHE,QAAQ,KAAK,OAAO,KAAA,KACpB,aAAa,QAAQ,KAAK,GAAG,KAAK,cAAc,OAAO,KAAK,IAAI,CAGhE,OAAM,CACH,YAAY,KAAA,EAAU,CACtB,MAAM,YAAY;AAIjB,SAAQ,OAAO,MAAM,YAAY,QAAQ,KAAK,MAAM,IAAI,QAAQ,CAAC;AACjE,SAAQ,KAAK,EAAE;EACf"}
@@ -1,6 +1,7 @@
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";
4
5
  //#region src/hooks/_project.ts
5
6
  function resolveProject(cwd) {
6
7
  const explicit = process.env["AGENTMEMORY_PROJECT_NAME"];
@@ -37,14 +38,10 @@ function isSdkChildContext(payload) {
37
38
  if (!payload || typeof payload !== "object") return false;
38
39
  return payload.entrypoint === "sdk-ts";
39
40
  }
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
- }
41
+ const OBSERVE_TIMEOUT_MS = 2e3;
42
+ const EXIT_CAP_MS = 2500;
47
43
  async function main() {
44
+ if (isDrainChild()) return runDrainChild();
48
45
  let input = "";
49
46
  for await (const chunk of process.stdin) input += chunk;
50
47
  let data;
@@ -64,24 +61,19 @@ async function main() {
64
61
  ].find((v) => typeof v === "string" && v.length > 0);
65
62
  const sessionId = typeof rawSessionId === "string" ? rawSessionId : "unknown";
66
63
  const cwd = hookCwd(data) || process.cwd();
67
- fetch(`${REST_URL}/agentmemory/observe`, {
68
- method: "POST",
69
- headers: authHeaders(),
70
- body: JSON.stringify({
71
- hookType: "notification",
72
- sessionId,
73
- project: resolveProject(cwd),
74
- cwd,
75
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
76
- data: {
77
- notification_type: notificationType,
78
- title: data.title,
79
- message: data.message
80
- }
81
- }),
82
- signal: AbortSignal.timeout(2e3)
83
- }).catch(() => {});
84
- setTimeout(() => process.exit(0), 500).unref();
64
+ captureObservation(withEventId({
65
+ hookType: "notification",
66
+ sessionId,
67
+ project: resolveProject(cwd),
68
+ cwd,
69
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
70
+ data: {
71
+ notification_type: notificationType,
72
+ title: data.title,
73
+ message: data.message
74
+ }
75
+ }, data), OBSERVE_TIMEOUT_MS);
76
+ setTimeout(() => process.exit(0), EXIT_CAP_MS).unref();
85
77
  }
86
78
  main().catch(() => process.exit(0));
87
79
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"notification.mjs","names":[],"sources":["../../src/hooks/_project.ts","../../src/hooks/notification.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 const notificationType = data.notification_type ?? data.notificationType;\n if (notificationType !== \"permission_prompt\") return;\n\n const rawSessionId = [data.session_id, data.sessionId, data.conversation_id].find(\n (v) => typeof v === \"string\" && v.length > 0,\n );\n const sessionId = typeof rawSessionId === \"string\" ? rawSessionId : \"unknown\";\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: \"notification\",\n sessionId,\n project: resolveProject(cwd),\n cwd,\n timestamp: new Date().toISOString(),\n data: {\n notification_type: notificationType,\n title: data.title,\n message: data.message,\n },\n }),\n signal: AbortSignal.timeout(2000),\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;CAC7B,MAAM,mBAAmB,KAAK,qBAAqB,KAAK;AACxD,KAAI,qBAAqB,oBAAqB;CAE9C,MAAM,eAAe;EAAC,KAAK;EAAY,KAAK;EAAW,KAAK;EAAgB,CAAC,MAC1E,MAAM,OAAO,MAAM,YAAY,EAAE,SAAS,EAC5C;CACD,MAAM,YAAY,OAAO,iBAAiB,WAAW,eAAe;CAEpE,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,mBAAmB;IACnB,OAAO,KAAK;IACZ,SAAS,KAAK;IACf;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":"notification.mjs","names":[],"sources":["../../src/hooks/_project.ts","../../src/hooks/notification.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\";\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 = 2000;\nconst EXIT_CAP_MS = 2500;\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 const notificationType = data.notification_type ?? data.notificationType;\n if (notificationType !== \"permission_prompt\") return;\n\n const rawSessionId = [data.session_id, data.sessionId, data.conversation_id].find(\n (v) => typeof v === \"string\" && v.length > 0,\n );\n const sessionId = typeof rawSessionId === \"string\" ? rawSessionId : \"unknown\";\n\n const cwd = hookCwd(data) || process.cwd();\n\n void captureObservation(\n withEventId(\n {\n hookType: \"notification\",\n sessionId,\n project: resolveProject(cwd),\n cwd,\n timestamp: new Date().toISOString(),\n data: {\n notification_type: notificationType,\n title: data.title,\n message: data.message,\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":";;;;;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;;;;AC5B9C,SAAS,kBAAkB,SAA2B;AACpD,KAAI,QAAQ,IAAI,6BAA6B,IAAK,QAAO;AACzD,KAAI,CAAC,WAAW,OAAO,YAAY,SAAU,QAAO;AACpD,QAAQ,QAAqC,eAAe;;AAG9D,MAAM,qBAAqB;AAC3B,MAAM,cAAc;AAEpB,eAAe,OAAO;AACpB,KAAI,cAAc,CAAE,QAAO,eAAe;CAC1C,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;CAC7B,MAAM,mBAAmB,KAAK,qBAAqB,KAAK;AACxD,KAAI,qBAAqB,oBAAqB;CAE9C,MAAM,eAAe;EAAC,KAAK;EAAY,KAAK;EAAW,KAAK;EAAgB,CAAC,MAC1E,MAAM,OAAO,MAAM,YAAY,EAAE,SAAS,EAC5C;CACD,MAAM,YAAY,OAAO,iBAAiB,WAAW,eAAe;CAEpE,MAAM,MAAM,QAAQ,KAAK,IAAI,QAAQ,KAAK;AAErC,oBACH,YACE;EACE,UAAU;EACV;EACA,SAAS,eAAe,IAAI;EAC5B;EACA,4BAAW,IAAI,MAAM,EAAC,aAAa;EACnC,MAAM;GACJ,mBAAmB;GACnB,OAAO,KAAK;GACZ,SAAS,KAAK;GACf;EACF,EACD,KACD,EACD,mBACD;AACD,kBAAiB,QAAQ,KAAK,EAAE,EAAE,YAAY,CAAC,OAAO;;AAGxD,MAAM,CAAC,YAAY,QAAQ,KAAK,EAAE,CAAC"}
@@ -1,6 +1,76 @@
1
1
  #!/usr/bin/env node
2
+ import { readFileSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { join } from "node:path";
2
5
  import { execFile } from "node:child_process";
3
6
  import { promisify } from "node:util";
7
+ //#region src/secret-store.ts
8
+ const SECRET_KEY = "AGENTMEMORY_SECRET";
9
+ function agentmemoryHomeDir() {
10
+ return join(homedir(), ".agentmemory");
11
+ }
12
+ function secretFilePath() {
13
+ return join(agentmemoryHomeDir(), "secret");
14
+ }
15
+ function usable(value) {
16
+ if (typeof value !== "string") return "";
17
+ const trimmed = value.trim();
18
+ if (!trimmed) return "";
19
+ if (trimmed.startsWith("${") && trimmed.endsWith("}")) return "";
20
+ return trimmed;
21
+ }
22
+ function unquote(value) {
23
+ const quote = value[0];
24
+ if ((quote === "\"" || quote === "'") && value.length > 1) {
25
+ const close = value.indexOf(quote, 1);
26
+ if (close !== -1) return value.slice(1, close);
27
+ }
28
+ const hash = value.indexOf(" #");
29
+ return hash === -1 ? value : value.slice(0, hash).trim();
30
+ }
31
+ function readEnvFileSecret() {
32
+ let content;
33
+ try {
34
+ content = readFileSync(join(agentmemoryHomeDir(), ".env"), "utf-8");
35
+ } catch {
36
+ return "";
37
+ }
38
+ if (typeof content !== "string") return "";
39
+ let found = "";
40
+ for (const line of content.split("\n")) {
41
+ const trimmed = line.trim();
42
+ if (trimmed.startsWith("#")) continue;
43
+ const eq = trimmed.indexOf("=");
44
+ if (eq === -1) continue;
45
+ if (trimmed.slice(0, eq).replace(/^export\s+/, "").trim() !== SECRET_KEY) continue;
46
+ found = usable(unquote(trimmed.slice(eq + 1).trim()));
47
+ }
48
+ return found;
49
+ }
50
+ function readStoredSecret() {
51
+ try {
52
+ return usable(readFileSync(secretFilePath(), "utf-8"));
53
+ } catch {
54
+ return "";
55
+ }
56
+ }
57
+ function isLoopbackUrl(url) {
58
+ let hostname;
59
+ try {
60
+ hostname = new URL(url).hostname.toLowerCase();
61
+ } catch {
62
+ return false;
63
+ }
64
+ const bare = hostname.replace(/^\[|\]$/g, "");
65
+ return bare === "localhost" || bare === "::1" || /^127(?:\.\d{1,3}){3}$/.test(bare);
66
+ }
67
+ function resolveClientSecret(baseUrl, env = process.env) {
68
+ const fromEnv = usable(env[SECRET_KEY]);
69
+ if (fromEnv) return fromEnv;
70
+ if (!isLoopbackUrl(baseUrl)) return "";
71
+ return readEnvFileSecret() || readStoredSecret();
72
+ }
73
+ //#endregion
4
74
  //#region src/hooks/_project.ts
5
75
  function hookCwd(data) {
6
76
  if (!data || typeof data !== "object") return void 0;
@@ -21,7 +91,7 @@ function isSdkChildContext(payload) {
21
91
  return payload.entrypoint === "sdk-ts";
22
92
  }
23
93
  const REST_URL = process.env["AGENTMEMORY_URL"] || "http://localhost:3111";
24
- const SECRET = process.env["AGENTMEMORY_SECRET"] || "";
94
+ const SECRET = resolveClientSecret(REST_URL);
25
95
  const TIMEOUT_MS = 1500;
26
96
  function authHeaders() {
27
97
  const h = { "Content-Type": "application/json" };
@@ -1 +1 @@
1
- {"version":3,"file":"post-commit.mjs","names":[],"sources":["../../src/hooks/_project.ts","../../src/hooks/post-commit.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\n\nimport { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport { hookCwd } from \"./_project.js\";\n\nconst exec = promisify(execFile);\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\"] || \"\";\nconst TIMEOUT_MS = 1500;\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 git(args: string[], cwd: string): Promise<string | null> {\n try {\n const { stdout } = await exec(\"git\", args, { cwd, timeout: 1500 });\n return stdout.trim();\n } catch {\n return null;\n }\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 if (input.trim()) {\n try {\n data = JSON.parse(input);\n } catch {\n // Direct invocation from .git/hooks/post-commit may pass no stdin.\n }\n }\n\n if (!data || typeof data !== \"object\") data = {};\n if (isSdkChildContext(data)) return;\n\n const cwd =\n hookCwd(data) || process.env[\"AGENTMEMORY_CWD\"] || process.cwd();\n const sessionId =\n (data.session_id as string) ||\n process.env[\"AGENTMEMORY_SESSION_ID\"] ||\n undefined;\n\n const sha =\n process.env[\"AGENTMEMORY_COMMIT_SHA\"] ||\n (await git([\"rev-parse\", \"HEAD\"], cwd));\n if (!sha) return;\n\n const branch = await git([\"rev-parse\", \"--abbrev-ref\", \"HEAD\"], cwd);\n const repo = await git([\"config\", \"--get\", \"remote.origin.url\"], cwd);\n const message = await git([\"log\", \"-1\", \"--pretty=%B\", sha], cwd);\n const author = await git([\"log\", \"-1\", \"--pretty=%an <%ae>\", sha], cwd);\n const authoredAt = await git([\"log\", \"-1\", \"--pretty=%aI\", sha], cwd);\n const filesRaw = await git(\n [\"diff-tree\", \"--no-commit-id\", \"--name-only\", \"-r\", sha],\n cwd,\n );\n const files = filesRaw ? filesRaw.split(\"\\n\").filter(Boolean) : undefined;\n\n const body = {\n sessionId,\n sha,\n branch: branch || undefined,\n repo: repo || undefined,\n message: message || undefined,\n author: author || undefined,\n authoredAt: authoredAt || undefined,\n files,\n };\n\n try {\n await fetch(`${REST_URL}/agentmemory/session/commit`, {\n method: \"POST\",\n headers: authHeaders(),\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(TIMEOUT_MS),\n });\n } catch {\n // best-effort\n }\n}\n\nmain().catch(() => process.exit(0));\n"],"mappings":";;;;AAqBA,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;;;;AC1B9C,MAAM,OAAO,UAAU,SAAS;AAEhC,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;AACpD,MAAM,aAAa;AAEnB,SAAS,cAAsC;CAC7C,MAAM,IAA4B,EAAE,gBAAgB,oBAAoB;AACxE,KAAI,OAAQ,GAAE,mBAAmB,UAAU;AAC3C,QAAO;;AAGT,eAAe,IAAI,MAAgB,KAAqC;AACtE,KAAI;EACF,MAAM,EAAE,WAAW,MAAM,KAAK,OAAO,MAAM;GAAE;GAAK,SAAS;GAAM,CAAC;AAClE,SAAO,OAAO,MAAM;SACd;AACN,SAAO;;;AAIX,eAAe,OAAO;CACpB,IAAI,QAAQ;AACZ,YAAW,MAAM,SAAS,QAAQ,MAChC,UAAS;CAGX,IAAI,OAAgC,EAAE;AACtC,KAAI,MAAM,MAAM,CACd,KAAI;AACF,SAAO,KAAK,MAAM,MAAM;SAClB;AAKV,KAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO,EAAE;AAChD,KAAI,kBAAkB,KAAK,CAAE;CAE7B,MAAM,MACJ,QAAQ,KAAK,IAAI,QAAQ,IAAI,sBAAsB,QAAQ,KAAK;CAClE,MAAM,YACH,KAAK,cACN,QAAQ,IAAI,6BACZ,KAAA;CAEF,MAAM,MACJ,QAAQ,IAAI,6BACX,MAAM,IAAI,CAAC,aAAa,OAAO,EAAE,IAAI;AACxC,KAAI,CAAC,IAAK;CAEV,MAAM,SAAS,MAAM,IAAI;EAAC;EAAa;EAAgB;EAAO,EAAE,IAAI;CACpE,MAAM,OAAO,MAAM,IAAI;EAAC;EAAU;EAAS;EAAoB,EAAE,IAAI;CACrE,MAAM,UAAU,MAAM,IAAI;EAAC;EAAO;EAAM;EAAe;EAAI,EAAE,IAAI;CACjE,MAAM,SAAS,MAAM,IAAI;EAAC;EAAO;EAAM;EAAsB;EAAI,EAAE,IAAI;CACvE,MAAM,aAAa,MAAM,IAAI;EAAC;EAAO;EAAM;EAAgB;EAAI,EAAE,IAAI;CACrE,MAAM,WAAW,MAAM,IACrB;EAAC;EAAa;EAAkB;EAAe;EAAM;EAAI,EACzD,IACD;CACD,MAAM,QAAQ,WAAW,SAAS,MAAM,KAAK,CAAC,OAAO,QAAQ,GAAG,KAAA;CAEhE,MAAM,OAAO;EACX;EACA;EACA,QAAQ,UAAU,KAAA;EAClB,MAAM,QAAQ,KAAA;EACd,SAAS,WAAW,KAAA;EACpB,QAAQ,UAAU,KAAA;EAClB,YAAY,cAAc,KAAA;EAC1B;EACD;AAED,KAAI;AACF,QAAM,MAAM,GAAG,SAAS,8BAA8B;GACpD,QAAQ;GACR,SAAS,aAAa;GACtB,MAAM,KAAK,UAAU,KAAK;GAC1B,QAAQ,YAAY,QAAQ,WAAW;GACxC,CAAC;SACI;;AAKV,MAAM,CAAC,YAAY,QAAQ,KAAK,EAAE,CAAC"}
1
+ {"version":3,"file":"post-commit.mjs","names":[],"sources":["../../src/secret-store.ts","../../src/hooks/_project.ts","../../src/hooks/post-commit.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","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\";\n\nimport { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport { hookCwd } from \"./_project.js\";\n\nconst exec = promisify(execFile);\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);\nconst TIMEOUT_MS = 1500;\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 git(args: string[], cwd: string): Promise<string | null> {\n try {\n const { stdout } = await exec(\"git\", args, { cwd, timeout: 1500 });\n return stdout.trim();\n } catch {\n return null;\n }\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 if (input.trim()) {\n try {\n data = JSON.parse(input);\n } catch {\n // Direct invocation from .git/hooks/post-commit may pass no stdin.\n }\n }\n\n if (!data || typeof data !== \"object\") data = {};\n if (isSdkChildContext(data)) return;\n\n const cwd =\n hookCwd(data) || process.env[\"AGENTMEMORY_CWD\"] || process.cwd();\n const sessionId =\n (data.session_id as string) ||\n process.env[\"AGENTMEMORY_SESSION_ID\"] ||\n undefined;\n\n const sha =\n process.env[\"AGENTMEMORY_COMMIT_SHA\"] ||\n (await git([\"rev-parse\", \"HEAD\"], cwd));\n if (!sha) return;\n\n const branch = await git([\"rev-parse\", \"--abbrev-ref\", \"HEAD\"], cwd);\n const repo = await git([\"config\", \"--get\", \"remote.origin.url\"], cwd);\n const message = await git([\"log\", \"-1\", \"--pretty=%B\", sha], cwd);\n const author = await git([\"log\", \"-1\", \"--pretty=%an <%ae>\", sha], cwd);\n const authoredAt = await git([\"log\", \"-1\", \"--pretty=%aI\", sha], cwd);\n const filesRaw = await git(\n [\"diff-tree\", \"--no-commit-id\", \"--name-only\", \"-r\", sha],\n cwd,\n );\n const files = filesRaw ? filesRaw.split(\"\\n\").filter(Boolean) : undefined;\n\n const body = {\n sessionId,\n sha,\n branch: branch || undefined,\n repo: repo || undefined,\n message: message || undefined,\n author: author || undefined,\n authoredAt: authoredAt || undefined,\n files,\n };\n\n try {\n await fetch(`${REST_URL}/agentmemory/session/commit`, {\n method: \"POST\",\n headers: authHeaders(),\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(TIMEOUT_MS),\n });\n } catch {\n // best-effort\n }\n}\n\nmain().catch(() => process.exit(0));\n"],"mappings":"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"}