agentix-cli 0.17.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/dist/agent-RDVGL2TW.js +2 -0
  2. package/dist/audit-Z47AQFFD.js +3 -0
  3. package/dist/audit-Z47AQFFD.js.map +1 -0
  4. package/dist/{board-dashboard-CXIVJKDP.js → board-dashboard-KX2CNOSY.js} +44 -44
  5. package/dist/board-dashboard-KX2CNOSY.js.map +1 -0
  6. package/dist/{chunk-EKTPCSN4.js → chunk-4ITQP3XL.js} +2 -2
  7. package/dist/chunk-53NRX2NA.js +2 -0
  8. package/dist/chunk-53NRX2NA.js.map +1 -0
  9. package/dist/{chunk-5WNGHDFC.js → chunk-ASSJ4Z6Z.js} +11 -11
  10. package/dist/{chunk-5WNGHDFC.js.map → chunk-ASSJ4Z6Z.js.map} +1 -1
  11. package/dist/chunk-BLUXL6HR.js +854 -0
  12. package/dist/chunk-BLUXL6HR.js.map +1 -0
  13. package/dist/chunk-BTAEYL3P.js +72 -0
  14. package/dist/chunk-BTAEYL3P.js.map +1 -0
  15. package/dist/chunk-DJEZ2HVU.js +5 -0
  16. package/dist/chunk-DJEZ2HVU.js.map +1 -0
  17. package/dist/{chunk-SRHLXZRF.js → chunk-DSCMCVTV.js} +3 -3
  18. package/dist/chunk-DSCMCVTV.js.map +1 -0
  19. package/dist/chunk-HPDHNIA3.js +2 -0
  20. package/dist/chunk-IM376SD6.js +13 -0
  21. package/dist/chunk-IM376SD6.js.map +1 -0
  22. package/dist/chunk-J4WKMKTP.js +5 -0
  23. package/dist/{chunk-MBEQASP3.js.map → chunk-J4WKMKTP.js.map} +1 -1
  24. package/dist/chunk-JGXE2YNV.js +46 -0
  25. package/dist/chunk-JGXE2YNV.js.map +1 -0
  26. package/dist/{chunk-TT2Q7YMZ.js → chunk-JXEPJ3YD.js} +6 -6
  27. package/dist/{chunk-TT2Q7YMZ.js.map → chunk-JXEPJ3YD.js.map} +1 -1
  28. package/dist/chunk-LY7JGDV7.js +6 -0
  29. package/dist/chunk-LY7JGDV7.js.map +1 -0
  30. package/dist/chunk-SR3X25TN.js +3 -0
  31. package/dist/chunk-SR3X25TN.js.map +1 -0
  32. package/dist/{chunk-63UYRJIC.js → chunk-TRQSP67X.js} +2 -2
  33. package/dist/chunk-U364GNQX.js +719 -0
  34. package/dist/chunk-U364GNQX.js.map +1 -0
  35. package/dist/chunk-VJUTQAPJ.js +2 -0
  36. package/dist/chunk-VJUTQAPJ.js.map +1 -0
  37. package/dist/cli.js +1 -1
  38. package/dist/cli.js.map +1 -1
  39. package/dist/{compaction-X46EQEPS.js → compaction-S5H6DTOR.js} +4 -4
  40. package/dist/config-UOHEG4BW.js +2 -0
  41. package/dist/config-mutator-KSEBWKJH.js +2 -0
  42. package/dist/{context-planner-G2J7FNNU.js → context-planner-2RGHJHSO.js} +3 -3
  43. package/dist/debug-TGAP7E7W.js +2 -0
  44. package/dist/discover-OATU7SHI.js +4 -0
  45. package/dist/discover-OATU7SHI.js.map +1 -0
  46. package/dist/{editor-chat-R54E65W5.js → editor-chat-FUBQLMAQ.js} +2 -2
  47. package/dist/heal-YP7QI7D3.js +2 -0
  48. package/dist/{inbox-L5ZZ623Y.js → inbox-O5UB3YQN.js} +2 -2
  49. package/dist/index.d.ts +462 -22
  50. package/dist/index.js +8 -8
  51. package/dist/index.js.map +1 -1
  52. package/dist/{ingest-whatsapp-NS4OWDOU.js → ingest-whatsapp-C3O5UPBX.js} +2 -2
  53. package/dist/loader-BGWA7IRY.js +2 -0
  54. package/dist/loader-LBHKSXBK.js +2 -0
  55. package/dist/memory-extract-KKPKAOPH.js +2 -0
  56. package/dist/{processes-XF5CV4LK.js → processes-7W27G3J3.js} +2 -2
  57. package/dist/program-2MDELQLX.js +2 -0
  58. package/dist/providers-WCQSKTNP.js +2 -0
  59. package/dist/query-FZAL7AQD.js +2 -0
  60. package/dist/recipes-LZ56TA3X.js +2 -0
  61. package/dist/registry-ONJSILH4.js +2 -0
  62. package/dist/scheduler-XRM2RJIV.js +2 -0
  63. package/dist/{slack-PSQLVTGB.js → slack-D27BE37Q.js} +2 -2
  64. package/dist/store-4UCO56K2.js +2 -0
  65. package/dist/{subagent-2SJHSSJS.js → subagent-EPOT3QDM.js} +2 -2
  66. package/dist/{task-store-NM7VKNLE.js → task-store-3NUWLYRY.js} +2 -2
  67. package/dist/{telegram-W4SLFMNY.js → telegram-UWV67KWG.js} +2 -2
  68. package/dist/timers-QB4KYNAE.js +2 -0
  69. package/dist/types-ECMEC5BZ.js +2 -0
  70. package/dist/types-TTB6DJUE.js +2 -0
  71. package/dist/{usage-dashboard-AXPXB2KP.js → usage-dashboard-FQGPPABD.js} +2 -2
  72. package/dist/web/workflow-editor.global.js +35 -12
  73. package/dist/web/workflow-editor.global.js.map +1 -1
  74. package/dist/{whatsapp-DCHRBRVV.js → whatsapp-KJ2NRKKQ.js} +2 -2
  75. package/dist/{whatsapp-state-6D6FHHD2.js → whatsapp-state-3ZVQ6MQD.js} +2 -2
  76. package/dist/{wiki-UDPYFJBL.js → wiki-OUYKM7MB.js} +2 -2
  77. package/dist/wiki-OUYKM7MB.js.map +1 -0
  78. package/dist/workspace-setup-6GDBEORT.js +2 -0
  79. package/dist/workspace-setup-6GDBEORT.js.map +1 -0
  80. package/package.json +11 -1
  81. package/dist/agent-4AOMVXRJ.js +0 -2
  82. package/dist/board-dashboard-CXIVJKDP.js.map +0 -1
  83. package/dist/chunk-4HP6UC2X.js +0 -13
  84. package/dist/chunk-4HP6UC2X.js.map +0 -1
  85. package/dist/chunk-ABCHHNG6.js +0 -625
  86. package/dist/chunk-ABCHHNG6.js.map +0 -1
  87. package/dist/chunk-FIOB3MEX.js +0 -2
  88. package/dist/chunk-HCFSHGK4.js +0 -46
  89. package/dist/chunk-HCFSHGK4.js.map +0 -1
  90. package/dist/chunk-HHJGKD5B.js +0 -71
  91. package/dist/chunk-HHJGKD5B.js.map +0 -1
  92. package/dist/chunk-MBEQASP3.js +0 -5
  93. package/dist/chunk-MCJUAE6V.js +0 -6
  94. package/dist/chunk-MCJUAE6V.js.map +0 -1
  95. package/dist/chunk-SRHLXZRF.js.map +0 -1
  96. package/dist/chunk-XDJSYQST.js +0 -6
  97. package/dist/chunk-XDJSYQST.js.map +0 -1
  98. package/dist/chunk-YPOELN6Z.js +0 -798
  99. package/dist/chunk-YPOELN6Z.js.map +0 -1
  100. package/dist/config-KCYHMKCT.js +0 -2
  101. package/dist/config-mutator-QP4GLBOE.js +0 -2
  102. package/dist/debug-3MEUO2P2.js +0 -2
  103. package/dist/heal-JDXUPRET.js +0 -2
  104. package/dist/loader-PTCXLJEX.js +0 -2
  105. package/dist/memory-extract-WH4FZ456.js +0 -2
  106. package/dist/program-FFWEILAS.js +0 -2
  107. package/dist/providers-AKQ4HTB4.js +0 -2
  108. package/dist/query-PKGYE5HR.js +0 -2
  109. package/dist/registry-IFMDJWQ7.js +0 -2
  110. package/dist/scheduler-63D5KFO7.js +0 -2
  111. package/dist/store-5MJOO74U.js +0 -2
  112. package/dist/timers-PCB4ZIBD.js +0 -2
  113. package/dist/types-C7IFH42Z.js +0 -2
  114. package/dist/types-OIFU3KSQ.js +0 -2
  115. package/dist/workspace-setup-C4VB3QZV.js +0 -2
  116. /package/dist/{agent-4AOMVXRJ.js.map → agent-RDVGL2TW.js.map} +0 -0
  117. /package/dist/{chunk-EKTPCSN4.js.map → chunk-4ITQP3XL.js.map} +0 -0
  118. /package/dist/{chunk-FIOB3MEX.js.map → chunk-HPDHNIA3.js.map} +0 -0
  119. /package/dist/{chunk-63UYRJIC.js.map → chunk-TRQSP67X.js.map} +0 -0
  120. /package/dist/{compaction-X46EQEPS.js.map → compaction-S5H6DTOR.js.map} +0 -0
  121. /package/dist/{config-KCYHMKCT.js.map → config-UOHEG4BW.js.map} +0 -0
  122. /package/dist/{config-mutator-QP4GLBOE.js.map → config-mutator-KSEBWKJH.js.map} +0 -0
  123. /package/dist/{context-planner-G2J7FNNU.js.map → context-planner-2RGHJHSO.js.map} +0 -0
  124. /package/dist/{debug-3MEUO2P2.js.map → debug-TGAP7E7W.js.map} +0 -0
  125. /package/dist/{editor-chat-R54E65W5.js.map → editor-chat-FUBQLMAQ.js.map} +0 -0
  126. /package/dist/{heal-JDXUPRET.js.map → heal-YP7QI7D3.js.map} +0 -0
  127. /package/dist/{inbox-L5ZZ623Y.js.map → inbox-O5UB3YQN.js.map} +0 -0
  128. /package/dist/{ingest-whatsapp-NS4OWDOU.js.map → ingest-whatsapp-C3O5UPBX.js.map} +0 -0
  129. /package/dist/{loader-PTCXLJEX.js.map → loader-BGWA7IRY.js.map} +0 -0
  130. /package/dist/{memory-extract-WH4FZ456.js.map → loader-LBHKSXBK.js.map} +0 -0
  131. /package/dist/{program-FFWEILAS.js.map → memory-extract-KKPKAOPH.js.map} +0 -0
  132. /package/dist/{processes-XF5CV4LK.js.map → processes-7W27G3J3.js.map} +0 -0
  133. /package/dist/{providers-AKQ4HTB4.js.map → program-2MDELQLX.js.map} +0 -0
  134. /package/dist/{query-PKGYE5HR.js.map → providers-WCQSKTNP.js.map} +0 -0
  135. /package/dist/{registry-IFMDJWQ7.js.map → query-FZAL7AQD.js.map} +0 -0
  136. /package/dist/{scheduler-63D5KFO7.js.map → recipes-LZ56TA3X.js.map} +0 -0
  137. /package/dist/{store-5MJOO74U.js.map → registry-ONJSILH4.js.map} +0 -0
  138. /package/dist/{task-store-NM7VKNLE.js.map → scheduler-XRM2RJIV.js.map} +0 -0
  139. /package/dist/{slack-PSQLVTGB.js.map → slack-D27BE37Q.js.map} +0 -0
  140. /package/dist/{timers-PCB4ZIBD.js.map → store-4UCO56K2.js.map} +0 -0
  141. /package/dist/{subagent-2SJHSSJS.js.map → subagent-EPOT3QDM.js.map} +0 -0
  142. /package/dist/{types-C7IFH42Z.js.map → task-store-3NUWLYRY.js.map} +0 -0
  143. /package/dist/{telegram-W4SLFMNY.js.map → telegram-UWV67KWG.js.map} +0 -0
  144. /package/dist/{types-OIFU3KSQ.js.map → timers-QB4KYNAE.js.map} +0 -0
  145. /package/dist/{wiki-UDPYFJBL.js.map → types-ECMEC5BZ.js.map} +0 -0
  146. /package/dist/{workspace-setup-C4VB3QZV.js.map → types-TTB6DJUE.js.map} +0 -0
  147. /package/dist/{usage-dashboard-AXPXB2KP.js.map → usage-dashboard-FQGPPABD.js.map} +0 -0
  148. /package/dist/{whatsapp-DCHRBRVV.js.map → whatsapp-KJ2NRKKQ.js.map} +0 -0
  149. /package/dist/{whatsapp-state-6D6FHHD2.js.map → whatsapp-state-3ZVQ6MQD.js.map} +0 -0
