agentix-cli 0.137.2 → 0.137.3
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/dist/{chunk-Q72TK3DO.js → chunk-5HLD66CW.js} +2 -2
- package/dist/chunk-A5GYG56I.js +2 -0
- package/dist/chunk-A5GYG56I.js.map +1 -0
- package/dist/{chunk-RTKSSFX2.js → chunk-ID4DCD2T.js} +5 -5
- package/dist/chunk-IS2IWRP7.js +4 -0
- package/dist/chunk-IS2IWRP7.js.map +1 -0
- package/dist/{chunk-DIE6XXJF.js → chunk-TEM7IIDC.js} +2 -2
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/net-family-X2P3OVDP.js +2 -0
- package/dist/ntfy-Q4YHFKWW.js +2 -0
- package/dist/{program-AMQ7JGHO.js → program-3TO4YAY7.js} +4 -4
- package/dist/push-PRFZMCIK.js +2 -0
- package/dist/push-bridge-HNQG2TOI.js +2 -0
- package/package.json +1 -1
- package/dist/chunk-GFKLKPPY.js +0 -4
- package/dist/chunk-GFKLKPPY.js.map +0 -1
- package/dist/chunk-PBP3D2BM.js +0 -2
- package/dist/chunk-PBP3D2BM.js.map +0 -1
- package/dist/net-family-OFHIZDHS.js +0 -2
- package/dist/ntfy-W56TUC5C.js +0 -2
- package/dist/push-B2GNI3VQ.js +0 -2
- package/dist/push-bridge-F4PCXWJF.js +0 -2
- /package/dist/{chunk-Q72TK3DO.js.map → chunk-5HLD66CW.js.map} +0 -0
- /package/dist/{chunk-RTKSSFX2.js.map → chunk-ID4DCD2T.js.map} +0 -0
- /package/dist/{chunk-DIE6XXJF.js.map → chunk-TEM7IIDC.js.map} +0 -0
- /package/dist/{net-family-OFHIZDHS.js.map → net-family-X2P3OVDP.js.map} +0 -0
- /package/dist/{ntfy-W56TUC5C.js.map → ntfy-Q4YHFKWW.js.map} +0 -0
- /package/dist/{program-AMQ7JGHO.js.map → program-3TO4YAY7.js.map} +0 -0
- /package/dist/{push-B2GNI3VQ.js.map → push-PRFZMCIK.js.map} +0 -0
- /package/dist/{push-bridge-F4PCXWJF.js.map → push-bridge-HNQG2TOI.js.map} +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{b as c}from"./chunk-
|
|
2
|
-
//# sourceMappingURL=chunk-
|
|
1
|
+
import{b as c}from"./chunk-IS2IWRP7.js";function f(n,e){return`${n}:${e}`}function m(n){return n.includes(":")}var P="/app#alerts",h=3e3,x=2,w=40,A=120,k=1e3;function M(n,e="AgentX"){let o=c(n.text||""),r=(n.buttons??[]).filter(t=>t.url&&t.url.length<=k).slice(0,x).map((t,d)=>({action:`b${d}`,title:g(t.label,w),url:t.url})),s={title:g(o.title||e,A),body:o.body,url:r[0]?.url??P,actions:r},a=()=>Buffer.byteLength(JSON.stringify(s),"utf8");if(a()>h){let t=[...s.body],d=a()-h;for(t=t.slice(0,Math.max(0,t.length-d-1)),s.body=t.join("")+"\u2026";a()>h&&t.length>0;)t=t.slice(0,Math.max(0,t.length-32)),s.body=t.join("")+"\u2026"}return s}function g(n,e){let o=[...n];return o.length>e?o.slice(0,e-1).join("")+"\u2026":n}var y=class{constructor(e){this.deps=e;this.log=e.log??console.error.bind(console,"[push]")}name="push";log;async start(){let e=this.deps.store.list().length;this.log(`push: ready (${e} subscribed phone${e===1?"":"s"})${this.deps.keys()?"":" \u2014 no keys yet, run: agentx app push-keys"}`)}async stop(){}onMessage(e){}async send(e){let o=this.deps.keys();if(!o)throw new Error("push: no keys \u2014 run `agentx app push-keys` on this node");let r=(e.chatId||"").trim(),s=r&&r!=="default"?r:void 0;s&&e.origin&&!m(s)&&(s=f(e.origin,s));let a=[];for(let i of this.deps.store.list(s))this.deps.deviceActive(i.deviceId)&&i.publicKey===o.publicKey?a.push(i):this.deps.store.unsubscribe(i.endpoint);if(a.length===0)throw new Error(s?`push: phone ${s} has not turned on notifications`:"push: no phone has turned on notifications \u2014 open Alerts in the phone app");let t=M(e),d=JSON.stringify(t),v={vapidDetails:{subject:this.deps.subject,publicKey:o.publicKey,privateKey:o.privateKey},TTL:this.deps.ttlSeconds},u=0,l=[];if(await Promise.all(a.map(async i=>{try{await this.deps.sender({endpoint:i.endpoint,keys:{p256dh:i.p256dh,auth:i.auth}},d,v),u++}catch(p){p?.statusCode===404||p?.statusCode===410?(this.deps.store.unsubscribe(i.endpoint),this.log(`push: removed expired subscription for ${i.deviceName}`)):l.push(`${i.deviceName}: ${p?.statusCode?`HTTP ${p.statusCode} `:""}${p?.body||p?.message||p}`.slice(0,200))}})),u===0)throw new Error(`push: not delivered \u2014 ${l.join("; ")||"every subscription had expired"}`);return l.length&&this.log(`push: ${l.length} phone(s) failed: ${l.join("; ")}`),this.deps.store.log({title:t.title,body:t.body,url:t.url,delivered:u,deviceId:s??null},this.deps.keepRecent),String(u)}},b=class{constructor(e,o,r=console.error.bind(console,"[push]"),s){this.peer=e;this.forward=o;this.log=r;this.origin=s}name="push";async start(){this.log(`push: relaying to ${this.peer}`)}async stop(){}onMessage(e){}async send(e){if(e.relayed)throw new Error(`push: this node relays too (channels.push.relayTo = ${this.peer}); set relayTo only on nodes that don't host the phone app`);return this.forward(this.peer,{channel:"push",chatId:e.chatId||"default",text:e.text,...e.buttons?.length?{buttons:e.buttons}:{},relayed:!0,...this.origin?{origin:this.origin}:{}})}};export{f as a,m as b,P as c,h as d,M as e,y as f,b as g};
|
|
2
|
+
//# sourceMappingURL=chunk-5HLD66CW.js.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import u from"node:net";import{existsSync as f,readFileSync as s}from"node:fs";import{resolve as i}from"node:path";function p(e){let t=u.getDefaultAutoSelectFamilyAttemptTimeout,n=u.setDefaultAutoSelectFamilyAttemptTimeout;if(typeof t!="function"||typeof n!="function")return;let o=t();return e>o&&n(e),t()}var c=5e3;function g(e=process.cwd(),t){let n=t?[i(e,t)]:[i(e,"agentx.json"),i(e,".agentx/config.json")];for(let o of n)if(f(o)){try{let r=JSON.parse(s(o,"utf-8"))?.node?.connectAttemptTimeoutMs;if(Number.isInteger(r)&&r>=250&&r<=6e4)return r}catch{}break}return c}function l(e){for(let t=0;t<e.length;t++){let n=e[t];if(n==="--")break;if(n.startsWith("--config="))return n.slice(9)||void 0;let o=e[t+1];if(n==="--config"&&o||n==="-c"&&o?.endsWith(".json"))return o}}export{p as a,c as b,g as c,l as d};
|
|
2
|
+
//# sourceMappingURL=chunk-A5GYG56I.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/daemon/net-family.ts"],"sourcesContent":["import net from \"node:net\"\nimport { existsSync, readFileSync } from \"node:fs\"\nimport { resolve } from \"node:path\"\n\n// --- Connect attempt timeout for dual-stack hosts (#930) ---\n//\n// Node 20+ turns on `autoSelectFamily` (happy eyeballs) for every outbound\n// connection, fetch included, and gives each address only 250 ms to connect\n// before moving to the next. On a host that resolves both A and AAAA but has\n// no IPv6 route, the IPv6 attempt fails at once and the IPv4 attempt is then\n// abandoned after 250 ms. A slow uplink whose TCP connect takes longer fails\n// every request with ETIMEDOUT about 300 ms in, while curl from the same\n// host succeeds. Raising the per-attempt timeout keeps happy eyeballs and\n// lets a slow but working link connect.\n\n/**\n * Raise Node's per-address connect attempt timeout to `ms`. Never lowers a\n * larger value already set (for example with\n * `--network-family-autoselection-attempt-timeout`). Returns the value in\n * effect afterwards, or undefined when this Node has no such setting.\n */\nexport function applyConnectAttemptTimeout(ms: number): number | undefined {\n const get = (net as any).getDefaultAutoSelectFamilyAttemptTimeout as (() => number) | undefined\n const set = (net as any).setDefaultAutoSelectFamilyAttemptTimeout as ((v: number) => void) | undefined\n if (typeof get !== \"function\" || typeof set !== \"function\") return undefined\n const current = get()\n if (ms > current) set(ms)\n return get()\n}\n\nexport const DEFAULT_CONNECT_ATTEMPT_TIMEOUT_MS = 5000\n\n/**\n * `node.connectAttemptTimeoutMs` read straight from the config file, for CLI\n * commands that call out on their own and never load the full daemon config.\n * Falls back to the default when there is no file or no valid value.\n */\nexport function readConnectAttemptTimeout(cwd: string = process.cwd(), configPath?: string): number {\n const candidates = configPath\n ? [resolve(cwd, configPath)]\n : [resolve(cwd, \"agentx.json\"), resolve(cwd, \".agentx/config.json\")]\n for (const p of candidates) {\n if (!existsSync(p)) continue\n try {\n const v = JSON.parse(readFileSync(p, \"utf-8\"))?.node?.connectAttemptTimeoutMs\n if (Number.isInteger(v) && v >= 250 && v <= 60_000) return v\n } catch { /* an invalid file is reported by whatever loads it in full */ }\n break\n }\n return DEFAULT_CONNECT_ATTEMPT_TIMEOUT_MS\n}\n\n/**\n * The config path given on the command line (`--config <path>`,\n * `--config=<path>`, or `-c <path>` naming a `.json` file), read before\n * commander parses anything. `-c` alone also means `--cwd` for some\n * commands, which names a folder, hence the `.json` check.\n */\nexport function configPathFromArgv(argv: string[]): string | undefined {\n for (let i = 0; i < argv.length; i++) {\n const a = argv[i]\n if (a === \"--\") break\n if (a.startsWith(\"--config=\")) return a.slice(\"--config=\".length) || undefined\n const next = argv[i + 1]\n if (a === \"--config\" && next) return next\n if (a === \"-c\" && next?.endsWith(\".json\")) return next\n }\n return undefined\n}\n"],"mappings":"AAAA,OAAOA,MAAS,WAChB,OAAS,cAAAC,EAAY,gBAAAC,MAAoB,UACzC,OAAS,WAAAC,MAAe,YAmBjB,SAASC,EAA2BC,EAAgC,CACzE,IAAMC,EAAON,EAAY,yCACnBO,EAAOP,EAAY,yCACzB,GAAI,OAAOM,GAAQ,YAAc,OAAOC,GAAQ,WAAY,OAC5D,IAAMC,EAAUF,EAAI,EACpB,OAAID,EAAKG,GAASD,EAAIF,CAAE,EACjBC,EAAI,CACb,CAEO,IAAMG,EAAqC,IAO3C,SAASC,EAA0BC,EAAc,QAAQ,IAAI,EAAGC,EAA6B,CAClG,IAAMC,EAAaD,EACf,CAACT,EAAQQ,EAAKC,CAAU,CAAC,EACzB,CAACT,EAAQQ,EAAK,aAAa,EAAGR,EAAQQ,EAAK,qBAAqB,CAAC,EACrE,QAAWG,KAAKD,EACd,GAAKZ,EAAWa,CAAC,EACjB,IAAI,CACF,IAAMC,EAAI,KAAK,MAAMb,EAAaY,EAAG,OAAO,CAAC,GAAG,MAAM,wBACtD,GAAI,OAAO,UAAUC,CAAC,GAAKA,GAAK,KAAOA,GAAK,IAAQ,OAAOA,CAC7D,MAAE,CAAuE,CACzE,MAEF,OAAON,CACT,CAQO,SAASO,EAAmBC,EAAoC,CACrE,QAASC,EAAI,EAAGA,EAAID,EAAK,OAAQC,IAAK,CACpC,IAAMC,EAAIF,EAAKC,CAAC,EAChB,GAAIC,IAAM,KAAM,MAChB,GAAIA,EAAE,WAAW,WAAW,EAAG,OAAOA,EAAE,MAAM,CAAkB,GAAK,OACrE,IAAMC,EAAOH,EAAKC,EAAI,CAAC,EAEvB,GADIC,IAAM,YAAcC,GACpBD,IAAM,MAAQC,GAAM,SAAS,OAAO,EAAG,OAAOA,EAGtD","names":["net","existsSync","readFileSync","resolve","applyConnectAttemptTimeout","ms","get","set","current","DEFAULT_CONNECT_ATTEMPT_TIMEOUT_MS","readConnectAttemptTimeout","cwd","configPath","candidates","p","v","configPathFromArgv","argv","i","a","next"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{Aa as Vu,D as Kn,Ea as Ju,F as mu,Fa as es,Ga as ji,Ha as Xu,Ia as Ku,J as yu,Ja as zu,Ka as Yu,L as wu,Ma as Qu,N as ku,O as _i,P as bu,Q as He,R as vu,S as zn,Ta as Zu,U as Mi,Ua as eh,V as Su,W as Di,X as tt,Y as Pu,Z as _u,a as Rd,aa as nt,b as Ii,ba as Mu,bb as Li,ca as Ou,cb as Fi,da as Du,ea as Nu,eb as th,f as Ti,fa as ju,g as Ed,ga as Lu,h as Cd,i as Ge,ia as be,j as Pd,ja as Yn,k as _d,ka as Ni,la as Fu,m as Md,ma as Kt,na as Uu,o as Od,oa as Pe,p as Kd,pa as Gu,q as zd,qa as Wu,r as Yd,ra as Hu,s as Qd,sa as Qn,t as Zd,ta as Zn,u as eu,ua as Bu,v as tu,va as qu,w as nu,x as su,y as Ci}from"./chunk-PNITKKDR.js";import{d as Td,g as xd,h as Ad}from"./chunk-Z2QOQBUS.js";import{b as Cu}from"./chunk-7LN4VKDU.js";import{a as Dd}from"./chunk-ZVP6GB4G.js";import{m as Ld,n as Fd,o as Ai,p as Ce,r as Ud,s as ke}from"./chunk-S2GAEH2F.js";import{i as iu,m as Pi,n as ou,p as au,q as cu,r as lu,s as du,t as uu,u as hu}from"./chunk-UNIXYV3U.js";import{f as Ri,g as qd,h as Jn,i as vt,k as We,m as Vd,n as Jd,o as Xd,p as Ei,q as Xn}from"./chunk-AKWBDCMU.js";import{a as gu}from"./chunk-PPUN7YHA.js";import{a as pu,b as fu}from"./chunk-VZLIBDLH.js";import{b as $u,c as Iu,d as Oi,e as Tu,f as xu,g as Au,h as Ru,i as Eu}from"./chunk-EKJG63EJ.js";import{b as Vn}from"./chunk-EMGB23H5.js";import{d as Gd}from"./chunk-
|
|
1
|
+
import{Aa as Vu,D as Kn,Ea as Ju,F as mu,Fa as es,Ga as ji,Ha as Xu,Ia as Ku,J as yu,Ja as zu,Ka as Yu,L as wu,Ma as Qu,N as ku,O as _i,P as bu,Q as He,R as vu,S as zn,Ta as Zu,U as Mi,Ua as eh,V as Su,W as Di,X as tt,Y as Pu,Z as _u,a as Rd,aa as nt,b as Ii,ba as Mu,bb as Li,ca as Ou,cb as Fi,da as Du,ea as Nu,eb as th,f as Ti,fa as ju,g as Ed,ga as Lu,h as Cd,i as Ge,ia as be,j as Pd,ja as Yn,k as _d,ka as Ni,la as Fu,m as Md,ma as Kt,na as Uu,o as Od,oa as Pe,p as Kd,pa as Gu,q as zd,qa as Wu,r as Yd,ra as Hu,s as Qd,sa as Qn,t as Zd,ta as Zn,u as eu,ua as Bu,v as tu,va as qu,w as nu,x as su,y as Ci}from"./chunk-PNITKKDR.js";import{d as Td,g as xd,h as Ad}from"./chunk-Z2QOQBUS.js";import{b as Cu}from"./chunk-7LN4VKDU.js";import{a as Dd}from"./chunk-ZVP6GB4G.js";import{m as Ld,n as Fd,o as Ai,p as Ce,r as Ud,s as ke}from"./chunk-S2GAEH2F.js";import{i as iu,m as Pi,n as ou,p as au,q as cu,r as lu,s as du,t as uu,u as hu}from"./chunk-UNIXYV3U.js";import{f as Ri,g as qd,h as Jn,i as vt,k as We,m as Vd,n as Jd,o as Xd,p as Ei,q as Xn}from"./chunk-AKWBDCMU.js";import{a as gu}from"./chunk-PPUN7YHA.js";import{a as pu,b as fu}from"./chunk-VZLIBDLH.js";import{b as $u,c as Iu,d as Oi,e as Tu,f as xu,g as Au,h as Ru,i as Eu}from"./chunk-EKJG63EJ.js";import{b as Vn}from"./chunk-EMGB23H5.js";import{d as Gd}from"./chunk-TEM7IIDC.js";import{g as Wd}from"./chunk-LQE73AZR.js";import{b as Hd,c as Bd}from"./chunk-2D3RYIDU.js";import{b as ru}from"./chunk-MRIVENBY.js";import{a as Nd,b as jd}from"./chunk-A5GYG56I.js";import{o as xi}from"./chunk-635B6SXI.js";import{A as ml,B as yl,C as wl,D as de,E as bl,F as vl,G as Sl,H as $l,I as Il,J as Tl,K as xl,L as dd,M as ud,N as hd,O as gd,P as pd,Q as fd,U as wd,a as Hc,b as gi,c as qc,ca as kd,d as Zc,e as el,f as Vt,h as fi,i as mi,j as yi,k as wi,l as tl,s as nl,t as il,u as Ze,v as ol,w as ll,x as J,z as fl}from"./chunk-5ADXRVYT.js";import{A as Zl,B as ed,C as td,D as nd,E as sd,F as rd,H as id,K as od,P as ad,Q as cd,T as ld,X as md,a as sl,ba as yd,c as al,d as cl,e as et,f as Ne,l as Al,n as Rl,p as Wl,q as Hl,r as Jl,s as Xl,t as Kl,u as zl,w as $i,x as Bn,y as Yl,z as Ql}from"./chunk-3K3LRLZX.js";import{b as Nl,c as jl,d as vi,f as Wn,g as Ll,h as Fl,i as Ul,j as Jt,k as Hn,l as Gl}from"./chunk-QSKG754X.js";import{a as ki,b as rl}from"./chunk-MEFPXHQJ.js";import{A as bd,B as qn,C as vd,h as ic,n as oc,o as Zr}from"./chunk-ZYUVKFX2.js";import{b as nc}from"./chunk-SFQUP3BP.js";import{a as Ue}from"./chunk-Y4CYQV7G.js";import{a as Sd,b as Xt,c as $d,d as Id}from"./chunk-LLNPKE7H.js";import{a as kt,b as bt}from"./chunk-SHEUCITK.js";import{a as El,b as bi,c as Cl,d as Pl,e as _l,i as Ml,j as Ol}from"./chunk-HF5EF335.js";import{b as kl}from"./chunk-W4YR2RO2.js";import{b as le,d as wt,f as je,g as Un,i as ue,j as Dl,k as Le,l as Fe,m as Vl,s as Si}from"./chunk-23LWLYOK.js";import{a as Bl,b as ql}from"./chunk-BXXP6DTB.js";import{d as dl,e as ul,f as hl,g as gl,h as pl,j as Gn}from"./chunk-X5C5W3E4.js";import{c as Fn}from"./chunk-OWNQ2FD4.js";import{d as cc,e as gk,m as Fc,n as $k,q as Uc,v as Gc,y as yt,z as jn}from"./chunk-SQ37I2MD.js";import{a as Qe,b as qt,f as hi}from"./chunk-72SZI2UW.js";import{a as Ht,b as ac}from"./chunk-FCPTN6NF.js";import{c as Wc}from"./chunk-MYVHVJWZ.js";import{e as Bc}from"./chunk-6TURWSMN.js";import{d as Vc,e as Jc,f as pi,g as Ln,h as Xc,i as Kc,j as zc,k as Yc,q as Qc}from"./chunk-AV7T3SYT.js";import{a as _n,b as hk}from"./chunk-7I7IEX4S.js";import{b as sc,e as rc}from"./chunk-JHCI6XCJ.js";import{e as Ke}from"./chunk-DSYKYZMT.js";import{A as Ac,Aa as Sk,D as Rc,E as ai,F as ci,H as li,J as Ec,K as Cc,M as Bt,N as Pc,O as _c,P as Mc,Q as Oc,R as kk,U as Dc,X as Nc,Y as jc,aa as bk,ca as di,d as mt,e as dc,ea as vk,f as ti,g as pk,h as Mn,i as ni,j as On,l as ze,m as si,n as uc,o as fk,p as Dn,q as mk,r as Nn,s as hc,sa as ui,t as yk,ta as Lc,u as gc,v as pc,w as fc,z as xc}from"./chunk-KHYF6KTY.js";import{e as Ye,f as mc,g as yc,h as ri}from"./chunk-BRK6M6LA.js";import{a as wc,b as kc,c as bc,e as vc,f as ii,g as Sc,h as $c,i as oi,j as Ic,k as Tc,n as wk}from"./chunk-UIBUZ2N3.js";import{b as ei,e as lc}from"./chunk-2QNT5JDS.js";import{d as ft}from"./chunk-QIJLFPWE.js";import{existsSync as Bi,readFileSync as qi}from"fs";fc();var nh=["answer","narration","line"],sh=5e3;function rh(r){return r==="/voice/queue"||r.startsWith("/voice/queue/")}async function ih(r,e,t,n,s,i){if(n==="/voice/queue"){if(t==="GET")return{status:200,body:{...r.view(),...i?{floor:i.view()}:{}}};if(t!=="POST")return{status:405,body:{error:"GET or POST"}};let l=String(s.text??"").trim(),d=String(s.agentId??s.agent??""),u=String(s.kind??"line");if(!l||!d||!nh.includes(u))return{status:400,body:{error:`Required: text and agentId; kind is ${nh.join(" | ")}`}};if(l.length>sh)return{status:413,body:{error:`text is over ${sh} characters`}};let g=e(d);if(!g)return{status:404,body:{error:`Unknown agent: ${d}`}};let{item:m,done:f}=r.enqueue({voice:g,text:l,agentId:d,kind:u,...typeof s.floor=="string"?{floor:s.floor}:{}});return s.wait!==!0?{status:202,body:{item:m}}:{status:200,body:{item:m,played:await f}}}if(t==="POST"&&n==="/voice/queue/pause")return r.pause(),{status:200,body:r.view()};if(t==="POST"&&n==="/voice/queue/resume")return r.resume(),{status:200,body:r.view()};let[,,,o,a,c]=n.split("/");if(t!=="POST"||!o||c!==void 0)return{status:404,body:{error:"Not found"}};switch(a){case"skip":return r.skip(o)?{status:200,body:r.view()}:Ui(o);case"front":return r.front(o)?{status:200,body:r.view()}:Ui(o);case"replay":{let l=r.replay(o);return l?{status:202,body:{item:l}}:Ui(o)}default:return{status:404,body:{error:"Not found"}}}}function oh(r){let e=n=>`${n.agentId??"someone"} (${n.kind}): "${n.text.length>80?`${n.text.slice(0,79)}\u2026`:n.text}" [${n.id}]`,t=r.playing?`Speaking: ${e(r.playing)}`:r.paused?"Paused: the listener is speaking; the queue plays on after.":"Nothing is speaking.";return[...r.floor?.held?[pc(r.floor)]:[],t,...r.waiting.map((n,s)=>`${s+1}. waiting: ${e(n)}`)].join(`
|
|
2
2
|
`)}var Ui=r=>({status:404,body:{error:`No such line in the queue: ${r}`}});import{resolve as Vi}from"path";vk();var Ik={name:"agentx",version:"0.1.0"},Tk=["2024-11-05","2025-03-26","2025-06-18","2025-11-25"],xk="2024-11-05";function Ak(r){return typeof r=="string"&&Tk.includes(r)?r:xk}var Rk={tools:{},elicitation:{form:{}}},Ek=0;async function ah(r,e){let t=++Ek;return st({jsonrpc:"2.0",id:`elicit-${t}`,method:"elicitation/create",params:{mode:"form",message:r,requestedSchema:e}}),new Promise(n=>{zt.set(`elicit-${t}`,s=>{s?.action==="accept"&&s.content?n(s.content):n(null)}),setTimeout(()=>{zt.has(`elicit-${t}`)&&(zt.delete(`elicit-${t}`),n(null))},6e4)})}var zt=new Map,Ck="http://localhost:18800",Gi;function Pk(){if(process.env.AGENTX_DAEMON_URL)return process.env.AGENTX_DAEMON_URL;for(let r of["agentx.json",".agentx/config.json"])try{let e=Vi(process.cwd(),r);if(!Bi(e))continue;let t=JSON.parse(qi(e,"utf-8"))?.node?.bind;if(typeof t!="string"||!t.includes(":"))continue;{let n=t.lastIndexOf(":"),s=t.slice(0,n),i=t.slice(n+1),o=!s||s==="0.0.0.0"||s==="::"||s==="*"?"127.0.0.1":s;if(i)return`http://${o}:${i}`}}catch{}return Ck}function X(){return Gi===void 0&&(Gi=Pk()),Gi}var Wi;function _k(r){try{let e=Vi(process.cwd(),".env");if(!Bi(e))return"";for(let t of qi(e,"utf-8").split(`
|
|
3
3
|
`)){let n=t.trim().replace(/^export\s+/,"");if(!n.startsWith(`${r}=`))continue;let s=n.slice(r.length+1).trim();return/^(["']).*\1$/.test(s)?s.slice(1,-1):s}}catch{}return""}function Mk(r=process.env){if(r.AGENTX_TOKEN)return r.AGENTX_TOKEN;if(r.MESH_TOKEN)return r.MESH_TOKEN;let e=_k("MESH_TOKEN");if(e)return e;for(let t of["agentx.json",".agentx/config.json"])try{let n=Vi(process.cwd(),t);if(!Bi(n))continue;let s=JSON.parse(qi(n,"utf-8"))?.dashboard?.token;if(typeof s=="string"&&s)return s}catch{}return""}function Ok(){return Wi===void 0&&(Wi=Mk()),Wi}var K=(r,e)=>{let t=Ok();if(!t)return fetch(r,e);let n=new Headers(e?.headers);return n.has("Authorization")||n.set("Authorization",`Bearer ${t}`),fetch(r,{...e,headers:n})};function Hi(r=process.env){let e={};return r.AGENTX_TASK_ID&&(e.callerTaskId=r.AGENTX_TASK_ID),r.AGENTX_CHANNEL&&(e.callerChannel=r.AGENTX_CHANNEL),r.AGENTX_CHAT_ID&&(e.callerChatId=r.AGENTX_CHAT_ID),e}async function Dk(){let r=process.env.AGENTX_AGENT_ID,e=Hi();if(!(!r||!e.callerChannel||!e.callerChatId))try{let t=new URLSearchParams({channel:e.callerChannel,chatId:e.callerChatId}),n=await K(`${X()}/agents/${encodeURIComponent(r)}/intent-path?${t}`,{signal:AbortSignal.timeout(2e3)});if(!n.ok)return;let s=await n.json();return{...Array.isArray(s.path)&&s.path.length?{path:s.path}:{},...typeof s.graphWeight=="number"?{graphWeight:s.graphWeight}:{}}}catch{return}}var Nk=1e4,jk=new Set(["ECONNREFUSED","ENOTFOUND","EAI_AGAIN","EHOSTUNREACH","ENETUNREACH"]);function Lk(r,e){let t=e,n=t?.cause?.code??t?.code;if(n&&jk.has(n))return`Error: the AgentX daemon could not be reached (${n}); the message to ${r} was not sent.`;let s=t?.name==="TimeoutError"||t?.name==="AbortError"?"no answer in time":t?.message||String(e);return`Probably delivered to ${r}: the daemon did not confirm (${s}), but the request was sent and is most likely queued. Do not treat this as a failure. If unsure, send the same message again: within 10 minutes a repeat returns the existing task id instead of a second task, and delivers it if the first never arrived. A busy ${r} shows no trace until its turn starts; later, \`agentx trace list --agent ${r} --since 1h\` or the daemon log ("[send/agent]") shows it.`}var Fk=/\x1B\[[0-9;]*[A-Za-z]/g;function St(r){return r.replace(Fk,"")}async function Uk(r,e,t){let n=await K(`${X()}/recall`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({agent:r,channel:e,limit:Math.min(Math.max(t??30,1),100)})}),s=await n.json().catch(()=>({}));if(!n.ok)return{content:[{type:"text",text:`Error: ${s.error||n.statusText}`}]};let i=s.turns||[],o=e?`on ${e}`:"on any channel";return i.length===0?{content:[{type:"text",text:`No other messages ${o} today for ${r}.`}]}:{content:[{type:"text",text:Gk(i,!!s.hasMore)}]}}function Gk(r,e){let t=new Map;for(let s of r){let i=`${s.channel}/${s.chatId}`;t.has(i)||t.set(i,[]),t.get(i).push(s)}let n=[];for(let[s,i]of t){let o=[...i].sort((a,c)=>a.ts.localeCompare(c.ts)).map(a=>{let c=a.role==="agent"?"you":a.senderName||"user";return` [${a.ts.slice(11,16)}] ${c}: ${a.content.slice(0,300)}`});n.push(`From ${s}:
|
|
4
4
|
${o.join(`
|
|
@@ -530,7 +530,7 @@ ${l}`)}let S={agentId:o,message:k,context:{channel:w,sender:f?"opencode":"openai
|
|
|
530
530
|
|
|
531
531
|
`)}}function $g(r){r.writeHead(200,{"Content-Type":"text/event-stream; charset=utf-8","Cache-Control":"no-cache",Connection:"keep-alive","X-Accel-Buffering":"no"})}function Ig(r,e,t,n,s,i){r.write(`data: ${JSON.stringify({id:e,object:"chat.completion.chunk",created:t,model:n,choices:[{index:0,delta:s,finish_reason:i}]})}
|
|
532
532
|
|
|
533
|
-
`)}function Tg(r,e,t,n,s,i){let o=i?.usage?.inputTokens??0,a=i?.usage?.outputTokens??Math.ceil(s.length/4);wo(r,200,{id:e,object:"chat.completion",created:t,model:n,choices:[{index:0,message:{role:"assistant",content:s},finish_reason:"stop"}],usage:{prompt_tokens:o,completion_tokens:a,total_tokens:o+a}})}function wo(r,e,t){r.writeHead(e,{"Content-Type":"application/json"}),r.end(JSON.stringify(t))}var an=class{module;minLevel;constructor(e,t="info"){this.module=e,this.minLevel=t}child(e){return new an(`${this.module}:${e}`,this.minLevel)}debug(e,t){this.emit("debug",e,t)}info(e,t){this.emit("info",e,t)}warn(e,t){this.emit("warn",e,t)}error(e,t){this.emit("error",e,t)}asConsoleLog(){return(...e)=>{let n=e.map(s=>typeof s=="string"?s:JSON.stringify(s)).join(" ").replace(/^\[agentx\]\s*/,"");n&&this.info(n)}}emit(e,t,n){if(an.levelOrder[e]<an.levelOrder[this.minLevel])return;let s={time:new Date().toISOString(),level:e,module:this.module,msg:t,...n},i=e==="error"?"ERROR":e==="warn"?"WARN":"",o=`[${this.module}]`,a=i?`${o} ${i}: ${t}`:`${o} ${t}`;console.error(a)}},on=an;ft(on,"levelOrder",{debug:0,info:1,warn:2,error:3});import{realpathSync as Vv,statSync as Jv}from"fs";var Et={version:"0.137.
|
|
533
|
+
`)}function Tg(r,e,t,n,s,i){let o=i?.usage?.inputTokens??0,a=i?.usage?.outputTokens??Math.ceil(s.length/4);wo(r,200,{id:e,object:"chat.completion",created:t,model:n,choices:[{index:0,message:{role:"assistant",content:s},finish_reason:"stop"}],usage:{prompt_tokens:o,completion_tokens:a,total_tokens:o+a}})}function wo(r,e,t){r.writeHead(e,{"Content-Type":"application/json"}),r.end(JSON.stringify(t))}var an=class{module;minLevel;constructor(e,t="info"){this.module=e,this.minLevel=t}child(e){return new an(`${this.module}:${e}`,this.minLevel)}debug(e,t){this.emit("debug",e,t)}info(e,t){this.emit("info",e,t)}warn(e,t){this.emit("warn",e,t)}error(e,t){this.emit("error",e,t)}asConsoleLog(){return(...e)=>{let n=e.map(s=>typeof s=="string"?s:JSON.stringify(s)).join(" ").replace(/^\[agentx\]\s*/,"");n&&this.info(n)}}emit(e,t,n){if(an.levelOrder[e]<an.levelOrder[this.minLevel])return;let s={time:new Date().toISOString(),level:e,module:this.module,msg:t,...n},i=e==="error"?"ERROR":e==="warn"?"WARN":"",o=`[${this.module}]`,a=i?`${o} ${i}: ${t}`:`${o} ${t}`;console.error(a)}},on=an;ft(on,"levelOrder",{debug:0,info:1,warn:2,error:3});import{realpathSync as Vv,statSync as Jv}from"fs";var Et={version:"0.137.3",commit:"c2e6a0a",startedAt:new Date(Date.now()-process.uptime()*1e3).toISOString()};function Xv(r,e){let t=e.mtimeMs!==null&&e.mtimeMs>Date.parse(r.startedAt),n=!!e.version&&!!r.version&&e.version!==r.version;return{newer:t||n,changedAt:e.mtimeMs===null?null:new Date(e.mtimeMs).toISOString(),version:e.version}}function Ag(){let r=null;try{r=Jv(Vv(process.argv[1])).mtimeMs}catch{}let e=Xh().version;return Xv(Et,{mtimeMs:r,version:e&&e!=="0.0.0"?e:null})}import{randomUUID as Kv}from"crypto";var Rs=class{constructor(e=new ll){this.core=e}count=0;subscribe(e,t){this.count++;let n=this.core.subscribe((i,o)=>{if(!o||!zv.includes(o.kind))return;let a={...o,rootId:i.rootId,node:i.node};Qv(e,a)&&t(a)}),s=!0;return()=>{s&&(s=!1,this.count--,n())}}publish(e){let t={id:typeof e.id=="string"?e.id:Kv(),at:typeof e.at=="string"?e.at:new Date().toISOString(),...e},{type:n,summary:s,ref:i,agentId:o}=Yv(t);this.core.publish({id:t.id,at:t.at,kind:t.kind,type:n,summary:s,ref:i,agentId:o,rootId:typeof e.rootId=="string"?e.rootId:void 0,data:t})}get size(){return this.count}},zv=["run","task","signal","mesh","channel","status"];function Yv(r){switch(r.kind){case"run":return{type:r.phase,ref:r.runId,summary:[r.workflowId,r.nodeId,r.phase,r.note].filter(Boolean).join(" ")};case"task":return{type:r.phase,ref:r.taskId,summary:`${r.title??r.taskId} ${r.phase}`};case"signal":return{type:"emitted",summary:`signal ${r.name} (${r.scope})`};case"mesh":return{type:r.delta,summary:`peer ${r.peer} ${r.delta}`};case"channel":return{type:r.direction,ref:r.messageId,agentId:r.agentId,summary:`${r.channel} ${r.direction==="in"?"inbound":"outbound"}`};case"status":return{type:"status",summary:r.message}}}function Qv(r,e){return!(r.kinds&&r.kinds.length>0&&!r.kinds.includes(e.kind)||r.workflowId&&(e.kind==="run"&&e.workflowId!==r.workflowId||e.kind==="task"&&e.workflowId!==r.workflowId||e.kind==="signal"&&e.workflowId&&e.workflowId!==r.workflowId)||r.runId&&(e.kind==="run"&&e.runId!==r.runId||e.kind==="task"&&e.runId!==r.runId)||r.actor&&(e.kind!=="task"||!(e.assignedTo?.includes(r.actor)||e.submittedBy===r.actor))||r.channel&&(e.kind!=="channel"||e.channel!==r.channel))}function Rg(r){if(!r)return;let e=["run","task","signal","mesh","channel","status"],n=r.split(",").map(s=>s.trim()).filter(Boolean).filter(s=>e.includes(s));return n.length?n:void 0}import{createHmac as Es,timingSafeEqual as Eg}from"crypto";var Cs=class{registry;config;mesh;webhookEntries;log;workflowDispatcher;hooks;constructor(e,t={},n=console.error.bind(console,"[webhook]"),s,i=[],o){this.registry=e,this.config=t,this.log=n,this.mesh=s,this.webhookEntries=i,this.hooks=o}hookEventFor(e,t){if(e==="stripe")return"on:stripe-event";if(e==="sentry")return"on:sentry-issue";if(e==="vercel")return"on:vercel-deployment";if(e==="hubspot")return"on:hubspot-event";if(e==="odoo")return"on:odoo-event";if(e==="github"){let n=(t??"").split(".")[0];return n==="issues"||n==="issue_comment"?"on:github-issue":n==="pull_request"||n==="pull_request_review"?"on:github-pr":n==="push"?"on:github-push":null}return null}async fireHookSafe(e,t,n,s){let i=this.hookEventFor(e,t);if(!i||!this.hooks||!this.hooks.has(i))return;let o={};if(e==="github"){let a=s;typeof a.sender?.login=="string"&&(o.author=a.sender.login),typeof a.repository?.full_name=="string"&&(o.repo=a.repository.full_name);let c=a.issue?.number??a.pull_request?.number;typeof c=="number"&&(o.number=c)}try{await this.hooks.execute(i,{event:i,source:e,eventType:t,agentId:n,payload:s,...o})}catch(a){this.log(`${i} hook error (non-fatal): ${a?.message??a}`)}}setWorkflowDispatcher(e){this.workflowDispatcher=e}setWebhookEntries(e){this.webhookEntries=e}async handle(e,t,n){let s=n.replace(/^\/webhook\/?/,"").split("/").filter(Boolean),i=s[0],o=s[1];if(!i){this.sendJson(t,400,{error:"Missing agent ID. Use /webhook/:agentId"});return}let{raw:a,parsed:c}=await this.readBody(e),l=o||this.detectSource(e.headers);if(process.env.AGENTX_WEBHOOK_DEBUG_HEADERS==="1"){let b={};for(let[k,S]of Object.entries(e.headers))(k.startsWith("x-hubspot")||k.startsWith("x-hub")||k.startsWith("x-github")||k.startsWith("x-gitlab")||k.startsWith("stripe")||k.startsWith("sentry")||k.startsWith("x-vercel")||k.startsWith("x-odoo")||k.startsWith("x-webhook")||k==="user-agent"||k==="content-type"||k==="host"||k==="x-forwarded-host"||k==="x-forwarded-proto"||k==="x-forwarded-for")&&(b[k]=S);this.log(`Webhook [${l}] reqUrl=${e.url} headers: ${JSON.stringify(b)}`)}let d=this.validateSignature(e.headers,a,l,i,e.url);if(d){this.log(`Webhook [${l}] -> ${i}: REJECTED (${d})`),this.sendJson(t,401,{error:d});return}let u=this.buildSummary(l,c,e.headers);this.log(`Webhook [${l}] -> ${i}: ${u.slice(0,100)}`);let g=this.extractEventType(l,c,e.headers),m=this.webhookEntries.find(b=>b.enabled&&b.agentId===i&&b.source===l),f=g&&m?.triggers?.[g]||m?.defaultWorkflow||null;if(await this.fireHookSafe(l,g,i,c),f&&this.workflowDispatcher)try{let b=await this.workflowDispatcher.dispatchWorkflow({workflowId:f,entityRef:{kind:"webhook",id:`${l}:${i}:${g??"\u2014"}:${Date.now()}`},event:{kind:"webhook",source:l,eventType:g,payload:c},trigger:{source:l}});this.log(`Webhook [${l}/${g??"\u2014"}] -> workflow=${f} (claimed=${b.claimed})`),this.sendJson(t,202,{ok:!0,agent:i,source:l,eventType:g,workflow:f,claimed:b.claimed});return}catch(b){this.log(`Workflow dispatch failed [${l}/${g}/${f}]: ${b.message}`)}let w=this.webhookEntries.find(b=>b.enabled&&b.node&&b.agentId===i&&b.source===l);if(w?.node&&this.mesh){this.sendJson(t,202,{ok:!0,agent:i,source:l,node:w.node,status:"accepted"}),this.mesh.sendTask(w.node,u,i,{context:{channel:`webhook:${l}`,sender:`webhook:${l}`}}).then(b=>this.log(`Mesh-forwarded webhook [${l}] -> ${w.node}/${i}: ${b?.slice(0,100)||"ok"}`),b=>this.log(`Mesh forward failed [${l}] -> ${w.node}/${i}: ${b.message}`));return}try{let b=await this.registry.execute({message:u,agentId:i,context:{channel:`webhook:${l}`,sender:`webhook:${l}`}});this.sendJson(t,b.error?500:200,{ok:!b.error,agent:i,source:l,response:b.content?.slice(0,500),error:b.error,duration:b.duration})}catch(b){this.sendJson(t,500,{error:b.message})}}extractEventType(e,t,n){let s=i=>{let o=n[i.toLowerCase()];return Array.isArray(o)?o[0]:o};if(e==="github"){let i=s("x-github-event");if(!i)return;let o=typeof t.action=="string"?t.action:void 0;return o?`${i}.${o}`:i}if(e==="gitlab")return s("x-gitlab-event")||(typeof t.object_kind=="string"?t.object_kind:void 0);if(e==="stripe")return typeof t.type=="string"?t.type:void 0;if(e==="sentry")return s("sentry-hook-resource");if(e==="hubspot"){if(Array.isArray(t)&&t.length>0){let i=t[0];if(i&&typeof i.subscriptionType=="string")return i.subscriptionType}return typeof t.subscriptionType=="string"?t.subscriptionType:void 0}if(e==="odoo")return typeof t.event=="string"?t.event:typeof t.subscriptionType=="string"?t.subscriptionType:typeof t.model=="string"?typeof t.action=="string"?`${t.model}.${t.action}`:t.model:void 0}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-hubspot-signature-v3"]||e["x-hubspot-signature-version"]?"hubspot":e["x-odoo-database"]||e["x-odoo-signature"]?"odoo":e["x-hub-signature-256"]?"github":"unknown"}buildSummary(e,t,n){let s=[`[Webhook from ${e}]`];switch(e){case"gitlab":{let o=n["x-gitlab-event"]||t.object_kind||"event",a=t.project?.path_with_namespace||"",c=t.user?.name||t.user_username||"";if(s.push(`Event: ${o}`),a&&s.push(`Project: ${a}`),c&&s.push(`User: ${c}`),t.ref&&s.push(`Ref: ${t.ref}`),t.commits&&Array.isArray(t.commits)){s.push(`Commits: ${t.commits.length}`);for(let d of t.commits.slice(0,3))s.push(` - ${d.message?.split(`
|
|
534
534
|
`)[0]||"no message"} (${d.author?.name||""})`)}let l=t.object_attributes;if(l?.title&&(s.push(`Title: ${l.title}`),s.push(`State: ${l.state||""}`),s.push(`Action: ${l.action||""}`),l.source_branch&&s.push(`Branch: ${l.source_branch} -> ${l.target_branch}`),l.url&&s.push(`URL: ${l.url}`)),l?.iid&&!l?.source_branch&&(s.push(`Issue #${l.iid}: ${l.title||""}`),l.description&&s.push(`Description: ${l.description.slice(0,200)}`)),t.object_kind==="pipeline"){let d=t.object_attributes;s.push(`Pipeline: ${d?.status||""} (${d?.ref||""})`),s.push(`Duration: ${d?.duration||0}s`)}break}case"github":{let o=n["x-github-event"]||"event",a=t.repository?.full_name||"",c=t.sender?.login||"";if(s.push(`Event: ${o}`),a&&s.push(`Repository: ${a}`),c&&s.push(`Sender: ${c}`),t.ref&&s.push(`Ref: ${t.ref}`),t.commits&&Array.isArray(t.commits))for(let u of t.commits.slice(0,3))s.push(` - ${u.message?.split(`
|
|
535
535
|
`)[0]||""} (${u.author?.name||""})`);let l=t.pull_request;l&&(s.push(`PR #${l.number}: ${l.title}`),s.push(`Action: ${t.action}`),s.push(`Branch: ${l.head?.ref} -> ${l.base?.ref}`));let d=t.issue;d&&(s.push(`Issue #${d.number}: ${d.title}`),s.push(`Action: ${t.action}`));break}case"stripe":{let o=t.type||"event",a=t.data?.object||{};s.push(`Event: ${o}`),a.amount&&s.push(`Amount: ${(a.amount/100).toFixed(2)} ${a.currency?.toUpperCase()||""}`),a.customer_email&&s.push(`Customer: ${a.customer_email}`),a.description&&s.push(`Description: ${a.description}`),a.status&&s.push(`Status: ${a.status}`);break}case"sentry":{let o=n["sentry-hook-resource"]||"event";s.push(`Resource: ${o}`);let a=t.data||t;a.error?.title&&s.push(`Error: ${a.error.title}`),a.error?.culprit&&s.push(`Culprit: ${a.error.culprit}`),a.error?.metadata?.value&&s.push(`Message: ${a.error.metadata.value}`),t.url&&s.push(`URL: ${t.url}`);break}case"hubspot":{let o=Array.isArray(t)?t:null,a=o||null;if(a&&a.length){s.push(`Events: ${a.length}`);for(let l of a.slice(0,3)){let d=l.subscriptionType??"?",u=l.objectId??"?",g=l.changeFlag??l.changeSource??"";s.push(` - ${d} (object ${u}${g?`, ${g}`:""})`)}}else{let l=t.subscriptionType??"event",d=t.objectId??"?";s.push(`Event: ${l} (object ${d})`)}let c=a?.[0]?.portalId??t.portalId;c!=null&&s.push(`Portal: ${c}`);break}case"odoo":{let o=typeof t.event=="string"&&t.event||typeof t.subscriptionType=="string"&&t.subscriptionType||typeof t.model=="string"&&(typeof t.action=="string"?`${t.model}.${t.action}`:t.model)||"event";s.push(`Event: ${o}`);let a=n["x-odoo-database"]||(typeof t.database=="string"?t.database:"");a&&s.push(`Database: ${a}`);let c=t.record||t;c?.id&&s.push(`Record id: ${c.id}`),c?.name&&s.push(`Name: ${String(c.name).slice(0,200)}`),c?.partner_id?.[1]&&s.push(`Partner: ${c.partner_id[1]}`),c?.email&&s.push(`Email: ${c.email}`),c?.amount_total!=null&&s.push(`Amount: ${c.amount_total}`),c?.state&&s.push(`State: ${c.state}`);break}default:s.push(`Headers: ${JSON.stringify(Object.keys(n).filter(o=>o.startsWith("x-")).slice(0,5))}`),s.push(`Payload keys: ${Object.keys(t).slice(0,10).join(", ")}`);let i=JSON.stringify(t).slice(0,500);s.push(`Body: ${i}`)}return s.join(`
|
|
536
536
|
`)}async readBody(e){return new Promise(t=>{let n="";e.on("data",s=>n+=s.toString()),e.on("end",()=>{let s;try{s=n?JSON.parse(n):{}}catch{s={raw:n}}t({raw:n,parsed:s})}),e.on("error",()=>t({raw:"",parsed:{}}))})}validateSignature(e,t,n,s,i){let o=this.webhookEntries.find(d=>d.enabled&&d.agentId===s&&d.source===n);if(!o?.secretEnv)return null;let a=process.env[o.secretEnv];if(!a)return`webhook entry "${o.id}" expects env ${o.secretEnv} but it is unset`;let c=d=>{let u=e[d.toLowerCase()];return Array.isArray(u)?u[0]:u};if(n==="github"){let d=c("x-hub-signature-256");if(!d)return"missing X-Hub-Signature-256";let u="sha256="+Es("sha256",a).update(t).digest("hex");return Ct(u,d)}if(n==="gitlab"){let d=c("x-gitlab-token");return d?Ct(a,d):"missing X-Gitlab-Token"}if(n==="stripe"){let d=c("stripe-signature");if(!d)return"missing Stripe-Signature";let u=Object.fromEntries(d.split(",").map(m=>m.split("=")));if(!u.t||!u.v1)return"malformed Stripe-Signature";let g=Es("sha256",a).update(`${u.t}.${t}`).digest("hex");return Ct(g,u.v1)}if(n==="hubspot"){let d=c("x-hubspot-signature-v3"),u=c("x-hubspot-request-timestamp");if(!d)return"missing X-HubSpot-Signature-v3";if(!u)return"missing X-HubSpot-Request-Timestamp";let g=parseInt(u,10);if(!Number.isFinite(g))return"malformed X-HubSpot-Request-Timestamp";let m=Date.now()-g;if(m>5*6e4)return`HubSpot webhook is ${Math.floor(m/1e3)}s old (>5min replay window)`;let f="POST",w=i||e["x-original-uri"]||e["x-forwarded-path"]||"",b=e["x-forwarded-host"],k=e.host,S=e["x-forwarded-proto"]||"https",I=new Set;b&&w&&I.add(`${S}://${b}${w}`),k&&w&&I.add(`https://${k}${w}`),k&&w&&I.add(`http://${k}${w}`),w&&I.add(w),I.add("");for(let C of I){let N=Es("sha256",a).update(`${f}${C}${t}${u}`).digest("base64");if(d.length===N.length&&Eg(Buffer.from(d),Buffer.from(N)))return null}return`signature mismatch (tried ${I.size} URI variants)`}if(n==="odoo"){let d=c("x-odoo-signature");if(d){let g=Es("sha256",a).update(t).digest("hex"),m=d.startsWith("sha256=")?d.slice(7):d;return Ct(g,m)}let u=c("x-odoo-webhook-token")??c("x-webhook-secret");return u?Ct(a,u):"missing X-Odoo-Signature or X-Odoo-Webhook-Token"}let l=c("x-webhook-secret");return l?Ct(a,l):"missing X-Webhook-Secret"}sendJson(e,t,n){e.writeHead(t,{"Content-Type":"application/json"}),e.end(JSON.stringify(n,null,2))}};function Ct(r,e){if(r.length!==e.length)return"signature mismatch";let t=Buffer.from(r),n=Buffer.from(e);return Eg(t,n)?null:"signature mismatch"}wk();ac();vc();function Zv(r){return{insertTaskHistory:r.prepare(`
|
|
@@ -1108,7 +1108,7 @@ Error: ${o.slice(0,300)}`),this.broadcastSSE("cron-failure",JSON.stringify({jobI
|
|
|
1108
1108
|
${o.slice(0,300)}`,record:!0,taskId:`cron-fail:${s.replace(/\//g,":")}:${Date.now()}`,outcome:"error",summary:`Cron "${s}" failed (${a}x)`})}catch(l){this.log(`[CRON ALERT] notify send failed: ${l.message}`)}}),e.setDeliverCallback(async(s,i,o,a)=>{let c=t.crons[s]?.notify;c&&await Ur(n,{channel:c.channel,chatId:c.chatId,accountId:c.accountId,agentId:i,text:o,record:!0,taskId:`cron:${a.replace(/\//g,":")}`,idempotencyKey:a,outcome:"done",summary:`Cron "${s}" result`})})}async reload(){let e;try{e=ui(this.configPath)}catch(k){return this.log(`[reload] config invalid, keeping previous: ${k.message}`),{applied:[],restartRequired:[],error:k.message}}let t=[],n=[];if(["maxClaudeCodeDispatchesPerHour","maxClaudeCodeDispatchesPer5h"].some(k=>this.config.session[k]!==e.session[k])&&(bl({maxPerHour:e.session.maxClaudeCodeDispatchesPerHour,maxPer5h:e.session.maxClaudeCodeDispatchesPer5h}),t.push("dispatch-budget")),JSON.stringify(this.config.crons)!==JSON.stringify(e.crons))try{await this.cron.stop(),this.cron=new Si(e,this.registry,this.hooks,this.log),this.wireCronCallbacks(this.cron,e),await this.cron.start(),t.push("crons")}catch(k){this.log(`[reload] cron reload failed: ${k.message}`)}let i=this.config.channels?.telegram,o=e.channels?.telegram,a=JSON.stringify(i)!==JSON.stringify(o),c=!1;if(a&&o?.enabled){let k=this.router.getChannel("telegram");if(k)try{let S=await k.reloadAccounts(o.accounts,o.policy);this.router.updateConfig(e);let I=[];S.added.length&&I.push(`+${S.added.join(",")}`),S.removed.length&&I.push(`-${S.removed.join(",")}`),S.tokenChanged.length&&I.push(`~${S.tokenChanged.join(",")}`),t.push(I.length?`telegram(${I.join(" ")})`:"telegram"),c=!0}catch(S){this.log(`[reload] telegram hot-reload failed: ${S.message}`)}}else a&&!o?.enabled&&i?.enabled||a&&o?.enabled&&!i?.enabled?c=!1:a||(c=!0);if(JSON.stringify(this.config.providers)!==JSON.stringify(e.providers))try{this.registry.setProviders(e.providers),t.push("providers")}catch(k){this.log(`[reload] providers hot-reload failed: ${k.message}`)}let l=this.config.mesh,d=e.mesh,u=JSON.stringify(l)!==JSON.stringify(d),g=!u;if(u&&d.enabled&&l.enabled&&this.mesh&&!(JSON.stringify(l.healthCheck)!==JSON.stringify(d.healthCheck)))try{let S=await this.mesh.reloadPeers(e),I=[];S.added.length&&I.push(`+${S.added.join(",")}`),S.removed.length&&I.push(`-${S.removed.join(",")}`),S.updated.length&&I.push(`~${S.updated.join(",")}`),t.push(I.length?`mesh(${I.join(" ")})`:"mesh"),g=!0}catch(S){this.log(`[reload] mesh hot-reload failed: ${S.message}`)}if(JSON.stringify(this.config.services)!==JSON.stringify(e.services))try{let k=this.router.getServiceMatcher();if(k){let{count:S}=k.reload(e.services);t.push(`services(${S})`)}else if(Object.keys(e.services).length>0){let S=new En(e.services,this.log);this.router.setServiceMatcher(S),t.push(`services(${Object.keys(e.services).length})`)}}catch(k){this.log(`[reload] services hot-reload failed: ${k.message}`)}try{let k=this.hooks.size?.()??0;this.hooks.clear(),Zr(process.cwd(),this.hooks);let S=this.hooks.size?.()??0;k!==S&&t.push(`hooks(${S})`)}catch(k){this.log(`[reload] hooks reload failed: ${k.message}`)}if(JSON.stringify(this.config.business)!==JSON.stringify(e.business)||JSON.stringify(this.config.agents)!==JSON.stringify(e.agents)){try{this.landscape=new fn(e),this.registry.setLandscape(this.landscape),t.push("landscape")}catch(k){this.log(`[reload] landscape rebuild failed: ${k.message}`)}if(this.mesh&&this.mesh.peerCount()>0)try{let k=await this.mesh.refreshAll(),S=k.filter(I=>I.healthy).length;t.push(`mesh.refresh(${S}/${k.length})`)}catch(k){this.log(`[reload] mesh refresh failed: ${k.message}`)}}if(JSON.stringify(this.config.agents)!==JSON.stringify(e.agents)){let k=Object.keys(this.config.agents).sort().join(","),S=Object.keys(e.agents).sort().join(",");k===S&&(this.registry.setConfig(e),t.push("agents.meta"));let I=fA(this.config.agents,e.agents);I.length>0&&n.push(`agents(${I.join(",")})`)}JSON.stringify(this.config.people)!==JSON.stringify(e.people)&&(this.registry.setPeople(e.people),t.push(`people(${e.people.length})`)),g||n.push("mesh"),JSON.stringify(this.config.node)!==JSON.stringify(e.node)&&n.push("node");let m={...this.config.channels,telegram:void 0},f={...e.channels,telegram:void 0};JSON.stringify(m)!==JSON.stringify(f)?n.push("channels"):a&&!c&&n.push("channels.telegram");{let k=this.contacts.size(),S=this.contacts.reload();S.error?this.log(`[reload] contacts.json invalid: ${S.error}`):S.count!==k&&t.push(`contacts(${S.count})`)}if(JSON.stringify(this.config.webhooks)!==JSON.stringify(e.webhooks))try{this.webhooks.setWebhookEntries(e.webhooks),t.push(`webhooks(${e.webhooks.length})`)}catch(k){this.log(`[reload] webhooks hot-reload failed: ${k.message}`)}JSON.stringify(this.config.workflows)!==JSON.stringify(e.workflows)&&(this.registry.setWorkflows(e.workflows),t.push("workflows"),this.config.workflows.enabled!==e.workflows.enabled&&n.push("workflows.enabled")),JSON.stringify(this.config.whatsappTriage)!==JSON.stringify(e.whatsappTriage)&&t.push(`whatsappTriage(${e.whatsappTriage.rules.length} rule(s))`);let w=JSON.stringify(this.config.screen)!==JSON.stringify(e.screen),b=JSON.stringify(this.config.reminders)!==JSON.stringify(e.reminders);return this.config=e,J().configure({node:e.node.name||e.node.id,ringSize:e.events.ringSize}),this.router.updateConfig(e),w&&(this.screenBuffer?.configure(Fi(e.screen)),t.push("screen")),b&&(this.stopReminders?.(),this.startReminders(),t.push("reminders")),t.length&&this.log(`[reload] applied: ${t.join(", ")}`),n.length?(this.log(`[reload] restart required to apply changes in: ${n.join(", ")}`),this.broadcastSSE("reload-partial",JSON.stringify({applied:t,restartRequired:n}))):t.length&&this.broadcastSSE("reload-complete",JSON.stringify({applied:t})),this.workflowPolls?.sync(),{applied:t,restartRequired:n}}requests=null;guestStore=new gu(process.cwd());guestJoinLimiter=new mu;operatorKey=null;requestsSweeping=!1;status=null;forwardCommentEdit(e){return async(t,n,s,i,o)=>{let a=this.mesh?.directory().find(d=>d.peer===t&&d.healthy);if(!a)return this.log(`[${e}] edit-comment forward: peer "${t}" not found or unhealthy`),!1;let c=await fetch(`${a.peerUrl}/channel/edit-comment`,{method:"POST",headers:{"Content-Type":"application/json",...this.mesh.authHeaders(a.peer)},body:JSON.stringify({channel:e,chatId:n,commentId:s,agentId:i,text:o})}),l=await c.json().catch(()=>({}));return c.ok&&l.ok===!0}}approvalsTimer;pushRosterTimer;pushHost;approvalsSweeping=!1;startApprovalsSweep(){let e=async()=>{if(!(this.approvalsSweeping||this.shuttingDown)){this.approvalsSweeping=!0;try{let t=this.config.notifications?.destination;if(await vp({ctx:{root:process.cwd()},settings:this.config.approvals,fallbackDestination:t?{channel:t.channel,chatId:t.chatId,accountId:t.accountId}:void 0,hasAgent:n=>!!this.registry.getAgent(n),onCardResult:n=>{this.requests?.tracker.cardResolved(n),this.status?.board.cardResolved(n),this.workflowCardResult(n)},tellAgent:async(n,s,i)=>this.tellAgentCardResult(i,s),tellPeer:async(n,s)=>{let i=this.approvalsPeer(n);if(!i)throw new Error(`no mesh peer named "${n}"`);await Ap(s,{self:this.config.node.name,peer:i})},sendDigest:async(n,s)=>{await this.router.sendOutbound({channel:n.channel,chatId:n.chatId,accountId:n.accountId,text:s})},log:this.log}),this.requests&&!this.requestsSweeping){this.requestsSweeping=!0;let n=new Promise((s,i)=>{setTimeout(()=>i(new Error("timed out after 5 min")),3e5).unref?.()});Promise.race([this.sweepRequests(this.requests),n]).catch(s=>this.log(`[requests] sweep failed: ${s?.message??s}`)).finally(()=>{this.requestsSweeping=!1})}this.status?.board.sweep(),this.voiceTalk.floor.view().held||yu({ctx:{root:process.cwd()},settings:this.config.approvals.popup,log:this.log,agentName:n=>this.registry.getAgent(n)?.name,speak:n=>this.voiceTalk.speech.say({voice:{provider:"system",elevenlabs:"",system:this.config.approvals.popup.voice??null,fallback:!1},text:n,kind:"line"})}).catch(n=>this.log(`[approvals] popup failed: ${n?.message??n}`)),this.runCheckin().catch(n=>this.log(`[checkin] failed: ${n?.message??n}`))}catch(t){this.log(`[approvals] sweep failed: ${t?.message??t}`)}finally{this.approvalsSweeping=!1}}};this.approvalsTimer=setInterval(()=>{e()},6e4),this.approvalsTimer.unref?.()}tellAgentCardResult(e,t=Xn(e)){this.registry.execute({agentId:e.raised_by,message:t,context:{channel:"approvals",chatId:e.id,sender:"operator"}}).catch(n=>this.log(`[approvals] ${e.id}: turn on ${e.raised_by} failed: ${n?.message??n}`))}approvalsPeer(e){return Ep(e,this.config.mesh.peers,this.mesh?.directory()??[])}forwardToWarned;approvalsForward(){let e=this.config.approvals.forwardTo;if(!e)return;let t=this.approvalsPeer(e);if(!t){this.forwardToWarned!==e&&(this.forwardToWarned=e,this.log(`[approvals] approvals.forwardTo names "${e}", which is not in mesh.peers; cards stay on this node`));return}return{self:this.config.node.name,peer:t}}carriesMeshToken(e){let t=String(e.headers.authorization||""),n=t.startsWith("Bearer ")?t.slice(7).trim():"";return!!n&&zn(this.config).has(n)}async notifyRequestOwner(e,t,n){await dt({title:e,message:t,from:n.agentId,priority:4},async s=>{try{await this.router.sendOutbound({channel:s.channel??ut(this.config),chatId:s.chatId??"default",text:`${s.title}
|
|
1109
1109
|
${s.message}`,priority:s.priority,agentId:n.agentId})}catch(i){if(this.log(`[requests] push for ${n.id} failed: ${i?.message??i}`),process.platform!=="darwin")throw i}},{alert:ke(Ce(this.config.notifications.local))})}plansSweeping=!1;plansKick;kickPlans(){this.plansKick||!this.requests||(this.plansKick=setTimeout(()=>{this.plansKick=void 0,this.requests&&!this.shuttingDown&&this.sweepPlans(this.requests).catch(e=>this.log(`[plans] check failed: ${e?.message??e}`))},1e3),this.plansKick.unref?.())}async sweepPlans(e){if(!(this.plansSweeping||!this.config.requests.enabled)){this.plansSweeping=!0;try{await Xp({requests:e.store,plans:e.plans,settings:this.config.requests.plans??Hs,tellAgent:async(t,n,s,i)=>{let o=a=>{let c=e.plans.step(i.requestId,i.idx);c?.state==="active"&&Oe(e.store,e.plans,c,`could not hand it to ${t}: ${a}`,Date.now()),this.log(`[plans] ${s.id} step ${i.idx}: turn on ${t} failed: ${a}`)};this.registry.execute({agentId:t,message:n,context:ci(s.id)}).then(a=>{a?.error&&!Ne(a.error)&&o(a.error)}).catch(a=>o(a?.message??String(a)))},send:async(t,n)=>{await this.router.sendOutbound({channel:t.toChannel,chatId:t.toChat,text:n,agentId:t.agentId,...t.toAccount?{accountId:t.toAccount}:{}},{idempotencyKey:`plan:${t.requestId}:${t.idx}`,dedupeWindowMs:24*36e5})},notify:(t,n,s)=>this.notifyRequestOwner(t,n,s),raiseCard:(t,n,s)=>{let i=We(process.cwd(),{...s,if_silent:"discard",raised_by:n.agentId},{settings:this.config.approvals,origin:{kind:"plan-step",requestId:t.id,step:n.idx}});if(!i.ok)throw new Error(i.error);return i.card.id},readCard:t=>vt(process.cwd(),t),hasAgent:t=>!!this.registry.getAgent(t),log:this.log})}finally{this.plansSweeping=!1}}}async sweepRequests(e){await this.sweepPlans(e).catch(t=>this.log(`[plans] check failed: ${t?.message??t}`)),await _p({store:e.store,settings:this.config.requests,notify:(t,n,s)=>this.notifyRequestOwner(t,n,s),hasAgent:t=>!!this.registry.getAgent(t),tellAgent:async(t,n,s)=>{this.registry.execute({agentId:t,message:n,context:ci(s.id)}).then(i=>{Oo(e.store,s,i,Date.now())&&this.log(`[requests] ${s.id}: could not hand it back to ${t}: ${i.error}`)}).catch(i=>{Oo(e.store,s,{error:i?.message??String(i)},Date.now()),this.log(`[requests] ${s.id}: turn on ${t} failed: ${i?.message??i}`)})},log:this.log})}async notifyOwner(e,t,n,s=4){await dt({title:e,message:t,from:n,priority:s},async i=>{try{await this.router.sendOutbound({channel:i.channel??ut(this.config),chatId:i.chatId??"default",text:`${i.title}
|
|
1110
1110
|
${i.message}`,priority:i.priority,agentId:n})}catch(o){if(this.log(`[workflows] push "${e}" failed: ${o?.message??o}`),process.platform!=="darwin")throw o}},{alert:ke(Ce(this.config.notifications.local))})}workflowCardResult(e){if(e.origin?.kind==="workflow"){this.workflowDispatcher?.resumeFromCard(e).catch(n=>this.log(`[workflows] card ${e.id}: resume failed: ${n?.message??n}`));return}if(e.origin?.kind!=="workflow-proposal"||!this.workflowStore)return;let t=this.workflowStore.get(e.origin.workflowId);t&&(e.status==="decided"&&e.verdict==="yes"?(this.workflowStore.save({...t,status:"active",state:"active"}),this.log(`[workflows] ${t.id} kept as a reusable workflow (card ${e.id})`)):this.log(`[workflows] ${t.id} not kept (card ${e.id} ${e.status==="decided"?"no":"expired"}); it stays switched off`))}workflowRequestEnded(e){let t=e.meta?.requestId,n=this.requests?.store;if(!t||!n)return;let s=Date.now();try{e.status==="completed"?n.close(t,"done",nd(e),s):n.needsAttention(t,`Workflow run ${e.id} ${e.status==="canceled"?"was stopped":"did not finish"}: ${e.history.at(-1)?.note??e.status}`,s)&&n.markNotified(t,s)}catch(i){this.log(`[workflows] request ${t} after run ${e.id}: ${i?.message??i}`)}}runCheckin(e){if(process.platform!=="darwin")return Promise.resolve(null);let t={root:process.cwd(),settings:this.config.approvals.checkin,cardSettings:this.config.approvals,source:Zs(this.config.reminders.command),...this.config.reminders.enabled?{claimsPath:Xs(process.cwd())}:{},hasAgent:n=>!!this.registry.getAgent(n),ask:async(n,s,i)=>{let o=await this.registry.execute({agentId:n,message:s,timeoutMinutes:this.config.approvals.checkin.composeTimeoutSeconds/60,context:{channel:"reminder",chatId:`checkin:${i.id}`,sender:"approvals"}});if(o.error)throw new Error(o.error);return o.content??""},notify:async(n,s)=>{let i=this.config.approvals.checkin.agent;await dt({title:n,message:s,from:i,priority:4},async o=>{try{await this.router.sendOutbound({channel:o.channel??ut(this.config),chatId:o.chatId??"default",text:`${o.title}
|
|
1111
|
-
${o.message}`,priority:o.priority,agentId:i})}catch(a){this.log(`[checkin] push for "${o.title}" failed: ${a?.message??a}`)}},{alert:ke(Ce(this.config.notifications.local))})},log:this.log};return kf(t,e)}stopReminders;startReminders(){let e=this.config.notifications?.destination;this.stopReminders=Sf({settings:this.config.reminders,root:process.cwd(),execute:(t,n,s,i)=>this.registry.execute(t,n,s,i),hasAgent:t=>!!this.registry.getAgent(t),send:t=>this.router.sendOutbound(t),fallbackDestination:e?{channel:e.channel,chatId:e.chatId,accountId:e.accountId}:void 0,isStopping:()=>this.shuttingDown,log:this.log})}scheduleMidnightHook(){let e=()=>{let t=new Date,n=new Date(t);n.setDate(n.getDate()+1),n.setHours(0,0,5,0);let s=n.getTime()-t.getTime();this.log(` Cost tracking: next run in ${Math.round(s/6e4)}min`),this.midnightTimer=setTimeout(async()=>{await this.runDailyCostReport(),e()},Math.max(s,6e4))};e()}async runDailyCostReport(){let e=new Date(Date.now()-864e5).toISOString().slice(0,10),t=this.registry.getTokenTracker();if(t.hasTokenCostsEntry(e)){this.log(` Cost tracking: ${e} already logged, skipping`);return}let n=t.generateDailyReport(e);if(!n||n.totalTasks===0){this.log(` Cost tracking: no usage for ${e}, skipping`);return}t.appendToTokenCosts(n),this.log(` Cost tracking: ${e} \u2014 ${n.totalTasks} tasks, $${n.totalCost.toFixed(4)} (top: ${n.topAgent} $${n.topCost.toFixed(4)})`)}async startChannels(){if(this.config.channels.telegram.enabled){let e=this.config.channels.telegram.accounts;if(Object.keys(e).length>0){let t=this.config.channels.telegram.policy,n=new ct(e,{policy:{allowFrom:t?.allowFrom}},this.log);this.router.addChannel(n);let s=t?.allowFrom?.length??0,i=Object.entries(e).filter(([,a])=>!a.allowFrom&&s===0).length;i>0?this.log(` Telegram: enabled \u2014 WARNING: ${i} account(s) have no allowFrom (global or per-account). All incoming messages will be DROPPED.`):this.log(` Telegram: enabled \u2014 global allowFrom entries: ${s}`);let o=Object.entries(e).filter(([,a])=>(a.allowFrom??t?.allowFrom??[]).includes(cs)).map(([a])=>a);o.length>0&&this.log(` Telegram: public \u2014 anyone can message ${o.join(", ")} (allowFrom contains "${cs}")`)}}if(this.config.channels.whatsapp.enabled){let{setWhatsAppQR:e,setWhatsAppStatus:t}=await import("./whatsapp-state-LB3R32BK.js"),n=new us({sessionDir:this.config.channels.whatsapp.sessionDir,defaultAgent:this.config.channels.whatsapp.defaultAgent,allowFrom:this.config.channels.whatsapp.allowFrom,routes:this.config.channels.whatsapp.routes,throttle:{minMsBetweenCalls:this.config.channels.whatsapp.ingest?.throttle?.minMsBetweenCalls,maxCallsPerMinute:this.config.channels.whatsapp.ingest?.throttle?.maxCallsPerMinute},onQR:e,onStatus:t},this.log);this.router.addChannel(n);let s=this.config.channels.whatsapp.allowFrom??[];s.length===0?this.log(` WhatsApp: enabled (${this.config.channels.whatsapp.routes.length} routes) \u2014 WARNING: no allowFrom. All incoming messages will be DROPPED.`):s.some(i=>i.trim()==="*")?this.log(` WhatsApp: enabled (${this.config.channels.whatsapp.routes.length} routes) \u2014 WARNING: allowFrom contains "*". Every chat on the paired account is answered.`):this.log(` WhatsApp: enabled (${this.config.channels.whatsapp.routes.length} routes) \u2014 allowFrom entries: ${s.length}`)}if(this.config.channels.gitlab?.enabled&&this.config.channels.gitlab.token){let e=new ps({webhookPort:this.config.channels.gitlab.webhookPort,webhookSecret:this.config.channels.gitlab.webhookSecret,host:this.config.channels.gitlab.host,token:this.config.channels.gitlab.token,routes:this.config.channels.gitlab.routes,agentMappings:this.config.channels.gitlab.agentMappings,knownAgentIds:Object.keys(this.config.agents),agentUsernamePrefixes:this.config.channels.gitlab.agentUsernamePrefixes},this.log,this.hooks);e.setStatusComments(()=>this.config.requestStatus.channels.some(t=>t.toLowerCase()==="gitlab")),this.mesh&&(e.setMesh(this.mesh),e.setEditCommentForwarder(this.forwardCommentEdit("gitlab")),e.setReactForwarder(async(t,n,s,i,o,a,c)=>{let l=this.mesh.directory().find(u=>u.peer===t&&u.healthy);if(!l){this.log(`[gitlab] react forward: peer "${t}" not found or unhealthy`);return}let d=`${l.peerUrl}/gitlab/react`;try{let u=await fetch(d,{method:"POST",headers:{"Content-Type":"application/json",...this.mesh.authHeaders(l.peer)},body:JSON.stringify({project:n,noteableType:s,noteableIid:i,noteId:o,agentId:a,name:c})}),g=await u.text().catch(()=>"");this.log(`[gitlab] react forward -> ${d} : ${u.status} ${g.slice(0,200)}`)}catch(u){this.log(`[gitlab] react forward FAILED -> ${d} : ${u.message}`)}}),e.setSendNoteForwarder(async(t,n,s,i,o,a)=>{let c=this.mesh.directory().find(d=>d.peer===t&&d.healthy);if(!c)return this.log(`[gitlab] send-note forward: peer "${t}" not found or unhealthy`),"";let l=`${c.peerUrl}/gitlab/send-note`;try{let d=await fetch(l,{method:"POST",headers:{"Content-Type":"application/json",...this.mesh.authHeaders(c.peer)},body:JSON.stringify({project:n,noteableType:s,noteableIid:i,agentId:o,text:a})}),u=await d.json().catch(()=>({}));return this.log(`[gitlab] send-note forward -> ${l} : ${d.status} noteId=${u.noteId||"?"}`),u.noteId||""}catch(d){return this.log(`[gitlab] send-note forward FAILED -> ${l} : ${d.message}`),""}}),e.setLogTimeForwarder(async(t,n,s,i,o,a)=>{let c=this.mesh.directory().find(d=>d.peer===t&&d.healthy);if(!c){this.log(`[gitlab] log-time forward: peer "${t}" not found or unhealthy`);return}let l=`${c.peerUrl}/gitlab/log-time`;try{let d=await fetch(l,{method:"POST",headers:{"Content-Type":"application/json",...this.mesh.authHeaders(c.peer)},body:JSON.stringify({project:n,noteableType:s,noteableIid:i,agentId:o,durationMs:a})}),u=await d.text().catch(()=>"");this.log(`[gitlab] log-time forward -> ${l} : ${d.status} ${u.slice(0,160)}`)}catch(d){this.log(`[gitlab] log-time forward FAILED -> ${l} : ${d.message}`)}})),e.setProjectRules(this.projectRules),this.router.addChannel(e),this.log(` GitLab: enabled (${this.config.channels.gitlab.routes.length} project routes, webhook :${this.config.channels.gitlab.webhookPort})`)}if(this.config.channels.github?.enabled){let e=this.config.channels.github;this.github=new ys({token:e.token,tokenFile:e.tokenFile,appId:e.appId,clientId:e.clientId,privateKeyFile:e.privateKeyFile,webhookSecret:e.webhookSecret,routes:e.routes,agentMappings:e.agentMappings,issueActions:e.issueActions,pullRequestActions:e.pullRequestActions,ignoreOwnChanges:e.ignoreOwnChanges,debounceSeconds:e.debounceSeconds},this.log),this.mesh&&(this.github.setEditCommentForwarder(this.forwardCommentEdit("github")),this.github.setSendCommentForwarder(async(t,n,s,i,o)=>{let a=this.mesh.directory().find(l=>l.peer===t&&l.healthy);if(!a)return this.log(`[github] send-comment forward: peer "${t}" not found or unhealthy`),"";let c=`${a.peerUrl}/github/send-comment`;try{let l=await fetch(c,{method:"POST",headers:{"Content-Type":"application/json",...this.mesh.authHeaders(a.peer)},body:JSON.stringify({repo:n,issueNumber:s,agentId:i,text:o})}),d=await l.json().catch(()=>({}));return this.log(`[github] send-comment forward -> ${c} : ${l.status} commentId=${d.commentId||"?"}`),d.commentId||""}catch(l){return this.log(`[github] send-comment forward FAILED -> ${c} : ${l.message}`),""}})),this.mesh&&this.github.setPeerPostingLogins(()=>this.mesh.directory().flatMap(t=>t.githubLogins)),this.github.setProjectRules(this.projectRules),this.router.addChannel(this.github),this.log(` GitHub: enabled (${e.routes.length} repo routes)`)}if(this.config.channels.ntfy?.enabled){let e=this.config.channels.ntfy;if(!e.topic)this.log(" ntfy: enabled but channels.ntfy.topic is unset \u2014 skipping");else{let{NtfyAdapter:t}=await import("./ntfy-W56TUC5C.js"),n=new t({server:e.server,topic:e.topic,token:e.token,defaultPriority:e.defaultPriority,defaultTitle:e.defaultTitle},this.log);this.router.addChannel(n),await n.start(),this.log(` ntfy: enabled (${e.server})`)}}if(this.config.channels.push?.enabled){let e=this.config.channels.push;if(e.relayTo){let{PushRelayAdapter:t}=await import("./push-B2GNI3VQ.js"),{pushOrigin:n,PushRosterSync:s,ROSTER_CHECK_MS:i,relayTokenProblem:o}=await import("./push-bridge-F4PCXWJF.js"),a=e.relayTo,c=n(this.config.node.name),l=o(a,this.config.mesh.peers,process.env.MESH_TOKEN);l&&this.log(` push: ${l}`);let d=new t(a,(m,f)=>this.postToPushHost(m,"/channel/send",f),this.log,c);this.router.addChannel(d),await d.start();let u=new Vn,g=new s({active:()=>u.activePhoneIds(),send:m=>this.postToPushHost(a,"/push/app",{op:"roster",origin:c,active:m}),log:this.log});this.pushRosterTimer=setInterval(()=>{g.tick()},i),this.pushRosterTimer.unref?.()}else if(!e.subject)this.log(" push: enabled but channels.push.subject is unset \u2014 skipping");else{let t=ii();if(!t)this.log(" push: enabled but the SQLite database is unavailable \u2014 skipping");else{let{PushAdapter:n,isRemoteDevice:s}=await import("./push-B2GNI3VQ.js"),{peerOrigins:i}=await import("./push-bridge-F4PCXWJF.js"),{PushStore:o}=await import("./push-store-L5QHMSST.js"),{pushKeysPath:a,readPushKeys:c}=await import("./push-keys-2DTD7YNQ.js"),{default:l}=await import("web-push"),d=new Vn,u=a(e.keysFile),g=new n({store:new o(t),keys:()=>c(u),subject:e.subject,ttlSeconds:e.ttlSeconds,keepRecent:e.keepRecent,deviceActive:b=>s(b)?i(this.config.mesh.peers).has(b.slice(0,b.indexOf(":"))):d.isActive(b),sender:(b,k,S)=>l.sendNotification(b,k,S),log:this.log});this.router.addChannel(g),await g.start();let m=new o(t);this.pushHost={store:()=>m,publicKey:()=>c(u)?.publicKey??null,keepRecent:e.keepRecent,allowedHosts:e.allowedHosts};let{attachAnnouncePush:f}=await import("./push-announce-PM5FZJRW.js"),{PushPrefs:w}=await import("./push-prefs-YOJNTOJF.js");f({bus:J(),store:new o(t),prefs:new w(t),send:b=>g.send(b),log:this.log})}}}if(this.config.channels.webrtc?.enabled){let e=this.config.channels.webrtc;if(this.webrtc=new Md(this.config.node.name,e.allowedCallers,this.log),this.mesh&&this.webrtc.setForwarder(async(t,n)=>{try{return await this.mesh.sendSignal(t,n)}catch(s){return this.log(`[webrtc] forward to "${t}" failed: ${s.message}`),!1}}),e.ringNotify.length>0){let t=e.callUrlBase||`http://${this.config.node.bind}`;this.webrtc.setRingHandler(async n=>{let s=_d(n,t);for(let i of e.ringNotify)try{await this.router.sendOutbound({channel:i.channel,chatId:i.chatId,text:s,parseMode:"plain",...i.accountId?{accountId:i.accountId}:{}})}catch(o){this.log(`[webrtc] ring notify via ${i.channel}:${i.chatId} failed: ${o.message}`)}})}if(e.bot.enabled){let t=e.bot,n=[...e.stunServers.map(s=>({urls:s})),...e.turnServers];this.botManager=new Ts({broker:this.webrtc,iceServers:n,whisperBackend:t.whisperBackend,whisperModel:t.whisperModel,whisperLanguage:t.whisperLanguage,mlxBinary:t.mlxBinary,maxCallMinutes:t.maxCallMinutes,log:this.log,onTranscript:async({invite:s,text:i,durationMs:o})=>{let a=t.transcriptChannel;if(!a)return;let c=`[${new Date().toLocaleTimeString()} \u2022 ${(o/1e3).toFixed(1)}s \u2022 ${s.target}]`;try{await this.router.sendOutbound({channel:a.channel,chatId:a.chatId,text:`${c}
|
|
1111
|
+
${o.message}`,priority:o.priority,agentId:i})}catch(a){this.log(`[checkin] push for "${o.title}" failed: ${a?.message??a}`)}},{alert:ke(Ce(this.config.notifications.local))})},log:this.log};return kf(t,e)}stopReminders;startReminders(){let e=this.config.notifications?.destination;this.stopReminders=Sf({settings:this.config.reminders,root:process.cwd(),execute:(t,n,s,i)=>this.registry.execute(t,n,s,i),hasAgent:t=>!!this.registry.getAgent(t),send:t=>this.router.sendOutbound(t),fallbackDestination:e?{channel:e.channel,chatId:e.chatId,accountId:e.accountId}:void 0,isStopping:()=>this.shuttingDown,log:this.log})}scheduleMidnightHook(){let e=()=>{let t=new Date,n=new Date(t);n.setDate(n.getDate()+1),n.setHours(0,0,5,0);let s=n.getTime()-t.getTime();this.log(` Cost tracking: next run in ${Math.round(s/6e4)}min`),this.midnightTimer=setTimeout(async()=>{await this.runDailyCostReport(),e()},Math.max(s,6e4))};e()}async runDailyCostReport(){let e=new Date(Date.now()-864e5).toISOString().slice(0,10),t=this.registry.getTokenTracker();if(t.hasTokenCostsEntry(e)){this.log(` Cost tracking: ${e} already logged, skipping`);return}let n=t.generateDailyReport(e);if(!n||n.totalTasks===0){this.log(` Cost tracking: no usage for ${e}, skipping`);return}t.appendToTokenCosts(n),this.log(` Cost tracking: ${e} \u2014 ${n.totalTasks} tasks, $${n.totalCost.toFixed(4)} (top: ${n.topAgent} $${n.topCost.toFixed(4)})`)}async startChannels(){if(this.config.channels.telegram.enabled){let e=this.config.channels.telegram.accounts;if(Object.keys(e).length>0){let t=this.config.channels.telegram.policy,n=new ct(e,{policy:{allowFrom:t?.allowFrom}},this.log);this.router.addChannel(n);let s=t?.allowFrom?.length??0,i=Object.entries(e).filter(([,a])=>!a.allowFrom&&s===0).length;i>0?this.log(` Telegram: enabled \u2014 WARNING: ${i} account(s) have no allowFrom (global or per-account). All incoming messages will be DROPPED.`):this.log(` Telegram: enabled \u2014 global allowFrom entries: ${s}`);let o=Object.entries(e).filter(([,a])=>(a.allowFrom??t?.allowFrom??[]).includes(cs)).map(([a])=>a);o.length>0&&this.log(` Telegram: public \u2014 anyone can message ${o.join(", ")} (allowFrom contains "${cs}")`)}}if(this.config.channels.whatsapp.enabled){let{setWhatsAppQR:e,setWhatsAppStatus:t}=await import("./whatsapp-state-LB3R32BK.js"),n=new us({sessionDir:this.config.channels.whatsapp.sessionDir,defaultAgent:this.config.channels.whatsapp.defaultAgent,allowFrom:this.config.channels.whatsapp.allowFrom,routes:this.config.channels.whatsapp.routes,throttle:{minMsBetweenCalls:this.config.channels.whatsapp.ingest?.throttle?.minMsBetweenCalls,maxCallsPerMinute:this.config.channels.whatsapp.ingest?.throttle?.maxCallsPerMinute},onQR:e,onStatus:t},this.log);this.router.addChannel(n);let s=this.config.channels.whatsapp.allowFrom??[];s.length===0?this.log(` WhatsApp: enabled (${this.config.channels.whatsapp.routes.length} routes) \u2014 WARNING: no allowFrom. All incoming messages will be DROPPED.`):s.some(i=>i.trim()==="*")?this.log(` WhatsApp: enabled (${this.config.channels.whatsapp.routes.length} routes) \u2014 WARNING: allowFrom contains "*". Every chat on the paired account is answered.`):this.log(` WhatsApp: enabled (${this.config.channels.whatsapp.routes.length} routes) \u2014 allowFrom entries: ${s.length}`)}if(this.config.channels.gitlab?.enabled&&this.config.channels.gitlab.token){let e=new ps({webhookPort:this.config.channels.gitlab.webhookPort,webhookSecret:this.config.channels.gitlab.webhookSecret,host:this.config.channels.gitlab.host,token:this.config.channels.gitlab.token,routes:this.config.channels.gitlab.routes,agentMappings:this.config.channels.gitlab.agentMappings,knownAgentIds:Object.keys(this.config.agents),agentUsernamePrefixes:this.config.channels.gitlab.agentUsernamePrefixes},this.log,this.hooks);e.setStatusComments(()=>this.config.requestStatus.channels.some(t=>t.toLowerCase()==="gitlab")),this.mesh&&(e.setMesh(this.mesh),e.setEditCommentForwarder(this.forwardCommentEdit("gitlab")),e.setReactForwarder(async(t,n,s,i,o,a,c)=>{let l=this.mesh.directory().find(u=>u.peer===t&&u.healthy);if(!l){this.log(`[gitlab] react forward: peer "${t}" not found or unhealthy`);return}let d=`${l.peerUrl}/gitlab/react`;try{let u=await fetch(d,{method:"POST",headers:{"Content-Type":"application/json",...this.mesh.authHeaders(l.peer)},body:JSON.stringify({project:n,noteableType:s,noteableIid:i,noteId:o,agentId:a,name:c})}),g=await u.text().catch(()=>"");this.log(`[gitlab] react forward -> ${d} : ${u.status} ${g.slice(0,200)}`)}catch(u){this.log(`[gitlab] react forward FAILED -> ${d} : ${u.message}`)}}),e.setSendNoteForwarder(async(t,n,s,i,o,a)=>{let c=this.mesh.directory().find(d=>d.peer===t&&d.healthy);if(!c)return this.log(`[gitlab] send-note forward: peer "${t}" not found or unhealthy`),"";let l=`${c.peerUrl}/gitlab/send-note`;try{let d=await fetch(l,{method:"POST",headers:{"Content-Type":"application/json",...this.mesh.authHeaders(c.peer)},body:JSON.stringify({project:n,noteableType:s,noteableIid:i,agentId:o,text:a})}),u=await d.json().catch(()=>({}));return this.log(`[gitlab] send-note forward -> ${l} : ${d.status} noteId=${u.noteId||"?"}`),u.noteId||""}catch(d){return this.log(`[gitlab] send-note forward FAILED -> ${l} : ${d.message}`),""}}),e.setLogTimeForwarder(async(t,n,s,i,o,a)=>{let c=this.mesh.directory().find(d=>d.peer===t&&d.healthy);if(!c){this.log(`[gitlab] log-time forward: peer "${t}" not found or unhealthy`);return}let l=`${c.peerUrl}/gitlab/log-time`;try{let d=await fetch(l,{method:"POST",headers:{"Content-Type":"application/json",...this.mesh.authHeaders(c.peer)},body:JSON.stringify({project:n,noteableType:s,noteableIid:i,agentId:o,durationMs:a})}),u=await d.text().catch(()=>"");this.log(`[gitlab] log-time forward -> ${l} : ${d.status} ${u.slice(0,160)}`)}catch(d){this.log(`[gitlab] log-time forward FAILED -> ${l} : ${d.message}`)}})),e.setProjectRules(this.projectRules),this.router.addChannel(e),this.log(` GitLab: enabled (${this.config.channels.gitlab.routes.length} project routes, webhook :${this.config.channels.gitlab.webhookPort})`)}if(this.config.channels.github?.enabled){let e=this.config.channels.github;this.github=new ys({token:e.token,tokenFile:e.tokenFile,appId:e.appId,clientId:e.clientId,privateKeyFile:e.privateKeyFile,webhookSecret:e.webhookSecret,routes:e.routes,agentMappings:e.agentMappings,issueActions:e.issueActions,pullRequestActions:e.pullRequestActions,ignoreOwnChanges:e.ignoreOwnChanges,debounceSeconds:e.debounceSeconds},this.log),this.mesh&&(this.github.setEditCommentForwarder(this.forwardCommentEdit("github")),this.github.setSendCommentForwarder(async(t,n,s,i,o)=>{let a=this.mesh.directory().find(l=>l.peer===t&&l.healthy);if(!a)return this.log(`[github] send-comment forward: peer "${t}" not found or unhealthy`),"";let c=`${a.peerUrl}/github/send-comment`;try{let l=await fetch(c,{method:"POST",headers:{"Content-Type":"application/json",...this.mesh.authHeaders(a.peer)},body:JSON.stringify({repo:n,issueNumber:s,agentId:i,text:o})}),d=await l.json().catch(()=>({}));return this.log(`[github] send-comment forward -> ${c} : ${l.status} commentId=${d.commentId||"?"}`),d.commentId||""}catch(l){return this.log(`[github] send-comment forward FAILED -> ${c} : ${l.message}`),""}})),this.mesh&&this.github.setPeerPostingLogins(()=>this.mesh.directory().flatMap(t=>t.githubLogins)),this.github.setProjectRules(this.projectRules),this.router.addChannel(this.github),this.log(` GitHub: enabled (${e.routes.length} repo routes)`)}if(this.config.channels.ntfy?.enabled){let e=this.config.channels.ntfy;if(!e.topic)this.log(" ntfy: enabled but channels.ntfy.topic is unset \u2014 skipping");else{let{NtfyAdapter:t}=await import("./ntfy-Q4YHFKWW.js"),n=new t({server:e.server,topic:e.topic,token:e.token,defaultPriority:e.defaultPriority,defaultTitle:e.defaultTitle},this.log);this.router.addChannel(n),await n.start(),this.log(` ntfy: enabled (${e.server})`)}}if(this.config.channels.push?.enabled){let e=this.config.channels.push;if(e.relayTo){let{PushRelayAdapter:t}=await import("./push-PRFZMCIK.js"),{pushOrigin:n,PushRosterSync:s,ROSTER_CHECK_MS:i,relayTokenProblem:o}=await import("./push-bridge-HNQG2TOI.js"),a=e.relayTo,c=n(this.config.node.name),l=o(a,this.config.mesh.peers,process.env.MESH_TOKEN);l&&this.log(` push: ${l}`);let d=new t(a,(m,f)=>this.postToPushHost(m,"/channel/send",f),this.log,c);this.router.addChannel(d),await d.start();let u=new Vn,g=new s({active:()=>u.activePhoneIds(),send:m=>this.postToPushHost(a,"/push/app",{op:"roster",origin:c,active:m}),log:this.log});this.pushRosterTimer=setInterval(()=>{g.tick()},i),this.pushRosterTimer.unref?.()}else if(!e.subject)this.log(" push: enabled but channels.push.subject is unset \u2014 skipping");else{let t=ii();if(!t)this.log(" push: enabled but the SQLite database is unavailable \u2014 skipping");else{let{PushAdapter:n,isRemoteDevice:s}=await import("./push-PRFZMCIK.js"),{peerOrigins:i}=await import("./push-bridge-HNQG2TOI.js"),{PushStore:o}=await import("./push-store-L5QHMSST.js"),{pushKeysPath:a,readPushKeys:c}=await import("./push-keys-2DTD7YNQ.js"),{default:l}=await import("web-push"),d=new Vn,u=a(e.keysFile),g=new n({store:new o(t),keys:()=>c(u),subject:e.subject,ttlSeconds:e.ttlSeconds,keepRecent:e.keepRecent,deviceActive:b=>s(b)?i(this.config.mesh.peers).has(b.slice(0,b.indexOf(":"))):d.isActive(b),sender:(b,k,S)=>l.sendNotification(b,k,S),log:this.log});this.router.addChannel(g),await g.start();let m=new o(t);this.pushHost={store:()=>m,publicKey:()=>c(u)?.publicKey??null,keepRecent:e.keepRecent,allowedHosts:e.allowedHosts};let{attachAnnouncePush:f}=await import("./push-announce-PM5FZJRW.js"),{PushPrefs:w}=await import("./push-prefs-YOJNTOJF.js");f({bus:J(),store:new o(t),prefs:new w(t),send:b=>g.send(b),log:this.log})}}}if(this.config.channels.webrtc?.enabled){let e=this.config.channels.webrtc;if(this.webrtc=new Md(this.config.node.name,e.allowedCallers,this.log),this.mesh&&this.webrtc.setForwarder(async(t,n)=>{try{return await this.mesh.sendSignal(t,n)}catch(s){return this.log(`[webrtc] forward to "${t}" failed: ${s.message}`),!1}}),e.ringNotify.length>0){let t=e.callUrlBase||`http://${this.config.node.bind}`;this.webrtc.setRingHandler(async n=>{let s=_d(n,t);for(let i of e.ringNotify)try{await this.router.sendOutbound({channel:i.channel,chatId:i.chatId,text:s,parseMode:"plain",...i.accountId?{accountId:i.accountId}:{}})}catch(o){this.log(`[webrtc] ring notify via ${i.channel}:${i.chatId} failed: ${o.message}`)}})}if(e.bot.enabled){let t=e.bot,n=[...e.stunServers.map(s=>({urls:s})),...e.turnServers];this.botManager=new Ts({broker:this.webrtc,iceServers:n,whisperBackend:t.whisperBackend,whisperModel:t.whisperModel,whisperLanguage:t.whisperLanguage,mlxBinary:t.mlxBinary,maxCallMinutes:t.maxCallMinutes,log:this.log,onTranscript:async({invite:s,text:i,durationMs:o})=>{let a=t.transcriptChannel;if(!a)return;let c=`[${new Date().toLocaleTimeString()} \u2022 ${(o/1e3).toFixed(1)}s \u2022 ${s.target}]`;try{await this.router.sendOutbound({channel:a.channel,chatId:a.chatId,text:`${c}
|
|
1112
1112
|
${i}`,parseMode:"plain",...a.accountId?{accountId:a.accountId}:{}})}catch(l){this.log(`[bot-manager] transcript send via ${a.channel}:${a.chatId} failed: ${l.message}`)}}}),this.log(` WebRTC bot: enabled (whisper=${t.whisperBackend}, default-agent=${t.defaultAgentId||"(none)"}, transcript=${t.transcriptChannel?`${t.transcriptChannel.channel}:${t.transcriptChannel.chatId}`:"(none)"})`)}this.cameraWatch=this.createCameraWatch(),this.log(` WebRTC signaling: enabled (stun=${e.stunServers.length}, turn=${e.turnServers.length}, allowedCallers=${e.allowedCallers.length||"all"}, ringNotify=${e.ringNotify.length}, bot=${e.bot.enabled?"on":"off"}, camera-bot frames=${e.camera.bot.frameIntervalSeconds?`every ${e.camera.bot.frameIntervalSeconds}s`:"on demand"})`)}await this.bootWorkflowEngine(),this.registry.getSessionStore().setAdapterResolver(e=>this.router.getChannel(e)),await this.router.startAll(),this.delegations.recover().then(e=>{e&&this.log(`[delegation] ${e} delegation(s) lost in the restart were reported to their callers`)}).catch(e=>this.log(`[delegation] recovery failed: ${e?.message??e}`))}async bootWorkflowEngine(){let e=this.config.workflows;if(!e?.enabled){this.log(" Workflows: disabled (set workflows.enabled to turn the engine on)");return}let t=new Al({baseDir:ne(process.cwd(),e.dir)}),n=new Rl({baseDir:ne(process.cwd(),e.dir),nodeId:this.config.node.id}),s={};for(let k of this.router.getChannelNames()){let S=this.router.getChannel(k);S&&(s[k]=S)}let i={execute:async k=>{let S=Date.now();try{let I=k.workflowRunId?`workflow:${k.workflowRunId}`:"workflow:adhoc",C,N=await this.registry.execute({agentId:k.agentId,message:k.message,workflowRunId:k.workflowRunId,timeoutMinutes:k.timeoutMinutes,autonomy:k.autonomy,context:{channel:"workflow",chatId:I,sender:"workflow"},onStart:R=>{C=R}});return{content:N.content??"",error:N.error,errorKind:N.errorKind,autonomyBlocks:N.autonomyBlocks,taskId:`wf-${k.workflowRunId??"na"}-${S.toString(36)}`,...C?{runTaskId:C}:{},durationMs:Date.now()-S}}catch(I){return{content:"",error:I.message}}}},o=this.mesh?{forwardTransition:async(k,S)=>{let I=this.mesh.directory().find(N=>N.peer===k&&N.healthy);if(!I){this.log(`[workflows] forward to "${k}" skipped \u2014 peer not found or unhealthy`);return}let C=`${I.peerUrl}/workflow/event`;try{let N=await fetch(C,{method:"POST",headers:{"Content-Type":"application/json",...this.mesh.authHeaders(I.peer)},body:JSON.stringify({payload:S})});N.ok||this.log(`[workflows] forward ${C} -> ${N.status}`)}catch(N){this.log(`[workflows] forward ${C} failed: ${N.message}`)}},broadcastTrigger:async k=>{let S=this.mesh.directory().filter(C=>C.healthy);if(S.length===0)return;let I=this.config.node.id;this.log(`[workflows/mesh] broadcasting trigger source="${k.trigger.source}" chat="${k.trigger.chat??"*"}" -> ${S.length} peer(s): ${S.map(C=>C.peer).join(", ")}`),await Promise.allSettled(S.map(async C=>{let N=`${C.peerUrl}/workflow/event`;try{let R=await fetch(N,{method:"POST",headers:{"Content-Type":"application/json",...this.mesh.authHeaders(C.peer)},body:JSON.stringify({kind:"trigger",fromPeer:I,payload:k}),signal:AbortSignal.timeout(5e3)});R.ok||this.log(`[workflows/mesh] broadcastTrigger ${C.peer} -> ${R.status}`)}catch(R){this.log(`[workflows/mesh] broadcastTrigger ${C.peer} failed: ${R.message}`)}}))},forwardChannelSend:async k=>{let S=this.mesh.directory().filter(A=>A.healthy&&A.channels?.includes(k.channel));if(S.length===0)throw new Error(`no healthy mesh peer hosts channel "${k.channel}"`);let I=S[0],C=`${I.peerUrl}/channel/send`,N=await fetch(C,{method:"POST",headers:{"Content-Type":"application/json",...this.mesh.authHeaders(I.peer)},body:JSON.stringify(k),signal:AbortSignal.timeout(15e3)});if(!N.ok){let A=await N.text().catch(()=>"");throw new Error(`peer ${I.peer} /channel/send -> ${N.status} ${A.slice(0,200)}`)}return{messageId:(await N.json().catch(()=>({}))).messageId??null}}}:void 0,{TimerService:a}=await import("./timers-KK7GF77Y.js"),c=new a({baseDir:e.dir?ne(process.cwd(),e.dir):void 0,log:k=>this.log(k)}),l={notify:(k,S)=>this.notifyOwner(S.meta?.title?`Workflow: ${S.meta.title}`.slice(0,120):"Workflow",k,S.meta?.startedBy??"workflow",3),ask:async(k,S,I)=>{let C=We(process.cwd(),{...k,if_silent:"discard",raised_by:S.meta?.startedBy??t.get(S.workflowId)?.ownerAgent??"workflow"},{settings:this.config.approvals,origin:{kind:"workflow",runId:S.id,nodeId:I}});if(!C.ok)throw new Error(C.error);return this.log(`[workflows] run ${S.id}: card ${C.card.id} for step "${I}"`),{cardId:C.card.id}}},d=new Wl({store:t,runs:n,nodeId:this.config.node.id,channels:s,agents:i,forwarder:o,timers:c,events:this.events,log:k=>this.log(k),owner:l,followUp:()=>this.config.workflows.followUp,onRunEnded:async(k,S)=>{let{title:I,message:C}=td(k,S);await this.notifyOwner(I,C,k.meta?.startedBy??"workflow",k.status==="completed"?3:4),this.workflowRequestEnded(k)},onBlocked:async(k,S,I,C)=>{let{title:N,message:R}=sd(k,S,I,C);await this.notifyOwner(N,R,k.meta?.startedBy??"workflow",4);let A=k.meta?.requestId;A&&this.requests?.store.needsAttention(A,`Workflow step "${I}" is blocked: ${C}`,Date.now())&&this.requests.store.markNotified(A,Date.now())}});c.start();let u=Hl(d);for(let[k,S]of Object.entries(u))S&&this.hooks.registerHandler(k,`workflows:${k}`,S,50);this.workflowDispatcher=d,this.workflowStore=t,this.workflowRuns=n,this.registry.setWorkflowRunStore(n);try{let k=n.moveTaskRuns();k&&this.log(` Workflows: moved ${k} task run(s) to their own folder`)}catch(k){this.log(` Workflows: moving task runs failed (non-fatal): ${k?.message||k}`)}try{let k=ld(n);k&&this.log(` Workflows: closed ${k} task run(s) cut off by the last stop`)}catch(k){this.log(` Workflows: closing cut-off task runs failed (non-fatal): ${k?.message||k}`)}let g=()=>{try{let k=n.pruneTaskRuns(this.config.workflows.required?.retentionDays??30);k&&this.log(` Workflows: removed ${k} task run(s) past workflows.required.retentionDays`)}catch(k){this.log(` Workflows: pruning task runs failed (non-fatal): ${k?.message||k}`)}};g(),this.taskRunPrune&&clearInterval(this.taskRunPrune),this.taskRunPrune=setInterval(g,6*60*60*1e3),this.taskRunPrune.unref?.(),this.webhooks.setWorkflowDispatcher(d),this.registry.setWorkflowAutoRunner(async({workflowId:k,agentId:S,channel:I,chatId:C,message:N,payload:R,follow:A})=>{let M=t.get(k);if(!M)throw new Error(`auto-run target workflow not found: ${k}`);let j=zl(M,{chatId:C,channel:I,message:typeof R.message=="string"?R.message:void 0,agentId:typeof R.agentId=="string"?R.agentId:void 0,senderId:typeof R.senderId=="string"?R.senderId:void 0,senderUsername:typeof R.senderUsername=="string"?R.senderUsername:void 0});if(j.missing.length>0)throw new Error(`inputSchema requires ${j.missing.join(", ")} \u2014 chatId+defaults filled [chatId: ${j.filledFrom.chatId.join(",")||"\u2014"} | defaults: ${j.filledFrom.defaults.join(",")||"\u2014"}]; auto-run skipped (operator can fill manually or extend the inputSchema).`);if(this.log(`[workflows] auto-run input resolution for ${k}: passthrough=[${j.filledFrom.passthrough.join(",")}] chatId=[${j.filledFrom.chatId.join(",")}] defaults=[${j.filledFrom.defaults.join(",")}]`),A){let y=await d.startRun({workflowId:k,inputs:{...j.inputs,requestedBy:S,channel:I,chatId:C},meta:{title:N.replace(/\s+/g," ").trim().slice(0,80)||M.title,tags:[`agent:${S}`],startedBy:S}});if(!y.run)throw new Error(y.error??`could not start ${k}`);return{runId:y.run.id,title:M.title}}let E={backend:"manual",id:C||`auto-${Date.now().toString(36)}`},h=`auto:${k}:${C}:${Date.now()}`;return{runId:(await d.dispatchWorkflow({workflowId:k,entityRef:E,event:{id:h,payload:j.inputs},trigger:{source:"manual"}})).run?.id}});let{cronTimers:m,hookSubscribers:f}=Jl({store:t,dispatcher:d,hooks:this.hooks,log:k=>this.log(k),forgeUsernames:k=>[...ot(this.config.channels.gitlab?.agentMappings,"gitlabUsernames",k),...ot(this.config.channels.github?.agentMappings,"githubUsernames",k)]});this.workflowPolls=new Xl({store:t,dispatcher:d,log:k=>this.log(k)});let w=this.workflowPolls.start(),b=t.list().length;this.log(` Workflows: enabled (${b} definition${b===1?"":"s"} loaded from ${e.dir}, editor=${e.editor}, cron=${m}, hook=${f}, poll=${w})`);try{let{detectConflicts:k}=await import("./conflict-detector-KS7OSS45.js"),S=k(t.list(),this.config);if(S.length>0){this.log(` Workflows: ${S.length} dispatch conflict(s) detected`);for(let I of S)this.log(` [${I.severity}] ${I.workflowId}: ${I.summary}`),this.log(` \u2192 ${I.suggestion}`)}}catch(k){this.log(` Workflows: conflict-detector failed: ${k.message}`)}}async startHttpApi(){let[e,t]=this.config.node.bind.split(":"),n=parseInt(t||"18800",10);this.httpServer=cA(async(s,i)=>{if(Mi(s.headers)==="foreign"&&Su(s.method)){this.log(`[auth] \u2717 refused ${s.method} ${(s.url||"").split("?")[0]} from a page on another origin (${s.headers.origin||s.headers["sec-fetch-site"]})`),this.json(i,403,{error:"Forbidden: request from a page on another origin"});return}if(this.shuttingDown&&Eu(s.method,(s.url||"/").split("?")[0])){i.setHeader("Retry-After","30"),this.json(i,503,{error:"daemon is restarting; retry shortly"});return}if(s.method==="OPTIONS"){i.writeHead(204),i.end();return}await this.handleHttp(s,i)}),this.httpServer.on("error",s=>{s.code==="EADDRINUSE"?(this.log(` ERROR: Port ${n} is already in use. Retrying in 5s...`),setTimeout(()=>{this.httpServer?.close(),this.httpServer?.listen(n,e||"0.0.0.0")},5e3)):this.log(` HTTP error: ${s.message}`)}),this.httpServer.listen(n,e||"0.0.0.0",()=>{this.log(` HTTP API: http://${e||"0.0.0.0"}:${n}`);let s=Of(this.config,i=>!!this.registry.getAgent(i));for(let i of s)this.log(` [mesh-relay] \u26A0 inbox "${i.name}" names agent "${i.agent}", which is not on this node \u2014 it will refuse messages`)}),this.attachSweep=setInterval(()=>{try{let s=de();s.sweep(),s.prune()}catch{}},1e3),this.attachSweep.unref?.()}delegationGate(e,t,n,s={}){let i=ja(e,t),o=La(i,this.registry),a=Ua(n.callee,o,this.registry,this.syncWaits);if(a)return{refused:a};if(!n.peer&&this.registry.getAgent(n.callee)){let d=Uw(n.callee,i,o,this.registry,this.syncWaits);if(d)return{refused:d}}let c=s.callback===!1?null:this.startCallback(t,o,n);if(c)return{accepted:c};let l;return{track:{...o?{root:Ye(o.context,o.agentId)}:{},onStart:d=>{l=d,o?.taskId&&this.syncWaits.begin(o.taskId,d,Fa(o,this.syncWaits))},end:()=>{l&&this.syncWaits.end(l)}}}}startCallback(e,t,n){let s=e.async===!0?!0:e.async===!1?!1:void 0;if(!t||!this.delegations.shouldCallback(t,s))return null;let i=n.peer;if(!i&&!this.registry.getAgent(n.callee)){let c=this.mesh?.findAgentPeer(n.callee);if(!c?.healthy)return null;i=c.peer}let o={};if(!i){let c=this.recordInboundDispatch(n.callee,{channel:"a2a",sender:`agent:${t.agentId}`,chatId:`a2a:${t.agentId}:${n.callee}`,initiator:Ye(t.context,t.agentId)},n.message,t.agentId);c&&(o.intentRef=c)}let{taskId:a}=this.delegations.start({caller:t,callee:n.callee,peer:i,message:n.message,calleeContext:n.calleeContext,extras:o});return Ww(a,n.callee,i)}recordInboundDispatch(e,t,n,s,i=jc(this.config.people,t)?.id??null){if(ue("mesh")!=="off")try{let o=Jf(le(),{agentId:e,senderAgentId:s,context:t},Vf(e,s,t,n,i),{agentId:e,outcome:"dispatched",reason:s?`from ${s}`:null});return o.outcome==="dispatched"?{eventId:o.eventId,decidedBy:o.decidedBy}:void 0}catch(o){this.log(`[ledger] ${t?.channel??"mesh"} agent="${e}" record failed: ${o?.message??o}`);return}}broadcastSSE(e,t){if(this.sseClients.size===0)return;let n=`event: ${e}
|
|
1113
1113
|
data: ${JSON.stringify({time:new Date().toISOString(),message:t})}
|
|
1114
1114
|
|
|
@@ -1175,5 +1175,5 @@ data: ${JSON.stringify(G)}
|
|
|
1175
1175
|
|
|
1176
1176
|
`)}catch{}},D=setInterval(()=>{try{t.write(`: ping
|
|
1177
1177
|
|
|
1178
|
-
`)}catch{}},15e3);D.unref?.();let L=new AbortController;t.on("close",()=>{t.writableEnded||L.abort()});try{for await(let F of this.mesh.sendTaskStream(h.peer,h.message,h.agent,{context:h.context,signal:L.signal,vouchOwner:v}))x(F.event,F.data)}catch(F){x("error",{error:F?.message??String(F)})}finally{clearInterval(D);try{t.end()}catch{}}break}let _=h.context,P=typeof h.senderAgentId=="string"?h.senderAgentId:void 0;if(Gw(h,!0)==="callback"){let x=this.delegationGate(e,h,{callee:h.agent,peer:h.peer,message:h.message,calleeContext:_});if(!("track"in x)){let D=Fr(x);this.json(t,D.status,D.body);break}}if(h.async===!0){let x=_?.channel,D=_?.chatId;if(!x||!D){this.json(t,400,{error:"async requires context.channel and context.chatId \u2014 there is no route back otherwise"});return}let L=`mesh-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,8)}`;this.json(t,202,{accepted:!0,taskId:L,deliverTo:`${x}:${D}`,note:"result will be delivered to the originating chat when the peer finishes"});let F=h.peer,G=h.agent,Z=this.mesh;(async()=>{let W=Date.now(),V;try{V=await Z.sendTask(F,h.message,G,{vouchOwner:v,context:_,senderAgentId:P}),this.log(`[mesh/task async ${L}] ${F}/${G??"default"} finished in ${Date.now()-W}ms`)}catch(B){V=`Delegation to ${F}/${G??"default"} failed: ${B?.message??B}`,this.log(`[mesh/task async ${L}] failed: ${B?.message??B}`)}try{await this.router.sendOutbound({channel:x,chatId:D,text:V,agentId:_?.agentId??P,accountId:_?.accountId})}catch(B){this.log(`[mesh/task async ${L}] delivery failed: ${B?.message??B}`)}})();break}let O=await this.mesh.sendTask(h.peer,h.message,h.agent,{context:_,senderAgentId:P,vouchOwner:v});this.json(t,200,{response:O});break}case"POST /webrtc/signal/out":{if(!this.webrtc){this.json(t,404,{error:"WebRTC signaling not enabled"});return}let p=await H(e);if(!p.kind||!p.callId||!p.from||!p.to){this.json(t,400,{error:"Missing: kind, callId, from, to"});return}let y=await this.webrtc.handleOutgoing(p);this.json(t,y.ok?200:400,y);break}case"POST /webrtc/signal":{if(!this.webrtc){this.json(t,404,{error:"WebRTC signaling not enabled"});return}let p=await H(e);if(!p.kind||!p.callId||!p.from||!p.to){this.json(t,400,{error:"Missing: kind, callId, from, to"});return}let y=this.webrtc.handleIncoming(p);this.json(t,y.ok?200:400,y);break}case"POST /webrtc/bot/invite":{if(!this.botManager){this.json(t,404,{error:"WebRTC bot not enabled (channels.webrtc.bot.enabled=false)"});return}let h=await H(e),p=h.callId,y=h.target,v=h.agentId||this.config.channels.webrtc?.bot.defaultAgentId;if(!p||!y||!v){this.json(t,400,{error:"Missing: callId, target, agentId (or defaultAgentId in config)"});return}let $=await this.botManager.invite({callId:p,target:y,agentId:v});this.json(t,$.ok?200:500,$);break}case"GET /webrtc/bots":{if(!this.botManager){this.json(t,404,{error:"WebRTC bot not enabled"});return}this.json(t,200,{active:this.botManager.active()});break}case"GET /webrtc/history":{if(!this.botManager){this.json(t,404,{error:"WebRTC bot not enabled"});return}this.json(t,200,{active:this.botManager.active(),history:this.botManager.history()});break}case"GET /webrtc/config":{let h=this.config.channels.webrtc;if(!h?.enabled){this.json(t,404,{error:"WebRTC signaling not enabled"});return}this.json(t,200,{localName:this.config.node.name,iceServers:[...h.stunServers.map(p=>({urls:p})),...h.turnServers],peers:this.mesh?.directory().map(p=>({name:p.peer,healthy:p.healthy}))||[],camera:h.camera,agents:this.registry.list().map(p=>({id:p.id,name:p.name}))});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(h=>{let p=this.config.agents[h.id]||{},y=typeof p.description=="string"&&p.description.trim()||pA(p.systemPrompt)||`Agent "${h.name}" (${h.tier})`,v=Array.isArray(p.mentions)?p.mentions.slice(0,4):[];return{id:h.id,name:h.name,description:y,tags:[h.tier,...v],color:fe(h.id,p).color}}),channels:this.router.getChannelNames(),githubLogins:this.github?.postingAccounts()??[],defaultInputModes:["text"],defaultOutputModes:["text"]});break;case"GET /wiki/agents":{let h=this.registry.getWikiHub();this.json(t,200,{nodeId:this.config.node.id,agents:h.summary()});break}case"GET /wiki/entries":{let h=this.registry.getWikiHub(),p=n.searchParams.get("agent")||void 0,y=n.searchParams.get("after")||void 0,v=p?h.getAgentEntries(p):h.getSharedStore().listEntries({after:y});this.json(t,200,{nodeId:this.config.node.id,count:v.length,entries:v.map($=>({id:$.id,date:$.date,agentId:$.agentId,source:$.source,sourceContext:$.sourceContext,content:$.content}))});break}case"GET /wiki/articles":{let h=this.registry.getWikiHub(),p=n.searchParams.get("agent");if(!p){this.json(t,400,{error:"?agent= required"});break}let v=h.getAgentWiki(p).rebuildIndex();this.json(t,200,{nodeId:this.config.node.id,agentId:p,articles:v.articles});break}case"GET /wiki/article":{let h=this.registry.getWikiHub(),p=n.searchParams.get("agent"),y=n.searchParams.get("path");if(!p||!y){this.json(t,400,{error:"?agent= and ?path= required"});break}let $=h.getAgentWiki(p).readArticle(y);if(!$){this.json(t,404,{error:"Article not found"});break}this.json(t,200,{nodeId:this.config.node.id,agentId:p,path:$.path,title:$.meta.title,tags:$.meta.tags,owner:$.meta.owner,created:$.meta.created,lastUpdated:$.meta.lastUpdated,sources:$.meta.sources,content:$.content});break}case"GET /wiki/notes":{let{NoteStore:h}=await import("./notes-WJJEJ4CJ.js"),p=n.searchParams.get("status")||"waiting";if(!["waiting","open","patched","rejected","deferred","expired","all"].includes(p)){this.json(t,400,{error:"status must be waiting, open, patched, rejected, deferred, expired or all"});break}let y=Math.max(1,Math.min(200,parseInt(n.searchParams.get("limit")||"50",10)||50)),$=new h(this.registry.getWikiHub().getBaseDir()).list(p==="all"?void 0:p);this.json(t,200,{nodeId:this.config.node.id,enabled:this.config.wikiNotes.enabled,inbox:this.config.wikiNotes.inbox??null,crons:this.config.wikiNotes.crons,absorbAgent:this.config.wikiNotes.absorbAgent??null,total:$.length,notes:$.slice(-y).reverse()});break}case"POST /wiki/notes":{let{NoteStore:h,validateNote:p}=await import("./notes-WJJEJ4CJ.js"),y=this.config.wikiNotes;if(!y.enabled||!y.inbox){this.json(t,409,{error:"wiki notes are off on this node: set wikiNotes.enabled and wikiNotes.inbox"});break}let v=await H(e),$=p({...v,to:y.inbox});if("error"in $){this.json(t,400,{error:$.error});break}let T=$.note;if(this.registry.getAgent(y.inbox)){try{let{note:O,added:x}=new h(this.registry.getWikiHub().getBaseDir()).add(T);this.json(t,x?201:200,{ok:!0,added:x,note:O,node:this.config.node.id})}catch(O){this.json(t,507,{error:O?.message??String(O)})}break}if(v.forwarded===!0){this.json(t,404,{error:`inbox agent "${y.inbox}" is not on this node`});break}let _=this.mesh?.findAgentPeer(y.inbox);if(!_){this.json(t,404,{error:`inbox agent "${y.inbox}" is not on this node or any known peer`});break}let P=this.mesh.directory().find(O=>O.peer===_.peer)?.peerUrl;if(!_.healthy||!P){this.json(t,503,{error:`the node hosting "${y.inbox}" (${_.peer}) is unreachable; try again later`});break}try{let O=await fetch(`${P.replace(/\/$/,"")}/wiki/notes`,{method:"POST",headers:{"Content-Type":"application/json",...this.mesh.authHeaders(_.peer)},body:JSON.stringify({...T,fromNode:T.fromNode||this.config.node.id,forwarded:!0}),signal:AbortSignal.timeout(1e4)}),x=await O.json().catch(()=>({}));this.json(t,O.status,{...x,peer:_.peer})}catch(O){this.json(t,502,{error:`forward to ${_.peer} failed: ${O?.message??O}`,peer:_.peer})}break}case"GET /graph/schema":{let h=this.registry.getGraphStore();if(!h){this.json(t,404,{error:"graph disabled on this node"});break}this.json(t,200,{nodeId:this.config.node.id,schema:h.loadSchema()});break}case"GET /graph/nodes":{let h=this.registry.getGraphStore();if(!h){this.json(t,404,{error:"graph disabled on this node"});break}this.json(t,200,{nodeId:this.config.node.id,...h.loadNodes()});break}case"GET /graph/classifications":{let h=this.registry.getGraphStore();if(!h){this.json(t,404,{error:"graph disabled on this node"});break}let p=n.searchParams.get("status")||"approved",y=Math.max(1,Math.min(1e3,parseInt(n.searchParams.get("limit")||"200",10)||200)),v=h.listByStatus(p,y);this.json(t,200,{nodeId:this.config.node.id,status:p,count:v.length,classifications:v});break}default:this.json(t,404,{error:"Not found",endpoints:["GET /health","GET /agents","GET /crons","GET /routines \u2014 schedules + cron/hook workflows with staleness flags","GET /mesh","GET /wiki/agents","GET /wiki/entries[?agent=X&after=YYYY-MM-DD]","GET /wiki/articles?agent=X","GET /wiki/notes[?status=waiting|open|patched|rejected|deferred|expired|all&limit=N]","POST /wiki/notes { from, change, source, date? } \u2014 a note for the wiki observe/sweep run","GET /graph/schema","GET /graph/nodes","GET /graph/classifications[?status=approved|pending|rejected&limit=N]","POST /task { agent, message, context?, freshSession?: bool }","GET /agents/:id \u2014 resolved agent config (permission, tier, model, persistentProcess, toolUseRequired)","POST /agents/:id/selftest { message? } \u2014 canary probe; runs a fresh-session task and reports {ok, durationMs, tokens, billedModel}","GET /traces[?agentId=&channel=&chatId=&workflowRunId=&status=&since=&until=&limit=]","GET /traces/:taskId \u2014 full per-task execution trace (steps + tokens)","GET /voice/history[?agent=&limit=&before=] \u2014 past voice exchanges, bounded summaries","GET /voice/history/:id \u2014 one voice exchange in full; POST /voice/history/:id/replay","GET /api/processes \u2014 live persistent claude processes (JSON)","POST /api/processes/kill { agentId, channel, chatId, reason? }","GET /api/actions/builtin \u2014 list shipped built-in typed actions","POST /api/actions/builtin/:name \u2014 run a built-in with JSON body input","POST /mesh/task { peer, message }","POST /webhook/:agentId[/:source] \u2014 webhook callback","POST /reload \u2014 re-read agentx.json (hot-swaps crons)","GET /usage/plan \u2014 Claude plan windows, cold-dispatch counters, active holds","POST /usage/plan/lift \u2014 drop the active Claude plan holds","GET /daemon/restart \u2014 in-flight count, service manager, pending restart","POST /daemon/restart \u2014 restart when idle { timeoutMinutes?, onTimeout? }","POST /daemon/restart/cancel","GET /.well-known/agent-card.json","GET /call \u2014 browser UI for P2P A/V calls (requires channels.webrtc.enabled)","GET /webrtc/config \u2014 ICE servers + peer directory for the call page","GET /webrtc/events?callId=&as= \u2014 SSE stream of signaling events","POST /webrtc/signal/out \u2014 browser-originated signal, forwarded to remote peer","POST /webrtc/signal \u2014 remote-peer-originated signal, fanned out to local browser","POST /webrtc/bot/invite { callId, target, agentId } \u2014 spawn a transcribing bot peer for a call","GET /webrtc/bots \u2014 active bot sessions","POST /webrtc/camera/watch { callId, agentId } \u2014 an agent watches the phone camera share","GET /webrtc/camera/watch \u2014 agents watching a camera now","POST /webrtc/camera/watch/:id/look { note? } \u2014 ask the watching agent what it sees","POST /webrtc/camera/watch/:id/stream { seconds, note? } \u2014 the agent keeps watching for a while (0 stops)","POST /webrtc/camera/look { agentId } \u2014 from an agent's run: the newest frame as a PNG path"]})}}catch(i){this.json(t,500,{error:i.message})}}async handleOpenAICompat(e,t,n){let s=await H(e);await xg(e,t,n,s,{execute:(i,o,a,c)=>this.registry.execute(i,o,a,c),agentIds:Object.keys(this.config.agents),cancel:(i,o,a,c)=>{let l=this.registry.list().find(d=>d.id===i)?.runningTasks.filter(d=>d.channel===o&&d.chatId===a)||[];for(let d of l)this.registry.cancelRunningTask(d.id,c)},log:i=>this.log(i)})}json(e,t,n){e.writeHead(t,{"Content-Type":"application/json"}),e.end(JSON.stringify(n,null,2))}resolveGitlabTokenForAgent(e){if(!e)return{token:this.config.channels.gitlab?.token,source:"global"};let t=(this.config.channels.gitlab?.agentMappings||[]).find(s=>s.agentId===e);if(t?.token)return{token:t.token,source:"agentMappings"};let n=bm(this.config,e,"gitlab-user","tokenEnv");return n.status==="ok"&&n.value?{token:n.value,source:`integrations(${n.envVar})`}:this.config.channels.gitlab?.token?{token:this.config.channels.gitlab.token,source:"global"}:{token:void 0,source:n.status}}resolveTodayUsage(){let e=Zg();if(e==="json")return this.registry.getTodayUsage();let t=Qg(this.db);return e==="sqlite"||Object.keys(t.agents).length>0?t:this.registry.getTodayUsage()}installAgentMemorySurface(){let e=parseInt(this.config.node.bind.split(":")[1]||"18800",10);for(let t of this.registry.list()){let n=t.workspace;if(!(!n||!Wt(n)))try{let s=ne(n,".claude","skills");dk(s,{recursive:!0});let i=ne(s,vm);if(!Wt(i))Qa(i,Zo(e)),this.log(` memory-skill: installed remember.md \u2192 ${t.id}`);else{let o=$m(Cn(i,"utf-8"),e);o&&(Qa(i,o),this.log(` memory-skill: updated remember.md \u2192 ${t.id}`))}this.agentMemory.syncToWorkspace(t.id,n,this.config.session.memoryIndexMaxChars)}catch(s){this.log(` memory-skill: ${t.id} install failed \u2014 ${s?.message??s}`)}}}installAgentMcpConfig(){for(let[e,t]of Object.entries(this.config.agents)){let n=t.workspace;if(!n||!Wt(n))continue;let s=this.config.workflows.enabled&&(this.config.workflows.followUp.enabled&&(this.config.workflows.followUp.agents[e]??!0)||id(this.config.workflows,e)),i=Tl(Vc(t),this.config.requests.enabled||s?Il():null);try{switch(xl(n,i)){case"installed":this.log(` mcp: installed ${Object.keys(i).length} server(s) \u2192 ${e}`);break;case"updated":this.log(` mcp: updated .mcp.json (${Object.keys(i).length} server(s)) \u2192 ${e}`);break;case"removed":this.log(` mcp: removed managed .mcp.json (config now empty) \u2192 ${e}`);break;case"skipped-operator-owned":Object.keys(i).length>0&&this.log(` mcp: ${e} has operator-owned .mcp.json \u2014 skipping (delete file or marker to let agentx manage)`);break;case"noop":break}}catch(o){this.log(` mcp: ${e} install failed \u2014 ${o?.message??o}`)}}}workspaceFor(e){for(let t of this.registry.list())if(t.id===e)return t.workspace;return null}handleMemoryApi(e,t,n,s){return Zf(e,t,n,s,{mem:this.agentMemory,workspaceFor:i=>this.workspaceFor(i),runningTaskOwner:i=>this.registry.runningTaskOwner(i),indexMaxChars:()=>this.config.session.memoryIndexMaxChars})}async handleWorkflowEvent(e,t){if(!this.workflowDispatcher){this.json(t,503,{error:"workflow engine not enabled on this node"});return}let n;try{n=await se(e)}catch(c){this.json(t,400,{error:"invalid JSON body",message:c.message});return}let s=n?.payload??n,i=s?.event,o=s?.entityRef;if(!i?.id||!o?.backend||!o?.id){this.json(t,400,{error:"missing event or entityRef"});return}let a=n?.kind;try{if(a==="trigger"&&s.trigger){let g=typeof n?.fromPeer=="string"?n.fromPeer:"unknown";this.log(`[workflows/mesh] received trigger from "${g}" source="${s.trigger.source}" chat="${s.trigger.chat??"*"}"`);let m=await this.workflowDispatcher.dispatch({trigger:s.trigger,entityRef:o,event:i,fromRemote:{peer:g}});this.log(`[workflows/mesh] dispatched broadcast: matched=${m.claimed.length} ${m.claimed.length?`(${m.claimed.map(f=>f.id).join(", ")})`:"[none \u2014 no workflow with mesh.allowRemote matched]"}`),this.json(t,202,{ok:!0,mode:"trigger-broadcast",matched:m.claimed.map(f=>f.id)});return}let d=(s.workflowId?this.workflowStore?.get(s.workflowId):null)?.nodes.find(g=>g.type.startsWith("trigger."))?.config??{},u={source:String(d.source??"hook"),project:d.filter?.project,repo:d.filter?.repo,chat:d.filter?.chat,labels:d.filter?.labels};await this.workflowDispatcher.dispatch({trigger:u,entityRef:o,event:i}),this.json(t,202,{ok:!0})}catch(c){this.log(`[workflows] /workflow/event failed: ${c.message}`),this.json(t,500,{error:c.message})}}async postToPushHost(e,t,n){let s=e.toLowerCase(),i=this.mesh?.directory().find(c=>c.peer.toLowerCase()===s);if(!i)throw new Error(`push: channels.push.relayTo "${e}" is not a mesh peer of this node`);let o=await fetch(`${i.peerUrl}${t}`,{method:"POST",headers:{"Content-Type":"application/json",...this.mesh.authHeaders(i.peer)},body:JSON.stringify(n),signal:AbortSignal.timeout(15e3)});if(!o.ok)throw new Error(`push: ${i.peer} ${t} -> ${o.status} ${(await o.text().catch(()=>"")).slice(0,200)}`);let a=await o.json().catch(()=>({}));if(t==="/channel/send")return a.messageId??void 0;if(typeof a.status!="number")throw new Error(`push: ${i.peer} ${t} gave no answer`);return{status:a.status,body:a.body??{}}}async handlePushApp(e,t){let n;try{n=await se(e)}catch(c){this.json(t,400,{error:"invalid JSON body",message:c.message});return}let{handlePushBridge:s,pushOrigin:i}=await import("./push-bridge-F4PCXWJF.js"),o=this.config.channels.push;if(!o?.enabled){this.json(t,200,{status:503,body:{error:`Notifications are off on ${this.config.node.name}.`}});return}if(o.relayTo){if(n?.origin!==void 0){this.json(t,200,{status:409,body:{error:`${this.config.node.name} relays its notifications too (channels.push.relayTo = ${o.relayTo})`}});return}if(n?.op!=="app"){this.json(t,400,{error:"op must be app"});return}try{this.json(t,200,await this.postToPushHost(o.relayTo,"/push/app",{op:"app",origin:i(this.config.node.name),call:n.call}))}catch(c){this.log(`[push] /push/app to ${o.relayTo} failed: ${c.message}`),this.json(t,502,{error:c.message})}return}let a=this.pushBridgeCaller(e);"error"in a&&n?.op!=="roster"&&this.log(`[push] /push/app from ${e.socket?.remoteAddress||"?"} refused: ${a.error}`),this.json(t,200,s(n,a,this.pushHost??null,this.pushHost?void 0:`Notifications are not running on ${this.config.node.name}. Check channels.push.subject and the database there.`))}pushBridgeCaller(e){return Gd(String(e.headers.authorization||""),this.config.mesh.peers,process.env.MESH_TOKEN)}async handleChannelSend(e,t){let n;try{n=await se(e)}catch(l){this.json(t,400,{error:"invalid JSON body",message:l.message});return}let s=String(n?.channel??""),i=String(n?.chatId??""),o=String(n?.text??"");if(!s||!i||!o){this.json(t,400,{error:"channel, chatId, and text are required"});return}if(!this.router.getChannel(s)){this.json(t,404,{error:`channel "${s}" not hosted on this node`});return}let c;if(n.relayed===!0&&n.origin!==void 0){let l=this.pushBridgeCaller(e);if("origin"in l)c=l.origin;else if(i!=="default"){this.json(t,403,{error:l.error});return}}if(s==="push"&&i.includes(":")){let{scopedPushTarget:l}=await import("./push-bridge-F4PCXWJF.js"),d=l(i,this.pushBridgeCaller(e));if("error"in d){this.json(t,403,{error:d.error});return}}try{let l=await this.router.sendOutbound({channel:s,chatId:i,text:o,accountId:typeof n.accountId=="string"?n.accountId:void 0,parseMode:typeof n.parseMode=="string"?n.parseMode:void 0,replyTo:typeof n.replyTo=="string"?n.replyTo:void 0,buttons:Array.isArray(n.buttons)?n.buttons:void 0,agentId:typeof n.agentId=="string"?n.agentId:void 0,relayed:n.relayed===!0?!0:void 0,origin:c},{recordInSession:!1});this.json(t,200,{ok:!0,messageId:l??null})}catch(l){this.log(`[mesh] /channel/send "${s}" failed: ${l.message}`),this.json(t,500,{error:l.message})}}async handleChannelChats(e,t,n={}){let s=new Set,i=[],o=[],a=(l,d)=>{let u=0;for(let g of l)!g.id||s.has(g.id)||(s.add(g.id),i.push({...g,source:d}),u++);u>0&&o.push(`${d}:${u}`)},c=this.router.getChannel(t);if(c?.listKnownChats)try{a(c.listKnownChats(),"local")}catch{}else if(c?.listChats)try{a(c.listChats().map(l=>({id:l.jid,name:l.name,kind:l.isGroup?"group":"dm"})),"local")}catch{}if(!n.localOnly&&this.mesh){let l=this.mesh.directory().filter(d=>d.healthy&&d.channels?.includes(t));await Promise.allSettled(l.map(async d=>{try{let u=await fetch(`${d.peerUrl}/channels/${encodeURIComponent(t)}/chats?local=1`,{headers:this.mesh.authHeaders(d.peer),signal:AbortSignal.timeout(5e3)});if(!u.ok)return;let g=await u.json();Array.isArray(g.chats)&&a(g.chats.map(m=>({id:m.id,name:m.name,kind:m.kind??"dm",accountId:m.accountId})),`mesh:${d.peer}`)}catch{}}))}this.json(e,200,{channel:t,source:o.join(", ")||"none",chats:i})}async handleWorkflowManualRun(e,t,n){if(!this.workflowDispatcher||!this.workflowStore){this.json(t,503,{error:"workflow engine not enabled on this node"});return}let s;try{s=await se(e)}catch{s={}}try{let i=await km({get:o=>this.workflowStore.get(o),dispatchWorkflow:o=>this.workflowDispatcher.dispatchWorkflow(o),startRun:o=>this.workflowDispatcher.startRun(o)},n,s);this.json(t,i.status,i.body)}catch(i){this.log(`[workflows] manual run "${n}" failed: ${i.message}`),this.json(t,500,{error:i.message})}}},Za=ec;ft(Za,"MESH_PROTECTED_PATHS",new Set(["/task","/ask","/mesh/task","/mesh/inbox/send","/workflow/event","/workflow/transition","/channel/send","/push/app","/webrtc/signal","/gitlab/react","/gitlab/send-note","/gitlab/log-time","/channel/edit-comment","/github/send-comment"]));function pA(r){if(typeof r!="string")return"";let e=r.trim();if(!e)return"";let t=e.match(/^[^.!?\n]+[.!?]?/),n=(t?t[0]:e).trim();return n.length>140?n.slice(0,137)+"\u2026":n}function se(r){return new Promise((e,t)=>{let n="";r.setEncoding("utf8"),r.on("data",s=>{n+=s}),r.on("end",()=>{try{e(n?JSON.parse(n):{})}catch(s){t(s)}}),r.on("error",t)})}function fA(r,e){let t=["model","workspace","tier","permissionMode","mcpServers"],n=new Set,s=new Set([...Object.keys(r),...Object.keys(e)]);for(let i of s){let o=r[i],a=e[i];if(!o||!a){n.add("add/remove");continue}for(let c of t)JSON.stringify(o[c])!==JSON.stringify(a[c])&&n.add(`${i}.${c}`)}return Array.from(n).slice(0,6)}async function H(r){return new Promise((e,t)=>{let n="";r.on("data",s=>n+=s.toString()),r.on("end",()=>{try{e(n?JSON.parse(n):{})}catch{e({})}}),r.on("error",t)})}hk();function SU(r){typeof r=="string"&&(_n.error(r),process.exit(1)),r instanceof Error&&(_n.error(r.message),process.exit(1)),_n.error("Something went wrong. Please try again."),process.exit(1)}export{CA as a,Ji as b,rt as c,Qt as d,tn as e,ct as f,us as g,Xh as h,Wg as i,lS as j,dS as k,Jg as l,Be as m,dt as n,zg as o,ut as p,yP as q,wP as r,Hs as s,y_ as t,o$ as u,Vo as v,FM as w,UM as x,GM as y,cm as z,L$ as A,VM as B,JM as C,fn as D,Dt as E,mn as F,kO as G,Um as H,EI as I,uD as J,ur as K,ha as L,ga as M,mr as N,fe as O,Py as P,kr as Q,xT as R,By as S,RT as T,Xy as U,Sr as V,In as W,xr as X,Rr as Y,ew as Z,Ra as _,Je as $,Re as aa,cw as ba,lw as ca,dw as da,uw as ea,Xr as fa,tk as ga,Ka as ha,sk as ia,nA as ja,OL as ka,Za as la,SU as ma};
|
|
1179
|
-
//# sourceMappingURL=chunk-
|
|
1178
|
+
`)}catch{}},15e3);D.unref?.();let L=new AbortController;t.on("close",()=>{t.writableEnded||L.abort()});try{for await(let F of this.mesh.sendTaskStream(h.peer,h.message,h.agent,{context:h.context,signal:L.signal,vouchOwner:v}))x(F.event,F.data)}catch(F){x("error",{error:F?.message??String(F)})}finally{clearInterval(D);try{t.end()}catch{}}break}let _=h.context,P=typeof h.senderAgentId=="string"?h.senderAgentId:void 0;if(Gw(h,!0)==="callback"){let x=this.delegationGate(e,h,{callee:h.agent,peer:h.peer,message:h.message,calleeContext:_});if(!("track"in x)){let D=Fr(x);this.json(t,D.status,D.body);break}}if(h.async===!0){let x=_?.channel,D=_?.chatId;if(!x||!D){this.json(t,400,{error:"async requires context.channel and context.chatId \u2014 there is no route back otherwise"});return}let L=`mesh-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,8)}`;this.json(t,202,{accepted:!0,taskId:L,deliverTo:`${x}:${D}`,note:"result will be delivered to the originating chat when the peer finishes"});let F=h.peer,G=h.agent,Z=this.mesh;(async()=>{let W=Date.now(),V;try{V=await Z.sendTask(F,h.message,G,{vouchOwner:v,context:_,senderAgentId:P}),this.log(`[mesh/task async ${L}] ${F}/${G??"default"} finished in ${Date.now()-W}ms`)}catch(B){V=`Delegation to ${F}/${G??"default"} failed: ${B?.message??B}`,this.log(`[mesh/task async ${L}] failed: ${B?.message??B}`)}try{await this.router.sendOutbound({channel:x,chatId:D,text:V,agentId:_?.agentId??P,accountId:_?.accountId})}catch(B){this.log(`[mesh/task async ${L}] delivery failed: ${B?.message??B}`)}})();break}let O=await this.mesh.sendTask(h.peer,h.message,h.agent,{context:_,senderAgentId:P,vouchOwner:v});this.json(t,200,{response:O});break}case"POST /webrtc/signal/out":{if(!this.webrtc){this.json(t,404,{error:"WebRTC signaling not enabled"});return}let p=await H(e);if(!p.kind||!p.callId||!p.from||!p.to){this.json(t,400,{error:"Missing: kind, callId, from, to"});return}let y=await this.webrtc.handleOutgoing(p);this.json(t,y.ok?200:400,y);break}case"POST /webrtc/signal":{if(!this.webrtc){this.json(t,404,{error:"WebRTC signaling not enabled"});return}let p=await H(e);if(!p.kind||!p.callId||!p.from||!p.to){this.json(t,400,{error:"Missing: kind, callId, from, to"});return}let y=this.webrtc.handleIncoming(p);this.json(t,y.ok?200:400,y);break}case"POST /webrtc/bot/invite":{if(!this.botManager){this.json(t,404,{error:"WebRTC bot not enabled (channels.webrtc.bot.enabled=false)"});return}let h=await H(e),p=h.callId,y=h.target,v=h.agentId||this.config.channels.webrtc?.bot.defaultAgentId;if(!p||!y||!v){this.json(t,400,{error:"Missing: callId, target, agentId (or defaultAgentId in config)"});return}let $=await this.botManager.invite({callId:p,target:y,agentId:v});this.json(t,$.ok?200:500,$);break}case"GET /webrtc/bots":{if(!this.botManager){this.json(t,404,{error:"WebRTC bot not enabled"});return}this.json(t,200,{active:this.botManager.active()});break}case"GET /webrtc/history":{if(!this.botManager){this.json(t,404,{error:"WebRTC bot not enabled"});return}this.json(t,200,{active:this.botManager.active(),history:this.botManager.history()});break}case"GET /webrtc/config":{let h=this.config.channels.webrtc;if(!h?.enabled){this.json(t,404,{error:"WebRTC signaling not enabled"});return}this.json(t,200,{localName:this.config.node.name,iceServers:[...h.stunServers.map(p=>({urls:p})),...h.turnServers],peers:this.mesh?.directory().map(p=>({name:p.peer,healthy:p.healthy}))||[],camera:h.camera,agents:this.registry.list().map(p=>({id:p.id,name:p.name}))});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(h=>{let p=this.config.agents[h.id]||{},y=typeof p.description=="string"&&p.description.trim()||pA(p.systemPrompt)||`Agent "${h.name}" (${h.tier})`,v=Array.isArray(p.mentions)?p.mentions.slice(0,4):[];return{id:h.id,name:h.name,description:y,tags:[h.tier,...v],color:fe(h.id,p).color}}),channels:this.router.getChannelNames(),githubLogins:this.github?.postingAccounts()??[],defaultInputModes:["text"],defaultOutputModes:["text"]});break;case"GET /wiki/agents":{let h=this.registry.getWikiHub();this.json(t,200,{nodeId:this.config.node.id,agents:h.summary()});break}case"GET /wiki/entries":{let h=this.registry.getWikiHub(),p=n.searchParams.get("agent")||void 0,y=n.searchParams.get("after")||void 0,v=p?h.getAgentEntries(p):h.getSharedStore().listEntries({after:y});this.json(t,200,{nodeId:this.config.node.id,count:v.length,entries:v.map($=>({id:$.id,date:$.date,agentId:$.agentId,source:$.source,sourceContext:$.sourceContext,content:$.content}))});break}case"GET /wiki/articles":{let h=this.registry.getWikiHub(),p=n.searchParams.get("agent");if(!p){this.json(t,400,{error:"?agent= required"});break}let v=h.getAgentWiki(p).rebuildIndex();this.json(t,200,{nodeId:this.config.node.id,agentId:p,articles:v.articles});break}case"GET /wiki/article":{let h=this.registry.getWikiHub(),p=n.searchParams.get("agent"),y=n.searchParams.get("path");if(!p||!y){this.json(t,400,{error:"?agent= and ?path= required"});break}let $=h.getAgentWiki(p).readArticle(y);if(!$){this.json(t,404,{error:"Article not found"});break}this.json(t,200,{nodeId:this.config.node.id,agentId:p,path:$.path,title:$.meta.title,tags:$.meta.tags,owner:$.meta.owner,created:$.meta.created,lastUpdated:$.meta.lastUpdated,sources:$.meta.sources,content:$.content});break}case"GET /wiki/notes":{let{NoteStore:h}=await import("./notes-WJJEJ4CJ.js"),p=n.searchParams.get("status")||"waiting";if(!["waiting","open","patched","rejected","deferred","expired","all"].includes(p)){this.json(t,400,{error:"status must be waiting, open, patched, rejected, deferred, expired or all"});break}let y=Math.max(1,Math.min(200,parseInt(n.searchParams.get("limit")||"50",10)||50)),$=new h(this.registry.getWikiHub().getBaseDir()).list(p==="all"?void 0:p);this.json(t,200,{nodeId:this.config.node.id,enabled:this.config.wikiNotes.enabled,inbox:this.config.wikiNotes.inbox??null,crons:this.config.wikiNotes.crons,absorbAgent:this.config.wikiNotes.absorbAgent??null,total:$.length,notes:$.slice(-y).reverse()});break}case"POST /wiki/notes":{let{NoteStore:h,validateNote:p}=await import("./notes-WJJEJ4CJ.js"),y=this.config.wikiNotes;if(!y.enabled||!y.inbox){this.json(t,409,{error:"wiki notes are off on this node: set wikiNotes.enabled and wikiNotes.inbox"});break}let v=await H(e),$=p({...v,to:y.inbox});if("error"in $){this.json(t,400,{error:$.error});break}let T=$.note;if(this.registry.getAgent(y.inbox)){try{let{note:O,added:x}=new h(this.registry.getWikiHub().getBaseDir()).add(T);this.json(t,x?201:200,{ok:!0,added:x,note:O,node:this.config.node.id})}catch(O){this.json(t,507,{error:O?.message??String(O)})}break}if(v.forwarded===!0){this.json(t,404,{error:`inbox agent "${y.inbox}" is not on this node`});break}let _=this.mesh?.findAgentPeer(y.inbox);if(!_){this.json(t,404,{error:`inbox agent "${y.inbox}" is not on this node or any known peer`});break}let P=this.mesh.directory().find(O=>O.peer===_.peer)?.peerUrl;if(!_.healthy||!P){this.json(t,503,{error:`the node hosting "${y.inbox}" (${_.peer}) is unreachable; try again later`});break}try{let O=await fetch(`${P.replace(/\/$/,"")}/wiki/notes`,{method:"POST",headers:{"Content-Type":"application/json",...this.mesh.authHeaders(_.peer)},body:JSON.stringify({...T,fromNode:T.fromNode||this.config.node.id,forwarded:!0}),signal:AbortSignal.timeout(1e4)}),x=await O.json().catch(()=>({}));this.json(t,O.status,{...x,peer:_.peer})}catch(O){this.json(t,502,{error:`forward to ${_.peer} failed: ${O?.message??O}`,peer:_.peer})}break}case"GET /graph/schema":{let h=this.registry.getGraphStore();if(!h){this.json(t,404,{error:"graph disabled on this node"});break}this.json(t,200,{nodeId:this.config.node.id,schema:h.loadSchema()});break}case"GET /graph/nodes":{let h=this.registry.getGraphStore();if(!h){this.json(t,404,{error:"graph disabled on this node"});break}this.json(t,200,{nodeId:this.config.node.id,...h.loadNodes()});break}case"GET /graph/classifications":{let h=this.registry.getGraphStore();if(!h){this.json(t,404,{error:"graph disabled on this node"});break}let p=n.searchParams.get("status")||"approved",y=Math.max(1,Math.min(1e3,parseInt(n.searchParams.get("limit")||"200",10)||200)),v=h.listByStatus(p,y);this.json(t,200,{nodeId:this.config.node.id,status:p,count:v.length,classifications:v});break}default:this.json(t,404,{error:"Not found",endpoints:["GET /health","GET /agents","GET /crons","GET /routines \u2014 schedules + cron/hook workflows with staleness flags","GET /mesh","GET /wiki/agents","GET /wiki/entries[?agent=X&after=YYYY-MM-DD]","GET /wiki/articles?agent=X","GET /wiki/notes[?status=waiting|open|patched|rejected|deferred|expired|all&limit=N]","POST /wiki/notes { from, change, source, date? } \u2014 a note for the wiki observe/sweep run","GET /graph/schema","GET /graph/nodes","GET /graph/classifications[?status=approved|pending|rejected&limit=N]","POST /task { agent, message, context?, freshSession?: bool }","GET /agents/:id \u2014 resolved agent config (permission, tier, model, persistentProcess, toolUseRequired)","POST /agents/:id/selftest { message? } \u2014 canary probe; runs a fresh-session task and reports {ok, durationMs, tokens, billedModel}","GET /traces[?agentId=&channel=&chatId=&workflowRunId=&status=&since=&until=&limit=]","GET /traces/:taskId \u2014 full per-task execution trace (steps + tokens)","GET /voice/history[?agent=&limit=&before=] \u2014 past voice exchanges, bounded summaries","GET /voice/history/:id \u2014 one voice exchange in full; POST /voice/history/:id/replay","GET /api/processes \u2014 live persistent claude processes (JSON)","POST /api/processes/kill { agentId, channel, chatId, reason? }","GET /api/actions/builtin \u2014 list shipped built-in typed actions","POST /api/actions/builtin/:name \u2014 run a built-in with JSON body input","POST /mesh/task { peer, message }","POST /webhook/:agentId[/:source] \u2014 webhook callback","POST /reload \u2014 re-read agentx.json (hot-swaps crons)","GET /usage/plan \u2014 Claude plan windows, cold-dispatch counters, active holds","POST /usage/plan/lift \u2014 drop the active Claude plan holds","GET /daemon/restart \u2014 in-flight count, service manager, pending restart","POST /daemon/restart \u2014 restart when idle { timeoutMinutes?, onTimeout? }","POST /daemon/restart/cancel","GET /.well-known/agent-card.json","GET /call \u2014 browser UI for P2P A/V calls (requires channels.webrtc.enabled)","GET /webrtc/config \u2014 ICE servers + peer directory for the call page","GET /webrtc/events?callId=&as= \u2014 SSE stream of signaling events","POST /webrtc/signal/out \u2014 browser-originated signal, forwarded to remote peer","POST /webrtc/signal \u2014 remote-peer-originated signal, fanned out to local browser","POST /webrtc/bot/invite { callId, target, agentId } \u2014 spawn a transcribing bot peer for a call","GET /webrtc/bots \u2014 active bot sessions","POST /webrtc/camera/watch { callId, agentId } \u2014 an agent watches the phone camera share","GET /webrtc/camera/watch \u2014 agents watching a camera now","POST /webrtc/camera/watch/:id/look { note? } \u2014 ask the watching agent what it sees","POST /webrtc/camera/watch/:id/stream { seconds, note? } \u2014 the agent keeps watching for a while (0 stops)","POST /webrtc/camera/look { agentId } \u2014 from an agent's run: the newest frame as a PNG path"]})}}catch(i){this.json(t,500,{error:i.message})}}async handleOpenAICompat(e,t,n){let s=await H(e);await xg(e,t,n,s,{execute:(i,o,a,c)=>this.registry.execute(i,o,a,c),agentIds:Object.keys(this.config.agents),cancel:(i,o,a,c)=>{let l=this.registry.list().find(d=>d.id===i)?.runningTasks.filter(d=>d.channel===o&&d.chatId===a)||[];for(let d of l)this.registry.cancelRunningTask(d.id,c)},log:i=>this.log(i)})}json(e,t,n){e.writeHead(t,{"Content-Type":"application/json"}),e.end(JSON.stringify(n,null,2))}resolveGitlabTokenForAgent(e){if(!e)return{token:this.config.channels.gitlab?.token,source:"global"};let t=(this.config.channels.gitlab?.agentMappings||[]).find(s=>s.agentId===e);if(t?.token)return{token:t.token,source:"agentMappings"};let n=bm(this.config,e,"gitlab-user","tokenEnv");return n.status==="ok"&&n.value?{token:n.value,source:`integrations(${n.envVar})`}:this.config.channels.gitlab?.token?{token:this.config.channels.gitlab.token,source:"global"}:{token:void 0,source:n.status}}resolveTodayUsage(){let e=Zg();if(e==="json")return this.registry.getTodayUsage();let t=Qg(this.db);return e==="sqlite"||Object.keys(t.agents).length>0?t:this.registry.getTodayUsage()}installAgentMemorySurface(){let e=parseInt(this.config.node.bind.split(":")[1]||"18800",10);for(let t of this.registry.list()){let n=t.workspace;if(!(!n||!Wt(n)))try{let s=ne(n,".claude","skills");dk(s,{recursive:!0});let i=ne(s,vm);if(!Wt(i))Qa(i,Zo(e)),this.log(` memory-skill: installed remember.md \u2192 ${t.id}`);else{let o=$m(Cn(i,"utf-8"),e);o&&(Qa(i,o),this.log(` memory-skill: updated remember.md \u2192 ${t.id}`))}this.agentMemory.syncToWorkspace(t.id,n,this.config.session.memoryIndexMaxChars)}catch(s){this.log(` memory-skill: ${t.id} install failed \u2014 ${s?.message??s}`)}}}installAgentMcpConfig(){for(let[e,t]of Object.entries(this.config.agents)){let n=t.workspace;if(!n||!Wt(n))continue;let s=this.config.workflows.enabled&&(this.config.workflows.followUp.enabled&&(this.config.workflows.followUp.agents[e]??!0)||id(this.config.workflows,e)),i=Tl(Vc(t),this.config.requests.enabled||s?Il():null);try{switch(xl(n,i)){case"installed":this.log(` mcp: installed ${Object.keys(i).length} server(s) \u2192 ${e}`);break;case"updated":this.log(` mcp: updated .mcp.json (${Object.keys(i).length} server(s)) \u2192 ${e}`);break;case"removed":this.log(` mcp: removed managed .mcp.json (config now empty) \u2192 ${e}`);break;case"skipped-operator-owned":Object.keys(i).length>0&&this.log(` mcp: ${e} has operator-owned .mcp.json \u2014 skipping (delete file or marker to let agentx manage)`);break;case"noop":break}}catch(o){this.log(` mcp: ${e} install failed \u2014 ${o?.message??o}`)}}}workspaceFor(e){for(let t of this.registry.list())if(t.id===e)return t.workspace;return null}handleMemoryApi(e,t,n,s){return Zf(e,t,n,s,{mem:this.agentMemory,workspaceFor:i=>this.workspaceFor(i),runningTaskOwner:i=>this.registry.runningTaskOwner(i),indexMaxChars:()=>this.config.session.memoryIndexMaxChars})}async handleWorkflowEvent(e,t){if(!this.workflowDispatcher){this.json(t,503,{error:"workflow engine not enabled on this node"});return}let n;try{n=await se(e)}catch(c){this.json(t,400,{error:"invalid JSON body",message:c.message});return}let s=n?.payload??n,i=s?.event,o=s?.entityRef;if(!i?.id||!o?.backend||!o?.id){this.json(t,400,{error:"missing event or entityRef"});return}let a=n?.kind;try{if(a==="trigger"&&s.trigger){let g=typeof n?.fromPeer=="string"?n.fromPeer:"unknown";this.log(`[workflows/mesh] received trigger from "${g}" source="${s.trigger.source}" chat="${s.trigger.chat??"*"}"`);let m=await this.workflowDispatcher.dispatch({trigger:s.trigger,entityRef:o,event:i,fromRemote:{peer:g}});this.log(`[workflows/mesh] dispatched broadcast: matched=${m.claimed.length} ${m.claimed.length?`(${m.claimed.map(f=>f.id).join(", ")})`:"[none \u2014 no workflow with mesh.allowRemote matched]"}`),this.json(t,202,{ok:!0,mode:"trigger-broadcast",matched:m.claimed.map(f=>f.id)});return}let d=(s.workflowId?this.workflowStore?.get(s.workflowId):null)?.nodes.find(g=>g.type.startsWith("trigger."))?.config??{},u={source:String(d.source??"hook"),project:d.filter?.project,repo:d.filter?.repo,chat:d.filter?.chat,labels:d.filter?.labels};await this.workflowDispatcher.dispatch({trigger:u,entityRef:o,event:i}),this.json(t,202,{ok:!0})}catch(c){this.log(`[workflows] /workflow/event failed: ${c.message}`),this.json(t,500,{error:c.message})}}async postToPushHost(e,t,n){let s=e.toLowerCase(),i=this.mesh?.directory().find(c=>c.peer.toLowerCase()===s);if(!i)throw new Error(`push: channels.push.relayTo "${e}" is not a mesh peer of this node`);let o=await fetch(`${i.peerUrl}${t}`,{method:"POST",headers:{"Content-Type":"application/json",...this.mesh.authHeaders(i.peer)},body:JSON.stringify(n),signal:AbortSignal.timeout(15e3)});if(!o.ok)throw new Error(`push: ${i.peer} ${t} -> ${o.status} ${(await o.text().catch(()=>"")).slice(0,200)}`);let a=await o.json().catch(()=>({}));if(t==="/channel/send")return a.messageId??void 0;if(typeof a.status!="number")throw new Error(`push: ${i.peer} ${t} gave no answer`);return{status:a.status,body:a.body??{}}}async handlePushApp(e,t){let n;try{n=await se(e)}catch(c){this.json(t,400,{error:"invalid JSON body",message:c.message});return}let{handlePushBridge:s,pushOrigin:i}=await import("./push-bridge-HNQG2TOI.js"),o=this.config.channels.push;if(!o?.enabled){this.json(t,200,{status:503,body:{error:`Notifications are off on ${this.config.node.name}.`}});return}if(o.relayTo){if(n?.origin!==void 0){this.json(t,200,{status:409,body:{error:`${this.config.node.name} relays its notifications too (channels.push.relayTo = ${o.relayTo})`}});return}if(n?.op!=="app"){this.json(t,400,{error:"op must be app"});return}try{this.json(t,200,await this.postToPushHost(o.relayTo,"/push/app",{op:"app",origin:i(this.config.node.name),call:n.call}))}catch(c){this.log(`[push] /push/app to ${o.relayTo} failed: ${c.message}`),this.json(t,502,{error:c.message})}return}let a=this.pushBridgeCaller(e);"error"in a&&n?.op!=="roster"&&this.log(`[push] /push/app from ${e.socket?.remoteAddress||"?"} refused: ${a.error}`),this.json(t,200,s(n,a,this.pushHost??null,this.pushHost?void 0:`Notifications are not running on ${this.config.node.name}. Check channels.push.subject and the database there.`))}pushBridgeCaller(e){return Gd(String(e.headers.authorization||""),this.config.mesh.peers,process.env.MESH_TOKEN)}async handleChannelSend(e,t){let n;try{n=await se(e)}catch(l){this.json(t,400,{error:"invalid JSON body",message:l.message});return}let s=String(n?.channel??""),i=String(n?.chatId??""),o=String(n?.text??"");if(!s||!i||!o){this.json(t,400,{error:"channel, chatId, and text are required"});return}if(!this.router.getChannel(s)){this.json(t,404,{error:`channel "${s}" not hosted on this node`});return}let c;if(n.relayed===!0&&n.origin!==void 0){let l=this.pushBridgeCaller(e);if("origin"in l)c=l.origin;else if(i!=="default"){this.json(t,403,{error:l.error});return}}if(s==="push"&&i.includes(":")){let{scopedPushTarget:l}=await import("./push-bridge-HNQG2TOI.js"),d=l(i,this.pushBridgeCaller(e));if("error"in d){this.json(t,403,{error:d.error});return}}try{let l=await this.router.sendOutbound({channel:s,chatId:i,text:o,accountId:typeof n.accountId=="string"?n.accountId:void 0,parseMode:typeof n.parseMode=="string"?n.parseMode:void 0,replyTo:typeof n.replyTo=="string"?n.replyTo:void 0,buttons:Array.isArray(n.buttons)?n.buttons:void 0,agentId:typeof n.agentId=="string"?n.agentId:void 0,relayed:n.relayed===!0?!0:void 0,origin:c},{recordInSession:!1});this.json(t,200,{ok:!0,messageId:l??null})}catch(l){this.log(`[mesh] /channel/send "${s}" failed: ${l.message}`),this.json(t,500,{error:l.message})}}async handleChannelChats(e,t,n={}){let s=new Set,i=[],o=[],a=(l,d)=>{let u=0;for(let g of l)!g.id||s.has(g.id)||(s.add(g.id),i.push({...g,source:d}),u++);u>0&&o.push(`${d}:${u}`)},c=this.router.getChannel(t);if(c?.listKnownChats)try{a(c.listKnownChats(),"local")}catch{}else if(c?.listChats)try{a(c.listChats().map(l=>({id:l.jid,name:l.name,kind:l.isGroup?"group":"dm"})),"local")}catch{}if(!n.localOnly&&this.mesh){let l=this.mesh.directory().filter(d=>d.healthy&&d.channels?.includes(t));await Promise.allSettled(l.map(async d=>{try{let u=await fetch(`${d.peerUrl}/channels/${encodeURIComponent(t)}/chats?local=1`,{headers:this.mesh.authHeaders(d.peer),signal:AbortSignal.timeout(5e3)});if(!u.ok)return;let g=await u.json();Array.isArray(g.chats)&&a(g.chats.map(m=>({id:m.id,name:m.name,kind:m.kind??"dm",accountId:m.accountId})),`mesh:${d.peer}`)}catch{}}))}this.json(e,200,{channel:t,source:o.join(", ")||"none",chats:i})}async handleWorkflowManualRun(e,t,n){if(!this.workflowDispatcher||!this.workflowStore){this.json(t,503,{error:"workflow engine not enabled on this node"});return}let s;try{s=await se(e)}catch{s={}}try{let i=await km({get:o=>this.workflowStore.get(o),dispatchWorkflow:o=>this.workflowDispatcher.dispatchWorkflow(o),startRun:o=>this.workflowDispatcher.startRun(o)},n,s);this.json(t,i.status,i.body)}catch(i){this.log(`[workflows] manual run "${n}" failed: ${i.message}`),this.json(t,500,{error:i.message})}}},Za=ec;ft(Za,"MESH_PROTECTED_PATHS",new Set(["/task","/ask","/mesh/task","/mesh/inbox/send","/workflow/event","/workflow/transition","/channel/send","/push/app","/webrtc/signal","/gitlab/react","/gitlab/send-note","/gitlab/log-time","/channel/edit-comment","/github/send-comment"]));function pA(r){if(typeof r!="string")return"";let e=r.trim();if(!e)return"";let t=e.match(/^[^.!?\n]+[.!?]?/),n=(t?t[0]:e).trim();return n.length>140?n.slice(0,137)+"\u2026":n}function se(r){return new Promise((e,t)=>{let n="";r.setEncoding("utf8"),r.on("data",s=>{n+=s}),r.on("end",()=>{try{e(n?JSON.parse(n):{})}catch(s){t(s)}}),r.on("error",t)})}function fA(r,e){let t=["model","workspace","tier","permissionMode","mcpServers"],n=new Set,s=new Set([...Object.keys(r),...Object.keys(e)]);for(let i of s){let o=r[i],a=e[i];if(!o||!a){n.add("add/remove");continue}for(let c of t)JSON.stringify(o[c])!==JSON.stringify(a[c])&&n.add(`${i}.${c}`)}return Array.from(n).slice(0,6)}async function H(r){return new Promise((e,t)=>{let n="";r.on("data",s=>n+=s.toString()),r.on("end",()=>{try{e(n?JSON.parse(n):{})}catch{e({})}}),r.on("error",t)})}hk();function SU(r){typeof r=="string"&&(_n.error(r),process.exit(1)),r instanceof Error&&(_n.error(r.message),process.exit(1)),_n.error("Something went wrong. Please try again."),process.exit(1)}export{CA as a,Ji as b,rt as c,Qt as d,tn as e,ct as f,us as g,Xh as h,Wg as i,lS as j,dS as k,Jg as l,Be as m,dt as n,zg as o,ut as p,yP as q,wP as r,Hs as s,y_ as t,o$ as u,Vo as v,FM as w,UM as x,GM as y,cm as z,L$ as A,VM as B,JM as C,fn as D,Dt as E,mn as F,kO as G,Um as H,EI as I,uD as J,ur as K,ha as L,ga as M,mr as N,fe as O,Py as P,kr as Q,xT as R,By as S,RT as T,Xy as U,Sr as V,In as W,xr as X,Rr as Y,ew as Z,Ra as _,Je as $,Re as aa,cw as ba,lw as ca,dw as da,uw as ea,Xr as fa,tk as ga,Ka as ha,sk as ia,nA as ja,OL as ka,Za as la,SU as ma};
|
|
1179
|
+
//# sourceMappingURL=chunk-ID4DCD2T.js.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
var w=[1e3,3e3,8e3],b=2e4;function M(e){let t=e,n=t?.message??String(e),s=t?.cause;return s?.code?`${n} code=${s.code}`:s?.message&&s.message!==n?`${n} (${s.message})`:n}var A=new Set(["ECONNREFUSED","ETIMEDOUT","ENETUNREACH","EHOSTUNREACH","EADDRNOTAVAIL","ENOTFOUND","EAI_AGAIN","UND_ERR_CONNECT_TIMEOUT"]);function N(e){let t=e?.cause?.code;return!!t&&A.has(t)}function D(e){return e===429||e===503}function y(e){return/^[\x20-\x7E]*$/.test(e)?e:`=?UTF-8?B?${Buffer.from(e,"utf8").toString("base64")}?=`}function U(e){let t=e.split(`
|
|
2
|
+
`),n=(t[0]??"").trim(),s=t.slice(1).join(`
|
|
3
|
+
`).trim();return s?n.length===0||n.length>80?{body:e.trim()}:{title:n,body:s}:{body:e.trim()}}var p=class{name="ntfy";config;log;constructor(t,n=console.error.bind(console,"[ntfy]")){this.config=t,this.log=n}async start(){this.log(`ntfy: ready (${this.serverUrl()}/${this.config.topic})`)}async stop(){}onMessage(t){}serverUrl(){return(this.config.server||"https://ntfy.sh").replace(/\/+$/,"")}async send(t){let n=(t.chatId||"").trim(),s=n&&n!=="default"?n:this.config.topic;if(!s)throw new Error("ntfy: no topic (pass chatId, or set channels.ntfy.topic)");let f=U(t.text||""),E=f.title||this.config.defaultTitle||"AgentX",c={"Content-Type":"text/plain; charset=utf-8",Title:y(E),Priority:String(this.config.defaultPriority??3)};if(this.config.token&&(c.Authorization=`Bearer ${this.config.token}`),t.parseMode==="markdown"&&(c.Markdown="yes"),t.buttons?.length){let o=t.buttons.slice(0,3).map(i=>`view, ${i.label.replace(/[,;]/g," ")}, ${i.url}`).join("; ");c.Actions=y(o)}let T=`${this.serverUrl()}/${encodeURIComponent(s)}`,$=f.body||t.text||"",d=this.config.retryDelaysMs??w,u=d.length+1,m=this.config.sendTimeoutMs??b,h=Date.now()+m;for(let o=1;;o++){let i,a,l=!1;try{let r=AbortSignal.timeout(Math.max(1,h-Date.now()));i=await fetch(T,{method:"POST",headers:c,body:$,signal:r})}catch(r){a=r?.name==="TimeoutError"?new Error(`ntfy: send time limit of ${m} ms reached`,{cause:r}):new Error(`ntfy: ${M(r)}`,{cause:r}),l=N(r)}if(i){if(i.ok)return(await i.json().catch(()=>null))?.id;let r=await i.text().catch(()=>"");a=new Error(`ntfy: ${i.status} ${r.slice(0,200)}`),l=D(i.status)}let g=d[o-1];if(!l||o>=u||Date.now()+g>=h)throw this.log(`ntfy: send failed after ${o} attempt(s): ${a.message}`),a;this.log(`ntfy: attempt ${o}/${u} failed (${a.message}); retrying in ${g} ms`),await new Promise(r=>setTimeout(r,g))}}};export{M as a,U as b,p as c};
|
|
4
|
+
//# sourceMappingURL=chunk-IS2IWRP7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/channels/ntfy.ts"],"sourcesContent":["import type { ChannelAdapter, IncomingMessage, OutgoingMessage } from \"./types\"\n\n// --- ntfy channel — push notifications to the operator's phone ---\n//\n// Outbound only. ntfy has an inbound side (SSE subscribe), but nothing here\n// wants it: a push notification is a one-way tap on the shoulder, and replies\n// come back through the channels that already carry conversation (Telegram,\n// WhatsApp) or through POST /ask when Siri asks a follow-up.\n//\n// Registering it as a ChannelAdapter rather than a bespoke notifier is what\n// makes it free everywhere: notifications.destination, POST /send, cron\n// failure pings, and the agent-facing `channel` action all funnel through\n// router.sendOutbound() and already know how to address a channel + chatId.\n//\n// Addressing: `chatId` is the ntfy topic. The literal `\"default\"` (and an\n// empty string) resolve to the configured topic instead. That indirection\n// exists so agents never have to carry the topic in their prompts — on the\n// public ntfy.sh the topic IS the credential, and every caller that funnels\n// through the router (POST /send, channel.reply) requires a non-empty chatId.\n\nexport interface NtfyConfig {\n /** Base URL of the ntfy server. Default is the public ntfy.sh. */\n server?: string\n /** Default topic when an OutgoingMessage carries no chatId. */\n topic: string\n /** Access token for protected topics (ntfy `Authorization: Bearer`). */\n token?: string\n /** 1 (min) .. 5 (max). ntfy's default is 3. */\n defaultPriority?: number\n /** Notification title. Per-message titles override it. */\n defaultTitle?: string\n /** Test hook, not in the config schema: waits between attempts when a\n * publish is safe to repeat. One attempt more than there are entries. */\n retryDelaysMs?: number[]\n /** Test hook, not in the config schema: time limit for one send, all\n * attempts and waits included. */\n sendTimeoutMs?: number\n}\n\nconst DEFAULT_RETRY_DELAYS_MS = [1000, 3000, 8000]\nconst DEFAULT_SEND_TIMEOUT_MS = 20_000\n\n/** `fetch failed` hides the reason in `cause`; surface its code so a log\n * line says ETIMEDOUT or ECONNREFUSED rather than nothing useful (#930). */\nexport function describeFetchError(e: unknown): string {\n const err = e as { message?: string; cause?: { code?: string; message?: string } }\n const message = err?.message ?? String(e)\n const cause = err?.cause\n if (cause?.code) return `${message} code=${cause.code}`\n if (cause?.message && cause.message !== message) return `${message} (${cause.message})`\n return message\n}\n\n// A publish is retried only when the server cannot have stored it: ntfy has\n// no key to drop a repeat, so re-posting after a 500 or a reset mid-response\n// could put the same notification on the phone twice.\n\n/** The connection never opened, so the request never reached the server.\n * ETIMEDOUT can also, rarely, mean an open connection went silent; then a\n * repeat is possible. Telling the two apart would need to know whether the\n * body was written, which fetch does not expose. */\nconst CONNECT_FAILURE_CODES = new Set([\n \"ECONNREFUSED\", \"ETIMEDOUT\", \"ENETUNREACH\", \"EHOSTUNREACH\", \"EADDRNOTAVAIL\",\n \"ENOTFOUND\", \"EAI_AGAIN\", \"UND_ERR_CONNECT_TIMEOUT\",\n])\n\nfunction isRetryableFetchError(e: unknown): boolean {\n const code = (e as { cause?: { code?: string } })?.cause?.code\n return !!code && CONNECT_FAILURE_CODES.has(code)\n}\n\n/** 429 and 503 say the server turned the request away without handling it. */\nfunction isRetryableStatus(status: number): boolean {\n return status === 429 || status === 503\n}\n\n/**\n * ntfy requires header values to be ASCII. Titles and tags routinely aren't\n * (French accents, Arabic), so non-ASCII values go out as RFC 2047 encoded\n * words, which ntfy decodes. Pure-ASCII values are passed through untouched\n * so the common case stays readable in logs.\n */\nfunction encodeHeader(value: string): string {\n // eslint-disable-next-line no-control-regex\n if (/^[\\x20-\\x7E]*$/.test(value)) return value\n return `=?UTF-8?B?${Buffer.from(value, \"utf8\").toString(\"base64\")}?=`\n}\n\n/** First line of the body, when it reads like a title (short, no trailing\n * punctuation that implies a sentence continues). Lets an agent write a\n * single blob and still get a well-formed notification. */\nexport function splitTitle(text: string): { title?: string; body: string } {\n const lines = text.split(\"\\n\")\n const first = (lines[0] ?? \"\").trim()\n const rest = lines.slice(1).join(\"\\n\").trim()\n if (!rest) return { body: text.trim() }\n if (first.length === 0 || first.length > 80) return { body: text.trim() }\n return { title: first, body: rest }\n}\n\nexport class NtfyAdapter implements ChannelAdapter {\n readonly name = \"ntfy\"\n private config: NtfyConfig\n private log: (...args: unknown[]) => void\n\n constructor(config: NtfyConfig, log: (...args: unknown[]) => void = console.error.bind(console, \"[ntfy]\")) {\n this.config = config\n this.log = log\n }\n\n async start(): Promise<void> {\n this.log(`ntfy: ready (${this.serverUrl()}/${this.config.topic})`)\n }\n\n async stop(): Promise<void> {}\n\n /** No inbound side — the handler is accepted and never invoked, which\n * satisfies router.addChannel() without pretending to poll. */\n onMessage(_handler: (msg: IncomingMessage) => Promise<void>): void {}\n\n private serverUrl(): string {\n return (this.config.server || \"https://ntfy.sh\").replace(/\\/+$/, \"\")\n }\n\n async send(msg: OutgoingMessage): Promise<string | void> {\n const requested = (msg.chatId || \"\").trim()\n const topic = requested && requested !== \"default\" ? requested : this.config.topic\n if (!topic) throw new Error(\"ntfy: no topic (pass chatId, or set channels.ntfy.topic)\")\n\n const split = splitTitle(msg.text || \"\")\n const title = split.title || this.config.defaultTitle || \"AgentX\"\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"text/plain; charset=utf-8\",\n \"Title\": encodeHeader(title),\n \"Priority\": String(this.config.defaultPriority ?? 3),\n }\n if (this.config.token) headers[\"Authorization\"] = `Bearer ${this.config.token}`\n if (msg.parseMode === \"markdown\") headers[\"Markdown\"] = \"yes\"\n\n // URL buttons map onto ntfy's view actions, so an alert can carry\n // \"open the MR\" without the operator hunting for it.\n if (msg.buttons?.length) {\n const actions = msg.buttons\n .slice(0, 3) // ntfy caps at 3\n .map(b => `view, ${b.label.replace(/[,;]/g, \" \")}, ${b.url}`)\n .join(\"; \")\n headers[\"Actions\"] = encodeHeader(actions)\n }\n\n const url = `${this.serverUrl()}/${encodeURIComponent(topic)}`\n const body = split.body || msg.text || \"\"\n const delays = this.config.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS\n const attempts = delays.length + 1\n // Each try can spend several seconds per address on a dead link, so the\n // whole send gets one time limit and callers never wait much past it.\n const limitMs = this.config.sendTimeoutMs ?? DEFAULT_SEND_TIMEOUT_MS\n const deadline = Date.now() + limitMs\n\n // A slow or flapping link loses pushes that a few seconds later would\n // go through. Retry only what cannot have been stored (see above).\n for (let attempt = 1; ; attempt++) {\n let res: Response | undefined\n let failure!: Error\n let retryable = false\n try {\n const signal = AbortSignal.timeout(Math.max(1, deadline - Date.now()))\n res = await fetch(url, { method: \"POST\", headers, body, signal })\n } catch (e) {\n // The abort only says \"aborted due to timeout\"; name the send limit.\n failure = (e as { name?: string })?.name === \"TimeoutError\"\n ? new Error(`ntfy: send time limit of ${limitMs} ms reached`, { cause: e })\n : new Error(`ntfy: ${describeFetchError(e)}`, { cause: e })\n retryable = isRetryableFetchError(e)\n }\n if (res) {\n if (res.ok) {\n const json = await res.json().catch(() => null) as { id?: string } | null\n return json?.id\n }\n const detail = await res.text().catch(() => \"\")\n failure = new Error(`ntfy: ${res.status} ${detail.slice(0, 200)}`)\n retryable = isRetryableStatus(res.status)\n }\n const wait = delays[attempt - 1]\n if (!retryable || attempt >= attempts || Date.now() + wait >= deadline) {\n this.log(`ntfy: send failed after ${attempt} attempt(s): ${failure.message}`)\n throw failure\n }\n this.log(`ntfy: attempt ${attempt}/${attempts} failed (${failure.message}); retrying in ${wait} ms`)\n await new Promise(r => setTimeout(r, wait))\n }\n }\n}\n"],"mappings":"AAuCA,IAAMA,EAA0B,CAAC,IAAM,IAAM,GAAI,EAC3CC,EAA0B,IAIzB,SAASC,EAAmB,EAAoB,CACrD,IAAMC,EAAM,EACNC,EAAUD,GAAK,SAAW,OAAO,CAAC,EAClCE,EAAQF,GAAK,MACnB,OAAIE,GAAO,KAAa,GAAGD,UAAgBC,EAAM,OAC7CA,GAAO,SAAWA,EAAM,UAAYD,EAAgB,GAAGA,MAAYC,EAAM,WACtED,CACT,CAUA,IAAME,EAAwB,IAAI,IAAI,CACpC,eAAgB,YAAa,cAAe,eAAgB,gBAC5D,YAAa,YAAa,yBAC5B,CAAC,EAED,SAASC,EAAsB,EAAqB,CAClD,IAAMC,EAAQ,GAAqC,OAAO,KAC1D,MAAO,CAAC,CAACA,GAAQF,EAAsB,IAAIE,CAAI,CACjD,CAGA,SAASC,EAAkBC,EAAyB,CAClD,OAAOA,IAAW,KAAOA,IAAW,GACtC,CAQA,SAASC,EAAaC,EAAuB,CAE3C,MAAI,iBAAiB,KAAKA,CAAK,EAAUA,EAClC,aAAa,OAAO,KAAKA,EAAO,MAAM,EAAE,SAAS,QAAQ,KAClE,CAKO,SAASC,EAAWC,EAAgD,CACzE,IAAMC,EAAQD,EAAK,MAAM;AAAA,CAAI,EACvBE,GAASD,EAAM,CAAC,GAAK,IAAI,KAAK,EAC9BE,EAAOF,EAAM,MAAM,CAAC,EAAE,KAAK;AAAA,CAAI,EAAE,KAAK,EAC5C,OAAKE,EACDD,EAAM,SAAW,GAAKA,EAAM,OAAS,GAAW,CAAE,KAAMF,EAAK,KAAK,CAAE,EACjE,CAAE,MAAOE,EAAO,KAAMC,CAAK,EAFhB,CAAE,KAAMH,EAAK,KAAK,CAAE,CAGxC,CAEO,IAAMI,EAAN,KAA4C,CACxC,KAAO,OACR,OACA,IAER,YAAYC,EAAoBC,EAAoC,QAAQ,MAAM,KAAK,QAAS,QAAQ,EAAG,CACzG,KAAK,OAASD,EACd,KAAK,IAAMC,CACb,CAEA,MAAM,OAAuB,CAC3B,KAAK,IAAI,gBAAgB,KAAK,UAAU,KAAK,KAAK,OAAO,QAAQ,CACnE,CAEA,MAAM,MAAsB,CAAC,CAI7B,UAAUC,EAAyD,CAAC,CAE5D,WAAoB,CAC1B,OAAQ,KAAK,OAAO,QAAU,mBAAmB,QAAQ,OAAQ,EAAE,CACrE,CAEA,MAAM,KAAKC,EAA8C,CACvD,IAAMC,GAAaD,EAAI,QAAU,IAAI,KAAK,EACpCE,EAAQD,GAAaA,IAAc,UAAYA,EAAY,KAAK,OAAO,MAC7E,GAAI,CAACC,EAAO,MAAM,IAAI,MAAM,0DAA0D,EAEtF,IAAMC,EAAQZ,EAAWS,EAAI,MAAQ,EAAE,EACjCI,EAAQD,EAAM,OAAS,KAAK,OAAO,cAAgB,SAEnDE,EAAkC,CACtC,eAAgB,4BAChB,MAAShB,EAAae,CAAK,EAC3B,SAAY,OAAO,KAAK,OAAO,iBAAmB,CAAC,CACrD,EAMA,GALI,KAAK,OAAO,QAAOC,EAAQ,cAAmB,UAAU,KAAK,OAAO,SACpEL,EAAI,YAAc,aAAYK,EAAQ,SAAc,OAIpDL,EAAI,SAAS,OAAQ,CACvB,IAAMM,EAAUN,EAAI,QACjB,MAAM,EAAG,CAAC,EACV,IAAIO,GAAK,SAASA,EAAE,MAAM,QAAQ,QAAS,GAAG,MAAMA,EAAE,KAAK,EAC3D,KAAK,IAAI,EACZF,EAAQ,QAAahB,EAAaiB,CAAO,EAG3C,IAAME,EAAM,GAAG,KAAK,UAAU,KAAK,mBAAmBN,CAAK,IACrDO,EAAON,EAAM,MAAQH,EAAI,MAAQ,GACjCU,EAAS,KAAK,OAAO,eAAiBhC,EACtCiC,EAAWD,EAAO,OAAS,EAG3BE,EAAU,KAAK,OAAO,eAAiBjC,EACvCkC,EAAW,KAAK,IAAI,EAAID,EAI9B,QAASE,EAAU,GAAKA,IAAW,CACjC,IAAIC,EACAC,EACAC,EAAY,GAChB,GAAI,CACF,IAAMC,EAAS,YAAY,QAAQ,KAAK,IAAI,EAAGL,EAAW,KAAK,IAAI,CAAC,CAAC,EACrEE,EAAM,MAAM,MAAMP,EAAK,CAAE,OAAQ,OAAQ,QAAAH,EAAS,KAAAI,EAAM,OAAAS,CAAO,CAAC,CAClE,OAASC,EAAP,CAEAH,EAAWG,GAAyB,OAAS,eACzC,IAAI,MAAM,4BAA4BP,eAAsB,CAAE,MAAOO,CAAE,CAAC,EACxE,IAAI,MAAM,SAASvC,EAAmBuC,CAAC,IAAK,CAAE,MAAOA,CAAE,CAAC,EAC5DF,EAAYhC,EAAsBkC,CAAC,CACrC,CACA,GAAIJ,EAAK,CACP,GAAIA,EAAI,GAEN,OADa,MAAMA,EAAI,KAAK,EAAE,MAAM,IAAM,IAAI,IACjC,GAEf,IAAMK,EAAS,MAAML,EAAI,KAAK,EAAE,MAAM,IAAM,EAAE,EAC9CC,EAAU,IAAI,MAAM,SAASD,EAAI,UAAUK,EAAO,MAAM,EAAG,GAAG,GAAG,EACjEH,EAAY9B,EAAkB4B,EAAI,MAAM,EAE1C,IAAMM,EAAOX,EAAOI,EAAU,CAAC,EAC/B,GAAI,CAACG,GAAaH,GAAWH,GAAY,KAAK,IAAI,EAAIU,GAAQR,EAC5D,WAAK,IAAI,2BAA2BC,iBAAuBE,EAAQ,SAAS,EACtEA,EAER,KAAK,IAAI,iBAAiBF,KAAWH,aAAoBK,EAAQ,yBAAyBK,MAAS,EACnG,MAAM,IAAI,QAAQ,GAAK,WAAW,EAAGA,CAAI,CAAC,EAE9C,CACF","names":["DEFAULT_RETRY_DELAYS_MS","DEFAULT_SEND_TIMEOUT_MS","describeFetchError","err","message","cause","CONNECT_FAILURE_CODES","isRetryableFetchError","code","isRetryableStatus","status","encodeHeader","value","splitTitle","text","lines","first","rest","NtfyAdapter","config","log","_handler","msg","requested","topic","split","title","headers","actions","b","url","body","delays","attempts","limitMs","deadline","attempt","res","failure","retryable","signal","e","detail","wait"]}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{c as d,g}from"./chunk-WCXXXAHV.js";import{a as f}from"./chunk-
|
|
2
|
-
//# sourceMappingURL=chunk-
|
|
1
|
+
import{c as d,g}from"./chunk-WCXXXAHV.js";import{a as f}from"./chunk-5HLD66CW.js";var k=/^[A-Za-z0-9._-]{1,64}$/,m=/^[A-Za-z0-9_-]{1,64}$/,A=100,w=1e3,E=6e4,P=15*6e4;function p(r){return(r.replace(/[^A-Za-z0-9._-]+/g,"-").replace(/^-+|-+$/g,"")||"node").slice(0,64)}function _(r,t,n){let i=/^bearer /i.test(r)?r.slice(7).trim():"",e="Phones paired with another computer need that computer's own peer token: give it a token in mesh.peers on both computers.";if(!i||n&&i===n)return{error:e};let a=new Set(t.filter(s=>s.token===i).map(s=>p(s.name)));if(a.size===0)return{error:e};if(a.size>1)return{error:`Several mesh.peers entries share this token (${[...a].join(", ")}). Give each computer its own token.`};let[o]=a,u=t.filter(s=>s.token!==i&&p(s.name)===o).map(s=>s.name);return u.length?{error:`mesh.peers names ${u.map(s=>`"${s}"`).join(", ")} too close to this computer's name: rename one so they differ in letters or digits.`}:{origin:o}}function C(r,t){let n=r.indexOf(":");return n<0?{ok:!0}:"error"in t?{error:t.error}:r.slice(0,n)!==t.origin?{error:`${r} is a phone paired with another computer; only that computer can send to it.`}:{ok:!0}}function x(r,t,n){let i=r.toLowerCase(),e=t.find(a=>a.name.toLowerCase()===i);return e?e.token?n&&e.token===n?`mesh.peers "${e.name}" uses the shared MESH_TOKEN, so phones paired here can't turn notifications on. Give this pair a token of its own on both computers.`:null:`mesh.peers "${e.name}" has no token, so phones paired here can't turn notifications on. Give it a token of its own on both computers.`:null}function O(r){return new Set(r.map(t=>p(t.name)))}function j(r,t,n,i){let e=r&&typeof r=="object"?r:{};if("error"in t)return{status:403,body:{error:t.error}};let a=t.origin;if(!k.test(a))return{status:403,body:{error:"this peer's name can't be used as a notification origin"}};if(!n)return{status:503,body:{error:i??"Notifications are not set up on the computer that sends them."}};if(e.op==="roster"){if(!Array.isArray(e.active)||e.active.length>w||!e.active.every(l=>typeof l=="string"&&m.test(l)))return{status:400,body:{error:"active must be a list of device ids"}};let h=n.store();return h?{status:200,body:{ok:!0,removed:h.pruneOrigin(a,e.active)}}:{status:503,body:{error:n.reason??"the database is unavailable"}}}if(e.op!=="app")return{status:400,body:{error:"op must be app or roster"}};let o=e.call&&typeof e.call=="object"?e.call:{},u=o.device&&typeof o.device=="object"?o.device:{},s=typeof o.path=="string"?o.path:"",c=o.method==="GET"||o.method==="POST"?o.method:"";if(!d(s)||!c)return{status:404,body:{error:"not found"}};if(typeof u.id!="string"||!m.test(u.id))return{status:400,body:{error:"device.id is required"}};let b=typeof u.name=="string"&&u.name.trim()?u.name.trim():u.id,v=o.body&&typeof o.body=="object"&&!Array.isArray(o.body)?o.body:{};return g({path:s,method:c,body:v,device:{id:f(a,u.id),name:S(`${b} (${a})`,A)}},n)}var y=class{constructor(t){this.deps=t}lastKey=null;lastSentAt=0;failing=!1;async tick(){let t=(this.deps.now??Date.now)(),n;try{n=[...new Set(this.deps.active())].sort()}catch(e){return this.deps.log?.(`push: phone list not read, not sent this time: ${e?.message??e}`),!1}let i=n.join(",");if(i===this.lastKey&&t-this.lastSentAt<P)return!1;try{let e=await this.deps.send(n);if(e&&typeof e=="object"&&typeof e.status=="number"&&e.status!==200)throw new Error(`${e.status} ${typeof e.body?.error=="string"?e.body.error:"refused"}`);return this.lastKey=i,this.lastSentAt=t,this.failing&&this.deps.log?.("push: phone list sent to the push host again"),this.failing=!1,!0}catch(e){return this.failing||this.deps.log?.(`push: phone list not sent to the push host, retrying every minute: ${e?.message??e}`),this.failing=!0,!1}}};function S(r,t){let n=[...r];return n.length>t?n.slice(0,t-1).join("")+"\u2026":r}export{E as a,P as b,p as c,_ as d,C as e,x as f,O as g,j as h,y as i};
|
|
2
|
+
//# sourceMappingURL=chunk-TEM7IIDC.js.map
|
package/dist/cli.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{b as
|
|
2
|
+
import{b as o}from"./chunk-JXF54PT7.js";import"./chunk-QIJLFPWE.js";var e=o(process.versions.node);e&&(console.error(e),process.exit(1));var{buildProgram:s}=await import("./program-3TO4YAY7.js"),{installCliSignalExit:r}=await import("./signal-exit-WL7G3EEZ.js"),{applyConnectAttemptTimeout:i,configPathFromArgv:a,readConnectAttemptTimeout:n}=await import("./net-family-X2P3OVDP.js");r();i(n(process.cwd(),a(process.argv.slice(2))));var t=await s();process.argv.slice(2).length===0?t.outputHelp():t.parse();
|
|
3
3
|
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { unsupportedNodeMessage } from \"@/utils/node-version\"\n\n// Static imports load before any code here runs, and a dependency fails to\n// load on an old Node. So the check comes first and the rest is imported after.\nconst refusal = unsupportedNodeMessage(process.versions.node)\nif (refusal) {\n console.error(refusal)\n process.exit(1)\n}\n\nconst { buildProgram } = await import(\"@/program\")\nconst { installCliSignalExit } = await import(\"@/utils/signal-exit\")\nconst { applyConnectAttemptTimeout, readConnectAttemptTimeout } = await import(\"@/daemon/net-family\")\n\ninstallCliSignalExit()\n// CLI commands that call out on their own need the same connect attempt\n// timeout as the daemon, or they fail on a slow dual-stack link (#930).\napplyConnectAttemptTimeout(readConnectAttemptTimeout())\n\nconst program = await buildProgram()\n\nif (process.argv.slice(2).length === 0) {\n program.outputHelp()\n} else {\n program.parse()\n}\n"],"mappings":";oEAKA,IAAMA,EAAUC,EAAuB,QAAQ,SAAS,IAAI,EACxDD,IACF,QAAQ,MAAMA,CAAO,EACrB,QAAQ,KAAK,CAAC,GAGhB,GAAM,CAAE,aAAAE,CAAa,EAAI,KAAM,QAAO,uBAAW,EAC3C,CAAE,qBAAAC,CAAqB,EAAI,KAAM,QAAO,2BAAqB,EAC7D,CAAE,2BAAAC,EAA4B,0BAAAC,CAA0B,EAAI,KAAM,QAAO,0BAAqB,
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { unsupportedNodeMessage } from \"@/utils/node-version\"\n\n// Static imports load before any code here runs, and a dependency fails to\n// load on an old Node. So the check comes first and the rest is imported after.\nconst refusal = unsupportedNodeMessage(process.versions.node)\nif (refusal) {\n console.error(refusal)\n process.exit(1)\n}\n\nconst { buildProgram } = await import(\"@/program\")\nconst { installCliSignalExit } = await import(\"@/utils/signal-exit\")\nconst { applyConnectAttemptTimeout, configPathFromArgv, readConnectAttemptTimeout } = await import(\"@/daemon/net-family\")\n\ninstallCliSignalExit()\n// CLI commands that call out on their own need the same connect attempt\n// timeout as the daemon, or they fail on a slow dual-stack link (#930).\n// Read the file the command was pointed at, so a daemon started with\n// --config gets its own value and not the default first.\napplyConnectAttemptTimeout(readConnectAttemptTimeout(process.cwd(), configPathFromArgv(process.argv.slice(2))))\n\nconst program = await buildProgram()\n\nif (process.argv.slice(2).length === 0) {\n program.outputHelp()\n} else {\n program.parse()\n}\n"],"mappings":";oEAKA,IAAMA,EAAUC,EAAuB,QAAQ,SAAS,IAAI,EACxDD,IACF,QAAQ,MAAMA,CAAO,EACrB,QAAQ,KAAK,CAAC,GAGhB,GAAM,CAAE,aAAAE,CAAa,EAAI,KAAM,QAAO,uBAAW,EAC3C,CAAE,qBAAAC,CAAqB,EAAI,KAAM,QAAO,2BAAqB,EAC7D,CAAE,2BAAAC,EAA4B,mBAAAC,EAAoB,0BAAAC,CAA0B,EAAI,KAAM,QAAO,0BAAqB,EAExHH,EAAqB,EAKrBC,EAA2BE,EAA0B,QAAQ,IAAI,EAAGD,EAAmB,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,EAE9G,IAAME,EAAU,MAAML,EAAa,EAE/B,QAAQ,KAAK,MAAM,CAAC,EAAE,SAAW,EACnCK,EAAQ,WAAW,EAEnBA,EAAQ,MAAM","names":["refusal","unsupportedNodeMessage","buildProgram","installCliSignalExit","applyConnectAttemptTimeout","configPathFromArgv","readConnectAttemptTimeout","program"]}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{a as St,b as kt,c as vt,d as xt,e as $t,f as Ct,g as Pt,h as bt,la as Tt,ma as Mt}from"./chunk-
|
|
1
|
+
import{a as St,b as kt,c as vt,d as xt,e as $t,f as Ct,g as Pt,h as bt,la as Tt,ma as Mt}from"./chunk-ID4DCD2T.js";import"./chunk-PNITKKDR.js";import"./chunk-GTJIKXDB.js";import"./chunk-Z2QOQBUS.js";import"./chunk-7LN4VKDU.js";import"./chunk-IMLAPB7G.js";import"./chunk-ZVP6GB4G.js";import"./chunk-S2GAEH2F.js";import"./chunk-UNIXYV3U.js";import"./chunk-AKWBDCMU.js";import"./chunk-PPUN7YHA.js";import"./chunk-VZLIBDLH.js";import"./chunk-EKJG63EJ.js";import"./chunk-EMGB23H5.js";import"./chunk-TEM7IIDC.js";import"./chunk-WCXXXAHV.js";import"./chunk-JWDJMFU6.js";import"./chunk-5HLD66CW.js";import"./chunk-IS2IWRP7.js";import"./chunk-LQE73AZR.js";import"./chunk-2D3RYIDU.js";import"./chunk-MRIVENBY.js";import"./chunk-A5GYG56I.js";import"./chunk-635B6SXI.js";import"./chunk-2AXFYDWF.js";import"./chunk-AQASR6KR.js";import{ca as yt,m as lt,n as ct,o as dt,p as mt,q as pt}from"./chunk-5ADXRVYT.js";import"./chunk-3K3LRLZX.js";import"./chunk-QSKG754X.js";import"./chunk-MEFPXHQJ.js";import{a as L}from"./chunk-UG2OF2EI.js";import{A as D,B as z,C as V,a as ye,b as we,c as Se,d as Ae,e as Ie,f as Oe,g as Ne,h as He,i as je,j as Ue,k as _e,l as I,m as q,n as Be,o as qe,p as Ye,q as O,r as ze,s as Ve,t as Xe,u as x,v as Y,w as Qe,x as Ze,y as et,z as wt}from"./chunk-ZYUVKFX2.js";import{a as B,b as M,c as F}from"./chunk-SFQUP3BP.js";import"./chunk-Y4CYQV7G.js";import"./chunk-LLNPKE7H.js";import"./chunk-SHEUCITK.js";import"./chunk-5RHCM3L7.js";import"./chunk-SZDACK6O.js";import"./chunk-HF5EF335.js";import"./chunk-W4YR2RO2.js";import"./chunk-AFRHKA7B.js";import{s as ut}from"./chunk-23LWLYOK.js";import"./chunk-BXXP6DTB.js";import"./chunk-3UOTLMXD.js";import"./chunk-36AXN6ZB.js";import"./chunk-PE5V2LI7.js";import"./chunk-GQ76J72O.js";import"./chunk-ILEC3IAV.js";import"./chunk-J3SOEFRE.js";import"./chunk-SIU6STB2.js";import"./chunk-X5C5W3E4.js";import"./chunk-GV7S5BAX.js";import"./chunk-GSX4NK2N.js";import"./chunk-VWTBHLMX.js";import"./chunk-RWMDDMYX.js";import{a as ht}from"./chunk-MXDWLBIA.js";import"./chunk-OWNQ2FD4.js";import"./chunk-KEXBEUSU.js";import"./chunk-ROLSSYXE.js";import"./chunk-PNYBZPPB.js";import"./chunk-SKSYII3K.js";import{f as gt,g as ft}from"./chunk-E3AEOQMK.js";import"./chunk-WPNRDEHW.js";import"./chunk-MWNCS57L.js";import"./chunk-4LWFJ4MZ.js";import"./chunk-BIJHMWXK.js";import"./chunk-7JUISIE7.js";import{j as it,k as rt,l as at}from"./chunk-SQ37I2MD.js";import"./chunk-72SZI2UW.js";import"./chunk-FCPTN6NF.js";import"./chunk-MCI3DIWW.js";import"./chunk-MYVHVJWZ.js";import"./chunk-6TURWSMN.js";import"./chunk-AV7T3SYT.js";import"./chunk-Y5MFVVTM.js";import"./chunk-QD6IICK7.js";import"./chunk-QOYAX2FT.js";import{a as ke,b as ve,c as xe,d as $e,e as Ce,f as Pe,g as be,h as Te,i as Me,j as Re,l as Ee,m as Ge,o as T,p as W}from"./chunk-2PJKGZWP.js";import{a as m,b as G}from"./chunk-7I7IEX4S.js";import{b as Fe,c as Le,d as A,e as De}from"./chunk-JHCI6XCJ.js";import{a as Ke,b as Je,e as We}from"./chunk-DSYKYZMT.js";import{Aa as nt,qa as tt,sa as st,ta as ot}from"./chunk-KHYF6KTY.js";import"./chunk-BRK6M6LA.js";import"./chunk-UIBUZ2N3.js";import"./chunk-2QNT5JDS.js";import"./chunk-RGDKHYY2.js";import"./chunk-QIJLFPWE.js";W();G();import{execa as Rt}from"execa";import{existsSync as se,promises as X}from"fs";import S from"path";import ie from"node-fetch";async function Et(o,e){let t=o.split("/");if(t.length<2)throw new Error(`Invalid skill package ID: ${o}. Expected format: owner/repo`);let s=t[0],i=t[1],n=t[2];try{let a=n?`${s}/${i}/${n}`:`${s}/${i}`;await Rt("npx",["-y","skills","add",a],{cwd:e,stdio:"pipe"}),m.success(`Installed skill package: ${o}`);let r=S.resolve(e,".skills",`${s}_${i}`);if(se(r))return await ne(r,o);let d=S.resolve(e,".skills");return se(d)?await ne(d,o):[]}catch{return m.info(`Fetching skills directly from GitHub: ${s}/${i}`),await Gt(s,i,n,e)}}async function Gt(o,e,t,s){let i=[];try{let n=`https://api.github.com/repos/${o}/${e}/git/trees/main?recursive=1`,a=await ie(n);if(!a.ok)throw new Error(`GitHub API error: ${a.status}`);let d=(await a.json()).tree.filter(p=>p.type==="blob"&&p.path.endsWith("SKILL.md"));if(t){let p=d.find(f=>f.path.includes(`/${t}/`)||f.path===`${t}/SKILL.md`);if(p){let f=await oe(o,e,p.path,s);f&&i.push(f)}}else for(let p of d){let f=await oe(o,e,p.path,s);f&&i.push(f)}}catch(n){m.error(`Failed to fetch skills from GitHub: ${n.message}`)}return i}async function oe(o,e,t,s){try{let i=`https://raw.githubusercontent.com/${o}/${e}/main/${t}`,n=await ie(i);if(!n.ok)return null;let a=await n.text(),r=A(a);if(r){r.source="remote",r.packageId=`${o}/${e}`;let d=S.dirname(t),p=S.resolve(s,".skills",`${o}_${e}`,d);await X.mkdir(p,{recursive:!0}),await X.writeFile(S.resolve(p,"SKILL.md"),a),r.path=S.resolve(p,"SKILL.md")}return r}catch{return null}}async function ne(o,e){let t=[],{default:s}=await import("fast-glob"),i=await s.glob("**/SKILL.md",{cwd:o,deep:5});for(let n of i)try{let a=S.resolve(o,n),r=await X.readFile(a,"utf8"),d=A(r);d&&(d.source="remote",d.path=a,d.packageId=e,t.push(d))}catch{}return t}async function At(o,e,t,s,i){let n=["---",`name: ${o}`,`description: ${e}`];if(s?.length){n.push("tags:");for(let a of s)n.push(` - ${a}`)}if(i?.length){n.push("globs:");for(let a of i)n.push(` - ${a}`)}return n.push("---"),`${n.join(`
|
|
2
2
|
`)}
|
|
3
3
|
|
|
4
4
|
${t}`}async function It(o){let{loadLocalSkills:e}=await import("./loader-YFVSSYSL.js");return e(o)}async function Ot(o,e,t,s,i){let a=[{role:"system",content:`You are a skill author for the Agent Skills ecosystem (skills.sh).
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{a as xm,b as Mr,g as Sm}from"./chunk-IIFHX625.js";import{b as bm}from"./chunk-JC5KLH2K.js";import{A as gm,B as mm,D as Oa,E as um,G as pm,H as fm,I as hm,a as Jg,e as Ht,f as zg,g as Yg,h as Qg,i as Zg,k as em,l as tm,m as om,n as Ea,o as nm,t as sm,u as rm,v as Ca,w as im,z as dm}from"./chunk-QY5YUHGW.js";import{n as Ta}from"./chunk-B6PMIUVV.js";import{a as ym,b as wm,d as xs,e as $m}from"./chunk-XZIKAHFU.js";import{a as km,b as vm}from"./chunk-VDG6Y5L3.js";import"./chunk-H4NSB54D.js";import"./chunk-MTX4UPNB.js";import{d as Vg,e as Aa,h as Xg,i as Kg}from"./chunk-YXFTUOZR.js";import{a as am,c as cm,d as lm}from"./chunk-5NTWGUSJ.js";import"./chunk-QAWKN2N4.js";import"./chunk-B2QYMUTD.js";import{a as vt}from"./chunk-GIQKQD2B.js";import{a as Gg,c as Hg}from"./chunk-PLPDG6GE.js";import{a as xa}from"./chunk-LNY6N73E.js";import{a as ra,b as ud}from"./chunk-RKPAVWXF.js";import{$ as Pg,A as ug,B as ua,C as pg,D as fg,E as hg,F as yg,G as wg,H as $g,I as bg,J as kg,K as Og,L as Ir,M as no,N as wo,O as Tn,P as Nr,Q as jr,R as wa,S as Tg,T as Rg,U as Ig,V as Ng,W as jg,X as _g,Y as Rn,Z as In,_ as Dg,a as kd,aa as tt,b as vd,ba as _r,ca as vs,da as $a,ea as Mg,fa as qg,ga as va,h as yr,ha as Wg,i as Ed,ia as Dr,j as $s,ja as Nn,k as wr,ka as Pr,l as Od,la as Sa,m as Td,ma as Bg,n as Ud,o as Wd,p as Sr,q as da,r as Vd,s as og,t as ng,u as rg,v as ig,w as lg,x as dg,y as gg,z as mg}from"./chunk-
|
|
1
|
+
import{a as xm,b as Mr,g as Sm}from"./chunk-IIFHX625.js";import{b as bm}from"./chunk-JC5KLH2K.js";import{A as gm,B as mm,D as Oa,E as um,G as pm,H as fm,I as hm,a as Jg,e as Ht,f as zg,g as Yg,h as Qg,i as Zg,k as em,l as tm,m as om,n as Ea,o as nm,t as sm,u as rm,v as Ca,w as im,z as dm}from"./chunk-QY5YUHGW.js";import{n as Ta}from"./chunk-B6PMIUVV.js";import{a as ym,b as wm,d as xs,e as $m}from"./chunk-XZIKAHFU.js";import{a as km,b as vm}from"./chunk-VDG6Y5L3.js";import"./chunk-H4NSB54D.js";import"./chunk-MTX4UPNB.js";import{d as Vg,e as Aa,h as Xg,i as Kg}from"./chunk-YXFTUOZR.js";import{a as am,c as cm,d as lm}from"./chunk-5NTWGUSJ.js";import"./chunk-QAWKN2N4.js";import"./chunk-B2QYMUTD.js";import{a as vt}from"./chunk-GIQKQD2B.js";import{a as Gg,c as Hg}from"./chunk-PLPDG6GE.js";import{a as xa}from"./chunk-LNY6N73E.js";import{a as ra,b as ud}from"./chunk-RKPAVWXF.js";import{$ as Pg,A as ug,B as ua,C as pg,D as fg,E as hg,F as yg,G as wg,H as $g,I as bg,J as kg,K as Og,L as Ir,M as no,N as wo,O as Tn,P as Nr,Q as jr,R as wa,S as Tg,T as Rg,U as Ig,V as Ng,W as jg,X as _g,Y as Rn,Z as In,_ as Dg,a as kd,aa as tt,b as vd,ba as _r,ca as vs,da as $a,ea as Mg,fa as qg,ga as va,h as yr,ha as Wg,i as Ed,ia as Dr,j as $s,ja as Nn,k as wr,ka as Pr,l as Od,la as Sa,m as Td,ma as Bg,n as Ud,o as Wd,p as Sr,q as da,r as Vd,s as og,t as ng,u as rg,v as ig,w as lg,x as dg,y as gg,z as mg}from"./chunk-ID4DCD2T.js";import{$ as Tr,A as eg,D as tg,E as Or,I as sg,Oa as Lg,Pa as ba,Qa as Fg,Ra as ka,X as bs,_ as pa,aa as vg,db as Gt,fb as Ug,ha as yo,ia as xg,ja as fa,la as ha,na as Sg,oa as Ag,pa as ks,ta as Rr,wa as Eg,x as Zd,xa as Cg,ya,z as ma}from"./chunk-PNITKKDR.js";import"./chunk-GTJIKXDB.js";import{a as xd,c as aa,e as Sd,f as Ad}from"./chunk-Z2QOQBUS.js";import"./chunk-7LN4VKDU.js";import"./chunk-IMLAPB7G.js";import"./chunk-ZVP6GB4G.js";import{a as $r,b as br,c as Rd,d as Id,e as Nd,g as jd,h as _d,j as Dd,k as Pd,l as Md,m as Ld,p as kr,s as Fd,t as qd}from"./chunk-S2GAEH2F.js";import"./chunk-UNIXYV3U.js";import{a as Xd,b as Wo,c as Kd,d as Ar,e as Qd,f as ga,i as Er,j as Bo,k as Cr}from"./chunk-AKWBDCMU.js";import"./chunk-PPUN7YHA.js";import"./chunk-VZLIBDLH.js";import{a as ag,b as cg}from"./chunk-EKJG63EJ.js";import{b as kt}from"./chunk-EMGB23H5.js";import{c as Bd}from"./chunk-TEM7IIDC.js";import"./chunk-WCXXXAHV.js";import"./chunk-JWDJMFU6.js";import{b as la}from"./chunk-5HLD66CW.js";import"./chunk-IS2IWRP7.js";import{c as Jd,e as vr,g as xr}from"./chunk-LQE73AZR.js";import{a as Gd,b as Hd,c as zd,d as Yd}from"./chunk-2D3RYIDU.js";import"./chunk-MRIVENBY.js";import"./chunk-A5GYG56I.js";import{a as ca,p as Cd}from"./chunk-635B6SXI.js";import"./chunk-2AXFYDWF.js";import"./chunk-AQASR6KR.js";import{L as ho,R as cd,ca as dd,g as Bl,r as Jl,y as oa,z as Vl}from"./chunk-5ADXRVYT.js";import{F as id,U as ad,W as ld,b as xn,g as Sn,h as pr,i as fr,j as An,k as Xl,l as fo,n as qo}from"./chunk-3K3LRLZX.js";import{e as od,f as Uo,k as Cn}from"./chunk-QSKG754X.js";import{b as Fo}from"./chunk-MEFPXHQJ.js";import"./chunk-ZYUVKFX2.js";import"./chunk-SFQUP3BP.js";import{a as oo}from"./chunk-Y4CYQV7G.js";import{b as ws}from"./chunk-LLNPKE7H.js";import{a as gd,b as md}from"./chunk-SHEUCITK.js";import{a as ia,e as pd,f as fd,h as hd,i as yd,k as wd,l as $d,m as bd}from"./chunk-5RHCM3L7.js";import{c as Ql}from"./chunk-SZDACK6O.js";import{f as Zl,g as ed,h as na}from"./chunk-HF5EF335.js";import"./chunk-W4YR2RO2.js";import"./chunk-AFRHKA7B.js";import{a as zl,k as td}from"./chunk-23LWLYOK.js";import{a as hr,b as ge,c as We,d as On,e as nd}from"./chunk-BXXP6DTB.js";import{a as sd,b as sa,h as rd}from"./chunk-3UOTLMXD.js";import"./chunk-36AXN6ZB.js";import"./chunk-PE5V2LI7.js";import{a as ta,b as Hl}from"./chunk-GQ76J72O.js";import"./chunk-ILEC3IAV.js";import"./chunk-J3SOEFRE.js";import"./chunk-SIU6STB2.js";import{j as Yl}from"./chunk-X5C5W3E4.js";import{a as En}from"./chunk-GV7S5BAX.js";import{e as Kl}from"./chunk-GSX4NK2N.js";import"./chunk-VWTBHLMX.js";import{a as ea}from"./chunk-RWMDDMYX.js";import"./chunk-MXDWLBIA.js";import{c as Gl}from"./chunk-OWNQ2FD4.js";import"./chunk-KEXBEUSU.js";import"./chunk-ROLSSYXE.js";import"./chunk-PNYBZPPB.js";import"./chunk-SKSYII3K.js";import{g as po,i as ur}from"./chunk-E3AEOQMK.js";import"./chunk-WPNRDEHW.js";import"./chunk-MWNCS57L.js";import"./chunk-4LWFJ4MZ.js";import"./chunk-BIJHMWXK.js";import"./chunk-7JUISIE7.js";import{a as Vi,b as kl,c as ty,d as lr,e as vl,f as Dl,g as cy,h as Pl,i as ly,m as vn,n as Ll,q as Fl,u as ql,y as Jt,z as ys}from"./chunk-SQ37I2MD.js";import{a as to,b as kn,e as Nl,f as hs}from"./chunk-72SZI2UW.js";import"./chunk-FCPTN6NF.js";import{a as mr,b as Ml}from"./chunk-MCI3DIWW.js";import"./chunk-MYVHVJWZ.js";import{g as uo,i as Ul}from"./chunk-6TURWSMN.js";import{n as Wl}from"./chunk-AV7T3SYT.js";import"./chunk-Y5MFVVTM.js";import"./chunk-QD6IICK7.js";import"./chunk-QOYAX2FT.js";import"./chunk-JXF54PT7.js";import"./chunk-2PJKGZWP.js";import{a as Bt,b as ey}from"./chunk-7I7IEX4S.js";import"./chunk-JHCI6XCJ.js";import"./chunk-DSYKYZMT.js";import{Aa as le,D as Ol,E as sy,S as Tl,T as Ki,V as Qi,aa as ry,ba as Zi,da as Rl,ea as iy,h as xl,i as Sl,j as Al,k as El,l as Cl,m as oy,qa as Il,ra as dr,sa as O,ta as gr,v as Xi,w as ny}from"./chunk-KHYF6KTY.js";import"./chunk-BRK6M6LA.js";import{f as bt,n as fs}from"./chunk-UIBUZ2N3.js";import"./chunk-2QNT5JDS.js";import{g as jl,k as _l,l as ay}from"./chunk-RGDKHYY2.js";import"./chunk-QIJLFPWE.js";import{Command as dy}from"commander";var Am=new dy().name("a2a").description("start an A2A (Agent-to-Agent) protocol server for external agent integration").option("--port <port>","server port","3171").option("--host <host>","server host","0.0.0.0").option("-p, --provider <provider>","AI provider","claude-code").option("-m, --model <model>","model to use").option("--api-key <key>","API key for the provider").option("-c, --cwd <cwd>","working directory",process.cwd()).option("--no-cors","disable CORS headers").action(async e=>{await new vd({port:parseInt(e.port,10),host:e.host,provider:e.provider,model:e.model,apiKey:e.apiKey,cwd:e.cwd,cors:e.cors!==!1}).start()});le();import{Command as gy}from"commander";import{execFileSync as Tt}from"node:child_process";import{cpSync as Em,existsSync as Na,mkdirSync as Ra,mkdtempSync as my,readdirSync as uy,rmSync as Cm,writeFileSync as Om}from"node:fs";import{dirname as Tm,join as ot}from"node:path";import{homedir as Lr,release as py,tmpdir as fy}from"node:os";import{setTimeout as hy}from"node:timers/promises";function Fr(){let e=Rd(process.platform,process.arch,py());if(e)throw new Error(e)}function qr(){let e=ot(Lr(),"Library/LaunchAgents"),t=[];try{t=uy(e).filter(jd)}catch{}let o=t.flatMap(n=>{let s=ot(e,n);try{return[{file:s,plist:Tt("plutil",["-convert","xml1","-o","-",s],{encoding:"utf8",stdio:["ignore","pipe","ignore"]})}]}catch{return[]}});return _d(e,o)}function Ss(e){return`gui/${process.getuid()}/${e.label}`}async function Ia(e){try{Tt("launchctl",["bootout",Ss(e)],{stdio:"pipe"})}catch{}for(let t=0;t<30;t++){try{Tt("launchctl",["print",Ss(e)],{stdio:"pipe"})}catch{return}await hy(200)}throw new Error("The desktop login service did not stop. Retry agentx desktop stop before installing.")}function Rm(e){if(!Na(e.file))throw new Error("Run agentx desktop install first.");let t=!0;try{Tt("launchctl",["print",Ss(e)],{stdio:"pipe"})}catch{t=!1}t||Tt("launchctl",["bootstrap",`gui/${process.getuid()}`,e.file],{stdio:"pipe"}),Tt("launchctl",["kickstart",Ss(e)],{stdio:"pipe"})}function yy(){let e=Ld();if(e&&Na(ot(e,"apps/mac-voice/build.sh")))return e;throw new Error("Desktop sources are missing from this installation. Update AgentX to a release containing apps/mac-voice and apps/mac-helper.")}var jn=new gy("desktop").description("install and control the macOS desktop assistant");jn.command("install").description("build, install, and start voice and computer-use helpers at login").option("--agent <id>","pin this agent; without it, pick the agent from the menu-bar icon").option("--dry-run","show the installation plan without building or changing login items").action(async e=>{Fr();let t=O(),o=Id(t.agents,e.agent),n=e.agent,s=t.dashboard?.daemonUrl||`http://${t.node.bind.replace(/^0\.0\.0\.0:/,"127.0.0.1:")}`,r=yy(),i=ot(Lr(),"Applications"),{keep:a,remove:c}=qr();if(console.log(`Desktop assistant \u2192 ${ot(i,$r)}
|
|
2
2
|
Computer-use helper \u2192 ${ot(i,br)}
|
|
3
3
|
Agent \u2192 ${n?`${o} (pinned)`:"picked from the menu-bar icon"}
|
|
4
4
|
Daemon \u2192 ${s}
|
|
@@ -493,7 +493,7 @@ return aid ?? null`}function Zb(e,t){if(e.kind==="path"){let s=e.pts,r=s[Math.fl
|
|
|
493
493
|
Plan ${V.dim(`[${s.state}] by ${s.createdBy}, ${Xn(s.createdAt)}`)}`),df(n.steps(e)),console.log(`
|
|
494
494
|
Log`);for(let r of n.events(e))console.log(V.dim(` ${Xn(r.at)} ${r.step?`step ${r.step} `:""}${r.kind}: ${r.detail}`))}});gn.command("step <id> <step>").description("move a step of a request's plan on: --retry, --skip or --done").option("--retry","hand the step to its agent again, with a fresh count of nudges (approves a message whose card expired)").option("--skip","leave the step out; the plan goes on with the next one").option("--done","mark the step done yourself").option("--note <text>","why, or the evidence for --done").action((e,t,o)=>{let n=["retry","skip","done"].filter(c=>o[c]);if(n.length!==1){console.error(V.red(" give one of --retry, --skip or --done")),process.exitCode=1;return}let s=vi();if(!s)return;let r=lf();if(!r){process.exitCode=1;return}let i=Number(t),a=ng(s,r,{requestId:e,step:i,action:n[0],detail:o.note},Date.now());if(a){console.error(V.red(` ${a}`)),process.exitCode=1;return}console.log(V.green(` \u2713 step ${i} of ${e}: ${n[0]==="retry"?"handed over again":n[0]==="skip"?"skipped":"done"}`)+V.dim(" (the daemon goes on within a minute)")),df(r.steps(e))});function mf(e,t,o,n){gn.command(`${e} <id>`).description(t).option(o,e==="done"?"link to the proof: PR, issue, message, deploy":"why it is dropped").action((s,r)=>{let i=vi();if(!i)return;let a=e==="done"?r.evidence:r.reason??"dropped by the owner";if(!a){console.error(V.red(` ${n}`)),process.exitCode=1;return}let c=i.get(s);if(!c||c.state==="candidate"){console.error(V.red(` no request "${s}"`)),process.exitCode=1;return}if(!i.close(s,e==="done"?"done":"dropped",a,Date.now())){console.error(V.red(` ${s} is already closed (${cf[c.state]??c.state})`)),process.exitCode=1;return}console.log(V.green(` \u2713 ${s} ${e==="done"?"closed as done":"dropped"}`))})}mf("done","close a request as finished, with a link to the evidence","--evidence <link>","a finished request needs --evidence <link>");mf("drop","drop a request you no longer want","--reason <text>","");gn.command("settings").description("show or change the requests settings").option("--enabled <on|off>","record and follow your requests").option("--from <id,...>",'who counts as you on channels other people can reach, as channel:id (your login on GitLab and GitHub, your sender id elsewhere); "none" to clear').option("--channels <name,...>",'channels to record on; "all" for every channel a person writes on').option("--stale-hours <n>","hours without activity before an open request comes back to you").option("--retention-days <n>","days a closed request is kept").option("--plans <on|off>","follow requests of two or more steps as tracked plans").option("--stall-minutes <n>","minutes without progress before a plan step's agent is nudged").option("--max-nudges <n>","nudges per step before it counts as blocked and you are told").option("--approve-kinds <kind,...>",'step kinds you approve once when the plan is made; "none" to clear').option("--plans-off-for <agent,...>",'agents that may not open a plan; "none" to clear').action(async e=>{let t={};try{let s=(a,c)=>{if(c===void 0)return;let l=Number(c);if(!(l>0))throw new Error(`${a} must be a positive number`);return l},r=(a,c)=>a===void 0?void 0:a===c?[]:a.split(",").map(l=>l.trim()).filter(Boolean);if(e.enabled!==void 0){if(e.enabled!=="on"&&e.enabled!=="off")throw new Error("--enabled takes on or off");t.enabled=e.enabled==="on"}t.from=r(e.from,"none"),t.channels=r(e.channels,"all"),t.staleAfterHours=s("--stale-hours",e.staleHours),t.retentionDays=s("--retention-days",e.retentionDays);let i={};if(e.plans!==void 0){if(e.plans!=="on"&&e.plans!=="off")throw new Error("--plans takes on or off");i.enabled=e.plans==="on"}if(i.stallMinutes=s("--stall-minutes",e.stallMinutes),e.maxNudges!==void 0){let a=Number(e.maxNudges);if(!Number.isInteger(a)||a<0)throw new Error("--max-nudges must be a whole number, 0 or more");i.maxNudges=a}i.approveKinds=r(e.approveKinds,"none"),i.disabledAgents=r(e.plansOffFor,"none"),Object.values(i).some(a=>a!==void 0)&&(t.plans=i)}catch(s){console.error(V.red(` ${s.message}`)),process.exitCode=1;return}if(Object.values(t).some(s=>s!==void 0)){let s=await eg(t);if(!s.success){console.error(V.red(` ${s.error}`)),process.exitCode=1;return}console.log(V.green(" \u2713 saved")+(s.reloaded?V.dim(" (daemon reloaded)"):""))}let o=ma();console.log(` Requests ${o.enabled?"on":"off"}`),console.log(` Channels ${o.channels.length?o.channels.join(", "):"every channel a person writes on"}`),console.log(` Counts as you ${o.from.length?o.from.join(", "):"only voice, the app and the dashboard on this machine"}`),console.log(` Comes back after ${o.staleAfterHours} hour(s) without activity`),console.log(` Closed requests kept ${o.retentionDays} day(s)`);let n=o.plans??og;console.log(` Plans ${n.enabled?"on":"off"}`),console.log(` Nudge a quiet step after ${n.stallMinutes} minute(s), up to ${n.maxNudges} time(s)`),console.log(` You approve up front ${n.approveKinds.length?n.approveKinds.join(", "):"nothing"}`),console.log(` Plans off for ${n.disabledAgents.length?n.disabledAgents.join(", "):"no agent"}`)});import{Command as Sv}from"commander";import Kn from"chalk";le();var uf=new Sv("request-status").description("show each person the state of their request in the thread where they asked (GitLab, GitHub)").argument("[channel]",Ca.join(" or ")).argument("[state]","on or off").action(async(e,t)=>{if(e!==void 0){t!=="on"&&t!=="off"&&(console.error(Kn.red("\u2717 say on or off, for example: agentx request-status gitlab on")),process.exit(1));let n="",s=await ge(r=>{n=im(r,e,t==="on")});s.success||(console.error(Kn.red(`\u2717 ${s.error}`)),process.exit(1)),console.log(Kn.green(`\u2713 ${n}`)),s.reloaded&&console.log(Kn.dim(" daemon hot-reloaded"))}let o=O().requestStatus.channels.map(n=>n.toLowerCase());for(let n of Ca)console.log(` ${n.padEnd(8)} ${o.includes(n)?Kn.green("on"):Kn.dim("off")}`)});import{Command as zv}from"commander";import Qn from"chalk";import ye from"chalk";import zs from"prompts";import{readFileSync as Av,writeFileSync as Ev,existsSync as Cv}from"fs";import{resolve as pf}from"path";var Ov=/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/;function Tv(e){let t=e.trim();return t.startsWith('"')&&t.endsWith('"')||t.startsWith("'")&&t.endsWith("'")?t.slice(1,-1):t}function jc(e){return/[\s#"'\\]/.test(e)||e===""?JSON.stringify(e):e}function ff(e=".env"){let t=pf(e);if(!Cv(t))return[];let o=Av(t,"utf-8"),n=[];for(let s of o.split(/\r?\n/)){if(s.trim()===""){n.push({kind:"blank",raw:s});continue}if(s.trim().startsWith("#")){n.push({kind:"comment",raw:s});continue}let r=s.match(Ov);r?n.push({kind:"kv",key:r[1],value:Tv(r[2]),raw:s}):n.push({kind:"comment",raw:s})}return n.length&&n[n.length-1].kind==="blank"&&n.pop(),n}function Rv(e){return e.map(t=>t.kind==="kv"?`${t.key}=${jc(t.value)}`:t.raw).join(`
|
|
495
495
|
`)+`
|
|
496
|
-
`}function Hs(e,t,o=".env"){let n=pf(o),s=ff(n),r=s.findIndex(i=>i.kind==="kv"&&i.key===e);r>=0?s[r]={kind:"kv",key:e,value:t,raw:`${e}=${jc(t)}`}:(s.length&&s[s.length-1].kind!=="blank"&&s.push({kind:"blank",raw:""}),s.push({kind:"kv",key:e,value:t,raw:`${e}=${jc(t)}`})),Ev(n,Rv(s))}function _c(e,t=".env"){return ff(t).find(s=>s.kind==="kv"&&s.key===e)?.value}var hf="https://t.me/BotFather";async function Iv(e){try{let t=await fetch(`https://api.telegram.org/bot${e}/getMe`,{signal:AbortSignal.timeout(8e3)});if(!t.ok)return null;let o=await t.json();return o?.ok?o.result:null}catch{return null}}async function Nv(e,t){let o=Date.now()+t,n=0;try{let r=await(await fetch(`https://api.telegram.org/bot${e}/getUpdates?offset=-1&timeout=0`,{signal:AbortSignal.timeout(5e3)})).json();r?.result?.length&&(n=r.result[0].update_id+1)}catch{}for(;Date.now()<o;)try{let r=await(await fetch(`https://api.telegram.org/bot${e}/getUpdates?offset=${n}&timeout=10`,{signal:AbortSignal.timeout(15e3)})).json();for(let i of r?.result||[]){n=Math.max(n,i.update_id+1);let a=i.message||i.edited_message||i.channel_post;if(a?.chat?.id)return{chatId:a.chat.id,chatTitle:a.chat.title||a.chat.username||a.chat.first_name,from:a.from?.first_name||a.from?.username||"unknown"}}}catch{}return null}async function yf(e={}){console.log(),console.log(ye.bold(" Connect Telegram")),console.log(),console.log(ye.dim(` 1. Open ${hf}`)),console.log(ye.dim(" /newbot (or /mybots \u2192 pick existing \u2192 API Token)")),console.log(ye.dim(" 2. Copy the HTTP API token \u2014 looks like 123456789:ABCdef...")),console.log();let{wantsBrowser:t}=await zs({type:"confirm",name:"wantsBrowser",message:"Open BotFather in your browser?",initial:!0});t&&Ho(hf);let{token:o}=await zs({type:"password",name:"token",message:"Paste the bot token from BotFather:",validate:b=>/^\d+:[A-Za-z0-9_-]{20,}$/.test(b.trim())||"That doesn't look like a Telegram token"});o||(console.log(ye.red(" Aborted")),process.exit(1)),process.stdout.write(" Verifying token... ");let n=await Iv(o.trim());n||(console.log(ye.red("\u2717 invalid or revoked")),process.exit(1)),console.log(ye.green(`\u2713 ${n.first_name} (@${n.username})`));let{readFileSync:s,existsSync:r}=await import("fs"),{resolve:i}=await import("path"),a=e.configPath||i(process.cwd(),"agentx.json");r(a)||(console.log(ye.red(` No agentx.json at ${a}. Run: agentx init`)),process.exit(1));let c=JSON.parse(s(a,"utf-8")),l=Object.keys(c.agents||{});l.length===0&&(console.log(ye.red(" No agents configured. Run: agentx agent add")),process.exit(1));let g=e.agent;g||(g=(await zs({type:"select",name:"agent",message:"Bind this bot to which agent?",choices:l.map(v=>({title:v,value:v})),initial:0})).agent),g||(console.log(ye.red(" Aborted")),process.exit(1));let d=c.channels?.telegram?.accounts||{},f=e.account||g;d[f]&&d[f].agentBinding!==g&&(f=(await zs({type:"text",name:"account",message:`Account "${f}" is already in use. Pick a different label:`,initial:`${g}-2`,validate:v=>/^[a-z0-9-]+$/i.test(v)||"lowercase letters, digits, hyphens only"})).account),f||(console.log(ye.red(" Aborted")),process.exit(1));let p=`TG_${f.toUpperCase().replace(/-/g,"_")}_BOT_TOKEN`;Hs(p,o.trim()),process.env[p]=o.trim();let u=await ge(b=>{We(b,"channels.telegram.enabled",!0),We(b,`channels.telegram.accounts.${f}`,{token:`\${${p}}`,agentBinding:g});let v=On(b,`agents.${g}.mentions`)||[],A=`@${n.username}`;v.includes(A)||(v.push(A),We(b,`agents.${g}.mentions`,v))},{configPath:e.configPath});if(u.success||(console.log(ye.red(` \u2717 ${u.error}`)),process.exit(1)),console.log(),console.log(ye.green(` \u2713 Bot @${n.username} bound to agent "${g}"`)),console.log(ye.dim(` Token stored in .env as ${p}`)),console.log(ye.dim(` Mentions updated to include @${n.username}`)),u.reloaded?console.log(ye.dim(" Daemon hot-reloaded.")):console.log(ye.dim(" Start the daemon to begin polling (agentx daemon start)")),e.skipChatCapture)return;console.log();let{capture:h}=await zs({type:"confirm",name:"capture",message:"Listen for a message so we can set this as your default --notify target?",initial:!0});if(!h)return;console.log(),console.log(ye.dim(` Send any message to @${n.username} from the chat you want to use.`)),console.log(ye.dim(" Waiting up to 90s..."));let y=await Nv(o.trim(),9e4);if(!y){console.log(ye.yellow(" No message received \u2014 skipping. You can set it later with:")),console.log(ye.dim(" agentx config set notifications.destination.channel telegram")),console.log(ye.dim(" agentx config set notifications.destination.chatId <chatId>")),console.log(ye.dim(` agentx config set notifications.destination.accountId ${f}`));return}let $=await ge(b=>{We(b,"notifications.destination",{channel:"telegram",chatId:String(y.chatId),accountId:f})},{configPath:e.configPath});if(!$.success){console.log(ye.red(` \u2717 failed to save destination: ${$.error}`));return}console.log(ye.green(" \u2713 Default --notify target set")),console.log(ye.dim(` Chat: ${y.chatTitle||y.chatId} (from ${y.from})`)),console.log(ye.dim(" From now on, --notify me resolves to this chat.")),$.reloaded&&console.log(ye.dim(" Daemon hot-reloaded."))}import Ce from"chalk";import Dc from"prompts";import{existsSync as Mc,mkdirSync as jv,readdirSync as _v,readFileSync as wf}from"fs";import{resolve as Pc}from"path";var Dv=".agentx/whatsapp-sessions";function Pv(){let e=Pc(process.cwd(),".agentx/daemon.pid");if(!Mc(e))return!1;try{let t=parseInt(wf(e,"utf-8").trim(),10);return t?(process.kill(t,0),!0):!1}catch{return!1}}function Mv(e){if(!Mc(e))return!1;try{return _v(e).some(t=>t.includes("creds")||t.endsWith(".json"))}catch{return!1}}async function Lv(e,t){let o;try{o=await import("@whiskeysockets/baileys")}catch{return t(Ce.red(" WhatsApp pairing needs @whiskeysockets/baileys.")),t(Ce.red(" Install with: pnpm add @whiskeysockets/baileys (or npm install)")),!1}let{makeWASocket:n,useMultiFileAuthState:s}=o.default||o;jv(e,{recursive:!0});let{state:r,saveCreds:i}=await s(e),a;try{a=(await o.fetchLatestBaileysVersion()).version}catch{}let c={level:"silent",trace:()=>{},debug:()=>{},info:()=>{},warn:()=>{},fatal:()=>{},error:()=>{},child:()=>c},l=n({auth:{creds:r.creds,keys:o.makeCacheableSignalKeyStore?o.makeCacheableSignalKeyStore(r.keys,c):r.keys},...a?{version:a}:{},logger:c,printQRInTerminal:!1,browser:["agentx-connect","pairing","1.0"],syncFullHistory:!1,markOnlineOnConnect:!1});return l.ev.on("creds.update",i),await new Promise(g=>{let f=setTimeout(()=>{t(Ce.yellow(" Timed out waiting for scan (2min)."));try{l.end(void 0)}catch{}g(!1)},12e4);l.ev.on("connection.update",async p=>{if(p.qr){t(Ce.dim(" Scan this QR with WhatsApp \u2192 Linked devices \u2192 Link a device:")),t("");try{let{default:u}=await import("qrcode-terminal");u.generate(p.qr,{small:!0})}catch{t(` QR: ${p.qr}`)}t("")}if(p.connection==="open"){clearTimeout(f),t(Ce.green(" \u2713 Paired"));try{l.end(void 0)}catch{}g(!0)}p.connection==="close"&&!p.qr&&p.lastDisconnect?.error?.output?.statusCode===401&&(clearTimeout(f),t(Ce.red(" \u2717 Authorization failed \u2014 rescan required")),g(!1))})})}async function $f(e={}){if(console.log(),console.log(Ce.bold(" Connect WhatsApp")),console.log(),Pv()){console.log(Ce.yellow(" \u26A0 The daemon is running. Stop it first so pairing can open a fresh Baileys session:")),console.log(Ce.dim(" agentx daemon stop")),console.log();let{cont:l}=await Dc({type:"confirm",name:"cont",message:"Continue anyway? (expect errors)",initial:!1});l||(console.log(Ce.red(" Aborted")),process.exit(1))}let t=e.configPath||Pc(process.cwd(),"agentx.json");Mc(t)||(console.log(Ce.red(` No agentx.json at ${t}. Run: agentx init`)),process.exit(1));let o=JSON.parse(wf(t,"utf-8")),n=e.sessionDir||o.channels?.whatsapp?.sessionDir||Dv,s=Pc(process.cwd(),n),r=Object.keys(o.agents||{});r.length===0&&(console.log(Ce.red(" No agents configured. Run: agentx agent add")),process.exit(1));let i=e.agent;i||(i=(await Dc({type:"select",name:"agent",message:"Default agent for incoming messages:",choices:r.map(g=>({title:g,value:g})),initial:0})).agent),i||(console.log(Ce.red(" Aborted")),process.exit(1));let a=!e.skipPair;if(Mv(s)&&a){let{keep:l}=await Dc({type:"confirm",name:"keep",message:`An existing session was found at ${n}. Keep it (recommended), or pair again?`,initial:!0});a=!l,l&&console.log(Ce.dim(" Keeping existing session \u2014 skipping QR pair."))}a&&(await Lv(s,g=>console.log(g))||(console.log(Ce.red(" Pairing failed. You can retry with `agentx connect whatsapp`.")),process.exit(1)));let c=await ge(l=>{We(l,"channels.whatsapp.enabled",!0),We(l,"channels.whatsapp.sessionDir",n),We(l,"channels.whatsapp.defaultAgent",i)},{configPath:e.configPath});c.success||(console.log(Ce.red(` \u2717 ${c.error}`)),process.exit(1)),console.log(),console.log(Ce.green(` \u2713 WhatsApp bound to agent "${i}"`)),console.log(Ce.dim(` Session: ${n}`)),console.log(Ce.dim(" Start the daemon to begin receiving:")),console.log(Ce.dim(" agentx daemon start")),console.log(Ce.yellow(" No chat is answered until you list the allowed numbers (add your own to talk from your self-chat):")),console.log(Ce.dim(` agentx config set channels.whatsapp.allowFrom '["+15550001111"]'`)),console.log(Ce.dim(" Add routes later with:")),console.log(Ce.dim(` agentx config set channels.whatsapp.routes '[{"contact":"+1234567890","agent":"${i}"}]'`)),console.log()}import{randomBytes as Fv}from"crypto";import Fe from"chalk";import bf from"prompts";var xi="agentx-mesh:",Si="//join/";function qv(e){return Buffer.from(e,"utf-8").toString("base64url")}function Uv(e){return Buffer.from(e,"base64url").toString("utf-8")}function Wv(e){return`${xi}${Si}${qv(JSON.stringify(e))}`}function Bv(e){if(!e.startsWith(`${xi}${Si}`))throw new Error(`Not a mesh invite link \u2014 expected ${xi}${Si}\u2026`);let t=e.slice(xi.length+Si.length),o=JSON.parse(Uv(t));if(o.version!==1)throw new Error(`Unsupported invite version: ${o.version}`);if(typeof o.url!="string"||typeof o.token!="string"||typeof o.name!="string")throw new Error("Invite payload missing required fields (url, token, name)");return o}function Jv(){return Fv(32).toString("hex")}function Gv(e){let t=e?.node?.bind||"127.0.0.1:18800",[o,n]=t.split(":");return`http://${o}:${n}`}function Hv(e){return e?.node?.name||e?.node?.id||"node"}async function kf(e){let{readFileSync:t,existsSync:o}=await import("fs"),{resolve:n}=await import("path"),s=e.configPath||n(process.cwd(),"agentx.json");o(s)||(console.log(Fe.red(` No agentx.json at ${s}. Run: agentx init`)),process.exit(1));let r=JSON.parse(t(s,"utf-8")),i=_c("MESH_TOKEN");i||(i=Jv(),Hs("MESH_TOKEN",i),console.log(Fe.dim(" Generated new MESH_TOKEN (32 bytes) and saved to .env")));let a=e.url||Gv(r);if((a.includes("0.0.0.0")||a.includes("127.0.0.1")||a.includes("localhost"))&&!e.url){let{url:d}=await bf({type:"text",name:"url",message:"This node's URL as peers should reach it (e.g. http://100.x.x.x:18800):",initial:a,validate:f=>/^https?:\/\//.test(f)||"must start with http:// or https://"});d||(console.log(Fe.red(" Aborted")),process.exit(1)),a=d}let l=await ge(d=>{We(d,"mesh.enabled",!0)},{configPath:e.configPath});l.success||(console.log(Fe.red(` \u2717 ${l.error}`)),process.exit(1));let g=Wv({url:a,token:i,name:Hv(r),version:1});console.log(),console.log(Fe.bold(" Mesh invite")),console.log(),console.log(` ${Fe.cyan(g)}`),console.log(),console.log(Fe.dim(" Run on the other node:")),console.log(Fe.dim(` agentx connect mesh join "${g}"`)),console.log(),console.log(Fe.yellow(" Share over a trusted channel only \u2014 whoever holds this link can join your mesh.")),console.log()}async function vf(e,t={}){let o;try{o=Bv(e)}catch(r){console.log(Fe.red(` \u2717 ${r.message}`)),process.exit(1)}let n=_c("MESH_TOKEN");if(n&&n!==o.token){let{overwrite:r}=await bf({type:"confirm",name:"overwrite",message:"A different MESH_TOKEN already exists in .env. Overwrite with the invite's token?",initial:!1});r||(console.log(Fe.red(" Aborted (keeping existing .env)")),process.exit(1))}Hs("MESH_TOKEN",o.token);let s=await ge(r=>{We(r,"mesh.enabled",!0);let i=On(r,"mesh.peers")||[],a=i.findIndex(l=>l.name===o.name),c={name:o.name,url:o.url,token:"${MESH_TOKEN}"};a>=0?i[a]=c:i.push(c),We(r,"mesh.peers",i)},{configPath:t.configPath});s.success||(console.log(Fe.red(` \u2717 ${s.error}`)),process.exit(1)),console.log(Fe.green(" \u2713 Joined mesh")),console.log(Fe.dim(` Peer: ${o.name} @ ${o.url}`)),console.log(Fe.dim(" Shared token stored in .env as MESH_TOKEN")),s.reloaded?console.log(Fe.dim(" Daemon hot-reloaded.")):console.log(Fe.dim(" Restart the daemon to start the mesh (agentx daemon start).")),console.log();try{let r=await fetch(`${o.url.replace(/\/$/,"")}/.well-known/agent-card.json`,{signal:AbortSignal.timeout(5e3)});if(r.ok){let i=await r.json(),a=Array.isArray(i.agents)?i.agents.length:"?";console.log(Fe.dim(` Reachable \u2014 ${o.name} exposes ${a} agents`))}else console.log(Fe.yellow(` Peer returned HTTP ${r.status} \u2014 double-check the URL`))}catch(r){console.log(Fe.yellow(` Could not reach ${o.url} right now (${r?.cause?.code||r.message}) \u2014 verify VPN / Tailscale when the peer daemon is up`))}}var Zn=new zv().name("connect").description("connect a channel or mesh peer \u2014 no manual file editing required");Zn.command("telegram").description("pair a Telegram bot \u2014 persists token in .env, binds to an agent, auto-detects default chat").option("--agent <id>","agent to bind the bot to (skips the prompt)").option("--account <name>","account label in channels.telegram.accounts (defaults to agent id)").option("--skip-chat-capture","don't listen for the first message after pairing").option("-c, --config <path>","path to agentx.json").action(async e=>{await yf({agent:e.agent,account:e.account,configPath:e.config,skipChatCapture:!!e.skipChatCapture})});Zn.command("whatsapp").description("pair WhatsApp via QR (prints in terminal), then writes channels.whatsapp config").option("--agent <id>","default agent for inbound messages").option("--session-dir <path>","session directory (default: .agentx/whatsapp-sessions)").option("--skip-pair","skip QR pairing (use when session already exists)").option("-c, --config <path>","path to agentx.json").action(async e=>{await $f({agent:e.agent,sessionDir:e.sessionDir,skipPair:!!e.skipPair,configPath:e.config})});var xf=Zn.command("mesh").description("mesh invite + join \u2014 replaces manual MESH_TOKEN copy-paste");xf.command("invite").description("emit a single-use join link for another AgentX node").option("--url <url>","this node's URL as peers should reach it (Tailscale IP etc.)").option("-c, --config <path>","path to agentx.json").action(async e=>{await kf({url:e.url,configPath:e.config})});xf.command("join <link>").description("accept an invite link from another AgentX node").option("-c, --config <path>","path to agentx.json").action(async(e,t)=>{await vf(e,{configPath:t.config})});Zn.action(()=>{console.log(),console.log(Qn.bold(" agentx connect \u2014 which channel?")),console.log(),console.log(` ${Qn.cyan("agentx connect telegram")} \u2014 pair a Telegram bot`),console.log(` ${Qn.cyan("agentx connect whatsapp")} \u2014 pair WhatsApp via QR`),console.log(` ${Qn.cyan("agentx connect mesh invite")} \u2014 emit a mesh join link for another node`),console.log(` ${Qn.cyan("agentx connect mesh join <link>")} \u2014 accept a mesh invite`),console.log(),console.log(Qn.dim(" GitLab connect is the next slice.")),console.log()});le();import{Command as Yv}from"commander";import te from"chalk";var mn=new Yv().name("usage").description("token usage analysis and reporting");mn.command("today",{isDefault:!0}).description("show today's token usage").action(async()=>{try{let e=O(),[t,o]=e.node.bind.split(":"),s=await(await fetch(`http://${t||"127.0.0.1"}:${o}/usage`,{signal:AbortSignal.timeout(3e3)})).json();if(console.log(),console.log(te.bold(" Token Usage (last 7 days)")),console.log(),s.totalTasks===0){console.log(te.dim(" No tasks recorded yet")),console.log();return}let r=a=>a.toLocaleString();console.log(` Total: ${te.bold(r(s.totalTokens))} tokens across ${s.totalTasks} tasks`),console.log(` Input: ${r(s.totalInput)}`),console.log(` Output: ${r(s.totalOutput)}`),console.log(` Cache read: ${r(s.totalCacheRead)}`),console.log(` Cache create: ${r(s.totalCacheCreate)}`),s.cacheHitRatio>0&&console.log(` Cache hit: ${(s.cacheHitRatio*100).toFixed(1)}%`),s.totalErrors>0&&console.log(` Errors: ${te.red(String(s.totalErrors))}`),console.log(),console.log(te.bold(" By Agent:"));let i=Object.entries(s.byAgent||{});i.sort((a,c)=>c[1].total-a[1].total);for(let[a,c]of i){let l=r(c.total||c.input+c.output+c.cacheRead+c.cacheCreate),g=c.avgDuration?`${(c.avgDuration/1e3).toFixed(1)}s avg`:"";console.log(` ${te.cyan(a)}: ${l} tokens (${c.tasks} tasks) ${te.dim(g)}`)}console.log()}catch(e){e.cause?.code==="ECONNREFUSED"?console.log(te.red(" Daemon not running. Start with: agentx daemon start")):console.log(te.red(` ${e.message}`))}});mn.command("plan").description("show the Claude plan windows as Claude Code last reported them, and any hold on fresh sessions").option("--lift","lift the hold on fresh sessions now, without waiting for the reset time").option("--json","raw JSON output").action(async e=>{try{let t=O(),[o,n]=t.node.bind.split(":"),s=`http://${o||"127.0.0.1"}:${n}`,r=e.lift?await fetch(`${s}/usage/plan/lift`,{method:"POST",signal:AbortSignal.timeout(3e3)}):await fetch(`${s}/usage/plan`,{signal:AbortSignal.timeout(3e3)});if(!r.ok)throw new Error(`Daemon answered ${r.status}. It may be older than this command; restart it.`);let i=await r.json();if(e.json){console.log(JSON.stringify(i,null,2));return}let a=d=>d.replace(/_/g," ");if(console.log(),e.lift){let d=i.lifted.map(f=>a(f.window));console.log(d.length?` Lifted the hold on: ${d.join(", ")}. The next fresh session asks Claude again.`:te.dim(" No hold to lift.")),console.log();return}let c=i.now,l=new Set(i.holds.map(d=>d.window));console.log(te.bold(" Claude plan (as Claude Code last reported it)")),console.log(),i.provider.length===0&&console.log(te.dim(" No report yet. Claude Code reports on each turn; the list is empty after a daemon restart."));for(let d of i.provider){let f=d.usingOverage?"extra usage":d.status==="allowed_warning"?"nearly full":d.status,p=typeof d.utilization=="number"?`${Math.round(d.utilization*100)}% used`:"",u=Math.max(1,Math.round((d.resetsAt-c)/6e4)),h=d.resetsAt>c?u<60?`resets in ${u} min`:u<48*60?`resets in ${Math.round(u/60)} h`:`resets in ${Math.round(u/1440)} days`:"",y=l.has(d.window)?te.red("holding fresh sessions"):"";console.log(` ${a(d.window).padEnd(18)} ${f.padEnd(12)} ${[p,h,y].filter(Boolean).join(", ")}`)}console.log();let g=d=>d?` of ${d}`:"";console.log(` Fresh sessions started: ${i.lastHour}${g(i.maxPerHour)} in the last hour, ${i.last5h}${g(i.maxPer5h)} in the last 5 hours`),l.size&&console.log(te.dim(" To try again now: agentx usage plan --lift")),console.log()}catch(t){t.cause?.code==="ECONNREFUSED"?console.log(te.red(" Daemon not running. Start with: agentx daemon start")):console.log(te.red(` ${t.message}`))}});mn.command("report").description("run full session analysis (parses Claude Code JSONL files)").option("--days <n>","analyze last N days","7").action(async e=>{let{execSync:t}=await import("child_process"),{existsSync:o}=await import("fs"),{resolve:n}=await import("path"),s=n(process.cwd(),"scripts/token-report.py");if(!o(s)){console.log(te.red(` Script not found: ${s}`)),console.log(te.dim(" The token analyzer script should be at scripts/token-report.py"));return}console.log(te.dim(" Analyzing Claude Code sessions..."));try{t(`SINCE_DAYS=${e.days} python3 "${s}"`,{stdio:"inherit"})}catch{console.log(te.red(" Analysis failed. Is Python 3 installed?"))}});mn.command("channels").description("cost per channel over a fixed range of days, next to a saved baseline").requiredOption("--from <date>","first day, YYYY-MM-DD").requiredOption("--to <date>","last day, YYYY-MM-DD").option("--save <file>","write this range's figures to a JSON file").option("--baseline <file>","a file written by --save, shown beside this range").option("--json","raw JSON output").action(async e=>{let{readFileSync:t,writeFileSync:o}=await import("fs"),{TokenTracker:n}=await import("./token-tracker-W7DFDEFN.js"),{channelCosts:s,costPerTask:r}=await import("./usage-channels-KBLEC4NE.js"),i=u=>/^\d{4}-\d{2}-\d{2}$/.test(u)&&!Number.isNaN(Date.parse(u));if(!i(e.from)||!i(e.to)||e.from>e.to){console.log(te.red(" --from and --to must be YYYY-MM-DD, with --from not after --to")),process.exitCode=1;return}let a=s(new n,e.from,e.to);e.save&&o(e.save,JSON.stringify(a,null,2));let c=null;if(e.baseline)try{c=JSON.parse(t(e.baseline,"utf-8"))}catch(u){console.log(te.red(` Cannot read baseline ${e.baseline}: ${u.message}`)),process.exitCode=1;return}if(e.json){console.log(JSON.stringify(c?{baseline:c,current:a}:a,null,2));return}let l=u=>u===null?"-":`$${u.toFixed(u<10?3:2)}`,g=u=>`${(u/1e6).toFixed(1)}M`;console.log(),console.log(te.bold(` Cost per channel, ${a.from} to ${a.to} (${a.days} day(s) with data)`)),c&&console.log(te.dim(` Baseline: ${c.from} to ${c.to} (${c.days} day(s) with data)`)),console.log();let d=` ${"channel".padEnd(14)} ${"tasks".padStart(6)} ${"cache read".padStart(11)} ${"cache write".padStart(11)} ${"output".padStart(8)} ${"cost".padStart(10)} ${"per task".padStart(9)}`;console.log(te.dim(c?`${d} ${"baseline".padStart(9)} ${"change".padStart(7)}`:d));let p=[...new Set([...Object.keys(a.channels),...Object.keys(c?.channels||{})])].map(u=>[u,a.channels[u],c?.channels[u]]).sort((u,h)=>(h[1]?.cost||0)-(u[1]?.cost||0));p.push(["TOTAL",a.total,c?.total]);for(let[u,h,y]of p){let $=r(h),b=` ${u.padEnd(14)} ${String(h?.tasks||0).padStart(6)} ${g(h?.cacheRead||0).padStart(11)} ${g(h?.cacheCreate||0).padStart(11)} ${g(h?.output||0).padStart(8)} ${l(h?.cost||0).padStart(10)} ${l($).padStart(9)}`;if(c){let v=r(y),A=$!==null&&v?`${(($-v)/v*100).toFixed(0)}%`:"-";b+=` ${l(v).padStart(9)} ${A.padStart(7)}`}console.log(b)}console.log(),c&&console.log(te.dim(" change = cost per task against the baseline; the two ranges do not have the same tasks.")),e.save&&console.log(te.dim(` Saved ${e.save}`)),console.log()});mn.command("surfaces").description("which CLI commands and dashboard pages are actually used").option("--days <n>","window in days","30").option("--kind <kind>","cli | page (default: both)").option("--unused","list registered surfaces with ZERO recorded use instead").option("--json","raw JSON output").action(async e=>{let{listSurfaceUse:t}=await import("./surface-usage-WSY4SHHL.js"),o=e.kind==="cli"||e.kind==="page"?e.kind:void 0,n=t({kind:o,sinceDays:Number(e.days)||30});if(e.unused){let{buildProgram:r}=await import("./program-AMQ7JGHO.js"),i=await r(),a=new Set(n.filter(d=>d.kind==="cli").map(d=>d.name)),c=[],l=(d,f)=>{for(let p of d.commands??[]){let u=f?`${f} ${p.name()}`:p.name();c.push(u),l(p,u)}};l(i,"");let g=c.filter(d=>!a.has(d)).sort();if(e.json){console.log(JSON.stringify({windowDays:Number(e.days)||30,unused:g},null,2));return}console.log(),console.log(te.bold(` Never used in the last ${e.days} days (${g.length}/${c.length})`)),console.log();for(let d of g)console.log(` ${te.dim("\xB7")} ${d}`);console.log(),console.log(te.dim(" Zero use is necessary but not sufficient to remove a surface \u2014")),console.log(te.dim(" it must also fail the impact test. See docs/architecture/surface-reduction.md")),console.log();return}if(e.json){console.log(JSON.stringify(n,null,2));return}if(n.length===0){console.log(),console.log(te.dim(` No surface usage recorded in the last ${e.days} days.`)),console.log(te.dim(" Counting starts once this build has been running for a while.")),console.log();return}console.log(),console.log(te.bold(` Surface usage \u2014 last ${e.days} days`)),console.log();let s="";for(let r of n){r.kind!==s&&(console.log(te.dim(` ${r.kind==="cli"?"CLI commands":"Dashboard pages"}`)),s=r.kind);let i=String(r.count).padStart(6),a=te.dim(`${r.days}d`);console.log(` ${te.cyan(i)} ${r.name.padEnd(28)} ${a}`)}console.log(),console.log(te.dim(` ${n.length} surface(s) with recorded use. See --unused for the rest.`)),console.log()});import{Command as Vv}from"commander";import Pe from"chalk";import{readFileSync as Xv}from"fs";import{resolve as Kv}from"path";le();var Io=new Vv().name("board").description("kanban board dashboard \u2014 visual work view over configured sources");Io.command("serve",{isDefault:!0}).description("start the dashboard server (live view always; boards if configured)").option("--port <n>","override dashboard.port").option("--bind <host>","override dashboard.bind (e.g. 0.0.0.0)").action(async e=>{try{try{Ul(Kv(process.cwd(),".env"))}catch{}let t=Qv(e.port,e.bind),{startBoardDashboard:o}=await import("./board-dashboard-2A6VSQ7V.js");o(t)}catch(t){console.log(Pe.red(` ${t.message}`)),process.env.DEBUG&&t.stack&&console.log(Pe.dim(t.stack)),process.exit(1)}});function Qv(e,t){try{let o=O();return e&&(o.dashboard.port=parseInt(e,10)),t&&(o.dashboard.bind=t),o}catch{return console.log(Pe.yellow(" No agentx.json found \u2014 starting dashboard in setup-only mode.")),console.log(Pe.dim(" Visit http://127.0.0.1:"+(e||"4202")+` to run the wizard.
|
|
496
|
+
`}function Hs(e,t,o=".env"){let n=pf(o),s=ff(n),r=s.findIndex(i=>i.kind==="kv"&&i.key===e);r>=0?s[r]={kind:"kv",key:e,value:t,raw:`${e}=${jc(t)}`}:(s.length&&s[s.length-1].kind!=="blank"&&s.push({kind:"blank",raw:""}),s.push({kind:"kv",key:e,value:t,raw:`${e}=${jc(t)}`})),Ev(n,Rv(s))}function _c(e,t=".env"){return ff(t).find(s=>s.kind==="kv"&&s.key===e)?.value}var hf="https://t.me/BotFather";async function Iv(e){try{let t=await fetch(`https://api.telegram.org/bot${e}/getMe`,{signal:AbortSignal.timeout(8e3)});if(!t.ok)return null;let o=await t.json();return o?.ok?o.result:null}catch{return null}}async function Nv(e,t){let o=Date.now()+t,n=0;try{let r=await(await fetch(`https://api.telegram.org/bot${e}/getUpdates?offset=-1&timeout=0`,{signal:AbortSignal.timeout(5e3)})).json();r?.result?.length&&(n=r.result[0].update_id+1)}catch{}for(;Date.now()<o;)try{let r=await(await fetch(`https://api.telegram.org/bot${e}/getUpdates?offset=${n}&timeout=10`,{signal:AbortSignal.timeout(15e3)})).json();for(let i of r?.result||[]){n=Math.max(n,i.update_id+1);let a=i.message||i.edited_message||i.channel_post;if(a?.chat?.id)return{chatId:a.chat.id,chatTitle:a.chat.title||a.chat.username||a.chat.first_name,from:a.from?.first_name||a.from?.username||"unknown"}}}catch{}return null}async function yf(e={}){console.log(),console.log(ye.bold(" Connect Telegram")),console.log(),console.log(ye.dim(` 1. Open ${hf}`)),console.log(ye.dim(" /newbot (or /mybots \u2192 pick existing \u2192 API Token)")),console.log(ye.dim(" 2. Copy the HTTP API token \u2014 looks like 123456789:ABCdef...")),console.log();let{wantsBrowser:t}=await zs({type:"confirm",name:"wantsBrowser",message:"Open BotFather in your browser?",initial:!0});t&&Ho(hf);let{token:o}=await zs({type:"password",name:"token",message:"Paste the bot token from BotFather:",validate:b=>/^\d+:[A-Za-z0-9_-]{20,}$/.test(b.trim())||"That doesn't look like a Telegram token"});o||(console.log(ye.red(" Aborted")),process.exit(1)),process.stdout.write(" Verifying token... ");let n=await Iv(o.trim());n||(console.log(ye.red("\u2717 invalid or revoked")),process.exit(1)),console.log(ye.green(`\u2713 ${n.first_name} (@${n.username})`));let{readFileSync:s,existsSync:r}=await import("fs"),{resolve:i}=await import("path"),a=e.configPath||i(process.cwd(),"agentx.json");r(a)||(console.log(ye.red(` No agentx.json at ${a}. Run: agentx init`)),process.exit(1));let c=JSON.parse(s(a,"utf-8")),l=Object.keys(c.agents||{});l.length===0&&(console.log(ye.red(" No agents configured. Run: agentx agent add")),process.exit(1));let g=e.agent;g||(g=(await zs({type:"select",name:"agent",message:"Bind this bot to which agent?",choices:l.map(v=>({title:v,value:v})),initial:0})).agent),g||(console.log(ye.red(" Aborted")),process.exit(1));let d=c.channels?.telegram?.accounts||{},f=e.account||g;d[f]&&d[f].agentBinding!==g&&(f=(await zs({type:"text",name:"account",message:`Account "${f}" is already in use. Pick a different label:`,initial:`${g}-2`,validate:v=>/^[a-z0-9-]+$/i.test(v)||"lowercase letters, digits, hyphens only"})).account),f||(console.log(ye.red(" Aborted")),process.exit(1));let p=`TG_${f.toUpperCase().replace(/-/g,"_")}_BOT_TOKEN`;Hs(p,o.trim()),process.env[p]=o.trim();let u=await ge(b=>{We(b,"channels.telegram.enabled",!0),We(b,`channels.telegram.accounts.${f}`,{token:`\${${p}}`,agentBinding:g});let v=On(b,`agents.${g}.mentions`)||[],A=`@${n.username}`;v.includes(A)||(v.push(A),We(b,`agents.${g}.mentions`,v))},{configPath:e.configPath});if(u.success||(console.log(ye.red(` \u2717 ${u.error}`)),process.exit(1)),console.log(),console.log(ye.green(` \u2713 Bot @${n.username} bound to agent "${g}"`)),console.log(ye.dim(` Token stored in .env as ${p}`)),console.log(ye.dim(` Mentions updated to include @${n.username}`)),u.reloaded?console.log(ye.dim(" Daemon hot-reloaded.")):console.log(ye.dim(" Start the daemon to begin polling (agentx daemon start)")),e.skipChatCapture)return;console.log();let{capture:h}=await zs({type:"confirm",name:"capture",message:"Listen for a message so we can set this as your default --notify target?",initial:!0});if(!h)return;console.log(),console.log(ye.dim(` Send any message to @${n.username} from the chat you want to use.`)),console.log(ye.dim(" Waiting up to 90s..."));let y=await Nv(o.trim(),9e4);if(!y){console.log(ye.yellow(" No message received \u2014 skipping. You can set it later with:")),console.log(ye.dim(" agentx config set notifications.destination.channel telegram")),console.log(ye.dim(" agentx config set notifications.destination.chatId <chatId>")),console.log(ye.dim(` agentx config set notifications.destination.accountId ${f}`));return}let $=await ge(b=>{We(b,"notifications.destination",{channel:"telegram",chatId:String(y.chatId),accountId:f})},{configPath:e.configPath});if(!$.success){console.log(ye.red(` \u2717 failed to save destination: ${$.error}`));return}console.log(ye.green(" \u2713 Default --notify target set")),console.log(ye.dim(` Chat: ${y.chatTitle||y.chatId} (from ${y.from})`)),console.log(ye.dim(" From now on, --notify me resolves to this chat.")),$.reloaded&&console.log(ye.dim(" Daemon hot-reloaded."))}import Ce from"chalk";import Dc from"prompts";import{existsSync as Mc,mkdirSync as jv,readdirSync as _v,readFileSync as wf}from"fs";import{resolve as Pc}from"path";var Dv=".agentx/whatsapp-sessions";function Pv(){let e=Pc(process.cwd(),".agentx/daemon.pid");if(!Mc(e))return!1;try{let t=parseInt(wf(e,"utf-8").trim(),10);return t?(process.kill(t,0),!0):!1}catch{return!1}}function Mv(e){if(!Mc(e))return!1;try{return _v(e).some(t=>t.includes("creds")||t.endsWith(".json"))}catch{return!1}}async function Lv(e,t){let o;try{o=await import("@whiskeysockets/baileys")}catch{return t(Ce.red(" WhatsApp pairing needs @whiskeysockets/baileys.")),t(Ce.red(" Install with: pnpm add @whiskeysockets/baileys (or npm install)")),!1}let{makeWASocket:n,useMultiFileAuthState:s}=o.default||o;jv(e,{recursive:!0});let{state:r,saveCreds:i}=await s(e),a;try{a=(await o.fetchLatestBaileysVersion()).version}catch{}let c={level:"silent",trace:()=>{},debug:()=>{},info:()=>{},warn:()=>{},fatal:()=>{},error:()=>{},child:()=>c},l=n({auth:{creds:r.creds,keys:o.makeCacheableSignalKeyStore?o.makeCacheableSignalKeyStore(r.keys,c):r.keys},...a?{version:a}:{},logger:c,printQRInTerminal:!1,browser:["agentx-connect","pairing","1.0"],syncFullHistory:!1,markOnlineOnConnect:!1});return l.ev.on("creds.update",i),await new Promise(g=>{let f=setTimeout(()=>{t(Ce.yellow(" Timed out waiting for scan (2min)."));try{l.end(void 0)}catch{}g(!1)},12e4);l.ev.on("connection.update",async p=>{if(p.qr){t(Ce.dim(" Scan this QR with WhatsApp \u2192 Linked devices \u2192 Link a device:")),t("");try{let{default:u}=await import("qrcode-terminal");u.generate(p.qr,{small:!0})}catch{t(` QR: ${p.qr}`)}t("")}if(p.connection==="open"){clearTimeout(f),t(Ce.green(" \u2713 Paired"));try{l.end(void 0)}catch{}g(!0)}p.connection==="close"&&!p.qr&&p.lastDisconnect?.error?.output?.statusCode===401&&(clearTimeout(f),t(Ce.red(" \u2717 Authorization failed \u2014 rescan required")),g(!1))})})}async function $f(e={}){if(console.log(),console.log(Ce.bold(" Connect WhatsApp")),console.log(),Pv()){console.log(Ce.yellow(" \u26A0 The daemon is running. Stop it first so pairing can open a fresh Baileys session:")),console.log(Ce.dim(" agentx daemon stop")),console.log();let{cont:l}=await Dc({type:"confirm",name:"cont",message:"Continue anyway? (expect errors)",initial:!1});l||(console.log(Ce.red(" Aborted")),process.exit(1))}let t=e.configPath||Pc(process.cwd(),"agentx.json");Mc(t)||(console.log(Ce.red(` No agentx.json at ${t}. Run: agentx init`)),process.exit(1));let o=JSON.parse(wf(t,"utf-8")),n=e.sessionDir||o.channels?.whatsapp?.sessionDir||Dv,s=Pc(process.cwd(),n),r=Object.keys(o.agents||{});r.length===0&&(console.log(Ce.red(" No agents configured. Run: agentx agent add")),process.exit(1));let i=e.agent;i||(i=(await Dc({type:"select",name:"agent",message:"Default agent for incoming messages:",choices:r.map(g=>({title:g,value:g})),initial:0})).agent),i||(console.log(Ce.red(" Aborted")),process.exit(1));let a=!e.skipPair;if(Mv(s)&&a){let{keep:l}=await Dc({type:"confirm",name:"keep",message:`An existing session was found at ${n}. Keep it (recommended), or pair again?`,initial:!0});a=!l,l&&console.log(Ce.dim(" Keeping existing session \u2014 skipping QR pair."))}a&&(await Lv(s,g=>console.log(g))||(console.log(Ce.red(" Pairing failed. You can retry with `agentx connect whatsapp`.")),process.exit(1)));let c=await ge(l=>{We(l,"channels.whatsapp.enabled",!0),We(l,"channels.whatsapp.sessionDir",n),We(l,"channels.whatsapp.defaultAgent",i)},{configPath:e.configPath});c.success||(console.log(Ce.red(` \u2717 ${c.error}`)),process.exit(1)),console.log(),console.log(Ce.green(` \u2713 WhatsApp bound to agent "${i}"`)),console.log(Ce.dim(` Session: ${n}`)),console.log(Ce.dim(" Start the daemon to begin receiving:")),console.log(Ce.dim(" agentx daemon start")),console.log(Ce.yellow(" No chat is answered until you list the allowed numbers (add your own to talk from your self-chat):")),console.log(Ce.dim(` agentx config set channels.whatsapp.allowFrom '["+15550001111"]'`)),console.log(Ce.dim(" Add routes later with:")),console.log(Ce.dim(` agentx config set channels.whatsapp.routes '[{"contact":"+1234567890","agent":"${i}"}]'`)),console.log()}import{randomBytes as Fv}from"crypto";import Fe from"chalk";import bf from"prompts";var xi="agentx-mesh:",Si="//join/";function qv(e){return Buffer.from(e,"utf-8").toString("base64url")}function Uv(e){return Buffer.from(e,"base64url").toString("utf-8")}function Wv(e){return`${xi}${Si}${qv(JSON.stringify(e))}`}function Bv(e){if(!e.startsWith(`${xi}${Si}`))throw new Error(`Not a mesh invite link \u2014 expected ${xi}${Si}\u2026`);let t=e.slice(xi.length+Si.length),o=JSON.parse(Uv(t));if(o.version!==1)throw new Error(`Unsupported invite version: ${o.version}`);if(typeof o.url!="string"||typeof o.token!="string"||typeof o.name!="string")throw new Error("Invite payload missing required fields (url, token, name)");return o}function Jv(){return Fv(32).toString("hex")}function Gv(e){let t=e?.node?.bind||"127.0.0.1:18800",[o,n]=t.split(":");return`http://${o}:${n}`}function Hv(e){return e?.node?.name||e?.node?.id||"node"}async function kf(e){let{readFileSync:t,existsSync:o}=await import("fs"),{resolve:n}=await import("path"),s=e.configPath||n(process.cwd(),"agentx.json");o(s)||(console.log(Fe.red(` No agentx.json at ${s}. Run: agentx init`)),process.exit(1));let r=JSON.parse(t(s,"utf-8")),i=_c("MESH_TOKEN");i||(i=Jv(),Hs("MESH_TOKEN",i),console.log(Fe.dim(" Generated new MESH_TOKEN (32 bytes) and saved to .env")));let a=e.url||Gv(r);if((a.includes("0.0.0.0")||a.includes("127.0.0.1")||a.includes("localhost"))&&!e.url){let{url:d}=await bf({type:"text",name:"url",message:"This node's URL as peers should reach it (e.g. http://100.x.x.x:18800):",initial:a,validate:f=>/^https?:\/\//.test(f)||"must start with http:// or https://"});d||(console.log(Fe.red(" Aborted")),process.exit(1)),a=d}let l=await ge(d=>{We(d,"mesh.enabled",!0)},{configPath:e.configPath});l.success||(console.log(Fe.red(` \u2717 ${l.error}`)),process.exit(1));let g=Wv({url:a,token:i,name:Hv(r),version:1});console.log(),console.log(Fe.bold(" Mesh invite")),console.log(),console.log(` ${Fe.cyan(g)}`),console.log(),console.log(Fe.dim(" Run on the other node:")),console.log(Fe.dim(` agentx connect mesh join "${g}"`)),console.log(),console.log(Fe.yellow(" Share over a trusted channel only \u2014 whoever holds this link can join your mesh.")),console.log()}async function vf(e,t={}){let o;try{o=Bv(e)}catch(r){console.log(Fe.red(` \u2717 ${r.message}`)),process.exit(1)}let n=_c("MESH_TOKEN");if(n&&n!==o.token){let{overwrite:r}=await bf({type:"confirm",name:"overwrite",message:"A different MESH_TOKEN already exists in .env. Overwrite with the invite's token?",initial:!1});r||(console.log(Fe.red(" Aborted (keeping existing .env)")),process.exit(1))}Hs("MESH_TOKEN",o.token);let s=await ge(r=>{We(r,"mesh.enabled",!0);let i=On(r,"mesh.peers")||[],a=i.findIndex(l=>l.name===o.name),c={name:o.name,url:o.url,token:"${MESH_TOKEN}"};a>=0?i[a]=c:i.push(c),We(r,"mesh.peers",i)},{configPath:t.configPath});s.success||(console.log(Fe.red(` \u2717 ${s.error}`)),process.exit(1)),console.log(Fe.green(" \u2713 Joined mesh")),console.log(Fe.dim(` Peer: ${o.name} @ ${o.url}`)),console.log(Fe.dim(" Shared token stored in .env as MESH_TOKEN")),s.reloaded?console.log(Fe.dim(" Daemon hot-reloaded.")):console.log(Fe.dim(" Restart the daemon to start the mesh (agentx daemon start).")),console.log();try{let r=await fetch(`${o.url.replace(/\/$/,"")}/.well-known/agent-card.json`,{signal:AbortSignal.timeout(5e3)});if(r.ok){let i=await r.json(),a=Array.isArray(i.agents)?i.agents.length:"?";console.log(Fe.dim(` Reachable \u2014 ${o.name} exposes ${a} agents`))}else console.log(Fe.yellow(` Peer returned HTTP ${r.status} \u2014 double-check the URL`))}catch(r){console.log(Fe.yellow(` Could not reach ${o.url} right now (${r?.cause?.code||r.message}) \u2014 verify VPN / Tailscale when the peer daemon is up`))}}var Zn=new zv().name("connect").description("connect a channel or mesh peer \u2014 no manual file editing required");Zn.command("telegram").description("pair a Telegram bot \u2014 persists token in .env, binds to an agent, auto-detects default chat").option("--agent <id>","agent to bind the bot to (skips the prompt)").option("--account <name>","account label in channels.telegram.accounts (defaults to agent id)").option("--skip-chat-capture","don't listen for the first message after pairing").option("-c, --config <path>","path to agentx.json").action(async e=>{await yf({agent:e.agent,account:e.account,configPath:e.config,skipChatCapture:!!e.skipChatCapture})});Zn.command("whatsapp").description("pair WhatsApp via QR (prints in terminal), then writes channels.whatsapp config").option("--agent <id>","default agent for inbound messages").option("--session-dir <path>","session directory (default: .agentx/whatsapp-sessions)").option("--skip-pair","skip QR pairing (use when session already exists)").option("-c, --config <path>","path to agentx.json").action(async e=>{await $f({agent:e.agent,sessionDir:e.sessionDir,skipPair:!!e.skipPair,configPath:e.config})});var xf=Zn.command("mesh").description("mesh invite + join \u2014 replaces manual MESH_TOKEN copy-paste");xf.command("invite").description("emit a single-use join link for another AgentX node").option("--url <url>","this node's URL as peers should reach it (Tailscale IP etc.)").option("-c, --config <path>","path to agentx.json").action(async e=>{await kf({url:e.url,configPath:e.config})});xf.command("join <link>").description("accept an invite link from another AgentX node").option("-c, --config <path>","path to agentx.json").action(async(e,t)=>{await vf(e,{configPath:t.config})});Zn.action(()=>{console.log(),console.log(Qn.bold(" agentx connect \u2014 which channel?")),console.log(),console.log(` ${Qn.cyan("agentx connect telegram")} \u2014 pair a Telegram bot`),console.log(` ${Qn.cyan("agentx connect whatsapp")} \u2014 pair WhatsApp via QR`),console.log(` ${Qn.cyan("agentx connect mesh invite")} \u2014 emit a mesh join link for another node`),console.log(` ${Qn.cyan("agentx connect mesh join <link>")} \u2014 accept a mesh invite`),console.log(),console.log(Qn.dim(" GitLab connect is the next slice.")),console.log()});le();import{Command as Yv}from"commander";import te from"chalk";var mn=new Yv().name("usage").description("token usage analysis and reporting");mn.command("today",{isDefault:!0}).description("show today's token usage").action(async()=>{try{let e=O(),[t,o]=e.node.bind.split(":"),s=await(await fetch(`http://${t||"127.0.0.1"}:${o}/usage`,{signal:AbortSignal.timeout(3e3)})).json();if(console.log(),console.log(te.bold(" Token Usage (last 7 days)")),console.log(),s.totalTasks===0){console.log(te.dim(" No tasks recorded yet")),console.log();return}let r=a=>a.toLocaleString();console.log(` Total: ${te.bold(r(s.totalTokens))} tokens across ${s.totalTasks} tasks`),console.log(` Input: ${r(s.totalInput)}`),console.log(` Output: ${r(s.totalOutput)}`),console.log(` Cache read: ${r(s.totalCacheRead)}`),console.log(` Cache create: ${r(s.totalCacheCreate)}`),s.cacheHitRatio>0&&console.log(` Cache hit: ${(s.cacheHitRatio*100).toFixed(1)}%`),s.totalErrors>0&&console.log(` Errors: ${te.red(String(s.totalErrors))}`),console.log(),console.log(te.bold(" By Agent:"));let i=Object.entries(s.byAgent||{});i.sort((a,c)=>c[1].total-a[1].total);for(let[a,c]of i){let l=r(c.total||c.input+c.output+c.cacheRead+c.cacheCreate),g=c.avgDuration?`${(c.avgDuration/1e3).toFixed(1)}s avg`:"";console.log(` ${te.cyan(a)}: ${l} tokens (${c.tasks} tasks) ${te.dim(g)}`)}console.log()}catch(e){e.cause?.code==="ECONNREFUSED"?console.log(te.red(" Daemon not running. Start with: agentx daemon start")):console.log(te.red(` ${e.message}`))}});mn.command("plan").description("show the Claude plan windows as Claude Code last reported them, and any hold on fresh sessions").option("--lift","lift the hold on fresh sessions now, without waiting for the reset time").option("--json","raw JSON output").action(async e=>{try{let t=O(),[o,n]=t.node.bind.split(":"),s=`http://${o||"127.0.0.1"}:${n}`,r=e.lift?await fetch(`${s}/usage/plan/lift`,{method:"POST",signal:AbortSignal.timeout(3e3)}):await fetch(`${s}/usage/plan`,{signal:AbortSignal.timeout(3e3)});if(!r.ok)throw new Error(`Daemon answered ${r.status}. It may be older than this command; restart it.`);let i=await r.json();if(e.json){console.log(JSON.stringify(i,null,2));return}let a=d=>d.replace(/_/g," ");if(console.log(),e.lift){let d=i.lifted.map(f=>a(f.window));console.log(d.length?` Lifted the hold on: ${d.join(", ")}. The next fresh session asks Claude again.`:te.dim(" No hold to lift.")),console.log();return}let c=i.now,l=new Set(i.holds.map(d=>d.window));console.log(te.bold(" Claude plan (as Claude Code last reported it)")),console.log(),i.provider.length===0&&console.log(te.dim(" No report yet. Claude Code reports on each turn; the list is empty after a daemon restart."));for(let d of i.provider){let f=d.usingOverage?"extra usage":d.status==="allowed_warning"?"nearly full":d.status,p=typeof d.utilization=="number"?`${Math.round(d.utilization*100)}% used`:"",u=Math.max(1,Math.round((d.resetsAt-c)/6e4)),h=d.resetsAt>c?u<60?`resets in ${u} min`:u<48*60?`resets in ${Math.round(u/60)} h`:`resets in ${Math.round(u/1440)} days`:"",y=l.has(d.window)?te.red("holding fresh sessions"):"";console.log(` ${a(d.window).padEnd(18)} ${f.padEnd(12)} ${[p,h,y].filter(Boolean).join(", ")}`)}console.log();let g=d=>d?` of ${d}`:"";console.log(` Fresh sessions started: ${i.lastHour}${g(i.maxPerHour)} in the last hour, ${i.last5h}${g(i.maxPer5h)} in the last 5 hours`),l.size&&console.log(te.dim(" To try again now: agentx usage plan --lift")),console.log()}catch(t){t.cause?.code==="ECONNREFUSED"?console.log(te.red(" Daemon not running. Start with: agentx daemon start")):console.log(te.red(` ${t.message}`))}});mn.command("report").description("run full session analysis (parses Claude Code JSONL files)").option("--days <n>","analyze last N days","7").action(async e=>{let{execSync:t}=await import("child_process"),{existsSync:o}=await import("fs"),{resolve:n}=await import("path"),s=n(process.cwd(),"scripts/token-report.py");if(!o(s)){console.log(te.red(` Script not found: ${s}`)),console.log(te.dim(" The token analyzer script should be at scripts/token-report.py"));return}console.log(te.dim(" Analyzing Claude Code sessions..."));try{t(`SINCE_DAYS=${e.days} python3 "${s}"`,{stdio:"inherit"})}catch{console.log(te.red(" Analysis failed. Is Python 3 installed?"))}});mn.command("channels").description("cost per channel over a fixed range of days, next to a saved baseline").requiredOption("--from <date>","first day, YYYY-MM-DD").requiredOption("--to <date>","last day, YYYY-MM-DD").option("--save <file>","write this range's figures to a JSON file").option("--baseline <file>","a file written by --save, shown beside this range").option("--json","raw JSON output").action(async e=>{let{readFileSync:t,writeFileSync:o}=await import("fs"),{TokenTracker:n}=await import("./token-tracker-W7DFDEFN.js"),{channelCosts:s,costPerTask:r}=await import("./usage-channels-KBLEC4NE.js"),i=u=>/^\d{4}-\d{2}-\d{2}$/.test(u)&&!Number.isNaN(Date.parse(u));if(!i(e.from)||!i(e.to)||e.from>e.to){console.log(te.red(" --from and --to must be YYYY-MM-DD, with --from not after --to")),process.exitCode=1;return}let a=s(new n,e.from,e.to);e.save&&o(e.save,JSON.stringify(a,null,2));let c=null;if(e.baseline)try{c=JSON.parse(t(e.baseline,"utf-8"))}catch(u){console.log(te.red(` Cannot read baseline ${e.baseline}: ${u.message}`)),process.exitCode=1;return}if(e.json){console.log(JSON.stringify(c?{baseline:c,current:a}:a,null,2));return}let l=u=>u===null?"-":`$${u.toFixed(u<10?3:2)}`,g=u=>`${(u/1e6).toFixed(1)}M`;console.log(),console.log(te.bold(` Cost per channel, ${a.from} to ${a.to} (${a.days} day(s) with data)`)),c&&console.log(te.dim(` Baseline: ${c.from} to ${c.to} (${c.days} day(s) with data)`)),console.log();let d=` ${"channel".padEnd(14)} ${"tasks".padStart(6)} ${"cache read".padStart(11)} ${"cache write".padStart(11)} ${"output".padStart(8)} ${"cost".padStart(10)} ${"per task".padStart(9)}`;console.log(te.dim(c?`${d} ${"baseline".padStart(9)} ${"change".padStart(7)}`:d));let p=[...new Set([...Object.keys(a.channels),...Object.keys(c?.channels||{})])].map(u=>[u,a.channels[u],c?.channels[u]]).sort((u,h)=>(h[1]?.cost||0)-(u[1]?.cost||0));p.push(["TOTAL",a.total,c?.total]);for(let[u,h,y]of p){let $=r(h),b=` ${u.padEnd(14)} ${String(h?.tasks||0).padStart(6)} ${g(h?.cacheRead||0).padStart(11)} ${g(h?.cacheCreate||0).padStart(11)} ${g(h?.output||0).padStart(8)} ${l(h?.cost||0).padStart(10)} ${l($).padStart(9)}`;if(c){let v=r(y),A=$!==null&&v?`${(($-v)/v*100).toFixed(0)}%`:"-";b+=` ${l(v).padStart(9)} ${A.padStart(7)}`}console.log(b)}console.log(),c&&console.log(te.dim(" change = cost per task against the baseline; the two ranges do not have the same tasks.")),e.save&&console.log(te.dim(` Saved ${e.save}`)),console.log()});mn.command("surfaces").description("which CLI commands and dashboard pages are actually used").option("--days <n>","window in days","30").option("--kind <kind>","cli | page (default: both)").option("--unused","list registered surfaces with ZERO recorded use instead").option("--json","raw JSON output").action(async e=>{let{listSurfaceUse:t}=await import("./surface-usage-WSY4SHHL.js"),o=e.kind==="cli"||e.kind==="page"?e.kind:void 0,n=t({kind:o,sinceDays:Number(e.days)||30});if(e.unused){let{buildProgram:r}=await import("./program-3TO4YAY7.js"),i=await r(),a=new Set(n.filter(d=>d.kind==="cli").map(d=>d.name)),c=[],l=(d,f)=>{for(let p of d.commands??[]){let u=f?`${f} ${p.name()}`:p.name();c.push(u),l(p,u)}};l(i,"");let g=c.filter(d=>!a.has(d)).sort();if(e.json){console.log(JSON.stringify({windowDays:Number(e.days)||30,unused:g},null,2));return}console.log(),console.log(te.bold(` Never used in the last ${e.days} days (${g.length}/${c.length})`)),console.log();for(let d of g)console.log(` ${te.dim("\xB7")} ${d}`);console.log(),console.log(te.dim(" Zero use is necessary but not sufficient to remove a surface \u2014")),console.log(te.dim(" it must also fail the impact test. See docs/architecture/surface-reduction.md")),console.log();return}if(e.json){console.log(JSON.stringify(n,null,2));return}if(n.length===0){console.log(),console.log(te.dim(` No surface usage recorded in the last ${e.days} days.`)),console.log(te.dim(" Counting starts once this build has been running for a while.")),console.log();return}console.log(),console.log(te.bold(` Surface usage \u2014 last ${e.days} days`)),console.log();let s="";for(let r of n){r.kind!==s&&(console.log(te.dim(` ${r.kind==="cli"?"CLI commands":"Dashboard pages"}`)),s=r.kind);let i=String(r.count).padStart(6),a=te.dim(`${r.days}d`);console.log(` ${te.cyan(i)} ${r.name.padEnd(28)} ${a}`)}console.log(),console.log(te.dim(` ${n.length} surface(s) with recorded use. See --unused for the rest.`)),console.log()});import{Command as Vv}from"commander";import Pe from"chalk";import{readFileSync as Xv}from"fs";import{resolve as Kv}from"path";le();var Io=new Vv().name("board").description("kanban board dashboard \u2014 visual work view over configured sources");Io.command("serve",{isDefault:!0}).description("start the dashboard server (live view always; boards if configured)").option("--port <n>","override dashboard.port").option("--bind <host>","override dashboard.bind (e.g. 0.0.0.0)").action(async e=>{try{try{Ul(Kv(process.cwd(),".env"))}catch{}let t=Qv(e.port,e.bind),{startBoardDashboard:o}=await import("./board-dashboard-2A6VSQ7V.js");o(t)}catch(t){console.log(Pe.red(` ${t.message}`)),process.env.DEBUG&&t.stack&&console.log(Pe.dim(t.stack)),process.exit(1)}});function Qv(e,t){try{let o=O();return e&&(o.dashboard.port=parseInt(e,10)),t&&(o.dashboard.bind=t),o}catch{return console.log(Pe.yellow(" No agentx.json found \u2014 starting dashboard in setup-only mode.")),console.log(Pe.dim(" Visit http://127.0.0.1:"+(e||"4202")+` to run the wizard.
|
|
497
497
|
`)),{node:{id:"setup",name:"Setup",bind:"127.0.0.1:18800"},providers:{},agents:{},channels:{},crons:{},mesh:{enabled:!1,peers:[],discovery:"static",healthCheck:{interval:60,timeout:10}},boards:[],dashboard:{enabled:!0,port:e?parseInt(e,10):4202,bind:t||"127.0.0.1",daemonUrl:"http://localhost:18800",daemons:[]},business:void 0,session:{staleMinutes:120}}}}Io.command("list").description("list configured boards").action(()=>{try{let e=O();if(!e.boards?.length){console.log(Pe.dim(`
|
|
498
498
|
No boards configured.
|
|
499
499
|
`));return}console.log();for(let t of e.boards){console.log(` ${Pe.cyan(t.id)} \u2014 ${t.name}`);let o=t.source;console.log(Pe.dim(` source: ${o.type}${o.projects?" ("+o.projects.join(", ")+")":""}`)),t.primaryToolLabel&&console.log(Pe.dim(` primary label: ${t.primaryToolLabel}`)),console.log(Pe.dim(` window: ${t.timeRangeDays} days`))}console.log()}catch(e){console.log(Pe.red(` ${e.message}`)),process.exit(1)}});Io.command("add <id>").description("add a GitLab board to agentx.json").requiredOption("--name <name>","human-readable board name").requiredOption("--projects <paths>","comma-separated GitLab project paths (e.g. 'globex/system,globex/website')").option("--label <label>","primary tool label ANDed into every query (e.g. 'Tool::Claude')").option("--days <n>","open-window time range in days","30").option("--closed-days <n>","closed-window in days","30").action((e,t)=>{try{Ys(o=>{if(Array.isArray(o.boards)||(o.boards=[]),o.boards.find(r=>r.id===e))throw new Error(`board "${e}" already exists`);let n=String(t.projects).split(",").map(r=>r.trim()).filter(Boolean);if(n.length===0)throw new Error("--projects must include at least one path");let s={id:e,name:t.name,source:{type:"gitlab",projects:n},timeRangeDays:parseInt(t.days,10)||30,closedWindowDays:parseInt(t.closedDays,10)||30};return t.label&&(s.primaryToolLabel=t.label),o.boards.push(s),`added board "${e}" (${n.join(", ")})`})}catch(o){console.log(Pe.red(` ${o.message}`)),process.exit(1)}});Io.command("edit <id>").description("edit board fields without re-creating it").option("--name <name>","rename").option("--projects <paths>","comma-separated GitLab project paths (replaces the list)").option("--label <label>","primary tool label (set to '-' to clear)").option("--days <n>","open-window time range in days").option("--closed-days <n>","closed-window in days").action((e,t)=>{try{Ys(o=>{let s=(Array.isArray(o.boards)?o.boards:[]).find(i=>i.id===e);if(!s)throw new Error(`board "${e}" not found`);let r=[];if(t.name&&(s.name=t.name,r.push(`name="${t.name}"`)),t.projects){let i=String(t.projects).split(",").map(a=>a.trim()).filter(Boolean);if(i.length===0)throw new Error("--projects must include at least one path");s.source={...s.source||{type:"gitlab"},type:"gitlab",projects:i},r.push(`projects=${i.length}`)}if(t.label!==void 0&&(t.label==="-"?(delete s.primaryToolLabel,r.push("label cleared")):(s.primaryToolLabel=t.label,r.push(`label="${t.label}"`))),t.days&&(s.timeRangeDays=parseInt(t.days,10)||s.timeRangeDays,r.push(`days=${s.timeRangeDays}`)),t.closedDays&&(s.closedWindowDays=parseInt(t.closedDays,10)||s.closedWindowDays,r.push(`closed-days=${s.closedWindowDays}`)),r.length===0)throw new Error("no changes \u2014 pass at least one option");return`board "${e}" updated (${r.join(", ")})`})}catch(o){console.log(Pe.red(` ${o.message}`)),process.exit(1)}});var Lc=Io.command("column").description("manage a board's columns (add / remove / edit / list)");Lc.command("list <boardId>").description("list columns on a board, in order").action(e=>{try{let o=(JSON.parse(Xv("agentx.json","utf-8")).boards||[]).find(s=>s.id===e);if(!o)throw new Error(`board "${e}" not found`);let n=o.columns||[];if(n.length===0){console.log(Pe.dim(" no columns (board falls back to GitLab default flow)"));return}for(let s of n){let r=s.kind==="scoped-label"?`scoped="${s.scopedLabel}"`:s.kind==="label"?`label="${s.mapsToLabel||""}"`:s.kind==="open-backlog"?`prefix="${s.scopedPrefix}"`:s.kind;console.log(` ${Pe.cyan(s.id.padEnd(12))} ${s.title.padEnd(14)} ${Pe.dim(r)}`)}}catch(t){console.log(Pe.red(` ${t.message}`)),process.exit(1)}});Lc.command("add <boardId> <columnId>").description("add a column to a board").requiredOption("--title <title>","column title").option("--kind <kind>","open-backlog | scoped-label | closed | label","scoped-label").option("--scoped <label>","for kind=scoped-label, the full label (e.g. 'Status::Doing')").option("--label <label>","for kind=label, the label name to add/remove").option("--scoped-prefix <prefix>","for kind=open-backlog/scoped-label","Status").option("--accent <color>","hex/CSS color for the column accent bar").action((e,t,o)=>{try{Ys(n=>{let r=(Array.isArray(n.boards)?n.boards:[]).find(a=>a.id===e);if(!r)throw new Error(`board "${e}" not found`);if(r.columns=r.columns||[],r.columns.find(a=>a.id===t))throw new Error(`column "${t}" already exists on board "${e}"`);let i={id:t,title:o.title,kind:o.kind,scopedPrefix:o.scopedPrefix};if(o.kind==="scoped-label"){if(!o.scoped)throw new Error("--scoped required for kind=scoped-label");i.scopedLabel=o.scoped}if(o.kind==="label"){if(!o.label)throw new Error("--label required for kind=label");i.mapsToLabel=o.label}return o.accent&&(i.accent=o.accent),r.columns.push(i),`column "${t}" added to board "${e}"`})}catch(n){console.log(Pe.red(` ${n.message}`)),process.exit(1)}});Lc.command("remove <boardId> <columnId>").alias("rm").description("remove a column from a board").action((e,t)=>{try{Ys(o=>{let s=(Array.isArray(o.boards)?o.boards:[]).find(i=>i.id===e);if(!s)throw new Error(`board "${e}" not found`);let r=(s.columns||[]).length;if(s.columns=(s.columns||[]).filter(i=>i.id!==t),s.columns.length===r)throw new Error(`column "${t}" not found`);return`column "${t}" removed from board "${e}"`})}catch(o){console.log(Pe.red(` ${o.message}`)),process.exit(1)}});Io.command("remove <id>").alias("rm").description("remove a board from agentx.json").action(e=>{try{Ys(t=>{if(!Array.isArray(t.boards)||t.boards.length===0)throw new Error("no boards configured");let o=t.boards.length;if(t.boards=t.boards.filter(n=>n.id!==e),t.boards.length===o)throw new Error(`board "${e}" not found`);return`removed board "${e}"`})}catch(t){console.log(Pe.red(` ${t.message}`)),process.exit(1)}});function Ys(e){let{summary:t,backupPath:o}=vt(n=>e(n));console.log(Pe.green(`
|
|
@@ -757,7 +757,7 @@ Restart the daemon to activate.
|
|
|
757
757
|
dist/
|
|
758
758
|
*.log
|
|
759
759
|
.env
|
|
760
|
-
`}import{Command as gS}from"commander";import Ke from"chalk";import{writeFileSync as mS,mkdirSync as uS,existsSync as Gi,readFileSync as Fh,appendFileSync as qh}from"fs";import{resolve as ms,dirname as Bh}from"path";import{homedir as pl}from"os";var Jh=new gS().name("completion").description("generate a shell completion script for agentx").option("-s, --shell <shell>","shell to generate completion for: zsh|bash|fish","zsh").option("-i, --install","install the completion script to a default path for the shell").option("-o, --output <path>","write the script to this path (overrides default install path)").option("-y, --yes","skip confirmation prompts (non-interactive)").action(async e=>{let{buildProgram:t}=await import("./program-
|
|
760
|
+
`}import{Command as gS}from"commander";import Ke from"chalk";import{writeFileSync as mS,mkdirSync as uS,existsSync as Gi,readFileSync as Fh,appendFileSync as qh}from"fs";import{resolve as ms,dirname as Bh}from"path";import{homedir as pl}from"os";var Jh=new gS().name("completion").description("generate a shell completion script for agentx").option("-s, --shell <shell>","shell to generate completion for: zsh|bash|fish","zsh").option("-i, --install","install the completion script to a default path for the shell").option("-o, --output <path>","write the script to this path (overrides default install path)").option("-y, --yes","skip confirmation prompts (non-interactive)").action(async e=>{let{buildProgram:t}=await import("./program-3TO4YAY7.js"),o=await t(),n=String(e.shell||"zsh").toLowerCase(),s;switch(n){case"zsh":s=hS(o);break;case"bash":s=kS(o);break;case"fish":s=vS(o);break;default:console.error(Ke.red(`unsupported shell: ${n}`)),console.error(Ke.dim("supported: zsh, bash, fish")),process.exit(1)}if(!e.install&&!e.output){process.stdout.write(s);return}let r=e.output?ms(String(e.output)):pS(n);uS(Bh(r),{recursive:!0}),mS(r,s),console.log(Ke.green(`\u2713 Wrote ${n} completion to:`)),console.log(` ${r}`),console.log(),fS(n,r,!!e.yes)});function pS(e){let t=pl();switch(e){case"zsh":return ms(t,".zsh","completions","_agentx");case"bash":return ms(t,".bash_completion.d","agentx.bash");case"fish":return ms(t,".config","fish","completions","agentx.fish");default:throw new Error(`unsupported shell: ${e}`)}}function fS(e,t,o){if(e==="zsh"){let n=ms(pl(),".zshrc"),s=Bh(t),r=`fpath=(${s} $fpath)`,i="autoload -U compinit && compinit",a=Gi(n)?Fh(n,"utf-8"):"",c=!a.includes(s),l=!/autoload\s+-U\s+compinit/.test(a);if(!c&&!l){console.log(Ke.dim("~/.zshrc already has fpath + compinit wired up. Run `exec zsh` to pick up the new script."));return}if(o&&Gi(n)){let g=["","# agentx completion"];c&&g.push(r),l&&g.push(i),qh(n,g.join(`
|
|
761
761
|
`)+`
|
|
762
762
|
`),console.log(Ke.green("\u2713 Patched ~/.zshrc")),console.log(Ke.dim("Reload your shell: exec zsh"));return}console.log(Ke.dim("Add this to ~/.zshrc if not already present:")),c&&console.log(Ke.cyan(` ${r}`)),l&&console.log(Ke.cyan(` ${i}`)),console.log(),console.log(Ke.dim("Then reload: exec zsh")),console.log(Ke.dim("Or rerun with --yes to auto-patch ~/.zshrc"));return}if(e==="bash"){let n=ms(pl(),".bashrc"),s=`[ -f "${t}" ] && source "${t}"`;if((Gi(n)?Fh(n,"utf-8"):"").includes(t)){console.log(Ke.dim("~/.bashrc already sources this file. Start a new shell to pick it up."));return}if(o&&Gi(n)){qh(n,`
|
|
763
763
|
# agentx completion
|
|
@@ -773,4 +773,4 @@ ${xS({...t,category:e.category})}`),g&&a.set(l.id,g)}let c=`https://github.com/$
|
|
|
773
773
|
${o.url}`)}catch(t){console.error(ps.red(` ${t.message}`)),process.exitCode=1}});ar.command("draft <file>").description("turn a draft JSON file ({category, title, fields}) into a pre-filled issue form link; '-' reads stdin").requiredOption("--model <model>","the model that wrote the draft").option("--models <pathOrUrl>","model list to read",hl).action(async(e,t)=>{try{let o=await $l(t.models);if(!yl(t.model,o)){console.log(wl(t.model,o)),process.exitCode=1;return}let n=e==="-"?await ES():await AS(e,"utf8"),s=JSON.parse(n),{version:r}=await yr(),i=Yh({...s,fields:s.fields??{}},{model:t.model,version:r??"unknown"});console.log(i)}catch(o){console.error(ps.red(` ${o.message}`)),process.exitCode=1}});async function ES(){let e=[];for await(let t of process.stdin)e.push(t);return Buffer.concat(e).toString("utf8")}async function HM(){let e=await yr(),t=new CS().name("agentx").description("self-hosted multi-agent orchestrator \u2014 Telegram, WhatsApp, crons, A2A mesh, wiki knowledge").version(e.version||"1.0.0","-v, --version","display the version number");t.hook("preAction",(o,n)=>{let s=cm(n);lm(s)&&am("cli",s)});for(let o of[Xm,Vm,Zn,jn,zo,Rt,Hg,Yo,Hr,ro,ao,gn,uf,Ro,Gr,fh,Eo,Pu,Mu,Hu,Us,np,ap,_u,dp,gp,Op,Np,jp,Et,mn,kh,Ts,ko,Jh,ar])t.addCommand(o);for(let o of Xh)t.addCommand(o,{hidden:!0});return t.addHelpText("after",()=>{let o=Xh.map(n=>n.name()).sort();return["",`Advanced (${o.length}, hidden \u2014 run \`agentx <command> --help\` for any of them):`,OS(o,74," "),""].join(`
|
|
774
774
|
`)}),t}var Xh=[J,os,lo,Ut,Ns,zn,Ve,co,Io,ln,is,as,So,ut,Fn,ri,bi,cn,mh,uh,so,xt,Am,Xo,rr,Ja,_o,yt,Mn,Ga,Li,Zt,go,jh,_h,$h];function OS(e,t,o){let n=[],s="";for(let r of e)s&&s.length+r.length+2>t&&(n.push(o+s),s=""),s+=(s?", ":"")+r;return s&&n.push(o+s),n.join(`
|
|
775
775
|
`)}export{HM as buildProgram};
|
|
776
|
-
//# sourceMappingURL=program-
|
|
776
|
+
//# sourceMappingURL=program-3TO4YAY7.js.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{a,b,c,d,e,f,g}from"./chunk-5HLD66CW.js";import"./chunk-IS2IWRP7.js";import"./chunk-QIJLFPWE.js";export{c as DEFAULT_OPEN_URL,d as MAX_PAYLOAD_BYTES,f as PushAdapter,g as PushRelayAdapter,e as buildPushPayload,b as isRemoteDevice,a as remoteDeviceId};
|
|
2
|
+
//# sourceMappingURL=push-PRFZMCIK.js.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{a,b,c,d,e,f,g,h,i}from"./chunk-TEM7IIDC.js";import"./chunk-WCXXXAHV.js";import"./chunk-JWDJMFU6.js";import"./chunk-5HLD66CW.js";import"./chunk-IS2IWRP7.js";import"./chunk-QIJLFPWE.js";export{i as PushRosterSync,a as ROSTER_CHECK_MS,b as ROSTER_RESEND_MS,h as handlePushBridge,g as peerOrigins,d as pushBridgeCaller,c as pushOrigin,f as relayTokenProblem,e as scopedPushTarget};
|
|
2
|
+
//# sourceMappingURL=push-bridge-HNQG2TOI.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentix-cli",
|
|
3
|
-
"version": "0.137.
|
|
3
|
+
"version": "0.137.3",
|
|
4
4
|
"description": "AI operations layer for small and medium businesses. Routes Telegram, WhatsApp, GitLab, GitHub, crons, webhooks, and mesh tasks to AI agents. Web wizard for operators, CLI for engineers. Self-hosted.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
package/dist/chunk-GFKLKPPY.js
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
var $=[1e3,3e3,8e3],w=2e4;function b(e){let t=e,n=t?.message??String(e),s=t?.cause;return s?.code?`${n} code=${s.code}`:s?.message&&s.message!==n?`${n} (${s.message})`:n}var A=new Set(["ECONNREFUSED","ETIMEDOUT","ENETUNREACH","EHOSTUNREACH","EADDRNOTAVAIL","ENOTFOUND","EAI_AGAIN","UND_ERR_CONNECT_TIMEOUT"]);function M(e){let t=e?.cause?.code;return!!t&&A.has(t)}function N(e){return e===429||e===503}function m(e){return/^[\x20-\x7E]*$/.test(e)?e:`=?UTF-8?B?${Buffer.from(e,"utf8").toString("base64")}?=`}function D(e){let t=e.split(`
|
|
2
|
-
`),n=(t[0]??"").trim(),s=t.slice(1).join(`
|
|
3
|
-
`).trim();return s?n.length===0||n.length>80?{body:e.trim()}:{title:n,body:s}:{body:e.trim()}}var y=class{name="ntfy";config;log;constructor(t,n=console.error.bind(console,"[ntfy]")){this.config=t,this.log=n}async start(){this.log(`ntfy: ready (${this.serverUrl()}/${this.config.topic})`)}async stop(){}onMessage(t){}serverUrl(){return(this.config.server||"https://ntfy.sh").replace(/\/+$/,"")}async send(t){let n=(t.chatId||"").trim(),s=n&&n!=="default"?n:this.config.topic;if(!s)throw new Error("ntfy: no topic (pass chatId, or set channels.ntfy.topic)");let f=D(t.text||""),p=f.title||this.config.defaultTitle||"AgentX",c={"Content-Type":"text/plain; charset=utf-8",Title:m(p),Priority:String(this.config.defaultPriority??3)};if(this.config.token&&(c.Authorization=`Bearer ${this.config.token}`),t.parseMode==="markdown"&&(c.Markdown="yes"),t.buttons?.length){let i=t.buttons.slice(0,3).map(r=>`view, ${r.label.replace(/[,;]/g," ")}, ${r.url}`).join("; ");c.Actions=m(i)}let E=`${this.serverUrl()}/${encodeURIComponent(s)}`,T=f.body||t.text||"",d=this.config.retryDelaysMs??$,u=d.length+1,h=Date.now()+(this.config.sendTimeoutMs??w);for(let i=1;;i++){let r,a,l=!1;try{let o=AbortSignal.timeout(Math.max(1,h-Date.now()));r=await fetch(E,{method:"POST",headers:c,body:T,signal:o})}catch(o){a=new Error(`ntfy: ${b(o)}`,{cause:o}),l=M(o)}if(r){if(r.ok)return(await r.json().catch(()=>null))?.id;let o=await r.text().catch(()=>"");a=new Error(`ntfy: ${r.status} ${o.slice(0,200)}`),l=N(r.status)}let g=d[i-1];if(!l||i>=u||Date.now()+g>=h)throw this.log(`ntfy: send failed after ${i} attempt(s): ${a.message}`),a;this.log(`ntfy: attempt ${i}/${u} failed (${a.message}); retrying in ${g} ms`),await new Promise(o=>setTimeout(o,g))}}};export{b as a,D as b,y as c};
|
|
4
|
-
//# sourceMappingURL=chunk-GFKLKPPY.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/channels/ntfy.ts"],"sourcesContent":["import type { ChannelAdapter, IncomingMessage, OutgoingMessage } from \"./types\"\n\n// --- ntfy channel — push notifications to the operator's phone ---\n//\n// Outbound only. ntfy has an inbound side (SSE subscribe), but nothing here\n// wants it: a push notification is a one-way tap on the shoulder, and replies\n// come back through the channels that already carry conversation (Telegram,\n// WhatsApp) or through POST /ask when Siri asks a follow-up.\n//\n// Registering it as a ChannelAdapter rather than a bespoke notifier is what\n// makes it free everywhere: notifications.destination, POST /send, cron\n// failure pings, and the agent-facing `channel` action all funnel through\n// router.sendOutbound() and already know how to address a channel + chatId.\n//\n// Addressing: `chatId` is the ntfy topic. The literal `\"default\"` (and an\n// empty string) resolve to the configured topic instead. That indirection\n// exists so agents never have to carry the topic in their prompts — on the\n// public ntfy.sh the topic IS the credential, and every caller that funnels\n// through the router (POST /send, channel.reply) requires a non-empty chatId.\n\nexport interface NtfyConfig {\n /** Base URL of the ntfy server. Default is the public ntfy.sh. */\n server?: string\n /** Default topic when an OutgoingMessage carries no chatId. */\n topic: string\n /** Access token for protected topics (ntfy `Authorization: Bearer`). */\n token?: string\n /** 1 (min) .. 5 (max). ntfy's default is 3. */\n defaultPriority?: number\n /** Notification title. Per-message titles override it. */\n defaultTitle?: string\n /** Test hook, not in the config schema: waits between attempts when a\n * publish is safe to repeat. One attempt more than there are entries. */\n retryDelaysMs?: number[]\n /** Test hook, not in the config schema: time limit for one send, all\n * attempts and waits included. */\n sendTimeoutMs?: number\n}\n\nconst DEFAULT_RETRY_DELAYS_MS = [1000, 3000, 8000]\nconst DEFAULT_SEND_TIMEOUT_MS = 20_000\n\n/** `fetch failed` hides the reason in `cause`; surface its code so a log\n * line says ETIMEDOUT or ECONNREFUSED rather than nothing useful (#930). */\nexport function describeFetchError(e: unknown): string {\n const err = e as { message?: string; cause?: { code?: string; message?: string } }\n const message = err?.message ?? String(e)\n const cause = err?.cause\n if (cause?.code) return `${message} code=${cause.code}`\n if (cause?.message && cause.message !== message) return `${message} (${cause.message})`\n return message\n}\n\n// A publish is retried only when the server cannot have stored it: ntfy has\n// no key to drop a repeat, so re-posting after a 500 or a reset mid-response\n// could put the same notification on the phone twice.\n\n/** The connection never opened, so the request never reached the server. */\nconst CONNECT_FAILURE_CODES = new Set([\n \"ECONNREFUSED\", \"ETIMEDOUT\", \"ENETUNREACH\", \"EHOSTUNREACH\", \"EADDRNOTAVAIL\",\n \"ENOTFOUND\", \"EAI_AGAIN\", \"UND_ERR_CONNECT_TIMEOUT\",\n])\n\nfunction isRetryableFetchError(e: unknown): boolean {\n const code = (e as { cause?: { code?: string } })?.cause?.code\n return !!code && CONNECT_FAILURE_CODES.has(code)\n}\n\n/** 429 and 503 say the server turned the request away without handling it. */\nfunction isRetryableStatus(status: number): boolean {\n return status === 429 || status === 503\n}\n\n/**\n * ntfy requires header values to be ASCII. Titles and tags routinely aren't\n * (French accents, Arabic), so non-ASCII values go out as RFC 2047 encoded\n * words, which ntfy decodes. Pure-ASCII values are passed through untouched\n * so the common case stays readable in logs.\n */\nfunction encodeHeader(value: string): string {\n // eslint-disable-next-line no-control-regex\n if (/^[\\x20-\\x7E]*$/.test(value)) return value\n return `=?UTF-8?B?${Buffer.from(value, \"utf8\").toString(\"base64\")}?=`\n}\n\n/** First line of the body, when it reads like a title (short, no trailing\n * punctuation that implies a sentence continues). Lets an agent write a\n * single blob and still get a well-formed notification. */\nexport function splitTitle(text: string): { title?: string; body: string } {\n const lines = text.split(\"\\n\")\n const first = (lines[0] ?? \"\").trim()\n const rest = lines.slice(1).join(\"\\n\").trim()\n if (!rest) return { body: text.trim() }\n if (first.length === 0 || first.length > 80) return { body: text.trim() }\n return { title: first, body: rest }\n}\n\nexport class NtfyAdapter implements ChannelAdapter {\n readonly name = \"ntfy\"\n private config: NtfyConfig\n private log: (...args: unknown[]) => void\n\n constructor(config: NtfyConfig, log: (...args: unknown[]) => void = console.error.bind(console, \"[ntfy]\")) {\n this.config = config\n this.log = log\n }\n\n async start(): Promise<void> {\n this.log(`ntfy: ready (${this.serverUrl()}/${this.config.topic})`)\n }\n\n async stop(): Promise<void> {}\n\n /** No inbound side — the handler is accepted and never invoked, which\n * satisfies router.addChannel() without pretending to poll. */\n onMessage(_handler: (msg: IncomingMessage) => Promise<void>): void {}\n\n private serverUrl(): string {\n return (this.config.server || \"https://ntfy.sh\").replace(/\\/+$/, \"\")\n }\n\n async send(msg: OutgoingMessage): Promise<string | void> {\n const requested = (msg.chatId || \"\").trim()\n const topic = requested && requested !== \"default\" ? requested : this.config.topic\n if (!topic) throw new Error(\"ntfy: no topic (pass chatId, or set channels.ntfy.topic)\")\n\n const split = splitTitle(msg.text || \"\")\n const title = split.title || this.config.defaultTitle || \"AgentX\"\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"text/plain; charset=utf-8\",\n \"Title\": encodeHeader(title),\n \"Priority\": String(this.config.defaultPriority ?? 3),\n }\n if (this.config.token) headers[\"Authorization\"] = `Bearer ${this.config.token}`\n if (msg.parseMode === \"markdown\") headers[\"Markdown\"] = \"yes\"\n\n // URL buttons map onto ntfy's view actions, so an alert can carry\n // \"open the MR\" without the operator hunting for it.\n if (msg.buttons?.length) {\n const actions = msg.buttons\n .slice(0, 3) // ntfy caps at 3\n .map(b => `view, ${b.label.replace(/[,;]/g, \" \")}, ${b.url}`)\n .join(\"; \")\n headers[\"Actions\"] = encodeHeader(actions)\n }\n\n const url = `${this.serverUrl()}/${encodeURIComponent(topic)}`\n const body = split.body || msg.text || \"\"\n const delays = this.config.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS\n const attempts = delays.length + 1\n // Each try can spend several seconds per address on a dead link, so the\n // whole send gets one time limit and callers never wait much past it.\n const deadline = Date.now() + (this.config.sendTimeoutMs ?? DEFAULT_SEND_TIMEOUT_MS)\n\n // A slow or flapping link loses pushes that a few seconds later would\n // go through. Retry only what cannot have been stored (see above).\n for (let attempt = 1; ; attempt++) {\n let res: Response | undefined\n let failure!: Error\n let retryable = false\n try {\n const signal = AbortSignal.timeout(Math.max(1, deadline - Date.now()))\n res = await fetch(url, { method: \"POST\", headers, body, signal })\n } catch (e) {\n failure = new Error(`ntfy: ${describeFetchError(e)}`, { cause: e })\n retryable = isRetryableFetchError(e)\n }\n if (res) {\n if (res.ok) {\n const json = await res.json().catch(() => null) as { id?: string } | null\n return json?.id\n }\n const detail = await res.text().catch(() => \"\")\n failure = new Error(`ntfy: ${res.status} ${detail.slice(0, 200)}`)\n retryable = isRetryableStatus(res.status)\n }\n const wait = delays[attempt - 1]\n if (!retryable || attempt >= attempts || Date.now() + wait >= deadline) {\n this.log(`ntfy: send failed after ${attempt} attempt(s): ${failure.message}`)\n throw failure\n }\n this.log(`ntfy: attempt ${attempt}/${attempts} failed (${failure.message}); retrying in ${wait} ms`)\n await new Promise(r => setTimeout(r, wait))\n }\n }\n}\n"],"mappings":"AAuCA,IAAMA,EAA0B,CAAC,IAAM,IAAM,GAAI,EAC3CC,EAA0B,IAIzB,SAASC,EAAmB,EAAoB,CACrD,IAAMC,EAAM,EACNC,EAAUD,GAAK,SAAW,OAAO,CAAC,EAClCE,EAAQF,GAAK,MACnB,OAAIE,GAAO,KAAa,GAAGD,UAAgBC,EAAM,OAC7CA,GAAO,SAAWA,EAAM,UAAYD,EAAgB,GAAGA,MAAYC,EAAM,WACtED,CACT,CAOA,IAAME,EAAwB,IAAI,IAAI,CACpC,eAAgB,YAAa,cAAe,eAAgB,gBAC5D,YAAa,YAAa,yBAC5B,CAAC,EAED,SAASC,EAAsB,EAAqB,CAClD,IAAMC,EAAQ,GAAqC,OAAO,KAC1D,MAAO,CAAC,CAACA,GAAQF,EAAsB,IAAIE,CAAI,CACjD,CAGA,SAASC,EAAkBC,EAAyB,CAClD,OAAOA,IAAW,KAAOA,IAAW,GACtC,CAQA,SAASC,EAAaC,EAAuB,CAE3C,MAAI,iBAAiB,KAAKA,CAAK,EAAUA,EAClC,aAAa,OAAO,KAAKA,EAAO,MAAM,EAAE,SAAS,QAAQ,KAClE,CAKO,SAASC,EAAWC,EAAgD,CACzE,IAAMC,EAAQD,EAAK,MAAM;AAAA,CAAI,EACvBE,GAASD,EAAM,CAAC,GAAK,IAAI,KAAK,EAC9BE,EAAOF,EAAM,MAAM,CAAC,EAAE,KAAK;AAAA,CAAI,EAAE,KAAK,EAC5C,OAAKE,EACDD,EAAM,SAAW,GAAKA,EAAM,OAAS,GAAW,CAAE,KAAMF,EAAK,KAAK,CAAE,EACjE,CAAE,MAAOE,EAAO,KAAMC,CAAK,EAFhB,CAAE,KAAMH,EAAK,KAAK,CAAE,CAGxC,CAEO,IAAMI,EAAN,KAA4C,CACxC,KAAO,OACR,OACA,IAER,YAAYC,EAAoBC,EAAoC,QAAQ,MAAM,KAAK,QAAS,QAAQ,EAAG,CACzG,KAAK,OAASD,EACd,KAAK,IAAMC,CACb,CAEA,MAAM,OAAuB,CAC3B,KAAK,IAAI,gBAAgB,KAAK,UAAU,KAAK,KAAK,OAAO,QAAQ,CACnE,CAEA,MAAM,MAAsB,CAAC,CAI7B,UAAUC,EAAyD,CAAC,CAE5D,WAAoB,CAC1B,OAAQ,KAAK,OAAO,QAAU,mBAAmB,QAAQ,OAAQ,EAAE,CACrE,CAEA,MAAM,KAAKC,EAA8C,CACvD,IAAMC,GAAaD,EAAI,QAAU,IAAI,KAAK,EACpCE,EAAQD,GAAaA,IAAc,UAAYA,EAAY,KAAK,OAAO,MAC7E,GAAI,CAACC,EAAO,MAAM,IAAI,MAAM,0DAA0D,EAEtF,IAAMC,EAAQZ,EAAWS,EAAI,MAAQ,EAAE,EACjCI,EAAQD,EAAM,OAAS,KAAK,OAAO,cAAgB,SAEnDE,EAAkC,CACtC,eAAgB,4BAChB,MAAShB,EAAae,CAAK,EAC3B,SAAY,OAAO,KAAK,OAAO,iBAAmB,CAAC,CACrD,EAMA,GALI,KAAK,OAAO,QAAOC,EAAQ,cAAmB,UAAU,KAAK,OAAO,SACpEL,EAAI,YAAc,aAAYK,EAAQ,SAAc,OAIpDL,EAAI,SAAS,OAAQ,CACvB,IAAMM,EAAUN,EAAI,QACjB,MAAM,EAAG,CAAC,EACV,IAAIO,GAAK,SAASA,EAAE,MAAM,QAAQ,QAAS,GAAG,MAAMA,EAAE,KAAK,EAC3D,KAAK,IAAI,EACZF,EAAQ,QAAahB,EAAaiB,CAAO,EAG3C,IAAME,EAAM,GAAG,KAAK,UAAU,KAAK,mBAAmBN,CAAK,IACrDO,EAAON,EAAM,MAAQH,EAAI,MAAQ,GACjCU,EAAS,KAAK,OAAO,eAAiBhC,EACtCiC,EAAWD,EAAO,OAAS,EAG3BE,EAAW,KAAK,IAAI,GAAK,KAAK,OAAO,eAAiBjC,GAI5D,QAASkC,EAAU,GAAKA,IAAW,CACjC,IAAIC,EACAC,EACAC,EAAY,GAChB,GAAI,CACF,IAAMC,EAAS,YAAY,QAAQ,KAAK,IAAI,EAAGL,EAAW,KAAK,IAAI,CAAC,CAAC,EACrEE,EAAM,MAAM,MAAMN,EAAK,CAAE,OAAQ,OAAQ,QAAAH,EAAS,KAAAI,EAAM,OAAAQ,CAAO,CAAC,CAClE,OAASC,EAAP,CACAH,EAAU,IAAI,MAAM,SAASnC,EAAmBsC,CAAC,IAAK,CAAE,MAAOA,CAAE,CAAC,EAClEF,EAAY/B,EAAsBiC,CAAC,CACrC,CACA,GAAIJ,EAAK,CACP,GAAIA,EAAI,GAEN,OADa,MAAMA,EAAI,KAAK,EAAE,MAAM,IAAM,IAAI,IACjC,GAEf,IAAMK,EAAS,MAAML,EAAI,KAAK,EAAE,MAAM,IAAM,EAAE,EAC9CC,EAAU,IAAI,MAAM,SAASD,EAAI,UAAUK,EAAO,MAAM,EAAG,GAAG,GAAG,EACjEH,EAAY7B,EAAkB2B,EAAI,MAAM,EAE1C,IAAMM,EAAOV,EAAOG,EAAU,CAAC,EAC/B,GAAI,CAACG,GAAaH,GAAWF,GAAY,KAAK,IAAI,EAAIS,GAAQR,EAC5D,WAAK,IAAI,2BAA2BC,iBAAuBE,EAAQ,SAAS,EACtEA,EAER,KAAK,IAAI,iBAAiBF,KAAWF,aAAoBI,EAAQ,yBAAyBK,MAAS,EACnG,MAAM,IAAI,QAAQC,GAAK,WAAWA,EAAGD,CAAI,CAAC,EAE9C,CACF","names":["DEFAULT_RETRY_DELAYS_MS","DEFAULT_SEND_TIMEOUT_MS","describeFetchError","err","message","cause","CONNECT_FAILURE_CODES","isRetryableFetchError","code","isRetryableStatus","status","encodeHeader","value","splitTitle","text","lines","first","rest","NtfyAdapter","config","log","_handler","msg","requested","topic","split","title","headers","actions","b","url","body","delays","attempts","deadline","attempt","res","failure","retryable","signal","e","detail","wait","r"]}
|
package/dist/chunk-PBP3D2BM.js
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import o from"node:net";import{existsSync as i,readFileSync as c}from"node:fs";import{resolve as r}from"node:path";function p(n){let e=o.getDefaultAutoSelectFamilyAttemptTimeout,t=o.setDefaultAutoSelectFamilyAttemptTimeout;if(typeof e!="function"||typeof t!="function")return;let u=e();return n>u&&t(n),e()}var f=5e3;function d(n=process.cwd()){for(let e of[r(n,"agentx.json"),r(n,".agentx/config.json")])if(i(e)){try{let t=JSON.parse(c(e,"utf-8"))?.node?.connectAttemptTimeoutMs;if(Number.isInteger(t)&&t>=250&&t<=6e4)return t}catch{}break}return f}export{p as a,f as b,d as c};
|
|
2
|
-
//# sourceMappingURL=chunk-PBP3D2BM.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/daemon/net-family.ts"],"sourcesContent":["import net from \"node:net\"\nimport { existsSync, readFileSync } from \"node:fs\"\nimport { resolve } from \"node:path\"\n\n// --- Connect attempt timeout for dual-stack hosts (#930) ---\n//\n// Node 20+ turns on `autoSelectFamily` (happy eyeballs) for every outbound\n// connection, fetch included, and gives each address only 250 ms to connect\n// before moving to the next. On a host that resolves both A and AAAA but has\n// no IPv6 route, the IPv6 attempt fails at once and the IPv4 attempt is then\n// abandoned after 250 ms. A slow uplink whose TCP connect takes longer fails\n// every request with ETIMEDOUT about 300 ms in, while curl from the same\n// host succeeds. Raising the per-attempt timeout keeps happy eyeballs and\n// lets a slow but working link connect.\n\n/**\n * Raise Node's per-address connect attempt timeout to `ms`. Never lowers a\n * larger value already set (for example with\n * `--network-family-autoselection-attempt-timeout`). Returns the value in\n * effect afterwards, or undefined when this Node has no such setting.\n */\nexport function applyConnectAttemptTimeout(ms: number): number | undefined {\n const get = (net as any).getDefaultAutoSelectFamilyAttemptTimeout as (() => number) | undefined\n const set = (net as any).setDefaultAutoSelectFamilyAttemptTimeout as ((v: number) => void) | undefined\n if (typeof get !== \"function\" || typeof set !== \"function\") return undefined\n const current = get()\n if (ms > current) set(ms)\n return get()\n}\n\nexport const DEFAULT_CONNECT_ATTEMPT_TIMEOUT_MS = 5000\n\n/**\n * `node.connectAttemptTimeoutMs` read straight from the config file, for CLI\n * commands that call out on their own and never load the full daemon config.\n * Falls back to the default when there is no file or no valid value.\n */\nexport function readConnectAttemptTimeout(cwd: string = process.cwd()): number {\n for (const p of [resolve(cwd, \"agentx.json\"), resolve(cwd, \".agentx/config.json\")]) {\n if (!existsSync(p)) continue\n try {\n const v = JSON.parse(readFileSync(p, \"utf-8\"))?.node?.connectAttemptTimeoutMs\n if (Number.isInteger(v) && v >= 250 && v <= 60_000) return v\n } catch { /* an invalid file is reported by whatever loads it in full */ }\n break\n }\n return DEFAULT_CONNECT_ATTEMPT_TIMEOUT_MS\n}\n"],"mappings":"AAAA,OAAOA,MAAS,WAChB,OAAS,cAAAC,EAAY,gBAAAC,MAAoB,UACzC,OAAS,WAAAC,MAAe,YAmBjB,SAASC,EAA2BC,EAAgC,CACzE,IAAMC,EAAON,EAAY,yCACnBO,EAAOP,EAAY,yCACzB,GAAI,OAAOM,GAAQ,YAAc,OAAOC,GAAQ,WAAY,OAC5D,IAAMC,EAAUF,EAAI,EACpB,OAAID,EAAKG,GAASD,EAAIF,CAAE,EACjBC,EAAI,CACb,CAEO,IAAMG,EAAqC,IAO3C,SAASC,EAA0BC,EAAc,QAAQ,IAAI,EAAW,CAC7E,QAAWC,IAAK,CAACT,EAAQQ,EAAK,aAAa,EAAGR,EAAQQ,EAAK,qBAAqB,CAAC,EAC/E,GAAKV,EAAWW,CAAC,EACjB,IAAI,CACF,IAAMC,EAAI,KAAK,MAAMX,EAAaU,EAAG,OAAO,CAAC,GAAG,MAAM,wBACtD,GAAI,OAAO,UAAUC,CAAC,GAAKA,GAAK,KAAOA,GAAK,IAAQ,OAAOA,CAC7D,MAAE,CAAuE,CACzE,MAEF,OAAOJ,CACT","names":["net","existsSync","readFileSync","resolve","applyConnectAttemptTimeout","ms","get","set","current","DEFAULT_CONNECT_ATTEMPT_TIMEOUT_MS","readConnectAttemptTimeout","cwd","p","v"]}
|
package/dist/ntfy-W56TUC5C.js
DELETED
package/dist/push-B2GNI3VQ.js
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{a,b,c,d,e,f,g}from"./chunk-Q72TK3DO.js";import"./chunk-GFKLKPPY.js";import"./chunk-QIJLFPWE.js";export{c as DEFAULT_OPEN_URL,d as MAX_PAYLOAD_BYTES,f as PushAdapter,g as PushRelayAdapter,e as buildPushPayload,b as isRemoteDevice,a as remoteDeviceId};
|
|
2
|
-
//# sourceMappingURL=push-B2GNI3VQ.js.map
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{a,b,c,d,e,f,g,h,i}from"./chunk-DIE6XXJF.js";import"./chunk-WCXXXAHV.js";import"./chunk-JWDJMFU6.js";import"./chunk-Q72TK3DO.js";import"./chunk-GFKLKPPY.js";import"./chunk-QIJLFPWE.js";export{i as PushRosterSync,a as ROSTER_CHECK_MS,b as ROSTER_RESEND_MS,h as handlePushBridge,g as peerOrigins,d as pushBridgeCaller,c as pushOrigin,f as relayTokenProblem,e as scopedPushTarget};
|
|
2
|
-
//# sourceMappingURL=push-bridge-F4PCXWJF.js.map
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|