agentix-cli 0.18.0 → 0.19.0

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 (171) hide show
  1. package/README.md +75 -27
  2. package/dist/{agent-RDVGL2TW.js → agent-57MG4LBM.js} +2 -2
  3. package/dist/{audit-Z47AQFFD.js → audit-JU3HWISB.js} +2 -2
  4. package/dist/{board-dashboard-KX2CNOSY.js → board-dashboard-HXDOYGIG.js} +2237 -69
  5. package/dist/board-dashboard-HXDOYGIG.js.map +1 -0
  6. package/dist/chunk-23AUU4S2.js +102 -0
  7. package/dist/chunk-23AUU4S2.js.map +1 -0
  8. package/dist/chunk-4MREHZP5.js +111 -0
  9. package/dist/chunk-4MREHZP5.js.map +1 -0
  10. package/dist/chunk-4YCH6IZV.js +2 -0
  11. package/dist/chunk-6B74TO2J.js +743 -0
  12. package/dist/chunk-6B74TO2J.js.map +1 -0
  13. package/dist/chunk-7P5QRX5W.js +108 -0
  14. package/dist/chunk-7P5QRX5W.js.map +1 -0
  15. package/dist/chunk-A6BKSZQA.js +3 -0
  16. package/dist/chunk-A6BKSZQA.js.map +1 -0
  17. package/dist/{chunk-6PPIFDOQ.js → chunk-CNNLREHD.js} +67 -22
  18. package/dist/{chunk-6PPIFDOQ.js.map → chunk-CNNLREHD.js.map} +1 -1
  19. package/dist/{chunk-J4WKMKTP.js → chunk-DLVOHCTA.js} +2 -2
  20. package/dist/chunk-E4ZZILAK.js +15 -0
  21. package/dist/chunk-E4ZZILAK.js.map +1 -0
  22. package/dist/{chunk-TRQSP67X.js → chunk-IKBHHTIT.js} +2 -2
  23. package/dist/chunk-JTJIPNXY.js +2 -0
  24. package/dist/chunk-JTJIPNXY.js.map +1 -0
  25. package/dist/chunk-MBOLXKA2.js +6 -0
  26. package/dist/chunk-MBOLXKA2.js.map +1 -0
  27. package/dist/{inbox-O5UB3YQN.js → chunk-MTRWXRFF.js} +69 -4
  28. package/dist/chunk-MTRWXRFF.js.map +1 -0
  29. package/dist/{processes-7W27G3J3.js → chunk-NOF4HSGK.js} +3 -3
  30. package/dist/{processes-7W27G3J3.js.map → chunk-NOF4HSGK.js.map} +1 -1
  31. package/dist/chunk-QW2OYR3N.js +7 -0
  32. package/dist/chunk-QW2OYR3N.js.map +1 -0
  33. package/dist/chunk-SV3I4CI7.js +440 -0
  34. package/dist/chunk-SV3I4CI7.js.map +1 -0
  35. package/dist/{chunk-JXEPJ3YD.js → chunk-TC3BSUP7.js} +5 -5
  36. package/dist/chunk-VWTBHLMX.js +1 -0
  37. package/dist/chunk-WFHIIVLL.js +719 -0
  38. package/dist/chunk-WFHIIVLL.js.map +1 -0
  39. package/dist/chunk-WPPUOTY6.js +6 -0
  40. package/dist/chunk-WPPUOTY6.js.map +1 -0
  41. package/dist/cli.js +1 -1
  42. package/dist/cli.js.map +1 -1
  43. package/dist/{compaction-S5H6DTOR.js → compaction-BTUJJ3LJ.js} +4 -4
  44. package/dist/config-QXWKEQON.js +2 -0
  45. package/dist/config-mutator-53XGR2NT.js +2 -0
  46. package/dist/conflict-detector-D7VIYPDI.js +2 -0
  47. package/dist/conflict-detector-D7VIYPDI.js.map +1 -0
  48. package/dist/{context-planner-2RGHJHSO.js → context-planner-I7ID7XF3.js} +3 -3
  49. package/dist/debug-N3B5NVJU.js +2 -0
  50. package/dist/{discover-OATU7SHI.js → discover-XJXQXXIO.js} +2 -2
  51. package/dist/doctor-SEOUFHSL.js +2 -0
  52. package/dist/{editor-chat-FUBQLMAQ.js → editor-chat-RVLCKKB7.js} +2 -2
  53. package/dist/{heal-YP7QI7D3.js → heal-YEQYWX2L.js} +2 -2
  54. package/dist/inbox-BIN2S5XN.js +2 -0
  55. package/dist/index.d.ts +830 -100
  56. package/dist/index.js +2 -2
  57. package/dist/index.js.map +1 -1
  58. package/dist/{ingest-whatsapp-C3O5UPBX.js → ingest-whatsapp-LZE62INA.js} +2 -2
  59. package/dist/loader-5CA3O5GM.js +2 -0
  60. package/dist/loader-6IHUCPVO.js +2 -0
  61. package/dist/memory-extract-KRCHD3OP.js +2 -0
  62. package/dist/migrate-v2-QO7SLKIM.js +4 -0
  63. package/dist/migrate-v2-QO7SLKIM.js.map +1 -0
  64. package/dist/processes-RJ6RDP32.js +2 -0
  65. package/dist/program-SASDC2GV.js +2 -0
  66. package/dist/{providers-WCQSKTNP.js → providers-WVCMIW67.js} +2 -2
  67. package/dist/query-HZYYRCLU.js +2 -0
  68. package/dist/recipes-EPLE2SNT.js +2 -0
  69. package/dist/registry-HM4ELSXS.js +2 -0
  70. package/dist/rotation-memo-GMTZPMNE.js +13 -0
  71. package/dist/rotation-memo-GMTZPMNE.js.map +1 -0
  72. package/dist/scheduler-XVFIK4XH.js +2 -0
  73. package/dist/{slack-D27BE37Q.js → slack-6P7ZJ32Y.js} +2 -2
  74. package/dist/store-BAPDZHXE.js +2 -0
  75. package/dist/{subagent-EPOT3QDM.js → subagent-WV7QKQ3L.js} +2 -2
  76. package/dist/{task-store-3NUWLYRY.js → task-store-RHISEU6V.js} +2 -2
  77. package/dist/{telegram-UWV67KWG.js → telegram-PIYDRM46.js} +2 -2
  78. package/dist/timers-5LPZN55J.js +2 -0
  79. package/dist/types-VECTMYTO.js +2 -0
  80. package/dist/types-VECTMYTO.js.map +1 -0
  81. package/dist/types-ZRLHHCGB.js +2 -0
  82. package/dist/types-ZRLHHCGB.js.map +1 -0
  83. package/dist/web/activity-graph.global.js +654 -0
  84. package/dist/web/activity-graph.global.js.map +1 -0
  85. package/dist/web/workflow-editor.global.js +1 -1
  86. package/dist/web/workflow-editor.global.js.map +1 -1
  87. package/dist/{whatsapp-KJ2NRKKQ.js → whatsapp-PFRDMUMT.js} +2 -2
  88. package/dist/{whatsapp-state-3ZVQ6MQD.js → whatsapp-state-XMTCSRXU.js} +2 -2
  89. package/dist/wiki-53W5NLZV.js +2 -0
  90. package/dist/wiki-53W5NLZV.js.map +1 -0
  91. package/dist/workspace-setup-FPGN5VNY.js +2 -0
  92. package/dist/workspace-setup-FPGN5VNY.js.map +1 -0
  93. package/package.json +3 -2
  94. package/dist/board-dashboard-KX2CNOSY.js.map +0 -1
  95. package/dist/chunk-34NG7UDX.js +0 -11
  96. package/dist/chunk-34NG7UDX.js.map +0 -1
  97. package/dist/chunk-ASSJ4Z6Z.js +0 -164
  98. package/dist/chunk-ASSJ4Z6Z.js.map +0 -1
  99. package/dist/chunk-BLUXL6HR.js +0 -854
  100. package/dist/chunk-BLUXL6HR.js.map +0 -1
  101. package/dist/chunk-BTAEYL3P.js +0 -72
  102. package/dist/chunk-BTAEYL3P.js.map +0 -1
  103. package/dist/chunk-DSCMCVTV.js +0 -7
  104. package/dist/chunk-DSCMCVTV.js.map +0 -1
  105. package/dist/chunk-HPDHNIA3.js +0 -2
  106. package/dist/chunk-IM376SD6.js +0 -13
  107. package/dist/chunk-IM376SD6.js.map +0 -1
  108. package/dist/chunk-LEIYC3BF.js +0 -104
  109. package/dist/chunk-LEIYC3BF.js.map +0 -1
  110. package/dist/chunk-LY7JGDV7.js +0 -6
  111. package/dist/chunk-LY7JGDV7.js.map +0 -1
  112. package/dist/chunk-U364GNQX.js +0 -719
  113. package/dist/chunk-U364GNQX.js.map +0 -1
  114. package/dist/chunk-WZEYII2S.js +0 -6
  115. package/dist/chunk-WZEYII2S.js.map +0 -1
  116. package/dist/config-UOHEG4BW.js +0 -2
  117. package/dist/config-mutator-KSEBWKJH.js +0 -2
  118. package/dist/debug-TGAP7E7W.js +0 -2
  119. package/dist/inbox-O5UB3YQN.js.map +0 -1
  120. package/dist/loader-BGWA7IRY.js +0 -2
  121. package/dist/loader-LBHKSXBK.js +0 -2
  122. package/dist/memory-extract-KKPKAOPH.js +0 -2
  123. package/dist/program-2MDELQLX.js +0 -2
  124. package/dist/query-FZAL7AQD.js +0 -2
  125. package/dist/recipes-LZ56TA3X.js +0 -2
  126. package/dist/registry-ONJSILH4.js +0 -2
  127. package/dist/scheduler-XRM2RJIV.js +0 -2
  128. package/dist/store-4UCO56K2.js +0 -2
  129. package/dist/timers-QB4KYNAE.js +0 -2
  130. package/dist/types-ECMEC5BZ.js +0 -2
  131. package/dist/types-TTB6DJUE.js +0 -2
  132. package/dist/usage-dashboard-FQGPPABD.js +0 -803
  133. package/dist/usage-dashboard-FQGPPABD.js.map +0 -1
  134. package/dist/wiki-OUYKM7MB.js +0 -2
  135. package/dist/workspace-setup-6GDBEORT.js +0 -2
  136. /package/dist/{agent-RDVGL2TW.js.map → agent-57MG4LBM.js.map} +0 -0
  137. /package/dist/{audit-Z47AQFFD.js.map → audit-JU3HWISB.js.map} +0 -0
  138. /package/dist/{chunk-HPDHNIA3.js.map → chunk-4YCH6IZV.js.map} +0 -0
  139. /package/dist/{chunk-J4WKMKTP.js.map → chunk-DLVOHCTA.js.map} +0 -0
  140. /package/dist/{chunk-TRQSP67X.js.map → chunk-IKBHHTIT.js.map} +0 -0
  141. /package/dist/{chunk-JXEPJ3YD.js.map → chunk-TC3BSUP7.js.map} +0 -0
  142. /package/dist/{config-UOHEG4BW.js.map → chunk-VWTBHLMX.js.map} +0 -0
  143. /package/dist/{compaction-S5H6DTOR.js.map → compaction-BTUJJ3LJ.js.map} +0 -0
  144. /package/dist/{config-mutator-KSEBWKJH.js.map → config-QXWKEQON.js.map} +0 -0
  145. /package/dist/{debug-TGAP7E7W.js.map → config-mutator-53XGR2NT.js.map} +0 -0
  146. /package/dist/{context-planner-2RGHJHSO.js.map → context-planner-I7ID7XF3.js.map} +0 -0
  147. /package/dist/{heal-YP7QI7D3.js.map → debug-N3B5NVJU.js.map} +0 -0
  148. /package/dist/{discover-OATU7SHI.js.map → discover-XJXQXXIO.js.map} +0 -0
  149. /package/dist/{loader-BGWA7IRY.js.map → doctor-SEOUFHSL.js.map} +0 -0
  150. /package/dist/{editor-chat-FUBQLMAQ.js.map → editor-chat-RVLCKKB7.js.map} +0 -0
  151. /package/dist/{loader-LBHKSXBK.js.map → heal-YEQYWX2L.js.map} +0 -0
  152. /package/dist/{memory-extract-KKPKAOPH.js.map → inbox-BIN2S5XN.js.map} +0 -0
  153. /package/dist/{ingest-whatsapp-C3O5UPBX.js.map → ingest-whatsapp-LZE62INA.js.map} +0 -0
  154. /package/dist/{program-2MDELQLX.js.map → loader-5CA3O5GM.js.map} +0 -0
  155. /package/dist/{providers-WCQSKTNP.js.map → loader-6IHUCPVO.js.map} +0 -0
  156. /package/dist/{query-FZAL7AQD.js.map → memory-extract-KRCHD3OP.js.map} +0 -0
  157. /package/dist/{recipes-LZ56TA3X.js.map → processes-RJ6RDP32.js.map} +0 -0
  158. /package/dist/{registry-ONJSILH4.js.map → program-SASDC2GV.js.map} +0 -0
  159. /package/dist/{scheduler-XRM2RJIV.js.map → providers-WVCMIW67.js.map} +0 -0
  160. /package/dist/{store-4UCO56K2.js.map → query-HZYYRCLU.js.map} +0 -0
  161. /package/dist/{task-store-3NUWLYRY.js.map → recipes-EPLE2SNT.js.map} +0 -0
  162. /package/dist/{timers-QB4KYNAE.js.map → registry-HM4ELSXS.js.map} +0 -0
  163. /package/dist/{types-ECMEC5BZ.js.map → scheduler-XVFIK4XH.js.map} +0 -0
  164. /package/dist/{slack-D27BE37Q.js.map → slack-6P7ZJ32Y.js.map} +0 -0
  165. /package/dist/{types-TTB6DJUE.js.map → store-BAPDZHXE.js.map} +0 -0
  166. /package/dist/{subagent-EPOT3QDM.js.map → subagent-WV7QKQ3L.js.map} +0 -0
  167. /package/dist/{wiki-OUYKM7MB.js.map → task-store-RHISEU6V.js.map} +0 -0
  168. /package/dist/{telegram-UWV67KWG.js.map → telegram-PIYDRM46.js.map} +0 -0
  169. /package/dist/{workspace-setup-6GDBEORT.js.map → timers-5LPZN55J.js.map} +0 -0
  170. /package/dist/{whatsapp-KJ2NRKKQ.js.map → whatsapp-PFRDMUMT.js.map} +0 -0
  171. /package/dist/{whatsapp-state-3ZVQ6MQD.js.map → whatsapp-state-XMTCSRXU.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/mcp/index.ts","../src/a2a/client.ts","../src/a2a/mesh.ts","../src/channels/group-log.ts","../src/utils/ellipsize.ts","../src/channels/block-stream.ts","../src/channels/router.ts","../src/channels/inbound/envelope.ts","../src/channels/inbound/pipeline.ts","../src/channels/outbound-marker.ts","../src/channels/inbound/stages/self-reply-guard.ts","../src/channels/inbound/stages/handover.ts","../src/channels/inbound/stages/adapter-resolved.ts","../src/channels/inbound/stages/dm-binding.ts","../src/channels/inbound/stages/bot-policy.ts","../src/channels/inbound/stages/mention.ts","../src/channels/inbound/stages/fallback-binding.ts","../src/channels/inbound/stages/index.ts","../src/channels/account-resolution.ts","../src/intent/sources/router.ts","../src/channels/telegram-format.ts","../src/channels/telegram.ts","../src/channels/cursor-store.ts","../src/channels/whatsapp.ts","../src/channels/whatsapp-cache.ts","../src/daemon/index.ts","../src/channels/discord.ts","../src/channels/slack.ts","../src/channels/gitlab.ts","../src/intent/sources/gitlab.ts","../src/channels/github.ts","../src/channels/webrtc-signal.ts","../src/daemon/call-page.ts","../src/channels/webrtc-bot.ts","../src/channels/webrtc-audio.ts","../src/whisper/index.ts","../src/daemon/bot-manager.ts","../src/daemon/logger.ts","../src/daemon/event-bus.ts","../src/daemon/webhooks.ts","../src/storage/sqlite.ts","../src/storage/subscribers.ts","../src/storage/usage-query.ts","../src/plugins/types.ts","../src/plugins/context.ts","../src/plugins/loader.ts","../src/intent/sources/mesh.ts","../src/agents/capabilities.ts","../src/agents/landscape.ts","../src/agents/contacts.ts","../src/agents/agent-mcp.ts","../src/agents/skills/remember-skill.ts","../src/agents/heartbeat.ts","../src/services/matcher.ts","../src/business/organization.ts","../src/business/schedule.ts","../src/business/reporter.ts","../src/business/kpi.ts","../src/business/http.ts","../src/business/day-cycle.ts","../src/business/index.ts","../src/utils/handle-error.ts","../src/utils/get-package-info.ts"],"sourcesContent":["import { createAgentContext } from \"@/agent\"\nimport { detectTechStack, formatTechStack } from \"@/agent/context/tech-stack\"\nimport { detectSchemas, formatSchemas } from \"@/agent/context/schema\"\nimport { loadLocalSkills, matchSkillsToTask } from \"@/agent/skills/loader\"\nimport { resolveOutputType } from \"@/agent/outputs/types\"\nimport { generate } from \"@/agent\"\nimport type { OutputType } from \"@/agent/providers/types\"\n\n// --- MCP Server: expose agentx as a Model Context Protocol server ---\n// This allows Claude Code, Cursor, Windsurf, and any MCP client to use\n// agentx's capabilities as tools.\n\n// JSON-RPC types\ninterface JsonRpcRequest {\n jsonrpc: \"2.0\"\n id: number | string\n method: string\n params?: Record<string, unknown>\n}\n\ninterface JsonRpcResponse {\n jsonrpc: \"2.0\"\n id: number | string | null\n result?: unknown\n error?: { code: number; message: string; data?: unknown }\n}\n\ninterface JsonRpcNotification {\n jsonrpc: \"2.0\"\n method: string\n params?: Record<string, unknown>\n}\n\n// MCP protocol constants\nconst SERVER_INFO = {\n name: \"agentx\",\n version: \"0.1.0\",\n}\n\nconst PROTOCOL_VERSION = \"2024-11-05\"\n\nconst CAPABILITIES = {\n tools: {},\n elicitation: { form: {} },\n}\n\n/** Counter for elicitation request IDs */\nlet elicitationIdCounter = 0\n\n/**\n * Request information from the user via MCP elicitation.\n * Returns the user's response or null if declined/cancelled.\n */\nasync function elicit(\n message: string,\n schema: Record<string, unknown>,\n): Promise<Record<string, unknown> | null> {\n const id = ++elicitationIdCounter\n\n // Send elicitation request (server → client)\n send({\n jsonrpc: \"2.0\",\n id: `elicit-${id}`,\n method: \"elicitation/create\",\n params: {\n mode: \"form\",\n message,\n requestedSchema: schema,\n },\n } as any)\n\n // Wait for response (blocking — MCP is request/response)\n return new Promise((resolve) => {\n elicitationResolvers.set(`elicit-${id}`, (result: any) => {\n if (result?.action === \"accept\" && result.content) {\n resolve(result.content)\n } else {\n resolve(null)\n }\n })\n\n // Timeout after 60s\n setTimeout(() => {\n if (elicitationResolvers.has(`elicit-${id}`)) {\n elicitationResolvers.delete(`elicit-${id}`)\n resolve(null)\n }\n }, 60_000)\n })\n}\n\n/** Pending elicitation response handlers */\nconst elicitationResolvers = new Map<string, (result: any) => void>()\n\n// Default daemon URL (local)\nconst DAEMON_URL = process.env.AGENTX_DAEMON_URL || \"http://localhost:19900\"\n\n// Strip ANSI escape codes from CLI output (we re-invoke the CLI for tools\n// that shell out — chalk colors its output, MCP clients want plain text).\n// eslint-disable-next-line no-control-regex\nconst ANSI_RE = /\\x1B\\[[0-9;]*[A-Za-z]/g\nfunction stripAnsi(s: string): string { return s.replace(ANSI_RE, \"\") }\n\n// Tool definitions\nconst TOOLS = [\n {\n name: \"agentx_generate\",\n description:\n \"Generate code, components, pages, APIs, documents, tests, workflows, schemas, emails, diagrams, and more using AI. Understands the project's tech stack, schemas, and skills automatically.\",\n inputSchema: {\n type: \"object\" as const,\n properties: {\n task: {\n type: \"string\",\n description:\n \"Describe what to generate (e.g., 'a responsive pricing card', 'REST API for users', 'GitHub Actions CI pipeline')\",\n },\n type: {\n type: \"string\",\n description:\n \"Output type: component, page, api, website, document, script, config, skill, media, report, test, workflow, schema, email, diagram, auto\",\n default: \"auto\",\n },\n output_dir: {\n type: \"string\",\n description: \"Optional output directory (relative to project root)\",\n },\n cwd: {\n type: \"string\",\n description: \"Project working directory (defaults to current directory)\",\n },\n },\n required: [\"task\"],\n },\n },\n {\n name: \"agentx_inspect\",\n description:\n \"Analyze a project and return its tech stack, frameworks, databases, schemas, installed skills, and dependencies. Use this to understand a project before generating code.\",\n inputSchema: {\n type: \"object\" as const,\n properties: {\n cwd: {\n type: \"string\",\n description: \"Project working directory (defaults to current directory)\",\n },\n },\n },\n },\n {\n name: \"agentx_skill_match\",\n description:\n \"Find installed skills that are relevant to a given task description. Returns matched skills with relevance scores.\",\n inputSchema: {\n type: \"object\" as const,\n properties: {\n task: {\n type: \"string\",\n description: \"The task to match skills against\",\n },\n cwd: {\n type: \"string\",\n description: \"Project working directory\",\n },\n },\n required: [\"task\"],\n },\n },\n {\n name: \"agentx_detect_output_type\",\n description:\n \"Auto-detect the best output type for a given task description based on keyword analysis.\",\n inputSchema: {\n type: \"object\" as const,\n properties: {\n task: {\n type: \"string\",\n description: \"The task description to analyze\",\n },\n },\n required: [\"task\"],\n },\n },\n\n // --- Daemon tools (require running daemon) ---\n\n {\n name: \"agentx_send\",\n description:\n \"Low-level outbound send when you ALREADY have a channel-native chatId (Telegram numeric chat id, GitLab 'group/project:issue:123', WhatsApp '+phone@s.whatsapp.net'). For sending to another agent by name, use agentx_send_agent instead. For sending to a human contact by name, use agentx_send_contact instead.\",\n inputSchema: {\n type: \"object\" as const,\n properties: {\n channel: {\n type: \"string\",\n description: \"Target channel: telegram, whatsapp, gitlab, discord\",\n },\n chatId: {\n type: \"string\",\n description: 'Chat ID. Telegram: numeric (\"-1001234567890\"). GitLab: \"group/project:issue:123\". WhatsApp: \"+phone@s.whatsapp.net\".',\n },\n text: {\n type: \"string\",\n description: \"Message text to send\",\n },\n agentId: {\n type: \"string\",\n description: \"Agent ID to send as (determines bot account on Telegram, GitLab token, etc.)\",\n },\n },\n required: [\"channel\", \"chatId\", \"text\"],\n },\n },\n {\n name: \"agentx_send_agent\",\n description:\n \"Send a message to ANOTHER AGENT by exact agentId — uses the AgentX A2A mesh (or local registry when the agent lives on this daemon). This is the deterministic path for agent-to-agent communication; it never falls through to a contact lookup, so an unknown agentId returns 404 with the list of known agents instead of silently sending to a similarly-named human. Use this when the target is a registered agent.\",\n inputSchema: {\n type: \"object\" as const,\n properties: {\n agentId: {\n type: \"string\",\n description: \"Exact agentId of the target agent (e.g. 'clawd', 'atlas'). Must match an agent in this daemon's registry or in a healthy mesh peer's directory.\",\n },\n text: {\n type: \"string\",\n description: \"Message to send to the target agent\",\n },\n senderAgentId: {\n type: \"string\",\n description: \"Optional. The agentId on whose behalf this call is being made. Recorded in route_traces and (with A2A protocolVersion >= 2) validated by the receiving daemon.\",\n },\n },\n required: [\"agentId\", \"text\"],\n },\n },\n {\n name: \"agentx_recent\",\n description:\n \"Read the most recent messages from a chat across ALL agents that have sessions for it. Returns inbound + each agent's replies in chronological order, so you can see what's actually been said in a Telegram chat / GitLab thread / WhatsApp DM regardless of which agent recorded it. Use this BEFORE speculating about what was sent — the cx/devops/marketing thread on 2026-04-29 about a Nadia/CX bot mixup would have been resolved in one call instead of three agents speculating. Bounded by sinceISO (default: last 24h) and limit (default: 30, max: 200).\",\n inputSchema: {\n type: \"object\" as const,\n properties: {\n channel: {\n type: \"string\",\n description: \"Channel name. Examples: telegram, whatsapp, gitlab, github, discord, cron, api, a2a.\",\n },\n chatId: {\n type: \"string\",\n description: \"Channel-native chat id. Telegram: numeric (e.g. \\\"1816212449\\\" for a DM, \\\"-1001234567890\\\" for a group). GitLab: \\\"group/project:issue:123\\\". WhatsApp: \\\"+phone@s.whatsapp.net\\\".\",\n },\n sinceISO: {\n type: \"string\",\n description: \"Optional ISO timestamp lower bound. Defaults to 24h ago.\",\n },\n limit: {\n type: \"number\",\n description: \"Optional cap on returned messages. Default 30, max 200.\",\n },\n },\n required: [\"channel\", \"chatId\"],\n },\n },\n {\n name: \"agentx_send_contact\",\n description:\n \"Send a message to a HUMAN CONTACT by name. Resolves through .agentx/contacts.json (id → exact alias → fuzzy substring). Refuses fuzzy matches without confirmed:true so the agent must ask the user to disambiguate before sending. Refuses when the name also matches a registered agent (use agentx_send_agent for those). Use this when the target is a person, not a bot.\",\n inputSchema: {\n type: \"object\" as const,\n properties: {\n contactName: {\n type: \"string\",\n description: \"Free-form contact name. Tried as id first, then alias, then fuzzy substring.\",\n },\n text: {\n type: \"string\",\n description: \"Message to send to the contact\",\n },\n channel: {\n type: \"string\",\n description: \"Optional explicit channel (telegram | whatsapp | gitlab | discord). Defaults to the first channel configured for the contact.\",\n },\n confirmed: {\n type: \"boolean\",\n description: \"Pass true ONLY after the user has confirmed a fuzzy match. Without it, fuzzy matches return 409 with the candidate so you can ask the user.\",\n },\n agentId: {\n type: \"string\",\n description: \"Optional agent identity used for the outbound send (determines bot account / token).\",\n },\n },\n required: [\"contactName\", \"text\"],\n },\n },\n {\n name: \"agentx_task\",\n description:\n \"Send a task to a specific agent on the daemon. The agent processes it and returns a response. Use to delegate work to specialized agents.\",\n inputSchema: {\n type: \"object\" as const,\n properties: {\n agent: {\n type: \"string\",\n description: \"Agent ID to send the task to\",\n },\n message: {\n type: \"string\",\n description: \"Task message for the agent\",\n },\n },\n required: [\"agent\", \"message\"],\n },\n },\n {\n name: \"agentx_agents\",\n description:\n \"List all agents registered on the daemon with their status (active tasks, total tasks, errors, tier).\",\n inputSchema: {\n type: \"object\" as const,\n properties: {},\n },\n },\n {\n name: \"agentx_health\",\n description:\n \"Get the daemon health status including node info, agents, crons, mesh peers, and uptime.\",\n inputSchema: {\n type: \"object\" as const,\n properties: {},\n },\n },\n {\n name: \"agentx_crons\",\n description:\n \"List cron jobs and their health. Shows healthy, failing, disabled counts and per-job status with consecutive error counts.\",\n inputSchema: {\n type: \"object\" as const,\n properties: {},\n },\n },\n {\n name: \"agentx_debug\",\n description:\n \"Toggle debug mode on the daemon. Enable verbose logging for specific categories (webhook, agent, channel, cron, mesh, context, memory, all) or disable it.\",\n inputSchema: {\n type: \"object\" as const,\n properties: {\n action: {\n type: \"string\",\n description: '\"on\", \"off\", or \"status\"',\n default: \"status\",\n },\n categories: {\n type: \"string\",\n description: 'Comma-separated categories to enable (e.g. \"webhook,agent\"). Only used with action=on.',\n },\n },\n },\n },\n {\n name: \"agentx_wiki_query\",\n description:\n \"Query the agentx institutional wiki: a cross-agent knowledge base organized by article type (person, project, place, concept, event, decision, pattern). Use this BEFORE grep/memory-search when the question is about who / what happened / what we decided / how we do something. The query walks the catalog + wikilink graph and returns a synthesized answer with citations.\",\n inputSchema: {\n type: \"object\" as const,\n properties: {\n question: {\n type: \"string\",\n description: \"The question to answer from the wiki, in natural language.\",\n },\n agent: {\n type: \"string\",\n description: \"Which agent's wiki to query (default: first agent with a catalog). Each agent has its own wiki dir under .agentx/wiki/agents/<id>/\",\n },\n wiki_dir: {\n type: \"string\",\n description: \"Wiki root dir (default: <cwd>/.agentx/wiki).\",\n },\n max_hops: {\n type: \"number\",\n description: \"Wikilink hops from candidates (default 2, max 3).\",\n default: 2,\n },\n },\n required: [\"question\"],\n },\n },\n {\n name: \"agentx_wiki_patch\",\n description:\n \"Edit a single wiki article via an LLM-applied instruction and write the result. Use this for targeted fixes, additions, or clarifications to an existing article when you already know what's wrong. For brand-new articles, use `agentx_wiki_interview` instead.\",\n inputSchema: {\n type: \"object\" as const,\n properties: {\n agent: {\n type: \"string\",\n description: \"Agent ID whose wiki owns the article (required).\",\n },\n title_or_path: {\n type: \"string\",\n description: \"Article title (case-insensitive) or relative path like 'people/anis.md'.\",\n },\n instruction: {\n type: \"string\",\n description: \"Plain-English edit instruction (e.g. 'Add a section on the KSA Supabase rollout'). The LLM makes the minimum edit that satisfies it.\",\n },\n model: {\n type: \"string\",\n description: \"Claude model for the patch (default: sonnet).\",\n default: \"sonnet\",\n },\n dry_run: {\n type: \"boolean\",\n description: \"Preview the patched body without writing.\",\n default: false,\n },\n },\n required: [\"agent\", \"title_or_path\", \"instruction\"],\n },\n },\n {\n name: \"agentx_wiki_interview\",\n description:\n \"Run a scripted wiki interview — synthesize ONE typed article from a list of Q&A answers, and save it. Non-interactive: caller supplies the answers upfront. Use for capturing tacit knowledge (person/project/decision/pattern/...) that never hit a channel. For editing an existing article, use `agentx_wiki_patch`.\",\n inputSchema: {\n type: \"object\" as const,\n properties: {\n agent: {\n type: \"string\",\n description: \"Agent ID that will own the new article (required).\",\n },\n topic: {\n type: \"string\",\n description: \"What the article is about — e.g. 'MTGL deployment procedure', 'Yousef Al-Fahad', 'Hackathonat KSA migration'.\",\n },\n type: {\n type: \"string\",\n description: \"Article type: person | project | place | concept | event | decision | pattern\",\n },\n answers: {\n type: \"array\",\n description: \"Answers to the type-specific question bank (one per question, in order). Empty string = skip that question. Final entry is typically 'save'.\",\n items: { type: \"string\" },\n },\n model: {\n type: \"string\",\n description: \"Synthesis model (default: sonnet).\",\n default: \"sonnet\",\n },\n commit: {\n type: \"boolean\",\n description: \"Write the article (true) or just preview (false).\",\n default: true,\n },\n },\n required: [\"agent\", \"topic\", \"type\", \"answers\"],\n },\n },\n {\n name: \"agentx_graph_review\",\n description:\n \"Triage pending intent-graph classifications via the configured review agent. The review agent sees each pending classification, may call `wiki query` for institutional context, and decides approve/reject/skip. Structural changes (new org, new unit) gate here; leaf additions auto-approve without this step.\",\n inputSchema: {\n type: \"object\" as const,\n properties: {\n max: {\n type: \"number\",\n description: \"Cap reviews this run (default 20).\",\n default: 20,\n },\n dry_run: {\n type: \"boolean\",\n description: \"Show decisions but don't apply approvals/rejections.\",\n default: false,\n },\n agent: {\n type: \"string\",\n description: \"Override the review agent (defaults to graph.reviewAgent or graph.draftAgent).\",\n },\n },\n },\n },\n]\n\n// Tool handlers\nasync function handleToolCall(\n name: string,\n args: Record<string, unknown>\n): Promise<{ content: { type: string; text: string }[] }> {\n const cwd = (args.cwd as string) || process.cwd()\n\n switch (name) {\n case \"agentx_generate\": {\n const task = args.task as string\n const outputType = (args.type as string) || \"auto\"\n const outputDir = args.output_dir as string | undefined\n\n const result = await generate({\n task,\n outputType: outputType as OutputType,\n outputDir,\n cwd,\n overwrite: true,\n dryRun: false,\n context7: true,\n interactive: false,\n maxSteps: 5,\n })\n\n const summary: string[] = []\n if (result.content) summary.push(result.content)\n if (result.files.written.length) {\n summary.push(\n `\\nCreated ${result.files.written.length} file(s):\\n${result.files.written.map((f) => ` - ${f}`).join(\"\\n\")}`\n )\n }\n if (result.files.skipped.length) {\n summary.push(\n `\\nSkipped ${result.files.skipped.length} existing file(s):\\n${result.files.skipped.map((f) => ` - ${f}`).join(\"\\n\")}`\n )\n }\n if (result.followUp) {\n summary.push(`\\nNeeds clarification: ${result.followUp}`)\n }\n\n return {\n content: [{ type: \"text\", text: summary.join(\"\\n\") || \"Generation complete.\" }],\n }\n }\n\n case \"agentx_inspect\": {\n const context = await createAgentContext(cwd, \"inspect\", {\n context7: { enabled: false },\n })\n\n const info: Record<string, unknown> = {\n languages: context.techStack.languages,\n frameworks: context.techStack.frameworks,\n packageManager: context.techStack.packageManager,\n databases: context.techStack.databases,\n styling: context.techStack.styling,\n testing: context.techStack.testing,\n deployment: context.techStack.deployment,\n monorepo: context.techStack.monorepo,\n srcDir: context.techStack.srcDir,\n dependencyCount: Object.keys(context.techStack.dependencies).length,\n devDependencyCount: Object.keys(context.techStack.devDependencies).length,\n schemas: {\n database: context.schemas.database\n ? {\n type: context.schemas.database.type,\n tables: context.schemas.database.tables,\n }\n : null,\n api: context.schemas.api ? { type: context.schemas.api.type } : null,\n env: context.schemas.env\n ? { variableCount: context.schemas.env.variables.length }\n : null,\n models: context.schemas.models?.map((m) => m.path) || [],\n },\n skills: context.skills.map((s) => ({\n name: s.frontmatter.name,\n description: s.frontmatter.description,\n source: s.source,\n })),\n }\n\n return {\n content: [\n {\n type: \"text\",\n text: `Project analysis:\\n\\n${formatTechStack(context.techStack)}\\n\\n${JSON.stringify(info, null, 2)}`,\n },\n ],\n }\n }\n\n case \"agentx_skill_match\": {\n const task = args.task as string\n const skills = await loadLocalSkills(cwd)\n const matches = matchSkillsToTask(skills, task)\n\n if (!matches.length) {\n return {\n content: [\n {\n type: \"text\",\n text: \"No matching skills found. Install skills with: agentx skill install <owner/repo>\",\n },\n ],\n }\n }\n\n const text = matches\n .map(\n (m) =>\n `- **${m.skill.frontmatter.name}** (relevance: ${(m.relevance * 100).toFixed(0)}%)\\n ${m.skill.frontmatter.description}\\n Match: ${m.matchReason}`\n )\n .join(\"\\n\\n\")\n\n return {\n content: [{ type: \"text\", text: `Matching skills:\\n\\n${text}` }],\n }\n }\n\n case \"agentx_detect_output_type\": {\n const task = args.task as string\n const type = resolveOutputType(undefined, task)\n return {\n content: [\n {\n type: \"text\",\n text: `Detected output type: ${type}`,\n },\n ],\n }\n }\n\n // --- Daemon tools ---\n\n case \"agentx_send\": {\n let channel = args.channel as string | undefined\n let chatId = args.chatId as string | undefined\n let text = args.text as string | undefined\n\n // Elicit missing required params\n if (!channel || !chatId || !text) {\n const channelsRes = await fetch(`${DAEMON_URL}/channels`).catch(() => null)\n const channels = channelsRes ? await channelsRes.json() as string[] : [\"telegram\", \"whatsapp\", \"gitlab\", \"discord\"]\n\n const response = await elicit(\n \"Please provide the message details:\",\n {\n type: \"object\",\n properties: {\n channel: { type: \"string\", title: \"Channel\", description: \"Target channel\", enum: channels, default: channel || channels[0] },\n chatId: { type: \"string\", title: \"Chat ID\", description: \"Telegram: numeric ID. GitLab: group/project:issue:123\", default: chatId || \"\" },\n text: { type: \"string\", title: \"Message\", description: \"Message text to send\", default: text || \"\" },\n },\n required: [\"channel\", \"chatId\", \"text\"],\n },\n )\n if (!response) return { content: [{ type: \"text\", text: \"Send cancelled.\" }] }\n channel = response.channel as string\n chatId = response.chatId as string\n text = response.text as string\n }\n\n const res = await fetch(`${DAEMON_URL}/send`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ channel, chatId, text, agentId: args.agentId }),\n })\n const data = await res.json() as any\n if (!res.ok) {\n return { content: [{ type: \"text\", text: `Error: ${data.error || res.statusText}` }] }\n }\n return { content: [{ type: \"text\", text: `Message sent. ID: ${data.messageId || \"ok\"}` }] }\n }\n\n case \"agentx_send_agent\": {\n const agentId = args.agentId as string | undefined\n const text = args.text as string | undefined\n const senderAgentId = args.senderAgentId as string | undefined\n if (!agentId || !text) {\n return { content: [{ type: \"text\", text: \"Error: agentId and text are required.\" }] }\n }\n const res = await fetch(`${DAEMON_URL}/send/agent`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ agentId, text, senderAgentId }),\n })\n const data = await res.json() as any\n if (!res.ok) {\n const known = Array.isArray(data?.known) ? ` Known agents: ${data.known.join(\", \")}.` : \"\"\n return { content: [{ type: \"text\", text: `Error: ${data.error || res.statusText}.${known}` }] }\n }\n const peerInfo = data.peer ? ` (via mesh peer ${data.peer})` : \"\"\n return { content: [{ type: \"text\", text: `Message sent to agent ${agentId}${peerInfo}. ID: ${data.messageId || \"ok\"}` }] }\n }\n\n case \"agentx_recent\": {\n const channel = args.channel as string | undefined\n const chatId = args.chatId as string | undefined\n const sinceISO = args.sinceISO as string | undefined\n const limit = args.limit as number | undefined\n if (!channel || !chatId) {\n return { content: [{ type: \"text\", text: \"Error: channel and chatId are required.\" }] }\n }\n const res = await fetch(`${DAEMON_URL}/chat/recent`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ channel, chatId, sinceISO, limit }),\n })\n const data = await res.json() as any\n if (!res.ok) {\n return { content: [{ type: \"text\", text: `Error: ${data.error || res.statusText}` }] }\n }\n const messages = (data.messages || []) as Array<{ ts: string; role: string; senderName: string; content: string; agentId: string }>\n if (messages.length === 0) {\n return { content: [{ type: \"text\", text: `No messages in ${channel}/${chatId} within the requested window.` }] }\n }\n // Render as a readable transcript with agentId attribution so the\n // caller can distinguish who said what.\n const lines = messages.map((m) => {\n const time = m.ts.slice(11, 16) // HH:MM\n const day = m.ts.slice(0, 10)\n const who = m.role === \"agent\" ? `${m.agentId}` : (m.senderName || \"user\")\n return `[${day} ${time}] ${who}: ${m.content}`\n })\n return { content: [{ type: \"text\", text: lines.join(\"\\n\") }] }\n }\n\n case \"agentx_send_contact\": {\n const contactName = args.contactName as string | undefined\n const text = args.text as string | undefined\n const channel = args.channel as string | undefined\n const confirmed = args.confirmed === true\n const agentId = args.agentId as string | undefined\n if (!contactName || !text) {\n return { content: [{ type: \"text\", text: \"Error: contactName and text are required.\" }] }\n }\n const res = await fetch(`${DAEMON_URL}/send/contact`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ contactName, text, channel, confirmed, agentId }),\n })\n const data = await res.json() as any\n if (res.status === 409) {\n // Surface the resolution result so the caller can ask the user to disambiguate.\n return { content: [{ type: \"text\", text: `Refused: ${JSON.stringify(data, null, 2)}` }] }\n }\n if (!res.ok) {\n return { content: [{ type: \"text\", text: `Error: ${data.error || res.statusText}` }] }\n }\n return { content: [{ type: \"text\", text: `Message sent to contact ${data.contactId} via ${data.channel}. ID: ${data.messageId || \"ok\"}` }] }\n }\n\n case \"agentx_task\": {\n let agent = args.agent as string | undefined\n let message = args.message as string | undefined\n\n // Elicit missing params\n if (!agent || !message) {\n const agentsRes = await fetch(`${DAEMON_URL}/agents`).catch(() => null)\n const agentList = agentsRes ? (await agentsRes.json() as any[]).map((a: any) => a.id) : []\n\n const response = await elicit(\n \"Which agent should handle this task?\",\n {\n type: \"object\",\n properties: {\n agent: { type: \"string\", title: \"Agent\", description: \"Agent ID\", ...(agentList.length ? { enum: agentList } : {}), default: agent || \"\" },\n message: { type: \"string\", title: \"Task\", description: \"Task message for the agent\", default: message || \"\" },\n },\n required: [\"agent\", \"message\"],\n },\n )\n if (!response) return { content: [{ type: \"text\", text: \"Task cancelled.\" }] }\n agent = response.agent as string\n message = response.message as string\n }\n\n const res = await fetch(`${DAEMON_URL}/task`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ agent, message }),\n })\n const data = await res.json() as any\n if (data.error) {\n return { content: [{ type: \"text\", text: `Agent error: ${data.error}` }] }\n }\n return { content: [{ type: \"text\", text: data.content || \"Task completed.\" }] }\n }\n\n case \"agentx_agents\": {\n const res = await fetch(`${DAEMON_URL}/agents`)\n const agents = await res.json() as any[]\n const lines = agents.map((a: any) =>\n `${a.id} (${a.name}) — ${a.tier}, active: ${a.active}/${a.total}, errors: ${a.errors}`\n )\n return { content: [{ type: \"text\", text: lines.join(\"\\n\") || \"No agents.\" }] }\n }\n\n case \"agentx_health\": {\n const res = await fetch(`${DAEMON_URL}/health`)\n const data = await res.json() as any\n return { content: [{ type: \"text\", text: JSON.stringify(data, null, 2) }] }\n }\n\n case \"agentx_crons\": {\n const res = await fetch(`${DAEMON_URL}/crons/health`)\n const data = await res.json() as any\n const summary = `Healthy: ${data.healthy}, Failing: ${data.failing}, Disabled: ${data.disabled}, Missed: ${data.missed}`\n const jobs = (data.jobs || []).map((j: any) =>\n ` ${j.id}: ${j.status}${j.consecutiveErrors ? ` (${j.consecutiveErrors} errors)` : \"\"}${j.lastError ? ` — ${j.lastError.slice(0, 100)}` : \"\"}`\n ).join(\"\\n\")\n return { content: [{ type: \"text\", text: `${summary}\\n\\n${jobs}` }] }\n }\n\n case \"agentx_wiki_query\": {\n const question = String(args.question || \"\").trim()\n if (!question) {\n return { content: [{ type: \"text\", text: \"Error: `question` is required.\" }] }\n }\n const { WikiHub } = await import(\"@/wiki\")\n const { agenticQuery } = await import(\"@/wiki/query\")\n const { resolve } = await import(\"path\")\n const wikiDir = (args.wiki_dir as string) || resolve(process.cwd(), \".agentx/wiki\")\n const hub = new WikiHub(wikiDir, undefined, \"graph\")\n let agentId = (args.agent as string) || \"\"\n if (!agentId) {\n // Fall back to first agent with a catalog\n const { existsSync } = await import(\"fs\")\n for (const id of hub.listAgents()) {\n const catPath = resolve(hub.getAgentWiki(id).baseDir, \"_index.md\")\n if (existsSync(catPath)) { agentId = id; break }\n }\n }\n if (!agentId) {\n return { content: [{ type: \"text\", text: \"Error: no agent has a wiki catalog yet. Run `agentx wiki absorb` first.\" }] }\n }\n const store = hub.getAgentWiki(agentId)\n const maxHops = typeof args.max_hops === \"number\" ? Math.min(3, Math.max(0, args.max_hops)) : 2\n const result = await agenticQuery(question, store, agentId, { maxHops })\n if (result.status !== \"ok\") {\n return { content: [{ type: \"text\", text: `Query returned status \"${result.status}\"${result.error ? `: ${result.error}` : \"\"}` }] }\n }\n const cites = result.citations.map(c => ` - ${c.title} [${c.type || \"?\"}] (${c.path})`).join(\"\\n\")\n const walkCount = result.walked.length\n const body = `${result.answer}\\n\\nCitations (${walkCount} article${walkCount === 1 ? \"\" : \"s\"} walked):\\n${cites}`\n return { content: [{ type: \"text\", text: body }] }\n }\n\n case \"agentx_wiki_patch\": {\n const agent = String(args.agent || \"\").trim()\n const titleOrPath = String(args.title_or_path || \"\").trim()\n const instruction = String(args.instruction || \"\").trim()\n if (!agent || !titleOrPath || !instruction) {\n return { content: [{ type: \"text\", text: \"Error: `agent`, `title_or_path`, and `instruction` are required.\" }] }\n }\n const model = String(args.model || \"sonnet\")\n const dry = args.dry_run === true\n const { execFileSync } = await import(\"child_process\")\n const flags = [\"wiki\", \"patch\", agent, titleOrPath, instruction, \"--patch-model\", model, ...(dry ? [\"--no-commit\"] : [\"--yes\"])]\n try {\n const out = execFileSync(process.execPath, [process.argv[1], ...flags], {\n cwd,\n encoding: \"utf-8\",\n timeout: 180_000,\n maxBuffer: 8 * 1024 * 1024,\n })\n return { content: [{ type: \"text\", text: stripAnsi(out).trim() || \"(no output)\" }] }\n } catch (e: any) {\n const combined = [e.stdout, e.stderr, e.message].filter(Boolean).map((s: any) => stripAnsi(String(s))).join(\"\\n\").trim()\n return { content: [{ type: \"text\", text: `patch failed:\\n${combined.slice(0, 2000)}` }] }\n }\n }\n\n case \"agentx_wiki_interview\": {\n const agent = String(args.agent || \"\").trim()\n const topic = String(args.topic || \"\").trim()\n const type = String(args.type || \"\").trim().toLowerCase()\n const answers = Array.isArray(args.answers) ? (args.answers as any[]).map(a => String(a ?? \"\")) : null\n if (!agent || !topic || !type || !answers || answers.length === 0) {\n return { content: [{ type: \"text\", text: \"Error: `agent`, `topic`, `type`, and non-empty `answers[]` are required.\" }] }\n }\n const model = String(args.model || \"sonnet\")\n const commit = args.commit !== false\n // Last line in --answers is typically the save/edit/scrap verdict — default save.\n const terminal = answers[answers.length - 1]?.toLowerCase()\n const finalAnswers = [\"save\", \"edit\", \"scrap\"].includes(terminal) ? answers : [...answers, \"save\"]\n const { execFileSync } = await import(\"child_process\")\n const { writeFileSync, mkdtempSync } = await import(\"fs\")\n const { tmpdir } = await import(\"os\")\n const { join } = await import(\"path\")\n const tmp = mkdtempSync(join(tmpdir(), \"agentx-mcp-interview-\"))\n const answersPath = join(tmp, \"answers.txt\")\n writeFileSync(answersPath, finalAnswers.join(\"\\n\"))\n const flags = [\n \"wiki\", \"interview\",\n \"--agent\", agent,\n \"--topic\", topic,\n \"--type\", type,\n \"--model\", model,\n \"--answers\", answersPath,\n ...(commit ? [] : [\"--no-commit\"]),\n ]\n try {\n const out = execFileSync(process.execPath, [process.argv[1], ...flags], {\n cwd,\n encoding: \"utf-8\",\n timeout: 240_000,\n maxBuffer: 8 * 1024 * 1024,\n })\n return { content: [{ type: \"text\", text: stripAnsi(out).trim() || \"(no output)\" }] }\n } catch (e: any) {\n const combined = [e.stdout, e.stderr, e.message].filter(Boolean).map((s: any) => stripAnsi(String(s))).join(\"\\n\").trim()\n return { content: [{ type: \"text\", text: `interview failed:\\n${combined.slice(0, 2000)}` }] }\n }\n }\n\n case \"agentx_graph_review\": {\n const max = typeof args.max === \"number\" ? Math.max(1, args.max as number) : 20\n const dry = args.dry_run === true\n const agent = args.agent ? String(args.agent) : \"\"\n const { execFileSync } = await import(\"child_process\")\n const flags = [\"graph\", \"review\", \"--max\", String(max), ...(dry ? [\"--dry-run\"] : []), ...(agent ? [\"--agent\", agent] : [])]\n try {\n const out = execFileSync(process.execPath, [process.argv[1], ...flags], {\n cwd,\n encoding: \"utf-8\",\n timeout: 600_000,\n maxBuffer: 8 * 1024 * 1024,\n })\n return { content: [{ type: \"text\", text: stripAnsi(out).trim() || \"(no output)\" }] }\n } catch (e: any) {\n const combined = [e.stdout, e.stderr, e.message].filter(Boolean).map((s: any) => stripAnsi(String(s))).join(\"\\n\").trim()\n return { content: [{ type: \"text\", text: `graph review failed:\\n${combined.slice(0, 2000)}` }] }\n }\n }\n\n case \"agentx_debug\": {\n const action = (args.action as string) || \"status\"\n if (action === \"on\") {\n const cats = (args.categories as string) || \"all\"\n await fetch(`${DAEMON_URL}/debug/on?categories=${cats}`, { method: \"POST\" })\n return { content: [{ type: \"text\", text: `Debug enabled: ${cats}` }] }\n } else if (action === \"off\") {\n await fetch(`${DAEMON_URL}/debug/off`, { method: \"POST\" })\n return { content: [{ type: \"text\", text: \"Debug disabled.\" }] }\n } else {\n const res = await fetch(`${DAEMON_URL}/debug`)\n const data = await res.json() as any\n return { content: [{ type: \"text\", text: JSON.stringify(data, null, 2) }] }\n }\n }\n\n default:\n throw new Error(`Unknown tool: ${name}`)\n }\n}\n\n// --- Stdio transport ---\n\n/** Parse one frame + dispatch. Shared between the header-framed and\n * newline-framed paths so the routing is in one place. */\nfunction dispatch(raw: string, log: (...args: unknown[]) => void): void {\n let msg: any\n try {\n msg = JSON.parse(raw)\n } catch (e) {\n log(\"Failed to parse message:\", e)\n return\n }\n if (msg.id && typeof msg.id === \"string\" && msg.id.startsWith(\"elicit-\") && msg.result) {\n const resolver = elicitationResolvers.get(msg.id)\n if (resolver) {\n elicitationResolvers.delete(msg.id)\n resolver(msg.result)\n }\n return\n }\n handleMessage(msg, log).catch((e) => log(\"Error:\", e))\n}\n\nexport async function startMcpServer(): Promise<void> {\n // Use stderr for logging (stdout is reserved for JSON-RPC)\n const log = (...args: unknown[]) => console.error(\"[agentx-mcp]\", ...args)\n\n log(\"Starting MCP server (stdio transport)...\")\n\n let buffer = \"\"\n\n process.stdin.setEncoding(\"utf8\")\n process.stdin.on(\"data\", (chunk: string) => {\n buffer += chunk\n\n // MCP stdio transport is newline-delimited JSON. Some legacy clients\n // (LSP-style) use `Content-Length: N\\r\\n\\r\\n<body>` framing, so we\n // accept both: prefer header framing when the start of the buffer\n // looks like a header, otherwise fall back to NDJSON.\n while (buffer.length > 0) {\n const looksLikeHeader = buffer.startsWith(\"Content-Length:\")\n if (looksLikeHeader) {\n const headerEnd = buffer.indexOf(\"\\r\\n\\r\\n\")\n if (headerEnd === -1) break\n const header = buffer.slice(0, headerEnd)\n const m = header.match(/Content-Length:\\s*(\\d+)/)\n if (!m) {\n // Malformed header; drop up to the separator and continue.\n buffer = buffer.slice(headerEnd + 4)\n continue\n }\n const contentLength = parseInt(m[1], 10)\n const bodyStart = headerEnd + 4\n const bodyEnd = bodyStart + contentLength\n if (buffer.length < bodyEnd) break\n const body = buffer.slice(bodyStart, bodyEnd)\n buffer = buffer.slice(bodyEnd)\n dispatch(body, log)\n } else {\n const newlineIdx = buffer.indexOf(\"\\n\")\n if (newlineIdx === -1) break\n const line = buffer.slice(0, newlineIdx).trim()\n buffer = buffer.slice(newlineIdx + 1)\n if (line) dispatch(line, log)\n }\n }\n })\n\n process.stdin.on(\"end\", () => {\n log(\"stdin closed, shutting down.\")\n process.exit(0)\n })\n}\n\nfunction send(message: JsonRpcResponse | JsonRpcNotification): void {\n // MCP stdio: newline-delimited JSON. Modern MCP clients (Claude Code,\n // Cursor, Windsurf) all expect this. Content-Length framing is LSP-era\n // and no observed client requires it; if one does, they can parse the\n // trailing newline harmlessly.\n process.stdout.write(JSON.stringify(message) + \"\\n\")\n}\n\nasync function handleMessage(\n msg: JsonRpcRequest,\n log: (...args: unknown[]) => void\n): Promise<void> {\n const { method, id, params } = msg\n\n log(`Received: ${method}`)\n\n switch (method) {\n case \"initialize\": {\n send({\n jsonrpc: \"2.0\",\n id,\n result: {\n protocolVersion: PROTOCOL_VERSION,\n capabilities: CAPABILITIES,\n serverInfo: SERVER_INFO,\n },\n })\n break\n }\n\n case \"notifications/initialized\": {\n log(\"Client initialized.\")\n break\n }\n\n case \"tools/list\": {\n send({\n jsonrpc: \"2.0\",\n id,\n result: { tools: TOOLS },\n })\n break\n }\n\n case \"tools/call\": {\n const toolName = (params as any)?.name as string\n const toolArgs = ((params as any)?.arguments || {}) as Record<string, unknown>\n\n try {\n const result = await handleToolCall(toolName, toolArgs)\n send({\n jsonrpc: \"2.0\",\n id,\n result,\n })\n } catch (error: any) {\n send({\n jsonrpc: \"2.0\",\n id,\n result: {\n content: [{ type: \"text\", text: `Error: ${error.message}` }],\n isError: true,\n },\n })\n }\n break\n }\n\n case \"ping\": {\n send({ jsonrpc: \"2.0\", id, result: {} })\n break\n }\n\n default: {\n if (id !== undefined) {\n send({\n jsonrpc: \"2.0\",\n id,\n error: { code: -32601, message: `Method not found: ${method}` },\n })\n }\n }\n }\n}\n","import type { AgentCard, Task, MessagePart, JsonRpcResponse } from \"./types\"\n\n// --- A2A Client: call remote agents via A2A protocol ---\n\nexport class A2AClient {\n private baseUrl: string\n private token?: string\n\n constructor(baseUrl: string, token?: string) {\n this.baseUrl = baseUrl.replace(/\\/$/, \"\")\n this.token = token\n }\n\n /**\n * Fetch the remote agent's card.\n */\n async getAgentCard(): Promise<AgentCard> {\n const res = await fetch(`${this.baseUrl}/.well-known/agent-card.json`, {\n headers: this.headers(),\n })\n if (!res.ok) throw new Error(`Failed to fetch agent card: ${res.status}`)\n return res.json() as Promise<AgentCard>\n }\n\n /**\n * Send a task synchronously and wait for completion.\n */\n async sendTask(\n text: string,\n metadata?: Record<string, unknown>,\n ): Promise<Task> {\n const response = await this.rpc(\"tasks/send\", {\n id: `task-${Date.now().toString(36)}`,\n message: {\n role: \"user\",\n parts: [{ type: \"text\", text }] as MessagePart[],\n },\n metadata,\n })\n\n if (response.error) {\n throw new Error(`A2A error: ${response.error.message}`)\n }\n\n return response.result as Task\n }\n\n /**\n * Send a task with SSE streaming.\n */\n async *sendTaskStream(\n text: string,\n metadata?: Record<string, unknown>,\n ): AsyncIterable<{ state: string; message?: string; final: boolean }> {\n const body = JSON.stringify({\n jsonrpc: \"2.0\",\n id: 1,\n method: \"tasks/sendSubscribe\",\n params: {\n id: `task-${Date.now().toString(36)}`,\n message: {\n role: \"user\",\n parts: [{ type: \"text\", text }],\n },\n metadata,\n },\n })\n\n const res = await fetch(this.baseUrl, {\n method: \"POST\",\n headers: {\n ...this.headers(),\n \"Content-Type\": \"application/json\",\n \"Accept\": \"text/event-stream\",\n },\n body,\n })\n\n if (!res.ok || !res.body) {\n throw new Error(`A2A stream error: ${res.status}`)\n }\n\n const reader = res.body.getReader()\n const decoder = new TextDecoder()\n let buffer = \"\"\n\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n\n buffer += decoder.decode(value, { stream: true })\n const lines = buffer.split(\"\\n\")\n buffer = lines.pop() || \"\"\n\n for (const line of lines) {\n if (line.startsWith(\"data: \")) {\n try {\n const data = JSON.parse(line.slice(6))\n yield {\n state: data.state,\n message: data.message?.parts?.[0]?.text,\n final: data.final,\n }\n } catch {\n // Skip malformed events\n }\n }\n }\n }\n }\n\n /**\n * Get task status by ID.\n */\n async getTask(taskId: string): Promise<Task> {\n const response = await this.rpc(\"tasks/get\", { id: taskId })\n if (response.error) {\n throw new Error(`A2A error: ${response.error.message}`)\n }\n return response.result as Task\n }\n\n /**\n * Cancel a running task.\n */\n async cancelTask(taskId: string): Promise<Task> {\n const response = await this.rpc(\"tasks/cancel\", { id: taskId })\n if (response.error) {\n throw new Error(`A2A error: ${response.error.message}`)\n }\n return response.result as Task\n }\n\n private async rpc(method: string, params: Record<string, unknown>): Promise<JsonRpcResponse> {\n const res = await fetch(this.baseUrl, {\n method: \"POST\",\n headers: {\n ...this.headers(),\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n jsonrpc: \"2.0\",\n id: Date.now(),\n method,\n params,\n }),\n })\n\n if (!res.ok) {\n throw new Error(`A2A HTTP error: ${res.status}`)\n }\n\n return res.json() as Promise<JsonRpcResponse>\n }\n\n private headers(): Record<string, string> {\n const h: Record<string, string> = {}\n if (this.token) {\n h[\"Authorization\"] = `Bearer ${this.token}`\n }\n return h\n }\n}\n","import type { MeshPeer, DaemonConfig } from \"@/daemon/config\"\nimport type { AgentCard, AgentSkill } from \"./types\"\nimport { A2AClient } from \"./client\"\nimport { Agent as UndiciAgent, fetch as undiciFetch } from \"undici\"\n\n// --- A2A Mesh: peer discovery, health checks, agent directory ---\n\n/** Custom undici dispatcher used by sendTask: peer agent calls can\n * block writing response headers for the full duration of the agent\n * run (often 5–30 min), so the default 300s headersTimeout has to be\n * disabled. AbortController on the call site enforces our actual cap. */\nconst longTaskDispatcher = new UndiciAgent({\n headersTimeout: 0,\n bodyTimeout: 0,\n})\n\nexport interface PeerState {\n peer: MeshPeer\n client: A2AClient\n healthy: boolean\n lastCheck?: Date\n agentCard?: AgentCard\n agents: AgentSkill[]\n /** Count of back-to-back failed health probes since the last successful one.\n * Used to suppress transient flaps: the `healthy` flag only flips to false\n * after N consecutive failures (see UNHEALTHY_AFTER). Reset to 0 on success. */\n consecutiveFailures: number\n /** Last probe error message — surfaced for debugging via /mesh. */\n lastError?: string\n}\n\n/** Number of consecutive failed probes required before we mark a peer\n * unhealthy. A busy remote daemon whose event loop stalls for one probe\n * cycle (e.g. during a 200K-token tier-2 agent turn) used to flip to\n * \"unreachable\" and back on the next tick, which was visible in the\n * dashboard as a cycling peer. With hysteresis, a single slow probe is\n * tolerated; only sustained unreachability actually flips the flag. */\nconst UNHEALTHY_AFTER = 3\n\nexport class A2AMesh {\n private peers: Map<string, PeerState> = new Map()\n private healthTimer?: ReturnType<typeof setInterval>\n private config: DaemonConfig\n private log: (...args: unknown[]) => void\n /** Optional callback fired on peer state transitions (recovered /\n * lost / skills changed). Daemon wires this to the event bus for\n * live operator visibility. */\n private peerChangeCallback?: (event: {\n peer: string; healthy: boolean; skills: string[]; delta: \"recovered\" | \"lost\" | \"skills-changed\"\n }) => void\n\n /** Register a listener for peer state transitions. Only one listener\n * is kept — repeat calls overwrite. Used by the daemon to bridge\n * into the EventBus without introducing a circular dependency. */\n onPeerChange(cb: typeof A2AMesh.prototype.peerChangeCallback): void {\n this.peerChangeCallback = cb\n }\n\n constructor(\n config: DaemonConfig,\n log: (...args: unknown[]) => void = console.error.bind(console, \"[mesh]\"),\n ) {\n this.config = config\n this.log = log\n\n for (const peer of config.mesh.peers) {\n this.peers.set(peer.name, {\n peer,\n client: new A2AClient(peer.url, peer.token),\n healthy: false,\n agents: [],\n consecutiveFailures: 0,\n })\n }\n }\n\n /**\n * Start the mesh: discover peers and begin health checks.\n */\n async start(): Promise<void> {\n this.log(`Mesh starting with ${this.peers.size} peer(s)`)\n\n // Initial discovery\n await this.discoverAll()\n\n // Periodic health checks\n const interval = this.config.mesh.healthCheck.interval * 1000\n this.healthTimer = setInterval(() => this.discoverAll(), interval)\n }\n\n async stop(): Promise<void> {\n if (this.healthTimer) {\n clearInterval(this.healthTimer)\n }\n }\n\n /** Hot-reload the peer set from a fresh config. Adds new peers (kicks off\n * immediate discovery), removes vanished ones, and rebuilds the A2AClient\n * for peers whose url or token changed. Health-check interval is honored\n * on the next tick — we don't reset the timer just for peer-set edits.\n * Returns the diff for the caller to log. */\n async reloadPeers(next: DaemonConfig): Promise<{ added: string[]; removed: string[]; updated: string[] }> {\n this.config = next\n const oldIds = new Set(this.peers.keys())\n const newPeers = new Map(next.mesh.peers.map((p) => [p.name, p] as const))\n const added: string[] = []\n const removed: string[] = []\n const updated: string[] = []\n\n // Remove vanished peers.\n for (const id of oldIds) {\n if (!newPeers.has(id)) {\n this.peers.delete(id)\n removed.push(id)\n }\n }\n\n // Add or update the rest.\n const rediscover: Array<[string, PeerState]> = []\n for (const [id, peer] of newPeers) {\n const existing = this.peers.get(id)\n if (!existing) {\n const state: PeerState = {\n peer,\n client: new A2AClient(peer.url, peer.token),\n healthy: false,\n agents: [],\n consecutiveFailures: 0,\n }\n this.peers.set(id, state)\n added.push(id)\n rediscover.push([id, state])\n continue\n }\n // URL or token changed — rebuild the client and redo discovery.\n if (existing.peer.url !== peer.url || existing.peer.token !== peer.token) {\n existing.peer = peer\n existing.client = new A2AClient(peer.url, peer.token)\n existing.healthy = false\n existing.consecutiveFailures = 0 // fresh client; old counter is stale\n updated.push(id)\n rediscover.push([id, existing])\n } else {\n existing.peer = peer // pick up tag/other metadata edits\n }\n }\n\n // Immediate discovery for changed/new peers — don't wait for the next\n // interval tick since the whole point of /reload is instant feedback.\n if (rediscover.length) {\n await Promise.allSettled(rediscover.map(([id, state]) => this.discoverPeer(id, state)))\n }\n\n return { added, removed, updated }\n }\n\n /** Number of peers currently registered (regardless of health). */\n peerCount(): number {\n return this.peers.size\n }\n\n /**\n * Re-probe a single peer immediately. Use case: an operator just added\n * an agent on the remote daemon and doesn't want to wait up to a full\n * health-check interval (default 60s) before mentioning the new agent\n * resolves. The probe pulls a fresh agent card and updates `state.agents`\n * so `directory()` reflects the new roster on the very next route lookup.\n *\n * Returns true on success, false when the peer is unknown or the probe\n * failed. Does NOT throw — callers can blindly fan-out across peers.\n */\n async refreshPeer(name: string): Promise<boolean> {\n const state = this.peers.get(name)\n if (!state) return false\n await this.discoverPeer(name, state)\n return state.healthy\n }\n\n /**\n * Re-probe every peer in parallel. Cheap when peers are healthy\n * (single GET /a2a/agent-card per peer). Used by `agentx mesh refresh`\n * and after agent-roster changes to invalidate the cached directory.\n */\n async refreshAll(): Promise<{ name: string; healthy: boolean }[]> {\n const probes = Array.from(this.peers.entries()).map(async ([name, state]) => {\n await this.discoverPeer(name, state)\n return { name, healthy: state.healthy }\n })\n return Promise.all(probes)\n }\n\n /**\n * Discover agent cards from all peers.\n */\n async discoverAll(): Promise<void> {\n const results = await Promise.allSettled(\n Array.from(this.peers.entries()).map(([name, state]) =>\n this.discoverPeer(name, state),\n ),\n )\n\n const healthy = Array.from(this.peers.values()).filter((p) => p.healthy).length\n this.log(`Discovery complete: ${healthy}/${this.peers.size} peers healthy`)\n }\n\n private async discoverPeer(name: string, state: PeerState): Promise<void> {\n const timeout = this.config.mesh.healthCheck.timeout * 1000\n\n try {\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), timeout)\n\n const card = await state.client.getAgentCard()\n clearTimeout(timer)\n\n // Success — clear failure counter, flip healthy on if it was off.\n const wasDown = !state.healthy\n const prevSkills = new Set(state.agents.map((a) => a.id))\n state.healthy = true\n state.lastCheck = new Date()\n state.agentCard = card\n state.agents = card.skills || []\n state.consecutiveFailures = 0\n state.lastError = undefined\n const nextSkills = new Set(state.agents.map((a) => a.id))\n const skillsChanged = prevSkills.size !== nextSkills.size || [...nextSkills].some((s) => !prevSkills.has(s))\n\n if (wasDown) {\n this.log(`Peer \"${name}\" recovered: ${card.name} (${state.agents.length} skills)`)\n this.peerChangeCallback?.({ peer: name, healthy: true, skills: [...nextSkills], delta: \"recovered\" })\n } else {\n this.log(`Peer \"${name}\" healthy: ${card.name} (${state.agents.length} skills)`)\n if (skillsChanged) {\n this.peerChangeCallback?.({ peer: name, healthy: true, skills: [...nextSkills], delta: \"skills-changed\" })\n }\n }\n } catch (e: any) {\n state.lastCheck = new Date()\n state.lastError = e.message\n state.consecutiveFailures++\n // Hysteresis: require UNHEALTHY_AFTER consecutive failures before\n // flipping the flag. Prevents transient event-loop stalls on the\n // remote daemon from cycling the peer as seen from the dashboard.\n if (state.consecutiveFailures >= UNHEALTHY_AFTER && state.healthy) {\n state.healthy = false\n this.log(`Peer \"${name}\" unreachable (${state.consecutiveFailures} consecutive failures): ${e.message}`)\n this.peerChangeCallback?.({ peer: name, healthy: false, skills: state.agents.map((a) => a.id), delta: \"lost\" })\n } else if (state.consecutiveFailures < UNHEALTHY_AFTER) {\n this.log(`Peer \"${name}\" probe failed (${state.consecutiveFailures}/${UNHEALTHY_AFTER}): ${e.message}`)\n }\n }\n }\n\n /**\n * Send a task to a remote peer by name.\n * Uses the peer's /task HTTP endpoint (agentx daemon API).\n * If no agent specified, uses the first available agent on the peer.\n */\n async sendTask(\n peerName: string,\n text: string,\n agentId?: string,\n opts: {\n timeoutMs?: number\n /** Identity of the agent on whose behalf this call is made. Forwarded\n * in the request body so the receiving daemon can record it in\n * route_traces and (in a future protocol revision) validate that the\n * caller is allowed to act for this agent. Optional during the\n * log-warn rollout; missing values produce a server-side warning. */\n senderAgentId?: string\n /** Origin context — channel, chatId, sender, channelMeta, etc. Forwarded\n * to the receiving daemon's /task handler verbatim so the receiver's\n * registry.execute() keys the session by the SAME (channel, chatId)\n * the sender used, instead of falling back to api/default. Without\n * this, a GitLab webhook routed across the mesh lands in the\n * recipient's api:default bucket with no project, no issue id, no\n * channelMeta — i.e., the mtgl/hasanah confusion incident on\n * 2026-04-29 issue #709. Shape matches AgentTask.context. */\n context?: Record<string, unknown>\n } = {},\n ): Promise<string> {\n const state = this.peers.get(peerName)\n if (!state) throw new Error(`Unknown peer: ${peerName}`)\n if (!state.healthy) throw new Error(`Peer \"${peerName}\" is not healthy`)\n\n // Default to first agent on the peer\n const agent = agentId || state.agents[0]?.id\n if (!agent) throw new Error(`Peer \"${peerName}\" has no agents`)\n\n const url = `${state.peer.url}/task`\n const headers: Record<string, string> = { \"Content-Type\": \"application/json\" }\n if (state.peer.token) {\n headers[\"Authorization\"] = `Bearer ${state.peer.token}`\n }\n\n // Agent tasks frequently run for minutes (Claude Code sessions in\n // particular). Two timeout layers to defeat:\n //\n // 1. AbortController on our side — explicit request timeout. Default 30 min.\n // 2. undici dispatcher's `headersTimeout` (300s default) — fires\n // independently of AbortController while the peer is still\n // synchronously processing the agent task before writing\n // response headers. This is what surfaces as the opaque\n // \"fetch failed\" exactly 5 minutes in. Per-call dispatcher\n // with disabled headersTimeout/bodyTimeout fixes it.\n const timeoutMs = opts.timeoutMs ?? 30 * 60 * 1000\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), timeoutMs)\n // Use undici's own fetch — the Node global `fetch` ignores the\n // `dispatcher` option (its undici instance is internal and separate\n // from this package's). Without our custom dispatcher, headersTimeout\n // would still default to 300s and abort before the agent finishes.\n let res: Awaited<ReturnType<typeof undiciFetch>>\n try {\n res = await undiciFetch(url, {\n method: \"POST\",\n headers,\n body: JSON.stringify({\n agent,\n message: text,\n // A2A protocol field — receiving daemon validates and records.\n // Omitted from the body (rather than sent as undefined) so the\n // server's \"missing senderAgentId\" log-warn fires only when the\n // caller genuinely didn't pass one.\n ...(opts.senderAgentId ? { senderAgentId: opts.senderAgentId } : {}),\n // Origin context (channel, chatId, channelMeta, sender, ...).\n // Optional for back-compat — older callers continue to work, with\n // the receiver defaulting channel/chatId as before.\n ...(opts.context ? { context: opts.context } : {}),\n }),\n signal: controller.signal,\n dispatcher: longTaskDispatcher,\n })\n } catch (e: any) {\n clearTimeout(timer)\n if (controller.signal.aborted) {\n throw new Error(`Peer \"${peerName}\" /task timed out after ${Math.round(timeoutMs / 1000)}s`)\n }\n throw e\n }\n clearTimeout(timer)\n\n if (!res.ok) {\n throw new Error(`Peer \"${peerName}\" /task error: ${res.status}`)\n }\n\n const data = await res.json() as { content?: string; error?: string }\n if (data.error) throw new Error(`Peer \"${peerName}\" agent error: ${data.error}`)\n return data.content || \"No response\"\n }\n\n /**\n * Forward a WebRTC signaling message (SDP / ICE / hangup) to a remote peer's\n * /webrtc/signal endpoint. Not gated by peer health — a healthy control plane\n * is useful but a one-off probe miss should not drop a live call; the browser\n * layer will retry on timeout.\n *\n * Peer lookup is tolerant: the `to` field comes from whichever daemon\n * originated the signal, which often spells the same node differently\n * (\"MacBook-Local\" vs \"macbook-local\"). Match on a normalized name so config\n * drift across sides doesn't break the signaling path.\n */\n async sendSignal(peerName: string, signal: unknown): Promise<boolean> {\n const state = this.peers.get(peerName) || this.findPeerByNormalizedName(peerName)\n if (!state) throw new Error(`Unknown peer: ${peerName}`)\n\n const url = `${state.peer.url}/webrtc/signal`\n const headers: Record<string, string> = { \"Content-Type\": \"application/json\" }\n if (state.peer.token) {\n headers[\"Authorization\"] = `Bearer ${state.peer.token}`\n }\n try {\n const res = await fetch(url, {\n method: \"POST\",\n headers,\n body: JSON.stringify(signal),\n })\n return res.ok\n } catch (e: any) {\n this.log(`sendSignal to \"${peerName}\" failed: ${e.message}`)\n return false\n }\n }\n\n /** Look up a peer by a name that differs only in case / non-alphanumeric\n * characters (e.g. spaces, hyphens). Used by WebRTC signaling where the\n * `to` value is controlled by the opposite daemon and may not match the\n * exact spelling in this daemon's `mesh.peers[].name`. */\n private findPeerByNormalizedName(name: string): PeerState | undefined {\n const want = name.toLowerCase().replace(/[^a-z0-9]/g, \"\")\n for (const [key, state] of this.peers) {\n if (key.toLowerCase().replace(/[^a-z0-9]/g, \"\") === want) return state\n }\n return undefined\n }\n\n /**\n * Find a peer that has a specific skill.\n */\n findPeerWithSkill(skillId: string): PeerState | undefined {\n for (const state of this.peers.values()) {\n if (state.healthy && state.agents.some((a) => a.id === skillId)) {\n return state\n }\n }\n return undefined\n }\n\n /**\n * Get the combined agent directory across all healthy peers.\n *\n * `channels` lists the channel adapter names each peer hosts (telegram,\n * whatsapp, gitlab, ...) — sourced from the peer's agent-card. Used by\n * workflow `action.send` to forward outbound messages back to the peer\n * that owns the channel when the workflow runs on a different node.\n */\n directory(): Array<{\n peer: string\n peerUrl: string\n healthy: boolean\n skills: AgentSkill[]\n channels: string[]\n lastCheck?: Date\n }> {\n return Array.from(this.peers.entries()).map(([name, state]) => ({\n peer: name,\n peerUrl: state.peer.url,\n healthy: state.healthy,\n skills: state.agents,\n channels: Array.isArray((state.agentCard as any)?.channels)\n ? ((state.agentCard as any).channels as unknown[]).map((c) => String(c))\n : [],\n lastCheck: state.lastCheck,\n }))\n }\n}\n","import { readFileSync, writeFileSync, existsSync, mkdirSync } from \"fs\"\nimport { resolve } from \"path\"\nimport { ellipsize } from \"@/utils/ellipsize\"\n\n// --- Persistent group conversation log ---\n// Stores recent messages per group chat in .agentx/groups/{chatId}.jsonl\n// Survives daemon restarts. Trimmed to max entries on write.\n\nexport interface GroupLogEntry {\n sender: string\n text: string\n timestamp: number\n}\n\nconst MAX_ENTRIES = 30\nconst MAX_CONTEXT_CHARS = 6000\nconst MAX_ENTRY_CHARS = 1500\n/** Drop identical (sender,text) re-sends that arrive within this window.\n * Protects the log from users who mash Enter N times or from clients that\n * mirror the same message across bots. Observed cost: 6× Seif spam blew the\n * prompt budget on the next agent's first turn. */\nconst REPEAT_WINDOW_MS = 60_000\n\n/** Bucket a timestamp into a stable 15-min window so the rendered history\n * header only changes every quarter-hour. Stamping every line with HH:MM\n * invalidates the cache on every new message; bucketing preserves it. */\nfunction bucketLabel(ts: number): string {\n const d = new Date(ts)\n const hh = d.getUTCHours().toString().padStart(2, \"0\")\n const mm = Math.floor(d.getUTCMinutes() / 15) * 15\n return `${hh}:${mm.toString().padStart(2, \"0\")}`\n}\n\nexport class GroupLog {\n private dir: string\n private cache: Map<string, GroupLogEntry[]> = new Map()\n\n constructor(baseDir: string = resolve(process.cwd(), \".agentx/groups\")) {\n this.dir = baseDir\n mkdirSync(this.dir, { recursive: true })\n }\n\n /**\n * Log a message in a group conversation.\n */\n add(chatId: string, sender: string, text: string): void {\n const log = this.load(chatId)\n const now = Date.now()\n const ellipsized = ellipsize(text, MAX_ENTRY_CHARS)\n\n // On-insert dedup: if the most recent entry has the same sender+text and\n // arrived within REPEAT_WINDOW_MS, skip. Don't walk further back — spam\n // is usually contiguous, and a legitimate \"say it again\" later is still\n // useful signal for the agent.\n const last = log[log.length - 1]\n if (last && last.sender === sender && last.text === ellipsized && now - last.timestamp < REPEAT_WINDOW_MS) {\n return\n }\n\n log.push({ sender, text: ellipsized, timestamp: now })\n\n // Trim\n while (log.length > MAX_ENTRIES) log.shift()\n\n this.cache.set(chatId, log)\n this.save(chatId, log)\n }\n\n /**\n * Build conversation context string for an agent.\n * Returns recent messages so the agent knows what was discussed.\n */\n buildContext(chatId: string): string {\n const log = this.load(chatId)\n if (log.length <= 1) return \"\"\n\n const lines: string[] = [\"[Recent group conversation]\"]\n let chars = 0\n\n // Walk backwards from most recent, skip the very last (it's the current\n // message). Coalesce runs of identical (sender,text) into one line with a\n // repeat count — protects the prompt from any spam that predates on-insert\n // dedup (historical log entries) and keeps the context compact.\n // Bucket headers only appear when the 15-min window changes, so the\n // rendered history is byte-stable within the bucket (cache-friendly).\n let pending: { line: string; repeat: number; bucket: string } | null = null\n const buffered: string[] = []\n let lastBucket = \"\"\n const flushPending = (): boolean => {\n if (!pending) return true\n const out = pending.repeat > 1 ? `${pending.line} [×${pending.repeat}]` : pending.line\n if (chars + out.length > MAX_CONTEXT_CHARS) return false\n buffered.push(out)\n chars += out.length\n // Emit a bucket header when the window changes (we're walking back in\n // time — so the \"new bucket\" is older than the previous one).\n if (pending.bucket !== lastBucket) {\n const hdr = `— ${pending.bucket} —`\n if (chars + hdr.length > MAX_CONTEXT_CHARS) { pending = null; return false }\n buffered.push(hdr)\n chars += hdr.length\n lastBucket = pending.bucket\n }\n pending = null\n return true\n }\n\n for (let i = log.length - 2; i >= 0; i--) {\n const entry = log[i]\n const line = `${entry.sender}: ${entry.text}`\n const bucket = bucketLabel(entry.timestamp)\n if (pending && pending.line === line && pending.bucket === bucket) {\n pending.repeat++\n continue\n }\n if (!flushPending()) break\n pending = { line, repeat: 1, bucket }\n }\n flushPending()\n // Splice buffered entries (in reverse — they're back-to-front) under the\n // opening header in oldest-first order.\n for (let i = buffered.length - 1; i >= 0; i--) lines.splice(1, 0, buffered[i])\n\n if (lines.length <= 1) return \"\"\n lines.push(\"[End of conversation — respond to the latest message]\")\n return lines.join(\"\\n\")\n }\n\n /**\n * Get raw log entries for a group.\n */\n getEntries(chatId: string): GroupLogEntry[] {\n return [...this.load(chatId)]\n }\n\n // --- Persistence ---\n\n private filePath(chatId: string): string {\n const safe = chatId.replace(/[^a-zA-Z0-9_-]/g, \"_\")\n return resolve(this.dir, `${safe}.json`)\n }\n\n private load(chatId: string): GroupLogEntry[] {\n // Check cache first\n if (this.cache.has(chatId)) return this.cache.get(chatId)!\n\n // Load from disk\n const path = this.filePath(chatId)\n if (!existsSync(path)) {\n this.cache.set(chatId, [])\n return []\n }\n\n try {\n const data = JSON.parse(readFileSync(path, \"utf-8\"))\n const entries = Array.isArray(data) ? data : []\n this.cache.set(chatId, entries)\n return entries\n } catch {\n this.cache.set(chatId, [])\n return []\n }\n }\n\n private save(chatId: string, entries: GroupLogEntry[]): void {\n try {\n writeFileSync(this.filePath(chatId), JSON.stringify(entries))\n } catch {\n // Best-effort persistence\n }\n }\n}\n","// --- Smart truncation ---\n//\n// Hard `.slice(0, N)` cuts words and lines at arbitrary offsets, which turns\n// multi-line webhook bodies (GitLab pipeline events, MR descriptions, long\n// agent responses) into visibly-broken fragments in Telegram notifications.\n// Prefer a line boundary, fall back to a word boundary, only hard-cut as\n// last resort. Append \"…\" only when truncation actually happened.\n\nexport function ellipsize(text: string, maxChars: number, ellipsis: string = \"…\"): string {\n if (!text) return \"\"\n if (text.length <= maxChars) return text\n\n const budget = maxChars - ellipsis.length\n if (budget <= 0) return text.slice(0, maxChars)\n\n const slice = text.slice(0, budget)\n\n // Prefer the last line break if it's past 60% of the budget — keeps\n // multi-line bodies readable instead of chopping mid-line.\n const nl = slice.lastIndexOf(\"\\n\")\n if (nl > budget * 0.6) return slice.slice(0, nl) + \"\\n\" + ellipsis\n\n // Fall back to the last word boundary past 60% of the budget.\n const sp = Math.max(slice.lastIndexOf(\" \"), slice.lastIndexOf(\"\\t\"))\n if (sp > budget * 0.6) return slice.slice(0, sp) + ellipsis\n\n return slice + ellipsis\n}\n\n/**\n * Take the first `maxLines` lines, then ellipsize the result to `maxChars`.\n * Useful for multi-line event bodies where only the summary lines matter.\n */\nexport function firstLines(text: string, maxLines: number, maxChars: number): string {\n if (!text) return \"\"\n const lines = text.split(\"\\n\")\n const picked = lines.slice(0, maxLines).join(\"\\n\")\n return ellipsize(picked, maxChars)\n}\n","// --- Smart Block Streaming ---\n//\n// Intelligent chunking for channel message delivery.\n// Instead of sending raw text deltas, accumulates text and emits\n// well-formed blocks that respect markdown structure.\n//\n// Features:\n// - minChars: don't emit until enough content (avoids fragment spam)\n// - Paragraph-aware breaks: prefer splitting at \\n\\n boundaries\n// - Code fence protection: never split inside ```...``` blocks\n// - Coalescing: merge rapid consecutive blocks via idle debounce\n// - Human-like pacing: optional randomized delay between blocks\n\nexport interface BlockStreamConfig {\n /** Minimum characters before emitting a block (default: 80) */\n minChars: number\n /** Maximum characters per block (default: 4000) */\n maxChars: number\n /** Idle time (ms) before flushing partial content (default: 2000) */\n idleFlushMs: number\n /** Minimum time between emissions (ms) (default: 1500) */\n minIntervalMs: number\n /** Whether to add human-like random delay (default: false) */\n humanPacing: boolean\n}\n\nconst DEFAULT_CONFIG: BlockStreamConfig = {\n minChars: 80,\n maxChars: 4000,\n idleFlushMs: 2000,\n minIntervalMs: 1500,\n humanPacing: false,\n}\n\n/** Per-channel default overrides */\nexport const CHANNEL_DEFAULTS: Record<string, Partial<BlockStreamConfig>> = {\n telegram: { minChars: 60, maxChars: 4096, minIntervalMs: 1500 },\n whatsapp: { minChars: 40, maxChars: 3000, minIntervalMs: 2000, humanPacing: true },\n discord: { minChars: 80, maxChars: 2000, minIntervalMs: 1000 },\n gitlab: { minChars: 200, maxChars: 60000, minIntervalMs: 3000 },\n}\n\nexport type BlockEmitter = (block: string) => void | Promise<void>\n\n/**\n * Smart block streamer that accumulates text deltas and emits\n * well-formed blocks respecting markdown structure.\n */\nexport class BlockStream {\n private config: BlockStreamConfig\n private buffer = \"\"\n private emitter: BlockEmitter\n private lastEmitTime = 0\n private idleTimer?: ReturnType<typeof setTimeout>\n private insideCodeFence = false\n private fencePattern = /^```/\n /**\n * Serialized chain of pending emissions. Each emit appends to this chain so\n * emissions run in order, and `flush()` can `await` the tail to guarantee\n * all emissions have completed before the caller proceeds — critical to\n * avoid the router racing to send a final message while a flushed-but-not-\n * yet-executed first emission is still queued (which would produce two\n * separate outbound messages instead of one send + subsequent edits).\n */\n private lastEmission: Promise<void> = Promise.resolve()\n\n constructor(emitter: BlockEmitter, config?: Partial<BlockStreamConfig>, channel?: string) {\n const channelDefaults = channel ? CHANNEL_DEFAULTS[channel] : {}\n this.config = { ...DEFAULT_CONFIG, ...channelDefaults, ...config }\n this.emitter = emitter\n }\n\n /**\n * Feed a text delta into the stream.\n */\n push(delta: string): void {\n this.buffer += delta\n\n // Reset idle timer\n if (this.idleTimer) {\n clearTimeout(this.idleTimer)\n }\n\n // Track code fence state\n this.updateCodeFenceState(delta)\n\n // Check if we should emit\n if (this.shouldEmit()) {\n this.emit()\n } else {\n // Set idle flush timer\n this.idleTimer = setTimeout(() => {\n if (this.buffer.length > 0) {\n this.emit()\n }\n }, this.config.idleFlushMs)\n }\n }\n\n /**\n * Flush any remaining content (call when stream ends). Awaits all pending\n * emissions so the caller can safely inspect state (e.g. whether a message\n * was already sent) immediately after flush() returns.\n */\n async flush(): Promise<void> {\n if (this.idleTimer) {\n clearTimeout(this.idleTimer)\n this.idleTimer = undefined\n }\n if (this.buffer.length > 0) {\n this.emitImmediate(this.buffer)\n this.buffer = \"\"\n }\n await this.lastEmission\n }\n\n /**\n * Get the full accumulated text so far (buffer + already emitted).\n */\n get pendingLength(): number {\n return this.buffer.length\n }\n\n private shouldEmit(): boolean {\n // Don't emit if inside a code fence (wait for it to close)\n if (this.insideCodeFence) return false\n\n // Don't emit below minimum chars\n if (this.buffer.length < this.config.minChars) return false\n\n // Respect minimum interval\n const elapsed = Date.now() - this.lastEmitTime\n if (elapsed < this.config.minIntervalMs) return false\n\n // Emit if we have a good break point\n return this.findBreakPoint() > 0\n }\n\n private emit(): void {\n const breakPoint = this.findBreakPoint()\n if (breakPoint <= 0 && this.buffer.length < this.config.maxChars) return\n\n const cutAt = breakPoint > 0\n ? breakPoint\n : Math.min(this.buffer.length, this.config.maxChars)\n\n const block = this.buffer.slice(0, cutAt)\n this.buffer = this.buffer.slice(cutAt)\n\n if (this.config.humanPacing) {\n const delay = 800 + Math.random() * 1700 // 800-2500ms\n setTimeout(() => this.emitImmediate(block), delay)\n } else {\n this.emitImmediate(block)\n }\n }\n\n private emitImmediate(block: string): void {\n this.lastEmitTime = Date.now()\n // Append to the serialized chain so emissions run in order and flush()\n // can await the tail. Swallow per-emission errors — streaming is\n // best-effort and must not break the chain for subsequent blocks.\n this.lastEmission = this.lastEmission.then(() =>\n Promise.resolve()\n .then(() => this.emitter(block))\n .catch(() => { /* best-effort */ })\n )\n }\n\n /**\n * Find the best point to break the buffer.\n * Priority: paragraph > newline > sentence > word.\n * Returns -1 if no good break found.\n */\n private findBreakPoint(): number {\n const buf = this.buffer\n const max = Math.min(buf.length, this.config.maxChars)\n\n // 1. Paragraph break (\\n\\n)\n const paraIdx = buf.lastIndexOf(\"\\n\\n\", max)\n if (paraIdx >= this.config.minChars) return paraIdx + 2\n\n // 2. Single newline\n const nlIdx = buf.lastIndexOf(\"\\n\", max)\n if (nlIdx >= this.config.minChars) return nlIdx + 1\n\n // 3. Sentence end (. ! ? followed by space)\n for (let i = max - 1; i >= this.config.minChars; i--) {\n if ((buf[i] === \".\" || buf[i] === \"!\" || buf[i] === \"?\") &&\n (i + 1 >= buf.length || buf[i + 1] === \" \" || buf[i + 1] === \"\\n\")) {\n return i + 1\n }\n }\n\n // 4. Hard cap\n if (buf.length >= this.config.maxChars) {\n // Find nearest word break\n const spaceIdx = buf.lastIndexOf(\" \", max)\n if (spaceIdx >= this.config.minChars) return spaceIdx + 1\n return max\n }\n\n return -1\n }\n\n /**\n * Track whether we're inside a code fence.\n * Don't break inside fences to keep markdown valid.\n */\n private updateCodeFenceState(delta: string): void {\n const lines = delta.split(\"\\n\")\n for (const line of lines) {\n if (this.fencePattern.test(line.trim())) {\n this.insideCodeFence = !this.insideCodeFence\n }\n }\n }\n}\n","import type { DaemonConfig } from \"@/daemon/config\"\nimport type { AgentRegistry } from \"@/agents/registry\"\nimport type { A2AMesh } from \"@/a2a/mesh\"\nimport type { ChannelAdapter, IncomingMessage, OutgoingMessage } from \"./types\"\nimport type { TelegramAdapter } from \"./telegram\"\nimport type { HookRegistry } from \"@/hooks\"\nimport { GroupLog } from \"./group-log\"\nimport { HandoverStore, type HandoverOverride } from \"./handover-store\"\nimport { BlockStream } from \"./block-stream\"\nimport { ellipsize, firstLines } from \"@/utils/ellipsize\"\nimport type { ServiceMatcher } from \"@/services/matcher\"\nimport type { BusinessLayer } from \"@/business\"\nimport { readFileSync, writeFileSync, existsSync, mkdirSync, appendFileSync } from \"fs\"\nimport { resolve, dirname } from \"path\"\nimport { fromIncoming, type InboundEnvelope } from \"./inbound/envelope\"\nimport { runPipeline, type PipelineResult } from \"./inbound/pipeline\"\nimport { defaultPipeline } from \"./inbound/stages\"\nimport { pickAccountForAgent } from \"./account-resolution\"\nimport { getEventBus } from \"@/events/bus\"\nimport { getLedgerMode } from \"@/intent/mode\"\nimport { getDefaultLedger } from \"@/intent/instance\"\nimport { recordRouterDispatch, routerChannelToSource } from \"@/intent/sources/router\"\nimport type { LegacyOutcome } from \"@/intent/divergence\"\n\n/**\n * Crash-safe inflight task log. Every message we commit to handling is\n * appended as a `start` entry to .agentx/router/inflight.jsonl. When the\n * handler returns (success OR error), we append a matching `done` entry.\n *\n * On daemon boot, any `start` without a `done` is replayed through the\n * handler with a `replay` flag that bypasses dedup — because a crash\n * between start and done means the user never got a reply.\n *\n * Trade-off vs always replaying: we only resume work that was IN PROGRESS\n * at crash time. Genuine handler failures (task returned an error) still\n * get their `done` marker and are NOT retried — so we don't infinite-loop\n * on a poison-pill message.\n */\ninterface InflightStart {\n type: \"start\"\n id: string\n channel: string\n accountId: string\n text: string\n sender: IncomingMessage[\"sender\"]\n group?: IncomingMessage[\"group\"]\n replyTo?: string\n replyToText?: string\n timestamp: string\n resolvedAgent?: string\n preferNode?: string\n ts: number\n}\n\nclass InflightLog {\n private filePath: string\n\n constructor(baseDir: string) {\n this.filePath = resolve(baseDir, \".agentx/router/inflight.jsonl\")\n mkdirSync(dirname(this.filePath), { recursive: true })\n }\n\n start(entry: Omit<InflightStart, \"type\" | \"ts\">): void {\n try {\n const line = JSON.stringify({ type: \"start\", ts: Date.now(), ...entry }) + \"\\n\"\n appendFileSync(this.filePath, line)\n } catch { /* best-effort persistence */ }\n }\n\n done(id: string): void {\n try {\n appendFileSync(this.filePath, JSON.stringify({ type: \"done\", id, ts: Date.now() }) + \"\\n\")\n } catch { /* */ }\n }\n\n /** Scan the log and return all `start` entries without a matching `done`. */\n loadUnfinished(): InflightStart[] {\n if (!existsSync(this.filePath)) return []\n try {\n const raw = readFileSync(this.filePath, \"utf-8\")\n const byId = new Map<string, InflightStart>()\n for (const line of raw.split(\"\\n\")) {\n if (!line.trim()) continue\n try {\n const entry = JSON.parse(line) as { type: string; id: string } & InflightStart\n if (entry.type === \"start\") byId.set(entry.id, entry as InflightStart)\n else if (entry.type === \"done\") byId.delete(entry.id)\n } catch { /* skip corrupt line */ }\n }\n return [...byId.values()]\n } catch { return [] }\n }\n\n /** Rewrite the file with only currently-unfinished entries. Call on graceful\n * stop or periodically — keeps the file from growing without bound. */\n compact(): void {\n try {\n const unfinished = this.loadUnfinished()\n const out = unfinished.map((e) => JSON.stringify(e)).join(\"\\n\") + (unfinished.length ? \"\\n\" : \"\")\n writeFileSync(this.filePath, out)\n } catch { /* */ }\n }\n}\n\n// --- Message Router ---\n// Routes channel messages to agents. Supports:\n// - Typing indicator while processing\n// - Streaming response edits\n// - Seen reaction (👀) on mention\n// - Bot-to-bot: if response mentions another agent, route it\n// - Correct bot account sends the reply (not always the first one)\n\nconst STREAM_EDIT_INTERVAL_MS = 1500\nconst TYPING_INTERVAL_MS = 4000\n\nexport class MessageRouter {\n private registry: AgentRegistry\n private config: DaemonConfig\n private channels: Map<string, ChannelAdapter> = new Map()\n private hooks?: HookRegistry\n private mesh?: A2AMesh\n private serviceMatcher?: ServiceMatcher\n private business?: BusinessLayer\n private groupLog: GroupLog\n /** Runtime per-chat routing overrides (handovers). Consulted at the top\n * of resolveAgent; config routes + mention matching only run when no\n * override applies. */\n private handoverStore: HandoverStore\n private log: (...args: unknown[]) => void\n\n /** Exposed so the admin panel + registry can share the same store. */\n getHandoverStore(): HandoverStore { return this.handoverStore }\n\n /**\n * Short-TTL cache of recently processed incoming message IDs, keyed by\n * `<channel>:<accountId>:<id>`. Prevents duplicate replies from:\n * - GitLab webhook retries (same object_attributes.id redelivered)\n * - WhatsApp Baileys double-emitting messages.upsert (notify + append)\n * - Telegram polling re-delivering an update after a long-poll hiccup\n * - A daemon crash-loop replaying messages the previous process had\n * already handled (fixed by persisting this map to disk — see below)\n *\n * The key INCLUDES accountId so two bots in the same Telegram group\n * (different accountId) both get to run their routing logic — the router's\n * downstream boundAccount check then picks the one bot that should actually\n * reply. Without accountId in the key, the second bot's legitimate view of\n * the same Telegram message_id would be wrongly suppressed.\n *\n * Persisted to .agentx/router/dedup.json so survival-across-restart is not\n * just a Telegram concern — the same universal LRU covers every channel.\n * Writes are debounced (every 20 new ids, or 5s, whichever first) and the\n * file is pruned to only live TTL entries before each write.\n */\n private recentMessageIds: Map<string, number> = new Map()\n private readonly MESSAGE_ID_TTL_MS = 5 * 60 * 1000\n private readonly MESSAGE_ID_MAX_ENTRIES = 2000\n private readonly DEDUP_STORE_PATH = \".agentx/router/dedup.json\"\n private dedupDirtyCount = 0\n private dedupSaveTimer?: ReturnType<typeof setTimeout>\n\n /** Crash-safe inflight log (see InflightLog class above). */\n private inflight: InflightLog\n\n /** In-flight mesh forwards (this node is awaiting a peer's response).\n * Counted so the daemon's shutdown drain can wait for these in addition\n * to local agent tasks — otherwise a clawd restart mid-forward kills the\n * awaiter, the peer's response lands on a dead connection, and the\n * inflight-replay re-runs the task on the peer. */\n private activeMeshForwards = 0\n getActiveMeshForwardCount(): number { return this.activeMeshForwards }\n\n constructor(\n registry: AgentRegistry,\n config: DaemonConfig,\n hooks?: HookRegistry,\n log: (...args: unknown[]) => void = console.error.bind(console, \"[router]\"),\n ) {\n this.registry = registry\n this.config = config\n this.hooks = hooks\n this.log = log\n this.groupLog = new GroupLog()\n this.handoverStore = new HandoverStore({ log: (...a) => log(\"[handover]\", ...a) })\n this.inflight = new InflightLog(process.cwd())\n this.loadDedupFromDisk()\n }\n\n private loadDedupFromDisk(): void {\n try {\n const p = resolve(process.cwd(), this.DEDUP_STORE_PATH)\n if (!existsSync(p)) return\n const raw = JSON.parse(readFileSync(p, \"utf-8\")) as Record<string, number>\n const now = Date.now()\n let restored = 0\n for (const [key, ts] of Object.entries(raw)) {\n if (typeof ts === \"number\" && now - ts < this.MESSAGE_ID_TTL_MS) {\n this.recentMessageIds.set(key, ts)\n restored++\n }\n }\n if (restored > 0) {\n this.log(`Router dedup: restored ${restored} recent message ids from disk`)\n }\n } catch {\n // Corrupt file — start fresh, not worth failing startup over\n }\n }\n\n private scheduleDedupSave(): void {\n this.dedupDirtyCount++\n // Fast flush on bursty traffic (every 20 new ids) plus a 5s debounce\n // safety net for slow-trickle activity so we never lose more than a few\n // seconds of entries in a hard crash.\n if (this.dedupDirtyCount >= 20) {\n this.flushDedupToDisk()\n return\n }\n if (this.dedupSaveTimer) return\n this.dedupSaveTimer = setTimeout(() => this.flushDedupToDisk(), 5000)\n }\n\n private flushDedupToDisk(): void {\n if (this.dedupSaveTimer) {\n clearTimeout(this.dedupSaveTimer)\n this.dedupSaveTimer = undefined\n }\n this.dedupDirtyCount = 0\n try {\n const now = Date.now()\n const live: Record<string, number> = {}\n for (const [key, ts] of this.recentMessageIds) {\n if (now - ts < this.MESSAGE_ID_TTL_MS) live[key] = ts\n }\n const p = resolve(process.cwd(), this.DEDUP_STORE_PATH)\n const dir = dirname(p)\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true })\n writeFileSync(p, JSON.stringify(live))\n } catch {\n // Best-effort — a failed write means at-most-restart-window of replays,\n // not a correctness problem.\n }\n }\n\n /** Called by the daemon on graceful shutdown so no in-memory ids are lost. */\n flushPersistence(): void {\n this.flushDedupToDisk()\n // Compact the inflight log so the file doesn't grow unboundedly across\n // clean restarts. A crash skips this; the next boot just reads the\n // untrimmed log and does the same replay — still correct, just slightly\n // slower on the first scan.\n this.inflight.compact()\n }\n\n setMesh(mesh: A2AMesh): void {\n this.mesh = mesh\n }\n\n setServiceMatcher(matcher: ServiceMatcher): void {\n this.serviceMatcher = matcher\n }\n\n /** Accessor so the daemon reload path can call matcher.reload() without\n * holding a duplicate reference. Returns undefined if services were\n * empty at boot. */\n getServiceMatcher(): ServiceMatcher | undefined {\n return this.serviceMatcher\n }\n\n setBusiness(business: BusinessLayer): void {\n this.business = business\n }\n\n /** Swap the live DaemonConfig reference after a hot-reload. Router reads\n * from this.config at send-time for things like per-account Telegram token\n * resolution, default agents, and policy allowlists — so any /reload that\n * touches channels must call this to avoid sending from a stale snapshot. */\n updateConfig(next: DaemonConfig): void {\n this.config = next\n }\n\n /** Look up a live adapter by name, e.g. \"telegram\". Used by the daemon\n * reload path to hot-swap account configs without a restart. */\n getChannel(name: string): ChannelAdapter | undefined {\n return this.channels.get(name)\n }\n\n addChannel(adapter: ChannelAdapter): void {\n this.channels.set(adapter.name, adapter)\n adapter.onMessage((msg) => this.handleMessage(adapter, msg))\n }\n\n /**\n * Send an outbound message to any registered channel.\n * Used for agent-initiated messages, cron notifications, cross-channel routing.\n *\n * If accountId is not provided for Telegram, auto-resolves from agentId binding.\n */\n async sendOutbound(msg: OutgoingMessage & { accountId?: string }): Promise<string | void> {\n const adapter = this.channels.get(msg.channel)\n if (!adapter) {\n throw new Error(`Unknown channel: \"${msg.channel}\". Available: ${[...this.channels.keys()].join(\", \")}`)\n }\n\n // Auto-resolve Telegram accountId from agentId if not provided\n let accountId = msg.accountId\n if (adapter.name === \"telegram\" && !accountId && msg.agentId) {\n accountId = this.getAccountForAgent(msg.agentId)\n }\n\n this.log(`Outbound [${msg.channel}] -> ${msg.chatId}: ${msg.text.slice(0, 80)}`)\n\n let messageId: string | void\n if (adapter.name === \"telegram\" && accountId) {\n messageId = await this.adapterSend(adapter, { ...msg, accountId })\n } else {\n messageId = await adapter.send(msg)\n }\n\n // Record outbound into the recipient agent's channel session so cron/api/\n // a2a sends become visible to the next inbound turn on the same chatId.\n // This closes the cron-vs-telegram split: when marketing-agent's daily\n // cron sends \"trend brief\" to Anis at 06:17, the next telegram message\n // from Anis at 08:36 sees the brief in session history instead of treating\n // the conversation as fresh.\n //\n // Only fires when an agentId is set — manual /send tests without an agent\n // identity stay out of session JSON to avoid polluting an agent's\n // conversation with anonymous bot traffic.\n if (msg.agentId) {\n try {\n this.registry.getSessionStore().addAgentMessage(msg.agentId, msg.channel, msg.chatId, msg.text)\n } catch (e: any) {\n this.log(`Failed to record outbound in recipient session: ${e.message}`)\n }\n }\n\n return messageId\n }\n\n /** List registered channel names. */\n getChannelNames(): string[] {\n return [...this.channels.keys()]\n }\n\n async startAll(): Promise<void> {\n for (const [name, adapter] of this.channels) {\n this.log(`Starting channel: ${name}`)\n await adapter.start()\n }\n // Replay any work the previous process committed to but never finished.\n // Deferred until after channel startup so adapters can actually send\n // replies. Fire-and-forget — we don't block normal traffic on it.\n this.replayInflight().catch((e) => this.log(`Inflight replay error: ${e.message}`))\n }\n\n /**\n * On startup, re-run any incoming message that was recorded as `start` but\n * never got a matching `done` in the inflight log. Those are tasks the\n * previous process committed to handling but crashed before completing.\n */\n private async replayInflight(): Promise<void> {\n const unfinished = this.inflight.loadUnfinished()\n if (unfinished.length === 0) return\n this.log(`Inflight replay: ${unfinished.length} task(s) to resume from previous run`)\n for (const entry of unfinished) {\n const adapter = this.channels.get(entry.channel)\n if (!adapter) {\n this.log(`Inflight replay: adapter \"${entry.channel}\" not available, skipping task ${entry.id}`)\n // Mark it done so we don't re-attempt forever — channel may be\n // permanently disabled in the current config.\n this.inflight.done(entry.id)\n continue\n }\n const msg: IncomingMessage = {\n id: entry.id,\n channel: entry.channel,\n accountId: entry.accountId,\n sender: entry.sender,\n group: entry.group,\n text: entry.text,\n replyTo: entry.replyTo,\n replyToText: entry.replyToText,\n timestamp: new Date(entry.timestamp),\n resolvedAgent: entry.resolvedAgent,\n preferNode: entry.preferNode,\n }\n this.log(`Inflight replay: ${entry.channel}/${entry.id} (agent=${entry.resolvedAgent || \"resolve\"})`)\n this.handleMessage(adapter, msg, { replay: true })\n .catch((e) => this.log(`Inflight replay failed for ${entry.id}: ${e.message}`))\n // Always mark done on replay — single-shot by design. If the replayed\n // handler re-committed and wrote its own done, the duplicate is\n // harmless (loadUnfinished dedups by id). If the handler returned\n // early (agent removed from config, no adapter, etc.), this clears\n // the stale entry so we don't retry forever.\n .finally(() => this.inflight.done(entry.id))\n }\n // Compact the file now that we've snapshotted — old entries are either\n // being replayed (will get a fresh start/done) or already drained.\n this.inflight.compact()\n }\n\n async stopAll(): Promise<void> {\n for (const [name, adapter] of this.channels) {\n this.log(`Stopping channel: ${name}`)\n await adapter.stop()\n }\n }\n\n /**\n * Returns true if this message id was processed within MESSAGE_ID_TTL_MS.\n * Stamps the key when false. Size-bounded GC avoids unbounded growth.\n */\n private isDuplicateMessage(msg: IncomingMessage): boolean {\n if (!msg.id) return false\n const key = `${msg.channel}:${msg.accountId || \"default\"}:${msg.id}`\n const now = Date.now()\n if (this.recentMessageIds.size >= this.MESSAGE_ID_MAX_ENTRIES) {\n for (const [k, t] of this.recentMessageIds) {\n if (now - t > this.MESSAGE_ID_TTL_MS) this.recentMessageIds.delete(k)\n }\n }\n const seen = this.recentMessageIds.get(key)\n if (seen && now - seen < this.MESSAGE_ID_TTL_MS) return true\n this.recentMessageIds.set(key, now)\n this.scheduleDedupSave()\n return false\n }\n\n /**\n * Phase 1 commit 6.b — record one router decision in the intent ledger\n * when the source is enabled (mode != \"off\"). Covers all router\n * channels (telegram, slack, whatsapp, discord). Each channel has its\n * own per-source mode flag so they can be promoted independently in 1c.\n * Wrapped in try/catch so a ledger failure can never break message\n * routing — legacy is still authoritative until the 1c per-source\n * promotion.\n *\n * Returns the recorded decision when ledger fired (and outcome was\n * \"dispatched\"), so the caller can tag the IncomingMessage with\n * `intentRef` for resolution-on-completion. Returns undefined when\n * the source is off, the channel isn't supported, ledger threw, or\n * the ledger's own decision was non-dispatched (deduped/halted —\n * no resolution to write).\n */\n private recordRouterDecisionInLedger(\n msg: IncomingMessage,\n legacy: LegacyOutcome,\n ): { eventId: string; decidedBy: string } | undefined {\n const source = routerChannelToSource(msg.channel)\n if (!source) return undefined // channel not router-supported (e.g., gitlab routes itself)\n if (getLedgerMode(source) === \"off\") return undefined\n try {\n const decision = recordRouterDispatch(getDefaultLedger(), msg, source, JSON.stringify(msg), legacy)\n if (decision.outcome === \"dispatched\") {\n return { eventId: decision.eventId, decidedBy: decision.decidedBy }\n }\n return undefined\n } catch (e: any) {\n this.log(`[ledger] router ${msg.channel}/${msg.id} record failed: ${e?.message ?? e}`)\n return undefined\n }\n }\n\n private async handleMessage(\n adapter: ChannelAdapter,\n msg: IncomingMessage,\n opts: { replay?: boolean } = {},\n ): Promise<void> {\n // Dedup: drop redeliveries of the same incoming message id within a TTL.\n // Guards against GitLab webhook retries, Baileys double-emit, and Telegram\n // offset hiccups. Scoped per-accountId so multi-bot Telegram groups still\n // route each account's legitimate view of the message.\n //\n // Replay-on-resume (from inflight log) deliberately bypasses dedup — a\n // replayed message is one the previous process committed to handling but\n // crashed before finishing, so the dedup entry is stale and the user\n // still hasn't received their answer.\n if (!opts.replay && this.isDuplicateMessage(msg)) {\n this.log(`Duplicate ${msg.channel} message dropped: ${msg.accountId || \"default\"}/${msg.id}`)\n this.recordRouterDecisionInLedger(msg, {\n agentId: null, outcome: \"deduped\", reason: \"isDuplicateMessage\",\n })\n return\n }\n\n // Pre-hook\n if (this.hooks?.has(\"pre:channel-message\" as any)) {\n const hookResult = await this.hooks.execute(\"pre:channel-message\" as any, {\n event: \"pre:channel-message\" as any,\n channel: msg.channel,\n sender: msg.sender.name,\n text: msg.text,\n group: msg.group?.name,\n // Full IncomingMessage for subscribers that need structured access\n // (e.g. the workflow dispatcher). Existing subscribers read the\n // flat fields above and are unaffected by this addition.\n msg,\n })\n\n if (hookResult.blocked) {\n this.log(`Message blocked by hook: ${hookResult.message}`)\n this.recordRouterDecisionInLedger(msg, {\n agentId: null, outcome: \"halted\",\n reason: `pre-hook blocked: ${hookResult.message ?? \"\"}`.trim(),\n })\n return\n }\n\n if (hookResult.modified?.text) {\n msg = { ...msg, text: hookResult.modified.text as string }\n }\n }\n\n // Log ALL group messages for conversation context (before agent resolution)\n if (msg.group) {\n const chatId = msg.group.id\n this.groupLog.add(chatId, msg.sender.name, msg.text)\n }\n\n // Check if message matches a defined service (before agent routing)\n if (this.serviceMatcher) {\n const chatId = msg.group?.id || msg.sender.id\n const matched = this.serviceMatcher.match(msg.text, msg.sender.id, msg.channel)\n if (matched) {\n this.log(`Service matched: \"${matched.service.name}\" for ${msg.sender.name} (trigger: ${matched.trigger})`)\n // Phase 1 commit 6.b-extended: service-matcher claims the message\n // before agent routing. From the agent-router's POV no agent was\n // dispatched — a service handler ran instead. Recorded as halted\n // with the service name in the reason for forensics.\n this.recordRouterDecisionInLedger(msg, {\n agentId: null, outcome: \"halted\",\n reason: `service-match: ${matched.service.name} (trigger: ${matched.trigger})`,\n })\n const replyAccountId = msg.accountId\n this.adapterReact(adapter, chatId, msg.id, \"👀\", replyAccountId)\n const typingTimer = this.startTypingLoop(adapter, chatId, replyAccountId)\n\n await this.serviceMatcher.execute(\n matched.service,\n this.registry,\n { channel: msg.channel, sender: msg.sender.name, chatId },\n async (text) => {\n clearInterval(typingTimer)\n await this.adapterSend(adapter, {\n channel: msg.channel, chatId, text, replyTo: msg.id, accountId: replyAccountId,\n })\n },\n )\n return\n }\n }\n\n // Resolve agent via the routing pipeline (Phase 2). Each stage produces\n // a named decision; we trace the deciding stage so \"why didn't agent X\n // get this message?\" is one log-line away.\n const result = this.runRoutingPipeline(msg)\n const agentId = result.agentId\n\n if (!agentId) {\n this.traceRoute(msg, \"drop\", result.reason, result.decidingStage)\n this.recordRouterDecisionInLedger(msg, {\n agentId: null, outcome: \"halted\", reason: `routing:${result.reason}`,\n })\n return\n }\n\n // Dedup: in groups, multiple bot accounts receive the same message.\n // Only the account BOUND to this agent should handle it. When multiple\n // accounts share the same agentBinding, getAccountForAgent prefers one\n // that's in the group — without this, dropping by the absent canonical\n // bot would silently swallow messages received via the in-group bot.\n if (msg.group && msg.channel === \"telegram\") {\n const boundAccount = this.getAccountForAgent(agentId, msg.group.id)\n if (boundAccount && boundAccount !== msg.accountId) {\n this.traceRoute(msg, \"drop\", `multi-account-dedup (bound=${boundAccount} got=${msg.accountId})`, \"multi-account-dedup\")\n this.recordRouterDecisionInLedger(msg, {\n agentId: null, outcome: \"halted\",\n reason: `multi-account-dedup (bound=${boundAccount} got=${msg.accountId})`,\n })\n return\n }\n }\n\n this.traceRoute(msg, \"match\", `${result.decidingStage} agent=${agentId}`, result.decidingStage, agentId)\n const intentRef = this.recordRouterDecisionInLedger(msg, {\n agentId, outcome: \"dispatched\", reason: result.decidingStage,\n })\n // Phase 1 / 6 — tag the IncomingMessage so registry.execute records\n // a ledger resolution when the agent task completes. Only set when\n // the ledger's own decision was \"dispatched\" (recordRouterDecision...\n // already filtered the deduped/halted cases).\n if (intentRef) {\n msg = { ...msg, intentRef }\n }\n\n const chatId = msg.group?.id || msg.sender.id\n\n // From here on we're committed to processing this message. Write a `start`\n // entry to the inflight log so a crash between now and the task's\n // completion gets the replay treatment on next boot. `done` is always\n // written via the try/finally wrapper further down.\n this.inflight.start({\n id: msg.id,\n channel: msg.channel,\n accountId: msg.accountId,\n text: msg.text,\n sender: msg.sender,\n group: msg.group,\n replyTo: msg.replyTo,\n replyToText: msg.replyToText,\n timestamp: (msg.timestamp instanceof Date ? msg.timestamp : new Date()).toISOString(),\n resolvedAgent: agentId,\n preferNode: msg.preferNode,\n })\n\n try { return await this.processResolvedMessage(adapter, msg, agentId, chatId) }\n finally { this.inflight.done(msg.id) }\n }\n\n /** The actual processing body — split out so handleMessage can wrap it in\n * the inflight try/finally without drowning the happy path in indentation. */\n private async processResolvedMessage(\n adapter: ChannelAdapter,\n msg: IncomingMessage,\n agentId: string,\n chatId: string,\n ): Promise<void> {\n // If preferNode is set, skip local and route directly to the specified mesh peer\n if (msg.preferNode) {\n this.log(`preferNode=\"${msg.preferNode}\" — forcing mesh routing for agent \"${agentId}\"`)\n const routed = await this.handleViaMeshByPeer(adapter, msg, agentId, msg.preferNode)\n if (!routed) {\n this.log(`Mesh peer \"${msg.preferNode}\" not found or unhealthy for agent \"${agentId}\"`)\n }\n return\n }\n\n const agentDef = this.registry.getAgent(agentId)\n\n // Agent not found locally — try forwarding to a mesh peer\n if (!agentDef) {\n const routed = await this.handleViaMeshByAgentId(adapter, msg, agentId)\n if (!routed) {\n this.log(`Agent \"${agentId}\" not found locally or on any mesh peer`)\n }\n return\n }\n\n const agentName = agentDef.name || agentId\n\n // Determine which bot account should send the response. In groups, prefer\n // the account whose bot is actually present (matters for multi-bound agents).\n const replyAccountId = this.getAccountForAgent(agentId, msg.group?.id) || msg.accountId\n\n this.log(\n `Routing [${msg.channel}/${msg.sender.name}] -> \"${agentName}\": ${msg.text.slice(0, 80)}`,\n )\n\n // React with 👀 to acknowledge\n this.adapterReact(adapter, chatId, msg.id, \"👀\", replyAccountId)\n\n // Start typing indicator loop (from the correct bot)\n const typingTimer = this.startTypingLoop(adapter, chatId, replyAccountId)\n\n // Streaming setup with smart block streaming.\n //\n // Single mode for every channel: first block creates a real message,\n // subsequent blocks `editMessageText` it in place. Stream is gated by\n // adapter.editMessage support so adapters that can't edit just receive\n // a single final message.\n //\n // Earlier versions used `sendMessageDraft` for Telegram DMs as a \"smoother\n // animation\" path — but Telegram drafts are bot-side typing affordances\n // that auto-clear when the bot stops updating them, never becoming\n // persistent messages (sendMessageDraft returns `result: true`, not a\n // message id). The final reply written via sendDraft therefore vanished\n // shortly after delivery. Edit-in-place sits well under Telegram's edit\n // rate limit at the existing 1.5s throttle and gives a uniform path\n // across DMs, groups, and non-Telegram channels.\n const canStream = typeof adapter.editMessage === \"function\"\n let sentMessageId: string | undefined\n let fullStreamText = \"\"\n\n const blockStream = canStream\n ? new BlockStream(\n async (block: string) => {\n fullStreamText += block\n if (!sentMessageId) {\n const preview = fullStreamText.length > 20\n ? fullStreamText\n : `_${agentName} is writing..._\\n\\n${fullStreamText}`\n try {\n sentMessageId = await this.adapterSend(adapter, {\n channel: msg.channel,\n chatId,\n text: preview,\n replyTo: msg.id,\n accountId: replyAccountId,\n })\n } catch { /* retry next block */ }\n } else {\n try {\n await this.adapterEdit(adapter, chatId, sentMessageId, fullStreamText, undefined, replyAccountId)\n } catch { /* retry next block */ }\n }\n },\n undefined,\n msg.channel,\n )\n : undefined\n\n const onDelta = blockStream\n ? (_delta: string, _fullText: string) => {\n blockStream.push(_delta)\n }\n : undefined\n\n // Enrich channelMeta agents with handles from config\n if (msg.channelMeta?.agents) {\n for (const agent of msg.channelMeta.agents) {\n if (!agent.handle) {\n const def = this.registry.getAgent(agent.id)\n if (def) {\n agent.handle = def.mentions.find((m: string) => m.startsWith(\"@\"))\n agent.name = def.name\n }\n }\n }\n }\n\n // Build group conversation context (recent messages from the group)\n const groupContext = msg.group ? this.groupLog.buildContext(chatId) : \"\"\n const messageWithContext = groupContext\n ? `${groupContext}\\n\\n${msg.sender.name}: ${msg.text}`\n : msg.text\n\n // Execute agent task\n const response = await this.registry.execute(\n {\n message: messageWithContext,\n agentId,\n // Phase 1 / 6 — propagate the intent-ledger reference so the\n // registry can record a resolution on completion. Set by\n // gitlab/router shadow-mode wiring; absent under mode=off.\n intentRef: msg.intentRef,\n context: {\n channel: msg.channel,\n sender: msg.sender.name,\n senderId: msg.sender.id,\n senderUsername: msg.sender.username,\n group: msg.group?.name,\n chatId, // stable ID for session keying (issue path for GitLab, group ID for Telegram)\n mediaPath: msg.media?.path,\n mediaType: msg.media?.type,\n replyToText: msg.replyToText,\n channelMeta: msg.channelMeta,\n },\n },\n onDelta,\n )\n\n clearInterval(typingTimer)\n\n // Flush any remaining streamed content. Awaited so that sentMessageId is\n // guaranteed set (if a stream block arrived) before we decide whether the\n // final write goes via adapterSend (new message) or adapterEdit (update).\n // Skipping the await caused double-sends on fast streams: the flushed\n // first emission queued an adapterSend, the router moved on with\n // sentMessageId still undefined, then the router's own adapterSend fired\n // — producing two separate messages instead of one edited.\n await blockStream?.flush()\n\n if (response.error) {\n // Queued messages are not errors — the message will be processed later\n if (response.error.startsWith(\"__queued__\")) {\n clearInterval(typingTimer)\n this.log(`Message queued for ${agentName}`)\n return\n }\n\n this.log(`Agent error: ${response.error}`)\n const errorText = `Error: ${response.error}`\n if (sentMessageId) {\n await this.adapterEdit(adapter, chatId, sentMessageId, errorText, \"plain\", replyAccountId)\n } else {\n await this.adapterSend(adapter, {\n channel: msg.channel,\n chatId,\n text: errorText,\n replyTo: msg.id,\n parseMode: \"plain\",\n accountId: replyAccountId,\n })\n }\n return\n }\n\n // Post-hook\n let responseText = response.content\n if (this.hooks?.has(\"post:channel-message\" as any)) {\n const hookResult = await this.hooks.execute(\"post:channel-message\" as any, {\n event: \"post:channel-message\" as any,\n channel: msg.channel,\n sender: msg.sender.name,\n response: responseText,\n agentId,\n })\n\n if (hookResult.blocked) {\n this.log(`Response blocked by hook: ${hookResult.message}`)\n return\n }\n\n if (hookResult.modified?.response) {\n responseText = hookResult.modified.response as string\n }\n }\n\n // Prefix response with agent identity on shared-identity channels.\n // GitLab: when no per-agent token (all share one user)\n // WhatsApp: always (single phone number, all agents share it)\n if (msg.channel === \"gitlab\" && responseText) {\n const gitlabAdapter = this.channels.get(\"gitlab\") as any\n const hasOwnToken = gitlabAdapter?.getAgentToken?.(agentId)\n if (!hasOwnToken) {\n responseText = `> **${agentName}** (${agentId})\\n\\n${responseText}`\n }\n }\n if (msg.channel === \"whatsapp\" && responseText) {\n responseText = `*${agentName}*\\n\\n${responseText}`\n }\n\n // Final message:\n // 1. Stream landed → editMessageText to replace the streamed preview\n // with the canonical post-hook responseText.\n // 2. No stream → plain sendMessage.\n let sentResponseId: string | undefined\n if (responseText) {\n if (sentMessageId) {\n await this.adapterEdit(adapter, chatId, sentMessageId, responseText, undefined, replyAccountId)\n sentResponseId = sentMessageId\n } else {\n sentResponseId = await this.adapterSend(adapter, {\n channel: msg.channel,\n chatId,\n text: responseText,\n replyTo: msg.id,\n accountId: replyAccountId,\n agentId,\n })\n }\n }\n\n // Log bot response in group conversation\n if (msg.group && responseText) {\n this.groupLog.add(chatId, agentName, responseText)\n }\n\n // Notify on long-running task completion (cross-channel)\n const notifyConfig = this.config.notifications\n if (notifyConfig?.destination && response.duration) {\n const thresholdMs = (notifyConfig.longTaskThreshold || 30) * 1000\n const shouldNotify =\n (response.duration >= thresholdMs && !response.error && notifyConfig.on?.taskComplete) ||\n (response.error && notifyConfig.on?.taskError)\n\n if (shouldNotify) {\n const durSec = Math.round(response.duration / 1000)\n const status = response.error ? \"failed\" : \"completed\"\n // Preserve line boundaries so pipeline/MR bodies aren't chopped\n // mid-word (e.g., \"Duration: 328s\\nPipeline #369\" shouldn't become\n // \"Duration: 328s\\nP\"). Keep the first few lines + more chars.\n const trigger = firstLines(msg.text, 6, 300)\n const preview = response.error\n ? ellipsize(response.error, 300)\n : firstLines(response.content, 6, 400)\n const notifyText = `${status === \"failed\" ? \"🔴\" : \"✅\"} **${agentName}** ${status} (${durSec}s)\\n${msg.channel}/${msg.sender.name}: ${trigger}\\n${preview}`\n\n this.sendOutbound({\n channel: notifyConfig.destination.channel,\n chatId: notifyConfig.destination.chatId,\n text: notifyText,\n agentId,\n accountId: notifyConfig.destination.accountId,\n }).catch((e) => {\n this.log(`Task notification failed: ${e.message}`)\n })\n }\n }\n\n // Business layer: record task completion for KPI utilization tracking.\n if (this.business && response.duration) {\n this.business.recordTaskCompletion(\n agentId,\n Math.round(response.duration / 1000),\n !response.error,\n msg.channel,\n )\n }\n\n // GitLab: auto-log time spent on the issue/MR\n if (msg.channel === \"gitlab\" && response.duration && !response.error) {\n const gitlabAdapter = this.channels.get(\"gitlab\") as any\n if (gitlabAdapter) {\n gitlabAdapter.logTimeSpent(chatId, response.duration, agentId).catch((e: any) => {\n this.log(`GitLab time tracking failed: ${e.message}`)\n })\n }\n }\n\n // Bot-to-bot delegation: if response mentions another agent, route to them.\n // Works on Telegram, WhatsApp, and Discord. Not GitLab (uses its own @mention webhook flow).\n const delegationChannels = [\"telegram\", \"whatsapp\", \"discord\", \"slack\"]\n if (responseText && sentResponseId && delegationChannels.includes(msg.channel)) {\n this.handleBotToBotChain(adapter, msg, agentId, responseText, sentResponseId, 0).catch((e) => {\n this.log(`Bot-to-bot error: ${e.message}`)\n })\n }\n }\n\n private static readonly MAX_BOT_CHAIN_DEPTH = 3\n\n /**\n * Bot-to-bot conversation chain.\n * Guards:\n * 1. Max depth (default 3)\n * 2. No agent called twice in the same chain (prevents A→B→A→B loops)\n * Works on Telegram (multi-account), WhatsApp (shared number), Discord.\n */\n private async handleBotToBotChain(\n adapter: ChannelAdapter,\n originalMsg: IncomingMessage,\n sourceAgentId: string,\n responseText: string,\n responseMessageId: string,\n depth: number,\n visited: Set<string> = new Set(),\n ): Promise<void> {\n if (depth >= MessageRouter.MAX_BOT_CHAIN_DEPTH) {\n this.log(`Bot-to-bot: max depth (${depth}) reached, stopping`)\n return\n }\n\n // Track who's been in this chain\n visited.add(sourceAgentId)\n\n for (const [id, def] of Object.entries(this.config.agents)) {\n if (id === sourceAgentId) continue\n\n // Stop if this agent was already in the chain (prevents A→B→A loop)\n if (visited.has(id)) {\n this.log(`Bot-to-bot: \"${id}\" already participated, stopping chain`)\n continue\n }\n\n // Only trigger bot-to-bot on explicit @-handle mentions (e.g. @my_bot),\n // not bare keywords which appear in normal conversation text.\n const atMentions = def.mentions.filter((m: string) => m.startsWith(\"@\"))\n const mentioned = atMentions.some((m: string) =>\n responseText.toLowerCase().includes(m.toLowerCase()),\n )\n if (!mentioned) continue\n\n this.log(`Bot-to-bot [${depth + 1}]: \"${sourceAgentId}\" -> \"${id}\"`)\n\n const chatId = originalMsg.group?.id || originalMsg.sender.id\n const targetAccountId = this.getAccountForAgent(id)\n const sourceAccountId = this.getAccountForAgent(sourceAgentId)\n\n try {\n // Target bot reacts 👀 to the source bot's message\n this.adapterReact(adapter, chatId, responseMessageId, \"👀\", targetAccountId)\n\n // Target bot shows typing\n const typingTimer = this.startTypingLoop(adapter, chatId, targetAccountId)\n\n // Include original user message as context so target bot knows the full picture\n const contextMessage = depth === 0\n ? `[Original from ${originalMsg.sender.name}]: ${originalMsg.text}\\n\\n[${sourceAgentId} said]: ${responseText}`\n : responseText\n\n const response = await this.registry.execute({\n message: contextMessage,\n agentId: id,\n context: {\n channel: originalMsg.channel,\n sender: `agent:${sourceAgentId}`,\n group: originalMsg.group?.name,\n // Bot-to-bot recursion must keep the SAME chatId as the\n // original message so the recursive reply lands in the same\n // session as the conversation it's continuing. Previously\n // chatId was derived from `group` (a name), which keyed the\n // session by group NAME instead of group ID — leaking the\n // recursive turn into a separate \"default\"/name-based bucket.\n chatId: originalMsg.group?.id || originalMsg.sender.id,\n channelMeta: originalMsg.channelMeta,\n },\n })\n\n clearInterval(typingTimer)\n\n if (response.content && !response.error) {\n // Prefix with agent identity on shared-number channels\n let replyText = response.content\n if (originalMsg.channel === \"whatsapp\") {\n replyText = `*${def.name}*\\n\\n${replyText}`\n }\n\n const sentId = await this.adapterSend(adapter, {\n channel: originalMsg.channel,\n chatId,\n text: replyText,\n accountId: targetAccountId,\n })\n\n // Chain: check if this response also mentions another agent\n if (sentId && response.content) {\n await this.handleBotToBotChain(\n adapter, originalMsg, id, response.content, sentId as string, depth + 1, visited,\n )\n }\n } else if (response.error) {\n this.log(`Bot-to-bot \"${id}\" error: ${response.error}`)\n }\n } catch (e: any) {\n this.log(`Bot-to-bot \"${id}\" failed: ${e.message}`)\n }\n\n break // Route to first mentioned agent per level\n }\n }\n\n // --- Adapter helpers that pass accountId for Telegram ---\n\n private async adapterSend(\n adapter: ChannelAdapter,\n msg: { channel: string; chatId: string; text: string; replyTo?: string; parseMode?: string; accountId?: string; agentId?: string },\n ): Promise<string> {\n // For Telegram, pass accountId so the correct bot sends the message\n if (adapter.name === \"telegram\" && msg.accountId) {\n return (adapter as unknown as TelegramAdapter).send({\n ...msg,\n parseMode: msg.parseMode as any,\n accountId: msg.accountId,\n }) as Promise<string>\n }\n return (adapter.send(msg as any) || \"\") as Promise<string>\n }\n\n private async adapterEdit(\n adapter: ChannelAdapter,\n chatId: string,\n messageId: string,\n text: string,\n parseMode?: string,\n accountId?: string,\n ): Promise<boolean> {\n if (adapter.name === \"telegram\" && accountId) {\n return (adapter as unknown as TelegramAdapter).editMessage(chatId, messageId, text, parseMode, accountId)\n }\n return adapter.editMessage?.(chatId, messageId, text, parseMode) ?? false\n }\n\n private adapterReact(\n adapter: ChannelAdapter,\n chatId: string,\n messageId: string,\n emoji: string,\n accountId?: string,\n ): void {\n if (adapter.name === \"telegram\" && accountId) {\n (adapter as unknown as TelegramAdapter).react(chatId, messageId, emoji, accountId)\n } else {\n adapter.react?.(chatId, messageId, emoji)\n }\n }\n\n private startTypingLoop(\n adapter: ChannelAdapter,\n chatId: string,\n accountId?: string,\n ): ReturnType<typeof setInterval> {\n const sendTyping = () => {\n if (adapter.name === \"telegram\" && accountId) {\n (adapter as unknown as TelegramAdapter).sendTyping(chatId, accountId)\n } else {\n adapter.sendTyping?.(chatId)\n }\n }\n\n sendTyping()\n return setInterval(sendTyping, TYPING_INTERVAL_MS)\n }\n\n // --- Routing observability ---\n //\n // Every inbound message produces exactly one [route] log line, with the\n // routing decision (match | drop), the deciding stage, and a reason.\n // Goes to the daemon stderr log so the existing ~/.agentx/logs/ audit\n // captures it. Also publishes on the event bus so SQLite writers,\n // dashboards, and plugins can subscribe without touching this code.\n private traceRoute(msg: IncomingMessage, kind: \"match\" | \"drop\", reason: string, decidingStage = \"unknown\", agentId?: string): void {\n const chat = msg.group?.id || msg.sender?.id || \"?\"\n this.log(\n `[route] ${msg.channel}:${chat} msgId=${msg.id} acct=${msg.accountId ?? \"—\"} kind=${kind} ${reason}`,\n )\n const at = new Date().toISOString()\n if (kind === \"match\" && agentId) {\n getEventBus().emit(\"message:matched\", {\n channel: msg.channel,\n chatId: String(chat),\n msgId: msg.id,\n accountId: msg.accountId,\n agentId,\n decidingStage,\n at,\n })\n } else if (kind === \"drop\") {\n getEventBus().emit(\"message:dropped\", {\n channel: msg.channel,\n chatId: String(chat),\n msgId: msg.id,\n accountId: msg.accountId,\n decidingStage,\n reason,\n at,\n })\n }\n }\n\n // --- Routing pipeline ---\n //\n // Phase 2: replaces the legacy resolveAgent() switch. The pipeline runs\n // a fixed-order list of named stages (see src/channels/inbound/stages/);\n // the first non-`pass` decision wins. resolveAgent() is kept as a thin\n // delegate for callers that only need the agentId and don't care about\n // the trace.\n private runRoutingPipeline(msg: IncomingMessage): PipelineResult {\n const env: InboundEnvelope = fromIncoming(msg)\n return runPipeline(env, defaultPipeline, {\n config: this.config,\n registry: this.registry,\n handoverStore: this.handoverStore,\n hasAgent: (id) => !!this.registry.getAgent(id),\n hasMeshAgent: (id) => {\n if (!this.mesh) return false\n return this.mesh.directory().some((p) => p.healthy && p.skills.some((s) => s.id === id))\n },\n })\n }\n\n // --- Agent resolution ---\n\n /**\n * Handle a message by routing to a mesh peer's agent.\n * Searches peer agent cards for mention matches.\n */\n /** Build the context payload that mesh.sendTask forwards to the receiving\n * daemon's /task handler. Without this, the receiver runs registry.execute\n * with default channel=\"api\"/chatId=\"default\" — losing the channel,\n * project, issue id, sender, and channelMeta the original adapter built\n * from the inbound webhook. Shape matches what processResolvedMessage\n * already passes to registry.execute on the local path, so a mesh-routed\n * task and a locally-routed task produce the same session keying and\n * the same prompt context. */\n private buildMeshContext(msg: IncomingMessage, chatId: string): Record<string, unknown> {\n return {\n channel: msg.channel,\n sender: msg.sender.name,\n senderId: msg.sender.id,\n senderUsername: msg.sender.username,\n group: msg.group?.name,\n chatId,\n mediaPath: msg.media?.path,\n mediaType: msg.media?.type,\n replyToText: msg.replyToText,\n channelMeta: msg.channelMeta,\n }\n }\n\n private async handleViaMesh(\n adapter: ChannelAdapter,\n msg: IncomingMessage,\n ): Promise<boolean> {\n if (!this.mesh) return false\n\n const textLower = msg.text.toLowerCase()\n const directory = this.mesh.directory()\n\n for (const peer of directory) {\n if (!peer.healthy) continue\n\n for (const skill of peer.skills) {\n // Check if the message mentions this remote agent by name or ID\n if (\n textLower.includes(skill.id.toLowerCase()) ||\n textLower.includes(skill.name.toLowerCase())\n ) {\n this.log(`Mesh routing [${msg.channel}/${msg.sender.name}] -> peer \"${peer.peer}\" agent \"${skill.id}\"`)\n\n const chatId = msg.group?.id || msg.sender.id\n const replyAccountId = msg.accountId\n\n // React + typing\n this.adapterReact(adapter, chatId, msg.id, \"👀\", replyAccountId)\n const typingTimer = this.startTypingLoop(adapter, chatId, replyAccountId)\n\n try {\n const response = await this.mesh.sendTask(peer.peer, msg.text, skill.id, {\n context: this.buildMeshContext(msg, chatId),\n })\n\n clearInterval(typingTimer)\n\n if (response) {\n // Prefix with remote agent name so user knows who's responding\n const header = `**${skill.name}** _(${peer.peer})_:\\n\\n`\n await this.adapterSend(adapter, {\n channel: msg.channel,\n chatId,\n text: header + response,\n replyTo: msg.id,\n accountId: replyAccountId,\n })\n }\n\n return true\n } catch (e: any) {\n clearInterval(typingTimer)\n this.log(`Mesh routing error: ${e.message}`)\n\n await this.adapterSend(adapter, {\n channel: msg.channel,\n chatId,\n text: `Error from ${peer.peer}/${skill.name}: ${e.message}`,\n replyTo: msg.id,\n parseMode: \"plain\",\n accountId: replyAccountId,\n })\n return true\n }\n }\n }\n }\n\n return false\n }\n\n /**\n * Route to a mesh peer by resolved agentId (not text matching).\n * Used when the channel adapter resolved the agent but it's not local.\n */\n private async handleViaMeshByAgentId(\n adapter: ChannelAdapter,\n msg: IncomingMessage,\n agentId: string,\n ): Promise<boolean> {\n if (!this.mesh) return false\n\n const directory = this.mesh.directory()\n\n for (const peer of directory) {\n if (!peer.healthy) continue\n\n const skill = peer.skills.find(s => s.id === agentId)\n if (!skill) continue\n\n this.log(`Mesh routing by agentId [${msg.channel}/${msg.sender.name}] -> peer \"${peer.peer}\" agent \"${agentId}\"`)\n\n const chatId = msg.group?.id || msg.sender.id\n const replyAccountId = msg.accountId\n\n this.adapterReact(adapter, chatId, msg.id, \"👀\", replyAccountId)\n const typingTimer = this.startTypingLoop(adapter, chatId, replyAccountId)\n\n try {\n const response = await this.mesh.sendTask(peer.peer, msg.text, agentId, {\n context: this.buildMeshContext(msg, chatId),\n })\n clearInterval(typingTimer)\n\n if (response) {\n await this.adapterSend(adapter, {\n channel: msg.channel,\n chatId,\n text: response,\n replyTo: msg.id,\n accountId: replyAccountId,\n })\n }\n\n return true\n } catch (e: any) {\n clearInterval(typingTimer)\n this.log(`Mesh routing error for ${agentId}: ${e.message}`)\n\n await this.adapterSend(adapter, {\n channel: msg.channel,\n chatId,\n text: `Error from ${peer.peer}/${skill.name}: ${e.message}`,\n replyTo: msg.id,\n parseMode: \"plain\",\n accountId: replyAccountId,\n })\n return true\n }\n }\n\n return false\n }\n\n /**\n * Route to a specific named mesh peer by agentId.\n * Used when preferNode is set on the incoming message.\n */\n private async handleViaMeshByPeer(\n adapter: ChannelAdapter,\n msg: IncomingMessage,\n agentId: string,\n peerName: string,\n ): Promise<boolean> {\n if (!this.mesh) return false\n\n const directory = this.mesh.directory()\n const peer = directory.find(p => p.peer === peerName)\n\n if (!peer || !peer.healthy) return false\n\n const skill = peer.skills.find(s => s.id === agentId) ?? { name: agentId, id: agentId }\n\n this.log(`Mesh routing by preferNode [${msg.channel}/${msg.sender.name}] -> peer \"${peerName}\" agent \"${agentId}\"`)\n\n const chatId = msg.group?.id || msg.sender.id\n const replyAccountId = msg.accountId\n\n this.adapterReact(adapter, chatId, msg.id, \"👀\", replyAccountId)\n const typingTimer = this.startTypingLoop(adapter, chatId, replyAccountId)\n\n const start = Date.now()\n this.activeMeshForwards++\n try {\n const response = await this.mesh.sendTask(peerName, msg.text, agentId, {\n context: this.buildMeshContext(msg, chatId),\n })\n const duration = Date.now() - start\n clearInterval(typingTimer)\n\n if (response) {\n await this.adapterSend(adapter, {\n channel: msg.channel,\n chatId,\n text: response,\n replyTo: msg.id,\n accountId: replyAccountId,\n agentId,\n })\n }\n\n if (msg.channel === \"gitlab\" && duration) {\n const gitlabAdapter = this.channels.get(\"gitlab\") as any\n if (gitlabAdapter) {\n gitlabAdapter.logTimeSpent(chatId, duration, agentId).catch((e: any) => {\n this.log(`GitLab time tracking (mesh) failed: ${e.message}`)\n })\n }\n }\n\n return true\n } catch (e: any) {\n clearInterval(typingTimer)\n this.log(`Mesh routing error for ${peerName}/${agentId}: ${e.message}`)\n\n await this.adapterSend(adapter, {\n channel: msg.channel,\n chatId,\n text: `Error from ${peerName}/${skill.name}: ${e.message}`,\n replyTo: msg.id,\n parseMode: \"plain\",\n accountId: replyAccountId,\n })\n return true\n } finally {\n this.activeMeshForwards--\n }\n }\n\n private getAccountForAgent(agentId: string, groupId?: string): string | undefined {\n const adapter = this.channels.get(\"telegram\") as TelegramAdapter | undefined\n return pickAccountForAgent(\n this.config.channels.telegram.accounts,\n agentId,\n groupId,\n adapter?.getGroupBotAccounts?.bind(adapter),\n )\n }\n\n /** @deprecated Phase 2 — resolveAgent now delegates to the pipeline.\n * Kept as a thin wrapper for callers that only need the agentId; new\n * code should call runRoutingPipeline() to get the full PipelineResult\n * (deciding stage, drop reason, per-stage trace). */\n private resolveAgent(msg: IncomingMessage): string | undefined {\n return this.runRoutingPipeline(msg).agentId\n }\n}\n","import type { IncomingMessage } from \"../types\"\n\n// --- Inbound envelope ---\n//\n// Canonical shape every routing stage operates on. We keep IncomingMessage\n// as the adapter contract for now (every channel adapter still emits it) —\n// `fromIncoming()` lifts it to an envelope the pipeline can reason about\n// without per-channel branching.\n//\n// The shape intentionally has slots for things that need to be observable\n// at routing time: who the sender is (and whether they're a bot or another\n// agent), what conversation scope this is, and which canonical reasoning\n// the pipeline used to land at a decision.\n\nexport type ConversationScope =\n | { type: \"dm\"; chatId: string }\n | { type: \"group\"; chatId: string; name?: string }\n | { type: \"project\"; project: string; entityType?: string; entityIid?: string }\n | { type: \"repo\"; repo: string; issueOrPr?: number }\n\nexport interface InboundEnvelope {\n /** Stable id for dedup + observability. Per-adapter format. */\n id: string\n channel: string\n accountId: string\n sender: {\n id: string\n name?: string\n username?: string\n /** True when the sender is a bot account (Telegram from.is_bot, etc.). */\n isBot: boolean\n /** True when the sender is one of OUR agentx-managed bots — set by the\n * Telegram adapter from a known-handles roster, populated by the\n * router on dispatch. Phase 2 stage `self-reply-guard` short-circuits\n * on this. */\n isAgent?: boolean\n /** Canonical agentId of the sender when isAgent. */\n agentId?: string\n }\n conversation: ConversationScope\n content: {\n text: string\n /** Reply-to text, if the platform supports threaded replies. */\n replyTo?: string\n }\n /** Event-type extracted from inbound headers / body for webhook-shaped\n * channels. Examples: `\"issues.opened\"`, `\"pull_request.synchronize\"`,\n * `\"note.MergeRequest\"`. Null for chat platforms. */\n eventType?: string\n /** The original IncomingMessage — adapters still hold platform-specific\n * state (mediaPath, replyToMessageId, …) that downstream code consumes.\n * We don't try to relift everything into the envelope right now. */\n raw: IncomingMessage\n}\n\n/** Lift an `IncomingMessage` to an `InboundEnvelope`. Pure mapping; no\n * policy decisions belong here. */\nexport function fromIncoming(msg: IncomingMessage): InboundEnvelope {\n const conversation: ConversationScope = msg.group\n ? { type: \"group\", chatId: msg.group.id, name: msg.group.name }\n : msg.channel === \"gitlab\" || msg.channel === \"github\"\n ? msg.channel === \"gitlab\"\n ? { type: \"project\", project: msg.sender.id }\n : { type: \"repo\", repo: msg.sender.id }\n : { type: \"dm\", chatId: msg.sender.id }\n return {\n id: msg.id,\n channel: msg.channel,\n accountId: msg.accountId,\n sender: {\n id: msg.sender.id,\n name: msg.sender.name,\n username: msg.sender.username,\n isBot: msg.sender.isBot === true,\n },\n conversation,\n content: {\n text: msg.text,\n replyTo: msg.replyToText,\n },\n raw: msg,\n }\n}\n","import type { InboundEnvelope } from \"./envelope\"\nimport type { DaemonConfig } from \"@/daemon/config\"\nimport type { HandoverStore } from \"../handover-store\"\nimport type { AgentRegistry } from \"@/agents/registry\"\n\n// --- Routing pipeline ---\n//\n// Replaces the 7-branch resolveAgent() switch in router.ts with a fixed,\n// declarative ordering of named stages. Each stage runs in turn; the first\n// non-`pass` decision wins. The pipeline emits exactly one trace line per\n// envelope (in the router's traceRoute) so every drop has a reason and\n// every match names which stage decided.\n//\n// The order is the contract — matches what resolveAgent did before plus\n// two new stages (self-reply-guard, bot-policy) extracted from logic that\n// was previously inlined and Telegram-specific.\n//\n// Order:\n// 1. self-reply-guard — drop messages we sent ourselves (any channel).\n// 2. handover — operator runtime override wins over config.\n// 3. adapter-resolved — channels that pre-resolve in the adapter\n// (GitLab agentMappings, WhatsApp routes).\n// 4. dm-binding — non-group channels route to the bound agent.\n// 5. bot-policy — set atMentionsOnly when sender is a bot.\n// 6. mention — find a match via registry.findByMention.\n// 7. fallback-binding — chat platforms: fall back to the account's\n// bound agent when nothing else matched.\n\nexport type StageDecision =\n | { kind: \"match\"; agentId: string }\n | { kind: \"drop\"; reason: string }\n | { kind: \"pass\" }\n\nexport interface StageContext {\n config: DaemonConfig\n registry: AgentRegistry\n handoverStore: HandoverStore\n /** True when `agentId` exists in the local registry. Closure (not a Set)\n * so /reload-driven agent additions take effect without rebuilding the\n * context. Optional for tests / older callers — when undefined, the\n * pipeline skips the existence check (legacy behavior). */\n hasAgent?: (agentId: string) => boolean\n /** True when `agentId` is advertised by a healthy mesh peer. Same lazy-\n * evaluation rationale as `hasAgent`. Optional. */\n hasMeshAgent?: (agentId: string) => boolean\n /** Set by `bot-policy` and read by `mention` — sender is a bot, so only\n * explicit `@`-prefixed mentions count. Mutable across stages. */\n atMentionsOnly: boolean\n /** Surfaced trace lines for every stage that ran. The last entry is\n * always the deciding stage. Useful for tests. */\n trace: Array<{ stage: string; decision: StageDecision }>\n}\n\nexport interface Stage {\n readonly name: string\n run(env: InboundEnvelope, ctx: StageContext): StageDecision\n}\n\nexport interface PipelineResult {\n /** The agentId that should handle this envelope, or undefined when\n * every stage either passed or dropped. */\n agentId?: string\n /** The stage that produced the final decision (match | drop). */\n decidingStage: string\n /** \"match\" | \"drop\". */\n kind: \"match\" | \"drop\"\n reason: string\n /** Per-stage trace, oldest first. */\n trace: Array<{ stage: string; decision: StageDecision }>\n}\n\nexport function runPipeline(\n env: InboundEnvelope,\n stages: Stage[],\n baseCtx: Omit<StageContext, \"atMentionsOnly\" | \"trace\">,\n): PipelineResult {\n const ctx: StageContext = { ...baseCtx, atMentionsOnly: false, trace: [] }\n for (const stage of stages) {\n const decision = stage.run(env, ctx)\n ctx.trace.push({ stage: stage.name, decision })\n if (decision.kind === \"match\") {\n // Validate the resolved agent exists locally OR is reachable via the\n // mesh. Without this guard, a stale config or a typo silently dropped\n // the message in the dispatcher; now it's recorded in route_traces\n // with reason=\"unknown_agent:<id>\" so the operator can see it.\n // Skipped when neither hasAgent nor hasMeshAgent is provided\n // (back-compat for tests + legacy callers).\n const someoneCanCheck = !!(ctx.hasAgent || ctx.hasMeshAgent)\n const knownLocally = !!ctx.hasAgent?.(decision.agentId)\n const knownInMesh = !!ctx.hasMeshAgent?.(decision.agentId)\n if (someoneCanCheck && !knownLocally && !knownInMesh) {\n const dropDecision: StageDecision = {\n kind: \"drop\",\n reason: `unknown_agent:${decision.agentId}`,\n }\n // Replace the last trace entry so the trace's deciding stage shows\n // the drop, not the match that led to the unknown agent.\n ctx.trace[ctx.trace.length - 1] = { stage: stage.name, decision: dropDecision }\n return {\n decidingStage: stage.name,\n kind: \"drop\",\n reason: dropDecision.reason,\n trace: ctx.trace,\n }\n }\n return {\n agentId: decision.agentId,\n decidingStage: stage.name,\n kind: \"match\",\n reason: `agent=${decision.agentId}`,\n trace: ctx.trace,\n }\n }\n if (decision.kind === \"drop\") {\n return {\n decidingStage: stage.name,\n kind: \"drop\",\n reason: decision.reason,\n trace: ctx.trace,\n }\n }\n }\n // Every stage passed — fallthrough is a drop.\n return {\n decidingStage: \"fallthrough\",\n kind: \"drop\",\n reason: \"no stage matched\",\n trace: ctx.trace,\n }\n}\n","// --- Outbound markers — single source of truth for self-reply detection ---\n//\n// Every message agentx sends out on a channel that re-delivers via webhook\n// (GitLab notes, GitHub comments, Slack/Discord webhooks) MUST carry a\n// marker that identifies it as ours. Without it, the same content can come\n// straight back through the inbound pipeline as if a human posted it,\n// causing reply loops.\n//\n// History:\n// - 2026-04-20 GitHub coder-agent loop incident — fixed per-channel with\n// an HTML comment marker. GitLab grew the same hack independently.\n// Both adapters now drop comments containing `<!-- agentx:` early in\n// their inbound paths.\n// - 2026-04-27 — consolidating into one helper. The marker format stays\n// the same (`<!-- agentx:<agentId> -->`); the change is that adapters\n// stop hand-rolling the regex.\n//\n// Chat platforms (Telegram, WhatsApp, Slack, Discord) handle self-detection\n// differently: Telegram's `getUpdates` doesn't replay a bot's own messages,\n// WhatsApp Baileys exposes `key.fromMe`, Slack/Discord events carry\n// `bot_id`/`webhook_id`. Those signals are surfaced on the InboundEnvelope\n// (Phase 2) under `sender.isAgent`. The HTML-marker helpers here are for\n// channels whose only signal IS the message body.\n\nconst MARKER_RE = /<!--\\s*agentx:([^\\s-][^\\s>]*?)\\s*-->/\n\n/** Append the marker to an outbound HTML / Markdown body. Idempotent —\n * if the marker is already present (e.g. agent quoted itself), do not\n * append again. */\nexport function markBody(body: string, agentId: string): string {\n if (MARKER_RE.test(body)) return body\n return `${body}\\n\\n<!-- agentx:${agentId} -->`\n}\n\n/** Detect a marker in an inbound body. Returns the agentId that signed it\n * (so logging can say \"self-reply from coder-agent dropped\") or null. */\nexport function detectAgentxMarker(body: string | undefined | null): string | null {\n if (!body) return null\n const m = body.match(MARKER_RE)\n return m ? m[1] : null\n}\n\n/** Strip every marker from a body — used when surfacing the body to the\n * agent so it doesn't see its own bookkeeping. */\nexport function stripAgentxMarkers(body: string): string {\n return body.replace(/\\n*<!--\\s*agentx:[^>]*?\\s*-->/g, \"\")\n}\n\n/** Convenience for the Phase 2 self-reply-guard pipeline stage. */\nexport function isAgentxOutbound(body: string | undefined | null): boolean {\n return detectAgentxMarker(body) !== null\n}\n","import type { Stage, StageContext, StageDecision } from \"../pipeline\"\nimport type { InboundEnvelope } from \"../envelope\"\nimport { detectAgentxMarker } from \"../../outbound-marker\"\n\n// Stage 1 — self-reply guard.\n//\n// An agent's outbound message must never re-enter the routing pipeline.\n// For HTML-bodied channels (GitLab, GitHub), every outbound carries a\n// `<!-- agentx:<agentId> -->` marker; we drop here when we see it on\n// inbound.\n//\n// Chat platforms (Telegram, WhatsApp, Slack, Discord) have native signals\n// that surface as `sender.isAgent` on the envelope. For Telegram, that's\n// matched against the `from.is_bot` field plus a known-handles roster\n// the adapter populates when sending. WhatsApp Baileys sets `key.fromMe`\n// on the raw message which the adapter lifts.\n\nexport const selfReplyGuard: Stage = {\n name: \"self-reply-guard\",\n run(env: InboundEnvelope, _ctx: StageContext): StageDecision {\n if (env.sender.isAgent) {\n return { kind: \"drop\", reason: `self-reply (agent=${env.sender.agentId ?? \"?\"})` }\n }\n // GitLab / GitHub style HTML marker\n const marker = detectAgentxMarker(env.content.text)\n if (marker) {\n return { kind: \"drop\", reason: `agentx-marker (agent=${marker})` }\n }\n return { kind: \"pass\" }\n },\n}\n\n","import type { Stage, StageContext, StageDecision } from \"../pipeline\"\nimport type { InboundEnvelope } from \"../envelope\"\n\n// Stage 2 — operator-initiated handover override.\n//\n// Survives daemon restart (handover-store persists to disk). Wins over\n// every config-driven route below this stage. Used when an operator\n// redirects \"all messages on chat X\" from agent A to agent B for a\n// session, without editing agentx.json.\n\nexport const handover: Stage = {\n name: \"handover\",\n run(env: InboundEnvelope, ctx: StageContext): StageDecision {\n const chatId =\n env.conversation.type === \"group\" || env.conversation.type === \"dm\"\n ? env.conversation.chatId\n : env.sender.id\n const override = ctx.handoverStore.get(env.channel, chatId, env.accountId)\n if (override) {\n return { kind: \"match\", agentId: override.toAgent }\n }\n return { kind: \"pass\" }\n },\n}\n","import type { Stage, StageContext, StageDecision } from \"../pipeline\"\nimport type { InboundEnvelope } from \"../envelope\"\n\n// Stage 3 — channels that resolve the agent inside the adapter.\n//\n// - GitLab: `agentMappings` map GitLab usernames to agentIds; the adapter\n// sets `msg.resolvedAgent` before dispatch. We trust it.\n// - WhatsApp: route table (contact/group → agent) is consulted in the\n// adapter and `msg.resolvedAgent` is set when a route hits.\n//\n// GitLab additionally has a hard rule: if the adapter did NOT pre-resolve\n// (no mapping matched the comment author), the message is dropped here.\n// Letting it fall through to mention-matching would try to resolve via\n// Telegram-style handles, which never match GitLab usernames.\n\nexport const adapterResolved: Stage = {\n name: \"adapter-resolved\",\n run(env: InboundEnvelope, _ctx: StageContext): StageDecision {\n if (env.channel === \"gitlab\") {\n const resolved = env.raw.resolvedAgent\n if (resolved) return { kind: \"match\", agentId: resolved }\n return { kind: \"drop\", reason: \"gitlab username has no agent mapping\" }\n }\n if (env.raw.resolvedAgent) {\n return { kind: \"match\", agentId: env.raw.resolvedAgent }\n }\n return { kind: \"pass\" }\n },\n}\n","import type { Stage, StageContext, StageDecision } from \"../pipeline\"\nimport type { InboundEnvelope } from \"../envelope\"\n\n// Stage 4 — DM binding.\n//\n// In a non-group conversation, the agent is determined by the bot account\n// the message arrived on (Telegram), or by the channel's default agent\n// (WhatsApp). No mention parsing in DMs — every message is implicitly\n// addressed to the bound agent.\n\nexport const dmBinding: Stage = {\n name: \"dm-binding\",\n run(env: InboundEnvelope, ctx: StageContext): StageDecision {\n if (env.conversation.type !== \"dm\") return { kind: \"pass\" }\n if (env.channel === \"telegram\") {\n const account = ctx.config.channels.telegram.accounts[env.accountId]\n if (account?.agentBinding) {\n return { kind: \"match\", agentId: account.agentBinding }\n }\n return { kind: \"drop\", reason: `telegram dm: no agentBinding for account \"${env.accountId}\"` }\n }\n if (env.channel === \"whatsapp\") {\n const a = ctx.config.channels.whatsapp.defaultAgent\n if (a) return { kind: \"match\", agentId: a }\n return { kind: \"drop\", reason: \"whatsapp dm: no defaultAgent configured\" }\n }\n return { kind: \"pass\" }\n },\n}\n","import type { Stage, StageContext, StageDecision } from \"../pipeline\"\nimport type { InboundEnvelope } from \"../envelope\"\n\n// Stage 5 — bot-to-bot policy.\n//\n// When a bot's reply lands in a group another bot is also in, the second\n// bot's polling sees the first bot's prose. Without a guard, mention\n// matchers will fire on bare-word handles (\"nadia\", \"devops-mtgl\") in\n// the prose and trigger spurious cross-bot cascades.\n//\n// The contract: bot-origin messages match ONLY explicit `@`-prefixed\n// handles of a local agent. Bare-word matches are silently ignored.\n//\n// Today the policy is hardcoded to `strict-mention` for bot-origin\n// traffic and the implementation is shared across channels via the\n// `atMentionsOnly` flag on the StageContext. (Future Phase: surface as\n// `channels.<channel>.botPolicy` in config so individual channels can\n// opt to \"ignore\" bot traffic entirely or \"allow\" with no narrowing.)\n\nexport const botPolicy: Stage = {\n name: \"bot-policy\",\n run(env: InboundEnvelope, ctx: StageContext): StageDecision {\n if (env.sender.isBot) {\n ctx.atMentionsOnly = true\n }\n return { kind: \"pass\" }\n },\n}\n","import type { Stage, StageContext, StageDecision } from \"../pipeline\"\nimport type { InboundEnvelope } from \"../envelope\"\n\n// Stage 6 — mention matching.\n//\n// Single canonical entry point for \"did the user @mention an agent?\".\n// Calls `registry.findByMention(text, { atMentionsOnly })`. The\n// `atMentionsOnly` flag is set by the bot-policy stage upstream and\n// changes the matching semantics:\n// - false: bare-word matches like \"nadia\" count\n// - true: only explicit `@`-prefixed handles count (bot-origin)\n//\n// Telegram has an extra rule: groups under `policy.group=mention-required`\n// must have an `@`-mention even from human senders. We honor it here so\n// the rule lives in one place rather than being scattered.\n\nexport const mention: Stage = {\n name: \"mention\",\n run(env: InboundEnvelope, ctx: StageContext): StageDecision {\n const requireAtMentionInGroup =\n env.channel === \"telegram\" &&\n env.conversation.type === \"group\" &&\n ctx.config.channels.telegram.policy.group === \"mention-required\"\n\n const flag = ctx.atMentionsOnly || requireAtMentionInGroup\n const agentId = ctx.registry.findByMention(env.content.text, {\n atMentionsOnly: flag,\n })\n if (agentId) return { kind: \"match\", agentId }\n if (requireAtMentionInGroup) {\n return { kind: \"drop\", reason: \"telegram group mention-required: no @mention found\" }\n }\n if (ctx.atMentionsOnly) {\n return { kind: \"drop\", reason: \"bot-origin: no @mention of a local agent\" }\n }\n return { kind: \"pass\" }\n },\n}\n","import type { Stage, StageContext, StageDecision } from \"../pipeline\"\nimport type { InboundEnvelope } from \"../envelope\"\n\n// Stage 7 — fallback to channel binding.\n//\n// Reached only when nothing earlier matched. For chat platforms in groups,\n// fall back to the bound agent: Telegram routes to the account's\n// `agentBinding`, WhatsApp routes to `defaultAgent`. This is the historic\n// behavior — without it, group messages without an explicit @-mention\n// would silently drop on policy=`mention-any` channels.\n\nexport const fallbackBinding: Stage = {\n name: \"fallback-binding\",\n run(env: InboundEnvelope, ctx: StageContext): StageDecision {\n if (env.channel === \"telegram\") {\n const account = ctx.config.channels.telegram.accounts[env.accountId]\n if (account?.agentBinding) {\n return { kind: \"match\", agentId: account.agentBinding }\n }\n return { kind: \"drop\", reason: `no telegram agentBinding for account \"${env.accountId}\"` }\n }\n if (env.channel === \"whatsapp\") {\n const a = ctx.config.channels.whatsapp.defaultAgent\n if (a) return { kind: \"match\", agentId: a }\n return { kind: \"drop\", reason: \"no whatsapp defaultAgent\" }\n }\n return { kind: \"pass\" }\n },\n}\n","import type { Stage } from \"../pipeline\"\nimport { selfReplyGuard } from \"./self-reply-guard\"\nimport { handover } from \"./handover\"\nimport { adapterResolved } from \"./adapter-resolved\"\nimport { dmBinding } from \"./dm-binding\"\nimport { botPolicy } from \"./bot-policy\"\nimport { mention } from \"./mention\"\nimport { fallbackBinding } from \"./fallback-binding\"\n\n// Default ordered pipeline. Order is the contract.\n//\n// 1. self-reply-guard — drop our own outbound coming back via webhook.\n// 2. handover — operator runtime override.\n// 3. adapter-resolved — GitLab agentMappings, WhatsApp routes.\n// 4. dm-binding — non-group: bound account agent / defaultAgent.\n// 5. bot-policy — set atMentionsOnly when sender is a bot.\n// 6. mention — registry.findByMention with the flag.\n// 7. fallback-binding — chat platforms in groups: bound agent.\n//\n// Phase 3 will insert a `workflow-trigger` stage between adapter-resolved\n// and dm-binding for webhook-shaped channels.\nexport const defaultPipeline: Stage[] = [\n selfReplyGuard,\n handover,\n adapterResolved,\n dmBinding,\n botPolicy,\n mention,\n fallbackBinding,\n]\n\nexport {\n selfReplyGuard,\n handover,\n adapterResolved,\n dmBinding,\n botPolicy,\n mention,\n fallbackBinding,\n}\n","// Pure helper extracted from MessageRouter so it's testable without\n// instantiating a registry/router (heavy filesystem deps). The router calls\n// this with its live config + an adapter callback for group membership.\n\ninterface AccountLike {\n agentBinding: string\n}\n\n/**\n * Pick the Telegram account that should canonically handle traffic for an\n * agent. When multiple accounts share the same agentBinding (e.g. pm-ksi has\n * both @noqta_ksi_bot and @noqta_pm_ksi_bot), prefer one whose bot is a\n * member of the target group — without that, multi-account-dedup picks the\n * first config-order match and silently drops messages received via the\n * other bot when the \"canonical\" one isn't in the chat.\n *\n * - groupId omitted → first config-order match (DM path).\n * - groupId set, getGroupBotAccounts returns members → first matching member,\n * else fall back to first candidate.\n */\nexport function pickAccountForAgent(\n accounts: Record<string, AccountLike>,\n agentId: string,\n groupId?: string,\n getGroupBotAccounts?: (groupId: string) => string[],\n): string | undefined {\n const candidates: string[] = []\n for (const [accountId, account] of Object.entries(accounts)) {\n if (account.agentBinding === agentId) candidates.push(accountId)\n }\n if (candidates.length === 0) return undefined\n if (candidates.length === 1 || !groupId) return candidates[0]\n\n const inGroup = getGroupBotAccounts?.(groupId) ?? []\n const memberCandidates = candidates.filter((id) => inGroup.includes(id))\n return memberCandidates[0] || candidates[0]\n}\n","import type { IntentLedger } from \"../ledger\"\nimport { decideAndCommit, type DispatchPolicy } from \"../decide\"\nimport { reportDivergence, type LegacyOutcome } from \"../divergence\"\nimport type { IntentDecision, IntentEventInput, IntentSource } from \"../types\"\n\n// Source adapter — channel-router. Phase 1 commit 6.b (telegram) +\n// 6.b-extended (slack, whatsapp, discord).\n//\n// The router is a 1:1 dispatch source: one IncomingMessage → one routing\n// decision (drop, deduped, or dispatched-to-one-agent). The legacy path\n// makes the decision; the ledger records it alongside via a pass-through\n// policy. Divergences during shadow soak come from ledger MECHANICS\n// (idempotency on re-delivery, active-task safety on chat) rather than\n// from policy disagreement — that's the soak's intended signal.\n//\n// **Channel coverage.** Supports four channels: telegram, slack,\n// whatsapp, discord. Each has its own per-source mode flag\n// (`INTENT_LEDGER_MODE_<CHANNEL>`) so an operator can promote them\n// independently during 1c. The msg.channel string is mapped to the\n// IntentSource enum via `routerChannelToSource`; channels outside the\n// supported set return null and the call site short-circuits.\n\n/** Channels the router covers. The `IntentSource` enum is wider but\n * these are the four chat channels that flow through the router. */\nconst ROUTER_CHANNELS = new Set<string>([\"telegram\", \"slack\", \"whatsapp\", \"discord\"])\n\n/** Map a channel string from IncomingMessage.channel to an IntentSource.\n * Returns null when the channel isn't router-supported (e.g., gitlab —\n * which has its own dispatcher and isn't routed through router.ts). */\nexport function routerChannelToSource(channel: string): IntentSource | null {\n return ROUTER_CHANNELS.has(channel) ? (channel as IntentSource) : null\n}\n\n/** Subset of IncomingMessage the adapter consumes. Local projection so\n * this file does not import from src/channels/types.ts and decouples\n * the adapter from that type's evolution. */\nexport interface RouterMessageProjection {\n id: string\n channel: string\n accountId?: string\n sender: { id: string }\n group?: { id: string }\n replyTo?: string\n}\n\n/**\n * Build the IntentEventInput for one router message. The sourceEventId\n * is `accountId/msgId` so re-deliveries from the same channel adapter's\n * view collapse to one event. Subject scopes active-task safety to the\n * chat (group id when present, else sender id — mirroring the legacy\n * chatId derivation in router.ts).\n *\n * `source` derives from `msg.channel` via `routerChannelToSource` and\n * MUST match the IntentSource enum; channels outside the supported set\n * cause the call site to short-circuit BEFORE reaching this builder.\n */\nexport function buildRouterEventInput(\n msg: RouterMessageProjection,\n source: IntentSource,\n rawJson: string,\n now: () => number = Date.now,\n): IntentEventInput {\n const accountId = msg.accountId || \"default\"\n const chatId = msg.group?.id || msg.sender.id\n return {\n ts: now(),\n source,\n sourceEventId: `${accountId}/${msg.id}`,\n project: null, // router has no project axis\n subject: `chat:${chatId}`,\n intent: msg.replyTo ? \"message.reply\" : \"message.received\",\n rawJson,\n }\n}\n\n/**\n * Pass-through policy: the policy reports whatever the legacy path decided.\n * In shadow mode this means agreement-by-construction at the policy level;\n * divergences arise solely from the ledger's idempotency + active-task\n * safety machinery seeing things the legacy LRU dedup didn't.\n *\n * `outcome === \"deduped\"` is illegal at the PolicyDecision layer (the\n * ledger owns dedup as a structural concept), so we project legacy\n * dedups to `halted` with a reason that flags them. The legacy outcome\n * passed to reportDivergence keeps the original \"deduped\", so the\n * divergence row's legacy column still says \"deduped\" — no information\n * loss in the audit trail.\n */\nexport function buildRouterPolicyFromLegacy(legacy: LegacyOutcome): DispatchPolicy {\n return {\n decidedBy: \"channel-router\",\n decide: () => ({\n agentId: legacy.agentId,\n outcome: legacy.outcome === \"deduped\" ? \"halted\" : legacy.outcome,\n reason:\n legacy.outcome === \"deduped\"\n ? `legacy-dedup: ${legacy.reason ?? \"\"}`.trim()\n : legacy.reason ?? null,\n }),\n }\n}\n\n/**\n * Convenience wrapper: record + report for one router message. The\n * caller computes `legacyOutcome` from its routing decision and passes\n * it; the helper writes the event, decision, and (if the two disagree)\n * a divergence row.\n *\n * The caller must catch exceptions — the router must continue serving\n * messages even if the ledger errors. The caller must also have already\n * mapped msg.channel to a valid IntentSource via `routerChannelToSource`\n * (returning null = unsupported channel = caller short-circuits).\n */\nexport function recordRouterDispatch(\n ledger: IntentLedger,\n msg: RouterMessageProjection,\n source: IntentSource,\n rawJson: string,\n legacyOutcome: LegacyOutcome,\n now: () => number = Date.now,\n): IntentDecision {\n const eventInput = buildRouterEventInput(msg, source, rawJson, now)\n const policy = buildRouterPolicyFromLegacy(legacyOutcome)\n const ledgerDecision = decideAndCommit(ledger, eventInput, policy, now)\n reportDivergence(ledger, source, ledgerDecision, legacyOutcome, now)\n return ledgerDecision\n}\n","// --- Convert standard Markdown to Telegram HTML ---\n//\n// Uses parse_mode: \"HTML\" (not MarkdownV2) — much simpler and more reliable.\n// Based on OpenClaw's approach: markdown → HTML tags.\n//\n// Supported Telegram HTML tags:\n// <b>bold</b>, <i>italic</i>, <s>strikethrough</s>,\n// <code>inline code</code>, <pre><code>code block</code></pre>,\n// <a href=\"url\">link</a>, <blockquote>quote</blockquote>,\n// <tg-spoiler>spoiler</tg-spoiler>\n//\n// Tables are converted to bullet-point format (Telegram has no table support).\n// File extensions that look like TLDs are wrapped in <code> to prevent\n// Telegram generating spurious link previews.\n\nfunction escapeHtml(text: string): string {\n return text\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n}\n\n/**\n * Convert standard Markdown (Claude output) to Telegram HTML.\n */\nexport function markdownToTelegramHtml(md: string): string {\n // Strip MarkdownV2 escape backslashes that Claude Code sometimes outputs\n // e.g., \\_ → _, \\# → #, \\| → |, \\. → ., \\! → !, \\- → -, \\( → (, \\) → )\n const cleaned = md.replace(/\\\\([_*\\[\\]()~`>#+=|{}.!\\\\-])/g, \"$1\")\n const lines = cleaned.split(\"\\n\")\n const result: string[] = []\n let inCodeBlock = false\n let codeBlockLang = \"\"\n let codeBlockLines: string[] = []\n let inBlockquote = false\n let tableHeaders: string[] = []\n let inTable = false\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i]\n\n // Code block toggle\n if (line.trimStart().startsWith(\"```\")) {\n if (!inCodeBlock) {\n inCodeBlock = true\n codeBlockLang = line.trimStart().slice(3).trim()\n codeBlockLines = []\n continue\n } else {\n inCodeBlock = false\n const code = escapeHtml(codeBlockLines.join(\"\\n\"))\n // Language hint as comment for context\n const langHint = codeBlockLang ? `// ${codeBlockLang}\\n` : \"\"\n result.push(`<pre><code>${langHint}${code}</code></pre>`)\n codeBlockLang = \"\"\n continue\n }\n }\n\n if (inCodeBlock) {\n codeBlockLines.push(line)\n continue\n }\n\n // Close blockquote if we left it\n if (inBlockquote && !line.trimStart().startsWith(\">\")) {\n result.push(\"</blockquote>\")\n inBlockquote = false\n }\n\n // Close table if we left it\n if (inTable && !line.trim().startsWith(\"|\")) {\n inTable = false\n tableHeaders = []\n }\n\n // Blockquote\n if (line.trimStart().startsWith(\"> \")) {\n const content = line.replace(/^>\\s*/, \"\")\n if (!inBlockquote) {\n result.push(\"<blockquote>\")\n inBlockquote = true\n }\n result.push(convertInline(content))\n continue\n }\n\n // Headers → bold with blank line before\n const headerMatch = line.match(/^(#{1,6})\\s+(.+)$/)\n if (headerMatch) {\n result.push(\"\")\n result.push(`<b>${convertInline(headerMatch[2])}</b>`)\n continue\n }\n\n // Horizontal rule\n if (/^[-*_]{3,}\\s*$/.test(line.trim())) {\n result.push(\"———\")\n continue\n }\n\n // Table handling — convert to readable bullet format\n if (line.trim().startsWith(\"|\") && line.trim().endsWith(\"|\")) {\n const cells = line.split(\"|\").slice(1, -1).map(c => c.trim())\n\n // Skip separator rows (|---|---|)\n if (/^\\|[\\s\\-:|]+\\|$/.test(line.trim())) {\n inTable = true\n continue\n }\n\n if (!inTable) {\n // This is the header row\n tableHeaders = cells\n inTable = true\n continue\n }\n\n // Data row — render as bullet with header labels\n if (tableHeaders.length > 0 && cells.length > 0) {\n if (cells.length >= 2) {\n // First cell as bold label, rest as key: value pairs\n const parts: string[] = []\n for (let c = 0; c < cells.length; c++) {\n if (c === 0) {\n parts.push(`<b>${convertInline(cells[c])}</b>`)\n } else {\n const header = tableHeaders[c] ? `${convertInline(tableHeaders[c])}: ` : \"\"\n parts.push(`${header}${convertInline(cells[c])}`)\n }\n }\n result.push(`• ${parts.join(\" — \")}`)\n } else {\n result.push(`• ${convertInline(cells[0])}`)\n }\n }\n continue\n }\n\n // Unordered list\n const ulMatch = line.match(/^(\\s*)[-*+]\\s+(.+)$/)\n if (ulMatch) {\n const indent = ulMatch[1].length > 0 ? \" \" : \"\"\n result.push(`${indent}• ${convertInline(ulMatch[2])}`)\n continue\n }\n\n // Ordered list\n const olMatch = line.match(/^(\\s*)\\d+[.)]\\s+(.+)$/)\n if (olMatch) {\n const indent = olMatch[1].length > 0 ? \" \" : \"\"\n const num = line.match(/^(\\s*)(\\d+)/)?.[2] || \"1\"\n result.push(`${indent}${num}. ${convertInline(olMatch[2])}`)\n continue\n }\n\n // Empty line\n if (!line.trim()) {\n result.push(\"\")\n continue\n }\n\n // Normal text\n result.push(convertInline(line))\n }\n\n // Close unclosed blocks\n if (inCodeBlock) {\n result.push(`<pre><code>${escapeHtml(codeBlockLines.join(\"\\n\"))}</code></pre>`)\n }\n if (inBlockquote) {\n result.push(\"</blockquote>\")\n }\n\n let html = result.join(\"\\n\").trim()\n\n // Wrap file extensions that look like TLDs to prevent Telegram link previews\n // e.g., \"config.ts\" would get wrapped as \"config<code>.ts</code>\"\n html = wrapFileRefsInCode(html)\n\n return html\n}\n\n/**\n * Convert inline markdown to HTML tags.\n */\nfunction convertInline(text: string): string {\n let result = escapeHtml(text)\n\n // Inline code (first, to protect content from further processing)\n result = result.replace(/`([^`]+)`/g, \"<code>$1</code>\")\n\n // Links: [text](url) — must come before bold/italic to preserve URLs\n result = result.replace(\n /\\[([^\\]]+)\\]\\(([^)]+)\\)/g,\n (_m, label, url) => `<a href=\"${escapeHtml(url)}\">${label}</a>`,\n )\n\n // Bold+italic: ***text***\n result = result.replace(/\\*\\*\\*(.+?)\\*\\*\\*/g, \"<b><i>$1</i></b>\")\n\n // Bold: **text**\n result = result.replace(/\\*\\*(.+?)\\*\\*/g, \"<b>$1</b>\")\n\n // Italic: *text* or _text_ (not inside other tags)\n result = result.replace(/(?<!\\*)\\*([^*]+?)\\*(?!\\*)/g, \"<i>$1</i>\")\n\n // Strikethrough: ~~text~~\n result = result.replace(/~~(.+?)~~/g, \"<s>$1</s>\")\n\n // Spoiler: ||text||\n result = result.replace(/\\|\\|(.+?)\\|\\|/g, \"<tg-spoiler>$1</tg-spoiler>\")\n\n return result\n}\n\n/**\n * Wrap file extensions that share TLDs in <code> to prevent Telegram\n * from generating spurious link previews (e.g., config.ts, main.py).\n * Skips content already inside <code>, <pre>, or <a> tags.\n */\nconst FILE_EXT_TLDS = /(?<=\\w)\\.(ts|js|py|rs|go|rb|cs|sh|md|yml|yaml|toml|json|env|css|html|xml|sql|tf|hcl)(?=[\\s,;:)\\]}<]|$)/gi\n\nfunction wrapFileRefsInCode(html: string): string {\n // Split on existing tags to avoid modifying content inside them\n const parts = html.split(/(<\\/?(?:code|pre|a)[^>]*>)/gi)\n let insideTag = false\n\n return parts.map((part) => {\n if (/<(?:code|pre|a)\\b/i.test(part)) {\n insideTag = true\n return part\n }\n if (/<\\/(?:code|pre|a)>/i.test(part)) {\n insideTag = false\n return part\n }\n if (insideTag) return part\n\n return part.replace(FILE_EXT_TLDS, \"<code>.$1</code>\")\n }).join(\"\")\n}\n","import type { ChannelAdapter, IncomingMessage, OutgoingMessage, ChannelMeta, SeededMessage } from \"./types\"\nimport { markdownToTelegramHtml } from \"./telegram-format\"\nimport { readFileSync, writeFileSync, mkdirSync, existsSync } from \"fs\"\nimport { resolve, dirname } from \"path\"\nimport { getCursorStore, type CursorStore } from \"./cursor-store\"\n\n// --- Telegram Bot API adapter (long-polling, no dependencies) ---\n\ninterface TelegramUpdate {\n update_id: number\n message?: {\n message_id: number\n from: { id: number; first_name: string; last_name?: string; username?: string; is_bot?: boolean }\n chat: { id: number; type: string; title?: string }\n text?: string\n caption?: string\n date: number\n reply_to_message?: { message_id: number; text?: string; caption?: string; from?: { first_name: string } }\n photo?: Array<{ file_id: string; file_unique_id: string; width: number; height: number; file_size?: number }>\n voice?: { file_id: string; duration: number; mime_type?: string }\n audio?: { file_id: string; duration: number; mime_type?: string; title?: string }\n video?: { file_id: string; duration: number; mime_type?: string }\n document?: { file_id: string; file_name?: string; mime_type?: string }\n sticker?: { file_id: string; emoji?: string }\n }\n my_chat_member?: {\n chat: { id: number; type: string; title?: string }\n from: { id: number; first_name: string }\n new_chat_member: {\n user: { id: number; username?: string; is_bot?: boolean }\n status: string // \"member\" | \"administrator\" | \"left\" | \"kicked\" | \"creator\"\n }\n }\n}\n\ninterface TelegramAccountConfig {\n token: string\n agentBinding: string\n allowFrom?: string[]\n /** When false, register the token for outbound send only (no long-poll). */\n pollInbound?: boolean\n}\n\n// --- Persistent group membership store ---\n// Tracks which bots are in which groups, persisted to .agentx/telegram/groups.json\n// Updated via my_chat_member events and one-time API seed per group.\n\ninterface GroupMembership {\n /** groupId → { accountId → { username, status, updatedAt } } */\n [groupId: string]: {\n name?: string\n bots: {\n [accountId: string]: {\n username: string\n agentId: string\n status: string // \"member\" | \"administrator\" | \"left\" | \"kicked\"\n updatedAt: string\n }\n }\n }\n}\n\nclass TelegramGroupStore {\n private data: GroupMembership = {}\n private filePath: string\n private dirty = false\n\n constructor(dataDir: string) {\n this.filePath = resolve(dataDir, \".agentx/telegram/groups.json\")\n this.load()\n }\n\n private load(): void {\n try {\n if (existsSync(this.filePath)) {\n this.data = JSON.parse(readFileSync(this.filePath, \"utf-8\"))\n }\n } catch {\n this.data = {}\n }\n }\n\n private save(): void {\n try {\n mkdirSync(dirname(this.filePath), { recursive: true })\n writeFileSync(this.filePath, JSON.stringify(this.data, null, 2))\n this.dirty = false\n } catch {\n // best-effort\n }\n }\n\n /** Record a bot's membership status in a group. */\n setBotStatus(groupId: string, groupName: string | undefined, accountId: string, agentId: string, username: string, status: string): void {\n if (!this.data[groupId]) {\n this.data[groupId] = { name: groupName, bots: {} }\n }\n if (groupName) this.data[groupId].name = groupName\n this.data[groupId].bots[accountId] = {\n username,\n agentId,\n status,\n updatedAt: new Date().toISOString(),\n }\n this.dirty = true\n this.save()\n }\n\n /** Get all active bots in a group. */\n getGroupBots(groupId: string): Array<{ accountId: string; agentId: string; username: string }> {\n const group = this.data[groupId]\n if (!group) return []\n return Object.entries(group.bots)\n .filter(([, info]) => info.status !== \"left\" && info.status !== \"kicked\")\n .map(([accountId, info]) => ({\n accountId,\n agentId: info.agentId,\n username: info.username,\n }))\n }\n\n /** Check if we have data for a group. */\n hasGroup(groupId: string): boolean {\n return !!this.data[groupId]\n }\n\n /** All groups this store knows about, with their best-effort display name. */\n listGroups(): Array<{ id: string; name?: string; botCount: number }> {\n return Object.entries(this.data).map(([id, g]) => ({\n id,\n name: g.name,\n botCount: Object.values(g.bots).filter((b) => b.status !== \"left\" && b.status !== \"kicked\").length,\n }))\n }\n}\n\n/**\n * @deprecated Replaced by FileCursorStore in src/channels/cursor-store.ts.\n * Kept temporarily for callers we haven't migrated yet; do not introduce\n * new uses. The ad-hoc 500ms debounce here was the root cause of the\n * 2026-04-27 message-replay incident.\n *\n * Persist long-poll `offset` per account to disk so a daemon restart doesn't\n * re-fetch updates Telegram still holds in its 24h retention window. Without\n * this, a crash loop (e.g. restart-counter cascade) causes the same message\n * to be handled N times — seen on 2026-04-15 when clawd spun through 20\n * systemd restarts while a zombie daemon held the pidfile, resulting in 6×\n * duplicate replies from the queued incoming messages.\n */\nclass TelegramOffsetStore {\n private offsets: Record<string, number> = {}\n private filePath: string\n private dirty = false\n private saveTimer?: ReturnType<typeof setTimeout>\n\n constructor(dataDir: string) {\n this.filePath = resolve(dataDir, \".agentx/telegram/offsets.json\")\n try {\n if (existsSync(this.filePath)) {\n this.offsets = JSON.parse(readFileSync(this.filePath, \"utf-8\"))\n }\n } catch { this.offsets = {} }\n }\n\n get(accountId: string): number {\n return this.offsets[accountId] || 0\n }\n\n set(accountId: string, offset: number): void {\n if (this.offsets[accountId] === offset) return\n this.offsets[accountId] = offset\n this.dirty = true\n // Debounce disk writes — each poll can produce many offset updates.\n if (!this.saveTimer) {\n this.saveTimer = setTimeout(() => this.flush(), 500)\n }\n }\n\n flush(): void {\n if (this.saveTimer) {\n clearTimeout(this.saveTimer)\n this.saveTimer = undefined\n }\n if (!this.dirty) return\n try {\n const dir = dirname(this.filePath)\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true })\n writeFileSync(this.filePath, JSON.stringify(this.offsets, null, 2))\n this.dirty = false\n } catch { /* best-effort */ }\n }\n}\n\nexport class TelegramAdapter implements ChannelAdapter {\n readonly name = \"telegram\"\n private accounts: Map<string, TelegramAccountConfig>\n private cursors: CursorStore\n private handler?: (msg: IncomingMessage) => Promise<void>\n private polling = false\n /** Per-account polling gate — flipped to false when an account is removed\n * or its token changes, so its pollLoop observes the stop and exits without\n * affecting siblings. Keyed by accountId. */\n private accountPolling = new Map<string, boolean>()\n private log: (...args: unknown[]) => void\n /** Global allowlist fallback. When a per-account allowFrom is unset,\n * this list applies. When BOTH are unset, everything is rejected\n * (closed by default). */\n private globalAllowFrom?: string[]\n\n constructor(\n accounts: Record<string, TelegramAccountConfig>,\n opts: { policy?: { allowFrom?: string[] } } = {},\n log: (...args: unknown[]) => void = console.error.bind(console, \"[telegram]\"),\n ) {\n this.accounts = new Map(Object.entries(accounts))\n this.globalAllowFrom = opts.policy?.allowFrom\n this.log = log\n this.groupStore = new TelegramGroupStore(process.cwd())\n this.cursors = getCursorStore(process.cwd())\n }\n\n /** Effective sender allowlist for an account. Returns undefined when the\n * account has no explicit config AND the global policy is unset — the\n * caller treats undefined as \"closed, drop everything\". */\n private effectiveAllowFrom(accountId: string): string[] | undefined {\n const cfg = this.accounts.get(accountId)\n if (cfg?.allowFrom !== undefined) return cfg.allowFrom\n return this.globalAllowFrom\n }\n\n /** Accept a message only when at least one allowlist entry matches the\n * sender's user id, the chat id, or the sender's @username. */\n private isAllowed(\n accountId: string,\n fromId: number | string | undefined,\n chatId: number | string | undefined,\n fromUsername?: string,\n ): boolean {\n const list = this.effectiveAllowFrom(accountId)\n if (!list || list.length === 0) return false\n const fromStr = fromId != null ? String(fromId) : \"\"\n const chatStr = chatId != null ? String(chatId) : \"\"\n const userLc = fromUsername?.toLowerCase()\n for (const entry of list) {\n if (!entry) continue\n if (entry === fromStr || entry === chatStr) return true\n if (entry.startsWith(\"@\") && userLc && entry.slice(1).toLowerCase() === userLc) return true\n }\n return false\n }\n\n onMessage(handler: (msg: IncomingMessage) => Promise<void>): void {\n this.handler = handler\n }\n\n async start(): Promise<void> {\n this.polling = true\n\n const entries = Array.from(this.accounts.entries())\n this.log(`${entries.length} Telegram account(s) to start`)\n\n for (let i = 0; i < entries.length; i++) {\n const [accountId, config] = entries[i]\n await this.startAccount(accountId, config, { index: i + 1, of: entries.length })\n // Small delay between account starts to avoid Telegram rate limits\n if (i < entries.length - 1) {\n await new Promise((r) => setTimeout(r, 300))\n }\n }\n\n this.log(`All ${entries.length} Telegram account(s) started`)\n }\n\n /** Start a single account's poll loop. Used by both start() (boot) and\n * reloadAccounts() (hot-add on /reload). Verifies the bot token, records\n * the bot's identity, then spawns the long-poll loop. No-op if the account\n * is already polling. */\n private async startAccount(\n accountId: string,\n config: TelegramAccountConfig,\n meta: { index?: number; of?: number } = {},\n ): Promise<void> {\n if (this.accountPolling.get(accountId)) {\n this.log(`Account \"${accountId}\" already polling — skipping duplicate start`)\n return\n }\n const prefix = meta.index != null && meta.of != null\n ? `(${meta.index}/${meta.of})`\n : \"(hot-reload)\"\n // Send-only mode: keep the token registered for outbound send()/ring but\n // don't long-poll. Used when the bound agent lives on a different daemon\n // — otherwise both daemons race on getUpdates and Telegram returns 409.\n if (config.pollInbound === false) {\n this.log(`Account \"${accountId}\" ${prefix} — send-only (pollInbound=false), skipping getUpdates loop`)\n try {\n const me = await this.apiCall(config.token, \"getMe\")\n const botUserId = me.result?.id\n const botUsername = me.result?.username\n if (botUserId) {\n this.botInfo.set(accountId, { userId: botUserId, username: botUsername || accountId })\n }\n } catch (e: any) {\n this.log(`Send-only account \"${accountId}\" getMe failed: ${e.message} — send may still work if token is valid`)\n }\n return\n }\n this.log(`Starting polling for account \"${accountId}\" ${prefix}`)\n try {\n // Drop any stale webhook/long-poll session from a previous run.\n // This prevents 409 conflicts when restarting.\n await this.apiCall(config.token, \"deleteWebhook\", { drop_pending_updates: false }).catch(() => {})\n\n // Retry getMe with backoff. Cold-start fleet boot does N parallel\n // verifications and a transient `fetch failed` (DNS/TLS hiccup,\n // upstream 502) used to silently skip the account permanently —\n // observed in production when 2 of 7 bots dropped out for the rest\n // of the session. Three tries at 2s/5s/10s catches the common case\n // without delaying the legit-failure path much.\n let me: any\n let lastErr: any\n const delays = [0, 2000, 5000, 10000]\n for (let attempt = 0; attempt < delays.length; attempt++) {\n if (delays[attempt] > 0) await new Promise((r) => setTimeout(r, delays[attempt]))\n try {\n me = await this.apiCall(config.token, \"getMe\")\n if (attempt > 0) this.log(`Bot account \"${accountId}\" verified on retry ${attempt}`)\n break\n } catch (e: any) {\n lastErr = e\n if (attempt < delays.length - 1) {\n this.log(`Verify attempt ${attempt + 1} failed for \"${accountId}\" (${e.message}) — retrying`)\n }\n }\n }\n if (!me) throw lastErr\n const botUserId = me.result?.id\n const botUsername = me.result?.username\n this.log(`Bot @${botUsername} ready (account: ${accountId})`)\n if (botUserId) {\n this.botInfo.set(accountId, { userId: botUserId, username: botUsername || accountId })\n }\n this.accountPolling.set(accountId, true)\n this.pollLoop(accountId, config)\n } catch (e: any) {\n this.log(`Failed to verify bot for account \"${accountId}\" after retries: ${e.message}`)\n }\n }\n\n /** Stop a single account's poll loop without touching siblings. Flips the\n * per-account gate and aborts the in-flight long-poll so Telegram releases\n * the server-side session immediately (otherwise a new start with the same\n * token hits 409 Conflict). */\n private async stopAccount(accountId: string): Promise<void> {\n this.accountPolling.set(accountId, false)\n const cfg = this.accounts.get(accountId)\n if (!cfg) return\n try {\n await this.apiCall(cfg.token, \"getUpdates\", { offset: -1, timeout: 0 })\n } catch {\n // Best-effort\n }\n }\n\n async stop(): Promise<void> {\n this.polling = false\n // Cursors commit synchronously per-update now (FileCursorStore), so\n // there's nothing to flush here — every offset advance has already\n // landed on disk before the next getUpdates ack.\n const aborts = Array.from(this.accounts.keys()).map((id) => this.stopAccount(id))\n await Promise.allSettled(aborts)\n }\n\n /** Hot-reload the account map in place. Diffs the new config against the\n * current one: removed accounts get their pollers stopped, added accounts\n * get started, and accounts whose token changed get restarted (allowFrom\n * changes are effectively read-through via effectiveAllowFrom so they\n * don't need a restart, but we do refresh the stored config). Returns the\n * diff for the caller to log/surface.\n *\n * Requires the adapter to be already started (polling=true). */\n async reloadAccounts(\n next: Record<string, TelegramAccountConfig>,\n policy?: { allowFrom?: string[] },\n ): Promise<{ added: string[]; removed: string[]; tokenChanged: string[] }> {\n if (!this.polling) {\n // Adapter hasn't been started — just swap the map, start() will use it.\n this.accounts = new Map(Object.entries(next))\n this.globalAllowFrom = policy?.allowFrom\n return { added: [], removed: [], tokenChanged: [] }\n }\n\n const oldIds = new Set(this.accounts.keys())\n const newIds = new Set(Object.keys(next))\n const added: string[] = []\n const removed: string[] = []\n const tokenChanged: string[] = []\n\n // Update the global allowlist unconditionally — it's read-through.\n this.globalAllowFrom = policy?.allowFrom\n\n // 1. Removed accounts — stop + drop from map.\n for (const id of oldIds) {\n if (newIds.has(id)) continue\n await this.stopAccount(id)\n this.accounts.delete(id)\n this.botInfo.delete(id)\n removed.push(id)\n }\n\n // 2. Retained accounts — swap config in place (for allowFrom etc.) and\n // restart when the token changed, since pollLoop captures the token\n // in its closure argument.\n for (const id of newIds) {\n if (!oldIds.has(id)) continue\n const oldCfg = this.accounts.get(id)!\n const newCfg = next[id]\n this.accounts.set(id, newCfg)\n if (oldCfg.token !== newCfg.token) {\n await this.stopAccount(id)\n await this.startAccount(id, newCfg)\n tokenChanged.push(id)\n }\n }\n\n // 3. Added accounts — write config then start polling.\n for (const id of newIds) {\n if (oldIds.has(id)) continue\n this.accounts.set(id, next[id])\n await this.startAccount(id, next[id])\n added.push(id)\n }\n\n return { added, removed, tokenChanged }\n }\n\n /**\n * Get token for a specific account ID. Used by router to send from correct bot.\n */\n getTokenForAccount(accountId: string): string | undefined {\n return this.accounts.get(accountId)?.token\n }\n\n /**\n * Get the default (first) account token as fallback.\n */\n private getDefaultToken(): string | undefined {\n const [, config] = Array.from(this.accounts.entries())[0]\n return config?.token\n }\n\n /**\n * Resolve which token to use: prefer accountId, fall back to chatAccountMap, then default.\n */\n private resolveToken(chatId: string, accountId?: string): string | undefined {\n if (accountId) {\n const token = this.getTokenForAccount(accountId)\n if (token) return token\n }\n return this.chatAccountMap.get(chatId)\n ? this.getTokenForAccount(this.chatAccountMap.get(chatId)!)\n : this.getDefaultToken()\n }\n\n /** Track which account a chat was last seen on (for DMs) */\n private chatAccountMap: Map<string, string> = new Map()\n // Bot user IDs and usernames resolved at startup (accountId → { userId, username })\n private botInfo: Map<string, { userId: number; username: string }> = new Map()\n // Persistent group membership store\n private groupStore: TelegramGroupStore\n\n /** Account IDs of bots currently active (member/administrator) in a group.\n * Used by the router to pick the correct bot when an agent has multiple\n * Telegram accounts bound to it (e.g. pm-ksi → both @noqta_ksi_bot and\n * @noqta_pm_ksi_bot) — the in-group account should win, otherwise messages\n * to the absent bot get silently dropped by multi-account-dedup. */\n getGroupBotAccounts(groupId: string): string[] {\n return this.groupStore.getGroupBots(groupId).map((b) => b.accountId)\n }\n\n /**\n * Send a message. Returns the sent message ID.\n * Pass accountId to send from a specific bot account.\n */\n async send(msg: OutgoingMessage & { accountId?: string }): Promise<string> {\n const token = this.resolveToken(msg.chatId, msg.accountId)\n if (!token) {\n this.log(\"No telegram token found for sending\")\n return \"\"\n }\n\n const maxLen = 4096\n const text = msg.text.length > maxLen ? msg.text.slice(0, maxLen - 3) + \"...\" : msg.text\n\n // Convert markdown to Telegram MarkdownV2\n const formatted = msg.parseMode === \"markdown\" || msg.parseMode === undefined\n ? markdownToTelegramHtml(text)\n : text\n\n const params: Record<string, unknown> = {\n chat_id: msg.chatId,\n text: formatted,\n parse_mode: \"HTML\",\n }\n\n if (msg.replyTo) {\n params.reply_to_message_id = parseInt(msg.replyTo, 10)\n }\n\n if (msg.parseMode === \"html\") {\n params.parse_mode = \"HTML\"\n params.text = text\n } else if (msg.parseMode === \"plain\") {\n delete params.parse_mode\n params.text = text\n }\n\n try {\n const result = await this.apiCall(token, \"sendMessage\", params)\n const messageId = String(result.result?.message_id || \"\")\n this.recordOutboundShadow(msg.chatId, messageId, text, msg.agentId, msg.accountId)\n return messageId\n } catch (e: any) {\n // Retry without formatting if MarkdownV2 fails\n if (params.parse_mode) {\n delete params.parse_mode\n params.text = text\n const result = await this.apiCall(token, \"sendMessage\", params)\n const messageId = String(result.result?.message_id || \"\")\n this.recordOutboundShadow(msg.chatId, messageId, text, msg.agentId, msg.accountId)\n return messageId\n }\n throw e\n }\n }\n\n /** Best-effort outbound shadow log. Captures every successful send into\n * the same per-chat shadow file the inbound writer uses, so seedHistory\n * + agent introspection see a complete picture (which bot identity sent\n * what, at which time). Failures are swallowed — the message already\n * went out on the wire; logging is observability, not correctness. */\n private recordOutboundShadow(\n chatId: string,\n messageId: string,\n content: string,\n agentId?: string,\n accountId?: string,\n ): void {\n if (!messageId) return // Telegram returned no id (formatted retry path leaves us nothing to dedup on)\n try {\n this.appendShadowLog(chatId, {\n externalId: messageId,\n role: \"agent\",\n // Prefer agentId for the rendered \"Agent: ...\" line; fall back to\n // accountId so even un-attributed sends (e.g., manual /send tests)\n // surface a name in seeded history.\n name: agentId || accountId || \"agent\",\n content,\n timestamp: new Date().toISOString(),\n accountId,\n })\n } catch (e: any) {\n this.log(`outbound shadow-log write failed: ${e.message}`)\n }\n }\n\n /**\n * Send a message with an inline keyboard. Used by the workflow user-task\n * renderer so approve/reject-style tasks get one-tap URL buttons in\n * Telegram pointing at the daemon's one-click submit endpoint.\n *\n * Each button is a URL button (not callback_data) so the user's client\n * opens the link in a browser — this avoids wiring callback_query\n * plumbing through the adapter's polling loop for Phase 3 MVP.\n */\n async sendWithInlineButtons(args: {\n chatId: string\n text: string\n buttons: Array<{ label: string; url: string }>\n accountId?: string\n parseMode?: \"markdown\" | \"html\" | \"plain\"\n }): Promise<string> {\n const token = this.resolveToken(args.chatId, args.accountId)\n if (!token) { this.log(\"No telegram token found for sending\"); return \"\" }\n\n const maxLen = 4096\n const text = args.text.length > maxLen ? args.text.slice(0, maxLen - 3) + \"...\" : args.text\n const formatted = args.parseMode === \"markdown\" || args.parseMode === undefined\n ? markdownToTelegramHtml(text)\n : text\n\n const params: Record<string, unknown> = {\n chat_id: args.chatId,\n text: formatted,\n parse_mode: args.parseMode === \"plain\" ? undefined : \"HTML\",\n reply_markup: JSON.stringify({\n inline_keyboard: args.buttons.map((b) => [{ text: b.label, url: b.url }]),\n }),\n }\n if (params.parse_mode === undefined) delete params.parse_mode\n\n try {\n const result = await this.apiCall(token, \"sendMessage\", params)\n return String(result.result?.message_id || \"\")\n } catch (e: any) {\n if (params.parse_mode) {\n delete params.parse_mode\n params.text = text\n const result = await this.apiCall(token, \"sendMessage\", params)\n return String(result.result?.message_id || \"\")\n }\n throw e\n }\n }\n\n /**\n * Edit an existing message (for streaming updates).\n */\n async editMessage(chatId: string, messageId: string, text: string, parseMode?: string, accountId?: string): Promise<boolean> {\n const token = this.resolveToken(chatId, accountId)\n if (!token) return false\n\n const maxLen = 4096\n const trimmed = text.length > maxLen ? text.slice(0, maxLen - 3) + \"...\" : text\n\n const formatted = parseMode !== \"html\" && parseMode !== \"plain\"\n ? markdownToTelegramHtml(trimmed)\n : trimmed\n\n const params: Record<string, unknown> = {\n chat_id: chatId,\n message_id: parseInt(messageId, 10),\n text: formatted,\n parse_mode: \"HTML\",\n }\n\n if (parseMode === \"html\") {\n params.parse_mode = \"HTML\"\n params.text = trimmed\n } else if (parseMode === \"plain\") {\n delete params.parse_mode\n params.text = trimmed\n }\n\n try {\n await this.apiCall(token, \"editMessageText\", params)\n return true\n } catch (e: any) {\n if (e.message?.includes(\"message is not modified\")) return true\n if (params.parse_mode) {\n delete params.parse_mode\n params.text = trimmed\n try {\n await this.apiCall(token, \"editMessageText\", params)\n return true\n } catch { return false }\n }\n return false\n }\n }\n\n /**\n * Push a streaming draft to a private chat (Bot API 9.5+ `sendMessageDraft`).\n * Subsequent calls with the same `draftId` animate in place — much smoother\n * than `editMessageText` and not subject to the same edit-rate limits.\n *\n * **Constraint:** private chats only (DMs). Telegram returns an error for\n * groups/channels. Callers must check `isDirectMessage(chatId)` first.\n *\n * Returns `true` on success, `false` on any error (best-effort streaming —\n * never throw or callers will lose blocks). Falls back to no-formatting on\n * a parse-mode error so a malformed HTML chunk doesn't kill the stream.\n */\n async sendDraft(\n chatId: string,\n draftId: number,\n text: string,\n parseMode?: string,\n accountId?: string,\n ): Promise<boolean> {\n if (!TelegramAdapter.isDirectMessage(chatId)) return false\n const token = this.resolveToken(chatId, accountId)\n if (!token) return false\n\n const maxLen = 4096\n const trimmed = text.length > maxLen ? text.slice(0, maxLen - 3) + \"...\" : text\n if (!trimmed) return false\n\n const formatted = parseMode !== \"html\" && parseMode !== \"plain\"\n ? markdownToTelegramHtml(trimmed)\n : trimmed\n\n const params: Record<string, unknown> = {\n chat_id: parseInt(chatId, 10),\n draft_id: draftId,\n text: formatted,\n parse_mode: \"HTML\",\n }\n if (parseMode === \"plain\") {\n delete params.parse_mode\n params.text = trimmed\n } else if (parseMode === \"html\") {\n params.text = trimmed\n }\n\n try {\n await this.apiCall(token, \"sendMessageDraft\", params)\n return true\n } catch (e: any) {\n // Retry without parse_mode if HTML parsing tripped on a partial fragment\n // (common while streaming — half a tag mid-buffer).\n if (params.parse_mode) {\n delete params.parse_mode\n params.text = trimmed\n try {\n await this.apiCall(token, \"sendMessageDraft\", params)\n return true\n } catch { return false }\n }\n return false\n }\n }\n\n /** Telegram convention: positive chat_id → private chat (DM with a user),\n * negative → group/supergroup/channel. Used to gate features that only\n * work in DMs (sendMessageDraft, message_effect_id, ...). */\n static isDirectMessage(chatId: string): boolean {\n const n = parseInt(chatId, 10)\n return Number.isFinite(n) && n > 0\n }\n\n /**\n * React to a message with an emoji.\n */\n async react(chatId: string, messageId: string, emoji: string = \"👀\", accountId?: string): Promise<void> {\n const token = this.resolveToken(chatId, accountId)\n if (!token) return\n\n try {\n await this.apiCall(token, \"setMessageReaction\", {\n chat_id: chatId,\n message_id: parseInt(messageId, 10),\n reaction: [{ type: \"emoji\", emoji }],\n })\n } catch {\n // Best-effort\n }\n }\n\n /**\n * Send typing indicator.\n */\n async sendTyping(chatId: string, accountId?: string): Promise<void> {\n const token = this.resolveToken(chatId, accountId)\n if (!token) return\n\n try {\n await this.apiCall(token, \"sendChatAction\", {\n chat_id: chatId,\n action: \"typing\",\n })\n } catch {\n // Best-effort\n }\n }\n\n // --- Internal ---\n\n private async pollLoop(accountId: string, config: TelegramAccountConfig): Promise<void> {\n let consecutiveErrors = 0\n\n while (this.polling && this.accountPolling.get(accountId) !== false) {\n try {\n const stored = this.cursors.read(\"telegram\", accountId)\n const offset = typeof stored === \"number\" ? stored : 0\n const data = await this.apiCall(config.token, \"getUpdates\", {\n offset: offset || undefined,\n timeout: 30,\n allowed_updates: [\"message\", \"my_chat_member\"],\n })\n\n const updates: TelegramUpdate[] = data.result || []\n\n for (const update of updates) {\n // Synchronous commit BEFORE we hand the message to the router. The\n // next getUpdates with this offset is what acks the upstream — if\n // we crash between this line and the next poll, restart resumes\n // from the same offset and Telegram redelivers the unprocessed\n // updates. (Pre-2026-04-27 the old TelegramOffsetStore debounced\n // 500ms, which left a window where Telegram had already deleted\n // the message but the disk still pointed at the old offset.)\n this.cursors.commit(\"telegram\", accountId, update.update_id + 1)\n\n if (update.message && this.handler) {\n const msg = update.message\n\n // Extract text from any message type\n let text = msg.text || msg.caption || \"\"\n let mediaInfo: IncomingMessage[\"media\"] | undefined\n\n // Handle media messages\n const hasPhoto = msg.photo && msg.photo.length > 0\n const hasVoice = !!msg.voice\n const hasAudio = !!msg.audio\n const hasVideo = !!msg.video\n const hasDocument = !!msg.document\n const hasMedia = hasPhoto || hasVoice || hasAudio || hasVideo || hasDocument\n\n if (hasMedia) {\n // Download media file\n let fileId: string | undefined\n let mime = \"application/octet-stream\"\n\n if (hasPhoto) {\n fileId = msg.photo![msg.photo!.length - 1].file_id // largest photo\n mime = \"image/jpeg\"\n if (!text) text = \"[Photo attached — please describe what you see]\"\n } else if (hasVoice) {\n fileId = msg.voice!.file_id\n mime = msg.voice!.mime_type || \"audio/ogg\"\n if (!text) text = \"[Voice message — please transcribe and respond]\"\n } else if (hasAudio) {\n fileId = msg.audio!.file_id\n mime = msg.audio!.mime_type || \"audio/mpeg\"\n if (!text) text = `[Audio: ${msg.audio!.title || \"audio file\"}]`\n } else if (hasVideo) {\n fileId = msg.video!.file_id\n mime = msg.video!.mime_type || \"video/mp4\"\n if (!text) text = \"[Video attached]\"\n } else if (hasDocument) {\n fileId = msg.document!.file_id\n mime = msg.document!.mime_type || \"application/octet-stream\"\n if (!text) text = `[Document: ${msg.document!.file_name || \"file\"}]`\n }\n\n if (fileId) {\n try {\n // Get file path from Telegram\n const fileInfo = await this.apiCall(config.token, \"getFile\", { file_id: fileId })\n const filePath = fileInfo.result?.file_path\n if (filePath) {\n // Download file\n const fileUrl = `https://api.telegram.org/file/bot${config.token}/${filePath}`\n const res = await fetch(fileUrl)\n if (res.ok) {\n const buffer = Buffer.from(await res.arrayBuffer())\n const ext = mime.split(\"/\")[1]?.split(\";\")[0] || \"bin\"\n const { mkdirSync, writeFileSync } = await import(\"fs\")\n const { randomUUID } = await import(\"crypto\")\n const { resolve, join } = await import(\"path\")\n const mediaDir = resolve(process.cwd(), \".agentx/media/telegram\")\n mkdirSync(mediaDir, { recursive: true })\n const fileName = msg.document?.file_name || `${randomUUID()}.${ext}`\n const localPath = join(mediaDir, fileName)\n writeFileSync(localPath, buffer)\n mediaInfo = { path: localPath, type: mime, fileName }\n }\n }\n } catch (e: any) {\n this.log(`Media download failed: ${e.message}`)\n }\n }\n }\n\n if (!text) continue\n\n // Sender allowlist gate — enforced BEFORE the router sees anything\n // so an unauthorized message burns zero tokens and leaves no trace\n // in the agent's session log.\n if (!this.isAllowed(accountId, msg.from?.id, msg.chat?.id, msg.from?.username)) {\n this.log(\n `[telegram/${accountId}] dropped message from ${msg.from?.id}${msg.from?.username ? ` (@${msg.from.username})` : \"\"} in chat ${msg.chat?.id} — not in allowlist`,\n )\n continue\n }\n\n // Build channel meta for groups (verified bot membership)\n const isGroup = msg.chat.type !== \"private\"\n const groupId = String(msg.chat.id)\n let channelMeta: ChannelMeta | undefined\n if (isGroup) {\n // Seed group membership on first encounter via API\n if (!this.groupStore.hasGroup(groupId)) {\n await this.seedGroupMembership(groupId, msg.chat.title)\n }\n channelMeta = await this.getChannelMeta(groupId)\n }\n\n const incoming: IncomingMessage = {\n id: String(msg.message_id),\n channel: \"telegram\",\n accountId,\n sender: {\n id: String(msg.from.id),\n name: [msg.from.first_name, msg.from.last_name].filter(Boolean).join(\" \"),\n username: msg.from.username,\n isBot: msg.from.is_bot === true,\n },\n group: isGroup\n ? { id: groupId, name: msg.chat.title || \"\" }\n : undefined,\n text,\n media: mediaInfo,\n replyTo: msg.reply_to_message\n ? String(msg.reply_to_message.message_id)\n : undefined,\n replyToText: msg.reply_to_message\n ? (msg.reply_to_message.text || msg.reply_to_message.caption || `[message from ${msg.reply_to_message.from?.first_name || \"unknown\"}]`)\n : undefined,\n timestamp: new Date(msg.date * 1000),\n raw: update,\n channelMeta,\n }\n\n // Track chat→account mapping for DM replies\n this.chatAccountMap.set(String(msg.chat.id), accountId)\n\n // Shadow log every allowed inbound message — used by seedHistory\n // on cold session create. Keyed by chat id (DM uses sender.id ==\n // chat.id; group uses chat.id), matching how the router computes\n // the SessionStore key (msg.group?.id || msg.sender.id). Off the\n // hot path: best-effort, never blocks dispatch. accountId records\n // WHICH of our bots received the message — the audit trail\n // counterpart to the outbound write.\n try {\n this.appendShadowLog(String(msg.chat.id), {\n externalId: String(msg.message_id),\n role: \"user\",\n name: incoming.sender.name,\n content: text,\n timestamp: incoming.timestamp.toISOString(),\n accountId,\n })\n } catch (e: any) {\n this.log(`shadow-log write failed: ${e.message}`)\n }\n\n this.handler(incoming).catch((e) => {\n this.log(`Error handling message: ${e.message}`)\n })\n }\n\n // Handle bot membership changes (added/removed from group)\n if (update.my_chat_member) {\n const mcm = update.my_chat_member\n const chatId = String(mcm.chat.id)\n const chatTitle = mcm.chat.title\n const status = mcm.new_chat_member.status\n const botUsername = mcm.new_chat_member.user.username || accountId\n\n this.groupStore.setBotStatus(\n chatId,\n chatTitle,\n accountId,\n config.agentBinding,\n botUsername,\n status,\n )\n\n this.log(`Group membership: @${botUsername} is now \"${status}\" in \"${chatTitle || chatId}\"`)\n }\n }\n consecutiveErrors = 0\n } catch (e: any) {\n consecutiveErrors++\n const backoff = Math.min(5000 * Math.pow(2, consecutiveErrors - 1), 60000)\n this.log(`Poll error (${accountId}): ${e.message} [retry in ${backoff / 1000}s, errors: ${consecutiveErrors}]`)\n await new Promise((r) => setTimeout(r, backoff))\n }\n }\n }\n\n /**\n * Get verified context for a Telegram chat.\n * Reads from persistent group store (fed by my_chat_member events + API seed).\n */\n /** Surface chats the adapter has observed, so the workflow editor (or CLI)\n * can present a picker when an author is authoring an `action.send` that\n * targets a different chat than the trigger's. DMs come from the per-chat\n * account map (populated when a user messages a bot); groups come from\n * the persistent group membership store. Both lists are small — bounded\n * by the number of distinct chats the bots have been in. */\n listKnownChats(): Array<{ id: string; name?: string; kind: \"dm\" | \"group\"; accountId?: string }> {\n const out: Array<{ id: string; name?: string; kind: \"dm\" | \"group\"; accountId?: string }> = []\n for (const [id, accountId] of this.chatAccountMap) {\n if (TelegramAdapter.isDirectMessage(id)) {\n out.push({ id, kind: \"dm\", accountId })\n }\n }\n for (const g of this.groupStore.listGroups()) {\n out.push({ id: g.id, name: g.name, kind: \"group\" })\n }\n return out\n }\n\n async getChannelMeta(chatId: string): Promise<ChannelMeta | undefined> {\n const bots = this.groupStore.getGroupBots(chatId)\n if (!bots.length) return undefined\n\n return {\n channel: \"telegram\",\n agents: bots.map(b => ({\n id: b.agentId,\n name: b.username,\n handle: `@${b.username}`,\n })),\n facts: [`${bots.length} bot(s) verified in this group`],\n }\n }\n\n /**\n * Seed group membership by querying the Telegram API for each bot.\n * Called once per group on first encounter, then maintained via my_chat_member events.\n */\n private async seedGroupMembership(groupId: string, groupTitle?: string): Promise<void> {\n this.log(`Seeding group membership for \"${groupTitle || groupId}\"`)\n\n for (const [accountId, config] of this.accounts) {\n const info = this.botInfo.get(accountId)\n if (!info) continue\n\n try {\n const res = await this.apiCall(config.token, \"getChatMember\", {\n chat_id: Number(groupId),\n user_id: info.userId,\n })\n const status = res.result?.status\n if (status) {\n this.groupStore.setBotStatus(\n groupId,\n groupTitle,\n accountId,\n config.agentBinding,\n info.username,\n status,\n )\n }\n } catch {\n // Bot not in group or API error — record as \"left\"\n this.groupStore.setBotStatus(\n groupId,\n groupTitle,\n accountId,\n config.agentBinding,\n info.username,\n \"left\",\n )\n }\n }\n\n this.log(`Seeded: ${this.groupStore.getGroupBots(groupId).length} bot(s) in \"${groupTitle || groupId}\"`)\n }\n\n /** Append one entry to the shadow log for `chatId`. The shadow log is a\n * daily file at `.agentx/sessions/_telegram_raw:{chatId}:{day}.json` that\n * captures every allowed inbound (and adapter-side outbound) message\n * regardless of how the router resolved it — including periods when no\n * agent was bound or the daemon was offline (the next inbound triggers\n * a write). seedHistory reads it back on cold session create so a fresh\n * session mirrors the live chat instead of starting blank. */\n private appendShadowLog(\n chatId: string,\n entry: {\n externalId: string\n role: \"user\" | \"agent\"\n name: string\n content: string\n timestamp: string\n /** Telegram bot account id used to send/receive (e.g., \"noqta_cx_bot\").\n * Recorded on outbound only — provides the audit trail for \"which bot\n * actually sent this message\" so debugging cross-account confusion\n * (the Nadia/CX bug) doesn't require API forensics. */\n accountId?: string\n },\n ): void {\n const day = entry.timestamp.slice(0, 10) // YYYY-MM-DD\n const file = this.shadowLogPath(chatId, day)\n let existing: { messages: typeof entry[] } = { messages: [] }\n if (existsSync(file)) {\n try {\n existing = JSON.parse(readFileSync(file, \"utf-8\")) as { messages: typeof entry[] }\n if (!Array.isArray(existing.messages)) existing.messages = []\n } catch {\n existing = { messages: [] }\n }\n }\n // Dedup: skip if this externalId is already recorded for the day. Note\n // that inbound and outbound externalIds CAN collide (Telegram numbers\n // them per-chat, not per-direction), so we additionally key on role to\n // avoid the rare case where an inbound `message_id=42` and an outbound\n // `message_id=42` race on the same day.\n if (existing.messages.some((m) => m.externalId === entry.externalId && m.role === entry.role)) return\n existing.messages.push(entry)\n mkdirSync(dirname(file), { recursive: true })\n writeFileSync(file, JSON.stringify(existing, null, 2))\n }\n\n private shadowLogPath(chatId: string, day: string): string {\n const safeChatId = chatId.replace(/[^a-zA-Z0-9_-]/g, \"_\")\n return resolve(process.cwd(), \".agentx/sessions\", `_telegram_raw:${safeChatId}:${day}.json`)\n }\n\n /** ChannelAdapter.seedHistory: returns the most recent shadow-logged\n * messages for `chatId`, walking back day-by-day until we hit\n * `maxMessages` or `maxChars`. Telegram's Bot API has no \"fetch chat\n * history\" endpoint, so the shadow log is our only source — meaning the\n * first deployment sees an empty seed for any pre-existing chat, and\n * context grows as the bot observes new traffic. */\n async seedHistory(\n chatId: string,\n opts: { sinceISO?: string; maxMessages: number; maxChars: number },\n ): Promise<SeededMessage[]> {\n const out: SeededMessage[] = []\n let chars = 0\n const today = new Date()\n // Look back up to 14 days — enough to bridge weekend gaps without\n // unbounded scanning. The maxMessages/maxChars caps stop us long before\n // hitting day 14 in any normal chat.\n const minDate = opts.sinceISO ? new Date(opts.sinceISO) : new Date(today.getTime() - 14 * 86400000)\n for (let i = 0; i < 14; i++) {\n const d = new Date(today.getTime() - i * 86400000)\n if (d.getTime() < minDate.getTime() - 86400000) break\n const day = d.toISOString().slice(0, 10)\n const file = this.shadowLogPath(chatId, day)\n if (!existsSync(file)) continue\n try {\n const data = JSON.parse(readFileSync(file, \"utf-8\")) as {\n messages?: Array<{ externalId: string; role: \"user\" | \"agent\"; name: string; content: string; timestamp: string; accountId?: string }>\n }\n const msgs = (data.messages ?? []).slice() // newest within the file\n // Files are append-only in chronological order, so reverse to walk\n // newest-first per day (matches our outer newest-first loop).\n for (let j = msgs.length - 1; j >= 0; j--) {\n const m = msgs[j]\n if (opts.sinceISO && m.timestamp < opts.sinceISO) continue\n out.push({\n role: m.role,\n name: m.name,\n content: m.content,\n timestamp: m.timestamp,\n externalId: m.externalId,\n accountId: m.accountId,\n })\n chars += m.content.length\n if (out.length >= opts.maxMessages) break\n if (chars >= opts.maxChars) break\n }\n } catch {\n // Skip corrupt shadow files silently — they self-heal on next write.\n }\n if (out.length >= opts.maxMessages || chars >= opts.maxChars) break\n }\n // Caller wants oldest-first for buildHistoryContext rendering.\n return out.reverse()\n }\n\n private async apiCall(\n token: string,\n method: string,\n params?: Record<string, unknown>,\n ): Promise<any> {\n const url = `https://api.telegram.org/bot${token}/${method}`\n const res = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: params ? JSON.stringify(params) : undefined,\n })\n\n if (!res.ok) {\n const text = await res.text()\n throw new Error(`Telegram API error: ${res.status} ${text}`)\n }\n\n return res.json()\n }\n}\n","import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from \"fs\"\nimport { dirname, resolve } from \"path\"\n\n// --- Generic per-channel cursor store ---\n//\n// Any channel that consumes a polling cursor (Telegram offset, IMAP UID,\n// changefeed seq, …) must persist the cursor to disk BEFORE the upstream\n// considers the messages acknowledged — otherwise a daemon crash between\n// \"received\" and \"acked\" causes message replay (Telegram) or message loss\n// (most other systems) on restart.\n//\n// Discipline: `commit()` is synchronous. Cursors are small (≤ a JSON object\n// per account) and committed only once per poll cycle, so the cost of\n// fsync is negligible. The previous Telegram-specific store debounced\n// writes by 500ms, which is exactly the race window that caused message\n// replay on 2026-04-27.\n\nexport interface CursorStore {\n read(channel: string, account: string): string | number | null\n commit(channel: string, account: string, cursor: string | number): void\n /** Optional: list every (channel, account) → cursor pair, for diagnostics. */\n snapshot(): Record<string, string | number>\n}\n\nexport class FileCursorStore implements CursorStore {\n private filePath: string\n private data: Record<string, string | number>\n\n constructor(cwd: string, relPath = \".agentx/cursors.json\") {\n this.filePath = resolve(cwd, relPath)\n this.data = {}\n try {\n if (existsSync(this.filePath)) {\n const parsed = JSON.parse(readFileSync(this.filePath, \"utf-8\"))\n if (parsed && typeof parsed === \"object\") {\n this.data = parsed\n }\n }\n } catch {\n // First-run / corrupt — start clean. The cost of replay is bounded\n // by the upstream's retention window.\n }\n }\n\n private key(channel: string, account: string): string {\n return `${channel}:${account}`\n }\n\n read(channel: string, account: string): string | number | null {\n const v = this.data[this.key(channel, account)]\n return v === undefined ? null : v\n }\n\n commit(channel: string, account: string, cursor: string | number): void {\n const k = this.key(channel, account)\n if (this.data[k] === cursor) return\n this.data[k] = cursor\n this.persist()\n }\n\n snapshot(): Record<string, string | number> {\n return { ...this.data }\n }\n\n private persist(): void {\n const dir = dirname(this.filePath)\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true })\n // Atomic write: write to .tmp, then rename. Crash mid-write leaves the\n // prior valid file in place rather than a truncated one.\n const tmp = `${this.filePath}.tmp`\n writeFileSync(tmp, JSON.stringify(this.data, null, 2))\n // Node's fs.renameSync is atomic on POSIX (overwrites the target).\n // On Linux fsync is implicit before rename for ext4/journaled FS in\n // practice; macOS APFS guarantees atomicity. No explicit fsync needed\n // for the tiny payloads we write here.\n renameSync(tmp, this.filePath)\n }\n}\n\n/** Singleton lazily initialized at first call. The cwd is the daemon's\n * process.cwd() at boot (where agentx.json lives). */\nlet _instance: FileCursorStore | undefined\nexport function getCursorStore(cwd?: string): CursorStore {\n if (!_instance) _instance = new FileCursorStore(cwd ?? process.cwd())\n return _instance\n}\n","import type { ChannelAdapter, IncomingMessage, OutgoingMessage } from \"./types\"\nimport { existsSync, mkdirSync, writeFileSync } from \"fs\"\nimport { resolve, join } from \"path\"\nimport { randomUUID } from \"crypto\"\nimport { WhatsAppCache, type ContactRecord, type ChatRecord, type GroupRecord } from \"./whatsapp-cache\"\n\n/** Shapes returned by the adapter's read API — used by the wiki ingestor.\n * These are stable contracts so the ingestor can mock the adapter without\n * pulling in Baileys types. */\nexport interface ContactSummary {\n jid: string\n phone: string\n name?: string // best display name (savedName → pushName → phone)\n pushName?: string\n savedName?: string\n status?: string\n updatedAt?: string\n}\nexport interface ChatSummary {\n jid: string\n name: string // \"best we have\" — never undefined so CLI output is useful\n isGroup: boolean\n lastMessageAt?: number\n unreadCount?: number\n}\nexport interface GroupInfo {\n jid: string\n subject: string\n description?: string\n owner?: string\n members: Array<{ jid: string; admin?: \"admin\" | \"superadmin\" }>\n memberCount: number\n}\nexport interface HistoryMessage {\n id: string\n fromJid: string // sender JID (for groups) or chat JID (for DMs)\n fromMe: boolean\n timestamp?: number // unix seconds\n text: string // empty string when the message was media-only with no caption\n media?: { kind: \"image\" | \"audio\" | \"video\" | \"document\" | \"sticker\"; caption?: string; filename?: string }\n}\n\n// --- WhatsApp adapter using Baileys (WhatsApp Web multi-device) ---\n// Baileys is an optional dependency. If not installed, adapter logs a warning.\n//\n// First run: displays QR code in terminal for pairing.\n// Subsequent runs: reconnects automatically using saved session.\n\nexport interface WhatsAppRoute {\n contact?: string // phone number or partial match\n group?: string // group name or JID partial match\n agent: string // agent ID to route to\n}\n\nexport class WhatsAppAdapter implements ChannelAdapter {\n readonly name = \"whatsapp\"\n private sessionDir: string\n private defaultAgent?: string\n private allowFrom?: string[]\n private routes: WhatsAppRoute[]\n private handler?: (msg: IncomingMessage) => Promise<void>\n private sock: any = null\n private reconnectAttempts = 0\n /** Generation counter — incremented on each start() to ignore stale socket events */\n private generation = 0\n private sentMessageIds: Set<string> = new Set() // Track our own replies to prevent loops\n private log: (...args: unknown[]) => void\n\n /** Passive snapshot of contacts/chats/groups populated from Baileys events.\n * Consumed by the wiki ingestor. Persisted to {sessionDir}/cache.json on stop. */\n private cache: WhatsAppCache\n\n /** Token-bucket throttle for live Baileys reads. Configurable via\n * channels.whatsapp.ingest.throttle. Defaults keep the read surface\n * below what personal-account rate limits tend to trigger. */\n private throttleMinMs = 1500\n private throttleMaxPerMinute = 20\n private lastReadAt = 0\n private readsInWindow: number[] = []\n\n /** Fires whenever the underlying WhatsApp socket emits a new QR or clears it.\n * Passing null means the session is now connected. */\n private onQR?: (qr: string | null) => void\n /** Fires whenever the connection state changes (connecting / open / close). */\n private onStatus?: (status: \"connecting\" | \"open\" | \"close\", detail?: string) => void\n\n constructor(\n config: {\n sessionDir: string\n defaultAgent?: string\n allowFrom?: string[]\n routes?: WhatsAppRoute[]\n onQR?: (qr: string | null) => void\n onStatus?: (status: \"connecting\" | \"open\" | \"close\", detail?: string) => void\n /** Throttle settings for live Baileys reads. Falls back to safe defaults. */\n throttle?: { minMsBetweenCalls?: number; maxCallsPerMinute?: number }\n },\n log: (...args: unknown[]) => void = console.error.bind(console, \"[whatsapp]\"),\n ) {\n this.sessionDir = resolve(config.sessionDir)\n this.defaultAgent = config.defaultAgent\n this.allowFrom = config.allowFrom\n this.routes = config.routes || []\n this.onQR = config.onQR\n this.onStatus = config.onStatus\n this.log = log\n this.cache = new WhatsAppCache(this.sessionDir)\n if (config.throttle?.minMsBetweenCalls) this.throttleMinMs = config.throttle.minMsBetweenCalls\n if (config.throttle?.maxCallsPerMinute) this.throttleMaxPerMinute = config.throttle.maxCallsPerMinute\n }\n\n /** Read-only access for the wiki ingestor. Exposed so the ingestor can\n * iterate the passive snapshot without reaching into adapter internals. */\n getCache(): WhatsAppCache { return this.cache }\n\n /** True once the socket has connected at least once and emitted the\n * authenticated user. The ingestor checks this before attempting any\n * live read — a torn-down session will otherwise throw a cryptic error. */\n isConnected(): boolean {\n return !!this.sock && !!this.sock.user\n }\n\n /**\n * Resolve which agent handles a message based on routes.\n */\n resolveAgent(senderPhone: string, groupName?: string, groupJid?: string): string | undefined {\n for (const route of this.routes) {\n // Match by contact phone number\n if (route.contact) {\n const normalized = route.contact.replace(/\\+/g, \"\")\n if (senderPhone.includes(normalized) || normalized.includes(senderPhone)) {\n return route.agent\n }\n }\n // Match by group name or JID\n if (route.group && (groupName || groupJid)) {\n const pattern = route.group.toLowerCase()\n if (\n groupName?.toLowerCase().includes(pattern) ||\n groupJid?.toLowerCase().includes(pattern)\n ) {\n return route.agent\n }\n }\n }\n return this.defaultAgent\n }\n\n onMessage(handler: (msg: IncomingMessage) => Promise<void>): void {\n this.handler = handler\n }\n\n async start(): Promise<void> {\n // Dynamic import — Baileys is optional\n let makeWASocket: any\n let useMultiFileAuthState: any\n let DisconnectReason: any\n\n let baileys: any\n try {\n baileys = await import(\"@whiskeysockets/baileys\")\n makeWASocket = baileys.default || baileys.makeWASocket\n useMultiFileAuthState = baileys.useMultiFileAuthState\n DisconnectReason = baileys.DisconnectReason\n } catch {\n this.log(\"WhatsApp requires @whiskeysockets/baileys. Install with:\")\n this.log(\" npm install @whiskeysockets/baileys\")\n return\n }\n\n mkdirSync(this.sessionDir, { recursive: true })\n\n // Increment generation — stale socket events from previous start() calls\n // will see a mismatched generation and be ignored\n const myGeneration = ++this.generation\n\n // Close existing socket before creating a new one\n if (this.sock) {\n try { this.sock.end(undefined) } catch {}\n this.sock = null\n }\n\n const { state, saveCreds } = await useMultiFileAuthState(this.sessionDir)\n\n // Fetch latest WhatsApp Web version (critical for avoiding 405 errors)\n let version: [number, number, number] | undefined\n try {\n const { version: v } = await baileys.fetchLatestBaileysVersion()\n version = v\n this.log(`WhatsApp Web version: ${v.join(\".\")}`)\n } catch {\n this.log(\"Could not fetch WA version, using default\")\n }\n\n // Silent logger with proper method stubs (Baileys v7 requires all methods)\n const silentLogger = {\n level: \"silent\",\n trace: () => {}, debug: () => {}, info: () => {},\n warn: () => {}, fatal: () => {},\n error: (...args: any[]) => this.log(\"WA error:\", ...args),\n child: () => silentLogger,\n } as any\n\n this.sock = makeWASocket({\n auth: {\n creds: state.creds,\n keys: baileys.makeCacheableSignalKeyStore\n ? baileys.makeCacheableSignalKeyStore(state.keys, silentLogger)\n : state.keys,\n },\n ...(version ? { version } : {}),\n logger: silentLogger,\n printQRInTerminal: false,\n browser: [\"agentx\", \"server\", \"1.0\"],\n syncFullHistory: false,\n markOnlineOnConnect: false,\n })\n\n // Save credentials on update\n this.sock.ev.on(\"creds.update\", saveCreds)\n\n // Debug + cache hydration: history-set carries chats+contacts we can\n // seed the cache with (message bodies are intentionally ignored —\n // see whatsapp-cache.ts for why).\n this.sock.ev.on(\"messaging-history.set\", (data: any) => {\n this.log(`WA history sync: ${data.messages?.length || 0} messages, ${data.isLatest ? \"latest\" : \"partial\"}`)\n try { this.cache.applyHistorySet(data) } catch (e: any) { this.log(`cache history-set error: ${e.message}`) }\n })\n\n // Passive cache maintenance. These four events arrive for free whenever\n // Baileys observes updates; previously ignored. The wiki ingestor\n // reads from this cache instead of hitting Baileys live, which keeps\n // ingest latency low and avoids adding to the personal-account\n // ban-risk surface.\n this.sock.ev.on(\"contacts.update\", (updates: any) => {\n try { this.cache.applyContactsUpdate(updates) } catch (e: any) { this.log(`cache contacts.update error: ${e.message}`) }\n })\n this.sock.ev.on(\"contacts.upsert\", (contacts: any) => {\n try { this.cache.applyContactsUpdate(contacts) } catch (e: any) { this.log(`cache contacts.upsert error: ${e.message}`) }\n })\n this.sock.ev.on(\"chats.upsert\", (chats: any) => {\n try { this.cache.applyChatsUpsert(chats) } catch (e: any) { this.log(`cache chats.upsert error: ${e.message}`) }\n })\n this.sock.ev.on(\"chats.update\", (updates: any) => {\n try { this.cache.applyChatsUpsert(updates) } catch (e: any) { this.log(`cache chats.update error: ${e.message}`) }\n })\n this.sock.ev.on(\"groups.update\", (updates: any) => {\n try { this.cache.applyGroupsUpdate(updates) } catch (e: any) { this.log(`cache groups.update error: ${e.message}`) }\n })\n\n // Handle connection updates (ignore events from stale sockets via generation check)\n this.sock.ev.on(\"connection.update\", async (update: any) => {\n const { connection, lastDisconnect, qr } = update\n if (myGeneration !== this.generation) return // stale socket event\n\n if (qr) {\n this.log(\"Scan QR code with WhatsApp to connect:\")\n // Publish the raw QR so the admin dashboard can render it in-browser.\n try { this.onQR?.(qr) } catch { /* consumer crashed — don't block the socket */ }\n // Render QR as text in terminal too (keeps SSH-only setups working).\n try {\n // @ts-ignore - no type declarations for qrcode-terminal\n const { default: qrcode } = await import(\"qrcode-terminal\") as any\n qrcode.generate(qr, { small: true })\n } catch {\n // If qrcode-terminal not available, log raw QR string\n this.log(`QR: ${qr}`)\n this.log(\"Install qrcode-terminal for visual QR: npm install qrcode-terminal\")\n }\n }\n if (connection) {\n try { this.onStatus?.(connection, lastDisconnect?.error?.message) } catch { /* */ }\n }\n if (connection === \"open\") {\n // Clear the cached QR once the pairing succeeds so the dashboard stops\n // showing a scannable code the user already consumed.\n try { this.onQR?.(null) } catch { /* */ }\n }\n\n if (connection === \"close\") {\n const statusCode = lastDisconnect?.error?.output?.statusCode\n const reason = lastDisconnect?.error?.output?.payload?.message || \"\"\n this.log(`WhatsApp connection closed (status: ${statusCode}, reason: ${reason})`)\n\n if (statusCode === DisconnectReason?.loggedOut || statusCode === 401) {\n this.log(\"Logged out. Delete session dir and restart to re-scan QR.\")\n } else if (statusCode === 440 || statusCode === 408) {\n // 440 = conflict:replaced (another session took over)\n // 408 = connection timed out (QR not scanned)\n this.reconnectAttempts++\n\n if (this.reconnectAttempts >= 5) {\n this.log(`WhatsApp: giving up after ${this.reconnectAttempts} conflict attempts. Another session is active for this number — close it or re-scan QR.`)\n return // Stop reconnecting\n }\n\n const delay = Math.min(10_000 * Math.pow(2, this.reconnectAttempts - 1), 120_000)\n this.log(`Reconnecting in ${delay / 1000}s (attempt ${this.reconnectAttempts}/5)...`)\n setTimeout(() => this.start(), delay)\n } else if (statusCode === 515) {\n // Stream error — restart after delay\n this.log(\"Stream error, reconnecting in 5s...\")\n setTimeout(() => this.start(), 5000)\n } else if (statusCode !== undefined) {\n this.log(\"Reconnecting in 3s...\")\n setTimeout(() => this.start(), 3000)\n }\n // If statusCode is undefined, don't reconnect (likely startup failure)\n }\n\n if (connection === \"open\") {\n // Don't reset reconnectAttempts here — it gets reset on first\n // successful message send/receive (proving connection is stable)\n this.log(\"WhatsApp connected\")\n }\n })\n\n // Handle incoming messages\n this.sock.ev.on(\"messages.upsert\", async (m: any) => {\n // Connection is confirmed stable — reset reconnect backoff\n if (this.reconnectAttempts > 0) {\n this.log(`WhatsApp connection stable, resetting reconnect counter (was ${this.reconnectAttempts})`)\n this.reconnectAttempts = 0\n }\n\n this.log(`WA messages.upsert: ${m.messages?.length || 0} messages, type: ${m.type}`)\n\n if (!this.handler) return\n\n for (const msg of m.messages || []) {\n const jidShort = (msg.key.remoteJid || \"\").replace(/@.*/, \"\").slice(-6)\n const hasText = !!(msg.message?.conversation || msg.message?.extendedTextMessage?.text)\n this.log(`WA msg: from=${jidShort} fromMe=${msg.key.fromMe} hasText=${hasText} type=${Object.keys(msg.message || {}).join(\",\")}`)\n\n // Skip status broadcasts\n if (msg.key.remoteJid === \"status@broadcast\") continue\n\n // Skip messages we sent as replies (prevents loops)\n if (msg.key.id && this.sentMessageIds.has(msg.key.id)) {\n this.sentMessageIds.delete(msg.key.id)\n continue\n }\n\n // For fromMe: only process self-chat (messaging yourself = talking to agent)\n // OpenClaw pattern: if your own number is in allowFrom, self-chat is enabled.\n // WhatsApp uses LID format for self-chat remoteJid.\n if (msg.key.fromMe) {\n const me = this.sock?.user\n const remoteJid = msg.key.remoteJid || \"\"\n // Self-chat: remoteJid matches our JID or LID\n const myJid = me?.id?.replace(/:.*/, \"\") || \"\" // 21624309128\n const myLid = me?.lid?.replace(/:.*/, \"\") || \"\" // 214997540012179\n const chatUser = remoteJid.replace(/:.*/, \"\").replace(/@.*/, \"\")\n const isSelfChat = (chatUser === myJid) || (chatUser === myLid)\n if (!isSelfChat) continue\n }\n\n // Extract text content + detect media\n let text = msg.message?.conversation\n || msg.message?.extendedTextMessage?.text\n || msg.message?.imageMessage?.caption\n || msg.message?.videoMessage?.caption\n || \"\"\n\n // Detect media type\n const msgContent = msg.message || {}\n const hasImage = !!msgContent.imageMessage\n const hasAudio = !!msgContent.audioMessage\n const hasVideo = !!msgContent.videoMessage\n const hasDocument = !!msgContent.documentMessage\n const hasSticker = !!msgContent.stickerMessage\n const hasMedia = hasImage || hasAudio || hasVideo || hasDocument || hasSticker\n\n // Add media placeholder to text if no caption\n if (hasMedia && !text) {\n if (hasImage) text = \"[Image attached — please describe what you see]\"\n else if (hasAudio) text = \"[Voice message attached — please transcribe and respond]\"\n else if (hasVideo) text = \"[Video attached]\"\n else if (hasDocument) text = `[Document: ${msgContent.documentMessage?.fileName || \"file\"}]`\n else if (hasSticker) text = \"[Sticker]\"\n }\n\n if (!text) continue\n\n const jid = msg.key.remoteJid || \"\"\n const isGroup = jid.endsWith(\"@g.us\")\n\n // For DMs: remoteJid is the conversation partner\n // For groups: participant is the sender\n const chatPhone = jid.replace(/@.*$/, \"\")\n const senderJid = isGroup ? (msg.key.participant || \"\") : jid\n const senderPhone = senderJid.replace(/@.*$/, \"\")\n\n // Allowlist check — match against chat (conversation partner) not just sender\n // This allows the owner (fromMe) to talk to their agent via any contact\n if (this.allowFrom?.length && !msg.key.fromMe) {\n const allowed = this.allowFrom.some(p => {\n const normalized = p.replace(/\\+/g, \"\")\n return senderPhone.includes(normalized) || chatPhone.includes(normalized)\n })\n if (!allowed) continue\n }\n\n const senderName = msg.key.fromMe ? \"me\" : (msg.pushName || senderPhone)\n\n // Get group name if available\n let groupName: string | undefined\n if (isGroup && this.sock) {\n try {\n const metadata = await this.sock.groupMetadata(jid)\n groupName = metadata.subject\n } catch { /* group metadata unavailable */ }\n }\n\n // Route to agent based on contact/group rules\n const resolvedAgent = this.resolveAgent(senderPhone, groupName, isGroup ? jid : undefined)\n if (!resolvedAgent) {\n this.log(`No route for ${isGroup ? `group ${groupName || jid}` : senderPhone}, skipping`)\n continue\n }\n\n // Download and save media if present\n let mediaInfo: IncomingMessage[\"media\"] | undefined\n if (hasMedia && this.sock) {\n try {\n const baileys = await import(\"@whiskeysockets/baileys\")\n const buffer = await baileys.downloadMediaMessage(msg, \"buffer\", {}, {\n reuploadRequest: this.sock.updateMediaMessage,\n logger: this.sock.logger,\n })\n if (buffer) {\n const mime = msgContent.imageMessage?.mimetype\n || msgContent.audioMessage?.mimetype || \"audio/ogg\"\n || msgContent.videoMessage?.mimetype || \"video/mp4\"\n || msgContent.documentMessage?.mimetype\n || msgContent.stickerMessage?.mimetype || \"image/webp\"\n || \"application/octet-stream\"\n const ext = mime.split(\"/\")[1]?.split(\";\")[0] || \"bin\"\n const mediaDir = resolve(this.sessionDir, \"../media/inbound\")\n mkdirSync(mediaDir, { recursive: true })\n const fileName = msgContent.documentMessage?.fileName || `${randomUUID()}.${ext}`\n const filePath = join(mediaDir, fileName)\n writeFileSync(filePath, buffer)\n mediaInfo = { path: filePath, type: mime, fileName }\n this.log(`WA media saved: ${mime} -> ${filePath}`)\n }\n } catch (e: any) {\n this.log(`WA media download failed: ${e.message}`)\n }\n }\n\n const incoming: IncomingMessage = {\n id: msg.key.id || String(Date.now()),\n channel: \"whatsapp\",\n accountId: \"default\",\n sender: {\n id: msg.key.fromMe\n ? (this.sock?.user?.id?.replace(/:.*/, \"\") || senderPhone) + \"@s.whatsapp.net\"\n : senderPhone,\n name: senderName,\n username: senderPhone,\n },\n group: isGroup ? {\n id: jid,\n name: groupName || jid,\n } : undefined,\n text,\n media: mediaInfo,\n replyTo: msg.message?.extendedTextMessage?.contextInfo?.stanzaId,\n timestamp: new Date((msg.messageTimestamp || 0) * 1000),\n raw: msg,\n resolvedAgent,\n }\n\n this.handler(incoming).catch((e: any) => {\n this.log(`Error handling message: ${e.message}`)\n })\n }\n })\n }\n\n async stop(): Promise<void> {\n // Snapshot the passive cache before tearing down the socket — Baileys\n // events stop firing once `sock.end()` runs, so anything we haven't\n // persisted is lost on restart otherwise.\n try { this.cache.save() } catch (e: any) { this.log(`cache save error: ${e.message}`) }\n if (this.sock) {\n this.sock.end()\n this.sock = null\n }\n }\n\n async send(msg: OutgoingMessage): Promise<string> {\n if (!this.sock) {\n this.log(\"WhatsApp not connected\")\n return \"\"\n }\n\n const jid = msg.chatId.includes(\"@\")\n ? msg.chatId\n : `${msg.chatId}@s.whatsapp.net`\n\n try {\n let content: Record<string, unknown>\n\n if (msg.poll) {\n content = {\n poll: {\n name: msg.poll.name,\n values: msg.poll.values,\n selectableCount: msg.poll.selectableCount ?? 1,\n },\n }\n } else if (msg.media) {\n const mediaPayload: Record<string, unknown> = {\n mimetype: msg.media.mimetype,\n }\n if (msg.media.caption) mediaPayload.caption = msg.media.caption\n if (msg.media.fileName) mediaPayload.fileName = msg.media.fileName\n\n const source = { url: msg.media.url }\n switch (msg.media.type) {\n case \"image\":\n content = { image: source, ...mediaPayload }\n break\n case \"video\":\n content = { video: source, ...mediaPayload }\n break\n case \"audio\":\n content = { audio: source, ptt: false, ...mediaPayload }\n break\n case \"document\":\n content = { document: source, ...mediaPayload }\n break\n default:\n content = { document: source, ...mediaPayload }\n }\n } else {\n content = { text: msg.text }\n }\n\n const sent = await this.sock.sendMessage(jid, content)\n const sentId = sent?.key?.id || \"\"\n if (sentId) this.sentMessageIds.add(sentId)\n return sentId\n } catch (e: any) {\n this.log(`Send error: ${e.message}`)\n return \"\"\n }\n }\n\n async editMessage(chatId: string, messageId: string, text: string): Promise<boolean> {\n if (!this.sock) return false\n const jid = chatId.includes(\"@\") ? chatId : `${chatId}@s.whatsapp.net`\n try {\n await this.sock.sendMessage(jid, {\n text,\n edit: { remoteJid: jid, id: messageId, fromMe: true },\n })\n return true\n } catch { return false }\n }\n\n async sendTyping(chatId: string): Promise<void> {\n if (!this.sock) return\n const jid = chatId.includes(\"@\") ? chatId : `${chatId}@s.whatsapp.net`\n try {\n await this.sock.sendPresenceUpdate(\"composing\", jid)\n } catch { /* best-effort */ }\n }\n\n async react(chatId: string, messageId: string, emoji: string = \"👀\"): Promise<void> {\n if (!this.sock) return\n const jid = chatId.includes(\"@\") ? chatId : `${chatId}@s.whatsapp.net`\n try {\n await this.sock.sendMessage(jid, {\n react: { text: emoji, key: { remoteJid: jid, id: messageId } },\n })\n } catch { /* best-effort */ }\n }\n\n // --- Read API (consumed by src/wiki/ingest-whatsapp.ts) ---\n //\n // All methods prefer the passive cache. Live Baileys calls happen only\n // for getHistory (there's no cached message body) and are routed through\n // `throttleGate()`. Callers never import Baileys.\n\n listContacts(): ContactSummary[] {\n return this.cache.listContacts().map(contactRecordToSummary)\n }\n\n listChats(): ChatSummary[] {\n return this.cache.listChats().map(chatRecordToSummary)\n }\n\n async getContactProfile(jid: string): Promise<ContactSummary | null> {\n const record = this.cache.getContact(jid)\n // Live enrichment is optional — we return whatever the cache has even\n // if the status field is empty. Keeps the ingestor's happy path fast\n // on bulk list-then-ingest workflows.\n return record ? contactRecordToSummary(record) : null\n }\n\n async getGroupMetadata(jid: string): Promise<GroupInfo | null> {\n // Cache hit? return immediately. Otherwise fall back to a live call\n // via the throttle — first-time lookups on freshly-added groups go\n // this path, and bulk sweeps respect the rate limit.\n const cached = this.cache.getGroup(jid)\n if (cached && cached.subject) return groupRecordToInfo(cached)\n if (!this.isConnected()) return cached ? groupRecordToInfo(cached) : null\n try {\n const live: any = await this.throttleGate(() => this.sock.groupMetadata(jid))\n // Hydrate the cache so subsequent sweeps hit in memory.\n this.cache.applyGroupsUpdate([{\n id: jid,\n subject: live?.subject,\n desc: live?.desc,\n owner: live?.owner,\n participants: live?.participants,\n }])\n const refreshed = this.cache.getGroup(jid)\n return refreshed ? groupRecordToInfo(refreshed) : null\n } catch (e: any) {\n this.log(`getGroupMetadata(${jid}) failed: ${e.message}`)\n return cached ? groupRecordToInfo(cached) : null\n }\n }\n\n /** Fetch up to `limit` recent messages for `jid`. This is the one read\n * that always hits Baileys live — there's no cached message body and\n * baileys doesn't persist history by default with the options we use.\n * Throttled. */\n async getHistory(\n jid: string,\n opts: { limit?: number; before?: string } = {},\n ): Promise<HistoryMessage[]> {\n if (!this.isConnected()) return []\n const limit = Math.max(1, Math.min(500, opts.limit ?? 50))\n try {\n const messages = await this.throttleGate(async () => {\n // Baileys' `fetchMessagesFromWA` signature varies across versions.\n // Call the stable `loadMessages` if present, else `fetchMessagesFromWA`.\n const sock: any = this.sock\n if (typeof sock.loadMessages === \"function\") {\n return await sock.loadMessages(jid, limit, opts.before ? { before: { id: opts.before } } : undefined)\n }\n if (typeof sock.fetchMessagesFromWA === \"function\") {\n return await sock.fetchMessagesFromWA(jid, limit, opts.before ? { before: { id: opts.before } } : undefined)\n }\n return []\n })\n return (messages || []).map(mapBaileysMessage).filter(Boolean) as HistoryMessage[]\n } catch (e: any) {\n this.log(`getHistory(${jid}) failed: ${e.message}`)\n return []\n }\n }\n\n /** ChannelAdapter.seedHistory: thin wrapper over getHistory() that maps\n * Baileys' HistoryMessage shape to the channel-agnostic SeededMessage\n * shape consumed by SessionStore on cold session create. Bounded by the\n * caller's maxMessages/maxChars; fromMe → role:agent. The chatId is the\n * Baileys jid (e.g., \"21694xxx@s.whatsapp.net\" for DMs). */\n async seedHistory(\n chatId: string,\n opts: { sinceISO?: string; maxMessages: number; maxChars: number },\n ): Promise<import(\"./types\").SeededMessage[]> {\n const sinceMs = opts.sinceISO ? new Date(opts.sinceISO).getTime() : 0\n const history = await this.getHistory(chatId, { limit: opts.maxMessages })\n const out: import(\"./types\").SeededMessage[] = []\n let chars = 0\n for (const m of history) {\n if (sinceMs && (m.timestamp ?? 0) * 1000 < sinceMs) continue\n if (!m.text) continue\n const ts = m.timestamp ? new Date(m.timestamp * 1000).toISOString() : new Date().toISOString()\n out.push({\n role: m.fromMe ? \"agent\" : \"user\",\n name: m.fromMe ? \"agent\" : (m.fromJid || \"user\"),\n content: m.text,\n timestamp: ts,\n externalId: m.id,\n })\n chars += m.text.length\n if (out.length >= opts.maxMessages) break\n if (chars >= opts.maxChars) break\n }\n return out\n }\n\n /** Token-bucket gate for live Baileys reads. Enforces\n * `throttleMinMs` between calls and caps the per-minute call count.\n * Returns a promise that resolves once it's safe to fire. */\n private async throttleGate<T>(fn: () => Promise<T>): Promise<T> {\n const now = Date.now()\n // Remove calls older than the 60s window\n this.readsInWindow = this.readsInWindow.filter((t) => now - t < 60_000)\n // If we're over the per-minute cap, wait until the oldest call ages out\n if (this.readsInWindow.length >= this.throttleMaxPerMinute) {\n const wait = 60_000 - (now - this.readsInWindow[0]!) + 10\n await delay(wait)\n }\n // Enforce minimum spacing between calls\n const sinceLast = Date.now() - this.lastReadAt\n if (sinceLast < this.throttleMinMs) {\n await delay(this.throttleMinMs - sinceLast)\n }\n this.lastReadAt = Date.now()\n this.readsInWindow.push(this.lastReadAt)\n return fn()\n }\n}\n\n// --- Helpers: shape translators (keep the public API decoupled from cache schema) ---\n\nfunction contactRecordToSummary(r: ContactRecord): ContactSummary {\n return {\n jid: r.jid,\n phone: r.phone,\n name: r.savedName || r.pushName || r.phone || r.jid,\n pushName: r.pushName,\n savedName: r.savedName,\n status: r.status,\n updatedAt: r.updatedAt,\n }\n}\n\nfunction chatRecordToSummary(r: ChatRecord): ChatSummary {\n return {\n jid: r.jid,\n name: r.name || r.jid.replace(/@.*$/, \"\"),\n isGroup: r.isGroup,\n lastMessageAt: r.lastMessageAt,\n unreadCount: r.unreadCount,\n }\n}\n\nfunction groupRecordToInfo(r: GroupRecord): GroupInfo {\n return {\n jid: r.jid,\n subject: r.subject || \"\",\n description: r.description,\n owner: r.owner,\n members: r.members.map((m) => ({ jid: m.jid, admin: m.admin })),\n memberCount: r.members.length,\n }\n}\n\nfunction mapBaileysMessage(raw: any): HistoryMessage | null {\n if (!raw || !raw.key) return null\n const msg = raw.message || {}\n const text =\n msg.conversation ||\n msg.extendedTextMessage?.text ||\n msg.imageMessage?.caption ||\n msg.videoMessage?.caption ||\n \"\"\n let media: HistoryMessage[\"media\"] | undefined\n if (msg.imageMessage) media = { kind: \"image\", caption: msg.imageMessage.caption }\n else if (msg.audioMessage) media = { kind: \"audio\" }\n else if (msg.videoMessage) media = { kind: \"video\", caption: msg.videoMessage.caption }\n else if (msg.documentMessage) media = { kind: \"document\", filename: msg.documentMessage.fileName }\n else if (msg.stickerMessage) media = { kind: \"sticker\" }\n // Skip empty-text messages with no media — they're almost always\n // receipts, reactions, or protocol frames not useful to the wiki.\n if (!text && !media) return null\n const timestamp = typeof raw.messageTimestamp === \"number\"\n ? raw.messageTimestamp\n : (raw.messageTimestamp?.low ?? undefined)\n return {\n id: raw.key.id,\n fromJid: raw.key.fromMe\n ? (raw.key.remoteJid || \"\")\n : (raw.key.participant || raw.key.remoteJid || \"\"),\n fromMe: !!raw.key.fromMe,\n timestamp,\n text,\n media,\n }\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, Math.max(0, ms)))\n}\n","import { readFileSync, writeFileSync, mkdirSync, existsSync } from \"fs\"\nimport { resolve, dirname } from \"path\"\n\n// --- WhatsApp passive cache ---\n//\n// Baileys emits `contacts.update`, `chats.upsert`, `groups.update`,\n// `messaging-history.set` whenever the socket observes changes. Previously\n// we ignored those events — meaning the only WhatsApp data the system\n// knew about came from messages that happened to trigger an agent reply.\n//\n// This cache subscribes passively to those events and keeps a small\n// snapshot of contacts / chats / groups. The wiki ingestor (src/wiki/\n// ingest-whatsapp.ts) reads from here instead of hitting Baileys live,\n// which keeps ingest fast and avoids adding to the personal-account\n// ban-risk surface. The cache is best-effort — entries appear when the\n// socket sees them, and stay until the daemon restarts (or are hydrated\n// from disk).\n//\n// Shape decisions:\n// - JID is the canonical key everywhere. \"Phone\" is derived from the\n// JID's user segment for display.\n// - We never store message bodies here. Messages are pulled on demand\n// by the ingestor when an allowlist opts into `mode: \"messages\"`,\n// and they're dropped after the ingestor hands the raw entry to the\n// wiki. Caching message bodies would duplicate WhatsApp's own store\n// and explode the cache file size.\n\nexport interface ContactRecord {\n jid: string\n /** Digits extracted from the JID's user segment. May be empty for LIDs. */\n phone: string\n /** The contact's own \"push name\" (what they set on their device). */\n pushName?: string\n /** Saved name from WhatsApp address book (if the linked device has it). */\n savedName?: string\n /** Status / about text. Rarely populated from events alone; usually needs a live fetch. */\n status?: string\n /** Last time we got any update for this contact. ISO8601. */\n updatedAt: string\n}\n\nexport interface ChatRecord {\n jid: string\n /** Best display name we have for the chat. Falls back to phone if missing. */\n name?: string\n /** Derived from the JID — group chats end with @g.us. */\n isGroup: boolean\n /** Last conversation timestamp we observed, as unix seconds. */\n lastMessageAt?: number\n unreadCount?: number\n updatedAt: string\n}\n\nexport interface GroupMember {\n jid: string\n admin?: \"admin\" | \"superadmin\"\n}\n\nexport interface GroupRecord {\n jid: string\n subject: string\n description?: string\n owner?: string\n members: GroupMember[]\n updatedAt: string\n}\n\ninterface CacheSnapshot {\n version: 1\n contacts: ContactRecord[]\n chats: ChatRecord[]\n groups: GroupRecord[]\n}\n\nexport class WhatsAppCache {\n private contacts: Map<string, ContactRecord> = new Map()\n private chats: Map<string, ChatRecord> = new Map()\n private groups: Map<string, GroupRecord> = new Map()\n private cachePath: string\n\n constructor(sessionDir: string) {\n this.cachePath = resolve(sessionDir, \"cache.json\")\n this.load()\n }\n\n // --- Read API (what the ingestor consumes) ---\n\n listContacts(): ContactRecord[] {\n return Array.from(this.contacts.values())\n }\n\n listChats(): ChatRecord[] {\n return Array.from(this.chats.values())\n }\n\n listGroups(): GroupRecord[] {\n return Array.from(this.groups.values())\n }\n\n getContact(jid: string): ContactRecord | undefined {\n return this.contacts.get(jid)\n }\n\n getChat(jid: string): ChatRecord | undefined {\n return this.chats.get(jid)\n }\n\n getGroup(jid: string): GroupRecord | undefined {\n return this.groups.get(jid)\n }\n\n // --- Event handlers (wired from WhatsAppAdapter) ---\n\n applyContactsUpdate(updates: Array<Record<string, unknown>>): void {\n const now = new Date().toISOString()\n for (const u of updates) {\n const jid = typeof u.id === \"string\" ? u.id : \"\"\n if (!jid) continue\n const prev = this.contacts.get(jid) ?? {\n jid,\n phone: extractPhone(jid),\n updatedAt: now,\n }\n const next: ContactRecord = {\n ...prev,\n pushName: firstString(u.notify, u.pushName, prev.pushName),\n savedName: firstString(u.name, u.verifiedName, prev.savedName),\n status: firstString(u.status, prev.status),\n updatedAt: now,\n }\n this.contacts.set(jid, next)\n }\n }\n\n applyChatsUpsert(chats: Array<Record<string, unknown>>): void {\n const now = new Date().toISOString()\n for (const c of chats) {\n const jid = typeof c.id === \"string\" ? c.id : \"\"\n if (!jid) continue\n const record: ChatRecord = {\n jid,\n name: typeof c.name === \"string\" ? c.name : this.chats.get(jid)?.name,\n isGroup: jid.endsWith(\"@g.us\"),\n lastMessageAt: toSeconds(c.conversationTimestamp) ?? this.chats.get(jid)?.lastMessageAt,\n unreadCount: typeof c.unreadCount === \"number\" ? c.unreadCount : this.chats.get(jid)?.unreadCount,\n updatedAt: now,\n }\n this.chats.set(jid, record)\n }\n }\n\n applyGroupsUpdate(updates: Array<Record<string, unknown>>): void {\n const now = new Date().toISOString()\n for (const g of updates) {\n const jid = typeof g.id === \"string\" ? g.id : \"\"\n if (!jid) continue\n const prev = this.groups.get(jid) ?? {\n jid,\n subject: \"\",\n members: [] as GroupMember[],\n updatedAt: now,\n }\n const members = Array.isArray(g.participants)\n ? (g.participants as Array<Record<string, unknown>>)\n .map((p) => ({\n jid: typeof p.id === \"string\" ? p.id : \"\",\n admin: typeof p.admin === \"string\" ? (p.admin as GroupMember[\"admin\"]) : undefined,\n }))\n .filter((m) => m.jid)\n : prev.members\n const next: GroupRecord = {\n ...prev,\n subject: typeof g.subject === \"string\" ? g.subject : prev.subject,\n description: typeof g.desc === \"string\" ? g.desc : prev.description,\n owner: typeof g.owner === \"string\" ? g.owner : prev.owner,\n members,\n updatedAt: now,\n }\n this.groups.set(jid, next)\n }\n }\n\n applyHistorySet(data: Record<string, unknown>): void {\n if (Array.isArray(data.chats)) this.applyChatsUpsert(data.chats as Array<Record<string, unknown>>)\n if (Array.isArray(data.contacts)) this.applyContactsUpdate(data.contacts as Array<Record<string, unknown>>)\n // Messages inside history.set are intentionally ignored — message\n // bodies are not cached here (see file-level comment).\n }\n\n // --- Persistence ---\n\n /** Write snapshot to cache.json. Call on graceful shutdown and periodically. */\n save(): void {\n const snapshot: CacheSnapshot = {\n version: 1,\n contacts: this.listContacts(),\n chats: this.listChats(),\n groups: this.listGroups(),\n }\n const dir = dirname(this.cachePath)\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true })\n writeFileSync(this.cachePath, JSON.stringify(snapshot, null, 2), \"utf8\")\n }\n\n private load(): void {\n if (!existsSync(this.cachePath)) return\n try {\n const raw = JSON.parse(readFileSync(this.cachePath, \"utf8\")) as CacheSnapshot\n if (raw.version !== 1) return\n for (const c of raw.contacts ?? []) this.contacts.set(c.jid, c)\n for (const c of raw.chats ?? []) this.chats.set(c.jid, c)\n for (const g of raw.groups ?? []) this.groups.set(g.jid, g)\n } catch {\n // Corrupted cache — start empty. Not fatal; we'll rebuild from live events.\n }\n }\n}\n\n/** \"+21624XXXXXXX@s.whatsapp.net\" → \"21624XXXXXXX\". LIDs (`...@lid`) have\n * no phone under WhatsApp's privacy model and return \"\". */\nfunction extractPhone(jid: string): string {\n const user = jid.split(\"@\")[0] ?? \"\"\n if (!user) return \"\"\n // LIDs are numeric too but their @-suffix is `@lid` — exclude.\n if (jid.endsWith(\"@lid\")) return \"\"\n return user.replace(/[^0-9]/g, \"\")\n}\n\n/** First non-empty string among the inputs — used to prefer live-event\n * fields over cached ones without losing the cached value when the\n * event didn't re-send it. Narrows `unknown` from Baileys payloads\n * to `string | undefined` so the call sites stay type-safe. */\nfunction firstString(...values: Array<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\n/** Baileys emits timestamps as either numeric seconds or Long-like objects.\n * Normalize to plain seconds. */\nfunction toSeconds(v: unknown): number | undefined {\n if (typeof v === \"number\" && Number.isFinite(v)) return v\n if (v && typeof v === \"object\" && \"low\" in (v as any) && \"high\" in (v as any)) {\n const low = (v as any).low as number\n const high = (v as any).high as number\n return high * 0x100000000 + (low >>> 0)\n }\n return undefined\n}\n","import { createServer, type IncomingMessage, type ServerResponse } from \"http\"\nimport { writeFileSync, existsSync, unlinkSync, mkdirSync, readFileSync, watch, type FSWatcher } from \"fs\"\nimport { resolve, dirname } from \"path\"\nimport { loadDaemonConfig, validateWorkspaces, type DaemonConfig } from \"./config\"\nimport { AgentRegistry, setGlobalRegistry } from \"@/agents/registry\"\nimport { MessageRouter } from \"@/channels/router\"\nimport { TelegramAdapter } from \"@/channels/telegram\"\nimport { WhatsAppAdapter } from \"@/channels/whatsapp\"\nimport { DiscordAdapter } from \"@/channels/discord\"\nimport { SlackAdapter } from \"@/channels/slack\"\nimport { GitLabAdapter } from \"@/channels/gitlab\"\nimport { GitHubAdapter } from \"@/channels/github\"\nimport { WebRtcSignalBroker, type WebRtcSignal } from \"@/channels/webrtc-signal\"\nimport { CALL_PAGE_HTML } from \"./call-page\"\nimport { BotManager } from \"./bot-manager\"\nimport { CronScheduler } from \"@/crons/scheduler\"\nimport { Logger } from \"./logger\"\nimport { EventBus, parseKindsParam } from \"./event-bus\"\nimport { WebhookHandler } from \"./webhooks\"\nimport { openDb, pruneSqliteTables } from \"@/storage/sqlite\"\nimport { attachSqliteSubscribers } from \"@/storage/subscribers\"\nimport { getUsageReadMode, loadTodayRollup } from \"@/storage/usage-query\"\nimport { loadPlugins, type LoadedPlugin } from \"@/plugins\"\nimport { getLedgerMode } from \"@/intent/mode\"\nimport { getDefaultLedger } from \"@/intent/instance\"\nimport { recordMeshDispatch } from \"@/intent/sources/mesh\"\nimport { setDefaultGovernance } from \"@/intent/governance\"\nimport { agentCanHandleIntent, withinDelegationBudget } from \"@/agents/capabilities\"\nimport { A2AMesh } from \"@/a2a/mesh\"\nimport { HookRegistry, loadHooks } from \"@/hooks\"\nimport {\n RunStore as WorkflowRunStore,\n WorkflowDispatcher,\n WorkflowStore,\n startWorkflowTriggers,\n type AgentExecuteRequest,\n type AgentExecuteResponse,\n type MeshForwarder as WorkflowMeshForwarder,\n} from \"@/workflows\"\nimport { createWorkflowHookHandlers } from \"@/workflows/hooks\"\nimport { LandscapeBuilder } from \"@/agents/landscape\"\nimport { AgentMemory } from \"@/agents/agent-memory\"\nimport { ContactDirectory } from \"@/agents/contacts\"\nimport { syncMcpToWorkspace, type McpServerMap } from \"@/agents/agent-mcp\"\nimport { REMEMBER_SKILL_BODY, REMEMBER_SKILL_FILENAME } from \"@/agents/skills/remember-skill\"\nimport { HeartbeatManager } from \"@/agents/heartbeat\"\nimport { setupAllWorkspaces } from \"@/agents/workspace-setup\"\nimport { ServiceMatcher } from \"@/services/matcher\"\nimport { BusinessLayer } from \"@/business\"\n\n// --- AgentX Daemon: the thin orchestration layer ---\n//\n// This is NOT an AI runtime. It's a message router + scheduler + mesh\n// that triggers Claude Code sessions in workspace directories.\n//\n// Each agent = a workspace with .claude/ config\n// agentx just orchestrates WHEN and WHERE Claude Code runs.\n\nexport class AgentXDaemon {\n private config: DaemonConfig\n private registry: AgentRegistry\n private router: MessageRouter\n private cron: CronScheduler\n private mesh?: A2AMesh\n private hooks: HookRegistry\n private landscape: LandscapeBuilder\n private heartbeat: HeartbeatManager\n private business?: BusinessLayer\n private httpServer?: ReturnType<typeof createServer>\n private webhooks: WebhookHandler\n private github?: GitHubAdapter\n private webrtc?: WebRtcSignalBroker\n private botManager?: BotManager\n private workflowDispatcher?: WorkflowDispatcher\n private workflowStore?: WorkflowStore\n private workflowRuns?: WorkflowRunStore\n private db: import(\"better-sqlite3\").Database | null = null\n private loadedPlugins: LoadedPlugin[] = []\n private readonly agentMemory: AgentMemory = new AgentMemory()\n private contacts!: ContactDirectory\n readonly events: EventBus = new EventBus()\n private log: (...args: unknown[]) => void\n private sseClients: Set<ServerResponse> = new Set()\n private configPath?: string\n private configWatcher?: FSWatcher\n private reloadTimer?: ReturnType<typeof setTimeout>\n\n constructor(configPath?: string) {\n const logger = new Logger(\"agentx\")\n const baseLog = logger.asConsoleLog()\n\n // Wrap log to also broadcast to SSE clients\n this.log = (...args: unknown[]) => {\n baseLog(...args)\n const line = args.map(a => typeof a === \"string\" ? a : JSON.stringify(a)).join(\" \")\n this.broadcastSSE(\"log\", line)\n }\n\n // Load config\n this.log(\"Loading configuration...\")\n this.configPath = configPath\n this.config = loadDaemonConfig(configPath)\n\n // Initialize the contact directory now that `this.log` is available.\n // Empty file (or missing file) is fine — operators populate it later.\n this.contacts = new ContactDirectory(process.cwd(), this.log)\n\n // Validate\n const warnings = validateWorkspaces(this.config)\n for (const w of warnings) {\n this.log(` ⚠ ${w}`)\n }\n\n // Set up agent workspaces with Claude Code best practices (non-destructive)\n const [, portStr] = this.config.node.bind.split(\":\")\n setupAllWorkspaces(this.config.agents, portStr || \"19900\", this.log)\n\n // Initialize hooks\n this.hooks = new HookRegistry()\n loadHooks(process.cwd(), this.hooks)\n\n // Initialize landscape builder\n this.landscape = new LandscapeBuilder(this.config)\n\n // Initialize agent registry\n this.registry = new AgentRegistry(this.config, this.log)\n setGlobalRegistry(this.registry)\n this.registry.setLandscape(this.landscape)\n\n // Initialize heartbeat manager\n this.heartbeat = new HeartbeatManager(this.registry, this.log)\n for (const [id, agent] of Object.entries(this.config.agents)) {\n if (agent.heartbeat?.enabled) {\n this.heartbeat.register(id, agent.heartbeat)\n }\n }\n\n // Initialize message router\n this.router = new MessageRouter(this.registry, this.config, this.hooks, this.log)\n\n // Initialize service matcher (automated client services)\n if (Object.keys(this.config.services).length > 0) {\n const serviceMatcher = new ServiceMatcher(this.config.services, this.log)\n this.router.setServiceMatcher(serviceMatcher)\n }\n\n // Initialize cron scheduler with failure notifications\n this.cron = new CronScheduler(this.config, this.registry, this.hooks, this.log)\n this.cron.setNotifyCallback(async (jobId, agent, error, consecutiveErrors) => {\n const msg = `Cron \"${jobId}\" failed (${consecutiveErrors}x)\\nAgent: ${agent}\\nError: ${error.slice(0, 300)}`\n this.log(`[CRON ALERT] ${msg}`)\n this.broadcastSSE(\"cron-failure\", JSON.stringify({ jobId, agent, error, consecutiveErrors }))\n\n // Send to the cron job's configured notify destination (if set)\n const cronDef = this.config.crons[jobId]\n if (cronDef?.notify) {\n try {\n await this.router.sendOutbound({\n channel: cronDef.notify.channel,\n chatId: cronDef.notify.chatId,\n text: `🔴 **Cron \"${jobId}\" failed** (${consecutiveErrors}x)\\n${error.slice(0, 300)}`,\n agentId: agent,\n accountId: cronDef.notify.accountId,\n })\n } catch (e: any) {\n this.log(`[CRON ALERT] notify send failed: ${e.message}`)\n }\n }\n })\n\n // Initialize mesh (if enabled)\n if (this.config.mesh.enabled) {\n this.mesh = new A2AMesh(this.config, this.log)\n this.router.setMesh(this.mesh)\n // Bridge mesh peer-state transitions into the event bus so\n // operators watching /events see recoveries + losses in real time.\n this.mesh.onPeerChange((e) => {\n this.events.publish({\n kind: \"mesh\",\n peer: e.peer,\n healthy: e.healthy,\n skills: e.skills,\n delta: e.delta,\n })\n })\n // Let the registry reach the mesh so it can forward unknown-agent\n // tasks to a peer that advertises the requested agent. Keeps\n // workflow authoring portable: a workflow that references\n // `coo-agent` works on any node in the mesh as long as SOMEONE\n // hosts that agent.\n this.registry.setMeshFallback({\n findPeerWithSkill: (skillId) => this.mesh!.findPeerWithSkill(skillId),\n sendTask: (peer, text, agentId) => this.mesh!.sendTask(peer, text, agentId),\n directory: () => this.mesh!.directory(),\n })\n }\n\n // Initialize webhook handler (after mesh so mesh-forwarding works)\n this.webhooks = new WebhookHandler(this.registry, {}, this.log, this.mesh, this.config.webhooks)\n\n // Move 2: open SQLite + attach bus subscribers. Best-effort — if the\n // native binding isn't available (operator hasn't run pnpm install),\n // openDb returns null and subscribers are skipped. Existing JSON\n // writes continue regardless. SQLite is observability-grade for now.\n try {\n const db = openDb()\n this.db = db\n if (db) {\n attachSqliteSubscribers(db)\n this.log(` SQLite: ${db.name}`)\n } else {\n this.log(` SQLite: not opened (native binding unavailable or path unwritable)`)\n }\n } catch (e: any) {\n this.log(` SQLite: skipped (${e.message})`)\n }\n\n // Initialize business layer (if enabled)\n if (this.config.business?.enabled) {\n try {\n this.business = new BusinessLayer(\n this.config.business,\n this.config,\n this.registry,\n this.router,\n this.log,\n )\n this.router.setBusiness(this.business)\n\n // Phase 3 — wire org-chart governance into decideAndCommit when\n // INTENT_PM_GATE_ENABLED is set. Default off so the live shadow\n // soak isn't disturbed; flip on per-deployment after soak\n // findings settle. The DispatchGovernance hooks read from\n // BusinessLayer.org so PM/canHandle changes apply on next event\n // without a daemon restart.\n if (process.env.INTENT_PM_GATE_ENABLED === \"true\" || process.env.INTENT_PM_GATE_ENABLED === \"1\") {\n const org = this.business.org\n const agents = this.config.agents\n setDefaultGovernance({\n pmFor: (project) => org.pmFor(project),\n // canHandle layers two checks:\n // 1. Org-chart membership (Phase 3) — agent is registered.\n // 2. Phase 5 typed-capability check — agent's `intents`\n // list (when non-empty) must include the dispatched\n // intent. When the list is empty (default), handles all.\n canHandle: (agentId, project, intent) => {\n if (!org.canHandle(agentId, project, intent)) return false\n return agentCanHandleIntent(agents[agentId], intent)\n },\n // Phase 8 — delegation-depth check. Walks the ledger's\n // prior decisions on (project, subject) and refuses\n // dispatches past the target agent's maxDelegationDepth.\n withinDelegationBudget: (agentId, project, subject) =>\n withinDelegationBudget(getDefaultLedger(), agents[agentId], agentId, project, subject),\n })\n this.log(\" Ledger governance: PM gate + capability + delegation-depth ENABLED\")\n }\n } catch (e: any) {\n this.log(`[business] initialization failed: ${e.message}`)\n }\n }\n }\n\n async start(): Promise<void> {\n this.log(\"\")\n this.log(\" ┌─────────────────────────────────────┐\")\n this.log(\" │ agentx daemon │\")\n this.log(\" └─────────────────────────────────────┘\")\n this.log(\"\")\n this.log(` Node: ${this.config.node.name} (${this.config.node.id})`)\n this.log(` Bind: ${this.config.node.bind}`)\n this.log(\"\")\n\n // 0. Phase 1 — clean up orphaned in-flight ledger dispatches from\n // the previous process. Their agents died with the previous\n // daemon; without writing a \"canceled\" resolution they'd block\n // Inv-ActiveTaskSafety on the same (project, subject) forever.\n // Only fires when the ledger is reachable (some source has\n // mode != \"off\"); the IntentLedger singleton is lazy and\n // construction would otherwise be skipped entirely under\n // deploy-default mode=off.\n try {\n const anySourceActive = (\n [\"telegram\", \"slack\", \"whatsapp\", \"discord\", \"gitlab\", \"github\", \"workflow\", \"cron\", \"mesh\"] as const\n ).some((s) => getLedgerMode(s) !== \"off\")\n if (anySourceActive) {\n const closed = getDefaultLedger().cleanupOrphanedDispatches(\"daemon-restart\")\n if (closed > 0) {\n this.log(` Ledger: cleaned up ${closed} orphaned in-flight dispatch(es) from previous process`)\n }\n }\n } catch (e: any) {\n this.log(` Ledger orphan-cleanup failed: ${e?.message ?? e}`)\n }\n\n // 0.5. Move B — load JS/TS plugins. Each plugin's setup() runs here so\n // it can register channel adapters BEFORE startChannels() and\n // attach bus subscribers BEFORE any event fires. setup() is\n // raced against a 15s timeout per plugin; failures log and\n // continue. Plugin-registered channels are folded into the\n // router right after startChannels() so they participate in\n // routing alongside built-in adapters.\n try {\n const builtInChannelNames = new Set([\n \"telegram\", \"whatsapp\", \"discord\", \"slack\", \"gitlab\", \"github\",\n ])\n this.loadedPlugins = await loadPlugins({\n config: this.config,\n agents: new Map(Object.entries(this.config.agents)),\n log: this.log,\n isChannelNameTaken: (n) => builtInChannelNames.has(n),\n daemonVersion: process.env.npm_package_version,\n })\n if (this.loadedPlugins.length > 0) {\n this.log(` Plugins: ${this.loadedPlugins.length} loaded — ${this.loadedPlugins.map(p => p.manifest.name).join(\", \")}`)\n }\n } catch (e: any) {\n // loadPlugins() is supposed to never throw, but defend anyway —\n // a plugin failure must not abort daemon boot.\n this.log(` Plugins: loader threw (${e?.message ?? e}) — continuing without plugins`)\n this.loadedPlugins = []\n }\n\n // 1. Start channels\n await this.startChannels()\n // After startChannels, fold plugin-registered channels into the router\n // and call their start(). Done here (not inside startChannels itself)\n // so the lifecycle ordering is explicit: built-in channels first,\n // plugin channels second.\n for (const p of this.loadedPlugins) {\n for (const ch of p.channels) {\n try {\n this.router.addChannel(ch)\n await ch.start()\n this.log(` Plugin channel: ${ch.name} (from ${p.manifest.name})`)\n } catch (e: any) {\n this.log(` Plugin channel \"${ch.name}\" failed to start: ${e?.message ?? e}`)\n }\n }\n }\n\n // 2. Start cron scheduler\n await this.cron.start()\n\n // 3. Start mesh\n if (this.mesh) {\n await this.mesh.start()\n }\n\n // 4. Build agent landscape (after mesh so remote peers are discovered)\n const meshPeers = this.mesh?.directory().map(p => ({\n peer: p.peer,\n healthy: p.healthy,\n skills: p.skills,\n })) || []\n this.landscape.build(meshPeers)\n this.log(` Landscape: built for ${Object.keys(this.config.agents).length} agents`)\n\n // 5. Schedule midnight cost tracking hook + catch up any missed days.\n // Catch-up is idempotent and bounded to 30 days — a daemon restarted\n // after an outage backfills TOKEN_COSTS.md instead of silently losing\n // those rows. (The previous behavior only ever scheduled tomorrow's\n // midnight run; any restart before midnight lost that day's row.)\n this.scheduleMidnightHook()\n try {\n const yesterday = new Date(Date.now() - 86400_000).toISOString().slice(0, 10)\n const appended = this.registry.getTokenTracker().catchUpTokenCosts(yesterday)\n if (appended.length > 0) {\n this.log(` Cost tracking: caught up ${appended.length} missed day${appended.length === 1 ? \"\" : \"s\"} (${appended[0]}${appended.length > 1 ? ` → ${appended[appended.length - 1]}` : \"\"})`)\n }\n } catch (err: any) {\n this.log(` Cost tracking: catch-up failed (non-fatal): ${err.message}`)\n }\n\n // 5b. Start business layer day cycle (if configured)\n if (this.business) {\n this.business.start()\n this.log(` ${this.business.summary()}`)\n }\n\n // 6. Start HTTP API\n await this.startHttpApi()\n\n // Print agent summary\n this.log(\"\")\n this.log(\" Agents:\")\n for (const agent of this.registry.list()) {\n this.log(` ${agent.id} (${agent.tier}) → ${agent.workspace}`)\n }\n\n // Install the `remember` skill + sync existing memory into each\n // agent's workspace. Write-if-absent for the skill (respect any\n // customizations); replace-in-place for the CLAUDE.md sentinel\n // block and the explicit .agentx-memory.md file.\n this.installAgentMemorySurface()\n\n // Sync each agent's MCP server config to <workspace>/.mcp.json.\n // Operator-owned files (no agentx marker) are skipped; only files\n // we wrote ourselves are rewritten or removed.\n this.installAgentMcpConfig()\n\n // Print cron summary\n const cronJobs = this.cron.list()\n if (cronJobs.length) {\n this.log(\"\")\n this.log(\" Cron Jobs:\")\n for (const job of cronJobs) {\n const status = job.enabled ? \"enabled\" : \"disabled\"\n this.log(` ${job.id} [${status}] → ${job.agent} (${job.schedule})`)\n }\n }\n\n // Print mesh summary\n if (this.mesh) {\n this.log(\"\")\n this.log(\" Mesh Peers:\")\n for (const peer of this.mesh.directory()) {\n const status = peer.healthy ? \"✓\" : \"✗\"\n this.log(` ${status} ${peer.peer} (${peer.peerUrl})`)\n }\n }\n\n // Start config file watcher (opt-out via AGENTX_AUTO_RELOAD=false)\n if (process.env.AGENTX_AUTO_RELOAD !== \"false\") {\n this.startConfigWatcher()\n }\n\n // Drop old task-history folders past the retention window.\n try {\n const removed = this.registry.pruneTaskHistory()\n if (removed > 0) this.log(` Pruned ${removed} old task-history folder(s)`)\n } catch { /* best-effort */ }\n\n // SQLite-side retention sweep — task_history / rotations / route_traces\n // grow unbounded otherwise. 90 days is generous for live debugging while\n // keeping the file small enough to rsync. Same window as task-history files.\n try {\n if (this.db) {\n const r = pruneSqliteTables(this.db, 90)\n const total = r.taskHistory + r.rotations + r.routeTraces\n if (total > 0) {\n this.log(` Pruned ${total} old SQLite row(s) (task_history=${r.taskHistory}, rotations=${r.rotations}, route_traces=${r.routeTraces})`)\n }\n }\n } catch (e: any) {\n this.log(` SQLite prune skipped: ${e?.message ?? e}`)\n }\n\n // Warm the per-agent last-summary cache from disk so dashboard cards have\n // something to show the instant the daemon boots.\n try {\n const loaded = this.registry.hydrateLastSummariesFromDisk()\n if (loaded > 0) this.log(` Loaded last-activity summaries for ${loaded} agent(s)`)\n } catch { /* best-effort */ }\n\n this.log(\"\")\n this.log(\" Ready.\")\n this.log(\"\")\n\n // Write PID file\n const pidFile = resolve(process.cwd(), \".agentx/daemon.pid\")\n mkdirSync(dirname(pidFile), { recursive: true })\n writeFileSync(pidFile, String(process.pid))\n this.log(` PID: ${process.pid} (${pidFile})`)\n\n // Catch unhandled errors — log but don't crash\n process.on(\"uncaughtException\", (err) => {\n this.log(`UNCAUGHT EXCEPTION: ${err.message}`)\n this.log(err.stack || \"\")\n })\n process.on(\"unhandledRejection\", (reason) => {\n this.log(`UNHANDLED REJECTION: ${reason}`)\n })\n\n // Graceful shutdown\n let stopping = false\n const shutdown = async (signal: string) => {\n if (stopping) return\n stopping = true\n this.log(`\\n Received ${signal}, shutting down gracefully...`)\n await this.stop()\n }\n process.on(\"SIGINT\", () => shutdown(\"SIGINT\"))\n process.on(\"SIGTERM\", () => shutdown(\"SIGTERM\"))\n }\n\n async stop(): Promise<void> {\n const start = Date.now()\n\n try {\n this.log(\" Stopping channels...\")\n await Promise.race([this.router.stopAll(), new Promise(r => setTimeout(r, 5000))])\n } catch (e: any) {\n this.log(` Channel stop error: ${e.message}`)\n }\n\n // Drain active agent tasks before exit. Channels are already stopped, so\n // the active count can only shrink. This is what lets `systemctl restart`\n // survive long-running agent turns (e.g. a 3-minute coder reply) without\n // killing them mid-flight. The inflight log handles messages that hadn't\n // started yet — drain handles ones already executing.\n //\n // Drain ceiling: AGENTX_DRAIN_TIMEOUT_MS (default 300_000 = 5 min). Keep\n // systemd's TimeoutStopSec ≥ this + ~30s margin or systemd will SIGKILL\n // mid-drain.\n try {\n const drainTimeoutMs = parseInt(process.env.AGENTX_DRAIN_TIMEOUT_MS || \"300000\", 10)\n const drainStart = Date.now()\n const inflightCount = () => this.registry.getActiveTaskCount() + this.router.getActiveMeshForwardCount()\n let active = inflightCount()\n if (active > 0) {\n this.log(` Draining ${active} in-flight task(s) — local=${this.registry.getActiveTaskCount()}, mesh-forwards=${this.router.getActiveMeshForwardCount()} (max ${Math.round(drainTimeoutMs / 1000)}s)...`)\n while (active > 0 && Date.now() - drainStart < drainTimeoutMs) {\n await new Promise(r => setTimeout(r, 500))\n active = inflightCount()\n }\n const elapsedMs = Date.now() - drainStart\n if (active === 0) {\n this.log(` Drain complete (${elapsedMs}ms)`)\n } else {\n this.log(` Drain timeout after ${elapsedMs}ms — ${active} task(s) still in flight (local=${this.registry.getActiveTaskCount()}, mesh-forwards=${this.router.getActiveMeshForwardCount()}), exiting anyway`)\n }\n }\n } catch (e: any) {\n this.log(` Drain error: ${e.message}`)\n }\n\n try {\n // Persist router dedup + any debounced per-channel state before exit so\n // a clean stop doesn't drop the last in-memory window of processed ids.\n this.router.flushPersistence?.()\n } catch (e: any) {\n this.log(` Router flush error: ${e.message}`)\n }\n\n try {\n this.log(\" Stopping crons (saving last run times)...\")\n await this.cron.stop()\n } catch {}\n\n try {\n this.log(\" Stopping heartbeats...\")\n this.heartbeat.stopAll()\n } catch {}\n\n try {\n if (this.business) {\n this.log(\" Stopping business layer...\")\n this.business.stop()\n }\n } catch {}\n\n try {\n if (this.mesh) {\n this.log(\" Stopping mesh...\")\n await this.mesh.stop()\n }\n } catch {}\n\n try {\n if (this.botManager) {\n this.botManager.shutdown()\n }\n } catch {}\n\n try {\n if (this.webrtc) {\n this.webrtc.shutdown()\n }\n } catch {}\n\n // Move B — dispose plugins. Each dispose() runs the plugin's optional\n // teardown() AND removes every bus subscription it attached via\n // ctx.on(). Run sequentially so a slow teardown doesn't race with\n // the daemon's own shutdown logging.\n for (const p of this.loadedPlugins) {\n try {\n await p.dispose()\n } catch (e: any) {\n this.log(` Plugin dispose error (${p.manifest.name}): ${e?.message ?? e}`)\n }\n }\n\n if (this.midnightTimer) clearTimeout(this.midnightTimer)\n\n if (this.reloadTimer) clearTimeout(this.reloadTimer)\n if (this.configWatcher) {\n try { this.configWatcher.close() } catch { /* best effort */ }\n }\n\n if (this.httpServer) {\n this.httpServer.close()\n }\n\n // Clean PID file\n try {\n const pidFile = resolve(process.cwd(), \".agentx/daemon.pid\")\n if (existsSync(pidFile)) unlinkSync(pidFile)\n } catch {}\n\n this.log(` Shutdown complete (${Date.now() - start}ms)`)\n process.exit(0)\n }\n\n private midnightTimer?: ReturnType<typeof setTimeout>\n\n /**\n * Watch agentx.json for external edits (e.g. `agentx config set ...`) and\n * reload cron jobs + in-memory config. Channels / agents / mesh changes\n * still require a restart — we log a warning so the operator knows.\n */\n private startConfigWatcher(): void {\n const path = this.configPath || resolve(process.cwd(), \"agentx.json\")\n if (!existsSync(path)) return\n try {\n this.configWatcher = watch(path, { persistent: false }, (eventType) => {\n if (eventType !== \"change\") return\n if (this.reloadTimer) clearTimeout(this.reloadTimer)\n this.reloadTimer = setTimeout(() => {\n this.reload().catch((e) => this.log(`[reload] failed: ${e?.message || e}`))\n }, 500) // debounce\n })\n this.log(` Watching ${path} for config changes`)\n } catch (e: any) {\n this.log(` Config watcher failed to start: ${e.message}`)\n }\n }\n\n /**\n * Re-read agentx.json, diff against the in-memory config, apply what we\n * can hot-reload (crons, notify-destination, business metadata), and warn\n * about sections that require a daemon restart (channels, agents, mesh,\n * node.bind, providers).\n */\n async reload(): Promise<{ applied: string[]; restartRequired: string[]; error?: string }> {\n let next: DaemonConfig\n try {\n next = loadDaemonConfig(this.configPath)\n } catch (e: any) {\n this.log(`[reload] config invalid, keeping previous: ${e.message}`)\n return { applied: [], restartRequired: [], error: e.message }\n }\n\n const applied: string[] = []\n const restartRequired: string[] = []\n\n // 1. Crons — safe to hot-swap (stop + reinit)\n if (JSON.stringify(this.config.crons) !== JSON.stringify(next.crons)) {\n try {\n await this.cron.stop()\n this.cron = new CronScheduler(next, this.registry, this.hooks, this.log)\n this.cron.setNotifyCallback(async (jobId, agent, error, consecutiveErrors) => {\n this.log(`[CRON ALERT] Cron \"${jobId}\" failed (${consecutiveErrors}x) — ${error.slice(0, 200)}`)\n this.broadcastSSE(\"cron-failure\", JSON.stringify({ jobId, agent, error, consecutiveErrors }))\n const cronDef = next.crons[jobId]\n if (cronDef?.notify) {\n try {\n await this.router.sendOutbound({\n channel: cronDef.notify.channel,\n chatId: cronDef.notify.chatId,\n text: `Cron \"${jobId}\" failed (${consecutiveErrors}x)\\n${error.slice(0, 300)}`,\n agentId: agent,\n accountId: cronDef.notify.accountId,\n })\n } catch (e: any) {\n this.log(`[CRON ALERT] notify send failed: ${e.message}`)\n }\n }\n })\n await this.cron.start()\n applied.push(\"crons\")\n } catch (e: any) {\n this.log(`[reload] cron reload failed: ${e.message}`)\n }\n }\n\n // 2. Telegram accounts — hot-swap where we can. Adding a new bot account\n // (or rotating an existing token) used to need a full restart; now the\n // adapter diffs the new map against its live account set and starts/\n // stops poll loops surgically. Router also gets the fresh config so\n // send-side paths resolve the new token. Policy.allowFrom flips through\n // effectiveAllowFrom without restart.\n const tgPrev = this.config.channels?.telegram\n const tgNext = next.channels?.telegram\n const tgChanged = JSON.stringify(tgPrev) !== JSON.stringify(tgNext)\n let tgHandled = false\n if (tgChanged && tgNext?.enabled) {\n const telegram = this.router.getChannel(\"telegram\") as TelegramAdapter | undefined\n if (telegram) {\n try {\n const diff = await telegram.reloadAccounts(tgNext.accounts, tgNext.policy)\n this.router.updateConfig(next)\n const parts: string[] = []\n if (diff.added.length) parts.push(`+${diff.added.join(\",\")}`)\n if (diff.removed.length) parts.push(`-${diff.removed.join(\",\")}`)\n if (diff.tokenChanged.length) parts.push(`~${diff.tokenChanged.join(\",\")}`)\n applied.push(parts.length ? `telegram(${parts.join(\" \")})` : \"telegram\")\n tgHandled = true\n } catch (e: any) {\n this.log(`[reload] telegram hot-reload failed: ${e.message}`)\n }\n }\n } else if (tgChanged && !tgNext?.enabled && tgPrev?.enabled) {\n // enabled→disabled transition still needs a restart — the adapter and\n // the channels Map can't be torn down cleanly mid-flight.\n tgHandled = false\n } else if (tgChanged && tgNext?.enabled && !tgPrev?.enabled) {\n // disabled→enabled: adapter doesn't exist yet, needs full init path.\n tgHandled = false\n } else if (!tgChanged) {\n tgHandled = true // no change, nothing to do\n }\n\n // 3. Providers — hot-swap the credential table. Registry re-reads it\n // per-task execution, so rotating API keys or adding a provider lands\n // instantly for the next task (in-flight tasks keep their closure).\n if (JSON.stringify(this.config.providers) !== JSON.stringify(next.providers)) {\n try {\n this.registry.setProviders(next.providers)\n applied.push(\"providers\")\n } catch (e: any) {\n this.log(`[reload] providers hot-reload failed: ${e.message}`)\n }\n }\n\n // 4. Mesh peers — diff the peer list: adds, removes, and url/token\n // rotations are handled in-place and trigger an immediate rediscovery\n // for changed peers. Health-check interval change needs restart.\n const meshPrev = this.config.mesh\n const meshNext = next.mesh\n const meshChanged = JSON.stringify(meshPrev) !== JSON.stringify(meshNext)\n let meshHandled = !meshChanged\n if (meshChanged && meshNext.enabled && meshPrev.enabled && this.mesh) {\n // Only the peers list and per-peer url/token are hot. healthCheck\n // interval/timeout changes still need a restart because we'd have to\n // re-install setInterval.\n const intervalChanged = JSON.stringify(meshPrev.healthCheck) !== JSON.stringify(meshNext.healthCheck)\n if (!intervalChanged) {\n try {\n const diff = await this.mesh.reloadPeers(next)\n const parts: string[] = []\n if (diff.added.length) parts.push(`+${diff.added.join(\",\")}`)\n if (diff.removed.length) parts.push(`-${diff.removed.join(\",\")}`)\n if (diff.updated.length) parts.push(`~${diff.updated.join(\",\")}`)\n applied.push(parts.length ? `mesh(${parts.join(\" \")})` : \"mesh\")\n meshHandled = true\n } catch (e: any) {\n this.log(`[reload] mesh hot-reload failed: ${e.message}`)\n }\n }\n }\n\n // 5. Services — recompile the matcher's regex table. match() is sync and\n // stateless, so a swap between iterations is safe.\n if (JSON.stringify(this.config.services) !== JSON.stringify(next.services)) {\n try {\n const matcher = this.router.getServiceMatcher()\n if (matcher) {\n const { count } = matcher.reload(next.services)\n applied.push(`services(${count})`)\n } else if (Object.keys(next.services).length > 0) {\n // Services went from empty at boot to non-empty — we never created\n // a matcher, so we do create one now and wire it into the router.\n const fresh = new ServiceMatcher(next.services, this.log)\n this.router.setServiceMatcher(fresh)\n applied.push(`services(${Object.keys(next.services).length})`)\n }\n } catch (e: any) {\n this.log(`[reload] services hot-reload failed: ${e.message}`)\n }\n }\n\n // 6. Hooks — clear + reload from disk. Registry is just a Map<event, defs>\n // so the swap is atomic between events.\n try {\n const beforeSize = this.hooks.size?.() ?? 0\n this.hooks.clear()\n loadHooks(process.cwd(), this.hooks)\n const afterSize = this.hooks.size?.() ?? 0\n if (beforeSize !== afterSize) applied.push(`hooks(${afterSize})`)\n } catch (e: any) {\n this.log(`[reload] hooks reload failed: ${e.message}`)\n }\n\n // 7. Landscape — cheap rebuild, always safe.\n if (JSON.stringify(this.config.business) !== JSON.stringify(next.business)\n || JSON.stringify(this.config.agents) !== JSON.stringify(next.agents)) {\n try {\n this.landscape = new LandscapeBuilder(next)\n this.registry.setLandscape(this.landscape)\n applied.push(\"landscape\")\n } catch (e: any) {\n this.log(`[reload] landscape rebuild failed: ${e.message}`)\n }\n // Phase 4: when agents change locally, kick mesh peers to re-probe\n // us — and equally re-probe THEM so their roster changes are pulled\n // into our directory without waiting up to a full health-check tick.\n // Closes the \"newly-added remote agent silently unreachable for up\n // to 60s\" symptom the operator reported.\n if (this.mesh && this.mesh.peerCount() > 0) {\n try {\n const refreshed = await this.mesh.refreshAll()\n const healthy = refreshed.filter(r => r.healthy).length\n applied.push(`mesh.refresh(${healthy}/${refreshed.length})`)\n } catch (e: any) {\n this.log(`[reload] mesh refresh failed: ${e.message}`)\n }\n }\n }\n\n // 8. Sections that still require a full restart — narrowed down to:\n // agents (runtime state captured per-task), node.bind (listen socket),\n // non-telegram channels (session-bound sockets), mesh.healthCheck\n // (interval timer), and enabling/disabling a channel adapter wholesale.\n if (JSON.stringify(this.config.agents) !== JSON.stringify(next.agents)) {\n // Let registry swap the config reference so landscape/business reads\n // pick up new agent metadata (avatar, access, tier display). New\n // physical agents (adding/removing keys) still need restart because\n // the registry initializes state maps on construction.\n const oldIds = Object.keys(this.config.agents).sort().join(\",\")\n const newIds = Object.keys(next.agents).sort().join(\",\")\n if (oldIds === newIds) {\n // Same set of agent ids — hot-swap config-only fields through the\n // registry. Model changes still need a restart because Claude Code\n // subprocesses capture it at spawn; we surface this below.\n this.registry.setConfig(next)\n applied.push(\"agents.meta\")\n }\n // Detect which specific fields changed and whether restart is needed.\n const restartFields = detectAgentRestartFields(this.config.agents, next.agents)\n if (restartFields.length > 0) {\n restartRequired.push(`agents(${restartFields.join(\",\")})`)\n }\n }\n\n if (!meshHandled) restartRequired.push(\"mesh\")\n if (JSON.stringify(this.config.node) !== JSON.stringify(next.node)) {\n restartRequired.push(\"node\")\n }\n // Channels: hot-handle telegram, everything else is still restart-required.\n const channelsPrevMinusTg = { ...this.config.channels, telegram: undefined }\n const channelsNextMinusTg = { ...next.channels, telegram: undefined }\n if (JSON.stringify(channelsPrevMinusTg) !== JSON.stringify(channelsNextMinusTg)) {\n restartRequired.push(\"channels\")\n } else if (tgChanged && !tgHandled) {\n restartRequired.push(\"channels.telegram\")\n }\n\n // 8a. Contact directory — re-read .agentx/contacts.json. The contacts\n // file is independent of agentx.json (operator-managed, sibling of\n // .agentx/sessions); reloading it here lets `agentx_send_contact`\n // pick up edits without a daemon restart.\n {\n const before = this.contacts.size()\n const result = this.contacts.reload()\n if (result.error) {\n this.log(`[reload] contacts.json invalid: ${result.error}`)\n } else if (result.count !== before) {\n applied.push(`contacts(${result.count})`)\n }\n }\n\n // 8b. Webhook entries — hot-reload triggers / secretEnv / mesh routes\n // without a daemon bounce. Phase 4 closes the recurring complaint\n // that adding a webhook route to agentx.json required a restart.\n if (JSON.stringify(this.config.webhooks) !== JSON.stringify(next.webhooks)) {\n try {\n this.webhooks.setWebhookEntries(next.webhooks)\n applied.push(`webhooks(${next.webhooks.length})`)\n } catch (e: any) {\n this.log(`[reload] webhooks hot-reload failed: ${e.message}`)\n }\n }\n\n // 9. Swap in the new config so read-only endpoints (GET /crons etc.)\n // reflect it, and router send-side paths see fresh channel config.\n this.config = next\n this.router.updateConfig(next)\n\n if (applied.length) this.log(`[reload] applied: ${applied.join(\", \")}`)\n if (restartRequired.length) {\n this.log(`[reload] restart required to apply changes in: ${restartRequired.join(\", \")}`)\n this.broadcastSSE(\"reload-partial\", JSON.stringify({ applied, restartRequired }))\n } else if (applied.length) {\n this.broadcastSSE(\"reload-complete\", JSON.stringify({ applied }))\n }\n\n return { applied, restartRequired }\n }\n\n private scheduleMidnightHook(): void {\n const scheduleNext = () => {\n const now = new Date()\n // Next midnight in Africa/Tunis (5s buffer to ensure day rollover)\n const tunisStr = now.toLocaleString(\"en-US\", { timeZone: \"Africa/Tunis\" })\n const tunisNow = new Date(tunisStr)\n\n const target = new Date(tunisNow)\n target.setDate(target.getDate() + 1)\n target.setHours(0, 0, 5, 0)\n\n const tunisOffset = tunisNow.getTime() - now.getTime()\n const delay = target.getTime() - tunisOffset - now.getTime()\n\n this.log(` Cost tracking: next run in ${Math.round(delay / 60_000)}min`)\n\n this.midnightTimer = setTimeout(async () => {\n await this.runDailyCostReport()\n scheduleNext()\n }, Math.max(delay, 60_000)) // min 1 minute guard\n }\n\n scheduleNext()\n }\n\n private async runDailyCostReport(): Promise<void> {\n const yesterday = new Date(Date.now() - 86400_000).toISOString().slice(0, 10)\n const tracker = this.registry.getTokenTracker()\n\n // Idempotent guard — catch-up at startup may have already appended this\n // date, and a timer-edge double-fire would otherwise produce duplicate\n // rows (as happened with the 2026-04-08 duplicate in the current file).\n if (tracker.hasTokenCostsEntry(yesterday)) {\n this.log(` Cost tracking: ${yesterday} already logged, skipping`)\n return\n }\n\n const report = tracker.generateDailyReport(yesterday)\n if (!report || report.totalTasks === 0) {\n this.log(` Cost tracking: no usage for ${yesterday}, skipping`)\n return\n }\n\n tracker.appendToTokenCosts(report)\n this.log(\n ` Cost tracking: ${yesterday} — ${report.totalTasks} tasks, $${report.totalCost.toFixed(4)} (top: ${report.topAgent} $${report.topCost.toFixed(4)})`,\n )\n }\n\n private async startChannels(): Promise<void> {\n // Telegram\n if (this.config.channels.telegram.enabled) {\n const accounts = this.config.channels.telegram.accounts\n if (Object.keys(accounts).length > 0) {\n const policy = this.config.channels.telegram.policy\n const telegram = new TelegramAdapter(\n accounts,\n { policy: { allowFrom: policy?.allowFrom } },\n this.log,\n )\n this.router.addChannel(telegram)\n const globalSize = policy?.allowFrom?.length ?? 0\n const closedAccts = Object.entries(accounts).filter(\n ([, c]) => !c.allowFrom && globalSize === 0,\n ).length\n if (closedAccts > 0) {\n this.log(\n ` Telegram: enabled — WARNING: ${closedAccts} account(s) have no allowFrom (global or per-account). All incoming messages will be DROPPED.`,\n )\n } else {\n this.log(` Telegram: enabled — global allowFrom entries: ${globalSize}`)\n }\n }\n }\n\n // WhatsApp\n if (this.config.channels.whatsapp.enabled) {\n const { setWhatsAppQR, setWhatsAppStatus } = await import(\"./whatsapp-state\")\n const whatsapp = new WhatsAppAdapter(\n {\n sessionDir: this.config.channels.whatsapp.sessionDir,\n defaultAgent: this.config.channels.whatsapp.defaultAgent,\n allowFrom: this.config.channels.whatsapp.allowFrom,\n routes: this.config.channels.whatsapp.routes,\n // Throttle for live Baileys reads (ingestor). Defaults in the\n // adapter are conservative; operators can tighten/loosen via\n // channels.whatsapp.ingest.throttle.\n throttle: {\n minMsBetweenCalls: this.config.channels.whatsapp.ingest?.throttle?.minMsBetweenCalls,\n maxCallsPerMinute: this.config.channels.whatsapp.ingest?.throttle?.maxCallsPerMinute,\n },\n // Publish QR + status so /api/admin/channels/whatsapp/state can\n // surface the pairing code in the browser.\n onQR: setWhatsAppQR,\n onStatus: setWhatsAppStatus,\n },\n this.log,\n )\n this.router.addChannel(whatsapp)\n this.log(` WhatsApp: enabled (${this.config.channels.whatsapp.routes.length} routes)`)\n }\n\n // Discord\n if (this.config.channels.discord?.enabled && this.config.channels.discord.token) {\n const discord = new DiscordAdapter(\n {\n token: this.config.channels.discord.token,\n agentBinding: this.config.channels.discord.agentBinding,\n },\n this.log,\n )\n this.router.addChannel(discord)\n this.log(\" Discord: enabled\")\n }\n\n // Slack\n if (this.config.channels.slack?.enabled && this.config.channels.slack.botToken && this.config.channels.slack.appToken) {\n const slack = new SlackAdapter(\n {\n botToken: this.config.channels.slack.botToken,\n appToken: this.config.channels.slack.appToken,\n agentBinding: this.config.channels.slack.agentBinding,\n },\n this.log,\n )\n this.router.addChannel(slack)\n this.log(\" Slack: enabled\")\n } else if (this.config.channels.slack?.enabled) {\n this.log(\" Slack: enabled in config but missing botToken/appToken — skipped\")\n }\n\n // GitLab\n if (this.config.channels.gitlab?.enabled && this.config.channels.gitlab.token) {\n const gitlab = new GitLabAdapter(\n {\n webhookPort: this.config.channels.gitlab.webhookPort,\n webhookSecret: this.config.channels.gitlab.webhookSecret,\n host: this.config.channels.gitlab.host,\n token: this.config.channels.gitlab.token,\n routes: this.config.channels.gitlab.routes,\n agentMappings: this.config.channels.gitlab.agentMappings,\n // Every agent becomes an @-mention target on GitLab by default.\n // Explicit agentMappings above take precedence (per-agent token,\n // custom usernames); anything else gets a default derivation on\n // (re)start. Add/remove an agent → no GitLab config change needed.\n knownAgentIds: Object.keys(this.config.agents),\n },\n this.log,\n this.hooks,\n )\n // Wire up mesh reaction forwarder — for agents hosted on remote peers\n if (this.mesh) {\n gitlab.setReactForwarder(async (node, project, noteableType, noteableIid, noteId, agentId) => {\n const peer = this.mesh!.directory().find(p => p.peer === node && p.healthy)\n if (!peer) {\n this.log(`[gitlab] react forward: peer \"${node}\" not found or unhealthy`)\n return\n }\n const url = `${peer.peerUrl}/gitlab/react`\n try {\n const r = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ project, noteableType, noteableIid, noteId, agentId }),\n })\n const respText = await r.text().catch(() => \"\")\n this.log(`[gitlab] react forward -> ${url} : ${r.status} ${respText.slice(0, 200)}`)\n } catch (e: any) {\n this.log(`[gitlab] react forward FAILED -> ${url} : ${e.message}`)\n }\n })\n\n // Forward note posting to peer so reply identity stays under the\n // agent's real GitLab user (e.g. @devops-noqta) instead of whatever\n // the local global token resolves to (the group-access-token bot).\n gitlab.setSendNoteForwarder(async (node, project, noteableType, noteableIid, agentId, text): Promise<string> => {\n const peer = this.mesh!.directory().find(p => p.peer === node && p.healthy)\n if (!peer) {\n this.log(`[gitlab] send-note forward: peer \"${node}\" not found or unhealthy`)\n return \"\"\n }\n const url = `${peer.peerUrl}/gitlab/send-note`\n try {\n const r = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ project, noteableType, noteableIid, agentId, text }),\n })\n const data = await r.json().catch(() => ({}))\n this.log(`[gitlab] send-note forward -> ${url} : ${r.status} noteId=${(data as any).noteId || \"?\"}`)\n return (data as any).noteId || \"\"\n } catch (e: any) {\n this.log(`[gitlab] send-note forward FAILED -> ${url} : ${e.message}`)\n return \"\"\n }\n })\n\n // Same for time tracking — peer posts /add_spent_time as its own user.\n gitlab.setLogTimeForwarder(async (node, project, noteableType, noteableIid, agentId, durationMs): Promise<void> => {\n const peer = this.mesh!.directory().find(p => p.peer === node && p.healthy)\n if (!peer) {\n this.log(`[gitlab] log-time forward: peer \"${node}\" not found or unhealthy`)\n return\n }\n const url = `${peer.peerUrl}/gitlab/log-time`\n try {\n const r = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ project, noteableType, noteableIid, agentId, durationMs }),\n })\n const respText = await r.text().catch(() => \"\")\n this.log(`[gitlab] log-time forward -> ${url} : ${r.status} ${respText.slice(0, 160)}`)\n } catch (e: any) {\n this.log(`[gitlab] log-time forward FAILED -> ${url} : ${e.message}`)\n }\n })\n }\n this.router.addChannel(gitlab)\n this.log(` GitLab: enabled (${this.config.channels.gitlab.routes.length} project routes, webhook :${this.config.channels.gitlab.webhookPort})`)\n }\n\n // GitHub\n if (this.config.channels.github?.enabled) {\n const githubConfig = this.config.channels.github\n this.github = new GitHubAdapter(\n {\n token: githubConfig.token,\n tokenFile: githubConfig.tokenFile,\n appId: githubConfig.appId,\n clientId: githubConfig.clientId,\n privateKeyFile: githubConfig.privateKeyFile,\n webhookSecret: githubConfig.webhookSecret,\n routes: githubConfig.routes,\n agentMappings: githubConfig.agentMappings,\n },\n this.log,\n )\n // Wire mesh comment forwarder for remote agents\n if (this.mesh) {\n this.github.setSendCommentForwarder(async (node, repo, issueNumber, agentId, text): Promise<string> => {\n const peer = this.mesh!.directory().find(p => p.peer === node && p.healthy)\n if (!peer) {\n this.log(`[github] send-comment forward: peer \"${node}\" not found or unhealthy`)\n return \"\"\n }\n const url = `${peer.peerUrl}/github/send-comment`\n try {\n const r = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ repo, issueNumber, agentId, text }),\n })\n const data = await r.json().catch(() => ({}))\n this.log(`[github] send-comment forward -> ${url} : ${r.status} commentId=${(data as any).commentId || \"?\"}`)\n return (data as any).commentId || \"\"\n } catch (e: any) {\n this.log(`[github] send-comment forward FAILED -> ${url} : ${e.message}`)\n return \"\"\n }\n })\n }\n this.router.addChannel(this.github)\n this.log(` GitHub: enabled (${githubConfig.routes.length} repo routes)`)\n }\n\n // WebRTC signaling — control plane only. Media flows browser-to-browser\n // via WebRTC direct, never through this daemon. See src/channels/webrtc-signal.ts.\n if (this.config.channels.webrtc?.enabled) {\n const wrtcCfg = this.config.channels.webrtc\n this.webrtc = new WebRtcSignalBroker(\n this.config.node.name,\n wrtcCfg.allowedCallers,\n this.log,\n )\n if (this.mesh) {\n this.webrtc.setForwarder(async (peer, signal) => {\n try {\n return await this.mesh!.sendSignal(peer, signal)\n } catch (e: any) {\n this.log(`[webrtc] forward to \"${peer}\" failed: ${e.message}`)\n return false\n }\n })\n }\n // Ring notifications: when a remote peer rings us, emit a \"tap to join\"\n // message through each configured channel so the callee actually sees\n // the incoming call even if their browser isn't open.\n if (wrtcCfg.ringNotify.length > 0) {\n const urlBase = wrtcCfg.callUrlBase || `http://${this.config.node.bind}`\n this.webrtc.setRingHandler(async (signal) => {\n const link = `${urlBase}/call?to=${encodeURIComponent(signal.from)}&callId=${encodeURIComponent(signal.callId)}`\n const text = `📞 ${signal.from} is calling — tap to join: ${link}`\n for (const target of wrtcCfg.ringNotify) {\n try {\n await this.router.sendOutbound({\n channel: target.channel,\n chatId: target.chatId,\n text,\n parseMode: \"plain\",\n ...(target.accountId ? { accountId: target.accountId } : {}),\n })\n } catch (e: any) {\n this.log(`[webrtc] ring notify via ${target.channel}:${target.chatId} failed: ${e.message}`)\n }\n }\n })\n }\n // AI participant (\"bot\") — server-side WebRTC peer that joins on\n // ?bot=<id>, transcribes remote audio, posts chunks to a channel.\n if (wrtcCfg.bot.enabled) {\n const botCfg = wrtcCfg.bot\n const iceServers: RTCIceServer[] = [\n ...wrtcCfg.stunServers.map(urls => ({ urls })),\n ...wrtcCfg.turnServers,\n ]\n this.botManager = new BotManager({\n broker: this.webrtc,\n iceServers,\n whisperBackend: botCfg.whisperBackend,\n whisperModel: botCfg.whisperModel,\n whisperLanguage: botCfg.whisperLanguage,\n mlxBinary: botCfg.mlxBinary,\n maxCallMinutes: botCfg.maxCallMinutes,\n log: this.log,\n onTranscript: async ({ invite, text, durationMs }) => {\n const dest = botCfg.transcriptChannel\n if (!dest) return\n const stamp = `[${new Date().toLocaleTimeString()} • ${(durationMs / 1000).toFixed(1)}s • ${invite.target}]`\n try {\n await this.router.sendOutbound({\n channel: dest.channel,\n chatId: dest.chatId,\n text: `${stamp}\\n${text}`,\n parseMode: \"plain\",\n ...(dest.accountId ? { accountId: dest.accountId } : {}),\n })\n } catch (e: any) {\n this.log(`[bot-manager] transcript send via ${dest.channel}:${dest.chatId} failed: ${e.message}`)\n }\n },\n })\n this.log(` WebRTC bot: enabled (whisper=${botCfg.whisperBackend}, default-agent=${botCfg.defaultAgentId || \"(none)\"}, transcript=${botCfg.transcriptChannel ? `${botCfg.transcriptChannel.channel}:${botCfg.transcriptChannel.chatId}` : \"(none)\"})`)\n }\n this.log(` WebRTC signaling: enabled (stun=${wrtcCfg.stunServers.length}, turn=${wrtcCfg.turnServers.length}, allowedCallers=${wrtcCfg.allowedCallers.length || \"all\"}, ringNotify=${wrtcCfg.ringNotify.length}, bot=${wrtcCfg.bot.enabled ? \"on\" : \"off\"})`)\n }\n\n // Workflow engine — register hook subscribers BEFORE startAll so a\n // webhook arriving immediately sees an engine ready to evaluate. Skipped\n // when `workflows.enabled` is false to keep existing installs silent.\n await this.bootWorkflowEngine()\n\n // Wire SessionStore to the channel adapters so cold-create sessions can\n // call adapter.seedHistory() and mirror the live channel before the\n // first turn renders. Installed AFTER all addChannel calls (above) so\n // the resolver sees the full channel set.\n this.registry.getSessionStore().setAdapterResolver((channel) => this.router.getChannel(channel))\n\n await this.router.startAll()\n }\n\n /** Wires the workflow dispatcher + hook subscribers against the running\n * daemon. No-op when `workflows.enabled` is false — the observability\n * page + editor still work (they read/write disk directly via the\n * board-dashboard routes), but no transitions fire. */\n private async bootWorkflowEngine(): Promise<void> {\n const cfg = this.config.workflows\n if (!cfg?.enabled) {\n this.log(\" Workflows: disabled (set workflows.enabled to turn the engine on)\")\n return\n }\n\n const store = new WorkflowStore({ baseDir: resolve(process.cwd(), cfg.dir) })\n const runs = new WorkflowRunStore({ baseDir: resolve(process.cwd(), cfg.dir), nodeId: this.config.node.id })\n\n // channels record: name -> adapter instance. Node handlers narrow to the\n // specific method they need (send / createIssue / logTimeSpent / ...).\n const channels: Record<string, unknown> = {}\n for (const name of this.router.getChannelNames()) {\n const adapter = this.router.getChannel(name)\n if (adapter) channels[name] = adapter\n }\n\n // Agent-execute shim for the `agent` node handler. Awaits AgentRegistry\n // inside the walk loop — the walk itself runs in a background async\n // context detached from the webhook response, so long-running agent\n // calls don't block webhook delivery.\n const agents = {\n execute: async (req: AgentExecuteRequest): Promise<AgentExecuteResponse> => {\n const start = Date.now()\n try {\n // Scope each workflow run to its own session bucket so concurrent\n // runs of the same workflow (or even different workflows hitting\n // the same agent) don't collide in api:default. Without this,\n // every workflow turn for `coder-agent` ends up in\n // `coder-agent:api:default:<day>.json` regardless of which run\n // produced it — bleeding context across runs and breaking the\n // single-conversation guarantee per workflow run.\n const wfChatId = req.workflowRunId ? `workflow:${req.workflowRunId}` : \"workflow:adhoc\"\n const resp = await this.registry.execute({\n agentId: req.agentId,\n message: req.message,\n workflowRunId: req.workflowRunId,\n timeoutMinutes: req.timeoutMinutes,\n context: {\n channel: \"workflow\",\n chatId: wfChatId,\n sender: \"workflow\",\n },\n })\n return {\n content: resp.content ?? \"\",\n error: resp.error,\n taskId: `wf-${req.workflowRunId ?? \"na\"}-${start.toString(36)}`,\n durationMs: Date.now() - start,\n }\n } catch (e: any) {\n return { content: \"\", error: e.message }\n }\n },\n }\n\n // Mesh forwarder: when an event arrives here but the run is home'd on\n // another peer, POST directly to the peer's /workflow/event endpoint.\n // Also handles trigger fan-out: a fresh channel event arriving locally\n // is broadcast to every healthy peer so peer-side workflows with\n // `mesh.allowRemote: true` can match. Each receiver runs its own dispatch\n // with `fromRemote` set, scoping the match to opted-in workflows.\n const forwarder: WorkflowMeshForwarder | undefined = this.mesh ? {\n forwardTransition: async (peerName, payload) => {\n const peer = this.mesh!.directory().find((p) => p.peer === peerName && p.healthy)\n if (!peer) {\n this.log(`[workflows] forward to \"${peerName}\" skipped — peer not found or unhealthy`)\n return\n }\n const url = `${peer.peerUrl}/workflow/event`\n try {\n const r = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ payload }),\n })\n if (!r.ok) this.log(`[workflows] forward ${url} -> ${r.status}`)\n } catch (e: any) {\n this.log(`[workflows] forward ${url} failed: ${e.message}`)\n }\n },\n broadcastTrigger: async (payload) => {\n const peers = this.mesh!.directory().filter((p) => p.healthy)\n if (peers.length === 0) return\n const localPeerName = this.config.node.id\n // Visibility: emit one summary line per broadcast so operators can see\n // a channel event leaving the originating node and trace the round\n // trip (look for a matching `[workflows/mesh] received trigger ...`\n // on the receiving peer).\n this.log(`[workflows/mesh] broadcasting trigger source=\"${payload.trigger.source}\" chat=\"${payload.trigger.chat ?? \"*\"}\" -> ${peers.length} peer(s): ${peers.map((p) => p.peer).join(\", \")}`)\n await Promise.allSettled(peers.map(async (peer) => {\n const url = `${peer.peerUrl}/workflow/event`\n try {\n const r = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n // Receiver discriminates on `kind: \"trigger\"` and dispatches\n // with fromRemote.peer = the originating node id. Peers without\n // an opted-in workflow no-op cleanly.\n body: JSON.stringify({ kind: \"trigger\", fromPeer: localPeerName, payload }),\n signal: AbortSignal.timeout(5000),\n })\n if (!r.ok) this.log(`[workflows/mesh] broadcastTrigger ${peer.peer} -> ${r.status}`)\n } catch (e: any) {\n this.log(`[workflows/mesh] broadcastTrigger ${peer.peer} failed: ${e.message}`)\n }\n }))\n },\n forwardChannelSend: async (payload) => {\n // Find a healthy peer that hosts this channel. Channels are typically\n // unique to a node (whatsapp on clawd-server, telegram on macbook),\n // so we just take the first hit. If multiple peers somehow host the\n // same channel, this picks deterministically by directory order.\n const peers = this.mesh!.directory().filter((p) => p.healthy && p.channels?.includes(payload.channel))\n if (peers.length === 0) {\n throw new Error(`no healthy mesh peer hosts channel \"${payload.channel}\"`)\n }\n const target = peers[0]\n const url = `${target.peerUrl}/channel/send`\n const r = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(payload),\n signal: AbortSignal.timeout(15000),\n })\n if (!r.ok) {\n const txt = await r.text().catch(() => \"\")\n throw new Error(`peer ${target.peer} /channel/send -> ${r.status} ${txt.slice(0, 200)}`)\n }\n const body = await r.json().catch(() => ({})) as { messageId?: string | null }\n return { messageId: body.messageId ?? null }\n },\n } : undefined\n\n // Build the Telegram user-task renderer if the Telegram adapter is\n // active. The renderer posts a notification to each assignee's\n // preferred channel when a userTask node pauses the run.\n const { ActorStore } = await import(\"@/actors/store\")\n const { TaskStore } = await import(\"@/workflows/task-store\")\n const actorStore = new ActorStore()\n const taskStore = new TaskStore(cfg.dir ? { baseDir: resolve(process.cwd(), cfg.dir) } : undefined)\n const inboxBaseUrl = process.env.AGENTX_INBOX_BASE_URL || \"\"\n const taskRenderers: Array<(t: import(\"@/workflows/task-store\").UserTaskRecord) => Promise<void>> = []\n\n const telegramAdapter = channels[\"telegram\"] as {\n sendMessage?: (msg: { chatId: string; text: string; parseMode?: string; accountId?: string }) => Promise<string | undefined | void>\n sendWithInlineButtons?: (args: { chatId: string; text: string; buttons: Array<{ label: string; url: string }>; parseMode?: \"markdown\" | \"html\" | \"plain\"; accountId?: string }) => Promise<string | undefined | void>\n } | undefined\n if (telegramAdapter?.sendMessage) {\n const { createTelegramTaskRenderer } = await import(\"@/forms/renderers/telegram\")\n taskRenderers.push(createTelegramTaskRenderer({\n actors: actorStore,\n tasks: taskStore,\n adapter: {\n sendMessage: telegramAdapter.sendMessage.bind(telegramAdapter),\n sendWithInlineButtons: telegramAdapter.sendWithInlineButtons?.bind(telegramAdapter),\n },\n inboxBaseUrl,\n log: (m) => this.log(m),\n }))\n }\n\n const whatsappAdapter = channels[\"whatsapp\"] as {\n send?: (msg: { channel: string; chatId: string; text: string; parseMode?: \"markdown\" | \"html\" | \"plain\" }) => Promise<string | void>\n } | undefined\n if (whatsappAdapter?.send) {\n const { createWhatsappTaskRenderer } = await import(\"@/forms/renderers/whatsapp\")\n taskRenderers.push(createWhatsappTaskRenderer({\n actors: actorStore,\n tasks: taskStore,\n adapter: { send: whatsappAdapter.send.bind(whatsappAdapter) },\n inboxBaseUrl,\n log: (m) => this.log(m),\n }))\n }\n\n const slackAdapter = channels[\"slack\"] as {\n send?: (msg: { channel: string; chatId: string; text: string; parseMode?: \"markdown\" | \"html\" | \"plain\" }) => Promise<string | void>\n } | undefined\n if (slackAdapter?.send) {\n const { createSlackTaskRenderer } = await import(\"@/forms/renderers/slack\")\n taskRenderers.push(createSlackTaskRenderer({\n actors: actorStore,\n tasks: taskStore,\n adapter: { send: slackAdapter.send.bind(slackAdapter) },\n inboxBaseUrl,\n log: (m) => this.log(m),\n }))\n }\n\n // Compose into a single callback that fans out to every registered\n // per-channel renderer. Each renderer short-circuits when the\n // assignee has no handle on its channel, so delivery lands on\n // whichever channel the actor has configured without double-posting.\n const renderUserTask: ((task: import(\"@/workflows/task-store\").UserTaskRecord) => Promise<void>) | undefined =\n taskRenderers.length\n ? async (task) => { for (const r of taskRenderers) { try { await r(task) } catch { /* already logged per-renderer */ } } }\n : undefined\n\n const { TimerService } = await import(\"@/workflows/timers\")\n const timerService = new TimerService({\n baseDir: cfg.dir ? resolve(process.cwd(), cfg.dir) : undefined,\n log: (m) => this.log(m),\n })\n\n const dispatcher = new WorkflowDispatcher({\n store, runs,\n nodeId: this.config.node.id,\n channels,\n agents,\n forwarder,\n actors: actorStore,\n tasks: taskStore,\n timers: timerService,\n events: this.events,\n renderUserTask,\n log: (m) => this.log(m),\n })\n\n // Start the tick loop now that the dispatcher has registered its\n // resume-on-fire callback. Timers persisted from a prior run will be\n // picked up on the first tick.\n timerService.start()\n\n // Subscribe the built-in hook handlers.\n const handlers = createWorkflowHookHandlers(dispatcher)\n for (const [event, handler] of Object.entries(handlers)) {\n if (!handler) continue\n this.hooks.registerHandler(event as any, `workflows:${event}`, handler, 50)\n }\n\n // Stash for the mesh receiver endpoint and for manual-run RPC — see the\n // HTTP handler in startHttpApi where /workflow/transition arrives.\n this.workflowDispatcher = dispatcher\n this.workflowStore = store\n this.workflowRuns = runs\n // Phase 3: webhook handler can now dispatch workflows per event-type\n // (webhooks[].triggers map). When `triggers` is unset, behavior is\n // unchanged from prior versions.\n this.webhooks.setWorkflowDispatcher(dispatcher)\n\n // Phase 3: wire trigger.cron timers + trigger.hook subscribers for\n // workflows that declare them. Channel-triggered workflows (gitlab-issue,\n // whatsapp-message, ...) are already wired via the hooks registered\n // above.\n const { cronTimers, hookSubscribers } = startWorkflowTriggers({\n store, dispatcher, hooks: this.hooks, log: (m) => this.log(m),\n })\n\n const count = store.list().length\n this.log(` Workflows: enabled (${count} definition${count === 1 ? \"\" : \"s\"} loaded from ${cfg.dir}, editor=${cfg.editor}, cron=${cronTimers}, hook=${hookSubscribers})`)\n\n // Static dispatch-graph analysis. v0 only logs — auto-quarantine + admin\n // surface land in follow-up turns. Surfacing the conflicts at boot already\n // tells operators which workflows are racing against existing dispatch\n // paths (e.g. gitlab agentMappings) so they can flip state to `disabled`\n // by hand until the auto-fix layer ships.\n try {\n const { detectConflicts } = await import(\"@/workflows/conflict-detector\")\n const conflicts = detectConflicts(store.list(), this.config)\n if (conflicts.length > 0) {\n this.log(` Workflows: ${conflicts.length} dispatch conflict(s) detected`)\n for (const c of conflicts) {\n this.log(` [${c.severity}] ${c.workflowId}: ${c.summary}`)\n this.log(` → ${c.suggestion}`)\n }\n }\n } catch (e: any) {\n this.log(` Workflows: conflict-detector failed: ${e.message}`)\n }\n }\n\n private async startHttpApi(): Promise<void> {\n const [host, portStr] = this.config.node.bind.split(\":\")\n const port = parseInt(portStr || \"18800\", 10)\n\n this.httpServer = createServer(async (req, res) => {\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\")\n res.setHeader(\"Access-Control-Allow-Methods\", \"GET, POST, OPTIONS\")\n res.setHeader(\"Access-Control-Allow-Headers\", \"Content-Type, Authorization\")\n\n if (req.method === \"OPTIONS\") {\n res.writeHead(204)\n res.end()\n return\n }\n\n await this.handleHttp(req, res)\n })\n\n this.httpServer.on(\"error\", (err: any) => {\n if (err.code === \"EADDRINUSE\") {\n this.log(` ERROR: Port ${port} is already in use. Retrying in 5s...`)\n setTimeout(() => {\n this.httpServer?.close()\n this.httpServer?.listen(port, host || \"0.0.0.0\")\n }, 5000)\n } else {\n this.log(` HTTP error: ${err.message}`)\n }\n })\n\n this.httpServer.listen(port, host || \"0.0.0.0\", () => {\n this.log(` HTTP API: http://${host || \"0.0.0.0\"}:${port}`)\n })\n }\n\n /**\n * Broadcast an SSE event to all connected clients.\n */\n private broadcastSSE(event: string, data: string): void {\n if (this.sseClients.size === 0) return\n const payload = `event: ${event}\\ndata: ${JSON.stringify({ time: new Date().toISOString(), message: data })}\\n\\n`\n for (const client of this.sseClients) {\n try { client.write(payload) } catch { this.sseClients.delete(client) }\n }\n }\n\n /**\n * Handle SSE connection for live event streaming.\n * GET /events — streams daemon logs in real-time.\n */\n private handleSSE(req: IncomingMessage, res: ServerResponse): void {\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n \"Connection\": \"keep-alive\",\n \"Access-Control-Allow-Origin\": \"*\",\n })\n\n // Parse filter query-string. ?type=run,task filters which event\n // kinds are forwarded; ?workflow=, ?actor=, ?run=, ?channel=\n // narrow further. Empty query = unfiltered (all kinds forward).\n const url = new URL(req.url || \"/\", `http://${req.headers.host || \"_\"}`)\n const kinds = parseKindsParam(url.searchParams.get(\"type\"))\n const filter = {\n kinds,\n workflowId: url.searchParams.get(\"workflow\") || undefined,\n actor: url.searchParams.get(\"actor\") || undefined,\n runId: url.searchParams.get(\"run\") || undefined,\n channel: url.searchParams.get(\"channel\") || undefined,\n }\n\n // Initial \"status\" snapshot — matches legacy /events shape so existing\n // consumers (dashboard status widget) keep working even after this\n // upgrade.\n const agents = this.registry.list()\n const active = agents.filter(a => a.active > 0)\n res.write(`event: status\\ndata: ${JSON.stringify({\n node: this.config.node.name,\n agents: agents.length,\n active: active.map(a => ({ id: a.id, name: a.name, tasks: a.active })),\n mesh: this.mesh?.directory().map(p => ({ peer: p.peer, healthy: p.healthy })) || [],\n })}\\n\\n`)\n\n this.sseClients.add(res)\n\n // Bridge EventBus → this SSE client. Events that don't match the\n // filter are dropped before serialisation.\n const unsubscribe = this.events.subscribe(filter, (e) => {\n try { res.write(`event: ${e.kind}\\ndata: ${JSON.stringify(e)}\\n\\n`) }\n catch { /* client disconnected — unsubscribe below fires on close */ }\n })\n\n req.on(\"close\", () => {\n unsubscribe()\n this.sseClients.delete(res)\n })\n }\n\n /**\n * Stream a single running task's live output as SSE.\n * Sends a `start` event with the existing buffer (so a late opener catches up),\n * then `chunk` events for every new delta, and `end` when the task finishes.\n */\n private handleTaskStream(req: IncomingMessage, res: ServerResponse, _agentId: string, taskId: string): void {\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n \"Connection\": \"keep-alive\",\n \"Access-Control-Allow-Origin\": \"*\",\n \"X-Accel-Buffering\": \"no\",\n })\n let closed = false\n const send = (ev: string, data: unknown) => {\n if (closed) return\n try { res.write(`event: ${ev}\\ndata: ${JSON.stringify(data)}\\n\\n`) } catch { closed = true }\n }\n const sub = this.registry.subscribeToTaskOutput(taskId, (chunk) => send(\"chunk\", { text: chunk }))\n if (!sub) {\n send(\"error\", { message: \"task not found or already evicted\" })\n res.end()\n return\n }\n send(\"start\", { taskId, initial: sub.initial, done: sub.done })\n if (sub.done) {\n send(\"end\", { reason: \"already finished\" })\n res.end()\n return\n }\n // Heartbeat so proxies don't kill idle SSE connections.\n const heartbeat = setInterval(() => { if (!closed) try { res.write(\": ping\\n\\n\") } catch { /* */ } }, 15000)\n req.on(\"close\", () => {\n closed = true\n clearInterval(heartbeat)\n sub.unsubscribe()\n })\n }\n\n /**\n * SSE stream for WebRTC signaling. Browser connects here identifying itself\n * with (callId, as). The broker fans out any signal addressed `to=<as>` on\n * this `callId` to the stream.\n */\n private handleWebRtcSSE(req: IncomingMessage, res: ServerResponse, url: URL): void {\n if (!this.webrtc) {\n this.json(res, 404, { error: \"WebRTC signaling not enabled\" })\n return\n }\n const callId = url.searchParams.get(\"callId\")\n const as = url.searchParams.get(\"as\")\n if (!callId || !as) {\n this.json(res, 400, { error: \"Missing ?callId= and ?as=\" })\n return\n }\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n \"Connection\": \"keep-alive\",\n \"Access-Control-Allow-Origin\": \"*\",\n \"X-Accel-Buffering\": \"no\",\n })\n res.write(`event: ready\\ndata: ${JSON.stringify({ callId, as })}\\n\\n`)\n const unsubscribe = this.webrtc.subscribe(callId, as, res)\n // Proxies kill idle SSE; ping every 15s.\n const heartbeat = setInterval(() => {\n try { res.write(\": ping\\n\\n\") } catch { /* best effort */ }\n }, 15000)\n req.on(\"close\", () => {\n clearInterval(heartbeat)\n unsubscribe()\n })\n }\n\n /**\n * Serve the minimal browser call page. Static HTML; no framework.\n * The page does getUserMedia, RTCPeerConnection, and POSTs/listens signals.\n */\n private serveCallPage(res: ServerResponse): void {\n res.writeHead(200, { \"Content-Type\": \"text/html; charset=utf-8\" })\n res.end(CALL_PAGE_HTML)\n }\n\n private async handleHttp(req: IncomingMessage, res: ServerResponse): Promise<void> {\n const url = new URL(req.url || \"/\", `http://${req.headers.host || \"localhost\"}`)\n const path = url.pathname\n\n try {\n // SSE live event stream\n if (req.method === \"GET\" && path === \"/events\") {\n this.handleSSE(req, res)\n return\n }\n\n // WebRTC signaling SSE stream (browser subscribes here to receive\n // offers/answers/ICE forwarded by the daemon).\n if (req.method === \"GET\" && path === \"/webrtc/events\") {\n this.handleWebRtcSSE(req, res, url)\n return\n }\n\n // --- Workflow RPC endpoints ---\n //\n // /workflow/transition — mesh receiver. Peer node B forwarded a\n // triggering event here because this node is the run's home. We\n // just re-enter the local dispatcher with the payload.\n //\n // /workflows/:id/run — manual trigger. The CLI's `agentx workflow\n // run <id> --input ...` POSTs here. Only workflows whose trigger\n // source is \"manual\" are allowed.\n // Workflow mesh + manual-run endpoints. /workflow/event receives\n // forwarded dispatches from peers when the run is home'd here.\n // /workflows/:id/run fires a manual-triggered workflow.\n if (req.method === \"POST\" && (path === \"/workflow/event\" || path === \"/workflow/transition\")) {\n await this.handleWorkflowEvent(req, res)\n return\n }\n if (req.method === \"POST\" && path === \"/channel/send\") {\n await this.handleChannelSend(req, res)\n return\n }\n // Known-chats discovery. Local if this node hosts the channel; else\n // forward to a peer that does. Lets the workflow editor populate a\n // chatId picker without authors memorizing platform ids. The `local=1`\n // query param suppresses mesh fan-out so peer-to-peer recursion\n // (both nodes advertise the same channel) can't loop forever.\n const channelChatsMatch = req.method === \"GET\" && path.match(/^\\/channels\\/([^/]+)\\/chats$/)\n if (channelChatsMatch) {\n const localOnly = url.searchParams.get(\"local\") === \"1\"\n await this.handleChannelChats(res, decodeURIComponent(channelChatsMatch[1]), { localOnly })\n return\n }\n const manualRun = req.method === \"POST\" && path.match(/^\\/workflows\\/([^/]+)\\/run$/)\n if (manualRun) {\n await this.handleWorkflowManualRun(req, res, decodeURIComponent(manualRun[1]))\n return\n }\n\n // Agent-memory API — lets running Claude Code sessions save their\n // own experiential memory from inside a Bash tool call.\n // GET /api/memory?agent=<id> → list records + MEMORY.md\n // GET /api/memory/<id>?agent=<id> → one record\n // POST /api/memory body: {agentId, type, name, description, body, append?}\n // DELETE /api/memory/<name>?agent=<id> → remove\n if (path.startsWith(\"/api/memory\")) {\n if (await this.handleMemoryApi(req, res, path, url)) return\n }\n\n // BPM user-task API: list + submit. Lives on the main daemon\n // because the dispatcher is the one that drives run resumes.\n if (path.startsWith(\"/api/workflows/tasks\") && this.workflowDispatcher) {\n if (await this.handleTaskApi(req, res, path)) return\n }\n // GET /api/workflows/kpis — actor-level + total task stats.\n if (req.method === \"GET\" && path === \"/api/workflows/kpis\" && this.workflowDispatcher) {\n const { computeKpis } = await import(\"@/workflows/task-store\")\n this.json(res, 200, computeKpis(this.workflowDispatcher.tasks))\n return\n }\n // GET /api/workflows/runs[?limit=&workflowId=] + /runs/:id\n // Runs live on the node that dispatches them (home-node). The\n // board-dashboard on another host proxies to this endpoint so\n // \"Recent runs\" on /workflows reflects what's actually happening.\n if (req.method === \"GET\" && this.workflowRuns && this.workflowStore) {\n if (path === \"/api/workflows\") {\n this.json(res, 200, { workflows: this.workflowStore.list() })\n return\n }\n if (path === \"/api/workflows/runs\") {\n const limit = Math.max(1, Math.min(500, Number(url.searchParams.get(\"limit\") || 50)))\n const workflowId = url.searchParams.get(\"workflowId\") || undefined\n this.json(res, 200, { runs: this.workflowRuns.list({ workflowId, limit }) })\n return\n }\n const runMatch = path.match(/^\\/api\\/workflows\\/runs\\/([^\\/]+)$/)\n if (runMatch) {\n const run = this.workflowRuns.get(decodeURIComponent(runMatch[1]))\n if (!run) { this.json(res, 404, { error: \"run not found\" }); return }\n this.json(res, 200, { run })\n return\n }\n }\n // POST /api/workflows/editor/chat — author chat dispatched to an agent.\n //\n // Body: { messages: [{role, content}], currentWorkflow?, agentId?, context? }\n // Returns: { reply, workflow? | null, error? }\n //\n // The endpoint packs the full V2 schema + environment (available\n // agents, actors, roles, channels, existing workflows) into the\n // agent's prompt so a generic agent with no special training can\n // still produce a valid workflow JSON.\n if (req.method === \"POST\" && path === \"/api/workflows/editor/chat\" && this.workflowDispatcher) {\n let body: any\n try { body = await readJsonBody(req) } catch (e: any) {\n this.json(res, 400, { error: \"invalid JSON body\", message: e.message }); return\n }\n const messages = Array.isArray(body?.messages) ? body.messages as Array<{ role: string; content: string }> : []\n if (!messages.length) { this.json(res, 400, { error: \"messages array required\" }); return }\n const normMessages = messages\n .filter((m) => m && (m.role === \"user\" || m.role === \"assistant\") && typeof m.content === \"string\")\n .map((m) => ({ role: m.role as \"user\" | \"assistant\", content: m.content }))\n if (!normMessages.length) { this.json(res, 400, { error: \"messages must contain at least one {role: 'user'|'assistant', content}\" }); return }\n\n const agentId = typeof body?.agentId === \"string\" && body.agentId\n ? body.agentId\n : (process.env.AGENTX_WORKFLOW_AUTHOR_AGENT || this.registry.list()[0]?.id)\n if (!agentId) { this.json(res, 503, { error: \"no authoring agent available — register an agent or set AGENTX_WORKFLOW_AUTHOR_AGENT\" }); return }\n\n const { buildWorkflowAuthorPrompt, extractWorkflowJson } = await import(\"@/workflows/editor-chat\")\n const availableChannels = Object.keys(this.workflowDispatcher[\"channels\"] as Record<string, unknown>).sort()\n const availableAgents = this.registry.list().map((a) => ({ id: a.id, description: a.name }))\n const prompt = buildWorkflowAuthorPrompt({\n messages: normMessages,\n store: this.workflowStore!,\n actors: this.workflowDispatcher.actors,\n availableAgents,\n availableChannels,\n currentWorkflow: body?.currentWorkflow,\n })\n try {\n const resp = await this.registry.execute({\n agentId,\n message: prompt,\n context: { channel: \"workflow-editor\", chatId: \"editor\", sender: \"editor\" } as any,\n })\n if (resp.error) { this.json(res, 502, { error: resp.error, agentId }); return }\n const reply = resp.content ?? \"\"\n const workflow = extractWorkflowJson(reply)\n this.json(res, 200, { reply, workflow, agentId })\n } catch (e: any) {\n this.json(res, 500, { error: \"agent execute failed\", message: e.message })\n }\n return\n }\n\n // POST /api/workflows/signal/:name — manual signal emission.\n if (req.method === \"POST\" && path.startsWith(\"/api/workflows/signal/\") && this.workflowDispatcher) {\n const name = decodeURIComponent(path.replace(/^\\/api\\/workflows\\/signal\\//, \"\"))\n if (!name) { this.json(res, 400, { error: \"signal name required\" }); return }\n let body: any\n try { body = await readJsonBody(req) } catch (e: any) {\n this.json(res, 400, { error: \"invalid JSON body\", message: e.message }); return\n }\n const emission = this.workflowDispatcher.emitSignal({\n name,\n scope: body?.scope,\n workflowId: body?.workflowId,\n payload: body?.payload,\n })\n this.json(res, 200, { ok: true, emission })\n return\n }\n // /inbox — per-actor task list page. Static HTML, fetches the task\n // API above via same-origin.\n if (req.method === \"GET\" && path === \"/inbox\") {\n const { renderInboxPage } = await import(\"./ui/pages/inbox\")\n const qs = url.searchParams\n const html = renderInboxPage({ actor: qs.get(\"actor\") || undefined })\n res.writeHead(200, { \"Content-Type\": \"text/html; charset=utf-8\" })\n res.end(html)\n return\n }\n // /processes — composition-tree + SLA view of runs.\n if (req.method === \"GET\" && path === \"/processes\") {\n const { renderProcessesPage } = await import(\"./ui/pages/processes\")\n res.writeHead(200, { \"Content-Type\": \"text/html; charset=utf-8\" })\n res.end(renderProcessesPage({}))\n return\n }\n // GET /t/:taskId/:action — one-click task submission from chat\n // clients (Telegram inline-keyboard URL buttons). Submits with\n // empty values → validator fills defaults. Only works for forms\n // whose required fields have defaults (or none at all).\n const oneClick = req.method === \"GET\" && path.match(/^\\/t\\/([^\\/]+)\\/(primary|secondary)$/)\n if (oneClick && this.workflowDispatcher) {\n const taskId = decodeURIComponent(oneClick[1])\n const action = oneClick[2] as \"primary\" | \"secondary\"\n const actor = url.searchParams.get(\"actor\") || \"anonymous\"\n const result = await this.workflowDispatcher.submitTask(taskId, { action, values: {} }, actor)\n res.writeHead(result.ok ? 200 : 400, { \"Content-Type\": \"text/html; charset=utf-8\" })\n if (result.ok) {\n res.end(`<!doctype html><meta charset=utf-8><title>Submitted</title>\n<body style=\"font-family:system-ui;max-width:520px;margin:48px auto;padding:0 16px;text-align:center\">\n<h1 style=\"color:#27ae60\">✓ Submitted</h1>\n<p>Your <strong>${action === \"primary\" ? \"approval\" : \"rejection\"}</strong> has been recorded. You can close this tab.</p>\n<p style=\"color:#888;font-size:13px\">Run <code>${result.runId}</code></p>\n</body>`)\n } else {\n res.end(`<!doctype html><meta charset=utf-8><title>Submit failed</title>\n<body style=\"font-family:system-ui;max-width:520px;margin:48px auto;padding:0 16px;text-align:center\">\n<h1 style=\"color:#c0392b\">✗ ${result.error}</h1>\n${Array.isArray(result.fieldErrors) && result.fieldErrors.length ? `<p>This task has required fields. Please open the <a href=\"/inbox?actor=${encodeURIComponent(actor)}\">inbox</a> instead.</p>` : \"\"}\n</body>`)\n }\n return\n }\n\n // Static browser call page.\n if (req.method === \"GET\" && path === \"/call\") {\n this.serveCallPage(res)\n return\n }\n\n // Dynamic routes (before static switch)\n if (req.method === \"POST\" && path.startsWith(\"/webhook/\")) {\n // GitHub channel adapter: intercept webhooks with X-GitHub-Event header\n // when the GitHub channel is enabled — routes internally by repo.\n if (this.github && req.headers[\"x-github-event\"]) {\n const body = await new Promise<string>((resolve) => {\n let data = \"\"\n req.on(\"data\", (chunk: Buffer) => (data += chunk.toString()))\n req.on(\"end\", () => resolve(data))\n req.on(\"error\", () => resolve(\"\"))\n })\n let parsed: Record<string, unknown>\n try {\n // GitHub may send as application/json or application/x-www-form-urlencoded\n const contentType = req.headers[\"content-type\"] || \"\"\n if (contentType.includes(\"form-urlencoded\") && body.startsWith(\"payload=\")) {\n parsed = JSON.parse(decodeURIComponent(body.slice(8)))\n } else {\n parsed = body ? JSON.parse(body) : {}\n }\n } catch { parsed = {} }\n this.log(`[github] webhook body keys: ${Object.keys(parsed).slice(0, 5).join(\", \")} | repo: ${(parsed.repository as any)?.full_name || \"MISSING\"}`)\n // Respond immediately (GitHub has a 10s timeout)\n res.writeHead(202, { \"Content-Type\": \"application/json\" })\n res.end(JSON.stringify({ ok: true, channel: \"github\", status: \"accepted\" }))\n // Process asynchronously\n this.github.handleWebhook(\n req.headers as Record<string, string | string[] | undefined>,\n parsed,\n body, // raw body for signature verification\n ).catch(e => this.log(`[github] webhook handler error: ${(e as Error).message}`))\n return\n }\n await this.webhooks.handle(req, res, path)\n return\n }\n\n // SSE stream for a single running task — drives the dashboard modal.\n // GET /agents/:agentId/tasks/:taskId/stream\n const taskStreamMatch = req.method === \"GET\" && path.match(/^\\/agents\\/([^/]+)\\/tasks\\/([^/]+)\\/stream$/)\n if (taskStreamMatch) {\n this.handleTaskStream(req, res, taskStreamMatch[1], taskStreamMatch[2])\n return\n }\n\n // Persisted task history (for the dashboard \"Recent activities\" panel).\n // GET /agents/:agentId/tasks?limit=N → list of summaries (newest first)\n // GET /agents/:agentId/tasks/:taskId → one full record (transcript + response)\n const taskHistoryListMatch = req.method === \"GET\" && path.match(/^\\/agents\\/([^/]+)\\/tasks$/)\n if (taskHistoryListMatch) {\n const limit = Math.max(1, Math.min(500, parseInt(url.searchParams.get(\"limit\") || \"50\", 10) || 50))\n this.json(res, 200, this.registry.listTaskHistory(taskHistoryListMatch[1], limit))\n return\n }\n const taskRecordMatch = req.method === \"GET\" && path.match(/^\\/agents\\/([^/]+)\\/tasks\\/([^/]+)$/)\n if (taskRecordMatch) {\n const rec = this.registry.getTaskRecord(taskRecordMatch[1], taskRecordMatch[2])\n if (!rec) { this.json(res, 404, { error: \"not found\" }); return }\n this.json(res, 200, rec)\n return\n }\n\n // OpenAI-compatible endpoint for ElevenLabs, Cursor, etc.\n // POST /v1/chat/completions or /llm/:agentId/v1/chat/completions\n if (req.method === \"POST\" && (path === \"/v1/chat/completions\" || path.match(/^\\/llm\\/[^/]+\\/v1\\/chat\\/completions$/))) {\n await this.handleOpenAICompat(req, res, path)\n return\n }\n\n // Business layer endpoints (/business/status, /business/work, ...)\n if (this.business && path.startsWith(\"/business\")) {\n const handled = await this.business.handleHttp(`${req.method} ${path}`, req, res)\n if (handled) return\n }\n\n switch (`${req.method} ${path}`) {\n case \"GET /health\":\n this.json(res, 200, {\n status: \"ok\",\n node: this.config.node,\n uptime: process.uptime(),\n agents: this.registry.list(),\n crons: this.cron.list().map((j) => ({ id: j.id, enabled: j.enabled, nextRun: j.nextRun })),\n mesh: this.mesh?.directory() || [],\n usage: this.resolveTodayUsage(),\n })\n break\n\n case \"GET /usage\":\n this.json(res, 200, this.registry.getUsage(7))\n break\n\n case \"GET /agents\":\n this.json(res, 200, this.registry.list())\n break\n\n case \"GET /whatsapp/state\": {\n const { getWhatsAppState } = await import(\"./whatsapp-state\")\n this.json(res, 200, getWhatsAppState())\n break\n }\n\n case \"GET /whatsapp/qr.svg\": {\n const { getWhatsAppState } = await import(\"./whatsapp-state\")\n const s = getWhatsAppState()\n if (!s.qr) { res.writeHead(204, { \"Cache-Control\": \"no-store\" }); res.end(); break }\n try {\n // @ts-ignore — qrcode has no shipped types\n const mod = await import(\"qrcode\")\n const QRCode: any = (mod as any).default || mod\n const svg: string = await QRCode.toString(s.qr, { type: \"svg\", errorCorrectionLevel: \"L\", margin: 1, color: { dark: \"#000\", light: \"#fff\" } })\n res.writeHead(200, { \"Content-Type\": \"image/svg+xml; charset=utf-8\", \"Cache-Control\": \"no-store\" })\n res.end(svg)\n } catch (e: any) {\n res.writeHead(503, { \"Content-Type\": \"text/plain; charset=utf-8\" })\n res.end(\"qrcode package not installed or failed: \" + (e?.message || \"unknown\"))\n }\n break\n }\n\n case \"GET /whatsapp/chats\": {\n const wa = this.router.getChannel(\"whatsapp\") as WhatsAppAdapter | undefined\n if (!wa) { this.json(res, 503, { error: \"WhatsApp channel not enabled\" }); break }\n this.json(res, 200, { chats: wa.listChats() })\n break\n }\n\n case \"GET /whatsapp/contacts\": {\n const wa = this.router.getChannel(\"whatsapp\") as WhatsAppAdapter | undefined\n if (!wa) { this.json(res, 503, { error: \"WhatsApp channel not enabled\" }); break }\n this.json(res, 200, { contacts: wa.listContacts() })\n break\n }\n\n case \"POST /whatsapp/ingest\": {\n const wa = this.router.getChannel(\"whatsapp\") as WhatsAppAdapter | undefined\n if (!wa) { this.json(res, 503, { error: \"WhatsApp channel not enabled\" }); break }\n const body = await readBody(req)\n const cfg = this.config.channels.whatsapp.ingest\n if (!cfg.enabled && !body.force) {\n this.json(res, 400, { error: \"channels.whatsapp.ingest.enabled is false. Set it to true in agentx.json or pass {\\\"force\\\": true}.\" })\n break\n }\n const agentId = (typeof body.agent === \"string\" && body.agent)\n || this.config.channels.whatsapp.defaultAgent\n || this.config.node.defaultAgent\n if (!agentId) {\n this.json(res, 400, { error: \"No agent to own the entries. Pass {agent: '...'} or set channels.whatsapp.defaultAgent.\" })\n break\n }\n const dryRun = !!body.dryRun\n const { runSweep } = await import(\"@/wiki/ingest-whatsapp\")\n const store = this.registry.getWikiHub().getAgentWiki(agentId)\n // Support per-chat CLI commands (ingest-contact / ingest-chat):\n // narrow the allowlist to the single JID and optionally override\n // the ingest mode for this pass only. Leaves the persistent\n // allowlist in agentx.json untouched.\n let effectiveCfg = cfg\n if (typeof body.onlyJid === \"string\") {\n const jid = body.onlyJid as string\n const isGroup = jid.endsWith(\"@g.us\")\n const forced: \"metadata-only\" | \"messages\" | undefined =\n body.forceMode === \"metadata-only\" || body.forceMode === \"messages\"\n ? body.forceMode\n : undefined\n effectiveCfg = {\n ...cfg,\n // Ignore the master enabled flag when a specific JID was named\n // via the CLI — operator has explicitly pointed at this one.\n enabled: true,\n mode: forced ?? cfg.mode,\n // Narrow scope to exactly this JID regardless of existing lists.\n allowContacts: isGroup || body.onlyKind === \"group\" ? [] : [jid],\n allowGroups: isGroup || body.onlyKind === \"group\" ? [jid] : [],\n denyContacts: [],\n denyGroups: [],\n }\n }\n const report = await runSweep({\n source: wa,\n store,\n config: effectiveCfg,\n agentId,\n dryRun,\n })\n this.json(res, 200, report)\n break\n }\n\n case \"GET /crons\":\n this.json(res, 200, this.cron.list())\n break\n\n case \"GET /crons/health\":\n this.json(res, 200, this.cron.health())\n break\n\n case \"GET /mesh\":\n this.json(res, 200, this.mesh?.directory() || [])\n break\n\n case \"GET /debug\": {\n const { getDebugState, getDebugLogs } = await import(\"@/observability/debug\")\n this.json(res, 200, { ...getDebugState(), recentLogs: getDebugLogs(50) })\n break\n }\n\n case \"POST /debug/on\": {\n const { setDebug } = await import(\"@/observability/debug\")\n const cats = url.searchParams.get(\"categories\")?.split(\",\") || [\"all\"]\n setDebug(true, cats as any)\n this.log(`Debug enabled: ${cats.join(\", \")}`)\n this.json(res, 200, { enabled: true, categories: cats })\n break\n }\n\n case \"POST /debug/off\": {\n const { setDebug } = await import(\"@/observability/debug\")\n setDebug(false)\n this.log(\"Debug disabled\")\n this.json(res, 200, { enabled: false })\n break\n }\n\n case \"POST /gitlab/react\": {\n // Forwarded from a mesh peer: perform a 👀 reaction using local agent token\n const body = await readBody(req)\n const { project, noteableType, noteableIid, noteId, agentId } = body as any\n const mappings = this.config.channels.gitlab?.agentMappings || []\n const mapping = mappings.find((m: any) => m.agentId === agentId)\n const token = mapping?.token || this.config.channels.gitlab?.token\n const host = this.config.channels.gitlab?.host\n this.log(`[gitlab/react] agent=\"${agentId}\" project=\"${project}\" mappingFound=${!!mapping} mappingToken=${!!mapping?.token} globalToken=${!!this.config.channels.gitlab?.token}(len=${(this.config.channels.gitlab?.token || \"\").length}) host=${host || \"MISSING\"}`)\n if (!token || !host) {\n this.json(res, 404, {\n error: \"no gitlab token for agent\",\n debug: { agentId, mappingFound: !!mapping, hasMappingToken: !!mapping?.token, hasGlobalToken: !!this.config.channels.gitlab?.token, hasHost: !!host },\n })\n break\n }\n const encoded = encodeURIComponent(project)\n const ep = noteableType === \"issue\"\n ? `${host}/api/v4/projects/${encoded}/issues/${noteableIid}/notes/${noteId}/award_emoji`\n : `${host}/api/v4/projects/${encoded}/merge_requests/${noteableIid}/notes/${noteId}/award_emoji`\n try {\n const glRes = await fetch(ep, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", \"PRIVATE-TOKEN\": token },\n body: JSON.stringify({ name: \"eyes\" }),\n })\n const respBody = await glRes.text().catch(() => \"\")\n this.log(`[gitlab/react] -> POST ${ep} : ${glRes.status} ${respBody.slice(0, 120)}`)\n this.json(res, 200, { ok: glRes.ok, status: glRes.status, gitlabResponse: respBody.slice(0, 200) })\n } catch (e: any) {\n this.log(`[gitlab/react] FETCH ERROR: ${e.message}`)\n this.json(res, 500, { error: e.message })\n }\n break\n }\n\n case \"POST /gitlab/send-note\": {\n // Forwarded from a mesh peer: post a note using local agent token\n // so the comment shows up under the agent's real GitLab user.\n const body = await readBody(req)\n const { project, noteableType, noteableIid, agentId, text } = body as any\n const mappings = this.config.channels.gitlab?.agentMappings || []\n const mapping = mappings.find((m: any) => m.agentId === agentId)\n const token = mapping?.token || this.config.channels.gitlab?.token\n const host = this.config.channels.gitlab?.host\n if (!token || !host) {\n this.json(res, 404, { error: \"no gitlab token for agent\", debug: { agentId, hasToken: !!token, hasHost: !!host } })\n break\n }\n const encoded = encodeURIComponent(project)\n const ep = noteableType === \"issue\"\n ? `${host}/api/v4/projects/${encoded}/issues/${noteableIid}/notes`\n : `${host}/api/v4/projects/${encoded}/merge_requests/${noteableIid}/notes`\n try {\n const glRes = await fetch(ep, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", \"PRIVATE-TOKEN\": token },\n body: JSON.stringify({ body: text }),\n })\n const respBody = await glRes.text().catch(() => \"\")\n let noteId = \"\"\n try { noteId = String((JSON.parse(respBody) as any).id || \"\") } catch { /* ignore */ }\n this.log(`[gitlab/send-note] agent=\"${agentId}\" -> POST ${ep} : ${glRes.status} noteId=${noteId || \"?\"}`)\n this.json(res, 200, { ok: glRes.ok, status: glRes.status, noteId })\n } catch (e: any) {\n this.log(`[gitlab/send-note] FETCH ERROR: ${e.message}`)\n this.json(res, 500, { error: e.message })\n }\n break\n }\n\n case \"POST /gitlab/log-time\": {\n // Forwarded from a mesh peer: log spent time under the agent's own user.\n const body = await readBody(req)\n const { project, noteableType, noteableIid, agentId, durationMs } = body as any\n const mappings = this.config.channels.gitlab?.agentMappings || []\n const mapping = mappings.find((m: any) => m.agentId === agentId)\n const token = mapping?.token || this.config.channels.gitlab?.token\n const host = this.config.channels.gitlab?.host\n if (!token || !host) {\n this.json(res, 404, { error: \"no gitlab token for agent\" })\n break\n }\n const totalSeconds = Math.max(60, Math.round(Number(durationMs) / 1000))\n const hours = Math.floor(totalSeconds / 3600)\n const minutes = Math.ceil((totalSeconds % 3600) / 60)\n const duration = hours > 0 ? `${hours}h${minutes > 0 ? ` ${minutes}m` : \"\"}` : `${minutes}m`\n const encoded = encodeURIComponent(project)\n const seg = noteableType === \"merge_request\" ? \"merge_requests\" : \"issues\"\n const ep = `${host}/api/v4/projects/${encoded}/${seg}/${noteableIid}/add_spent_time`\n try {\n const glRes = await fetch(ep, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", \"PRIVATE-TOKEN\": token },\n body: JSON.stringify({ duration }),\n })\n this.log(`[gitlab/log-time] agent=\"${agentId}\" duration=${duration} -> POST ${ep} : ${glRes.status}`)\n this.json(res, 200, { ok: glRes.ok, status: glRes.status, duration })\n } catch (e: any) {\n this.log(`[gitlab/log-time] FETCH ERROR: ${e.message}`)\n this.json(res, 500, { error: e.message })\n }\n break\n }\n\n case \"POST /github/send-comment\": {\n // Forwarded from a mesh peer: post a comment using the local GitHub token.\n const body = await readBody(req)\n const { repo, issueNumber, agentId, text } = body as any\n // Resolve token: per-agent tokenFile, per-agent token, or global\n const ghConfig = this.config.channels.github\n const mappings = ghConfig?.agentMappings || []\n const mapping = mappings.find((m: any) => m.agentId === agentId)\n let token: string | undefined\n if (mapping?.tokenFile) {\n try { token = readFileSync(mapping.tokenFile, \"utf-8\").trim().split(\"\\n\")[0].trim() } catch { /* */ }\n }\n token = token || mapping?.token\n if (!token && ghConfig?.tokenFile) {\n try { token = readFileSync(ghConfig.tokenFile, \"utf-8\").trim().split(\"\\n\")[0].trim() } catch { /* */ }\n }\n token = token || ghConfig?.token\n if (!token) {\n this.json(res, 404, { error: \"no github token for agent\", debug: { agentId, hasMapping: !!mapping } })\n break\n }\n const ep = `https://api.github.com/repos/${repo}/issues/${issueNumber}/comments`\n try {\n const ghRes = await fetch(ep, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", Authorization: `Bearer ${token}`, \"User-Agent\": \"AgentX\" },\n body: JSON.stringify({ body: text }),\n })\n const respBody = await ghRes.text().catch(() => \"\")\n let commentId = \"\"\n try { commentId = String((JSON.parse(respBody) as any).id || \"\") } catch { /* */ }\n this.log(`[github/send-comment] agent=\"${agentId}\" -> POST ${ep} : ${ghRes.status} commentId=${commentId || \"?\"}`)\n this.json(res, 200, { ok: ghRes.ok, status: ghRes.status, commentId })\n } catch (e: any) {\n this.log(`[github/send-comment] FETCH ERROR: ${e.message}`)\n this.json(res, 500, { error: e.message })\n }\n break\n }\n\n case \"POST /github/react\": {\n // Forwarded from a mesh peer: react with 👀 on a GitHub comment.\n const body = await readBody(req)\n const { repo, commentId, agentId } = body as any\n const ghConfig = this.config.channels.github\n const mappings = ghConfig?.agentMappings || []\n const mapping = mappings.find((m: any) => m.agentId === agentId)\n let token: string | undefined\n if (mapping?.tokenFile) {\n try { token = readFileSync(mapping.tokenFile, \"utf-8\").trim().split(\"\\n\")[0].trim() } catch { /* */ }\n }\n token = token || mapping?.token\n if (!token && ghConfig?.tokenFile) {\n try { token = readFileSync(ghConfig.tokenFile, \"utf-8\").trim().split(\"\\n\")[0].trim() } catch { /* */ }\n }\n token = token || ghConfig?.token\n if (!token) {\n this.json(res, 404, { error: \"no github token for agent\" })\n break\n }\n const ep = `https://api.github.com/repos/${repo}/issues/comments/${commentId}/reactions`\n try {\n const ghRes = await fetch(ep, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", Authorization: `Bearer ${token}`, \"User-Agent\": \"AgentX\" },\n body: JSON.stringify({ content: \"eyes\" }),\n })\n this.log(`[github/react] agent=\"${agentId}\" -> POST ${ep} : ${ghRes.status}`)\n this.json(res, 200, { ok: ghRes.ok, status: ghRes.status })\n } catch (e: any) {\n this.log(`[github/react] FETCH ERROR: ${e.message}`)\n this.json(res, 500, { error: e.message })\n }\n break\n }\n\n case \"POST /reload\": {\n try {\n const result = await this.reload()\n this.json(res, 200, { ok: true, ...result })\n } catch (e: any) {\n this.json(res, 500, { ok: false, error: e?.message || String(e) })\n }\n break\n }\n\n case \"POST /recall\": {\n // Conversation recall — reads stored sessions for a given agent\n // (optionally narrowed to a channel/chatId) and returns turns\n // newest-first with a cursor for pagination. Read-only.\n const body = await readBody(req)\n if (!body.agent) {\n this.json(res, 400, { error: \"Required: agent (id of the agent whose sessions to recall)\" })\n break\n }\n try {\n const result = this.registry.getSessionStore().recallTurns({\n agentId: String(body.agent),\n channel: body.channel ? String(body.channel) : undefined,\n chatId: body.chatId ? String(body.chatId) : undefined,\n before: body.before ? String(body.before) : undefined,\n after: body.after ? String(body.after) : undefined,\n lookbackDays: typeof body.lookbackDays === \"number\" ? body.lookbackDays : undefined,\n limit: typeof body.limit === \"number\" ? body.limit : undefined,\n query: body.query ? String(body.query) : undefined,\n participants: Array.isArray(body.participants)\n ? body.participants.map((p: unknown) => String(p))\n : undefined,\n })\n this.json(res, 200, result)\n } catch (e: any) {\n this.json(res, 500, { error: e?.message || String(e) })\n }\n break\n }\n\n case \"POST /chat/recent\": {\n // Cross-agent view of a chat. Returns the most recent messages\n // across EVERY agent's session for the given (channel, chatId)\n // — sorted oldest-first so the caller reads it like a transcript.\n // Use cases:\n // - Agent introspection (\"what was just said in this chat?\")\n // - Multi-agent groups where one agent needs to see what another\n // replied without going through A2A indirection (today's\n // cx→devops→marketing speculation thread is the failure mode\n // this fixes).\n // Bounded by sinceISO (default: last 24h) and limit (default: 30,\n // max 200) so a long-running group can't blow up an agent's prompt.\n const body = await readBody(req)\n if (!body.channel || !body.chatId) {\n this.json(res, 400, { error: \"Required: channel, chatId\" })\n break\n }\n try {\n const messages = this.registry.getSessionStore().recentByChatId({\n channel: String(body.channel),\n chatId: String(body.chatId),\n sinceISO: typeof body.sinceISO === \"string\" ? body.sinceISO : undefined,\n limit: typeof body.limit === \"number\" ? body.limit : undefined,\n })\n this.json(res, 200, { messages, count: messages.length })\n } catch (e: any) {\n this.json(res, 500, { error: e?.message || String(e) })\n }\n break\n }\n\n case \"POST /send/agent\": {\n // Explicit A2A send: route to another agent via mesh (or local\n // dispatch when the target lives on this daemon). This is the\n // deterministic path for \"agent A asks agent B to do X\" — no\n // contact-directory fallback, no name fuzzy-matching. Caller\n // must pass an exact agentId. Returns the resulting task id /\n // remote message id when the mesh accepts the task.\n const body = await readBody(req)\n if (!body.agentId || !body.text) {\n this.json(res, 400, { error: \"Required: agentId, text\" })\n break\n }\n const targetAgent = String(body.agentId)\n const text = String(body.text)\n // Local agent? Dispatch directly through the registry.\n const localDef = this.registry.getAgent(targetAgent)\n if (localDef) {\n try {\n const senderAgentId = body.senderAgentId ? String(body.senderAgentId) : undefined\n const response = await this.registry.execute({\n agentId: targetAgent,\n message: text,\n context: { channel: \"a2a\", sender: senderAgentId ? `agent:${senderAgentId}` : \"agent\", chatId: senderAgentId || \"a2a\" },\n })\n this.json(res, response.error ? 500 : 200, { ok: !response.error, content: response.content, error: response.error })\n } catch (e: any) {\n this.json(res, 500, { error: e.message })\n }\n break\n }\n // Remote agent? Find the mesh peer that advertises this agent.\n if (!this.mesh) {\n this.json(res, 400, { error: `Unknown agent \"${targetAgent}\" — mesh disabled, no remote lookup possible` })\n break\n }\n const directory = this.mesh.directory()\n const peer = directory.find((p) => p.healthy && p.skills.some((s) => s.id === targetAgent))\n if (!peer) {\n const known = [\n ...Object.keys(this.config.agents),\n ...directory.flatMap((p) => p.skills.map((s) => s.id)),\n ]\n this.json(res, 404, { error: `Unknown agent \"${targetAgent}\"`, known })\n break\n }\n try {\n const senderAgentId = body.senderAgentId ? String(body.senderAgentId) : undefined\n const messageId = await this.mesh.sendTask(peer.peer, text, targetAgent, { senderAgentId })\n this.json(res, 200, { ok: true, messageId, peer: peer.peer })\n } catch (e: any) {\n this.json(res, 500, { error: e.message, peer: peer.peer })\n }\n break\n }\n\n case \"POST /send/contact\": {\n // Resolve a free-form contact name through the contact directory,\n // pick a channel address, then dispatch via the regular\n // sendOutbound path. Refuses on miss/ambiguous/fuzzy(no-confirm)\n // so the caller can ask the user to disambiguate. Distinct from\n // /send (which requires a chatId) and /send/agent (which uses\n // mesh), so route_traces records why each path was taken.\n const body = await readBody(req)\n if (!body.contactName || !body.text) {\n this.json(res, 400, { error: \"Required: contactName, text\" })\n break\n }\n const name = String(body.contactName)\n const text = String(body.text)\n const preferredChannel = body.channel ? String(body.channel) : undefined\n const confirmed = body.confirmed === true\n const result = this.contacts.resolve(name)\n\n if (result.kind === \"miss\") {\n this.json(res, 404, { error: `No contact matches \"${name}\"`, hint: \"Add the contact to .agentx/contacts.json and POST /reload.\" })\n break\n }\n if (result.kind === \"ambiguous\") {\n this.json(res, 409, { error: \"ambiguous\", candidates: result.candidates.map((c) => ({ id: c.id, name: c.name, channels: Object.keys(c.channels) })) })\n break\n }\n if (result.kind === \"fuzzy\" && !confirmed) {\n // Refuse silent fuzzy match — caller (the LLM) must confirm.\n // Returns the candidate so the agent can ask the user \"did you mean X?\".\n this.json(res, 409, {\n error: \"fuzzy-match-needs-confirmation\",\n candidate: { id: result.contact.id, name: result.contact.name, channels: Object.keys(result.contact.channels) },\n confidence: result.confidence,\n hint: \"Re-call with confirmed:true once the user has confirmed this is the intended recipient.\",\n })\n break\n }\n // Cross-channel collision check: if the name also resolves to a\n // registered local agent, surface ambiguity rather than silently\n // pick the contact. Mesh peers handled the same way.\n const collidesWithLocalAgent = !!this.registry.getAgent(name)\n const collidesWithMeshAgent = this.mesh?.directory()?.some((p) => p.skills.some((s) => s.id.toLowerCase() === name.toLowerCase()))\n if (collidesWithLocalAgent || collidesWithMeshAgent) {\n this.json(res, 409, {\n error: \"ambiguous\",\n hint: `\"${name}\" resolves to BOTH a contact (id=${result.contact.id}) and an agent. Use /send/agent for the agent, or pass contactId=\"${result.contact.id}\" to /send/contact for the contact.`,\n })\n break\n }\n const picked = this.contacts.pickChannel(result.contact, preferredChannel)\n if (!picked) {\n this.json(res, 400, { error: `Contact \"${result.contact.id}\" has no channels configured` })\n break\n }\n if (preferredChannel && picked.channel !== preferredChannel) {\n this.json(res, 400, { error: `Contact \"${result.contact.id}\" has no \"${preferredChannel}\" channel`, available: Object.keys(result.contact.channels) })\n break\n }\n try {\n const messageId = await this.router.sendOutbound({\n channel: picked.channel,\n chatId: picked.address,\n text,\n agentId: body.agentId as string | undefined,\n accountId: body.accountId as string | undefined,\n })\n this.json(res, 200, { ok: true, messageId: messageId || null, contactId: result.contact.id, channel: picked.channel })\n } catch (e: any) {\n this.json(res, 400, { error: e.message })\n }\n break\n }\n\n case \"POST /send\": {\n const body = await readBody(req)\n if (!body.channel || !body.chatId || !body.text) {\n this.json(res, 400, { error: \"Required: channel, chatId, text\" })\n return\n }\n try {\n const messageId = await this.router.sendOutbound({\n channel: body.channel as string,\n chatId: body.chatId as string,\n text: body.text as string,\n replyTo: body.replyTo as string | undefined,\n parseMode: body.parseMode as any,\n agentId: body.agentId as string | undefined,\n accountId: body.accountId as string | undefined,\n })\n this.json(res, 200, { ok: true, messageId: messageId || null })\n } catch (e: any) {\n this.json(res, 400, { error: e.message })\n }\n break\n }\n\n case \"GET /channels\":\n this.json(res, 200, this.router.getChannelNames())\n break\n\n case \"GET /services\":\n this.json(res, 200, (this.router as any).serviceMatcher?.list() || [])\n break\n\n case \"POST /task\": {\n const body = await readBody(req)\n const agentId = (body.agent as string) || this.config.node.defaultAgent\n if (!agentId || !body.message) {\n this.json(res, 400, { error: \"Missing: message (and no defaultAgent configured)\" })\n return\n }\n // A2A sender identity — when present, must resolve to an agent on\n // the calling peer. Log-warn (not enforce) for one release so\n // older mesh peers can roll out the field before we reject. The\n // shape \"no body.context\" is what classic A2A calls look like;\n // human-facing /task callers (CLI, dashboard) always set\n // context.channel, so the warning targets only mesh traffic.\n const senderAgentId = typeof body.senderAgentId === \"string\" ? body.senderAgentId : undefined\n const looksLikeA2A = !body.context\n if (looksLikeA2A && !senderAgentId) {\n this.log(`[a2a] /task accepted without senderAgentId for agent=\"${agentId}\" from ${(req.socket?.remoteAddress) || \"unknown\"} — caller should upgrade. Required in next release.`)\n }\n // Per-task context strategy override. When absent, registry falls\n // back to config.session.contextStrategy. Used by the bench\n // harness to A/B the same request under \"layered\" vs \"planner\"\n // without a daemon reload.\n const contextStrategy =\n body.contextStrategy === \"layered\" || body.contextStrategy === \"planner\"\n ? body.contextStrategy\n : undefined\n // Phase 1 commit 6.d — record the inbound mesh dispatch decision\n // in the ledger. The mesh protocol has no stable request id, so\n // each call records as its own event row (no per-event idempotency).\n // Wrapped in try/catch so a ledger failure cannot break /task —\n // legacy stays authoritative until 1c per-source promotion lands.\n let intentRef: { eventId: string; decidedBy: string } | undefined\n if (getLedgerMode(\"mesh\") !== \"off\") {\n try {\n const decision = recordMeshDispatch(\n getDefaultLedger(),\n {\n agentId,\n senderAgentId,\n context: body.context as any,\n },\n JSON.stringify({ agentId, senderAgentId, context: body.context, message: typeof body.message === \"string\" ? body.message.slice(0, 200) : \"\" }),\n {\n agentId,\n outcome: \"dispatched\",\n reason: senderAgentId ? `from ${senderAgentId}` : null,\n },\n )\n if (decision.outcome === \"dispatched\") {\n intentRef = { eventId: decision.eventId, decidedBy: decision.decidedBy }\n }\n } catch (e: any) {\n this.log(`[ledger] mesh /task agent=\"${agentId}\" record failed: ${e?.message ?? e}`)\n }\n }\n\n // No-op onDelta enables stream-json runtime mode so the dashboard\n // task modal can see tool calls + tool results live. The caller still\n // awaits the final response — they don't see deltas, just the result.\n const response = await this.registry.execute(\n {\n agentId,\n message: body.message as string,\n context: body.context as any,\n contextStrategy,\n intentRef,\n },\n () => {},\n )\n this.json(res, response.error ? 500 : 200, response)\n break\n }\n\n case \"POST /ask\":\n case \"GET /ask\": {\n // Voice-optimized endpoint for Siri/voice assistants\n // Accepts message via body.message (POST) or ?q= (GET)\n const url = new URL(req.url || \"/\", `http://${req.headers.host || \"localhost\"}`)\n let message = url.searchParams.get(\"q\") || url.searchParams.get(\"message\") || \"\"\n if (req.method === \"POST\") {\n const body = await readBody(req)\n message = (body.message as string) || (body.q as string) || message\n }\n\n const agentId = this.config.node.defaultAgent\n if (!agentId) {\n this.json(res, 400, { error: \"No defaultAgent configured in node config\" })\n return\n }\n if (!message) {\n this.json(res, 400, { error: \"Missing message (pass as ?q=... or {message:...})\" })\n return\n }\n\n // Prepend instruction so the agent responds in a TTS-friendly way\n const voicePrompt = `[VOICE MODE — Your response will be spoken aloud by a TTS engine. Keep it to 2-3 short sentences. Use plain language, no markdown, no code blocks, no bullet points, no URLs. Speak conversationally.]\\n\\n${message}`\n\n const response = await this.registry.execute({\n agentId,\n message: voicePrompt,\n context: { channel: \"voice\", sender: \"Siri\" },\n })\n\n // Convert response to speakable text (TTS-friendly)\n const speakable = toSpeakable(response.content)\n\n this.json(res, response.error ? 500 : 200, {\n text: speakable,\n full: response.content,\n error: response.error,\n duration: response.duration,\n })\n break\n }\n\n case \"POST /mesh/task\": {\n const body = await readBody(req)\n if (!body.peer || !body.message) {\n this.json(res, 400, { error: \"Missing: peer, message\" })\n return\n }\n if (!this.mesh) {\n this.json(res, 400, { error: \"Mesh not enabled\" })\n return\n }\n const result = await this.mesh.sendTask(body.peer as string, body.message as string, body.agent as string | undefined)\n this.json(res, 200, { response: result })\n break\n }\n\n // Browser → daemon → remote peer\n case \"POST /webrtc/signal/out\": {\n if (!this.webrtc) {\n this.json(res, 404, { error: \"WebRTC signaling not enabled\" })\n return\n }\n const body = await readBody(req)\n const signal = body as unknown as WebRtcSignal\n if (!signal.kind || !signal.callId || !signal.from || !signal.to) {\n this.json(res, 400, { error: \"Missing: kind, callId, from, to\" })\n return\n }\n const result = await this.webrtc.handleOutgoing(signal)\n this.json(res, result.ok ? 200 : 400, result)\n break\n }\n\n // Remote peer → daemon → local browser (SSE fan-out).\n // Called by `A2AMesh.sendSignal()` on the sending peer.\n case \"POST /webrtc/signal\": {\n if (!this.webrtc) {\n this.json(res, 404, { error: \"WebRTC signaling not enabled\" })\n return\n }\n const body = await readBody(req)\n const signal = body as unknown as WebRtcSignal\n if (!signal.kind || !signal.callId || !signal.from || !signal.to) {\n this.json(res, 400, { error: \"Missing: kind, callId, from, to\" })\n return\n }\n const result = this.webrtc.handleIncoming(signal)\n this.json(res, result.ok ? 200 : 400, result)\n break\n }\n\n // Browser asks the local daemon to spawn a bot peer for this call.\n case \"POST /webrtc/bot/invite\": {\n if (!this.botManager) {\n this.json(res, 404, { error: \"WebRTC bot not enabled (channels.webrtc.bot.enabled=false)\" })\n return\n }\n const body = await readBody(req)\n const callId = body.callId as string | undefined\n const target = body.target as string | undefined\n const agentId = (body.agentId as string | undefined) || this.config.channels.webrtc?.bot.defaultAgentId\n if (!callId || !target || !agentId) {\n this.json(res, 400, { error: \"Missing: callId, target, agentId (or defaultAgentId in config)\" })\n return\n }\n const result = await this.botManager.invite({ callId, target, agentId })\n this.json(res, result.ok ? 200 : 500, result)\n break\n }\n\n case \"GET /webrtc/bots\": {\n if (!this.botManager) {\n this.json(res, 404, { error: \"WebRTC bot not enabled\" })\n return\n }\n this.json(res, 200, { active: this.botManager.active() })\n break\n }\n\n case \"GET /webrtc/history\": {\n if (!this.botManager) {\n this.json(res, 404, { error: \"WebRTC bot not enabled\" })\n return\n }\n // In-process ring buffer of recently-completed sessions. Surfaces\n // call activity to the admin UI without requiring transcript\n // persistence to disk (that's still opt-in via webrtcBot.transcriptChannel).\n this.json(res, 200, { active: this.botManager.active(), history: this.botManager.history() })\n break\n }\n\n case \"GET /webrtc/config\": {\n const wrtc = this.config.channels.webrtc\n if (!wrtc?.enabled) {\n this.json(res, 404, { error: \"WebRTC signaling not enabled\" })\n return\n }\n this.json(res, 200, {\n localName: this.config.node.name,\n iceServers: [\n ...wrtc.stunServers.map(urls => ({ urls })),\n ...wrtc.turnServers,\n ],\n peers: this.mesh?.directory().map(p => ({ name: p.peer, healthy: p.healthy })) || [],\n })\n break\n }\n\n // A2A agent card discovery\n case \"GET /.well-known/agent-card.json\":\n this.json(res, 200, {\n name: this.config.node.name,\n description: `AgentX daemon node \"${this.config.node.name}\"`,\n url: `http://${this.config.node.bind}`,\n version: \"1.0.0\",\n capabilities: {\n streaming: false,\n pushNotifications: false,\n stateTransitionHistory: false,\n },\n // Each agent advertised as a skill (legacy A2A naming). The\n // description is what peers SEE in their [Landscape]: anaemic\n // text here means the calling agent has no idea what we do.\n // Prefer (in order) an explicit `description`, the first\n // sentence of the systemPrompt, then the agent name as a\n // last-resort placeholder.\n skills: this.registry.list().map((a) => {\n // registry.list() returns a flat shape; pull the raw def\n // from the config so we can read systemPrompt / mentions\n // / description.\n const def = (this.config.agents as any)[a.id] || {}\n const desc =\n (typeof def.description === \"string\" && def.description.trim()) ||\n firstSentence(def.systemPrompt) ||\n `Agent \"${a.name}\" (${a.tier})`\n const mentions = Array.isArray(def.mentions) ? def.mentions.slice(0, 4) : []\n return {\n id: a.id,\n name: a.name,\n description: desc,\n tags: [a.tier, ...mentions],\n }\n }),\n // Channels this node hosts. Used by mesh peers to route\n // workflow `action.send` calls back to the originating channel\n // when the workflow runs on a different node than the channel\n // adapter (e.g. workflow on macbook, whatsapp on clawd-server).\n channels: this.router.getChannelNames(),\n defaultInputModes: [\"text\"],\n defaultOutputModes: [\"text\"],\n })\n break\n\n // --- Wiki API (for mesh sync) ---\n\n case \"GET /wiki/agents\": {\n const hub = this.registry.getWikiHub()\n this.json(res, 200, {\n nodeId: this.config.node.id,\n agents: hub.summary(),\n })\n break\n }\n\n case \"GET /wiki/entries\": {\n const hub = this.registry.getWikiHub()\n const agentId = url.searchParams.get(\"agent\") || undefined\n const after = url.searchParams.get(\"after\") || undefined\n const entries = agentId\n ? hub.getAgentEntries(agentId)\n : hub.getSharedStore().listEntries({ after })\n this.json(res, 200, {\n nodeId: this.config.node.id,\n count: entries.length,\n entries: entries.map(e => ({\n id: e.id,\n date: e.date,\n agentId: e.agentId,\n source: e.source,\n sourceContext: e.sourceContext,\n content: e.content,\n })),\n })\n break\n }\n\n case \"GET /wiki/articles\": {\n const hub = this.registry.getWikiHub()\n const agentId = url.searchParams.get(\"agent\")\n if (!agentId) {\n this.json(res, 400, { error: \"?agent= required\" })\n break\n }\n const store = hub.getAgentWiki(agentId)\n const index = store.rebuildIndex()\n this.json(res, 200, {\n nodeId: this.config.node.id,\n agentId,\n articles: index.articles,\n })\n break\n }\n\n case \"GET /wiki/article\": {\n const hub = this.registry.getWikiHub()\n const agentId = url.searchParams.get(\"agent\")\n const articlePath = url.searchParams.get(\"path\")\n if (!agentId || !articlePath) {\n this.json(res, 400, { error: \"?agent= and ?path= required\" })\n break\n }\n const store = hub.getAgentWiki(agentId)\n const article = store.readArticle(articlePath)\n if (!article) {\n this.json(res, 404, { error: \"Article not found\" })\n break\n }\n this.json(res, 200, {\n nodeId: this.config.node.id,\n agentId,\n path: article.path,\n title: article.meta.title,\n tags: article.meta.tags,\n owner: article.meta.owner,\n created: article.meta.created,\n lastUpdated: article.meta.lastUpdated,\n sources: article.meta.sources,\n content: article.content,\n })\n break\n }\n\n // --- Graph API (read-only, for mesh sync) ---\n\n case \"GET /graph/schema\": {\n const store = this.registry.getGraphStore()\n if (!store) { this.json(res, 404, { error: \"graph disabled on this node\" }); break }\n this.json(res, 200, {\n nodeId: this.config.node.id,\n schema: store.loadSchema(),\n })\n break\n }\n\n case \"GET /graph/nodes\": {\n const store = this.registry.getGraphStore()\n if (!store) { this.json(res, 404, { error: \"graph disabled on this node\" }); break }\n this.json(res, 200, {\n nodeId: this.config.node.id,\n ...store.loadNodes(),\n })\n break\n }\n\n case \"GET /graph/classifications\": {\n const store = this.registry.getGraphStore()\n if (!store) { this.json(res, 404, { error: \"graph disabled on this node\" }); break }\n const status = (url.searchParams.get(\"status\") || \"approved\") as \"pending\" | \"approved\" | \"rejected\"\n const limit = Math.max(1, Math.min(1000, parseInt(url.searchParams.get(\"limit\") || \"200\", 10) || 200))\n const items = store.listByStatus(status, limit)\n this.json(res, 200, {\n nodeId: this.config.node.id,\n status,\n count: items.length,\n classifications: items,\n })\n break\n }\n\n default:\n this.json(res, 404, {\n error: \"Not found\",\n endpoints: [\n \"GET /health\",\n \"GET /agents\",\n \"GET /crons\",\n \"GET /mesh\",\n \"GET /wiki/agents\",\n \"GET /wiki/entries[?agent=X&after=YYYY-MM-DD]\",\n \"GET /wiki/articles?agent=X\",\n \"GET /graph/schema\",\n \"GET /graph/nodes\",\n \"GET /graph/classifications[?status=approved|pending|rejected&limit=N]\",\n \"POST /task { agent, message, context? }\",\n \"POST /mesh/task { peer, message }\",\n \"POST /webhook/:agentId[/:source] — webhook callback\",\n \"POST /reload — re-read agentx.json (hot-swaps crons)\",\n \"GET /.well-known/agent-card.json\",\n \"GET /call — browser UI for P2P A/V calls (requires channels.webrtc.enabled)\",\n \"GET /webrtc/config — ICE servers + peer directory for the call page\",\n \"GET /webrtc/events?callId=&as= — SSE stream of signaling events\",\n \"POST /webrtc/signal/out — browser-originated signal, forwarded to remote peer\",\n \"POST /webrtc/signal — remote-peer-originated signal, fanned out to local browser\",\n \"POST /webrtc/bot/invite { callId, target, agentId } — spawn a transcribing bot peer for a call\",\n \"GET /webrtc/bots — active bot sessions\",\n ],\n })\n }\n } catch (e: any) {\n this.json(res, 500, { error: e.message })\n }\n }\n\n /**\n * OpenAI-compatible chat completions endpoint.\n * Allows ElevenLabs, Cursor, or any OpenAI-compatible client to use an AgentX agent as an LLM.\n *\n * Routes:\n * POST /v1/chat/completions — uses \"model\" field as agent ID\n * POST /llm/:agentId/v1/chat/completions — explicit agent ID in URL\n *\n * Request format (OpenAI): { model, messages: [{role, content}], stream?, temperature? }\n * Response format (OpenAI): { id, object, choices: [{message: {role, content}}], usage }\n */\n private async handleOpenAICompat(req: IncomingMessage, res: ServerResponse, path: string): Promise<void> {\n const body = await readBody(req)\n\n // Resolve agent ID: from URL path or \"model\" field\n const pathMatch = path.match(/^\\/llm\\/([^/]+)\\//)\n const fallbackAgent = Object.keys(this.config.agents)[0] || \"default\"\n const agentId = pathMatch?.[1] || (body.model as string) || fallbackAgent\n\n // Extract messages — use the last user message as the task\n const messages = (body.messages as Array<{ role: string; content: string }>) || []\n const lastUserMsg = [...messages].reverse().find(m => m.role === \"user\")\n\n if (!lastUserMsg?.content) {\n this.json(res, 400, { error: { message: \"No user message found\", type: \"invalid_request_error\" } })\n return\n }\n\n // Build conversation context from message history\n const historyLines = messages\n .slice(0, -1) // exclude the last message (it's the prompt)\n .map(m => `${m.role === \"user\" ? \"User\" : \"Assistant\"}: ${m.content.slice(0, 200)}`)\n\n const contextPrefix = historyLines.length > 0\n ? `[Conversation]\\n${historyLines.slice(-10).join(\"\\n\")}\\n\\n`\n : \"\"\n\n const stream = body.stream === true\n\n if (stream) {\n // SSE streaming response\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n \"Connection\": \"keep-alive\",\n })\n\n const requestId = `chatcmpl-${Date.now().toString(36)}`\n\n const response = await this.registry.execute({\n agentId,\n message: contextPrefix + lastUserMsg.content,\n context: { channel: \"api\", sender: \"openai-compat\" },\n })\n\n const content = response.error || response.content || \"\"\n\n // Send as a single chunk (Claude Code doesn't stream to us via execFile)\n const chunk = {\n id: requestId,\n object: \"chat.completion.chunk\",\n created: Math.floor(Date.now() / 1000),\n model: agentId,\n choices: [{\n index: 0,\n delta: { role: \"assistant\", content },\n finish_reason: \"stop\",\n }],\n }\n res.write(`data: ${JSON.stringify(chunk)}\\n\\n`)\n res.write(\"data: [DONE]\\n\\n\")\n res.end()\n } else {\n // Standard response\n const response = await this.registry.execute({\n agentId,\n message: contextPrefix + lastUserMsg.content,\n context: { channel: \"api\", sender: \"openai-compat\" },\n })\n\n const content = response.error || response.content || \"\"\n const tokens = Math.ceil(content.length / 4)\n\n this.json(res, 200, {\n id: `chatcmpl-${Date.now().toString(36)}`,\n object: \"chat.completion\",\n created: Math.floor(Date.now() / 1000),\n model: agentId,\n choices: [{\n index: 0,\n message: { role: \"assistant\", content },\n finish_reason: \"stop\",\n }],\n usage: {\n prompt_tokens: Math.ceil(lastUserMsg.content.length / 4),\n completion_tokens: tokens,\n total_tokens: Math.ceil(lastUserMsg.content.length / 4) + tokens,\n },\n })\n }\n }\n\n private json(res: ServerResponse, status: number, data: unknown): void {\n res.writeHead(status, { \"Content-Type\": \"application/json\" })\n res.end(JSON.stringify(data, null, 2))\n }\n\n /** Pick today's usage rollup according to AGENTX_USAGE_READ:\n * - \"sqlite\": SQLite only (empty if db null / no rows)\n * - \"json\": JSON only (legacy behaviour)\n * - \"sqlite-then-json\" (default): try SQLite, fall back to JSON when\n * the rollup is empty (no agents seen today). Transparent for\n * operators on first deploy where SQLite is empty until the first\n * task completes. */\n private resolveTodayUsage(): ReturnType<AgentRegistry[\"getTodayUsage\"]> {\n const mode = getUsageReadMode()\n if (mode === \"json\") return this.registry.getTodayUsage()\n const rollup = loadTodayRollup(this.db)\n if (mode === \"sqlite\") return rollup\n // sqlite-then-json\n if (Object.keys(rollup.agents).length > 0) return rollup\n return this.registry.getTodayUsage()\n }\n\n /** Boot-time: install the `remember` skill into every agent workspace\n * that doesn't already have it, and sync each agent's existing memory\n * into <workspace>/CLAUDE.md + .agentx-memory.md. Idempotent — safe to\n * run on every daemon start. Write-if-absent for the skill, sentinel-\n * replace for CLAUDE.md, so operator edits survive. */\n private installAgentMemorySurface(): void {\n for (const agent of this.registry.list()) {\n const ws = agent.workspace\n if (!ws || !existsSync(ws)) continue\n try {\n const skillsDir = resolve(ws, \".claude\", \"skills\")\n mkdirSync(skillsDir, { recursive: true })\n const skillPath = resolve(skillsDir, REMEMBER_SKILL_FILENAME)\n if (!existsSync(skillPath)) {\n writeFileSync(skillPath, REMEMBER_SKILL_BODY)\n this.log(` memory-skill: installed remember.md → ${agent.id}`)\n }\n // Always re-sync: rewrites .agentx-memory.md and the CLAUDE.md\n // sentinel block from whatever is currently on disk.\n this.agentMemory.syncToWorkspace(agent.id, ws)\n } catch (e: any) {\n this.log(` memory-skill: ${agent.id} install failed — ${e?.message ?? e}`)\n }\n }\n }\n\n /** Boot-time: sync each agent's `mcp` config block to\n * <workspace>/.mcp.json. Mirrors installAgentMemorySurface — same\n * ownership semantics, marker-based to preserve operator edits. */\n private installAgentMcpConfig(): void {\n for (const [agentId, def] of Object.entries(this.config.agents)) {\n const ws = def.workspace\n if (!ws || !existsSync(ws)) continue\n const mcp = ((def as any).mcp ?? {}) as McpServerMap\n try {\n const result = syncMcpToWorkspace(ws, mcp)\n switch (result) {\n case \"installed\":\n this.log(` mcp: installed ${Object.keys(mcp).length} server(s) → ${agentId}`)\n break\n case \"updated\":\n this.log(` mcp: updated .mcp.json (${Object.keys(mcp).length} server(s)) → ${agentId}`)\n break\n case \"removed\":\n this.log(` mcp: removed managed .mcp.json (config now empty) → ${agentId}`)\n break\n case \"skipped-operator-owned\":\n // Only worth surfacing when there's a config the operator\n // is silently overriding. Otherwise stay quiet.\n if (Object.keys(mcp).length > 0) {\n this.log(` mcp: ${agentId} has operator-owned .mcp.json — skipping (delete file or marker to let agentx manage)`)\n }\n break\n case \"noop\":\n break\n }\n } catch (e: any) {\n this.log(` mcp: ${agentId} install failed — ${e?.message ?? e}`)\n }\n }\n }\n\n /** Look up an agent's workspace from the live registry. Returns null\n * for agents the daemon doesn't know about (e.g., a stale memory\n * entry for a since-removed agent). */\n private workspaceFor(agentId: string): string | null {\n for (const a of this.registry.list()) if (a.id === agentId) return a.workspace\n return null\n }\n\n /** Agent-memory HTTP surface. Agents call this from inside a Claude\n * Code session via `Bash` + `curl` — see the `remember` skill. The\n * heavy lifting is in `AgentRegistry.agentMemory` (AgentMemory); this\n * is a thin JSON wrapper that validates + delegates. */\n private async handleMemoryApi(\n req: IncomingMessage, res: ServerResponse, path: string, url: URL,\n ): Promise<boolean> {\n const mem = this.agentMemory\n\n // GET /api/memory?agent=<id>\n if (req.method === \"GET\" && path === \"/api/memory\") {\n const agent = url.searchParams.get(\"agent\") || \"\"\n if (!agent) { this.json(res, 400, { error: \"missing agent query param\" }); return true }\n this.json(res, 200, { agent, memories: mem.list(agent), index: mem.indexMarkdown(agent) })\n return true\n }\n // GET /api/memory/:name?agent=<id>\n const oneMatch = req.method === \"GET\" && path.match(/^\\/api\\/memory\\/([^\\/?]+)$/)\n if (oneMatch) {\n const agent = url.searchParams.get(\"agent\") || \"\"\n if (!agent) { this.json(res, 400, { error: \"missing agent query param\" }); return true }\n const rec = mem.get(agent, decodeURIComponent(oneMatch[1]))\n if (!rec) { this.json(res, 404, { error: \"no such memory\" }); return true }\n this.json(res, 200, { memory: rec })\n return true\n }\n // POST /api/memory — write a memory\n if (req.method === \"POST\" && path === \"/api/memory\") {\n let body: any\n try { body = await readJsonBody(req) } catch (e: any) {\n this.json(res, 400, { error: \"invalid JSON body\", message: e.message }); return true\n }\n const agentId = typeof body?.agentId === \"string\" ? body.agentId : \"\"\n const type = typeof body?.type === \"string\" ? body.type : \"\"\n const name = typeof body?.name === \"string\" ? body.name : \"\"\n const description = typeof body?.description === \"string\" ? body.description : \"\"\n const newBody = typeof body?.body === \"string\" ? body.body : \"\"\n const append = body?.append === true\n if (!agentId || !type || !name || !description || !newBody) {\n this.json(res, 400, { error: \"required fields: agentId, type, name, description, body\" })\n return true\n }\n try {\n let finalBody = newBody\n if (append) {\n const existing = mem.get(agentId, name)\n if (existing) finalBody = `${existing.body.trimEnd()}\\n\\n${newBody.trim()}`\n }\n const rec = mem.save({ agentId, type: type as any, name, description, body: finalBody })\n const ws = this.workspaceFor(agentId)\n if (ws) { try { mem.syncToWorkspace(agentId, ws) } catch { /* best effort */ } }\n this.json(res, 200, { ok: true, memory: rec, syncedToWorkspace: !!ws })\n } catch (e: any) {\n this.json(res, 400, { error: e?.message || \"save failed\" })\n }\n return true\n }\n // DELETE /api/memory/:name?agent=<id>\n const delMatch = req.method === \"DELETE\" && path.match(/^\\/api\\/memory\\/([^\\/?]+)$/)\n if (delMatch) {\n const agent = url.searchParams.get(\"agent\") || \"\"\n if (!agent) { this.json(res, 400, { error: \"missing agent query param\" }); return true }\n const ok = mem.remove(agent, decodeURIComponent(delMatch[1]))\n if (!ok) { this.json(res, 404, { error: \"no such memory\" }); return true }\n const ws = this.workspaceFor(agent)\n if (ws) { try { mem.syncToWorkspace(agent, ws) } catch { /* best effort */ } }\n this.json(res, 200, { ok: true })\n return true\n }\n return false\n }\n\n /** Mesh receiver for forwarded workflow events. Peers POST the same\n * payload shape the MeshForwarder sends. Enforces that the run is\n * actually home'd here before acting — protects against routing loops. */\n private async handleTaskApi(req: IncomingMessage, res: ServerResponse, path: string): Promise<boolean> {\n const d = this.workflowDispatcher\n if (!d) return false\n const { formSubmissionSchema } = await import(\"@/forms/types\")\n\n // GET /api/workflows/tasks[?actor=<id>]\n if (req.method === \"GET\" && (path === \"/api/workflows/tasks\" || path.startsWith(\"/api/workflows/tasks?\"))) {\n const url = new URL(req.url || \"/\", `http://_`)\n const actor = url.searchParams.get(\"actor\") || undefined\n const tasks = actor ? d.tasks.listForActor(actor) : d.tasks.listOpen()\n this.json(res, 200, { tasks })\n return true\n }\n\n // GET /api/workflows/tasks/:id\n // POST /api/workflows/tasks/:id/submit\n const trail = path.replace(/^\\/api\\/workflows\\/tasks/, \"\")\n const match = trail.match(/^\\/([^\\/?]+)(\\/submit)?$/)\n if (!match) return false\n const taskId = decodeURIComponent(match[1])\n\n if (match[2]) {\n if (req.method !== \"POST\") { this.json(res, 405, { error: \"method not allowed\" }); return true }\n let body: any\n try { body = await readJsonBody(req) } catch (e: any) {\n this.json(res, 400, { error: \"invalid JSON body\", message: e.message }); return true\n }\n const submissionRaw = body && typeof body === \"object\" && \"submission\" in body ? (body as any).submission : body\n const parsed = formSubmissionSchema.safeParse(submissionRaw)\n if (!parsed.success) {\n this.json(res, 400, {\n error: \"invalid submission\",\n issues: parsed.error.issues.map((i) => ({ path: i.path.join(\".\"), message: i.message })),\n })\n return true\n }\n const submittedBy = typeof body?.submittedBy === \"string\" ? body.submittedBy : \"anonymous\"\n const result = await d.submitTask(taskId, parsed.data, submittedBy)\n if (!result.ok) { this.json(res, 400, { error: result.error, fieldErrors: result.fieldErrors }); return true }\n this.json(res, 200, { ok: true, runId: result.runId })\n return true\n }\n\n if (req.method === \"GET\") {\n const task = d.tasks.get(taskId)\n if (!task) { this.json(res, 404, { error: \"task not found\" }); return true }\n this.json(res, 200, { task })\n return true\n }\n return false\n }\n\n private async handleWorkflowEvent(req: IncomingMessage, res: ServerResponse): Promise<void> {\n if (!this.workflowDispatcher) {\n this.json(res, 503, { error: \"workflow engine not enabled on this node\" })\n return\n }\n let body: any\n try { body = await readJsonBody(req) } catch (e: any) {\n this.json(res, 400, { error: \"invalid JSON body\", message: e.message }); return\n }\n const payload = body?.payload ?? body\n const event = payload?.event\n const entityRef = payload?.entityRef\n if (!event?.id || !entityRef?.backend || !entityRef?.id) {\n this.json(res, 400, { error: \"missing event or entityRef\" }); return\n }\n\n // Two flavours arrive on this endpoint:\n // 1. kind=\"trigger\" — a fresh trigger broadcast from a peer that\n // observed the channel event. Trigger fields are carried in the\n // payload itself; we dispatch with fromRemote so only opted-in\n // workflows match.\n // 2. (legacy / transition) — a workflow-targeted forward for an active\n // run home'd here. Reconstruct the trigger from the local workflow\n // definition just like before.\n const kind = body?.kind\n try {\n if (kind === \"trigger\" && payload.trigger) {\n const fromPeer = typeof body?.fromPeer === \"string\" ? body.fromPeer : \"unknown\"\n this.log(`[workflows/mesh] received trigger from \"${fromPeer}\" source=\"${payload.trigger.source}\" chat=\"${payload.trigger.chat ?? \"*\"}\"`)\n const r = await this.workflowDispatcher.dispatch({\n trigger: payload.trigger,\n entityRef,\n event,\n fromRemote: { peer: fromPeer },\n })\n this.log(`[workflows/mesh] dispatched broadcast: matched=${r.claimed.length} ${r.claimed.length ? `(${r.claimed.map((w) => w.id).join(\", \")})` : \"[none — no workflow with mesh.allowRemote matched]\"}`)\n this.json(res, 202, { ok: true, mode: \"trigger-broadcast\", matched: r.claimed.map((w) => w.id) })\n return\n }\n const wf = payload.workflowId ? this.workflowStore?.get(payload.workflowId) : null\n const triggerNode = wf?.nodes.find((n) => n.type.startsWith(\"trigger.\"))\n const cfg = (triggerNode?.config ?? {}) as {\n source?: string\n filter?: { project?: string; repo?: string; chat?: string; labels?: string[] }\n }\n const trigger = {\n source: String(cfg.source ?? \"hook\"),\n project: cfg.filter?.project,\n repo: cfg.filter?.repo,\n chat: cfg.filter?.chat,\n labels: cfg.filter?.labels,\n }\n await this.workflowDispatcher.dispatch({ trigger, entityRef, event })\n this.json(res, 202, { ok: true })\n } catch (e: any) {\n this.log(`[workflows] /workflow/event failed: ${e.message}`)\n this.json(res, 500, { error: e.message })\n }\n }\n\n /** Mesh-callable outbound send. A peer's workflow `action.send` invokes\n * this when the channel lives on this node (e.g. clawd-server hosts\n * whatsapp; macbook's workflow forwards here). Just unwraps to the local\n * router's outbound path so all the same per-account/per-bot resolution\n * applies. Authentication is currently the mesh token at the network\n * edge — the endpoint trusts callers that reach it. */\n private async handleChannelSend(req: IncomingMessage, res: ServerResponse): Promise<void> {\n let body: any\n try { body = await readJsonBody(req) } catch (e: any) {\n this.json(res, 400, { error: \"invalid JSON body\", message: e.message }); return\n }\n const channel = String(body?.channel ?? \"\")\n const chatId = String(body?.chatId ?? \"\")\n const text = String(body?.text ?? \"\")\n if (!channel || !chatId || !text) {\n this.json(res, 400, { error: \"channel, chatId, and text are required\" }); return\n }\n const adapter = this.router.getChannel(channel)\n if (!adapter) {\n this.json(res, 404, { error: `channel \"${channel}\" not hosted on this node` }); return\n }\n try {\n const messageId = await this.router.sendOutbound({\n channel,\n chatId,\n text,\n accountId: typeof body.accountId === \"string\" ? body.accountId : undefined,\n parseMode: typeof body.parseMode === \"string\" ? body.parseMode : undefined,\n replyTo: typeof body.replyTo === \"string\" ? body.replyTo : undefined,\n } as any)\n this.json(res, 200, { ok: true, messageId: messageId ?? null })\n } catch (e: any) {\n this.log(`[mesh] /channel/send \"${channel}\" failed: ${e.message}`)\n this.json(res, 500, { error: e.message })\n }\n }\n\n /** List chats the given channel adapter has observed. Merges local with\n * every healthy mesh peer that hosts the channel — a channel's \"true\"\n * chat list is spread across whichever nodes are actually paired\n * (e.g. macbook has the channel enabled but WhatsApp Baileys is unpaired,\n * while clawd-server is paired; macbook's list is empty but we still want\n * the author to pick from clawd-server's cache). Dedup by `id`; the first\n * source with a given id wins.\n *\n * `sources` in the response tells the editor where the entries came from\n * so it can show that metadata alongside. */\n private async handleChannelChats(\n res: ServerResponse,\n channel: string,\n opts: { localOnly?: boolean } = {},\n ): Promise<void> {\n const seen = new Set<string>()\n const chats: Array<{ id: string; name?: string; kind: \"dm\" | \"group\"; accountId?: string; source: string }> = []\n const sources: string[] = []\n const add = (items: Array<{ id: string; name?: string; kind: \"dm\" | \"group\"; accountId?: string }>, source: string) => {\n let added = 0\n for (const c of items) {\n if (!c.id || seen.has(c.id)) continue\n seen.add(c.id)\n chats.push({ ...c, source })\n added++\n }\n if (added > 0) sources.push(`${source}:${added}`)\n }\n\n // Local adapter — Telegram or WhatsApp shape (duck-typed).\n const local = this.router.getChannel(channel) as unknown as\n | { listKnownChats?: () => Array<{ id: string; name?: string; kind: \"dm\" | \"group\"; accountId?: string }>\n listChats?: () => Array<{ jid: string; name: string; isGroup: boolean }> }\n | undefined\n if (local?.listKnownChats) {\n try { add(local.listKnownChats(), \"local\") } catch { /* adapter still booting */ }\n } else if (local?.listChats) {\n try {\n add(local.listChats().map((c) => ({ id: c.jid, name: c.name, kind: c.isGroup ? (\"group\" as const) : (\"dm\" as const) })), \"local\")\n } catch { /* */ }\n }\n\n // Mesh peers — fan out in parallel with `?local=1` so peers only report\n // their OWN adapter's chats (no recursive fan-out back to us). Failures\n // don't block the response; a partial result is better than a timeout.\n if (!opts.localOnly && this.mesh) {\n const peers = this.mesh.directory().filter((p) => p.healthy && p.channels?.includes(channel))\n await Promise.allSettled(peers.map(async (peer) => {\n try {\n const r = await fetch(`${peer.peerUrl}/channels/${encodeURIComponent(channel)}/chats?local=1`, {\n signal: AbortSignal.timeout(5000),\n })\n if (!r.ok) return\n const body = await r.json() as { chats?: Array<{ id: string; name?: string; kind?: \"dm\" | \"group\"; accountId?: string }> }\n if (Array.isArray(body.chats)) {\n add(body.chats.map((c) => ({ id: c.id, name: c.name, kind: c.kind ?? \"dm\", accountId: c.accountId })), `mesh:${peer.peer}`)\n }\n } catch {\n // ignore — soft availability; we'll just return what we have\n }\n }))\n }\n\n this.json(res, 200, { channel, source: sources.join(\", \") || \"none\", chats })\n }\n\n /** Manual run endpoint used by `agentx workflow run <id>`. Only workflows\n * whose trigger node is `trigger.manual` are runnable here — any other\n * source expects a live event and shouldn't race with a manual kick. */\n private async handleWorkflowManualRun(req: IncomingMessage, res: ServerResponse, workflowId: string): Promise<void> {\n if (!this.workflowDispatcher || !this.workflowStore) {\n this.json(res, 503, { error: \"workflow engine not enabled on this node\" })\n return\n }\n const wf = this.workflowStore.get(workflowId)\n if (!wf) { this.json(res, 404, { error: `unknown workflow \"${workflowId}\"` }); return }\n const triggerNode = wf.nodes.find((n) => n.type.startsWith(\"trigger.\"))\n if (!triggerNode) { this.json(res, 400, { error: `workflow \"${workflowId}\" has no trigger node` }); return }\n\n let body: any\n try { body = await readJsonBody(req) } catch { body = {} }\n const force = !!body?.force\n const payload = body?.payload || {}\n\n // By default we only allow running workflows whose trigger is\n // `trigger.manual` — otherwise a manual kick would race against live\n // channel events. `force: true` overrides this for testing: we\n // synthesize a trigger event with the workflow's declared source so\n // the dispatcher's filter still matches, and seed the provided payload\n // into the trigger node's output bundle. Useful when the live channel\n // is disconnected (WhatsApp not paired, Telegram 409 conflict) and\n // you just want to exercise the graph.\n if (triggerNode.type !== \"trigger.manual\" && !force) {\n this.json(res, 409, {\n error: `workflow \"${workflowId}\" trigger is \"${triggerNode.type}\"`,\n hint: `pass { \"force\": true } to fire anyway with a synthesized event (for testing)`,\n })\n return\n }\n\n const cfg = (triggerNode.config ?? {}) as {\n source?: string\n filter?: { project?: string; repo?: string; chat?: string; labels?: string[] }\n }\n const source = force ? String(cfg.source ?? \"manual\") : \"manual\"\n const entityId = String(payload.entityId || payload.chatId || `manual-${Date.now().toString(36)}`)\n const entityRef = {\n backend: force ? (cfg.source ? \"channel\" : \"manual\") : \"manual\",\n id: entityId,\n }\n const eventId = `manual:${workflowId}:${entityId}:${Date.now()}`\n try {\n const updated = await this.workflowDispatcher.dispatch({\n trigger: force\n ? {\n source,\n project: cfg.filter?.project,\n repo: cfg.filter?.repo,\n chat: cfg.filter?.chat,\n labels: cfg.filter?.labels,\n }\n : { source: \"manual\" },\n entityRef,\n event: { id: eventId, payload },\n })\n const runId = updated.runs[0]?.id\n this.json(res, runId ? 200 : 202, { ok: true, runId, entityRef, source, force })\n } catch (e: any) {\n this.log(`[workflows] manual run \"${workflowId}\" failed: ${e.message}`)\n this.json(res, 500, { error: e.message })\n }\n }\n}\n\n/** Pull the first sentence (or ~120 chars) out of a systemPrompt so the\n * agent-card description tells peers something useful. Returns \"\" when\n * the input is empty/missing — callers fall through to a default. */\nfunction firstSentence(prompt: unknown): string {\n if (typeof prompt !== \"string\") return \"\"\n const trimmed = prompt.trim()\n if (!trimmed) return \"\"\n const m = trimmed.match(/^[^.!?\\n]+[.!?]?/)\n const head = (m ? m[0] : trimmed).trim()\n return head.length > 140 ? head.slice(0, 137) + \"…\" : head\n}\n\nfunction readJsonBody(req: IncomingMessage): Promise<unknown> {\n return new Promise((resolve, reject) => {\n let raw = \"\"\n req.setEncoding(\"utf8\")\n req.on(\"data\", (chunk) => { raw += chunk })\n req.on(\"end\", () => {\n try { resolve(raw ? JSON.parse(raw) : {}) } catch (e) { reject(e) }\n })\n req.on(\"error\", reject)\n })\n}\n\n/**\n * Convert text to TTS-friendly speech.\n * Strips markdown, expands technical terms, removes code, handles symbols.\n */\nfunction toSpeakable(text: string): string {\n let s = text\n\n // Remove code blocks entirely (don't read code aloud)\n s = s.replace(/```[\\s\\S]*?```/g, \". \")\n s = s.replace(/`[^`]*`/g, \"\") // inline code\n\n // Remove markdown syntax characters\n s = s.replace(/\\*\\*(.*?)\\*\\*/g, \"$1\") // bold\n s = s.replace(/__(.*?)__/g, \"$1\") // bold\n s = s.replace(/\\*([^*]+)\\*/g, \"$1\") // italic\n s = s.replace(/_([^_]+)_/g, \"$1\") // italic\n s = s.replace(/~~(.*?)~~/g, \"$1\") // strikethrough\n\n // Links: keep text, drop URL\n s = s.replace(/\\[([^\\]]+)\\]\\([^)]+\\)/g, \"$1\")\n\n // Images: describe\n s = s.replace(/!\\[([^\\]]*)\\]\\([^)]+\\)/g, \"image$1 \")\n\n // Headers: remove # markers\n s = s.replace(/^#{1,6}\\s+/gm, \"\")\n\n // List bullets: convert to pauses\n s = s.replace(/^[\\s]*[-*+]\\s+/gm, \". \")\n s = s.replace(/^[\\s]*\\d+\\.\\s+/gm, \". \")\n\n // Horizontal rules\n s = s.replace(/^[-=_]{3,}$/gm, \"\")\n\n // Blockquotes\n s = s.replace(/^>\\s*/gm, \"\")\n\n // URLs in plain text: replace with \"link\"\n s = s.replace(/https?:\\/\\/\\S+/g, \"link\")\n\n // File paths: keep readable\n s = s.replace(/\\/\\w+(\\/\\w+)+/g, (match) => match.split(\"/\").filter(Boolean).join(\" slash \"))\n\n // Technical symbols → words\n s = s.replace(/&/g, \" and \")\n s = s.replace(/@/g, \" at \")\n s = s.replace(/=>/g, \" returns \")\n s = s.replace(/->/g, \" to \")\n s = s.replace(/\\|/g, \" or \")\n\n // Common abbreviations\n s = s.replace(/\\be\\.g\\./gi, \"for example\")\n s = s.replace(/\\bi\\.e\\./gi, \"that is\")\n s = s.replace(/\\betc\\./gi, \"etcetera\")\n s = s.replace(/\\bvs\\.?\\b/gi, \"versus\")\n\n // Collapse whitespace\n s = s.replace(/\\n{2,}/g, \". \")\n s = s.replace(/\\n/g, \" \")\n s = s.replace(/\\s+/g, \" \")\n s = s.replace(/\\.\\s*\\./g, \".\")\n s = s.replace(/\\s+([.,!?])/g, \"$1\")\n\n // Strip leading/trailing whitespace and punctuation\n s = s.trim().replace(/^[.,:;]+\\s*/, \"\")\n\n // Cap at 800 chars for voice UX\n if (s.length > 800) {\n s = s.slice(0, 800).replace(/\\s+\\S*$/, \"\") + \"...\"\n }\n\n return s\n}\n\n/**\n * Diff two agent-definition maps and return the list of field names that\n * would require a daemon restart to take effect. Hot-swappable fields\n * (systemPrompt, mentions, maxConcurrent, access, avatar, queueMode,\n * heartbeat, tags) are read fresh per-task so the registry.setConfig swap\n * is enough. Restart-required fields are those captured at Claude Code\n * subprocess spawn time: model, workspace, tier, permissionMode, mcpServers.\n */\nfunction detectAgentRestartFields(\n prev: Record<string, any>,\n next: Record<string, any>,\n): string[] {\n const restartFields = [\"model\", \"workspace\", \"tier\", \"permissionMode\", \"mcpServers\"]\n const changed = new Set<string>()\n const ids = new Set([...Object.keys(prev), ...Object.keys(next)])\n for (const id of ids) {\n const p = prev[id], n = next[id]\n if (!p || !n) { changed.add(\"add/remove\"); continue }\n for (const f of restartFields) {\n if (JSON.stringify(p[f]) !== JSON.stringify(n[f])) {\n changed.add(`${id}.${f}`)\n }\n }\n }\n return Array.from(changed).slice(0, 6) // cap to keep summary readable\n}\n\nasync function readBody(req: IncomingMessage): Promise<Record<string, unknown>> {\n return new Promise((resolve, reject) => {\n let body = \"\"\n req.on(\"data\", (chunk: Buffer) => (body += chunk.toString()))\n req.on(\"end\", () => {\n try {\n resolve(body ? JSON.parse(body) : {})\n } catch {\n resolve({})\n }\n })\n req.on(\"error\", reject)\n })\n}\n","import type { ChannelAdapter, IncomingMessage, OutgoingMessage } from \"./types\"\n\n// --- Discord adapter using discord.js ---\n// discord.js is an optional dependency.\n// Bot needs MESSAGE_CONTENT intent enabled in Discord Developer Portal.\n\nexport class DiscordAdapter implements ChannelAdapter {\n readonly name = \"discord\"\n private token: string\n private agentBinding?: string\n private handler?: (msg: IncomingMessage) => Promise<void>\n private client: any = null\n private log: (...args: unknown[]) => void\n\n constructor(\n config: { token: string; agentBinding?: string },\n log: (...args: unknown[]) => void = console.error.bind(console, \"[discord]\"),\n ) {\n this.token = config.token\n this.agentBinding = config.agentBinding\n this.log = log\n }\n\n onMessage(handler: (msg: IncomingMessage) => Promise<void>): void {\n this.handler = handler\n }\n\n async start(): Promise<void> {\n let Discord: any\n\n try {\n // @ts-ignore — optional dependency, not always installed\n Discord = await import(\"discord.js\")\n } catch {\n this.log(\"Discord requires discord.js. Install with:\")\n this.log(\" npm install discord.js\")\n return\n }\n\n const { Client, GatewayIntentBits } = Discord\n\n this.client = new Client({\n intents: [\n GatewayIntentBits.Guilds,\n GatewayIntentBits.GuildMessages,\n GatewayIntentBits.MessageContent,\n GatewayIntentBits.DirectMessages,\n ],\n })\n\n this.client.on(\"ready\", () => {\n this.log(`Discord connected as ${this.client.user?.tag}`)\n })\n\n this.client.on(\"messageCreate\", async (msg: any) => {\n if (!this.handler) return\n if (msg.author.bot) return\n\n // Check if bot is mentioned or it's a DM\n const isMentioned = msg.mentions.users.has(this.client.user?.id)\n const isDM = !msg.guild\n\n if (!isMentioned && !isDM) return\n\n // Strip bot mention from text\n let text = msg.content\n if (this.client.user) {\n text = text.replace(new RegExp(`<@!?${this.client.user.id}>`, \"g\"), \"\").trim()\n }\n\n if (!text) return\n\n const incoming: IncomingMessage = {\n id: msg.id,\n channel: \"discord\",\n accountId: \"default\",\n sender: {\n id: msg.author.id,\n name: msg.author.displayName || msg.author.username,\n username: msg.author.username,\n },\n group: msg.guild ? {\n id: msg.channelId,\n name: msg.channel?.name || msg.channelId,\n } : undefined,\n text,\n replyTo: msg.reference?.messageId,\n timestamp: msg.createdAt,\n raw: msg,\n }\n\n this.handler(incoming).catch((e: any) => {\n this.log(`Error handling message: ${e.message}`)\n })\n })\n\n try {\n await this.client.login(this.token)\n } catch (e: any) {\n this.log(`Discord login failed: ${e.message}`)\n }\n }\n\n async stop(): Promise<void> {\n if (this.client) {\n this.client.destroy()\n this.client = null\n }\n }\n\n async send(msg: OutgoingMessage): Promise<string> {\n if (!this.client) return \"\"\n try {\n const channel = await this.client.channels.fetch(msg.chatId)\n if (!channel?.isTextBased()) return \"\"\n\n const sent = await channel.send({\n content: msg.text,\n ...(msg.replyTo ? { reply: { messageReference: msg.replyTo } } : {}),\n })\n return sent.id\n } catch (e: any) {\n this.log(`Send error: ${e.message}`)\n return \"\"\n }\n }\n\n async editMessage(chatId: string, messageId: string, text: string): Promise<boolean> {\n if (!this.client) return false\n try {\n const channel = await this.client.channels.fetch(chatId)\n if (!channel?.isTextBased()) return false\n const message = await channel.messages.fetch(messageId)\n await message.edit(text)\n return true\n } catch { return false }\n }\n\n async sendTyping(chatId: string): Promise<void> {\n if (!this.client) return\n try {\n const channel = await this.client.channels.fetch(chatId)\n if (channel?.isTextBased()) await channel.sendTyping()\n } catch { /* best-effort */ }\n }\n\n async react(chatId: string, messageId: string, emoji: string = \"👀\"): Promise<void> {\n if (!this.client) return\n try {\n const channel = await this.client.channels.fetch(chatId)\n if (!channel?.isTextBased()) return\n const message = await channel.messages.fetch(messageId)\n await message.react(emoji)\n } catch { /* best-effort */ }\n }\n}\n","import type { ChannelAdapter, IncomingMessage, OutgoingMessage } from \"./types\"\n\n// --- Slack adapter (Socket Mode) ---\n//\n// Two optional deps — both peer-installed via `npm i @slack/socket-mode @slack/web-api`\n// when the operator enables this channel. Kept optional so agentx-cli stays\n// lean for installs that never touch Slack.\n//\n// Prerequisites on the Slack side:\n// - Create a Slack app at https://api.slack.com/apps\n// - Enable Socket Mode; generate an app-level token with `connections:write`\n// (starts with xapp-). Put it in .env as SLACK_APP_TOKEN.\n// - Install the bot to the workspace, grab the bot token (starts with xoxb-).\n// Put it in .env as SLACK_BOT_TOKEN.\n// - Scopes needed: chat:write, channels:history, groups:history,\n// im:history, mpim:history, reactions:write, app_mentions:read, users:read.\n// - Subscribe to events: app_mention, message.channels, message.im,\n// message.mpim, message.groups.\n//\n// Routing: DMs always route (no mention required). In channels/groups, the\n// bot has to be @-mentioned — same policy as Discord, matches our \"mention-\n// required in groups\" default.\n\nexport class SlackAdapter implements ChannelAdapter {\n readonly name = \"slack\"\n private botToken: string\n private appToken: string\n private agentBinding?: string\n private handler?: (msg: IncomingMessage) => Promise<void>\n private socket: any = null\n private web: any = null\n private botUserId?: string\n private log: (...args: unknown[]) => void\n\n constructor(\n config: { botToken: string; appToken: string; agentBinding?: string },\n log: (...args: unknown[]) => void = console.error.bind(console, \"[slack]\"),\n ) {\n this.botToken = config.botToken\n this.appToken = config.appToken\n this.agentBinding = config.agentBinding\n this.log = log\n }\n\n onMessage(handler: (msg: IncomingMessage) => Promise<void>): void {\n this.handler = handler\n }\n\n async start(): Promise<void> {\n let SocketModeMod: any\n let WebApiMod: any\n try {\n // @ts-ignore — optional deps\n SocketModeMod = await import(\"@slack/socket-mode\")\n // @ts-ignore\n WebApiMod = await import(\"@slack/web-api\")\n } catch {\n this.log(\"Slack requires @slack/socket-mode and @slack/web-api. Install with:\")\n this.log(\" npm install @slack/socket-mode @slack/web-api\")\n return\n }\n\n const { SocketModeClient } = SocketModeMod\n const { WebClient } = WebApiMod\n\n this.web = new WebClient(this.botToken)\n\n // Resolve our own bot user id once so we can detect mentions + skip self-posts.\n try {\n const authResult = await this.web.auth.test()\n this.botUserId = authResult.user_id\n this.log(`Slack connected as @${authResult.user} (${this.botUserId}) in team ${authResult.team}`)\n } catch (e: any) {\n this.log(`Slack auth.test failed: ${e.message}`)\n return\n }\n\n this.socket = new SocketModeClient({ appToken: this.appToken })\n\n // Socket Mode emits generic \"message\" and \"app_mention\" events. We handle\n // both so direct mentions still fire even if the workspace dropped the\n // message-channels subscription.\n const handle = async (payload: any, ack: () => Promise<void>) => {\n try { await ack() } catch { /* */ }\n if (!this.handler) return\n const event = payload.event\n if (!event || typeof event.text !== \"string\") return\n // Skip our own bot posts and any message that carries a bot_id (prevents\n // bot-on-bot cascades within a workspace).\n if (event.bot_id || event.user === this.botUserId || event.subtype === \"bot_message\") return\n\n const isDM = event.channel_type === \"im\"\n const isMentioned = this.botUserId && event.text.includes(`<@${this.botUserId}>`)\n if (!isDM && !isMentioned) return\n\n // Strip our bot mention from the text so the agent receives a clean prompt.\n let text = event.text\n if (this.botUserId) {\n text = text.replace(new RegExp(`<@${this.botUserId}>`, \"g\"), \"\").trim()\n }\n if (!text) return\n\n // Resolve the sender's display name (best-effort; fall back to the raw id).\n let senderName = event.user || \"slack-user\"\n try {\n const info = await this.web.users.info({ user: event.user })\n senderName = info?.user?.profile?.display_name || info?.user?.real_name || senderName\n } catch { /* */ }\n\n const incoming: IncomingMessage = {\n id: event.client_msg_id || event.ts,\n channel: \"slack\",\n accountId: \"default\",\n sender: {\n id: event.user,\n name: senderName,\n username: senderName,\n },\n group: !isDM ? { id: event.channel, name: event.channel } : undefined,\n text,\n replyTo: event.thread_ts && event.thread_ts !== event.ts ? event.thread_ts : undefined,\n timestamp: event.ts ? new Date(parseFloat(event.ts) * 1000) : new Date(),\n raw: event,\n }\n try { await this.handler(incoming) }\n catch (e: any) { this.log(`Error handling message: ${e.message}`) }\n }\n\n this.socket.on(\"app_mention\", handle)\n this.socket.on(\"message\", handle)\n this.socket.on(\"error\", (e: any) => this.log(`Slack socket error: ${e?.message || e}`))\n\n try {\n await this.socket.start()\n } catch (e: any) {\n this.log(`Slack socket start failed: ${e.message}`)\n }\n }\n\n async stop(): Promise<void> {\n if (this.socket) {\n try { await this.socket.disconnect() } catch { /* */ }\n this.socket = null\n }\n this.web = null\n }\n\n async send(msg: OutgoingMessage): Promise<string> {\n if (!this.web) return \"\"\n try {\n const r = await this.web.chat.postMessage({\n channel: msg.chatId,\n text: msg.text,\n thread_ts: msg.replyTo,\n // Slack's mrkdwn is closest to our default. Plain keeps literal braces\n // and code fences without escaping.\n mrkdwn: msg.parseMode !== \"plain\",\n })\n return r.ts as string\n } catch (e: any) {\n this.log(`Send error: ${e.message}`)\n return \"\"\n }\n }\n\n async editMessage(chatId: string, messageId: string, text: string): Promise<boolean> {\n if (!this.web) return false\n try {\n await this.web.chat.update({ channel: chatId, ts: messageId, text })\n return true\n } catch (e: any) {\n this.log(`Edit error: ${e.message}`)\n return false\n }\n }\n\n async react(chatId: string, messageId: string, emoji: string = \"eyes\"): Promise<void> {\n if (!this.web) return\n try {\n const name = emoji.replace(/^:|:$/g, \"\")\n await this.web.reactions.add({ channel: chatId, timestamp: messageId, name })\n } catch { /* best-effort — the reaction emoji might already be set */ }\n }\n}\n","import type { ChannelAdapter, IncomingMessage, OutgoingMessage, ChannelMeta, SeededMessage } from \"./types\"\nimport { createServer, type IncomingMessage as HttpRequest, type ServerResponse } from \"http\"\nimport { debug } from \"@/observability/debug\"\nimport type { HookRegistry } from \"@/hooks\"\nimport { markBody, detectAgentxMarker, stripAgentxMarkers } from \"./outbound-marker\"\nimport { getLedgerMode } from \"@/intent/mode\"\nimport { getDefaultLedger } from \"@/intent/instance\"\nimport { recordGitLabTargetDispatch, recordGitLabNoteDispatch, recordGitLabIssueLevelDecision } from \"@/intent/sources/gitlab\"\n\n// --- GitLab webhook channel adapter ---\n//\n// Receives GitLab webhook events (note/comment, issue, MR, push, pipeline)\n// and routes @mention comments to agents. Agents reply as GitLab API comments.\n//\n// Config:\n// channels.gitlab:\n// enabled: true\n// webhookPort: 18810 # separate port for GitLab webhooks\n// webhookSecret: \"secret\" # validates X-Gitlab-Token header\n// host: \"https://gitlab.example.com\"\n// token: \"${GITLAB_TOKEN}\" # for posting comments back\n// routes:\n// - project: \"org/my-project\"\n// agent: \"pm-agent\"\n// - project: \"*\"\n// agent: \"default-agent\" # default for unmatched projects\n\nexport interface GitLabRoute {\n project: string // \"group/project\" or \"*\" for default\n agent: string\n}\n\nexport interface GitLabAgentMapping {\n agentId: string\n gitlabUsernames: string[] // GitLab @usernames that map to this agent\n keywords: string[] // keywords in comments that trigger this agent (e.g. \"coder\", \"devops\")\n token?: string // per-agent GitLab PAT — posts comments as this agent's user\n node?: string // if set, forward to this mesh peer instead of handling locally\n}\n\nexport interface GitLabChannelConfig {\n webhookPort: number\n webhookSecret?: string\n host: string\n token: string\n routes: GitLabRoute[]\n agentMappings?: GitLabAgentMapping[] // @mention -> agent mappings\n /** All known agent ids in this daemon — passed from the outer config by\n * the daemon at construction time. Used to auto-derive default GitLab\n * username mappings so operators don't have to hand-register every agent\n * for @mentions to work. Explicit entries in `agentMappings` always take\n * precedence (they carry per-agent tokens, non-standard usernames, etc.)\n * The auto-derived defaults use the convention `{agentId, noqta-<agentId>}`\n * which mirrors the existing hand-maintained rows (pm-mtgl, atlas, ...).\n *\n * Removal: when an agent is deleted from agents.<id>, its default mapping\n * disappears on next daemon restart. */\n knownAgentIds?: string[]\n}\n\ninterface GitLabNoteEvent {\n object_kind: \"note\"\n event_type: string\n user: { name: string; username: string }\n project: { path_with_namespace: string; web_url: string }\n object_attributes: {\n id: number\n note: string\n noteable_type: string // \"Issue\", \"MergeRequest\", \"Commit\"\n url: string\n }\n issue?: { iid: number; title: string; state: string }\n merge_request?: { iid: number; title: string; state: string; source_branch: string; target_branch: string }\n}\n\ninterface GitLabIssueEvent {\n object_kind: \"issue\"\n user: { name: string; username: string }\n project: { path_with_namespace: string }\n object_attributes: {\n iid: number\n title: string\n description: string\n state: string\n action: string\n url: string\n assignee_ids?: number[]\n }\n /** Top-level current assignees (GitLab webhook payload). Each carries\n * username + name + id. Present on create, update, and reopen. Optional\n * in the type because some older payloads only carry assignee_ids. */\n assignees?: Array<{ id: number; name?: string; username: string }>\n /** Diff payload — present on `update` actions. We use changes.assignees\n * to detect assignment-add events (a username appears in current but\n * not previous). */\n changes?: {\n assignees?: { previous?: Array<{ id: number; username: string }>; current?: Array<{ id: number; username: string }> }\n }\n}\n\ninterface GitLabMREvent {\n object_kind: \"merge_request\"\n user: { name: string; username: string }\n project: { path_with_namespace: string }\n object_attributes: {\n iid: number\n title: string\n description: string\n state: string\n action: string\n source_branch: string\n target_branch: string\n url: string\n assignee_ids?: number[]\n reviewer_ids?: number[]\n }\n assignees?: Array<{ id: number; name?: string; username: string }>\n reviewers?: Array<{ id: number; name?: string; username: string }>\n changes?: {\n assignees?: { previous?: Array<{ id: number; username: string }>; current?: Array<{ id: number; username: string }> }\n reviewers?: { previous?: Array<{ id: number; username: string }>; current?: Array<{ id: number; username: string }> }\n }\n}\n\ninterface GitLabPipelineEvent {\n object_kind: \"pipeline\"\n user: { name: string; username: string }\n project: { path_with_namespace: string }\n object_attributes: {\n id: number\n ref: string\n status: string\n duration: number\n }\n}\n\ntype GitLabEvent = GitLabNoteEvent | GitLabIssueEvent | GitLabMREvent | GitLabPipelineEvent | Record<string, unknown>\n\nexport class GitLabAdapter implements ChannelAdapter {\n readonly name = \"gitlab\"\n private config: GitLabChannelConfig\n private handler?: (msg: IncomingMessage) => Promise<void>\n private server?: ReturnType<typeof createServer>\n private botUsername?: string // resolved on first API call\n private botUsernames: Set<string> = new Set() // all known bot users (to prevent cascading)\n private sentNoteIds: Set<string> = new Set() // track our own comments\n /** Maps actual GitLab username -> agentId (resolved from tokens at startup) */\n private usernameToAgent: Map<string, string> = new Map()\n /** Maps agentId -> actual GitLab username (resolved from tokens at startup) */\n private agentToUsername: Map<string, string> = new Map()\n /** Per-event dedup window for handleIssue/handleMR. Key:\n * `{project}:{kind}:{iid}:{agentId}:{action}:{trigger}`. Value:\n * expiresAtMs. Pruned lazily on each access. 5-min TTL covers GitLab's\n * webhook-retry window without keeping stale entries forever. */\n private dispatchedTargets: Map<string, number> = new Map()\n private readonly DISPATCH_TTL_MS = 5 * 60 * 1000\n private log: (...args: unknown[]) => void\n private hooks?: HookRegistry\n private reactForwarder?: (node: string, project: string, noteableType: string, noteableIid: string, noteId: number, agentId: string) => Promise<void>\n private sendNoteForwarder?: (node: string, project: string, noteableType: string, noteableIid: string, agentId: string, text: string) => Promise<string>\n private logTimeForwarder?: (node: string, project: string, noteableType: string, noteableIid: string, agentId: string, durationMs: number) => Promise<void>\n private createIssueForwarder?: (node: string, project: string, title: string, description: string, labels: string[], assignees: string[], agentId: string) => Promise<{ iid: number; url: string } | null>\n private setLabelsForwarder?: (node: string, project: string, kind: \"issue\" | \"merge_request\", iid: string, add: string[], remove: string[], agentId: string) => Promise<string[] | null>\n\n constructor(config: GitLabChannelConfig, log: (...args: unknown[]) => void = console.error.bind(console, \"[gitlab]\"), hooks?: HookRegistry) {\n this.config = config\n this.log = log\n this.hooks = hooks\n }\n\n setReactForwarder(fn: (node: string, project: string, noteableType: string, noteableIid: string, noteId: number, agentId: string) => Promise<void>): void {\n this.reactForwarder = fn\n }\n\n setSendNoteForwarder(fn: (node: string, project: string, noteableType: string, noteableIid: string, agentId: string, text: string) => Promise<string>): void {\n this.sendNoteForwarder = fn\n }\n\n setLogTimeForwarder(fn: (node: string, project: string, noteableType: string, noteableIid: string, agentId: string, durationMs: number) => Promise<void>): void {\n this.logTimeForwarder = fn\n }\n\n setCreateIssueForwarder(fn: (node: string, project: string, title: string, description: string, labels: string[], assignees: string[], agentId: string) => Promise<{ iid: number; url: string } | null>): void {\n this.createIssueForwarder = fn\n }\n\n setSetLabelsForwarder(fn: (node: string, project: string, kind: \"issue\" | \"merge_request\", iid: string, add: string[], remove: string[], agentId: string) => Promise<string[] | null>): void {\n this.setLabelsForwarder = fn\n }\n\n onMessage(handler: (msg: IncomingMessage) => Promise<void>): void {\n this.handler = handler\n }\n\n async start(): Promise<void> {\n // Resolve bot usernames from ALL tokens (global + per-agent)\n // This is critical for cascade prevention — we must know every username\n // that posts on behalf of an agent.\n const tokensToResolve: Array<{ label: string; token: string }> = []\n\n if (this.config.token) {\n tokensToResolve.push({ label: \"global\", token: this.config.token })\n }\n for (const mapping of this.config.agentMappings || []) {\n if (mapping.token) {\n tokensToResolve.push({ label: mapping.agentId, token: mapping.token })\n }\n }\n\n // Resolve usernames from all tokens in parallel\n const resolutions = await Promise.allSettled(\n tokensToResolve.map(async ({ label, token }) => {\n const res = await fetch(`${this.config.host}/api/v4/user`, {\n headers: { \"PRIVATE-TOKEN\": token },\n })\n if (!res.ok) throw new Error(`${res.status}`)\n const data = await res.json() as any\n return { label, username: data.username as string }\n })\n )\n\n for (const result of resolutions) {\n if (result.status === \"fulfilled\") {\n const { label, username } = result.value\n this.botUsernames.add(username)\n if (label === \"global\") {\n this.botUsername = username\n this.log(`Global bot user: ${username}`)\n } else {\n // Map actual username <-> agentId (authoritative — from API, not config)\n this.usernameToAgent.set(username.toLowerCase(), label)\n this.agentToUsername.set(label, username.toLowerCase())\n this.log(`Agent \"${label}\" -> GitLab user @${username}`)\n }\n }\n }\n\n // Also register configured gitlabUsernames for cascade prevention\n // AND as fallback mention matching (if token resolution failed)\n for (const mapping of this.config.agentMappings || []) {\n for (const username of mapping.gitlabUsernames) {\n this.botUsernames.add(username)\n // Remote-routed mappings (node property) take explicit priority over token resolution.\n // Token resolution is authoritative only for local agents.\n const alreadySet = this.usernameToAgent.has(username.toLowerCase())\n if (!alreadySet || mapping.node) {\n this.usernameToAgent.set(username.toLowerCase(), mapping.agentId)\n }\n }\n // Fallback: if token resolution didn't set agentToUsername, use first configured username\n if (!this.agentToUsername.has(mapping.agentId) && mapping.gitlabUsernames.length > 0) {\n this.agentToUsername.set(mapping.agentId, mapping.gitlabUsernames[0].toLowerCase())\n }\n }\n\n // Auto-derived defaults: every agent in the daemon that doesn't have an\n // explicit `agentMappings` row gets a default entry so @-mentions route\n // without operators hand-maintaining a parallel list.\n //\n // Convention: `@<agentId>` and `@noqta-<agentId>` both route to the\n // agent — mirrors existing hand-maintained rows (pm-mtgl → [pm-mtgl,\n // noqta-pm-mtgl], atlas → [atlas, noqta-atlas], ...). Author explicit\n // entries in agentMappings when an agent needs a per-agent token or a\n // non-standard username.\n const explicitAgentIds = new Set((this.config.agentMappings ?? []).map((m) => m.agentId))\n const autoMapped: string[] = []\n for (const agentId of this.config.knownAgentIds ?? []) {\n if (explicitAgentIds.has(agentId)) continue\n // Skip internal/utility ids that aren't actual agents in the GitLab\n // sense (e.g. \"graph-agent\" only ever talks on the a2a mesh).\n const defaultUsernames = [agentId, `noqta-${agentId}`]\n for (const username of defaultUsernames) {\n this.botUsernames.add(username)\n if (!this.usernameToAgent.has(username.toLowerCase())) {\n this.usernameToAgent.set(username.toLowerCase(), agentId)\n }\n }\n if (!this.agentToUsername.has(agentId)) {\n this.agentToUsername.set(agentId, agentId.toLowerCase())\n }\n autoMapped.push(agentId)\n }\n\n this.log(`Bot users (${this.botUsernames.size}): ${[...this.botUsernames].join(\", \")}`)\n this.log(`Username->Agent map: ${[...this.usernameToAgent.entries()].map(([u, a]) => `@${u}->${a}`).join(\", \")}`)\n if (autoMapped.length > 0) {\n this.log(`GitLab auto-derived defaults for ${autoMapped.length} agent(s): ${autoMapped.join(\", \")} (override by adding an agentMappings entry)`)\n }\n\n this.server = createServer(async (req, res) => {\n if (req.method === \"POST\") {\n await this.handleWebhook(req, res)\n } else {\n res.writeHead(200, { \"Content-Type\": \"text/plain\" })\n res.end(\"GitLab webhook endpoint. POST events here.\")\n }\n })\n\n this.server.on(\"error\", (err: any) => {\n if (err.code === \"EADDRINUSE\") {\n this.log(`GitLab webhook port ${this.config.webhookPort} in use, retrying in 5s...`)\n setTimeout(() => {\n this.server?.close()\n this.server?.listen(this.config.webhookPort)\n }, 5000)\n } else {\n this.log(`GitLab webhook server error: ${err.message}`)\n }\n })\n\n this.server.listen(this.config.webhookPort, () => {\n this.log(`GitLab webhook listening on :${this.config.webhookPort}`)\n })\n }\n\n async stop(): Promise<void> {\n if (this.server) this.server.close()\n }\n\n /**\n * Send a reply — posts a comment back to GitLab via API.\n * chatId format: \"project:noteable_type:iid\" (e.g. \"org/project:issue:123\")\n */\n async send(msg: OutgoingMessage): Promise<string> {\n // chatId format: \"group/project:type:iid\" — split from the end\n const parts = msg.chatId.split(\":\")\n if (parts.length < 3) {\n this.log(`Invalid chatId for GitLab reply: ${msg.chatId}`)\n return \"\"\n }\n const iid = parts.pop()!\n const noteableType = parts.pop()!\n const project = parts.join(\":\") // rejoin in case project path had colons\n\n const encodedProject = encodeURIComponent(project)\n let endpoint: string\n\n switch (noteableType) {\n case \"issue\":\n endpoint = `${this.config.host}/api/v4/projects/${encodedProject}/issues/${iid}/notes`\n break\n case \"merge_request\":\n endpoint = `${this.config.host}/api/v4/projects/${encodedProject}/merge_requests/${iid}/notes`\n break\n default:\n this.log(`Unsupported noteable type: ${noteableType}`)\n return \"\"\n }\n\n // Identity rules — posts must go out under the agent's own GitLab user,\n // never under the shared group-access-token user (e.g. @group_<id>_bot_*):\n //\n // 1. If the agent has a per-agent token configured locally → use it.\n // 2. Else if the agent lives on a remote peer (mapping.node) and a\n // sendNoteForwarder is wired → forward the post there. The peer uses\n // its own local token (owned by that agent's GitLab user) to POST.\n // 3. Else fall back to the global token (signed content from a generic\n // shared identity — legacy path, only reached for local agents with\n // no token mapping).\n const mapping = this.config.agentMappings?.find((m) => m.agentId === msg.agentId)\n const agentToken = this.getAgentToken(msg.agentId)\n if (!agentToken && mapping?.node && this.sendNoteForwarder) {\n try {\n const body = markBody(msg.text, msg.agentId || \"unknown\")\n const noteId = await this.sendNoteForwarder(mapping.node, project, noteableType, iid, msg.agentId || \"\", body)\n if (noteId) this.sentNoteIds.add(noteId)\n return noteId\n } catch (e: any) {\n this.log(`GitLab send forward to \"${mapping.node}\" failed: ${e.message} — skipping (would post as group bot)`)\n return \"\"\n }\n }\n\n const token = agentToken || this.config.token\n debug.webhook(\"gitlab\", \"send\", `agentId=\"${msg.agentId}\" token=${agentToken ? \"per-agent\" : \"GLOBAL(\" + this.botUsername + \")\"}`)\n\n try {\n const res = await fetch(endpoint, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"PRIVATE-TOKEN\": token,\n },\n // Append hidden signature so we can detect our own comments on webhook\n body: JSON.stringify({ body: markBody(msg.text, msg.agentId || \"unknown\") }),\n })\n\n if (!res.ok) {\n const text = await res.text()\n this.log(`GitLab API error: ${res.status} ${text}`)\n return \"\"\n }\n\n const data = await res.json() as any\n const noteId = String(data.id || \"\")\n if (noteId) this.sentNoteIds.add(noteId)\n return noteId\n } catch (e: any) {\n this.log(`GitLab send error: ${e.message}`)\n return \"\"\n }\n }\n\n // --- Webhook handling ---\n\n private async handleWebhook(req: HttpRequest, res: ServerResponse): Promise<void> {\n // Validate secret\n if (this.config.webhookSecret) {\n const token = req.headers[\"x-gitlab-token\"]\n if (token !== this.config.webhookSecret) {\n res.writeHead(401, { \"Content-Type\": \"application/json\" })\n res.end(JSON.stringify({ error: \"Invalid token\" }))\n return\n }\n }\n\n const body = await this.readBody(req)\n const event = body as GitLabEvent\n const objectKind = ((event as any).object_kind || (event as any).event_type || \"unknown\") as string\n\n this.log(`Event: ${objectKind} from ${(event as any).project?.path_with_namespace || \"unknown\"}`)\n\n // Route based on event type\n switch (objectKind) {\n case \"note\":\n await this.handleNote(event as GitLabNoteEvent, res)\n break\n case \"issue\":\n await this.handleIssue(event as GitLabIssueEvent, res)\n break\n case \"merge_request\":\n await this.handleMR(event as GitLabMREvent, res)\n break\n case \"pipeline\":\n await this.handlePipeline(event as GitLabPipelineEvent, res)\n break\n default:\n this.log(`Unhandled event: ${objectKind}`)\n res.writeHead(200)\n res.end(\"ok\")\n }\n }\n\n /**\n * Handle note/comment events — the primary use case.\n * Routes @mention comments to the right agent.\n */\n private async handleNote(event: GitLabNoteEvent, res: ServerResponse): Promise<void> {\n if (!this.handler) { res.writeHead(200); res.end(\"ok\"); return }\n\n const note = event.object_attributes.note\n const project = event.project.path_with_namespace\n const user = event.user\n const noteId = String(event.object_attributes.id)\n\n // PRIMARY CASCADE PREVENTION: Check for AgentX signature.\n // Every comment posted by AgentX has <!-- agentx:AGENT_ID --> appended.\n // This is the most reliable check — immune to race conditions and\n // username misconfiguration.\n const sourceAgent = detectAgentxMarker(note)\n if (sourceAgent) {\n // Allow bot-to-bot handoff: if an agent's comment @mentions a DIFFERENT agent\n const mentions = note.match(/@(\\w[\\w.-]*)/g)?.map(m => m.slice(1).replace(/[.]+$/, \"\")) || []\n const mentionsDifferentAgent = mentions.some(m => {\n const targetAgent = this.usernameToAgent.get(m.toLowerCase())\n return targetAgent && targetAgent !== sourceAgent\n })\n if (!mentionsDifferentAgent) {\n this.log(`AgentX comment from ${sourceAgent}, no cross-agent mention, skipping (note ${noteId})`)\n res.writeHead(200); res.end(\"ok\"); return\n }\n this.log(`Bot-to-bot handoff: ${sourceAgent} → ${mentions.join(\", \")} (note ${noteId})`)\n }\n\n // SECONDARY: Also check sentNoteIds (catches race with signature)\n if (this.sentNoteIds.has(noteId)) {\n this.sentNoteIds.delete(noteId)\n res.writeHead(200); res.end(\"ok\"); return\n }\n\n // TERTIARY: Skip comments from known bot users that have no @mentions\n // (catches comments posted via CLI tools without the signature)\n const mentions = note.match(/@(\\w[\\w.-]*)/g)?.map(m => m.slice(1).replace(/[.]+$/, \"\")) || []\n if (mentions.length === 0) {\n this.log(`No @mention in note ${noteId}, skipping`)\n res.writeHead(200); res.end(\"ok\"); return\n }\n\n if (this.isBotUser(user.username) && !sourceAgent) {\n this.log(`Bot comment from ${user.username} without signature, skipping (note ${noteId})`)\n res.writeHead(200); res.end(\"ok\"); return\n }\n\n // Determine the noteable context\n let noteableType = \"\"\n let noteableIid = \"\"\n let noteableTitle = \"\"\n if (event.issue) {\n noteableType = \"issue\"\n noteableIid = String(event.issue.iid)\n noteableTitle = event.issue.title\n } else if (event.merge_request) {\n noteableType = \"merge_request\"\n noteableIid = String(event.merge_request.iid)\n noteableTitle = event.merge_request.title\n }\n\n // Resolve agent deterministically from GitLab @mention -> usernameToAgent map.\n debug.webhook(\"gitlab\", \"handleNote\", `noteId=${noteId} mentions=[${mentions.join(\",\")}] user=${user.username}`)\n\n const resolvedAgentId = this.resolveAgentFromMention(note)\n debug.webhook(\"gitlab\", \"resolve\", `agentId=${resolvedAgentId ?? \"NONE\"}`)\n\n if (!resolvedAgentId) {\n this.log(`[handleNote] @mentions in note ${noteId} are not agents (${mentions.join(\", \")}), skipping`)\n // Phase 1 commit 6.a-extended (note path): record the halt — this\n // is a real dispatch decision (@mention names that don't resolve),\n // distinct from the cascade-prevention early returns above.\n if (getLedgerMode(\"gitlab\") !== \"off\") {\n try {\n recordGitLabNoteDispatch(\n getDefaultLedger(),\n {\n noteId, project,\n noteableType, noteableIid,\n mentions: mentions.map((m) => m.toLowerCase()),\n },\n JSON.stringify(event),\n { agentId: null, outcome: \"halted\", reason: `unresolved-mentions: ${mentions.join(\",\")}` },\n )\n } catch (e: any) {\n this.log(`[ledger] gitlab note ${noteId} (no-resolve) record failed: ${e?.message ?? e}`)\n }\n }\n res.writeHead(200); res.end(\"ok\"); return\n }\n\n const targetAgentId = resolvedAgentId\n\n // React with 👀 using the RESOLVED agent's own token (deterministic identity)\n const agentMapping = this.config.agentMappings?.find(m => m.agentId === targetAgentId)\n const agentToken = agentMapping?.token\n const agentNode = (agentMapping as any)?.node as string | undefined\n debug.webhook(\"gitlab\", \"token\", `agent=\"${targetAgentId}\" mapping=${agentMapping ? \"found\" : \"MISSING\"} hasToken=${!!agentToken} node=${agentNode ?? \"local\"}`)\n this.reactToNote(project, noteableType, noteableIid, event.object_attributes.id, agentToken, agentNode, targetAgentId).catch(() => {})\n\n const chatId = `${project}:${noteableType}:${noteableIid}`\n\n const channelMeta = await this.getChannelMeta(chatId)\n\n // Download any images attached in the comment\n const noteClean = stripAgentxMarkers(note)\n const imageAttachment = await this.downloadNoteImages(noteClean, project, agentToken || this.config.token)\n\n // Phase 1 / 6 — record the note dispatch first so we can tag the\n // IncomingMessage with intentRef for resolution writes.\n let intentRef: { eventId: string; decidedBy: string } | undefined\n if (getLedgerMode(\"gitlab\") !== \"off\") {\n try {\n const decision = recordGitLabNoteDispatch(\n getDefaultLedger(),\n {\n noteId, project,\n noteableType, noteableIid,\n mentions: mentions.map((m) => m.toLowerCase()),\n },\n JSON.stringify(event),\n { agentId: targetAgentId, outcome: \"dispatched\", reason: `mention:${mentions[0]}` },\n )\n if (decision.outcome === \"dispatched\") {\n intentRef = { eventId: decision.eventId, decidedBy: decision.decidedBy }\n }\n } catch (e: any) {\n this.log(`[ledger] gitlab note ${noteId} record failed: ${e?.message ?? e}`)\n }\n }\n\n const incoming: IncomingMessage = {\n id: String(event.object_attributes.id),\n channel: \"gitlab\",\n accountId: \"default\",\n sender: {\n id: chatId,\n name: user.name,\n username: user.username,\n },\n text: `[GitLab ${project} ${noteableType} #${noteableIid}: ${noteableTitle}]\\n${user.name} commented:\\n${noteClean}`,\n timestamp: new Date(),\n raw: event,\n resolvedAgent: targetAgentId,\n preferNode: agentMapping?.node,\n channelMeta: channelMeta ? { ...channelMeta, issue: { type: noteableType, iid: noteableIid, title: noteableTitle } } : undefined,\n media: imageAttachment,\n intentRef,\n }\n\n this.handler(incoming).catch((e) => {\n this.log(`Error handling note: ${e.message}`)\n })\n\n res.writeHead(200)\n res.end(\"ok\")\n }\n\n /**\n * Handle issue events (opened, updated, closed).\n * Only routes events from human users, not bot-triggered updates.\n */\n private async handleIssue(event: GitLabIssueEvent, res: ServerResponse): Promise<void> {\n if (!this.handler) { res.writeHead(200); res.end(\"ok\"); return }\n\n const attrs = event.object_attributes\n const project = event.project.path_with_namespace\n const defaultAgentId = this.resolveAgent(project)\n\n // Bot-authored issue events (label changes, assignments by agents) skip\n // the DEFAULT agent dispatch path to prevent cascade loops — but still\n // fire the `on:gitlab-issue` hook below so workflow subscribers and\n // custom hook handlers can opt in to them. This matters for BPM-style\n // workflows (gitlab-sdlc-loop, etc.) that want to react to every label\n // transition, including bot-driven ones.\n const authoredByBot = this.isBotUser(event.user.username)\n if (authoredByBot) {\n this.log(`Issue #${attrs.iid} authored by bot \"${event.user.username}\" — default dispatch suppressed (hooks still fire)`)\n }\n\n // Fire `on:gitlab-issue` hook so projects can customize routing — e.g.\n // trigger a specific agent on assignment rather than the project default.\n // The hook may:\n // - return `blocked: true` to suppress the default dispatch\n // - return `modified.dispatch: Array<{ agentId, preferNode?, prompt? }>`\n // to dispatch to one or more specific agents (skips default)\n // - return nothing, letting the default project-agent behavior run\n // The event body stays inside the context so hook scripts have full detail\n // (changes.assignees, attrs.assignees, labels, etc.) without us re-parsing.\n let dispatch: Array<{ agentId: string; preferNode?: string; prompt?: string; assignee?: string }> | undefined\n let hookBlocked = false\n if (this.hooks?.has(\"on:gitlab-issue\" as any)) {\n try {\n const result = await this.hooks.execute(\"on:gitlab-issue\" as any, {\n event: \"on:gitlab-issue\" as any,\n issueEvent: event,\n project,\n iid: attrs.iid,\n title: attrs.title,\n description: attrs.description || \"\",\n url: attrs.url,\n action: attrs.action,\n usernameToAgent: Object.fromEntries(this.usernameToAgent.entries()),\n agentMappings: this.config.agentMappings || [],\n defaultAgentId: defaultAgentId || null,\n })\n if (result.blocked) {\n hookBlocked = true\n }\n const modDispatch = (result.modified as any)?.dispatch\n if (Array.isArray(modDispatch) && modDispatch.length > 0) {\n dispatch = modDispatch.filter((d: any) => d && typeof d.agentId === \"string\")\n }\n } catch (e: any) {\n this.log(`on:gitlab-issue hook error: ${e.message}`)\n }\n }\n\n // Hook-driven dispatch: route one IncomingMessage per dispatch entry.\n if (dispatch && dispatch.length > 0) {\n const ledgerEnabled = getLedgerMode(\"gitlab\") !== \"off\"\n const issueProjectionForHook = ledgerEnabled\n ? {\n entityKind: \"issue\" as const,\n project,\n iid: attrs.iid,\n action: attrs.action,\n title: attrs.title,\n description: attrs.description,\n url: attrs.url,\n }\n : null\n const eventJsonForHook = ledgerEnabled ? JSON.stringify(event) : \"\"\n\n for (const d of dispatch) {\n const mapping = this.config.agentMappings?.find((m) => m.agentId === d.agentId)\n const chatId = `${project}:issue:${attrs.iid}`\n const channelMeta = await this.getChannelMeta(chatId)\n const id = `issue-hook-${attrs.iid}-${d.agentId}${d.assignee ? `-${d.assignee}` : \"\"}-${attrs.action}`\n\n // Phase 1 / 6 — record per-target hook dispatch first so we can\n // tag the IncomingMessage with intentRef for resolution writes\n // when the agent task completes.\n let intentRef: { eventId: string; decidedBy: string } | undefined\n if (issueProjectionForHook) {\n try {\n const decision = recordGitLabTargetDispatch(\n getDefaultLedger(),\n issueProjectionForHook,\n { agentId: d.agentId, trigger: \"hook-dispatch\" },\n eventJsonForHook,\n { agentId: d.agentId, outcome: \"dispatched\", reason: d.preferNode ? `hook (node:${d.preferNode})` : \"hook\" },\n )\n if (decision.outcome === \"dispatched\") {\n intentRef = { eventId: decision.eventId, decidedBy: decision.decidedBy }\n }\n } catch (e: any) {\n this.log(`[ledger] gitlab issue #${attrs.iid} hook-dispatch ${d.agentId} record failed: ${e?.message ?? e}`)\n }\n }\n\n const incoming: IncomingMessage = {\n id,\n channel: \"gitlab\",\n accountId: \"default\",\n sender: {\n id: chatId,\n name: event.user.name,\n username: event.user.username,\n },\n text: d.prompt || `[GitLab ${project} Issue #${attrs.iid} ${attrs.action}]: ${attrs.title}\\n${attrs.description?.slice(0, 500) || \"\"}\\nURL: ${attrs.url}`,\n timestamp: new Date(),\n raw: event,\n resolvedAgent: d.agentId,\n preferNode: d.preferNode || mapping?.node,\n channelMeta: channelMeta ? {\n ...channelMeta,\n issue: { type: \"issue\", iid: String(attrs.iid), title: attrs.title },\n } : undefined,\n intentRef,\n }\n this.log(`Issue #${attrs.iid} hook dispatch -> agent \"${d.agentId}\"${d.preferNode || mapping?.node ? ` (remote: ${d.preferNode || mapping?.node})` : \"\"}`)\n this.handler(incoming).catch((e) => this.log(`Error handling hook dispatch: ${e.message}`))\n }\n res.writeHead(200); res.end(\"ok\"); return\n }\n\n if (hookBlocked) {\n this.log(`Issue #${attrs.iid}: on:gitlab-issue hook suppressed default dispatch`)\n // Phase 1 commit 6.a-extended (hook path): record the hook-blocked\n // halt as an issue-level decision (no specific target). Distinct\n // from per-target halts because no agent was even considered.\n if (getLedgerMode(\"gitlab\") !== \"off\") {\n try {\n recordGitLabIssueLevelDecision(\n getDefaultLedger(),\n {\n entityKind: \"issue\",\n project,\n iid: attrs.iid,\n action: attrs.action,\n title: attrs.title,\n description: attrs.description,\n url: attrs.url,\n },\n \"hook-blocked\",\n \"gitlab:issue:hook\",\n JSON.stringify(event),\n { agentId: null, outcome: \"halted\", reason: \"on:gitlab-issue hook returned blocked:true\" },\n )\n } catch (e: any) {\n this.log(`[ledger] gitlab issue #${attrs.iid} hook-blocked record failed: ${e?.message ?? e}`)\n }\n }\n res.writeHead(200); res.end(\"ok\"); return\n }\n\n // Bot-authored events: hook(s) above already ran; we stop before default\n // agent dispatch to prevent cascade loops.\n if (authoredByBot) { res.writeHead(200); res.end(\"ok\"); return }\n\n // Build a deterministic target set: mentions in description ∪ current\n // assignees mapped to agents ∪ project default route. Each unique agent\n // gets exactly one IncomingMessage. Replaces the prior \"default route only\"\n // behavior so an issue assigned to an agent always gets that agent\n // engaged — matching how comments resolve via @mention. The\n // `on:gitlab-issue` hook above can still fully override or block.\n const targets = this.computeIssueTargets(event, defaultAgentId)\n if (targets.length === 0) {\n this.log(`Issue #${attrs.iid}: no agent target (no mention, no agent assignee, no default route)`)\n res.writeHead(200); res.end(\"ok\"); return\n }\n\n // Phase 1 commit 6.a: shadow-mode ledger observes the loop. We\n // compute `wasDuplicate` up front (rather than `continue`-ing on\n // legacy dedup hits) so the ledger sees BOTH branches — fresh\n // dispatches AND legacy-deduped ones — and the divergence reporter\n // surfaces gaps between the legacy TTL dedup and the ledger's\n // active-task model. Both views live side-by-side until the 1c→1d\n // promotions retire legacy dedup.\n const ledgerEnabled = getLedgerMode(\"gitlab\") !== \"off\"\n const issueProjection = ledgerEnabled\n ? {\n entityKind: \"issue\" as const,\n project,\n iid: attrs.iid,\n action: attrs.action,\n title: attrs.title,\n description: attrs.description,\n url: attrs.url,\n }\n : null\n const eventJson = ledgerEnabled ? JSON.stringify(event) : \"\"\n\n for (const t of targets) {\n const dedupKey = `${project}:issue:${attrs.iid}:${t.agentId}:${attrs.action}:${t.trigger}`\n const wasDuplicate = this.isDispatchedRecently(dedupKey)\n\n // Phase 1 / 6 — record ledger first so we have the decision row\n // for the IncomingMessage's intentRef. Wrapped in try/catch because\n // a ledger failure must never break dispatch — legacy stays\n // authoritative until 1c.\n let intentRef: { eventId: string; decidedBy: string } | undefined\n if (issueProjection) {\n try {\n const decision = recordGitLabTargetDispatch(\n getDefaultLedger(),\n issueProjection,\n t,\n eventJson,\n wasDuplicate\n ? { agentId: null, outcome: \"deduped\", reason: \"isDispatchedRecently\" }\n : { agentId: t.agentId, outcome: \"dispatched\" },\n )\n // Tag the IncomingMessage only when ledger ALSO decided\n // \"dispatched\" (active-task safety could still force \"deduped\").\n // Resolution writes only make sense for dispatched decisions.\n if (decision.outcome === \"dispatched\") {\n intentRef = { eventId: decision.eventId, decidedBy: decision.decidedBy }\n }\n } catch (e: any) {\n this.log(`[ledger] gitlab issue #${attrs.iid} target ${t.agentId} record failed: ${e?.message ?? e}`)\n }\n }\n\n if (wasDuplicate) {\n this.log(`Issue #${attrs.iid}: skip duplicate dispatch ${dedupKey}`)\n } else {\n this.markDispatched(dedupKey)\n\n const mapping = this.config.agentMappings?.find((m) => m.agentId === t.agentId)\n const chatId = `${project}:issue:${attrs.iid}`\n const channelMeta = await this.getChannelMeta(chatId)\n\n // Assignment-trigger gets a \"start working\" prompt; mention/default\n // get the standard issue summary. Both end with the issue URL so the\n // agent can navigate to it.\n const text = t.trigger === \"assignee-added\"\n ? `[GitLab ${project} Issue #${attrs.iid} assigned to you: ${attrs.title}]\\n${attrs.description?.slice(0, 1500) || \"\"}\\nURL: ${attrs.url}\\n\\nPlease acknowledge this assignment in a comment, then start working on the issue.`\n : `[GitLab ${project} Issue #${attrs.iid} ${attrs.action}]: ${attrs.title}\\n${attrs.description?.slice(0, 500) || \"\"}\\nURL: ${attrs.url}`\n\n const incoming: IncomingMessage = {\n id: `issue-${attrs.iid}-${attrs.action}-${t.agentId}-${t.trigger}`,\n channel: \"gitlab\",\n accountId: \"default\",\n sender: {\n id: chatId,\n name: event.user.name,\n username: event.user.username,\n },\n text,\n timestamp: new Date(),\n raw: event,\n resolvedAgent: t.agentId,\n preferNode: mapping?.node,\n channelMeta: channelMeta ? {\n ...channelMeta,\n issue: { type: \"issue\", iid: String(attrs.iid), title: attrs.title },\n } : undefined,\n intentRef,\n }\n\n this.log(`Issue #${attrs.iid} -> agent \"${t.agentId}\" (trigger: ${t.trigger})`)\n this.handler(incoming).catch((e) => this.log(`Error handling issue: ${e.message}`))\n }\n }\n\n res.writeHead(200)\n res.end(\"ok\")\n }\n\n /** Compute the deterministic target set for an issue event. Order:\n * 1. agent usernames @mentioned in the description (open events)\n * 2. agent usernames newly added as assignees (changes.assignees diff)\n * 3. agent usernames in the current full assignee list (catches\n * create-with-assignee where there are no `changes`)\n * 4. project default route (fallback only when 1-3 produced nothing)\n * Each agent appears at most once; the first trigger that found them\n * wins for the dedup key. */\n private computeIssueTargets(\n event: GitLabIssueEvent,\n defaultAgentId: string | undefined,\n ): Array<{ agentId: string; trigger: \"mention\" | \"assignee-added\" | \"assignee-current\" | \"default-route\" }> {\n const out: Array<{ agentId: string; trigger: \"mention\" | \"assignee-added\" | \"assignee-current\" | \"default-route\" }> = []\n const seen = new Set<string>()\n const add = (agentId: string | undefined, trigger: \"mention\" | \"assignee-added\" | \"assignee-current\" | \"default-route\") => {\n if (!agentId || seen.has(agentId)) return\n seen.add(agentId)\n out.push({ agentId, trigger })\n }\n\n // 1. Mentions in description\n const desc = event.object_attributes.description || \"\"\n const mentions = desc.match(/@(\\w[\\w.-]*)/g)?.map(m => m.slice(1).replace(/[.]+$/, \"\").toLowerCase()) || []\n for (const u of mentions) {\n add(this.usernameToAgent.get(u), \"mention\")\n }\n\n // 2. Newly added assignees (assignment trigger — the \"start working\" path)\n const previous = new Set((event.changes?.assignees?.previous ?? []).map(a => a.username.toLowerCase()))\n const current = (event.changes?.assignees?.current ?? []).map(a => a.username.toLowerCase())\n for (const u of current) {\n if (previous.has(u)) continue // unchanged assignment, not a fresh add\n add(this.usernameToAgent.get(u), \"assignee-added\")\n }\n\n // 3. Current full assignee list (covers create-with-assignee where\n // `changes` is absent). Skipped when (2) already produced an agent\n // for this event to avoid the same assignee firing twice.\n if (out.length === 0) {\n const allAssignees = (event.assignees ?? []).map(a => a.username.toLowerCase())\n for (const u of allAssignees) {\n add(this.usernameToAgent.get(u), \"assignee-current\")\n }\n }\n\n // 4. Project default route — only when nothing above resolved\n if (out.length === 0) {\n add(defaultAgentId, \"default-route\")\n }\n\n return out\n }\n\n /** Was this dispatch key fired within the dedup TTL window? */\n private isDispatchedRecently(key: string): boolean {\n const expires = this.dispatchedTargets.get(key)\n if (!expires) return false\n if (expires < Date.now()) {\n this.dispatchedTargets.delete(key)\n return false\n }\n return true\n }\n\n /** Record that we dispatched `key` and prune stale entries. */\n private markDispatched(key: string): void {\n const now = Date.now()\n this.dispatchedTargets.set(key, now + this.DISPATCH_TTL_MS)\n // Lazy prune: drop expired entries while we're touching the map.\n if (this.dispatchedTargets.size > 200) {\n for (const [k, exp] of this.dispatchedTargets) {\n if (exp < now) this.dispatchedTargets.delete(k)\n }\n }\n }\n\n /**\n * Handle merge request events.\n */\n private async handleMR(event: GitLabMREvent, res: ServerResponse): Promise<void> {\n if (!this.handler) { res.writeHead(200); res.end(\"ok\"); return }\n\n // Skip bot-triggered MR updates (cascade prevention)\n if (this.isBotUser(event.user.username)) {\n res.writeHead(200); res.end(\"ok\"); return\n }\n\n const attrs = event.object_attributes\n const project = event.project.path_with_namespace\n const defaultAgentId = this.resolveAgent(project)\n\n // Same target-resolution model as handleIssue: mentions ∪ assignees ∪\n // reviewers ∪ default route, deduped per-agent.\n const targets = this.computeMRTargets(event, defaultAgentId)\n if (targets.length === 0) {\n res.writeHead(200); res.end(\"ok\"); return\n }\n\n // Same shadow-mode wiring as handleIssue (Phase 1 commit 6.a). Computed\n // once outside the loop; per-target ledger record happens after the\n // legacy dispatch path so the ledger sees both fresh and deduped branches.\n const ledgerEnabled = getLedgerMode(\"gitlab\") !== \"off\"\n const mrProjection = ledgerEnabled\n ? {\n entityKind: \"merge_request\" as const,\n project,\n iid: attrs.iid,\n action: attrs.action,\n title: attrs.title,\n description: attrs.description,\n url: attrs.url,\n }\n : null\n const eventJson = ledgerEnabled ? JSON.stringify(event) : \"\"\n\n for (const t of targets) {\n const dedupKey = `${project}:merge_request:${attrs.iid}:${t.agentId}:${attrs.action}:${t.trigger}`\n const wasDuplicate = this.isDispatchedRecently(dedupKey)\n\n let intentRef: { eventId: string; decidedBy: string } | undefined\n if (mrProjection) {\n try {\n const decision = recordGitLabTargetDispatch(\n getDefaultLedger(),\n mrProjection,\n t,\n eventJson,\n wasDuplicate\n ? { agentId: null, outcome: \"deduped\", reason: \"isDispatchedRecently\" }\n : { agentId: t.agentId, outcome: \"dispatched\" },\n )\n if (decision.outcome === \"dispatched\") {\n intentRef = { eventId: decision.eventId, decidedBy: decision.decidedBy }\n }\n } catch (e: any) {\n this.log(`[ledger] gitlab MR !${attrs.iid} target ${t.agentId} record failed: ${e?.message ?? e}`)\n }\n }\n\n if (!wasDuplicate) {\n this.markDispatched(dedupKey)\n\n const mapping = this.config.agentMappings?.find((m) => m.agentId === t.agentId)\n const chatId = `${project}:merge_request:${attrs.iid}`\n const channelMeta = await this.getChannelMeta(chatId)\n\n const isAssignmentTrigger = t.trigger === \"assignee-added\" || t.trigger === \"reviewer-added\"\n const text = isAssignmentTrigger\n ? `[GitLab ${project} MR !${attrs.iid} ${t.trigger === \"reviewer-added\" ? \"review requested\" : \"assigned to you\"}: ${attrs.title}]\\nBranch: ${attrs.source_branch} -> ${attrs.target_branch}\\n${attrs.description?.slice(0, 1500) || \"\"}\\nURL: ${attrs.url}\\n\\nPlease acknowledge in a comment, then ${t.trigger === \"reviewer-added\" ? \"review this MR\" : \"start working on it\"}.`\n : `[GitLab ${project} MR !${attrs.iid} ${attrs.action}]: ${attrs.title}\\nBranch: ${attrs.source_branch} -> ${attrs.target_branch}\\n${attrs.description?.slice(0, 500) || \"\"}\\nURL: ${attrs.url}`\n\n const incoming: IncomingMessage = {\n id: `mr-${attrs.iid}-${attrs.action}-${t.agentId}-${t.trigger}`,\n channel: \"gitlab\",\n accountId: \"default\",\n sender: {\n id: chatId,\n name: event.user.name,\n username: event.user.username,\n },\n text,\n timestamp: new Date(),\n raw: event,\n resolvedAgent: t.agentId,\n preferNode: mapping?.node,\n channelMeta: channelMeta ? {\n ...channelMeta,\n issue: { type: \"merge_request\", iid: String(attrs.iid), title: attrs.title },\n } : undefined,\n intentRef,\n }\n\n this.log(`MR !${attrs.iid} -> agent \"${t.agentId}\" (trigger: ${t.trigger})`)\n this.handler(incoming).catch((e) => this.log(`Error handling MR: ${e.message}`))\n }\n }\n\n res.writeHead(200)\n res.end(\"ok\")\n }\n\n /** Compute MR targets — mirrors computeIssueTargets, with reviewer\n * changes added as a separate trigger so an agent added as reviewer\n * gets the \"review this MR\" prompt instead of \"start working on it\". */\n private computeMRTargets(\n event: GitLabMREvent,\n defaultAgentId: string | undefined,\n ): Array<{ agentId: string; trigger: \"mention\" | \"assignee-added\" | \"assignee-current\" | \"reviewer-added\" | \"reviewer-current\" | \"default-route\" }> {\n const out: Array<{ agentId: string; trigger: \"mention\" | \"assignee-added\" | \"assignee-current\" | \"reviewer-added\" | \"reviewer-current\" | \"default-route\" }> = []\n const seen = new Set<string>()\n const add = (\n agentId: string | undefined,\n trigger: \"mention\" | \"assignee-added\" | \"assignee-current\" | \"reviewer-added\" | \"reviewer-current\" | \"default-route\",\n ) => {\n if (!agentId || seen.has(agentId)) return\n seen.add(agentId)\n out.push({ agentId, trigger })\n }\n\n const desc = event.object_attributes.description || \"\"\n const mentions = desc.match(/@(\\w[\\w.-]*)/g)?.map(m => m.slice(1).replace(/[.]+$/, \"\").toLowerCase()) || []\n for (const u of mentions) add(this.usernameToAgent.get(u), \"mention\")\n\n const prevA = new Set((event.changes?.assignees?.previous ?? []).map(a => a.username.toLowerCase()))\n for (const a of event.changes?.assignees?.current ?? []) {\n const u = a.username.toLowerCase()\n if (prevA.has(u)) continue\n add(this.usernameToAgent.get(u), \"assignee-added\")\n }\n const prevR = new Set((event.changes?.reviewers?.previous ?? []).map(a => a.username.toLowerCase()))\n for (const r of event.changes?.reviewers?.current ?? []) {\n const u = r.username.toLowerCase()\n if (prevR.has(u)) continue\n add(this.usernameToAgent.get(u), \"reviewer-added\")\n }\n\n if (out.length === 0) {\n for (const a of event.assignees ?? []) add(this.usernameToAgent.get(a.username.toLowerCase()), \"assignee-current\")\n for (const r of event.reviewers ?? []) add(this.usernameToAgent.get(r.username.toLowerCase()), \"reviewer-current\")\n }\n\n if (out.length === 0) add(defaultAgentId, \"default-route\")\n return out\n }\n\n /**\n * Handle pipeline events (success, failed).\n */\n private async handlePipeline(event: GitLabPipelineEvent, res: ServerResponse): Promise<void> {\n const attrs = event.object_attributes\n const project = event.project.path_with_namespace\n const terminalStatuses = [\"success\", \"failed\", \"canceled\"]\n\n // Fire on:gitlab-pipeline hook for all terminal pipelines (side-effect hooks, e.g. time logging)\n if (terminalStatuses.includes(attrs.status) && this.hooks?.has(\"on:gitlab-pipeline\" as any)) {\n this.hooks.execute(\"on:gitlab-pipeline\" as any, {\n event: \"on:gitlab-pipeline\" as any,\n pipelineId: attrs.id,\n status: attrs.status,\n ref: attrs.ref,\n duration: attrs.duration,\n project,\n projectId: (event as any).project?.id,\n raw: event,\n }).catch((e: Error) => this.log(`on:gitlab-pipeline hook error: ${e.message}`))\n }\n\n // Only route to agent on failures\n if (!this.handler || attrs.status !== \"failed\") {\n res.writeHead(200)\n res.end(\"ok\")\n return\n }\n\n const agentId = this.resolveAgent(project)\n\n const incoming: IncomingMessage = {\n id: `pipeline-${attrs.id}`,\n channel: \"gitlab\",\n accountId: \"default\",\n sender: {\n id: `${project}:pipeline:${attrs.id}`,\n name: event.user.name,\n username: event.user.username,\n },\n // No group — sender.id has project:type:iid for reply routing\n text: `[GitLab Pipeline FAILED] Project: ${project}\\nRef: ${attrs.ref}\\nDuration: ${attrs.duration}s\\nPlease investigate the failure.`,\n timestamp: new Date(),\n raw: event,\n resolvedAgent: agentId,\n }\n\n this.handler(incoming).catch((e) => this.log(`Error handling pipeline: ${e.message}`))\n res.writeHead(200)\n res.end(\"ok\")\n }\n\n /**\n * Resolve agent from @mentions in a comment.\n * Uses the authoritative usernameToAgent map (built from API token resolution\n * at startup) — not the manually configured gitlabUsernames which may be wrong.\n */\n private resolveAgentFromMention(text: string): string | undefined {\n // Extract @mentions from the comment, strip trailing dots/punctuation\n const mentions = text.match(/@(\\w[\\w.-]*)/g)?.map(m => m.slice(1).replace(/[.]+$/, \"\").toLowerCase()) || []\n if (mentions.length === 0) return undefined\n\n // Check against the authoritative username->agent map (resolved from tokens)\n for (const mention of mentions) {\n const agentId = this.usernameToAgent.get(mention)\n if (agentId) {\n this.log(`Mention @${mention} -> agent \"${agentId}\" (resolved from token)`)\n return agentId\n }\n }\n\n return undefined\n }\n\n /**\n * Resolve which agent handles a project based on routes.\n */\n private resolveAgent(project: string): string | undefined {\n for (const route of this.config.routes) {\n if (route.project === project || route.project === \"*\") {\n return route.agent\n }\n }\n return undefined\n }\n\n /**\n * Get verified context for a GitLab project chat.\n * chatId format: \"project/path:issue:123\" or \"project/path:merge_request:456\"\n */\n async getChannelMeta(chatId: string): Promise<ChannelMeta | undefined> {\n const parts = chatId.split(\":\")\n const project = parts[0]\n const noteableType = parts[1]\n const noteableIid = parts[2]\n\n // Find all agents mapped to this project\n const agents: ChannelMeta[\"agents\"] = []\n for (const route of this.config.routes) {\n if (route.project === project || route.project === \"*\") {\n agents.push({ id: route.agent, name: route.agent })\n }\n }\n for (const mapping of this.config.agentMappings || []) {\n if (!agents.some(a => a.id === mapping.agentId)) {\n agents.push({ id: mapping.agentId, name: mapping.agentId })\n }\n }\n\n const facts: string[] = [\n \"This is a GitLab webhook event — respond as a GitLab comment\",\n \"Do NOT use Telegram handles or delegate to other agents\",\n \"Do NOT mention other agents by name — you are the only agent responding to this event\",\n \"Stay in your role — do not act as or speak for other agents\",\n ]\n\n return {\n channel: \"gitlab\",\n agents,\n project,\n issue: noteableType && noteableIid ? { type: noteableType, iid: noteableIid, title: \"\" } : undefined,\n facts,\n }\n }\n\n /**\n * Check if a username belongs to a known bot/agent user.\n */\n private isBotUser(username: string): boolean {\n return this.botUsernames.has(username)\n }\n\n /** ChannelAdapter.seedHistory: fetch the issue/MR's existing notes from\n * the GitLab API and return them oldest-first so a fresh agent session\n * starts mirroring the live thread. chatId is the canonical\n * \"project:type:iid\" the rest of the adapter already uses (see send()).\n * Filters out agentx-signed notes from the calling agent (those are this\n * agent's own past replies — already represented in the model's prior\n * turns). Best-effort: API errors return [] rather than throwing. */\n async seedHistory(\n chatId: string,\n opts: { sinceISO?: string; maxMessages: number; maxChars: number },\n ): Promise<SeededMessage[]> {\n const parts = chatId.split(\":\")\n if (parts.length < 3) return []\n const iid = parts.pop()!\n const noteableType = parts.pop()!\n const project = parts.join(\":\")\n\n const encodedProject = encodeURIComponent(project)\n let endpoint: string\n if (noteableType === \"issue\") {\n endpoint = `${this.config.host}/api/v4/projects/${encodedProject}/issues/${iid}/notes?sort=asc&per_page=${Math.max(20, Math.min(100, opts.maxMessages))}`\n } else if (noteableType === \"merge_request\") {\n endpoint = `${this.config.host}/api/v4/projects/${encodedProject}/merge_requests/${iid}/notes?sort=asc&per_page=${Math.max(20, Math.min(100, opts.maxMessages))}`\n } else {\n return []\n }\n\n // Use global token for the read — seedHistory is a context-rebuild\n // operation, not an identity-bound action, and the global token is\n // guaranteed to have read access across the configured projects.\n const token = this.config.token\n if (!token) return []\n\n let notes: Array<{\n id: number\n body: string\n author?: { username?: string; name?: string }\n created_at: string\n system?: boolean\n }>\n try {\n const res = await fetch(endpoint, {\n headers: { \"PRIVATE-TOKEN\": token },\n })\n if (!res.ok) return []\n notes = (await res.json()) as typeof notes\n } catch {\n return []\n }\n\n const out: SeededMessage[] = []\n let chars = 0\n const sinceMs = opts.sinceISO ? new Date(opts.sinceISO).getTime() : 0\n for (const n of notes) {\n if (n.system) continue // skip GitLab-generated \"assigned to / closed\" lines\n if (sinceMs && new Date(n.created_at).getTime() < sinceMs) continue\n const sourceAgent = detectAgentxMarker(n.body)\n const cleanBody = stripAgentxMarkers(n.body)\n out.push({\n role: sourceAgent ? \"agent\" : \"user\",\n name: n.author?.name || n.author?.username || (sourceAgent ?? \"user\"),\n content: cleanBody,\n timestamp: n.created_at,\n externalId: String(n.id),\n })\n chars += cleanBody.length\n if (out.length >= opts.maxMessages) break\n if (chars >= opts.maxChars) break\n }\n return out\n }\n\n /**\n * Get the per-agent GitLab token from agentMappings.\n * Falls back to undefined if no per-agent token is configured.\n */\n getAgentToken(agentId?: string): string | undefined {\n if (!agentId || !this.config.agentMappings?.length) return undefined\n const mapping = this.config.agentMappings.find(m => m.agentId === agentId)\n return mapping?.token\n }\n\n /**\n * Find the per-agent token for the first @mentioned agent in note text.\n */\n private getTokenForMentionedAgent(text: string): string | undefined {\n if (!this.config.agentMappings?.length) return undefined\n const mentionedUsers = text.match(/@(\\w[\\w.-]*)/g)?.map(m => m.slice(1).replace(/[.]+$/, \"\").toLowerCase()) || []\n for (const mapping of this.config.agentMappings) {\n if (mapping.token && mapping.gitlabUsernames.some(u => mentionedUsers.includes(u.toLowerCase()))) {\n return mapping.token\n }\n }\n return undefined\n }\n\n /**\n * React to a GitLab note with an emoji (👀 eyes) to acknowledge receipt.\n * ONLY uses the agent's own token — never the global token (which may\n * belong to a different agent user, causing the wrong identity to react).\n */\n private async reactToNote(project: string, noteableType: string, noteableIid: string, noteId: number, agentToken?: string, node?: string, agentId?: string): Promise<void> {\n // Agent lives on a remote mesh peer — forward the reaction request there\n if (!agentToken && node && agentId && this.reactForwarder) {\n this.log(`Forwarding 👀 reaction for \"${agentId}\" to mesh peer \"${node}\"`)\n await this.reactForwarder(node, project, noteableType, noteableIid, noteId, agentId)\n return\n }\n\n if (!agentToken) {\n this.log(`No token for reaction on note ${noteId} (agent \"${agentId}\") — skipping`)\n return\n }\n\n const encodedProject = encodeURIComponent(project)\n let endpoint: string\n\n switch (noteableType) {\n case \"issue\":\n endpoint = `${this.config.host}/api/v4/projects/${encodedProject}/issues/${noteableIid}/notes/${noteId}/award_emoji`\n break\n case \"merge_request\":\n endpoint = `${this.config.host}/api/v4/projects/${encodedProject}/merge_requests/${noteableIid}/notes/${noteId}/award_emoji`\n break\n default:\n return\n }\n\n try {\n const res = await fetch(endpoint, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"PRIVATE-TOKEN\": agentToken,\n },\n body: JSON.stringify({ name: \"eyes\" }),\n })\n if (!res.ok) {\n this.log(`Reaction failed on note ${noteId}: ${res.status}`)\n }\n } catch (e: any) {\n this.log(`Failed to react to note ${noteId}: ${e.message}`)\n }\n }\n\n /**\n * Log time spent on a GitLab issue/MR after agent completes work.\n * Uses the /add_spent_time API endpoint.\n * chatId format: \"project:type:iid\" (e.g. \"mtgl/mtgl-system-v2:issue:646\")\n */\n async logTimeSpent(chatId: string, durationMs: number, agentId?: string): Promise<void> {\n const parts = chatId.split(\":\")\n if (parts.length < 3) return\n\n const iid = parts.pop()!\n const noteableType = parts.pop()!\n const project = parts.join(\":\")\n\n // Convert ms to GitLab duration string (minimum 1m)\n const totalSeconds = Math.max(60, Math.round(durationMs / 1000))\n const hours = Math.floor(totalSeconds / 3600)\n const minutes = Math.ceil((totalSeconds % 3600) / 60)\n const duration = hours > 0 ? `${hours}h${minutes > 0 ? ` ${minutes}m` : \"\"}` : `${minutes}m`\n\n const encodedProject = encodeURIComponent(project)\n const typeSegment = noteableType === \"merge_request\" ? \"merge_requests\" : \"issues\"\n const endpoint = `${this.config.host}/api/v4/projects/${encodedProject}/${typeSegment}/${iid}/add_spent_time`\n\n // Same identity rules as send(): for remote-hosted agents with no local\n // per-agent token, forward the time-log to the peer so it posts as the\n // agent's real GitLab user rather than the shared group bot.\n const mapping = this.config.agentMappings?.find((m) => m.agentId === agentId)\n const agentToken = this.getAgentToken(agentId)\n if (!agentToken && mapping?.node && this.logTimeForwarder) {\n try {\n await this.logTimeForwarder(mapping.node, project, noteableType, iid, agentId || \"\", durationMs)\n this.log(`Time logged (via peer \"${mapping.node}\"): ${duration} on ${project} ${typeSegment}/${iid} (${agentId})`)\n } catch (e: any) {\n this.log(`Time log forward to \"${mapping.node}\" failed: ${e.message} — skipping (would log as group bot)`)\n }\n return\n }\n\n const token = agentToken || this.config.token\n\n try {\n const res = await fetch(endpoint, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"PRIVATE-TOKEN\": token,\n },\n body: JSON.stringify({ duration }),\n })\n\n if (res.ok) {\n this.log(`Time logged: ${duration} on ${project} ${noteableType} #${iid} (${agentId || \"global\"})`)\n } else {\n const text = await res.text()\n this.log(`Time log failed (${res.status}): ${text.slice(0, 100)}`)\n }\n } catch (e: any) {\n this.log(`Time log error: ${e.message}`)\n }\n }\n\n /** Create a new GitLab issue. Mirrors `logTimeSpent`'s identity resolution:\n * prefers the per-agent token; if the agent is hosted on a remote peer\n * and no local token exists, forwards via mesh so the issue appears under\n * the agent's real GitLab user. Returns `{ iid, url }` or null on failure. */\n async createIssue(args: {\n project: string\n title: string\n description?: string\n labels?: string[]\n assignees?: string[]\n agentId?: string\n }): Promise<{ iid: number; url: string } | null> {\n const { project, title, description = \"\", labels = [], assignees = [], agentId } = args\n const mapping = this.config.agentMappings?.find((m) => m.agentId === agentId)\n const agentToken = this.getAgentToken(agentId)\n\n // Remote-hosted agent with no local token — forward to peer.\n if (!agentToken && mapping?.node && this.createIssueForwarder) {\n try {\n const result = await this.createIssueForwarder(mapping.node, project, title, description, labels, assignees, agentId || \"\")\n if (result) this.log(`Issue created (via peer \"${mapping.node}\") #${result.iid} on ${project}`)\n return result\n } catch (e: any) {\n this.log(`Issue-create forward to \"${mapping.node}\" failed: ${e.message}`)\n return null\n }\n }\n\n const token = agentToken || this.config.token\n if (!token) {\n this.log(`Issue-create failed: no token available for agent \"${agentId || \"global\"}\"`)\n return null\n }\n\n const encodedProject = encodeURIComponent(project)\n const endpoint = `${this.config.host}/api/v4/projects/${encodedProject}/issues`\n const body = new URLSearchParams()\n body.set(\"title\", title)\n if (description) body.set(\"description\", description)\n if (labels.length > 0) body.set(\"labels\", labels.join(\",\"))\n // GitLab takes `assignee_ids[]`; users pass usernames for ergonomics,\n // so resolve them to numeric ids first. Failure to resolve any one\n // assignee is logged but doesn't block the create.\n for (const username of assignees) {\n try {\n const r = await fetch(`${this.config.host}/api/v4/users?username=${encodeURIComponent(username)}`, {\n headers: { \"PRIVATE-TOKEN\": token },\n })\n if (r.ok) {\n const users = await r.json() as Array<{ id: number }>\n if (users[0]?.id) body.append(\"assignee_ids[]\", String(users[0].id))\n }\n } catch (e: any) {\n this.log(`Issue-create: failed to resolve assignee \"${username}\": ${e.message}`)\n }\n }\n\n try {\n const res = await fetch(endpoint, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n \"PRIVATE-TOKEN\": token,\n },\n body: body.toString(),\n })\n if (!res.ok) {\n const text = await res.text()\n this.log(`Issue-create failed (${res.status}): ${text.slice(0, 200)}`)\n return null\n }\n const issue = await res.json() as { iid: number; web_url: string }\n this.log(`Issue created #${issue.iid} on ${project} (${agentId || \"global\"})`)\n return { iid: issue.iid, url: issue.web_url }\n } catch (e: any) {\n this.log(`Issue-create error: ${e.message}`)\n return null\n }\n }\n\n /** Add / remove labels on an existing issue or MR. Same identity\n * resolution as createIssue: per-agent token > mesh-forward to home > global.\n * Returns the updated label set on success, or null. */\n async setLabels(args: {\n project: string\n kind?: \"issue\" | \"merge_request\"\n iid: string\n add?: string[]\n remove?: string[]\n agentId?: string\n }): Promise<string[] | null> {\n const { project, kind = \"issue\", iid, add = [], remove = [], agentId } = args\n const mapping = this.config.agentMappings?.find((m) => m.agentId === agentId)\n const agentToken = this.getAgentToken(agentId)\n\n if (!agentToken && mapping?.node && this.setLabelsForwarder) {\n try {\n const labels = await this.setLabelsForwarder(mapping.node, project, kind, iid, add, remove, agentId || \"\")\n if (labels) this.log(`Labels updated (via peer \"${mapping.node}\") on ${project} ${kind}/${iid}`)\n return labels\n } catch (e: any) {\n this.log(`setLabels forward to \"${mapping.node}\" failed: ${e.message}`)\n return null\n }\n }\n const token = agentToken || this.config.token\n if (!token) { this.log(`setLabels failed: no token for \"${agentId || \"global\"}\"`); return null }\n\n const segment = kind === \"merge_request\" ? \"merge_requests\" : \"issues\"\n const endpoint = `${this.config.host}/api/v4/projects/${encodeURIComponent(project)}/${segment}/${iid}`\n const body = new URLSearchParams()\n if (add.length) body.set(\"add_labels\", add.join(\",\"))\n if (remove.length) body.set(\"remove_labels\", remove.join(\",\"))\n\n try {\n const res = await fetch(endpoint, {\n method: \"PUT\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\", \"PRIVATE-TOKEN\": token },\n body: body.toString(),\n })\n if (!res.ok) {\n const text = await res.text()\n this.log(`setLabels failed (${res.status}): ${text.slice(0, 200)}`)\n return null\n }\n const data = await res.json() as { labels?: string[] }\n return data.labels ?? []\n } catch (e: any) {\n this.log(`setLabels error: ${e.message}`)\n return null\n }\n }\n\n /** Read current labels on an issue or MR. Read-only, so doesn't need\n * per-agent identity. Uses the global token. */\n async getLabels(args: { project: string; kind?: \"issue\" | \"merge_request\"; iid: string }): Promise<string[] | null> {\n const { project, kind = \"issue\", iid } = args\n if (!this.config.token) return null\n const segment = kind === \"merge_request\" ? \"merge_requests\" : \"issues\"\n const endpoint = `${this.config.host}/api/v4/projects/${encodeURIComponent(project)}/${segment}/${iid}`\n try {\n const res = await fetch(endpoint, { headers: { \"PRIVATE-TOKEN\": this.config.token } })\n if (!res.ok) { this.log(`getLabels failed (${res.status})`); return null }\n const data = await res.json() as { labels?: string[] }\n return data.labels ?? []\n } catch (e: any) {\n this.log(`getLabels error: ${e.message}`)\n return null\n }\n }\n\n /**\n * Extract and download images from a GitLab comment.\n * GitLab markdown images: ![alt](/uploads/hash/filename.png)\n * Returns the first image found as media attachment, or undefined.\n */\n private async downloadNoteImages(\n note: string,\n project: string,\n token: string,\n ): Promise<IncomingMessage[\"media\"] | undefined> {\n // Match GitLab upload paths: ![...](/uploads/...) or full URLs\n const imagePattern = /!\\[[^\\]]*\\]\\(([^)]+\\.(?:png|jpg|jpeg|gif|webp|svg))\\)/gi\n const matches = [...note.matchAll(imagePattern)]\n if (matches.length === 0) return undefined\n\n const imagePath = matches[0][1] // First image\n let imageUrl: string\n\n if (imagePath.startsWith(\"http\")) {\n imageUrl = imagePath\n } else {\n // Relative path — resolve against GitLab project\n const encodedProject = encodeURIComponent(project)\n imageUrl = `${this.config.host}/${project}${imagePath}`\n }\n\n try {\n const res = await fetch(imageUrl, {\n headers: { \"PRIVATE-TOKEN\": token },\n })\n if (!res.ok) {\n this.log(`Failed to download image: ${res.status} ${imageUrl}`)\n return undefined\n }\n\n const buffer = Buffer.from(await res.arrayBuffer())\n const contentType = res.headers.get(\"content-type\") || \"image/png\"\n const ext = contentType.split(\"/\")[1]?.split(\";\")[0] || \"png\"\n\n // Save to disk\n const { mkdirSync, writeFileSync } = await import(\"fs\")\n const { resolve } = await import(\"path\")\n const { randomUUID } = await import(\"crypto\")\n const mediaDir = resolve(process.cwd(), \".agentx/media/gitlab\")\n mkdirSync(mediaDir, { recursive: true })\n const fileName = `${randomUUID().slice(0, 8)}.${ext}`\n const filePath = resolve(mediaDir, fileName)\n writeFileSync(filePath, buffer)\n\n this.log(`Downloaded GitLab image: ${filePath} (${buffer.length} bytes)`)\n\n return {\n path: filePath,\n type: contentType,\n fileName: imagePath.split(\"/\").pop() || fileName,\n }\n } catch (e: any) {\n this.log(`Image download error: ${e.message}`)\n return undefined\n }\n }\n\n private async readBody(req: HttpRequest): Promise<Record<string, unknown>> {\n return new Promise((resolve) => {\n let body = \"\"\n req.on(\"data\", (chunk: Buffer) => (body += chunk.toString()))\n req.on(\"end\", () => {\n try { resolve(body ? JSON.parse(body) : {}) }\n catch { resolve({ raw: body }) }\n })\n req.on(\"error\", () => resolve({}))\n })\n }\n}\n","import type { IntentLedger } from \"../ledger\"\nimport { decideAndCommit, type DispatchPolicy } from \"../decide\"\nimport { reportDivergence, type LegacyOutcome } from \"../divergence\"\nimport type { IntentDecision, IntentEventInput } from \"../types\"\n\n// Source adapter — gitlab issue + merge_request dispatch paths. Phase 1\n// commits 6.a (handleIssue) and 6.a-extended (handleMR).\n//\n// One webhook event can produce N target dispatches (mentions, assignees,\n// reviewers, project default route). The ledger's natural unit is \"one\n// dispatch consideration\", not \"one webhook arrival\", so each target\n// becomes its own ledger event. The synthetic sourceEventId\n// `${entityKind}:${iid}:${action}:${agentId}:${trigger}` mirrors the\n// legacy dedup key, with the entity-kind prefix preventing collision\n// between an issue #5 and an MR !5 in the same project.\n//\n// `handleNote` (comments) is a different shape — its targets come from\n// @mention parsing of the note body — and lands in its own commit.\n\nexport type GitLabEntityKind = \"issue\" | \"merge_request\"\n\n/** Subset of the gitlab webhook payload the ledger needs. The helpers\n * do not consult anything outside this projection — keeps tests free\n * of full-payload fixtures and decouples the adapter from\n * `src/channels/gitlab.ts` evolving its private types. */\nexport interface GitLabEventProjection {\n entityKind: GitLabEntityKind\n project: string\n iid: number\n action: string\n title: string\n description: string\n url: string\n}\n\n/** Per-target shape produced by `computeIssueTargets` /\n * `computeMRTargets`. The trigger is free-form `string` so MR-specific\n * triggers (`reviewer-added`, `reviewer-current`) flow through without\n * needing a discriminated union. The trigger is part of the per-target\n * dedup key — it's the dispatch reason and it's expected to be a\n * small fixed set per entity kind. */\nexport interface GitLabTarget {\n agentId: string\n trigger: string\n}\n\n/** Build the IntentEventInput for one (gitlab event, target) combination.\n * Per-target sourceEventId for idempotency scope; per-target subject so\n * Inv-ActiveTaskSafety scopes per (issue|MR, agent, trigger). */\nexport function buildGitLabTargetEventInput(\n event: GitLabEventProjection,\n target: GitLabTarget,\n rawJson: string,\n now: () => number = Date.now,\n): IntentEventInput {\n return {\n ts: now(),\n source: \"gitlab\",\n sourceEventId: `${event.entityKind}:${event.iid}:${event.action}:${target.agentId}:${target.trigger}`,\n project: event.project,\n subject: `${event.entityKind}:${event.iid}:agent:${target.agentId}:trigger:${target.trigger}`,\n intent: `${event.entityKind}.${event.action}`,\n rawJson,\n }\n}\n\n/** Build the per-target DispatchPolicy. `computeIssueTargets` /\n * `computeMRTargets` already chose the agentId; the ledger's policy is\n * a thin wrapper that records that fact. The decidedBy preserves the\n * entity-kind + trigger so chain readouts identify the source path. */\nexport function buildGitLabDispatchPolicy(\n entityKind: GitLabEntityKind,\n target: GitLabTarget,\n): DispatchPolicy {\n return {\n decidedBy: `gitlab:${entityKind}:target-${target.trigger}`,\n decide: () => ({\n agentId: target.agentId,\n outcome: \"dispatched\",\n reason: null,\n }),\n }\n}\n\n/**\n * Convenience wrapper: record + report for one (event, target). Caller\n * computes the legacy outcome (typically \"dispatched\" when fresh and\n * \"deduped\" when `isDispatchedRecently` short-circuited the legacy\n * dispatch) and supplies it; the helper handles the rest.\n *\n * The caller is responsible for catching exceptions — the gitlab handler\n * must continue dispatching legacy-style even if the ledger errors.\n */\nexport function recordGitLabTargetDispatch(\n ledger: IntentLedger,\n event: GitLabEventProjection,\n target: GitLabTarget,\n rawJson: string,\n legacyOutcome: LegacyOutcome,\n now: () => number = Date.now,\n): IntentDecision {\n const eventInput = buildGitLabTargetEventInput(event, target, rawJson, now)\n const policy = buildGitLabDispatchPolicy(event.entityKind, target)\n const ledgerDecision = decideAndCommit(ledger, eventInput, policy, now)\n reportDivergence(ledger, \"gitlab\", ledgerDecision, legacyOutcome, now)\n return ledgerDecision\n}\n\n// ---------------------------------------------------------------------------\n// Note (comment) dispatch\n// ---------------------------------------------------------------------------\n//\n// Notes are 1:1 (one comment → one resolved agent from the @mention).\n// Different shape from issue/MR per-target dispatch: the trigger is\n// always \"@mention\" and the agent comes from `resolveAgentFromMention`,\n// not from a target-set producer. handleNote in src/channels/gitlab.ts\n// has many cascade-prevention early-return paths (sourceAgent loops,\n// sentNoteIds dedup, no-mention, bot users without signature). Those\n// are operational guards rather than dispatch decisions — they do NOT\n// reach the ledger. Only the two real decision points are instrumented:\n//\n// match → dispatched / resolved agent\n// mention-no-resolve → halted (the @mentioned name doesn't map to an agent)\n\nexport interface GitLabNoteProjection {\n /** GitLab's stable note id — unique across the whole instance. */\n noteId: string\n project: string\n /** \"issue\" or \"merge_request\" — the noteable the comment was on. */\n noteableType: string\n noteableIid: string\n /** The @mentions parsed out of the note body, deduped + lowercased.\n * Carried for forensic value in `intent` / `rawJson`; not load-\n * bearing for the dispatch decision (the resolved agent already\n * encodes the choice). */\n mentions: string[]\n}\n\nexport function buildNoteEventInput(\n proj: GitLabNoteProjection,\n rawJson: string,\n now: () => number = Date.now,\n): IntentEventInput {\n return {\n ts: now(),\n source: \"gitlab\",\n sourceEventId: `note:${proj.noteId}`,\n project: proj.project,\n subject: `${proj.noteableType}:${proj.noteableIid}:note:${proj.noteId}`,\n intent: `note.${proj.noteableType}`,\n rawJson,\n }\n}\n\nexport function buildNoteDispatchPolicy(legacyAgentId: string | null): DispatchPolicy {\n return {\n decidedBy: \"gitlab:note:mention\",\n decide: () => ({\n agentId: legacyAgentId,\n outcome: legacyAgentId ? \"dispatched\" : \"halted\",\n reason: legacyAgentId ? null : \"no @mention resolved to an agent\",\n }),\n }\n}\n\n/** Record + report for one note dispatch decision. */\nexport function recordGitLabNoteDispatch(\n ledger: IntentLedger,\n proj: GitLabNoteProjection,\n rawJson: string,\n legacyOutcome: LegacyOutcome,\n now: () => number = Date.now,\n): IntentDecision {\n const eventInput = buildNoteEventInput(proj, rawJson, now)\n const policy = buildNoteDispatchPolicy(legacyOutcome.agentId)\n const ledgerDecision = decideAndCommit(ledger, eventInput, policy, now)\n reportDivergence(ledger, \"gitlab\", ledgerDecision, legacyOutcome, now)\n return ledgerDecision\n}\n\n// ---------------------------------------------------------------------------\n// Issue/MR level (no-target) decisions\n// ---------------------------------------------------------------------------\n//\n// Some dispatch decisions don't correspond to a specific target — the\n// `on:gitlab-issue` hook can return `blocked: true` to suppress all\n// default dispatch, for example. The decision is \"we considered this\n// event and chose to dispatch nothing\", which is still a real dispatch\n// decision the ledger should track.\n\n/** Record + report for an issue/MR-level decision (no specific target).\n * `marker` distinguishes the decision sub-type (\"hook-blocked\",\n * \"no-targets\", etc.) and is part of the sourceEventId + subject so\n * multiple issue-level decisions on the same event can coexist. */\nexport function recordGitLabIssueLevelDecision(\n ledger: IntentLedger,\n event: GitLabEventProjection,\n marker: string,\n decidedBy: string,\n rawJson: string,\n legacyOutcome: LegacyOutcome,\n now: () => number = Date.now,\n): IntentDecision {\n const eventInput: IntentEventInput = {\n ts: now(),\n source: \"gitlab\",\n sourceEventId: `${event.entityKind}:${event.iid}:${event.action}:${marker}`,\n project: event.project,\n subject: `${event.entityKind}:${event.iid}:${marker}`,\n intent: `${event.entityKind}.${event.action}`,\n rawJson,\n }\n const policy: DispatchPolicy = {\n decidedBy,\n decide: () => ({\n agentId: legacyOutcome.agentId,\n outcome: legacyOutcome.outcome === \"deduped\" ? \"halted\" : legacyOutcome.outcome,\n reason: legacyOutcome.outcome === \"deduped\"\n ? `legacy-dedup: ${legacyOutcome.reason ?? \"\"}`.trim()\n : legacyOutcome.reason ?? null,\n }),\n }\n const ledgerDecision = decideAndCommit(ledger, eventInput, policy, now)\n reportDivergence(ledger, \"gitlab\", ledgerDecision, legacyOutcome, now)\n return ledgerDecision\n}\n","import type { ChannelAdapter, IncomingMessage, OutgoingMessage, ChannelMeta } from \"./types\"\nimport { createHmac, createSign } from \"crypto\"\nimport { readFileSync } from \"fs\"\nimport { debug } from \"@/observability/debug\"\nimport { markBody, detectAgentxMarker } from \"./outbound-marker\"\n\n// --- GitHub webhook channel adapter ---\n//\n// Receives GitHub webhook events (issue_comment, pull_request, push, etc.)\n// and routes them to agents. Agents reply as GitHub API comments.\n//\n// Config (channels.github):\n// enabled: true\n// token: \"${GITHUB_TOKEN}\" # PAT for posting comments\n// tokenFile: \"/path/to/token\" # alternative: read token from file\n// webhookSecret: \"secret\" # validates X-Hub-Signature-256\n// routes:\n// - repo: \"owner/repo\"\n// agent: \"coder-agent\"\n// - repo: \"*\"\n// agent: \"atlas\"\n// agentMappings:\n// - agentId: \"coder-agent\"\n// githubUsernames: [\"my-bot\"]\n// node: \"macbook-local\" # forward to mesh peer\n\nexport interface GitHubRoute {\n repo: string // \"owner/repo\" or \"*\" for default\n agent: string\n}\n\nexport interface GitHubAgentMapping {\n agentId: string\n githubUsernames: string[]\n token?: string\n tokenFile?: string\n node?: string // mesh peer for remote agents\n}\n\nexport interface GitHubChannelConfig {\n /** PAT-based auth (legacy — comments appear as the token owner) */\n token?: string\n tokenFile?: string\n /** GitHub App auth (comments appear as the app bot) */\n appId?: number\n /** Client ID (preferred JWT issuer per GitHub's updated docs). */\n clientId?: string\n privateKeyFile?: string\n /** Webhook secret for validating X-Hub-Signature-256. */\n webhookSecret?: string\n routes: GitHubRoute[]\n agentMappings?: GitHubAgentMapping[]\n}\n\n// --- GitHub event type definitions ---\n\ninterface GitHubUser {\n login: string\n id: number\n}\n\ninterface GitHubRepo {\n full_name: string\n html_url: string\n}\n\ninterface GitHubIssueCommentEvent {\n action: string // \"created\", \"edited\", \"deleted\"\n comment: {\n id: number\n body: string\n html_url: string\n user: GitHubUser\n }\n issue: {\n number: number\n title: string\n state: string\n html_url: string\n pull_request?: unknown // present if this is a PR\n }\n repository: GitHubRepo\n}\n\ninterface GitHubPREvent {\n action: string\n pull_request: {\n number: number\n title: string\n state: string\n body: string\n html_url: string\n head: { ref: string }\n base: { ref: string }\n user: GitHubUser\n }\n repository: GitHubRepo\n}\n\ninterface GitHubPRReviewEvent {\n action: string\n review: {\n id: number\n body: string\n state: string // \"approved\", \"changes_requested\", \"commented\"\n html_url: string\n user: GitHubUser\n }\n pull_request: {\n number: number\n title: string\n html_url: string\n }\n repository: GitHubRepo\n}\n\ninterface GitHubPRReviewCommentEvent {\n action: string\n comment: {\n id: number\n body: string\n html_url: string\n path: string\n line: number\n user: GitHubUser\n }\n pull_request: {\n number: number\n title: string\n html_url: string\n }\n repository: GitHubRepo\n}\n\ninterface GitHubPushEvent {\n ref: string\n commits: Array<{\n id: string\n message: string\n author: { name: string; username: string }\n }>\n repository: GitHubRepo\n pusher: { name: string }\n}\n\ninterface GitHubIssueEvent {\n action: string\n issue: {\n number: number\n title: string\n body: string\n state: string\n html_url: string\n user: GitHubUser\n assignees: GitHubUser[]\n }\n repository: GitHubRepo\n}\n\ntype GitHubEvent = GitHubIssueCommentEvent | GitHubPREvent | GitHubPRReviewEvent\n | GitHubPRReviewCommentEvent | GitHubPushEvent | GitHubIssueEvent | Record<string, unknown>\n\nexport class GitHubAdapter implements ChannelAdapter {\n readonly name = \"github\"\n private config: GitHubChannelConfig\n private handler?: (msg: IncomingMessage) => Promise<void>\n private globalToken?: string\n private botUsernames: Set<string> = new Set()\n private sentCommentIds: Set<string> = new Set()\n private log: (...args: unknown[]) => void\n private sendCommentForwarder?: (node: string, repo: string, issueNumber: number, agentId: string, text: string) => Promise<string>\n // GitHub App auth state\n private appPrivateKey?: string\n private installationTokens: Map<string, { token: string; expiresAt: number }> = new Map()\n\n constructor(config: GitHubChannelConfig, log: (...args: unknown[]) => void = console.error.bind(console, \"[github]\")) {\n this.config = config\n this.log = log\n }\n\n setSendCommentForwarder(fn: (node: string, repo: string, issueNumber: number, agentId: string, text: string) => Promise<string>): void {\n this.sendCommentForwarder = fn\n }\n\n onMessage(handler: (msg: IncomingMessage) => Promise<void>): void {\n this.handler = handler\n }\n\n async start(): Promise<void> {\n // GitHub App auth (preferred — posts as the app bot identity)\n const hasAppAuth = (this.config.clientId || this.config.appId) && this.config.privateKeyFile\n if (hasAppAuth) {\n try {\n this.appPrivateKey = readFileSync(this.config.privateKeyFile!, \"utf-8\").trim()\n this.log(`GitHub App mode: issuer=${this.config.clientId || this.config.appId}`)\n\n // Verify the app works by fetching app info\n const jwt = this.generateAppJWT()\n const res = await fetch(\"https://api.github.com/app\", {\n headers: { Authorization: `Bearer ${jwt}`, Accept: \"application/vnd.github+json\", \"User-Agent\": \"AgentX\" },\n })\n if (res.ok) {\n const data = await res.json() as any\n this.log(`GitHub App: \"${data.name}\" (slug: ${data.slug})`)\n // Add the app's bot username to cascade prevention\n if (data.slug) this.botUsernames.add(`${data.slug}[bot]`.toLowerCase())\n } else {\n this.log(`GitHub App auth failed: ${res.status} ${await res.text().catch(() => \"\")}`)\n }\n } catch (e: any) {\n this.log(`Failed to load GitHub App key: ${e.message}`)\n }\n }\n\n // PAT fallback (legacy — posts as token owner)\n if (!this.appPrivateKey) {\n this.globalToken = this.resolveToken(this.config.token, this.config.tokenFile)\n if (this.globalToken) {\n try {\n const res = await fetch(\"https://api.github.com/user\", {\n headers: { Authorization: `Bearer ${this.globalToken}`, \"User-Agent\": \"AgentX\" },\n })\n if (res.ok) {\n const data = await res.json() as any\n this.log(`GitHub PAT fallback: ${data.login}`)\n }\n } catch (e: any) {\n this.log(`Failed to resolve GitHub user: ${e.message}`)\n }\n }\n }\n\n // Register agent mapping usernames for cascade prevention\n for (const mapping of this.config.agentMappings || []) {\n for (const username of mapping.githubUsernames) {\n this.botUsernames.add(username.toLowerCase())\n }\n }\n\n this.log(`GitHub channel started (${this.config.routes.length} routes, ${this.botUsernames.size} bot users)`)\n }\n\n async stop(): Promise<void> {\n // No persistent server — webhooks come through the main daemon HTTP server\n }\n\n /**\n * Handle an incoming webhook request from the daemon's HTTP server.\n * Called by the daemon when it detects a GitHub webhook (X-GitHub-Event header).\n * @param rawBody - raw request body string, needed for signature verification\n */\n async handleWebhook(headers: Record<string, string | string[] | undefined>, body: Record<string, unknown>, rawBody?: string): Promise<void> {\n // Validate signature\n if (this.config.webhookSecret) {\n const signature = headers[\"x-hub-signature-256\"] as string\n const payload = rawBody || JSON.stringify(body)\n if (!this.verifySignature(payload, signature)) {\n this.log(\"GitHub webhook signature verification failed\")\n return\n }\n }\n\n const event = headers[\"x-github-event\"] as string || \"unknown\"\n const repo = (body.repository as any)?.full_name || \"unknown\"\n this.log(`GitHub event: ${event} from ${repo}`)\n\n switch (event) {\n case \"issue_comment\":\n await this.handleIssueComment(body as unknown as GitHubIssueCommentEvent)\n break\n case \"pull_request\":\n await this.handlePR(body as unknown as GitHubPREvent)\n break\n case \"pull_request_review\":\n await this.handlePRReview(body as unknown as GitHubPRReviewEvent)\n break\n case \"pull_request_review_comment\":\n await this.handlePRReviewComment(body as unknown as GitHubPRReviewCommentEvent)\n break\n case \"issues\":\n await this.handleIssue(body as unknown as GitHubIssueEvent)\n break\n case \"push\":\n await this.handlePush(body as unknown as GitHubPushEvent)\n break\n case \"ping\":\n this.log(\"GitHub webhook ping received\")\n break\n default:\n this.log(`Unhandled GitHub event: ${event}`)\n }\n }\n\n /**\n * Send a reply — posts a comment on a GitHub issue or PR.\n * chatId format: \"owner/repo:issue:123\" or \"owner/repo:pull:456\"\n */\n async send(msg: OutgoingMessage): Promise<string> {\n const parts = msg.chatId.split(\":\")\n if (parts.length < 3) {\n this.log(`Invalid chatId for GitHub reply: ${msg.chatId}`)\n return \"\"\n }\n const issueNumber = parseInt(parts.pop()!, 10)\n const type = parts.pop()! // \"issue\" or \"pull\"\n const repo = parts.join(\":\")\n\n // Identity: use per-agent token, forward via mesh, or fall back to global\n const mapping = this.config.agentMappings?.find(m => m.agentId === msg.agentId)\n const agentToken = this.getAgentToken(msg.agentId)\n\n // Build comment body — when using App auth the bot identity is implicit,\n // so we only add the agent header for PAT mode.\n const agentLabel = msg.agentId || \"unknown\"\n const usingApp = !!this.appPrivateKey\n const agentHeader = usingApp ? \"\" : `> 🤖 **${agentLabel}** (via AgentX)\\n\\n`\n const commentBody = markBody(`${agentHeader}${msg.text}`, agentLabel)\n\n // Resolve token: per-agent PAT > App installation token > global PAT.\n // Prefer the App token over forwarding to a peer — the App posts as the\n // bot identity (e.g. \"noqta-agentx[bot]\"), whereas a peer's PAT posts as\n // its owner. Forwarding is only useful when this node has no way to post\n // for the target repo (no App, no PAT).\n const token = agentToken || await this.getTokenForRepo(repo)\n\n // Fall back to forwarding to a peer (e.g. macbook's PAT) only when this\n // node cannot post for the repo at all.\n if (!token && mapping?.node && this.sendCommentForwarder) {\n try {\n const commentId = await this.sendCommentForwarder(mapping.node, repo, issueNumber, agentLabel, commentBody)\n if (commentId) {\n this.sentCommentIds.add(commentId)\n return commentId\n }\n this.log(`GitHub send forward to \"${mapping.node}\" returned empty — no local token available either`)\n } catch (e: any) {\n this.log(`GitHub send forward to \"${mapping.node}\" failed: ${e.message}`)\n }\n }\n\n if (!token) {\n this.log(`No GitHub token available for posting comment (agent: ${msg.agentId})`)\n return \"\"\n }\n\n const endpoint = `https://api.github.com/repos/${repo}/issues/${issueNumber}/comments`\n try {\n const res = await fetch(endpoint, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${token}`,\n Accept: \"application/vnd.github+json\",\n \"User-Agent\": \"AgentX\",\n },\n body: JSON.stringify({ body: commentBody }),\n })\n if (!res.ok) {\n const text = await res.text()\n this.log(`GitHub API error: ${res.status} ${text.slice(0, 200)}`)\n return \"\"\n }\n const data = await res.json() as any\n const commentId = String(data.id || \"\")\n if (commentId) this.sentCommentIds.add(commentId)\n return commentId\n } catch (e: any) {\n this.log(`GitHub send error: ${e.message}`)\n return \"\"\n }\n }\n\n async react(chatId: string, messageId: string, emoji?: string): Promise<void> {\n // GitHub reactions on comments: POST /repos/{owner}/{repo}/issues/comments/{comment_id}/reactions\n const parts = chatId.split(\":\")\n if (parts.length < 3) return\n parts.pop() // issue number\n parts.pop() // type\n const repo = parts.join(\":\")\n const token = await this.getTokenForRepo(repo)\n if (!token || !messageId) return\n\n try {\n await fetch(`https://api.github.com/repos/${repo}/issues/comments/${messageId}/reactions`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${token}`,\n Accept: \"application/vnd.github+json\",\n \"User-Agent\": \"AgentX\",\n },\n body: JSON.stringify({ content: \"eyes\" }),\n })\n } catch { /* best effort */ }\n }\n\n async getChannelMeta(chatId: string): Promise<ChannelMeta | undefined> {\n const parts = chatId.split(\":\")\n const repo = parts[0] + (parts.length > 1 ? \"\" : \"\")\n const type = parts.length >= 2 ? parts[parts.length - 2] : undefined\n const number = parts.length >= 3 ? parts[parts.length - 1] : undefined\n\n // Rebuild repo from all parts except last two\n const repoParts = parts.slice(0, -2)\n const repoName = repoParts.length > 0 ? repoParts.join(\":\") : parts[0]\n\n const agents: ChannelMeta[\"agents\"] = []\n for (const route of this.config.routes) {\n if (route.repo === repoName || route.repo === \"*\") {\n agents.push({ id: route.agent, name: route.agent })\n }\n }\n\n return {\n channel: \"github\",\n agents,\n project: repoName,\n issue: type && number ? { type, iid: number, title: \"\" } : undefined,\n facts: [\n \"This is a GitHub webhook event — respond as a GitHub comment\",\n \"Use `gh` CLI or the GitHub API to interact with the repository\",\n \"Do NOT use Telegram handles or delegate to other agents\",\n ],\n }\n }\n\n // --- Event handlers ---\n\n private async handleIssueComment(event: GitHubIssueCommentEvent): Promise<void> {\n if (!this.handler) {\n this.log(`[github] issue_comment dropped: no handler attached`)\n return\n }\n if (event.action !== \"created\") {\n this.log(`[github] issue_comment skipped: action=\"${event.action}\" (only \"created\" routes)`)\n return\n }\n\n const comment = event.comment\n const repo = event.repository.full_name\n const user = comment.user\n\n // Cascade prevention: check for AgentX signature\n const sourceAgent = detectAgentxMarker(comment.body)\n if (sourceAgent) {\n this.log(`AgentX comment from ${sourceAgent}, skipping (comment ${comment.id})`)\n return\n }\n\n // Skip comments from known bot users (e.g. noqta-agentx[bot] when using App auth)\n if (this.isBotUser(user.login)) {\n this.log(`Bot user ${user.login}, skipping (comment ${comment.id})`)\n return\n }\n\n const commentId = String(comment.id)\n if (this.sentCommentIds.has(commentId)) {\n this.log(`[github] echo skip: comment ${commentId} matched our own post`)\n this.sentCommentIds.delete(commentId)\n return\n }\n\n const isPR = !!event.issue.pull_request\n const type = isPR ? \"pull\" : \"issue\"\n const chatId = `${repo}:${type}:${event.issue.number}`\n const agentId = this.resolveAgent(repo)\n const mapping = agentId ? this.config.agentMappings?.find(m => m.agentId === agentId) : undefined\n\n const channelMeta = await this.getChannelMeta(chatId)\n\n const incoming: IncomingMessage = {\n id: commentId,\n channel: \"github\",\n accountId: \"default\",\n sender: {\n id: chatId,\n name: user.login,\n username: user.login,\n },\n text: `[GitHub ${isPR ? \"PR\" : \"Issue\"} #${event.issue.number}: ${event.issue.title}]\\n${user.login} commented:\\n${comment.body}`,\n timestamp: new Date(),\n raw: event,\n resolvedAgent: agentId,\n preferNode: mapping?.node,\n channelMeta,\n }\n\n // React with 👀\n this.reactToComment(repo, comment.id).catch(() => {})\n\n this.handler(incoming).catch(e => this.log(`Error handling issue_comment: ${e.message}`))\n }\n\n private async handlePR(event: GitHubPREvent): Promise<void> {\n if (!this.handler) return\n\n const pr = event.pull_request\n const repo = event.repository.full_name\n\n // Skip bot PRs\n if (this.isBotUser(pr.user.login)) return\n\n // Only handle actionable events\n const actionable = [\"opened\", \"reopened\", \"ready_for_review\"]\n if (!actionable.includes(event.action)) return\n\n const chatId = `${repo}:pull:${pr.number}`\n const agentId = this.resolveAgent(repo)\n const mapping = agentId ? this.config.agentMappings?.find(m => m.agentId === agentId) : undefined\n\n const channelMeta = await this.getChannelMeta(chatId)\n\n const incoming: IncomingMessage = {\n id: `pr-${repo}-${pr.number}`,\n channel: \"github\",\n accountId: \"default\",\n sender: {\n id: chatId,\n name: pr.user.login,\n username: pr.user.login,\n },\n text: `[GitHub PR #${pr.number} ${event.action}]: ${pr.title}\\nBranch: ${pr.head.ref} -> ${pr.base.ref}\\n${pr.body?.slice(0, 500) || \"\"}\\nURL: ${pr.html_url}`,\n timestamp: new Date(),\n raw: event,\n resolvedAgent: agentId,\n preferNode: mapping?.node,\n channelMeta,\n }\n\n this.handler(incoming).catch(e => this.log(`Error handling PR: ${e.message}`))\n }\n\n private async handlePRReview(event: GitHubPRReviewEvent): Promise<void> {\n if (!this.handler || event.action !== \"submitted\") return\n\n const review = event.review\n const repo = event.repository.full_name\n\n if (this.isBotUser(review.user.login)) return\n\n // Only handle reviews with actual content\n if (review.state === \"commented\" && !review.body) return\n\n const chatId = `${repo}:pull:${event.pull_request.number}`\n const agentId = this.resolveAgent(repo)\n const mapping = agentId ? this.config.agentMappings?.find(m => m.agentId === agentId) : undefined\n\n const incoming: IncomingMessage = {\n id: `review-${review.id}`,\n channel: \"github\",\n accountId: \"default\",\n sender: {\n id: chatId,\n name: review.user.login,\n username: review.user.login,\n },\n text: `[GitHub PR #${event.pull_request.number} Review (${review.state})]: ${event.pull_request.title}\\n${review.user.login} reviewed:\\n${review.body || \"(no body)\"}`,\n timestamp: new Date(),\n raw: event,\n resolvedAgent: agentId,\n preferNode: mapping?.node,\n }\n\n this.handler(incoming).catch(e => this.log(`Error handling PR review: ${e.message}`))\n }\n\n private async handlePRReviewComment(event: GitHubPRReviewCommentEvent): Promise<void> {\n if (!this.handler || event.action !== \"created\") return\n\n const comment = event.comment\n const repo = event.repository.full_name\n\n if (this.isBotUser(comment.user.login)) return\n\n // Skip AgentX-signed comments\n if (detectAgentxMarker(comment.body)) return\n\n const chatId = `${repo}:pull:${event.pull_request.number}`\n const agentId = this.resolveAgent(repo)\n const mapping = agentId ? this.config.agentMappings?.find(m => m.agentId === agentId) : undefined\n\n const incoming: IncomingMessage = {\n id: `review-comment-${comment.id}`,\n channel: \"github\",\n accountId: \"default\",\n sender: {\n id: chatId,\n name: comment.user.login,\n username: comment.user.login,\n },\n text: `[GitHub PR #${event.pull_request.number} Review Comment]: ${event.pull_request.title}\\n${comment.user.login} commented on ${comment.path}:${comment.line}:\\n${comment.body}`,\n timestamp: new Date(),\n raw: event,\n resolvedAgent: agentId,\n preferNode: mapping?.node,\n }\n\n this.handler(incoming).catch(e => this.log(`Error handling PR review comment: ${e.message}`))\n }\n\n private async handleIssue(event: GitHubIssueEvent): Promise<void> {\n if (!this.handler) return\n\n const issue = event.issue\n const repo = event.repository.full_name\n\n if (this.isBotUser(issue.user.login)) return\n\n const actionable = [\"opened\", \"assigned\", \"reopened\"]\n if (!actionable.includes(event.action)) return\n\n const chatId = `${repo}:issue:${issue.number}`\n const agentId = this.resolveAgent(repo)\n const mapping = agentId ? this.config.agentMappings?.find(m => m.agentId === agentId) : undefined\n\n const incoming: IncomingMessage = {\n id: `issue-${repo}-${issue.number}`,\n channel: \"github\",\n accountId: \"default\",\n sender: {\n id: chatId,\n name: issue.user.login,\n username: issue.user.login,\n },\n text: `[GitHub Issue #${issue.number} ${event.action}]: ${issue.title}\\n${issue.body?.slice(0, 500) || \"\"}\\nURL: ${issue.html_url}`,\n timestamp: new Date(),\n raw: event,\n resolvedAgent: agentId,\n preferNode: mapping?.node,\n }\n\n this.handler(incoming).catch(e => this.log(`Error handling issue: ${e.message}`))\n }\n\n private async handlePush(event: GitHubPushEvent): Promise<void> {\n if (!this.handler) return\n\n const repo = event.repository.full_name\n const ref = event.ref\n const commits = event.commits || []\n if (commits.length === 0) return\n\n const agentId = this.resolveAgent(repo)\n const mapping = agentId ? this.config.agentMappings?.find(m => m.agentId === agentId) : undefined\n\n const commitSummary = commits.slice(0, 5).map(c =>\n ` - ${c.message.split(\"\\n\")[0]} (${c.author?.name || c.author?.username || \"\"})`\n ).join(\"\\n\")\n\n const incoming: IncomingMessage = {\n id: `push-${commits[0]?.id?.slice(0, 8) || Date.now()}`,\n channel: \"github\",\n accountId: \"default\",\n sender: {\n id: `${repo}:push:${ref}`,\n name: event.pusher.name,\n username: event.pusher.name,\n },\n text: `[GitHub Push] ${repo} ref:${ref}\\n${commits.length} commit(s):\\n${commitSummary}`,\n timestamp: new Date(),\n raw: event,\n resolvedAgent: agentId,\n preferNode: mapping?.node,\n }\n\n this.handler(incoming).catch(e => this.log(`Error handling push: ${e.message}`))\n }\n\n // --- Helpers ---\n\n private resolveAgent(repo: string): string | undefined {\n for (const route of this.config.routes) {\n if (route.repo === repo || route.repo === \"*\") {\n return route.agent\n }\n }\n return undefined\n }\n\n private isBotUser(username: string): boolean {\n return this.botUsernames.has(username.toLowerCase())\n }\n\n private getAgentToken(agentId?: string): string | undefined {\n if (!agentId || !this.config.agentMappings?.length) return undefined\n const mapping = this.config.agentMappings.find(m => m.agentId === agentId)\n if (!mapping) return undefined\n return this.resolveToken(mapping.token, mapping.tokenFile)\n }\n\n private resolveToken(token?: string, tokenFile?: string): string | undefined {\n if (token) return token\n if (tokenFile) {\n try {\n return readFileSync(tokenFile, \"utf-8\").trim().split(\"\\n\")[0].trim()\n } catch (e: any) {\n this.log(`Failed to read token file ${tokenFile}: ${e.message}`)\n }\n }\n return undefined\n }\n\n private verifySignature(payload: string, signature?: string): boolean {\n if (!signature || !this.config.webhookSecret) return false\n const expected = \"sha256=\" + createHmac(\"sha256\", this.config.webhookSecret).update(payload).digest(\"hex\")\n return signature === expected\n }\n\n private async reactToComment(repo: string, commentId: number): Promise<void> {\n const token = await this.getTokenForRepo(repo)\n if (!token) return\n try {\n await fetch(`https://api.github.com/repos/${repo}/issues/comments/${commentId}/reactions`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${token}`,\n Accept: \"application/vnd.github+json\",\n \"User-Agent\": \"AgentX\",\n },\n body: JSON.stringify({ content: \"eyes\" }),\n })\n } catch { /* best effort */ }\n }\n\n // --- GitHub App auth ---\n\n /**\n * Get a valid token for a given repo.\n * Prefers GitHub App installation token, falls back to PAT.\n */\n async getTokenForRepo(repo?: string): Promise<string | undefined> {\n if (this.appPrivateKey && this.config.appId && repo) {\n const token = await this.getInstallationToken(repo)\n if (token) return token\n }\n return this.globalToken\n }\n\n /**\n * Generate a JWT signed with the GitHub App private key.\n * Valid for 10 minutes (GitHub's max).\n */\n private generateAppJWT(): string {\n const now = Math.floor(Date.now() / 1000)\n const payload = {\n iat: now - 60, // issued at (60s clock skew buffer)\n exp: now + 600, // expires in 10 min\n iss: this.config.clientId || String(this.config.appId),\n }\n\n // Build JWT manually (header.payload.signature)\n const header = Buffer.from(JSON.stringify({ alg: \"RS256\", typ: \"JWT\" })).toString(\"base64url\")\n const body = Buffer.from(JSON.stringify(payload)).toString(\"base64url\")\n const unsigned = `${header}.${body}`\n\n const sign = createSign(\"RSA-SHA256\")\n sign.update(unsigned)\n const signature = sign.sign(this.appPrivateKey!, \"base64url\")\n\n return `${unsigned}.${signature}`\n }\n\n /**\n * Get an installation access token for a repo.\n * Caches tokens until they expire (1 hour).\n */\n private async getInstallationToken(repo: string): Promise<string | undefined> {\n // Check cache\n const cached = this.installationTokens.get(repo)\n if (cached && cached.expiresAt > Date.now() + 60000) {\n return cached.token\n }\n\n try {\n const jwt = this.generateAppJWT()\n\n // Find installation for this repo\n const installRes = await fetch(`https://api.github.com/repos/${repo}/installation`, {\n headers: {\n Authorization: `Bearer ${jwt}`,\n Accept: \"application/vnd.github+json\",\n \"User-Agent\": \"AgentX\",\n },\n })\n if (!installRes.ok) {\n this.log(`GitHub App not installed on ${repo}: ${installRes.status}`)\n return undefined\n }\n const install = await installRes.json() as any\n\n // Create installation access token\n const tokenRes = await fetch(`https://api.github.com/app/installations/${install.id}/access_tokens`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${jwt}`,\n Accept: \"application/vnd.github+json\",\n \"User-Agent\": \"AgentX\",\n },\n })\n if (!tokenRes.ok) {\n this.log(`Failed to get installation token for ${repo}: ${tokenRes.status}`)\n return undefined\n }\n const tokenData = await tokenRes.json() as any\n const expiresAt = new Date(tokenData.expires_at).getTime()\n\n this.installationTokens.set(repo, { token: tokenData.token, expiresAt })\n this.log(`Got installation token for ${repo} (expires: ${tokenData.expires_at})`)\n return tokenData.token\n } catch (e: any) {\n this.log(`Installation token error for ${repo}: ${e.message}`)\n return undefined\n }\n }\n}\n","import type { ServerResponse } from \"http\"\n\n// --- WebRTC signal broker ---\n//\n// AgentX itself never carries audio/video frames. This broker only relays the\n// control-plane messages (SDP offer/answer + ICE candidates + hangup) between a\n// local browser and a remote peer's browser — the media connection is negotiated\n// directly between the two browsers via WebRTC (SRTP over UDP).\n//\n// Flow:\n// Alice's browser -- SSE + POST --> agentx-A -- HTTP /webrtc/signal --> agentx-B -- SSE --> Bob's browser\n//\n// Subscribers index by (callId, recipient). A call is two-sided so there are\n// typically two subscribers per callId: one on each daemon.\n\nexport type WebRtcSignalKind = \"offer\" | \"answer\" | \"ice\" | \"hangup\" | \"ring\"\n\nexport interface WebRtcSignal {\n kind: WebRtcSignalKind\n callId: string\n /** Agent/peer name the signal is FROM (which daemon sent it). */\n from: string\n /** Agent/peer name the signal is FOR (which local subscriber should receive). */\n to: string\n /** SDP blob for offer/answer. */\n sdp?: string\n /** ICE candidate payload. */\n candidate?: {\n candidate: string\n sdpMid: string | null\n sdpMLineIndex: number | null\n usernameFragment?: string | null\n }\n /** Optional short reason for hangup/ring. */\n reason?: string\n}\n\n/** A subscriber may be either an SSE-backed browser (we write the raw event\n * stream into its ServerResponse) OR an in-process consumer like the\n * WebRtcBot (we just call its callback). One Set, two delivery shapes. */\ntype SubscriberSink =\n | { kind: \"sse\"; res: ServerResponse }\n | { kind: \"callback\"; deliver: (signal: WebRtcSignal) => void }\n\ninterface Subscriber {\n callId: string\n recipient: string\n sink: SubscriberSink\n}\n\ninterface BufferedSignal {\n signal: WebRtcSignal\n expiresAt: number\n}\n\n/** How long (ms) to hold a signal that arrived with no matching subscriber.\n * 30s comfortably covers the \"caller offered before callee joined\" race\n * without keeping stale ICE around long enough to confuse a later call. */\nconst BUFFER_TTL_MS = 30_000\n\nexport interface WebRtcSignalForwarder {\n /** POST the signal to the named mesh peer's /webrtc/signal endpoint.\n * Returns true on 2xx. */\n (peerName: string, signal: WebRtcSignal): Promise<boolean>\n}\n\nexport interface RingHandler {\n /** Invoked when a ring signal arrives from a remote peer. The broker\n * dedups by callId so this fires at most once per call over a short TTL. */\n (signal: WebRtcSignal): void | Promise<void>\n}\n\n/** Dedup window for ring notifications. A single call shouldn't ping the\n * callee's channels twice, but two distinct calls on the same day must\n * each notify. Five minutes comfortably covers retry-after-missed-call\n * without muting legitimate repeat dials. */\nconst RING_DEDUP_TTL_MS = 5 * 60_000\n\n/** Normalize a peer/node name for tolerant matching: lowercase and strip any\n * non-alphanumeric characters. This papers over the fact that a node's\n * self-reported `node.name` and the name other peers use for it in their\n * `mesh.peers[].name` list frequently disagree on case, spaces, or hyphens\n * (\"MacBook-Local\" vs \"macbook-local\" vs \"macbook_local\"). Signaling is\n * looser than agent dispatch because a single call is a tight two-party\n * negotiation where ambiguity is vanishingly unlikely. */\nexport function normalizeName(name: string): string {\n return name.toLowerCase().replace(/[^a-z0-9]/g, \"\")\n}\n\nexport class WebRtcSignalBroker {\n private subs: Set<Subscriber> = new Set()\n private log: (...args: unknown[]) => void\n private forwarder?: WebRtcSignalForwarder\n /** Local peer/agent name — used when a browser client identifies as \"us\". */\n private localName: string\n /** Peer names allowed to initiate *inbound* calls. Empty means allow all.\n * Stored pre-normalized so every comparison is apples-to-apples. */\n private allowedCallers: Set<string>\n /** Signals that arrived before their matching subscriber showed up.\n * Keyed by `${callId}|${normalizedRecipient}`; flushed on subscribe. */\n private pending: Map<string, BufferedSignal[]> = new Map()\n private cleanupTimer?: ReturnType<typeof setInterval>\n /** Ring notification callback (set by daemon wiring). */\n private ringHandler?: RingHandler\n /** Recent ring callIds, for dedup. Value is the expiry timestamp. */\n private recentRings: Map<string, number> = new Map()\n\n constructor(\n localName: string,\n allowedCallers: string[] = [],\n log: (...args: unknown[]) => void = console.error.bind(console, \"[webrtc]\"),\n ) {\n this.localName = localName\n this.allowedCallers = new Set(allowedCallers.map(normalizeName))\n this.log = log\n // Sweep expired buffered signals every 10s.\n this.cleanupTimer = setInterval(() => this.cleanupExpired(), 10_000)\n }\n\n private pendingKey(callId: string, recipient: string): string {\n return `${callId}|${normalizeName(recipient)}`\n }\n\n private cleanupExpired(): void {\n const now = Date.now()\n for (const [key, bucket] of this.pending) {\n const kept = bucket.filter(b => b.expiresAt > now)\n if (kept.length === 0) this.pending.delete(key)\n else if (kept.length !== bucket.length) this.pending.set(key, kept)\n }\n for (const [callId, expiry] of this.recentRings) {\n if (expiry <= now) this.recentRings.delete(callId)\n }\n }\n\n setForwarder(fn: WebRtcSignalForwarder): void {\n this.forwarder = fn\n }\n\n setRingHandler(fn: RingHandler): void {\n this.ringHandler = fn\n }\n\n /** Register an SSE subscriber (a browser tab). Returns an unsubscribe fn. */\n subscribe(callId: string, recipient: string, res: ServerResponse): () => void {\n return this.addSubscriber(callId, recipient, { kind: \"sse\", res })\n }\n\n /** Register an in-process subscriber (a server-side bot). The callback fires\n * for every signal addressed to `recipient` on `callId`. Returns unsubscribe. */\n subscribeInternal(callId: string, recipient: string, deliver: (signal: WebRtcSignal) => void): () => void {\n return this.addSubscriber(callId, recipient, { kind: \"callback\", deliver })\n }\n\n private addSubscriber(callId: string, recipient: string, sink: SubscriberSink): () => void {\n const sub: Subscriber = { callId, recipient, sink }\n this.subs.add(sub)\n const tag = sink.kind === \"sse\" ? \"sse\" : \"internal\"\n this.log(`subscribe[${tag}] call=${callId} as=${recipient} (${this.subs.size} total)`)\n\n // Flush any signals that arrived before this subscriber. Common race: the\n // caller offers before the callee opens its page, or SSE connects slightly\n // after the POST that sent the offer.\n const key = this.pendingKey(callId, recipient)\n const buffered = this.pending.get(key)\n if (buffered && buffered.length) {\n const now = Date.now()\n const fresh = buffered.filter(b => b.expiresAt > now)\n for (const { signal } of fresh) {\n this.deliverTo(sub, signal)\n }\n this.pending.delete(key)\n if (fresh.length) this.log(`flushed ${fresh.length} buffered signal(s) to new subscriber call=${callId} as=${recipient}`)\n }\n\n return () => {\n this.subs.delete(sub)\n this.log(`unsubscribe[${tag}] call=${callId} as=${recipient} (${this.subs.size} remaining)`)\n }\n }\n\n /** Deliver a single signal to a subscriber, regardless of sink kind.\n * Returns false if the sink looks dead (caller should drop the sub). */\n private deliverTo(sub: Subscriber, signal: WebRtcSignal): boolean {\n try {\n if (sub.sink.kind === \"sse\") {\n sub.sink.res.write(`event: signal\\ndata: ${JSON.stringify(signal)}\\n\\n`)\n } else {\n sub.sink.deliver(signal)\n }\n return true\n } catch {\n return false\n }\n }\n\n /** Called when a browser POSTs a signal. Routes locally if the target is\n * this node OR matches a local in-process subscriber (e.g. the AI bot\n * peer); otherwise forwards through the mesh. */\n async handleOutgoing(signal: WebRtcSignal): Promise<{ ok: boolean; error?: string }> {\n // Local fan-out: addressed to ourselves (loopback) OR addressed to a\n // peer we're hosting in-process (the bot subscribes via callback, not via\n // mesh — its name like \"bot:atlas\" isn't in mesh.peers[]).\n const wantRecipient = normalizeName(signal.to)\n const hasLocalSub = wantRecipient === normalizeName(this.localName) || this.hasSubscriberFor(signal.callId, wantRecipient)\n if (hasLocalSub) {\n this.fanOut(signal)\n return { ok: true }\n }\n if (!this.forwarder) {\n return { ok: false, error: \"mesh forwarder not wired\" }\n }\n const ok = await this.forwarder(signal.to, signal)\n return ok ? { ok } : { ok: false, error: `forward to \"${signal.to}\" failed` }\n }\n\n private hasSubscriberFor(callId: string, normalizedRecipient: string): boolean {\n for (const sub of this.subs) {\n if (sub.callId === callId && normalizeName(sub.recipient) === normalizedRecipient) return true\n }\n return false\n }\n\n /** Called when the daemon receives a signal from a remote peer via /webrtc/signal. */\n handleIncoming(signal: WebRtcSignal): { ok: boolean; error?: string } {\n // Enforce allowedCallers on the *first* signal of a call (offer or ring).\n // Answers/ICE/hangup for an already-established call go through — the\n // authoritative gate is the offer.\n if ((signal.kind === \"offer\" || signal.kind === \"ring\") && this.allowedCallers.size > 0) {\n if (!this.allowedCallers.has(normalizeName(signal.from))) {\n this.log(`reject inbound ${signal.kind} from \"${signal.from}\": not in allowedCallers`)\n return { ok: false, error: \"caller not allowed\" }\n }\n }\n\n // Rings are purely for out-of-band notification — don't fan out over SSE\n // or buffer (they'd be noise on the callee's page). Dedup by callId so\n // the same call only pings the callee's channels once.\n if (signal.kind === \"ring\") {\n const now = Date.now()\n const expiry = this.recentRings.get(signal.callId)\n if (expiry && expiry > now) {\n this.log(`dedup ring for call=${signal.callId} (already notified)`)\n return { ok: true }\n }\n this.recentRings.set(signal.callId, now + RING_DEDUP_TTL_MS)\n if (this.ringHandler) {\n try { void this.ringHandler(signal) }\n catch (e: any) { this.log(`ringHandler threw: ${e.message}`) }\n } else {\n this.log(`ring received for call=${signal.callId} but no handler wired`)\n }\n return { ok: true }\n }\n\n const delivered = this.fanOut(signal)\n if (delivered === 0) {\n // Callee's page isn't up yet. Buffer the signal so the subscribe() call\n // that lands within BUFFER_TTL_MS replays it. Keeps the flow robust to\n // normal race conditions (SSE reconnects, slow first paint) without\n // forcing the caller to implement retry.\n const key = this.pendingKey(signal.callId, signal.to)\n const bucket = this.pending.get(key) || []\n bucket.push({ signal, expiresAt: Date.now() + BUFFER_TTL_MS })\n this.pending.set(key, bucket)\n this.log(`buffered ${signal.kind} for call=${signal.callId} to=${signal.to} (no subscriber yet, ${bucket.length} pending)`)\n }\n return { ok: true }\n }\n\n private fanOut(signal: WebRtcSignal): number {\n let delivered = 0\n const wantRecipient = normalizeName(signal.to)\n for (const sub of this.subs) {\n if (sub.callId !== signal.callId) continue\n if (normalizeName(sub.recipient) !== wantRecipient) continue\n if (this.deliverTo(sub, signal)) delivered++\n else this.subs.delete(sub)\n }\n return delivered\n }\n\n /** Active subscriber count — for /mesh or admin diagnostics. */\n get subscriberCount(): number {\n return this.subs.size\n }\n\n /** Close every open SSE stream (daemon shutdown). Internal subscribers\n * are simply dropped — they're owned by the bot manager which has its own\n * shutdown path. */\n shutdown(): void {\n if (this.cleanupTimer) clearInterval(this.cleanupTimer)\n for (const sub of this.subs) {\n if (sub.sink.kind === \"sse\") {\n try { sub.sink.res.end() } catch { /* already closed */ }\n }\n }\n this.subs.clear()\n this.pending.clear()\n }\n}\n","// --- Browser page for P2P A/V calls ---\n//\n// Served by the daemon at GET /call when channels.webrtc.enabled is true.\n// Media flows browser↔browser via WebRTC; agentx only carries the\n// SDP/ICE/hangup signals on the control plane.\n//\n// The page is intentionally framework-free and self-contained so it can be\n// served as a single response.\n\nexport const CALL_PAGE_HTML = `<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n <title>AgentX Call</title>\n <style>\n :root { color-scheme: dark; }\n body { margin: 0; font: 14px/1.4 -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif; background: #0e1116; color: #e6edf3; }\n header { padding: 10px 14px; border-bottom: 1px solid #21262d; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }\n header h1 { font-size: 15px; margin: 0; font-weight: 600; }\n header .sp { flex: 1; }\n main { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; padding: 8px; }\n video { width: 100%; aspect-ratio: 16 / 9; background: #161b22; border-radius: 6px; object-fit: cover; }\n .tile { position: relative; }\n .tile .label { position: absolute; top: 6px; left: 8px; font-size: 11px; background: rgba(0,0,0,.55); padding: 2px 6px; border-radius: 3px; }\n form { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }\n input, select, button { background: #21262d; color: #e6edf3; border: 1px solid #30363d; border-radius: 4px; padding: 6px 10px; font: inherit; }\n button { cursor: pointer; }\n button:hover { background: #30363d; }\n button.primary { background: #238636; border-color: #2ea043; }\n button.primary:hover { background: #2ea043; }\n button.danger { background: #da3633; border-color: #f85149; }\n button:disabled { opacity: .5; cursor: not-allowed; }\n #log { padding: 8px 14px; max-height: 160px; overflow: auto; font: 12px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace; border-top: 1px solid #21262d; color: #8b949e; }\n #log .err { color: #f85149; }\n #log .ok { color: #56d364; }\n .status { font-size: 12px; padding: 2px 8px; border-radius: 10px; background: #30363d; }\n .status.ok { background: #1f6f3d; color: #aff5b4; }\n .status.err { background: #6e2b2b; color: #ffabab; }\n </style>\n</head>\n<body>\n <header>\n <h1>AgentX&nbsp;/&nbsp;P2P Call</h1>\n <span id=\"selfName\" class=\"status\">…</span>\n <span class=\"sp\"></span>\n <form id=\"form\">\n <input id=\"to\" placeholder=\"peer name\" required autocomplete=\"off\" />\n <input id=\"callId\" placeholder=\"call id\" required autocomplete=\"off\" />\n <button type=\"submit\" id=\"joinBtn\" class=\"primary\">Join</button>\n <button type=\"button\" id=\"hangupBtn\" class=\"danger\" disabled>Hang up</button>\n </form>\n </header>\n <main>\n <div class=\"tile\"><video id=\"local\" autoplay playsinline muted></video><span class=\"label\">You</span></div>\n <div class=\"tile\"><video id=\"remote\" autoplay playsinline></video><span class=\"label\" id=\"remoteLabel\">Remote</span></div>\n </main>\n <div id=\"log\"></div>\n\n<script>\n(() => {\n const $ = (id) => document.getElementById(id);\n const log = (msg, cls) => {\n const line = document.createElement(\"div\");\n if (cls) line.className = cls;\n line.textContent = \"[\" + new Date().toLocaleTimeString() + \"] \" + msg;\n $(\"log\").appendChild(line);\n $(\"log\").scrollTop = $(\"log\").scrollHeight;\n };\n\n const qs = new URLSearchParams(location.search);\n if (qs.get(\"to\")) $(\"to\").value = qs.get(\"to\");\n $(\"callId\").value = qs.get(\"callId\") || Math.random().toString(36).slice(2, 10);\n const botName = qs.get(\"bot\") || null; // e.g. ?bot=atlas → invite server-side bot\n\n const norm = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, \"\");\n\n // Multi-peer state. For a pair call there's one entry (the human). When a\n // server-side bot joins, a second entry is added whose isPrimary is false\n // so we don't overwrite the remote video with the bot's (silent) stream.\n let state = null; // { es, localStream, cfg, primary, callId, peers: Map<name, {pc, isPrimary}> }\n\n async function loadConfig() {\n const r = await fetch(\"/webrtc/config\");\n if (!r.ok) {\n log(\"webrtc signaling is not enabled on this daemon (\" + r.status + \")\", \"err\");\n $(\"selfName\").textContent = \"disabled\";\n $(\"selfName\").className = \"status err\";\n $(\"joinBtn\").disabled = true;\n return null;\n }\n const cfg = await r.json();\n $(\"selfName\").textContent = cfg.localName;\n $(\"selfName\").className = \"status ok\";\n return cfg;\n }\n\n async function sendSignal(signal) {\n const r = await fetch(\"/webrtc/signal/out\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(signal),\n });\n if (!r.ok) log(\"send \" + signal.kind + \" -> \" + signal.to + \" failed: \" + r.status, \"err\");\n }\n\n /** Find an existing peer by name (normalized — handles MacBook-Local vs\n * macbook-local spelling drift between the URL and the remote's node.name). */\n function findPeerEntry(name) {\n if (!state) return null;\n const want = norm(name);\n for (const [key, entry] of state.peers) {\n if (norm(key) === want) return entry;\n }\n return null;\n }\n\n /** Build an RTCPeerConnection for a specific remote peer. Primary peer\n * drives the remote video tile; non-primary peers (bots) just receive. */\n function createPeer(peerName, isPrimary) {\n const pc = new RTCPeerConnection({ iceServers: state.cfg.iceServers });\n for (const track of state.localStream.getTracks()) pc.addTrack(track, state.localStream);\n pc.ontrack = (ev) => {\n if (isPrimary) {\n $(\"remote\").srcObject = ev.streams[0];\n $(\"remoteLabel\").textContent = peerName;\n }\n log(\"track from \" + peerName + (isPrimary ? \" (primary)\" : \" (bot)\"), \"ok\");\n };\n pc.onicecandidate = (ev) => {\n if (!ev.candidate) return;\n sendSignal({\n kind: \"ice\",\n callId: state.callId, from: state.cfg.localName, to: peerName,\n candidate: {\n candidate: ev.candidate.candidate,\n sdpMid: ev.candidate.sdpMid,\n sdpMLineIndex: ev.candidate.sdpMLineIndex,\n usernameFragment: ev.candidate.usernameFragment,\n },\n });\n };\n pc.onconnectionstatechange = () => {\n log(\"pc[\" + peerName + \"] state: \" + pc.connectionState);\n };\n state.peers.set(peerName, { pc, isPrimary });\n return pc;\n }\n\n async function handleInboundSignal(sig) {\n if (sig.callId !== state.callId) return;\n if (sig.kind === \"hangup\") {\n log(\"<- hangup from \" + sig.from, \"err\");\n teardown();\n return;\n }\n // Find by normalized name so URL-spelling vs node.name-spelling mismatches\n // don't drop signals (same trick the broker uses for its fan-out).\n let entry = findPeerEntry(sig.from);\n if (!entry && sig.kind === \"offer\") {\n // New peer offering — figure out if it's the primary human (matches the\n // URL's 'to' after normalization) or an additional peer (bot). Only an\n // offer can introduce a new peer; answer/ice without a PC is stale.\n const isPrimary = norm(sig.from) === norm(state.primary);\n log(\"new peer offering: \" + sig.from + (isPrimary ? \" (primary)\" : \" (additional)\"));\n createPeer(sig.from, isPrimary);\n entry = findPeerEntry(sig.from);\n }\n if (!entry) {\n log(\"signal from unknown peer \" + sig.from + \" kind=\" + sig.kind + \" (dropped)\", \"err\");\n return;\n }\n const { pc } = entry;\n try {\n if (sig.kind === \"offer\") {\n await pc.setRemoteDescription({ type: \"offer\", sdp: sig.sdp });\n const ans = await pc.createAnswer();\n await pc.setLocalDescription(ans);\n await sendSignal({ kind: \"answer\", callId: state.callId, from: state.cfg.localName, to: sig.from, sdp: ans.sdp });\n log(\"<- offer from \" + sig.from + \" → answered\");\n } else if (sig.kind === \"answer\") {\n await pc.setRemoteDescription({ type: \"answer\", sdp: sig.sdp });\n log(\"<- answer from \" + sig.from);\n } else if (sig.kind === \"ice\") {\n await pc.addIceCandidate(sig.candidate);\n }\n } catch (err) {\n log(sig.kind + \" handling error from \" + sig.from + \": \" + err.message, \"err\");\n }\n }\n\n async function join(e) {\n e?.preventDefault();\n const cfg = state?.cfg || await loadConfig();\n if (!cfg) return;\n\n const to = $(\"to\").value.trim();\n const callId = $(\"callId\").value.trim();\n if (!to || !callId) return;\n\n log(\"joining call \" + callId + \" with peer=\" + to + (botName ? \" + bot=\" + botName : \"\"));\n\n let localStream;\n try {\n localStream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true });\n } catch (err) {\n log(\"getUserMedia failed: \" + err.message, \"err\");\n return;\n }\n $(\"local\").srcObject = localStream;\n\n state = { cfg, localStream, callId, primary: to, peers: new Map(), es: null };\n\n // Subscribe to inbound signals BEFORE sending any offer so we don't miss\n // the answer or ICE candidates from the callee. The broker's 30s buffer\n // also covers late subscribes.\n const esUrl = \"/webrtc/events?callId=\" + encodeURIComponent(callId) + \"&as=\" + encodeURIComponent(cfg.localName);\n const es = new EventSource(esUrl);\n state.es = es;\n es.addEventListener(\"signal\", (evt) => {\n let sig; try { sig = JSON.parse(evt.data); } catch { return; }\n handleInboundSignal(sig);\n });\n es.addEventListener(\"ready\", () => log(\"signaling SSE ready\", \"ok\"));\n es.onerror = () => log(\"signaling SSE error (will reconnect)\", \"err\");\n\n // Create the primary PC to the human peer.\n const primaryPc = createPeer(to, /* isPrimary */ true);\n\n // Deterministic caller role between us and the human peer. If we're\n // smaller, we offer; otherwise we wait for the offer (handled in\n // handleInboundSignal).\n const isCaller = norm(cfg.localName) < norm(to);\n log(isCaller ? \"role: caller (will offer to \" + to + \")\" : \"role: callee (waiting for offer from \" + to + \")\");\n if (isCaller) {\n await sendSignal({ kind: \"ring\", callId, from: cfg.localName, to });\n try {\n const offer = await primaryPc.createOffer();\n await primaryPc.setLocalDescription(offer);\n await sendSignal({ kind: \"offer\", callId, from: cfg.localName, to, sdp: offer.sdp });\n log(\"-> offer sent to \" + to, \"ok\");\n } catch (err) {\n log(\"offer failed: \" + err.message, \"err\");\n }\n }\n\n // If a bot is requested, ask this daemon to spawn it. The bot will join\n // as a separate peer; the existing signal handler creates its PC when\n // its offer arrives.\n if (botName) {\n try {\n const r = await fetch(\"/webrtc/bot/invite\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ callId, target: cfg.localName, agentId: botName }),\n });\n if (r.ok) log(\"bot '\" + botName + \"' invited\", \"ok\");\n else log(\"bot invite failed: \" + r.status, \"err\");\n } catch (err) {\n log(\"bot invite error: \" + err.message, \"err\");\n }\n }\n\n $(\"joinBtn\").disabled = true;\n $(\"hangupBtn\").disabled = false;\n }\n\n function teardown() {\n if (!state) return;\n for (const { pc } of state.peers.values()) { try { pc.close(); } catch {} }\n try { state.es.close(); } catch {}\n try { state.localStream.getTracks().forEach(t => t.stop()); } catch {}\n $(\"local\").srcObject = null;\n $(\"remote\").srcObject = null;\n $(\"joinBtn\").disabled = false;\n $(\"hangupBtn\").disabled = true;\n state = null;\n }\n\n async function hangup() {\n if (!state) return;\n // Send hangup to every active peer (human + any bots).\n for (const peerName of state.peers.keys()) {\n try {\n await sendSignal({ kind: \"hangup\", callId: state.callId, from: state.cfg.localName, to: peerName });\n } catch { /* best effort */ }\n }\n log(\"-> hangup sent to \" + state.peers.size + \" peer(s)\");\n teardown();\n }\n\n $(\"form\").addEventListener(\"submit\", join);\n $(\"hangupBtn\").addEventListener(\"click\", hangup);\n loadConfig();\n})();\n</script>\n</body>\n</html>`;\n","import type { WebRtcSignalBroker, WebRtcSignal } from \"./webrtc-signal\"\n\n// --- Server-side WebRTC bot peer ---\n//\n// Joins a call as a third participant by running RTCPeerConnection on the\n// daemon side via @roamhq/wrtc (libwebrtc Node bindings). Same wire protocol\n// as a browser tab — same offer/answer/ICE/hangup signal kinds, same\n// deterministic-caller role, same mesh routing. The difference is that\n// instead of subscribing to /webrtc/events over SSE the bot uses the\n// in-process broker.subscribeInternal callback, and instead of POSTing to\n// /webrtc/signal/out it calls broker.handleOutgoing directly.\n//\n// v1 scope: receive-only audio. The bot does not produce media — it adds an\n// audio receiver via setLocalDescription/createOffer with offerToReceiveAudio,\n// and consumes inbound audio frames through RTCAudioSink (PCM Int16Array).\n//\n// `@roamhq/wrtc` is loaded lazily on first bot start so daemons that don't\n// run WebRTC (e.g. headless production servers without the native binary\n// installed) can boot cleanly. The module-level static import would crash\n// the whole daemon at startup with ERR_MODULE_NOT_FOUND, blocking unrelated\n// channels — see clawd-server 2026-04-23 incident.\n\nlet wrtcModule: any | null = null\n\nasync function loadWrtc(): Promise<{ RTCPeerConnection: any; RTCAudioSink: any }> {\n if (wrtcModule) return wrtcModule\n let mod: any\n try {\n // Dynamic specifier so the bundler can't statically resolve and chunk it\n // alongside always-loaded code.\n mod = await import(/* @vite-ignore */ \"@roamhq/wrtc\" as string)\n } catch (e: any) {\n throw new Error(\n `WebRTC bot requires \"@roamhq/wrtc\" — install it on this node ` +\n `(\\`pnpm add @roamhq/wrtc\\`) or set channels.webrtc.bot.enabled=false. ` +\n `Original: ${e?.message ?? e}`,\n )\n }\n const pkg = mod.default ?? mod\n const RTCPeerConnection = pkg.RTCPeerConnection\n const RTCAudioSink = pkg.nonstandard?.RTCAudioSink\n if (!RTCPeerConnection || !RTCAudioSink) {\n throw new Error(`@roamhq/wrtc loaded but missing expected exports (RTCPeerConnection / nonstandard.RTCAudioSink)`)\n }\n wrtcModule = { RTCPeerConnection, RTCAudioSink }\n return wrtcModule\n}\n\nexport interface AudioFrame {\n /** Interleaved (or mono) signed 16-bit PCM samples. */\n samples: Int16Array\n sampleRate: number\n channelCount: number\n /** Timestamp (ms since epoch) when the frame was received. */\n receivedAt: number\n}\n\nexport interface WebRtcBotOptions {\n callId: string\n /** Name we identify ourselves as. Convention: `bot:<agentId>`. Used in\n * signals' `from` field, in subscriber registration, and for the\n * deterministic caller comparison. */\n botName: string\n /** Primary human peer to negotiate with. If a call has two humans and one\n * bot, the bot only connects to the inviting browser in v1. */\n target: string\n iceServers: RTCIceServer[]\n broker: WebRtcSignalBroker\n log: (...args: unknown[]) => void\n /** Fired for each remote audio buffer. Bot consumes only — no echo back. */\n onAudioFrame: (frame: AudioFrame) => void\n /** Fired when the remote ends the call (hangup signal received) or when\n * the connection enters a terminal state. Idempotent. */\n onClosed?: (reason: string) => void\n}\n\nconst norm = (s: string) => s.toLowerCase().replace(/[^a-z0-9]/g, \"\")\n\nexport class WebRtcBot {\n private pc?: any // RTCPeerConnection from @roamhq/wrtc — lib types disagree with DOM lib\n private sinks: Set<{ stop: () => void }> = new Set()\n private unsubscribe?: () => void\n private closed = false\n private opts: WebRtcBotOptions\n\n constructor(opts: WebRtcBotOptions) {\n this.opts = opts\n }\n\n /** Open a peer connection, register as an in-process subscriber, and\n * initiate the offer if our deterministic-caller role says we should. */\n async start(): Promise<void> {\n if (this.pc) throw new Error(\"WebRtcBot already started\")\n const { broker, callId, botName, target, iceServers, log } = this.opts\n\n const { RTCPeerConnection } = await loadWrtc()\n this.pc = new RTCPeerConnection({ iceServers })\n\n this.pc.ontrack = (ev: any) => {\n if (this.closed) return\n const track = ev.track\n if (track.kind !== \"audio\") {\n log(`[bot:${botName}] non-audio track received (kind=${track.kind}) — ignored in v1`)\n return\n }\n log(`[bot:${botName}] audio track received from ${target}`)\n this.tapAudio(track)\n }\n\n this.pc.onicecandidate = (ev: any) => {\n if (!ev.candidate || this.closed) return\n void this.send({\n kind: \"ice\",\n callId, from: botName, to: target,\n candidate: {\n candidate: ev.candidate.candidate,\n sdpMid: ev.candidate.sdpMid,\n sdpMLineIndex: ev.candidate.sdpMLineIndex,\n usernameFragment: ev.candidate.usernameFragment,\n },\n })\n }\n\n this.pc.onconnectionstatechange = () => {\n const s = this.pc?.connectionState\n log(`[bot:${botName}] pc state: ${s}`)\n if (s === \"failed\" || s === \"disconnected\" || s === \"closed\") {\n this.close(`pc state ${s}`)\n }\n }\n\n // Register as an in-process subscriber so signals flow back without HTTP.\n this.unsubscribe = broker.subscribeInternal(callId, botName, (sig) => this.handleSignal(sig))\n\n // Tell the human we're calling them so any channel-side ring fires.\n await this.send({ kind: \"ring\", callId, from: botName, to: target })\n\n // Deterministic caller role: smaller normalized name offers.\n const isCaller = norm(botName) < norm(target)\n if (isCaller) {\n // We need to RECEIVE audio from the human; make the receiver explicit.\n // @roamhq/wrtc honors createOffer({ offerToReceiveAudio: true }).\n const offer = await this.pc.createOffer({ offerToReceiveAudio: true, offerToReceiveVideo: false })\n await this.pc.setLocalDescription(offer)\n await this.send({ kind: \"offer\", callId, from: botName, to: target, sdp: offer.sdp })\n log(`[bot:${botName}] role=caller, offer sent to ${target}`)\n } else {\n log(`[bot:${botName}] role=callee, waiting for offer from ${target}`)\n }\n }\n\n private async send(signal: WebRtcSignal): Promise<void> {\n const result = await this.opts.broker.handleOutgoing(signal)\n if (!result.ok) {\n this.opts.log(`[bot:${this.opts.botName}] send ${signal.kind} -> ${signal.to} failed: ${result.error}`)\n }\n }\n\n private async handleSignal(sig: WebRtcSignal): Promise<void> {\n if (this.closed || !this.pc) return\n if (sig.callId !== this.opts.callId) return\n try {\n if (sig.kind === \"offer\") {\n await this.pc.setRemoteDescription({ type: \"offer\", sdp: sig.sdp })\n const ans = await this.pc.createAnswer()\n await this.pc.setLocalDescription(ans)\n await this.send({\n kind: \"answer\",\n callId: this.opts.callId,\n from: this.opts.botName,\n to: sig.from,\n sdp: ans.sdp,\n })\n } else if (sig.kind === \"answer\") {\n await this.pc.setRemoteDescription({ type: \"answer\", sdp: sig.sdp })\n } else if (sig.kind === \"ice\") {\n await this.pc.addIceCandidate(sig.candidate)\n } else if (sig.kind === \"hangup\") {\n this.close(`hangup from ${sig.from}`)\n }\n } catch (e: any) {\n this.opts.log(`[bot:${this.opts.botName}] signal ${sig.kind} from ${sig.from} failed: ${e.message}`)\n }\n }\n\n /** Wire RTCAudioSink to the inbound track and forward PCM frames upstream.\n * Synchronous-by-design — by the time we hit `ontrack`, `start()` has\n * already awaited `loadWrtc()`, so wrtcModule is populated. */\n private tapAudio(track: any): void {\n if (!wrtcModule) throw new Error(\"WebRtcBot.tapAudio called before loadWrtc — bug\")\n const { RTCAudioSink } = wrtcModule\n const sink = new RTCAudioSink(track)\n sink.ondata = (data: { samples: Int16Array; sampleRate: number; channelCount: number }) => {\n if (this.closed) return\n this.opts.onAudioFrame({\n samples: data.samples,\n sampleRate: data.sampleRate,\n channelCount: data.channelCount,\n receivedAt: Date.now(),\n })\n }\n this.sinks.add({ stop: () => { try { sink.stop() } catch { /* */ } } })\n }\n\n /** Tear down: stop sinks, close PC, unsubscribe from the broker.\n * Idempotent — called from track handlers, signal handlers, or BotManager. */\n close(reason: string): void {\n if (this.closed) return\n this.closed = true\n this.opts.log(`[bot:${this.opts.botName}] closing (${reason})`)\n for (const s of this.sinks) s.stop()\n this.sinks.clear()\n try { this.unsubscribe?.() } catch { /* */ }\n try { this.pc?.close() } catch { /* */ }\n this.pc = undefined\n this.opts.onClosed?.(reason)\n }\n}\n","import type { AudioFrame } from \"./webrtc-bot\"\n\n// --- Audio buffer with energy-threshold VAD + WAV serialization ---\n//\n// AudioFrame in: signed 16-bit PCM at the source rate (typically 48kHz from\n// WebRTC). Whisper wants 16kHz mono, so we downsample on flush and emit a\n// minimal WAV (RIFF) buffer. VAD is energy-threshold on the source samples;\n// a chunk closes when we observe a `silenceMs` gap of below-threshold energy\n// after at least `minSpeechMs` of speech.\n//\n// Sufficient for v1 (single speaker per channel, no diarization). Later\n// upgrades: swap in webrtcvad / silero-vad, add streaming partial transcripts.\n\nexport interface AudioBufferOptions {\n /** Energy threshold (RMS over the int16 range, normalized to [0,1]).\n * ~0.012 catches normal speech without firing on background noise. */\n energyThreshold?: number\n /** Continuous silence (ms) before closing a chunk. 400ms ≈ pause. */\n silenceMs?: number\n /** Minimum total speech (ms) before a chunk is eligible to close. */\n minSpeechMs?: number\n /** Hard upper bound on chunk length so very long monologues still flush. */\n maxChunkMs?: number\n log: (...args: unknown[]) => void\n /** Fired with a 16kHz mono WAV buffer once a chunk closes. */\n onChunk: (wav: Buffer, durationMs: number) => void | Promise<void>\n}\n\nconst DEFAULTS = {\n energyThreshold: 0.012,\n silenceMs: 400,\n minSpeechMs: 600,\n maxChunkMs: 15_000,\n}\n\ninterface ActiveChunk {\n /** Samples accumulated at the source rate, mono (channel-mixed if needed). */\n samples: Int16Array[]\n totalSamples: number\n sourceRate: number\n startedAt: number\n /** Wall-clock ms of the most recent above-threshold frame. */\n lastVoiceAt: number\n}\n\nexport class AudioChunker {\n private opts: Required<Omit<AudioBufferOptions, \"log\" | \"onChunk\">> & Pick<AudioBufferOptions, \"log\" | \"onChunk\">\n private active: ActiveChunk | null = null\n private silenceTimer?: ReturnType<typeof setInterval>\n\n constructor(opts: AudioBufferOptions) {\n this.opts = {\n energyThreshold: opts.energyThreshold ?? DEFAULTS.energyThreshold,\n silenceMs: opts.silenceMs ?? DEFAULTS.silenceMs,\n minSpeechMs: opts.minSpeechMs ?? DEFAULTS.minSpeechMs,\n maxChunkMs: opts.maxChunkMs ?? DEFAULTS.maxChunkMs,\n log: opts.log,\n onChunk: opts.onChunk,\n }\n // Periodic check so the chunk closes even if frames stop arriving\n // (track ended mid-speech, peer dropped, etc.).\n this.silenceTimer = setInterval(() => this.maybeClose(), 100)\n }\n\n /** Feed a single inbound audio frame. Frames are typically ~10ms each at\n * 48kHz, so this fires often. Cheap path. */\n push(frame: AudioFrame): void {\n const mono = mixToMono(frame.samples, frame.channelCount)\n const energy = rmsNormalized(mono)\n const isSpeech = energy >= this.opts.energyThreshold\n\n if (!this.active) {\n if (!isSpeech) return // Don't open a chunk on silence.\n this.active = {\n samples: [],\n totalSamples: 0,\n sourceRate: frame.sampleRate,\n startedAt: frame.receivedAt,\n lastVoiceAt: frame.receivedAt,\n }\n }\n\n this.active.samples.push(mono)\n this.active.totalSamples += mono.length\n if (isSpeech) this.active.lastVoiceAt = frame.receivedAt\n }\n\n private maybeClose(): void {\n if (!this.active) return\n const now = Date.now()\n const ageMs = now - this.active.startedAt\n const silenceFor = now - this.active.lastVoiceAt\n const speechMs = ageMs - silenceFor\n if (ageMs >= this.opts.maxChunkMs) {\n this.flush(\"max-duration\")\n return\n }\n if (speechMs >= this.opts.minSpeechMs && silenceFor >= this.opts.silenceMs) {\n this.flush(\"silence-gap\")\n }\n }\n\n /** Force-close the active chunk and emit it (or do nothing if no audio). */\n flush(reason = \"manual\"): void {\n if (!this.active) return\n const chunk = this.active\n this.active = null\n const ageMs = Date.now() - chunk.startedAt\n if (chunk.totalSamples === 0) return\n const merged = concatInt16(chunk.samples, chunk.totalSamples)\n const downsampled = downsampleTo16k(merged, chunk.sourceRate)\n const wav = encodeWav(downsampled, 16_000)\n this.opts.log(`audio chunk closed (${reason}, ${ageMs}ms, ${(downsampled.length / 16000).toFixed(2)}s) — ${wav.length} bytes`)\n void this.opts.onChunk(wav, ageMs)\n }\n\n shutdown(): void {\n if (this.silenceTimer) clearInterval(this.silenceTimer)\n this.silenceTimer = undefined\n this.flush(\"shutdown\")\n }\n}\n\n// --- Pure helpers ---\n\nfunction mixToMono(samples: Int16Array, channels: number): Int16Array {\n if (channels === 1) return samples\n const out = new Int16Array(samples.length / channels)\n for (let i = 0, j = 0; i < samples.length; i += channels, j++) {\n let sum = 0\n for (let c = 0; c < channels; c++) sum += samples[i + c]\n out[j] = sum / channels\n }\n return out\n}\n\nfunction rmsNormalized(samples: Int16Array): number {\n if (samples.length === 0) return 0\n let sumSq = 0\n for (let i = 0; i < samples.length; i++) {\n const s = samples[i] / 32768\n sumSq += s * s\n }\n return Math.sqrt(sumSq / samples.length)\n}\n\nfunction concatInt16(parts: Int16Array[], total: number): Int16Array {\n const out = new Int16Array(total)\n let offset = 0\n for (const p of parts) {\n out.set(p, offset)\n offset += p.length\n }\n return out\n}\n\n/** Linear-interpolation downsample. Adequate for speech intelligibility;\n * not audiophile-grade but Whisper is robust to it. */\nfunction downsampleTo16k(samples: Int16Array, sourceRate: number): Int16Array {\n if (sourceRate === 16_000) return samples\n const ratio = sourceRate / 16_000\n const outLen = Math.floor(samples.length / ratio)\n const out = new Int16Array(outLen)\n for (let i = 0; i < outLen; i++) {\n const src = i * ratio\n const lo = Math.floor(src)\n const hi = Math.min(lo + 1, samples.length - 1)\n const t = src - lo\n out[i] = (1 - t) * samples[lo] + t * samples[hi]\n }\n return out\n}\n\n/** Minimal RIFF/WAVE encoder for 16-bit PCM mono. */\nfunction encodeWav(samples: Int16Array, sampleRate: number): Buffer {\n const dataBytes = samples.length * 2\n const header = Buffer.alloc(44)\n header.write(\"RIFF\", 0)\n header.writeUInt32LE(36 + dataBytes, 4)\n header.write(\"WAVE\", 8)\n header.write(\"fmt \", 12)\n header.writeUInt32LE(16, 16) // fmt chunk size\n header.writeUInt16LE(1, 20) // PCM\n header.writeUInt16LE(1, 22) // mono\n header.writeUInt32LE(sampleRate, 24)\n header.writeUInt32LE(sampleRate * 2, 28) // byte rate\n header.writeUInt16LE(2, 32) // block align\n header.writeUInt16LE(16, 34) // bits per sample\n header.write(\"data\", 36)\n header.writeUInt32LE(dataBytes, 40)\n const data = Buffer.alloc(dataBytes)\n for (let i = 0; i < samples.length; i++) data.writeInt16LE(samples[i], i * 2)\n return Buffer.concat([header, data])\n}\n","import { spawn } from \"child_process\"\nimport { writeFile, mkdtemp, rm } from \"fs/promises\"\nimport { tmpdir } from \"os\"\nimport { join } from \"path\"\n\n// --- Whisper transcription drivers ---\n//\n// Two backends share one tiny interface so the call-side code doesn't care:\n// - \"mlx\" → spawns `mlx_whisper` CLI (Apple Silicon native, fast, free)\n// - \"openai\" → POSTs WAV to /v1/audio/transcriptions (works anywhere, paid)\n// - \"auto\" → tries mlx first; falls back to openai if mlx isn't available\n//\n// Each backend takes a 16kHz mono WAV buffer and returns transcript text.\n// Errors return null instead of throwing so a single bad chunk doesn't take\n// down a long call.\n\nexport type WhisperBackend = \"auto\" | \"mlx\" | \"openai\"\n\nexport interface WhisperOptions {\n backend: WhisperBackend\n /** Whisper model name (mlx) or model id (openai). Default \"small\". */\n model?: string\n /** Language hint (BCP-47). \"auto\" lets the model detect. */\n language?: string\n /** Absolute path to mlx_whisper if not on PATH. launchd-managed daemons on\n * macOS often have a stripped PATH that misses ~/.pyenv/shims, so a\n * config-level override beats trying to inject env vars. */\n mlxBinary?: string\n log: (...args: unknown[]) => void\n}\n\nexport class Whisper {\n private opts: WhisperOptions\n /** Cached probe of which backend actually works. Set on first use. */\n private resolvedBackend: \"mlx\" | \"openai\" | null = null\n\n constructor(opts: WhisperOptions) {\n this.opts = opts\n }\n\n /** Transcribe a 16kHz mono WAV buffer. Returns trimmed text or null on\n * failure. Errors are logged once at the call site — never thrown. */\n async transcribe(wav: Buffer): Promise<string | null> {\n const backend = await this.resolveBackend()\n if (!backend) {\n this.opts.log(\"[whisper] no backend available — chunk dropped\")\n return null\n }\n try {\n const text = backend === \"mlx\"\n ? await this.transcribeMlx(wav)\n : await this.transcribeOpenAI(wav)\n const trimmed = text?.trim() ?? \"\"\n return trimmed || null\n } catch (e: any) {\n this.opts.log(`[whisper:${backend}] transcription failed: ${e.message}`)\n return null\n }\n }\n\n /** Decide once which backend to use and cache the answer. */\n private async resolveBackend(): Promise<\"mlx\" | \"openai\" | null> {\n if (this.resolvedBackend) return this.resolvedBackend\n const target = this.opts.backend\n if (target === \"mlx\") {\n this.resolvedBackend = (await this.probeMlx()) ? \"mlx\" : null\n } else if (target === \"openai\") {\n this.resolvedBackend = process.env.OPENAI_API_KEY ? \"openai\" : null\n } else {\n // auto: prefer mlx (no network, no spend)\n if (await this.probeMlx()) this.resolvedBackend = \"mlx\"\n else if (process.env.OPENAI_API_KEY) this.resolvedBackend = \"openai\"\n }\n if (!this.resolvedBackend) {\n this.opts.log(`[whisper] backend resolution failed (target=${target}, OPENAI_API_KEY=${process.env.OPENAI_API_KEY ? \"set\" : \"unset\"})`)\n } else {\n this.opts.log(`[whisper] using backend: ${this.resolvedBackend}`)\n }\n return this.resolvedBackend\n }\n\n private mlxCmd(): string {\n return this.opts.mlxBinary || \"mlx_whisper\"\n }\n\n private async probeMlx(): Promise<boolean> {\n return new Promise((resolve) => {\n const child = spawn(this.mlxCmd(), [\"--help\"], { stdio: \"ignore\" })\n child.on(\"error\", () => resolve(false))\n child.on(\"exit\", (code) => resolve(code === 0))\n })\n }\n\n private async transcribeMlx(wav: Buffer): Promise<string> {\n const dir = await mkdtemp(join(tmpdir(), \"agentx-whisper-\"))\n const wavPath = join(dir, \"chunk.wav\")\n try {\n await writeFile(wavPath, wav)\n const args = [\n \"--model\", this.opts.model || \"small\",\n \"--output-format\", \"txt\",\n \"--output-dir\", dir,\n ]\n if (this.opts.language && this.opts.language !== \"auto\") {\n args.push(\"--language\", this.opts.language)\n }\n args.push(wavPath)\n const text = await runProcess(this.mlxCmd(), args)\n // mlx_whisper writes <basename>.txt to the output dir AND echoes to stdout\n // depending on version. Prefer stdout if non-empty.\n if (text.trim()) return text\n // Fallback: read the .txt file\n const { readFile } = await import(\"fs/promises\")\n try {\n return await readFile(join(dir, \"chunk.txt\"), \"utf-8\")\n } catch { return \"\" }\n } finally {\n await rm(dir, { recursive: true, force: true }).catch(() => {})\n }\n }\n\n private async transcribeOpenAI(wav: Buffer): Promise<string> {\n const key = process.env.OPENAI_API_KEY\n if (!key) throw new Error(\"OPENAI_API_KEY not set\")\n // multipart/form-data without a third-party lib — Node 18+ has FormData/Blob globally.\n const fd = new FormData()\n fd.set(\"file\", new Blob([wav], { type: \"audio/wav\" }), \"chunk.wav\")\n fd.set(\"model\", this.opts.model || \"whisper-1\")\n if (this.opts.language && this.opts.language !== \"auto\") {\n fd.set(\"language\", this.opts.language)\n }\n fd.set(\"response_format\", \"text\")\n const res = await fetch(\"https://api.openai.com/v1/audio/transcriptions\", {\n method: \"POST\",\n headers: { Authorization: `Bearer ${key}` },\n body: fd,\n })\n if (!res.ok) {\n const errText = await res.text().catch(() => \"\")\n throw new Error(`OpenAI ${res.status}: ${errText.slice(0, 200)}`)\n }\n return await res.text()\n }\n}\n\nfunction runProcess(cmd: string, args: string[]): Promise<string> {\n return new Promise((resolve, reject) => {\n const child = spawn(cmd, args, { stdio: [\"ignore\", \"pipe\", \"pipe\"] })\n let out = \"\"\n let err = \"\"\n child.stdout.on(\"data\", (d) => { out += d.toString() })\n child.stderr.on(\"data\", (d) => { err += d.toString() })\n child.on(\"error\", reject)\n child.on(\"exit\", (code) => {\n if (code === 0) resolve(out)\n else reject(new Error(`${cmd} exited ${code}: ${err.slice(0, 200)}`))\n })\n })\n}\n","import { WebRtcBot, type WebRtcBotOptions, type AudioFrame } from \"@/channels/webrtc-bot\"\nimport { AudioChunker } from \"@/channels/webrtc-audio\"\nimport { Whisper, type WhisperBackend } from \"@/whisper\"\nimport type { WebRtcSignalBroker } from \"@/channels/webrtc-signal\"\n\n// --- Per-call AI participant lifecycle ---\n//\n// Spawned by POST /webrtc/bot/invite with { callId, target, agentId }.\n// Wires WebRtcBot → AudioChunker → Whisper → onTranscript(text). The actual\n// \"do something with the text\" step is provided by the daemon (e.g. send to\n// Telegram, append to wiki) so this manager stays UI-agnostic.\n\nexport interface BotInvite {\n callId: string\n /** The peer name we're transcribing from (the human's normalized name). */\n target: string\n /** Agent id to attribute the transcript to. */\n agentId: string\n}\n\nexport interface BotManagerOptions {\n broker: WebRtcSignalBroker\n iceServers: RTCIceServer[]\n whisperBackend: WhisperBackend\n whisperModel?: string\n whisperLanguage?: string\n /** Absolute path override for mlx_whisper. */\n mlxBinary?: string\n /** Hard cap to stop runaway bots. */\n maxCallMinutes: number\n log: (...args: unknown[]) => void\n /** Fired with each transcribed chunk. Empty/null transcripts are filtered\n * out before this hook is called. */\n onTranscript: (event: { invite: BotInvite; text: string; durationMs: number }) => void | Promise<void>\n}\n\ninterface ActiveBot {\n invite: BotInvite\n bot: WebRtcBot\n chunker: AudioChunker\n startedAt: number\n timeoutTimer: ReturnType<typeof setTimeout>\n /** Number of transcript chunks dispatched so far. Surfaces in the\n * session-history ring buffer so /webrtc/history can show how\n * much speech actually happened on a call. */\n transcriptChunks: number\n}\n\n/** Completed-session snapshot kept in a small ring buffer for the\n * admin /webrtc/history view. Persistence to disk is deliberately\n * deferred — operators wanting an audit trail enable a transcript\n * log via the `webrtcBot.transcriptChannel` config (separate flow). */\nexport interface CompletedBotSession {\n callId: string\n agentId: string\n target: string\n startedAt: number\n endedAt: number\n durationSec: number\n transcriptChunks: number\n reason: string\n}\n\nconst HISTORY_RING_SIZE = 50\n\nexport class BotManager {\n private opts: BotManagerOptions\n private bots: Map<string, ActiveBot> = new Map()\n private whisper: Whisper\n /** Ring buffer of recently-completed sessions. Surfaced via history(). */\n private completed: CompletedBotSession[] = []\n\n constructor(opts: BotManagerOptions) {\n this.opts = opts\n this.whisper = new Whisper({\n backend: opts.whisperBackend,\n model: opts.whisperModel,\n language: opts.whisperLanguage,\n mlxBinary: opts.mlxBinary,\n log: opts.log,\n })\n }\n\n /** Spawn a bot for a call. Idempotent — if one is already running for this\n * callId it's reused instead of duplicated. */\n async invite(invite: BotInvite): Promise<{ ok: boolean; error?: string }> {\n const existing = this.bots.get(invite.callId)\n if (existing) {\n this.opts.log(`[bot-manager] bot already active for call=${invite.callId} agent=${existing.invite.agentId}`)\n return { ok: true }\n }\n\n const botName = `bot:${invite.agentId}`\n this.opts.log(`[bot-manager] spawning ${botName} for call=${invite.callId} target=${invite.target}`)\n\n const chunker = new AudioChunker({\n log: this.opts.log,\n onChunk: async (wav, durationMs) => {\n const text = await this.whisper.transcribe(wav)\n if (!text) return\n this.recordTranscriptChunk(invite.callId)\n try {\n await this.opts.onTranscript({ invite, text, durationMs })\n } catch (e: any) {\n this.opts.log(`[bot-manager] onTranscript hook threw: ${e.message}`)\n }\n },\n })\n\n const botOpts: WebRtcBotOptions = {\n callId: invite.callId,\n botName,\n target: invite.target,\n iceServers: this.opts.iceServers,\n broker: this.opts.broker,\n log: this.opts.log,\n onAudioFrame: (frame: AudioFrame) => chunker.push(frame),\n onClosed: (reason) => {\n this.opts.log(`[bot-manager] bot for call=${invite.callId} closed (${reason})`)\n this.tearDown(invite.callId)\n },\n }\n const bot = new WebRtcBot(botOpts)\n try {\n await bot.start()\n } catch (e: any) {\n this.opts.log(`[bot-manager] bot.start failed: ${e.message}`)\n chunker.shutdown()\n return { ok: false, error: e.message }\n }\n\n const timeoutTimer = setTimeout(() => {\n this.opts.log(`[bot-manager] hard-cap reached for call=${invite.callId} (${this.opts.maxCallMinutes}min) — closing`)\n this.tearDown(invite.callId)\n }, this.opts.maxCallMinutes * 60_000)\n\n this.bots.set(invite.callId, {\n invite, bot, chunker, startedAt: Date.now(), timeoutTimer,\n transcriptChunks: 0,\n })\n return { ok: true }\n }\n\n /** Close the bot for a call (manual hangup, peer disconnect, or timeout). */\n tearDown(callId: string, reason: string = \"teardown\"): void {\n const active = this.bots.get(callId)\n if (!active) return\n this.bots.delete(callId)\n clearTimeout(active.timeoutTimer)\n try { active.bot.close(\"manager teardown\") } catch { /* */ }\n try { active.chunker.shutdown() } catch { /* */ }\n const endedAt = Date.now()\n const durationSec = Math.round((endedAt - active.startedAt) / 1000)\n this.opts.log(`[bot-manager] tore down bot for call=${callId} (was up ${durationSec}s)`)\n // Push to ring buffer.\n this.completed.unshift({\n callId,\n agentId: active.invite.agentId,\n target: active.invite.target,\n startedAt: active.startedAt,\n endedAt,\n durationSec,\n transcriptChunks: active.transcriptChunks,\n reason,\n })\n if (this.completed.length > HISTORY_RING_SIZE) this.completed.length = HISTORY_RING_SIZE\n }\n\n /** Snapshot for /health or admin UI. */\n active(): Array<{ callId: string; agentId: string; target: string; uptimeSec: number }> {\n const now = Date.now()\n return [...this.bots.values()].map(b => ({\n callId: b.invite.callId,\n agentId: b.invite.agentId,\n target: b.invite.target,\n uptimeSec: Math.round((now - b.startedAt) / 1000),\n }))\n }\n\n /** Recently-completed sessions (ring buffer, newest first). Used by the\n * admin /webrtc/history view to surface call activity even after the\n * bot has torn down. Persistence to disk is deferred — opt in via the\n * webrtcBot.transcriptChannel config to also push transcripts to a\n * channel. */\n history(): CompletedBotSession[] {\n return [...this.completed]\n }\n\n /** Increment the transcript-chunk counter for an active call. Wired\n * from the onTranscript hook so /webrtc/history shows how much\n * speech actually happened on a call. */\n recordTranscriptChunk(callId: string): void {\n const active = this.bots.get(callId)\n if (active) active.transcriptChunks++\n }\n\n /** Tear down every active bot (daemon shutdown). */\n shutdown(): void {\n for (const callId of [...this.bots.keys()]) this.tearDown(callId, \"shutdown\")\n }\n}\n","// --- Structured logger for the daemon ---\n// Outputs JSON lines to stderr for machine parsing, human-readable to stdout.\n\nexport type LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\"\n\ninterface LogEntry {\n time: string\n level: LogLevel\n module: string\n msg: string\n [key: string]: unknown\n}\n\nexport class Logger {\n private module: string\n private minLevel: LogLevel\n private static levelOrder: Record<LogLevel, number> = { debug: 0, info: 1, warn: 2, error: 3 }\n\n constructor(module: string, minLevel: LogLevel = \"info\") {\n this.module = module\n this.minLevel = minLevel\n }\n\n child(module: string): Logger {\n return new Logger(`${this.module}:${module}`, this.minLevel)\n }\n\n debug(msg: string, data?: Record<string, unknown>): void {\n this.emit(\"debug\", msg, data)\n }\n\n info(msg: string, data?: Record<string, unknown>): void {\n this.emit(\"info\", msg, data)\n }\n\n warn(msg: string, data?: Record<string, unknown>): void {\n this.emit(\"warn\", msg, data)\n }\n\n error(msg: string, data?: Record<string, unknown>): void {\n this.emit(\"error\", msg, data)\n }\n\n /**\n * Create a console.error-compatible function for backward compat.\n */\n asConsoleLog(): (...args: unknown[]) => void {\n return (...args: unknown[]) => {\n const msg = args.map(a => typeof a === \"string\" ? a : JSON.stringify(a)).join(\" \")\n // Strip [agentx] prefix if present\n const clean = msg.replace(/^\\[agentx\\]\\s*/, \"\")\n if (clean) this.info(clean)\n }\n }\n\n private emit(level: LogLevel, msg: string, data?: Record<string, unknown>): void {\n if (Logger.levelOrder[level] < Logger.levelOrder[this.minLevel]) return\n\n const entry: LogEntry = {\n time: new Date().toISOString(),\n level,\n module: this.module,\n msg,\n ...data,\n }\n\n // Human-readable to stderr (what the user sees)\n const prefix = level === \"error\" ? \"ERROR\" : level === \"warn\" ? \"WARN\" : \"\"\n const tag = `[${this.module}]`\n const display = prefix ? `${tag} ${prefix}: ${msg}` : `${tag} ${msg}`\n console.error(display)\n }\n}\n","// --- Event bus ---\n//\n// Single in-process fanout for operator-observable daemon events. Each\n// event is typed (run / task / signal / mesh / channel / status) and\n// carries a small structured payload. Subscribers register a filter —\n// by kind, by workflow id, by actor, by channel — and receive events\n// that match.\n//\n// Wire-level shape emitted over SSE:\n// event: <kind>\n// data: <JSON payload>\n//\n// Not persistent. Retention is \"what's currently connected\". That is\n// deliberate — run jsonl is the durable audit trail; this bus is\n// strictly a live observability surface.\n\nimport { randomUUID } from \"crypto\"\n\nexport type EventKind =\n | \"run\" // workflow run transitions (created, node ok/failed/paused/resumed, completed)\n | \"task\" // user-task lifecycle (created, submitted, canceled)\n | \"signal\" // signal emissions\n | \"mesh\" // peer state deltas (healthy/unhealthy, skills change)\n | \"channel\" // incoming / outgoing channel messages\n | \"status\" // daemon-level status (legacy /events shape kept for compatibility)\n\nexport interface BaseEvent {\n kind: EventKind\n /** Monotonic-ish identifier; just for debug/replay. */\n id: string\n /** ISO-8601 emit time. */\n at: string\n}\n\nexport interface RunEvent extends BaseEvent {\n kind: \"run\"\n runId: string\n workflowId: string\n /** Node that just transitioned. Optional for run-created events. */\n nodeId?: string\n /** \"created\" | \"ok\" | \"failed\" | \"paused\" | \"resumed\" | \"skipped\" | \"completed\" | \"timeout\". */\n phase: string\n /** Current run status after the transition. */\n status?: string\n homeNode?: string\n note?: string\n}\n\nexport interface TaskEvent extends BaseEvent {\n kind: \"task\"\n taskId: string\n workflowId: string\n runId: string\n phase: \"created\" | \"submitted\" | \"canceled\"\n assignedTo?: string[]\n title?: string\n submittedBy?: string\n}\n\nexport interface SignalEvent extends BaseEvent {\n kind: \"signal\"\n name: string\n scope: \"workflow\" | \"global\"\n workflowId?: string\n payload?: Record<string, unknown>\n}\n\nexport interface MeshEvent extends BaseEvent {\n kind: \"mesh\"\n peer: string\n healthy: boolean\n skills?: string[]\n delta: \"recovered\" | \"lost\" | \"skills-changed\" | \"added\" | \"removed\"\n}\n\nexport interface ChannelEvent extends BaseEvent {\n kind: \"channel\"\n channel: string // telegram, whatsapp, slack, gitlab, ...\n direction: \"in\" | \"out\"\n chatId?: string\n sender?: string // for inbound\n agentId?: string // for outbound\n textPreview?: string // first 140 chars, redacted\n messageId?: string\n}\n\nexport interface StatusEvent extends BaseEvent {\n kind: \"status\"\n message: string\n meta?: Record<string, unknown>\n}\n\nexport type DaemonEvent = RunEvent | TaskEvent | SignalEvent | MeshEvent | ChannelEvent | StatusEvent\n\nexport interface SubscriptionFilter {\n /** Match only these kinds. Empty / omitted = all kinds. */\n kinds?: EventKind[]\n /** Workflow id filter (applies to run/task/signal events). */\n workflowId?: string\n /** Actor id filter (task.assignedTo includes this, or task.submittedBy equals). */\n actor?: string\n /** Channel filter (for channel events). */\n channel?: string\n /** Run id filter (run events for a specific run). */\n runId?: string\n}\n\ntype Listener = (e: DaemonEvent) => void\n\n/** Simple in-memory publish/subscribe. No ordering guarantees beyond\n * \"subscribers see events in the order they were published\". No\n * durability. Listener callbacks are invoked synchronously; if a\n * listener throws, the error is swallowed so a single bad subscriber\n * can't break the fanout. */\nexport class EventBus {\n private listeners: Map<string, { filter: SubscriptionFilter; fn: Listener }> = new Map()\n\n subscribe(filter: SubscriptionFilter, fn: Listener): () => void {\n const id = randomUUID()\n this.listeners.set(id, { filter, fn })\n return () => { this.listeners.delete(id) }\n }\n\n /** Publish an event. The `id` and `at` fields are filled in if absent.\n * We deliberately accept a loose input shape — callers construct the\n * per-kind literal and we trust them; runtime validation happens at\n * the SSE serialisation layer. */\n publish(event: { kind: EventKind } & Record<string, unknown>): void {\n const full: DaemonEvent = {\n id: typeof event.id === \"string\" ? event.id : randomUUID(),\n at: typeof event.at === \"string\" ? event.at : new Date().toISOString(),\n ...event,\n } as DaemonEvent\n for (const { filter, fn } of this.listeners.values()) {\n if (!matches(filter, full)) continue\n try { fn(full) } catch { /* swallow — a bad listener must not break peers */ }\n }\n }\n\n /** Active subscriber count — useful for health endpoints. */\n get size(): number { return this.listeners.size }\n}\n\nexport function matches(filter: SubscriptionFilter, e: DaemonEvent): boolean {\n if (filter.kinds && filter.kinds.length > 0 && !filter.kinds.includes(e.kind)) return false\n if (filter.workflowId) {\n if (e.kind === \"run\" && e.workflowId !== filter.workflowId) return false\n if (e.kind === \"task\" && e.workflowId !== filter.workflowId) return false\n if (e.kind === \"signal\" && e.workflowId && e.workflowId !== filter.workflowId) return false\n }\n if (filter.runId) {\n if (e.kind === \"run\" && e.runId !== filter.runId) return false\n if (e.kind === \"task\" && e.runId !== filter.runId) return false\n }\n if (filter.actor) {\n if (e.kind !== \"task\") return false\n const hit = (e.assignedTo?.includes(filter.actor)) || e.submittedBy === filter.actor\n if (!hit) return false\n }\n if (filter.channel) {\n if (e.kind !== \"channel\" || e.channel !== filter.channel) return false\n }\n return true\n}\n\n/** Parse the ?type=run,task,... query-string param into an EventKind[].\n * Unknown values are dropped silently (forward-compat with future kinds). */\nexport function parseKindsParam(raw: string | null | undefined): EventKind[] | undefined {\n if (!raw) return undefined\n const known: EventKind[] = [\"run\", \"task\", \"signal\", \"mesh\", \"channel\", \"status\"]\n const parts = raw.split(\",\").map((s) => s.trim()).filter(Boolean) as EventKind[]\n const kept = parts.filter((p) => known.includes(p))\n return kept.length ? kept : undefined\n}\n","import type { IncomingMessage, ServerResponse } from \"http\"\nimport { createHmac, timingSafeEqual } from \"crypto\"\nimport type { AgentRegistry } from \"@/agents/registry\"\nimport type { A2AMesh } from \"@/a2a/mesh\"\nimport type { DaemonConfig } from \"./config\"\nimport type { WorkflowDispatcher } from \"@/workflows\"\n\n// --- Webhook handler ---\n// Routes incoming webhooks from GitLab, GitHub, Stripe, Sentry, etc.\n// to the appropriate agent with parsed payload context.\n//\n// Endpoints:\n// POST /webhook/:agentId — route to specific agent\n// POST /webhook/:agentId/:source — route with source hint (gitlab, github, etc.)\n//\n// The webhook payload is parsed and a human-readable summary is built\n// so the agent doesn't need to parse raw JSON.\n\ninterface WebhookConfig {\n /** Secret for validating webhook signatures (optional) */\n secrets?: Record<string, string> // source -> secret\n}\n\nexport class WebhookHandler {\n private registry: AgentRegistry\n private config: WebhookConfig\n private mesh?: A2AMesh\n private webhookEntries: DaemonConfig[\"webhooks\"]\n private log: (...args: unknown[]) => void\n private workflowDispatcher?: WorkflowDispatcher\n\n constructor(\n registry: AgentRegistry,\n config: WebhookConfig = {},\n log: (...args: unknown[]) => void = console.error.bind(console, \"[webhook]\"),\n mesh?: A2AMesh,\n webhookEntries: DaemonConfig[\"webhooks\"] = [],\n ) {\n this.registry = registry\n this.config = config\n this.log = log\n this.mesh = mesh\n this.webhookEntries = webhookEntries\n }\n\n /** Wired by the daemon after the workflow subsystem boots. When set,\n * webhook entries with a `triggers` map can dispatch a workflow per\n * inbound event-type instead of running the bound agent directly. */\n setWorkflowDispatcher(d: WorkflowDispatcher): void {\n this.workflowDispatcher = d\n }\n\n /** Hot-reload the webhook entry table. Called by the daemon's reload\n * handler when `webhooks[]` changes in agentx.json — adding a route,\n * rotating a secretEnv, flipping an entry from local to mesh-routed,\n * or editing a triggers map. Phase 4: closes the recurring complaint\n * that route changes require a full daemon restart. */\n setWebhookEntries(entries: DaemonConfig[\"webhooks\"]): void {\n this.webhookEntries = entries\n }\n\n /**\n * Handle an incoming webhook request.\n * URL format: /webhook/:agentId or /webhook/:agentId/:source\n */\n async handle(req: IncomingMessage, res: ServerResponse, path: string): Promise<void> {\n // Parse path: /webhook/<agentId> or /webhook/<agentId>/<source>\n const parts = path.replace(/^\\/webhook\\/?/, \"\").split(\"/\").filter(Boolean)\n const agentId = parts[0]\n const sourceHint = parts[1] // optional: \"gitlab\", \"github\", \"stripe\", \"sentry\"\n\n if (!agentId) {\n this.sendJson(res, 400, { error: \"Missing agent ID. Use /webhook/:agentId\" })\n return\n }\n\n // Read raw body bytes BEFORE JSON parsing — HMAC signatures are over\n // the wire bytes; parsing-then-reserializing breaks GitHub's, Stripe's,\n // and most providers' signature schemes (key ordering, whitespace).\n const { raw, parsed } = await this.readBody(req)\n\n // Detect source from headers if not in URL\n const source = sourceHint || this.detectSource(req.headers)\n\n // Mandatory signature check when the matching webhook entry has\n // `secretEnv` set. Silent no-op was the prior behavior — that's a\n // security trap: an operator configures a secret expecting protection,\n // gets none. Reject 401 instead.\n const sigError = this.validateSignature(req.headers, raw, source, agentId)\n if (sigError) {\n this.log(`Webhook [${source}] -> ${agentId}: REJECTED (${sigError})`)\n this.sendJson(res, 401, { error: sigError })\n return\n }\n\n // Parse the webhook payload into a human-readable message\n const summary = this.buildSummary(source, parsed, req.headers)\n\n this.log(`Webhook [${source}] -> ${agentId}: ${summary.slice(0, 100)}`)\n\n // Phase 3: per-event-type workflow routing. Each registered webhook\n // entry can map specific event-types to workflow ids; on match we\n // dispatch the workflow instead of running the bound agent directly.\n // Falls through to the existing agent path if nothing matches and no\n // defaultWorkflow is set — backward-compatible.\n const eventType = this.extractEventType(source, parsed, req.headers)\n const triggerEntry = this.webhookEntries.find(\n w => w.enabled && w.agentId === agentId && w.source === source,\n )\n const workflowId =\n (eventType && triggerEntry?.triggers?.[eventType]) ||\n triggerEntry?.defaultWorkflow ||\n null\n if (workflowId && this.workflowDispatcher) {\n try {\n const result = await this.workflowDispatcher.dispatchWorkflow({\n workflowId,\n entityRef: { kind: \"webhook\", id: `${source}:${agentId}:${eventType ?? \"—\"}:${Date.now()}` } as any,\n event: { kind: \"webhook\", source, eventType, payload: parsed } as any,\n trigger: { source },\n })\n this.log(\n `Webhook [${source}/${eventType ?? \"—\"}] -> workflow=${workflowId} (claimed=${result.claimed})`,\n )\n this.sendJson(res, 202, {\n ok: true,\n agent: agentId,\n source,\n eventType,\n workflow: workflowId,\n claimed: result.claimed,\n })\n return\n } catch (e: any) {\n this.log(`Workflow dispatch failed [${source}/${eventType}/${workflowId}]: ${e.message}`)\n // Fall through to agent path on error so we don't drop the event entirely.\n }\n }\n\n // Check if there's a webhook entry with mesh routing\n const meshEntry = this.webhookEntries.find(\n w => w.enabled && w.node && w.agentId === agentId && w.source === source,\n )\n\n if (meshEntry?.node && this.mesh) {\n // Respond immediately — GitHub/GitLab have short delivery timeouts.\n // Forward to mesh peer asynchronously.\n this.sendJson(res, 202, {\n ok: true,\n agent: agentId,\n source,\n node: meshEntry.node,\n status: \"accepted\",\n })\n // Forward the same context shape the local execution path would use,\n // so the receiving daemon's registry.execute keys the session by\n // `webhook:${source}` instead of falling back to api/default and\n // losing the source attribution.\n this.mesh.sendTask(meshEntry.node, summary, agentId, {\n context: {\n channel: `webhook:${source}`,\n sender: `webhook:${source}`,\n },\n }).then(\n (response) => this.log(`Mesh-forwarded webhook [${source}] -> ${meshEntry.node}/${agentId}: ${response?.slice(0, 100) || \"ok\"}`),\n (e: any) => this.log(`Mesh forward failed [${source}] -> ${meshEntry.node}/${agentId}: ${e.message}`),\n )\n return\n }\n\n // Execute on the local agent — pass `parsed` instead of re-parsing\n try {\n const response = await this.registry.execute({\n message: summary,\n agentId,\n context: {\n channel: `webhook:${source}`,\n sender: `webhook:${source}`,\n },\n })\n\n this.sendJson(res, response.error ? 500 : 200, {\n ok: !response.error,\n agent: agentId,\n source,\n response: response.content?.slice(0, 500),\n error: response.error,\n duration: response.duration,\n })\n } catch (e: any) {\n this.sendJson(res, 500, { error: e.message })\n }\n }\n\n /**\n * Extract a canonical event-type string from inbound headers + body.\n * Used by the per-event-type trigger map. Format conventions:\n * - GitHub: `<X-GitHub-Event>.<body.action>` when action exists,\n * else just `<X-GitHub-Event>` (e.g. \"push\", \"ping\").\n * - GitLab: `<X-Gitlab-Event>` (already includes a \"Hook\" suffix —\n * \"Push Hook\", \"Note Hook\", \"Merge Request Hook\").\n * - Stripe: `<body.type>` (e.g. \"invoice.paid\").\n * - Sentry: `<Sentry-Hook-Resource>` (e.g. \"issue\", \"event_alert\").\n * - Anything else: returns null and the dispatcher falls back to\n * `defaultWorkflow`.\n */\n private extractEventType(\n source: string,\n body: Record<string, unknown>,\n headers: Record<string, string | string[] | undefined>,\n ): string | undefined {\n const header = (k: string): string | undefined => {\n const v = headers[k.toLowerCase()]\n return Array.isArray(v) ? v[0] : v\n }\n if (source === \"github\") {\n const evt = header(\"x-github-event\")\n if (!evt) return undefined\n const action = typeof body.action === \"string\" ? body.action : undefined\n return action ? `${evt}.${action}` : evt\n }\n if (source === \"gitlab\") {\n return header(\"x-gitlab-event\") || (typeof body.object_kind === \"string\" ? body.object_kind : undefined)\n }\n if (source === \"stripe\") {\n return typeof body.type === \"string\" ? body.type : undefined\n }\n if (source === \"sentry\") {\n return header(\"sentry-hook-resource\")\n }\n return undefined\n }\n\n /**\n * Detect webhook source from request headers.\n */\n private detectSource(headers: Record<string, string | string[] | undefined>): string {\n if (headers[\"x-gitlab-event\"] || headers[\"x-gitlab-token\"]) return \"gitlab\"\n if (headers[\"x-github-event\"]) return \"github\"\n if (headers[\"stripe-signature\"]) return \"stripe\"\n if (headers[\"sentry-hook-resource\"]) return \"sentry\"\n if (headers[\"x-vercel-signature\"]) return \"vercel\"\n if (headers[\"x-hub-signature-256\"]) return \"github\"\n return \"unknown\"\n }\n\n /**\n * Build a human-readable summary from the webhook payload.\n * Agent reads this instead of raw JSON.\n */\n private buildSummary(source: string, body: Record<string, unknown>, headers: Record<string, string | string[] | undefined>): string {\n const lines: string[] = [`[Webhook from ${source}]`]\n\n switch (source) {\n case \"gitlab\": {\n const event = headers[\"x-gitlab-event\"] as string || body.object_kind as string || \"event\"\n const project = (body.project as any)?.path_with_namespace || \"\"\n const user = (body.user as any)?.name || (body.user_username as string) || \"\"\n\n lines.push(`Event: ${event}`)\n if (project) lines.push(`Project: ${project}`)\n if (user) lines.push(`User: ${user}`)\n\n // Push events\n if (body.ref) lines.push(`Ref: ${body.ref}`)\n if (body.commits && Array.isArray(body.commits)) {\n lines.push(`Commits: ${body.commits.length}`)\n for (const c of (body.commits as any[]).slice(0, 3)) {\n lines.push(` - ${c.message?.split(\"\\n\")[0] || \"no message\"} (${c.author?.name || \"\"})`)\n }\n }\n\n // MR events\n const mr = body.object_attributes as any\n if (mr?.title) {\n lines.push(`Title: ${mr.title}`)\n lines.push(`State: ${mr.state || \"\"}`)\n lines.push(`Action: ${mr.action || \"\"}`)\n if (mr.source_branch) lines.push(`Branch: ${mr.source_branch} -> ${mr.target_branch}`)\n if (mr.url) lines.push(`URL: ${mr.url}`)\n }\n\n // Issue events\n if (mr?.iid && !mr?.source_branch) {\n lines.push(`Issue #${mr.iid}: ${mr.title || \"\"}`)\n if (mr.description) lines.push(`Description: ${mr.description.slice(0, 200)}`)\n }\n\n // Pipeline events\n if (body.object_kind === \"pipeline\") {\n const attrs = body.object_attributes as any\n lines.push(`Pipeline: ${attrs?.status || \"\"} (${attrs?.ref || \"\"})`)\n lines.push(`Duration: ${attrs?.duration || 0}s`)\n }\n break\n }\n\n case \"github\": {\n const event = headers[\"x-github-event\"] as string || \"event\"\n const repo = (body.repository as any)?.full_name || \"\"\n const sender = (body.sender as any)?.login || \"\"\n\n lines.push(`Event: ${event}`)\n if (repo) lines.push(`Repository: ${repo}`)\n if (sender) lines.push(`Sender: ${sender}`)\n\n // Push\n if (body.ref) lines.push(`Ref: ${body.ref}`)\n if (body.commits && Array.isArray(body.commits)) {\n for (const c of (body.commits as any[]).slice(0, 3)) {\n lines.push(` - ${c.message?.split(\"\\n\")[0] || \"\"} (${c.author?.name || \"\"})`)\n }\n }\n\n // PR\n const pr = body.pull_request as any\n if (pr) {\n lines.push(`PR #${pr.number}: ${pr.title}`)\n lines.push(`Action: ${body.action}`)\n lines.push(`Branch: ${pr.head?.ref} -> ${pr.base?.ref}`)\n }\n\n // Issue\n const issue = body.issue as any\n if (issue) {\n lines.push(`Issue #${issue.number}: ${issue.title}`)\n lines.push(`Action: ${body.action}`)\n }\n break\n }\n\n case \"stripe\": {\n const type = body.type as string || \"event\"\n const data = (body.data as any)?.object || {}\n\n lines.push(`Event: ${type}`)\n if (data.amount) lines.push(`Amount: ${(data.amount / 100).toFixed(2)} ${data.currency?.toUpperCase() || \"\"}`)\n if (data.customer_email) lines.push(`Customer: ${data.customer_email}`)\n if (data.description) lines.push(`Description: ${data.description}`)\n if (data.status) lines.push(`Status: ${data.status}`)\n break\n }\n\n case \"sentry\": {\n const resource = headers[\"sentry-hook-resource\"] as string || \"event\"\n lines.push(`Resource: ${resource}`)\n\n const data = body.data as any || body\n if (data.error?.title) lines.push(`Error: ${data.error.title}`)\n if (data.error?.culprit) lines.push(`Culprit: ${data.error.culprit}`)\n if (data.error?.metadata?.value) lines.push(`Message: ${data.error.metadata.value}`)\n if (body.url) lines.push(`URL: ${body.url}`)\n break\n }\n\n default:\n lines.push(`Headers: ${JSON.stringify(Object.keys(headers).filter(k => k.startsWith(\"x-\")).slice(0, 5))}`)\n lines.push(`Payload keys: ${Object.keys(body).slice(0, 10).join(\", \")}`)\n // Include raw body summary for unknown sources\n const raw = JSON.stringify(body).slice(0, 500)\n lines.push(`Body: ${raw}`)\n }\n\n return lines.join(\"\\n\")\n }\n\n private async readBody(req: IncomingMessage): Promise<{ raw: string; parsed: Record<string, unknown> }> {\n return new Promise((resolve) => {\n let body = \"\"\n req.on(\"data\", (chunk: Buffer) => (body += chunk.toString()))\n req.on(\"end\", () => {\n let parsed: Record<string, unknown>\n try {\n parsed = body ? JSON.parse(body) : {}\n } catch {\n parsed = { raw: body }\n }\n resolve({ raw: body, parsed })\n })\n req.on(\"error\", () => resolve({ raw: \"\", parsed: {} }))\n })\n }\n\n /**\n * Validate the inbound webhook signature against the configured secret\n * for this (source, agentId). Returns a human-readable error string when\n * the request must be rejected, or null when the request is allowed\n * through.\n *\n * Per source:\n * - github: HMAC-SHA256 over the raw body, compared against\n * `X-Hub-Signature-256: sha256=<hex>`.\n * - gitlab: simple equality of `X-Gitlab-Token` against the secret.\n * - stripe: HMAC-SHA256 over `<timestamp>.<body>`, parsed from the\n * `Stripe-Signature: t=<ts>,v1=<sig>[,v0=...]` header.\n * - everything else: require `X-Webhook-Secret` to equal the secret.\n *\n * If no `secretEnv` is configured for the matching webhook entry, we let\n * the request through. (Configuring a secret = opt-in to enforcement.)\n */\n private validateSignature(\n headers: Record<string, string | string[] | undefined>,\n raw: string,\n source: string,\n agentId: string,\n ): string | null {\n const entry = this.webhookEntries.find(\n w => w.enabled && w.agentId === agentId && w.source === source,\n )\n if (!entry?.secretEnv) return null\n const secret = process.env[entry.secretEnv]\n if (!secret) {\n return `webhook entry \"${entry.id}\" expects env ${entry.secretEnv} but it is unset`\n }\n const headerStr = (key: string): string | undefined => {\n const v = headers[key.toLowerCase()]\n return Array.isArray(v) ? v[0] : v\n }\n\n if (source === \"github\") {\n const sig = headerStr(\"x-hub-signature-256\")\n if (!sig) return \"missing X-Hub-Signature-256\"\n const expected = \"sha256=\" + createHmac(\"sha256\", secret).update(raw).digest(\"hex\")\n return safeEqualOrReject(expected, sig)\n }\n if (source === \"gitlab\") {\n const tok = headerStr(\"x-gitlab-token\")\n if (!tok) return \"missing X-Gitlab-Token\"\n return safeEqualOrReject(secret, tok)\n }\n if (source === \"stripe\") {\n const sigHeader = headerStr(\"stripe-signature\")\n if (!sigHeader) return \"missing Stripe-Signature\"\n const parts = Object.fromEntries(\n sigHeader.split(\",\").map(p => p.split(\"=\")) as [string, string][],\n )\n if (!parts.t || !parts.v1) return \"malformed Stripe-Signature\"\n const expected = createHmac(\"sha256\", secret).update(`${parts.t}.${raw}`).digest(\"hex\")\n return safeEqualOrReject(expected, parts.v1)\n }\n // Generic / discord / slack / sentry / custom: a plain shared secret\n // header is the simplest contract. Operators of these can override at\n // their proxy if they need richer schemes.\n const generic = headerStr(\"x-webhook-secret\")\n if (!generic) return \"missing X-Webhook-Secret\"\n return safeEqualOrReject(secret, generic)\n }\n\n private sendJson(res: ServerResponse, status: number, data: unknown): void {\n res.writeHead(status, { \"Content-Type\": \"application/json\" })\n res.end(JSON.stringify(data, null, 2))\n }\n}\n\n/** Constant-time compare of two hex/text strings of (potentially) equal\n * length. Returns null on match, a \"signature mismatch\" error string\n * otherwise. Different lengths fail without leaking the expected length. */\nfunction safeEqualOrReject(expected: string, actual: string): string | null {\n if (expected.length !== actual.length) return \"signature mismatch\"\n const a = Buffer.from(expected)\n const b = Buffer.from(actual)\n return timingSafeEqual(a, b) ? null : \"signature mismatch\"\n}\n","import Database from \"better-sqlite3\"\nimport { existsSync, mkdirSync } from \"fs\"\nimport { dirname, resolve } from \"path\"\n\n// --- SQLite storage layer ---\n//\n// Move 2 of the architectural review (docs/architecture/hexabot-vs-agentx.md).\n// SQLite for the operational tables — task_history, usage, dedup, sessions —\n// where queryable, indexed access has user-visible value (dashboards,\n// retention, analytics). Workflow definitions, wiki, agent-memory, and\n// references stay as files: they're git-diffable, human-readable, and don't\n// need queries.\n//\n// Approach: subscribe to the event bus (src/events/bus.ts) and write to\n// SQLite from the subscribers. Existing JSON writes are not touched —\n// SQLite runs alongside as a parallel pipeline. Once we have a query API\n// the dashboard depends on, we can deprecate the JSON path. Until then,\n// SQLite is observability-grade only.\n//\n// Synchronous API: better-sqlite3 is sync, no callbacks. The daemon's\n// event loop is the single writer; multiple readers (dashboard) use WAL\n// mode for concurrent read while we write.\n\nlet _db: Database.Database | undefined\n\nexport interface OpenOptions {\n /** Resolved relative to cwd. Default: .agentx/db.sqlite */\n path?: string\n /** When true, SQLite writes are skipped (used by tests + opt-out). */\n disabled?: boolean\n}\n\n/**\n * Open the per-process SQLite database. Idempotent — repeated calls\n * return the same handle. The file is created on first open; the schema\n * is migrated forward to the latest version.\n *\n * Returns null when disabled — every subscriber should check for null\n * before issuing a write so the daemon stays runnable without SQLite if\n * someone's environment can't compile the native binding.\n */\nexport function openDb(opts: OpenOptions = {}): Database.Database | null {\n if (opts.disabled) return null\n if (_db) return _db\n const path = resolve(process.cwd(), opts.path ?? \".agentx/db.sqlite\")\n try {\n mkdirSync(dirname(path), { recursive: true })\n const db = new Database(path)\n // WAL keeps readers (dashboard) unblocked while the daemon writes.\n // Synchronous=NORMAL is the SQLite-recommended balance for WAL: the\n // db is durable on power-loss, but a crash within the last few ms can\n // lose the most recent commit. For our use (observability), that's fine.\n db.pragma(\"journal_mode = WAL\")\n db.pragma(\"synchronous = NORMAL\")\n db.pragma(\"foreign_keys = ON\")\n runMigrations(db)\n _db = db\n return _db\n } catch (e: any) {\n // Native binding missing, file unwritable, etc. Surface always — the\n // alternative was a silent no-op, which makes the \"SQLite not opened\"\n // message in the daemon log impossible to debug.\n console.error(`[storage/sqlite] openDb failed at ${path}: ${e.message}`)\n return null\n }\n}\n\n/** Close the global handle. Test-only — production never closes. */\nexport function closeDb(): void {\n if (_db) {\n _db.close()\n _db = undefined\n }\n}\n\n/**\n * Per-version schema steps. New tables / columns go in a new migration —\n * never mutate an old one. Run on every open(); each step is idempotent\n * because it CREATE TABLE IF NOT EXISTS. SQLite has no concurrent\n * migration issue here (single writer process).\n */\nfunction runMigrations(db: Database.Database): void {\n db.exec(`\n CREATE TABLE IF NOT EXISTS schema_version (\n v INTEGER PRIMARY KEY\n );\n `)\n const current = (db\n .prepare(\"SELECT MAX(v) AS v FROM schema_version\")\n .get() as { v: number | null }).v ?? 0\n\n const steps: Array<{ v: number; sql: string }> = [\n {\n v: 1,\n sql: `\n CREATE TABLE task_history (\n id TEXT PRIMARY KEY,\n agent_id TEXT NOT NULL,\n channel TEXT,\n chat_id TEXT,\n status TEXT NOT NULL, -- 'ok' | 'error' | 'timeout'\n message_preview TEXT,\n error TEXT,\n duration_ms INTEGER,\n input_tokens INTEGER,\n output_tokens INTEGER,\n cache_read_tokens INTEGER,\n cache_create_tokens INTEGER,\n started_at TEXT NOT NULL,\n finished_at TEXT NOT NULL\n );\n CREATE INDEX idx_task_history_agent_started\n ON task_history(agent_id, started_at);\n CREATE INDEX idx_task_history_status\n ON task_history(status, started_at);\n `,\n },\n {\n v: 2,\n sql: `\n CREATE TABLE usage_daily (\n agent_id TEXT NOT NULL,\n model TEXT NOT NULL,\n day TEXT NOT NULL, -- 'YYYY-MM-DD' UTC\n input_tokens INTEGER DEFAULT 0,\n output_tokens INTEGER DEFAULT 0,\n cache_read_tokens INTEGER DEFAULT 0,\n cache_create_tokens INTEGER DEFAULT 0,\n tasks INTEGER DEFAULT 0,\n PRIMARY KEY (agent_id, model, day)\n );\n CREATE INDEX idx_usage_day ON usage_daily(day);\n `,\n },\n {\n v: 3,\n sql: `\n CREATE TABLE rotations (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n agent_id TEXT NOT NULL,\n channel TEXT NOT NULL,\n chat_id TEXT NOT NULL,\n reason TEXT NOT NULL, -- 'stale' | 'tier-2' | 'max-turns'\n last_turn_input_tokens INTEGER,\n rotated_at TEXT NOT NULL\n );\n CREATE INDEX idx_rotations_agent_at\n ON rotations(agent_id, rotated_at);\n `,\n },\n {\n v: 4,\n sql: `\n CREATE TABLE route_traces (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n channel TEXT NOT NULL,\n chat_id TEXT NOT NULL,\n msg_id TEXT NOT NULL,\n account_id TEXT,\n kind TEXT NOT NULL, -- 'match' | 'drop'\n deciding_stage TEXT,\n agent_id TEXT,\n reason TEXT,\n at TEXT NOT NULL\n );\n CREATE INDEX idx_route_traces_channel_at\n ON route_traces(channel, at);\n `,\n },\n {\n // Move A — tier-2 token buckets on usage_daily. The per-request tier\n // split is decided at record time by TokenTracker (it's a function of\n // the live cumulative input, not the row totals), so we cannot\n // reconstruct it on read. Adding columns is additive and idempotent;\n // existing rows default to 0.\n v: 5,\n sql: `\n ALTER TABLE usage_daily ADD COLUMN tier2_input_tokens INTEGER DEFAULT 0;\n ALTER TABLE usage_daily ADD COLUMN tier2_output_tokens INTEGER DEFAULT 0;\n ALTER TABLE usage_daily ADD COLUMN tier2_cache_read_tokens INTEGER DEFAULT 0;\n ALTER TABLE usage_daily ADD COLUMN tier2_cache_create_tokens INTEGER DEFAULT 0;\n `,\n },\n ]\n\n const txn = db.transaction((step: { v: number; sql: string }) => {\n db.exec(step.sql)\n db.prepare(\"INSERT INTO schema_version (v) VALUES (?)\").run(step.v)\n })\n\n for (const step of steps) {\n if (step.v > current) txn(step)\n }\n}\n\n/** Schema version check for tests. */\nexport function getSchemaVersion(db: Database.Database): number {\n return (db.prepare(\"SELECT MAX(v) AS v FROM schema_version\").get() as { v: number | null }).v ?? 0\n}\n\nexport interface PruneResult {\n taskHistory: number\n rotations: number\n routeTraces: number\n}\n\n/**\n * Drop rows older than `retentionDays` from the operational tables that grow\n * unbounded over time. `usage_daily` is bounded by (agent, model, day) and\n * doesn't need pruning; the caller decides whether to keep aggregates beyond\n * the retention window.\n *\n * Mirrors the file-based `pruneTaskHistory` sweep that runs at daemon\n * startup. Cheap — three indexed deletes plus an incremental_vacuum.\n */\nexport function pruneSqliteTables(\n db: Database.Database,\n retentionDays: number,\n): PruneResult {\n const cutoffMs = Date.now() - retentionDays * 24 * 60 * 60 * 1000\n const cutoffIso = new Date(cutoffMs).toISOString()\n\n const taskHistory = (db\n .prepare(`DELETE FROM task_history WHERE started_at < ?`)\n .run(cutoffIso).changes ?? 0) as number\n const rotations = (db\n .prepare(`DELETE FROM rotations WHERE rotated_at < ?`)\n .run(cutoffIso).changes ?? 0) as number\n const routeTraces = (db\n .prepare(`DELETE FROM route_traces WHERE at < ?`)\n .run(cutoffIso).changes ?? 0) as number\n\n // Reclaim the freelist pages so the file shrinks. WAL writes still\n // checkpoint independently.\n if (taskHistory + rotations + routeTraces > 0) {\n try { db.pragma(\"incremental_vacuum\") } catch { /* best-effort */ }\n }\n\n return { taskHistory, rotations, routeTraces }\n}\n","import type Database from \"better-sqlite3\"\nimport { getEventBus, type AgentXEvents } from \"@/events/bus\"\n\n// --- Bus subscribers that persist to SQLite ---\n//\n// Move 2 wires the daemon's lifecycle events (Move 1) to SQLite tables\n// without touching the hot path. Each subscriber is a tiny function that\n// translates an event payload into a prepared-statement run.\n//\n// Idempotency: every subscriber uses INSERT OR REPLACE / UPSERT so\n// replays don't double-count. Cheap: better-sqlite3 prepared statements\n// run in microseconds, well under any agent turn's lifetime.\n\ninterface Stmts {\n insertTaskHistory: Database.Statement\n upsertUsageDaily: Database.Statement\n insertRotation: Database.Statement\n insertRouteTrace: Database.Statement\n}\n\nfunction prepare(db: Database.Database): Stmts {\n return {\n insertTaskHistory: db.prepare(`\n INSERT OR REPLACE INTO task_history (\n id, agent_id, channel, chat_id, status, message_preview, error,\n duration_ms, input_tokens, output_tokens,\n cache_read_tokens, cache_create_tokens,\n started_at, finished_at\n ) VALUES (\n @id, @agent_id, @channel, @chat_id, @status, @message_preview, @error,\n @duration_ms, @input_tokens, @output_tokens,\n @cache_read_tokens, @cache_create_tokens,\n @started_at, @finished_at\n )\n `),\n upsertUsageDaily: db.prepare(`\n INSERT INTO usage_daily (\n agent_id, model, day,\n input_tokens, output_tokens, cache_read_tokens, cache_create_tokens,\n tier2_input_tokens, tier2_output_tokens,\n tier2_cache_read_tokens, tier2_cache_create_tokens,\n tasks\n ) VALUES (\n @agent_id, @model, @day,\n @input_tokens, @output_tokens, @cache_read_tokens, @cache_create_tokens,\n @tier2_input_tokens, @tier2_output_tokens,\n @tier2_cache_read_tokens, @tier2_cache_create_tokens,\n 1\n )\n ON CONFLICT(agent_id, model, day) DO UPDATE SET\n input_tokens = input_tokens + excluded.input_tokens,\n output_tokens = output_tokens + excluded.output_tokens,\n cache_read_tokens = cache_read_tokens + excluded.cache_read_tokens,\n cache_create_tokens = cache_create_tokens + excluded.cache_create_tokens,\n tier2_input_tokens = tier2_input_tokens + excluded.tier2_input_tokens,\n tier2_output_tokens = tier2_output_tokens + excluded.tier2_output_tokens,\n tier2_cache_read_tokens = tier2_cache_read_tokens + excluded.tier2_cache_read_tokens,\n tier2_cache_create_tokens = tier2_cache_create_tokens + excluded.tier2_cache_create_tokens,\n tasks = tasks + 1\n `),\n insertRotation: db.prepare(`\n INSERT INTO rotations (agent_id, channel, chat_id, reason, last_turn_input_tokens, rotated_at)\n VALUES (@agent_id, @channel, @chat_id, @reason, @last_turn_input_tokens, @rotated_at)\n `),\n insertRouteTrace: db.prepare(`\n INSERT INTO route_traces (channel, chat_id, msg_id, account_id, kind, deciding_stage, agent_id, reason, at)\n VALUES (@channel, @chat_id, @msg_id, @account_id, @kind, @deciding_stage, @agent_id, @reason, @at)\n `),\n }\n}\n\ninterface PendingTask {\n agentId: string\n channel: string\n chatId: string\n startedAt: string\n messagePreview: string\n}\n\n/**\n * Wire all SQLite subscribers to the global event bus. Returns a\n * disposer that removes them — used by tests, not by the daemon.\n *\n * The pending-task map matches `task:started` to its corresponding\n * `task:completed` so we record one row with both starts and ends. Lost\n * starts (daemon crash mid-task) just won't have a row; that's fine —\n * task_history is observability, not source of truth.\n */\nexport function attachSqliteSubscribers(db: Database.Database, model = \"claude-opus-4-7\"): () => void {\n const bus = getEventBus()\n const stmts = prepare(db)\n const pending = new Map<string, PendingTask>()\n\n const onStarted = (p: AgentXEvents[\"task:started\"]) => {\n const key = `${p.agentId}:${p.channel}:${p.chatId}`\n pending.set(key, {\n agentId: p.agentId,\n channel: p.channel,\n chatId: p.chatId,\n startedAt: p.at,\n messagePreview: p.messagePreview,\n })\n }\n\n const onCompleted = (p: AgentXEvents[\"task:completed\"]) => {\n const key = `${p.agentId}:${p.channel}:${p.chatId}`\n const start = pending.get(key)\n pending.delete(key)\n const id = `${p.agentId}:${p.channel}:${p.chatId}:${p.at}`\n const status = p.error ? \"error\" : \"ok\"\n\n try {\n stmts.insertTaskHistory.run({\n id,\n agent_id: p.agentId,\n channel: p.channel,\n chat_id: p.chatId,\n status,\n message_preview: start?.messagePreview ?? null,\n error: p.error ?? null,\n duration_ms: p.durationMs,\n input_tokens: p.inputTokens ?? null,\n output_tokens: p.outputTokens ?? null,\n cache_read_tokens: p.cacheReadTokens ?? null,\n cache_create_tokens: p.cacheCreateTokens ?? null,\n started_at: start?.startedAt ?? p.at,\n finished_at: p.at,\n })\n } catch { /* best-effort observability */ }\n\n // Upsert into usage_daily when ANY token bucket — tier1 or tier2 —\n // saw activity. A tier2-only request still counts as a billable task,\n // and the previous \"(p.inputTokens || p.outputTokens)\" gate would\n // silently drop it.\n const hasUsage =\n p.inputTokens || p.outputTokens || p.cacheReadTokens || p.cacheCreateTokens ||\n p.tier2InputTokens || p.tier2OutputTokens || p.tier2CacheReadTokens || p.tier2CacheCreateTokens\n if (!p.error && hasUsage) {\n const day = p.at.slice(0, 10)\n try {\n stmts.upsertUsageDaily.run({\n agent_id: p.agentId,\n model,\n day,\n input_tokens: p.inputTokens ?? 0,\n output_tokens: p.outputTokens ?? 0,\n cache_read_tokens: p.cacheReadTokens ?? 0,\n cache_create_tokens: p.cacheCreateTokens ?? 0,\n tier2_input_tokens: p.tier2InputTokens ?? 0,\n tier2_output_tokens: p.tier2OutputTokens ?? 0,\n tier2_cache_read_tokens: p.tier2CacheReadTokens ?? 0,\n tier2_cache_create_tokens: p.tier2CacheCreateTokens ?? 0,\n })\n } catch { /* */ }\n }\n }\n\n const onRotated = (p: AgentXEvents[\"session:rotated\"]) => {\n try {\n stmts.insertRotation.run({\n agent_id: p.agentId,\n channel: p.channel,\n chat_id: p.chatId,\n reason: p.reason,\n last_turn_input_tokens: p.lastTurnInputTokens ?? null,\n rotated_at: p.at,\n })\n } catch { /* */ }\n }\n\n const onMatched = (p: AgentXEvents[\"message:matched\"]) => {\n try {\n stmts.insertRouteTrace.run({\n channel: p.channel,\n chat_id: p.chatId,\n msg_id: p.msgId,\n account_id: p.accountId ?? null,\n kind: \"match\",\n deciding_stage: p.decidingStage,\n agent_id: p.agentId,\n reason: null,\n at: p.at,\n })\n } catch { /* */ }\n }\n\n const onDropped = (p: AgentXEvents[\"message:dropped\"]) => {\n try {\n stmts.insertRouteTrace.run({\n channel: p.channel,\n chat_id: p.chatId,\n msg_id: p.msgId,\n account_id: p.accountId ?? null,\n kind: \"drop\",\n deciding_stage: p.decidingStage,\n agent_id: null,\n reason: p.reason,\n at: p.at,\n })\n } catch { /* */ }\n }\n\n bus.on(\"task:started\", onStarted)\n bus.on(\"task:completed\", onCompleted)\n bus.on(\"session:rotated\", onRotated)\n bus.on(\"message:matched\", onMatched)\n bus.on(\"message:dropped\", onDropped)\n\n return () => {\n bus.off(\"task:started\", onStarted)\n bus.off(\"task:completed\", onCompleted)\n bus.off(\"session:rotated\", onRotated)\n bus.off(\"message:matched\", onMatched)\n bus.off(\"message:dropped\", onDropped)\n }\n}\n","import type Database from \"better-sqlite3\"\nimport { TokenTracker, type AgentUsage, type DailyUsage } from \"@/daemon/token-tracker\"\n\n// --- SQLite read path for usage observability ---\n//\n// Move A — the dashboard's date-range loader and the daemon's /health\n// endpoint switch their source of truth from the per-day JSON files in\n// .agentx/usage/ to the usage_daily table in .agentx/db.sqlite.\n//\n// Pure read functions. Each accepts a Database handle (or null when\n// SQLite is disabled) plus the same string args the JSON readers used\n// to take, so call sites are a one-line swap. Returns the same shapes\n// the JSON layer produced — UsageDay (dashboard) and DailyUsage\n// (registry/health) — so downstream rendering doesn't fork.\n//\n// Cost computation: tier-1 columns + tier-2 columns are summed and\n// passed to TokenTracker.calculateCost() exactly as the JSON path\n// does, applying the TIER2_MULTIPLIER per the existing pricing table.\n// The math is the same; only the data source changes.\n\n/** Same shape the dashboard's loadUsageRange() returns. */\nexport interface UsageDay {\n date: string\n tasks: number\n input: number\n output: number\n cacheRead: number\n cacheCreate: number\n cost: number\n agents: Record<string, {\n tasks: number\n cost: number\n input: number\n output: number\n cacheRead: number\n cacheCreate: number\n model?: string\n }>\n}\n\ninterface UsageRow {\n agent_id: string\n model: string\n day: string\n input_tokens: number\n output_tokens: number\n cache_read_tokens: number\n cache_create_tokens: number\n tier2_input_tokens: number\n tier2_output_tokens: number\n tier2_cache_read_tokens: number\n tier2_cache_create_tokens: number\n tasks: number\n}\n\n/** Build an AgentUsage view over a single SQLite row so we can pass it\n * to TokenTracker.calculateCost without inventing a new pricing path. */\nfunction rowToAgentUsage(r: UsageRow): AgentUsage {\n return {\n tasks: r.tasks,\n inputTokens: r.input_tokens,\n outputTokens: r.output_tokens,\n cacheReadTokens: r.cache_read_tokens,\n cacheCreateTokens: r.cache_create_tokens,\n tier2InputTokens: r.tier2_input_tokens,\n tier2OutputTokens: r.tier2_output_tokens,\n tier2CacheReadTokens: r.tier2_cache_read_tokens,\n tier2CacheCreateTokens: r.tier2_cache_create_tokens,\n totalDuration: 0,\n errors: 0,\n model: r.model,\n }\n}\n\n/** Read `usage_daily` rows for a single day and roll them up into a\n * DailyUsage value compatible with `TokenTracker.today()`'s shape. */\nexport function loadDailyUsage(db: Database.Database | null, date: string): DailyUsage | null {\n if (!db) return null\n const rows = db\n .prepare(`SELECT * FROM usage_daily WHERE day = ?`)\n .all(date) as UsageRow[]\n if (rows.length === 0) return null\n\n const agents: Record<string, AgentUsage> = {}\n for (const r of rows) {\n // Multiple rows per (agent, model, day). Same agent + multiple models\n // → fold the second into the first; the dashboard's downstream cost\n // math handles a single AgentUsage object per agent.\n const existing = agents[r.agent_id]\n const next = rowToAgentUsage(r)\n if (!existing) {\n agents[r.agent_id] = next\n } else {\n existing.tasks += next.tasks\n existing.inputTokens += next.inputTokens\n existing.outputTokens += next.outputTokens\n existing.cacheReadTokens += next.cacheReadTokens\n existing.cacheCreateTokens += next.cacheCreateTokens\n existing.tier2InputTokens = (existing.tier2InputTokens || 0) + (next.tier2InputTokens || 0)\n existing.tier2OutputTokens = (existing.tier2OutputTokens || 0) + (next.tier2OutputTokens || 0)\n existing.tier2CacheReadTokens = (existing.tier2CacheReadTokens || 0) + (next.tier2CacheReadTokens || 0)\n existing.tier2CacheCreateTokens = (existing.tier2CacheCreateTokens || 0) + (next.tier2CacheCreateTokens || 0)\n // Last-wins on `model` — matches the JSON tracker which stores the\n // most recent model per agent. Cost is computed per-row regardless.\n existing.model = next.model\n }\n }\n return { date, agents }\n}\n\n/** Roll a date range into an array of UsageDay values (dashboard shape).\n * Inclusive `from` and `to`. Days with zero tasks are omitted, matching\n * the JSON loader's `.filter(d.tasks > 0)`. Sorted ASC by date. */\nexport function loadUsageRange(db: Database.Database | null, from: string, to: string): UsageDay[] {\n if (!db) return []\n\n const rows = db\n .prepare(\n `SELECT * FROM usage_daily\n WHERE (@from = '' OR day >= @from)\n AND (@to = '' OR day <= @to)\n ORDER BY day ASC`\n )\n .all({ from, to }) as UsageRow[]\n\n // Group by day → roll up per-agent.\n const byDay = new Map<string, UsageRow[]>()\n for (const r of rows) {\n const list = byDay.get(r.day) ?? []\n list.push(r)\n byDay.set(r.day, list)\n }\n\n const out: UsageDay[] = []\n for (const [date, dayRows] of byDay) {\n let tasks = 0, input = 0, output = 0, cacheRead = 0, cacheCreate = 0, cost = 0\n const agents: UsageDay[\"agents\"] = {}\n for (const r of dayRows) {\n const usage = rowToAgentUsage(r)\n const agentCost = TokenTracker.calculateCost(usage)\n const aInput = (usage.inputTokens || 0) + (usage.tier2InputTokens || 0)\n const aOutput = (usage.outputTokens || 0) + (usage.tier2OutputTokens || 0)\n const aCacheRead = (usage.cacheReadTokens || 0) + (usage.tier2CacheReadTokens || 0)\n const aCacheCreate = (usage.cacheCreateTokens || 0) + (usage.tier2CacheCreateTokens || 0)\n tasks += usage.tasks\n input += aInput\n output += aOutput\n cacheRead += aCacheRead\n cacheCreate += aCacheCreate\n cost += agentCost\n // Same-agent / multi-model rows get folded into a single agent entry.\n const prior = agents[r.agent_id]\n if (!prior) {\n agents[r.agent_id] = {\n tasks: usage.tasks,\n cost: agentCost,\n input: aInput,\n output: aOutput,\n cacheRead: aCacheRead,\n cacheCreate: aCacheCreate,\n model: usage.model,\n }\n } else {\n prior.tasks += usage.tasks\n prior.cost += agentCost\n prior.input += aInput\n prior.output += aOutput\n prior.cacheRead += aCacheRead\n prior.cacheCreate += aCacheCreate\n prior.model = usage.model || prior.model\n }\n }\n if (tasks > 0) {\n out.push({ date, tasks, input, output, cacheRead, cacheCreate, cost, agents })\n }\n }\n // Map iteration preserves insertion order; rows came back ORDER BY day,\n // so out[] is already ASC by date. Sort defensively in case SQLite's\n // collation returns rows in a different order under some PRAGMA.\n out.sort((a, b) => a.date.localeCompare(b.date))\n return out\n}\n\n/** Today's rollup in the same shape `TokenTracker.today()` returns,\n * for the daemon's /health endpoint. Empty `{ date, agents: {} }`\n * when SQLite is null or holds no rows for today. */\nexport function loadTodayRollup(db: Database.Database | null, today?: string): DailyUsage {\n const date = today ?? new Date().toISOString().slice(0, 10)\n const daily = loadDailyUsage(db, date)\n return daily ?? { date, agents: {} }\n}\n\nexport type UsageReadMode = \"sqlite\" | \"json\" | \"sqlite-then-json\"\n\n/** Resolve the dashboard / health read path against the AGENTX_USAGE_READ\n * env var. Default `sqlite-then-json` is the migration-safe choice — try\n * SQLite first, fall back to JSON when the handle is null or the SQLite\n * rollup is empty. Operators can pin \"sqlite\" once they've confirmed\n * rollups are populated, or pin \"json\" to keep legacy behaviour. */\nexport function getUsageReadMode(env: NodeJS.ProcessEnv = process.env): UsageReadMode {\n const v = (env.AGENTX_USAGE_READ ?? \"\").toLowerCase().trim()\n if (v === \"sqlite\" || v === \"json\" || v === \"sqlite-then-json\") return v\n return \"sqlite-then-json\"\n}\n","import { z } from \"zod\"\nimport type { ChannelAdapter } from \"@/channels/types\"\nimport type { AgentXEvents } from \"@/events/bus\"\nimport type { DaemonConfig, AgentDef } from \"@/daemon/config\"\n\n// --- Move B — Plugin contract ---\n//\n// Third-party JS/TS packages can register channel adapters and bus\n// subscribers at daemon boot. A plugin is an npm package whose default\n// export matches `AgentXPlugin`; operators list it in `agentx.json`'s\n// `plugins: []` array and `pnpm install` the package.\n//\n// The contract is minimal and stable on purpose:\n// - `manifest` declares identity + an optional version range to gate\n// against the daemon's major.minor.\n// - `setup(ctx)` is called once during daemon construction; the plugin\n// wires what it needs through the context's narrow API and returns.\n// - `teardown()` runs on `daemon.stop()`; subscribers attached via\n// ctx.on() are removed automatically, so teardown is only needed for\n// resources the plugin owns directly (timers, sockets, native\n// handles).\n//\n// The bus is the canonical extension point. Plugins observe what's\n// happening (message:matched, task:completed, session:rotated, …) and\n// react. Plugins that need to PRODUCE inbound messages do so via\n// addChannel() — the registered ChannelAdapter participates in routing\n// alongside built-in channels.\n//\n// Boundaries the contract deliberately does NOT cross in v1:\n// - No registry mutation. Plugins can't add agents at runtime;\n// agentx.json remains the source of truth.\n// - No MCP server registration. MCP is per-agent file sync today.\n// - No sandbox. Plugins run in-process with full daemon access; only\n// install plugins you trust.\n// - No hot-reload. Restart-required.\n\n/** Plugin identification + the version range it claims compatibility\n * with. The loader compares `agentxRange` against the daemon's\n * major.minor and skips on mismatch. Optional — omit for \"any\". */\nexport interface AgentXPluginManifest {\n name: string\n version: string\n /** Semver-style range. When omitted, plugin loads against any agentx\n * version. The check is a major.minor match for v1; full semver-range\n * parsing can come later if the ecosystem demands it. */\n agentxRange?: string\n}\n\n/** Zod schema for the manifest. Loader uses safeParse so a malformed\n * manifest produces a clear error instead of a TypeError. */\nexport const agentXPluginManifestSchema = z.object({\n name: z.string().min(1, \"manifest.name is required\"),\n version: z.string().min(1, \"manifest.version is required\"),\n agentxRange: z.string().optional(),\n})\n\n/** Narrow surface the loader hands to plugin.setup(). Plugins should\n * capture only what they need; the context is closed-over and not\n * re-emitted, so a plugin's reach is bounded by what's listed here. */\nexport interface AgentXPluginContext {\n /** Prefixed log helper — output is namespaced with the plugin name. */\n log: (...args: unknown[]) => void\n /** Read-only subscribe. Returned disposer is also tracked by the loader\n * so plugin teardown removes every subscription automatically. */\n on<E extends keyof AgentXEvents>(\n evt: E,\n fn: (payload: AgentXEvents[E]) => void,\n ): () => void\n /** Register a channel adapter. The router picks it up at startChannels()\n * alongside built-in adapters. The loader rejects names that collide\n * with an existing channel and logs a warning. */\n addChannel(adapter: ChannelAdapter): void\n /** Read-only snapshot of the daemon's agent registry at load time.\n * Plugins can iterate to discover capabilities; mutations are not\n * supported in v1. */\n agents: ReadonlyMap<string, AgentDef>\n /** Read-only daemon config. Useful for plugins that need to gate on\n * an env-driven setting (e.g. `config.mesh.enabled`). */\n config: Readonly<DaemonConfig>\n}\n\n/** The plugin shape itself — what every npm package must default-export.\n * setup() may be sync or async; the loader awaits it with a 15s timeout\n * so a misbehaving setup can never block daemon boot indefinitely. */\nexport interface AgentXPlugin {\n manifest: AgentXPluginManifest\n setup(ctx: AgentXPluginContext): void | Promise<void>\n teardown?(): void | Promise<void>\n}\n\n/** Tracked record of a plugin that successfully loaded. The loader\n * returns these so the daemon can run teardown in stop() and surface\n * them to `agentx plugin list`. */\nexport interface LoadedPlugin {\n manifest: AgentXPluginManifest\n /** The npm package name as it appeared in agentx.json's `plugins: []`. */\n packageName: string\n /** Channels this plugin contributed via ctx.addChannel(). Captured so\n * the daemon can include them in startChannels() and surface them in\n * the dashboard's channel list. */\n channels: ChannelAdapter[]\n /** Aggregate disposer that calls plugin.teardown() AND removes all bus\n * subscriptions the plugin attached via ctx.on(). */\n dispose: () => Promise<void>\n}\n","import { getEventBus } from \"@/events/bus\"\nimport type { AgentXEvents } from \"@/events/bus\"\nimport type { ChannelAdapter } from \"@/channels/types\"\nimport type { DaemonConfig, AgentDef } from \"@/daemon/config\"\nimport type { AgentXPluginContext } from \"./types\"\n\n// --- Plugin context builder ---\n//\n// One context per plugin. The builder owns the per-plugin disposal list:\n// every subscription registered via ctx.on() is captured here, so the\n// loader can tear the plugin down without the plugin's own teardown()\n// having to track its subscribers. addChannel() captures registered\n// adapters in the same record — the daemon reads `channels` to fold\n// them into startChannels().\n\nexport interface PluginContextHandle {\n ctx: AgentXPluginContext\n /** Channels the plugin contributed via ctx.addChannel(). */\n channels: ChannelAdapter[]\n /** Run all auto-disposers (bus subscribers attached via ctx.on()). */\n disposeSubscriptions: () => void\n}\n\nexport interface BuildContextArgs {\n packageName: string\n agents: ReadonlyMap<string, AgentDef>\n config: DaemonConfig\n log: (...args: unknown[]) => void\n /** Optional collision check — when an addChannel() name already exists,\n * we skip and warn rather than overwrite. The daemon supplies the\n * predicate so the loader doesn't depend on MessageRouter's internals. */\n isChannelNameTaken?: (name: string) => boolean\n}\n\nexport function buildPluginContext(args: BuildContextArgs): PluginContextHandle {\n const channels: ChannelAdapter[] = []\n const disposers: Array<() => void> = []\n const bus = getEventBus()\n const prefix = `[plugin:${args.packageName}]`\n\n const ctx: AgentXPluginContext = {\n log: (...rest) => args.log(prefix, ...rest),\n on(evt, fn) {\n // Wrap so listener errors don't crash the bus's other subscribers\n // (per the documented \"handlers must be fast & defensive\" guidance\n // in docs/architecture/plugins.md). We still rethrow nothing —\n // EventEmitter would otherwise propagate, breaking sibling\n // subscribers. The plugin's own bug is logged with its prefix.\n const wrapped = (payload: AgentXEvents[typeof evt]) => {\n try {\n fn(payload)\n } catch (e: any) {\n args.log(prefix, `subscriber for \"${String(evt)}\" threw:`, e?.stack ?? e)\n }\n }\n bus.on(evt, wrapped)\n const dispose = () => bus.off(evt, wrapped)\n disposers.push(dispose)\n return dispose\n },\n addChannel(adapter) {\n if (!adapter || typeof adapter.name !== \"string\" || !adapter.name) {\n args.log(prefix, `addChannel: adapter is missing a string name; skipped`)\n return\n }\n if (args.isChannelNameTaken?.(adapter.name)) {\n args.log(prefix, `addChannel: name \"${adapter.name}\" already in use; skipped`)\n return\n }\n // Also defend against intra-plugin duplicates.\n if (channels.some((c) => c.name === adapter.name)) {\n args.log(prefix, `addChannel: name \"${adapter.name}\" already added by this plugin; skipped`)\n return\n }\n channels.push(adapter)\n },\n agents: args.agents,\n config: args.config,\n }\n\n return {\n ctx,\n channels,\n disposeSubscriptions: () => {\n for (const d of disposers) {\n try { d() } catch { /* idempotent */ }\n }\n disposers.length = 0\n },\n }\n}\n","import {\n agentXPluginManifestSchema,\n type AgentXPlugin,\n type LoadedPlugin,\n} from \"./types\"\nimport { buildPluginContext } from \"./context\"\nimport type { DaemonConfig, AgentDef } from \"@/daemon/config\"\n\n// --- Plugin loader ---\n//\n// Move B.2. Iterates `config.plugins[]` (npm package names), dynamic-\n// imports each, validates the manifest with Zod, builds a per-plugin\n// context, and calls plugin.setup(ctx) within a 15 s race so a hung\n// setup can never block daemon boot.\n//\n// Discovery is explicit: only listed packages load. No node_modules\n// scan, no `file:` paths in v1 — operators must `pnpm install` the\n// package and add its name to agentx.json. The plan trades a small\n// install step for a meaningful security boundary (auto-loading\n// strangers from node_modules is a footgun).\n//\n// Failure modes are isolated: an import error, manifest mismatch, or\n// setup() throw is logged with the package name and the loader moves\n// on. The daemon never aborts because of a plugin.\n\nexport interface LoadPluginsArgs {\n config: DaemonConfig\n agents: ReadonlyMap<string, AgentDef>\n log: (...args: unknown[]) => void\n /** Used by ctx.addChannel() to refuse name collisions with built-ins. */\n isChannelNameTaken?: (name: string) => boolean\n /** The daemon's own version (read from package.json). When the plugin\n * declares an `agentxRange` the loader gates major.minor parity. */\n daemonVersion?: string\n /** Test seam — supply a custom dynamic import so fixtures can use\n * relative paths or pre-injected modules without touching node_modules. */\n importer?: (packageName: string) => Promise<unknown>\n /** Test seam — override the per-setup timeout (default 15 000 ms). */\n setupTimeoutMs?: number\n}\n\nconst DEFAULT_SETUP_TIMEOUT_MS = 15_000\n\nexport async function loadPlugins(args: LoadPluginsArgs): Promise<LoadedPlugin[]> {\n const names = args.config.plugins ?? []\n if (names.length === 0) return []\n\n const importer = args.importer ?? ((name: string) => import(name))\n const timeoutMs = args.setupTimeoutMs ?? DEFAULT_SETUP_TIMEOUT_MS\n const out: LoadedPlugin[] = []\n\n for (const packageName of names) {\n let mod: unknown\n try {\n mod = await importer(packageName)\n } catch (e: any) {\n args.log(`[plugins] import failed for \"${packageName}\": ${e?.message ?? e}`)\n continue\n }\n\n // Normalise ESM/CJS interop. CJS packages compiled with esbuild often\n // expose the real default at `default.default`; pure ESM exposes it at\n // `default`; some hand-rolled CJS attaches the manifest directly to\n // module.exports.\n const candidate = pickDefault(mod)\n if (!isPlugin(candidate)) {\n args.log(`[plugins] \"${packageName}\" did not export a default AgentXPlugin (no manifest/setup pair found)`)\n continue\n }\n const plugin = candidate\n\n const parsed = agentXPluginManifestSchema.safeParse(plugin.manifest)\n if (!parsed.success) {\n const issues = parsed.error.issues.map((i) => i.message).join(\"; \")\n args.log(`[plugins] \"${packageName}\" manifest invalid: ${issues}`)\n continue\n }\n\n // Optional version gate. Major.minor parity is the v1 contract; we\n // intentionally skip patch/prerelease parity to keep the gate stable\n // through alpha cycles.\n if (parsed.data.agentxRange && args.daemonVersion) {\n if (!majorMinorMatches(parsed.data.agentxRange, args.daemonVersion)) {\n args.log(`[plugins] \"${packageName}\" requires agentx ${parsed.data.agentxRange}, daemon is ${args.daemonVersion}; skipped`)\n continue\n }\n }\n\n const handle = buildPluginContext({\n packageName,\n agents: args.agents,\n config: args.config,\n log: args.log,\n isChannelNameTaken: args.isChannelNameTaken,\n })\n\n let setupOk = false\n try {\n await raceWithTimeout(\n Promise.resolve(plugin.setup(handle.ctx)),\n timeoutMs,\n `setup() exceeded ${timeoutMs}ms`,\n )\n setupOk = true\n } catch (e: any) {\n args.log(`[plugins] \"${packageName}\" setup() failed: ${e?.message ?? e}`)\n // Tear down anything the plugin managed to register before throwing.\n handle.disposeSubscriptions()\n continue\n }\n\n if (!setupOk) continue\n\n args.log(`[plugins] loaded ${parsed.data.name} v${parsed.data.version}`)\n\n out.push({\n manifest: parsed.data,\n packageName,\n channels: handle.channels,\n dispose: async () => {\n try {\n if (typeof plugin.teardown === \"function\") await plugin.teardown()\n } catch (e: any) {\n args.log(`[plugins] \"${packageName}\" teardown() threw: ${e?.message ?? e}`)\n } finally {\n handle.disposeSubscriptions()\n }\n },\n })\n }\n\n return out\n}\n\nfunction pickDefault(mod: unknown): unknown {\n if (mod == null) return mod\n if (typeof mod !== \"object\") return mod\n const m = mod as Record<string, unknown>\n // Pure ESM: `import x from 'pkg'` → x is the default export.\n // Dynamic import always returns the namespace; the default is at `.default`.\n // CJS through esbuild sometimes produces `default.default`.\n if (m.default && typeof m.default === \"object\") {\n const d = m.default as Record<string, unknown>\n if (d.manifest && typeof d.setup === \"function\") return d\n if (d.default && typeof d.default === \"object\") {\n const dd = d.default as Record<string, unknown>\n if (dd.manifest && typeof dd.setup === \"function\") return dd\n }\n }\n if (m.manifest && typeof m.setup === \"function\") return m\n return m.default ?? m\n}\n\nfunction isPlugin(c: unknown): c is AgentXPlugin {\n return (\n !!c &&\n typeof c === \"object\" &&\n \"manifest\" in (c as object) &&\n \"setup\" in (c as object) &&\n typeof (c as { setup: unknown }).setup === \"function\"\n )\n}\n\nfunction majorMinorMatches(range: string, daemonVersion: string): boolean {\n // Strip \"^\" / \"~\" / \"=\" / \"v\" prefixes; we only care about major.minor.\n const a = range.replace(/^[\\^~=v]+/, \"\").split(\".\")\n const b = daemonVersion.replace(/^v/, \"\").split(\".\")\n return a[0] === b[0] && (a[1] ?? \"\") === (b[1] ?? \"\")\n}\n\nfunction raceWithTimeout<T>(p: Promise<T>, ms: number, msg: string): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => reject(new Error(msg)), ms)\n p.then(\n (v) => { clearTimeout(timer); resolve(v) },\n (e) => { clearTimeout(timer); reject(e) },\n )\n })\n}\n","import type { IntentLedger } from \"../ledger\"\nimport { decideAndCommit, type DispatchPolicy } from \"../decide\"\nimport { reportDivergence, type LegacyOutcome } from \"../divergence\"\nimport type { IntentDecision, IntentEventInput } from \"../types\"\n\n// Source adapter — A2A mesh inbound /task receiver. Phase 1 commit 6.d.\n//\n// Mesh receives tasks via POST /task in the daemon's HTTP API. The\n// mesh protocol does not (yet) carry a stable request id, so the\n// ledger uses `sourceEventId: null` — each call records as its own\n// event row, with no per-event idempotency. Mesh peers do not\n// typically retry, so the absence of dedup is acceptable for the\n// shadow soak. If retries become a concern, the protocol can grow a\n// request-id field and the adapter can pick it up here.\n\nexport interface MeshTaskProjection {\n /** Agent the task is bound for (resolved at the receiver). */\n agentId: string\n /** Sender's identity claim (log-warn during rollout, will become\n * required in a future protocol revision). */\n senderAgentId?: string\n /** Forwarded origin context — channel, chatId, project, etc. */\n context?: {\n chatId?: string\n channel?: string\n project?: string\n }\n}\n\n/** Build the IntentEventInput for one mesh /task arrival. Subject prefers\n * the forwarded chatId (so a mesh-routed channel message keeps the same\n * subject as the originating channel). Falls back to a per-agent slot\n * when the call is a bare A2A invocation with no channel context. */\nexport function buildMeshEventInput(\n proj: MeshTaskProjection,\n rawJson: string,\n now: () => number = Date.now,\n): IntentEventInput {\n return {\n ts: now(),\n source: \"mesh\",\n sourceEventId: null, // mesh protocol has no stable request id\n project: proj.context?.project ?? null,\n subject: proj.context?.chatId\n ? `chat:${proj.context.chatId}`\n : `mesh:agent:${proj.agentId}`,\n intent: proj.context?.channel ? `mesh.${proj.context.channel}` : \"mesh.task\",\n rawJson,\n }\n}\n\n/** Pass-through policy. Mesh /task always dispatches (validation rejects\n * before reaching this layer), so the legacy outcome is generally\n * \"dispatched/agentId\" and divergences come from ledger active-task\n * safety surfacing concurrent inbound traffic on the same chat. */\nexport function buildMeshPolicyFromLegacy(legacy: LegacyOutcome): DispatchPolicy {\n return {\n decidedBy: \"mesh-receiver\",\n decide: () => ({\n agentId: legacy.agentId,\n outcome: legacy.outcome === \"deduped\" ? \"halted\" : legacy.outcome,\n reason:\n legacy.outcome === \"deduped\"\n ? `legacy-dedup: ${legacy.reason ?? \"\"}`.trim()\n : legacy.reason ?? null,\n }),\n }\n}\n\nexport function recordMeshDispatch(\n ledger: IntentLedger,\n proj: MeshTaskProjection,\n rawJson: string,\n legacyOutcome: LegacyOutcome,\n now: () => number = Date.now,\n): IntentDecision {\n const eventInput = buildMeshEventInput(proj, rawJson, now)\n const policy = buildMeshPolicyFromLegacy(legacyOutcome)\n const ledgerDecision = decideAndCommit(ledger, eventInput, policy, now)\n reportDivergence(ledger, \"mesh\", ledgerDecision, legacyOutcome, now)\n return ledgerDecision\n}\n","import type { AgentDef } from \"@/daemon/config\"\nimport type { IntentLedger } from \"@/intent/ledger\"\n\n// Phase 5 — typed capabilities. Drop-condition simplification.\n//\n// The kickoff (research-rescue-plan.md §2 phase 5) frames Phase 5 as\n// a typed capability lattice with subtyping + composition algebra\n// (\"agent_A ⊕ agent_B = agent_C\"). The same section flags a\n// drop-condition: \"if 2 weeks in it's not producing real type-check\n// rejections, drop it. Replace with a simpler 'registered intents\n// per agent' check.\"\n//\n// This file ships the simpler form upfront. The reasoning: the\n// concrete operational signal Phase 5 was meant to produce\n// (rejecting historical \"I cannot handle this\" agent errors) is\n// achievable with a string-set membership check; the lattice +\n// subtyping machinery would only matter if real rejections proved\n// the simple form insufficient. We don't have that data — so we\n// don't build the academic structure.\n//\n// If/when the simple form proves inadequate, this file can grow:\n// - intent globs (\"issue.*\" matches \"issue.opened\", \"issue.closed\")\n// - capability subtyping (a \"review\" capability subsumes\n// \"review.code\" and \"review.docs\")\n// - composition (combining two agents' capabilities into a\n// derived agent type)\n// For now: exact-string membership.\n\n/**\n * Returns true when the agent is permitted to handle the given\n * intent. Permissive defaults:\n * - When `agent.intents` is empty (the default config), the agent\n * handles ALL intents — preserves legacy behaviour for projects\n * that haven't yet declared per-agent intent lists.\n * - When `intent` is null/undefined (router-style events with no\n * classified intent), the agent handles it — the intent classifier\n * is best-effort and we don't reject on its absence.\n *\n * Hard rejection only when the agent has a non-empty intents list\n * AND the dispatched intent isn't in it. This is the production\n * failure mode the rescue plan's drop-condition described — every\n * \"I cannot handle this\" runtime error from the agent should\n * correspond to a config-time-rejectable mismatch here.\n */\nexport function agentCanHandleIntent(\n agent: AgentDef | undefined,\n intent: string | null | undefined,\n): boolean {\n if (!agent) return false\n if (agent.intents.length === 0) return true\n if (!intent) return true\n return agent.intents.includes(intent)\n}\n\n// ---------------------------------------------------------------------------\n// Phase 8 — capability-bounded security. Delegation depth check.\n// ---------------------------------------------------------------------------\n//\n// The ledger itself is the chain-of-command record. When dispatching\n// to an agent on a (project, subject), we walk back through prior\n// decisions on the same subject and count distinct agents. If the\n// count meets/exceeds the target's `maxDelegationDepth`, the dispatch\n// is refused — the chain is too deep.\n//\n// This is the simplest credible form of Phase 8. The kickoff also\n// names \"allowed downstream skills\" + \"Ed25519-signed ledger entries\"\n// as Phase 8 goals; both are deliberately deferred:\n//\n// - Allowed downstream skills overlaps with Phase 5's intents check\n// (already shipped). A future commit can extend AgentDef with\n// `canDelegateTo: string[]` if cascade-control needs to scope\n// beyond depth.\n//\n// - Ed25519-signed ledger entries serves regulatory/audit use cases\n// more than dispatch correctness. The signed-export tool can come\n// when there's a regulator asking for it.\n\n/**\n * Count the distinct agentIds in dispatched decisions on the given\n * (project, subject) within a recency window. Used to gate\n * delegation depth. Returns 0 when (project, subject) is null —\n * the chain concept doesn't apply without a slot identifier.\n */\nexport function delegationChainDepth(\n ledger: IntentLedger,\n project: string | null,\n subject: string | null,\n windowMs: number = 6 * 60 * 60 * 1000, // 6h — chains beyond that are likely independent\n): number {\n if (!project || !subject) return 0\n const since = Date.now() - windowMs\n const rows = ledger.db\n .prepare(`\n SELECT DISTINCT d.agent_id\n FROM intent_decisions d\n JOIN intent_events e ON e.id = d.event_id\n WHERE e.project = ? AND e.subject = ?\n AND d.outcome = 'dispatched'\n AND d.agent_id IS NOT NULL\n AND d.decided_at >= ?\n `)\n .all(project, subject, since) as Array<{ agent_id: string }>\n return rows.length\n}\n\n/**\n * Returns true when dispatching `targetAgent` on `(project, subject)`\n * stays within the agent's delegation budget. The check is per-agent —\n * the TARGET decides the depth limit, not the originator. (Reasoning:\n * the agent being dispatched is the one carrying the budget.)\n *\n * `maxDelegationDepth=0` disables the agent (any incoming chain depth\n * counts as \"too deep\"). `Infinity` / very-large values effectively\n * disable the check.\n */\nexport function withinDelegationBudget(\n ledger: IntentLedger,\n agent: AgentDef | undefined,\n agentId: string,\n project: string | null,\n subject: string | null,\n): boolean {\n if (!agent) return false\n const max = agent.maxDelegationDepth\n if (max <= 0) return false // explicitly disabled\n // Count distinct upstream agents on this slot. We treat the chain\n // as \"everyone who's already been on it\" — if dispatching to\n // agentId would exceed max, refuse.\n const upstream = delegationChainDepth(ledger, project, subject)\n // Subtle: if `agentId` is already in the chain (we're re-dispatching\n // to the same agent, e.g., a follow-up after resolution), don't\n // count them again — they shouldn't tip the budget over the edge\n // when the chain hasn't actually grown.\n const alreadyOnChain = ledger.db\n .prepare(`\n SELECT 1\n FROM intent_decisions d\n JOIN intent_events e ON e.id = d.event_id\n WHERE e.project = ? AND e.subject = ?\n AND d.agent_id = ?\n AND d.outcome = 'dispatched'\n LIMIT 1\n `)\n .get(project, subject, agentId)\n const projectedDepth = alreadyOnChain ? upstream : upstream + 1\n return projectedDepth <= max\n}\n","import type { DaemonConfig, AgentDef } from \"@/daemon/config\"\n\n// --- Landscape Builder ---\n//\n// Generates a cached \"world model\" string for each agent.\n// Built once at daemon startup from config + mesh discovery.\n// Gives every agent awareness of the full system: team, channels, rules.\n\nexport interface MeshPeerInfo {\n peer: string\n healthy: boolean\n skills: Array<{ id: string; name: string; description: string }>\n}\n\n/**\n * Build and cache per-agent landscape strings.\n */\nexport class LandscapeBuilder {\n private cache: Map<string, string> = new Map()\n private config: DaemonConfig\n private meshPeers: MeshPeerInfo[] = []\n\n constructor(config: DaemonConfig) {\n this.config = config\n }\n\n /** Fingerprint of the peer set that produced the current cache. A rebuild\n * is skipped when `refresh()` gets a topologically-identical set — this\n * is the common case on every mesh heartbeat and rebuilding would\n * needlessly invalidate every agent's cached prompt prefix. */\n private fingerprint = \"\"\n\n /**\n * Build landscape strings for all agents. Call at startup.\n */\n build(meshPeers?: MeshPeerInfo[]): void {\n if (meshPeers) this.meshPeers = meshPeers\n this.cache.clear()\n this.fingerprint = this.computeFingerprint()\n\n for (const agentId of Object.keys(this.config.agents)) {\n this.cache.set(agentId, this.renderForAgent(agentId))\n }\n }\n\n /** Set-only signature of peers + their skill IDs. Health is excluded on\n * purpose — we don't want a flapping heartbeat to rebuild the cache. */\n private computeFingerprint(): string {\n const parts = [...this.meshPeers]\n .sort((a, b) => a.peer.localeCompare(b.peer))\n .map(p => `${p.peer}:${p.skills.map(s => s.id).sort().join(\",\")}`)\n return parts.join(\"|\")\n }\n\n /**\n * Get the cached landscape string for a specific agent.\n */\n getForAgent(agentId: string): string | undefined {\n return this.cache.get(agentId)\n }\n\n /**\n * Rebuild after mesh topology changes. Skipped when the peer set is\n * unchanged so a flapping health heartbeat doesn't invalidate every\n * agent's cached prompt prefix.\n */\n refresh(meshPeers: MeshPeerInfo[]): void {\n this.meshPeers = meshPeers\n const next = this.computeFingerprint()\n if (next === this.fingerprint && this.cache.size > 0) return\n this.build()\n }\n\n private renderForAgent(selfId: string): string {\n const lines: string[] = [\"[Landscape]\"]\n const selfDef = this.config.agents[selfId]\n\n // Node identity\n lines.push(`Node: ${this.config.node.name} (${this.config.node.id})`)\n\n // Self\n const selfHandle = this.getPrimaryHandle(selfId)\n const selfCap = this.extractCapability(selfDef)\n const handleStr = selfHandle ? ` (${selfHandle})` : \"\"\n lines.push(`You: ${selfDef.name}${handleStr} — ${selfCap}`)\n lines.push(\"\")\n\n // Local team\n const localAgents = Object.entries(this.config.agents).filter(([id]) => id !== selfId)\n lines.push(`Available agents on this node (${localAgents.length} — not all may be in the current group):`)\n for (const [id, def] of localAgents) {\n const handle = this.getPrimaryHandle(id)\n const cap = this.extractCapability(def)\n const h = handle ? ` (${handle})` : \"\"\n lines.push(`• ${def.name}${h} — ${cap}`)\n }\n\n // Remote mesh agents. Deliberately NOT filtered by `healthy` — a transient\n // heartbeat flap would otherwise rebuild the landscape and bust every\n // agent's prompt cache. Listing a peer that's momentarily unreachable\n // costs nothing (the A2A call will fail cleanly and the agent will\n // surface that to the user); filtering them out costs us a cache-create\n // on every flap. Health shows up in /health and /live, not here.\n const remotePeers = this.meshPeers.filter(p => p.skills.length > 0)\n if (remotePeers.length) {\n lines.push(\"\")\n lines.push(\"Remote agents (on mesh peers — address by id via /mesh/task or A2A):\")\n // Stable ordering so the peer list doesn't shuffle between builds and\n // invalidate the cache even when the set is unchanged.\n const sortedPeers = [...remotePeers].sort((a, b) => a.peer.localeCompare(b.peer))\n for (const peer of sortedPeers) {\n const sortedSkills = [...peer.skills].sort((a, b) => a.id.localeCompare(b.id))\n for (const skill of sortedSkills) {\n // Surface the agent id (skill.id) — that's what /mesh/task and\n // A2A endpoints take as the routing key. Render the friendly\n // name in parens, then the description. Without the id, the\n // calling agent has to guess the routing key from the name.\n const desc = (skill.description || \"\").slice(0, 80)\n const nameSuffix = skill.name && skill.name !== skill.id ? ` (${skill.name})` : \"\"\n lines.push(`• ${skill.id}${nameSuffix} [${peer.peer}] — ${desc}`)\n }\n }\n }\n\n // Channels\n lines.push(\"\")\n lines.push(this.renderChannels())\n\n // Rules\n lines.push(\"\")\n lines.push(\"[Rules]\")\n const handles = [selfHandle, selfId].filter(Boolean).join(\" / \")\n lines.push(`- Only respond when YOU (${handles}) are mentioned or this is a DM to you`)\n lines.push(\"- If another agent was mentioned and you were NOT, stay silent — they will handle it\")\n lines.push(\"- The agent list above shows all agents on the node — do NOT assume they are all in the current chat group\")\n lines.push(\"- When asked about group members, only mention agents you have seen in the conversation history\")\n lines.push(\"- To delegate on Telegram: mention the agent's handle in your response\")\n lines.push(\"- On GitLab: reply directly, no Telegram handles. To send to other channels, use the /send API below\")\n lines.push(\"- On WhatsApp: mention another agent's handle to delegate (shared number — name prefixed automatically). To send to other channels, use /send API\")\n\n // Cross-channel outbound messaging\n const [, portStr] = this.config.node.bind.split(\":\")\n const port = portStr || \"19900\"\n lines.push(\"\")\n lines.push(\"[Cross-Channel Messaging]\")\n lines.push(`You can send messages to ANY channel proactively (not just reply):`)\n lines.push(` curl -X POST http://localhost:${port}/send -H \"Content-Type: application/json\" -d '{\"channel\":\"<channel>\",\"chatId\":\"<id>\",\"text\":\"<message>\"}'`)\n lines.push(\"Channels: telegram, whatsapp, gitlab, discord\")\n lines.push(\"ChatId formats:\")\n lines.push(' telegram: numeric (e.g. \"-1001234567890\" for group, \"123456\" for DM)')\n lines.push(' gitlab: \"group/project:issue:123\" or \"group/project:merge_request:45\"')\n lines.push(' whatsapp: JID (e.g. \"+21612345678@s.whatsapp.net\")')\n lines.push(\"Use this when asked to notify someone on a different channel, post to an issue, or broadcast updates.\")\n\n // Conversation recall — short vs long memory model. Short memory\n // (today, current chat) is the default and what 90% of recall calls\n // need. Long memory (older windows, broader scope) is opt-in via\n // explicit cues from the user message.\n lines.push(\"\")\n lines.push(\"[Conversation Recall — short/long memory]\")\n lines.push(\"If a message references prior context you don't have (\\\"that\\\", \\\"it\\\", \\\"yes please\\\", \\\"continue\\\", \\\"correct me if I'm wrong\\\", \\\"as discussed\\\"), recall the actual prior turns BEFORE replying — do not guess, do not invent context from other tools, and do not ask the user to repeat themselves.\")\n lines.push(\"\")\n lines.push(\"ALWAYS pass `channel` and `chatId` from the [Current Conversation] block above so you scope to the right thread:\")\n lines.push(` curl -s -X POST http://localhost:${port}/recall -H \"Content-Type: application/json\" -d '{\"agent\":\"<your-agent-id>\",\"channel\":\"<from prompt>\",\"chatId\":\"<from prompt>\",\"limit\":10}'`)\n lines.push(\"\")\n lines.push(\"Short memory (default — today only): omit `lookbackDays`. Returns last ~10 turns of THIS chat from today UTC.\")\n lines.push(\"Long memory (opt-in): set `lookbackDays` only when the user message has a clear long-memory cue:\")\n lines.push(\" - \\\"yesterday\\\" / \\\"أمس\\\" → lookbackDays: 2\")\n lines.push(\" - \\\"few days ago\\\" / \\\"couple of days\\\" → lookbackDays: 4\")\n lines.push(\" - \\\"last week\\\" / \\\"week ago\\\" → lookbackDays: 8\")\n lines.push(\" - \\\"remember when …\\\" / \\\"we discussed …\\\" → lookbackDays: 3\")\n lines.push(\"If unsure, start short and paginate backward using `oldestTs` from the previous response as the next call's `before`.\")\n lines.push(\"\")\n lines.push(\"Other optional fields: `query` (substring filter, e.g. an issue number or name), `participants` (filter by sender username), `after`/`before` (ISO ts overrides). Response shape: { turns: [{ ts, role, senderName, content, channel, chatId }], oldestTs, hasMore, totalScanned }.\")\n lines.push(\"Scope rule: omit chatId to span all of YOUR chats on the channel; omit channel to span all channels. Store is per-agent — you cannot read another agent's chats.\")\n\n // Background monitoring capability\n lines.push(\"\")\n lines.push(\"[Background Monitoring]\")\n lines.push(\"You can watch things in the background while continuing to work:\")\n lines.push(\"- Tail log files and react to errors as they appear\")\n lines.push(\"- Poll CI/CD pipelines and report when status changes\")\n lines.push(\"- Watch directories for file changes\")\n lines.push(\"Use the Monitor tool for this — it runs a script in the background and streams output to you.\")\n\n // Agent Teams capability\n lines.push(\"\")\n lines.push(\"[Agent Teams]\")\n lines.push(\"For complex tasks that benefit from parallel work, you can spawn an agent team:\")\n lines.push(\"- Tell Claude Code to 'create an agent team' with specialized teammates\")\n lines.push(\"- Each teammate works independently with its own context window\")\n lines.push(\"- Teammates coordinate via shared task list and direct messaging\")\n lines.push(\"- Best for: code review (security + performance + tests), debugging with competing hypotheses, multi-module features\")\n lines.push(\"- Use teams when work can be parallelized. Use single session for sequential/simple tasks.\")\n\n return lines.join(\"\\n\")\n }\n\n private renderChannels(): string {\n const parts: string[] = []\n const ch = this.config.channels\n\n if (ch.telegram.enabled) {\n const pol = ch.telegram.policy\n parts.push(`telegram(groups:${pol.group}, DMs:${pol.dm}, delegation:yes)`)\n }\n if (ch.gitlab?.enabled) {\n parts.push(\"gitlab(webhooks, no delegation)\")\n }\n if (ch.whatsapp.enabled) {\n parts.push(\"whatsapp(DMs only, no delegation)\")\n }\n if (ch.discord?.enabled) {\n parts.push(\"discord(enabled)\")\n }\n\n return `Channels: ${parts.join(\", \")}`\n }\n\n /**\n * Get the primary Telegram handle for an agent (the @username).\n */\n private getPrimaryHandle(agentId: string): string | undefined {\n const def = this.config.agents[agentId]\n if (!def) return undefined\n return def.mentions.find(m => m.startsWith(\"@\"))\n }\n\n /**\n * Extract a short capability description from systemPrompt.\n * Takes the first sentence, max 80 chars.\n */\n private extractCapability(def: AgentDef): string {\n if (!def.systemPrompt) return def.name\n\n // Take first sentence (up to period, max 80 chars)\n const first = def.systemPrompt.split(/[.\\n]/)[0]?.trim() || def.name\n return first.length > 80 ? first.slice(0, 77) + \"...\" : first\n }\n}\n","import { existsSync, readFileSync } from \"fs\"\nimport { resolve } from \"path\"\n\n// --- Contact directory ---\n//\n// Resolves human contacts by name → channel address (Telegram chat id, GitLab\n// username, WhatsApp jid). Backs the agentx_send_contact MCP tool.\n//\n// Source of truth: .agentx/contacts.json. Operator-edited; hot-reloadable via\n// the daemon's /reload endpoint. Sibling of .agentx/sessions and .agentx/wiki.\n//\n// Resolution order: exact id → exact alias → fuzzy substring (longest match,\n// requires explicit confirmation from the caller). On collision with a\n// registered agent (same name resolves to both an agent and a contact), the\n// caller gets `kind: \"ambiguous\"` and refuses to send — the agent must ask\n// the user which target was meant.\n\nexport interface Contact {\n /** Stable id (slug). Used as the primary key. */\n id: string\n /** Display name shown in confirmation dialogs. */\n name: string\n /** Aliases the caller can use to look up this contact (case-insensitive). */\n aliases?: string[]\n /** Channel-specific addresses. Key = channel name (telegram | whatsapp |\n * gitlab | github | discord); value = the channel-native id (chatId for\n * Telegram, jid for WhatsApp, username for GitLab/GitHub, etc.). */\n channels: Record<string, string>\n}\n\nexport type ResolveResult =\n | { kind: \"exact\"; contact: Contact }\n | { kind: \"alias\"; contact: Contact }\n | { kind: \"fuzzy\"; contact: Contact; confidence: number }\n | { kind: \"ambiguous\"; candidates: Contact[] }\n | { kind: \"miss\"; query: string }\n\nconst CONTACTS_RELPATH = \".agentx/contacts.json\"\n\nexport class ContactDirectory {\n private contacts: Contact[] = []\n private filePath: string\n private log: (...args: unknown[]) => void\n\n constructor(\n baseDir: string = process.cwd(),\n log: (...args: unknown[]) => void = () => {},\n ) {\n this.filePath = resolve(baseDir, CONTACTS_RELPATH)\n this.log = log\n this.reload()\n }\n\n /** Re-read .agentx/contacts.json. Safe to call on /reload. Silently keeps\n * the prior list if the file is missing or invalid (so a typo in the\n * contacts file doesn't take routing offline). */\n reload(): { count: number; error?: string } {\n if (!existsSync(this.filePath)) {\n this.contacts = []\n return { count: 0 }\n }\n try {\n const raw = readFileSync(this.filePath, \"utf-8\")\n const parsed = JSON.parse(raw) as { contacts?: Contact[] }\n const list = Array.isArray(parsed.contacts) ? parsed.contacts : []\n // Validate each entry — drop malformed ones rather than throw.\n const valid = list.filter((c) => {\n if (!c || typeof c.id !== \"string\" || !c.id) return false\n if (typeof c.name !== \"string\" || !c.name) return false\n if (!c.channels || typeof c.channels !== \"object\") return false\n return true\n })\n this.contacts = valid\n return { count: valid.length }\n } catch (e: any) {\n this.log(`[contacts] reload failed: ${e.message}`)\n return { count: this.contacts.length, error: e.message }\n }\n }\n\n /** Number of loaded contacts. */\n size(): number {\n return this.contacts.length\n }\n\n /** Snapshot of the loaded contacts (for debugging / health endpoints). */\n list(): Contact[] {\n return this.contacts.slice()\n }\n\n /** Look up by id (exact, case-sensitive). */\n getById(id: string): Contact | undefined {\n return this.contacts.find((c) => c.id === id)\n }\n\n /** Resolve a free-form name. Order: exact id → exact alias → fuzzy\n * substring (longest match wins). Multiple equally-long fuzzy hits return\n * `ambiguous`. Caller is expected to refuse fuzzy matches without explicit\n * user confirmation — the silent wrong-recipient bug is exactly what S2 is\n * meant to prevent. */\n resolve(query: string): ResolveResult {\n const q = (query || \"\").trim()\n if (!q) return { kind: \"miss\", query: q }\n\n const ql = q.toLowerCase()\n\n // 1. Exact id\n for (const c of this.contacts) {\n if (c.id.toLowerCase() === ql) return { kind: \"exact\", contact: c }\n }\n // 2. Exact alias (or display name)\n for (const c of this.contacts) {\n if (c.name.toLowerCase() === ql) return { kind: \"alias\", contact: c }\n for (const a of c.aliases ?? []) {\n if (a.toLowerCase() === ql) return { kind: \"alias\", contact: c }\n }\n }\n // 3. Fuzzy substring — longest-match heuristic mirroring registry.findByMention.\n // Score = matched-substring length (higher is better). Tie → ambiguous.\n let bestScore = 0\n let bestContacts: Contact[] = []\n for (const c of this.contacts) {\n const candidates = [c.id, c.name, ...(c.aliases ?? [])]\n let myBest = 0\n for (const cand of candidates) {\n const cl = cand.toLowerCase()\n if (ql.includes(cl) || cl.includes(ql)) {\n // Use the shorter overlap to reward precision: a 3-letter query\n // shouldn't pretend to match a 30-letter name perfectly.\n const overlap = Math.min(cl.length, ql.length)\n if (overlap > myBest) myBest = overlap\n }\n }\n if (myBest > bestScore) {\n bestScore = myBest\n bestContacts = [c]\n } else if (myBest > 0 && myBest === bestScore) {\n bestContacts.push(c)\n }\n }\n\n if (bestContacts.length === 0) return { kind: \"miss\", query: q }\n if (bestContacts.length > 1) return { kind: \"ambiguous\", candidates: bestContacts }\n return { kind: \"fuzzy\", contact: bestContacts[0], confidence: bestScore / Math.max(ql.length, 1) }\n }\n\n /** Pick a channel address for a contact. If `preferred` is given and\n * exists, use it; otherwise return the first channel the contact has.\n * Returns undefined when the contact has no channels at all. */\n pickChannel(\n contact: Contact,\n preferred?: string,\n ): { channel: string; address: string } | undefined {\n if (preferred && contact.channels[preferred]) {\n return { channel: preferred, address: contact.channels[preferred] }\n }\n const entries = Object.entries(contact.channels)\n if (entries.length === 0) return undefined\n const [channel, address] = entries[0]\n return { channel, address }\n }\n}\n","import { existsSync, readFileSync, unlinkSync, writeFileSync } from \"fs\"\nimport { resolve } from \"path\"\n\n// --- Agent MCP config: per-agent .mcp.json sync ---\n//\n// Centralizes which MCP servers each agent's Claude Code session loads.\n// Without this, operators hand-write `<workspace>/.mcp.json` per agent —\n// painful to keep in sync across 22 workspaces, easy to drift.\n//\n// Ownership model: agentx-managed files carry a top-level\n// `\"_agentxManaged\": true` marker. The Claude Code MCP loader ignores\n// unknown top-level fields, so the marker is invisible to the runtime\n// but lets us safely round-trip without clobbering operator edits.\n//\n// - file missing → write fresh (we own it)\n// - file present + marker is true → overwrite (we own it)\n// - file present + marker missing/false→ skip (operator owns it)\n//\n// Operator can opt out by either deleting the marker line or removing\n// the file entirely after editing — agentx will respect the choice on\n// the next boot.\n\nconst MARKER = \"_agentxManaged\"\n\nexport interface McpServerConfig {\n command: string\n args?: string[]\n env?: Record<string, string>\n}\n\nexport type McpServerMap = Record<string, McpServerConfig>\n\nexport type SyncResult =\n | \"installed\" // wrote a fresh .mcp.json (no prior file)\n | \"updated\" // overwrote an agentx-managed file\n | \"removed\" // deleted an agentx-managed file (config now empty)\n | \"skipped-operator-owned\" // file exists without our marker — left alone\n | \"noop\" // no config + no file — nothing to do\n\nexport function syncMcpToWorkspace(workspacePath: string, mcp: McpServerMap): SyncResult {\n const mcpPath = resolve(workspacePath, \".mcp.json\")\n const fileExists = existsSync(mcpPath)\n const parsed = fileExists ? tryParseJson(mcpPath) : null\n // Three states for an existing file:\n // parsed === null && fileExists → unparseable; treat as operator-\n // owned so we don't clobber a\n // half-saved edit.\n // parsed && marker !== true → operator-owned, skip.\n // parsed && marker === true → ours, safe to rewrite/remove.\n const isManaged = parsed !== null && parsed[MARKER] === true\n if (fileExists && !isManaged) return \"skipped-operator-owned\"\n\n const hasConfig = Object.keys(mcp).length > 0\n if (!hasConfig && !fileExists) return \"noop\"\n if (!hasConfig && isManaged) {\n unlinkSync(mcpPath)\n return \"removed\"\n }\n\n const next = { [MARKER]: true, mcpServers: mcp }\n writeFileSync(mcpPath, JSON.stringify(next, null, 2) + \"\\n\")\n return fileExists ? \"updated\" : \"installed\"\n}\n\nfunction tryParseJson(path: string): any | null {\n try { return JSON.parse(readFileSync(path, \"utf-8\")) } catch { return null }\n}\n","// Source-of-truth for the `remember` skill body. Exported as a string\n// constant so tsup bundles it into dist/ without needing an asset-copy\n// step. The standalone `.md` file in this directory is a human-readable\n// mirror kept in sync by hand; runtime installers read from here.\n\nexport const REMEMBER_SKILL_FILENAME = \"remember.md\"\n\nexport const REMEMBER_SKILL_BODY = `---\nname: remember\ndescription: Save a durable memory for yourself that will be inlined into your system prompt on every future session. Use when the user corrects your approach, confirms a non-obvious decision, reveals something durable about themselves or a project, or names an external resource you should reach back to. Writes go through the daemon's agent-memory HTTP API — no file system access required.\n---\n\n# Remember\n\nYou have a persistent memory layer that is auto-loaded into your system prompt on every task. It is SEPARATE from the shared wiki:\n\n- **Wiki** is cross-agent, authoritative, documented. Facts that multiple agents agree on.\n- **Memory** is yours alone, experiential. Things you learned the hard way, preferences that matter only for your work, pointers that help you navigate a specific user's stack.\n\nFour memory kinds:\n\n| Kind | Save something of type… |\n|---|---|\n| \\`user\\` | role, seniority, tone preference, what they are fluent in vs new to |\n| \\`feedback\\` | corrections (\"don't do X, we got burned last Q\") + confirmations (\"yes the bundled PR was right\") |\n| \\`project\\` | current initiative, known constraints, deadline context not in the code |\n| \\`reference\\` | external systems and when to use them (Linear, Grafana, a specific dashboard URL) |\n\n## When to save\n\n**Good triggers** — save a memory whenever:\n\n- The user corrects an approach (\"no not that\", \"stop doing X\"). Save as \\`feedback\\`.\n- The user confirms a non-obvious approach worked (\"yes exactly, keep doing that\"). Save as \\`feedback\\`.\n- You learn a durable fact about the user or their role you didn't know before. Save as \\`user\\`.\n- You learn a project-level fact that is not in the code and not in the wiki. Save as \\`project\\`.\n- The user points at an external system (\"check Linear INGEST for ticket context\"). Save as \\`reference\\`.\n\n**Skip** — do NOT save:\n\n- Conversation-specific state, current task TODOs, temporary context. Use the current session for those.\n- Things already in the wiki, or that feel cross-agent authoritative. Propose a wiki article instead.\n- Negative judgements about the user. Memories get re-read every session; stay professional.\n\n## How to save\n\nThe daemon exposes \\`POST /api/memory\\`. Call it from a Bash tool invocation.\n\n\\`\\`\\`bash\ncurl -sS -X POST http://localhost:18800/api/memory \\\\\n -H 'Content-Type: application/json' \\\\\n -d '{\n \"agentId\": \"<your-agent-id>\",\n \"type\": \"feedback\",\n \"name\": \"no-mock-db\",\n \"description\": \"Tests must hit a real database — mocks masked a prod migration bug in Q1.\",\n \"body\": \"In Q1 2026 the payments migration passed every mocked integration test but failed on prod because the mocks diverged from the real schema. Rule: integration tests run against a throwaway real database (we spin one up in CI). Reason: mock/prod divergence is a silent failure mode. How to apply: when a user asks for tests, propose a real DB setup; flag any suggestion to mock the DB.\"\n }'\n\\`\\`\\`\n\nYour agent id is the name of the workspace directory you're in (e.g. \\`atlas\\`, \\`mtgl-v2\\`). When in doubt, run \\`basename \"$(pwd)\"\\`.\n\n### Update an existing memory\n\nSame endpoint, same \\`name\\`. The daemon keeps \\`createdAt\\` stable and bumps \\`updatedAt\\`. If you want to *append* rather than replace, pass \\`\"append\": true\\`:\n\n\\`\\`\\`bash\ncurl -sS -X POST http://localhost:18800/api/memory \\\\\n -H 'Content-Type: application/json' \\\\\n -d '{ \"agentId\":\"atlas\",\"type\":\"feedback\",\"name\":\"no-mock-db\",\n \"description\":\"…\", \"body\":\"Also: 2026-04-15 hit the same class of bug on the grant-application webhook. Same rule.\",\"append\":true }'\n\\`\\`\\`\n\n### Read what you already remember\n\nYour CLAUDE.md already includes a sentinel block with every memory you've saved. Skim it first — if what you're about to save is already there, prefer updating the existing entry over creating a near-duplicate.\n\nFor the full JSON:\n\n\\`\\`\\`bash\ncurl -sS 'http://localhost:18800/api/memory?agent=atlas'\n\\`\\`\\`\n\n### Remove\n\n\\`\\`\\`bash\ncurl -sS -X DELETE 'http://localhost:18800/api/memory/no-mock-db?agent=atlas'\n\\`\\`\\`\n\n## Structuring the body\n\nFor **feedback** memories specifically, structure the body so future-you can judge edge cases instead of blindly obeying the rule:\n\n\\`\\`\\`\n<the rule itself, one line>\n\nWhy: <the reason — often a past incident, a strong preference>\nHow to apply: <when/where this guidance kicks in>\n\\`\\`\\`\n\nThat's the same shape Claude Code's own memory system uses. Keeps the memory useful even when context shifts.\n\n## Deduplication\n\nBefore saving, scan the AGENTX-MEMORY section of your CLAUDE.md. If a similar entry exists, prefer updating it over creating a new one — the index gets noisy otherwise.\n`\n","import type { AgentRegistry } from \"./registry\"\nimport type { MemoryStore } from \"./memory-store\"\n\n// --- Heartbeat System ---\n//\n// Periodic check-in that runs WITHIN the active session context.\n// Unlike cron (which creates isolated sessions), heartbeat carries\n// the full conversation context — useful for inbox checks, health\n// monitoring, and proactive agent behavior.\n//\n// Configured per-agent in agentx.json:\n// agents.<id>.heartbeat: {\n// enabled: true,\n// intervalMinutes: 30,\n// prompt: \"Check inbox, pending tasks, and system health.\"\n// }\n\nexport interface HeartbeatConfig {\n enabled: boolean\n /** Interval in minutes between heartbeats (default: 30) */\n intervalMinutes: number\n /** Prompt to execute during heartbeat */\n prompt: string\n /** Channel to execute in (default: \"heartbeat\") */\n channel?: string\n}\n\ninterface HeartbeatState {\n agentId: string\n config: HeartbeatConfig\n timer?: ReturnType<typeof setInterval>\n lastRun?: Date\n runCount: number\n errors: number\n}\n\n/**\n * Manages periodic heartbeat runs for agents.\n * Heartbeats run in the agent's existing session context,\n * preserving conversation history and memory.\n */\nexport class HeartbeatManager {\n private registry: AgentRegistry\n private memoryStore?: MemoryStore\n private states: Map<string, HeartbeatState> = new Map()\n private log: (...args: unknown[]) => void\n\n constructor(\n registry: AgentRegistry,\n log: (...args: unknown[]) => void = console.error.bind(console, \"[heartbeat]\"),\n memoryStore?: MemoryStore,\n ) {\n this.registry = registry\n this.log = log\n this.memoryStore = memoryStore\n }\n\n /**\n * Register a heartbeat for an agent.\n */\n register(agentId: string, config: HeartbeatConfig): void {\n if (!config.enabled) return\n\n // Clear existing timer if re-registering\n this.stop(agentId)\n\n const state: HeartbeatState = {\n agentId,\n config,\n runCount: 0,\n errors: 0,\n }\n\n const intervalMs = (config.intervalMinutes || 30) * 60 * 1000\n\n state.timer = setInterval(() => {\n this.runHeartbeat(state).catch((e) => {\n this.log(`[${agentId}] heartbeat error: ${e.message}`)\n state.errors++\n })\n }, intervalMs)\n\n this.states.set(agentId, state)\n this.log(`Registered heartbeat for \"${agentId}\" every ${config.intervalMinutes}min`)\n }\n\n /**\n * Stop heartbeat for an agent.\n */\n stop(agentId: string): void {\n const state = this.states.get(agentId)\n if (state?.timer) {\n clearInterval(state.timer)\n state.timer = undefined\n }\n this.states.delete(agentId)\n }\n\n /**\n * Stop all heartbeats.\n */\n stopAll(): void {\n for (const [id] of this.states) {\n this.stop(id)\n }\n }\n\n /**\n * Run a single heartbeat for an agent.\n */\n private async runHeartbeat(state: HeartbeatState): Promise<void> {\n const { agentId, config } = state\n\n this.log(`[${agentId}] heartbeat running...`)\n state.lastRun = new Date()\n state.runCount++\n\n const channel = config.channel || \"heartbeat\"\n\n try {\n // Inject memory recall context if memories are due for review\n let prompt = config.prompt\n if (this.memoryStore) {\n const recallContext = this.memoryStore.buildRecallContext(agentId)\n if (recallContext) {\n prompt = `${recallContext}\\n\\n${prompt}`\n }\n }\n\n const response = await this.registry.execute({\n message: prompt,\n agentId,\n context: {\n channel,\n sender: \"system:heartbeat\",\n chatId: `heartbeat:${agentId}`,\n },\n })\n\n if (response.error) {\n this.log(`[${agentId}] heartbeat error: ${response.error}`)\n state.errors++\n } else {\n this.log(\n `[${agentId}] heartbeat completed (${response.duration}ms, ` +\n `${response.content.length} chars)`,\n )\n }\n } catch (error: any) {\n this.log(`[${agentId}] heartbeat failed: ${error.message}`)\n state.errors++\n }\n }\n\n /**\n * Manually trigger a heartbeat for an agent.\n */\n async trigger(agentId: string): Promise<void> {\n const state = this.states.get(agentId)\n if (!state) {\n this.log(`[${agentId}] no heartbeat registered`)\n return\n }\n await this.runHeartbeat(state)\n }\n\n /**\n * List all registered heartbeats and their status.\n */\n list(): Array<{\n agentId: string\n intervalMinutes: number\n lastRun?: Date\n runCount: number\n errors: number\n }> {\n return Array.from(this.states.values()).map((s) => ({\n agentId: s.agentId,\n intervalMinutes: s.config.intervalMinutes,\n lastRun: s.lastRun,\n runCount: s.runCount,\n errors: s.errors,\n }))\n }\n}\n","import type { AgentRegistry } from \"@/agents/registry\"\nimport { debug } from \"@/observability/debug\"\n\n// --- Service Matcher: pattern-based interception for automated services ---\n//\n// Sits before agent routing in the message flow. If an incoming message\n// matches a defined service trigger, the service prompt is executed\n// instead of routing to the default agent.\n//\n// Use case: predictable client requests (\"send me the monthly report\")\n// that don't need the full LLM reasoning — just execute a known prompt.\n\nexport interface ServiceTrigger {\n pattern: string\n channel?: string // limit to specific channel (e.g. \"whatsapp\")\n}\n\nexport interface ServiceNotify {\n channel: string\n chatId: string\n accountId?: string\n}\n\nexport interface ServiceDef {\n id: string\n name: string\n triggers: ServiceTrigger[]\n allowedContacts?: string[]\n agent: string\n prompt: string\n schedule?: string\n timezone?: string\n notify?: ServiceNotify\n}\n\ninterface CompiledService {\n def: ServiceDef\n patterns: Array<{ regex: RegExp; channel?: string }>\n}\n\nexport interface ServiceMatch {\n service: ServiceDef\n trigger: string // which pattern matched\n captured: string // what text matched\n}\n\nexport class ServiceMatcher {\n private services: CompiledService[] = []\n private log: (...args: unknown[]) => void\n\n constructor(\n serviceDefs: Record<string, Omit<ServiceDef, \"id\">>,\n log: (...args: unknown[]) => void = console.error.bind(console, \"[services]\"),\n ) {\n this.log = log\n this.compile(serviceDefs)\n this.log(`${this.services.length} service(s) loaded`)\n }\n\n /** Recompile the service set from a fresh config. Safe to call at runtime —\n * the only state is the compiled regex array, which is fully replaced. A\n * message currently inside match() sees the old list for one iteration,\n * but that's fine: match() is synchronous and holds no cross-call state. */\n reload(serviceDefs: Record<string, Omit<ServiceDef, \"id\">>): { count: number } {\n this.compile(serviceDefs)\n this.log(`Services reloaded — ${this.services.length} active`)\n return { count: this.services.length }\n }\n\n private compile(serviceDefs: Record<string, Omit<ServiceDef, \"id\">>): void {\n const fresh: CompiledService[] = []\n for (const [id, def] of Object.entries(serviceDefs)) {\n const compiled: CompiledService = {\n def: { ...def, id },\n patterns: [],\n }\n\n for (const trigger of def.triggers) {\n try {\n compiled.patterns.push({\n regex: new RegExp(trigger.pattern, \"i\"),\n channel: trigger.channel,\n })\n } catch (e: any) {\n this.log(`Invalid regex in service \"${id}\": ${trigger.pattern} — ${e.message}`)\n }\n }\n\n if (compiled.patterns.length > 0) {\n fresh.push(compiled)\n }\n }\n this.services = fresh\n }\n\n /**\n * Match an incoming message against all service triggers.\n * Returns the first matching service or null.\n */\n match(text: string, senderId: string, channel: string): ServiceMatch | null {\n for (const svc of this.services) {\n // Check contact allowlist (if set)\n if (svc.def.allowedContacts?.length) {\n const senderNorm = senderId.replace(/[^0-9]/g, \"\")\n const allowed = svc.def.allowedContacts.some(c => {\n const contactNorm = c.replace(/[^0-9]/g, \"\")\n return senderNorm.includes(contactNorm) || contactNorm.includes(senderNorm)\n })\n if (!allowed) continue\n }\n\n // Check triggers\n for (const pattern of svc.patterns) {\n // Channel filter\n if (pattern.channel && pattern.channel !== channel) continue\n\n const match = text.match(pattern.regex)\n if (match) {\n debug.cat(\"agent\", `Service match: \"${svc.def.name}\" (trigger: ${pattern.regex.source})`)\n return {\n service: svc.def,\n trigger: pattern.regex.source,\n captured: match[0],\n }\n }\n }\n }\n\n return null\n }\n\n /**\n * Execute a matched service.\n * Sends the service's predefined prompt to the agent, returns the response.\n */\n async execute(\n service: ServiceDef,\n registry: AgentRegistry,\n context: { channel: string; sender: string; chatId: string },\n replyFn: (text: string) => Promise<void>,\n ): Promise<void> {\n this.log(`Executing service \"${service.name}\" -> agent \"${service.agent}\"`)\n\n try {\n const response = await registry.execute({\n message: service.prompt,\n agentId: service.agent,\n context: {\n channel: context.channel,\n sender: context.sender,\n chatId: `service:${service.id}:${context.chatId}`,\n },\n })\n\n if (response.error) {\n this.log(`Service \"${service.name}\" failed: ${response.error}`)\n await replyFn(`Service \"${service.name}\" encountered an error: ${response.error}`)\n return\n }\n\n // Reply on the same channel\n if (response.content) {\n await replyFn(response.content)\n }\n\n this.log(`Service \"${service.name}\" completed in ${response.duration}ms`)\n } catch (e: any) {\n this.log(`Service \"${service.name}\" threw: ${e.message}`)\n await replyFn(`Service error: ${e.message}`)\n }\n }\n\n /**\n * Get all services that have a schedule (for cron registration).\n */\n getScheduledServices(): ServiceDef[] {\n return this.services\n .filter(s => s.def.schedule)\n .map(s => s.def)\n }\n\n /**\n * List all registered services.\n */\n list(): Array<{ id: string; name: string; triggers: number; agent: string; scheduled: boolean }> {\n return this.services.map(s => ({\n id: s.def.id,\n name: s.def.name,\n triggers: s.patterns.length,\n agent: s.def.agent,\n scheduled: !!s.def.schedule,\n }))\n }\n}\n","import type { BusinessConfig, BusinessOrgEntry, BusinessProject, BusinessRole } from \"./config\"\n\n// --- Organization: org-chart queries and validation ---\n//\n// Phase 3 of the architectural rescue (research-rescue-plan.md §2)\n// adds `canHandle`, `pmFor`, and `escalationChain` to support PM\n// gating + escalation as runtime dispatch concepts. The Organization\n// class becomes consumed by `decideAndCommit` in a separate commit\n// (gated by a flag — flipping it mid-soak adds noise to the divergence\n// signal, so the wiring lands deliberately). For now the methods are\n// pure-function scaffolding ready to wire up.\n\nexport interface EmployeeProfile {\n agentId: string\n role: BusinessRole\n roleKey: string\n reportsTo?: string\n schedule: BusinessOrgEntry[\"schedule\"]\n utilizationTarget: number\n}\n\nexport class Organization {\n private employees: Map<string, EmployeeProfile> = new Map()\n private projects: Map<string, BusinessProject> = new Map()\n\n constructor(config: BusinessConfig) {\n for (const [agentId, entry] of Object.entries(config.orgChart)) {\n const role = config.roles[entry.role]\n if (!role) {\n throw new Error(`Org chart references unknown role \"${entry.role}\" for agent \"${agentId}\"`)\n }\n this.employees.set(agentId, {\n agentId,\n role,\n roleKey: entry.role,\n reportsTo: entry.reportsTo,\n schedule: entry.schedule,\n utilizationTarget: entry.utilizationTarget,\n })\n }\n\n // Validate reportsTo refs\n for (const emp of this.employees.values()) {\n if (emp.reportsTo && !this.employees.has(emp.reportsTo)) {\n throw new Error(`Agent \"${emp.agentId}\" reports to unknown agent \"${emp.reportsTo}\"`)\n }\n }\n\n // Cycle detection\n for (const emp of this.employees.values()) {\n const seen = new Set<string>()\n let cur: string | undefined = emp.agentId\n while (cur) {\n if (seen.has(cur)) {\n throw new Error(`Reporting cycle detected involving \"${emp.agentId}\"`)\n }\n seen.add(cur)\n cur = this.employees.get(cur)?.reportsTo\n }\n }\n\n // Per-project config — Phase 3.\n for (const proj of config.projects ?? []) {\n // Validate referenced PM exists in the org chart.\n if (proj.pm && !this.employees.has(proj.pm)) {\n throw new Error(`Project \"${proj.id}\" lists unknown PM \"${proj.pm}\"`)\n }\n this.projects.set(proj.id, proj)\n }\n }\n\n get(agentId: string): EmployeeProfile | undefined {\n return this.employees.get(agentId)\n }\n\n all(): EmployeeProfile[] {\n return [...this.employees.values()]\n }\n\n reportsTo(agentId: string): string | undefined {\n return this.employees.get(agentId)?.reportsTo\n }\n\n directReports(agentId: string): string[] {\n return [...this.employees.values()]\n .filter((e) => e.reportsTo === agentId)\n .map((e) => e.agentId)\n }\n\n /** Walk up the org tree to the root. First element is `agentId` itself. */\n chainOfCommand(agentId: string): string[] {\n const chain: string[] = []\n let cur: string | undefined = agentId\n while (cur && !chain.includes(cur)) {\n chain.push(cur)\n cur = this.employees.get(cur)?.reportsTo\n }\n return chain\n }\n\n /**\n * Phase 3 — PM lookup. Returns the agentId of the PM responsible\n * for `project`, or `undefined` when no PM is configured. The\n * dispatcher's PM gate (in a future commit) calls this before\n * dispatching to make sure project-scoped events flow through the\n * PM first. When undefined, dispatches proceed without a PM gate\n * (the legacy direct-routing behavior).\n */\n pmFor(project: string | null | undefined): string | undefined {\n if (!project) return undefined\n return this.projects.get(project)?.pm\n }\n\n /**\n * Phase 3 — escalation chain. Returns agents to escalate to in\n * upward order, EXCLUDING the agent itself. `chainOfCommand`\n * includes self at index 0; escalationChain skips that.\n *\n * Used when an agent fails / declines / times out — the dispatcher\n * walks up the chain looking for someone to take over.\n */\n escalationChain(agentId: string): string[] {\n return this.chainOfCommand(agentId).slice(1)\n }\n\n /**\n * Phase 3 — capability check (scaffold). Returns true when:\n * - The org chart is empty (`employees.size === 0`) — this is the\n * permissive default for partially-configured `business` blocks\n * that populate `projects[].pm` for the PM gate but haven't\n * filled in `orgChart`. Without this fallback, enabling business\n * with an empty orgChart would block every dispatch.\n * - OR the agent is registered in the org chart.\n *\n * Per-project / per-intent restrictions need typed capabilities\n * (Phase 5); until then this is a permissive-when-unconfigured\n * default that matches the legacy any-registered-agent-can-handle-\n * anything behavior.\n *\n * The `decideAndCommit` PM gate will call this *after* the PM\n * approves — the PM provides the project-scoped check, this\n * provides the agent-existence check.\n */\n canHandle(agentId: string, _project: string | null | undefined, _intent: string | null | undefined): boolean {\n if (this.employees.size === 0) return true\n return this.employees.has(agentId)\n }\n}\n","import type { BusinessConfig, BusinessSchedule } from \"./config\"\nimport type { Organization } from \"./organization\"\n\n// --- Schedule: pure functions over org chart work hours ---\n\nconst DAY_MAP: Record<string, number> = {\n sun: 0, mon: 1, tue: 2, wed: 3, thu: 4, fri: 5, sat: 6,\n}\n\n/** Get {hour, minute, dayOfWeek (0-6, sun=0)} in the business timezone for `when`. */\nfunction tzParts(when: Date, timezone: string): { hour: number; minute: number; dow: number } {\n // Intl.DateTimeFormat with timeZone gives us the wall-clock time in that zone.\n const fmt = new Intl.DateTimeFormat(\"en-US\", {\n timeZone: timezone,\n weekday: \"short\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n hour12: false,\n })\n const parts = fmt.formatToParts(when)\n const weekday = (parts.find((p) => p.type === \"weekday\")?.value || \"sun\").toLowerCase().slice(0, 3)\n const hourStr = parts.find((p) => p.type === \"hour\")?.value || \"00\"\n const minStr = parts.find((p) => p.type === \"minute\")?.value || \"00\"\n // \"24\" can appear in en-US locale for midnight — normalize.\n const hour = parseInt(hourStr, 10) % 24\n return {\n hour,\n minute: parseInt(minStr, 10),\n dow: DAY_MAP[weekday] ?? 0,\n }\n}\n\nfunction parseHHMM(s: string): number {\n const [h, m] = s.split(\":\").map(Number)\n return h * 60 + m\n}\n\nfunction worksOnDay(schedule: BusinessSchedule, dow: number): boolean {\n return schedule.days.some((d) => DAY_MAP[d] === dow)\n}\n\n/** Is `schedule` active at `when`? (Respects business timezone and lunch break.) */\nexport function isScheduleActive(schedule: BusinessSchedule, when: Date, timezone: string): boolean {\n const { hour, minute, dow } = tzParts(when, timezone)\n if (!worksOnDay(schedule, dow)) return false\n const nowMin = hour * 60 + minute\n const startMin = parseHHMM(schedule.start)\n const endMin = parseHHMM(schedule.end)\n if (nowMin < startMin || nowMin >= endMin) return false\n if (schedule.lunch) {\n const lStart = parseHHMM(schedule.lunch.start)\n const lEnd = parseHHMM(schedule.lunch.end)\n if (nowMin >= lStart && nowMin < lEnd) return false\n }\n return true\n}\n\nexport class Schedule {\n constructor(\n private org: Organization,\n private config: BusinessConfig,\n ) {}\n\n isOnClock(agentId: string, when: Date = new Date()): boolean {\n const emp = this.org.get(agentId)\n if (!emp) return false\n return isScheduleActive(emp.schedule, when, this.config.timezone)\n }\n\n /** Is `when` exactly (to the minute) the scheduled day-start for this agent? */\n isDayStart(agentId: string, when: Date = new Date()): boolean {\n const emp = this.org.get(agentId)\n if (!emp) return false\n const { hour, minute, dow } = tzParts(when, this.config.timezone)\n if (!worksOnDay(emp.schedule, dow)) return false\n const startMin = parseHHMM(emp.schedule.start)\n return hour * 60 + minute === startMin\n }\n\n /** Is `when` exactly the scheduled day-end for this agent? */\n isDayEnd(agentId: string, when: Date = new Date()): boolean {\n const emp = this.org.get(agentId)\n if (!emp) return false\n const { hour, minute, dow } = tzParts(when, this.config.timezone)\n if (!worksOnDay(emp.schedule, dow)) return false\n const endMin = parseHHMM(emp.schedule.end)\n return hour * 60 + minute === endMin\n }\n\n clockedInAgents(when: Date = new Date()): string[] {\n return this.org.all()\n .filter((e) => this.isOnClock(e.agentId, when))\n .map((e) => e.agentId)\n }\n}\n","import type { MessageRouter } from \"@/channels/router\"\nimport type { BusinessConfig } from \"./config\"\nimport type { Organization } from \"./organization\"\n\n// --- Reporter: posts to main channel + escalates blockers up the org chart ---\n\nexport class Reporter {\n constructor(\n private router: MessageRouter,\n private org: Organization,\n private config: BusinessConfig,\n private log: (...args: unknown[]) => void,\n ) {}\n\n async postToMain(text: string, agentId?: string): Promise<void> {\n const dest = this.config.mainChannel\n try {\n await this.router.sendOutbound({\n channel: dest.channel,\n chatId: dest.chatId,\n text,\n agentId,\n accountId: dest.accountId,\n })\n } catch (e: any) {\n this.log(`[business] postToMain failed: ${e.message}`)\n }\n }\n\n /** Post an escalation message up the chain of command. */\n async escalate(fromAgent: string, subject: string, detail: string): Promise<void> {\n const chain = this.org.chainOfCommand(fromAgent).slice(1) // drop self\n const targets = chain.length ? chain.slice(0, 1) : [] // first direct manager\n const body = `⚠️ **Escalation from ${fromAgent}** — ${subject}\\n\\n${detail}`\n\n if (!targets.length) {\n // No manager → fall back to main channel\n await this.postToMain(body, fromAgent)\n return\n }\n\n // Post to main channel tagging the manager (they watch the main channel).\n await this.postToMain(`${body}\\n\\ncc: ${targets.map((t) => `@${t}`).join(\" \")}`, fromAgent)\n }\n}\n","import { writeFileSync, existsSync, mkdirSync, readFileSync } from \"fs\"\nimport { resolve } from \"path\"\nimport type { Organization } from \"./organization\"\nimport type { Schedule } from \"./schedule\"\n\n// --- KPI: running utilization + daily summary ---\n\nexport interface DailyStats {\n date: string // YYYY-MM-DD\n perAgent: Record<string, {\n tasksCompleted: number\n timeLoggedSeconds: number\n onClockSeconds: number\n blockers: number\n utilization: number // timeLogged / onClockSeconds (capped at 1)\n }>\n}\n\nexport interface TaskCompletion {\n agentId: string\n durationSeconds: number\n success: boolean\n channel?: string\n}\n\nfunction today(): string {\n return new Date().toISOString().slice(0, 10)\n}\n\nexport class KPI {\n private stats: DailyStats\n private dir: string\n private onClockTick: Map<string, number> = new Map()\n\n constructor(\n private org: Organization,\n private schedule: Schedule,\n private log: (...args: unknown[]) => void,\n ) {\n this.dir = resolve(process.cwd(), \".agentx/kpi\")\n if (!existsSync(this.dir)) mkdirSync(this.dir, { recursive: true })\n this.stats = this.loadOrInit()\n }\n\n private loadOrInit(): DailyStats {\n const f = resolve(this.dir, `${today()}.json`)\n if (existsSync(f)) {\n try { return JSON.parse(readFileSync(f, \"utf-8\")) } catch { /* fall through */ }\n }\n const perAgent: DailyStats[\"perAgent\"] = {}\n for (const emp of this.org.all()) {\n perAgent[emp.agentId] = {\n tasksCompleted: 0,\n timeLoggedSeconds: 0,\n onClockSeconds: 0,\n blockers: 0,\n utilization: 0,\n }\n }\n return { date: today(), perAgent }\n }\n\n /** Called by the day-cycle minute tick to accumulate on-clock time. */\n tick(): void {\n const now = Date.now()\n for (const emp of this.org.all()) {\n if (!this.schedule.isOnClock(emp.agentId)) {\n this.onClockTick.delete(emp.agentId)\n continue\n }\n const prev = this.onClockTick.get(emp.agentId)\n if (prev) {\n const delta = Math.min(now - prev, 120_000) / 1000 // cap at 2 min in case of lag\n const row = this.stats.perAgent[emp.agentId]\n if (row) row.onClockSeconds += delta\n }\n this.onClockTick.set(emp.agentId, now)\n }\n this.recomputeUtil()\n this.persist()\n }\n\n recordTaskCompletion(t: TaskCompletion): void {\n const row = this.stats.perAgent[t.agentId]\n if (!row) return\n row.tasksCompleted += 1\n row.timeLoggedSeconds += t.durationSeconds\n this.recomputeUtil()\n this.persist()\n }\n\n recordBlocker(agentId: string): void {\n const row = this.stats.perAgent[agentId]\n if (!row) return\n row.blockers += 1\n this.persist()\n }\n\n private recomputeUtil(): void {\n for (const row of Object.values(this.stats.perAgent)) {\n row.utilization = row.onClockSeconds > 0\n ? Math.min(1, row.timeLoggedSeconds / row.onClockSeconds)\n : 0\n }\n }\n\n private persist(): void {\n // Rotate if day changed\n if (this.stats.date !== today()) {\n this.flush()\n this.stats = this.loadOrInit()\n }\n try {\n writeFileSync(resolve(this.dir, `${this.stats.date}.json`), JSON.stringify(this.stats, null, 2))\n } catch (e: any) {\n this.log(`[business] KPI persist failed: ${e.message}`)\n }\n }\n\n flush(): void {\n this.persist()\n }\n\n snapshot(): DailyStats {\n return JSON.parse(JSON.stringify(this.stats))\n }\n\n /** Markdown summary for end-of-day main-channel post. */\n dailySummaryText(): string {\n const lines: string[] = [`📊 **Daily report — ${this.stats.date}**`, \"\"]\n let totalLogged = 0\n let totalOnClock = 0\n for (const [agentId, row] of Object.entries(this.stats.perAgent)) {\n totalLogged += row.timeLoggedSeconds\n totalOnClock += row.onClockSeconds\n const hrs = (row.timeLoggedSeconds / 3600).toFixed(1)\n const util = (row.utilization * 100).toFixed(0)\n lines.push(`• **${agentId}** — ${row.tasksCompleted} tasks, ${hrs}h logged, ${util}% utilization${row.blockers ? `, ${row.blockers} blocker(s)` : \"\"}`)\n }\n lines.push(\"\")\n const overallUtil = totalOnClock > 0 ? ((totalLogged / totalOnClock) * 100).toFixed(0) : \"0\"\n lines.push(`**Team:** ${(totalLogged / 3600).toFixed(1)}h logged / ${(totalOnClock / 3600).toFixed(1)}h on-clock → ${overallUtil}% utilization`)\n return lines.join(\"\\n\")\n }\n}\n","import type { IncomingMessage, ServerResponse } from \"http\"\nimport type { WorkSource } from \"./work-pool\"\nimport type { Organization } from \"./organization\"\nimport type { Schedule } from \"./schedule\"\nimport type { Reporter } from \"./reporter\"\nimport type { KPI } from \"./kpi\"\n\n// --- Business HTTP endpoints ---\n// Agents running in their Claude Code workspaces reach the daemon over HTTP\n// (localhost:18800 by default). These endpoints expose the work pool,\n// reporter, and KPI snapshot so an agent's lifecycle prompts can call them.\n\nexport interface BusinessHttpDeps {\n workSource: WorkSource\n org: Organization\n schedule: Schedule\n reporter: Reporter\n kpi: KPI\n}\n\nfunction json(res: ServerResponse, status: number, body: unknown): void {\n res.statusCode = status\n res.setHeader(\"Content-Type\", \"application/json\")\n res.end(JSON.stringify(body))\n}\n\nasync function readJson(req: IncomingMessage): Promise<Record<string, unknown>> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = []\n req.on(\"data\", (c) => chunks.push(c as Buffer))\n req.on(\"end\", () => {\n const raw = Buffer.concat(chunks).toString(\"utf-8\")\n if (!raw) return resolve({})\n try { resolve(JSON.parse(raw)) } catch (e) { reject(e) }\n })\n req.on(\"error\", reject)\n })\n}\n\n/**\n * Try to handle a business HTTP request. Returns true if handled.\n * Call from the daemon's handleHttp() before the default case.\n */\nexport async function handleBusinessHttp(\n route: string, // \"METHOD PATH\"\n req: IncomingMessage,\n res: ServerResponse,\n deps: BusinessHttpDeps,\n): Promise<boolean> {\n // GET /business/status — full snapshot\n if (route === \"GET /business/status\") {\n const now = new Date()\n const onClock = deps.schedule.clockedInAgents(now)\n json(res, 200, {\n time: now.toISOString(),\n onClock,\n employees: deps.org.all().map((e) => ({\n agentId: e.agentId,\n role: e.role.title,\n reportsTo: e.reportsTo,\n onClock: onClock.includes(e.agentId),\n })),\n kpi: deps.kpi.snapshot(),\n })\n return true\n }\n\n // GET /business/work?agent=<id>\n if (route.startsWith(\"GET /business/work\")) {\n const url = new URL(req.url || \"/\", \"http://x\")\n const agentId = url.searchParams.get(\"agent\")\n if (!agentId) { json(res, 400, { error: \"?agent= required\" }); return true }\n try {\n const items = await deps.workSource.listOpen(agentId)\n json(res, 200, { items })\n } catch (e: any) {\n json(res, 500, { error: e.message })\n }\n return true\n }\n\n // POST /business/claim { agent, itemId }\n if (route === \"POST /business/claim\") {\n const body = await readJson(req)\n const { agent, itemId } = body as { agent?: string; itemId?: string }\n if (!agent || !itemId) { json(res, 400, { error: \"agent and itemId required\" }); return true }\n try {\n await deps.workSource.claim(agent, itemId)\n json(res, 200, { ok: true })\n } catch (e: any) {\n json(res, 500, { error: e.message })\n }\n return true\n }\n\n // POST /business/report { itemId, status, note?, timeSeconds?, blocker? }\n if (route === \"POST /business/report\") {\n const body = await readJson(req) as {\n itemId?: string; status?: string; note?: string; timeSeconds?: number; blocker?: string\n }\n if (!body.itemId || !body.status) { json(res, 400, { error: \"itemId and status required\" }); return true }\n if (![\"in-progress\", \"done\", \"blocked\"].includes(body.status)) {\n json(res, 400, { error: \"status must be in-progress | done | blocked\" }); return true\n }\n try {\n await deps.workSource.report(body.itemId, {\n status: body.status as any,\n note: body.note,\n timeSeconds: body.timeSeconds,\n blocker: body.blocker,\n })\n json(res, 200, { ok: true })\n } catch (e: any) {\n json(res, 500, { error: e.message })\n }\n return true\n }\n\n // POST /business/escalate { agent, subject, detail }\n if (route === \"POST /business/escalate\") {\n const body = await readJson(req) as { agent?: string; subject?: string; detail?: string }\n if (!body.agent || !body.subject) { json(res, 400, { error: \"agent and subject required\" }); return true }\n await deps.reporter.escalate(body.agent, body.subject, body.detail || \"\")\n deps.kpi.recordBlocker(body.agent)\n json(res, 200, { ok: true })\n return true\n }\n\n // POST /business/post { agent, text }\n if (route === \"POST /business/post\") {\n const body = await readJson(req) as { agent?: string; text?: string }\n if (!body.text) { json(res, 400, { error: \"text required\" }); return true }\n await deps.reporter.postToMain(body.text, body.agent)\n json(res, 200, { ok: true })\n return true\n }\n\n return false\n}\n\n/**\n * Human-readable tool documentation, injected into agent workspace CLAUDE.md\n * when the business layer is enabled. Agents use `curl` to call these.\n */\nexport function businessToolsDoc(daemonBase: string): string {\n return `## Business Layer Tools\n\nDuring business hours you'll receive lifecycle prompts ([STANDUP], [WORK], [WRAP]).\nUse these HTTP endpoints on the local daemon to act on them.\n\n- **List your work:**\n \\`curl -s ${daemonBase}/business/work?agent=<your-id>\\`\n- **Claim an item** (optional — some sources no-op):\n \\`curl -s -X POST ${daemonBase}/business/claim -d '{\"agent\":\"<your-id>\",\"itemId\":\"<id>\"}'\\`\n- **Report progress or completion:**\n \\`curl -s -X POST ${daemonBase}/business/report \\\\\n -d '{\"itemId\":\"<id>\",\"status\":\"done\",\"timeSeconds\":1800,\"note\":\"shipped\"}'\\`\n status: \\`in-progress\\` | \\`done\\` | \\`blocked\\`\n- **Escalate a blocker** up the org chart:\n \\`curl -s -X POST ${daemonBase}/business/escalate \\\\\n -d '{\"agent\":\"<your-id>\",\"subject\":\"short\",\"detail\":\"why it's stuck\"}'\\`\n- **Post to the main channel** (daily plans, reports):\n \\`curl -s -X POST ${daemonBase}/business/post \\\\\n -d '{\"agent\":\"<your-id>\",\"text\":\"your update\"}'\\`\n\nYour org chart, role, and schedule are fixed in agentx.json under \\`business.orgChart\\`.\n`\n}\n","import type { AgentRegistry } from \"@/agents/registry\"\nimport type { DaemonConfig } from \"@/daemon/config\"\nimport type { BusinessConfig } from \"./config\"\nimport type { Organization } from \"./organization\"\nimport type { Schedule } from \"./schedule\"\nimport type { Reporter } from \"./reporter\"\nimport type { KPI } from \"./kpi\"\nimport type { WorkSource } from \"./work-pool\"\nimport { businessToolsDoc } from \"./http\"\n\n// --- Day cycle: minute ticker that fires standup / work-tick / wrap prompts ---\n\nconst TICK_MS = 60_000 // one minute\n\ninterface AgentDayState {\n standupFiredAt?: number // Date.now when standup was triggered today\n wrapFiredAt?: number\n lastWorkTickAt?: number\n hadWorkToday?: boolean // to avoid wrap if agent did nothing\n}\n\nexport class DayCycle {\n private timer?: ReturnType<typeof setInterval>\n private day: Map<string, AgentDayState> = new Map()\n private currentDate: string = new Date().toISOString().slice(0, 10)\n private runningTask: Set<string> = new Set() // agentIds with an in-flight day-cycle call\n\n constructor(\n private config: BusinessConfig,\n private org: Organization,\n private schedule: Schedule,\n private registry: AgentRegistry,\n private reporter: Reporter,\n private kpi: KPI,\n private workSource: WorkSource,\n private daemonBase: string,\n private log: (...args: unknown[]) => void,\n ) {\n for (const emp of org.all()) this.day.set(emp.agentId, {})\n }\n\n start(): void {\n this.log(\"[business] day cycle starting\")\n // Tick immediately then every minute\n this.tick().catch((e) => this.log(`[business] tick error: ${e.message}`))\n this.timer = setInterval(() => {\n this.tick().catch((e) => this.log(`[business] tick error: ${e.message}`))\n }, TICK_MS)\n }\n\n stop(): void {\n if (this.timer) clearInterval(this.timer)\n this.timer = undefined\n this.kpi.flush()\n }\n\n private async tick(): Promise<void> {\n const now = new Date()\n const iso = now.toISOString().slice(0, 10)\n\n // Day rollover: reset per-agent day state\n if (iso !== this.currentDate) {\n this.currentDate = iso\n for (const key of this.day.keys()) this.day.set(key, {})\n }\n\n // KPI on-clock accrual\n this.kpi.tick()\n\n for (const emp of this.org.all()) {\n const state = this.day.get(emp.agentId) || {}\n this.day.set(emp.agentId, state)\n\n // Never fire multiple day-cycle tasks in parallel for the same agent\n if (this.runningTask.has(emp.agentId)) continue\n\n // 1. Morning standup — exactly at day-start minute\n if (this.schedule.isDayStart(emp.agentId, now) && !state.standupFiredAt) {\n state.standupFiredAt = Date.now()\n this.fireStandup(emp.agentId).catch((e) => this.log(`[business] standup err: ${e.message}`))\n continue\n }\n\n // 2. End-of-day wrap — exactly at day-end minute\n if (this.schedule.isDayEnd(emp.agentId, now) && !state.wrapFiredAt) {\n state.wrapFiredAt = Date.now()\n this.fireWrap(emp.agentId).catch((e) => this.log(`[business] wrap err: ${e.message}`))\n continue\n }\n\n // 3. Work tick — during business hours at workTickMinutes cadence\n if (this.schedule.isOnClock(emp.agentId, now)) {\n const elapsed = state.lastWorkTickAt ? (Date.now() - state.lastWorkTickAt) / 60_000 : Infinity\n if (elapsed >= this.config.workTickMinutes) {\n state.lastWorkTickAt = Date.now()\n this.fireWorkTick(emp.agentId).catch((e) => this.log(`[business] work-tick err: ${e.message}`))\n }\n }\n }\n }\n\n private roleContext(agentId: string): string {\n const emp = this.org.get(agentId)\n if (!emp) return \"\"\n const manager = emp.reportsTo ? `reporting to ${emp.reportsTo}` : \"(no manager — top of org chart)\"\n const responsibilities = emp.role.responsibilities.length\n ? `\\nResponsibilities:\\n${emp.role.responsibilities.map((r) => `- ${r}`).join(\"\\n\")}`\n : \"\"\n const sop = emp.role.sopPath ? `\\nSOP: @${emp.role.sopPath}` : \"\"\n return `You are ${agentId}, role: ${emp.role.title} ${manager}.${responsibilities}${sop}\\n\\n${businessToolsDoc(this.daemonBase)}`\n }\n\n private async runLifecycle(agentId: string, prompt: string): Promise<string | undefined> {\n if (this.runningTask.has(agentId)) return\n this.runningTask.add(agentId)\n const start = Date.now()\n try {\n const resp = await this.registry.execute({\n message: prompt,\n agentId,\n context: { channel: \"business\", chatId: `day-cycle:${this.currentDate}:${agentId}` },\n })\n const seconds = Math.round((resp.duration ?? (Date.now() - start)) / 1000)\n if (!resp.error) {\n this.kpi.recordTaskCompletion({ agentId, durationSeconds: seconds, success: true, channel: \"business\" })\n }\n return resp.content\n } finally {\n this.runningTask.delete(agentId)\n }\n }\n\n private async fireStandup(agentId: string): Promise<void> {\n this.log(`[business] STANDUP → ${agentId}`)\n const role = this.roleContext(agentId)\n const prompt =\n`${role}\n\n[STANDUP] It's the start of your work day.\n1. Call GET /business/work?agent=${agentId} to see your open work items.\n2. Pick your top 3 for today, in priority order.\n3. Post your plan to the main channel via POST /business/post — keep it to 4-6 short lines.\n4. Flag any blockers via POST /business/escalate.`\n const content = await this.runLifecycle(agentId, prompt)\n if (content) {\n const state = this.day.get(agentId) || {}\n state.hadWorkToday = true\n this.day.set(agentId, state)\n }\n }\n\n private async fireWorkTick(agentId: string): Promise<void> {\n const items = await this.workSource.listOpen(agentId).catch(() => [])\n if (!items.length) return // nothing assigned → skip (don't fake work)\n\n this.log(`[business] WORK-TICK → ${agentId} (${items.length} open)`)\n const role = this.roleContext(agentId)\n const prompt =\n`${role}\n\n[WORK] Continue your highest-priority open task.\n- Call GET /business/work?agent=${agentId} to see what's assigned to you.\n- If you complete one, POST /business/report with status:done and the time you spent.\n- If blocked, POST /business/escalate with a concise blocker description.\n- Then move on to the next item. Keep working until you hit a real stopping point — don't idle.`\n const content = await this.runLifecycle(agentId, prompt)\n if (content) {\n const state = this.day.get(agentId) || {}\n state.hadWorkToday = true\n this.day.set(agentId, state)\n }\n }\n\n private async fireWrap(agentId: string): Promise<void> {\n const state = this.day.get(agentId) || {}\n if (!state.hadWorkToday) {\n this.log(`[business] WRAP skipped for ${agentId} (no work done today)`)\n return\n }\n this.log(`[business] WRAP → ${agentId}`)\n const role = this.roleContext(agentId)\n const prompt =\n`${role}\n\n[WRAP] End of work day. Post a concise daily report to the main channel via POST /business/post:\n- Tasks closed today\n- Time logged (total)\n- Carry-overs to tomorrow\n- Any outstanding blockers\nKeep it under 8 lines.`\n await this.runLifecycle(agentId, prompt)\n }\n}\n","import type { AgentRegistry } from \"@/agents/registry\"\nimport type { MessageRouter } from \"@/channels/router\"\nimport type { DaemonConfig } from \"@/daemon/config\"\nimport type { BusinessConfig } from \"./config\"\nimport { Organization } from \"./organization\"\nimport { Schedule } from \"./schedule\"\nimport { createWorkSource, type WorkSource } from \"./work-pool\"\nimport { Reporter } from \"./reporter\"\nimport { KPI } from \"./kpi\"\nimport { DayCycle } from \"./day-cycle\"\nimport { handleBusinessHttp, type BusinessHttpDeps } from \"./http\"\nimport type { IncomingMessage, ServerResponse } from \"http\"\n\nexport { businessToolsDoc } from \"./http\"\nexport type { BusinessConfig } from \"./config\"\n\n/**\n * Business layer facade — constructed once by the daemon when\n * config.business?.enabled is true.\n */\nexport class BusinessLayer {\n public readonly org: Organization\n public readonly schedule: Schedule\n public readonly workSource: WorkSource\n public readonly reporter: Reporter\n public readonly kpi: KPI\n public readonly dayCycle: DayCycle\n\n constructor(\n business: BusinessConfig,\n daemon: DaemonConfig,\n registry: AgentRegistry,\n router: MessageRouter,\n log: (...args: unknown[]) => void,\n ) {\n this.org = new Organization(business)\n this.schedule = new Schedule(this.org, business)\n this.workSource = createWorkSource(business, daemon, log)\n this.reporter = new Reporter(router, this.org, business, log)\n this.kpi = new KPI(this.org, this.schedule, log)\n\n // Prefer loopback URL for local agents; external agents can override via env.\n const [host, port] = daemon.node.bind.split(\":\")\n const daemonBase = `http://${host === \"0.0.0.0\" ? \"127.0.0.1\" : host}:${port || \"18800\"}`\n\n this.dayCycle = new DayCycle(\n business, this.org, this.schedule, registry, this.reporter, this.kpi, this.workSource, daemonBase, log,\n )\n }\n\n start(): void {\n this.dayCycle.start()\n }\n\n stop(): void {\n this.dayCycle.stop()\n }\n\n /** Record a task completion (called by router when an agent finishes a task). */\n recordTaskCompletion(agentId: string, durationSeconds: number, success: boolean, channel?: string): void {\n this.kpi.recordTaskCompletion({ agentId, durationSeconds, success, channel })\n }\n\n async handleHttp(route: string, req: IncomingMessage, res: ServerResponse): Promise<boolean> {\n const deps: BusinessHttpDeps = {\n workSource: this.workSource,\n org: this.org,\n schedule: this.schedule,\n reporter: this.reporter,\n kpi: this.kpi,\n }\n return handleBusinessHttp(route, req, res, deps)\n }\n\n /** Short startup banner line. */\n summary(): string {\n const onClock = this.schedule.clockedInAgents().length\n return `Business layer: ${this.org.all().length} employees, ${onClock} on-clock now`\n }\n}\n","import { logger } from \"@/utils/logger\"\n\nexport function handleError(error: unknown) {\n if (typeof error === \"string\") {\n logger.error(error)\n process.exit(1)\n }\n\n if (error instanceof Error) {\n logger.error(error.message)\n process.exit(1)\n }\n\n logger.error(\"Something went wrong. Please try again.\")\n process.exit(1)\n}\n","import path from \"path\"\nimport fs from \"fs-extra\"\nimport { type PackageJson } from \"type-fest\"\n\n/**\n * Read the agentx-cli package.json so commands like --version work.\n *\n * Looks in three places, in order:\n * 1. A package.json beside the compiled entry (dist/cli.js → ../package.json),\n * which is how npm-installed usage finds it.\n * 2. A package.json in the CWD, which is what `pnpm dev` / `tsx src/cli.ts`\n * implicitly relies on.\n * 3. A safe fallback so the CLI never crashes on a truly bare environment\n * (e.g. a fresh user running `agentx setup` from an empty temp dir).\n */\nexport function getPackageInfo(): PackageJson {\n const candidates: string[] = []\n\n // npm / tsup bundled output puts cli.js in dist/; package.json is one dir up.\n // import.meta.dirname is dist/ at runtime, so resolve relative to that.\n // Bundle is ESM (tsup format: [\"esm\"]) — CommonJS __dirname is not defined.\n try {\n const here = import.meta.dirname\n if (here) {\n candidates.push(path.resolve(here, \"..\", \"package.json\"))\n candidates.push(path.resolve(here, \"package.json\"))\n }\n } catch { /* import.meta.dirname may be undefined in exotic setups */ }\n\n candidates.push(path.resolve(process.cwd(), \"package.json\"))\n\n for (const p of candidates) {\n try {\n if (fs.existsSync(p)) {\n const pkg = fs.readJSONSync(p) as PackageJson\n if (pkg && pkg.name) return pkg\n }\n } catch { /* try next candidate */ }\n }\n\n // Minimal fallback — lets --version and help output still work.\n return { name: \"agentix-cli\", version: \"0.0.0\" } as PackageJson\n}\n"],"mappings":"+pBAkCA,IAAMA,GAAc,CAClB,KAAM,SACN,QAAS,OACX,EAEMC,GAAmB,aAEnBC,GAAe,CACnB,MAAO,CAAC,EACR,YAAa,CAAE,KAAM,CAAC,CAAE,CAC1B,EAGIC,GAAuB,EAM3B,eAAeC,GACbC,EACAC,EACyC,CACzC,IAAMC,EAAK,EAAEJ,GAGb,OAAAK,EAAK,CACH,QAAS,MACT,GAAI,UAAUD,IACd,OAAQ,qBACR,OAAQ,CACN,KAAM,OACN,QAAAF,EACA,gBAAiBC,CACnB,CACF,CAAQ,EAGD,IAAI,QAASG,GAAY,CAC9BC,GAAqB,IAAI,UAAUH,IAAOI,GAAgB,CACpDA,GAAQ,SAAW,UAAYA,EAAO,QACxCF,EAAQE,EAAO,OAAO,EAEtBF,EAAQ,IAAI,CAEhB,CAAC,EAGD,WAAW,IAAM,CACXC,GAAqB,IAAI,UAAUH,GAAI,IACzCG,GAAqB,OAAO,UAAUH,GAAI,EAC1CE,EAAQ,IAAI,EAEhB,EAAG,GAAM,CACX,CAAC,CACH,CAGA,IAAMC,GAAuB,IAAI,IAG3BE,EAAa,QAAQ,IAAI,mBAAqB,yBAK9CC,GAAU,yBAChB,SAASC,EAAUC,EAAmB,CAAE,OAAOA,EAAE,QAAQF,GAAS,EAAE,CAAE,CAGtE,IAAMG,GAAQ,CACZ,CACE,KAAM,kBACN,YACE,8LACF,YAAa,CACX,KAAM,SACN,WAAY,CACV,KAAM,CACJ,KAAM,SACN,YACE,mHACJ,EACA,KAAM,CACJ,KAAM,SACN,YACE,2IACF,QAAS,MACX,EACA,WAAY,CACV,KAAM,SACN,YAAa,sDACf,EACA,IAAK,CACH,KAAM,SACN,YAAa,2DACf,CACF,EACA,SAAU,CAAC,MAAM,CACnB,CACF,EACA,CACE,KAAM,iBACN,YACE,4KACF,YAAa,CACX,KAAM,SACN,WAAY,CACV,IAAK,CACH,KAAM,SACN,YAAa,2DACf,CACF,CACF,CACF,EACA,CACE,KAAM,qBACN,YACE,qHACF,YAAa,CACX,KAAM,SACN,WAAY,CACV,KAAM,CACJ,KAAM,SACN,YAAa,kCACf,EACA,IAAK,CACH,KAAM,SACN,YAAa,2BACf,CACF,EACA,SAAU,CAAC,MAAM,CACnB,CACF,EACA,CACE,KAAM,4BACN,YACE,2FACF,YAAa,CACX,KAAM,SACN,WAAY,CACV,KAAM,CACJ,KAAM,SACN,YAAa,iCACf,CACF,EACA,SAAU,CAAC,MAAM,CACnB,CACF,EAIA,CACE,KAAM,cACN,YACE,sTACF,YAAa,CACX,KAAM,SACN,WAAY,CACV,QAAS,CACP,KAAM,SACN,YAAa,qDACf,EACA,OAAQ,CACN,KAAM,SACN,YAAa,sHACf,EACA,KAAM,CACJ,KAAM,SACN,YAAa,sBACf,EACA,QAAS,CACP,KAAM,SACN,YAAa,8EACf,CACF,EACA,SAAU,CAAC,UAAW,SAAU,MAAM,CACxC,CACF,EACA,CACE,KAAM,oBACN,YACE,iaACF,YAAa,CACX,KAAM,SACN,WAAY,CACV,QAAS,CACP,KAAM,SACN,YAAa,iJACf,EACA,KAAM,CACJ,KAAM,SACN,YAAa,qCACf,EACA,cAAe,CACb,KAAM,SACN,YAAa,gKACf,CACF,EACA,SAAU,CAAC,UAAW,MAAM,CAC9B,CACF,EACA,CACE,KAAM,gBACN,YACE,6iBACF,YAAa,CACX,KAAM,SACN,WAAY,CACV,QAAS,CACP,KAAM,SACN,YAAa,sFACf,EACA,OAAQ,CACN,KAAM,SACN,YAAa,6KACf,EACA,SAAU,CACR,KAAM,SACN,YAAa,0DACf,EACA,MAAO,CACL,KAAM,SACN,YAAa,yDACf,CACF,EACA,SAAU,CAAC,UAAW,QAAQ,CAChC,CACF,EACA,CACE,KAAM,sBACN,YACE,0XACF,YAAa,CACX,KAAM,SACN,WAAY,CACV,YAAa,CACX,KAAM,SACN,YAAa,8EACf,EACA,KAAM,CACJ,KAAM,SACN,YAAa,gCACf,EACA,QAAS,CACP,KAAM,SACN,YAAa,+HACf,EACA,UAAW,CACT,KAAM,UACN,YAAa,6IACf,EACA,QAAS,CACP,KAAM,SACN,YAAa,sFACf,CACF,EACA,SAAU,CAAC,cAAe,MAAM,CAClC,CACF,EACA,CACE,KAAM,cACN,YACE,4IACF,YAAa,CACX,KAAM,SACN,WAAY,CACV,MAAO,CACL,KAAM,SACN,YAAa,8BACf,EACA,QAAS,CACP,KAAM,SACN,YAAa,4BACf,CACF,EACA,SAAU,CAAC,QAAS,SAAS,CAC/B,CACF,EACA,CACE,KAAM,gBACN,YACE,wGACF,YAAa,CACX,KAAM,SACN,WAAY,CAAC,CACf,CACF,EACA,CACE,KAAM,gBACN,YACE,2FACF,YAAa,CACX,KAAM,SACN,WAAY,CAAC,CACf,CACF,EACA,CACE,KAAM,eACN,YACE,6HACF,YAAa,CACX,KAAM,SACN,WAAY,CAAC,CACf,CACF,EACA,CACE,KAAM,eACN,YACE,6JACF,YAAa,CACX,KAAM,SACN,WAAY,CACV,OAAQ,CACN,KAAM,SACN,YAAa,2BACb,QAAS,QACX,EACA,WAAY,CACV,KAAM,SACN,YAAa,wFACf,CACF,CACF,CACF,EACA,CACE,KAAM,oBACN,YACE,oXACF,YAAa,CACX,KAAM,SACN,WAAY,CACV,SAAU,CACR,KAAM,SACN,YAAa,4DACf,EACA,MAAO,CACL,KAAM,SACN,YAAa,oIACf,EACA,SAAU,CACR,KAAM,SACN,YAAa,8CACf,EACA,SAAU,CACR,KAAM,SACN,YAAa,oDACb,QAAS,CACX,CACF,EACA,SAAU,CAAC,UAAU,CACvB,CACF,EACA,CACE,KAAM,oBACN,YACE,oQACF,YAAa,CACX,KAAM,SACN,WAAY,CACV,MAAO,CACL,KAAM,SACN,YAAa,kDACf,EACA,cAAe,CACb,KAAM,SACN,YAAa,0EACf,EACA,YAAa,CACX,KAAM,SACN,YAAa,sIACf,EACA,MAAO,CACL,KAAM,SACN,YAAa,gDACb,QAAS,QACX,EACA,QAAS,CACP,KAAM,UACN,YAAa,4CACb,QAAS,EACX,CACF,EACA,SAAU,CAAC,QAAS,gBAAiB,aAAa,CACpD,CACF,EACA,CACE,KAAM,wBACN,YACE,+TACF,YAAa,CACX,KAAM,SACN,WAAY,CACV,MAAO,CACL,KAAM,SACN,YAAa,oDACf,EACA,MAAO,CACL,KAAM,SACN,YAAa,oHACf,EACA,KAAM,CACJ,KAAM,SACN,YAAa,+EACf,EACA,QAAS,CACP,KAAM,QACN,Y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","names":["SERVER_INFO","PROTOCOL_VERSION","CAPABILITIES","elicitationIdCounter","elicit","message","schema","id","send","resolve","elicitationResolvers","result","DAEMON_URL","ANSI_RE","stripAnsi","s","TOOLS","handleToolCall","name","args","cwd","task","outputType","outputDir","generate","summary","f","context","createAgentContext","info","m","formatTechStack","skills","loadLocalSkills","matches","matchSkillsToTask","resolveOutputType","channel","chatId","text","channelsRes","channels","response","res","data","agentId","senderAgentId","known","peerInfo","sinceISO","limit","messages","time","day","who","contactName","confirmed","agent","agentsRes","agentList","a","jobs","j","question","WikiHub","agenticQuery","wikiDir","hub","existsSync","catPath","store","maxHops","cites","c","walkCount","titleOrPath","instruction","model","dry","execFileSync","flags","out","e","topic","type","answers","commit","terminal","finalAnswers","writeFileSync","mkdtempSync","tmpdir","join","tmp","answersPath","max","action","cats","dispatch","raw","log","msg","resolver","handleMessage","startMcpServer","buffer","chunk","headerEnd","contentLength","bodyStart","bodyEnd","body","newlineIdx","line","method","params","toolName","toolArgs","error","A2AClient","baseUrl","token","res","text","metadata","response","body","reader","decoder","buffer","done","value","lines","line","data","taskId","method","params","h","UndiciAgent","undiciFetch","longTaskDispatcher","UNHEALTHY_AFTER","A2AMesh","cb","config","log","peer","A2AClient","interval","next","oldIds","newPeers","p","added","removed","updated","id","rediscover","existing","state","name","probes","results","healthy","timeout","controller","timer","card","wasDown","prevSkills","a","nextSkills","skillsChanged","s","e","peerName","text","agentId","opts","agent","url","headers","timeoutMs","res","data","signal","want","key","skillId","c","readFileSync","writeFileSync","existsSync","mkdirSync","resolve","ellipsize","text","maxChars","ellipsis","budget","slice","nl","sp","firstLines","maxLines","picked","MAX_ENTRIES","MAX_CONTEXT_CHARS","MAX_ENTRY_CHARS","REPEAT_WINDOW_MS","bucketLabel","ts","d","hh","mm","GroupLog","baseDir","resolve","mkdirSync","chatId","sender","text","log","now","ellipsized","ellipsize","last","lines","chars","pending","buffered","lastBucket","flushPending","out","hdr","i","entry","line","bucket","safe","path","existsSync","data","readFileSync","entries","writeFileSync","DEFAULT_CONFIG","CHANNEL_DEFAULTS","BlockStream","emitter","config","channel","channelDefaults","delta","breakPoint","cutAt","block","delay","buf","max","paraIdx","nlIdx","spaceIdx","lines","line","readFileSync","writeFileSync","existsSync","mkdirSync","appendFileSync","resolve","dirname","fromIncoming","msg","conversation","runPipeline","env","stages","baseCtx","ctx","stage","decision","someoneCanCheck","knownLocally","knownInMesh","dropDecision","MARKER_RE","markBody","body","agentId","detectAgentxMarker","m","stripAgentxMarkers","selfReplyGuard","env","_ctx","marker","detectAgentxMarker","handover","env","ctx","chatId","override","adapterResolved","env","_ctx","resolved","dmBinding","env","ctx","account","a","botPolicy","env","ctx","mention","env","ctx","requireAtMentionInGroup","flag","agentId","fallbackBinding","env","ctx","account","a","defaultPipeline","selfReplyGuard","handover","adapterResolved","dmBinding","botPolicy","mention","fallbackBinding","pickAccountForAgent","accounts","agentId","groupId","getGroupBotAccounts","candidates","accountId","account","inGroup","id","ROUTER_CHANNELS","routerChannelToSource","channel","buildRouterEventInput","msg","source","rawJson","now","accountId","chatId","buildRouterPolicyFromLegacy","legacy","recordRouterDispatch","ledger","legacyOutcome","eventInput","policy","ledgerDecision","decideAndCommit","reportDivergence","InflightLog","baseDir","resolve","mkdirSync","dirname","entry","line","appendFileSync","id","existsSync","raw","readFileSync","byId","unfinished","out","e","writeFileSync","TYPING_INTERVAL_MS","_MessageRouter","registry","config","hooks","log","GroupLog","HandoverStore","a","InflightLog","p","resolve","existsSync","raw","readFileSync","now","restored","key","ts","live","dir","dirname","mkdirSync","writeFileSync","mesh","matcher","business","next","name","adapter","msg","accountId","messageId","e","unfinished","entry","k","t","seen","legacy","source","routerChannelToSource","getLedgerMode","decision","recordRouterDispatch","getDefaultLedger","opts","hookResult","chatId","matched","replyAccountId","typingTimer","text","result","agentId","boundAccount","intentRef","agentDef","agentName","canStream","sentMessageId","fullStreamText","blockStream","BlockStream","block","preview","onDelta","_delta","_fullText","agent","def","m","groupContext","messageWithContext","response","errorText","responseText","sentResponseId","notifyConfig","thresholdMs","durSec","status","trigger","firstLines","ellipsize","notifyText","gitlabAdapter","originalMsg","sourceAgentId","responseMessageId","depth","visited","id","targetAccountId","sourceAccountId","contextMessage","replyText","sentId","parseMode","emoji","sendTyping","kind","reason","decidingStage","chat","at","getEventBus","env","fromIncoming","runPipeline","defaultPipeline","s","textLower","directory","peer","skill","header","peerName","start","duration","groupId","pickAccountForAgent","MessageRouter","__publicField","escapeHtml","text","markdownToTelegramHtml","md","lines","result","inCodeBlock","codeBlockLang","codeBlockLines","inBlockquote","tableHeaders","inTable","i","line","code","langHint","content","convertInline","headerMatch","cells","c","parts","header","ulMatch","indent","olMatch","num","html","wrapFileRefsInCode","_m","label","url","FILE_EXT_TLDS","insideTag","part","readFileSync","writeFileSync","mkdirSync","existsSync","resolve","dirname","existsSync","mkdirSync","readFileSync","renameSync","writeFileSync","dirname","resolve","FileCursorStore","cwd","relPath","parsed","channel","account","v","cursor","k","dir","tmp","_instance","getCursorStore","TelegramGroupStore","dataDir","resolve","existsSync","readFileSync","mkdirSync","dirname","writeFileSync","groupId","groupName","accountId","agentId","username","status","group","info","id","g","b","TelegramAdapter","accounts","opts","log","TelegramGroupStore","getCursorStore","accountId","cfg","fromId","chatId","fromUsername","list","fromStr","chatStr","userLc","entry","handler","entries","i","config","r","meta","prefix","me","botUserId","botUsername","e","lastErr","delays","attempt","aborts","id","next","policy","oldIds","newIds","added","removed","tokenChanged","oldCfg","newCfg","token","groupId","b","msg","maxLen","text","formatted","markdownToTelegramHtml","params","result","messageId","content","agentId","args","parseMode","trimmed","draftId","n","emoji","consecutiveErrors","stored","offset","updates","update","mediaInfo","hasPhoto","hasVoice","hasAudio","hasVideo","hasDocument","fileId","mime","filePath","fileUrl","res","buffer","ext","mkdirSync","writeFileSync","randomUUID","resolve","join","mediaDir","fileName","localPath","isGroup","channelMeta","incoming","mcm","chatTitle","status","backoff","out","g","bots","groupTitle","info","day","file","existing","existsSync","readFileSync","m","dirname","safeChatId","chars","today","minDate","d","msgs","j","method","url","mkdirSync","writeFileSync","resolve","join","randomUUID","readFileSync","writeFileSync","mkdirSync","existsSync","resolve","dirname","WhatsAppCache","sessionDir","jid","updates","now","u","prev","extractPhone","next","firstString","chats","c","record","toSeconds","g","members","p","m","data","snapshot","dir","raw","user","values","v","low","WhatsAppAdapter","config","log","resolve","WhatsAppCache","senderPhone","groupName","groupJid","route","normalized","pattern","handler","makeWASocket","useMultiFileAuthState","DisconnectReason","baileys","mkdirSync","myGeneration","state","saveCreds","version","v","silentLogger","args","data","e","updates","contacts","chats","update","connection","lastDisconnect","qr","qrcode","statusCode","reason","delay","m","msg","jidShort","hasText","me","remoteJid","myJid","myLid","chatUser","text","msgContent","hasImage","hasAudio","hasVideo","hasDocument","hasSticker","hasMedia","jid","isGroup","chatPhone","p","senderName","resolvedAgent","mediaInfo","buffer","mime","ext","mediaDir","fileName","randomUUID","filePath","join","writeFileSync","incoming","content","mediaPayload","source","sentId","chatId","messageId","emoji","contactRecordToSummary","chatRecordToSummary","record","cached","groupRecordToInfo","live","refreshed","opts","limit","sock","mapBaileysMessage","sinceMs","history","out","chars","ts","fn","now","t","wait","sinceLast","r","raw","media","timestamp","ms","createServer","writeFileSync","existsSync","unlinkSync","mkdirSync","readFileSync","watch","resolve","dirname","DiscordAdapter","config","log","handler","Discord","Client","GatewayIntentBits","msg","isMentioned","isDM","text","incoming","e","channel","chatId","messageId","emoji","SlackAdapter","config","log","handler","SocketModeMod","WebApiMod","SocketModeClient","WebClient","authResult","e","handle","payload","ack","event","isDM","isMentioned","text","senderName","info","incoming","msg","chatId","messageId","emoji","name","createServer","buildGitLabTargetEventInput","event","target","rawJson","now","buildGitLabDispatchPolicy","entityKind","recordGitLabTargetDispatch","ledger","legacyOutcome","eventInput","policy","ledgerDecision","decideAndCommit","reportDivergence","buildNoteEventInput","proj","buildNoteDispatchPolicy","legacyAgentId","recordGitLabNoteDispatch","recordGitLabIssueLevelDecision","marker","decidedBy","GitLabAdapter","config","log","hooks","fn","handler","tokensToResolve","mapping","resolutions","label","token","res","data","result","username","explicitAgentIds","m","autoMapped","agentId","defaultUsernames","u","a","createServer","req","err","msg","parts","iid","noteableType","project","encodedProject","endpoint","agentToken","body","markBody","noteId","e","debug","text","event","objectKind","note","user","sourceAgent","detectAgentxMarker","mentions","targetAgent","noteableIid","noteableTitle","resolvedAgentId","getLedgerMode","recordGitLabNoteDispatch","getDefaultLedger","targetAgentId","agentMapping","agentNode","chatId","channelMeta","noteClean","stripAgentxMarkers","imageAttachment","intentRef","decision","incoming","attrs","defaultAgentId","authoredByBot","dispatch","hookBlocked","modDispatch","d","ledgerEnabled","issueProjectionForHook","eventJsonForHook","id","recordGitLabTargetDispatch","recordGitLabIssueLevelDecision","targets","issueProjection","eventJson","t","dedupKey","wasDuplicate","out","seen","add","trigger","previous","current","allAssignees","key","expires","now","k","exp","mrProjection","prevA","prevR","r","mention","route","agents","opts","notes","chars","sinceMs","n","cleanBody","mentionedUsers","node","durationMs","totalSeconds","hours","minutes","duration","typeSegment","args","title","description","labels","assignees","users","issue","kind","remove","segment","imagePattern","matches","imagePath","imageUrl","buffer","contentType","ext","mkdirSync","writeFileSync","resolve","randomUUID","mediaDir","fileName","filePath","chunk","createHmac","createSign","readFileSync","GitHubAdapter","config","log","fn","handler","readFileSync","jwt","res","data","e","mapping","username","headers","body","rawBody","signature","payload","event","repo","msg","parts","issueNumber","type","m","agentToken","agentLabel","agentHeader","commentBody","markBody","token","commentId","endpoint","text","chatId","messageId","emoji","number","repoParts","repoName","agents","route","comment","user","sourceAgent","detectAgentxMarker","isPR","agentId","channelMeta","incoming","pr","review","issue","ref","commits","commitSummary","c","tokenFile","expected","createHmac","now","header","unsigned","sign","createSign","cached","installRes","install","tokenRes","tokenData","expiresAt","normalizeName","name","WebRtcSignalBroker","localName","allowedCallers","log","callId","recipient","now","key","bucket","kept","b","expiry","fn","res","deliver","sink","sub","tag","buffered","fresh","signal","wantRecipient","ok","normalizedRecipient","e","delivered","CALL_PAGE_HTML","wrtcModule","loadWrtc","mod","e","pkg","RTCPeerConnection","RTCAudioSink","norm","s","WebRtcBot","opts","broker","callId","botName","target","iceServers","log","ev","track","sig","offer","signal","result","ans","sink","data","reason","DEFAULTS","AudioChunker","opts","frame","mono","mixToMono","isSpeech","rmsNormalized","now","ageMs","silenceFor","speechMs","reason","chunk","merged","concatInt16","downsampled","downsampleTo16k","wav","encodeWav","samples","channels","out","i","j","sum","c","sumSq","s","parts","total","offset","p","sourceRate","ratio","outLen","src","lo","hi","t","sampleRate","dataBytes","header","data","spawn","writeFile","mkdtemp","rm","tmpdir","join","Whisper","opts","wav","backend","e","target","resolve","child","code","dir","wavPath","args","text","runProcess","readFile","key","fd","res","errText","cmd","reject","out","err","d","HISTORY_RING_SIZE","BotManager","opts","Whisper","invite","existing","botName","chunker","AudioChunker","wav","durationMs","text","e","botOpts","frame","reason","bot","WebRtcBot","timeoutTimer","callId","active","endedAt","durationSec","now","b","_Logger","module","minLevel","msg","data","args","clean","a","level","entry","prefix","tag","display","Logger","__publicField","randomUUID","EventBus","filter","fn","id","event","full","matches","parseKindsParam","raw","known","kept","p","createHmac","timingSafeEqual","WebhookHandler","registry","config","log","mesh","webhookEntries","d","entries","req","res","path","parts","agentId","sourceHint","raw","parsed","source","sigError","summary","eventType","triggerEntry","w","workflowId","result","e","meshEntry","response","body","headers","header","k","v","evt","action","lines","event","project","user","c","mr","attrs","repo","sender","pr","issue","type","data","resource","resolve","chunk","entry","secret","headerStr","key","sig","expected","safeEqualOrReject","tok","sigHeader","p","generic","status","actual","a","b","Database","mkdirSync","dirname","resolve","_db","openDb","opts","path","db","runMigrations","e","runMigrations","db","current","steps","txn","step","pruneSqliteTables","db","retentionDays","cutoffMs","cutoffIso","taskHistory","rotations","routeTraces","prepare","db","attachSqliteSubscribers","model","bus","getEventBus","stmts","pending","onStarted","p","key","onCompleted","start","id","status","hasUsage","day","onRotated","onMatched","onDropped","rowToAgentUsage","r","loadDailyUsage","db","date","rows","agents","existing","next","loadTodayRollup","db","today","date","loadDailyUsage","getUsageReadMode","env","v","z","agentXPluginManifestSchema","buildPluginContext","args","channels","disposers","bus","getEventBus","prefix","rest","evt","fn","wrapped","payload","e","dispose","adapter","c","d","DEFAULT_SETUP_TIMEOUT_MS","loadPlugins","args","names","importer","name","timeoutMs","out","packageName","mod","e","candidate","pickDefault","isPlugin","plugin","parsed","agentXPluginManifestSchema","issues","i","majorMinorMatches","handle","buildPluginContext","setupOk","raceWithTimeout","m","d","dd","range","daemonVersion","a","b","p","ms","msg","resolve","reject","timer","v","buildMeshEventInput","proj","rawJson","now","buildMeshPolicyFromLegacy","legacy","recordMeshDispatch","ledger","legacyOutcome","eventInput","policy","ledgerDecision","decideAndCommit","reportDivergence","agentCanHandleIntent","agent","intent","delegationChainDepth","ledger","project","subject","windowMs","since","withinDelegationBudget","agentId","max","upstream","LandscapeBuilder","config","meshPeers","agentId","a","b","p","s","selfId","lines","selfDef","selfHandle","selfCap","handleStr","localAgents","id","def","handle","cap","h","remotePeers","sortedPeers","peer","sortedSkills","skill","desc","nameSuffix","handles","portStr","port","parts","ch","pol","m","first","existsSync","readFileSync","resolve","CONTACTS_RELPATH","ContactDirectory","baseDir","log","raw","parsed","valid","c","id","query","q","ql","a","bestScore","bestContacts","candidates","myBest","cand","cl","overlap","contact","preferred","entries","channel","address","existsSync","readFileSync","unlinkSync","writeFileSync","resolve","MARKER","syncMcpToWorkspace","workspacePath","mcp","mcpPath","fileExists","parsed","tryParseJson","isManaged","hasConfig","next","path","REMEMBER_SKILL_FILENAME","REMEMBER_SKILL_BODY","HeartbeatManager","registry","log","memoryStore","agentId","config","state","intervalMs","e","id","channel","prompt","recallContext","response","error","s","ServiceMatcher","serviceDefs","log","fresh","id","def","compiled","trigger","e","text","senderId","channel","svc","senderNorm","c","contactNorm","pattern","match","debug","service","registry","context","replyFn","response","s","Organization","config","agentId","entry","role","emp","seen","cur","proj","e","chain","project","_project","_intent","DAY_MAP","tzParts","when","timezone","parts","weekday","p","hourStr","minStr","parseHHMM","s","h","m","worksOnDay","schedule","dow","d","isScheduleActive","hour","minute","nowMin","startMin","endMin","lStart","lEnd","Schedule","org","config","agentId","emp","e","Reporter","router","org","config","log","text","agentId","dest","e","fromAgent","subject","detail","chain","targets","body","t","writeFileSync","existsSync","mkdirSync","readFileSync","resolve","today","KPI","org","schedule","log","f","perAgent","emp","now","prev","delta","row","t","agentId","lines","totalLogged","totalOnClock","hrs","util","overallUtil","json","res","status","body","readJson","req","resolve","reject","chunks","c","raw","e","handleBusinessHttp","route","deps","now","onClock","agentId","items","agent","itemId","businessToolsDoc","daemonBase","TICK_MS","DayCycle","config","org","schedule","registry","reporter","kpi","workSource","daemonBase","log","emp","now","iso","key","state","e","agentId","manager","responsibilities","sop","businessToolsDoc","prompt","start","resp","seconds","items","BusinessLayer","business","daemon","registry","router","log","Organization","Schedule","createWorkSource","Reporter","KPI","host","port","daemonBase","DayCycle","agentId","durationSeconds","success","channel","route","req","res","deps","handleBusinessHttp","onClock","AgentXDaemon","AgentMemory","EventBus","configPath","baseLog","Logger","args","line","loadDaemonConfig","ContactDirectory","warnings","validateWorkspaces","w","portStr","setupAllWorkspaces","HookRegistry","loadHooks","LandscapeBuilder","AgentRegistry","setGlobalRegistry","HeartbeatManager","id","agent","MessageRouter","serviceMatcher","ServiceMatcher","CronScheduler","jobId","error","consecutiveErrors","msg","cronDef","e","A2AMesh","skillId","peer","text","agentId","WebhookHandler","db","openDb","attachSqliteSubscribers","BusinessLayer","org","agents","setDefaultGovernance","project","intent","agentCanHandleIntent","subject","withinDelegationBudget","getDefaultLedger","s","getLedgerMode","closed","builtInChannelNames","loadPlugins","n","p","ch","meshPeers","yesterday","appended","err","cronJobs","job","status","removed","pruneSqliteTables","total","loaded","pidFile","resolve","mkdirSync","dirname","writeFileSync","reason","stopping","shutdown","signal","start","r","drainTimeoutMs","drainStart","inflightCount","active","elapsedMs","existsSync","unlinkSync","path","watch","eventType","next","applied","restartRequired","tgPrev","tgNext","tgChanged","tgHandled","telegram","diff","parts","meshPrev","meshNext","meshChanged","meshHandled","matcher","count","fresh","beforeSize","afterSize","refreshed","healthy","oldIds","newIds","restartFields","detectAgentRestartFields","channelsPrevMinusTg","channelsNextMinusTg","before","result","scheduleNext","now","tunisStr","tunisNow","target","tunisOffset","delay","tracker","report","accounts","policy","TelegramAdapter","globalSize","closedAccts","c","setWhatsAppQR","setWhatsAppStatus","whatsapp","WhatsAppAdapter","discord","DiscordAdapter","slack","SlackAdapter","gitlab","GitLabAdapter","node","noteableType","noteableIid","noteId","url","respText","data","durationMs","githubConfig","GitHubAdapter","repo","issueNumber","wrtcCfg","WebRtcSignalBroker","urlBase","link","botCfg","iceServers","urls","BotManager","invite","dest","stamp","channel","cfg","store","WorkflowStore","runs","RunStore","channels","name","adapter","req","wfChatId","resp","forwarder","peerName","payload","peers","localPeerName","txt","ActorStore","TaskStore","actorStore","taskStore","inboxBaseUrl","taskRenderers","telegramAdapter","createTelegramTaskRenderer","m","whatsappAdapter","createWhatsappTaskRenderer","slackAdapter","createSlackTaskRenderer","renderUserTask","task","TimerService","timerService","dispatcher","WorkflowDispatcher","handlers","createWorkflowHookHandlers","event","handler","cronTimers","hookSubscribers","startWorkflowTriggers","detectConflicts","conflicts","host","port","createServer","res","client","filter","parseKindsParam","a","unsubscribe","_agentId","taskId","send","ev","sub","chunk","heartbeat","callId","as","CALL_PAGE_HTML","channelChatsMatch","localOnly","manualRun","computeKpis","limit","workflowId","runMatch","run","body","readJsonBody","messages","normMessages","buildWorkflowAuthorPrompt","extractWorkflowJson","availableChannels","availableAgents","prompt","reply","workflow","emission","renderInboxPage","qs","html","renderProcessesPage","oneClick","action","actor","parsed","taskStreamMatch","taskHistoryListMatch","taskRecordMatch","rec","j","getWhatsAppState","mod","svg","wa","readBody","dryRun","runSweep","effectiveCfg","jid","isGroup","forced","getDebugState","getDebugLogs","setDebug","cats","mapping","token","encoded","ep","glRes","respBody","totalSeconds","hours","minutes","duration","ghConfig","readFileSync","ghRes","commentId","targetAgent","senderAgentId","response","directory","known","messageId","preferredChannel","confirmed","collidesWithLocalAgent","collidesWithMeshAgent","picked","contextStrategy","intentRef","decision","recordMeshDispatch","message","voicePrompt","speakable","toSpeakable","wrtc","def","desc","firstSentence","mentions","hub","after","entries","index","articlePath","article","items","pathMatch","fallbackAgent","lastUserMsg","historyLines","contextPrefix","requestId","content","tokens","mode","getUsageReadMode","rollup","loadTodayRollup","ws","skillsDir","skillPath","REMEMBER_SKILL_FILENAME","REMEMBER_SKILL_BODY","mcp","syncMcpToWorkspace","mem","oneMatch","type","description","newBody","append","finalBody","existing","delMatch","d","formSubmissionSchema","tasks","match","submissionRaw","i","submittedBy","entityRef","kind","fromPeer","trigger","chatId","opts","seen","chats","sources","add","source","added","local","wf","triggerNode","force","entityId","eventId","runId","trimmed","head","reject","raw","prev","changed","ids","f","handleError","error","logger","path","fs","getPackageInfo","candidates","here","p","pkg"]}