agentix-cli 0.9.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -1
- package/dist/agent-5KIMJI3F.js +2 -0
- package/dist/{chunk-SBUX74OU.js → chunk-D6EDMQRV.js} +5 -5
- package/dist/chunk-HI24KAEY.js +54 -0
- package/dist/chunk-HI24KAEY.js.map +1 -0
- package/dist/chunk-MCJUAE6V.js +6 -0
- package/dist/chunk-MCJUAE6V.js.map +1 -0
- package/dist/chunk-QOYAX2FT.js +2 -0
- package/dist/chunk-QOYAX2FT.js.map +1 -0
- package/dist/chunk-QZP65I2H.js +45 -0
- package/dist/chunk-QZP65I2H.js.map +1 -0
- package/dist/chunk-T25UEXVK.js +126 -0
- package/dist/chunk-T25UEXVK.js.map +1 -0
- package/dist/chunk-VH23CDHL.js +157 -0
- package/dist/chunk-VH23CDHL.js.map +1 -0
- package/dist/chunk-WJ6ZF6EZ.js +6 -0
- package/dist/chunk-WJ6ZF6EZ.js.map +1 -0
- package/dist/cli.js +26 -29
- package/dist/cli.js.map +1 -1
- package/dist/compaction-7I3E5QUO.js +28 -0
- package/dist/compaction-7I3E5QUO.js.map +1 -0
- package/dist/config-HAD5E4YX.js +2 -0
- package/dist/index.d.ts +1084 -76
- package/dist/index.js +39 -35
- package/dist/index.js.map +1 -1
- package/dist/loader-YQE6XJBT.js +2 -0
- package/dist/registry-CDWY7CLL.js +2 -0
- package/dist/workspace-setup-QQTCJY4B.js +2 -0
- package/dist/workspace-setup-QQTCJY4B.js.map +1 -0
- package/package.json +1 -1
- package/dist/agent-QYQVGKLM.js +0 -2
- package/dist/chunk-BAIQJHQF.js +0 -64
- package/dist/chunk-BAIQJHQF.js.map +0 -1
- package/dist/chunk-BHDLKX3G.js +0 -6
- package/dist/chunk-BHDLKX3G.js.map +0 -1
- package/dist/chunk-FRFR27IN.js +0 -3
- package/dist/chunk-FRFR27IN.js.map +0 -1
- package/dist/chunk-IVVVYXPH.js +0 -136
- package/dist/chunk-IVVVYXPH.js.map +0 -1
- package/dist/chunk-X32247GM.js +0 -2
- package/dist/chunk-X32247GM.js.map +0 -1
- package/dist/compaction-E3MQRLTL.js +0 -28
- package/dist/compaction-E3MQRLTL.js.map +0 -1
- package/dist/config-MSWKC466.js +0 -2
- package/dist/loader-6VSM6FSY.js +0 -2
- package/dist/registry-FP6FUOXF.js +0 -2
- /package/dist/{agent-QYQVGKLM.js.map → agent-5KIMJI3F.js.map} +0 -0
- /package/dist/{chunk-SBUX74OU.js.map → chunk-D6EDMQRV.js.map} +0 -0
- /package/dist/{config-MSWKC466.js.map → config-HAD5E4YX.js.map} +0 -0
- /package/dist/{loader-6VSM6FSY.js.map → loader-YQE6XJBT.js.map} +0 -0
- /package/dist/{registry-FP6FUOXF.js.map → registry-CDWY7CLL.js.map} +0 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import{g as Be,i as Ue}from"./chunk-VH23CDHL.js";import{f as Le,g as Ne}from"./chunk-CJ45Y2IR.js";import{b as We,c as Ge}from"./chunk-WJ6ZF6EZ.js";import{b as He}from"./chunk-QZP65I2H.js";import{e as D}from"./chunk-DSYKYZMT.js";import{a as nt,b as z}from"./chunk-4YCH6IZV.js";var K=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 d=JSON.parse(h.slice(6));yield{state:d.state,message:d.message?.parts?.[0]?.text,final:d.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 q=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 K(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 it,writeFileSync as rt,existsSync as ot,mkdirSync as at}from"fs";import{resolve as Fe}from"path";var ct=30,lt=6e3,V=class{dir;cache=new Map;constructor(e=Fe(process.cwd(),".agentx/groups")){this.dir=e,at(this.dir,{recursive:!0})}add(e,t,s){let n=this.load(e);for(n.push({sender:t,text:s.slice(0,500),timestamp:Date.now()});n.length>ct;)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;for(let i=t.length-2;i>=0;i--){let r=t[i],a=`[${new Date(r.timestamp).toLocaleTimeString("en-US",{hour:"2-digit",minute:"2-digit",hour12:!1})}] ${r.sender}: ${r.text}`;if(n+a.length>lt)break;s.splice(1,0,a),n+=a.length}return s.length<=1?"":(s.push("[End of conversation \u2014 respond to the latest message]"),s.join(`
|
|
3
|
+
`))}getEntries(e){return[...this.load(e)]}filePath(e){let t=e.replace(/[^a-zA-Z0-9_-]/g,"_");return Fe(this.dir,`${t}.json`)}load(e){if(this.cache.has(e))return this.cache.get(e);let t=this.filePath(e);if(!ot(t))return this.cache.set(e,[]),[];try{let s=JSON.parse(it(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{rt(this.filePath(e),JSON.stringify(t))}catch{}}};var ht={minChars:80,maxChars:4e3,idleFlushMs:2e3,minIntervalMs:1500,humanPacing:!1},gt={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}},X=class{config;buffer="";emitter;lastEmitTime=0;idleTimer;insideCodeFence=!1;fencePattern=/^```/;constructor(e,t,s){let n=s?gt[s]:{};this.config={...ht,...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)}flush(){this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=void 0),this.buffer.length>0&&(this.emitImmediate(this.buffer),this.buffer="")}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();try{this.emitter(e)}catch{}}findBreakPoint(){let e=this.buffer,t=Math.min(e.length,this.config.maxChars),s=e.lastIndexOf(`
|
|
4
|
+
|
|
5
|
+
`,t);if(s>=this.config.minChars)return s+2;let n=e.lastIndexOf(`
|
|
6
|
+
`,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]===`
|
|
7
|
+
`))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(`
|
|
8
|
+
`);for(let s of t)this.fencePattern.test(s.trim())&&(this.insideCodeFence=!this.insideCodeFence)}};var dt=4e3,we=class{registry;config;channels=new Map;hooks;mesh;serviceMatcher;business;groupLog;log;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 V}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()}async handleMessage(e,t){if(this.hooks?.has("pre:channel-message")){let p=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(p.blocked){this.log(`Message blocked by hook: ${p.message}`);return}p.modified?.text&&(t={...t,text:p.modified.text})}if(t.group){let p=t.group.id;this.groupLog.add(p,t.sender.name,t.text)}if(this.serviceMatcher){let p=t.group?.id||t.sender.id,w=this.serviceMatcher.match(t.text,t.sender.id,t.channel);if(w){this.log(`Service matched: "${w.service.name}" for ${t.sender.name} (trigger: ${w.trigger})`);let $=t.accountId;this.adapterReact(e,p,t.id,"\u{1F440}",$);let T=this.startTypingLoop(e,p,$);await this.serviceMatcher.execute(w.service,this.registry,{channel:t.channel,sender:t.sender.name,chatId:p},async x=>{clearInterval(T),await this.adapterSend(e,{channel:t.channel,chatId:p,text:x,replyTo:t.id,accountId:$})});return}}let s=this.resolveAgent(t);if(!s)return;if(t.group&&t.channel==="telegram"){let p=this.getAccountForAgent(s);if(p&&p!==t.accountId)return}let n=t.group?.id||t.sender.id,r=this.registry.getAgent(s)?.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="",d=c?new X(async p=>{if(h+=p,l)try{await this.adapterEdit(e,n,l,h,void 0,o)}catch{}else{let w=h.length>20?h:`_${r} is writing..._
|
|
9
|
+
|
|
10
|
+
${h}`;try{l=await this.adapterSend(e,{channel:t.channel,chatId:n,text:w,replyTo:t.id,accountId:o})}catch{}}},void 0,t.channel):void 0,f=d?(p,w)=>{d.push(p)}:void 0;if(t.channelMeta?.agents){for(let p of t.channelMeta.agents)if(!p.handle){let w=this.registry.getAgent(p.id);w&&(p.handle=w.mentions.find($=>$.startsWith("@")),p.name=w.name)}}let y=t.group?this.groupLog.buildContext(n):"",b=y?`${y}
|
|
11
|
+
|
|
12
|
+
${t.sender.name}: ${t.text}`:t.text,u=await this.registry.execute({message:b,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),d?.flush(),u.error){if(u.error.startsWith("__queued__")){clearInterval(a),this.log(`Message queued for ${r}`);return}this.log(`Agent error: ${u.error}`);let p=`Error: ${u.error}`;l?await this.adapterEdit(e,n,l,p,"plain",o):await this.adapterSend(e,{channel:t.channel,chatId:n,text:p,replyTo:t.id,parseMode:"plain",accountId:o});return}let m=u.content;if(this.hooks?.has("post:channel-message")){let p=await this.hooks.execute("post:channel-message",{event:"post:channel-message",channel:t.channel,sender:t.sender.name,response:m,agentId:s});if(p.blocked){this.log(`Response blocked by hook: ${p.message}`);return}p.modified?.response&&(m=p.modified.response)}t.channel==="gitlab"&&m&&(this.channels.get("gitlab")?.getAgentToken?.(s)||(m=`> **${r}** (${s})
|
|
13
|
+
|
|
14
|
+
${m}`)),t.channel==="whatsapp"&&m&&(m=`*${r}*
|
|
15
|
+
|
|
16
|
+
${m}`);let k;m&&(l?(await this.adapterEdit(e,n,l,m,void 0,o),k=l):k=await this.adapterSend(e,{channel:t.channel,chatId:n,text:m,replyTo:t.id,accountId:o,agentId:s})),t.group&&m&&this.groupLog.add(n,r,m);let v=this.config.notifications;if(v?.destination&&u.duration){let p=(v.longTaskThreshold||30)*1e3;if(u.duration>=p&&!u.error&&v.on?.taskComplete||u.error&&v.on?.taskError){let $=Math.round(u.duration/1e3),T=u.error?"failed":"completed",x=u.error?u.error.slice(0,100):u.content.slice(0,100),C=`${T==="failed"?"\u{1F534}":"\u2705"} **${r}** ${T} (${$}s)
|
|
17
|
+
${t.channel}/${t.sender.name}: ${t.text.slice(0,80)}
|
|
18
|
+
${x}`;this.sendOutbound({channel:v.destination.channel,chatId:v.destination.chatId,text:C,agentId:s,accountId:v.destination.accountId}).catch(L=>{this.log(`Task notification failed: ${L.message}`)})}}if(this.business&&u.duration&&this.business.recordTaskCompletion(s,Math.round(u.duration/1e3),!u.error,t.channel),t.channel==="gitlab"&&u.duration&&!u.error){let p=this.channels.get("gitlab");p&&p.logTimeSpent(n,u.duration,s).catch(w=>{this.log(`GitLab time tracking failed: ${w.message}`)})}m&&k&&["telegram","whatsapp","discord"].includes(t.channel)&&this.handleBotToBotChain(e,t,s,m,k,0).catch(p=>{this.log(`Bot-to-bot error: ${p.message}`)})}async handleBotToBotChain(e,t,s,n,i,r,o=new Set){if(r>=we.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(b=>b.startsWith("@")).some(b=>n.toLowerCase().includes(b.toLowerCase())))continue;this.log(`Bot-to-bot [${r+1}]: "${s}" -> "${a}"`);let d=t.group?.id||t.sender.id,f=this.getAccountForAgent(a),y=this.getAccountForAgent(s);try{this.adapterReact(e,d,i,"\u{1F440}",f);let b=this.startTypingLoop(e,d,f),u=r===0?`[Original from ${t.sender.name}]: ${t.text}
|
|
19
|
+
|
|
20
|
+
[${s} said]: ${n}`:n,m=await this.registry.execute({message:u,agentId:a,context:{channel:t.channel,sender:`agent:${s}`,group:t.group?.name}});if(clearInterval(b),m.content&&!m.error){let k=m.content;t.channel==="whatsapp"&&(k=`*${c.name}*
|
|
21
|
+
|
|
22
|
+
${k}`);let v=await this.adapterSend(e,{channel:t.channel,chatId:d,text:k,accountId:f});v&&m.content&&await this.handleBotToBotChain(e,t,a,m.content,v,r+1,o)}else m.error&&this.log(`Bot-to-bot "${a}" error: ${m.error}`)}catch(b){this.log(`Bot-to-bot "${a}" failed: ${b.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,dt)}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})_:
|
|
23
|
+
|
|
24
|
+
`;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}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;if(e.channel==="telegram"&&this.config.channels.telegram.policy.group==="mention-required"){let n=this.registry.findByMention(e.text);return n||void 0}let t=this.registry.findByMention(e.text);return t||(e.channel==="telegram"?this.config.channels.telegram.accounts[e.accountId]?.agentBinding:this.config.channels.whatsapp.defaultAgent)}},N=we;z(N,"MAX_BOT_CHAIN_DEPTH",3);function Y(g){return g.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")}function ke(g){let t=g.replace(/\\([_*\[\]()~`>#+=|{}.!\\-])/g,"$1").split(`
|
|
25
|
+
`),s=[],n=!1,i="",r=[],o=!1,a=[],c=!1;for(let h=0;h<t.length;h++){let d=t[h];if(d.trimStart().startsWith("```"))if(n){n=!1;let u=Y(r.join(`
|
|
26
|
+
`)),m=i?`// ${i}
|
|
27
|
+
`:"";s.push(`<pre><code>${m}${u}</code></pre>`),i="";continue}else{n=!0,i=d.trimStart().slice(3).trim(),r=[];continue}if(n){r.push(d);continue}if(o&&!d.trimStart().startsWith(">")&&(s.push("</blockquote>"),o=!1),c&&!d.trim().startsWith("|")&&(c=!1,a=[]),d.trimStart().startsWith("> ")){let u=d.replace(/^>\s*/,"");o||(s.push("<blockquote>"),o=!0),s.push(P(u));continue}let f=d.match(/^(#{1,6})\s+(.+)$/);if(f){s.push(""),s.push(`<b>${P(f[2])}</b>`);continue}if(/^[-*_]{3,}\s*$/.test(d.trim())){s.push("\u2014\u2014\u2014");continue}if(d.trim().startsWith("|")&&d.trim().endsWith("|")){let u=d.split("|").slice(1,-1).map(m=>m.trim());if(/^\|[\s\-:|]+\|$/.test(d.trim())){c=!0;continue}if(!c){a=u,c=!0;continue}if(a.length>0&&u.length>0)if(u.length>=2){let m=[];for(let k=0;k<u.length;k++)if(k===0)m.push(`<b>${P(u[k])}</b>`);else{let v=a[k]?`${P(a[k])}: `:"";m.push(`${v}${P(u[k])}`)}s.push(`\u2022 ${m.join(" \u2014 ")}`)}else s.push(`\u2022 ${P(u[0])}`);continue}let y=d.match(/^(\s*)[-*+]\s+(.+)$/);if(y){let u=y[1].length>0?" ":"";s.push(`${u}\u2022 ${P(y[2])}`);continue}let b=d.match(/^(\s*)\d+[.)]\s+(.+)$/);if(b){let u=b[1].length>0?" ":"",m=d.match(/^(\s*)(\d+)/)?.[2]||"1";s.push(`${u}${m}. ${P(b[2])}`);continue}if(!d.trim()){s.push("");continue}s.push(P(d))}n&&s.push(`<pre><code>${Y(r.join(`
|
|
28
|
+
`))}</code></pre>`),o&&s.push("</blockquote>");let l=s.join(`
|
|
29
|
+
`).trim();return l=pt(l),l}function P(g){let e=Y(g);return e=e.replace(/`([^`]+)`/g,"<code>$1</code>"),e=e.replace(/\[([^\]]+)\]\(([^)]+)\)/g,(t,s,n)=>`<a href="${Y(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 ut=/(?<=\w)\.(ts|js|py|rs|go|rb|cs|sh|md|yml|yaml|toml|json|env|css|html|xml|sql|tf|hcl)(?=[\s,;:)\]}<]|$)/gi;function pt(g){let e=g.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(ut,"<code>.$1</code>")).join("")}import{readFileSync as mt,writeFileSync as ft,mkdirSync as yt,existsSync as bt}from"fs";import{resolve as wt,dirname as kt}from"path";var ve=class{data={};filePath;dirty=!1;constructor(e){this.filePath=wt(e,".agentx/telegram/groups.json"),this.load()}load(){try{bt(this.filePath)&&(this.data=JSON.parse(mt(this.filePath,"utf-8")))}catch{this.data={}}}save(){try{yt(kt(this.filePath),{recursive:!0}),ft(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]}},Q=class{name="telegram";accounts;offsets=new Map;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 ve(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;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?ke(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"?ke(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.offsets.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.offsets.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,d=!!a.voice,f=!!a.audio,y=!!a.video,b=!!a.document;if(h||d||f||y||b){let p,w="application/octet-stream";if(h?(p=a.photo[a.photo.length-1].file_id,w="image/jpeg",c||(c="[Photo attached \u2014 please describe what you see]")):d?(p=a.voice.file_id,w=a.voice.mime_type||"audio/ogg",c||(c="[Voice message \u2014 please transcribe and respond]")):f?(p=a.audio.file_id,w=a.audio.mime_type||"audio/mpeg",c||(c=`[Audio: ${a.audio.title||"audio file"}]`)):y?(p=a.video.file_id,w=a.video.mime_type||"video/mp4",c||(c="[Video attached]")):b&&(p=a.document.file_id,w=a.document.mime_type||"application/octet-stream",c||(c=`[Document: ${a.document.file_name||"file"}]`)),p)try{let T=(await this.apiCall(t.token,"getFile",{file_id:p})).result?.file_path;if(T){let x=`https://api.telegram.org/file/bot${t.token}/${T}`,C=await fetch(x);if(C.ok){let L=Buffer.from(await C.arrayBuffer()),E=w.split("/")[1]?.split(";")[0]||"bin",{mkdirSync:F,writeFileSync:J}=await import("fs"),{randomUUID:fe}=await import("crypto"),{resolve:A,join:R}=await import("path"),M=A(process.cwd(),".agentx/media/telegram");F(M,{recursive:!0});let j=a.document?.file_name||`${fe()}.${E}`,_=R(M,j);J(_,L),l={path:_,type:w,fileName:j}}}}catch($){this.log(`Media download failed: ${$.message}`)}}if(!c)continue;let m=a.chat.type!=="private",k=String(a.chat.id),v;m&&(this.groupStore.hasGroup(k)||await this.seedGroupMembership(k,a.chat.title),v=await this.getChannelMeta(k));let I={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},group:m?{id:k,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:v};this.chatAccountMap.set(String(a.chat.id),e),this.handler(I).catch(p=>{this.log(`Error handling message: ${p.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,d=a.new_chat_member.user.username||e;this.groupStore.setBotStatus(c,l,e,t.agentBinding,d,h),this.log(`Group membership: @${d} 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 Je,writeFileSync as vt}from"fs";import{resolve as ze,join as $t}from"path";import{randomUUID as St}from"crypto";var Z=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=ze(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}Je(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:d,qr:f}=l;if(i===this.generation){if(f){this.log("Scan QR code with WhatsApp to connect:");try{let{default:y}=await import("qrcode-terminal");y.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 y=d?.error?.output?.statusCode,b=d?.error?.output?.payload?.message||"";if(this.log(`WhatsApp connection closed (status: ${y}, reason: ${b})`),y===s?.loggedOut||y===401)this.log("Logged out. Delete session dir and restart to re-scan QR.");else if(y===440||y===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 u=Math.min(1e4*Math.pow(2,this.reconnectAttempts-1),12e4);this.log(`Reconnecting in ${u/1e3}s (attempt ${this.reconnectAttempts}/5)...`),setTimeout(()=>this.start(),u)}else y===515?(this.log("Stream error, reconnecting in 5s..."),setTimeout(()=>this.start(),5e3)):y!==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 d=(h.key.remoteJid||"").replace(/@.*/,"").slice(-6),f=!!(h.message?.conversation||h.message?.extendedTextMessage?.text);if(this.log(`WA msg: from=${d} 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 A=this.sock?.user,R=h.key.remoteJid||"",M=A?.id?.replace(/:.*/,"")||"",j=A?.lid?.replace(/:.*/,"")||"",_=R.replace(/:.*/,"").replace(/@.*/,"");if(!(_===M||_===j))continue}let y=h.message?.conversation||h.message?.extendedTextMessage?.text||h.message?.imageMessage?.caption||h.message?.videoMessage?.caption||"",b=h.message||{},u=!!b.imageMessage,m=!!b.audioMessage,k=!!b.videoMessage,v=!!b.documentMessage,I=!!b.stickerMessage,p=u||m||k||v||I;if(p&&!y&&(u?y="[Image attached \u2014 please describe what you see]":m?y="[Voice message attached \u2014 please transcribe and respond]":k?y="[Video attached]":v?y=`[Document: ${b.documentMessage?.fileName||"file"}]`:I&&(y="[Sticker]")),!y)continue;let w=h.key.remoteJid||"",$=w.endsWith("@g.us"),T=w.replace(/@.*$/,""),C=($?h.key.participant||"":w).replace(/@.*$/,"");if(this.allowFrom?.length&&!h.key.fromMe&&!this.allowFrom.some(R=>{let M=R.replace(/\+/g,"");return C.includes(M)||T.includes(M)}))continue;let L=h.key.fromMe?"me":h.pushName||C,E;if($&&this.sock)try{E=(await this.sock.groupMetadata(w)).subject}catch{}let F=this.resolveAgent(C,E,$?w:void 0);if(!F){this.log(`No route for ${$?`group ${E||w}`:C}, skipping`);continue}let J;if(p&&this.sock)try{let R=await(await import("@whiskeysockets/baileys")).downloadMediaMessage(h,"buffer",{},{reuploadRequest:this.sock.updateMediaMessage,logger:this.sock.logger});if(R){let M=b.imageMessage?.mimetype||b.audioMessage?.mimetype||"audio/ogg",j=M.split("/")[1]?.split(";")[0]||"bin",_=ze(this.sessionDir,"../media/inbound");Je(_,{recursive:!0});let ye=b.documentMessage?.fileName||`${St()}.${j}`,be=$t(_,ye);vt(be,R),J={path:be,type:M,fileName:ye},this.log(`WA media saved: ${M} -> ${be}`)}}catch(A){this.log(`WA media download failed: ${A.message}`)}let fe={id:h.key.id||String(Date.now()),channel:"whatsapp",accountId:"default",sender:{id:h.key.fromMe?(this.sock?.user?.id?.replace(/:.*/,"")||C)+"@s.whatsapp.net":C,name:L,username:C},group:$?{id:w,name:E||w}:void 0,text:y,media:J,replyTo:h.message?.extendedTextMessage?.contextInfo?.stanzaId,timestamp:new Date((h.messageTimestamp||0)*1e3),raw:h,resolvedAgent:F};this.handler(fe).catch(A=>{this.log(`Error handling message: ${A.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 Ke,mkdirSync as $e,existsSync as ee}from"fs";import{resolve as B}from"path";function U(g,e,t){let s=[];for(let n of g.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 qe(g,e,t){let s=g.trim().split(/\s+/);if(s.length!==5)throw new Error(`Invalid cron: ${g}`);let n=U(s[0],0,59),i=U(s[1],0,23),r=U(s[2],1,31),o=U(s[3],1,12),a=U(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 d=c.formatToParts(l),f=v=>parseInt(d.find(I=>I.type===v)?.value||"0",10),y=f("minute"),b=f("hour"),u=f("day"),m=f("month"),k=l.getDay();if(n.includes(y)&&i.includes(b)&&r.includes(u)&&o.includes(m)&&a.includes(k))return l;l.setTime(l.getTime()+6e4)}throw new Error(`No next run found for cron "${g}" within 1 year`)}var Se=5,Te=[3e4,6e4,3e5,9e5,36e5],te=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=B(process.cwd(),".agentx/cron/runs"),this.lastRunFile=B(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,onError:r.onError,consecutiveErrors:0,totalRuns:0,totalFailures:0})}setNotifyCallback(e){this.notifyCallback=e}async start(){this.running=!0,ee(this.runsDir)||$e(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=qe(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>=Se){this.log(`Job "${e}" exhausted all ${Se} retries, scheduling next regular run`),this.scheduleNext(e);return}let n=Te[t]||Te[Te.length-1];s.retryPending=!0,this.log(`Job "${e}" retry ${t+1}/${Se} 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:s.prompt,agentId:s.agent,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){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`),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=qe(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:`[MISSED RUN \u2014 was scheduled for ${s.toISOString()}]
|
|
30
|
+
|
|
31
|
+
${n.prompt}`,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=B(process.cwd(),".agentx/cron");ee(t)||$e(t,{recursive:!0}),Ke(this.lastRunFile,JSON.stringify(e,null,2))}catch{}}loadLastRuns(){try{if(!ee(this.lastRunFile))return null;let{readFileSync:e}=nt("fs");return JSON.parse(e(this.lastRunFile,"utf-8"))}catch{return null}}logRun(e){try{let t=B(this.runsDir,e.jobId);ee(t)||$e(t,{recursive:!0});let s=`${e.startedAt.toISOString().replace(/[:.]/g,"-")}.json`;Ke(B(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 Et}from"http";import{writeFileSync as jt,existsSync as Ot,unlinkSync as Lt,mkdirSync as Nt}from"fs";import{resolve as tt,dirname as Bt}from"path";var se=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 Tt}from"http";var ne=class{name="gitlab";config;handler;server;botUsername;botUsernames=new Set;sentNoteIds=new Set;usernameToAgent=new Map;agentToUsername=new Map;log;constructor(e,t=console.error.bind(console,"[gitlab]")){this.config=e,this.log=t}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())||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=Tt(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.getAgentToken(e.agentId),c=a||this.config.token;D.webhook("gitlab","send",`agentId="${e.agentId}" token=${a?"per-agent":"GLOBAL("+this.botUsername+")"}`);try{let l=await fetch(o,{method:"POST",headers:{"Content-Type":"application/json","PRIVATE-TOKEN":c},body:JSON.stringify({body:`${e.text}
|
|
32
|
+
|
|
33
|
+
<!-- agentx:${e.agentId||"unknown"} -->`})});if(!l.ok){let f=await l.text();return this.log(`GitLab API error: ${l.status} ${f}`),""}let h=await l.json(),d=String(h.id||"");return d&&this.sentNoteIds.add(d),d}catch(l){return this.log(`GitLab send error: ${l.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 p=o[1],w=s.match(/@(\w[\w.-]*)/g)?.map(T=>T.slice(1).replace(/[.]+$/,""))||[];if(!w.some(T=>{let x=this.usernameToAgent.get(T.toLowerCase());return x&&x!==p})){this.log(`AgentX comment from ${p}, no cross-agent mention, skipping (note ${r})`),t.writeHead(200),t.end("ok");return}this.log(`Bot-to-bot handoff: ${p} \u2192 ${w.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(p=>p.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),D.webhook("gitlab","handleNote",`noteId=${r} mentions=[${a.join(",")}] user=${i.username}`);let d=this.resolveAgentFromMention(s);if(D.webhook("gitlab","resolve",`agentId=${d??"NONE"}`),!d){this.log(`[handleNote] @mentions in note ${r} are not agents (${a.join(", ")}), skipping`),t.writeHead(200),t.end("ok");return}let f=d,y=this.config.agentMappings?.find(p=>p.agentId===f),b=y?.token;D.webhook("gitlab","token",`agent="${f}" mapping=${y?"found":"MISSING"} hasToken=${!!b}`),this.reactToNote(n,c,l,e.object_attributes.id,b).catch(()=>{});let u=`${n}:${c}:${l}`,m=await this.getChannelMeta(u),k=s.replace(/\n*<!-- agentx:\S+ -->/g,""),v=await this.downloadNoteImages(k,n,b||this.config.token),I={id:String(e.object_attributes.id),channel:"gitlab",accountId:"default",sender:{id:u,name:i.name,username:i.username},text:`[GitLab ${c} #${l}: ${h}]
|
|
34
|
+
${i.name} commented:
|
|
35
|
+
${k}`,timestamp:new Date,raw:e,resolvedAgent:f,channelMeta:m?{...m,issue:{type:c,iid:l,title:h}}:void 0,media:v};this.handler(I).catch(p=>{this.log(`Error handling note: ${p.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={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}
|
|
36
|
+
${s.description?.slice(0,500)||""}
|
|
37
|
+
URL: ${s.url}`,timestamp:new Date,raw:e,resolvedAgent:i};this.handler(r).catch(o=>this.log(`Error handling issue: ${o.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}
|
|
38
|
+
Branch: ${s.source_branch} -> ${s.target_branch}
|
|
39
|
+
${s.description?.slice(0,500)||""}
|
|
40
|
+
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){if(!this.handler){t.writeHead(200),t.end("ok");return}if(e.object_attributes.status!=="failed"){t.writeHead(200),t.end("ok");return}let s=e.object_attributes,n=e.project.path_with_namespace,i=this.resolveAgent(n),r={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}
|
|
41
|
+
Ref: ${s.ref}
|
|
42
|
+
Duration: ${s.duration}s
|
|
43
|
+
Please investigate the failure.`,timestamp:new Date,raw:e,resolvedAgent:i};this.handler(r).catch(o=>this.log(`Error handling pipeline: ${o.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){if(!i){this.log(`No agent token for reaction on note ${n} \u2014 skipping (would react as wrong user)`);return}let r=encodeURIComponent(e),o;switch(t){case"issue":o=`${this.config.host}/api/v4/projects/${r}/issues/${s}/notes/${n}/award_emoji`;break;case"merge_request":o=`${this.config.host}/api/v4/projects/${r}/merge_requests/${s}/notes/${n}/award_emoji`;break;default:return}try{let a=await fetch(o,{method:"POST",headers:{"Content-Type":"application/json","PRIVATE-TOKEN":i},body:JSON.stringify({name:"eyes"})});a.ok||this.log(`Reaction failed on note ${n}: ${a.status}`)}catch(a){this.log(`Failed to react to note ${n}: ${a.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`,d=encodeURIComponent(o),f=r==="merge_request"?"merge_requests":"issues",y=`${this.config.host}/api/v4/projects/${d}/${f}/${i}/add_spent_time`,b=this.getAgentToken(s)||this.config.token;try{let u=await fetch(y,{method:"POST",headers:{"Content-Type":"application/json","PRIVATE-TOKEN":b},body:JSON.stringify({duration:h})});if(u.ok)this.log(`Time logged: ${h} on ${o} ${r} #${i} (${s||"global"})`);else{let m=await u.text();this.log(`Time log failed (${u.status}): ${m.slice(0,100)}`)}}catch(u){this.log(`Time log error: ${u.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:d,writeFileSync:f}=await import("fs"),{resolve:y}=await import("path"),{randomUUID:b}=await import("crypto"),u=y(process.cwd(),".agentx/media/gitlab");d(u,{recursive:!0});let m=`${b().slice(0,8)}.${h}`,k=y(u,m);return f(k,c),this.log(`Downloaded GitLab image: ${k} (${c.length} bytes)`),{path:k,type:l,fileName:r.split("/").pop()||m}}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 G=class{module;minLevel;constructor(e,t="info"){this.module=e,this.minLevel=t}child(e){return new G(`${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(G.levelOrder[e]<G.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=G;z(W,"levelOrder",{debug:0,info:1,warn:2,error:3});var ie=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(`
|
|
44
|
+
`)[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(`
|
|
45
|
+
`)[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(`
|
|
46
|
+
`)}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 re=class{cache=new Map;config;meshPeers=[];constructor(e){this.config=e}build(e){e&&(this.meshPeers=e),this.cache.clear();for(let t of Object.keys(this.config.agents))this.cache.set(t,this.renderForAgent(t))}getForAgent(e){return this.cache.get(e)}refresh(e){this.build(e)}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(([d])=>d!==e);t.push(`Available agents on this node (${o.length} \u2014 not all may be in the current group):`);for(let[d,f]of o){let y=this.getPrimaryHandle(d),b=this.extractCapability(f),u=y?` (${y})`:"";t.push(`\u2022 ${f.name}${u} \u2014 ${b}`)}let a=this.meshPeers.filter(d=>d.healthy&&d.skills.length>0);if(a.length){t.push("Remote:");for(let d of a)for(let f of d.skills)t.push(`\u2022 ${f.name} [${d.peer}] \u2014 ${f.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(`
|
|
47
|
+
`)}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 oe=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}
|
|
48
|
+
|
|
49
|
+
${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 ae=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 D.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 ce=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 Ve={sun:0,mon:1,tue:2,wed:3,thu:4,fri:5,sat:6};function Ce(g,e){let s=new Intl.DateTimeFormat("en-US",{timeZone:e,weekday:"short",hour:"2-digit",minute:"2-digit",hour12:!1}).formatToParts(g),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:Ve[n]??0}}function O(g){let[e,t]=g.split(":").map(Number);return e*60+t}function Ae(g,e){return g.days.some(t=>Ve[t]===e)}function Ct(g,e,t){let{hour:s,minute:n,dow:i}=Ce(e,t);if(!Ae(g,i))return!1;let r=s*60+n,o=O(g.start),a=O(g.end);if(r<o||r>=a)return!1;if(g.lunch){let c=O(g.lunch.start),l=O(g.lunch.end);if(r>=c&&r<l)return!1}return!0}var le=class{constructor(e,t){this.org=e;this.config=t}isOnClock(e,t=new Date){let s=this.org.get(e);return s?Ct(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}=Ce(t,this.config.timezone);if(!Ae(s.schedule,r))return!1;let o=O(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}=Ce(t,this.config.timezone);if(!Ae(s.schedule,r))return!1;let o=O(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)}};import{readFileSync as Me,writeFileSync as Ye,existsSync as Ie,readdirSync as At,statSync as Mt}from"fs";import{resolve as xe,join as It}from"path";var Pe=/^(\s*)-\s*\[( |x|X)\]\s*(.*)$/,he=/@([a-z0-9_-]+)/i,Xe=/\[time:\s*([0-9hm\s]+)\]/i;function xt(g){let e=g.match(/(\d+)\s*h/i),t=g.match(/(\d+)\s*m/i);return(e?parseInt(e[1],10)*3600:0)+(t?parseInt(t[1],10)*60:0)}var Re=class{constructor(e){this.path=e}type="backlog";read(){let e=xe(process.cwd(),this.path);return Ie(e)?Me(e,"utf-8").split(`
|
|
50
|
+
`):[]}write(e){let t=xe(process.cwd(),this.path);Ye(t,e.join(`
|
|
51
|
+
`))}async listOpen(e){let t=this.read(),s=[];for(let n=0;n<t.length;n++){let i=t[n].match(Pe);if(!i||i[2].toLowerCase()==="x")continue;let o=i[3],a=o.match(he);if(a&&a[1].toLowerCase()!==e.toLowerCase())continue;let c=o.match(Xe),l=o.replace(he,"").replace(Xe,"").trim();s.push({id:`backlog:${n}`,title:l||`Task at line ${n+1}`,assignee:a?a[1]:void 0,estimatedSeconds:c?xt(c[1]):void 0,priority:n})}return s}async claim(e,t){}async report(e,t){if(!e.startsWith("backlog:"))throw new Error(`not a backlog id: ${e}`);let s=parseInt(e.slice(8),10),n=this.read(),i=n[s];if(!i)throw new Error(`backlog line ${s} missing`);if(!i.match(Pe))throw new Error(`backlog line ${s} not a checkbox`);if(t.status==="done"){n[s]=i.replace(/- \[ \]/,"- [x]");let o=t.timeSeconds?` <!-- done by agent, ${Math.round(t.timeSeconds/60)}m -->`:" <!-- done by agent -->";n.splice(s+1,0,o)}else t.status==="blocked"?n.splice(s+1,0,` <!-- BLOCKED: ${t.blocker||t.note||"?"} -->`):t.note&&n.splice(s+1,0,` <!-- ${t.note.replace(/-->/g,"\u2014 >")} -->`);this.write(n)}},_e=class{constructor(e,t){this.root=e;this.glob=t}type="wiki";walk(e,t){if(Ie(e))for(let s of At(e)){let n=It(e,s);Mt(n).isDirectory()?this.walk(n,t):s.endsWith(".md")&&t.push(n)}}async listOpen(e){let t=[];this.walk(xe(process.cwd(),this.root),t);let s=[];for(let n of t){let i=Me(n,"utf-8").split(`
|
|
52
|
+
`);for(let r=0;r<i.length;r++){let o=i[r].match(Pe);if(!o||o[2].toLowerCase()==="x")continue;let a=o[3].match(he);!a||a[1].toLowerCase()!==e.toLowerCase()||s.push({id:`wiki:${n}:${r}`,title:o[3].replace(he,"").trim(),assignee:a[1],priority:r})}}return s}async claim(){}async report(e,t){let s=e.split(":"),n=parseInt(s[s.length-1],10),i=s.slice(1,-1).join(":");if(!Ie(i))return;let r=Me(i,"utf-8").split(`
|
|
53
|
+
`);t.status==="done"&&r[n]&&(r[n]=r[n].replace(/- \[ \]/,"- [x]"),Ye(i,r.join(`
|
|
54
|
+
`)))}},De=class{constructor(e,t,s,n,i){this.host=e;this.token=t;this.projects=s;this.agentUsernames=n;this.log=i}type="gitlab";async api(e,t={}){let s=await fetch(`${this.host}/api/v4${e}`,{...t,headers:{"PRIVATE-TOKEN":this.token,"Content-Type":"application/json",...t.headers||{}}});if(!s.ok)throw new Error(`GitLab ${s.status}: ${await s.text().catch(()=>"")}`);return s.json()}async listOpen(e){let t=this.agentUsernames[e]||[];if(!t.length)return[];let s=[],n=this.projects.length?this.projects:["__ALL__"];for(let i of t)for(let r of n)try{let o=`assignee_username=${encodeURIComponent(i)}&state=opened&scope=all&per_page=20`,a=r==="__ALL__"?`/issues?${o}`:`/projects/${encodeURIComponent(r)}/issues?${o}`,c=await this.api(a);for(let l of c){let h=l.references?.full?.split("#")[0]||r;s.push({id:`gitlab:${h}:issue:${l.iid}`,title:l.title,description:l.description,assignee:e,url:l.web_url,priority:l.weight??99,estimatedSeconds:l.time_stats?.time_estimate||void 0})}}catch(o){this.log(`[business] GitLab listOpen failed for ${i} on ${r}: ${o.message}`)}return s}async claim(e,t){let{project:s,iid:n,type:i}=this.parseId(t),r=`/projects/${encodeURIComponent(s)}/${i==="issue"?"issues":"merge_requests"}/${n}?add_labels=Doing&remove_labels=To Do`;try{await this.api(r,{method:"PUT"})}catch(o){this.log(`[business] claim label update failed: ${o.message}`)}}async report(e,t){let{project:s,iid:n,type:i}=this.parseId(e),r=i==="issue"?"issues":"merge_requests";if(t.timeSeconds&&t.timeSeconds>0){let o=Math.max(1,Math.round(t.timeSeconds/60)),a=o>=60?`${Math.floor(o/60)}h${o%60?` ${o%60}m`:""}`:`${o}m`;try{await this.api(`/projects/${encodeURIComponent(s)}/${r}/${n}/add_spent_time?duration=${encodeURIComponent(a)}`,{method:"POST"})}catch(c){this.log(`[business] add_spent_time failed: ${c.message}`)}}if(t.status==="done")try{await this.api(`/projects/${encodeURIComponent(s)}/${r}/${n}?add_labels=Done&remove_labels=Doing`,{method:"PUT"})}catch(o){this.log(`[business] done label update failed: ${o.message}`)}else if(t.status==="blocked")try{await this.api(`/projects/${encodeURIComponent(s)}/${r}/${n}?add_labels=Blocked`,{method:"PUT"})}catch(o){this.log(`[business] blocked label update failed: ${o.message}`)}if(t.note)try{await this.api(`/projects/${encodeURIComponent(s)}/${r}/${n}/notes`,{method:"POST",body:JSON.stringify({body:t.note})})}catch(o){this.log(`[business] report note failed: ${o.message}`)}}parseId(e){let s=e.replace(/^gitlab:/,"").split(":"),n=s.pop(),i=s.pop()==="mr"?"mr":"issue";return{project:s.join(":"),type:i,iid:n}}};function Qe(g,e,t){let s=g.workSource;switch(s.type){case"backlog":return new Re(s.path);case"wiki":return new _e(s.path,s.glob);case"gitlab":{let n=e.channels.gitlab;if(!n?.token)throw new Error("business.workSource.type=gitlab requires channels.gitlab.token");let i={};for(let o of n.agentMappings)o.gitlabUsernames.length&&(i[o.agentId]=o.gitlabUsernames);let r=s.projects.length?s.projects:n.routes.map(o=>o.project);return new De(n.host,n.token,r,i,t)}}}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}
|
|
55
|
+
|
|
56
|
+
${s}`;if(!i.length){await this.postToMain(r,e);return}await this.postToMain(`${r}
|
|
57
|
+
|
|
58
|
+
cc: ${i.map(o=>`@${o}`).join(" ")}`,e)}};import{writeFileSync as Pt,existsSync as Ze,mkdirSync as Rt,readFileSync as _t}from"fs";import{resolve as Ee}from"path";function je(){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=Ee(process.cwd(),".agentx/kpi"),Ze(this.dir)||Rt(this.dir,{recursive:!0}),this.stats=this.loadOrInit()}stats;dir;onClockTick=new Map;loadOrInit(){let e=Ee(this.dir,`${je()}.json`);if(Ze(e))try{return JSON.parse(_t(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:je(),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!==je()&&(this.flush(),this.stats=this.loadOrInit());try{Pt(Ee(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(`
|
|
59
|
+
`)}};function S(g,e,t){g.statusCode=e,g.setHeader("Content-Type","application/json"),g.end(JSON.stringify(t))}async function ue(g){return new Promise((e,t)=>{let s=[];g.on("data",n=>s.push(n)),g.on("end",()=>{let n=Buffer.concat(s).toString("utf-8");if(!n)return e({});try{e(JSON.parse(n))}catch(i){t(i)}}),g.on("error",t)})}async function et(g,e,t,s){if(g==="GET /business/status"){let n=new Date,i=s.schedule.clockedInAgents(n);return S(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(g.startsWith("GET /business/work")){let i=new URL(e.url||"/","http://x").searchParams.get("agent");if(!i)return S(t,400,{error:"?agent= required"}),!0;try{let r=await s.workSource.listOpen(i);S(t,200,{items:r})}catch(r){S(t,500,{error:r.message})}return!0}if(g==="POST /business/claim"){let n=await ue(e),{agent:i,itemId:r}=n;if(!i||!r)return S(t,400,{error:"agent and itemId required"}),!0;try{await s.workSource.claim(i,r),S(t,200,{ok:!0})}catch(o){S(t,500,{error:o.message})}return!0}if(g==="POST /business/report"){let n=await ue(e);if(!n.itemId||!n.status)return S(t,400,{error:"itemId and status required"}),!0;if(!["in-progress","done","blocked"].includes(n.status))return S(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}),S(t,200,{ok:!0})}catch(i){S(t,500,{error:i.message})}return!0}if(g==="POST /business/escalate"){let n=await ue(e);return!n.agent||!n.subject?(S(t,400,{error:"agent and subject required"}),!0):(await s.reporter.escalate(n.agent,n.subject,n.detail||""),s.kpi.recordBlocker(n.agent),S(t,200,{ok:!0}),!0)}if(g==="POST /business/post"){let n=await ue(e);return n.text?(await s.reporter.postToMain(n.text,n.agent),S(t,200,{ok:!0}),!0):(S(t,400,{error:"text required"}),!0)}return!1}function Oe(g){return`## Business Layer Tools
|
|
60
|
+
|
|
61
|
+
During business hours you'll receive lifecycle prompts ([STANDUP], [WORK], [WRAP]).
|
|
62
|
+
Use these HTTP endpoints on the local daemon to act on them.
|
|
63
|
+
|
|
64
|
+
- **List your work:**
|
|
65
|
+
\`curl -s ${g}/business/work?agent=<your-id>\`
|
|
66
|
+
- **Claim an item** (optional \u2014 some sources no-op):
|
|
67
|
+
\`curl -s -X POST ${g}/business/claim -d '{"agent":"<your-id>","itemId":"<id>"}'\`
|
|
68
|
+
- **Report progress or completion:**
|
|
69
|
+
\`curl -s -X POST ${g}/business/report \\
|
|
70
|
+
-d '{"itemId":"<id>","status":"done","timeSeconds":1800,"note":"shipped"}'\`
|
|
71
|
+
status: \`in-progress\` | \`done\` | \`blocked\`
|
|
72
|
+
- **Escalate a blocker** up the org chart:
|
|
73
|
+
\`curl -s -X POST ${g}/business/escalate \\
|
|
74
|
+
-d '{"agent":"<your-id>","subject":"short","detail":"why it's stuck"}'\`
|
|
75
|
+
- **Post to the main channel** (daily plans, reports):
|
|
76
|
+
\`curl -s -X POST ${g}/business/post \\
|
|
77
|
+
-d '{"agent":"<your-id>","text":"your update"}'\`
|
|
78
|
+
|
|
79
|
+
Your org chart, role, and schedule are fixed in agentx.json under \`business.orgChart\`.
|
|
80
|
+
`}var Dt=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}`))},Dt)}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?`
|
|
81
|
+
Responsibilities:
|
|
82
|
+
${t.role.responsibilities.map(r=>`- ${r}`).join(`
|
|
83
|
+
`)}`:"",i=t.role.sopPath?`
|
|
84
|
+
SOP: @${t.role.sopPath}`:"";return`You are ${e}, role: ${t.role.title} ${s}.${n}${i}
|
|
85
|
+
|
|
86
|
+
${Oe(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)}
|
|
87
|
+
|
|
88
|
+
[STANDUP] It's the start of your work day.
|
|
89
|
+
1. Call GET /business/work?agent=${e} to see your open work items.
|
|
90
|
+
2. Pick your top 3 for today, in priority order.
|
|
91
|
+
3. Post your plan to the main channel via POST /business/post \u2014 keep it to 4-6 short lines.
|
|
92
|
+
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)}
|
|
93
|
+
|
|
94
|
+
[WORK] Continue your highest-priority open task.
|
|
95
|
+
- Call GET /business/work?agent=${e} to see what's assigned to you.
|
|
96
|
+
- If you complete one, POST /business/report with status:done and the time you spent.
|
|
97
|
+
- If blocked, POST /business/escalate with a concise blocker description.
|
|
98
|
+
- 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)}
|
|
99
|
+
|
|
100
|
+
[WRAP] End of work day. Post a concise daily report to the main channel via POST /business/post:
|
|
101
|
+
- Tasks closed today
|
|
102
|
+
- Time logged (total)
|
|
103
|
+
- Carry-overs to tomorrow
|
|
104
|
+
- Any outstanding blockers
|
|
105
|
+
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 ce(e),this.schedule=new le(this.org,e),this.workSource=Qe(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 et(e,t,s,n)}summary(){let e=this.schedule.clockedInAgents().length;return`Business layer: ${this.org.all().length} employees, ${e} on-clock now`}};var st=class{config;registry;router;cron;mesh;hooks;landscape;heartbeat;business;httpServer;webhooks;log;sseClients=new Set;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.config=We(e);let n=Ge(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 Le,Ne(process.cwd(),this.hooks),this.landscape=new re(this.config),this.registry=new Ue(this.config,this.log),Be(this.registry),this.registry.setLandscape(this.landscape),this.heartbeat=new oe(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 N(this.registry,this.config,this.hooks,this.log),this.webhooks=new ie(this.registry,{},this.log),Object.keys(this.config.services).length>0){let r=new ae(this.config.services,this.log);this.router.setServiceMatcher(r)}if(this.cron=new te(this.config,this.registry,this.hooks,this.log),this.cron.setNotifyCallback(async(r,o,a,c)=>{let l=`Cron "${r}" failed (${c}x)
|
|
106
|
+
Agent: ${o}
|
|
107
|
+
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)
|
|
108
|
+
${a.slice(0,300)}`,agentId:o,accountId:h.notify.accountId})}catch(d){this.log(`[CRON ALERT] notify send failed: ${d.message}`)}}),this.config.mesh.enabled&&(this.mesh=new q(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})`)}}this.log(""),this.log(" Ready."),this.log("");let s=tt(process.cwd(),".agentx/daemon.pid");Nt(Bt(s),{recursive:!0}),jt(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(`
|
|
109
|
+
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.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{}this.midnightTimer&&clearTimeout(this.midnightTimer),this.httpServer&&this.httpServer.close();try{let t=tt(process.cwd(),".agentx/daemon.pid");Ot(t)&&Lt(t)}catch{}this.log(` Shutdown complete (${Date.now()-e}ms)`),process.exit(0)}midnightTimer;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 Q(e,this.log);this.router.addChannel(t),this.log(" Telegram: enabled")}}if(this.config.channels.whatsapp.enabled){let e=new Z({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 se({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 ne({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.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=Et(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}
|
|
110
|
+
data: ${JSON.stringify({time:new Date().toISOString(),message:t})}
|
|
111
|
+
|
|
112
|
+
`;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
|
|
113
|
+
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}))||[]})}
|
|
114
|
+
|
|
115
|
+
`),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 /send":{let i=await H(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 H(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 H(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.]
|
|
116
|
+
|
|
117
|
+
${r}`,c=await this.registry.execute({agentId:o,message:a,context:{channel:"voice",sender:"Siri"}}),l=Ut(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 H(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);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","GET /.well-known/agent-card.json"]})}}catch(i){this.json(t,500,{error:i.message})}}async handleOpenAICompat(e,t,s){let n=await H(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]
|
|
118
|
+
${l.slice(-10).join(`
|
|
119
|
+
`)}
|
|
120
|
+
|
|
121
|
+
`:"";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)}`,y=await this.registry.execute({agentId:o,message:h+c.content,context:{channel:"api",sender:"openai-compat"}}),b=y.error||y.content||"",u={id:f,object:"chat.completion.chunk",created:Math.floor(Date.now()/1e3),model:o,choices:[{index:0,delta:{role:"assistant",content:b},finish_reason:"stop"}]};t.write(`data: ${JSON.stringify(u)}
|
|
122
|
+
|
|
123
|
+
`),t.write(`data: [DONE]
|
|
124
|
+
|
|
125
|
+
`),t.end()}else{let f=await this.registry.execute({agentId:o,message:h+c.content,context:{channel:"api",sender:"openai-compat"}}),y=f.error||f.content||"",b=Math.ceil(y.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:y},finish_reason:"stop"}],usage:{prompt_tokens:Math.ceil(c.content.length/4),completion_tokens:b,total_tokens:Math.ceil(c.content.length/4)+b}})}}json(e,t,s){e.writeHead(t,{"Content-Type":"application/json"}),e.end(JSON.stringify(s,null,2))}};function Ut(g){let e=g;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 H(g){return new Promise((e,t)=>{let s="";g.on("data",n=>s+=n.toString()),g.on("end",()=>{try{e(s?JSON.parse(s):{})}catch{e({})}}),g.on("error",t)})}import Wt from"path";import Gt from"fs-extra";function mn(){let g=Wt.join("package.json");return Gt.readJSONSync(g)}export{K as a,q as b,N as c,Q as d,Z as e,te as f,st as g,mn as h};
|
|
126
|
+
//# sourceMappingURL=chunk-T25UEXVK.js.map
|