agentix-cli 0.18.0 → 0.20.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.
- package/README.md +75 -27
- package/dist/{agent-RDVGL2TW.js → agent-57MG4LBM.js} +2 -2
- package/dist/{audit-Z47AQFFD.js → audit-JU3HWISB.js} +2 -2
- package/dist/{board-dashboard-KX2CNOSY.js → board-dashboard-HXDOYGIG.js} +2237 -69
- package/dist/board-dashboard-HXDOYGIG.js.map +1 -0
- package/dist/chunk-23AUU4S2.js +102 -0
- package/dist/chunk-23AUU4S2.js.map +1 -0
- package/dist/chunk-4MREHZP5.js +111 -0
- package/dist/chunk-4MREHZP5.js.map +1 -0
- package/dist/chunk-4YCH6IZV.js +2 -0
- package/dist/chunk-6B74TO2J.js +743 -0
- package/dist/chunk-6B74TO2J.js.map +1 -0
- package/dist/chunk-7GVXCOLO.js +440 -0
- package/dist/chunk-7GVXCOLO.js.map +1 -0
- package/dist/chunk-7P5QRX5W.js +108 -0
- package/dist/chunk-7P5QRX5W.js.map +1 -0
- package/dist/chunk-A6BKSZQA.js +3 -0
- package/dist/chunk-A6BKSZQA.js.map +1 -0
- package/dist/{chunk-6PPIFDOQ.js → chunk-CNNLREHD.js} +67 -22
- package/dist/{chunk-6PPIFDOQ.js.map → chunk-CNNLREHD.js.map} +1 -1
- package/dist/{chunk-J4WKMKTP.js → chunk-DLVOHCTA.js} +2 -2
- package/dist/chunk-E4ZZILAK.js +15 -0
- package/dist/chunk-E4ZZILAK.js.map +1 -0
- package/dist/{chunk-TRQSP67X.js → chunk-IKBHHTIT.js} +2 -2
- package/dist/chunk-JTJIPNXY.js +2 -0
- package/dist/chunk-JTJIPNXY.js.map +1 -0
- package/dist/chunk-MBOLXKA2.js +6 -0
- package/dist/chunk-MBOLXKA2.js.map +1 -0
- package/dist/{inbox-O5UB3YQN.js → chunk-MTRWXRFF.js} +69 -4
- package/dist/chunk-MTRWXRFF.js.map +1 -0
- package/dist/{processes-7W27G3J3.js → chunk-NOF4HSGK.js} +3 -3
- package/dist/{processes-7W27G3J3.js.map → chunk-NOF4HSGK.js.map} +1 -1
- package/dist/chunk-QW2OYR3N.js +7 -0
- package/dist/chunk-QW2OYR3N.js.map +1 -0
- package/dist/{chunk-JXEPJ3YD.js → chunk-TC3BSUP7.js} +5 -5
- package/dist/chunk-VWTBHLMX.js +1 -0
- package/dist/chunk-WFHIIVLL.js +719 -0
- package/dist/chunk-WFHIIVLL.js.map +1 -0
- package/dist/chunk-WPPUOTY6.js +6 -0
- package/dist/chunk-WPPUOTY6.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/{compaction-S5H6DTOR.js → compaction-BTUJJ3LJ.js} +4 -4
- package/dist/config-QXWKEQON.js +2 -0
- package/dist/config-mutator-53XGR2NT.js +2 -0
- package/dist/conflict-detector-D7VIYPDI.js +2 -0
- package/dist/conflict-detector-D7VIYPDI.js.map +1 -0
- package/dist/{context-planner-2RGHJHSO.js → context-planner-I7ID7XF3.js} +3 -3
- package/dist/debug-N3B5NVJU.js +2 -0
- package/dist/{discover-OATU7SHI.js → discover-XJXQXXIO.js} +2 -2
- package/dist/doctor-SEOUFHSL.js +2 -0
- package/dist/{editor-chat-FUBQLMAQ.js → editor-chat-RVLCKKB7.js} +2 -2
- package/dist/{heal-YP7QI7D3.js → heal-YEQYWX2L.js} +2 -2
- package/dist/inbox-BIN2S5XN.js +2 -0
- package/dist/index.d.ts +830 -100
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/{ingest-whatsapp-C3O5UPBX.js → ingest-whatsapp-LZE62INA.js} +2 -2
- package/dist/loader-5CA3O5GM.js +2 -0
- package/dist/loader-6IHUCPVO.js +2 -0
- package/dist/memory-extract-KRCHD3OP.js +2 -0
- package/dist/migrate-v2-QO7SLKIM.js +4 -0
- package/dist/migrate-v2-QO7SLKIM.js.map +1 -0
- package/dist/processes-RJ6RDP32.js +2 -0
- package/dist/program-WWCXMRU2.js +2 -0
- package/dist/{providers-WCQSKTNP.js → providers-WVCMIW67.js} +2 -2
- package/dist/query-HZYYRCLU.js +2 -0
- package/dist/recipes-EPLE2SNT.js +2 -0
- package/dist/registry-HM4ELSXS.js +2 -0
- package/dist/rotation-memo-GMTZPMNE.js +13 -0
- package/dist/rotation-memo-GMTZPMNE.js.map +1 -0
- package/dist/scheduler-XVFIK4XH.js +2 -0
- package/dist/{slack-D27BE37Q.js → slack-6P7ZJ32Y.js} +2 -2
- package/dist/store-BAPDZHXE.js +2 -0
- package/dist/{subagent-EPOT3QDM.js → subagent-WV7QKQ3L.js} +2 -2
- package/dist/{task-store-3NUWLYRY.js → task-store-RHISEU6V.js} +2 -2
- package/dist/{telegram-UWV67KWG.js → telegram-PIYDRM46.js} +2 -2
- package/dist/timers-5LPZN55J.js +2 -0
- package/dist/types-VECTMYTO.js +2 -0
- package/dist/types-VECTMYTO.js.map +1 -0
- package/dist/types-ZRLHHCGB.js +2 -0
- package/dist/types-ZRLHHCGB.js.map +1 -0
- package/dist/web/activity-graph.global.js +654 -0
- package/dist/web/activity-graph.global.js.map +1 -0
- package/dist/web/workflow-editor.global.js +1 -1
- package/dist/web/workflow-editor.global.js.map +1 -1
- package/dist/{whatsapp-KJ2NRKKQ.js → whatsapp-PFRDMUMT.js} +2 -2
- package/dist/{whatsapp-state-3ZVQ6MQD.js → whatsapp-state-XMTCSRXU.js} +2 -2
- package/dist/wiki-53W5NLZV.js +2 -0
- package/dist/wiki-53W5NLZV.js.map +1 -0
- package/dist/workspace-setup-FPGN5VNY.js +2 -0
- package/dist/workspace-setup-FPGN5VNY.js.map +1 -0
- package/package.json +3 -2
- package/dist/board-dashboard-KX2CNOSY.js.map +0 -1
- package/dist/chunk-34NG7UDX.js +0 -11
- package/dist/chunk-34NG7UDX.js.map +0 -1
- package/dist/chunk-ASSJ4Z6Z.js +0 -164
- package/dist/chunk-ASSJ4Z6Z.js.map +0 -1
- package/dist/chunk-BLUXL6HR.js +0 -854
- package/dist/chunk-BLUXL6HR.js.map +0 -1
- package/dist/chunk-BTAEYL3P.js +0 -72
- package/dist/chunk-BTAEYL3P.js.map +0 -1
- package/dist/chunk-DSCMCVTV.js +0 -7
- package/dist/chunk-DSCMCVTV.js.map +0 -1
- package/dist/chunk-HPDHNIA3.js +0 -2
- package/dist/chunk-IM376SD6.js +0 -13
- package/dist/chunk-IM376SD6.js.map +0 -1
- package/dist/chunk-LEIYC3BF.js +0 -104
- package/dist/chunk-LEIYC3BF.js.map +0 -1
- package/dist/chunk-LY7JGDV7.js +0 -6
- package/dist/chunk-LY7JGDV7.js.map +0 -1
- package/dist/chunk-U364GNQX.js +0 -719
- package/dist/chunk-U364GNQX.js.map +0 -1
- package/dist/chunk-WZEYII2S.js +0 -6
- package/dist/chunk-WZEYII2S.js.map +0 -1
- package/dist/config-UOHEG4BW.js +0 -2
- package/dist/config-mutator-KSEBWKJH.js +0 -2
- package/dist/debug-TGAP7E7W.js +0 -2
- package/dist/inbox-O5UB3YQN.js.map +0 -1
- package/dist/loader-BGWA7IRY.js +0 -2
- package/dist/loader-LBHKSXBK.js +0 -2
- package/dist/memory-extract-KKPKAOPH.js +0 -2
- package/dist/program-2MDELQLX.js +0 -2
- package/dist/query-FZAL7AQD.js +0 -2
- package/dist/recipes-LZ56TA3X.js +0 -2
- package/dist/registry-ONJSILH4.js +0 -2
- package/dist/scheduler-XRM2RJIV.js +0 -2
- package/dist/store-4UCO56K2.js +0 -2
- package/dist/timers-QB4KYNAE.js +0 -2
- package/dist/types-ECMEC5BZ.js +0 -2
- package/dist/types-TTB6DJUE.js +0 -2
- package/dist/usage-dashboard-FQGPPABD.js +0 -803
- package/dist/usage-dashboard-FQGPPABD.js.map +0 -1
- package/dist/wiki-OUYKM7MB.js +0 -2
- package/dist/workspace-setup-6GDBEORT.js +0 -2
- /package/dist/{agent-RDVGL2TW.js.map → agent-57MG4LBM.js.map} +0 -0
- /package/dist/{audit-Z47AQFFD.js.map → audit-JU3HWISB.js.map} +0 -0
- /package/dist/{chunk-HPDHNIA3.js.map → chunk-4YCH6IZV.js.map} +0 -0
- /package/dist/{chunk-J4WKMKTP.js.map → chunk-DLVOHCTA.js.map} +0 -0
- /package/dist/{chunk-TRQSP67X.js.map → chunk-IKBHHTIT.js.map} +0 -0
- /package/dist/{chunk-JXEPJ3YD.js.map → chunk-TC3BSUP7.js.map} +0 -0
- /package/dist/{config-UOHEG4BW.js.map → chunk-VWTBHLMX.js.map} +0 -0
- /package/dist/{compaction-S5H6DTOR.js.map → compaction-BTUJJ3LJ.js.map} +0 -0
- /package/dist/{config-mutator-KSEBWKJH.js.map → config-QXWKEQON.js.map} +0 -0
- /package/dist/{debug-TGAP7E7W.js.map → config-mutator-53XGR2NT.js.map} +0 -0
- /package/dist/{context-planner-2RGHJHSO.js.map → context-planner-I7ID7XF3.js.map} +0 -0
- /package/dist/{heal-YP7QI7D3.js.map → debug-N3B5NVJU.js.map} +0 -0
- /package/dist/{discover-OATU7SHI.js.map → discover-XJXQXXIO.js.map} +0 -0
- /package/dist/{loader-BGWA7IRY.js.map → doctor-SEOUFHSL.js.map} +0 -0
- /package/dist/{editor-chat-FUBQLMAQ.js.map → editor-chat-RVLCKKB7.js.map} +0 -0
- /package/dist/{loader-LBHKSXBK.js.map → heal-YEQYWX2L.js.map} +0 -0
- /package/dist/{memory-extract-KKPKAOPH.js.map → inbox-BIN2S5XN.js.map} +0 -0
- /package/dist/{ingest-whatsapp-C3O5UPBX.js.map → ingest-whatsapp-LZE62INA.js.map} +0 -0
- /package/dist/{program-2MDELQLX.js.map → loader-5CA3O5GM.js.map} +0 -0
- /package/dist/{providers-WCQSKTNP.js.map → loader-6IHUCPVO.js.map} +0 -0
- /package/dist/{query-FZAL7AQD.js.map → memory-extract-KRCHD3OP.js.map} +0 -0
- /package/dist/{recipes-LZ56TA3X.js.map → processes-RJ6RDP32.js.map} +0 -0
- /package/dist/{registry-ONJSILH4.js.map → program-WWCXMRU2.js.map} +0 -0
- /package/dist/{scheduler-XRM2RJIV.js.map → providers-WVCMIW67.js.map} +0 -0
- /package/dist/{store-4UCO56K2.js.map → query-HZYYRCLU.js.map} +0 -0
- /package/dist/{task-store-3NUWLYRY.js.map → recipes-EPLE2SNT.js.map} +0 -0
- /package/dist/{timers-QB4KYNAE.js.map → registry-HM4ELSXS.js.map} +0 -0
- /package/dist/{types-ECMEC5BZ.js.map → scheduler-XVFIK4XH.js.map} +0 -0
- /package/dist/{slack-D27BE37Q.js.map → slack-6P7ZJ32Y.js.map} +0 -0
- /package/dist/{types-TTB6DJUE.js.map → store-BAPDZHXE.js.map} +0 -0
- /package/dist/{subagent-EPOT3QDM.js.map → subagent-WV7QKQ3L.js.map} +0 -0
- /package/dist/{wiki-OUYKM7MB.js.map → task-store-RHISEU6V.js.map} +0 -0
- /package/dist/{telegram-UWV67KWG.js.map → telegram-PIYDRM46.js.map} +0 -0
- /package/dist/{workspace-setup-6GDBEORT.js.map → timers-5LPZN55J.js.map} +0 -0
- /package/dist/{whatsapp-KJ2NRKKQ.js.map → whatsapp-PFRDMUMT.js.map} +0 -0
- /package/dist/{whatsapp-state-3ZVQ6MQD.js.map → whatsapp-state-XMTCSRXU.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
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/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/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/webhooks.ts","../src/storage/sqlite.ts","../src/storage/subscribers.ts","../src/agents/landscape.ts","../src/agents/agent-memory.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 \"Send a message to any channel (Telegram, WhatsApp, GitLab, Discord). Use for cross-channel notifications, proactive outbound messages, or relaying information between channels.\",\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_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_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\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 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\n // Only log on state transition (healthy→healthy is spammy when the\n // health check interval is 60s). Still log \"recovered\" transitions\n // so operators see a peer coming back.\n if (wasDown) {\n this.log(`Peer \"${name}\" recovered: ${card.name} (${state.agents.length} skills)`)\n } else {\n this.log(`Peer \"${name}\" healthy: ${card.name} (${state.agents.length} skills)`)\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 } 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(peerName: string, text: string, agentId?: string, opts: { timeoutMs?: number } = {}): 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({ agent, message: text }),\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 { getEventBus } from \"@/events/bus\"\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 if (adapter.name === \"telegram\" && accountId) {\n return this.adapterSend(adapter, { ...msg, accountId })\n }\n\n return adapter.send(msg)\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 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 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 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 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 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.\n if (msg.group && msg.channel === \"telegram\") {\n const boundAccount = this.getAccountForAgent(agentId)\n if (boundAccount && boundAccount !== msg.accountId) {\n this.traceRoute(msg, \"drop\", `multi-account-dedup (bound=${boundAccount} got=${msg.accountId})`, \"multi-account-dedup\")\n return\n }\n }\n\n this.traceRoute(msg, \"match\", `${result.decidingStage} agent=${agentId}`, result.decidingStage, agentId)\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\n const replyAccountId = this.getAccountForAgent(agentId) || 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 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 },\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 })\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 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\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 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 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): string | undefined {\n for (const [accountId, account] of Object.entries(this.config.channels.telegram.accounts)) {\n if (account.agentBinding === agentId) {\n return accountId\n }\n }\n return undefined\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 /** 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 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","// --- 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, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\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 } 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 const me = await this.apiCall(config.token, \"getMe\")\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}\": ${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 /**\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 return String(result.result?.message_id || \"\")\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 return String(result.result?.message_id || \"\")\n }\n throw e\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\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 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 const sent = await this.sock.sendMessage(jid, { text: msg.text })\n const sentId = sent?.key?.id || \"\"\n // Track to prevent loop (our own reply echoed back as fromMe)\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 /** 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 { WebhookHandler } from \"./webhooks\"\nimport { openDb } from \"@/storage/sqlite\"\nimport { attachSqliteSubscribers } from \"@/storage/subscribers\"\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 { 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 /** Structured per-agent memory (Claude-Code-style user / feedback /\n * project / reference). Deliberately owned by the daemon (not the\n * registry) so HTTP callers — including an agent curling from its\n * own Claude Code session — can write without going through the\n * dispatcher. Inlined into agent system prompts happens elsewhere. */\n private readonly agentMemory: AgentMemory = new AgentMemory()\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 // 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 // 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 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 } 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 // 1. Start channels\n await this.startChannels()\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 // 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 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 // 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 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 const resp = await this.registry.execute({\n agentId: req.agentId,\n message: req.message,\n workflowRunId: req.workflowRunId,\n timeoutMinutes: req.timeoutMinutes,\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 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\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 // Send current status as first event\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 req.on(\"close\", () => this.sseClients.delete(res))\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.registry.getTodayUsage(),\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 /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 // 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 // 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 },\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/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 skills: this.registry.list().map((a) => ({\n id: a.id,\n name: a.name,\n description: `Agent \"${a.name}\" (${a.tier})`,\n tags: [a.tier],\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 /** 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\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 } 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\"\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}\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 }\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 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 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 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 ${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 }\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 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 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 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 }\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 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 // Default: route generic issue event to the project's configured agent.\n const incoming: IncomingMessage = {\n id: `issue-${attrs.iid}-${attrs.action}`,\n channel: \"gitlab\",\n accountId: \"default\",\n sender: {\n id: `${project}:issue:${attrs.iid}`,\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 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: defaultAgentId,\n }\n\n this.handler(incoming).catch((e) => this.log(`Error handling issue: ${e.message}`))\n res.writeHead(200)\n res.end(\"ok\")\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\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 agentId = this.resolveAgent(project)\n\n const incoming: IncomingMessage = {\n id: `mr-${attrs.iid}-${attrs.action}`,\n channel: \"gitlab\",\n accountId: \"default\",\n sender: {\n id: `${project}:merge_request:${attrs.iid}`,\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 MR !${attrs.iid} ${attrs.action}]: ${attrs.title}\\nBranch: ${attrs.source_branch} -> ${attrs.target_branch}\\n${attrs.description?.slice(0, 500) || \"\"}\\nURL: ${attrs.url}`,\n timestamp: new Date(),\n raw: event,\n resolvedAgent: agentId,\n }\n\n this.handler(incoming).catch((e) => this.log(`Error handling MR: ${e.message}`))\n res.writeHead(200)\n res.end(\"ok\")\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 /**\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: \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:  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 { 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 / 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}\n\nexport class BotManager {\n private opts: BotManagerOptions\n private bots: Map<string, ActiveBot> = new Map()\n private whisper: Whisper\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 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 })\n return { ok: true }\n }\n\n /** Close the bot for a call (manual hangup, peer disconnect, or timeout). */\n tearDown(callId: string): 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 this.opts.log(`[bot-manager] tore down bot for call=${callId} (was up ${Math.round((Date.now() - active.startedAt) / 1000)}s)`)\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 /** Tear down every active bot (daemon shutdown). */\n shutdown(): void {\n for (const callId of [...this.bots.keys()]) this.tearDown(callId)\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","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 this.mesh.sendTask(meshEntry.node, summary, agentId).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\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","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, tasks\n ) VALUES (\n @agent_id, @model, @day,\n @input_tokens, @output_tokens, @cache_read_tokens, @cache_create_tokens, 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 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 if (!p.error && (p.inputTokens || p.outputTokens)) {\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 })\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 { 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(\"Remote:\")\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 lines.push(`• ${skill.name} [${peer.peer}] — ${skill.description.slice(0, 60)}`)\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, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from \"fs\"\nimport { resolve } from \"path\"\n\n// --- AgentMemory: Claude-Code-style structured memory per agent ---\n//\n// Four memory kinds, each a separate file with YAML frontmatter + markdown\n// body, gathered under a per-agent root index (MEMORY.md):\n//\n// user/ — \"who this human is, what they do, what they know\"\n// feedback/ — \"rules the user gave me; what to do / avoid\"\n// project/ — \"facts about current work the code can't reveal\"\n// reference/ — \"pointers to external systems (linear board, grafana, …)\"\n//\n// Layout on disk:\n// .agentx/agent-memory/<agentId>/\n// MEMORY.md — one-line hook per memory, auto-maintained\n// user_deep_backend_expertise.md\n// feedback_no_mock_database.md\n// ...\n//\n// Philosophy: the wiki is authoritative / cross-agent / documented.\n// Memory is experiential / per-agent / \"what I learned across runs.\" A\n// memory can be promoted to a wiki article (planned follow-up) after\n// multiple agents confirm the same fact, but we don't enforce that\n// here. Authors keep both layers parallel.\n\nexport type MemoryType = \"user\" | \"feedback\" | \"project\" | \"reference\"\n\nexport interface MemoryRecord {\n /** Slug-safe name, unique within an agent. */\n name: string\n type: MemoryType\n /** One-line hook — used as the MEMORY.md index entry. */\n description: string\n /** The body, excluding frontmatter. */\n body: string\n createdAt: string\n updatedAt: string\n}\n\nconst TYPES: MemoryType[] = [\"user\", \"feedback\", \"project\", \"reference\"]\n\nexport interface AgentMemoryOptions {\n baseDir?: string\n}\n\nexport class AgentMemory {\n readonly baseDir: string\n\n constructor(opts: AgentMemoryOptions = {}) {\n const root = opts.baseDir ?? resolve(process.cwd(), \".agentx\")\n this.baseDir = resolve(root, \"agent-memory\")\n }\n\n private dirFor(agentId: string): string {\n const safe = slug(agentId)\n const dir = resolve(this.baseDir, safe)\n mkdirSync(dir, { recursive: true })\n return dir\n }\n\n private fileFor(agentId: string, type: MemoryType, name: string): string {\n return resolve(this.dirFor(agentId), `${type}_${slug(name)}.md`)\n }\n\n private indexPath(agentId: string): string {\n return resolve(this.dirFor(agentId), \"MEMORY.md\")\n }\n\n /** Save (create or update) a memory. Keeps createdAt stable across\n * updates; bumps updatedAt. Always rewrites MEMORY.md. */\n save(args: {\n agentId: string\n type: MemoryType\n name: string\n description: string\n body: string\n }): MemoryRecord {\n if (!TYPES.includes(args.type)) throw new Error(`invalid memory type: ${args.type}`)\n if (!args.name.trim()) throw new Error(\"memory name is required\")\n if (!args.description.trim()) throw new Error(\"memory description is required\")\n\n const path = this.fileFor(args.agentId, args.type, args.name)\n const now = new Date().toISOString()\n let createdAt = now\n const existing = safeRead(path)\n if (existing) {\n const parsed = parseMemoryFile(existing)\n if (parsed) createdAt = parsed.createdAt\n }\n const record: MemoryRecord = {\n name: slug(args.name),\n type: args.type,\n description: args.description.trim(),\n body: args.body.trimEnd(),\n createdAt,\n updatedAt: now,\n }\n writeFileSync(path, serialize(record))\n this.rewriteIndex(args.agentId)\n return record\n }\n\n get(agentId: string, name: string): MemoryRecord | null {\n for (const type of TYPES) {\n const path = this.fileFor(agentId, type, name)\n if (!existsSync(path)) continue\n const parsed = parseMemoryFile(safeRead(path) ?? \"\")\n if (parsed) return parsed\n }\n return null\n }\n\n /** Every memory for an agent. Sorted by type then name for stable\n * index rendering. */\n list(agentId: string): MemoryRecord[] {\n const dir = this.dirFor(agentId)\n if (!existsSync(dir)) return []\n const out: MemoryRecord[] = []\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (!entry.isFile() || !entry.name.endsWith(\".md\") || entry.name === \"MEMORY.md\") continue\n const parsed = parseMemoryFile(safeRead(resolve(dir, entry.name)) ?? \"\")\n if (parsed) out.push(parsed)\n }\n return out.sort((a, b) => {\n const ta = TYPES.indexOf(a.type)\n const tb = TYPES.indexOf(b.type)\n if (ta !== tb) return ta - tb\n return a.name.localeCompare(b.name)\n })\n }\n\n /** Remove a memory by name. Returns true on success, false if none found. */\n remove(agentId: string, name: string): boolean {\n let removed = false\n for (const type of TYPES) {\n const path = this.fileFor(agentId, type, name)\n if (existsSync(path)) { unlinkSync(path); removed = true }\n }\n if (removed) this.rewriteIndex(agentId)\n return removed\n }\n\n /** The content of MEMORY.md — used at prompt-build time to inline into\n * the agent's system prompt. Returns \"\" when the agent has no\n * memories (so the prompt builder can no-op). */\n indexMarkdown(agentId: string): string {\n const path = this.indexPath(agentId)\n return safeRead(path) ?? \"\"\n }\n\n /** Push the agent's memory into its workspace so Claude Code sessions\n * pick it up automatically. Does two things:\n *\n * 1. Writes `<workspace>/.agentx-memory.md` — full MEMORY.md content,\n * explicit file the skill can `Read` at session start.\n * 2. Merges that same content into `<workspace>/CLAUDE.md` between\n * sentinel comments so Claude Code auto-loads it into the cached\n * system context on every session. Idempotent: subsequent syncs\n * replace the sentinel block.\n *\n * Never touches content outside the sentinel block — if the file\n * doesn't exist, creates it with just the sentinels. Safe to call\n * on any save / delete. */\n syncToWorkspace(agentId: string, workspacePath: string): void {\n if (!workspacePath) return\n const memory = this.indexMarkdown(agentId).trim()\n\n // 1. Explicit file for the `remember` skill to Read if it wants to.\n const explicitPath = resolve(workspacePath, \".agentx-memory.md\")\n if (memory) {\n writeFileSync(explicitPath, memory + \"\\n\")\n } else if (existsSync(explicitPath)) {\n unlinkSync(explicitPath)\n }\n\n // 2. Sentinel-merged into CLAUDE.md for auto-inject.\n const claudePath = resolve(workspacePath, \"CLAUDE.md\")\n const start = \"<!-- AGENTX-MEMORY-START — auto-managed by agentx; edit via `agentx memory` -->\"\n const end = \"<!-- AGENTX-MEMORY-END -->\"\n const block = memory\n ? `${start}\\n${memory}\\n${end}`\n : \"\"\n\n const existing = safeRead(claudePath) ?? \"\"\n const startIdx = existing.indexOf(start)\n const endIdx = existing.indexOf(end)\n\n let next: string\n if (startIdx >= 0 && endIdx > startIdx) {\n // Replace existing sentinel block (or remove it if memory is empty).\n const before = existing.slice(0, startIdx).replace(/\\n+$/, \"\")\n const after = existing.slice(endIdx + end.length).replace(/^\\n+/, \"\")\n next = block\n ? [before, block, after].filter(Boolean).join(\"\\n\\n\") + \"\\n\"\n : [before, after].filter(Boolean).join(\"\\n\\n\") + (before || after ? \"\\n\" : \"\")\n } else if (block) {\n // No prior block — append to the end of CLAUDE.md (creates the file\n // if needed). Keeps any existing content untouched.\n next = existing\n ? existing.replace(/\\n+$/, \"\") + \"\\n\\n\" + block + \"\\n\"\n : block + \"\\n\"\n } else {\n // Empty memory + no prior block → nothing to do.\n return\n }\n writeFileSync(claudePath, next)\n }\n\n /** Regenerate MEMORY.md from the on-disk files. Called automatically\n * after every save / remove; safe to call manually. */\n rewriteIndex(agentId: string): void {\n const entries = this.list(agentId)\n if (entries.length === 0) {\n const p = this.indexPath(agentId)\n if (existsSync(p)) unlinkSync(p)\n return\n }\n const grouped: Record<MemoryType, MemoryRecord[]> = {\n user: [], feedback: [], project: [], reference: [],\n }\n for (const r of entries) grouped[r.type].push(r)\n\n const lines: string[] = []\n lines.push(`# Memory — ${agentId}`, \"\")\n lines.push(\"This is the agent's experiential memory: user preferences,\")\n lines.push(\"feedback, project-specific context, and external references.\")\n lines.push(\"Updated via `agentx memory` CLI or by the agent itself.\")\n for (const type of TYPES) {\n const group = grouped[type]\n if (!group.length) continue\n lines.push(\"\", `## ${titleCase(type)}`, \"\")\n for (const r of group) {\n lines.push(`- **${r.name}** — ${r.description} _(updated ${r.updatedAt.slice(0, 10)})_`)\n }\n }\n lines.push(\"\")\n writeFileSync(this.indexPath(agentId), lines.join(\"\\n\"))\n }\n}\n\n// --- Serialisation helpers ---------------------------------------------\n\nfunction serialize(r: MemoryRecord): string {\n const frontmatter = [\n \"---\",\n `name: ${r.name}`,\n `description: ${escapeYaml(r.description)}`,\n `type: ${r.type}`,\n `created: ${r.createdAt}`,\n `updated: ${r.updatedAt}`,\n \"---\",\n \"\",\n ].join(\"\\n\")\n return frontmatter + r.body + \"\\n\"\n}\n\n/** Minimal frontmatter parser. Accepts `key: value` lines between `---`\n * markers; body is everything after. No YAML flow syntax — if we need\n * it later, swap for a proper parser. */\nexport function parseMemoryFile(raw: string): MemoryRecord | null {\n if (!raw.startsWith(\"---\\n\")) return null\n const end = raw.indexOf(\"\\n---\\n\", 4)\n if (end < 0) return null\n const header = raw.slice(4, end)\n const body = raw.slice(end + 5).replace(/^\\n+/, \"\").trimEnd()\n const fm: Record<string, string> = {}\n for (const line of header.split(\"\\n\")) {\n const colon = line.indexOf(\":\")\n if (colon < 0) continue\n const key = line.slice(0, colon).trim()\n const val = unescapeYaml(line.slice(colon + 1).trim())\n fm[key] = val\n }\n if (!fm.name || !fm.type || !fm.description) return null\n if (!TYPES.includes(fm.type as MemoryType)) return null\n return {\n name: fm.name,\n type: fm.type as MemoryType,\n description: fm.description,\n body,\n createdAt: fm.created ?? \"\",\n updatedAt: fm.updated ?? fm.created ?? \"\",\n }\n}\n\nfunction escapeYaml(s: string): string {\n if (/[:#\\n]/.test(s)) return `\"${s.replace(/\"/g, \"\\\\\\\"\")}\"`\n return s\n}\n\nfunction unescapeYaml(s: string): string {\n if (s.startsWith(\"\\\"\") && s.endsWith(\"\\\"\") && s.length >= 2) {\n return s.slice(1, -1).replace(/\\\\\"/g, \"\\\"\")\n }\n return s\n}\n\nfunction slug(s: string): string {\n return s.toLowerCase().replace(/[^a-z0-9]+/g, \"_\").replace(/^_+|_+$/g, \"\") || \"unnamed\"\n}\n\nfunction titleCase(s: string): string {\n return s.charAt(0).toUpperCase() + s.slice(1)\n}\n\nfunction safeRead(path: string): string | null {\n try { return readFileSync(path, \"utf-8\") } catch { return null }\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, BusinessRole } from \"./config\"\n\n// --- Organization: org-chart queries and validation ---\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\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\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","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 // __dirname is dist/ at runtime, so resolve relative to that.\n try {\n candidates.push(path.resolve(__dirname, \"..\", \"package.json\"))\n candidates.push(path.resolve(__dirname, \"package.json\"))\n } catch { /* __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":"mlBAkCA,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,mLACF,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,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,YA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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","agent","agentsRes","agentList","a","jobs","j","question","WikiHub","agenticQuery","wikiDir","hub","agentId","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","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","e","peerName","text","agentId","opts","agent","url","headers","timeoutMs","res","data","signal","want","key","skillId","a","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","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","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","unfinished","entry","e","k","t","seen","opts","hookResult","chatId","matched","replyAccountId","typingTimer","text","result","agentId","boundAccount","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","messageId","parseMode","emoji","sendTyping","kind","reason","decidingStage","chat","at","getEventBus","env","fromIncoming","runPipeline","defaultPipeline","textLower","directory","peer","skill","header","s","peerName","start","duration","account","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","aborts","id","next","policy","oldIds","newIds","added","removed","tokenChanged","oldCfg","newCfg","token","msg","maxLen","text","formatted","markdownToTelegramHtml","params","result","args","b","messageId","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","groupId","channelMeta","incoming","mcm","chatTitle","status","backoff","out","g","bots","groupTitle","info","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","sentId","chatId","messageId","emoji","contactRecordToSummary","chatRecordToSummary","record","cached","groupRecordToInfo","live","refreshed","opts","limit","sock","mapBaileysMessage","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","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","targetAgentId","agentMapping","agentNode","chatId","channelMeta","noteClean","stripAgentxMarkers","imageAttachment","incoming","attrs","defaultAgentId","authoredByBot","dispatch","hookBlocked","modDispatch","d","mention","route","agents","mentionedUsers","node","durationMs","totalSeconds","hours","minutes","duration","typeSegment","args","title","description","labels","assignees","r","users","issue","kind","add","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","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","BotManager","opts","Whisper","invite","existing","botName","chunker","AudioChunker","wav","durationMs","text","e","botOpts","frame","reason","bot","WebRtcBot","timeoutTimer","callId","active","now","b","_Logger","module","minLevel","msg","data","args","clean","a","level","entry","prefix","tag","display","Logger","__publicField","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","prepare","db","attachSqliteSubscribers","model","bus","getEventBus","stmts","pending","onStarted","p","key","onCompleted","start","id","status","day","onRotated","onMatched","onDropped","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","handles","portStr","port","parts","ch","pol","m","first","existsSync","mkdirSync","readdirSync","readFileSync","unlinkSync","writeFileSync","resolve","TYPES","AgentMemory","opts","root","agentId","safe","slug","dir","type","name","args","path","now","createdAt","existing","safeRead","parsed","parseMemoryFile","record","serialize","out","entry","a","b","ta","tb","removed","workspacePath","memory","explicitPath","claudePath","start","end","block","startIdx","endIdx","next","before","after","entries","p","grouped","r","lines","group","titleCase","escapeYaml","raw","header","body","fm","line","colon","key","val","unescapeYaml","s","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","e","chain","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","configPath","baseLog","Logger","args","line","loadDaemonConfig","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","meshPeers","p","yesterday","appended","err","cronJobs","job","status","removed","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","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","project","noteableType","noteableIid","noteId","url","respText","data","durationMs","githubConfig","GitHubAdapter","repo","issueNumber","wrtcCfg","WebRtcSignalBroker","urlBase","link","botCfg","iceServers","urls","BotManager","invite","dest","stamp","cfg","store","WorkflowStore","runs","RunStore","channels","name","adapter","agents","req","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","host","port","createServer","res","client","a","_agentId","taskId","closed","send","ev","sub","chunk","heartbeat","callId","as","unsubscribe","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","result","parsed","taskStreamMatch","taskHistoryListMatch","taskRecordMatch","rec","j","getWhatsAppState","s","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","messageId","contextStrategy","response","message","voicePrompt","speakable","toSpeakable","wrtc","hub","after","entries","index","articlePath","article","items","pathMatch","fallbackAgent","lastUserMsg","historyLines","contextPrefix","requestId","content","tokens","ws","skillsDir","skillPath","REMEMBER_SKILL_FILENAME","REMEMBER_SKILL_BODY","def","mcp","syncMcpToWorkspace","mem","oneMatch","type","description","newBody","append","finalBody","existing","delMatch","d","formSubmissionSchema","tasks","match","submissionRaw","i","submittedBy","entityRef","kind","fromPeer","n","trigger","channel","chatId","opts","seen","chats","sources","add","source","added","local","wf","triggerNode","force","entityId","eventId","runId","reject","raw","prev","changed","ids","f","handleError","error","logger","path","fs","getPackageInfo","candidates","p","pkg"]}
|