agentix-cli 0.13.0 → 0.14.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 (48) hide show
  1. package/README.md +50 -493
  2. package/dist/{agent-5KIMJI3F.js → agent-DW3NYIIL.js} +2 -2
  3. package/dist/board-dashboard-6TKE6NMV.js +1435 -0
  4. package/dist/board-dashboard-6TKE6NMV.js.map +1 -0
  5. package/dist/chunk-6U2XOQHU.js +7 -0
  6. package/dist/chunk-6U2XOQHU.js.map +1 -0
  7. package/dist/chunk-DYOBFJLX.js +11 -0
  8. package/dist/chunk-DYOBFJLX.js.map +1 -0
  9. package/dist/chunk-ITZEVXZ4.js +2 -0
  10. package/dist/chunk-ITZEVXZ4.js.map +1 -0
  11. package/dist/chunk-M33CB2H4.js +6 -0
  12. package/dist/chunk-M33CB2H4.js.map +1 -0
  13. package/dist/chunk-NJR5WF5E.js +5 -0
  14. package/dist/chunk-NJR5WF5E.js.map +1 -0
  15. package/dist/chunk-O72LPMBR.js +12 -0
  16. package/dist/chunk-O72LPMBR.js.map +1 -0
  17. package/dist/chunk-RB73FZXJ.js +133 -0
  18. package/dist/chunk-RB73FZXJ.js.map +1 -0
  19. package/dist/{chunk-VH23CDHL.js → chunk-UPT2UMF6.js} +42 -40
  20. package/dist/chunk-UPT2UMF6.js.map +1 -0
  21. package/dist/{chunk-D6EDMQRV.js → chunk-XVYMZ4M7.js} +3 -3
  22. package/dist/cli.js +54 -48
  23. package/dist/cli.js.map +1 -1
  24. package/dist/config-7QJ2QJIC.js +2 -0
  25. package/dist/config-mutator-GAEVGHDD.js +2 -0
  26. package/dist/index.d.ts +670 -69
  27. package/dist/index.js +1 -1
  28. package/dist/index.js.map +1 -1
  29. package/dist/registry-YVAG4EO5.js +2 -0
  30. package/dist/registry-YVAG4EO5.js.map +1 -0
  31. package/dist/{usage-dashboard-2TJQBFSH.js → usage-dashboard-M7AFIGMJ.js} +2 -2
  32. package/package.json +9 -1
  33. package/dist/chunk-34UAFPK4.js +0 -10
  34. package/dist/chunk-34UAFPK4.js.map +0 -1
  35. package/dist/chunk-CJ45Y2IR.js +0 -12
  36. package/dist/chunk-CJ45Y2IR.js.map +0 -1
  37. package/dist/chunk-T25UEXVK.js +0 -126
  38. package/dist/chunk-T25UEXVK.js.map +0 -1
  39. package/dist/chunk-VH23CDHL.js.map +0 -1
  40. package/dist/chunk-WJ6ZF6EZ.js +0 -6
  41. package/dist/chunk-WJ6ZF6EZ.js.map +0 -1
  42. package/dist/config-HAD5E4YX.js +0 -2
  43. package/dist/registry-CDWY7CLL.js +0 -2
  44. /package/dist/{agent-5KIMJI3F.js.map → agent-DW3NYIIL.js.map} +0 -0
  45. /package/dist/{chunk-D6EDMQRV.js.map → chunk-XVYMZ4M7.js.map} +0 -0
  46. /package/dist/{config-HAD5E4YX.js.map → config-7QJ2QJIC.js.map} +0 -0
  47. /package/dist/{registry-CDWY7CLL.js.map → config-mutator-GAEVGHDD.js.map} +0 -0
  48. /package/dist/{usage-dashboard-2TJQBFSH.js.map → usage-dashboard-M7AFIGMJ.js.map} +0 -0