@@ -0,0 +1,719 @@
1
+ import{c as Ct,d as Mt,f as Rt,g as Pt,h as _t,j as jt}from"./chunk-IM376SD6.js";import{b as Et}from"./chunk-ZE2YFX4K.js";import{A as It,B as xt,h as kt,n as vt,o as qe}from"./chunk-JXEPJ3YD.js";import{b as Ye}from"./chunk-DSCMCVTV.js";import{e as Ke,f as At}from"./chunk-LY7JGDV7.js";import{e as ee,f as St,h as Tt}from"./chunk-BTAEYL3P.js";import{a as $t}from"./chunk-5F3J55LG.js";import{b as yt}from"./chunk-SFQUP3BP.js";import{a as pe}from"./chunk-VJUTQAPJ.js";import{a as bt,d as wt}from"./chunk-DJEZ2HVU.js";import{e as U}from"./chunk-DSYKYZMT.js";import{a as ue}from"./chunk-HPDHNIA3.js";var Cn={name:"agentx",version:"0.1.0"},Mn="2024-11-05",Rn={tools:{},elicitation:{form:{}}},Pn=0;async function Ot(h,e){let t=++Pn;return W({jsonrpc:"2.0",id:`elicit-${t}`,method:"elicitation/create",params:{mode:"form",message:h,requestedSchema:e}}),new Promise(n=>{te.set(`elicit-${t}`,s=>{s?.action==="accept"&&s.content?n(s.content):n(null)}),setTimeout(()=>{te.has(`elicit-${t}`)&&(te.delete(`elicit-${t}`),n(null))},6e4)})}var te=new Map,j=process.env.AGENTX_DAEMON_URL||"http://localhost:19900",_n=/\x1B\[[0-9;]*[A-Za-z]/g;function Y(h){return h.replace(_n,"")}var En=[{name:"agentx_generate",description:"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.",inputSchema:{type:"object",properties:{task:{type:"string",description:"Describe what to generate (e.g., 'a responsive pricing card', 'REST API for users', 'GitHub Actions CI pipeline')"},type:{type:"string",description:"Output type: component, page, api, website, document, script, config, skill, media, report, test, workflow, schema, email, diagram, auto",default:"auto"},output_dir:{type:"string",description:"Optional output directory (relative to project root)"},cwd:{type:"string",description:"Project working directory (defaults to current directory)"}},required:["task"]}},{name:"agentx_inspect",description:"Analyze a project and return its tech stack, frameworks, databases, schemas, installed skills, and dependencies. Use this to understand a project before generating code.",inputSchema:{type:"object",properties:{cwd:{type:"string",description:"Project working directory (defaults to current directory)"}}}},{name:"agentx_skill_match",description:"Find installed skills that are relevant to a given task description. Returns matched skills with relevance scores.",inputSchema:{type:"object",properties:{task:{type:"string",description:"The task to match skills against"},cwd:{type:"string",description:"Project working directory"}},required:["task"]}},{name:"agentx_detect_output_type",description:"Auto-detect the best output type for a given task description based on keyword analysis.",inputSchema:{type:"object",properties:{task:{type:"string",description:"The task description to analyze"}},required:["task"]}},{name:"agentx_send",description:"Send a message to any channel (Telegram, WhatsApp, GitLab, Discord). Use for cross-channel notifications, proactive outbound messages, or relaying information between channels.",inputSchema:{type:"object",properties:{channel:{type:"string",description:"Target channel: telegram, whatsapp, gitlab, discord"},chatId:{type:"string",description:'Chat ID. Telegram: numeric ("-1001234567890"). GitLab: "group/project:issue:123". WhatsApp: "+phone@s.whatsapp.net".'},text:{type:"string",description:"Message text to send"},agentId:{type:"string",description:"Agent ID to send as (determines bot account on Telegram, GitLab token, etc.)"}},required:["channel","chatId","text"]}},{name:"agentx_task",description:"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.",inputSchema:{type:"object",properties:{agent:{type:"string",description:"Agent ID to send the task to"},message:{type:"string",description:"Task message for the agent"}},required:["agent","message"]}},{name:"agentx_agents",description:"List all agents registered on the daemon with their status (active tasks, total tasks, errors, tier).",inputSchema:{type:"object",properties:{}}},{name:"agentx_health",description:"Get the daemon health status including node info, agents, crons, mesh peers, and uptime.",inputSchema:{type:"object",properties:{}}},{name:"agentx_crons",description:"List cron jobs and their health. Shows healthy, failing, disabled counts and per-job status with consecutive error counts.",inputSchema:{type:"object",properties:{}}},{name:"agentx_debug",description:"Toggle debug mode on the daemon. Enable verbose logging for specific categories (webhook, agent, channel, cron, mesh, context, memory, all) or disable it.",inputSchema:{type:"object",properties:{action:{type:"string",description:'"on", "off", or "status"',default:"status"},categories:{type:"string",description:'Comma-separated categories to enable (e.g. "webhook,agent"). Only used with action=on.'}}}},{name:"agentx_wiki_query",description:"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.",inputSchema:{type:"object",properties:{question:{type:"string",description:"The question to answer from the wiki, in natural language."},agent:{type:"string",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>/"},wiki_dir:{type:"string",description:"Wiki root dir (default: <cwd>/.agentx/wiki)."},max_hops:{type:"number",description:"Wikilink hops from candidates (default 2, max 3).",default:2}},required:["question"]}},{name:"agentx_wiki_patch",description:"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.",inputSchema:{type:"object",properties:{agent:{type:"string",description:"Agent ID whose wiki owns the article (required)."},title_or_path:{type:"string",description:"Article title (case-insensitive) or relative path like 'people/anis.md'."},instruction:{type:"string",description:"Plain-English edit instruction (e.g. 'Add a section on the KSA Supabase rollout'). The LLM makes the minimum edit that satisfies it."},model:{type:"string",description:"Claude model for the patch (default: sonnet).",default:"sonnet"},dry_run:{type:"boolean",description:"Preview the patched body without writing.",default:!1}},required:["agent","title_or_path","instruction"]}},{name:"agentx_wiki_interview",description:"Run a scripted wiki interview \u2014 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`.",inputSchema:{type:"object",properties:{agent:{type:"string",description:"Agent ID that will own the new article (required)."},topic:{type:"string",description:"What the article is about \u2014 e.g. 'MTGL deployment procedure', 'Yousef Al-Fahad', 'Hackathonat KSA migration'."},type:{type:"string",description:"Article type: person | project | place | concept | event | decision | pattern"},answers:{type:"array",description:"Answers to the type-specific question bank (one per question, in order). Empty string = skip that question. Final entry is typically 'save'.",items:{type:"string"}},model:{type:"string",description:"Synthesis model (default: sonnet).",default:"sonnet"},commit:{type:"boolean",description:"Write the article (true) or just preview (false).",default:!0}},required:["agent","topic","type","answers"]}},{name:"agentx_graph_review",description:"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.",inputSchema:{type:"object",properties:{max:{type:"number",description:"Cap reviews this run (default 20).",default:20},dry_run:{type:"boolean",description:"Show decisions but don't apply approvals/rejections.",default:!1},agent:{type:"string",description:"Override the review agent (defaults to graph.reviewAgent or graph.draftAgent)."}}}}];async function jn(h,e){let t=e.cwd||process.cwd();switch(h){case"agentx_generate":{let n=e.task,s=e.type||"auto",i=e.output_dir,r=await xt({task:n,outputType:s,outputDir:i,cwd:t,overwrite:!0,dryRun:!1,context7:!0,interactive:!1,maxSteps:5}),o=[];return r.content&&o.push(r.content),r.files.written.length&&o.push(`
2
+ Created ${r.files.written.length} file(s):
3
+ ${r.files.written.map(a=>` - ${a}`).join(`
4
+ `)}`),r.files.skipped.length&&o.push(`
5
+ Skipped ${r.files.skipped.length} existing file(s):
6
+ ${r.files.skipped.map(a=>` - ${a}`).join(`
7
+ `)}`),r.followUp&&o.push(`
8
+ Needs clarification: ${r.followUp}`),{content:[{type:"text",text:o.join(`
9
+ `)||"Generation complete."}]}}case"agentx_inspect":{let n=await It(t,"inspect",{context7:{enabled:!1}}),s={languages:n.techStack.languages,frameworks:n.techStack.frameworks,packageManager:n.techStack.packageManager,databases:n.techStack.databases,styling:n.techStack.styling,testing:n.techStack.testing,deployment:n.techStack.deployment,monorepo:n.techStack.monorepo,srcDir:n.techStack.srcDir,dependencyCount:Object.keys(n.techStack.dependencies).length,devDependencyCount:Object.keys(n.techStack.devDependencies).length,schemas:{database:n.schemas.database?{type:n.schemas.database.type,tables:n.schemas.database.tables}:null,api:n.schemas.api?{type:n.schemas.api.type}:null,env:n.schemas.env?{variableCount:n.schemas.env.variables.length}:null,models:n.schemas.models?.map(i=>i.path)||[]},skills:n.skills.map(i=>({name:i.frontmatter.name,description:i.frontmatter.description,source:i.source}))};return{content:[{type:"text",text:`Project analysis:
10
+
11
+ ${yt(n.techStack)}
12
+
13
+ ${JSON.stringify(s,null,2)}`}]}}case"agentx_skill_match":{let n=e.task,s=await bt(t),i=wt(s,n);return i.length?{content:[{type:"text",text:`Matching skills:
14
+
15
+ ${i.map(o=>`- **${o.skill.frontmatter.name}** (relevance: ${(o.relevance*100).toFixed(0)}%)
16
+ ${o.skill.frontmatter.description}
17
+ Match: ${o.matchReason}`).join(`
18
+
19
+ `)}`}]}:{content:[{type:"text",text:"No matching skills found. Install skills with: agentx skill install <owner/repo>"}]}}case"agentx_detect_output_type":{let n=e.task;return{content:[{type:"text",text:`Detected output type: ${kt(void 0,n)}`}]}}case"agentx_send":{let n=e.channel,s=e.chatId,i=e.text;if(!n||!s||!i){let a=await fetch(`${j}/channels`).catch(()=>null),c=a?await a.json():["telegram","whatsapp","gitlab","discord"],l=await Ot("Please provide the message details:",{type:"object",properties:{channel:{type:"string",title:"Channel",description:"Target channel",enum:c,default:n||c[0]},chatId:{type:"string",title:"Chat ID",description:"Telegram: numeric ID. GitLab: group/project:issue:123",default:s||""},text:{type:"string",title:"Message",description:"Message text to send",default:i||""}},required:["channel","chatId","text"]});if(!l)return{content:[{type:"text",text:"Send cancelled."}]};n=l.channel,s=l.chatId,i=l.text}let r=await fetch(`${j}/send`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({channel:n,chatId:s,text:i,agentId:e.agentId})}),o=await r.json();return r.ok?{content:[{type:"text",text:`Message sent. ID: ${o.messageId||"ok"}`}]}:{content:[{type:"text",text:`Error: ${o.error||r.statusText}`}]}}case"agentx_task":{let n=e.agent,s=e.message;if(!n||!s){let o=await fetch(`${j}/agents`).catch(()=>null),a=o?(await o.json()).map(l=>l.id):[],c=await Ot("Which agent should handle this task?",{type:"object",properties:{agent:{type:"string",title:"Agent",description:"Agent ID",...a.length?{enum:a}:{},default:n||""},message:{type:"string",title:"Task",description:"Task message for the agent",default:s||""}},required:["agent","message"]});if(!c)return{content:[{type:"text",text:"Task cancelled."}]};n=c.agent,s=c.message}let r=await(await fetch(`${j}/task`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({agent:n,message:s})})).json();return r.error?{content:[{type:"text",text:`Agent error: ${r.error}`}]}:{content:[{type:"text",text:r.content||"Task completed."}]}}case"agentx_agents":return{content:[{type:"text",text:(await(await fetch(`${j}/agents`)).json()).map(r=>`${r.id} (${r.name}) \u2014 ${r.tier}, active: ${r.active}/${r.total}, errors: ${r.errors}`).join(`
20
+ `)||"No agents."}]};case"agentx_health":{let s=await(await fetch(`${j}/health`)).json();return{content:[{type:"text",text:JSON.stringify(s,null,2)}]}}case"agentx_crons":{let s=await(await fetch(`${j}/crons/health`)).json(),i=`Healthy: ${s.healthy}, Failing: ${s.failing}, Disabled: ${s.disabled}, Missed: ${s.missed}`,r=(s.jobs||[]).map(o=>` ${o.id}: ${o.status}${o.consecutiveErrors?` (${o.consecutiveErrors} errors)`:""}${o.lastError?` \u2014 ${o.lastError.slice(0,100)}`:""}`).join(`
21
+ `);return{content:[{type:"text",text:`${i}
22
+
23
+ ${r}`}]}}case"agentx_wiki_query":{let n=String(e.question||"").trim();if(!n)return{content:[{type:"text",text:"Error: `question` is required."}]};let{WikiHub:s}=await import("./wiki-OUYKM7MB.js"),{agenticQuery:i}=await import("./query-FZAL7AQD.js"),{resolve:r}=await import("path"),o=e.wiki_dir||r(process.cwd(),".agentx/wiki"),a=new s(o,void 0,"graph"),c=e.agent||"";if(!c){let{existsSync:f}=await import("fs");for(let b of a.listAgents()){let y=r(a.getAgentWiki(b).baseDir,"_index.md");if(f(y)){c=b;break}}}if(!c)return{content:[{type:"text",text:"Error: no agent has a wiki catalog yet. Run `agentx wiki absorb` first."}]};let l=a.getAgentWiki(c),d=typeof e.max_hops=="number"?Math.min(3,Math.max(0,e.max_hops)):2,g=await i(n,l,c,{maxHops:d});if(g.status!=="ok")return{content:[{type:"text",text:`Query returned status "${g.status}"${g.error?`: ${g.error}`:""}`}]};let p=g.citations.map(f=>` - ${f.title} [${f.type||"?"}] (${f.path})`).join(`
24
+ `),u=g.walked.length;return{content:[{type:"text",text:`${g.answer}
25
+
26
+ Citations (${u} article${u===1?"":"s"} walked):
27
+ ${p}`}]}}case"agentx_wiki_patch":{let n=String(e.agent||"").trim(),s=String(e.title_or_path||"").trim(),i=String(e.instruction||"").trim();if(!n||!s||!i)return{content:[{type:"text",text:"Error: `agent`, `title_or_path`, and `instruction` are required."}]};let r=String(e.model||"sonnet"),o=e.dry_run===!0,{execFileSync:a}=await import("child_process"),c=["wiki","patch",n,s,i,"--patch-model",r,...o?["--no-commit"]:["--yes"]];try{let l=a(process.execPath,[process.argv[1],...c],{cwd:t,encoding:"utf-8",timeout:18e4,maxBuffer:8388608});return{content:[{type:"text",text:Y(l).trim()||"(no output)"}]}}catch(l){return{content:[{type:"text",text:`patch failed:
28
+ ${[l.stdout,l.stderr,l.message].filter(Boolean).map(g=>Y(String(g))).join(`
29
+ `).trim().slice(0,2e3)}`}]}}}case"agentx_wiki_interview":{let n=String(e.agent||"").trim(),s=String(e.topic||"").trim(),i=String(e.type||"").trim().toLowerCase(),r=Array.isArray(e.answers)?e.answers.map(w=>String(w??"")):null;if(!n||!s||!i||!r||r.length===0)return{content:[{type:"text",text:"Error: `agent`, `topic`, `type`, and non-empty `answers[]` are required."}]};let o=String(e.model||"sonnet"),a=e.commit!==!1,c=r[r.length-1]?.toLowerCase(),l=["save","edit","scrap"].includes(c)?r:[...r,"save"],{execFileSync:d}=await import("child_process"),{writeFileSync:g,mkdtempSync:p}=await import("fs"),{tmpdir:u}=await import("os"),{join:m}=await import("path"),f=p(m(u(),"agentx-mcp-interview-")),b=m(f,"answers.txt");g(b,l.join(`
30
+ `));let y=["wiki","interview","--agent",n,"--topic",s,"--type",i,"--model",o,"--answers",b,...a?[]:["--no-commit"]];try{let w=d(process.execPath,[process.argv[1],...y],{cwd:t,encoding:"utf-8",timeout:24e4,maxBuffer:8388608});return{content:[{type:"text",text:Y(w).trim()||"(no output)"}]}}catch(w){return{content:[{type:"text",text:`interview failed:
31
+ ${[w.stdout,w.stderr,w.message].filter(Boolean).map(k=>Y(String(k))).join(`
32
+ `).trim().slice(0,2e3)}`}]}}}case"agentx_graph_review":{let n=typeof e.max=="number"?Math.max(1,e.max):20,s=e.dry_run===!0,i=e.agent?String(e.agent):"",{execFileSync:r}=await import("child_process"),o=["graph","review","--max",String(n),...s?["--dry-run"]:[],...i?["--agent",i]:[]];try{let a=r(process.execPath,[process.argv[1],...o],{cwd:t,encoding:"utf-8",timeout:6e5,maxBuffer:8388608});return{content:[{type:"text",text:Y(a).trim()||"(no output)"}]}}catch(a){return{content:[{type:"text",text:`graph review failed:
33
+ ${[a.stdout,a.stderr,a.message].filter(Boolean).map(l=>Y(String(l))).join(`
34
+ `).trim().slice(0,2e3)}`}]}}}case"agentx_debug":{let n=e.action||"status";if(n==="on"){let s=e.categories||"all";return await fetch(`${j}/debug/on?categories=${s}`,{method:"POST"}),{content:[{type:"text",text:`Debug enabled: ${s}`}]}}else{if(n==="off")return await fetch(`${j}/debug/off`,{method:"POST"}),{content:[{type:"text",text:"Debug disabled."}]};{let i=await(await fetch(`${j}/debug`)).json();return{content:[{type:"text",text:JSON.stringify(i,null,2)}]}}}}default:throw new Error(`Unknown tool: ${h}`)}}function Dt(h,e){let t;try{t=JSON.parse(h)}catch(n){e("Failed to parse message:",n);return}if(t.id&&typeof t.id=="string"&&t.id.startsWith("elicit-")&&t.result){let n=te.get(t.id);n&&(te.delete(t.id),n(t.result));return}On(t,e).catch(n=>e("Error:",n))}async function wi(){let h=(...t)=>console.error("[agentx-mcp]",...t);h("Starting MCP server (stdio transport)...");let e="";process.stdin.setEncoding("utf8"),process.stdin.on("data",t=>{for(e+=t;e.length>0;)if(e.startsWith("Content-Length:")){let s=e.indexOf(`\r
35
+ \r
36
+ `);if(s===-1)break;let r=e.slice(0,s).match(/Content-Length:\s*(\d+)/);if(!r){e=e.slice(s+4);continue}let o=parseInt(r[1],10),a=s+4,c=a+o;if(e.length<c)break;let l=e.slice(a,c);e=e.slice(c),Dt(l,h)}else{let s=e.indexOf(`
37
+ `);if(s===-1)break;let i=e.slice(0,s).trim();e=e.slice(s+1),i&&Dt(i,h)}}),process.stdin.on("end",()=>{h("stdin closed, shutting down."),process.exit(0)})}function W(h){process.stdout.write(JSON.stringify(h)+`
38
+ `)}async function On(h,e){let{method:t,id:n,params:s}=h;switch(e(`Received: ${t}`),t){case"initialize":{W({jsonrpc:"2.0",id:n,result:{protocolVersion:Mn,capabilities:Rn,serverInfo:Cn}});break}case"notifications/initialized":{e("Client initialized.");break}case"tools/list":{W({jsonrpc:"2.0",id:n,result:{tools:En}});break}case"tools/call":{let i=s?.name,r=s?.arguments||{};try{let o=await jn(i,r);W({jsonrpc:"2.0",id:n,result:o})}catch(o){W({jsonrpc:"2.0",id:n,result:{content:[{type:"text",text:`Error: ${o.message}`}],isError:!0}})}break}case"ping":{W({jsonrpc:"2.0",id:n,result:{}});break}default:n!==void 0&&W({jsonrpc:"2.0",id:n,error:{code:-32601,message:`Method not found: ${t}`}})}}var V=class{baseUrl;token;constructor(e,t){this.baseUrl=e.replace(/\/$/,""),this.token=t}async getAgentCard(){let e=await fetch(`${this.baseUrl}/.well-known/agent-card.json`,{headers:this.headers()});if(!e.ok)throw new Error(`Failed to fetch agent card: ${e.status}`);return e.json()}async sendTask(e,t){let n=await this.rpc("tasks/send",{id:`task-${Date.now().toString(36)}`,message:{role:"user",parts:[{type:"text",text:e}]},metadata:t});if(n.error)throw new Error(`A2A error: ${n.error.message}`);return n.result}async*sendTaskStream(e,t){let n=JSON.stringify({jsonrpc:"2.0",id:1,method:"tasks/sendSubscribe",params:{id:`task-${Date.now().toString(36)}`,message:{role:"user",parts:[{type:"text",text:e}]},metadata:t}}),s=await fetch(this.baseUrl,{method:"POST",headers:{...this.headers(),"Content-Type":"application/json",Accept:"text/event-stream"},body:n});if(!s.ok||!s.body)throw new Error(`A2A stream error: ${s.status}`);let i=s.body.getReader(),r=new TextDecoder,o="";for(;;){let{done:a,value:c}=await i.read();if(a)break;o+=r.decode(c,{stream:!0});let l=o.split(`
39
+ `);o=l.pop()||"";for(let d of l)if(d.startsWith("data: "))try{let g=JSON.parse(d.slice(6));yield{state:g.state,message:g.message?.parts?.[0]?.text,final:g.final}}catch{}}}async getTask(e){let t=await this.rpc("tasks/get",{id:e});if(t.error)throw new Error(`A2A error: ${t.error.message}`);return t.result}async cancelTask(e){let t=await this.rpc("tasks/cancel",{id:e});if(t.error)throw new Error(`A2A error: ${t.error.message}`);return t.result}async rpc(e,t){let n=await fetch(this.baseUrl,{method:"POST",headers:{...this.headers(),"Content-Type":"application/json"},body:JSON.stringify({jsonrpc:"2.0",id:Date.now(),method:e,params:t})});if(!n.ok)throw new Error(`A2A HTTP error: ${n.status}`);return n.json()}headers(){let e={};return this.token&&(e.Authorization=`Bearer ${this.token}`),e}};import{Agent as Dn,fetch as Nn}from"undici";var Ln=new Dn({headersTimeout:0,bodyTimeout:0}),Ve=3,me=class{peers=new Map;healthTimer;config;log;constructor(e,t=console.error.bind(console,"[mesh]")){this.config=e,this.log=t;for(let n of e.mesh.peers)this.peers.set(n.name,{peer:n,client:new V(n.url,n.token),healthy:!1,agents:[],consecutiveFailures:0})}async start(){this.log(`Mesh starting with ${this.peers.size} peer(s)`),await this.discoverAll();let e=this.config.mesh.healthCheck.interval*1e3;this.healthTimer=setInterval(()=>this.discoverAll(),e)}async stop(){this.healthTimer&&clearInterval(this.healthTimer)}async reloadPeers(e){this.config=e;let t=new Set(this.peers.keys()),n=new Map(e.mesh.peers.map(a=>[a.name,a])),s=[],i=[],r=[];for(let a of t)n.has(a)||(this.peers.delete(a),i.push(a));let o=[];for(let[a,c]of n){let l=this.peers.get(a);if(!l){let d={peer:c,client:new V(c.url,c.token),healthy:!1,agents:[],consecutiveFailures:0};this.peers.set(a,d),s.push(a),o.push([a,d]);continue}l.peer.url!==c.url||l.peer.token!==c.token?(l.peer=c,l.client=new V(c.url,c.token),l.healthy=!1,l.consecutiveFailures=0,r.push(a),o.push([a,l])):l.peer=c}return o.length&&await Promise.allSettled(o.map(([a,c])=>this.discoverPeer(a,c))),{added:s,removed:i,updated:r}}peerCount(){return this.peers.size}async refreshPeer(e){let t=this.peers.get(e);return t?(await this.discoverPeer(e,t),t.healthy):!1}async refreshAll(){let e=Array.from(this.peers.entries()).map(async([t,n])=>(await this.discoverPeer(t,n),{name:t,healthy:n.healthy}));return Promise.all(e)}async discoverAll(){let e=await Promise.allSettled(Array.from(this.peers.entries()).map(([n,s])=>this.discoverPeer(n,s))),t=Array.from(this.peers.values()).filter(n=>n.healthy).length;this.log(`Discovery complete: ${t}/${this.peers.size} peers healthy`)}async discoverPeer(e,t){let n=this.config.mesh.healthCheck.timeout*1e3;try{let s=new AbortController,i=setTimeout(()=>s.abort(),n),r=await t.client.getAgentCard();clearTimeout(i);let o=!t.healthy;t.healthy=!0,t.lastCheck=new Date,t.agentCard=r,t.agents=r.skills||[],t.consecutiveFailures=0,t.lastError=void 0,o?this.log(`Peer "${e}" recovered: ${r.name} (${t.agents.length} skills)`):this.log(`Peer "${e}" healthy: ${r.name} (${t.agents.length} skills)`)}catch(s){t.lastCheck=new Date,t.lastError=s.message,t.consecutiveFailures++,t.consecutiveFailures>=Ve&&t.healthy?(t.healthy=!1,this.log(`Peer "${e}" unreachable (${t.consecutiveFailures} consecutive failures): ${s.message}`)):t.consecutiveFailures<Ve&&this.log(`Peer "${e}" probe failed (${t.consecutiveFailures}/${Ve}): ${s.message}`)}}async sendTask(e,t,n,s={}){let i=this.peers.get(e);if(!i)throw new Error(`Unknown peer: ${e}`);if(!i.healthy)throw new Error(`Peer "${e}" is not healthy`);let r=n||i.agents[0]?.id;if(!r)throw new Error(`Peer "${e}" has no agents`);let o=`${i.peer.url}/task`,a={"Content-Type":"application/json"};i.peer.token&&(a.Authorization=`Bearer ${i.peer.token}`);let c=s.timeoutMs??30*60*1e3,l=new AbortController,d=setTimeout(()=>l.abort(),c),g;try{g=await Nn(o,{method:"POST",headers:a,body:JSON.stringify({agent:r,message:t}),signal:l.signal,dispatcher:Ln})}catch(u){throw clearTimeout(d),l.signal.aborted?new Error(`Peer "${e}" /task timed out after ${Math.round(c/1e3)}s`):u}if(clearTimeout(d),!g.ok)throw new Error(`Peer "${e}" /task error: ${g.status}`);let p=await g.json();if(p.error)throw new Error(`Peer "${e}" agent error: ${p.error}`);return p.content||"No response"}async sendSignal(e,t){let n=this.peers.get(e)||this.findPeerByNormalizedName(e);if(!n)throw new Error(`Unknown peer: ${e}`);let s=`${n.peer.url}/webrtc/signal`,i={"Content-Type":"application/json"};n.peer.token&&(i.Authorization=`Bearer ${n.peer.token}`);try{return(await fetch(s,{method:"POST",headers:i,body:JSON.stringify(t)})).ok}catch(r){return this.log(`sendSignal to "${e}" failed: ${r.message}`),!1}}findPeerByNormalizedName(e){let t=e.toLowerCase().replace(/[^a-z0-9]/g,"");for(let[n,s]of this.peers)if(n.toLowerCase().replace(/[^a-z0-9]/g,"")===t)return s}findPeerWithSkill(e){for(let t of this.peers.values())if(t.healthy&&t.agents.some(n=>n.id===e))return t}directory(){return Array.from(this.peers.entries()).map(([e,t])=>({peer:e,peerUrl:t.peer.url,healthy:t.healthy,skills:t.agents,channels:Array.isArray(t.agentCard?.channels)?t.agentCard.channels.map(n=>String(n)):[],lastCheck:t.lastCheck}))}};import{readFileSync as Bn,writeFileSync as Gn,existsSync as Fn,mkdirSync as Un}from"fs";import{resolve as Nt}from"path";function ne(h,e,t="\u2026"){if(!h)return"";if(h.length<=e)return h;let n=e-t.length;if(n<=0)return h.slice(0,e);let s=h.slice(0,n),i=s.lastIndexOf(`
40
+ `);if(i>n*.6)return s.slice(0,i)+`
41
+ `+t;let r=Math.max(s.lastIndexOf(" "),s.lastIndexOf(" "));return r>n*.6?s.slice(0,r)+t:s+t}function Qe(h,e,t){if(!h)return"";let s=h.split(`
42
+ `).slice(0,e).join(`
43
+ `);return ne(s,t)}var Wn=30,Lt=6e3,Hn=1500,Jn=6e4;function zn(h){let e=new Date(h),t=e.getUTCHours().toString().padStart(2,"0"),n=Math.floor(e.getUTCMinutes()/15)*15;return`${t}:${n.toString().padStart(2,"0")}`}var fe=class{dir;cache=new Map;constructor(e=Nt(process.cwd(),".agentx/groups")){this.dir=e,Un(this.dir,{recursive:!0})}add(e,t,n){let s=this.load(e),i=Date.now(),r=ne(n,Hn),o=s[s.length-1];if(!(o&&o.sender===t&&o.text===r&&i-o.timestamp<Jn)){for(s.push({sender:t,text:r,timestamp:i});s.length>Wn;)s.shift();this.cache.set(e,s),this.save(e,s)}}buildContext(e){let t=this.load(e);if(t.length<=1)return"";let n=["[Recent group conversation]"],s=0,i=null,r=[],o="",a=()=>{if(!i)return!0;let c=i.repeat>1?`${i.line} [\xD7${i.repeat}]`:i.line;if(s+c.length>Lt)return!1;if(r.push(c),s+=c.length,i.bucket!==o){let l=`\u2014 ${i.bucket} \u2014`;if(s+l.length>Lt)return i=null,!1;r.push(l),s+=l.length,o=i.bucket}return i=null,!0};for(let c=t.length-2;c>=0;c--){let l=t[c],d=`${l.sender}: ${l.text}`,g=zn(l.timestamp);if(i&&i.line===d&&i.bucket===g){i.repeat++;continue}if(!a())break;i={line:d,repeat:1,bucket:g}}a();for(let c=r.length-1;c>=0;c--)n.splice(1,0,r[c]);return n.length<=1?"":(n.push("[End of conversation \u2014 respond to the latest message]"),n.join(`
44
+ `))}getEntries(e){return[...this.load(e)]}filePath(e){let t=e.replace(/[^a-zA-Z0-9_-]/g,"_");return Nt(this.dir,`${t}.json`)}load(e){if(this.cache.has(e))return this.cache.get(e);let t=this.filePath(e);if(!Fn(t))return this.cache.set(e,[]),[];try{let n=JSON.parse(Bn(t,"utf-8")),s=Array.isArray(n)?n:[];return this.cache.set(e,s),s}catch{return this.cache.set(e,[]),[]}}save(e,t){try{Gn(this.filePath(e),JSON.stringify(t))}catch{}}};var Xn={minChars:80,maxChars:4e3,idleFlushMs:2e3,minIntervalMs:1500,humanPacing:!1},qn={telegram:{minChars:60,maxChars:4096,minIntervalMs:1500},whatsapp:{minChars:40,maxChars:3e3,minIntervalMs:2e3,humanPacing:!0},discord:{minChars:80,maxChars:2e3,minIntervalMs:1e3},gitlab:{minChars:200,maxChars:6e4,minIntervalMs:3e3}},ye=class{config;buffer="";emitter;lastEmitTime=0;idleTimer;insideCodeFence=!1;fencePattern=/^```/;lastEmission=Promise.resolve();constructor(e,t,n){let s=n?qn[n]:{};this.config={...Xn,...s,...t},this.emitter=e}push(e){this.buffer+=e,this.idleTimer&&clearTimeout(this.idleTimer),this.updateCodeFenceState(e),this.shouldEmit()?this.emit():this.idleTimer=setTimeout(()=>{this.buffer.length>0&&this.emit()},this.config.idleFlushMs)}async flush(){this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=void 0),this.buffer.length>0&&(this.emitImmediate(this.buffer),this.buffer=""),await this.lastEmission}get pendingLength(){return this.buffer.length}shouldEmit(){return this.insideCodeFence||this.buffer.length<this.config.minChars||Date.now()-this.lastEmitTime<this.config.minIntervalMs?!1:this.findBreakPoint()>0}emit(){let e=this.findBreakPoint();if(e<=0&&this.buffer.length<this.config.maxChars)return;let t=e>0?e:Math.min(this.buffer.length,this.config.maxChars),n=this.buffer.slice(0,t);if(this.buffer=this.buffer.slice(t),this.config.humanPacing){let s=800+Math.random()*1700;setTimeout(()=>this.emitImmediate(n),s)}else this.emitImmediate(n)}emitImmediate(e){this.lastEmitTime=Date.now(),this.lastEmission=this.lastEmission.then(()=>Promise.resolve().then(()=>this.emitter(e)).catch(()=>{}))}findBreakPoint(){let e=this.buffer,t=Math.min(e.length,this.config.maxChars),n=e.lastIndexOf(`
45
+
46
+ `,t);if(n>=this.config.minChars)return n+2;let s=e.lastIndexOf(`
47
+ `,t);if(s>=this.config.minChars)return s+1;for(let i=t-1;i>=this.config.minChars;i--)if((e[i]==="."||e[i]==="!"||e[i]==="?")&&(i+1>=e.length||e[i+1]===" "||e[i+1]===`
48
+ `))return i+1;if(e.length>=this.config.maxChars){let i=e.lastIndexOf(" ",t);return i>=this.config.minChars?i+1:t}return-1}updateCodeFenceState(e){let t=e.split(`
49
+ `);for(let n of t)this.fencePattern.test(n.trim())&&(this.insideCodeFence=!this.insideCodeFence)}};import{readFileSync as Qt,writeFileSync as Zt,existsSync as Ze,mkdirSync as en,appendFileSync as Vt}from"fs";import{resolve as et,dirname as tn}from"path";function Bt(h){let e=h.group?{type:"group",chatId:h.group.id,name:h.group.name}:h.channel==="gitlab"||h.channel==="github"?h.channel==="gitlab"?{type:"project",project:h.sender.id}:{type:"repo",repo:h.sender.id}:{type:"dm",chatId:h.sender.id};return{id:h.id,channel:h.channel,accountId:h.accountId,sender:{id:h.sender.id,name:h.sender.name,username:h.sender.username,isBot:h.sender.isBot===!0},conversation:e,content:{text:h.text,replyTo:h.replyToText},raw:h}}function Gt(h,e,t){let n={...t,atMentionsOnly:!1,trace:[]};for(let s of e){let i=s.run(h,n);if(n.trace.push({stage:s.name,decision:i}),i.kind==="match")return{agentId:i.agentId,decidingStage:s.name,kind:"match",reason:`agent=${i.agentId}`,trace:n.trace};if(i.kind==="drop")return{decidingStage:s.name,kind:"drop",reason:i.reason,trace:n.trace}}return{decidingStage:"fallthrough",kind:"drop",reason:"no stage matched",trace:n.trace}}var Ft=/<!--\s*agentx:([^\s-][^\s>]*?)\s*-->/;function se(h,e){return Ft.test(h)?h:`${h}
50
+
51
+ <!-- agentx:${e} -->`}function H(h){if(!h)return null;let e=h.match(Ft);return e?e[1]:null}function Ut(h){return h.replace(/\n*<!--\s*agentx:[^>]*?\s*-->/g,"")}var Wt={name:"self-reply-guard",run(h,e){if(h.sender.isAgent)return{kind:"drop",reason:`self-reply (agent=${h.sender.agentId??"?"})`};let t=H(h.content.text);return t?{kind:"drop",reason:`agentx-marker (agent=${t})`}:{kind:"pass"}}};var Ht={name:"handover",run(h,e){let t=h.conversation.type==="group"||h.conversation.type==="dm"?h.conversation.chatId:h.sender.id,n=e.handoverStore.get(h.channel,t,h.accountId);return n?{kind:"match",agentId:n.toAgent}:{kind:"pass"}}};var Jt={name:"adapter-resolved",run(h,e){if(h.channel==="gitlab"){let t=h.raw.resolvedAgent;return t?{kind:"match",agentId:t}:{kind:"drop",reason:"gitlab username has no agent mapping"}}return h.raw.resolvedAgent?{kind:"match",agentId:h.raw.resolvedAgent}:{kind:"pass"}}};var zt={name:"dm-binding",run(h,e){if(h.conversation.type!=="dm")return{kind:"pass"};if(h.channel==="telegram"){let t=e.config.channels.telegram.accounts[h.accountId];return t?.agentBinding?{kind:"match",agentId:t.agentBinding}:{kind:"drop",reason:`telegram dm: no agentBinding for account "${h.accountId}"`}}if(h.channel==="whatsapp"){let t=e.config.channels.whatsapp.defaultAgent;return t?{kind:"match",agentId:t}:{kind:"drop",reason:"whatsapp dm: no defaultAgent configured"}}return{kind:"pass"}}};var Xt={name:"bot-policy",run(h,e){return h.sender.isBot&&(e.atMentionsOnly=!0),{kind:"pass"}}};var qt={name:"mention",run(h,e){let t=h.channel==="telegram"&&h.conversation.type==="group"&&e.config.channels.telegram.policy.group==="mention-required",n=e.atMentionsOnly||t,s=e.registry.findByMention(h.content.text,{atMentionsOnly:n});return s?{kind:"match",agentId:s}:t?{kind:"drop",reason:"telegram group mention-required: no @mention found"}:e.atMentionsOnly?{kind:"drop",reason:"bot-origin: no @mention of a local agent"}:{kind:"pass"}}};var Kt={name:"fallback-binding",run(h,e){if(h.channel==="telegram"){let t=e.config.channels.telegram.accounts[h.accountId];return t?.agentBinding?{kind:"match",agentId:t.agentBinding}:{kind:"drop",reason:`no telegram agentBinding for account "${h.accountId}"`}}if(h.channel==="whatsapp"){let t=e.config.channels.whatsapp.defaultAgent;return t?{kind:"match",agentId:t}:{kind:"drop",reason:"no whatsapp defaultAgent"}}return{kind:"pass"}}};var Yt=[Wt,Ht,Jt,zt,Xt,qt,Kt];var tt=class{filePath;constructor(e){this.filePath=et(e,".agentx/router/inflight.jsonl"),en(tn(this.filePath),{recursive:!0})}start(e){try{let t=JSON.stringify({type:"start",ts:Date.now(),...e})+`
52
+ `;Vt(this.filePath,t)}catch{}}done(e){try{Vt(this.filePath,JSON.stringify({type:"done",id:e,ts:Date.now()})+`
53
+ `)}catch{}}loadUnfinished(){if(!Ze(this.filePath))return[];try{let e=Qt(this.filePath,"utf-8"),t=new Map;for(let n of e.split(`
54
+ `))if(n.trim())try{let s=JSON.parse(n);s.type==="start"?t.set(s.id,s):s.type==="done"&&t.delete(s.id)}catch{}return[...t.values()]}catch{return[]}}compact(){try{let e=this.loadUnfinished(),t=e.map(n=>JSON.stringify(n)).join(`
55
+ `)+(e.length?`
56
+ `:"");Zt(this.filePath,t)}catch{}}};var Kn=4e3,nt=class{registry;config;channels=new Map;hooks;mesh;serviceMatcher;business;groupLog;handoverStore;log;getHandoverStore(){return this.handoverStore}recentMessageIds=new Map;MESSAGE_ID_TTL_MS=5*60*1e3;MESSAGE_ID_MAX_ENTRIES=2e3;DEDUP_STORE_PATH=".agentx/router/dedup.json";dedupDirtyCount=0;dedupSaveTimer;inflight;activeMeshForwards=0;getActiveMeshForwardCount(){return this.activeMeshForwards}constructor(e,t,n,s=console.error.bind(console,"[router]")){this.registry=e,this.config=t,this.hooks=n,this.log=s,this.groupLog=new fe,this.handoverStore=new $t({log:(...i)=>s("[handover]",...i)}),this.inflight=new tt(process.cwd()),this.loadDedupFromDisk()}loadDedupFromDisk(){try{let e=et(process.cwd(),this.DEDUP_STORE_PATH);if(!Ze(e))return;let t=JSON.parse(Qt(e,"utf-8")),n=Date.now(),s=0;for(let[i,r]of Object.entries(t))typeof r=="number"&&n-r<this.MESSAGE_ID_TTL_MS&&(this.recentMessageIds.set(i,r),s++);s>0&&this.log(`Router dedup: restored ${s} recent message ids from disk`)}catch{}}scheduleDedupSave(){if(this.dedupDirtyCount++,this.dedupDirtyCount>=20){this.flushDedupToDisk();return}this.dedupSaveTimer||(this.dedupSaveTimer=setTimeout(()=>this.flushDedupToDisk(),5e3))}flushDedupToDisk(){this.dedupSaveTimer&&(clearTimeout(this.dedupSaveTimer),this.dedupSaveTimer=void 0),this.dedupDirtyCount=0;try{let e=Date.now(),t={};for(let[i,r]of this.recentMessageIds)e-r<this.MESSAGE_ID_TTL_MS&&(t[i]=r);let n=et(process.cwd(),this.DEDUP_STORE_PATH),s=tn(n);Ze(s)||en(s,{recursive:!0}),Zt(n,JSON.stringify(t))}catch{}}flushPersistence(){this.flushDedupToDisk(),this.inflight.compact()}setMesh(e){this.mesh=e}setServiceMatcher(e){this.serviceMatcher=e}getServiceMatcher(){return this.serviceMatcher}setBusiness(e){this.business=e}updateConfig(e){this.config=e}getChannel(e){return this.channels.get(e)}addChannel(e){this.channels.set(e.name,e),e.onMessage(t=>this.handleMessage(e,t))}async sendOutbound(e){let t=this.channels.get(e.channel);if(!t)throw new Error(`Unknown channel: "${e.channel}". Available: ${[...this.channels.keys()].join(", ")}`);let n=e.accountId;return t.name==="telegram"&&!n&&e.agentId&&(n=this.getAccountForAgent(e.agentId)),this.log(`Outbound [${e.channel}] -> ${e.chatId}: ${e.text.slice(0,80)}`),t.name==="telegram"&&n?this.adapterSend(t,{...e,accountId:n}):t.send(e)}getChannelNames(){return[...this.channels.keys()]}async startAll(){for(let[e,t]of this.channels)this.log(`Starting channel: ${e}`),await t.start();this.replayInflight().catch(e=>this.log(`Inflight replay error: ${e.message}`))}async replayInflight(){let e=this.inflight.loadUnfinished();if(e.length!==0){this.log(`Inflight replay: ${e.length} task(s) to resume from previous run`);for(let t of e){let n=this.channels.get(t.channel);if(!n){this.log(`Inflight replay: adapter "${t.channel}" not available, skipping task ${t.id}`),this.inflight.done(t.id);continue}let s={id:t.id,channel:t.channel,accountId:t.accountId,sender:t.sender,group:t.group,text:t.text,replyTo:t.replyTo,replyToText:t.replyToText,timestamp:new Date(t.timestamp),resolvedAgent:t.resolvedAgent,preferNode:t.preferNode};this.log(`Inflight replay: ${t.channel}/${t.id} (agent=${t.resolvedAgent||"resolve"})`),this.handleMessage(n,s,{replay:!0}).catch(i=>this.log(`Inflight replay failed for ${t.id}: ${i.message}`)).finally(()=>this.inflight.done(t.id))}this.inflight.compact()}}async stopAll(){for(let[e,t]of this.channels)this.log(`Stopping channel: ${e}`),await t.stop()}isDuplicateMessage(e){if(!e.id)return!1;let t=`${e.channel}:${e.accountId||"default"}:${e.id}`,n=Date.now();if(this.recentMessageIds.size>=this.MESSAGE_ID_MAX_ENTRIES)for(let[i,r]of this.recentMessageIds)n-r>this.MESSAGE_ID_TTL_MS&&this.recentMessageIds.delete(i);let s=this.recentMessageIds.get(t);return s&&n-s<this.MESSAGE_ID_TTL_MS?!0:(this.recentMessageIds.set(t,n),this.scheduleDedupSave(),!1)}async handleMessage(e,t,n={}){if(!n.replay&&this.isDuplicateMessage(t)){this.log(`Duplicate ${t.channel} message dropped: ${t.accountId||"default"}/${t.id}`);return}if(this.hooks?.has("pre:channel-message")){let o=await this.hooks.execute("pre:channel-message",{event:"pre:channel-message",channel:t.channel,sender:t.sender.name,text:t.text,group:t.group?.name,msg:t});if(o.blocked){this.log(`Message blocked by hook: ${o.message}`);return}o.modified?.text&&(t={...t,text:o.modified.text})}if(t.group){let o=t.group.id;this.groupLog.add(o,t.sender.name,t.text)}if(this.serviceMatcher){let o=t.group?.id||t.sender.id,a=this.serviceMatcher.match(t.text,t.sender.id,t.channel);if(a){this.log(`Service matched: "${a.service.name}" for ${t.sender.name} (trigger: ${a.trigger})`);let c=t.accountId;this.adapterReact(e,o,t.id,"\u{1F440}",c);let l=this.startTypingLoop(e,o,c);await this.serviceMatcher.execute(a.service,this.registry,{channel:t.channel,sender:t.sender.name,chatId:o},async d=>{clearInterval(l),await this.adapterSend(e,{channel:t.channel,chatId:o,text:d,replyTo:t.id,accountId:c})});return}}let s=this.runRoutingPipeline(t),i=s.agentId;if(!i){this.traceRoute(t,"drop",s.reason,s.decidingStage);return}if(t.group&&t.channel==="telegram"){let o=this.getAccountForAgent(i);if(o&&o!==t.accountId){this.traceRoute(t,"drop",`multi-account-dedup (bound=${o} got=${t.accountId})`,"multi-account-dedup");return}}this.traceRoute(t,"match",`${s.decidingStage} agent=${i}`,s.decidingStage,i);let r=t.group?.id||t.sender.id;this.inflight.start({id:t.id,channel:t.channel,accountId:t.accountId,text:t.text,sender:t.sender,group:t.group,replyTo:t.replyTo,replyToText:t.replyToText,timestamp:(t.timestamp instanceof Date?t.timestamp:new Date).toISOString(),resolvedAgent:i,preferNode:t.preferNode});try{return await this.processResolvedMessage(e,t,i,r)}finally{this.inflight.done(t.id)}}async processResolvedMessage(e,t,n,s){if(t.preferNode){this.log(`preferNode="${t.preferNode}" \u2014 forcing mesh routing for agent "${n}"`),await this.handleViaMeshByPeer(e,t,n,t.preferNode)||this.log(`Mesh peer "${t.preferNode}" not found or unhealthy for agent "${n}"`);return}let i=this.registry.getAgent(n);if(!i){await this.handleViaMeshByAgentId(e,t,n)||this.log(`Agent "${n}" not found locally or on any mesh peer`);return}let r=i.name||n,o=this.getAccountForAgent(n)||t.accountId;this.log(`Routing [${t.channel}/${t.sender.name}] -> "${r}": ${t.text.slice(0,80)}`),this.adapterReact(e,s,t.id,"\u{1F440}",o);let a=this.startTypingLoop(e,s,o),c=typeof e.editMessage=="function",l,d="",g=c?new ye(async k=>{if(d+=k,l)try{await this.adapterEdit(e,s,l,d,void 0,o)}catch{}else{let S=d.length>20?d:`_${r} is writing..._
57
+
58
+ ${d}`;try{l=await this.adapterSend(e,{channel:t.channel,chatId:s,text:S,replyTo:t.id,accountId:o})}catch{}}},void 0,t.channel):void 0,p=g?(k,S)=>{g.push(k)}:void 0;if(t.channelMeta?.agents){for(let k of t.channelMeta.agents)if(!k.handle){let S=this.registry.getAgent(k.id);S&&(k.handle=S.mentions.find(T=>T.startsWith("@")),k.name=S.name)}}let u=t.group?this.groupLog.buildContext(s):"",m=u?`${u}
59
+
60
+ ${t.sender.name}: ${t.text}`:t.text,f=await this.registry.execute({message:m,agentId:n,context:{channel:t.channel,sender:t.sender.name,senderId:t.sender.id,senderUsername:t.sender.username,group:t.group?.name,chatId:s,mediaPath:t.media?.path,mediaType:t.media?.type,replyToText:t.replyToText,channelMeta:t.channelMeta}},p);if(clearInterval(a),await g?.flush(),f.error){if(f.error.startsWith("__queued__")){clearInterval(a),this.log(`Message queued for ${r}`);return}this.log(`Agent error: ${f.error}`);let k=`Error: ${f.error}`;l?await this.adapterEdit(e,s,l,k,"plain",o):await this.adapterSend(e,{channel:t.channel,chatId:s,text:k,replyTo:t.id,parseMode:"plain",accountId:o});return}let b=f.content;if(this.hooks?.has("post:channel-message")){let k=await this.hooks.execute("post:channel-message",{event:"post:channel-message",channel:t.channel,sender:t.sender.name,response:b,agentId:n});if(k.blocked){this.log(`Response blocked by hook: ${k.message}`);return}k.modified?.response&&(b=k.modified.response)}t.channel==="gitlab"&&b&&(this.channels.get("gitlab")?.getAgentToken?.(n)||(b=`> **${r}** (${n})
61
+
62
+ ${b}`)),t.channel==="whatsapp"&&b&&(b=`*${r}*
63
+
64
+ ${b}`);let y;b&&(l?(await this.adapterEdit(e,s,l,b,void 0,o),y=l):y=await this.adapterSend(e,{channel:t.channel,chatId:s,text:b,replyTo:t.id,accountId:o,agentId:n})),t.group&&b&&this.groupLog.add(s,r,b);let w=this.config.notifications;if(w?.destination&&f.duration){let k=(w.longTaskThreshold||30)*1e3;if(f.duration>=k&&!f.error&&w.on?.taskComplete||f.error&&w.on?.taskError){let T=Math.round(f.duration/1e3),$=f.error?"failed":"completed",I=Qe(t.text,6,300),A=f.error?ne(f.error,300):Qe(f.content,6,400),C=`${$==="failed"?"\u{1F534}":"\u2705"} **${r}** ${$} (${T}s)
65
+ ${t.channel}/${t.sender.name}: ${I}
66
+ ${A}`;this.sendOutbound({channel:w.destination.channel,chatId:w.destination.chatId,text:C,agentId:n,accountId:w.destination.accountId}).catch(x=>{this.log(`Task notification failed: ${x.message}`)})}}if(this.business&&f.duration&&this.business.recordTaskCompletion(n,Math.round(f.duration/1e3),!f.error,t.channel),t.channel==="gitlab"&&f.duration&&!f.error){let k=this.channels.get("gitlab");k&&k.logTimeSpent(s,f.duration,n).catch(S=>{this.log(`GitLab time tracking failed: ${S.message}`)})}b&&y&&["telegram","whatsapp","discord","slack"].includes(t.channel)&&this.handleBotToBotChain(e,t,n,b,y,0).catch(k=>{this.log(`Bot-to-bot error: ${k.message}`)})}async handleBotToBotChain(e,t,n,s,i,r,o=new Set){if(r>=nt.MAX_BOT_CHAIN_DEPTH){this.log(`Bot-to-bot: max depth (${r}) reached, stopping`);return}o.add(n);for(let[a,c]of Object.entries(this.config.agents)){if(a===n)continue;if(o.has(a)){this.log(`Bot-to-bot: "${a}" already participated, stopping chain`);continue}if(!c.mentions.filter(m=>m.startsWith("@")).some(m=>s.toLowerCase().includes(m.toLowerCase())))continue;this.log(`Bot-to-bot [${r+1}]: "${n}" -> "${a}"`);let g=t.group?.id||t.sender.id,p=this.getAccountForAgent(a),u=this.getAccountForAgent(n);try{this.adapterReact(e,g,i,"\u{1F440}",p);let m=this.startTypingLoop(e,g,p),f=r===0?`[Original from ${t.sender.name}]: ${t.text}
67
+
68
+ [${n} said]: ${s}`:s,b=await this.registry.execute({message:f,agentId:a,context:{channel:t.channel,sender:`agent:${n}`,group:t.group?.name}});if(clearInterval(m),b.content&&!b.error){let y=b.content;t.channel==="whatsapp"&&(y=`*${c.name}*
69
+
70
+ ${y}`);let w=await this.adapterSend(e,{channel:t.channel,chatId:g,text:y,accountId:p});w&&b.content&&await this.handleBotToBotChain(e,t,a,b.content,w,r+1,o)}else b.error&&this.log(`Bot-to-bot "${a}" error: ${b.error}`)}catch(m){this.log(`Bot-to-bot "${a}" failed: ${m.message}`)}break}}async adapterSend(e,t){return e.name==="telegram"&&t.accountId?e.send({...t,parseMode:t.parseMode,accountId:t.accountId}):e.send(t)||""}async adapterEdit(e,t,n,s,i,r){return e.name==="telegram"&&r?e.editMessage(t,n,s,i,r):e.editMessage?.(t,n,s,i)??!1}adapterReact(e,t,n,s,i){e.name==="telegram"&&i?e.react(t,n,s,i):e.react?.(t,n,s)}startTypingLoop(e,t,n){let s=()=>{e.name==="telegram"&&n?e.sendTyping(t,n):e.sendTyping?.(t)};return s(),setInterval(s,Kn)}traceRoute(e,t,n,s="unknown",i){let r=e.group?.id||e.sender?.id||"?";this.log(`[route] ${e.channel}:${r} msgId=${e.id} acct=${e.accountId??"\u2014"} kind=${t} ${n}`);let o=new Date().toISOString();t==="match"&&i?ee().emit("message:matched",{channel:e.channel,chatId:String(r),msgId:e.id,accountId:e.accountId,agentId:i,decidingStage:s,at:o}):t==="drop"&&ee().emit("message:dropped",{channel:e.channel,chatId:String(r),msgId:e.id,accountId:e.accountId,decidingStage:s,reason:n,at:o})}runRoutingPipeline(e){let t=Bt(e);return Gt(t,Yt,{config:this.config,registry:this.registry,handoverStore:this.handoverStore})}async handleViaMesh(e,t){if(!this.mesh)return!1;let n=t.text.toLowerCase(),s=this.mesh.directory();for(let i of s)if(i.healthy){for(let r of i.skills)if(n.includes(r.id.toLowerCase())||n.includes(r.name.toLowerCase())){this.log(`Mesh routing [${t.channel}/${t.sender.name}] -> peer "${i.peer}" agent "${r.id}"`);let o=t.group?.id||t.sender.id,a=t.accountId;this.adapterReact(e,o,t.id,"\u{1F440}",a);let c=this.startTypingLoop(e,o,a);try{let l=await this.mesh.sendTask(i.peer,t.text,r.id);if(clearInterval(c),l){let d=`**${r.name}** _(${i.peer})_:
71
+
72
+ `;await this.adapterSend(e,{channel:t.channel,chatId:o,text:d+l,replyTo:t.id,accountId:a})}return!0}catch(l){return clearInterval(c),this.log(`Mesh routing error: ${l.message}`),await this.adapterSend(e,{channel:t.channel,chatId:o,text:`Error from ${i.peer}/${r.name}: ${l.message}`,replyTo:t.id,parseMode:"plain",accountId:a}),!0}}}return!1}async handleViaMeshByAgentId(e,t,n){if(!this.mesh)return!1;let s=this.mesh.directory();for(let i of s){if(!i.healthy)continue;let r=i.skills.find(l=>l.id===n);if(!r)continue;this.log(`Mesh routing by agentId [${t.channel}/${t.sender.name}] -> peer "${i.peer}" agent "${n}"`);let o=t.group?.id||t.sender.id,a=t.accountId;this.adapterReact(e,o,t.id,"\u{1F440}",a);let c=this.startTypingLoop(e,o,a);try{let l=await this.mesh.sendTask(i.peer,t.text,n);return clearInterval(c),l&&await this.adapterSend(e,{channel:t.channel,chatId:o,text:l,replyTo:t.id,accountId:a}),!0}catch(l){return clearInterval(c),this.log(`Mesh routing error for ${n}: ${l.message}`),await this.adapterSend(e,{channel:t.channel,chatId:o,text:`Error from ${i.peer}/${r.name}: ${l.message}`,replyTo:t.id,parseMode:"plain",accountId:a}),!0}}return!1}async handleViaMeshByPeer(e,t,n,s){if(!this.mesh)return!1;let r=this.mesh.directory().find(g=>g.peer===s);if(!r||!r.healthy)return!1;let o=r.skills.find(g=>g.id===n)??{name:n,id:n};this.log(`Mesh routing by preferNode [${t.channel}/${t.sender.name}] -> peer "${s}" agent "${n}"`);let a=t.group?.id||t.sender.id,c=t.accountId;this.adapterReact(e,a,t.id,"\u{1F440}",c);let l=this.startTypingLoop(e,a,c),d=Date.now();this.activeMeshForwards++;try{let g=await this.mesh.sendTask(s,t.text,n),p=Date.now()-d;if(clearInterval(l),g&&await this.adapterSend(e,{channel:t.channel,chatId:a,text:g,replyTo:t.id,accountId:c,agentId:n}),t.channel==="gitlab"&&p){let u=this.channels.get("gitlab");u&&u.logTimeSpent(a,p,n).catch(m=>{this.log(`GitLab time tracking (mesh) failed: ${m.message}`)})}return!0}catch(g){return clearInterval(l),this.log(`Mesh routing error for ${s}/${n}: ${g.message}`),await this.adapterSend(e,{channel:t.channel,chatId:a,text:`Error from ${s}/${o.name}: ${g.message}`,replyTo:t.id,parseMode:"plain",accountId:c}),!0}finally{this.activeMeshForwards--}}getAccountForAgent(e){for(let[t,n]of Object.entries(this.config.channels.telegram.accounts))if(n.agentBinding===e)return t}resolveAgent(e){return this.runRoutingPipeline(e).agentId}},ie=nt;ue(ie,"MAX_BOT_CHAIN_DEPTH",3);function be(h){return h.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function re(h){let t=h.replace(/\\([_*\[\]()~`>#+=|{}.!\\-])/g,"$1").split(`
73
+ `),n=[],s=!1,i="",r=[],o=!1,a=[],c=!1;for(let d=0;d<t.length;d++){let g=t[d];if(g.trimStart().startsWith("```"))if(s){s=!1;let f=be(r.join(`
74
+ `)),b=i?`// ${i}
75
+ `:"";n.push(`<pre><code>${b}${f}</code></pre>`),i="";continue}else{s=!0,i=g.trimStart().slice(3).trim(),r=[];continue}if(s){r.push(g);continue}if(o&&!g.trimStart().startsWith(">")&&(n.push("</blockquote>"),o=!1),c&&!g.trim().startsWith("|")&&(c=!1,a=[]),g.trimStart().startsWith("> ")){let f=g.replace(/^>\s*/,"");o||(n.push("<blockquote>"),o=!0),n.push(D(f));continue}let p=g.match(/^(#{1,6})\s+(.+)$/);if(p){n.push(""),n.push(`<b>${D(p[2])}</b>`);continue}if(/^[-*_]{3,}\s*$/.test(g.trim())){n.push("\u2014\u2014\u2014");continue}if(g.trim().startsWith("|")&&g.trim().endsWith("|")){let f=g.split("|").slice(1,-1).map(b=>b.trim());if(/^\|[\s\-:|]+\|$/.test(g.trim())){c=!0;continue}if(!c){a=f,c=!0;continue}if(a.length>0&&f.length>0)if(f.length>=2){let b=[];for(let y=0;y<f.length;y++)if(y===0)b.push(`<b>${D(f[y])}</b>`);else{let w=a[y]?`${D(a[y])}: `:"";b.push(`${w}${D(f[y])}`)}n.push(`\u2022 ${b.join(" \u2014 ")}`)}else n.push(`\u2022 ${D(f[0])}`);continue}let u=g.match(/^(\s*)[-*+]\s+(.+)$/);if(u){let f=u[1].length>0?" ":"";n.push(`${f}\u2022 ${D(u[2])}`);continue}let m=g.match(/^(\s*)\d+[.)]\s+(.+)$/);if(m){let f=m[1].length>0?" ":"",b=g.match(/^(\s*)(\d+)/)?.[2]||"1";n.push(`${f}${b}. ${D(m[2])}`);continue}if(!g.trim()){n.push("");continue}n.push(D(g))}s&&n.push(`<pre><code>${be(r.join(`
76
+ `))}</code></pre>`),o&&n.push("</blockquote>");let l=n.join(`
77
+ `).trim();return l=Vn(l),l}function D(h){let e=be(h);return e=e.replace(/`([^`]+)`/g,"<code>$1</code>"),e=e.replace(/\[([^\]]+)\]\(([^)]+)\)/g,(t,n,s)=>`<a href="${be(s)}">${n}</a>`),e=e.replace(/\*\*\*(.+?)\*\*\*/g,"<b><i>$1</i></b>"),e=e.replace(/\*\*(.+?)\*\*/g,"<b>$1</b>"),e=e.replace(/(?<!\*)\*([^*]+?)\*(?!\*)/g,"<i>$1</i>"),e=e.replace(/~~(.+?)~~/g,"<s>$1</s>"),e=e.replace(/\|\|(.+?)\|\|/g,"<tg-spoiler>$1</tg-spoiler>"),e}var Yn=/(?<=\w)\.(ts|js|py|rs|go|rb|cs|sh|md|yml|yaml|toml|json|env|css|html|xml|sql|tf|hcl)(?=[\s,;:)\]}<]|$)/gi;function Vn(h){let e=h.split(/(<\/?(?:code|pre|a)[^>]*>)/gi),t=!1;return e.map(n=>/<(?:code|pre|a)\b/i.test(n)?(t=!0,n):/<\/(?:code|pre|a)>/i.test(n)?(t=!1,n):t?n:n.replace(Yn,"<code>.$1</code>")).join("")}import{readFileSync as is,writeFileSync as rs,mkdirSync as os,existsSync as as}from"fs";import{resolve as cs,dirname as ls}from"path";import{existsSync as nn,mkdirSync as Qn,readFileSync as Zn,renameSync as es,writeFileSync as ts}from"fs";import{dirname as ns,resolve as ss}from"path";var it=class{filePath;data;constructor(e,t=".agentx/cursors.json"){this.filePath=ss(e,t),this.data={};try{if(nn(this.filePath)){let n=JSON.parse(Zn(this.filePath,"utf-8"));n&&typeof n=="object"&&(this.data=n)}}catch{}}key(e,t){return`${e}:${t}`}read(e,t){let n=this.data[this.key(e,t)];return n===void 0?null:n}commit(e,t,n){let s=this.key(e,t);this.data[s]!==n&&(this.data[s]=n,this.persist())}snapshot(){return{...this.data}}persist(){let e=ns(this.filePath);nn(e)||Qn(e,{recursive:!0});let t=`${this.filePath}.tmp`;ts(t,JSON.stringify(this.data,null,2)),es(t,this.filePath)}},st;function sn(h){return st||(st=new it(h??process.cwd())),st}var rt=class{data={};filePath;dirty=!1;constructor(e){this.filePath=cs(e,".agentx/telegram/groups.json"),this.load()}load(){try{as(this.filePath)&&(this.data=JSON.parse(is(this.filePath,"utf-8")))}catch{this.data={}}}save(){try{os(ls(this.filePath),{recursive:!0}),rs(this.filePath,JSON.stringify(this.data,null,2)),this.dirty=!1}catch{}}setBotStatus(e,t,n,s,i,r){this.data[e]||(this.data[e]={name:t,bots:{}}),t&&(this.data[e].name=t),this.data[e].bots[n]={username:i,agentId:s,status:r,updatedAt:new Date().toISOString()},this.dirty=!0,this.save()}getGroupBots(e){let t=this.data[e];return t?Object.entries(t.bots).filter(([,n])=>n.status!=="left"&&n.status!=="kicked").map(([n,s])=>({accountId:n,agentId:s.agentId,username:s.username})):[]}hasGroup(e){return!!this.data[e]}listGroups(){return Object.entries(this.data).map(([e,t])=>({id:e,name:t.name,botCount:Object.values(t.bots).filter(n=>n.status!=="left"&&n.status!=="kicked").length}))}};var J=class{name="telegram";accounts;cursors;handler;polling=!1;accountPolling=new Map;log;globalAllowFrom;constructor(e,t={},n=console.error.bind(console,"[telegram]")){this.accounts=new Map(Object.entries(e)),this.globalAllowFrom=t.policy?.allowFrom,this.log=n,this.groupStore=new rt(process.cwd()),this.cursors=sn(process.cwd())}effectiveAllowFrom(e){let t=this.accounts.get(e);return t?.allowFrom!==void 0?t.allowFrom:this.globalAllowFrom}isAllowed(e,t,n,s){let i=this.effectiveAllowFrom(e);if(!i||i.length===0)return!1;let r=t!=null?String(t):"",o=n!=null?String(n):"",a=s?.toLowerCase();for(let c of i)if(c&&(c===r||c===o||c.startsWith("@")&&a&&c.slice(1).toLowerCase()===a))return!0;return!1}onMessage(e){this.handler=e}async start(){this.polling=!0;let e=Array.from(this.accounts.entries());this.log(`${e.length} Telegram account(s) to start`);for(let t=0;t<e.length;t++){let[n,s]=e[t];await this.startAccount(n,s,{index:t+1,of:e.length}),t<e.length-1&&await new Promise(i=>setTimeout(i,300))}this.log(`All ${e.length} Telegram account(s) started`)}async startAccount(e,t,n={}){if(this.accountPolling.get(e)){this.log(`Account "${e}" already polling \u2014 skipping duplicate start`);return}let s=n.index!=null&&n.of!=null?`(${n.index}/${n.of})`:"(hot-reload)";if(t.pollInbound===!1){this.log(`Account "${e}" ${s} \u2014 send-only (pollInbound=false), skipping getUpdates loop`);try{let i=await this.apiCall(t.token,"getMe"),r=i.result?.id,o=i.result?.username;r&&this.botInfo.set(e,{userId:r,username:o||e})}catch(i){this.log(`Send-only account "${e}" getMe failed: ${i.message} \u2014 send may still work if token is valid`)}return}this.log(`Starting polling for account "${e}" ${s}`);try{await this.apiCall(t.token,"deleteWebhook",{drop_pending_updates:!1}).catch(()=>{});let i=await this.apiCall(t.token,"getMe"),r=i.result?.id,o=i.result?.username;this.log(`Bot @${o} ready (account: ${e})`),r&&this.botInfo.set(e,{userId:r,username:o||e}),this.accountPolling.set(e,!0),this.pollLoop(e,t)}catch(i){this.log(`Failed to verify bot for account "${e}": ${i.message}`)}}async stopAccount(e){this.accountPolling.set(e,!1);let t=this.accounts.get(e);if(t)try{await this.apiCall(t.token,"getUpdates",{offset:-1,timeout:0})}catch{}}async stop(){this.polling=!1;let e=Array.from(this.accounts.keys()).map(t=>this.stopAccount(t));await Promise.allSettled(e)}async reloadAccounts(e,t){if(!this.polling)return this.accounts=new Map(Object.entries(e)),this.globalAllowFrom=t?.allowFrom,{added:[],removed:[],tokenChanged:[]};let n=new Set(this.accounts.keys()),s=new Set(Object.keys(e)),i=[],r=[],o=[];this.globalAllowFrom=t?.allowFrom;for(let a of n)s.has(a)||(await this.stopAccount(a),this.accounts.delete(a),this.botInfo.delete(a),r.push(a));for(let a of s){if(!n.has(a))continue;let c=this.accounts.get(a),l=e[a];this.accounts.set(a,l),c.token!==l.token&&(await this.stopAccount(a),await this.startAccount(a,l),o.push(a))}for(let a of s)n.has(a)||(this.accounts.set(a,e[a]),await this.startAccount(a,e[a]),i.push(a));return{added:i,removed:r,tokenChanged:o}}getTokenForAccount(e){return this.accounts.get(e)?.token}getDefaultToken(){let[,e]=Array.from(this.accounts.entries())[0];return e?.token}resolveToken(e,t){if(t){let n=this.getTokenForAccount(t);if(n)return n}return this.chatAccountMap.get(e)?this.getTokenForAccount(this.chatAccountMap.get(e)):this.getDefaultToken()}chatAccountMap=new Map;botInfo=new Map;groupStore;async send(e){let t=this.resolveToken(e.chatId,e.accountId);if(!t)return this.log("No telegram token found for sending"),"";let n=4096,s=e.text.length>n?e.text.slice(0,n-3)+"...":e.text,i=e.parseMode==="markdown"||e.parseMode===void 0?re(s):s,r={chat_id:e.chatId,text:i,parse_mode:"HTML"};e.replyTo&&(r.reply_to_message_id=parseInt(e.replyTo,10)),e.parseMode==="html"?(r.parse_mode="HTML",r.text=s):e.parseMode==="plain"&&(delete r.parse_mode,r.text=s);try{let o=await this.apiCall(t,"sendMessage",r);return String(o.result?.message_id||"")}catch(o){if(r.parse_mode){delete r.parse_mode,r.text=s;let a=await this.apiCall(t,"sendMessage",r);return String(a.result?.message_id||"")}throw o}}async sendWithInlineButtons(e){let t=this.resolveToken(e.chatId,e.accountId);if(!t)return this.log("No telegram token found for sending"),"";let n=4096,s=e.text.length>n?e.text.slice(0,n-3)+"...":e.text,i=e.parseMode==="markdown"||e.parseMode===void 0?re(s):s,r={chat_id:e.chatId,text:i,parse_mode:e.parseMode==="plain"?void 0:"HTML",reply_markup:JSON.stringify({inline_keyboard:e.buttons.map(o=>[{text:o.label,url:o.url}])})};r.parse_mode===void 0&&delete r.parse_mode;try{let o=await this.apiCall(t,"sendMessage",r);return String(o.result?.message_id||"")}catch(o){if(r.parse_mode){delete r.parse_mode,r.text=s;let a=await this.apiCall(t,"sendMessage",r);return String(a.result?.message_id||"")}throw o}}async editMessage(e,t,n,s,i){let r=this.resolveToken(e,i);if(!r)return!1;let o=4096,a=n.length>o?n.slice(0,o-3)+"...":n,c=s!=="html"&&s!=="plain"?re(a):a,l={chat_id:e,message_id:parseInt(t,10),text:c,parse_mode:"HTML"};s==="html"?(l.parse_mode="HTML",l.text=a):s==="plain"&&(delete l.parse_mode,l.text=a);try{return await this.apiCall(r,"editMessageText",l),!0}catch(d){if(d.message?.includes("message is not modified"))return!0;if(l.parse_mode){delete l.parse_mode,l.text=a;try{return await this.apiCall(r,"editMessageText",l),!0}catch{return!1}}return!1}}async sendDraft(e,t,n,s,i){if(!J.isDirectMessage(e))return!1;let r=this.resolveToken(e,i);if(!r)return!1;let o=4096,a=n.length>o?n.slice(0,o-3)+"...":n;if(!a)return!1;let c=s!=="html"&&s!=="plain"?re(a):a,l={chat_id:parseInt(e,10),draft_id:t,text:c,parse_mode:"HTML"};s==="plain"?(delete l.parse_mode,l.text=a):s==="html"&&(l.text=a);try{return await this.apiCall(r,"sendMessageDraft",l),!0}catch{if(l.parse_mode){delete l.parse_mode,l.text=a;try{return await this.apiCall(r,"sendMessageDraft",l),!0}catch{return!1}}return!1}}static isDirectMessage(e){let t=parseInt(e,10);return Number.isFinite(t)&&t>0}async react(e,t,n="\u{1F440}",s){let i=this.resolveToken(e,s);if(i)try{await this.apiCall(i,"setMessageReaction",{chat_id:e,message_id:parseInt(t,10),reaction:[{type:"emoji",emoji:n}]})}catch{}}async sendTyping(e,t){let n=this.resolveToken(e,t);if(n)try{await this.apiCall(n,"sendChatAction",{chat_id:e,action:"typing"})}catch{}}async pollLoop(e,t){let n=0;for(;this.polling&&this.accountPolling.get(e)!==!1;)try{let s=this.cursors.read("telegram",e),i=typeof s=="number"?s:0,o=(await this.apiCall(t.token,"getUpdates",{offset:i||void 0,timeout:30,allowed_updates:["message","my_chat_member"]})).result||[];for(let a of o){if(this.cursors.commit("telegram",e,a.update_id+1),a.message&&this.handler){let c=a.message,l=c.text||c.caption||"",d,g=c.photo&&c.photo.length>0,p=!!c.voice,u=!!c.audio,m=!!c.video,f=!!c.document;if(g||p||u||m||f){let S,T="application/octet-stream";if(g?(S=c.photo[c.photo.length-1].file_id,T="image/jpeg",l||(l="[Photo attached \u2014 please describe what you see]")):p?(S=c.voice.file_id,T=c.voice.mime_type||"audio/ogg",l||(l="[Voice message \u2014 please transcribe and respond]")):u?(S=c.audio.file_id,T=c.audio.mime_type||"audio/mpeg",l||(l=`[Audio: ${c.audio.title||"audio file"}]`)):m?(S=c.video.file_id,T=c.video.mime_type||"video/mp4",l||(l="[Video attached]")):f&&(S=c.document.file_id,T=c.document.mime_type||"application/octet-stream",l||(l=`[Document: ${c.document.file_name||"file"}]`)),S)try{let I=(await this.apiCall(t.token,"getFile",{file_id:S})).result?.file_path;if(I){let A=`https://api.telegram.org/file/bot${t.token}/${I}`,C=await fetch(A);if(C.ok){let x=Buffer.from(await C.arrayBuffer()),_=T.split("/")[1]?.split(";")[0]||"bin",{mkdirSync:O,writeFileSync:ze}=await import("fs"),{randomUUID:P}=await import("crypto"),{resolve:L,join:E}=await import("path"),q=L(process.cwd(),".agentx/media/telegram");O(q,{recursive:!0});let B=c.document?.file_name||`${P()}.${_}`,K=E(q,B);ze(K,x),d={path:K,type:T,fileName:B}}}}catch($){this.log(`Media download failed: ${$.message}`)}}if(!l)continue;if(!this.isAllowed(e,c.from?.id,c.chat?.id,c.from?.username)){this.log(`[telegram/${e}] dropped message from ${c.from?.id}${c.from?.username?` (@${c.from.username})`:""} in chat ${c.chat?.id} \u2014 not in allowlist`);continue}let y=c.chat.type!=="private",w=String(c.chat.id),v;y&&(this.groupStore.hasGroup(w)||await this.seedGroupMembership(w,c.chat.title),v=await this.getChannelMeta(w));let k={id:String(c.message_id),channel:"telegram",accountId:e,sender:{id:String(c.from.id),name:[c.from.first_name,c.from.last_name].filter(Boolean).join(" "),username:c.from.username,isBot:c.from.is_bot===!0},group:y?{id:w,name:c.chat.title||""}:void 0,text:l,media:d,replyTo:c.reply_to_message?String(c.reply_to_message.message_id):void 0,replyToText:c.reply_to_message?c.reply_to_message.text||c.reply_to_message.caption||`[message from ${c.reply_to_message.from?.first_name||"unknown"}]`:void 0,timestamp:new Date(c.date*1e3),raw:a,channelMeta:v};this.chatAccountMap.set(String(c.chat.id),e),this.handler(k).catch(S=>{this.log(`Error handling message: ${S.message}`)})}if(a.my_chat_member){let c=a.my_chat_member,l=String(c.chat.id),d=c.chat.title,g=c.new_chat_member.status,p=c.new_chat_member.user.username||e;this.groupStore.setBotStatus(l,d,e,t.agentBinding,p,g),this.log(`Group membership: @${p} is now "${g}" in "${d||l}"`)}}n=0}catch(s){n++;let i=Math.min(5e3*Math.pow(2,n-1),6e4);this.log(`Poll error (${e}): ${s.message} [retry in ${i/1e3}s, errors: ${n}]`),await new Promise(r=>setTimeout(r,i))}}listKnownChats(){let e=[];for(let[t,n]of this.chatAccountMap)J.isDirectMessage(t)&&e.push({id:t,kind:"dm",accountId:n});for(let t of this.groupStore.listGroups())e.push({id:t.id,name:t.name,kind:"group"});return e}async getChannelMeta(e){let t=this.groupStore.getGroupBots(e);if(t.length)return{channel:"telegram",agents:t.map(n=>({id:n.agentId,name:n.username,handle:`@${n.username}`})),facts:[`${t.length} bot(s) verified in this group`]}}async seedGroupMembership(e,t){this.log(`Seeding group membership for "${t||e}"`);for(let[n,s]of this.accounts){let i=this.botInfo.get(n);if(i)try{let o=(await this.apiCall(s.token,"getChatMember",{chat_id:Number(e),user_id:i.userId})).result?.status;o&&this.groupStore.setBotStatus(e,t,n,s.agentBinding,i.username,o)}catch{this.groupStore.setBotStatus(e,t,n,s.agentBinding,i.username,"left")}}this.log(`Seeded: ${this.groupStore.getGroupBots(e).length} bot(s) in "${t||e}"`)}async apiCall(e,t,n){let s=`https://api.telegram.org/bot${e}/${t}`,i=await fetch(s,{method:"POST",headers:{"Content-Type":"application/json"},body:n?JSON.stringify(n):void 0});if(!i.ok){let r=await i.text();throw new Error(`Telegram API error: ${i.status} ${r}`)}return i.json()}};import{mkdirSync as on,writeFileSync as ys}from"fs";import{resolve as an,join as bs}from"path";import{randomUUID as ws}from"crypto";import{readFileSync as ds,writeFileSync as hs,mkdirSync as gs,existsSync as rn}from"fs";import{resolve as us,dirname as ps}from"path";var we=class{contacts=new Map;chats=new Map;groups=new Map;cachePath;constructor(e){this.cachePath=us(e,"cache.json"),this.load()}listContacts(){return Array.from(this.contacts.values())}listChats(){return Array.from(this.chats.values())}listGroups(){return Array.from(this.groups.values())}getContact(e){return this.contacts.get(e)}getChat(e){return this.chats.get(e)}getGroup(e){return this.groups.get(e)}applyContactsUpdate(e){let t=new Date().toISOString();for(let n of e){let s=typeof n.id=="string"?n.id:"";if(!s)continue;let i=this.contacts.get(s)??{jid:s,phone:ms(s),updatedAt:t},r={...i,pushName:ot(n.notify,n.pushName,i.pushName),savedName:ot(n.name,n.verifiedName,i.savedName),status:ot(n.status,i.status),updatedAt:t};this.contacts.set(s,r)}}applyChatsUpsert(e){let t=new Date().toISOString();for(let n of e){let s=typeof n.id=="string"?n.id:"";if(!s)continue;let i={jid:s,name:typeof n.name=="string"?n.name:this.chats.get(s)?.name,isGroup:s.endsWith("@g.us"),lastMessageAt:fs(n.conversationTimestamp)??this.chats.get(s)?.lastMessageAt,unreadCount:typeof n.unreadCount=="number"?n.unreadCount:this.chats.get(s)?.unreadCount,updatedAt:t};this.chats.set(s,i)}}applyGroupsUpdate(e){let t=new Date().toISOString();for(let n of e){let s=typeof n.id=="string"?n.id:"";if(!s)continue;let i=this.groups.get(s)??{jid:s,subject:"",members:[],updatedAt:t},r=Array.isArray(n.participants)?n.participants.map(a=>({jid:typeof a.id=="string"?a.id:"",admin:typeof a.admin=="string"?a.admin:void 0})).filter(a=>a.jid):i.members,o={...i,subject:typeof n.subject=="string"?n.subject:i.subject,description:typeof n.desc=="string"?n.desc:i.description,owner:typeof n.owner=="string"?n.owner:i.owner,members:r,updatedAt:t};this.groups.set(s,o)}}applyHistorySet(e){Array.isArray(e.chats)&&this.applyChatsUpsert(e.chats),Array.isArray(e.contacts)&&this.applyContactsUpdate(e.contacts)}save(){let e={version:1,contacts:this.listContacts(),chats:this.listChats(),groups:this.listGroups()},t=ps(this.cachePath);rn(t)||gs(t,{recursive:!0}),hs(this.cachePath,JSON.stringify(e,null,2),"utf8")}load(){if(rn(this.cachePath))try{let e=JSON.parse(ds(this.cachePath,"utf8"));if(e.version!==1)return;for(let t of e.contacts??[])this.contacts.set(t.jid,t);for(let t of e.chats??[])this.chats.set(t.jid,t);for(let t of e.groups??[])this.groups.set(t.jid,t)}catch{}}};function ms(h){let e=h.split("@")[0]??"";return!e||h.endsWith("@lid")?"":e.replace(/[^0-9]/g,"")}function ot(...h){for(let e of h)if(typeof e=="string"&&e.length>0)return e}function fs(h){if(typeof h=="number"&&Number.isFinite(h))return h;if(h&&typeof h=="object"&&"low"in h&&"high"in h){let e=h.low;return h.high*4294967296+(e>>>0)}}var ve=class{name="whatsapp";sessionDir;defaultAgent;allowFrom;routes;handler;sock=null;reconnectAttempts=0;generation=0;sentMessageIds=new Set;log;cache;throttleMinMs=1500;throttleMaxPerMinute=20;lastReadAt=0;readsInWindow=[];onQR;onStatus;constructor(e,t=console.error.bind(console,"[whatsapp]")){this.sessionDir=an(e.sessionDir),this.defaultAgent=e.defaultAgent,this.allowFrom=e.allowFrom,this.routes=e.routes||[],this.onQR=e.onQR,this.onStatus=e.onStatus,this.log=t,this.cache=new we(this.sessionDir),e.throttle?.minMsBetweenCalls&&(this.throttleMinMs=e.throttle.minMsBetweenCalls),e.throttle?.maxCallsPerMinute&&(this.throttleMaxPerMinute=e.throttle.maxCallsPerMinute)}getCache(){return this.cache}isConnected(){return!!this.sock&&!!this.sock.user}resolveAgent(e,t,n){for(let s of this.routes){if(s.contact){let i=s.contact.replace(/\+/g,"");if(e.includes(i)||i.includes(e))return s.agent}if(s.group&&(t||n)){let i=s.group.toLowerCase();if(t?.toLowerCase().includes(i)||n?.toLowerCase().includes(i))return s.agent}}return this.defaultAgent}onMessage(e){this.handler=e}async start(){let e,t,n,s;try{s=await import("@whiskeysockets/baileys"),e=s.default||s.makeWASocket,t=s.useMultiFileAuthState,n=s.DisconnectReason}catch{this.log("WhatsApp requires @whiskeysockets/baileys. Install with:"),this.log(" npm install @whiskeysockets/baileys");return}on(this.sessionDir,{recursive:!0});let i=++this.generation;if(this.sock){try{this.sock.end(void 0)}catch{}this.sock=null}let{state:r,saveCreds:o}=await t(this.sessionDir),a;try{let{version:l}=await s.fetchLatestBaileysVersion();a=l,this.log(`WhatsApp Web version: ${l.join(".")}`)}catch{this.log("Could not fetch WA version, using default")}let c={level:"silent",trace:()=>{},debug:()=>{},info:()=>{},warn:()=>{},fatal:()=>{},error:(...l)=>this.log("WA error:",...l),child:()=>c};this.sock=e({auth:{creds:r.creds,keys:s.makeCacheableSignalKeyStore?s.makeCacheableSignalKeyStore(r.keys,c):r.keys},...a?{version:a}:{},logger:c,printQRInTerminal:!1,browser:["agentx","server","1.0"],syncFullHistory:!1,markOnlineOnConnect:!1}),this.sock.ev.on("creds.update",o),this.sock.ev.on("messaging-history.set",l=>{this.log(`WA history sync: ${l.messages?.length||0} messages, ${l.isLatest?"latest":"partial"}`);try{this.cache.applyHistorySet(l)}catch(d){this.log(`cache history-set error: ${d.message}`)}}),this.sock.ev.on("contacts.update",l=>{try{this.cache.applyContactsUpdate(l)}catch(d){this.log(`cache contacts.update error: ${d.message}`)}}),this.sock.ev.on("contacts.upsert",l=>{try{this.cache.applyContactsUpdate(l)}catch(d){this.log(`cache contacts.upsert error: ${d.message}`)}}),this.sock.ev.on("chats.upsert",l=>{try{this.cache.applyChatsUpsert(l)}catch(d){this.log(`cache chats.upsert error: ${d.message}`)}}),this.sock.ev.on("chats.update",l=>{try{this.cache.applyChatsUpsert(l)}catch(d){this.log(`cache chats.update error: ${d.message}`)}}),this.sock.ev.on("groups.update",l=>{try{this.cache.applyGroupsUpdate(l)}catch(d){this.log(`cache groups.update error: ${d.message}`)}}),this.sock.ev.on("connection.update",async l=>{let{connection:d,lastDisconnect:g,qr:p}=l;if(i===this.generation){if(p){this.log("Scan QR code with WhatsApp to connect:");try{this.onQR?.(p)}catch{}try{let{default:u}=await import("qrcode-terminal");u.generate(p,{small:!0})}catch{this.log(`QR: ${p}`),this.log("Install qrcode-terminal for visual QR: npm install qrcode-terminal")}}if(d)try{this.onStatus?.(d,g?.error?.message)}catch{}if(d==="open")try{this.onQR?.(null)}catch{}if(d==="close"){let u=g?.error?.output?.statusCode,m=g?.error?.output?.payload?.message||"";if(this.log(`WhatsApp connection closed (status: ${u}, reason: ${m})`),u===n?.loggedOut||u===401)this.log("Logged out. Delete session dir and restart to re-scan QR.");else if(u===440||u===408){if(this.reconnectAttempts++,this.reconnectAttempts>=5){this.log(`WhatsApp: giving up after ${this.reconnectAttempts} conflict attempts. Another session is active for this number \u2014 close it or re-scan QR.`);return}let f=Math.min(1e4*Math.pow(2,this.reconnectAttempts-1),12e4);this.log(`Reconnecting in ${f/1e3}s (attempt ${this.reconnectAttempts}/5)...`),setTimeout(()=>this.start(),f)}else u===515?(this.log("Stream error, reconnecting in 5s..."),setTimeout(()=>this.start(),5e3)):u!==void 0&&(this.log("Reconnecting in 3s..."),setTimeout(()=>this.start(),3e3))}d==="open"&&this.log("WhatsApp connected")}}),this.sock.ev.on("messages.upsert",async l=>{if(this.reconnectAttempts>0&&(this.log(`WhatsApp connection stable, resetting reconnect counter (was ${this.reconnectAttempts})`),this.reconnectAttempts=0),this.log(`WA messages.upsert: ${l.messages?.length||0} messages, type: ${l.type}`),!!this.handler)for(let d of l.messages||[]){let g=(d.key.remoteJid||"").replace(/@.*/,"").slice(-6),p=!!(d.message?.conversation||d.message?.extendedTextMessage?.text);if(this.log(`WA msg: from=${g} fromMe=${d.key.fromMe} hasText=${p} type=${Object.keys(d.message||{}).join(",")}`),d.key.remoteJid==="status@broadcast")continue;if(d.key.id&&this.sentMessageIds.has(d.key.id)){this.sentMessageIds.delete(d.key.id);continue}if(d.key.fromMe){let P=this.sock?.user,L=d.key.remoteJid||"",E=P?.id?.replace(/:.*/,"")||"",q=P?.lid?.replace(/:.*/,"")||"",B=L.replace(/:.*/,"").replace(/@.*/,"");if(!(B===E||B===q))continue}let u=d.message?.conversation||d.message?.extendedTextMessage?.text||d.message?.imageMessage?.caption||d.message?.videoMessage?.caption||"",m=d.message||{},f=!!m.imageMessage,b=!!m.audioMessage,y=!!m.videoMessage,w=!!m.documentMessage,v=!!m.stickerMessage,k=f||b||y||w||v;if(k&&!u&&(f?u="[Image attached \u2014 please describe what you see]":b?u="[Voice message attached \u2014 please transcribe and respond]":y?u="[Video attached]":w?u=`[Document: ${m.documentMessage?.fileName||"file"}]`:v&&(u="[Sticker]")),!u)continue;let S=d.key.remoteJid||"",T=S.endsWith("@g.us"),$=S.replace(/@.*$/,""),A=(T?d.key.participant||"":S).replace(/@.*$/,"");if(this.allowFrom?.length&&!d.key.fromMe&&!this.allowFrom.some(L=>{let E=L.replace(/\+/g,"");return A.includes(E)||$.includes(E)}))continue;let C=d.key.fromMe?"me":d.pushName||A,x;if(T&&this.sock)try{x=(await this.sock.groupMetadata(S)).subject}catch{}let _=this.resolveAgent(A,x,T?S:void 0);if(!_){this.log(`No route for ${T?`group ${x||S}`:A}, skipping`);continue}let O;if(k&&this.sock)try{let L=await(await import("@whiskeysockets/baileys")).downloadMediaMessage(d,"buffer",{},{reuploadRequest:this.sock.updateMediaMessage,logger:this.sock.logger});if(L){let E=m.imageMessage?.mimetype||m.audioMessage?.mimetype||"audio/ogg",q=E.split("/")[1]?.split(";")[0]||"bin",B=an(this.sessionDir,"../media/inbound");on(B,{recursive:!0});let K=m.documentMessage?.fileName||`${ws()}.${q}`,Xe=bs(B,K);ys(Xe,L),O={path:Xe,type:E,fileName:K},this.log(`WA media saved: ${E} -> ${Xe}`)}}catch(P){this.log(`WA media download failed: ${P.message}`)}let ze={id:d.key.id||String(Date.now()),channel:"whatsapp",accountId:"default",sender:{id:d.key.fromMe?(this.sock?.user?.id?.replace(/:.*/,"")||A)+"@s.whatsapp.net":A,name:C,username:A},group:T?{id:S,name:x||S}:void 0,text:u,media:O,replyTo:d.message?.extendedTextMessage?.contextInfo?.stanzaId,timestamp:new Date((d.messageTimestamp||0)*1e3),raw:d,resolvedAgent:_};this.handler(ze).catch(P=>{this.log(`Error handling message: ${P.message}`)})}})}async stop(){try{this.cache.save()}catch(e){this.log(`cache save error: ${e.message}`)}this.sock&&(this.sock.end(),this.sock=null)}async send(e){if(!this.sock)return this.log("WhatsApp not connected"),"";let t=e.chatId.includes("@")?e.chatId:`${e.chatId}@s.whatsapp.net`;try{let s=(await this.sock.sendMessage(t,{text:e.text}))?.key?.id||"";return s&&this.sentMessageIds.add(s),s}catch(n){return this.log(`Send error: ${n.message}`),""}}async editMessage(e,t,n){if(!this.sock)return!1;let s=e.includes("@")?e:`${e}@s.whatsapp.net`;try{return await this.sock.sendMessage(s,{text:n,edit:{remoteJid:s,id:t,fromMe:!0}}),!0}catch{return!1}}async sendTyping(e){if(!this.sock)return;let t=e.includes("@")?e:`${e}@s.whatsapp.net`;try{await this.sock.sendPresenceUpdate("composing",t)}catch{}}async react(e,t,n="\u{1F440}"){if(!this.sock)return;let s=e.includes("@")?e:`${e}@s.whatsapp.net`;try{await this.sock.sendMessage(s,{react:{text:n,key:{remoteJid:s,id:t}}})}catch{}}listContacts(){return this.cache.listContacts().map(cn)}listChats(){return this.cache.listChats().map(ks)}async getContactProfile(e){let t=this.cache.getContact(e);return t?cn(t):null}async getGroupMetadata(e){let t=this.cache.getGroup(e);if(t&&t.subject)return ke(t);if(!this.isConnected())return t?ke(t):null;try{let n=await this.throttleGate(()=>this.sock.groupMetadata(e));this.cache.applyGroupsUpdate([{id:e,subject:n?.subject,desc:n?.desc,owner:n?.owner,participants:n?.participants}]);let s=this.cache.getGroup(e);return s?ke(s):null}catch(n){return this.log(`getGroupMetadata(${e}) failed: ${n.message}`),t?ke(t):null}}async getHistory(e,t={}){if(!this.isConnected())return[];let n=Math.max(1,Math.min(500,t.limit??50));try{return(await this.throttleGate(async()=>{let i=this.sock;return typeof i.loadMessages=="function"?await i.loadMessages(e,n,t.before?{before:{id:t.before}}:void 0):typeof i.fetchMessagesFromWA=="function"?await i.fetchMessagesFromWA(e,n,t.before?{before:{id:t.before}}:void 0):[]})||[]).map(vs).filter(Boolean)}catch(s){return this.log(`getHistory(${e}) failed: ${s.message}`),[]}}async throttleGate(e){let t=Date.now();if(this.readsInWindow=this.readsInWindow.filter(s=>t-s<6e4),this.readsInWindow.length>=this.throttleMaxPerMinute){let s=6e4-(t-this.readsInWindow[0])+10;await ln(s)}let n=Date.now()-this.lastReadAt;return n<this.throttleMinMs&&await ln(this.throttleMinMs-n),this.lastReadAt=Date.now(),this.readsInWindow.push(this.lastReadAt),e()}};function cn(h){return{jid:h.jid,phone:h.phone,name:h.savedName||h.pushName||h.phone||h.jid,pushName:h.pushName,savedName:h.savedName,status:h.status,updatedAt:h.updatedAt}}function ks(h){return{jid:h.jid,name:h.name||h.jid.replace(/@.*$/,""),isGroup:h.isGroup,lastMessageAt:h.lastMessageAt,unreadCount:h.unreadCount}}function ke(h){return{jid:h.jid,subject:h.subject||"",description:h.description,owner:h.owner,members:h.members.map(e=>({jid:e.jid,admin:e.admin})),memberCount:h.members.length}}function vs(h){if(!h||!h.key)return null;let e=h.message||{},t=e.conversation||e.extendedTextMessage?.text||e.imageMessage?.caption||e.videoMessage?.caption||"",n;if(e.imageMessage?n={kind:"image",caption:e.imageMessage.caption}:e.audioMessage?n={kind:"audio"}:e.videoMessage?n={kind:"video",caption:e.videoMessage.caption}:e.documentMessage?n={kind:"document",filename:e.documentMessage.fileName}:e.stickerMessage&&(n={kind:"sticker"}),!t&&!n)return null;let s=typeof h.messageTimestamp=="number"?h.messageTimestamp:h.messageTimestamp?.low??void 0;return{id:h.key.id,fromJid:h.key.fromMe?h.key.remoteJid||"":h.key.participant||h.key.remoteJid||"",fromMe:!!h.key.fromMe,timestamp:s,text:t,media:n}}function ln(h){return new Promise(e=>setTimeout(e,Math.max(0,h)))}import{createServer as ai}from"http";import{writeFileSync as Tn,existsSync as ge,unlinkSync as ci,mkdirSync as In,readFileSync as Je,watch as li}from"fs";import{resolve as N,dirname as di}from"path";var $e=class{name="discord";token;agentBinding;handler;client=null;log;constructor(e,t=console.error.bind(console,"[discord]")){this.token=e.token,this.agentBinding=e.agentBinding,this.log=t}onMessage(e){this.handler=e}async start(){let e;try{e=await import("discord.js")}catch{this.log("Discord requires discord.js. Install with:"),this.log(" npm install discord.js");return}let{Client:t,GatewayIntentBits:n}=e;this.client=new t({intents:[n.Guilds,n.GuildMessages,n.MessageContent,n.DirectMessages]}),this.client.on("ready",()=>{this.log(`Discord connected as ${this.client.user?.tag}`)}),this.client.on("messageCreate",async s=>{if(!this.handler||s.author.bot)return;let i=s.mentions.users.has(this.client.user?.id),r=!s.guild;if(!i&&!r)return;let o=s.content;if(this.client.user&&(o=o.replace(new RegExp(`<@!?${this.client.user.id}>`,"g"),"").trim()),!o)return;let a={id:s.id,channel:"discord",accountId:"default",sender:{id:s.author.id,name:s.author.displayName||s.author.username,username:s.author.username},group:s.guild?{id:s.channelId,name:s.channel?.name||s.channelId}:void 0,text:o,replyTo:s.reference?.messageId,timestamp:s.createdAt,raw:s};this.handler(a).catch(c=>{this.log(`Error handling message: ${c.message}`)})});try{await this.client.login(this.token)}catch(s){this.log(`Discord login failed: ${s.message}`)}}async stop(){this.client&&(this.client.destroy(),this.client=null)}async send(e){if(!this.client)return"";try{let t=await this.client.channels.fetch(e.chatId);return t?.isTextBased()?(await t.send({content:e.text,...e.replyTo?{reply:{messageReference:e.replyTo}}:{}})).id:""}catch(t){return this.log(`Send error: ${t.message}`),""}}async editMessage(e,t,n){if(!this.client)return!1;try{let s=await this.client.channels.fetch(e);return s?.isTextBased()?(await(await s.messages.fetch(t)).edit(n),!0):!1}catch{return!1}}async sendTyping(e){if(this.client)try{let t=await this.client.channels.fetch(e);t?.isTextBased()&&await t.sendTyping()}catch{}}async react(e,t,n="\u{1F440}"){if(this.client)try{let s=await this.client.channels.fetch(e);if(!s?.isTextBased())return;await(await s.messages.fetch(t)).react(n)}catch{}}};var Se=class{name="slack";botToken;appToken;agentBinding;handler;socket=null;web=null;botUserId;log;constructor(e,t=console.error.bind(console,"[slack]")){this.botToken=e.botToken,this.appToken=e.appToken,this.agentBinding=e.agentBinding,this.log=t}onMessage(e){this.handler=e}async start(){let e,t;try{e=await import("@slack/socket-mode"),t=await import("@slack/web-api")}catch{this.log("Slack requires @slack/socket-mode and @slack/web-api. Install with:"),this.log(" npm install @slack/socket-mode @slack/web-api");return}let{SocketModeClient:n}=e,{WebClient:s}=t;this.web=new s(this.botToken);try{let r=await this.web.auth.test();this.botUserId=r.user_id,this.log(`Slack connected as @${r.user} (${this.botUserId}) in team ${r.team}`)}catch(r){this.log(`Slack auth.test failed: ${r.message}`);return}this.socket=new n({appToken:this.appToken});let i=async(r,o)=>{try{await o()}catch{}if(!this.handler)return;let a=r.event;if(!a||typeof a.text!="string"||a.bot_id||a.user===this.botUserId||a.subtype==="bot_message")return;let c=a.channel_type==="im",l=this.botUserId&&a.text.includes(`<@${this.botUserId}>`);if(!c&&!l)return;let d=a.text;if(this.botUserId&&(d=d.replace(new RegExp(`<@${this.botUserId}>`,"g"),"").trim()),!d)return;let g=a.user||"slack-user";try{let u=await this.web.users.info({user:a.user});g=u?.user?.profile?.display_name||u?.user?.real_name||g}catch{}let p={id:a.client_msg_id||a.ts,channel:"slack",accountId:"default",sender:{id:a.user,name:g,username:g},group:c?void 0:{id:a.channel,name:a.channel},text:d,replyTo:a.thread_ts&&a.thread_ts!==a.ts?a.thread_ts:void 0,timestamp:a.ts?new Date(parseFloat(a.ts)*1e3):new Date,raw:a};try{await this.handler(p)}catch(u){this.log(`Error handling message: ${u.message}`)}};this.socket.on("app_mention",i),this.socket.on("message",i),this.socket.on("error",r=>this.log(`Slack socket error: ${r?.message||r}`));try{await this.socket.start()}catch(r){this.log(`Slack socket start failed: ${r.message}`)}}async stop(){if(this.socket){try{await this.socket.disconnect()}catch{}this.socket=null}this.web=null}async send(e){if(!this.web)return"";try{return(await this.web.chat.postMessage({channel:e.chatId,text:e.text,thread_ts:e.replyTo,mrkdwn:e.parseMode!=="plain"})).ts}catch(t){return this.log(`Send error: ${t.message}`),""}}async editMessage(e,t,n){if(!this.web)return!1;try{return await this.web.chat.update({channel:e,ts:t,text:n}),!0}catch(s){return this.log(`Edit error: ${s.message}`),!1}}async react(e,t,n="eyes"){if(this.web)try{let s=n.replace(/^:|:$/g,"");await this.web.reactions.add({channel:e,timestamp:t,name:s})}catch{}}};import{createServer as $s}from"http";var Te=class{name="gitlab";config;handler;server;botUsername;botUsernames=new Set;sentNoteIds=new Set;usernameToAgent=new Map;agentToUsername=new Map;log;hooks;reactForwarder;sendNoteForwarder;logTimeForwarder;createIssueForwarder;setLabelsForwarder;constructor(e,t=console.error.bind(console,"[gitlab]"),n){this.config=e,this.log=t,this.hooks=n}setReactForwarder(e){this.reactForwarder=e}setSendNoteForwarder(e){this.sendNoteForwarder=e}setLogTimeForwarder(e){this.logTimeForwarder=e}setCreateIssueForwarder(e){this.createIssueForwarder=e}setSetLabelsForwarder(e){this.setLabelsForwarder=e}onMessage(e){this.handler=e}async start(){let e=[];this.config.token&&e.push({label:"global",token:this.config.token});for(let i of this.config.agentMappings||[])i.token&&e.push({label:i.agentId,token:i.token});let t=await Promise.allSettled(e.map(async({label:i,token:r})=>{let o=await fetch(`${this.config.host}/api/v4/user`,{headers:{"PRIVATE-TOKEN":r}});if(!o.ok)throw new Error(`${o.status}`);let a=await o.json();return{label:i,username:a.username}}));for(let i of t)if(i.status==="fulfilled"){let{label:r,username:o}=i.value;this.botUsernames.add(o),r==="global"?(this.botUsername=o,this.log(`Global bot user: ${o}`)):(this.usernameToAgent.set(o.toLowerCase(),r),this.agentToUsername.set(r,o.toLowerCase()),this.log(`Agent "${r}" -> GitLab user @${o}`))}for(let i of this.config.agentMappings||[]){for(let r of i.gitlabUsernames)this.botUsernames.add(r),(!this.usernameToAgent.has(r.toLowerCase())||i.node)&&this.usernameToAgent.set(r.toLowerCase(),i.agentId);!this.agentToUsername.has(i.agentId)&&i.gitlabUsernames.length>0&&this.agentToUsername.set(i.agentId,i.gitlabUsernames[0].toLowerCase())}let n=new Set((this.config.agentMappings??[]).map(i=>i.agentId)),s=[];for(let i of this.config.knownAgentIds??[]){if(n.has(i))continue;let r=[i,`noqta-${i}`];for(let o of r)this.botUsernames.add(o),this.usernameToAgent.has(o.toLowerCase())||this.usernameToAgent.set(o.toLowerCase(),i);this.agentToUsername.has(i)||this.agentToUsername.set(i,i.toLowerCase()),s.push(i)}this.log(`Bot users (${this.botUsernames.size}): ${[...this.botUsernames].join(", ")}`),this.log(`Username->Agent map: ${[...this.usernameToAgent.entries()].map(([i,r])=>`@${i}->${r}`).join(", ")}`),s.length>0&&this.log(`GitLab auto-derived defaults for ${s.length} agent(s): ${s.join(", ")} (override by adding an agentMappings entry)`),this.server=$s(async(i,r)=>{i.method==="POST"?await this.handleWebhook(i,r):(r.writeHead(200,{"Content-Type":"text/plain"}),r.end("GitLab webhook endpoint. POST events here."))}),this.server.on("error",i=>{i.code==="EADDRINUSE"?(this.log(`GitLab webhook port ${this.config.webhookPort} in use, retrying in 5s...`),setTimeout(()=>{this.server?.close(),this.server?.listen(this.config.webhookPort)},5e3)):this.log(`GitLab webhook server error: ${i.message}`)}),this.server.listen(this.config.webhookPort,()=>{this.log(`GitLab webhook listening on :${this.config.webhookPort}`)})}async stop(){this.server&&this.server.close()}async send(e){let t=e.chatId.split(":");if(t.length<3)return this.log(`Invalid chatId for GitLab reply: ${e.chatId}`),"";let n=t.pop(),s=t.pop(),i=t.join(":"),r=encodeURIComponent(i),o;switch(s){case"issue":o=`${this.config.host}/api/v4/projects/${r}/issues/${n}/notes`;break;case"merge_request":o=`${this.config.host}/api/v4/projects/${r}/merge_requests/${n}/notes`;break;default:return this.log(`Unsupported noteable type: ${s}`),""}let a=this.config.agentMappings?.find(d=>d.agentId===e.agentId),c=this.getAgentToken(e.agentId);if(!c&&a?.node&&this.sendNoteForwarder)try{let d=se(e.text,e.agentId||"unknown"),g=await this.sendNoteForwarder(a.node,i,s,n,e.agentId||"",d);return g&&this.sentNoteIds.add(g),g}catch(d){return this.log(`GitLab send forward to "${a.node}" failed: ${d.message} \u2014 skipping (would post as group bot)`),""}let l=c||this.config.token;U.webhook("gitlab","send",`agentId="${e.agentId}" token=${c?"per-agent":"GLOBAL("+this.botUsername+")"}`);try{let d=await fetch(o,{method:"POST",headers:{"Content-Type":"application/json","PRIVATE-TOKEN":l},body:JSON.stringify({body:se(e.text,e.agentId||"unknown")})});if(!d.ok){let u=await d.text();return this.log(`GitLab API error: ${d.status} ${u}`),""}let g=await d.json(),p=String(g.id||"");return p&&this.sentNoteIds.add(p),p}catch(d){return this.log(`GitLab send error: ${d.message}`),""}}async handleWebhook(e,t){if(this.config.webhookSecret&&e.headers["x-gitlab-token"]!==this.config.webhookSecret){t.writeHead(401,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"Invalid token"}));return}let s=await this.readBody(e),i=s.object_kind||s.event_type||"unknown";switch(this.log(`Event: ${i} from ${s.project?.path_with_namespace||"unknown"}`),i){case"note":await this.handleNote(s,t);break;case"issue":await this.handleIssue(s,t);break;case"merge_request":await this.handleMR(s,t);break;case"pipeline":await this.handlePipeline(s,t);break;default:this.log(`Unhandled event: ${i}`),t.writeHead(200),t.end("ok")}}async handleNote(e,t){if(!this.handler){t.writeHead(200),t.end("ok");return}let n=e.object_attributes.note,s=e.project.path_with_namespace,i=e.user,r=String(e.object_attributes.id),o=H(n);if(o){let S=n.match(/@(\w[\w.-]*)/g)?.map($=>$.slice(1).replace(/[.]+$/,""))||[];if(!S.some($=>{let I=this.usernameToAgent.get($.toLowerCase());return I&&I!==o})){this.log(`AgentX comment from ${o}, no cross-agent mention, skipping (note ${r})`),t.writeHead(200),t.end("ok");return}this.log(`Bot-to-bot handoff: ${o} \u2192 ${S.join(", ")} (note ${r})`)}if(this.sentNoteIds.has(r)){this.sentNoteIds.delete(r),t.writeHead(200),t.end("ok");return}let a=n.match(/@(\w[\w.-]*)/g)?.map(S=>S.slice(1).replace(/[.]+$/,""))||[];if(a.length===0){this.log(`No @mention in note ${r}, skipping`),t.writeHead(200),t.end("ok");return}if(this.isBotUser(i.username)&&!o){this.log(`Bot comment from ${i.username} without signature, skipping (note ${r})`),t.writeHead(200),t.end("ok");return}let c="",l="",d="";e.issue?(c="issue",l=String(e.issue.iid),d=e.issue.title):e.merge_request&&(c="merge_request",l=String(e.merge_request.iid),d=e.merge_request.title),U.webhook("gitlab","handleNote",`noteId=${r} mentions=[${a.join(",")}] user=${i.username}`);let g=this.resolveAgentFromMention(n);if(U.webhook("gitlab","resolve",`agentId=${g??"NONE"}`),!g){this.log(`[handleNote] @mentions in note ${r} are not agents (${a.join(", ")}), skipping`),t.writeHead(200),t.end("ok");return}let p=g,u=this.config.agentMappings?.find(S=>S.agentId===p),m=u?.token,f=u?.node;U.webhook("gitlab","token",`agent="${p}" mapping=${u?"found":"MISSING"} hasToken=${!!m} node=${f??"local"}`),this.reactToNote(s,c,l,e.object_attributes.id,m,f,p).catch(()=>{});let b=`${s}:${c}:${l}`,y=await this.getChannelMeta(b),w=Ut(n),v=await this.downloadNoteImages(w,s,m||this.config.token),k={id:String(e.object_attributes.id),channel:"gitlab",accountId:"default",sender:{id:b,name:i.name,username:i.username},text:`[GitLab ${c} #${l}: ${d}]
78
+ ${i.name} commented:
79
+ ${w}`,timestamp:new Date,raw:e,resolvedAgent:p,preferNode:u?.node,channelMeta:y?{...y,issue:{type:c,iid:l,title:d}}:void 0,media:v};this.handler(k).catch(S=>{this.log(`Error handling note: ${S.message}`)}),t.writeHead(200),t.end("ok")}async handleIssue(e,t){if(!this.handler){t.writeHead(200),t.end("ok");return}let n=e.object_attributes,s=e.project.path_with_namespace,i=this.resolveAgent(s),r=this.isBotUser(e.user.username);r&&this.log(`Issue #${n.iid} authored by bot "${e.user.username}" \u2014 default dispatch suppressed (hooks still fire)`);let o,a=!1;if(this.hooks?.has("on:gitlab-issue"))try{let l=await this.hooks.execute("on:gitlab-issue",{event:"on:gitlab-issue",issueEvent:e,project:s,iid:n.iid,title:n.title,description:n.description||"",url:n.url,action:n.action,usernameToAgent:Object.fromEntries(this.usernameToAgent.entries()),agentMappings:this.config.agentMappings||[],defaultAgentId:i||null});l.blocked&&(a=!0);let d=l.modified?.dispatch;Array.isArray(d)&&d.length>0&&(o=d.filter(g=>g&&typeof g.agentId=="string"))}catch(l){this.log(`on:gitlab-issue hook error: ${l.message}`)}if(o&&o.length>0){for(let l of o){let d=this.config.agentMappings?.find(f=>f.agentId===l.agentId),g=`${s}:issue:${n.iid}`,p=await this.getChannelMeta(g),m={id:`issue-hook-${n.iid}-${l.agentId}${l.assignee?`-${l.assignee}`:""}-${n.action}`,channel:"gitlab",accountId:"default",sender:{id:g,name:e.user.name,username:e.user.username},text:l.prompt||`[GitLab Issue #${n.iid} ${n.action}]: ${n.title}
80
+ ${n.description?.slice(0,500)||""}
81
+ URL: ${n.url}`,timestamp:new Date,raw:e,resolvedAgent:l.agentId,preferNode:l.preferNode||d?.node,channelMeta:p?{...p,issue:{type:"issue",iid:String(n.iid),title:n.title}}:void 0};this.log(`Issue #${n.iid} hook dispatch -> agent "${l.agentId}"${l.preferNode||d?.node?` (remote: ${l.preferNode||d?.node})`:""}`),this.handler(m).catch(f=>this.log(`Error handling hook dispatch: ${f.message}`))}t.writeHead(200),t.end("ok");return}if(a){this.log(`Issue #${n.iid}: on:gitlab-issue hook suppressed default dispatch`),t.writeHead(200),t.end("ok");return}if(r){t.writeHead(200),t.end("ok");return}let c={id:`issue-${n.iid}-${n.action}`,channel:"gitlab",accountId:"default",sender:{id:`${s}:issue:${n.iid}`,name:e.user.name,username:e.user.username},text:`[GitLab Issue #${n.iid} ${n.action}]: ${n.title}
82
+ ${n.description?.slice(0,500)||""}
83
+ URL: ${n.url}`,timestamp:new Date,raw:e,resolvedAgent:i};this.handler(c).catch(l=>this.log(`Error handling issue: ${l.message}`)),t.writeHead(200),t.end("ok")}async handleMR(e,t){if(!this.handler){t.writeHead(200),t.end("ok");return}if(this.isBotUser(e.user.username)){t.writeHead(200),t.end("ok");return}let n=e.object_attributes,s=e.project.path_with_namespace,i=this.resolveAgent(s),r={id:`mr-${n.iid}-${n.action}`,channel:"gitlab",accountId:"default",sender:{id:`${s}:merge_request:${n.iid}`,name:e.user.name,username:e.user.username},text:`[GitLab MR !${n.iid} ${n.action}]: ${n.title}
84
+ Branch: ${n.source_branch} -> ${n.target_branch}
85
+ ${n.description?.slice(0,500)||""}
86
+ URL: ${n.url}`,timestamp:new Date,raw:e,resolvedAgent:i};this.handler(r).catch(o=>this.log(`Error handling MR: ${o.message}`)),t.writeHead(200),t.end("ok")}async handlePipeline(e,t){let n=e.object_attributes,s=e.project.path_with_namespace;if(["success","failed","canceled"].includes(n.status)&&this.hooks?.has("on:gitlab-pipeline")&&this.hooks.execute("on:gitlab-pipeline",{event:"on:gitlab-pipeline",pipelineId:n.id,status:n.status,ref:n.ref,duration:n.duration,project:s,projectId:e.project?.id,raw:e}).catch(a=>this.log(`on:gitlab-pipeline hook error: ${a.message}`)),!this.handler||n.status!=="failed"){t.writeHead(200),t.end("ok");return}let r=this.resolveAgent(s),o={id:`pipeline-${n.id}`,channel:"gitlab",accountId:"default",sender:{id:`${s}:pipeline:${n.id}`,name:e.user.name,username:e.user.username},text:`[GitLab Pipeline FAILED] Project: ${s}
87
+ Ref: ${n.ref}
88
+ Duration: ${n.duration}s
89
+ Please investigate the failure.`,timestamp:new Date,raw:e,resolvedAgent:r};this.handler(o).catch(a=>this.log(`Error handling pipeline: ${a.message}`)),t.writeHead(200),t.end("ok")}resolveAgentFromMention(e){let t=e.match(/@(\w[\w.-]*)/g)?.map(n=>n.slice(1).replace(/[.]+$/,"").toLowerCase())||[];if(t.length!==0)for(let n of t){let s=this.usernameToAgent.get(n);if(s)return this.log(`Mention @${n} -> agent "${s}" (resolved from token)`),s}}resolveAgent(e){for(let t of this.config.routes)if(t.project===e||t.project==="*")return t.agent}async getChannelMeta(e){let t=e.split(":"),n=t[0],s=t[1],i=t[2],r=[];for(let a of this.config.routes)(a.project===n||a.project==="*")&&r.push({id:a.agent,name:a.agent});for(let a of this.config.agentMappings||[])r.some(c=>c.id===a.agentId)||r.push({id:a.agentId,name:a.agentId});return{channel:"gitlab",agents:r,project:n,issue:s&&i?{type:s,iid:i,title:""}:void 0,facts:["This is a GitLab webhook event \u2014 respond as a GitLab comment","Do NOT use Telegram handles or delegate to other agents","Do NOT mention other agents by name \u2014 you are the only agent responding to this event","Stay in your role \u2014 do not act as or speak for other agents"]}}isBotUser(e){return this.botUsernames.has(e)}getAgentToken(e){return!e||!this.config.agentMappings?.length?void 0:this.config.agentMappings.find(n=>n.agentId===e)?.token}getTokenForMentionedAgent(e){if(!this.config.agentMappings?.length)return;let t=e.match(/@(\w[\w.-]*)/g)?.map(n=>n.slice(1).replace(/[.]+$/,"").toLowerCase())||[];for(let n of this.config.agentMappings)if(n.token&&n.gitlabUsernames.some(s=>t.includes(s.toLowerCase())))return n.token}async reactToNote(e,t,n,s,i,r,o){if(!i&&r&&o&&this.reactForwarder){this.log(`Forwarding \u{1F440} reaction for "${o}" to mesh peer "${r}"`),await this.reactForwarder(r,e,t,n,s,o);return}if(!i){this.log(`No token for reaction on note ${s} (agent "${o}") \u2014 skipping`);return}let a=encodeURIComponent(e),c;switch(t){case"issue":c=`${this.config.host}/api/v4/projects/${a}/issues/${n}/notes/${s}/award_emoji`;break;case"merge_request":c=`${this.config.host}/api/v4/projects/${a}/merge_requests/${n}/notes/${s}/award_emoji`;break;default:return}try{let l=await fetch(c,{method:"POST",headers:{"Content-Type":"application/json","PRIVATE-TOKEN":i},body:JSON.stringify({name:"eyes"})});l.ok||this.log(`Reaction failed on note ${s}: ${l.status}`)}catch(l){this.log(`Failed to react to note ${s}: ${l.message}`)}}async logTimeSpent(e,t,n){let s=e.split(":");if(s.length<3)return;let i=s.pop(),r=s.pop(),o=s.join(":"),a=Math.max(60,Math.round(t/1e3)),c=Math.floor(a/3600),l=Math.ceil(a%3600/60),d=c>0?`${c}h${l>0?` ${l}m`:""}`:`${l}m`,g=encodeURIComponent(o),p=r==="merge_request"?"merge_requests":"issues",u=`${this.config.host}/api/v4/projects/${g}/${p}/${i}/add_spent_time`,m=this.config.agentMappings?.find(y=>y.agentId===n),f=this.getAgentToken(n);if(!f&&m?.node&&this.logTimeForwarder){try{await this.logTimeForwarder(m.node,o,r,i,n||"",t),this.log(`Time logged (via peer "${m.node}"): ${d} on ${o} ${p}/${i} (${n})`)}catch(y){this.log(`Time log forward to "${m.node}" failed: ${y.message} \u2014 skipping (would log as group bot)`)}return}let b=f||this.config.token;try{let y=await fetch(u,{method:"POST",headers:{"Content-Type":"application/json","PRIVATE-TOKEN":b},body:JSON.stringify({duration:d})});if(y.ok)this.log(`Time logged: ${d} on ${o} ${r} #${i} (${n||"global"})`);else{let w=await y.text();this.log(`Time log failed (${y.status}): ${w.slice(0,100)}`)}}catch(y){this.log(`Time log error: ${y.message}`)}}async createIssue(e){let{project:t,title:n,description:s="",labels:i=[],assignees:r=[],agentId:o}=e,a=this.config.agentMappings?.find(u=>u.agentId===o),c=this.getAgentToken(o);if(!c&&a?.node&&this.createIssueForwarder)try{let u=await this.createIssueForwarder(a.node,t,n,s,i,r,o||"");return u&&this.log(`Issue created (via peer "${a.node}") #${u.iid} on ${t}`),u}catch(u){return this.log(`Issue-create forward to "${a.node}" failed: ${u.message}`),null}let l=c||this.config.token;if(!l)return this.log(`Issue-create failed: no token available for agent "${o||"global"}"`),null;let d=encodeURIComponent(t),g=`${this.config.host}/api/v4/projects/${d}/issues`,p=new URLSearchParams;p.set("title",n),s&&p.set("description",s),i.length>0&&p.set("labels",i.join(","));for(let u of r)try{let m=await fetch(`${this.config.host}/api/v4/users?username=${encodeURIComponent(u)}`,{headers:{"PRIVATE-TOKEN":l}});if(m.ok){let f=await m.json();f[0]?.id&&p.append("assignee_ids[]",String(f[0].id))}}catch(m){this.log(`Issue-create: failed to resolve assignee "${u}": ${m.message}`)}try{let u=await fetch(g,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded","PRIVATE-TOKEN":l},body:p.toString()});if(!u.ok){let f=await u.text();return this.log(`Issue-create failed (${u.status}): ${f.slice(0,200)}`),null}let m=await u.json();return this.log(`Issue created #${m.iid} on ${t} (${o||"global"})`),{iid:m.iid,url:m.web_url}}catch(u){return this.log(`Issue-create error: ${u.message}`),null}}async setLabels(e){let{project:t,kind:n="issue",iid:s,add:i=[],remove:r=[],agentId:o}=e,a=this.config.agentMappings?.find(u=>u.agentId===o),c=this.getAgentToken(o);if(!c&&a?.node&&this.setLabelsForwarder)try{let u=await this.setLabelsForwarder(a.node,t,n,s,i,r,o||"");return u&&this.log(`Labels updated (via peer "${a.node}") on ${t} ${n}/${s}`),u}catch(u){return this.log(`setLabels forward to "${a.node}" failed: ${u.message}`),null}let l=c||this.config.token;if(!l)return this.log(`setLabels failed: no token for "${o||"global"}"`),null;let d=n==="merge_request"?"merge_requests":"issues",g=`${this.config.host}/api/v4/projects/${encodeURIComponent(t)}/${d}/${s}`,p=new URLSearchParams;i.length&&p.set("add_labels",i.join(",")),r.length&&p.set("remove_labels",r.join(","));try{let u=await fetch(g,{method:"PUT",headers:{"Content-Type":"application/x-www-form-urlencoded","PRIVATE-TOKEN":l},body:p.toString()});if(!u.ok){let f=await u.text();return this.log(`setLabels failed (${u.status}): ${f.slice(0,200)}`),null}return(await u.json()).labels??[]}catch(u){return this.log(`setLabels error: ${u.message}`),null}}async getLabels(e){let{project:t,kind:n="issue",iid:s}=e;if(!this.config.token)return null;let i=n==="merge_request"?"merge_requests":"issues",r=`${this.config.host}/api/v4/projects/${encodeURIComponent(t)}/${i}/${s}`;try{let o=await fetch(r,{headers:{"PRIVATE-TOKEN":this.config.token}});return o.ok?(await o.json()).labels??[]:(this.log(`getLabels failed (${o.status})`),null)}catch(o){return this.log(`getLabels error: ${o.message}`),null}}async downloadNoteImages(e,t,n){let s=/!\[[^\]]*\]\(([^)]+\.(?:png|jpg|jpeg|gif|webp|svg))\)/gi,i=[...e.matchAll(s)];if(i.length===0)return;let r=i[0][1],o;if(r.startsWith("http"))o=r;else{let a=encodeURIComponent(t);o=`${this.config.host}/${t}${r}`}try{let a=await fetch(o,{headers:{"PRIVATE-TOKEN":n}});if(!a.ok){this.log(`Failed to download image: ${a.status} ${o}`);return}let c=Buffer.from(await a.arrayBuffer()),l=a.headers.get("content-type")||"image/png",d=l.split("/")[1]?.split(";")[0]||"png",{mkdirSync:g,writeFileSync:p}=await import("fs"),{resolve:u}=await import("path"),{randomUUID:m}=await import("crypto"),f=u(process.cwd(),".agentx/media/gitlab");g(f,{recursive:!0});let b=`${m().slice(0,8)}.${d}`,y=u(f,b);return p(y,c),this.log(`Downloaded GitLab image: ${y} (${c.length} bytes)`),{path:y,type:l,fileName:r.split("/").pop()||b}}catch(a){this.log(`Image download error: ${a.message}`);return}}async readBody(e){return new Promise(t=>{let n="";e.on("data",s=>n+=s.toString()),e.on("end",()=>{try{t(n?JSON.parse(n):{})}catch{t({raw:n})}}),e.on("error",()=>t({}))})}};import{createHmac as Ss,createSign as Ts}from"crypto";import{readFileSync as dn}from"fs";var Ie=class{name="github";config;handler;globalToken;botUsernames=new Set;sentCommentIds=new Set;log;sendCommentForwarder;appPrivateKey;installationTokens=new Map;constructor(e,t=console.error.bind(console,"[github]")){this.config=e,this.log=t}setSendCommentForwarder(e){this.sendCommentForwarder=e}onMessage(e){this.handler=e}async start(){if((this.config.clientId||this.config.appId)&&this.config.privateKeyFile)try{this.appPrivateKey=dn(this.config.privateKeyFile,"utf-8").trim(),this.log(`GitHub App mode: issuer=${this.config.clientId||this.config.appId}`);let t=this.generateAppJWT(),n=await fetch("https://api.github.com/app",{headers:{Authorization:`Bearer ${t}`,Accept:"application/vnd.github+json","User-Agent":"AgentX"}});if(n.ok){let s=await n.json();this.log(`GitHub App: "${s.name}" (slug: ${s.slug})`),s.slug&&this.botUsernames.add(`${s.slug}[bot]`.toLowerCase())}else this.log(`GitHub App auth failed: ${n.status} ${await n.text().catch(()=>"")}`)}catch(t){this.log(`Failed to load GitHub App key: ${t.message}`)}if(!this.appPrivateKey&&(this.globalToken=this.resolveToken(this.config.token,this.config.tokenFile),this.globalToken))try{let t=await fetch("https://api.github.com/user",{headers:{Authorization:`Bearer ${this.globalToken}`,"User-Agent":"AgentX"}});if(t.ok){let n=await t.json();this.log(`GitHub PAT fallback: ${n.login}`)}}catch(t){this.log(`Failed to resolve GitHub user: ${t.message}`)}for(let t of this.config.agentMappings||[])for(let n of t.githubUsernames)this.botUsernames.add(n.toLowerCase());this.log(`GitHub channel started (${this.config.routes.length} routes, ${this.botUsernames.size} bot users)`)}async stop(){}async handleWebhook(e,t,n){if(this.config.webhookSecret){let r=e["x-hub-signature-256"],o=n||JSON.stringify(t);if(!this.verifySignature(o,r)){this.log("GitHub webhook signature verification failed");return}}let s=e["x-github-event"]||"unknown",i=t.repository?.full_name||"unknown";switch(this.log(`GitHub event: ${s} from ${i}`),s){case"issue_comment":await this.handleIssueComment(t);break;case"pull_request":await this.handlePR(t);break;case"pull_request_review":await this.handlePRReview(t);break;case"pull_request_review_comment":await this.handlePRReviewComment(t);break;case"issues":await this.handleIssue(t);break;case"push":await this.handlePush(t);break;case"ping":this.log("GitHub webhook ping received");break;default:this.log(`Unhandled GitHub event: ${s}`)}}async send(e){let t=e.chatId.split(":");if(t.length<3)return this.log(`Invalid chatId for GitHub reply: ${e.chatId}`),"";let n=parseInt(t.pop(),10),s=t.pop(),i=t.join(":"),r=this.config.agentMappings?.find(u=>u.agentId===e.agentId),o=this.getAgentToken(e.agentId),a=e.agentId||"unknown",l=!!this.appPrivateKey?"":`> \u{1F916} **${a}** (via AgentX)
90
+
91
+ `,d=se(`${l}${e.text}`,a),g=o||await this.getTokenForRepo(i);if(!g&&r?.node&&this.sendCommentForwarder)try{let u=await this.sendCommentForwarder(r.node,i,n,a,d);if(u)return this.sentCommentIds.add(u),u;this.log(`GitHub send forward to "${r.node}" returned empty \u2014 no local token available either`)}catch(u){this.log(`GitHub send forward to "${r.node}" failed: ${u.message}`)}if(!g)return this.log(`No GitHub token available for posting comment (agent: ${e.agentId})`),"";let p=`https://api.github.com/repos/${i}/issues/${n}/comments`;try{let u=await fetch(p,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${g}`,Accept:"application/vnd.github+json","User-Agent":"AgentX"},body:JSON.stringify({body:d})});if(!u.ok){let b=await u.text();return this.log(`GitHub API error: ${u.status} ${b.slice(0,200)}`),""}let m=await u.json(),f=String(m.id||"");return f&&this.sentCommentIds.add(f),f}catch(u){return this.log(`GitHub send error: ${u.message}`),""}}async react(e,t,n){let s=e.split(":");if(s.length<3)return;s.pop(),s.pop();let i=s.join(":"),r=await this.getTokenForRepo(i);if(!(!r||!t))try{await fetch(`https://api.github.com/repos/${i}/issues/comments/${t}/reactions`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${r}`,Accept:"application/vnd.github+json","User-Agent":"AgentX"},body:JSON.stringify({content:"eyes"})})}catch{}}async getChannelMeta(e){let t=e.split(":"),n=t[0]+(t.length>1,""),s=t.length>=2?t[t.length-2]:void 0,i=t.length>=3?t[t.length-1]:void 0,r=t.slice(0,-2),o=r.length>0?r.join(":"):t[0],a=[];for(let c of this.config.routes)(c.repo===o||c.repo==="*")&&a.push({id:c.agent,name:c.agent});return{channel:"github",agents:a,project:o,issue:s&&i?{type:s,iid:i,title:""}:void 0,facts:["This is a GitHub webhook event \u2014 respond as a GitHub comment","Use `gh` CLI or the GitHub API to interact with the repository","Do NOT use Telegram handles or delegate to other agents"]}}async handleIssueComment(e){if(!this.handler){this.log("[github] issue_comment dropped: no handler attached");return}if(e.action!=="created"){this.log(`[github] issue_comment skipped: action="${e.action}" (only "created" routes)`);return}let t=e.comment,n=e.repository.full_name,s=t.user,i=H(t.body);if(i){this.log(`AgentX comment from ${i}, skipping (comment ${t.id})`);return}if(this.isBotUser(s.login)){this.log(`Bot user ${s.login}, skipping (comment ${t.id})`);return}let r=String(t.id);if(this.sentCommentIds.has(r)){this.log(`[github] echo skip: comment ${r} matched our own post`),this.sentCommentIds.delete(r);return}let o=!!e.issue.pull_request,c=`${n}:${o?"pull":"issue"}:${e.issue.number}`,l=this.resolveAgent(n),d=l?this.config.agentMappings?.find(u=>u.agentId===l):void 0,g=await this.getChannelMeta(c),p={id:r,channel:"github",accountId:"default",sender:{id:c,name:s.login,username:s.login},text:`[GitHub ${o?"PR":"Issue"} #${e.issue.number}: ${e.issue.title}]
92
+ ${s.login} commented:
93
+ ${t.body}`,timestamp:new Date,raw:e,resolvedAgent:l,preferNode:d?.node,channelMeta:g};this.reactToComment(n,t.id).catch(()=>{}),this.handler(p).catch(u=>this.log(`Error handling issue_comment: ${u.message}`))}async handlePR(e){if(!this.handler)return;let t=e.pull_request,n=e.repository.full_name;if(this.isBotUser(t.user.login)||!["opened","reopened","ready_for_review"].includes(e.action))return;let i=`${n}:pull:${t.number}`,r=this.resolveAgent(n),o=r?this.config.agentMappings?.find(l=>l.agentId===r):void 0,a=await this.getChannelMeta(i),c={id:`pr-${n}-${t.number}`,channel:"github",accountId:"default",sender:{id:i,name:t.user.login,username:t.user.login},text:`[GitHub PR #${t.number} ${e.action}]: ${t.title}
94
+ Branch: ${t.head.ref} -> ${t.base.ref}
95
+ ${t.body?.slice(0,500)||""}
96
+ URL: ${t.html_url}`,timestamp:new Date,raw:e,resolvedAgent:r,preferNode:o?.node,channelMeta:a};this.handler(c).catch(l=>this.log(`Error handling PR: ${l.message}`))}async handlePRReview(e){if(!this.handler||e.action!=="submitted")return;let t=e.review,n=e.repository.full_name;if(this.isBotUser(t.user.login)||t.state==="commented"&&!t.body)return;let s=`${n}:pull:${e.pull_request.number}`,i=this.resolveAgent(n),r=i?this.config.agentMappings?.find(a=>a.agentId===i):void 0,o={id:`review-${t.id}`,channel:"github",accountId:"default",sender:{id:s,name:t.user.login,username:t.user.login},text:`[GitHub PR #${e.pull_request.number} Review (${t.state})]: ${e.pull_request.title}
97
+ ${t.user.login} reviewed:
98
+ ${t.body||"(no body)"}`,timestamp:new Date,raw:e,resolvedAgent:i,preferNode:r?.node};this.handler(o).catch(a=>this.log(`Error handling PR review: ${a.message}`))}async handlePRReviewComment(e){if(!this.handler||e.action!=="created")return;let t=e.comment,n=e.repository.full_name;if(this.isBotUser(t.user.login)||H(t.body))return;let s=`${n}:pull:${e.pull_request.number}`,i=this.resolveAgent(n),r=i?this.config.agentMappings?.find(a=>a.agentId===i):void 0,o={id:`review-comment-${t.id}`,channel:"github",accountId:"default",sender:{id:s,name:t.user.login,username:t.user.login},text:`[GitHub PR #${e.pull_request.number} Review Comment]: ${e.pull_request.title}
99
+ ${t.user.login} commented on ${t.path}:${t.line}:
100
+ ${t.body}`,timestamp:new Date,raw:e,resolvedAgent:i,preferNode:r?.node};this.handler(o).catch(a=>this.log(`Error handling PR review comment: ${a.message}`))}async handleIssue(e){if(!this.handler)return;let t=e.issue,n=e.repository.full_name;if(this.isBotUser(t.user.login)||!["opened","assigned","reopened"].includes(e.action))return;let i=`${n}:issue:${t.number}`,r=this.resolveAgent(n),o=r?this.config.agentMappings?.find(c=>c.agentId===r):void 0,a={id:`issue-${n}-${t.number}`,channel:"github",accountId:"default",sender:{id:i,name:t.user.login,username:t.user.login},text:`[GitHub Issue #${t.number} ${e.action}]: ${t.title}
101
+ ${t.body?.slice(0,500)||""}
102
+ URL: ${t.html_url}`,timestamp:new Date,raw:e,resolvedAgent:r,preferNode:o?.node};this.handler(a).catch(c=>this.log(`Error handling issue: ${c.message}`))}async handlePush(e){if(!this.handler)return;let t=e.repository.full_name,n=e.ref,s=e.commits||[];if(s.length===0)return;let i=this.resolveAgent(t),r=i?this.config.agentMappings?.find(c=>c.agentId===i):void 0,o=s.slice(0,5).map(c=>` - ${c.message.split(`
103
+ `)[0]} (${c.author?.name||c.author?.username||""})`).join(`
104
+ `),a={id:`push-${s[0]?.id?.slice(0,8)||Date.now()}`,channel:"github",accountId:"default",sender:{id:`${t}:push:${n}`,name:e.pusher.name,username:e.pusher.name},text:`[GitHub Push] ${t} ref:${n}
105
+ ${s.length} commit(s):
106
+ ${o}`,timestamp:new Date,raw:e,resolvedAgent:i,preferNode:r?.node};this.handler(a).catch(c=>this.log(`Error handling push: ${c.message}`))}resolveAgent(e){for(let t of this.config.routes)if(t.repo===e||t.repo==="*")return t.agent}isBotUser(e){return this.botUsernames.has(e.toLowerCase())}getAgentToken(e){if(!e||!this.config.agentMappings?.length)return;let t=this.config.agentMappings.find(n=>n.agentId===e);if(t)return this.resolveToken(t.token,t.tokenFile)}resolveToken(e,t){if(e)return e;if(t)try{return dn(t,"utf-8").trim().split(`
107
+ `)[0].trim()}catch(n){this.log(`Failed to read token file ${t}: ${n.message}`)}}verifySignature(e,t){if(!t||!this.config.webhookSecret)return!1;let n="sha256="+Ss("sha256",this.config.webhookSecret).update(e).digest("hex");return t===n}async reactToComment(e,t){let n=await this.getTokenForRepo(e);if(n)try{await fetch(`https://api.github.com/repos/${e}/issues/comments/${t}/reactions`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${n}`,Accept:"application/vnd.github+json","User-Agent":"AgentX"},body:JSON.stringify({content:"eyes"})})}catch{}}async getTokenForRepo(e){if(this.appPrivateKey&&this.config.appId&&e){let t=await this.getInstallationToken(e);if(t)return t}return this.globalToken}generateAppJWT(){let e=Math.floor(Date.now()/1e3),t={iat:e-60,exp:e+600,iss:this.config.clientId||String(this.config.appId)},n=Buffer.from(JSON.stringify({alg:"RS256",typ:"JWT"})).toString("base64url"),s=Buffer.from(JSON.stringify(t)).toString("base64url"),i=`${n}.${s}`,r=Ts("RSA-SHA256");r.update(i);let o=r.sign(this.appPrivateKey,"base64url");return`${i}.${o}`}async getInstallationToken(e){let t=this.installationTokens.get(e);if(t&&t.expiresAt>Date.now()+6e4)return t.token;try{let n=this.generateAppJWT(),s=await fetch(`https://api.github.com/repos/${e}/installation`,{headers:{Authorization:`Bearer ${n}`,Accept:"application/vnd.github+json","User-Agent":"AgentX"}});if(!s.ok){this.log(`GitHub App not installed on ${e}: ${s.status}`);return}let i=await s.json(),r=await fetch(`https://api.github.com/app/installations/${i.id}/access_tokens`,{method:"POST",headers:{Authorization:`Bearer ${n}`,Accept:"application/vnd.github+json","User-Agent":"AgentX"}});if(!r.ok){this.log(`Failed to get installation token for ${e}: ${r.status}`);return}let o=await r.json(),a=new Date(o.expires_at).getTime();return this.installationTokens.set(e,{token:o.token,expiresAt:a}),this.log(`Got installation token for ${e} (expires: ${o.expires_at})`),o.token}catch(n){this.log(`Installation token error for ${e}: ${n.message}`);return}}};function G(h){return h.toLowerCase().replace(/[^a-z0-9]/g,"")}var xe=class{subs=new Set;log;forwarder;localName;allowedCallers;pending=new Map;cleanupTimer;ringHandler;recentRings=new Map;constructor(e,t=[],n=console.error.bind(console,"[webrtc]")){this.localName=e,this.allowedCallers=new Set(t.map(G)),this.log=n,this.cleanupTimer=setInterval(()=>this.cleanupExpired(),1e4)}pendingKey(e,t){return`${e}|${G(t)}`}cleanupExpired(){let e=Date.now();for(let[t,n]of this.pending){let s=n.filter(i=>i.expiresAt>e);s.length===0?this.pending.delete(t):s.length!==n.length&&this.pending.set(t,s)}for(let[t,n]of this.recentRings)n<=e&&this.recentRings.delete(t)}setForwarder(e){this.forwarder=e}setRingHandler(e){this.ringHandler=e}subscribe(e,t,n){return this.addSubscriber(e,t,{kind:"sse",res:n})}subscribeInternal(e,t,n){return this.addSubscriber(e,t,{kind:"callback",deliver:n})}addSubscriber(e,t,n){let s={callId:e,recipient:t,sink:n};this.subs.add(s);let i=n.kind==="sse"?"sse":"internal";this.log(`subscribe[${i}] call=${e} as=${t} (${this.subs.size} total)`);let r=this.pendingKey(e,t),o=this.pending.get(r);if(o&&o.length){let a=Date.now(),c=o.filter(l=>l.expiresAt>a);for(let{signal:l}of c)this.deliverTo(s,l);this.pending.delete(r),c.length&&this.log(`flushed ${c.length} buffered signal(s) to new subscriber call=${e} as=${t}`)}return()=>{this.subs.delete(s),this.log(`unsubscribe[${i}] call=${e} as=${t} (${this.subs.size} remaining)`)}}deliverTo(e,t){try{return e.sink.kind==="sse"?e.sink.res.write(`event: signal
108
+ data: ${JSON.stringify(t)}
109
+
110
+ `):e.sink.deliver(t),!0}catch{return!1}}async handleOutgoing(e){let t=G(e.to);if(t===G(this.localName)||this.hasSubscriberFor(e.callId,t))return this.fanOut(e),{ok:!0};if(!this.forwarder)return{ok:!1,error:"mesh forwarder not wired"};let s=await this.forwarder(e.to,e);return s?{ok:s}:{ok:!1,error:`forward to "${e.to}" failed`}}hasSubscriberFor(e,t){for(let n of this.subs)if(n.callId===e&&G(n.recipient)===t)return!0;return!1}handleIncoming(e){if((e.kind==="offer"||e.kind==="ring")&&this.allowedCallers.size>0&&!this.allowedCallers.has(G(e.from)))return this.log(`reject inbound ${e.kind} from "${e.from}": not in allowedCallers`),{ok:!1,error:"caller not allowed"};if(e.kind==="ring"){let n=Date.now(),s=this.recentRings.get(e.callId);if(s&&s>n)return this.log(`dedup ring for call=${e.callId} (already notified)`),{ok:!0};if(this.recentRings.set(e.callId,n+3e5),this.ringHandler)try{this.ringHandler(e)}catch(i){this.log(`ringHandler threw: ${i.message}`)}else this.log(`ring received for call=${e.callId} but no handler wired`);return{ok:!0}}if(this.fanOut(e)===0){let n=this.pendingKey(e.callId,e.to),s=this.pending.get(n)||[];s.push({signal:e,expiresAt:Date.now()+3e4}),this.pending.set(n,s),this.log(`buffered ${e.kind} for call=${e.callId} to=${e.to} (no subscriber yet, ${s.length} pending)`)}return{ok:!0}}fanOut(e){let t=0,n=G(e.to);for(let s of this.subs)s.callId===e.callId&&G(s.recipient)===n&&(this.deliverTo(s,e)?t++:this.subs.delete(s));return t}get subscriberCount(){return this.subs.size}shutdown(){this.cleanupTimer&&clearInterval(this.cleanupTimer);for(let e of this.subs)if(e.sink.kind==="sse")try{e.sink.res.end()}catch{}this.subs.clear(),this.pending.clear()}};var hn=`<!doctype html>
111
+ <html lang="en">
112
+ <head>
113
+ <meta charset="utf-8">
114
+ <meta name="viewport" content="width=device-width,initial-scale=1">
115
+ <title>AgentX Call</title>
116
+ <style>
117
+ :root { color-scheme: dark; }
118
+ body { margin: 0; font: 14px/1.4 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: #0e1116; color: #e6edf3; }
119
+ header { padding: 10px 14px; border-bottom: 1px solid #21262d; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
120
+ header h1 { font-size: 15px; margin: 0; font-weight: 600; }
121
+ header .sp { flex: 1; }
122
+ main { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; padding: 8px; }
123
+ video { width: 100%; aspect-ratio: 16 / 9; background: #161b22; border-radius: 6px; object-fit: cover; }
124
+ .tile { position: relative; }
125
+ .tile .label { position: absolute; top: 6px; left: 8px; font-size: 11px; background: rgba(0,0,0,.55); padding: 2px 6px; border-radius: 3px; }
126
+ form { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
127
+ input, select, button { background: #21262d; color: #e6edf3; border: 1px solid #30363d; border-radius: 4px; padding: 6px 10px; font: inherit; }
128
+ button { cursor: pointer; }
129
+ button:hover { background: #30363d; }
130
+ button.primary { background: #238636; border-color: #2ea043; }
131
+ button.primary:hover { background: #2ea043; }
132
+ button.danger { background: #da3633; border-color: #f85149; }
133
+ button:disabled { opacity: .5; cursor: not-allowed; }
134
+ #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; }
135
+ #log .err { color: #f85149; }
136
+ #log .ok { color: #56d364; }
137
+ .status { font-size: 12px; padding: 2px 8px; border-radius: 10px; background: #30363d; }
138
+ .status.ok { background: #1f6f3d; color: #aff5b4; }
139
+ .status.err { background: #6e2b2b; color: #ffabab; }
140
+ </style>
141
+ </head>
142
+ <body>
143
+ <header>
144
+ <h1>AgentX&nbsp;/&nbsp;P2P Call</h1>
145
+ <span id="selfName" class="status">\u2026</span>
146
+ <span class="sp"></span>
147
+ <form id="form">
148
+ <input id="to" placeholder="peer name" required autocomplete="off" />
149
+ <input id="callId" placeholder="call id" required autocomplete="off" />
150
+ <button type="submit" id="joinBtn" class="primary">Join</button>
151
+ <button type="button" id="hangupBtn" class="danger" disabled>Hang up</button>
152
+ </form>
153
+ </header>
154
+ <main>
155
+ <div class="tile"><video id="local" autoplay playsinline muted></video><span class="label">You</span></div>
156
+ <div class="tile"><video id="remote" autoplay playsinline></video><span class="label" id="remoteLabel">Remote</span></div>
157
+ </main>
158
+ <div id="log"></div>
159
+
160
+ <script>
161
+ (() => {
162
+ const $ = (id) => document.getElementById(id);
163
+ const log = (msg, cls) => {
164
+ const line = document.createElement("div");
165
+ if (cls) line.className = cls;
166
+ line.textContent = "[" + new Date().toLocaleTimeString() + "] " + msg;
167
+ $("log").appendChild(line);
168
+ $("log").scrollTop = $("log").scrollHeight;
169
+ };
170
+
171
+ const qs = new URLSearchParams(location.search);
172
+ if (qs.get("to")) $("to").value = qs.get("to");
173
+ $("callId").value = qs.get("callId") || Math.random().toString(36).slice(2, 10);
174
+ const botName = qs.get("bot") || null; // e.g. ?bot=atlas \u2192 invite server-side bot
175
+
176
+ const norm = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
177
+
178
+ // Multi-peer state. For a pair call there's one entry (the human). When a
179
+ // server-side bot joins, a second entry is added whose isPrimary is false
180
+ // so we don't overwrite the remote video with the bot's (silent) stream.
181
+ let state = null; // { es, localStream, cfg, primary, callId, peers: Map<name, {pc, isPrimary}> }
182
+
183
+ async function loadConfig() {
184
+ const r = await fetch("/webrtc/config");
185
+ if (!r.ok) {
186
+ log("webrtc signaling is not enabled on this daemon (" + r.status + ")", "err");
187
+ $("selfName").textContent = "disabled";
188
+ $("selfName").className = "status err";
189
+ $("joinBtn").disabled = true;
190
+ return null;
191
+ }
192
+ const cfg = await r.json();
193
+ $("selfName").textContent = cfg.localName;
194
+ $("selfName").className = "status ok";
195
+ return cfg;
196
+ }
197
+
198
+ async function sendSignal(signal) {
199
+ const r = await fetch("/webrtc/signal/out", {
200
+ method: "POST",
201
+ headers: { "Content-Type": "application/json" },
202
+ body: JSON.stringify(signal),
203
+ });
204
+ if (!r.ok) log("send " + signal.kind + " -> " + signal.to + " failed: " + r.status, "err");
205
+ }
206
+
207
+ /** Find an existing peer by name (normalized \u2014 handles MacBook-Local vs
208
+ * macbook-local spelling drift between the URL and the remote's node.name). */
209
+ function findPeerEntry(name) {
210
+ if (!state) return null;
211
+ const want = norm(name);
212
+ for (const [key, entry] of state.peers) {
213
+ if (norm(key) === want) return entry;
214
+ }
215
+ return null;
216
+ }
217
+
218
+ /** Build an RTCPeerConnection for a specific remote peer. Primary peer
219
+ * drives the remote video tile; non-primary peers (bots) just receive. */
220
+ function createPeer(peerName, isPrimary) {
221
+ const pc = new RTCPeerConnection({ iceServers: state.cfg.iceServers });
222
+ for (const track of state.localStream.getTracks()) pc.addTrack(track, state.localStream);
223
+ pc.ontrack = (ev) => {
224
+ if (isPrimary) {
225
+ $("remote").srcObject = ev.streams[0];
226
+ $("remoteLabel").textContent = peerName;
227
+ }
228
+ log("track from " + peerName + (isPrimary ? " (primary)" : " (bot)"), "ok");
229
+ };
230
+ pc.onicecandidate = (ev) => {
231
+ if (!ev.candidate) return;
232
+ sendSignal({
233
+ kind: "ice",
234
+ callId: state.callId, from: state.cfg.localName, to: peerName,
235
+ candidate: {
236
+ candidate: ev.candidate.candidate,
237
+ sdpMid: ev.candidate.sdpMid,
238
+ sdpMLineIndex: ev.candidate.sdpMLineIndex,
239
+ usernameFragment: ev.candidate.usernameFragment,
240
+ },
241
+ });
242
+ };
243
+ pc.onconnectionstatechange = () => {
244
+ log("pc[" + peerName + "] state: " + pc.connectionState);
245
+ };
246
+ state.peers.set(peerName, { pc, isPrimary });
247
+ return pc;
248
+ }
249
+
250
+ async function handleInboundSignal(sig) {
251
+ if (sig.callId !== state.callId) return;
252
+ if (sig.kind === "hangup") {
253
+ log("<- hangup from " + sig.from, "err");
254
+ teardown();
255
+ return;
256
+ }
257
+ // Find by normalized name so URL-spelling vs node.name-spelling mismatches
258
+ // don't drop signals (same trick the broker uses for its fan-out).
259
+ let entry = findPeerEntry(sig.from);
260
+ if (!entry && sig.kind === "offer") {
261
+ // New peer offering \u2014 figure out if it's the primary human (matches the
262
+ // URL's 'to' after normalization) or an additional peer (bot). Only an
263
+ // offer can introduce a new peer; answer/ice without a PC is stale.
264
+ const isPrimary = norm(sig.from) === norm(state.primary);
265
+ log("new peer offering: " + sig.from + (isPrimary ? " (primary)" : " (additional)"));
266
+ createPeer(sig.from, isPrimary);
267
+ entry = findPeerEntry(sig.from);
268
+ }
269
+ if (!entry) {
270
+ log("signal from unknown peer " + sig.from + " kind=" + sig.kind + " (dropped)", "err");
271
+ return;
272
+ }
273
+ const { pc } = entry;
274
+ try {
275
+ if (sig.kind === "offer") {
276
+ await pc.setRemoteDescription({ type: "offer", sdp: sig.sdp });
277
+ const ans = await pc.createAnswer();
278
+ await pc.setLocalDescription(ans);
279
+ await sendSignal({ kind: "answer", callId: state.callId, from: state.cfg.localName, to: sig.from, sdp: ans.sdp });
280
+ log("<- offer from " + sig.from + " \u2192 answered");
281
+ } else if (sig.kind === "answer") {
282
+ await pc.setRemoteDescription({ type: "answer", sdp: sig.sdp });
283
+ log("<- answer from " + sig.from);
284
+ } else if (sig.kind === "ice") {
285
+ await pc.addIceCandidate(sig.candidate);
286
+ }
287
+ } catch (err) {
288
+ log(sig.kind + " handling error from " + sig.from + ": " + err.message, "err");
289
+ }
290
+ }
291
+
292
+ async function join(e) {
293
+ e?.preventDefault();
294
+ const cfg = state?.cfg || await loadConfig();
295
+ if (!cfg) return;
296
+
297
+ const to = $("to").value.trim();
298
+ const callId = $("callId").value.trim();
299
+ if (!to || !callId) return;
300
+
301
+ log("joining call " + callId + " with peer=" + to + (botName ? " + bot=" + botName : ""));
302
+
303
+ let localStream;
304
+ try {
305
+ localStream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
306
+ } catch (err) {
307
+ log("getUserMedia failed: " + err.message, "err");
308
+ return;
309
+ }
310
+ $("local").srcObject = localStream;
311
+
312
+ state = { cfg, localStream, callId, primary: to, peers: new Map(), es: null };
313
+
314
+ // Subscribe to inbound signals BEFORE sending any offer so we don't miss
315
+ // the answer or ICE candidates from the callee. The broker's 30s buffer
316
+ // also covers late subscribes.
317
+ const esUrl = "/webrtc/events?callId=" + encodeURIComponent(callId) + "&as=" + encodeURIComponent(cfg.localName);
318
+ const es = new EventSource(esUrl);
319
+ state.es = es;
320
+ es.addEventListener("signal", (evt) => {
321
+ let sig; try { sig = JSON.parse(evt.data); } catch { return; }
322
+ handleInboundSignal(sig);
323
+ });
324
+ es.addEventListener("ready", () => log("signaling SSE ready", "ok"));
325
+ es.onerror = () => log("signaling SSE error (will reconnect)", "err");
326
+
327
+ // Create the primary PC to the human peer.
328
+ const primaryPc = createPeer(to, /* isPrimary */ true);
329
+
330
+ // Deterministic caller role between us and the human peer. If we're
331
+ // smaller, we offer; otherwise we wait for the offer (handled in
332
+ // handleInboundSignal).
333
+ const isCaller = norm(cfg.localName) < norm(to);
334
+ log(isCaller ? "role: caller (will offer to " + to + ")" : "role: callee (waiting for offer from " + to + ")");
335
+ if (isCaller) {
336
+ await sendSignal({ kind: "ring", callId, from: cfg.localName, to });
337
+ try {
338
+ const offer = await primaryPc.createOffer();
339
+ await primaryPc.setLocalDescription(offer);
340
+ await sendSignal({ kind: "offer", callId, from: cfg.localName, to, sdp: offer.sdp });
341
+ log("-> offer sent to " + to, "ok");
342
+ } catch (err) {
343
+ log("offer failed: " + err.message, "err");
344
+ }
345
+ }
346
+
347
+ // If a bot is requested, ask this daemon to spawn it. The bot will join
348
+ // as a separate peer; the existing signal handler creates its PC when
349
+ // its offer arrives.
350
+ if (botName) {
351
+ try {
352
+ const r = await fetch("/webrtc/bot/invite", {
353
+ method: "POST",
354
+ headers: { "Content-Type": "application/json" },
355
+ body: JSON.stringify({ callId, target: cfg.localName, agentId: botName }),
356
+ });
357
+ if (r.ok) log("bot '" + botName + "' invited", "ok");
358
+ else log("bot invite failed: " + r.status, "err");
359
+ } catch (err) {
360
+ log("bot invite error: " + err.message, "err");
361
+ }
362
+ }
363
+
364
+ $("joinBtn").disabled = true;
365
+ $("hangupBtn").disabled = false;
366
+ }
367
+
368
+ function teardown() {
369
+ if (!state) return;
370
+ for (const { pc } of state.peers.values()) { try { pc.close(); } catch {} }
371
+ try { state.es.close(); } catch {}
372
+ try { state.localStream.getTracks().forEach(t => t.stop()); } catch {}
373
+ $("local").srcObject = null;
374
+ $("remote").srcObject = null;
375
+ $("joinBtn").disabled = false;
376
+ $("hangupBtn").disabled = true;
377
+ state = null;
378
+ }
379
+
380
+ async function hangup() {
381
+ if (!state) return;
382
+ // Send hangup to every active peer (human + any bots).
383
+ for (const peerName of state.peers.keys()) {
384
+ try {
385
+ await sendSignal({ kind: "hangup", callId: state.callId, from: state.cfg.localName, to: peerName });
386
+ } catch { /* best effort */ }
387
+ }
388
+ log("-> hangup sent to " + state.peers.size + " peer(s)");
389
+ teardown();
390
+ }
391
+
392
+ $("form").addEventListener("submit", join);
393
+ $("hangupBtn").addEventListener("click", hangup);
394
+ loadConfig();
395
+ })();
396
+ </script>
397
+ </body>
398
+ </html>`;var Q=null;async function Is(){if(Q)return Q;let h;try{h=await import("@roamhq/wrtc")}catch(s){throw new Error(`WebRTC bot requires "@roamhq/wrtc" \u2014 install it on this node (\`pnpm add @roamhq/wrtc\`) or set channels.webrtc.bot.enabled=false. Original: ${s?.message??s}`)}let e=h.default??h,t=e.RTCPeerConnection,n=e.nonstandard?.RTCAudioSink;if(!t||!n)throw new Error("@roamhq/wrtc loaded but missing expected exports (RTCPeerConnection / nonstandard.RTCAudioSink)");return Q={RTCPeerConnection:t,RTCAudioSink:n},Q}var gn=h=>h.toLowerCase().replace(/[^a-z0-9]/g,""),Ae=class{pc;sinks=new Set;unsubscribe;closed=!1;opts;constructor(e){this.opts=e}async start(){if(this.pc)throw new Error("WebRtcBot already started");let{broker:e,callId:t,botName:n,target:s,iceServers:i,log:r}=this.opts,{RTCPeerConnection:o}=await Is();if(this.pc=new o({iceServers:i}),this.pc.ontrack=c=>{if(this.closed)return;let l=c.track;if(l.kind!=="audio"){r(`[bot:${n}] non-audio track received (kind=${l.kind}) \u2014 ignored in v1`);return}r(`[bot:${n}] audio track received from ${s}`),this.tapAudio(l)},this.pc.onicecandidate=c=>{!c.candidate||this.closed||this.send({kind:"ice",callId:t,from:n,to:s,candidate:{candidate:c.candidate.candidate,sdpMid:c.candidate.sdpMid,sdpMLineIndex:c.candidate.sdpMLineIndex,usernameFragment:c.candidate.usernameFragment}})},this.pc.onconnectionstatechange=()=>{let c=this.pc?.connectionState;r(`[bot:${n}] pc state: ${c}`),(c==="failed"||c==="disconnected"||c==="closed")&&this.close(`pc state ${c}`)},this.unsubscribe=e.subscribeInternal(t,n,c=>this.handleSignal(c)),await this.send({kind:"ring",callId:t,from:n,to:s}),gn(n)<gn(s)){let c=await this.pc.createOffer({offerToReceiveAudio:!0,offerToReceiveVideo:!1});await this.pc.setLocalDescription(c),await this.send({kind:"offer",callId:t,from:n,to:s,sdp:c.sdp}),r(`[bot:${n}] role=caller, offer sent to ${s}`)}else r(`[bot:${n}] role=callee, waiting for offer from ${s}`)}async send(e){let t=await this.opts.broker.handleOutgoing(e);t.ok||this.opts.log(`[bot:${this.opts.botName}] send ${e.kind} -> ${e.to} failed: ${t.error}`)}async handleSignal(e){if(!(this.closed||!this.pc)&&e.callId===this.opts.callId)try{if(e.kind==="offer"){await this.pc.setRemoteDescription({type:"offer",sdp:e.sdp});let t=await this.pc.createAnswer();await this.pc.setLocalDescription(t),await this.send({kind:"answer",callId:this.opts.callId,from:this.opts.botName,to:e.from,sdp:t.sdp})}else e.kind==="answer"?await this.pc.setRemoteDescription({type:"answer",sdp:e.sdp}):e.kind==="ice"?await this.pc.addIceCandidate(e.candidate):e.kind==="hangup"&&this.close(`hangup from ${e.from}`)}catch(t){this.opts.log(`[bot:${this.opts.botName}] signal ${e.kind} from ${e.from} failed: ${t.message}`)}}tapAudio(e){if(!Q)throw new Error("WebRtcBot.tapAudio called before loadWrtc \u2014 bug");let{RTCAudioSink:t}=Q,n=new t(e);n.ondata=s=>{this.closed||this.opts.onAudioFrame({samples:s.samples,sampleRate:s.sampleRate,channelCount:s.channelCount,receivedAt:Date.now()})},this.sinks.add({stop:()=>{try{n.stop()}catch{}}})}close(e){if(!this.closed){this.closed=!0,this.opts.log(`[bot:${this.opts.botName}] closing (${e})`);for(let t of this.sinks)t.stop();this.sinks.clear();try{this.unsubscribe?.()}catch{}try{this.pc?.close()}catch{}this.pc=void 0,this.opts.onClosed?.(e)}}};var Ce={energyThreshold:.012,silenceMs:400,minSpeechMs:600,maxChunkMs:15e3},Me=class{opts;active=null;silenceTimer;constructor(e){this.opts={energyThreshold:e.energyThreshold??Ce.energyThreshold,silenceMs:e.silenceMs??Ce.silenceMs,minSpeechMs:e.minSpeechMs??Ce.minSpeechMs,maxChunkMs:e.maxChunkMs??Ce.maxChunkMs,log:e.log,onChunk:e.onChunk},this.silenceTimer=setInterval(()=>this.maybeClose(),100)}push(e){let t=xs(e.samples,e.channelCount),s=As(t)>=this.opts.energyThreshold;if(!this.active){if(!s)return;this.active={samples:[],totalSamples:0,sourceRate:e.sampleRate,startedAt:e.receivedAt,lastVoiceAt:e.receivedAt}}this.active.samples.push(t),this.active.totalSamples+=t.length,s&&(this.active.lastVoiceAt=e.receivedAt)}maybeClose(){if(!this.active)return;let e=Date.now(),t=e-this.active.startedAt,n=e-this.active.lastVoiceAt,s=t-n;if(t>=this.opts.maxChunkMs){this.flush("max-duration");return}s>=this.opts.minSpeechMs&&n>=this.opts.silenceMs&&this.flush("silence-gap")}flush(e="manual"){if(!this.active)return;let t=this.active;this.active=null;let n=Date.now()-t.startedAt;if(t.totalSamples===0)return;let s=Cs(t.samples,t.totalSamples),i=Ms(s,t.sourceRate),r=Rs(i,16e3);this.opts.log(`audio chunk closed (${e}, ${n}ms, ${(i.length/16e3).toFixed(2)}s) \u2014 ${r.length} bytes`),this.opts.onChunk(r,n)}shutdown(){this.silenceTimer&&clearInterval(this.silenceTimer),this.silenceTimer=void 0,this.flush("shutdown")}};function xs(h,e){if(e===1)return h;let t=new Int16Array(h.length/e);for(let n=0,s=0;n<h.length;n+=e,s++){let i=0;for(let r=0;r<e;r++)i+=h[n+r];t[s]=i/e}return t}function As(h){if(h.length===0)return 0;let e=0;for(let t=0;t<h.length;t++){let n=h[t]/32768;e+=n*n}return Math.sqrt(e/h.length)}function Cs(h,e){let t=new Int16Array(e),n=0;for(let s of h)t.set(s,n),n+=s.length;return t}function Ms(h,e){if(e===16e3)return h;let t=e/16e3,n=Math.floor(h.length/t),s=new Int16Array(n);for(let i=0;i<n;i++){let r=i*t,o=Math.floor(r),a=Math.min(o+1,h.length-1),c=r-o;s[i]=(1-c)*h[o]+c*h[a]}return s}function Rs(h,e){let t=h.length*2,n=Buffer.alloc(44);n.write("RIFF",0),n.writeUInt32LE(36+t,4),n.write("WAVE",8),n.write("fmt ",12),n.writeUInt32LE(16,16),n.writeUInt16LE(1,20),n.writeUInt16LE(1,22),n.writeUInt32LE(e,24),n.writeUInt32LE(e*2,28),n.writeUInt16LE(2,32),n.writeUInt16LE(16,34),n.write("data",36),n.writeUInt32LE(t,40);let s=Buffer.alloc(t);for(let i=0;i<h.length;i++)s.writeInt16LE(h[i],i*2);return Buffer.concat([n,s])}import{spawn as un}from"child_process";import{writeFile as Ps,mkdtemp as _s,rm as Es}from"fs/promises";import{tmpdir as js}from"os";import{join as at}from"path";var Re=class{opts;resolvedBackend=null;constructor(e){this.opts=e}async transcribe(e){let t=await this.resolveBackend();if(!t)return this.opts.log("[whisper] no backend available \u2014 chunk dropped"),null;try{return((t==="mlx"?await this.transcribeMlx(e):await this.transcribeOpenAI(e))?.trim()??"")||null}catch(n){return this.opts.log(`[whisper:${t}] transcription failed: ${n.message}`),null}}async resolveBackend(){if(this.resolvedBackend)return this.resolvedBackend;let e=this.opts.backend;return e==="mlx"?this.resolvedBackend=await this.probeMlx()?"mlx":null:e==="openai"?this.resolvedBackend=process.env.OPENAI_API_KEY?"openai":null:await this.probeMlx()?this.resolvedBackend="mlx":process.env.OPENAI_API_KEY&&(this.resolvedBackend="openai"),this.resolvedBackend?this.opts.log(`[whisper] using backend: ${this.resolvedBackend}`):this.opts.log(`[whisper] backend resolution failed (target=${e}, OPENAI_API_KEY=${process.env.OPENAI_API_KEY?"set":"unset"})`),this.resolvedBackend}mlxCmd(){return this.opts.mlxBinary||"mlx_whisper"}async probeMlx(){return new Promise(e=>{let t=un(this.mlxCmd(),["--help"],{stdio:"ignore"});t.on("error",()=>e(!1)),t.on("exit",n=>e(n===0))})}async transcribeMlx(e){let t=await _s(at(js(),"agentx-whisper-")),n=at(t,"chunk.wav");try{await Ps(n,e);let s=["--model",this.opts.model||"small","--output-format","txt","--output-dir",t];this.opts.language&&this.opts.language!=="auto"&&s.push("--language",this.opts.language),s.push(n);let i=await Os(this.mlxCmd(),s);if(i.trim())return i;let{readFile:r}=await import("fs/promises");try{return await r(at(t,"chunk.txt"),"utf-8")}catch{return""}}finally{await Es(t,{recursive:!0,force:!0}).catch(()=>{})}}async transcribeOpenAI(e){let t=process.env.OPENAI_API_KEY;if(!t)throw new Error("OPENAI_API_KEY not set");let n=new FormData;n.set("file",new Blob([e],{type:"audio/wav"}),"chunk.wav"),n.set("model",this.opts.model||"whisper-1"),this.opts.language&&this.opts.language!=="auto"&&n.set("language",this.opts.language),n.set("response_format","text");let s=await fetch("https://api.openai.com/v1/audio/transcriptions",{method:"POST",headers:{Authorization:`Bearer ${t}`},body:n});if(!s.ok){let i=await s.text().catch(()=>"");throw new Error(`OpenAI ${s.status}: ${i.slice(0,200)}`)}return await s.text()}};function Os(h,e){return new Promise((t,n)=>{let s=un(h,e,{stdio:["ignore","pipe","pipe"]}),i="",r="";s.stdout.on("data",o=>{i+=o.toString()}),s.stderr.on("data",o=>{r+=o.toString()}),s.on("error",n),s.on("exit",o=>{o===0?t(i):n(new Error(`${h} exited ${o}: ${r.slice(0,200)}`))})})}var Pe=class{opts;bots=new Map;whisper;constructor(e){this.opts=e,this.whisper=new Re({backend:e.whisperBackend,model:e.whisperModel,language:e.whisperLanguage,mlxBinary:e.mlxBinary,log:e.log})}async invite(e){let t=this.bots.get(e.callId);if(t)return this.opts.log(`[bot-manager] bot already active for call=${e.callId} agent=${t.invite.agentId}`),{ok:!0};let n=`bot:${e.agentId}`;this.opts.log(`[bot-manager] spawning ${n} for call=${e.callId} target=${e.target}`);let s=new Me({log:this.opts.log,onChunk:async(a,c)=>{let l=await this.whisper.transcribe(a);if(l)try{await this.opts.onTranscript({invite:e,text:l,durationMs:c})}catch(d){this.opts.log(`[bot-manager] onTranscript hook threw: ${d.message}`)}}}),i={callId:e.callId,botName:n,target:e.target,iceServers:this.opts.iceServers,broker:this.opts.broker,log:this.opts.log,onAudioFrame:a=>s.push(a),onClosed:a=>{this.opts.log(`[bot-manager] bot for call=${e.callId} closed (${a})`),this.tearDown(e.callId)}},r=new Ae(i);try{await r.start()}catch(a){return this.opts.log(`[bot-manager] bot.start failed: ${a.message}`),s.shutdown(),{ok:!1,error:a.message}}let o=setTimeout(()=>{this.opts.log(`[bot-manager] hard-cap reached for call=${e.callId} (${this.opts.maxCallMinutes}min) \u2014 closing`),this.tearDown(e.callId)},this.opts.maxCallMinutes*6e4);return this.bots.set(e.callId,{invite:e,bot:r,chunker:s,startedAt:Date.now(),timeoutTimer:o}),{ok:!0}}tearDown(e){let t=this.bots.get(e);if(t){this.bots.delete(e),clearTimeout(t.timeoutTimer);try{t.bot.close("manager teardown")}catch{}try{t.chunker.shutdown()}catch{}this.opts.log(`[bot-manager] tore down bot for call=${e} (was up ${Math.round((Date.now()-t.startedAt)/1e3)}s)`)}}active(){let e=Date.now();return[...this.bots.values()].map(t=>({callId:t.invite.callId,agentId:t.invite.agentId,target:t.invite.target,uptimeSec:Math.round((e-t.startedAt)/1e3)}))}shutdown(){for(let e of[...this.bots.keys()])this.tearDown(e)}};var ae=class{module;minLevel;constructor(e,t="info"){this.module=e,this.minLevel=t}child(e){return new ae(`${this.module}:${e}`,this.minLevel)}debug(e,t){this.emit("debug",e,t)}info(e,t){this.emit("info",e,t)}warn(e,t){this.emit("warn",e,t)}error(e,t){this.emit("error",e,t)}asConsoleLog(){return(...e)=>{let n=e.map(s=>typeof s=="string"?s:JSON.stringify(s)).join(" ").replace(/^\[agentx\]\s*/,"");n&&this.info(n)}}emit(e,t,n){if(ae.levelOrder[e]<ae.levelOrder[this.minLevel])return;let s={time:new Date().toISOString(),level:e,module:this.module,msg:t,...n},i=e==="error"?"ERROR":e==="warn"?"WARN":"",r=`[${this.module}]`,o=i?`${r} ${i}: ${t}`:`${r} ${t}`;console.error(o)}},oe=ae;ue(oe,"levelOrder",{debug:0,info:1,warn:2,error:3});import{createHmac as pn,timingSafeEqual as Ds}from"crypto";var Ee=class{registry;config;mesh;webhookEntries;log;workflowDispatcher;constructor(e,t={},n=console.error.bind(console,"[webhook]"),s,i=[]){this.registry=e,this.config=t,this.log=n,this.mesh=s,this.webhookEntries=i}setWorkflowDispatcher(e){this.workflowDispatcher=e}setWebhookEntries(e){this.webhookEntries=e}async handle(e,t,n){let s=n.replace(/^\/webhook\/?/,"").split("/").filter(Boolean),i=s[0],r=s[1];if(!i){this.sendJson(t,400,{error:"Missing agent ID. Use /webhook/:agentId"});return}let{raw:o,parsed:a}=await this.readBody(e),c=r||this.detectSource(e.headers),l=this.validateSignature(e.headers,o,c,i);if(l){this.log(`Webhook [${c}] -> ${i}: REJECTED (${l})`),this.sendJson(t,401,{error:l});return}let d=this.buildSummary(c,a,e.headers);this.log(`Webhook [${c}] -> ${i}: ${d.slice(0,100)}`);let g=this.extractEventType(c,a,e.headers),p=this.webhookEntries.find(f=>f.enabled&&f.agentId===i&&f.source===c),u=g&&p?.triggers?.[g]||p?.defaultWorkflow||null;if(u&&this.workflowDispatcher)try{let f=await this.workflowDispatcher.dispatchWorkflow({workflowId:u,entityRef:{kind:"webhook",id:`${c}:${i}:${g??"\u2014"}:${Date.now()}`},event:{kind:"webhook",source:c,eventType:g,payload:a},trigger:{source:c}});this.log(`Webhook [${c}/${g??"\u2014"}] -> workflow=${u} (claimed=${f.claimed})`),this.sendJson(t,202,{ok:!0,agent:i,source:c,eventType:g,workflow:u,claimed:f.claimed});return}catch(f){this.log(`Workflow dispatch failed [${c}/${g}/${u}]: ${f.message}`)}let m=this.webhookEntries.find(f=>f.enabled&&f.node&&f.agentId===i&&f.source===c);if(m?.node&&this.mesh){this.sendJson(t,202,{ok:!0,agent:i,source:c,node:m.node,status:"accepted"}),this.mesh.sendTask(m.node,d,i).then(f=>this.log(`Mesh-forwarded webhook [${c}] -> ${m.node}/${i}: ${f?.slice(0,100)||"ok"}`),f=>this.log(`Mesh forward failed [${c}] -> ${m.node}/${i}: ${f.message}`));return}try{let f=await this.registry.execute({message:d,agentId:i,context:{channel:`webhook:${c}`,sender:`webhook:${c}`}});this.sendJson(t,f.error?500:200,{ok:!f.error,agent:i,source:c,response:f.content?.slice(0,500),error:f.error,duration:f.duration})}catch(f){this.sendJson(t,500,{error:f.message})}}extractEventType(e,t,n){let s=i=>{let r=n[i.toLowerCase()];return Array.isArray(r)?r[0]:r};if(e==="github"){let i=s("x-github-event");if(!i)return;let r=typeof t.action=="string"?t.action:void 0;return r?`${i}.${r}`:i}if(e==="gitlab")return s("x-gitlab-event")||(typeof t.object_kind=="string"?t.object_kind:void 0);if(e==="stripe")return typeof t.type=="string"?t.type:void 0;if(e==="sentry")return s("sentry-hook-resource")}detectSource(e){return e["x-gitlab-event"]||e["x-gitlab-token"]?"gitlab":e["x-github-event"]?"github":e["stripe-signature"]?"stripe":e["sentry-hook-resource"]?"sentry":e["x-vercel-signature"]?"vercel":e["x-hub-signature-256"]?"github":"unknown"}buildSummary(e,t,n){let s=[`[Webhook from ${e}]`];switch(e){case"gitlab":{let r=n["x-gitlab-event"]||t.object_kind||"event",o=t.project?.path_with_namespace||"",a=t.user?.name||t.user_username||"";if(s.push(`Event: ${r}`),o&&s.push(`Project: ${o}`),a&&s.push(`User: ${a}`),t.ref&&s.push(`Ref: ${t.ref}`),t.commits&&Array.isArray(t.commits)){s.push(`Commits: ${t.commits.length}`);for(let l of t.commits.slice(0,3))s.push(` - ${l.message?.split(`
399
+ `)[0]||"no message"} (${l.author?.name||""})`)}let c=t.object_attributes;if(c?.title&&(s.push(`Title: ${c.title}`),s.push(`State: ${c.state||""}`),s.push(`Action: ${c.action||""}`),c.source_branch&&s.push(`Branch: ${c.source_branch} -> ${c.target_branch}`),c.url&&s.push(`URL: ${c.url}`)),c?.iid&&!c?.source_branch&&(s.push(`Issue #${c.iid}: ${c.title||""}`),c.description&&s.push(`Description: ${c.description.slice(0,200)}`)),t.object_kind==="pipeline"){let l=t.object_attributes;s.push(`Pipeline: ${l?.status||""} (${l?.ref||""})`),s.push(`Duration: ${l?.duration||0}s`)}break}case"github":{let r=n["x-github-event"]||"event",o=t.repository?.full_name||"",a=t.sender?.login||"";if(s.push(`Event: ${r}`),o&&s.push(`Repository: ${o}`),a&&s.push(`Sender: ${a}`),t.ref&&s.push(`Ref: ${t.ref}`),t.commits&&Array.isArray(t.commits))for(let d of t.commits.slice(0,3))s.push(` - ${d.message?.split(`
400
+ `)[0]||""} (${d.author?.name||""})`);let c=t.pull_request;c&&(s.push(`PR #${c.number}: ${c.title}`),s.push(`Action: ${t.action}`),s.push(`Branch: ${c.head?.ref} -> ${c.base?.ref}`));let l=t.issue;l&&(s.push(`Issue #${l.number}: ${l.title}`),s.push(`Action: ${t.action}`));break}case"stripe":{let r=t.type||"event",o=t.data?.object||{};s.push(`Event: ${r}`),o.amount&&s.push(`Amount: ${(o.amount/100).toFixed(2)} ${o.currency?.toUpperCase()||""}`),o.customer_email&&s.push(`Customer: ${o.customer_email}`),o.description&&s.push(`Description: ${o.description}`),o.status&&s.push(`Status: ${o.status}`);break}case"sentry":{let r=n["sentry-hook-resource"]||"event";s.push(`Resource: ${r}`);let o=t.data||t;o.error?.title&&s.push(`Error: ${o.error.title}`),o.error?.culprit&&s.push(`Culprit: ${o.error.culprit}`),o.error?.metadata?.value&&s.push(`Message: ${o.error.metadata.value}`),t.url&&s.push(`URL: ${t.url}`);break}default:s.push(`Headers: ${JSON.stringify(Object.keys(n).filter(r=>r.startsWith("x-")).slice(0,5))}`),s.push(`Payload keys: ${Object.keys(t).slice(0,10).join(", ")}`);let i=JSON.stringify(t).slice(0,500);s.push(`Body: ${i}`)}return s.join(`
401
+ `)}async readBody(e){return new Promise(t=>{let n="";e.on("data",s=>n+=s.toString()),e.on("end",()=>{let s;try{s=n?JSON.parse(n):{}}catch{s={raw:n}}t({raw:n,parsed:s})}),e.on("error",()=>t({raw:"",parsed:{}}))})}validateSignature(e,t,n,s){let i=this.webhookEntries.find(c=>c.enabled&&c.agentId===s&&c.source===n);if(!i?.secretEnv)return null;let r=process.env[i.secretEnv];if(!r)return`webhook entry "${i.id}" expects env ${i.secretEnv} but it is unset`;let o=c=>{let l=e[c.toLowerCase()];return Array.isArray(l)?l[0]:l};if(n==="github"){let c=o("x-hub-signature-256");if(!c)return"missing X-Hub-Signature-256";let l="sha256="+pn("sha256",r).update(t).digest("hex");return _e(l,c)}if(n==="gitlab"){let c=o("x-gitlab-token");return c?_e(r,c):"missing X-Gitlab-Token"}if(n==="stripe"){let c=o("stripe-signature");if(!c)return"missing Stripe-Signature";let l=Object.fromEntries(c.split(",").map(g=>g.split("=")));if(!l.t||!l.v1)return"malformed Stripe-Signature";let d=pn("sha256",r).update(`${l.t}.${t}`).digest("hex");return _e(d,l.v1)}let a=o("x-webhook-secret");return a?_e(r,a):"missing X-Webhook-Secret"}sendJson(e,t,n){e.writeHead(t,{"Content-Type":"application/json"}),e.end(JSON.stringify(n,null,2))}};function _e(h,e){if(h.length!==e.length)return"signature mismatch";let t=Buffer.from(h),n=Buffer.from(e);return Ds(t,n)?null:"signature mismatch"}import Ns from"better-sqlite3";import{mkdirSync as Ls}from"fs";import{dirname as Bs,resolve as Gs}from"path";var je;function mn(h={}){if(h.disabled)return null;if(je)return je;let e=Gs(process.cwd(),h.path??".agentx/db.sqlite");try{Ls(Bs(e),{recursive:!0});let t=new Ns(e);return t.pragma("journal_mode = WAL"),t.pragma("synchronous = NORMAL"),t.pragma("foreign_keys = ON"),Fs(t),je=t,je}catch(t){return console.error(`[storage/sqlite] openDb failed at ${e}: ${t.message}`),null}}function Fs(h){h.exec(`
402
+ CREATE TABLE IF NOT EXISTS schema_version (
403
+ v INTEGER PRIMARY KEY
404
+ );
405
+ `);let e=h.prepare("SELECT MAX(v) AS v FROM schema_version").get().v??0,t=[{v:1,sql:`
406
+ CREATE TABLE task_history (
407
+ id TEXT PRIMARY KEY,
408
+ agent_id TEXT NOT NULL,
409
+ channel TEXT,
410
+ chat_id TEXT,
411
+ status TEXT NOT NULL, -- 'ok' | 'error' | 'timeout'
412
+ message_preview TEXT,
413
+ error TEXT,
414
+ duration_ms INTEGER,
415
+ input_tokens INTEGER,
416
+ output_tokens INTEGER,
417
+ cache_read_tokens INTEGER,
418
+ cache_create_tokens INTEGER,
419
+ started_at TEXT NOT NULL,
420
+ finished_at TEXT NOT NULL
421
+ );
422
+ CREATE INDEX idx_task_history_agent_started
423
+ ON task_history(agent_id, started_at);
424
+ CREATE INDEX idx_task_history_status
425
+ ON task_history(status, started_at);
426
+ `},{v:2,sql:`
427
+ CREATE TABLE usage_daily (
428
+ agent_id TEXT NOT NULL,
429
+ model TEXT NOT NULL,
430
+ day TEXT NOT NULL, -- 'YYYY-MM-DD' UTC
431
+ input_tokens INTEGER DEFAULT 0,
432
+ output_tokens INTEGER DEFAULT 0,
433
+ cache_read_tokens INTEGER DEFAULT 0,
434
+ cache_create_tokens INTEGER DEFAULT 0,
435
+ tasks INTEGER DEFAULT 0,
436
+ PRIMARY KEY (agent_id, model, day)
437
+ );
438
+ CREATE INDEX idx_usage_day ON usage_daily(day);
439
+ `},{v:3,sql:`
440
+ CREATE TABLE rotations (
441
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
442
+ agent_id TEXT NOT NULL,
443
+ channel TEXT NOT NULL,
444
+ chat_id TEXT NOT NULL,
445
+ reason TEXT NOT NULL, -- 'stale' | 'tier-2' | 'max-turns'
446
+ last_turn_input_tokens INTEGER,
447
+ rotated_at TEXT NOT NULL
448
+ );
449
+ CREATE INDEX idx_rotations_agent_at
450
+ ON rotations(agent_id, rotated_at);
451
+ `},{v:4,sql:`
452
+ CREATE TABLE route_traces (
453
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
454
+ channel TEXT NOT NULL,
455
+ chat_id TEXT NOT NULL,
456
+ msg_id TEXT NOT NULL,
457
+ account_id TEXT,
458
+ kind TEXT NOT NULL, -- 'match' | 'drop'
459
+ deciding_stage TEXT,
460
+ agent_id TEXT,
461
+ reason TEXT,
462
+ at TEXT NOT NULL
463
+ );
464
+ CREATE INDEX idx_route_traces_channel_at
465
+ ON route_traces(channel, at);
466
+ `}],n=h.transaction(s=>{h.exec(s.sql),h.prepare("INSERT INTO schema_version (v) VALUES (?)").run(s.v)});for(let s of t)s.v>e&&n(s)}function Us(h){return{insertTaskHistory:h.prepare(`
467
+ INSERT OR REPLACE INTO task_history (
468
+ id, agent_id, channel, chat_id, status, message_preview, error,
469
+ duration_ms, input_tokens, output_tokens,
470
+ cache_read_tokens, cache_create_tokens,
471
+ started_at, finished_at
472
+ ) VALUES (
473
+ @id, @agent_id, @channel, @chat_id, @status, @message_preview, @error,
474
+ @duration_ms, @input_tokens, @output_tokens,
475
+ @cache_read_tokens, @cache_create_tokens,
476
+ @started_at, @finished_at
477
+ )
478
+ `),upsertUsageDaily:h.prepare(`
479
+ INSERT INTO usage_daily (
480
+ agent_id, model, day,
481
+ input_tokens, output_tokens, cache_read_tokens, cache_create_tokens, tasks
482
+ ) VALUES (
483
+ @agent_id, @model, @day,
484
+ @input_tokens, @output_tokens, @cache_read_tokens, @cache_create_tokens, 1
485
+ )
486
+ ON CONFLICT(agent_id, model, day) DO UPDATE SET
487
+ input_tokens = input_tokens + excluded.input_tokens,
488
+ output_tokens = output_tokens + excluded.output_tokens,
489
+ cache_read_tokens = cache_read_tokens + excluded.cache_read_tokens,
490
+ cache_create_tokens = cache_create_tokens + excluded.cache_create_tokens,
491
+ tasks = tasks + 1
492
+ `),insertRotation:h.prepare(`
493
+ INSERT INTO rotations (agent_id, channel, chat_id, reason, last_turn_input_tokens, rotated_at)
494
+ VALUES (@agent_id, @channel, @chat_id, @reason, @last_turn_input_tokens, @rotated_at)
495
+ `),insertRouteTrace:h.prepare(`
496
+ INSERT INTO route_traces (channel, chat_id, msg_id, account_id, kind, deciding_stage, agent_id, reason, at)
497
+ VALUES (@channel, @chat_id, @msg_id, @account_id, @kind, @deciding_stage, @agent_id, @reason, @at)
498
+ `)}}function fn(h,e="claude-opus-4-7"){let t=ee(),n=Us(h),s=new Map,i=l=>{let d=`${l.agentId}:${l.channel}:${l.chatId}`;s.set(d,{agentId:l.agentId,channel:l.channel,chatId:l.chatId,startedAt:l.at,messagePreview:l.messagePreview})},r=l=>{let d=`${l.agentId}:${l.channel}:${l.chatId}`,g=s.get(d);s.delete(d);let p=`${l.agentId}:${l.channel}:${l.chatId}:${l.at}`,u=l.error?"error":"ok";try{n.insertTaskHistory.run({id:p,agent_id:l.agentId,channel:l.channel,chat_id:l.chatId,status:u,message_preview:g?.messagePreview??null,error:l.error??null,duration_ms:l.durationMs,input_tokens:l.inputTokens??null,output_tokens:l.outputTokens??null,cache_read_tokens:l.cacheReadTokens??null,cache_create_tokens:l.cacheCreateTokens??null,started_at:g?.startedAt??l.at,finished_at:l.at})}catch{}if(!l.error&&(l.inputTokens||l.outputTokens)){let m=l.at.slice(0,10);try{n.upsertUsageDaily.run({agent_id:l.agentId,model:e,day:m,input_tokens:l.inputTokens??0,output_tokens:l.outputTokens??0,cache_read_tokens:l.cacheReadTokens??0,cache_create_tokens:l.cacheCreateTokens??0})}catch{}}},o=l=>{try{n.insertRotation.run({agent_id:l.agentId,channel:l.channel,chat_id:l.chatId,reason:l.reason,last_turn_input_tokens:l.lastTurnInputTokens??null,rotated_at:l.at})}catch{}},a=l=>{try{n.insertRouteTrace.run({channel:l.channel,chat_id:l.chatId,msg_id:l.msgId,account_id:l.accountId??null,kind:"match",deciding_stage:l.decidingStage,agent_id:l.agentId,reason:null,at:l.at})}catch{}},c=l=>{try{n.insertRouteTrace.run({channel:l.channel,chat_id:l.chatId,msg_id:l.msgId,account_id:l.accountId??null,kind:"drop",deciding_stage:l.decidingStage,agent_id:null,reason:l.reason,at:l.at})}catch{}};return t.on("task:started",i),t.on("task:completed",r),t.on("session:rotated",o),t.on("message:matched",a),t.on("message:dropped",c),()=>{t.off("task:started",i),t.off("task:completed",r),t.off("session:rotated",o),t.off("message:matched",a),t.off("message:dropped",c)}}var ce=class{cache=new Map;config;meshPeers=[];constructor(e){this.config=e}fingerprint="";build(e){e&&(this.meshPeers=e),this.cache.clear(),this.fingerprint=this.computeFingerprint();for(let t of Object.keys(this.config.agents))this.cache.set(t,this.renderForAgent(t))}computeFingerprint(){return[...this.meshPeers].sort((t,n)=>t.peer.localeCompare(n.peer)).map(t=>`${t.peer}:${t.skills.map(n=>n.id).sort().join(",")}`).join("|")}getForAgent(e){return this.cache.get(e)}refresh(e){this.meshPeers=e,!(this.computeFingerprint()===this.fingerprint&&this.cache.size>0)&&this.build()}renderForAgent(e){let t=["[Landscape]"],n=this.config.agents[e];t.push(`Node: ${this.config.node.name} (${this.config.node.id})`);let s=this.getPrimaryHandle(e),i=this.extractCapability(n),r=s?` (${s})`:"";t.push(`You: ${n.name}${r} \u2014 ${i}`),t.push("");let o=Object.entries(this.config.agents).filter(([g])=>g!==e);t.push(`Available agents on this node (${o.length} \u2014 not all may be in the current group):`);for(let[g,p]of o){let u=this.getPrimaryHandle(g),m=this.extractCapability(p),f=u?` (${u})`:"";t.push(`\u2022 ${p.name}${f} \u2014 ${m}`)}let a=this.meshPeers.filter(g=>g.skills.length>0);if(a.length){t.push("Remote:");let g=[...a].sort((p,u)=>p.peer.localeCompare(u.peer));for(let p of g){let u=[...p.skills].sort((m,f)=>m.id.localeCompare(f.id));for(let m of u)t.push(`\u2022 ${m.name} [${p.peer}] \u2014 ${m.description.slice(0,60)}`)}}t.push(""),t.push(this.renderChannels()),t.push(""),t.push("[Rules]");let c=[s,e].filter(Boolean).join(" / ");t.push(`- Only respond when YOU (${c}) are mentioned or this is a DM to you`),t.push("- If another agent was mentioned and you were NOT, stay silent \u2014 they will handle it"),t.push("- The agent list above shows all agents on the node \u2014 do NOT assume they are all in the current chat group"),t.push("- When asked about group members, only mention agents you have seen in the conversation history"),t.push("- To delegate on Telegram: mention the agent's handle in your response"),t.push("- On GitLab: reply directly, no Telegram handles. To send to other channels, use the /send API below"),t.push("- On WhatsApp: mention another agent's handle to delegate (shared number \u2014 name prefixed automatically). To send to other channels, use /send API");let[,l]=this.config.node.bind.split(":"),d=l||"19900";return t.push(""),t.push("[Cross-Channel Messaging]"),t.push("You can send messages to ANY channel proactively (not just reply):"),t.push(` curl -X POST http://localhost:${d}/send -H "Content-Type: application/json" -d '{"channel":"<channel>","chatId":"<id>","text":"<message>"}'`),t.push("Channels: telegram, whatsapp, gitlab, discord"),t.push("ChatId formats:"),t.push(' telegram: numeric (e.g. "-1001234567890" for group, "123456" for DM)'),t.push(' gitlab: "group/project:issue:123" or "group/project:merge_request:45"'),t.push(' whatsapp: JID (e.g. "+21612345678@s.whatsapp.net")'),t.push("Use this when asked to notify someone on a different channel, post to an issue, or broadcast updates."),t.push(""),t.push("[Conversation Recall \u2014 short/long memory]"),t.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 \u2014 do not guess, do not invent context from other tools, and do not ask the user to repeat themselves.`),t.push(""),t.push("ALWAYS pass `channel` and `chatId` from the [Current Conversation] block above so you scope to the right thread:"),t.push(` curl -s -X POST http://localhost:${d}/recall -H "Content-Type: application/json" -d '{"agent":"<your-agent-id>","channel":"<from prompt>","chatId":"<from prompt>","limit":10}'`),t.push(""),t.push("Short memory (default \u2014 today only): omit `lookbackDays`. Returns last ~10 turns of THIS chat from today UTC."),t.push("Long memory (opt-in): set `lookbackDays` only when the user message has a clear long-memory cue:"),t.push(' - "yesterday" / "\u0623\u0645\u0633" \u2192 lookbackDays: 2'),t.push(' - "few days ago" / "couple of days" \u2192 lookbackDays: 4'),t.push(' - "last week" / "week ago" \u2192 lookbackDays: 8'),t.push(' - "remember when \u2026" / "we discussed \u2026" \u2192 lookbackDays: 3'),t.push("If unsure, start short and paginate backward using `oldestTs` from the previous response as the next call's `before`."),t.push(""),t.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 }."),t.push("Scope rule: omit chatId to span all of YOUR chats on the channel; omit channel to span all channels. Store is per-agent \u2014 you cannot read another agent's chats."),t.push(""),t.push("[Background Monitoring]"),t.push("You can watch things in the background while continuing to work:"),t.push("- Tail log files and react to errors as they appear"),t.push("- Poll CI/CD pipelines and report when status changes"),t.push("- Watch directories for file changes"),t.push("Use the Monitor tool for this \u2014 it runs a script in the background and streams output to you."),t.push(""),t.push("[Agent Teams]"),t.push("For complex tasks that benefit from parallel work, you can spawn an agent team:"),t.push("- Tell Claude Code to 'create an agent team' with specialized teammates"),t.push("- Each teammate works independently with its own context window"),t.push("- Teammates coordinate via shared task list and direct messaging"),t.push("- Best for: code review (security + performance + tests), debugging with competing hypotheses, multi-module features"),t.push("- Use teams when work can be parallelized. Use single session for sequential/simple tasks."),t.join(`
499
+ `)}renderChannels(){let e=[],t=this.config.channels;if(t.telegram.enabled){let n=t.telegram.policy;e.push(`telegram(groups:${n.group}, DMs:${n.dm}, delegation:yes)`)}return t.gitlab?.enabled&&e.push("gitlab(webhooks, no delegation)"),t.whatsapp.enabled&&e.push("whatsapp(DMs only, no delegation)"),t.discord?.enabled&&e.push("discord(enabled)"),`Channels: ${e.join(", ")}`}getPrimaryHandle(e){let t=this.config.agents[e];if(t)return t.mentions.find(n=>n.startsWith("@"))}extractCapability(e){if(!e.systemPrompt)return e.name;let t=e.systemPrompt.split(/[.\n]/)[0]?.trim()||e.name;return t.length>80?t.slice(0,77)+"...":t}};import{existsSync as le,mkdirSync as Ws,readdirSync as Hs,readFileSync as Js,unlinkSync as ct,writeFileSync as Oe}from"fs";import{resolve as F}from"path";var z=["user","feedback","project","reference"],De=class{baseDir;constructor(e={}){let t=e.baseDir??F(process.cwd(),".agentx");this.baseDir=F(t,"agent-memory")}dirFor(e){let t=dt(e),n=F(this.baseDir,t);return Ws(n,{recursive:!0}),n}fileFor(e,t,n){return F(this.dirFor(e),`${t}_${dt(n)}.md`)}indexPath(e){return F(this.dirFor(e),"MEMORY.md")}save(e){if(!z.includes(e.type))throw new Error(`invalid memory type: ${e.type}`);if(!e.name.trim())throw new Error("memory name is required");if(!e.description.trim())throw new Error("memory description is required");let t=this.fileFor(e.agentId,e.type,e.name),n=new Date().toISOString(),s=n,i=de(t);if(i){let o=lt(i);o&&(s=o.createdAt)}let r={name:dt(e.name),type:e.type,description:e.description.trim(),body:e.body.trimEnd(),createdAt:s,updatedAt:n};return Oe(t,zs(r)),this.rewriteIndex(e.agentId),r}get(e,t){for(let n of z){let s=this.fileFor(e,n,t);if(!le(s))continue;let i=lt(de(s)??"");if(i)return i}return null}list(e){let t=this.dirFor(e);if(!le(t))return[];let n=[];for(let s of Hs(t,{withFileTypes:!0})){if(!s.isFile()||!s.name.endsWith(".md")||s.name==="MEMORY.md")continue;let i=lt(de(F(t,s.name))??"");i&&n.push(i)}return n.sort((s,i)=>{let r=z.indexOf(s.type),o=z.indexOf(i.type);return r!==o?r-o:s.name.localeCompare(i.name)})}remove(e,t){let n=!1;for(let s of z){let i=this.fileFor(e,s,t);le(i)&&(ct(i),n=!0)}return n&&this.rewriteIndex(e),n}indexMarkdown(e){let t=this.indexPath(e);return de(t)??""}syncToWorkspace(e,t){if(!t)return;let n=this.indexMarkdown(e).trim(),s=F(t,".agentx-memory.md");n?Oe(s,n+`
500
+ `):le(s)&&ct(s);let i=F(t,"CLAUDE.md"),r="<!-- AGENTX-MEMORY-START \u2014 auto-managed by agentx; edit via `agentx memory` -->",o="<!-- AGENTX-MEMORY-END -->",a=n?`${r}
501
+ ${n}
502
+ ${o}`:"",c=de(i)??"",l=c.indexOf(r),d=c.indexOf(o),g;if(l>=0&&d>l){let p=c.slice(0,l).replace(/\n+$/,""),u=c.slice(d+o.length).replace(/^\n+/,"");g=a?[p,a,u].filter(Boolean).join(`
503
+
504
+ `)+`
505
+ `:[p,u].filter(Boolean).join(`
506
+
507
+ `)+(p||u?`
508
+ `:"")}else if(a)g=c?c.replace(/\n+$/,"")+`
509
+
510
+ `+a+`
511
+ `:a+`
512
+ `;else return;Oe(i,g)}rewriteIndex(e){let t=this.list(e);if(t.length===0){let i=this.indexPath(e);le(i)&&ct(i);return}let n={user:[],feedback:[],project:[],reference:[]};for(let i of t)n[i.type].push(i);let s=[];s.push(`# Memory \u2014 ${e}`,""),s.push("This is the agent's experiential memory: user preferences,"),s.push("feedback, project-specific context, and external references."),s.push("Updated via `agentx memory` CLI or by the agent itself.");for(let i of z){let r=n[i];if(r.length){s.push("",`## ${Ks(i)}`,"");for(let o of r)s.push(`- **${o.name}** \u2014 ${o.description} _(updated ${o.updatedAt.slice(0,10)})_`)}}s.push(""),Oe(this.indexPath(e),s.join(`
513
+ `))}};function zs(h){return["---",`name: ${h.name}`,`description: ${Xs(h.description)}`,`type: ${h.type}`,`created: ${h.createdAt}`,`updated: ${h.updatedAt}`,"---",""].join(`
514
+ `)+h.body+`
515
+ `}function lt(h){if(!h.startsWith(`---
516
+ `))return null;let e=h.indexOf(`
517
+ ---
518
+ `,4);if(e<0)return null;let t=h.slice(4,e),n=h.slice(e+5).replace(/^\n+/,"").trimEnd(),s={};for(let i of t.split(`
519
+ `)){let r=i.indexOf(":");if(r<0)continue;let o=i.slice(0,r).trim(),a=qs(i.slice(r+1).trim());s[o]=a}return!s.name||!s.type||!s.description||!z.includes(s.type)?null:{name:s.name,type:s.type,description:s.description,body:n,createdAt:s.created??"",updatedAt:s.updated??s.created??""}}function Xs(h){return/[:#\n]/.test(h)?`"${h.replace(/"/g,'\\"')}"`:h}function qs(h){return h.startsWith('"')&&h.endsWith('"')&&h.length>=2?h.slice(1,-1).replace(/\\"/g,'"'):h}function dt(h){return h.toLowerCase().replace(/[^a-z0-9]+/g,"_").replace(/^_+|_+$/g,"")||"unnamed"}function Ks(h){return h.charAt(0).toUpperCase()+h.slice(1)}function de(h){try{return Js(h,"utf-8")}catch{return null}}import{existsSync as Ys,readFileSync as Vs,unlinkSync as Qs,writeFileSync as Zs}from"fs";import{resolve as ei}from"path";var yn="_agentxManaged";function bn(h,e){let t=ei(h,".mcp.json"),n=Ys(t),s=n?ti(t):null,i=s!==null&&s[yn]===!0;if(n&&!i)return"skipped-operator-owned";let r=Object.keys(e).length>0;if(!r&&!n)return"noop";if(!r&&i)return Qs(t),"removed";let o={[yn]:!0,mcpServers:e};return Zs(t,JSON.stringify(o,null,2)+`
520
+ `),n?"updated":"installed"}function ti(h){try{return JSON.parse(Vs(h,"utf-8"))}catch{return null}}var wn="remember.md",kn=`---
521
+ name: remember
522
+ description: 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 \u2014 no file system access required.
523
+ ---
524
+
525
+ # Remember
526
+
527
+ You have a persistent memory layer that is auto-loaded into your system prompt on every task. It is SEPARATE from the shared wiki:
528
+
529
+ - **Wiki** is cross-agent, authoritative, documented. Facts that multiple agents agree on.
530
+ - **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.
531
+
532
+ Four memory kinds:
533
+
534
+ | Kind | Save something of type\u2026 |
535
+ |---|---|
536
+ | \`user\` | role, seniority, tone preference, what they are fluent in vs new to |
537
+ | \`feedback\` | corrections ("don't do X, we got burned last Q") + confirmations ("yes the bundled PR was right") |
538
+ | \`project\` | current initiative, known constraints, deadline context not in the code |
539
+ | \`reference\` | external systems and when to use them (Linear, Grafana, a specific dashboard URL) |
540
+
541
+ ## When to save
542
+
543
+ **Good triggers** \u2014 save a memory whenever:
544
+
545
+ - The user corrects an approach ("no not that", "stop doing X"). Save as \`feedback\`.
546
+ - The user confirms a non-obvious approach worked ("yes exactly, keep doing that"). Save as \`feedback\`.
547
+ - You learn a durable fact about the user or their role you didn't know before. Save as \`user\`.
548
+ - You learn a project-level fact that is not in the code and not in the wiki. Save as \`project\`.
549
+ - The user points at an external system ("check Linear INGEST for ticket context"). Save as \`reference\`.
550
+
551
+ **Skip** \u2014 do NOT save:
552
+
553
+ - Conversation-specific state, current task TODOs, temporary context. Use the current session for those.
554
+ - Things already in the wiki, or that feel cross-agent authoritative. Propose a wiki article instead.
555
+ - Negative judgements about the user. Memories get re-read every session; stay professional.
556
+
557
+ ## How to save
558
+
559
+ The daemon exposes \`POST /api/memory\`. Call it from a Bash tool invocation.
560
+
561
+ \`\`\`bash
562
+ curl -sS -X POST http://localhost:18800/api/memory \\
563
+ -H 'Content-Type: application/json' \\
564
+ -d '{
565
+ "agentId": "<your-agent-id>",
566
+ "type": "feedback",
567
+ "name": "no-mock-db",
568
+ "description": "Tests must hit a real database \u2014 mocks masked a prod migration bug in Q1.",
569
+ "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."
570
+ }'
571
+ \`\`\`
572
+
573
+ Your agent id is the name of the workspace directory you're in (e.g. \`atlas\`, \`mtgl-v2\`). When in doubt, run \`basename "$(pwd)"\`.
574
+
575
+ ### Update an existing memory
576
+
577
+ Same endpoint, same \`name\`. The daemon keeps \`createdAt\` stable and bumps \`updatedAt\`. If you want to *append* rather than replace, pass \`"append": true\`:
578
+
579
+ \`\`\`bash
580
+ curl -sS -X POST http://localhost:18800/api/memory \\
581
+ -H 'Content-Type: application/json' \\
582
+ -d '{ "agentId":"atlas","type":"feedback","name":"no-mock-db",
583
+ "description":"\u2026", "body":"Also: 2026-04-15 hit the same class of bug on the grant-application webhook. Same rule.","append":true }'
584
+ \`\`\`
585
+
586
+ ### Read what you already remember
587
+
588
+ Your CLAUDE.md already includes a sentinel block with every memory you've saved. Skim it first \u2014 if what you're about to save is already there, prefer updating the existing entry over creating a near-duplicate.
589
+
590
+ For the full JSON:
591
+
592
+ \`\`\`bash
593
+ curl -sS 'http://localhost:18800/api/memory?agent=atlas'
594
+ \`\`\`
595
+
596
+ ### Remove
597
+
598
+ \`\`\`bash
599
+ curl -sS -X DELETE 'http://localhost:18800/api/memory/no-mock-db?agent=atlas'
600
+ \`\`\`
601
+
602
+ ## Structuring the body
603
+
604
+ For **feedback** memories specifically, structure the body so future-you can judge edge cases instead of blindly obeying the rule:
605
+
606
+ \`\`\`
607
+ <the rule itself, one line>
608
+
609
+ Why: <the reason \u2014 often a past incident, a strong preference>
610
+ How to apply: <when/where this guidance kicks in>
611
+ \`\`\`
612
+
613
+ That's the same shape Claude Code's own memory system uses. Keeps the memory useful even when context shifts.
614
+
615
+ ## Deduplication
616
+
617
+ Before saving, scan the AGENTX-MEMORY section of your CLAUDE.md. If a similar entry exists, prefer updating it over creating a new one \u2014 the index gets noisy otherwise.
618
+ `;var Ne=class{registry;memoryStore;states=new Map;log;constructor(e,t=console.error.bind(console,"[heartbeat]"),n){this.registry=e,this.log=t,this.memoryStore=n}register(e,t){if(!t.enabled)return;this.stop(e);let n={agentId:e,config:t,runCount:0,errors:0},s=(t.intervalMinutes||30)*60*1e3;n.timer=setInterval(()=>{this.runHeartbeat(n).catch(i=>{this.log(`[${e}] heartbeat error: ${i.message}`),n.errors++})},s),this.states.set(e,n),this.log(`Registered heartbeat for "${e}" every ${t.intervalMinutes}min`)}stop(e){let t=this.states.get(e);t?.timer&&(clearInterval(t.timer),t.timer=void 0),this.states.delete(e)}stopAll(){for(let[e]of this.states)this.stop(e)}async runHeartbeat(e){let{agentId:t,config:n}=e;this.log(`[${t}] heartbeat running...`),e.lastRun=new Date,e.runCount++;let s=n.channel||"heartbeat";try{let i=n.prompt;if(this.memoryStore){let o=this.memoryStore.buildRecallContext(t);o&&(i=`${o}
619
+
620
+ ${i}`)}let r=await this.registry.execute({message:i,agentId:t,context:{channel:s,sender:"system:heartbeat",chatId:`heartbeat:${t}`}});r.error?(this.log(`[${t}] heartbeat error: ${r.error}`),e.errors++):this.log(`[${t}] heartbeat completed (${r.duration}ms, ${r.content.length} chars)`)}catch(i){this.log(`[${t}] heartbeat failed: ${i.message}`),e.errors++}}async trigger(e){let t=this.states.get(e);if(!t){this.log(`[${e}] no heartbeat registered`);return}await this.runHeartbeat(t)}list(){return Array.from(this.states.values()).map(e=>({agentId:e.agentId,intervalMinutes:e.config.intervalMinutes,lastRun:e.lastRun,runCount:e.runCount,errors:e.errors}))}};var he=class{services=[];log;constructor(e,t=console.error.bind(console,"[services]")){this.log=t,this.compile(e),this.log(`${this.services.length} service(s) loaded`)}reload(e){return this.compile(e),this.log(`Services reloaded \u2014 ${this.services.length} active`),{count:this.services.length}}compile(e){let t=[];for(let[n,s]of Object.entries(e)){let i={def:{...s,id:n},patterns:[]};for(let r of s.triggers)try{i.patterns.push({regex:new RegExp(r.pattern,"i"),channel:r.channel})}catch(o){this.log(`Invalid regex in service "${n}": ${r.pattern} \u2014 ${o.message}`)}i.patterns.length>0&&t.push(i)}this.services=t}match(e,t,n){for(let s of this.services){if(s.def.allowedContacts?.length){let i=t.replace(/[^0-9]/g,"");if(!s.def.allowedContacts.some(o=>{let a=o.replace(/[^0-9]/g,"");return i.includes(a)||a.includes(i)}))continue}for(let i of s.patterns){if(i.channel&&i.channel!==n)continue;let r=e.match(i.regex);if(r)return U.cat("agent",`Service match: "${s.def.name}" (trigger: ${i.regex.source})`),{service:s.def,trigger:i.regex.source,captured:r[0]}}}return null}async execute(e,t,n,s){this.log(`Executing service "${e.name}" -> agent "${e.agent}"`);try{let i=await t.execute({message:e.prompt,agentId:e.agent,context:{channel:n.channel,sender:n.sender,chatId:`service:${e.id}:${n.chatId}`}});if(i.error){this.log(`Service "${e.name}" failed: ${i.error}`),await s(`Service "${e.name}" encountered an error: ${i.error}`);return}i.content&&await s(i.content),this.log(`Service "${e.name}" completed in ${i.duration}ms`)}catch(i){this.log(`Service "${e.name}" threw: ${i.message}`),await s(`Service error: ${i.message}`)}}getScheduledServices(){return this.services.filter(e=>e.def.schedule).map(e=>e.def)}list(){return this.services.map(e=>({id:e.def.id,name:e.def.name,triggers:e.patterns.length,agent:e.def.agent,scheduled:!!e.def.schedule}))}};var Le=class{employees=new Map;constructor(e){for(let[t,n]of Object.entries(e.orgChart)){let s=e.roles[n.role];if(!s)throw new Error(`Org chart references unknown role "${n.role}" for agent "${t}"`);this.employees.set(t,{agentId:t,role:s,roleKey:n.role,reportsTo:n.reportsTo,schedule:n.schedule,utilizationTarget:n.utilizationTarget})}for(let t of this.employees.values())if(t.reportsTo&&!this.employees.has(t.reportsTo))throw new Error(`Agent "${t.agentId}" reports to unknown agent "${t.reportsTo}"`);for(let t of this.employees.values()){let n=new Set,s=t.agentId;for(;s;){if(n.has(s))throw new Error(`Reporting cycle detected involving "${t.agentId}"`);n.add(s),s=this.employees.get(s)?.reportsTo}}}get(e){return this.employees.get(e)}all(){return[...this.employees.values()]}reportsTo(e){return this.employees.get(e)?.reportsTo}directReports(e){return[...this.employees.values()].filter(t=>t.reportsTo===e).map(t=>t.agentId)}chainOfCommand(e){let t=[],n=e;for(;n&&!t.includes(n);)t.push(n),n=this.employees.get(n)?.reportsTo;return t}};var vn={sun:0,mon:1,tue:2,wed:3,thu:4,fri:5,sat:6};function ht(h,e){let n=new Intl.DateTimeFormat("en-US",{timeZone:e,weekday:"short",hour:"2-digit",minute:"2-digit",hour12:!1}).formatToParts(h),s=(n.find(a=>a.type==="weekday")?.value||"sun").toLowerCase().slice(0,3),i=n.find(a=>a.type==="hour")?.value||"00",r=n.find(a=>a.type==="minute")?.value||"00";return{hour:parseInt(i,10)%24,minute:parseInt(r,10),dow:vn[s]??0}}function Z(h){let[e,t]=h.split(":").map(Number);return e*60+t}function gt(h,e){return h.days.some(t=>vn[t]===e)}function ni(h,e,t){let{hour:n,minute:s,dow:i}=ht(e,t);if(!gt(h,i))return!1;let r=n*60+s,o=Z(h.start),a=Z(h.end);if(r<o||r>=a)return!1;if(h.lunch){let c=Z(h.lunch.start),l=Z(h.lunch.end);if(r>=c&&r<l)return!1}return!0}var Be=class{constructor(e,t){this.org=e;this.config=t}isOnClock(e,t=new Date){let n=this.org.get(e);return n?ni(n.schedule,t,this.config.timezone):!1}isDayStart(e,t=new Date){let n=this.org.get(e);if(!n)return!1;let{hour:s,minute:i,dow:r}=ht(t,this.config.timezone);if(!gt(n.schedule,r))return!1;let o=Z(n.schedule.start);return s*60+i===o}isDayEnd(e,t=new Date){let n=this.org.get(e);if(!n)return!1;let{hour:s,minute:i,dow:r}=ht(t,this.config.timezone);if(!gt(n.schedule,r))return!1;let o=Z(n.schedule.end);return s*60+i===o}clockedInAgents(e=new Date){return this.org.all().filter(t=>this.isOnClock(t.agentId,e)).map(t=>t.agentId)}};var Ge=class{constructor(e,t,n,s){this.router=e;this.org=t;this.config=n;this.log=s}async postToMain(e,t){let n=this.config.mainChannel;try{await this.router.sendOutbound({channel:n.channel,chatId:n.chatId,text:e,agentId:t,accountId:n.accountId})}catch(s){this.log(`[business] postToMain failed: ${s.message}`)}}async escalate(e,t,n){let s=this.org.chainOfCommand(e).slice(1),i=s.length?s.slice(0,1):[],r=`\u26A0\uFE0F **Escalation from ${e}** \u2014 ${t}
621
+
622
+ ${n}`;if(!i.length){await this.postToMain(r,e);return}await this.postToMain(`${r}
623
+
624
+ cc: ${i.map(o=>`@${o}`).join(" ")}`,e)}};import{writeFileSync as si,existsSync as $n,mkdirSync as ii,readFileSync as ri}from"fs";import{resolve as ut}from"path";function pt(){return new Date().toISOString().slice(0,10)}var Fe=class{constructor(e,t,n){this.org=e;this.schedule=t;this.log=n;this.dir=ut(process.cwd(),".agentx/kpi"),$n(this.dir)||ii(this.dir,{recursive:!0}),this.stats=this.loadOrInit()}stats;dir;onClockTick=new Map;loadOrInit(){let e=ut(this.dir,`${pt()}.json`);if($n(e))try{return JSON.parse(ri(e,"utf-8"))}catch{}let t={};for(let n of this.org.all())t[n.agentId]={tasksCompleted:0,timeLoggedSeconds:0,onClockSeconds:0,blockers:0,utilization:0};return{date:pt(),perAgent:t}}tick(){let e=Date.now();for(let t of this.org.all()){if(!this.schedule.isOnClock(t.agentId)){this.onClockTick.delete(t.agentId);continue}let n=this.onClockTick.get(t.agentId);if(n){let s=Math.min(e-n,12e4)/1e3,i=this.stats.perAgent[t.agentId];i&&(i.onClockSeconds+=s)}this.onClockTick.set(t.agentId,e)}this.recomputeUtil(),this.persist()}recordTaskCompletion(e){let t=this.stats.perAgent[e.agentId];t&&(t.tasksCompleted+=1,t.timeLoggedSeconds+=e.durationSeconds,this.recomputeUtil(),this.persist())}recordBlocker(e){let t=this.stats.perAgent[e];t&&(t.blockers+=1,this.persist())}recomputeUtil(){for(let e of Object.values(this.stats.perAgent))e.utilization=e.onClockSeconds>0?Math.min(1,e.timeLoggedSeconds/e.onClockSeconds):0}persist(){this.stats.date!==pt()&&(this.flush(),this.stats=this.loadOrInit());try{si(ut(this.dir,`${this.stats.date}.json`),JSON.stringify(this.stats,null,2))}catch(e){this.log(`[business] KPI persist failed: ${e.message}`)}}flush(){this.persist()}snapshot(){return JSON.parse(JSON.stringify(this.stats))}dailySummaryText(){let e=[`\u{1F4CA} **Daily report \u2014 ${this.stats.date}**`,""],t=0,n=0;for(let[i,r]of Object.entries(this.stats.perAgent)){t+=r.timeLoggedSeconds,n+=r.onClockSeconds;let o=(r.timeLoggedSeconds/3600).toFixed(1),a=(r.utilization*100).toFixed(0);e.push(`\u2022 **${i}** \u2014 ${r.tasksCompleted} tasks, ${o}h logged, ${a}% utilization${r.blockers?`, ${r.blockers} blocker(s)`:""}`)}e.push("");let s=n>0?(t/n*100).toFixed(0):"0";return e.push(`**Team:** ${(t/3600).toFixed(1)}h logged / ${(n/3600).toFixed(1)}h on-clock \u2192 ${s}% utilization`),e.join(`
625
+ `)}};function M(h,e,t){h.statusCode=e,h.setHeader("Content-Type","application/json"),h.end(JSON.stringify(t))}async function Ue(h){return new Promise((e,t)=>{let n=[];h.on("data",s=>n.push(s)),h.on("end",()=>{let s=Buffer.concat(n).toString("utf-8");if(!s)return e({});try{e(JSON.parse(s))}catch(i){t(i)}}),h.on("error",t)})}async function Sn(h,e,t,n){if(h==="GET /business/status"){let s=new Date,i=n.schedule.clockedInAgents(s);return M(t,200,{time:s.toISOString(),onClock:i,employees:n.org.all().map(r=>({agentId:r.agentId,role:r.role.title,reportsTo:r.reportsTo,onClock:i.includes(r.agentId)})),kpi:n.kpi.snapshot()}),!0}if(h.startsWith("GET /business/work")){let i=new URL(e.url||"/","http://x").searchParams.get("agent");if(!i)return M(t,400,{error:"?agent= required"}),!0;try{let r=await n.workSource.listOpen(i);M(t,200,{items:r})}catch(r){M(t,500,{error:r.message})}return!0}if(h==="POST /business/claim"){let s=await Ue(e),{agent:i,itemId:r}=s;if(!i||!r)return M(t,400,{error:"agent and itemId required"}),!0;try{await n.workSource.claim(i,r),M(t,200,{ok:!0})}catch(o){M(t,500,{error:o.message})}return!0}if(h==="POST /business/report"){let s=await Ue(e);if(!s.itemId||!s.status)return M(t,400,{error:"itemId and status required"}),!0;if(!["in-progress","done","blocked"].includes(s.status))return M(t,400,{error:"status must be in-progress | done | blocked"}),!0;try{await n.workSource.report(s.itemId,{status:s.status,note:s.note,timeSeconds:s.timeSeconds,blocker:s.blocker}),M(t,200,{ok:!0})}catch(i){M(t,500,{error:i.message})}return!0}if(h==="POST /business/escalate"){let s=await Ue(e);return!s.agent||!s.subject?(M(t,400,{error:"agent and subject required"}),!0):(await n.reporter.escalate(s.agent,s.subject,s.detail||""),n.kpi.recordBlocker(s.agent),M(t,200,{ok:!0}),!0)}if(h==="POST /business/post"){let s=await Ue(e);return s.text?(await n.reporter.postToMain(s.text,s.agent),M(t,200,{ok:!0}),!0):(M(t,400,{error:"text required"}),!0)}return!1}function mt(h){return`## Business Layer Tools
626
+
627
+ During business hours you'll receive lifecycle prompts ([STANDUP], [WORK], [WRAP]).
628
+ Use these HTTP endpoints on the local daemon to act on them.
629
+
630
+ - **List your work:**
631
+ \`curl -s ${h}/business/work?agent=<your-id>\`
632
+ - **Claim an item** (optional \u2014 some sources no-op):
633
+ \`curl -s -X POST ${h}/business/claim -d '{"agent":"<your-id>","itemId":"<id>"}'\`
634
+ - **Report progress or completion:**
635
+ \`curl -s -X POST ${h}/business/report \\
636
+ -d '{"itemId":"<id>","status":"done","timeSeconds":1800,"note":"shipped"}'\`
637
+ status: \`in-progress\` | \`done\` | \`blocked\`
638
+ - **Escalate a blocker** up the org chart:
639
+ \`curl -s -X POST ${h}/business/escalate \\
640
+ -d '{"agent":"<your-id>","subject":"short","detail":"why it's stuck"}'\`
641
+ - **Post to the main channel** (daily plans, reports):
642
+ \`curl -s -X POST ${h}/business/post \\
643
+ -d '{"agent":"<your-id>","text":"your update"}'\`
644
+
645
+ Your org chart, role, and schedule are fixed in agentx.json under \`business.orgChart\`.
646
+ `}var oi=6e4,We=class{constructor(e,t,n,s,i,r,o,a,c){this.config=e;this.org=t;this.schedule=n;this.registry=s;this.reporter=i;this.kpi=r;this.workSource=o;this.daemonBase=a;this.log=c;for(let l of t.all())this.day.set(l.agentId,{})}timer;day=new Map;currentDate=new Date().toISOString().slice(0,10);runningTask=new Set;start(){this.log("[business] day cycle starting"),this.tick().catch(e=>this.log(`[business] tick error: ${e.message}`)),this.timer=setInterval(()=>{this.tick().catch(e=>this.log(`[business] tick error: ${e.message}`))},oi)}stop(){this.timer&&clearInterval(this.timer),this.timer=void 0,this.kpi.flush()}async tick(){let e=new Date,t=e.toISOString().slice(0,10);if(t!==this.currentDate){this.currentDate=t;for(let n of this.day.keys())this.day.set(n,{})}this.kpi.tick();for(let n of this.org.all()){let s=this.day.get(n.agentId)||{};if(this.day.set(n.agentId,s),!this.runningTask.has(n.agentId)){if(this.schedule.isDayStart(n.agentId,e)&&!s.standupFiredAt){s.standupFiredAt=Date.now(),this.fireStandup(n.agentId).catch(i=>this.log(`[business] standup err: ${i.message}`));continue}if(this.schedule.isDayEnd(n.agentId,e)&&!s.wrapFiredAt){s.wrapFiredAt=Date.now(),this.fireWrap(n.agentId).catch(i=>this.log(`[business] wrap err: ${i.message}`));continue}this.schedule.isOnClock(n.agentId,e)&&(s.lastWorkTickAt?(Date.now()-s.lastWorkTickAt)/6e4:1/0)>=this.config.workTickMinutes&&(s.lastWorkTickAt=Date.now(),this.fireWorkTick(n.agentId).catch(r=>this.log(`[business] work-tick err: ${r.message}`)))}}}roleContext(e){let t=this.org.get(e);if(!t)return"";let n=t.reportsTo?`reporting to ${t.reportsTo}`:"(no manager \u2014 top of org chart)",s=t.role.responsibilities.length?`
647
+ Responsibilities:
648
+ ${t.role.responsibilities.map(r=>`- ${r}`).join(`
649
+ `)}`:"",i=t.role.sopPath?`
650
+ SOP: @${t.role.sopPath}`:"";return`You are ${e}, role: ${t.role.title} ${n}.${s}${i}
651
+
652
+ ${mt(this.daemonBase)}`}async runLifecycle(e,t){if(this.runningTask.has(e))return;this.runningTask.add(e);let n=Date.now();try{let s=await this.registry.execute({message:t,agentId:e,context:{channel:"business",chatId:`day-cycle:${this.currentDate}:${e}`}}),i=Math.round((s.duration??Date.now()-n)/1e3);return s.error||this.kpi.recordTaskCompletion({agentId:e,durationSeconds:i,success:!0,channel:"business"}),s.content}finally{this.runningTask.delete(e)}}async fireStandup(e){this.log(`[business] STANDUP \u2192 ${e}`);let n=`${this.roleContext(e)}
653
+
654
+ [STANDUP] It's the start of your work day.
655
+ 1. Call GET /business/work?agent=${e} to see your open work items.
656
+ 2. Pick your top 3 for today, in priority order.
657
+ 3. Post your plan to the main channel via POST /business/post \u2014 keep it to 4-6 short lines.
658
+ 4. Flag any blockers via POST /business/escalate.`;if(await this.runLifecycle(e,n)){let i=this.day.get(e)||{};i.hadWorkToday=!0,this.day.set(e,i)}}async fireWorkTick(e){let t=await this.workSource.listOpen(e).catch(()=>[]);if(!t.length)return;this.log(`[business] WORK-TICK \u2192 ${e} (${t.length} open)`);let s=`${this.roleContext(e)}
659
+
660
+ [WORK] Continue your highest-priority open task.
661
+ - Call GET /business/work?agent=${e} to see what's assigned to you.
662
+ - If you complete one, POST /business/report with status:done and the time you spent.
663
+ - If blocked, POST /business/escalate with a concise blocker description.
664
+ - Then move on to the next item. Keep working until you hit a real stopping point \u2014 don't idle.`;if(await this.runLifecycle(e,s)){let r=this.day.get(e)||{};r.hadWorkToday=!0,this.day.set(e,r)}}async fireWrap(e){if(!(this.day.get(e)||{}).hadWorkToday){this.log(`[business] WRAP skipped for ${e} (no work done today)`);return}this.log(`[business] WRAP \u2192 ${e}`);let s=`${this.roleContext(e)}
665
+
666
+ [WRAP] End of work day. Post a concise daily report to the main channel via POST /business/post:
667
+ - Tasks closed today
668
+ - Time logged (total)
669
+ - Carry-overs to tomorrow
670
+ - Any outstanding blockers
671
+ Keep it under 8 lines.`;await this.runLifecycle(e,s)}};var He=class{org;schedule;workSource;reporter;kpi;dayCycle;constructor(e,t,n,s,i){this.org=new Le(e),this.schedule=new Be(this.org,e),this.workSource=jt(e,t,i),this.reporter=new Ge(s,this.org,e,i),this.kpi=new Fe(this.org,this.schedule,i);let[r,o]=t.node.bind.split(":"),a=`http://${r==="0.0.0.0"?"127.0.0.1":r}:${o||"18800"}`;this.dayCycle=new We(e,this.org,this.schedule,n,this.reporter,this.kpi,this.workSource,a,i)}start(){this.dayCycle.start()}stop(){this.dayCycle.stop()}recordTaskCompletion(e,t,n,s){this.kpi.recordTaskCompletion({agentId:e,durationSeconds:t,success:n,channel:s})}async handleHttp(e,t,n){let s={workSource:this.workSource,org:this.org,schedule:this.schedule,reporter:this.reporter,kpi:this.kpi};return Sn(e,t,n,s)}summary(){let e=this.schedule.clockedInAgents().length;return`Business layer: ${this.org.all().length} employees, ${e} on-clock now`}};var xn=class{config;registry;router;cron;mesh;hooks;landscape;heartbeat;business;httpServer;webhooks;github;webrtc;botManager;workflowDispatcher;workflowStore;workflowRuns;agentMemory=new De;log;sseClients=new Set;configPath;configWatcher;reloadTimer;constructor(e){let n=new oe("agentx").asConsoleLog();this.log=(...r)=>{n(...r);let o=r.map(a=>typeof a=="string"?a:JSON.stringify(a)).join(" ");this.broadcastSSE("log",o)},this.log("Loading configuration..."),this.configPath=e,this.config=Ke(e);let s=At(this.config);for(let r of s)this.log(` \u26A0 ${r}`);let[,i]=this.config.node.bind.split(":");Et(this.config.agents,i||"19900",this.log),this.hooks=new vt,qe(process.cwd(),this.hooks),this.landscape=new ce(this.config),this.registry=new Tt(this.config,this.log),St(this.registry),this.registry.setLandscape(this.landscape),this.heartbeat=new Ne(this.registry,this.log);for(let[r,o]of Object.entries(this.config.agents))o.heartbeat?.enabled&&this.heartbeat.register(r,o.heartbeat);if(this.router=new ie(this.registry,this.config,this.hooks,this.log),Object.keys(this.config.services).length>0){let r=new he(this.config.services,this.log);this.router.setServiceMatcher(r)}this.cron=new Ye(this.config,this.registry,this.hooks,this.log),this.cron.setNotifyCallback(async(r,o,a,c)=>{let l=`Cron "${r}" failed (${c}x)
672
+ Agent: ${o}
673
+ Error: ${a.slice(0,300)}`;this.log(`[CRON ALERT] ${l}`),this.broadcastSSE("cron-failure",JSON.stringify({jobId:r,agent:o,error:a,consecutiveErrors:c}));let d=this.config.crons[r];if(d?.notify)try{await this.router.sendOutbound({channel:d.notify.channel,chatId:d.notify.chatId,text:`\u{1F534} **Cron "${r}" failed** (${c}x)
674
+ ${a.slice(0,300)}`,agentId:o,accountId:d.notify.accountId})}catch(g){this.log(`[CRON ALERT] notify send failed: ${g.message}`)}}),this.config.mesh.enabled&&(this.mesh=new me(this.config,this.log),this.router.setMesh(this.mesh),this.registry.setMeshFallback({findPeerWithSkill:r=>this.mesh.findPeerWithSkill(r),sendTask:(r,o,a)=>this.mesh.sendTask(r,o,a),directory:()=>this.mesh.directory()})),this.webhooks=new Ee(this.registry,{},this.log,this.mesh,this.config.webhooks);try{let r=mn();r?(fn(r),this.log(` SQLite: ${r.name}`)):this.log(" SQLite: not opened (native binding unavailable or path unwritable)")}catch(r){this.log(` SQLite: skipped (${r.message})`)}if(this.config.business?.enabled)try{this.business=new He(this.config.business,this.config,this.registry,this.router,this.log),this.router.setBusiness(this.business)}catch(r){this.log(`[business] initialization failed: ${r.message}`)}}async start(){this.log(""),this.log(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"),this.log(" \u2502 agentx daemon \u2502"),this.log(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"),this.log(""),this.log(` Node: ${this.config.node.name} (${this.config.node.id})`),this.log(` Bind: ${this.config.node.bind}`),this.log(""),await this.startChannels(),await this.cron.start(),this.mesh&&await this.mesh.start();let e=this.mesh?.directory().map(r=>({peer:r.peer,healthy:r.healthy,skills:r.skills}))||[];this.landscape.build(e),this.log(` Landscape: built for ${Object.keys(this.config.agents).length} agents`),this.scheduleMidnightHook();try{let r=new Date(Date.now()-864e5).toISOString().slice(0,10),o=this.registry.getTokenTracker().catchUpTokenCosts(r);o.length>0&&this.log(` Cost tracking: caught up ${o.length} missed day${o.length===1?"":"s"} (${o[0]}${o.length>1?` \u2192 ${o[o.length-1]}`:""})`)}catch(r){this.log(` Cost tracking: catch-up failed (non-fatal): ${r.message}`)}this.business&&(this.business.start(),this.log(` ${this.business.summary()}`)),await this.startHttpApi(),this.log(""),this.log(" Agents:");for(let r of this.registry.list())this.log(` ${r.id} (${r.tier}) \u2192 ${r.workspace}`);this.installAgentMemorySurface(),this.installAgentMcpConfig();let t=this.cron.list();if(t.length){this.log(""),this.log(" Cron Jobs:");for(let r of t){let o=r.enabled?"enabled":"disabled";this.log(` ${r.id} [${o}] \u2192 ${r.agent} (${r.schedule})`)}}if(this.mesh){this.log(""),this.log(" Mesh Peers:");for(let r of this.mesh.directory()){let o=r.healthy?"\u2713":"\u2717";this.log(` ${o} ${r.peer} (${r.peerUrl})`)}}process.env.AGENTX_AUTO_RELOAD!=="false"&&this.startConfigWatcher();try{let r=this.registry.pruneTaskHistory();r>0&&this.log(` Pruned ${r} old task-history folder(s)`)}catch{}try{let r=this.registry.hydrateLastSummariesFromDisk();r>0&&this.log(` Loaded last-activity summaries for ${r} agent(s)`)}catch{}this.log(""),this.log(" Ready."),this.log("");let n=N(process.cwd(),".agentx/daemon.pid");In(di(n),{recursive:!0}),Tn(n,String(process.pid)),this.log(` PID: ${process.pid} (${n})`),process.on("uncaughtException",r=>{this.log(`UNCAUGHT EXCEPTION: ${r.message}`),this.log(r.stack||"")}),process.on("unhandledRejection",r=>{this.log(`UNHANDLED REJECTION: ${r}`)});let s=!1,i=async r=>{s||(s=!0,this.log(`
675
+ Received ${r}, shutting down gracefully...`),await this.stop())};process.on("SIGINT",()=>i("SIGINT")),process.on("SIGTERM",()=>i("SIGTERM"))}async stop(){let e=Date.now();try{this.log(" Stopping channels..."),await Promise.race([this.router.stopAll(),new Promise(t=>setTimeout(t,5e3))])}catch(t){this.log(` Channel stop error: ${t.message}`)}try{let t=parseInt(process.env.AGENTX_DRAIN_TIMEOUT_MS||"300000",10),n=Date.now(),s=()=>this.registry.getActiveTaskCount()+this.router.getActiveMeshForwardCount(),i=s();if(i>0){for(this.log(` Draining ${i} in-flight task(s) \u2014 local=${this.registry.getActiveTaskCount()}, mesh-forwards=${this.router.getActiveMeshForwardCount()} (max ${Math.round(t/1e3)}s)...`);i>0&&Date.now()-n<t;)await new Promise(o=>setTimeout(o,500)),i=s();let r=Date.now()-n;i===0?this.log(` Drain complete (${r}ms)`):this.log(` Drain timeout after ${r}ms \u2014 ${i} task(s) still in flight (local=${this.registry.getActiveTaskCount()}, mesh-forwards=${this.router.getActiveMeshForwardCount()}), exiting anyway`)}}catch(t){this.log(` Drain error: ${t.message}`)}try{this.router.flushPersistence?.()}catch(t){this.log(` Router flush error: ${t.message}`)}try{this.log(" Stopping crons (saving last run times)..."),await this.cron.stop()}catch{}try{this.log(" Stopping heartbeats..."),this.heartbeat.stopAll()}catch{}try{this.business&&(this.log(" Stopping business layer..."),this.business.stop())}catch{}try{this.mesh&&(this.log(" Stopping mesh..."),await this.mesh.stop())}catch{}try{this.botManager&&this.botManager.shutdown()}catch{}try{this.webrtc&&this.webrtc.shutdown()}catch{}if(this.midnightTimer&&clearTimeout(this.midnightTimer),this.reloadTimer&&clearTimeout(this.reloadTimer),this.configWatcher)try{this.configWatcher.close()}catch{}this.httpServer&&this.httpServer.close();try{let t=N(process.cwd(),".agentx/daemon.pid");ge(t)&&ci(t)}catch{}this.log(` Shutdown complete (${Date.now()-e}ms)`),process.exit(0)}midnightTimer;startConfigWatcher(){let e=this.configPath||N(process.cwd(),"agentx.json");if(ge(e))try{this.configWatcher=li(e,{persistent:!1},t=>{t==="change"&&(this.reloadTimer&&clearTimeout(this.reloadTimer),this.reloadTimer=setTimeout(()=>{this.reload().catch(n=>this.log(`[reload] failed: ${n?.message||n}`))},500))}),this.log(` Watching ${e} for config changes`)}catch(t){this.log(` Config watcher failed to start: ${t.message}`)}}async reload(){let e;try{e=Ke(this.configPath)}catch(u){return this.log(`[reload] config invalid, keeping previous: ${u.message}`),{applied:[],restartRequired:[],error:u.message}}let t=[],n=[];if(JSON.stringify(this.config.crons)!==JSON.stringify(e.crons))try{await this.cron.stop(),this.cron=new Ye(e,this.registry,this.hooks,this.log),this.cron.setNotifyCallback(async(u,m,f,b)=>{this.log(`[CRON ALERT] Cron "${u}" failed (${b}x) \u2014 ${f.slice(0,200)}`),this.broadcastSSE("cron-failure",JSON.stringify({jobId:u,agent:m,error:f,consecutiveErrors:b}));let y=e.crons[u];if(y?.notify)try{await this.router.sendOutbound({channel:y.notify.channel,chatId:y.notify.chatId,text:`Cron "${u}" failed (${b}x)
676
+ ${f.slice(0,300)}`,agentId:m,accountId:y.notify.accountId})}catch(w){this.log(`[CRON ALERT] notify send failed: ${w.message}`)}}),await this.cron.start(),t.push("crons")}catch(u){this.log(`[reload] cron reload failed: ${u.message}`)}let s=this.config.channels?.telegram,i=e.channels?.telegram,r=JSON.stringify(s)!==JSON.stringify(i),o=!1;if(r&&i?.enabled){let u=this.router.getChannel("telegram");if(u)try{let m=await u.reloadAccounts(i.accounts,i.policy);this.router.updateConfig(e);let f=[];m.added.length&&f.push(`+${m.added.join(",")}`),m.removed.length&&f.push(`-${m.removed.join(",")}`),m.tokenChanged.length&&f.push(`~${m.tokenChanged.join(",")}`),t.push(f.length?`telegram(${f.join(" ")})`:"telegram"),o=!0}catch(m){this.log(`[reload] telegram hot-reload failed: ${m.message}`)}}else r&&!i?.enabled&&s?.enabled||r&&i?.enabled&&!s?.enabled?o=!1:r||(o=!0);if(JSON.stringify(this.config.providers)!==JSON.stringify(e.providers))try{this.registry.setProviders(e.providers),t.push("providers")}catch(u){this.log(`[reload] providers hot-reload failed: ${u.message}`)}let a=this.config.mesh,c=e.mesh,l=JSON.stringify(a)!==JSON.stringify(c),d=!l;if(l&&c.enabled&&a.enabled&&this.mesh&&!(JSON.stringify(a.healthCheck)!==JSON.stringify(c.healthCheck)))try{let m=await this.mesh.reloadPeers(e),f=[];m.added.length&&f.push(`+${m.added.join(",")}`),m.removed.length&&f.push(`-${m.removed.join(",")}`),m.updated.length&&f.push(`~${m.updated.join(",")}`),t.push(f.length?`mesh(${f.join(" ")})`:"mesh"),d=!0}catch(m){this.log(`[reload] mesh hot-reload failed: ${m.message}`)}if(JSON.stringify(this.config.services)!==JSON.stringify(e.services))try{let u=this.router.getServiceMatcher();if(u){let{count:m}=u.reload(e.services);t.push(`services(${m})`)}else if(Object.keys(e.services).length>0){let m=new he(e.services,this.log);this.router.setServiceMatcher(m),t.push(`services(${Object.keys(e.services).length})`)}}catch(u){this.log(`[reload] services hot-reload failed: ${u.message}`)}try{let u=this.hooks.size?.()??0;this.hooks.clear(),qe(process.cwd(),this.hooks);let m=this.hooks.size?.()??0;u!==m&&t.push(`hooks(${m})`)}catch(u){this.log(`[reload] hooks reload failed: ${u.message}`)}if(JSON.stringify(this.config.business)!==JSON.stringify(e.business)||JSON.stringify(this.config.agents)!==JSON.stringify(e.agents)){try{this.landscape=new ce(e),this.registry.setLandscape(this.landscape),t.push("landscape")}catch(u){this.log(`[reload] landscape rebuild failed: ${u.message}`)}if(this.mesh&&this.mesh.peerCount()>0)try{let u=await this.mesh.refreshAll(),m=u.filter(f=>f.healthy).length;t.push(`mesh.refresh(${m}/${u.length})`)}catch(u){this.log(`[reload] mesh refresh failed: ${u.message}`)}}if(JSON.stringify(this.config.agents)!==JSON.stringify(e.agents)){let u=Object.keys(this.config.agents).sort().join(","),m=Object.keys(e.agents).sort().join(",");u===m&&(this.registry.setConfig(e),t.push("agents.meta"));let f=gi(this.config.agents,e.agents);f.length>0&&n.push(`agents(${f.join(",")})`)}d||n.push("mesh"),JSON.stringify(this.config.node)!==JSON.stringify(e.node)&&n.push("node");let g={...this.config.channels,telegram:void 0},p={...e.channels,telegram:void 0};if(JSON.stringify(g)!==JSON.stringify(p)?n.push("channels"):r&&!o&&n.push("channels.telegram"),JSON.stringify(this.config.webhooks)!==JSON.stringify(e.webhooks))try{this.webhooks.setWebhookEntries(e.webhooks),t.push(`webhooks(${e.webhooks.length})`)}catch(u){this.log(`[reload] webhooks hot-reload failed: ${u.message}`)}return this.config=e,this.router.updateConfig(e),t.length&&this.log(`[reload] applied: ${t.join(", ")}`),n.length?(this.log(`[reload] restart required to apply changes in: ${n.join(", ")}`),this.broadcastSSE("reload-partial",JSON.stringify({applied:t,restartRequired:n}))):t.length&&this.broadcastSSE("reload-complete",JSON.stringify({applied:t})),{applied:t,restartRequired:n}}scheduleMidnightHook(){let e=()=>{let t=new Date,n=t.toLocaleString("en-US",{timeZone:"Africa/Tunis"}),s=new Date(n),i=new Date(s);i.setDate(i.getDate()+1),i.setHours(0,0,5,0);let r=s.getTime()-t.getTime(),o=i.getTime()-r-t.getTime();this.log(` Cost tracking: next run in ${Math.round(o/6e4)}min`),this.midnightTimer=setTimeout(async()=>{await this.runDailyCostReport(),e()},Math.max(o,6e4))};e()}async runDailyCostReport(){let e=new Date(Date.now()-864e5).toISOString().slice(0,10),t=this.registry.getTokenTracker();if(t.hasTokenCostsEntry(e)){this.log(` Cost tracking: ${e} already logged, skipping`);return}let n=t.generateDailyReport(e);if(!n||n.totalTasks===0){this.log(` Cost tracking: no usage for ${e}, skipping`);return}t.appendToTokenCosts(n),this.log(` Cost tracking: ${e} \u2014 ${n.totalTasks} tasks, $${n.totalCost.toFixed(4)} (top: ${n.topAgent} $${n.topCost.toFixed(4)})`)}async startChannels(){if(this.config.channels.telegram.enabled){let e=this.config.channels.telegram.accounts;if(Object.keys(e).length>0){let t=this.config.channels.telegram.policy,n=new J(e,{policy:{allowFrom:t?.allowFrom}},this.log);this.router.addChannel(n);let s=t?.allowFrom?.length??0,i=Object.entries(e).filter(([,r])=>!r.allowFrom&&s===0).length;i>0?this.log(` Telegram: enabled \u2014 WARNING: ${i} account(s) have no allowFrom (global or per-account). All incoming messages will be DROPPED.`):this.log(` Telegram: enabled \u2014 global allowFrom entries: ${s}`)}}if(this.config.channels.whatsapp.enabled){let{setWhatsAppQR:e,setWhatsAppStatus:t}=await import("./whatsapp-state-3ZVQ6MQD.js"),n=new ve({sessionDir:this.config.channels.whatsapp.sessionDir,defaultAgent:this.config.channels.whatsapp.defaultAgent,allowFrom:this.config.channels.whatsapp.allowFrom,routes:this.config.channels.whatsapp.routes,throttle:{minMsBetweenCalls:this.config.channels.whatsapp.ingest?.throttle?.minMsBetweenCalls,maxCallsPerMinute:this.config.channels.whatsapp.ingest?.throttle?.maxCallsPerMinute},onQR:e,onStatus:t},this.log);this.router.addChannel(n),this.log(` WhatsApp: enabled (${this.config.channels.whatsapp.routes.length} routes)`)}if(this.config.channels.discord?.enabled&&this.config.channels.discord.token){let e=new $e({token:this.config.channels.discord.token,agentBinding:this.config.channels.discord.agentBinding},this.log);this.router.addChannel(e),this.log(" Discord: enabled")}if(this.config.channels.slack?.enabled&&this.config.channels.slack.botToken&&this.config.channels.slack.appToken){let e=new Se({botToken:this.config.channels.slack.botToken,appToken:this.config.channels.slack.appToken,agentBinding:this.config.channels.slack.agentBinding},this.log);this.router.addChannel(e),this.log(" Slack: enabled")}else this.config.channels.slack?.enabled&&this.log(" Slack: enabled in config but missing botToken/appToken \u2014 skipped");if(this.config.channels.gitlab?.enabled&&this.config.channels.gitlab.token){let e=new Te({webhookPort:this.config.channels.gitlab.webhookPort,webhookSecret:this.config.channels.gitlab.webhookSecret,host:this.config.channels.gitlab.host,token:this.config.channels.gitlab.token,routes:this.config.channels.gitlab.routes,agentMappings:this.config.channels.gitlab.agentMappings,knownAgentIds:Object.keys(this.config.agents)},this.log,this.hooks);this.mesh&&(e.setReactForwarder(async(t,n,s,i,r,o)=>{let a=this.mesh.directory().find(l=>l.peer===t&&l.healthy);if(!a){this.log(`[gitlab] react forward: peer "${t}" not found or unhealthy`);return}let c=`${a.peerUrl}/gitlab/react`;try{let l=await fetch(c,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({project:n,noteableType:s,noteableIid:i,noteId:r,agentId:o})}),d=await l.text().catch(()=>"");this.log(`[gitlab] react forward -> ${c} : ${l.status} ${d.slice(0,200)}`)}catch(l){this.log(`[gitlab] react forward FAILED -> ${c} : ${l.message}`)}}),e.setSendNoteForwarder(async(t,n,s,i,r,o)=>{let a=this.mesh.directory().find(l=>l.peer===t&&l.healthy);if(!a)return this.log(`[gitlab] send-note forward: peer "${t}" not found or unhealthy`),"";let c=`${a.peerUrl}/gitlab/send-note`;try{let l=await fetch(c,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({project:n,noteableType:s,noteableIid:i,agentId:r,text:o})}),d=await l.json().catch(()=>({}));return this.log(`[gitlab] send-note forward -> ${c} : ${l.status} noteId=${d.noteId||"?"}`),d.noteId||""}catch(l){return this.log(`[gitlab] send-note forward FAILED -> ${c} : ${l.message}`),""}}),e.setLogTimeForwarder(async(t,n,s,i,r,o)=>{let a=this.mesh.directory().find(l=>l.peer===t&&l.healthy);if(!a){this.log(`[gitlab] log-time forward: peer "${t}" not found or unhealthy`);return}let c=`${a.peerUrl}/gitlab/log-time`;try{let l=await fetch(c,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({project:n,noteableType:s,noteableIid:i,agentId:r,durationMs:o})}),d=await l.text().catch(()=>"");this.log(`[gitlab] log-time forward -> ${c} : ${l.status} ${d.slice(0,160)}`)}catch(l){this.log(`[gitlab] log-time forward FAILED -> ${c} : ${l.message}`)}})),this.router.addChannel(e),this.log(` GitLab: enabled (${this.config.channels.gitlab.routes.length} project routes, webhook :${this.config.channels.gitlab.webhookPort})`)}if(this.config.channels.github?.enabled){let e=this.config.channels.github;this.github=new Ie({token:e.token,tokenFile:e.tokenFile,appId:e.appId,clientId:e.clientId,privateKeyFile:e.privateKeyFile,webhookSecret:e.webhookSecret,routes:e.routes,agentMappings:e.agentMappings},this.log),this.mesh&&this.github.setSendCommentForwarder(async(t,n,s,i,r)=>{let o=this.mesh.directory().find(c=>c.peer===t&&c.healthy);if(!o)return this.log(`[github] send-comment forward: peer "${t}" not found or unhealthy`),"";let a=`${o.peerUrl}/github/send-comment`;try{let c=await fetch(a,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({repo:n,issueNumber:s,agentId:i,text:r})}),l=await c.json().catch(()=>({}));return this.log(`[github] send-comment forward -> ${a} : ${c.status} commentId=${l.commentId||"?"}`),l.commentId||""}catch(c){return this.log(`[github] send-comment forward FAILED -> ${a} : ${c.message}`),""}}),this.router.addChannel(this.github),this.log(` GitHub: enabled (${e.routes.length} repo routes)`)}if(this.config.channels.webrtc?.enabled){let e=this.config.channels.webrtc;if(this.webrtc=new xe(this.config.node.name,e.allowedCallers,this.log),this.mesh&&this.webrtc.setForwarder(async(t,n)=>{try{return await this.mesh.sendSignal(t,n)}catch(s){return this.log(`[webrtc] forward to "${t}" failed: ${s.message}`),!1}}),e.ringNotify.length>0){let t=e.callUrlBase||`http://${this.config.node.bind}`;this.webrtc.setRingHandler(async n=>{let s=`${t}/call?to=${encodeURIComponent(n.from)}&callId=${encodeURIComponent(n.callId)}`,i=`\u{1F4DE} ${n.from} is calling \u2014 tap to join: ${s}`;for(let r of e.ringNotify)try{await this.router.sendOutbound({channel:r.channel,chatId:r.chatId,text:i,parseMode:"plain",...r.accountId?{accountId:r.accountId}:{}})}catch(o){this.log(`[webrtc] ring notify via ${r.channel}:${r.chatId} failed: ${o.message}`)}})}if(e.bot.enabled){let t=e.bot,n=[...e.stunServers.map(s=>({urls:s})),...e.turnServers];this.botManager=new Pe({broker:this.webrtc,iceServers:n,whisperBackend:t.whisperBackend,whisperModel:t.whisperModel,whisperLanguage:t.whisperLanguage,mlxBinary:t.mlxBinary,maxCallMinutes:t.maxCallMinutes,log:this.log,onTranscript:async({invite:s,text:i,durationMs:r})=>{let o=t.transcriptChannel;if(!o)return;let a=`[${new Date().toLocaleTimeString()} \u2022 ${(r/1e3).toFixed(1)}s \u2022 ${s.target}]`;try{await this.router.sendOutbound({channel:o.channel,chatId:o.chatId,text:`${a}
677
+ ${i}`,parseMode:"plain",...o.accountId?{accountId:o.accountId}:{}})}catch(c){this.log(`[bot-manager] transcript send via ${o.channel}:${o.chatId} failed: ${c.message}`)}}}),this.log(` WebRTC bot: enabled (whisper=${t.whisperBackend}, default-agent=${t.defaultAgentId||"(none)"}, transcript=${t.transcriptChannel?`${t.transcriptChannel.channel}:${t.transcriptChannel.chatId}`:"(none)"})`)}this.log(` WebRTC signaling: enabled (stun=${e.stunServers.length}, turn=${e.turnServers.length}, allowedCallers=${e.allowedCallers.length||"all"}, ringNotify=${e.ringNotify.length}, bot=${e.bot.enabled?"on":"off"})`)}await this.bootWorkflowEngine(),await this.router.startAll()}async bootWorkflowEngine(){let e=this.config.workflows;if(!e?.enabled){this.log(" Workflows: disabled (set workflows.enabled to turn the engine on)");return}let t=new Ct({baseDir:N(process.cwd(),e.dir)}),n=new Mt({baseDir:N(process.cwd(),e.dir),nodeId:this.config.node.id}),s={};for(let $ of this.router.getChannelNames()){let I=this.router.getChannel($);I&&(s[$]=I)}let i={execute:async $=>{let I=Date.now();try{let A=await this.registry.execute({agentId:$.agentId,message:$.message,workflowRunId:$.workflowRunId,timeoutMinutes:$.timeoutMinutes});return{content:A.content??"",error:A.error,taskId:`wf-${$.workflowRunId??"na"}-${I.toString(36)}`,durationMs:Date.now()-I}}catch(A){return{content:"",error:A.message}}}},r=this.mesh?{forwardTransition:async($,I)=>{let A=this.mesh.directory().find(x=>x.peer===$&&x.healthy);if(!A){this.log(`[workflows] forward to "${$}" skipped \u2014 peer not found or unhealthy`);return}let C=`${A.peerUrl}/workflow/event`;try{let x=await fetch(C,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({payload:I})});x.ok||this.log(`[workflows] forward ${C} -> ${x.status}`)}catch(x){this.log(`[workflows] forward ${C} failed: ${x.message}`)}},broadcastTrigger:async $=>{let I=this.mesh.directory().filter(C=>C.healthy);if(I.length===0)return;let A=this.config.node.id;this.log(`[workflows/mesh] broadcasting trigger source="${$.trigger.source}" chat="${$.trigger.chat??"*"}" -> ${I.length} peer(s): ${I.map(C=>C.peer).join(", ")}`),await Promise.allSettled(I.map(async C=>{let x=`${C.peerUrl}/workflow/event`;try{let _=await fetch(x,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({kind:"trigger",fromPeer:A,payload:$}),signal:AbortSignal.timeout(5e3)});_.ok||this.log(`[workflows/mesh] broadcastTrigger ${C.peer} -> ${_.status}`)}catch(_){this.log(`[workflows/mesh] broadcastTrigger ${C.peer} failed: ${_.message}`)}}))},forwardChannelSend:async $=>{let I=this.mesh.directory().filter(O=>O.healthy&&O.channels?.includes($.channel));if(I.length===0)throw new Error(`no healthy mesh peer hosts channel "${$.channel}"`);let A=I[0],C=`${A.peerUrl}/channel/send`,x=await fetch(C,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify($),signal:AbortSignal.timeout(15e3)});if(!x.ok){let O=await x.text().catch(()=>"");throw new Error(`peer ${A.peer} /channel/send -> ${x.status} ${O.slice(0,200)}`)}return{messageId:(await x.json().catch(()=>({}))).messageId??null}}}:void 0,{ActorStore:o}=await import("./store-4UCO56K2.js"),{TaskStore:a}=await import("./task-store-3NUWLYRY.js"),c=new o,l=new a(e.dir?{baseDir:N(process.cwd(),e.dir)}:void 0),d=process.env.AGENTX_INBOX_BASE_URL||"",g=[],p=s.telegram;if(p?.sendMessage){let{createTelegramTaskRenderer:$}=await import("./telegram-UWV67KWG.js");g.push($({actors:c,tasks:l,adapter:{sendMessage:p.sendMessage.bind(p),sendWithInlineButtons:p.sendWithInlineButtons?.bind(p)},inboxBaseUrl:d,log:I=>this.log(I)}))}let u=s.whatsapp;if(u?.send){let{createWhatsappTaskRenderer:$}=await import("./whatsapp-KJ2NRKKQ.js");g.push($({actors:c,tasks:l,adapter:{send:u.send.bind(u)},inboxBaseUrl:d,log:I=>this.log(I)}))}let m=s.slack;if(m?.send){let{createSlackTaskRenderer:$}=await import("./slack-D27BE37Q.js");g.push($({actors:c,tasks:l,adapter:{send:m.send.bind(m)},inboxBaseUrl:d,log:I=>this.log(I)}))}let f=g.length?async $=>{for(let I of g)try{await I($)}catch{}}:void 0,{TimerService:b}=await import("./timers-QB4KYNAE.js"),y=new b({baseDir:e.dir?N(process.cwd(),e.dir):void 0,log:$=>this.log($)}),w=new Rt({store:t,runs:n,nodeId:this.config.node.id,channels:s,agents:i,forwarder:r,actors:c,tasks:l,timers:y,renderUserTask:f,log:$=>this.log($)});y.start();let v=Pt(w);for(let[$,I]of Object.entries(v))I&&this.hooks.registerHandler($,`workflows:${$}`,I,50);this.workflowDispatcher=w,this.workflowStore=t,this.workflowRuns=n,this.webhooks.setWorkflowDispatcher(w);let{cronTimers:k,hookSubscribers:S}=_t({store:t,dispatcher:w,hooks:this.hooks,log:$=>this.log($)}),T=t.list().length;this.log(` Workflows: enabled (${T} definition${T===1?"":"s"} loaded from ${e.dir}, editor=${e.editor}, cron=${k}, hook=${S})`)}async startHttpApi(){let[e,t]=this.config.node.bind.split(":"),n=parseInt(t||"18800",10);this.httpServer=ai(async(s,i)=>{if(i.setHeader("Access-Control-Allow-Origin","*"),i.setHeader("Access-Control-Allow-Methods","GET, POST, OPTIONS"),i.setHeader("Access-Control-Allow-Headers","Content-Type, Authorization"),s.method==="OPTIONS"){i.writeHead(204),i.end();return}await this.handleHttp(s,i)}),this.httpServer.on("error",s=>{s.code==="EADDRINUSE"?(this.log(` ERROR: Port ${n} is already in use. Retrying in 5s...`),setTimeout(()=>{this.httpServer?.close(),this.httpServer?.listen(n,e||"0.0.0.0")},5e3)):this.log(` HTTP error: ${s.message}`)}),this.httpServer.listen(n,e||"0.0.0.0",()=>{this.log(` HTTP API: http://${e||"0.0.0.0"}:${n}`)})}broadcastSSE(e,t){if(this.sseClients.size===0)return;let n=`event: ${e}
678
+ data: ${JSON.stringify({time:new Date().toISOString(),message:t})}
679
+
680
+ `;for(let s of this.sseClients)try{s.write(n)}catch{this.sseClients.delete(s)}}handleSSE(e,t){t.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache",Connection:"keep-alive","Access-Control-Allow-Origin":"*"});let n=this.registry.list(),s=n.filter(i=>i.active>0);t.write(`event: status
681
+ data: ${JSON.stringify({node:this.config.node.name,agents:n.length,active:s.map(i=>({id:i.id,name:i.name,tasks:i.active})),mesh:this.mesh?.directory().map(i=>({peer:i.peer,healthy:i.healthy}))||[]})}
682
+
683
+ `),this.sseClients.add(t),e.on("close",()=>this.sseClients.delete(t))}handleTaskStream(e,t,n,s){t.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache",Connection:"keep-alive","Access-Control-Allow-Origin":"*","X-Accel-Buffering":"no"});let i=!1,r=(c,l)=>{if(!i)try{t.write(`event: ${c}
684
+ data: ${JSON.stringify(l)}
685
+
686
+ `)}catch{i=!0}},o=this.registry.subscribeToTaskOutput(s,c=>r("chunk",{text:c}));if(!o){r("error",{message:"task not found or already evicted"}),t.end();return}if(r("start",{taskId:s,initial:o.initial,done:o.done}),o.done){r("end",{reason:"already finished"}),t.end();return}let a=setInterval(()=>{if(!i)try{t.write(`: ping
687
+
688
+ `)}catch{}},15e3);e.on("close",()=>{i=!0,clearInterval(a),o.unsubscribe()})}handleWebRtcSSE(e,t,n){if(!this.webrtc){this.json(t,404,{error:"WebRTC signaling not enabled"});return}let s=n.searchParams.get("callId"),i=n.searchParams.get("as");if(!s||!i){this.json(t,400,{error:"Missing ?callId= and ?as="});return}t.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache",Connection:"keep-alive","Access-Control-Allow-Origin":"*","X-Accel-Buffering":"no"}),t.write(`event: ready
689
+ data: ${JSON.stringify({callId:s,as:i})}
690
+
691
+ `);let r=this.webrtc.subscribe(s,i,t),o=setInterval(()=>{try{t.write(`: ping
692
+
693
+ `)}catch{}},15e3);e.on("close",()=>{clearInterval(o),r()})}serveCallPage(e){e.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),e.end(hn)}async handleHttp(e,t){let n=new URL(e.url||"/",`http://${e.headers.host||"localhost"}`),s=n.pathname;try{if(e.method==="GET"&&s==="/events"){this.handleSSE(e,t);return}if(e.method==="GET"&&s==="/webrtc/events"){this.handleWebRtcSSE(e,t,n);return}if(e.method==="POST"&&(s==="/workflow/event"||s==="/workflow/transition")){await this.handleWorkflowEvent(e,t);return}if(e.method==="POST"&&s==="/channel/send"){await this.handleChannelSend(e,t);return}let i=e.method==="GET"&&s.match(/^\/channels\/([^/]+)\/chats$/);if(i){let d=n.searchParams.get("local")==="1";await this.handleChannelChats(t,decodeURIComponent(i[1]),{localOnly:d});return}let r=e.method==="POST"&&s.match(/^\/workflows\/([^/]+)\/run$/);if(r){await this.handleWorkflowManualRun(e,t,decodeURIComponent(r[1]));return}if(s.startsWith("/api/memory")&&await this.handleMemoryApi(e,t,s,n)||s.startsWith("/api/workflows/tasks")&&this.workflowDispatcher&&await this.handleTaskApi(e,t,s))return;if(e.method==="GET"&&s==="/api/workflows/kpis"&&this.workflowDispatcher){let{computeKpis:d}=await import("./task-store-3NUWLYRY.js");this.json(t,200,d(this.workflowDispatcher.tasks));return}if(e.method==="GET"&&this.workflowRuns&&this.workflowStore){if(s==="/api/workflows"){this.json(t,200,{workflows:this.workflowStore.list()});return}if(s==="/api/workflows/runs"){let g=Math.max(1,Math.min(500,Number(n.searchParams.get("limit")||50))),p=n.searchParams.get("workflowId")||void 0;this.json(t,200,{runs:this.workflowRuns.list({workflowId:p,limit:g})});return}let d=s.match(/^\/api\/workflows\/runs\/([^\/]+)$/);if(d){let g=this.workflowRuns.get(decodeURIComponent(d[1]));if(!g){this.json(t,404,{error:"run not found"});return}this.json(t,200,{run:g});return}}if(e.method==="POST"&&s==="/api/workflows/editor/chat"&&this.workflowDispatcher){let d;try{d=await X(e)}catch(v){this.json(t,400,{error:"invalid JSON body",message:v.message});return}let g=Array.isArray(d?.messages)?d.messages:[];if(!g.length){this.json(t,400,{error:"messages array required"});return}let p=g.filter(v=>v&&(v.role==="user"||v.role==="assistant")&&typeof v.content=="string").map(v=>({role:v.role,content:v.content}));if(!p.length){this.json(t,400,{error:"messages must contain at least one {role: 'user'|'assistant', content}"});return}let u=typeof d?.agentId=="string"&&d.agentId?d.agentId:process.env.AGENTX_WORKFLOW_AUTHOR_AGENT||this.registry.list()[0]?.id;if(!u){this.json(t,503,{error:"no authoring agent available \u2014 register an agent or set AGENTX_WORKFLOW_AUTHOR_AGENT"});return}let{buildWorkflowAuthorPrompt:m,extractWorkflowJson:f}=await import("./editor-chat-FUBQLMAQ.js"),b=Object.keys(this.workflowDispatcher.channels).sort(),y=this.registry.list().map(v=>({id:v.id,description:v.name})),w=m({messages:p,store:this.workflowStore,actors:this.workflowDispatcher.actors,availableAgents:y,availableChannels:b,currentWorkflow:d?.currentWorkflow});try{let v=await this.registry.execute({agentId:u,message:w,context:{channel:"workflow-editor",chatId:"editor",sender:"editor"}});if(v.error){this.json(t,502,{error:v.error,agentId:u});return}let k=v.content??"",S=f(k);this.json(t,200,{reply:k,workflow:S,agentId:u})}catch(v){this.json(t,500,{error:"agent execute failed",message:v.message})}return}if(e.method==="POST"&&s.startsWith("/api/workflows/signal/")&&this.workflowDispatcher){let d=decodeURIComponent(s.replace(/^\/api\/workflows\/signal\//,""));if(!d){this.json(t,400,{error:"signal name required"});return}let g;try{g=await X(e)}catch(u){this.json(t,400,{error:"invalid JSON body",message:u.message});return}let p=this.workflowDispatcher.emitSignal({name:d,scope:g?.scope,workflowId:g?.workflowId,payload:g?.payload});this.json(t,200,{ok:!0,emission:p});return}if(e.method==="GET"&&s==="/inbox"){let{renderInboxPage:d}=await import("./inbox-O5UB3YQN.js"),g=n.searchParams,p=d({actor:g.get("actor")||void 0});t.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),t.end(p);return}if(e.method==="GET"&&s==="/processes"){let{renderProcessesPage:d}=await import("./processes-7W27G3J3.js");t.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),t.end(d({}));return}let o=e.method==="GET"&&s.match(/^\/t\/([^\/]+)\/(primary|secondary)$/);if(o&&this.workflowDispatcher){let d=decodeURIComponent(o[1]),g=o[2],p=n.searchParams.get("actor")||"anonymous",u=await this.workflowDispatcher.submitTask(d,{action:g,values:{}},p);t.writeHead(u.ok?200:400,{"Content-Type":"text/html; charset=utf-8"}),u.ok?t.end(`<!doctype html><meta charset=utf-8><title>Submitted</title>
694
+ <body style="font-family:system-ui;max-width:520px;margin:48px auto;padding:0 16px;text-align:center">
695
+ <h1 style="color:#27ae60">\u2713 Submitted</h1>
696
+ <p>Your <strong>${g==="primary"?"approval":"rejection"}</strong> has been recorded. You can close this tab.</p>
697
+ <p style="color:#888;font-size:13px">Run <code>${u.runId}</code></p>
698
+ </body>`):t.end(`<!doctype html><meta charset=utf-8><title>Submit failed</title>
699
+ <body style="font-family:system-ui;max-width:520px;margin:48px auto;padding:0 16px;text-align:center">
700
+ <h1 style="color:#c0392b">\u2717 ${u.error}</h1>
701
+ ${Array.isArray(u.fieldErrors)&&u.fieldErrors.length?`<p>This task has required fields. Please open the <a href="/inbox?actor=${encodeURIComponent(p)}">inbox</a> instead.</p>`:""}
702
+ </body>`);return}if(e.method==="GET"&&s==="/call"){this.serveCallPage(t);return}if(e.method==="POST"&&s.startsWith("/webhook/")){if(this.github&&e.headers["x-github-event"]){let d=await new Promise(p=>{let u="";e.on("data",m=>u+=m.toString()),e.on("end",()=>p(u)),e.on("error",()=>p(""))}),g;try{(e.headers["content-type"]||"").includes("form-urlencoded")&&d.startsWith("payload=")?g=JSON.parse(decodeURIComponent(d.slice(8))):g=d?JSON.parse(d):{}}catch{g={}}this.log(`[github] webhook body keys: ${Object.keys(g).slice(0,5).join(", ")} | repo: ${g.repository?.full_name||"MISSING"}`),t.writeHead(202,{"Content-Type":"application/json"}),t.end(JSON.stringify({ok:!0,channel:"github",status:"accepted"})),this.github.handleWebhook(e.headers,g,d).catch(p=>this.log(`[github] webhook handler error: ${p.message}`));return}await this.webhooks.handle(e,t,s);return}let a=e.method==="GET"&&s.match(/^\/agents\/([^/]+)\/tasks\/([^/]+)\/stream$/);if(a){this.handleTaskStream(e,t,a[1],a[2]);return}let c=e.method==="GET"&&s.match(/^\/agents\/([^/]+)\/tasks$/);if(c){let d=Math.max(1,Math.min(500,parseInt(n.searchParams.get("limit")||"50",10)||50));this.json(t,200,this.registry.listTaskHistory(c[1],d));return}let l=e.method==="GET"&&s.match(/^\/agents\/([^/]+)\/tasks\/([^/]+)$/);if(l){let d=this.registry.getTaskRecord(l[1],l[2]);if(!d){this.json(t,404,{error:"not found"});return}this.json(t,200,d);return}if(e.method==="POST"&&(s==="/v1/chat/completions"||s.match(/^\/llm\/[^/]+\/v1\/chat\/completions$/))){await this.handleOpenAICompat(e,t,s);return}if(this.business&&s.startsWith("/business")&&await this.business.handleHttp(`${e.method} ${s}`,e,t))return;switch(`${e.method} ${s}`){case"GET /health":this.json(t,200,{status:"ok",node:this.config.node,uptime:process.uptime(),agents:this.registry.list(),crons:this.cron.list().map(d=>({id:d.id,enabled:d.enabled,nextRun:d.nextRun})),mesh:this.mesh?.directory()||[],usage:this.registry.getTodayUsage()});break;case"GET /usage":this.json(t,200,this.registry.getUsage(7));break;case"GET /agents":this.json(t,200,this.registry.list());break;case"GET /whatsapp/state":{let{getWhatsAppState:d}=await import("./whatsapp-state-3ZVQ6MQD.js");this.json(t,200,d());break}case"GET /whatsapp/qr.svg":{let{getWhatsAppState:d}=await import("./whatsapp-state-3ZVQ6MQD.js"),g=d();if(!g.qr){t.writeHead(204,{"Cache-Control":"no-store"}),t.end();break}try{let p=await import("qrcode"),m=await(p.default||p).toString(g.qr,{type:"svg",errorCorrectionLevel:"L",margin:1,color:{dark:"#000",light:"#fff"}});t.writeHead(200,{"Content-Type":"image/svg+xml; charset=utf-8","Cache-Control":"no-store"}),t.end(m)}catch(p){t.writeHead(503,{"Content-Type":"text/plain; charset=utf-8"}),t.end("qrcode package not installed or failed: "+(p?.message||"unknown"))}break}case"GET /whatsapp/chats":{let d=this.router.getChannel("whatsapp");if(!d){this.json(t,503,{error:"WhatsApp channel not enabled"});break}this.json(t,200,{chats:d.listChats()});break}case"GET /whatsapp/contacts":{let d=this.router.getChannel("whatsapp");if(!d){this.json(t,503,{error:"WhatsApp channel not enabled"});break}this.json(t,200,{contacts:d.listContacts()});break}case"POST /whatsapp/ingest":{let d=this.router.getChannel("whatsapp");if(!d){this.json(t,503,{error:"WhatsApp channel not enabled"});break}let g=await R(e),p=this.config.channels.whatsapp.ingest;if(!p.enabled&&!g.force){this.json(t,400,{error:'channels.whatsapp.ingest.enabled is false. Set it to true in agentx.json or pass {"force": true}.'});break}let u=typeof g.agent=="string"&&g.agent||this.config.channels.whatsapp.defaultAgent||this.config.node.defaultAgent;if(!u){this.json(t,400,{error:"No agent to own the entries. Pass {agent: '...'} or set channels.whatsapp.defaultAgent."});break}let m=!!g.dryRun,{runSweep:f}=await import("./ingest-whatsapp-C3O5UPBX.js"),b=this.registry.getWikiHub().getAgentWiki(u),y=p;if(typeof g.onlyJid=="string"){let v=g.onlyJid,k=v.endsWith("@g.us"),S=g.forceMode==="metadata-only"||g.forceMode==="messages"?g.forceMode:void 0;y={...p,enabled:!0,mode:S??p.mode,allowContacts:k||g.onlyKind==="group"?[]:[v],allowGroups:k||g.onlyKind==="group"?[v]:[],denyContacts:[],denyGroups:[]}}let w=await f({source:d,store:b,config:y,agentId:u,dryRun:m});this.json(t,200,w);break}case"GET /crons":this.json(t,200,this.cron.list());break;case"GET /crons/health":this.json(t,200,this.cron.health());break;case"GET /mesh":this.json(t,200,this.mesh?.directory()||[]);break;case"GET /debug":{let{getDebugState:d,getDebugLogs:g}=await import("./debug-TGAP7E7W.js");this.json(t,200,{...d(),recentLogs:g(50)});break}case"POST /debug/on":{let{setDebug:d}=await import("./debug-TGAP7E7W.js"),g=n.searchParams.get("categories")?.split(",")||["all"];d(!0,g),this.log(`Debug enabled: ${g.join(", ")}`),this.json(t,200,{enabled:!0,categories:g});break}case"POST /debug/off":{let{setDebug:d}=await import("./debug-TGAP7E7W.js");d(!1),this.log("Debug disabled"),this.json(t,200,{enabled:!1});break}case"POST /gitlab/react":{let d=await R(e),{project:g,noteableType:p,noteableIid:u,noteId:m,agentId:f}=d,y=(this.config.channels.gitlab?.agentMappings||[]).find(T=>T.agentId===f),w=y?.token||this.config.channels.gitlab?.token,v=this.config.channels.gitlab?.host;if(this.log(`[gitlab/react] agent="${f}" project="${g}" mappingFound=${!!y} mappingToken=${!!y?.token} globalToken=${!!this.config.channels.gitlab?.token}(len=${(this.config.channels.gitlab?.token||"").length}) host=${v||"MISSING"}`),!w||!v){this.json(t,404,{error:"no gitlab token for agent",debug:{agentId:f,mappingFound:!!y,hasMappingToken:!!y?.token,hasGlobalToken:!!this.config.channels.gitlab?.token,hasHost:!!v}});break}let k=encodeURIComponent(g),S=p==="issue"?`${v}/api/v4/projects/${k}/issues/${u}/notes/${m}/award_emoji`:`${v}/api/v4/projects/${k}/merge_requests/${u}/notes/${m}/award_emoji`;try{let T=await fetch(S,{method:"POST",headers:{"Content-Type":"application/json","PRIVATE-TOKEN":w},body:JSON.stringify({name:"eyes"})}),$=await T.text().catch(()=>"");this.log(`[gitlab/react] -> POST ${S} : ${T.status} ${$.slice(0,120)}`),this.json(t,200,{ok:T.ok,status:T.status,gitlabResponse:$.slice(0,200)})}catch(T){this.log(`[gitlab/react] FETCH ERROR: ${T.message}`),this.json(t,500,{error:T.message})}break}case"POST /gitlab/send-note":{let d=await R(e),{project:g,noteableType:p,noteableIid:u,agentId:m,text:f}=d,w=(this.config.channels.gitlab?.agentMappings||[]).find(T=>T.agentId===m)?.token||this.config.channels.gitlab?.token,v=this.config.channels.gitlab?.host;if(!w||!v){this.json(t,404,{error:"no gitlab token for agent",debug:{agentId:m,hasToken:!!w,hasHost:!!v}});break}let k=encodeURIComponent(g),S=p==="issue"?`${v}/api/v4/projects/${k}/issues/${u}/notes`:`${v}/api/v4/projects/${k}/merge_requests/${u}/notes`;try{let T=await fetch(S,{method:"POST",headers:{"Content-Type":"application/json","PRIVATE-TOKEN":w},body:JSON.stringify({body:f})}),$=await T.text().catch(()=>""),I="";try{I=String(JSON.parse($).id||"")}catch{}this.log(`[gitlab/send-note] agent="${m}" -> POST ${S} : ${T.status} noteId=${I||"?"}`),this.json(t,200,{ok:T.ok,status:T.status,noteId:I})}catch(T){this.log(`[gitlab/send-note] FETCH ERROR: ${T.message}`),this.json(t,500,{error:T.message})}break}case"POST /gitlab/log-time":{let d=await R(e),{project:g,noteableType:p,noteableIid:u,agentId:m,durationMs:f}=d,w=(this.config.channels.gitlab?.agentMappings||[]).find(x=>x.agentId===m)?.token||this.config.channels.gitlab?.token,v=this.config.channels.gitlab?.host;if(!w||!v){this.json(t,404,{error:"no gitlab token for agent"});break}let k=Math.max(60,Math.round(Number(f)/1e3)),S=Math.floor(k/3600),T=Math.ceil(k%3600/60),$=S>0?`${S}h${T>0?` ${T}m`:""}`:`${T}m`,I=encodeURIComponent(g),C=`${v}/api/v4/projects/${I}/${p==="merge_request"?"merge_requests":"issues"}/${u}/add_spent_time`;try{let x=await fetch(C,{method:"POST",headers:{"Content-Type":"application/json","PRIVATE-TOKEN":w},body:JSON.stringify({duration:$})});this.log(`[gitlab/log-time] agent="${m}" duration=${$} -> POST ${C} : ${x.status}`),this.json(t,200,{ok:x.ok,status:x.status,duration:$})}catch(x){this.log(`[gitlab/log-time] FETCH ERROR: ${x.message}`),this.json(t,500,{error:x.message})}break}case"POST /github/send-comment":{let d=await R(e),{repo:g,issueNumber:p,agentId:u,text:m}=d,f=this.config.channels.github,y=(f?.agentMappings||[]).find(k=>k.agentId===u),w;if(y?.tokenFile)try{w=Je(y.tokenFile,"utf-8").trim().split(`
703
+ `)[0].trim()}catch{}if(w=w||y?.token,!w&&f?.tokenFile)try{w=Je(f.tokenFile,"utf-8").trim().split(`
704
+ `)[0].trim()}catch{}if(w=w||f?.token,!w){this.json(t,404,{error:"no github token for agent",debug:{agentId:u,hasMapping:!!y}});break}let v=`https://api.github.com/repos/${g}/issues/${p}/comments`;try{let k=await fetch(v,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${w}`,"User-Agent":"AgentX"},body:JSON.stringify({body:m})}),S=await k.text().catch(()=>""),T="";try{T=String(JSON.parse(S).id||"")}catch{}this.log(`[github/send-comment] agent="${u}" -> POST ${v} : ${k.status} commentId=${T||"?"}`),this.json(t,200,{ok:k.ok,status:k.status,commentId:T})}catch(k){this.log(`[github/send-comment] FETCH ERROR: ${k.message}`),this.json(t,500,{error:k.message})}break}case"POST /github/react":{let d=await R(e),{repo:g,commentId:p,agentId:u}=d,m=this.config.channels.github,b=(m?.agentMappings||[]).find(v=>v.agentId===u),y;if(b?.tokenFile)try{y=Je(b.tokenFile,"utf-8").trim().split(`
705
+ `)[0].trim()}catch{}if(y=y||b?.token,!y&&m?.tokenFile)try{y=Je(m.tokenFile,"utf-8").trim().split(`
706
+ `)[0].trim()}catch{}if(y=y||m?.token,!y){this.json(t,404,{error:"no github token for agent"});break}let w=`https://api.github.com/repos/${g}/issues/comments/${p}/reactions`;try{let v=await fetch(w,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${y}`,"User-Agent":"AgentX"},body:JSON.stringify({content:"eyes"})});this.log(`[github/react] agent="${u}" -> POST ${w} : ${v.status}`),this.json(t,200,{ok:v.ok,status:v.status})}catch(v){this.log(`[github/react] FETCH ERROR: ${v.message}`),this.json(t,500,{error:v.message})}break}case"POST /reload":{try{let d=await this.reload();this.json(t,200,{ok:!0,...d})}catch(d){this.json(t,500,{ok:!1,error:d?.message||String(d)})}break}case"POST /recall":{let d=await R(e);if(!d.agent){this.json(t,400,{error:"Required: agent (id of the agent whose sessions to recall)"});break}try{let g=this.registry.getSessionStore().recallTurns({agentId:String(d.agent),channel:d.channel?String(d.channel):void 0,chatId:d.chatId?String(d.chatId):void 0,before:d.before?String(d.before):void 0,after:d.after?String(d.after):void 0,lookbackDays:typeof d.lookbackDays=="number"?d.lookbackDays:void 0,limit:typeof d.limit=="number"?d.limit:void 0,query:d.query?String(d.query):void 0,participants:Array.isArray(d.participants)?d.participants.map(p=>String(p)):void 0});this.json(t,200,g)}catch(g){this.json(t,500,{error:g?.message||String(g)})}break}case"POST /send":{let d=await R(e);if(!d.channel||!d.chatId||!d.text){this.json(t,400,{error:"Required: channel, chatId, text"});return}try{let g=await this.router.sendOutbound({channel:d.channel,chatId:d.chatId,text:d.text,replyTo:d.replyTo,parseMode:d.parseMode,agentId:d.agentId,accountId:d.accountId});this.json(t,200,{ok:!0,messageId:g||null})}catch(g){this.json(t,400,{error:g.message})}break}case"GET /channels":this.json(t,200,this.router.getChannelNames());break;case"GET /services":this.json(t,200,this.router.serviceMatcher?.list()||[]);break;case"POST /task":{let d=await R(e),g=d.agent||this.config.node.defaultAgent;if(!g||!d.message){this.json(t,400,{error:"Missing: message (and no defaultAgent configured)"});return}let p=d.contextStrategy==="layered"||d.contextStrategy==="planner"?d.contextStrategy:void 0,u=await this.registry.execute({agentId:g,message:d.message,context:d.context,contextStrategy:p},()=>{});this.json(t,u.error?500:200,u);break}case"POST /ask":case"GET /ask":{let d=new URL(e.url||"/",`http://${e.headers.host||"localhost"}`),g=d.searchParams.get("q")||d.searchParams.get("message")||"";if(e.method==="POST"){let b=await R(e);g=b.message||b.q||g}let p=this.config.node.defaultAgent;if(!p){this.json(t,400,{error:"No defaultAgent configured in node config"});return}if(!g){this.json(t,400,{error:"Missing message (pass as ?q=... or {message:...})"});return}let u=`[VOICE MODE \u2014 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.]
707
+
708
+ ${g}`,m=await this.registry.execute({agentId:p,message:u,context:{channel:"voice",sender:"Siri"}}),f=hi(m.content);this.json(t,m.error?500:200,{text:f,full:m.content,error:m.error,duration:m.duration});break}case"POST /mesh/task":{let d=await R(e);if(!d.peer||!d.message){this.json(t,400,{error:"Missing: peer, message"});return}if(!this.mesh){this.json(t,400,{error:"Mesh not enabled"});return}let g=await this.mesh.sendTask(d.peer,d.message,d.agent);this.json(t,200,{response:g});break}case"POST /webrtc/signal/out":{if(!this.webrtc){this.json(t,404,{error:"WebRTC signaling not enabled"});return}let g=await R(e);if(!g.kind||!g.callId||!g.from||!g.to){this.json(t,400,{error:"Missing: kind, callId, from, to"});return}let p=await this.webrtc.handleOutgoing(g);this.json(t,p.ok?200:400,p);break}case"POST /webrtc/signal":{if(!this.webrtc){this.json(t,404,{error:"WebRTC signaling not enabled"});return}let g=await R(e);if(!g.kind||!g.callId||!g.from||!g.to){this.json(t,400,{error:"Missing: kind, callId, from, to"});return}let p=this.webrtc.handleIncoming(g);this.json(t,p.ok?200:400,p);break}case"POST /webrtc/bot/invite":{if(!this.botManager){this.json(t,404,{error:"WebRTC bot not enabled (channels.webrtc.bot.enabled=false)"});return}let d=await R(e),g=d.callId,p=d.target,u=d.agentId||this.config.channels.webrtc?.bot.defaultAgentId;if(!g||!p||!u){this.json(t,400,{error:"Missing: callId, target, agentId (or defaultAgentId in config)"});return}let m=await this.botManager.invite({callId:g,target:p,agentId:u});this.json(t,m.ok?200:500,m);break}case"GET /webrtc/bots":{if(!this.botManager){this.json(t,404,{error:"WebRTC bot not enabled"});return}this.json(t,200,{active:this.botManager.active()});break}case"GET /webrtc/config":{let d=this.config.channels.webrtc;if(!d?.enabled){this.json(t,404,{error:"WebRTC signaling not enabled"});return}this.json(t,200,{localName:this.config.node.name,iceServers:[...d.stunServers.map(g=>({urls:g})),...d.turnServers],peers:this.mesh?.directory().map(g=>({name:g.peer,healthy:g.healthy}))||[]});break}case"GET /.well-known/agent-card.json":this.json(t,200,{name:this.config.node.name,description:`AgentX daemon node "${this.config.node.name}"`,url:`http://${this.config.node.bind}`,version:"1.0.0",capabilities:{streaming:!1,pushNotifications:!1,stateTransitionHistory:!1},skills:this.registry.list().map(d=>({id:d.id,name:d.name,description:`Agent "${d.name}" (${d.tier})`,tags:[d.tier]})),channels:this.router.getChannelNames(),defaultInputModes:["text"],defaultOutputModes:["text"]});break;case"GET /wiki/agents":{let d=this.registry.getWikiHub();this.json(t,200,{nodeId:this.config.node.id,agents:d.summary()});break}case"GET /wiki/entries":{let d=this.registry.getWikiHub(),g=n.searchParams.get("agent")||void 0,p=n.searchParams.get("after")||void 0,u=g?d.getAgentEntries(g):d.getSharedStore().listEntries({after:p});this.json(t,200,{nodeId:this.config.node.id,count:u.length,entries:u.map(m=>({id:m.id,date:m.date,agentId:m.agentId,source:m.source,sourceContext:m.sourceContext,content:m.content}))});break}case"GET /wiki/articles":{let d=this.registry.getWikiHub(),g=n.searchParams.get("agent");if(!g){this.json(t,400,{error:"?agent= required"});break}let u=d.getAgentWiki(g).rebuildIndex();this.json(t,200,{nodeId:this.config.node.id,agentId:g,articles:u.articles});break}case"GET /wiki/article":{let d=this.registry.getWikiHub(),g=n.searchParams.get("agent"),p=n.searchParams.get("path");if(!g||!p){this.json(t,400,{error:"?agent= and ?path= required"});break}let m=d.getAgentWiki(g).readArticle(p);if(!m){this.json(t,404,{error:"Article not found"});break}this.json(t,200,{nodeId:this.config.node.id,agentId:g,path:m.path,title:m.meta.title,tags:m.meta.tags,owner:m.meta.owner,created:m.meta.created,lastUpdated:m.meta.lastUpdated,sources:m.meta.sources,content:m.content});break}case"GET /graph/schema":{let d=this.registry.getGraphStore();if(!d){this.json(t,404,{error:"graph disabled on this node"});break}this.json(t,200,{nodeId:this.config.node.id,schema:d.loadSchema()});break}case"GET /graph/nodes":{let d=this.registry.getGraphStore();if(!d){this.json(t,404,{error:"graph disabled on this node"});break}this.json(t,200,{nodeId:this.config.node.id,...d.loadNodes()});break}case"GET /graph/classifications":{let d=this.registry.getGraphStore();if(!d){this.json(t,404,{error:"graph disabled on this node"});break}let g=n.searchParams.get("status")||"approved",p=Math.max(1,Math.min(1e3,parseInt(n.searchParams.get("limit")||"200",10)||200)),u=d.listByStatus(g,p);this.json(t,200,{nodeId:this.config.node.id,status:g,count:u.length,classifications:u});break}default:this.json(t,404,{error:"Not found",endpoints:["GET /health","GET /agents","GET /crons","GET /mesh","GET /wiki/agents","GET /wiki/entries[?agent=X&after=YYYY-MM-DD]","GET /wiki/articles?agent=X","GET /graph/schema","GET /graph/nodes","GET /graph/classifications[?status=approved|pending|rejected&limit=N]","POST /task { agent, message, context? }","POST /mesh/task { peer, message }","POST /webhook/:agentId[/:source] \u2014 webhook callback","POST /reload \u2014 re-read agentx.json (hot-swaps crons)","GET /.well-known/agent-card.json","GET /call \u2014 browser UI for P2P A/V calls (requires channels.webrtc.enabled)","GET /webrtc/config \u2014 ICE servers + peer directory for the call page","GET /webrtc/events?callId=&as= \u2014 SSE stream of signaling events","POST /webrtc/signal/out \u2014 browser-originated signal, forwarded to remote peer","POST /webrtc/signal \u2014 remote-peer-originated signal, fanned out to local browser","POST /webrtc/bot/invite { callId, target, agentId } \u2014 spawn a transcribing bot peer for a call","GET /webrtc/bots \u2014 active bot sessions"]})}}catch(i){this.json(t,500,{error:i.message})}}async handleOpenAICompat(e,t,n){let s=await R(e),i=n.match(/^\/llm\/([^/]+)\//),r=Object.keys(this.config.agents)[0]||"default",o=i?.[1]||s.model||r,a=s.messages||[],c=[...a].reverse().find(p=>p.role==="user");if(!c?.content){this.json(t,400,{error:{message:"No user message found",type:"invalid_request_error"}});return}let l=a.slice(0,-1).map(p=>`${p.role==="user"?"User":"Assistant"}: ${p.content.slice(0,200)}`),d=l.length>0?`[Conversation]
709
+ ${l.slice(-10).join(`
710
+ `)}
711
+
712
+ `:"";if(s.stream===!0){t.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache",Connection:"keep-alive"});let p=`chatcmpl-${Date.now().toString(36)}`,u=await this.registry.execute({agentId:o,message:d+c.content,context:{channel:"api",sender:"openai-compat"}}),m=u.error||u.content||"",f={id:p,object:"chat.completion.chunk",created:Math.floor(Date.now()/1e3),model:o,choices:[{index:0,delta:{role:"assistant",content:m},finish_reason:"stop"}]};t.write(`data: ${JSON.stringify(f)}
713
+
714
+ `),t.write(`data: [DONE]
715
+
716
+ `),t.end()}else{let p=await this.registry.execute({agentId:o,message:d+c.content,context:{channel:"api",sender:"openai-compat"}}),u=p.error||p.content||"",m=Math.ceil(u.length/4);this.json(t,200,{id:`chatcmpl-${Date.now().toString(36)}`,object:"chat.completion",created:Math.floor(Date.now()/1e3),model:o,choices:[{index:0,message:{role:"assistant",content:u},finish_reason:"stop"}],usage:{prompt_tokens:Math.ceil(c.content.length/4),completion_tokens:m,total_tokens:Math.ceil(c.content.length/4)+m}})}}json(e,t,n){e.writeHead(t,{"Content-Type":"application/json"}),e.end(JSON.stringify(n,null,2))}installAgentMemorySurface(){for(let e of this.registry.list()){let t=e.workspace;if(!(!t||!ge(t)))try{let n=N(t,".claude","skills");In(n,{recursive:!0});let s=N(n,wn);ge(s)||(Tn(s,kn),this.log(` memory-skill: installed remember.md \u2192 ${e.id}`)),this.agentMemory.syncToWorkspace(e.id,t)}catch(n){this.log(` memory-skill: ${e.id} install failed \u2014 ${n?.message??n}`)}}}installAgentMcpConfig(){for(let[e,t]of Object.entries(this.config.agents)){let n=t.workspace;if(!n||!ge(n))continue;let s=t.mcp??{};try{switch(bn(n,s)){case"installed":this.log(` mcp: installed ${Object.keys(s).length} server(s) \u2192 ${e}`);break;case"updated":this.log(` mcp: updated .mcp.json (${Object.keys(s).length} server(s)) \u2192 ${e}`);break;case"removed":this.log(` mcp: removed managed .mcp.json (config now empty) \u2192 ${e}`);break;case"skipped-operator-owned":Object.keys(s).length>0&&this.log(` mcp: ${e} has operator-owned .mcp.json \u2014 skipping (delete file or marker to let agentx manage)`);break;case"noop":break}}catch(i){this.log(` mcp: ${e} install failed \u2014 ${i?.message??i}`)}}}workspaceFor(e){for(let t of this.registry.list())if(t.id===e)return t.workspace;return null}async handleMemoryApi(e,t,n,s){let i=this.agentMemory;if(e.method==="GET"&&n==="/api/memory"){let a=s.searchParams.get("agent")||"";return a?(this.json(t,200,{agent:a,memories:i.list(a),index:i.indexMarkdown(a)}),!0):(this.json(t,400,{error:"missing agent query param"}),!0)}let r=e.method==="GET"&&n.match(/^\/api\/memory\/([^\/?]+)$/);if(r){let a=s.searchParams.get("agent")||"";if(!a)return this.json(t,400,{error:"missing agent query param"}),!0;let c=i.get(a,decodeURIComponent(r[1]));return c?(this.json(t,200,{memory:c}),!0):(this.json(t,404,{error:"no such memory"}),!0)}if(e.method==="POST"&&n==="/api/memory"){let a;try{a=await X(e)}catch(m){return this.json(t,400,{error:"invalid JSON body",message:m.message}),!0}let c=typeof a?.agentId=="string"?a.agentId:"",l=typeof a?.type=="string"?a.type:"",d=typeof a?.name=="string"?a.name:"",g=typeof a?.description=="string"?a.description:"",p=typeof a?.body=="string"?a.body:"",u=a?.append===!0;if(!c||!l||!d||!g||!p)return this.json(t,400,{error:"required fields: agentId, type, name, description, body"}),!0;try{let m=p;if(u){let y=i.get(c,d);y&&(m=`${y.body.trimEnd()}
717
+
718
+ ${p.trim()}`)}let f=i.save({agentId:c,type:l,name:d,description:g,body:m}),b=this.workspaceFor(c);if(b)try{i.syncToWorkspace(c,b)}catch{}this.json(t,200,{ok:!0,memory:f,syncedToWorkspace:!!b})}catch(m){this.json(t,400,{error:m?.message||"save failed"})}return!0}let o=e.method==="DELETE"&&n.match(/^\/api\/memory\/([^\/?]+)$/);if(o){let a=s.searchParams.get("agent")||"";if(!a)return this.json(t,400,{error:"missing agent query param"}),!0;if(!i.remove(a,decodeURIComponent(o[1])))return this.json(t,404,{error:"no such memory"}),!0;let l=this.workspaceFor(a);if(l)try{i.syncToWorkspace(a,l)}catch{}return this.json(t,200,{ok:!0}),!0}return!1}async handleTaskApi(e,t,n){let s=this.workflowDispatcher;if(!s)return!1;let{formSubmissionSchema:i}=await import("./types-TTB6DJUE.js");if(e.method==="GET"&&(n==="/api/workflows/tasks"||n.startsWith("/api/workflows/tasks?"))){let l=new URL(e.url||"/","http://_").searchParams.get("actor")||void 0,d=l?s.tasks.listForActor(l):s.tasks.listOpen();return this.json(t,200,{tasks:d}),!0}let o=n.replace(/^\/api\/workflows\/tasks/,"").match(/^\/([^\/?]+)(\/submit)?$/);if(!o)return!1;let a=decodeURIComponent(o[1]);if(o[2]){if(e.method!=="POST")return this.json(t,405,{error:"method not allowed"}),!0;let c;try{c=await X(e)}catch(u){return this.json(t,400,{error:"invalid JSON body",message:u.message}),!0}let l=c&&typeof c=="object"&&"submission"in c?c.submission:c,d=i.safeParse(l);if(!d.success)return this.json(t,400,{error:"invalid submission",issues:d.error.issues.map(u=>({path:u.path.join("."),message:u.message}))}),!0;let g=typeof c?.submittedBy=="string"?c.submittedBy:"anonymous",p=await s.submitTask(a,d.data,g);return p.ok?(this.json(t,200,{ok:!0,runId:p.runId}),!0):(this.json(t,400,{error:p.error,fieldErrors:p.fieldErrors}),!0)}if(e.method==="GET"){let c=s.tasks.get(a);return c?(this.json(t,200,{task:c}),!0):(this.json(t,404,{error:"task not found"}),!0)}return!1}async handleWorkflowEvent(e,t){if(!this.workflowDispatcher){this.json(t,503,{error:"workflow engine not enabled on this node"});return}let n;try{n=await X(e)}catch(a){this.json(t,400,{error:"invalid JSON body",message:a.message});return}let s=n?.payload??n,i=s?.event,r=s?.entityRef;if(!i?.id||!r?.backend||!r?.id){this.json(t,400,{error:"missing event or entityRef"});return}let o=n?.kind;try{if(o==="trigger"&&s.trigger){let g=typeof n?.fromPeer=="string"?n.fromPeer:"unknown";this.log(`[workflows/mesh] received trigger from "${g}" source="${s.trigger.source}" chat="${s.trigger.chat??"*"}"`);let p=await this.workflowDispatcher.dispatch({trigger:s.trigger,entityRef:r,event:i,fromRemote:{peer:g}});this.log(`[workflows/mesh] dispatched broadcast: matched=${p.claimed.length} ${p.claimed.length?`(${p.claimed.map(u=>u.id).join(", ")})`:"[none \u2014 no workflow with mesh.allowRemote matched]"}`),this.json(t,202,{ok:!0,mode:"trigger-broadcast",matched:p.claimed.map(u=>u.id)});return}let l=(s.workflowId?this.workflowStore?.get(s.workflowId):null)?.nodes.find(g=>g.type.startsWith("trigger."))?.config??{},d={source:String(l.source??"hook"),project:l.filter?.project,repo:l.filter?.repo,chat:l.filter?.chat,labels:l.filter?.labels};await this.workflowDispatcher.dispatch({trigger:d,entityRef:r,event:i}),this.json(t,202,{ok:!0})}catch(a){this.log(`[workflows] /workflow/event failed: ${a.message}`),this.json(t,500,{error:a.message})}}async handleChannelSend(e,t){let n;try{n=await X(e)}catch(a){this.json(t,400,{error:"invalid JSON body",message:a.message});return}let s=String(n?.channel??""),i=String(n?.chatId??""),r=String(n?.text??"");if(!s||!i||!r){this.json(t,400,{error:"channel, chatId, and text are required"});return}if(!this.router.getChannel(s)){this.json(t,404,{error:`channel "${s}" not hosted on this node`});return}try{let a=await this.router.sendOutbound({channel:s,chatId:i,text:r,accountId:typeof n.accountId=="string"?n.accountId:void 0,parseMode:typeof n.parseMode=="string"?n.parseMode:void 0,replyTo:typeof n.replyTo=="string"?n.replyTo:void 0});this.json(t,200,{ok:!0,messageId:a??null})}catch(a){this.log(`[mesh] /channel/send "${s}" failed: ${a.message}`),this.json(t,500,{error:a.message})}}async handleChannelChats(e,t,n={}){let s=new Set,i=[],r=[],o=(c,l)=>{let d=0;for(let g of c)!g.id||s.has(g.id)||(s.add(g.id),i.push({...g,source:l}),d++);d>0&&r.push(`${l}:${d}`)},a=this.router.getChannel(t);if(a?.listKnownChats)try{o(a.listKnownChats(),"local")}catch{}else if(a?.listChats)try{o(a.listChats().map(c=>({id:c.jid,name:c.name,kind:c.isGroup?"group":"dm"})),"local")}catch{}if(!n.localOnly&&this.mesh){let c=this.mesh.directory().filter(l=>l.healthy&&l.channels?.includes(t));await Promise.allSettled(c.map(async l=>{try{let d=await fetch(`${l.peerUrl}/channels/${encodeURIComponent(t)}/chats?local=1`,{signal:AbortSignal.timeout(5e3)});if(!d.ok)return;let g=await d.json();Array.isArray(g.chats)&&o(g.chats.map(p=>({id:p.id,name:p.name,kind:p.kind??"dm",accountId:p.accountId})),`mesh:${l.peer}`)}catch{}}))}this.json(e,200,{channel:t,source:r.join(", ")||"none",chats:i})}async handleWorkflowManualRun(e,t,n){if(!this.workflowDispatcher||!this.workflowStore){this.json(t,503,{error:"workflow engine not enabled on this node"});return}let s=this.workflowStore.get(n);if(!s){this.json(t,404,{error:`unknown workflow "${n}"`});return}let i=s.nodes.find(u=>u.type.startsWith("trigger."));if(!i){this.json(t,400,{error:`workflow "${n}" has no trigger node`});return}let r;try{r=await X(e)}catch{r={}}let o=!!r?.force,a=r?.payload||{};if(i.type!=="trigger.manual"&&!o){this.json(t,409,{error:`workflow "${n}" trigger is "${i.type}"`,hint:'pass { "force": true } to fire anyway with a synthesized event (for testing)'});return}let c=i.config??{},l=o?String(c.source??"manual"):"manual",d=String(a.entityId||a.chatId||`manual-${Date.now().toString(36)}`),g={backend:o&&c.source?"channel":"manual",id:d},p=`manual:${n}:${d}:${Date.now()}`;try{let m=(await this.workflowDispatcher.dispatch({trigger:o?{source:l,project:c.filter?.project,repo:c.filter?.repo,chat:c.filter?.chat,labels:c.filter?.labels}:{source:"manual"},entityRef:g,event:{id:p,payload:a}})).runs[0]?.id;this.json(t,m?200:202,{ok:!0,runId:m,entityRef:g,source:l,force:o})}catch(u){this.log(`[workflows] manual run "${n}" failed: ${u.message}`),this.json(t,500,{error:u.message})}}};function X(h){return new Promise((e,t)=>{let n="";h.setEncoding("utf8"),h.on("data",s=>{n+=s}),h.on("end",()=>{try{e(n?JSON.parse(n):{})}catch(s){t(s)}}),h.on("error",t)})}function hi(h){let e=h;return e=e.replace(/```[\s\S]*?```/g,". "),e=e.replace(/`[^`]*`/g,""),e=e.replace(/\*\*(.*?)\*\*/g,"$1"),e=e.replace(/__(.*?)__/g,"$1"),e=e.replace(/\*([^*]+)\*/g,"$1"),e=e.replace(/_([^_]+)_/g,"$1"),e=e.replace(/~~(.*?)~~/g,"$1"),e=e.replace(/\[([^\]]+)\]\([^)]+\)/g,"$1"),e=e.replace(/!\[([^\]]*)\]\([^)]+\)/g,"image$1 "),e=e.replace(/^#{1,6}\s+/gm,""),e=e.replace(/^[\s]*[-*+]\s+/gm,". "),e=e.replace(/^[\s]*\d+\.\s+/gm,". "),e=e.replace(/^[-=_]{3,}$/gm,""),e=e.replace(/^>\s*/gm,""),e=e.replace(/https?:\/\/\S+/g,"link"),e=e.replace(/\/\w+(\/\w+)+/g,t=>t.split("/").filter(Boolean).join(" slash ")),e=e.replace(/&/g," and "),e=e.replace(/@/g," at "),e=e.replace(/=>/g," returns "),e=e.replace(/->/g," to "),e=e.replace(/\|/g," or "),e=e.replace(/\be\.g\./gi,"for example"),e=e.replace(/\bi\.e\./gi,"that is"),e=e.replace(/\betc\./gi,"etcetera"),e=e.replace(/\bvs\.?\b/gi,"versus"),e=e.replace(/\n{2,}/g,". "),e=e.replace(/\n/g," "),e=e.replace(/\s+/g," "),e=e.replace(/\.\s*\./g,"."),e=e.replace(/\s+([.,!?])/g,"$1"),e=e.trim().replace(/^[.,:;]+\s*/,""),e.length>800&&(e=e.slice(0,800).replace(/\s+\S*$/,"")+"..."),e}function gi(h,e){let t=["model","workspace","tier","permissionMode","mcpServers"],n=new Set,s=new Set([...Object.keys(h),...Object.keys(e)]);for(let i of s){let r=h[i],o=e[i];if(!r||!o){n.add("add/remove");continue}for(let a of t)JSON.stringify(r[a])!==JSON.stringify(o[a])&&n.add(`${i}.${a}`)}return Array.from(n).slice(0,6)}async function R(h){return new Promise((e,t)=>{let n="";h.on("data",s=>n+=s.toString()),h.on("end",()=>{try{e(n?JSON.parse(n):{})}catch{e({})}}),h.on("error",t)})}function fa(h){typeof h=="string"&&(pe.error(h),process.exit(1)),h instanceof Error&&(pe.error(h.message),process.exit(1)),pe.error("Something went wrong. Please try again."),process.exit(1)}import ft from"path";import An from"fs-extra";function ka(){let h=[];try{h.push(ft.resolve(__dirname,"..","package.json")),h.push(ft.resolve(__dirname,"package.json"))}catch{}h.push(ft.resolve(process.cwd(),"package.json"));for(let e of h)try{if(An.existsSync(e)){let t=An.readJSONSync(e);if(t&&t.name)return t}}catch{}return{name:"agentix-cli",version:"0.0.0"}}export{wi as a,V as b,me as c,ie as d,J as e,ve as f,mn as g,xn as h,fa as i,ka as j};
719
+ //# sourceMappingURL=chunk-U364GNQX.js.map