@@ -0,0 +1,133 @@
1
+ import{g as Le,i as Ne}from"./chunk-UPT2UMF6.js";import{f as Oe,g as je}from"./chunk-O72LPMBR.js";import{a as Fe,b as Ge}from"./chunk-NJR5WF5E.js";import{c as we,d as Be}from"./chunk-M33CB2H4.js";import{b as He}from"./chunk-QZP65I2H.js";import{b as Ue}from"./chunk-6U2XOQHU.js";import{e as O}from"./chunk-DSYKYZMT.js";import{a as lt,b as V}from"./chunk-4YCH6IZV.js";var q=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 s=await this.rpc("tasks/send",{id:`task-${Date.now().toString(36)}`,message:{role:"user",parts:[{type:"text",text:e}]},metadata:t});if(s.error)throw new Error(`A2A error: ${s.error.message}`);return s.result}async*sendTaskStream(e,t){let s=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}}),n=await fetch(this.baseUrl,{method:"POST",headers:{...this.headers(),"Content-Type":"application/json",Accept:"text/event-stream"},body:s});if(!n.ok||!n.body)throw new Error(`A2A stream error: ${n.status}`);let i=n.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(`
2
+ `);o=l.pop()||"";for(let h of l)if(h.startsWith("data: "))try{let g=JSON.parse(h.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 s=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(!s.ok)throw new Error(`A2A HTTP error: ${s.status}`);return s.json()}headers(){let e={};return this.token&&(e.Authorization=`Bearer ${this.token}`),e}};var X=class{peers=new Map;healthTimer;config;log;constructor(e,t=console.error.bind(console,"[mesh]")){this.config=e,this.log=t;for(let s of e.mesh.peers)this.peers.set(s.name,{peer:s,client:new q(s.url,s.token),healthy:!1,agents:[]})}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 discoverAll(){let e=await Promise.allSettled(Array.from(this.peers.entries()).map(([s,n])=>this.discoverPeer(s,n))),t=Array.from(this.peers.values()).filter(s=>s.healthy).length;this.log(`Discovery complete: ${t}/${this.peers.size} peers healthy`)}async discoverPeer(e,t){let s=this.config.mesh.healthCheck.timeout*1e3;try{let n=new AbortController,i=setTimeout(()=>n.abort(),s),r=await t.client.getAgentCard();clearTimeout(i),t.healthy=!0,t.lastCheck=new Date,t.agentCard=r,t.agents=r.skills||[],this.log(`Peer "${e}" healthy: ${r.name} (${t.agents.length} skills)`)}catch(n){t.healthy=!1,t.lastCheck=new Date,this.log(`Peer "${e}" unreachable: ${n.message}`)}}async sendTask(e,t,s){let n=this.peers.get(e);if(!n)throw new Error(`Unknown peer: ${e}`);if(!n.healthy)throw new Error(`Peer "${e}" is not healthy`);let i=s||n.agents[0]?.id;if(!i)throw new Error(`Peer "${e}" has no agents`);let r=`${n.peer.url}/task`,o={"Content-Type":"application/json"};n.peer.token&&(o.Authorization=`Bearer ${n.peer.token}`);let a=await fetch(r,{method:"POST",headers:o,body:JSON.stringify({agent:i,message:t})});if(!a.ok)throw new Error(`Peer "${e}" /task error: ${a.status}`);let c=await a.json();if(c.error)throw new Error(`Peer "${e}" agent error: ${c.error}`);return c.content||"No response"}findPeerWithSkill(e){for(let t of this.peers.values())if(t.healthy&&t.agents.some(s=>s.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,lastCheck:t.lastCheck}))}};import{readFileSync as ht,writeFileSync as gt,existsSync as dt,mkdirSync as ut}from"fs";import{resolve as We}from"path";function B(d,e,t="\u2026"){if(!d)return"";if(d.length<=e)return d;let s=e-t.length;if(s<=0)return d.slice(0,e);let n=d.slice(0,s),i=n.lastIndexOf(`
3
+ `);if(i>s*.6)return n.slice(0,i)+`
4
+ `+t;let r=Math.max(n.lastIndexOf(" "),n.lastIndexOf(" "));return r>s*.6?n.slice(0,r)+t:n+t}function ke(d,e,t){if(!d)return"";let n=d.split(`
5
+ `).slice(0,e).join(`
6
+ `);return B(n,t)}var pt=30,Je=6e3,mt=1500,ft=6e4;function yt(d){let e=new Date(d),t=e.getUTCHours().toString().padStart(2,"0"),s=Math.floor(e.getUTCMinutes()/15)*15;return`${t}:${s.toString().padStart(2,"0")}`}var Y=class{dir;cache=new Map;constructor(e=We(process.cwd(),".agentx/groups")){this.dir=e,ut(this.dir,{recursive:!0})}add(e,t,s){let n=this.load(e),i=Date.now(),r=B(s,mt),o=n[n.length-1];if(!(o&&o.sender===t&&o.text===r&&i-o.timestamp<ft)){for(n.push({sender:t,text:r,timestamp:i});n.length>pt;)n.shift();this.cache.set(e,n),this.save(e,n)}}buildContext(e){let t=this.load(e);if(t.length<=1)return"";let s=["[Recent group conversation]"],n=0,i=null,r=[],o="",a=()=>{if(!i)return!0;let c=i.repeat>1?`${i.line} [\xD7${i.repeat}]`:i.line;if(n+c.length>Je)return!1;if(r.push(c),n+=c.length,i.bucket!==o){let l=`\u2014 ${i.bucket} \u2014`;if(n+l.length>Je)return i=null,!1;r.push(l),n+=l.length,o=i.bucket}return i=null,!0};for(let c=t.length-2;c>=0;c--){let l=t[c],h=`${l.sender}: ${l.text}`,g=yt(l.timestamp);if(i&&i.line===h&&i.bucket===g){i.repeat++;continue}if(!a())break;i={line:h,repeat:1,bucket:g}}a();for(let c=r.length-1;c>=0;c--)s.splice(1,0,r[c]);return s.length<=1?"":(s.push("[End of conversation \u2014 respond to the latest message]"),s.join(`
7
+ `))}getEntries(e){return[...this.load(e)]}filePath(e){let t=e.replace(/[^a-zA-Z0-9_-]/g,"_");return We(this.dir,`${t}.json`)}load(e){if(this.cache.has(e))return this.cache.get(e);let t=this.filePath(e);if(!dt(t))return this.cache.set(e,[]),[];try{let s=JSON.parse(ht(t,"utf-8")),n=Array.isArray(s)?s:[];return this.cache.set(e,n),n}catch{return this.cache.set(e,[]),[]}}save(e,t){try{gt(this.filePath(e),JSON.stringify(t))}catch{}}};var bt={minChars:80,maxChars:4e3,idleFlushMs:2e3,minIntervalMs:1500,humanPacing:!1},wt={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}},Q=class{config;buffer="";emitter;lastEmitTime=0;idleTimer;insideCodeFence=!1;fencePattern=/^```/;lastEmission=Promise.resolve();constructor(e,t,s){let n=s?wt[s]:{};this.config={...bt,...n,...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),s=this.buffer.slice(0,t);if(this.buffer=this.buffer.slice(t),this.config.humanPacing){let n=800+Math.random()*1700;setTimeout(()=>this.emitImmediate(s),n)}else this.emitImmediate(s)}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),s=e.lastIndexOf(`
8
+
9
+ `,t);if(s>=this.config.minChars)return s+2;let n=e.lastIndexOf(`
10
+ `,t);if(n>=this.config.minChars)return n+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]===`
11
+ `))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(`
12
+ `);for(let s of t)this.fencePattern.test(s.trim())&&(this.insideCodeFence=!this.insideCodeFence)}};import{readFileSync as kt,writeFileSync as $t,existsSync as ze,mkdirSync as vt}from"fs";import{resolve as Ke,dirname as St}from"path";var Tt=4e3,$e=class{registry;config;channels=new Map;hooks;mesh;serviceMatcher;business;groupLog;log;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;constructor(e,t,s,n=console.error.bind(console,"[router]")){this.registry=e,this.config=t,this.hooks=s,this.log=n,this.groupLog=new Y,this.loadDedupFromDisk()}loadDedupFromDisk(){try{let e=Ke(process.cwd(),this.DEDUP_STORE_PATH);if(!ze(e))return;let t=JSON.parse(kt(e,"utf-8")),s=Date.now(),n=0;for(let[i,r]of Object.entries(t))typeof r=="number"&&s-r<this.MESSAGE_ID_TTL_MS&&(this.recentMessageIds.set(i,r),n++);n>0&&this.log(`Router dedup: restored ${n} 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 s=Ke(process.cwd(),this.DEDUP_STORE_PATH),n=St(s);ze(n)||vt(n,{recursive:!0}),$t(s,JSON.stringify(t))}catch{}}flushPersistence(){this.flushDedupToDisk()}setMesh(e){this.mesh=e}setServiceMatcher(e){this.serviceMatcher=e}setBusiness(e){this.business=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 s=e.accountId;return t.name==="telegram"&&!s&&e.agentId&&(s=this.getAccountForAgent(e.agentId)),this.log(`Outbound [${e.channel}] -> ${e.chatId}: ${e.text.slice(0,80)}`),t.name==="telegram"&&s?this.adapterSend(t,{...e,accountId:s}):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()}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}`,s=Date.now();if(this.recentMessageIds.size>=this.MESSAGE_ID_MAX_ENTRIES)for(let[i,r]of this.recentMessageIds)s-r>this.MESSAGE_ID_TTL_MS&&this.recentMessageIds.delete(i);let n=this.recentMessageIds.get(t);return n&&s-n<this.MESSAGE_ID_TTL_MS?!0:(this.recentMessageIds.set(t,s),this.scheduleDedupSave(),!1)}async handleMessage(e,t){if(this.isDuplicateMessage(t)){this.log(`Duplicate ${t.channel} message dropped: ${t.accountId||"default"}/${t.id}`);return}if(this.hooks?.has("pre:channel-message")){let b=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});if(b.blocked){this.log(`Message blocked by hook: ${b.message}`);return}b.modified?.text&&(t={...t,text:b.modified.text})}if(t.group){let b=t.group.id;this.groupLog.add(b,t.sender.name,t.text)}if(this.serviceMatcher){let b=t.group?.id||t.sender.id,k=this.serviceMatcher.match(t.text,t.sender.id,t.channel);if(k){this.log(`Service matched: "${k.service.name}" for ${t.sender.name} (trigger: ${k.trigger})`);let v=t.accountId;this.adapterReact(e,b,t.id,"\u{1F440}",v);let C=this.startTypingLoop(e,b,v);await this.serviceMatcher.execute(k.service,this.registry,{channel:t.channel,sender:t.sender.name,chatId:b},async P=>{clearInterval(C),await this.adapterSend(e,{channel:t.channel,chatId:b,text:P,replyTo:t.id,accountId:v})});return}}let s=this.resolveAgent(t);if(!s)return;if(t.group&&t.channel==="telegram"){let b=this.getAccountForAgent(s);if(b&&b!==t.accountId)return}let n=t.group?.id||t.sender.id;if(t.preferNode){this.log(`preferNode="${t.preferNode}" \u2014 forcing mesh routing for agent "${s}"`),await this.handleViaMeshByPeer(e,t,s,t.preferNode)||this.log(`Mesh peer "${t.preferNode}" not found or unhealthy for agent "${s}"`);return}let i=this.registry.getAgent(s);if(!i){await this.handleViaMeshByAgentId(e,t,s)||this.log(`Agent "${s}" not found locally or on any mesh peer`);return}let r=i.name||s,o=this.getAccountForAgent(s)||t.accountId;this.log(`Routing [${t.channel}/${t.sender.name}] -> "${r}": ${t.text.slice(0,80)}`),this.adapterReact(e,n,t.id,"\u{1F440}",o);let a=this.startTypingLoop(e,n,o),c=typeof e.editMessage=="function",l,h="",g=c?new Q(async b=>{if(h+=b,l)try{await this.adapterEdit(e,n,l,h,void 0,o)}catch{}else{let k=h.length>20?h:`_${r} is writing..._
13
+
14
+ ${h}`;try{l=await this.adapterSend(e,{channel:t.channel,chatId:n,text:k,replyTo:t.id,accountId:o})}catch{}}},void 0,t.channel):void 0,f=g?(b,k)=>{g.push(b)}:void 0;if(t.channelMeta?.agents){for(let b of t.channelMeta.agents)if(!b.handle){let k=this.registry.getAgent(b.id);k&&(b.handle=k.mentions.find(v=>v.startsWith("@")),b.name=k.name)}}let u=t.group?this.groupLog.buildContext(n):"",y=u?`${u}
15
+
16
+ ${t.sender.name}: ${t.text}`:t.text,m=await this.registry.execute({message:y,agentId:s,context:{channel:t.channel,sender:t.sender.name,senderId:t.sender.id,senderUsername:t.sender.username,group:t.group?.name,chatId:n,mediaPath:t.media?.path,mediaType:t.media?.type,replyToText:t.replyToText,channelMeta:t.channelMeta}},f);if(clearInterval(a),await g?.flush(),m.error){if(m.error.startsWith("__queued__")){clearInterval(a),this.log(`Message queued for ${r}`);return}this.log(`Agent error: ${m.error}`);let b=`Error: ${m.error}`;l?await this.adapterEdit(e,n,l,b,"plain",o):await this.adapterSend(e,{channel:t.channel,chatId:n,text:b,replyTo:t.id,parseMode:"plain",accountId:o});return}let p=m.content;if(this.hooks?.has("post:channel-message")){let b=await this.hooks.execute("post:channel-message",{event:"post:channel-message",channel:t.channel,sender:t.sender.name,response:p,agentId:s});if(b.blocked){this.log(`Response blocked by hook: ${b.message}`);return}b.modified?.response&&(p=b.modified.response)}t.channel==="gitlab"&&p&&(this.channels.get("gitlab")?.getAgentToken?.(s)||(p=`> **${r}** (${s})
17
+
18
+ ${p}`)),t.channel==="whatsapp"&&p&&(p=`*${r}*
19
+
20
+ ${p}`);let w;p&&(l?(await this.adapterEdit(e,n,l,p,void 0,o),w=l):w=await this.adapterSend(e,{channel:t.channel,chatId:n,text:p,replyTo:t.id,accountId:o,agentId:s})),t.group&&p&&this.groupLog.add(n,r,p);let $=this.config.notifications;if($?.destination&&m.duration){let b=($.longTaskThreshold||30)*1e3;if(m.duration>=b&&!m.error&&$.on?.taskComplete||m.error&&$.on?.taskError){let v=Math.round(m.duration/1e3),C=m.error?"failed":"completed",P=ke(t.text,6,300),S=m.error?B(m.error,300):ke(m.content,6,400),N=`${C==="failed"?"\u{1F534}":"\u2705"} **${r}** ${C} (${v}s)
21
+ ${t.channel}/${t.sender.name}: ${P}
22
+ ${S}`;this.sendOutbound({channel:$.destination.channel,chatId:$.destination.chatId,text:N,agentId:s,accountId:$.destination.accountId}).catch(R=>{this.log(`Task notification failed: ${R.message}`)})}}if(this.business&&m.duration&&this.business.recordTaskCompletion(s,Math.round(m.duration/1e3),!m.error,t.channel),t.channel==="gitlab"&&m.duration&&!m.error){let b=this.channels.get("gitlab");b&&b.logTimeSpent(n,m.duration,s).catch(k=>{this.log(`GitLab time tracking failed: ${k.message}`)})}p&&w&&["telegram","whatsapp","discord"].includes(t.channel)&&this.handleBotToBotChain(e,t,s,p,w,0).catch(b=>{this.log(`Bot-to-bot error: ${b.message}`)})}async handleBotToBotChain(e,t,s,n,i,r,o=new Set){if(r>=$e.MAX_BOT_CHAIN_DEPTH){this.log(`Bot-to-bot: max depth (${r}) reached, stopping`);return}o.add(s);for(let[a,c]of Object.entries(this.config.agents)){if(a===s)continue;if(o.has(a)){this.log(`Bot-to-bot: "${a}" already participated, stopping chain`);continue}if(!c.mentions.filter(y=>y.startsWith("@")).some(y=>n.toLowerCase().includes(y.toLowerCase())))continue;this.log(`Bot-to-bot [${r+1}]: "${s}" -> "${a}"`);let g=t.group?.id||t.sender.id,f=this.getAccountForAgent(a),u=this.getAccountForAgent(s);try{this.adapterReact(e,g,i,"\u{1F440}",f);let y=this.startTypingLoop(e,g,f),m=r===0?`[Original from ${t.sender.name}]: ${t.text}
23
+
24
+ [${s} said]: ${n}`:n,p=await this.registry.execute({message:m,agentId:a,context:{channel:t.channel,sender:`agent:${s}`,group:t.group?.name}});if(clearInterval(y),p.content&&!p.error){let w=p.content;t.channel==="whatsapp"&&(w=`*${c.name}*
25
+
26
+ ${w}`);let $=await this.adapterSend(e,{channel:t.channel,chatId:g,text:w,accountId:f});$&&p.content&&await this.handleBotToBotChain(e,t,a,p.content,$,r+1,o)}else p.error&&this.log(`Bot-to-bot "${a}" error: ${p.error}`)}catch(y){this.log(`Bot-to-bot "${a}" failed: ${y.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,s,n,i,r){return e.name==="telegram"&&r?e.editMessage(t,s,n,i,r):e.editMessage?.(t,s,n,i)??!1}adapterReact(e,t,s,n,i){e.name==="telegram"&&i?e.react(t,s,n,i):e.react?.(t,s,n)}startTypingLoop(e,t,s){let n=()=>{e.name==="telegram"&&s?e.sendTyping(t,s):e.sendTyping?.(t)};return n(),setInterval(n,Tt)}async handleViaMesh(e,t){if(!this.mesh)return!1;let s=t.text.toLowerCase(),n=this.mesh.directory();for(let i of n)if(i.healthy){for(let r of i.skills)if(s.includes(r.id.toLowerCase())||s.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 h=`**${r.name}** _(${i.peer})_:
27
+
28
+ `;await this.adapterSend(e,{channel:t.channel,chatId:o,text:h+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,s){if(!this.mesh)return!1;let n=this.mesh.directory();for(let i of n){if(!i.healthy)continue;let r=i.skills.find(l=>l.id===s);if(!r)continue;this.log(`Mesh routing by agentId [${t.channel}/${t.sender.name}] -> peer "${i.peer}" agent "${s}"`);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,s);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 ${s}: ${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,s,n){if(!this.mesh)return!1;let r=this.mesh.directory().find(g=>g.peer===n);if(!r||!r.healthy)return!1;let o=r.skills.find(g=>g.id===s)??{name:s,id:s};this.log(`Mesh routing by preferNode [${t.channel}/${t.sender.name}] -> peer "${n}" agent "${s}"`);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),h=Date.now();try{let g=await this.mesh.sendTask(n,t.text,s),f=Date.now()-h;if(clearInterval(l),g&&await this.adapterSend(e,{channel:t.channel,chatId:a,text:g,replyTo:t.id,accountId:c}),t.channel==="gitlab"&&f){let u=this.channels.get("gitlab");u&&u.logTimeSpent(a,f,s).catch(y=>{this.log(`GitLab time tracking (mesh) failed: ${y.message}`)})}return!0}catch(g){return clearInterval(l),this.log(`Mesh routing error for ${n}/${s}: ${g.message}`),await this.adapterSend(e,{channel:t.channel,chatId:a,text:`Error from ${n}/${o.name}: ${g.message}`,replyTo:t.id,parseMode:"plain",accountId:c}),!0}}getAccountForAgent(e){for(let[t,s]of Object.entries(this.config.channels.telegram.accounts))if(s.agentBinding===e)return t}resolveAgent(e){if(e.channel==="gitlab")return e.resolvedAgent?(this.log(`GitLab agent resolved by adapter: ${e.resolvedAgent}`),e.resolvedAgent):void 0;if(e.resolvedAgent)return e.resolvedAgent;if(!e.group)return e.channel==="telegram"?this.config.channels.telegram.accounts[e.accountId]?.agentBinding:e.channel==="whatsapp"?this.config.channels.whatsapp.defaultAgent:void 0;let t=e.sender.isBot===!0;if(e.channel==="telegram"&&this.config.channels.telegram.policy.group==="mention-required"){let i=this.registry.findByMention(e.text,{atMentionsOnly:t});return i||void 0}let s=this.registry.findByMention(e.text,{atMentionsOnly:t});if(s)return s;if(!t)return e.channel==="telegram"?this.config.channels.telegram.accounts[e.accountId]?.agentBinding:this.config.channels.whatsapp.defaultAgent}},F=$e;V(F,"MAX_BOT_CHAIN_DEPTH",3);function Z(d){return d.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function ve(d){let t=d.replace(/\\([_*\[\]()~`>#+=|{}.!\\-])/g,"$1").split(`
29
+ `),s=[],n=!1,i="",r=[],o=!1,a=[],c=!1;for(let h=0;h<t.length;h++){let g=t[h];if(g.trimStart().startsWith("```"))if(n){n=!1;let m=Z(r.join(`
30
+ `)),p=i?`// ${i}
31
+ `:"";s.push(`<pre><code>${p}${m}</code></pre>`),i="";continue}else{n=!0,i=g.trimStart().slice(3).trim(),r=[];continue}if(n){r.push(g);continue}if(o&&!g.trimStart().startsWith(">")&&(s.push("</blockquote>"),o=!1),c&&!g.trim().startsWith("|")&&(c=!1,a=[]),g.trimStart().startsWith("> ")){let m=g.replace(/^>\s*/,"");o||(s.push("<blockquote>"),o=!0),s.push(x(m));continue}let f=g.match(/^(#{1,6})\s+(.+)$/);if(f){s.push(""),s.push(`<b>${x(f[2])}</b>`);continue}if(/^[-*_]{3,}\s*$/.test(g.trim())){s.push("\u2014\u2014\u2014");continue}if(g.trim().startsWith("|")&&g.trim().endsWith("|")){let m=g.split("|").slice(1,-1).map(p=>p.trim());if(/^\|[\s\-:|]+\|$/.test(g.trim())){c=!0;continue}if(!c){a=m,c=!0;continue}if(a.length>0&&m.length>0)if(m.length>=2){let p=[];for(let w=0;w<m.length;w++)if(w===0)p.push(`<b>${x(m[w])}</b>`);else{let $=a[w]?`${x(a[w])}: `:"";p.push(`${$}${x(m[w])}`)}s.push(`\u2022 ${p.join(" \u2014 ")}`)}else s.push(`\u2022 ${x(m[0])}`);continue}let u=g.match(/^(\s*)[-*+]\s+(.+)$/);if(u){let m=u[1].length>0?" ":"";s.push(`${m}\u2022 ${x(u[2])}`);continue}let y=g.match(/^(\s*)\d+[.)]\s+(.+)$/);if(y){let m=y[1].length>0?" ":"",p=g.match(/^(\s*)(\d+)/)?.[2]||"1";s.push(`${m}${p}. ${x(y[2])}`);continue}if(!g.trim()){s.push("");continue}s.push(x(g))}n&&s.push(`<pre><code>${Z(r.join(`
32
+ `))}</code></pre>`),o&&s.push("</blockquote>");let l=s.join(`
33
+ `).trim();return l=It(l),l}function x(d){let e=Z(d);return e=e.replace(/`([^`]+)`/g,"<code>$1</code>"),e=e.replace(/\[([^\]]+)\]\(([^)]+)\)/g,(t,s,n)=>`<a href="${Z(n)}">${s}</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 Mt=/(?<=\w)\.(ts|js|py|rs|go|rb|cs|sh|md|yml|yaml|toml|json|env|css|html|xml|sql|tf|hcl)(?=[\s,;:)\]}<]|$)/gi;function It(d){let e=d.split(/(<\/?(?:code|pre|a)[^>]*>)/gi),t=!1;return e.map(s=>/<(?:code|pre|a)\b/i.test(s)?(t=!0,s):/<\/(?:code|pre|a)>/i.test(s)?(t=!1,s):t?s:s.replace(Mt,"<code>.$1</code>")).join("")}import{readFileSync as Ve,writeFileSync as qe,mkdirSync as Xe,existsSync as Se}from"fs";import{resolve as Ye,dirname as Qe}from"path";var Te=class{data={};filePath;dirty=!1;constructor(e){this.filePath=Ye(e,".agentx/telegram/groups.json"),this.load()}load(){try{Se(this.filePath)&&(this.data=JSON.parse(Ve(this.filePath,"utf-8")))}catch{this.data={}}}save(){try{Xe(Qe(this.filePath),{recursive:!0}),qe(this.filePath,JSON.stringify(this.data,null,2)),this.dirty=!1}catch{}}setBotStatus(e,t,s,n,i,r){this.data[e]||(this.data[e]={name:t,bots:{}}),t&&(this.data[e].name=t),this.data[e].bots[s]={username:i,agentId:n,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(([,s])=>s.status!=="left"&&s.status!=="kicked").map(([s,n])=>({accountId:s,agentId:n.agentId,username:n.username})):[]}hasGroup(e){return!!this.data[e]}},Me=class{offsets={};filePath;dirty=!1;saveTimer;constructor(e){this.filePath=Ye(e,".agentx/telegram/offsets.json");try{Se(this.filePath)&&(this.offsets=JSON.parse(Ve(this.filePath,"utf-8")))}catch{this.offsets={}}}get(e){return this.offsets[e]||0}set(e,t){this.offsets[e]!==t&&(this.offsets[e]=t,this.dirty=!0,this.saveTimer||(this.saveTimer=setTimeout(()=>this.flush(),500)))}flush(){if(this.saveTimer&&(clearTimeout(this.saveTimer),this.saveTimer=void 0),!!this.dirty)try{let e=Qe(this.filePath);Se(e)||Xe(e,{recursive:!0}),qe(this.filePath,JSON.stringify(this.offsets,null,2)),this.dirty=!1}catch{}}},ee=class{name="telegram";accounts;offsetStore;handler;polling=!1;log;constructor(e,t=console.error.bind(console,"[telegram]")){this.accounts=new Map(Object.entries(e)),this.log=t,this.groupStore=new Te(process.cwd()),this.offsetStore=new Me(process.cwd())}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[s,n]=e[t];this.log(`Starting polling for account "${s}" (${t+1}/${e.length})`);try{await this.apiCall(n.token,"deleteWebhook",{drop_pending_updates:!1}).catch(()=>{});let i=await this.apiCall(n.token,"getMe"),r=i.result?.id,o=i.result?.username;this.log(`Bot @${o} ready (account: ${s})`),r&&this.botInfo.set(s,{userId:r,username:o||s}),this.pollLoop(s,n)}catch(i){this.log(`Failed to verify bot for account "${s}": ${i.message}`)}t<e.length-1&&await new Promise(i=>setTimeout(i,300))}this.log(`All ${e.length} Telegram account(s) started`)}async stop(){this.polling=!1,this.offsetStore.flush();let e=Array.from(this.accounts.entries()).map(async([t,s])=>{try{await this.apiCall(s.token,"getUpdates",{offset:-1,timeout:0})}catch{}});await Promise.allSettled(e)}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 s=this.getTokenForAccount(t);if(s)return s}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 s=4096,n=e.text.length>s?e.text.slice(0,s-3)+"...":e.text,i=e.parseMode==="markdown"||e.parseMode===void 0?ve(n):n,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=n):e.parseMode==="plain"&&(delete r.parse_mode,r.text=n);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=n;let a=await this.apiCall(t,"sendMessage",r);return String(a.result?.message_id||"")}throw o}}async editMessage(e,t,s,n,i){let r=this.resolveToken(e,i);if(!r)return!1;let o=4096,a=s.length>o?s.slice(0,o-3)+"...":s,c=n!=="html"&&n!=="plain"?ve(a):a,l={chat_id:e,message_id:parseInt(t,10),text:c,parse_mode:"HTML"};n==="html"?(l.parse_mode="HTML",l.text=a):n==="plain"&&(delete l.parse_mode,l.text=a);try{return await this.apiCall(r,"editMessageText",l),!0}catch(h){if(h.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 react(e,t,s="\u{1F440}",n){let i=this.resolveToken(e,n);if(i)try{await this.apiCall(i,"setMessageReaction",{chat_id:e,message_id:parseInt(t,10),reaction:[{type:"emoji",emoji:s}]})}catch{}}async sendTyping(e,t){let s=this.resolveToken(e,t);if(s)try{await this.apiCall(s,"sendChatAction",{chat_id:e,action:"typing"})}catch{}}async pollLoop(e,t){let s=0;for(;this.polling;)try{let n=this.offsetStore.get(e)||0,r=(await this.apiCall(t.token,"getUpdates",{offset:n||void 0,timeout:30,allowed_updates:["message","my_chat_member"]})).result||[];for(let o of r){if(this.offsetStore.set(e,o.update_id+1),o.message&&this.handler){let a=o.message,c=a.text||a.caption||"",l,h=a.photo&&a.photo.length>0,g=!!a.voice,f=!!a.audio,u=!!a.video,y=!!a.document;if(h||g||f||u||y){let b,k="application/octet-stream";if(h?(b=a.photo[a.photo.length-1].file_id,k="image/jpeg",c||(c="[Photo attached \u2014 please describe what you see]")):g?(b=a.voice.file_id,k=a.voice.mime_type||"audio/ogg",c||(c="[Voice message \u2014 please transcribe and respond]")):f?(b=a.audio.file_id,k=a.audio.mime_type||"audio/mpeg",c||(c=`[Audio: ${a.audio.title||"audio file"}]`)):u?(b=a.video.file_id,k=a.video.mime_type||"video/mp4",c||(c="[Video attached]")):y&&(b=a.document.file_id,k=a.document.mime_type||"application/octet-stream",c||(c=`[Document: ${a.document.file_name||"file"}]`)),b)try{let C=(await this.apiCall(t.token,"getFile",{file_id:b})).result?.file_path;if(C){let P=`https://api.telegram.org/file/bot${t.token}/${C}`,S=await fetch(P);if(S.ok){let N=Buffer.from(await S.arrayBuffer()),R=k.split("/")[1]?.split(";")[0]||"bin",{mkdirSync:z,writeFileSync:K}=await import("fs"),{randomUUID:fe}=await import("crypto"),{resolve:M,join:D}=await import("path"),I=M(process.cwd(),".agentx/media/telegram");z(I,{recursive:!0});let j=a.document?.file_name||`${fe()}.${R}`,_=D(I,j);K(_,N),l={path:_,type:k,fileName:j}}}}catch(v){this.log(`Media download failed: ${v.message}`)}}if(!c)continue;let p=a.chat.type!=="private",w=String(a.chat.id),$;p&&(this.groupStore.hasGroup(w)||await this.seedGroupMembership(w,a.chat.title),$=await this.getChannelMeta(w));let A={id:String(a.message_id),channel:"telegram",accountId:e,sender:{id:String(a.from.id),name:[a.from.first_name,a.from.last_name].filter(Boolean).join(" "),username:a.from.username,isBot:a.from.is_bot===!0},group:p?{id:w,name:a.chat.title||""}:void 0,text:c,media:l,replyTo:a.reply_to_message?String(a.reply_to_message.message_id):void 0,replyToText:a.reply_to_message?a.reply_to_message.text||a.reply_to_message.caption||`[message from ${a.reply_to_message.from?.first_name||"unknown"}]`:void 0,timestamp:new Date(a.date*1e3),raw:o,channelMeta:$};this.chatAccountMap.set(String(a.chat.id),e),this.handler(A).catch(b=>{this.log(`Error handling message: ${b.message}`)})}if(o.my_chat_member){let a=o.my_chat_member,c=String(a.chat.id),l=a.chat.title,h=a.new_chat_member.status,g=a.new_chat_member.user.username||e;this.groupStore.setBotStatus(c,l,e,t.agentBinding,g,h),this.log(`Group membership: @${g} is now "${h}" in "${l||c}"`)}}s=0}catch(n){s++;let i=Math.min(5e3*Math.pow(2,s-1),6e4);this.log(`Poll error (${e}): ${n.message} [retry in ${i/1e3}s, errors: ${s}]`),await new Promise(r=>setTimeout(r,i))}}async getChannelMeta(e){let t=this.groupStore.getGroupBots(e);if(t.length)return{channel:"telegram",agents:t.map(s=>({id:s.agentId,name:s.username,handle:`@${s.username}`})),facts:[`${t.length} bot(s) verified in this group`]}}async seedGroupMembership(e,t){this.log(`Seeding group membership for "${t||e}"`);for(let[s,n]of this.accounts){let i=this.botInfo.get(s);if(i)try{let o=(await this.apiCall(n.token,"getChatMember",{chat_id:Number(e),user_id:i.userId})).result?.status;o&&this.groupStore.setBotStatus(e,t,s,n.agentBinding,i.username,o)}catch{this.groupStore.setBotStatus(e,t,s,n.agentBinding,i.username,"left")}}this.log(`Seeded: ${this.groupStore.getGroupBots(e).length} bot(s) in "${t||e}"`)}async apiCall(e,t,s){let n=`https://api.telegram.org/bot${e}/${t}`,i=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json"},body:s?JSON.stringify(s):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 Ze,writeFileSync as At}from"fs";import{resolve as et,join as Ct}from"path";import{randomUUID as Pt}from"crypto";var te=class{name="whatsapp";sessionDir;defaultAgent;allowFrom;routes;handler;sock=null;reconnectAttempts=0;generation=0;sentMessageIds=new Set;log;constructor(e,t=console.error.bind(console,"[whatsapp]")){this.sessionDir=et(e.sessionDir),this.defaultAgent=e.defaultAgent,this.allowFrom=e.allowFrom,this.routes=e.routes||[],this.log=t}resolveAgent(e,t,s){for(let n of this.routes){if(n.contact){let i=n.contact.replace(/\+/g,"");if(e.includes(i)||i.includes(e))return n.agent}if(n.group&&(t||s)){let i=n.group.toLowerCase();if(t?.toLowerCase().includes(i)||s?.toLowerCase().includes(i))return n.agent}}return this.defaultAgent}onMessage(e){this.handler=e}async start(){let e,t,s,n;try{n=await import("@whiskeysockets/baileys"),e=n.default||n.makeWASocket,t=n.useMultiFileAuthState,s=n.DisconnectReason}catch{this.log("WhatsApp requires @whiskeysockets/baileys. Install with:"),this.log(" npm install @whiskeysockets/baileys");return}Ze(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 n.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:n.makeCacheableSignalKeyStore?n.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"}`)}),this.sock.ev.on("connection.update",async l=>{let{connection:h,lastDisconnect:g,qr:f}=l;if(i===this.generation){if(f){this.log("Scan QR code with WhatsApp to connect:");try{let{default:u}=await import("qrcode-terminal");u.generate(f,{small:!0})}catch{this.log(`QR: ${f}`),this.log("Install qrcode-terminal for visual QR: npm install qrcode-terminal")}}if(h==="close"){let u=g?.error?.output?.statusCode,y=g?.error?.output?.payload?.message||"";if(this.log(`WhatsApp connection closed (status: ${u}, reason: ${y})`),u===s?.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 m=Math.min(1e4*Math.pow(2,this.reconnectAttempts-1),12e4);this.log(`Reconnecting in ${m/1e3}s (attempt ${this.reconnectAttempts}/5)...`),setTimeout(()=>this.start(),m)}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))}h==="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 h of l.messages||[]){let g=(h.key.remoteJid||"").replace(/@.*/,"").slice(-6),f=!!(h.message?.conversation||h.message?.extendedTextMessage?.text);if(this.log(`WA msg: from=${g} fromMe=${h.key.fromMe} hasText=${f} type=${Object.keys(h.message||{}).join(",")}`),h.key.remoteJid==="status@broadcast")continue;if(h.key.id&&this.sentMessageIds.has(h.key.id)){this.sentMessageIds.delete(h.key.id);continue}if(h.key.fromMe){let M=this.sock?.user,D=h.key.remoteJid||"",I=M?.id?.replace(/:.*/,"")||"",j=M?.lid?.replace(/:.*/,"")||"",_=D.replace(/:.*/,"").replace(/@.*/,"");if(!(_===I||_===j))continue}let u=h.message?.conversation||h.message?.extendedTextMessage?.text||h.message?.imageMessage?.caption||h.message?.videoMessage?.caption||"",y=h.message||{},m=!!y.imageMessage,p=!!y.audioMessage,w=!!y.videoMessage,$=!!y.documentMessage,A=!!y.stickerMessage,b=m||p||w||$||A;if(b&&!u&&(m?u="[Image attached \u2014 please describe what you see]":p?u="[Voice message attached \u2014 please transcribe and respond]":w?u="[Video attached]":$?u=`[Document: ${y.documentMessage?.fileName||"file"}]`:A&&(u="[Sticker]")),!u)continue;let k=h.key.remoteJid||"",v=k.endsWith("@g.us"),C=k.replace(/@.*$/,""),S=(v?h.key.participant||"":k).replace(/@.*$/,"");if(this.allowFrom?.length&&!h.key.fromMe&&!this.allowFrom.some(D=>{let I=D.replace(/\+/g,"");return S.includes(I)||C.includes(I)}))continue;let N=h.key.fromMe?"me":h.pushName||S,R;if(v&&this.sock)try{R=(await this.sock.groupMetadata(k)).subject}catch{}let z=this.resolveAgent(S,R,v?k:void 0);if(!z){this.log(`No route for ${v?`group ${R||k}`:S}, skipping`);continue}let K;if(b&&this.sock)try{let D=await(await import("@whiskeysockets/baileys")).downloadMediaMessage(h,"buffer",{},{reuploadRequest:this.sock.updateMediaMessage,logger:this.sock.logger});if(D){let I=y.imageMessage?.mimetype||y.audioMessage?.mimetype||"audio/ogg",j=I.split("/")[1]?.split(";")[0]||"bin",_=et(this.sessionDir,"../media/inbound");Ze(_,{recursive:!0});let ye=y.documentMessage?.fileName||`${Pt()}.${j}`,be=Ct(_,ye);At(be,D),K={path:be,type:I,fileName:ye},this.log(`WA media saved: ${I} -> ${be}`)}}catch(M){this.log(`WA media download failed: ${M.message}`)}let fe={id:h.key.id||String(Date.now()),channel:"whatsapp",accountId:"default",sender:{id:h.key.fromMe?(this.sock?.user?.id?.replace(/:.*/,"")||S)+"@s.whatsapp.net":S,name:N,username:S},group:v?{id:k,name:R||k}:void 0,text:u,media:K,replyTo:h.message?.extendedTextMessage?.contextInfo?.stanzaId,timestamp:new Date((h.messageTimestamp||0)*1e3),raw:h,resolvedAgent:z};this.handler(fe).catch(M=>{this.log(`Error handling message: ${M.message}`)})}})}async stop(){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 n=(await this.sock.sendMessage(t,{text:e.text}))?.key?.id||"";return n&&this.sentMessageIds.add(n),n}catch(s){return this.log(`Send error: ${s.message}`),""}}async editMessage(e,t,s){if(!this.sock)return!1;let n=e.includes("@")?e:`${e}@s.whatsapp.net`;try{return await this.sock.sendMessage(n,{text:s,edit:{remoteJid:n,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,s="\u{1F440}"){if(!this.sock)return;let n=e.includes("@")?e:`${e}@s.whatsapp.net`;try{await this.sock.sendMessage(n,{react:{text:s,key:{remoteJid:n,id:t}}})}catch{}}};import{writeFileSync as tt,mkdirSync as Ie,existsSync as se}from"fs";import{resolve as G}from"path";function st(d,e){if(!e)return d;let t=e*4;return`${d}
34
+
35
+ [Response budget]
36
+ Keep your response under ~${e} tokens (~${t} chars). Be concise \u2014 this is automated batch work, not a conversation.`}function H(d,e,t){let s=[];for(let n of d.split(","))if(n==="*")for(let i=e;i<=t;i++)s.push(i);else if(n.includes("/")){let[i,r]=n.split("/"),o=parseInt(r,10),a=i==="*"?e:parseInt(i,10);for(let c=a;c<=t;c+=o)s.push(c)}else if(n.includes("-")){let[i,r]=n.split("-").map(Number);for(let o=i;o<=r;o++)s.push(o)}else s.push(parseInt(n,10));return[...new Set(s)].sort((n,i)=>n-i)}function nt(d,e,t){let s=d.trim().split(/\s+/);if(s.length!==5)throw new Error(`Invalid cron: ${d}`);let n=H(s[0],0,59),i=H(s[1],0,23),r=H(s[2],1,31),o=H(s[3],1,12),a=H(s[4],0,6),c=new Intl.DateTimeFormat("en-US",{timeZone:t,year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",hour12:!1}),l=new Date(e.getTime()+6e4);l.setSeconds(0,0);let h=new Date(l.getTime()+366*24*60*60*1e3);for(;l<h;){let g=c.formatToParts(l),f=$=>parseInt(g.find(A=>A.type===$)?.value||"0",10),u=f("minute"),y=f("hour"),m=f("day"),p=f("month"),w=l.getDay();if(n.includes(u)&&i.includes(y)&&r.includes(m)&&o.includes(p)&&a.includes(w))return l;l.setTime(l.getTime()+6e4)}throw new Error(`No next run found for cron "${d}" within 1 year`)}var Ae=5,Ce=[3e4,6e4,3e5,9e5,36e5],U=class{jobs=new Map;timers=new Map;registry;hooks;runsDir;lastRunFile;running=!1;notifyCallback;log;constructor(e,t,s,n=console.error.bind(console,"[cron]")){this.registry=t,this.hooks=s,this.log=n,this.runsDir=G(process.cwd(),".agentx/cron/runs"),this.lastRunFile=G(process.cwd(),".agentx/cron/last-runs.json");for(let[i,r]of Object.entries(e.crons))this.jobs.set(i,{id:i,enabled:r.enabled,schedule:r.schedule,timezone:r.timezone,agent:r.agent,prompt:r.prompt,timeout:r.timeout,model:r.model,maxOutputTokens:r.maxOutputTokens,onError:r.onError,consecutiveErrors:0,totalRuns:0,totalFailures:0})}setNotifyCallback(e){this.notifyCallback=e}async start(){this.running=!0,se(this.runsDir)||Ie(this.runsDir,{recursive:!0});let e=this.detectMissedRuns();if(e.length>0){this.log(`Detected ${e.length} missed cron run(s) while daemon was down:`);for(let{jobId:t,missedAt:s}of e)this.log(` "${t}" should have run at ${s.toISOString()}`);this.executeMissedRuns(e).catch(t=>{this.log(`Error executing missed runs: ${t.message}`)})}for(let[t,s]of this.jobs){if(!s.enabled){this.log(`Job "${t}" is disabled, skipping`);continue}this.scheduleNext(t)}this.log(`${this.jobs.size} cron job(s) loaded, ${Array.from(this.jobs.values()).filter(t=>t.enabled).length} enabled`)}async stop(){this.running=!1;for(let e of this.timers.values())clearTimeout(e);this.timers.clear(),this.saveLastRuns()}scheduleNext(e){let t=this.jobs.get(e);if(!t||!t.enabled||!this.running)return;let s=this.timers.get(e);s&&clearTimeout(s);try{let n=nt(t.schedule,new Date,t.timezone);t.nextRun=n,t.retryPending=!1;let i=n.getTime()-Date.now();this.log(`Job "${e}" next run: ${n.toISOString()} (in ${Math.round(i/1e3)}s)`);let r=setTimeout(()=>this.executeJob(e),i);this.timers.set(e,r)}catch(n){this.log(`Failed to schedule "${e}": ${n.message}`)}}scheduleRetry(e,t){let s=this.jobs.get(e);if(!s||!s.enabled||!this.running)return;if(t>=Ae){this.log(`Job "${e}" exhausted all ${Ae} retries, scheduling next regular run`),this.scheduleNext(e);return}let n=Ce[t]||Ce[Ce.length-1];s.retryPending=!0,this.log(`Job "${e}" retry ${t+1}/${Ae} in ${Math.round(n/1e3)}s`);let i=setTimeout(()=>this.executeJob(e,t+1),n);this.timers.set(e,i)}async executeJob(e,t=0){let s=this.jobs.get(e);if(!s||!this.running)return;let n=t>0;if(this.hooks?.has("pre:cron-run")){let r=await this.hooks.execute("pre:cron-run",{event:"pre:cron-run",jobId:e,agent:s.agent,prompt:s.prompt});if(r.blocked){this.log(`Job "${e}" blocked by hook: ${r.message}`),this.scheduleNext(e);return}}this.log(`${n?`[retry ${t}] `:""}Executing job "${e}" -> agent "${s.agent}"`);let i=new Date;s.lastRun=i,s.totalRuns++;try{let r=await this.registry.execute({message:st(s.prompt,s.maxOutputTokens),agentId:s.agent,model:s.model,context:{channel:"cron"}}),o={jobId:e,startedAt:i,completedAt:new Date,success:!r.error,response:r.content,error:r.error,duration:r.duration||Date.now()-i.getTime(),isRetry:n,retryAttempt:t};if(r.error){if(s.consecutiveErrors++,s.totalFailures++,s.lastError=r.error,this.log(`Job "${e}" failed (${s.consecutiveErrors} consecutive): ${r.error}`),await this.notifyFailure(s,r.error),s.onError.includes("disable")&&s.consecutiveErrors>=3){s.enabled=!1,this.log(`Job "${e}" disabled after ${s.consecutiveErrors} consecutive errors`);try{let a=await Fe(c=>Ge(c,`crons.${e}.enabled`,!1),{reload:!1});a.success||this.log(`Failed to persist disable for "${e}": ${a.error}`)}catch(a){this.log(`Failed to persist disable for "${e}": ${a.message}`)}await this.notifyDisabled(s)}this.logRun(o),this.scheduleRetry(e,t);return}else s.consecutiveErrors>0&&this.log(`Job "${e}" recovered after ${s.consecutiveErrors} failure(s)`),s.consecutiveErrors=0,s.lastSuccess=new Date,s.lastError=void 0,s.retryPending=!1,this.log(`Job "${e}" completed in ${o.duration}ms`);this.logRun(o),this.hooks?.has("post:cron-run")&&await this.hooks.execute("post:cron-run",{event:"post:cron-run",jobId:e,success:o.success,duration:o.duration,error:o.error?new Error(o.error):void 0})}catch(r){s.consecutiveErrors++,s.totalFailures++,s.lastError=r.message,this.log(`Job "${e}" threw: ${r.message}`),await this.notifyFailure(s,r.message),this.scheduleRetry(e,t);return}this.saveLastRuns(),this.scheduleNext(e)}detectMissedRuns(){let e=[],t=this.loadLastRuns();if(!t)return e;for(let[s,n]of this.jobs){if(!n.enabled)continue;let i=t[s];if(!i)continue;let r=new Date(i);try{let o=nt(n.schedule,r,n.timezone);o.getTime()<Date.now()&&e.push({jobId:s,missedAt:o})}catch{}}return e}async executeMissedRuns(e){for(let{jobId:t,missedAt:s}of e){let n=this.jobs.get(t);if(!(!n||!n.enabled)){this.log(`Running missed job "${t}" (was due at ${s.toISOString()})`);try{let i=await this.registry.execute({message:st(`[MISSED RUN \u2014 was scheduled for ${s.toISOString()}]
37
+
38
+ ${n.prompt}`,n.maxOutputTokens),agentId:n.agent,context:{channel:"cron"}}),r={jobId:t,startedAt:new Date,completedAt:new Date,success:!i.error,response:i.content,error:i.error,duration:i.duration||0,isRetry:!1};this.logRun(r),i.error?this.log(`Missed job "${t}" failed: ${i.error}`):(this.log(`Missed job "${t}" completed`),n.lastSuccess=new Date),n.lastRun=new Date,n.totalRuns++}catch(i){this.log(`Missed job "${t}" threw: ${i.message}`)}}}this.saveLastRuns()}async notifyFailure(e,t){if(!(!e.onError.includes("notify")&&e.consecutiveErrors<2)){if(!this.notifyCallback){this.log(`[ALERT] Cron "${e.id}" failed (${e.consecutiveErrors}x): ${t.slice(0,200)}`);return}try{await this.notifyCallback(e.id,e.agent,t,e.consecutiveErrors)}catch{}}}async notifyDisabled(e){if(!this.notifyCallback){this.log(`[ALERT] Cron "${e.id}" has been DISABLED after ${e.consecutiveErrors} consecutive failures`);return}try{await this.notifyCallback(e.id,e.agent,`AUTO-DISABLED after ${e.consecutiveErrors} consecutive failures. Last error: ${e.lastError||"unknown"}`,e.consecutiveErrors)}catch{}}saveLastRuns(){try{let e={};for(let[s,n]of this.jobs)n.lastRun&&(e[s]=n.lastRun.toISOString());let t=G(process.cwd(),".agentx/cron");se(t)||Ie(t,{recursive:!0}),tt(this.lastRunFile,JSON.stringify(e,null,2))}catch{}}loadLastRuns(){try{if(!se(this.lastRunFile))return null;let{readFileSync:e}=lt("fs");return JSON.parse(e(this.lastRunFile,"utf-8"))}catch{return null}}logRun(e){try{let t=G(this.runsDir,e.jobId);se(t)||Ie(t,{recursive:!0});let s=`${e.startedAt.toISOString().replace(/[:.]/g,"-")}.json`;tt(G(t,s),JSON.stringify(e,null,2))}catch{}}list(){return Array.from(this.jobs.values())}health(){let e=Array.from(this.jobs.values()),t=this.detectMissedRuns();return{healthy:e.filter(s=>s.enabled&&s.consecutiveErrors===0).length,failing:e.filter(s=>s.enabled&&s.consecutiveErrors>0).length,disabled:e.filter(s=>!s.enabled).length,missed:t.length,jobs:e.map(s=>({id:s.id,status:s.enabled?s.retryPending?"retrying":s.consecutiveErrors>0?"failing":"healthy":"disabled",consecutiveErrors:s.consecutiveErrors,lastError:s.lastError}))}}};import{createServer as jt}from"http";import{writeFileSync as Lt,existsSync as at,unlinkSync as Nt,mkdirSync as Bt,watch as Ft}from"fs";import{resolve as Ee,dirname as Gt}from"path";var ne=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:s}=e;this.client=new t({intents:[s.Guilds,s.GuildMessages,s.MessageContent,s.DirectMessages]}),this.client.on("ready",()=>{this.log(`Discord connected as ${this.client.user?.tag}`)}),this.client.on("messageCreate",async n=>{if(!this.handler||n.author.bot)return;let i=n.mentions.users.has(this.client.user?.id),r=!n.guild;if(!i&&!r)return;let o=n.content;if(this.client.user&&(o=o.replace(new RegExp(`<@!?${this.client.user.id}>`,"g"),"").trim()),!o)return;let a={id:n.id,channel:"discord",accountId:"default",sender:{id:n.author.id,name:n.author.displayName||n.author.username,username:n.author.username},group:n.guild?{id:n.channelId,name:n.channel?.name||n.channelId}:void 0,text:o,replyTo:n.reference?.messageId,timestamp:n.createdAt,raw:n};this.handler(a).catch(c=>{this.log(`Error handling message: ${c.message}`)})});try{await this.client.login(this.token)}catch(n){this.log(`Discord login failed: ${n.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,s){if(!this.client)return!1;try{let n=await this.client.channels.fetch(e);return n?.isTextBased()?(await(await n.messages.fetch(t)).edit(s),!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,s="\u{1F440}"){if(this.client)try{let n=await this.client.channels.fetch(e);if(!n?.isTextBased())return;await(await n.messages.fetch(t)).react(s)}catch{}}};import{createServer as xt}from"http";var ie=class{name="gitlab";config;handler;server;botUsername;botUsernames=new Set;sentNoteIds=new Set;usernameToAgent=new Map;agentToUsername=new Map;log;hooks;reactForwarder;sendNoteForwarder;logTimeForwarder;constructor(e,t=console.error.bind(console,"[gitlab]"),s){this.config=e,this.log=t,this.hooks=s}setReactForwarder(e){this.reactForwarder=e}setSendNoteForwarder(e){this.sendNoteForwarder=e}setLogTimeForwarder(e){this.logTimeForwarder=e}onMessage(e){this.handler=e}async start(){let e=[];this.config.token&&e.push({label:"global",token:this.config.token});for(let s of this.config.agentMappings||[])s.token&&e.push({label:s.agentId,token:s.token});let t=await Promise.allSettled(e.map(async({label:s,token:n})=>{let i=await fetch(`${this.config.host}/api/v4/user`,{headers:{"PRIVATE-TOKEN":n}});if(!i.ok)throw new Error(`${i.status}`);let r=await i.json();return{label:s,username:r.username}}));for(let s of t)if(s.status==="fulfilled"){let{label:n,username:i}=s.value;this.botUsernames.add(i),n==="global"?(this.botUsername=i,this.log(`Global bot user: ${i}`)):(this.usernameToAgent.set(i.toLowerCase(),n),this.agentToUsername.set(n,i.toLowerCase()),this.log(`Agent "${n}" -> GitLab user @${i}`))}for(let s of this.config.agentMappings||[]){for(let n of s.gitlabUsernames)this.botUsernames.add(n),(!this.usernameToAgent.has(n.toLowerCase())||s.node)&&this.usernameToAgent.set(n.toLowerCase(),s.agentId);!this.agentToUsername.has(s.agentId)&&s.gitlabUsernames.length>0&&this.agentToUsername.set(s.agentId,s.gitlabUsernames[0].toLowerCase())}this.log(`Bot users (${this.botUsernames.size}): ${[...this.botUsernames].join(", ")}`),this.log(`Username->Agent map: ${[...this.usernameToAgent.entries()].map(([s,n])=>`@${s}->${n}`).join(", ")}`),this.server=xt(async(s,n)=>{s.method==="POST"?await this.handleWebhook(s,n):(n.writeHead(200,{"Content-Type":"text/plain"}),n.end("GitLab webhook endpoint. POST events here."))}),this.server.on("error",s=>{s.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: ${s.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 s=t.pop(),n=t.pop(),i=t.join(":"),r=encodeURIComponent(i),o;switch(n){case"issue":o=`${this.config.host}/api/v4/projects/${r}/issues/${s}/notes`;break;case"merge_request":o=`${this.config.host}/api/v4/projects/${r}/merge_requests/${s}/notes`;break;default:return this.log(`Unsupported noteable type: ${n}`),""}let a=this.config.agentMappings?.find(h=>h.agentId===e.agentId),c=this.getAgentToken(e.agentId);if(!c&&a?.node&&this.sendNoteForwarder)try{let h=`${e.text}
39
+
40
+ <!-- agentx:${e.agentId||"unknown"} -->`,g=await this.sendNoteForwarder(a.node,i,n,s,e.agentId||"",h);return g&&this.sentNoteIds.add(g),g}catch(h){return this.log(`GitLab send forward to "${a.node}" failed: ${h.message} \u2014 skipping (would post as group bot)`),""}let l=c||this.config.token;O.webhook("gitlab","send",`agentId="${e.agentId}" token=${c?"per-agent":"GLOBAL("+this.botUsername+")"}`);try{let h=await fetch(o,{method:"POST",headers:{"Content-Type":"application/json","PRIVATE-TOKEN":l},body:JSON.stringify({body:`${e.text}
41
+
42
+ <!-- agentx:${e.agentId||"unknown"} -->`})});if(!h.ok){let u=await h.text();return this.log(`GitLab API error: ${h.status} ${u}`),""}let g=await h.json(),f=String(g.id||"");return f&&this.sentNoteIds.add(f),f}catch(h){return this.log(`GitLab send error: ${h.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 n=await this.readBody(e),i=n.object_kind||n.event_type||"unknown";switch(this.log(`Event: ${i} from ${n.project?.path_with_namespace||"unknown"}`),i){case"note":await this.handleNote(n,t);break;case"issue":await this.handleIssue(n,t);break;case"merge_request":await this.handleMR(n,t);break;case"pipeline":await this.handlePipeline(n,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 s=e.object_attributes.note,n=e.project.path_with_namespace,i=e.user,r=String(e.object_attributes.id),o=s.match(/<!-- agentx:(\S+) -->/);if(o){let k=o[1],v=s.match(/@(\w[\w.-]*)/g)?.map(P=>P.slice(1).replace(/[.]+$/,""))||[];if(!v.some(P=>{let S=this.usernameToAgent.get(P.toLowerCase());return S&&S!==k})){this.log(`AgentX comment from ${k}, no cross-agent mention, skipping (note ${r})`),t.writeHead(200),t.end("ok");return}this.log(`Bot-to-bot handoff: ${k} \u2192 ${v.join(", ")} (note ${r})`)}if(this.sentNoteIds.has(r)){this.sentNoteIds.delete(r),t.writeHead(200),t.end("ok");return}let a=s.match(/@(\w[\w.-]*)/g)?.map(k=>k.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="",h="";e.issue?(c="issue",l=String(e.issue.iid),h=e.issue.title):e.merge_request&&(c="merge_request",l=String(e.merge_request.iid),h=e.merge_request.title),O.webhook("gitlab","handleNote",`noteId=${r} mentions=[${a.join(",")}] user=${i.username}`);let g=this.resolveAgentFromMention(s);if(O.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 f=g,u=this.config.agentMappings?.find(k=>k.agentId===f),y=u?.token,m=u?.node;O.webhook("gitlab","token",`agent="${f}" mapping=${u?"found":"MISSING"} hasToken=${!!y} node=${m??"local"}`),this.reactToNote(n,c,l,e.object_attributes.id,y,m,f).catch(()=>{});let p=`${n}:${c}:${l}`,w=await this.getChannelMeta(p),$=s.replace(/\n*<!-- agentx:\S+ -->/g,""),A=await this.downloadNoteImages($,n,y||this.config.token),b={id:String(e.object_attributes.id),channel:"gitlab",accountId:"default",sender:{id:p,name:i.name,username:i.username},text:`[GitLab ${c} #${l}: ${h}]
43
+ ${i.name} commented:
44
+ ${$}`,timestamp:new Date,raw:e,resolvedAgent:f,preferNode:u?.node,channelMeta:w?{...w,issue:{type:c,iid:l,title:h}}:void 0,media:A};this.handler(b).catch(k=>{this.log(`Error handling note: ${k.message}`)}),t.writeHead(200),t.end("ok")}async handleIssue(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 s=e.object_attributes,n=e.project.path_with_namespace,i=this.resolveAgent(n),r,o=!1;if(this.hooks?.has("on:gitlab-issue"))try{let c=await this.hooks.execute("on:gitlab-issue",{event:"on:gitlab-issue",issueEvent:e,project:n,iid:s.iid,title:s.title,description:s.description||"",url:s.url,action:s.action,usernameToAgent:Object.fromEntries(this.usernameToAgent.entries()),agentMappings:this.config.agentMappings||[],defaultAgentId:i||null});c.blocked&&(o=!0);let l=c.modified?.dispatch;Array.isArray(l)&&l.length>0&&(r=l.filter(h=>h&&typeof h.agentId=="string"))}catch(c){this.log(`on:gitlab-issue hook error: ${c.message}`)}if(r&&r.length>0){for(let c of r){let l=this.config.agentMappings?.find(y=>y.agentId===c.agentId),h=`${n}:issue:${s.iid}`,g=await this.getChannelMeta(h),u={id:`issue-hook-${s.iid}-${c.agentId}${c.assignee?`-${c.assignee}`:""}-${s.action}`,channel:"gitlab",accountId:"default",sender:{id:h,name:e.user.name,username:e.user.username},text:c.prompt||`[GitLab Issue #${s.iid} ${s.action}]: ${s.title}
45
+ ${s.description?.slice(0,500)||""}
46
+ URL: ${s.url}`,timestamp:new Date,raw:e,resolvedAgent:c.agentId,preferNode:c.preferNode||l?.node,channelMeta:g?{...g,issue:{type:"issue",iid:String(s.iid),title:s.title}}:void 0};this.log(`Issue #${s.iid} hook dispatch -> agent "${c.agentId}"${c.preferNode||l?.node?` (remote: ${c.preferNode||l?.node})`:""}`),this.handler(u).catch(y=>this.log(`Error handling hook dispatch: ${y.message}`))}t.writeHead(200),t.end("ok");return}if(o){this.log(`Issue #${s.iid}: on:gitlab-issue hook suppressed default dispatch`),t.writeHead(200),t.end("ok");return}let a={id:`issue-${s.iid}-${s.action}`,channel:"gitlab",accountId:"default",sender:{id:`${n}:issue:${s.iid}`,name:e.user.name,username:e.user.username},text:`[GitLab Issue #${s.iid} ${s.action}]: ${s.title}
47
+ ${s.description?.slice(0,500)||""}
48
+ URL: ${s.url}`,timestamp:new Date,raw:e,resolvedAgent:i};this.handler(a).catch(c=>this.log(`Error handling issue: ${c.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 s=e.object_attributes,n=e.project.path_with_namespace,i=this.resolveAgent(n),r={id:`mr-${s.iid}-${s.action}`,channel:"gitlab",accountId:"default",sender:{id:`${n}:merge_request:${s.iid}`,name:e.user.name,username:e.user.username},text:`[GitLab MR !${s.iid} ${s.action}]: ${s.title}
49
+ Branch: ${s.source_branch} -> ${s.target_branch}
50
+ ${s.description?.slice(0,500)||""}
51
+ URL: ${s.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 s=e.object_attributes,n=e.project.path_with_namespace;if(["success","failed","canceled"].includes(s.status)&&this.hooks?.has("on:gitlab-pipeline")&&this.hooks.execute("on:gitlab-pipeline",{event:"on:gitlab-pipeline",pipelineId:s.id,status:s.status,ref:s.ref,duration:s.duration,project:n,projectId:e.project?.id,raw:e}).catch(a=>this.log(`on:gitlab-pipeline hook error: ${a.message}`)),!this.handler||s.status!=="failed"){t.writeHead(200),t.end("ok");return}let r=this.resolveAgent(n),o={id:`pipeline-${s.id}`,channel:"gitlab",accountId:"default",sender:{id:`${n}:pipeline:${s.id}`,name:e.user.name,username:e.user.username},text:`[GitLab Pipeline FAILED] Project: ${n}
52
+ Ref: ${s.ref}
53
+ Duration: ${s.duration}s
54
+ 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(s=>s.slice(1).replace(/[.]+$/,"").toLowerCase())||[];if(t.length!==0)for(let s of t){let n=this.usernameToAgent.get(s);if(n)return this.log(`Mention @${s} -> agent "${n}" (resolved from token)`),n}}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(":"),s=t[0],n=t[1],i=t[2],r=[];for(let a of this.config.routes)(a.project===s||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:s,issue:n&&i?{type:n,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(s=>s.agentId===e)?.token}getTokenForMentionedAgent(e){if(!this.config.agentMappings?.length)return;let t=e.match(/@(\w[\w.-]*)/g)?.map(s=>s.slice(1).replace(/[.]+$/,"").toLowerCase())||[];for(let s of this.config.agentMappings)if(s.token&&s.gitlabUsernames.some(n=>t.includes(n.toLowerCase())))return s.token}async reactToNote(e,t,s,n,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,s,n,o);return}if(!i){this.log(`No token for reaction on note ${n} (agent "${o}") \u2014 skipping`);return}let a=encodeURIComponent(e),c;switch(t){case"issue":c=`${this.config.host}/api/v4/projects/${a}/issues/${s}/notes/${n}/award_emoji`;break;case"merge_request":c=`${this.config.host}/api/v4/projects/${a}/merge_requests/${s}/notes/${n}/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 ${n}: ${l.status}`)}catch(l){this.log(`Failed to react to note ${n}: ${l.message}`)}}async logTimeSpent(e,t,s){let n=e.split(":");if(n.length<3)return;let i=n.pop(),r=n.pop(),o=n.join(":"),a=Math.max(60,Math.round(t/1e3)),c=Math.floor(a/3600),l=Math.ceil(a%3600/60),h=c>0?`${c}h${l>0?` ${l}m`:""}`:`${l}m`,g=encodeURIComponent(o),f=r==="merge_request"?"merge_requests":"issues",u=`${this.config.host}/api/v4/projects/${g}/${f}/${i}/add_spent_time`,y=this.config.agentMappings?.find(w=>w.agentId===s),m=this.getAgentToken(s);if(!m&&y?.node&&this.logTimeForwarder){try{await this.logTimeForwarder(y.node,o,r,i,s||"",t),this.log(`Time logged (via peer "${y.node}"): ${h} on ${o} ${f}/${i} (${s})`)}catch(w){this.log(`Time log forward to "${y.node}" failed: ${w.message} \u2014 skipping (would log as group bot)`)}return}let p=m||this.config.token;try{let w=await fetch(u,{method:"POST",headers:{"Content-Type":"application/json","PRIVATE-TOKEN":p},body:JSON.stringify({duration:h})});if(w.ok)this.log(`Time logged: ${h} on ${o} ${r} #${i} (${s||"global"})`);else{let $=await w.text();this.log(`Time log failed (${w.status}): ${$.slice(0,100)}`)}}catch(w){this.log(`Time log error: ${w.message}`)}}async downloadNoteImages(e,t,s){let n=/!\[[^\]]*\]\(([^)]+\.(?:png|jpg|jpeg|gif|webp|svg))\)/gi,i=[...e.matchAll(n)];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":s}});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",h=l.split("/")[1]?.split(";")[0]||"png",{mkdirSync:g,writeFileSync:f}=await import("fs"),{resolve:u}=await import("path"),{randomUUID:y}=await import("crypto"),m=u(process.cwd(),".agentx/media/gitlab");g(m,{recursive:!0});let p=`${y().slice(0,8)}.${h}`,w=u(m,p);return f(w,c),this.log(`Downloaded GitLab image: ${w} (${c.length} bytes)`),{path:w,type:l,fileName:r.split("/").pop()||p}}catch(a){this.log(`Image download error: ${a.message}`);return}}async readBody(e){return new Promise(t=>{let s="";e.on("data",n=>s+=n.toString()),e.on("end",()=>{try{t(s?JSON.parse(s):{})}catch{t({raw:s})}}),e.on("error",()=>t({}))})}};var J=class{module;minLevel;constructor(e,t="info"){this.module=e,this.minLevel=t}child(e){return new J(`${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 s=e.map(n=>typeof n=="string"?n:JSON.stringify(n)).join(" ").replace(/^\[agentx\]\s*/,"");s&&this.info(s)}}emit(e,t,s){if(J.levelOrder[e]<J.levelOrder[this.minLevel])return;let n={time:new Date().toISOString(),level:e,module:this.module,msg:t,...s},i=e==="error"?"ERROR":e==="warn"?"WARN":"",r=`[${this.module}]`,o=i?`${r} ${i}: ${t}`:`${r} ${t}`;console.error(o)}},W=J;V(W,"levelOrder",{debug:0,info:1,warn:2,error:3});var re=class{registry;config;log;constructor(e,t={},s=console.error.bind(console,"[webhook]")){this.registry=e,this.config=t,this.log=s}async handle(e,t,s){let n=s.replace(/^\/webhook\/?/,"").split("/").filter(Boolean),i=n[0],r=n[1];if(!i){this.sendJson(t,400,{error:"Missing agent ID. Use /webhook/:agentId"});return}let o=await this.readBody(e),a=r||this.detectSource(e.headers),c=this.buildSummary(a,o,e.headers);this.log(`Webhook [${a}] -> ${i}: ${c.slice(0,100)}`);try{let l=await this.registry.execute({message:c,agentId:i,context:{channel:`webhook:${a}`,sender:`webhook:${a}`}});this.sendJson(t,l.error?500:200,{ok:!l.error,agent:i,source:a,response:l.content?.slice(0,500),error:l.error,duration:l.duration})}catch(l){this.sendJson(t,500,{error:l.message})}}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,s){let n=[`[Webhook from ${e}]`];switch(e){case"gitlab":{let r=s["x-gitlab-event"]||t.object_kind||"event",o=t.project?.path_with_namespace||"",a=t.user?.name||t.user_username||"";if(n.push(`Event: ${r}`),o&&n.push(`Project: ${o}`),a&&n.push(`User: ${a}`),t.ref&&n.push(`Ref: ${t.ref}`),t.commits&&Array.isArray(t.commits)){n.push(`Commits: ${t.commits.length}`);for(let l of t.commits.slice(0,3))n.push(` - ${l.message?.split(`
55
+ `)[0]||"no message"} (${l.author?.name||""})`)}let c=t.object_attributes;if(c?.title&&(n.push(`Title: ${c.title}`),n.push(`State: ${c.state||""}`),n.push(`Action: ${c.action||""}`),c.source_branch&&n.push(`Branch: ${c.source_branch} -> ${c.target_branch}`),c.url&&n.push(`URL: ${c.url}`)),c?.iid&&!c?.source_branch&&(n.push(`Issue #${c.iid}: ${c.title||""}`),c.description&&n.push(`Description: ${c.description.slice(0,200)}`)),t.object_kind==="pipeline"){let l=t.object_attributes;n.push(`Pipeline: ${l?.status||""} (${l?.ref||""})`),n.push(`Duration: ${l?.duration||0}s`)}break}case"github":{let r=s["x-github-event"]||"event",o=t.repository?.full_name||"",a=t.sender?.login||"";if(n.push(`Event: ${r}`),o&&n.push(`Repository: ${o}`),a&&n.push(`Sender: ${a}`),t.ref&&n.push(`Ref: ${t.ref}`),t.commits&&Array.isArray(t.commits))for(let h of t.commits.slice(0,3))n.push(` - ${h.message?.split(`
56
+ `)[0]||""} (${h.author?.name||""})`);let c=t.pull_request;c&&(n.push(`PR #${c.number}: ${c.title}`),n.push(`Action: ${t.action}`),n.push(`Branch: ${c.head?.ref} -> ${c.base?.ref}`));let l=t.issue;l&&(n.push(`Issue #${l.number}: ${l.title}`),n.push(`Action: ${t.action}`));break}case"stripe":{let r=t.type||"event",o=t.data?.object||{};n.push(`Event: ${r}`),o.amount&&n.push(`Amount: ${(o.amount/100).toFixed(2)} ${o.currency?.toUpperCase()||""}`),o.customer_email&&n.push(`Customer: ${o.customer_email}`),o.description&&n.push(`Description: ${o.description}`),o.status&&n.push(`Status: ${o.status}`);break}case"sentry":{let r=s["sentry-hook-resource"]||"event";n.push(`Resource: ${r}`);let o=t.data||t;o.error?.title&&n.push(`Error: ${o.error.title}`),o.error?.culprit&&n.push(`Culprit: ${o.error.culprit}`),o.error?.metadata?.value&&n.push(`Message: ${o.error.metadata.value}`),t.url&&n.push(`URL: ${t.url}`);break}default:n.push(`Headers: ${JSON.stringify(Object.keys(s).filter(r=>r.startsWith("x-")).slice(0,5))}`),n.push(`Payload keys: ${Object.keys(t).slice(0,10).join(", ")}`);let i=JSON.stringify(t).slice(0,500);n.push(`Body: ${i}`)}return n.join(`
57
+ `)}async readBody(e){return new Promise(t=>{let s="";e.on("data",n=>s+=n.toString()),e.on("end",()=>{try{t(s?JSON.parse(s):{})}catch{t({raw:s})}}),e.on("error",()=>t({}))})}sendJson(e,t,s){e.writeHead(t,{"Content-Type":"application/json"}),e.end(JSON.stringify(s,null,2))}};var oe=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,s)=>t.peer.localeCompare(s.peer)).map(t=>`${t.peer}:${t.skills.map(s=>s.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]"],s=this.config.agents[e];t.push(`Node: ${this.config.node.name} (${this.config.node.id})`);let n=this.getPrimaryHandle(e),i=this.extractCapability(s),r=n?` (${n})`:"";t.push(`You: ${s.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,f]of o){let u=this.getPrimaryHandle(g),y=this.extractCapability(f),m=u?` (${u})`:"";t.push(`\u2022 ${f.name}${m} \u2014 ${y}`)}let a=this.meshPeers.filter(g=>g.skills.length>0);if(a.length){t.push("Remote:");let g=[...a].sort((f,u)=>f.peer.localeCompare(u.peer));for(let f of g){let u=[...f.skills].sort((y,m)=>y.id.localeCompare(m.id));for(let y of u)t.push(`\u2022 ${y.name} [${f.peer}] \u2014 ${y.description.slice(0,60)}`)}}t.push(""),t.push(this.renderChannels()),t.push(""),t.push("[Rules]");let c=[n,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(":"),h=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:${h}/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("[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(`
58
+ `)}renderChannels(){let e=[],t=this.config.channels;if(t.telegram.enabled){let s=t.telegram.policy;e.push(`telegram(groups:${s.group}, DMs:${s.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(s=>s.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}};var ae=class{registry;memoryStore;states=new Map;log;constructor(e,t=console.error.bind(console,"[heartbeat]"),s){this.registry=e,this.log=t,this.memoryStore=s}register(e,t){if(!t.enabled)return;this.stop(e);let s={agentId:e,config:t,runCount:0,errors:0},n=(t.intervalMinutes||30)*60*1e3;s.timer=setInterval(()=>{this.runHeartbeat(s).catch(i=>{this.log(`[${e}] heartbeat error: ${i.message}`),s.errors++})},n),this.states.set(e,s),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:s}=e;this.log(`[${t}] heartbeat running...`),e.lastRun=new Date,e.runCount++;let n=s.channel||"heartbeat";try{let i=s.prompt;if(this.memoryStore){let o=this.memoryStore.buildRecallContext(t);o&&(i=`${o}
59
+
60
+ ${i}`)}let r=await this.registry.execute({message:i,agentId:t,context:{channel:n,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 ce=class{services=[];log;constructor(e,t=console.error.bind(console,"[services]")){this.log=t;for(let[s,n]of Object.entries(e)){let i={def:{...n,id:s},patterns:[]};for(let r of n.triggers)try{i.patterns.push({regex:new RegExp(r.pattern,"i"),channel:r.channel})}catch(o){this.log(`Invalid regex in service "${s}": ${r.pattern} \u2014 ${o.message}`)}i.patterns.length>0&&this.services.push(i)}this.log(`${this.services.length} service(s) loaded`)}match(e,t,s){for(let n of this.services){if(n.def.allowedContacts?.length){let i=t.replace(/[^0-9]/g,"");if(!n.def.allowedContacts.some(o=>{let a=o.replace(/[^0-9]/g,"");return i.includes(a)||a.includes(i)}))continue}for(let i of n.patterns){if(i.channel&&i.channel!==s)continue;let r=e.match(i.regex);if(r)return O.cat("agent",`Service match: "${n.def.name}" (trigger: ${i.regex.source})`),{service:n.def,trigger:i.regex.source,captured:r[0]}}}return null}async execute(e,t,s,n){this.log(`Executing service "${e.name}" -> agent "${e.agent}"`);try{let i=await t.execute({message:e.prompt,agentId:e.agent,context:{channel:s.channel,sender:s.sender,chatId:`service:${e.id}:${s.chatId}`}});if(i.error){this.log(`Service "${e.name}" failed: ${i.error}`),await n(`Service "${e.name}" encountered an error: ${i.error}`);return}i.content&&await n(i.content),this.log(`Service "${e.name}" completed in ${i.duration}ms`)}catch(i){this.log(`Service "${e.name}" threw: ${i.message}`),await n(`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,s]of Object.entries(e.orgChart)){let n=e.roles[s.role];if(!n)throw new Error(`Org chart references unknown role "${s.role}" for agent "${t}"`);this.employees.set(t,{agentId:t,role:n,roleKey:s.role,reportsTo:s.reportsTo,schedule:s.schedule,utilizationTarget:s.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 s=new Set,n=t.agentId;for(;n;){if(s.has(n))throw new Error(`Reporting cycle detected involving "${t.agentId}"`);s.add(n),n=this.employees.get(n)?.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=[],s=e;for(;s&&!t.includes(s);)t.push(s),s=this.employees.get(s)?.reportsTo;return t}};var it={sun:0,mon:1,tue:2,wed:3,thu:4,fri:5,sat:6};function Pe(d,e){let s=new Intl.DateTimeFormat("en-US",{timeZone:e,weekday:"short",hour:"2-digit",minute:"2-digit",hour12:!1}).formatToParts(d),n=(s.find(a=>a.type==="weekday")?.value||"sun").toLowerCase().slice(0,3),i=s.find(a=>a.type==="hour")?.value||"00",r=s.find(a=>a.type==="minute")?.value||"00";return{hour:parseInt(i,10)%24,minute:parseInt(r,10),dow:it[n]??0}}function L(d){let[e,t]=d.split(":").map(Number);return e*60+t}function xe(d,e){return d.days.some(t=>it[t]===e)}function Rt(d,e,t){let{hour:s,minute:n,dow:i}=Pe(e,t);if(!xe(d,i))return!1;let r=s*60+n,o=L(d.start),a=L(d.end);if(r<o||r>=a)return!1;if(d.lunch){let c=L(d.lunch.start),l=L(d.lunch.end);if(r>=c&&r<l)return!1}return!0}var he=class{constructor(e,t){this.org=e;this.config=t}isOnClock(e,t=new Date){let s=this.org.get(e);return s?Rt(s.schedule,t,this.config.timezone):!1}isDayStart(e,t=new Date){let s=this.org.get(e);if(!s)return!1;let{hour:n,minute:i,dow:r}=Pe(t,this.config.timezone);if(!xe(s.schedule,r))return!1;let o=L(s.schedule.start);return n*60+i===o}isDayEnd(e,t=new Date){let s=this.org.get(e);if(!s)return!1;let{hour:n,minute:i,dow:r}=Pe(t,this.config.timezone);if(!xe(s.schedule,r))return!1;let o=L(s.schedule.end);return n*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,s,n){this.router=e;this.org=t;this.config=s;this.log=n}async postToMain(e,t){let s=this.config.mainChannel;try{await this.router.sendOutbound({channel:s.channel,chatId:s.chatId,text:e,agentId:t,accountId:s.accountId})}catch(n){this.log(`[business] postToMain failed: ${n.message}`)}}async escalate(e,t,s){let n=this.org.chainOfCommand(e).slice(1),i=n.length?n.slice(0,1):[],r=`\u26A0\uFE0F **Escalation from ${e}** \u2014 ${t}
61
+
62
+ ${s}`;if(!i.length){await this.postToMain(r,e);return}await this.postToMain(`${r}
63
+
64
+ cc: ${i.map(o=>`@${o}`).join(" ")}`,e)}};import{writeFileSync as Dt,existsSync as rt,mkdirSync as _t,readFileSync as Et}from"fs";import{resolve as Re}from"path";function De(){return new Date().toISOString().slice(0,10)}var de=class{constructor(e,t,s){this.org=e;this.schedule=t;this.log=s;this.dir=Re(process.cwd(),".agentx/kpi"),rt(this.dir)||_t(this.dir,{recursive:!0}),this.stats=this.loadOrInit()}stats;dir;onClockTick=new Map;loadOrInit(){let e=Re(this.dir,`${De()}.json`);if(rt(e))try{return JSON.parse(Et(e,"utf-8"))}catch{}let t={};for(let s of this.org.all())t[s.agentId]={tasksCompleted:0,timeLoggedSeconds:0,onClockSeconds:0,blockers:0,utilization:0};return{date:De(),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 s=this.onClockTick.get(t.agentId);if(s){let n=Math.min(e-s,12e4)/1e3,i=this.stats.perAgent[t.agentId];i&&(i.onClockSeconds+=n)}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!==De()&&(this.flush(),this.stats=this.loadOrInit());try{Dt(Re(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,s=0;for(let[i,r]of Object.entries(this.stats.perAgent)){t+=r.timeLoggedSeconds,s+=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 n=s>0?(t/s*100).toFixed(0):"0";return e.push(`**Team:** ${(t/3600).toFixed(1)}h logged / ${(s/3600).toFixed(1)}h on-clock \u2192 ${n}% utilization`),e.join(`
65
+ `)}};function T(d,e,t){d.statusCode=e,d.setHeader("Content-Type","application/json"),d.end(JSON.stringify(t))}async function ue(d){return new Promise((e,t)=>{let s=[];d.on("data",n=>s.push(n)),d.on("end",()=>{let n=Buffer.concat(s).toString("utf-8");if(!n)return e({});try{e(JSON.parse(n))}catch(i){t(i)}}),d.on("error",t)})}async function ot(d,e,t,s){if(d==="GET /business/status"){let n=new Date,i=s.schedule.clockedInAgents(n);return T(t,200,{time:n.toISOString(),onClock:i,employees:s.org.all().map(r=>({agentId:r.agentId,role:r.role.title,reportsTo:r.reportsTo,onClock:i.includes(r.agentId)})),kpi:s.kpi.snapshot()}),!0}if(d.startsWith("GET /business/work")){let i=new URL(e.url||"/","http://x").searchParams.get("agent");if(!i)return T(t,400,{error:"?agent= required"}),!0;try{let r=await s.workSource.listOpen(i);T(t,200,{items:r})}catch(r){T(t,500,{error:r.message})}return!0}if(d==="POST /business/claim"){let n=await ue(e),{agent:i,itemId:r}=n;if(!i||!r)return T(t,400,{error:"agent and itemId required"}),!0;try{await s.workSource.claim(i,r),T(t,200,{ok:!0})}catch(o){T(t,500,{error:o.message})}return!0}if(d==="POST /business/report"){let n=await ue(e);if(!n.itemId||!n.status)return T(t,400,{error:"itemId and status required"}),!0;if(!["in-progress","done","blocked"].includes(n.status))return T(t,400,{error:"status must be in-progress | done | blocked"}),!0;try{await s.workSource.report(n.itemId,{status:n.status,note:n.note,timeSeconds:n.timeSeconds,blocker:n.blocker}),T(t,200,{ok:!0})}catch(i){T(t,500,{error:i.message})}return!0}if(d==="POST /business/escalate"){let n=await ue(e);return!n.agent||!n.subject?(T(t,400,{error:"agent and subject required"}),!0):(await s.reporter.escalate(n.agent,n.subject,n.detail||""),s.kpi.recordBlocker(n.agent),T(t,200,{ok:!0}),!0)}if(d==="POST /business/post"){let n=await ue(e);return n.text?(await s.reporter.postToMain(n.text,n.agent),T(t,200,{ok:!0}),!0):(T(t,400,{error:"text required"}),!0)}return!1}function _e(d){return`## Business Layer Tools
66
+
67
+ During business hours you'll receive lifecycle prompts ([STANDUP], [WORK], [WRAP]).
68
+ Use these HTTP endpoints on the local daemon to act on them.
69
+
70
+ - **List your work:**
71
+ \`curl -s ${d}/business/work?agent=<your-id>\`
72
+ - **Claim an item** (optional \u2014 some sources no-op):
73
+ \`curl -s -X POST ${d}/business/claim -d '{"agent":"<your-id>","itemId":"<id>"}'\`
74
+ - **Report progress or completion:**
75
+ \`curl -s -X POST ${d}/business/report \\
76
+ -d '{"itemId":"<id>","status":"done","timeSeconds":1800,"note":"shipped"}'\`
77
+ status: \`in-progress\` | \`done\` | \`blocked\`
78
+ - **Escalate a blocker** up the org chart:
79
+ \`curl -s -X POST ${d}/business/escalate \\
80
+ -d '{"agent":"<your-id>","subject":"short","detail":"why it's stuck"}'\`
81
+ - **Post to the main channel** (daily plans, reports):
82
+ \`curl -s -X POST ${d}/business/post \\
83
+ -d '{"agent":"<your-id>","text":"your update"}'\`
84
+
85
+ Your org chart, role, and schedule are fixed in agentx.json under \`business.orgChart\`.
86
+ `}var Ot=6e4,pe=class{constructor(e,t,s,n,i,r,o,a,c){this.config=e;this.org=t;this.schedule=s;this.registry=n;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}`))},Ot)}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 s of this.day.keys())this.day.set(s,{})}this.kpi.tick();for(let s of this.org.all()){let n=this.day.get(s.agentId)||{};if(this.day.set(s.agentId,n),!this.runningTask.has(s.agentId)){if(this.schedule.isDayStart(s.agentId,e)&&!n.standupFiredAt){n.standupFiredAt=Date.now(),this.fireStandup(s.agentId).catch(i=>this.log(`[business] standup err: ${i.message}`));continue}if(this.schedule.isDayEnd(s.agentId,e)&&!n.wrapFiredAt){n.wrapFiredAt=Date.now(),this.fireWrap(s.agentId).catch(i=>this.log(`[business] wrap err: ${i.message}`));continue}this.schedule.isOnClock(s.agentId,e)&&(n.lastWorkTickAt?(Date.now()-n.lastWorkTickAt)/6e4:1/0)>=this.config.workTickMinutes&&(n.lastWorkTickAt=Date.now(),this.fireWorkTick(s.agentId).catch(r=>this.log(`[business] work-tick err: ${r.message}`)))}}}roleContext(e){let t=this.org.get(e);if(!t)return"";let s=t.reportsTo?`reporting to ${t.reportsTo}`:"(no manager \u2014 top of org chart)",n=t.role.responsibilities.length?`
87
+ Responsibilities:
88
+ ${t.role.responsibilities.map(r=>`- ${r}`).join(`
89
+ `)}`:"",i=t.role.sopPath?`
90
+ SOP: @${t.role.sopPath}`:"";return`You are ${e}, role: ${t.role.title} ${s}.${n}${i}
91
+
92
+ ${_e(this.daemonBase)}`}async runLifecycle(e,t){if(this.runningTask.has(e))return;this.runningTask.add(e);let s=Date.now();try{let n=await this.registry.execute({message:t,agentId:e,context:{channel:"business",chatId:`day-cycle:${this.currentDate}:${e}`}}),i=Math.round((n.duration??Date.now()-s)/1e3);return n.error||this.kpi.recordTaskCompletion({agentId:e,durationSeconds:i,success:!0,channel:"business"}),n.content}finally{this.runningTask.delete(e)}}async fireStandup(e){this.log(`[business] STANDUP \u2192 ${e}`);let s=`${this.roleContext(e)}
93
+
94
+ [STANDUP] It's the start of your work day.
95
+ 1. Call GET /business/work?agent=${e} to see your open work items.
96
+ 2. Pick your top 3 for today, in priority order.
97
+ 3. Post your plan to the main channel via POST /business/post \u2014 keep it to 4-6 short lines.
98
+ 4. Flag any blockers via POST /business/escalate.`;if(await this.runLifecycle(e,s)){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 n=`${this.roleContext(e)}
99
+
100
+ [WORK] Continue your highest-priority open task.
101
+ - Call GET /business/work?agent=${e} to see what's assigned to you.
102
+ - If you complete one, POST /business/report with status:done and the time you spent.
103
+ - If blocked, POST /business/escalate with a concise blocker description.
104
+ - 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,n)){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 n=`${this.roleContext(e)}
105
+
106
+ [WRAP] End of work day. Post a concise daily report to the main channel via POST /business/post:
107
+ - Tasks closed today
108
+ - Time logged (total)
109
+ - Carry-overs to tomorrow
110
+ - Any outstanding blockers
111
+ Keep it under 8 lines.`;await this.runLifecycle(e,n)}};var me=class{org;schedule;workSource;reporter;kpi;dayCycle;constructor(e,t,s,n,i){this.org=new le(e),this.schedule=new he(this.org,e),this.workSource=Ue(e,t,i),this.reporter=new ge(n,this.org,e,i),this.kpi=new de(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 pe(e,this.org,this.schedule,s,this.reporter,this.kpi,this.workSource,a,i)}start(){this.dayCycle.start()}stop(){this.dayCycle.stop()}recordTaskCompletion(e,t,s,n){this.kpi.recordTaskCompletion({agentId:e,durationSeconds:t,success:s,channel:n})}async handleHttp(e,t,s){let n={workSource:this.workSource,org:this.org,schedule:this.schedule,reporter:this.reporter,kpi:this.kpi};return ot(e,t,s,n)}summary(){let e=this.schedule.clockedInAgents().length;return`Business layer: ${this.org.all().length} employees, ${e} on-clock now`}};var ct=class{config;registry;router;cron;mesh;hooks;landscape;heartbeat;business;httpServer;webhooks;log;sseClients=new Set;configPath;configWatcher;reloadTimer;constructor(e){let s=new W("agentx").asConsoleLog();this.log=(...r)=>{s(...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=we(e);let n=Be(this.config);for(let r of n)this.log(` \u26A0 ${r}`);let[,i]=this.config.node.bind.split(":");He(this.config.agents,i||"19900",this.log),this.hooks=new Oe,je(process.cwd(),this.hooks),this.landscape=new oe(this.config),this.registry=new Ne(this.config,this.log),Le(this.registry),this.registry.setLandscape(this.landscape),this.heartbeat=new ae(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 F(this.registry,this.config,this.hooks,this.log),this.webhooks=new re(this.registry,{},this.log),Object.keys(this.config.services).length>0){let r=new ce(this.config.services,this.log);this.router.setServiceMatcher(r)}if(this.cron=new U(this.config,this.registry,this.hooks,this.log),this.cron.setNotifyCallback(async(r,o,a,c)=>{let l=`Cron "${r}" failed (${c}x)
112
+ Agent: ${o}
113
+ 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 h=this.config.crons[r];if(h?.notify)try{await this.router.sendOutbound({channel:h.notify.channel,chatId:h.notify.chatId,text:`\u{1F534} **Cron "${r}" failed** (${c}x)
114
+ ${a.slice(0,300)}`,agentId:o,accountId:h.notify.accountId})}catch(g){this.log(`[CRON ALERT] notify send failed: ${g.message}`)}}),this.config.mesh.enabled&&(this.mesh=new X(this.config,this.log),this.router.setMesh(this.mesh)),this.config.business?.enabled)try{this.business=new me(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(),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}`);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(),this.log(""),this.log(" Ready."),this.log("");let s=Ee(process.cwd(),".agentx/daemon.pid");Bt(Gt(s),{recursive:!0}),Lt(s,String(process.pid)),this.log(` PID: ${process.pid} (${s})`),process.on("uncaughtException",r=>{this.log(`UNCAUGHT EXCEPTION: ${r.message}`),this.log(r.stack||"")}),process.on("unhandledRejection",r=>{this.log(`UNHANDLED REJECTION: ${r}`)});let n=!1,i=async r=>{n||(n=!0,this.log(`
115
+ 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{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{}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=Ee(process.cwd(),".agentx/daemon.pid");at(t)&&Nt(t)}catch{}this.log(` Shutdown complete (${Date.now()-e}ms)`),process.exit(0)}midnightTimer;startConfigWatcher(){let e=this.configPath||Ee(process.cwd(),"agentx.json");if(at(e))try{this.configWatcher=Ft(e,{persistent:!1},t=>{t==="change"&&(this.reloadTimer&&clearTimeout(this.reloadTimer),this.reloadTimer=setTimeout(()=>{this.reload().catch(s=>this.log(`[reload] failed: ${s?.message||s}`))},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=we(this.configPath)}catch(i){return this.log(`[reload] config invalid, keeping previous: ${i.message}`),{applied:[],restartRequired:[],error:i.message}}let t=[],s=[];if(JSON.stringify(this.config.crons)!==JSON.stringify(e.crons))try{await this.cron.stop(),this.cron=new U(e,this.registry,this.hooks,this.log),this.cron.setNotifyCallback(async(i,r,o,a)=>{this.log(`[CRON ALERT] Cron "${i}" failed (${a}x) \u2014 ${o.slice(0,200)}`),this.broadcastSSE("cron-failure",JSON.stringify({jobId:i,agent:r,error:o,consecutiveErrors:a}));let c=e.crons[i];if(c?.notify)try{await this.router.sendOutbound({channel:c.notify.channel,chatId:c.notify.chatId,text:`Cron "${i}" failed (${a}x)
116
+ ${o.slice(0,300)}`,agentId:r,accountId:c.notify.accountId})}catch(l){this.log(`[CRON ALERT] notify send failed: ${l.message}`)}}),await this.cron.start(),t.push("crons")}catch(i){this.log(`[reload] cron reload failed: ${i.message}`)}let n=["agents","channels","mesh","providers","node","services"];for(let i of n)JSON.stringify(this.config[i])!==JSON.stringify(e[i])&&s.push(String(i));return this.config=e,t.length&&this.log(`[reload] applied: ${t.join(", ")}`),s.length?(this.log(`[reload] restart required to apply changes in: ${s.join(", ")}`),this.broadcastSSE("reload-partial",JSON.stringify({applied:t,restartRequired:s}))):t.length&&this.broadcastSSE("reload-complete",JSON.stringify({applied:t})),{applied:t,restartRequired:s}}scheduleMidnightHook(){let e=()=>{let t=new Date,s=t.toLocaleString("en-US",{timeZone:"Africa/Tunis"}),n=new Date(s),i=new Date(n);i.setDate(i.getDate()+1),i.setHours(0,0,5,0);let r=n.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(),s=t.generateDailyReport(e);if(!s||s.totalTasks===0){this.log(` Cost tracking: no usage for ${e}, skipping`);return}t.appendToTokenCosts(s),this.log(` Cost tracking: ${e} \u2014 ${s.totalTasks} tasks, $${s.totalCost.toFixed(4)} (top: ${s.topAgent} $${s.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=new ee(e,this.log);this.router.addChannel(t),this.log(" Telegram: enabled")}}if(this.config.channels.whatsapp.enabled){let e=new te({sessionDir:this.config.channels.whatsapp.sessionDir,defaultAgent:this.config.channels.whatsapp.defaultAgent,allowFrom:this.config.channels.whatsapp.allowFrom,routes:this.config.channels.whatsapp.routes},this.log);this.router.addChannel(e),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 ne({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.gitlab?.enabled&&this.config.channels.gitlab.token){let e=new ie({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},this.log,this.hooks);this.mesh&&(e.setReactForwarder(async(t,s,n,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:s,noteableType:n,noteableIid:i,noteId:r,agentId:o})}),h=await l.text().catch(()=>"");this.log(`[gitlab] react forward -> ${c} : ${l.status} ${h.slice(0,200)}`)}catch(l){this.log(`[gitlab] react forward FAILED -> ${c} : ${l.message}`)}}),e.setSendNoteForwarder(async(t,s,n,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:s,noteableType:n,noteableIid:i,agentId:r,text:o})}),h=await l.json().catch(()=>({}));return this.log(`[gitlab] send-note forward -> ${c} : ${l.status} noteId=${h.noteId||"?"}`),h.noteId||""}catch(l){return this.log(`[gitlab] send-note forward FAILED -> ${c} : ${l.message}`),""}}),e.setLogTimeForwarder(async(t,s,n,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:s,noteableType:n,noteableIid:i,agentId:r,durationMs:o})}),h=await l.text().catch(()=>"");this.log(`[gitlab] log-time forward -> ${c} : ${l.status} ${h.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})`)}await this.router.startAll()}async startHttpApi(){let[e,t]=this.config.node.bind.split(":"),s=parseInt(t||"18800",10);this.httpServer=jt(async(n,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"),n.method==="OPTIONS"){i.writeHead(204),i.end();return}await this.handleHttp(n,i)}),this.httpServer.on("error",n=>{n.code==="EADDRINUSE"?(this.log(` ERROR: Port ${s} is already in use. Retrying in 5s...`),setTimeout(()=>{this.httpServer?.close(),this.httpServer?.listen(s,e||"0.0.0.0")},5e3)):this.log(` HTTP error: ${n.message}`)}),this.httpServer.listen(s,e||"0.0.0.0",()=>{this.log(` HTTP API: http://${e||"0.0.0.0"}:${s}`)})}broadcastSSE(e,t){if(this.sseClients.size===0)return;let s=`event: ${e}
117
+ data: ${JSON.stringify({time:new Date().toISOString(),message:t})}
118
+
119
+ `;for(let n of this.sseClients)try{n.write(s)}catch{this.sseClients.delete(n)}}handleSSE(e,t){t.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache",Connection:"keep-alive","Access-Control-Allow-Origin":"*"});let s=this.registry.list(),n=s.filter(i=>i.active>0);t.write(`event: status
120
+ data: ${JSON.stringify({node:this.config.node.name,agents:s.length,active:n.map(i=>({id:i.id,name:i.name,tasks:i.active})),mesh:this.mesh?.directory().map(i=>({peer:i.peer,healthy:i.healthy}))||[]})}
121
+
122
+ `),this.sseClients.add(t),e.on("close",()=>this.sseClients.delete(t))}async handleHttp(e,t){let s=new URL(e.url||"/",`http://${e.headers.host||"localhost"}`),n=s.pathname;try{if(e.method==="GET"&&n==="/events"){this.handleSSE(e,t);return}if(e.method==="POST"&&n.startsWith("/webhook/")){await this.webhooks.handle(e,t,n);return}if(e.method==="POST"&&(n==="/v1/chat/completions"||n.match(/^\/llm\/[^/]+\/v1\/chat\/completions$/))){await this.handleOpenAICompat(e,t,n);return}if(this.business&&n.startsWith("/business")&&await this.business.handleHttp(`${e.method} ${n}`,e,t))return;switch(`${e.method} ${n}`){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(i=>({id:i.id,enabled:i.enabled,nextRun:i.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 /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:i,getDebugLogs:r}=await import("./debug-N3B5NVJU.js");this.json(t,200,{...i(),recentLogs:r(50)});break}case"POST /debug/on":{let{setDebug:i}=await import("./debug-N3B5NVJU.js"),r=s.searchParams.get("categories")?.split(",")||["all"];i(!0,r),this.log(`Debug enabled: ${r.join(", ")}`),this.json(t,200,{enabled:!0,categories:r});break}case"POST /debug/off":{let{setDebug:i}=await import("./debug-N3B5NVJU.js");i(!1),this.log("Debug disabled"),this.json(t,200,{enabled:!1});break}case"POST /gitlab/react":{let i=await E(e),{project:r,noteableType:o,noteableIid:a,noteId:c,agentId:l}=i,g=(this.config.channels.gitlab?.agentMappings||[]).find(p=>p.agentId===l),f=g?.token||this.config.channels.gitlab?.token,u=this.config.channels.gitlab?.host;if(this.log(`[gitlab/react] agent="${l}" project="${r}" mappingFound=${!!g} mappingToken=${!!g?.token} globalToken=${!!this.config.channels.gitlab?.token}(len=${(this.config.channels.gitlab?.token||"").length}) host=${u||"MISSING"}`),!f||!u){this.json(t,404,{error:"no gitlab token for agent",debug:{agentId:l,mappingFound:!!g,hasMappingToken:!!g?.token,hasGlobalToken:!!this.config.channels.gitlab?.token,hasHost:!!u}});break}let y=encodeURIComponent(r),m=o==="issue"?`${u}/api/v4/projects/${y}/issues/${a}/notes/${c}/award_emoji`:`${u}/api/v4/projects/${y}/merge_requests/${a}/notes/${c}/award_emoji`;try{let p=await fetch(m,{method:"POST",headers:{"Content-Type":"application/json","PRIVATE-TOKEN":f},body:JSON.stringify({name:"eyes"})}),w=await p.text().catch(()=>"");this.log(`[gitlab/react] -> POST ${m} : ${p.status} ${w.slice(0,120)}`),this.json(t,200,{ok:p.ok,status:p.status,gitlabResponse:w.slice(0,200)})}catch(p){this.log(`[gitlab/react] FETCH ERROR: ${p.message}`),this.json(t,500,{error:p.message})}break}case"POST /gitlab/send-note":{let i=await E(e),{project:r,noteableType:o,noteableIid:a,agentId:c,text:l}=i,f=(this.config.channels.gitlab?.agentMappings||[]).find(p=>p.agentId===c)?.token||this.config.channels.gitlab?.token,u=this.config.channels.gitlab?.host;if(!f||!u){this.json(t,404,{error:"no gitlab token for agent",debug:{agentId:c,hasToken:!!f,hasHost:!!u}});break}let y=encodeURIComponent(r),m=o==="issue"?`${u}/api/v4/projects/${y}/issues/${a}/notes`:`${u}/api/v4/projects/${y}/merge_requests/${a}/notes`;try{let p=await fetch(m,{method:"POST",headers:{"Content-Type":"application/json","PRIVATE-TOKEN":f},body:JSON.stringify({body:l})}),w=await p.text().catch(()=>""),$="";try{$=String(JSON.parse(w).id||"")}catch{}this.log(`[gitlab/send-note] agent="${c}" -> POST ${m} : ${p.status} noteId=${$||"?"}`),this.json(t,200,{ok:p.ok,status:p.status,noteId:$})}catch(p){this.log(`[gitlab/send-note] FETCH ERROR: ${p.message}`),this.json(t,500,{error:p.message})}break}case"POST /gitlab/log-time":{let i=await E(e),{project:r,noteableType:o,noteableIid:a,agentId:c,durationMs:l}=i,f=(this.config.channels.gitlab?.agentMappings||[]).find(k=>k.agentId===c)?.token||this.config.channels.gitlab?.token,u=this.config.channels.gitlab?.host;if(!f||!u){this.json(t,404,{error:"no gitlab token for agent"});break}let y=Math.max(60,Math.round(Number(l)/1e3)),m=Math.floor(y/3600),p=Math.ceil(y%3600/60),w=m>0?`${m}h${p>0?` ${p}m`:""}`:`${p}m`,$=encodeURIComponent(r),b=`${u}/api/v4/projects/${$}/${o==="merge_request"?"merge_requests":"issues"}/${a}/add_spent_time`;try{let k=await fetch(b,{method:"POST",headers:{"Content-Type":"application/json","PRIVATE-TOKEN":f},body:JSON.stringify({duration:w})});this.log(`[gitlab/log-time] agent="${c}" duration=${w} -> POST ${b} : ${k.status}`),this.json(t,200,{ok:k.ok,status:k.status,duration:w})}catch(k){this.log(`[gitlab/log-time] FETCH ERROR: ${k.message}`),this.json(t,500,{error:k.message})}break}case"POST /reload":{try{let i=await this.reload();this.json(t,200,{ok:!0,...i})}catch(i){this.json(t,500,{ok:!1,error:i?.message||String(i)})}break}case"POST /send":{let i=await E(e);if(!i.channel||!i.chatId||!i.text){this.json(t,400,{error:"Required: channel, chatId, text"});return}try{let r=await this.router.sendOutbound({channel:i.channel,chatId:i.chatId,text:i.text,replyTo:i.replyTo,parseMode:i.parseMode,agentId:i.agentId,accountId:i.accountId});this.json(t,200,{ok:!0,messageId:r||null})}catch(r){this.json(t,400,{error:r.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 i=await E(e),r=i.agent||this.config.node.defaultAgent;if(!r||!i.message){this.json(t,400,{error:"Missing: message (and no defaultAgent configured)"});return}let o=await this.registry.execute({agentId:r,message:i.message,context:i.context});this.json(t,o.error?500:200,o);break}case"POST /ask":case"GET /ask":{let i=new URL(e.url||"/",`http://${e.headers.host||"localhost"}`),r=i.searchParams.get("q")||i.searchParams.get("message")||"";if(e.method==="POST"){let h=await E(e);r=h.message||h.q||r}let o=this.config.node.defaultAgent;if(!o){this.json(t,400,{error:"No defaultAgent configured in node config"});return}if(!r){this.json(t,400,{error:"Missing message (pass as ?q=... or {message:...})"});return}let a=`[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.]
123
+
124
+ ${r}`,c=await this.registry.execute({agentId:o,message:a,context:{channel:"voice",sender:"Siri"}}),l=Ht(c.content);this.json(t,c.error?500:200,{text:l,full:c.content,error:c.error,duration:c.duration});break}case"POST /mesh/task":{let i=await E(e);if(!i.peer||!i.message){this.json(t,400,{error:"Missing: peer, message"});return}if(!this.mesh){this.json(t,400,{error:"Mesh not enabled"});return}let r=await this.mesh.sendTask(i.peer,i.message,i.agent);this.json(t,200,{response:r});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(i=>({id:i.id,name:i.name,description:`Agent "${i.name}" (${i.tier})`,tags:[i.tier]})),defaultInputModes:["text"],defaultOutputModes:["text"]});break;case"GET /wiki/agents":{let i=this.registry.getWikiHub();this.json(t,200,{nodeId:this.config.node.id,agents:i.summary()});break}case"GET /wiki/entries":{let i=this.registry.getWikiHub(),r=s.searchParams.get("agent")||void 0,o=s.searchParams.get("after")||void 0,a=r?i.getAgentEntries(r):i.getSharedStore().listEntries({after:o});this.json(t,200,{nodeId:this.config.node.id,count:a.length,entries:a.map(c=>({id:c.id,date:c.date,agentId:c.agentId,source:c.source,sourceContext:c.sourceContext,content:c.content}))});break}case"GET /wiki/articles":{let i=this.registry.getWikiHub(),r=s.searchParams.get("agent");if(!r){this.json(t,400,{error:"?agent= required"});break}let a=i.getAgentWiki(r).rebuildIndex();this.json(t,200,{nodeId:this.config.node.id,agentId:r,articles:a.articles});break}case"GET /wiki/article":{let i=this.registry.getWikiHub(),r=s.searchParams.get("agent"),o=s.searchParams.get("path");if(!r||!o){this.json(t,400,{error:"?agent= and ?path= required"});break}let c=i.getAgentWiki(r).readArticle(o);if(!c){this.json(t,404,{error:"Article not found"});break}this.json(t,200,{nodeId:this.config.node.id,agentId:r,path:c.path,title:c.meta.title,tags:c.meta.tags,owner:c.meta.owner,created:c.meta.created,lastUpdated:c.meta.lastUpdated,sources:c.meta.sources,content:c.content});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","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"]})}}catch(i){this.json(t,500,{error:i.message})}}async handleOpenAICompat(e,t,s){let n=await E(e),i=s.match(/^\/llm\/([^/]+)\//),r=Object.keys(this.config.agents)[0]||"default",o=i?.[1]||n.model||r,a=n.messages||[],c=[...a].reverse().find(f=>f.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(f=>`${f.role==="user"?"User":"Assistant"}: ${f.content.slice(0,200)}`),h=l.length>0?`[Conversation]
125
+ ${l.slice(-10).join(`
126
+ `)}
127
+
128
+ `:"";if(n.stream===!0){t.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache",Connection:"keep-alive"});let f=`chatcmpl-${Date.now().toString(36)}`,u=await this.registry.execute({agentId:o,message:h+c.content,context:{channel:"api",sender:"openai-compat"}}),y=u.error||u.content||"",m={id:f,object:"chat.completion.chunk",created:Math.floor(Date.now()/1e3),model:o,choices:[{index:0,delta:{role:"assistant",content:y},finish_reason:"stop"}]};t.write(`data: ${JSON.stringify(m)}
129
+
130
+ `),t.write(`data: [DONE]
131
+
132
+ `),t.end()}else{let f=await this.registry.execute({agentId:o,message:h+c.content,context:{channel:"api",sender:"openai-compat"}}),u=f.error||f.content||"",y=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:y,total_tokens:Math.ceil(c.content.length/4)+y}})}}json(e,t,s){e.writeHead(t,{"Content-Type":"application/json"}),e.end(JSON.stringify(s,null,2))}};function Ht(d){let e=d;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}async function E(d){return new Promise((e,t)=>{let s="";d.on("data",n=>s+=n.toString()),d.on("end",()=>{try{e(s?JSON.parse(s):{})}catch{e({})}}),d.on("error",t)})}import Ut from"path";import Wt from"fs-extra";function kn(){let d=Ut.join("package.json");return Wt.readJSONSync(d)}export{q as a,X as b,F as c,ee as d,te as e,U as f,ct as g,kn as h};
133
+ //# sourceMappingURL=chunk-RB73FZXJ.js.map