agentix-cli 0.139.0 → 0.139.1
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-3W2BHUGM.js +11 -0
- package/dist/chunk-3W2BHUGM.js.map +1 -0
- package/dist/{chunk-34XYHSL6.js → chunk-FUJ4KEWS.js} +4 -4
- package/dist/cli.js +1 -1
- package/dist/{follow-up-tool-QRECR3AH.js → follow-up-tool-M2KCLRQQ.js} +2 -2
- package/dist/index.js +1 -1
- package/dist/{program-7VHY5RVZ.js → program-X6EKIO24.js} +4 -4
- package/dist/{tool-UPXUWIMM.js → tool-DFPOFXGA.js} +2 -2
- package/dist/{tool-5WCC7SKQ.js → tool-GJ75MWV5.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-Q4XP5TBI.js +0 -11
- package/dist/chunk-Q4XP5TBI.js.map +0 -1
- /package/dist/{chunk-34XYHSL6.js.map → chunk-FUJ4KEWS.js.map} +0 -0
- /package/dist/{follow-up-tool-QRECR3AH.js.map → follow-up-tool-M2KCLRQQ.js.map} +0 -0
- /package/dist/{program-7VHY5RVZ.js.map → program-X6EKIO24.js.map} +0 -0
- /package/dist/{tool-UPXUWIMM.js.map → tool-DFPOFXGA.js.map} +0 -0
- /package/dist/{tool-5WCC7SKQ.js.map → tool-GJ75MWV5.js.map} +0 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import{a as w,b as f}from"./chunk-SHEUCITK.js";import{randomUUID as k}from"crypto";function p(r){return r.status&&r.status!=="ringing"?", waiting_since=NULL, told_at=NULL":""}var E=["ringing","answered","later"],A=`id, agent_id AS agentId, kind, reason, urgency, status, created_at AS createdAt,
|
|
2
|
+
ringing_since AS ringingSince, answered_at AS answeredAt, ended_at AS endedAt,
|
|
3
|
+
ring_again_at AS ringAgainAt, note, summary, channel, chat_id AS chatId`,y=class{constructor(t){this.db=t;t.exec(`CREATE TABLE IF NOT EXISTS calls (
|
|
4
|
+
id TEXT PRIMARY KEY, agent_id TEXT NOT NULL, reason TEXT NOT NULL,
|
|
5
|
+
urgency TEXT NOT NULL, status TEXT NOT NULL, created_at INTEGER NOT NULL,
|
|
6
|
+
ringing_since INTEGER, answered_at INTEGER, ended_at INTEGER,
|
|
7
|
+
ring_again_at INTEGER, note TEXT, summary TEXT);
|
|
8
|
+
CREATE INDEX IF NOT EXISTS calls_recent ON calls(created_at DESC);`);let e=new Set(t.prepare("PRAGMA table_info(calls)").all().map(n=>n.name));e.has("kind")||t.exec("ALTER TABLE calls ADD COLUMN kind TEXT NOT NULL DEFAULT 'voice'"),e.has("channel")||t.exec("ALTER TABLE calls ADD COLUMN channel TEXT"),e.has("chat_id")||t.exec("ALTER TABLE calls ADD COLUMN chat_id TEXT"),e.has("waiting_since")||t.exec("ALTER TABLE calls ADD COLUMN waiting_since INTEGER"),e.has("told_at")||t.exec("ALTER TABLE calls ADD COLUMN told_at INTEGER")}waiting(){let t=this.db.prepare("SELECT id, waiting_since AS since, told_at AS toldAt FROM calls WHERE waiting_since IS NOT NULL AND status='ringing'").all();return new Map(t.map(({id:e,since:n,toldAt:i})=>[e,{since:n,toldAt:i??n}]))}setWaiting(t,e){this.db.prepare("UPDATE calls SET waiting_since=?, told_at=? WHERE id=?").run(e?.since??null,e?.toldAt??null,t)}insert(t){this.db.prepare(`INSERT INTO calls (id, agent_id, kind, reason, urgency, status, created_at, ringing_since,
|
|
9
|
+
answered_at, ended_at, ring_again_at, note, summary, channel, chat_id) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`).run(t.id,t.agentId,t.kind,t.reason,t.urgency,t.status,t.createdAt,t.ringingSince,t.answeredAt,t.endedAt,t.ringAgainAt,t.note,t.summary,t.channel,t.chatId)}get(t){return this.db.prepare(`SELECT ${A} FROM calls WHERE id=?`).get(t)}update(t,e){let n={reason:"reason",urgency:"urgency",status:"status",ringingSince:"ringing_since",answeredAt:"answered_at",endedAt:"ended_at",ringAgainAt:"ring_again_at",note:"note",summary:"summary"},i=Object.keys(e).filter(a=>a in n);if(i.length){let a=i.map(s=>`${n[s]}=?`).join(", ")+p(e);this.db.prepare(`UPDATE calls SET ${a} WHERE id=?`).run(...i.map(s=>e[s]??null),t)}return this.get(t)}transition(t,e,n){let i=Array.isArray(e)?e:[e],a={status:"status",ringingSince:"ringing_since",answeredAt:"answered_at",endedAt:"ended_at",ringAgainAt:"ring_again_at",note:"note",summary:"summary"},s=Object.keys(n).filter(l=>l in a),o=s.map(l=>`${a[l]}=?`).join(", ")+p(n),u=i.map(()=>"?").join(",");return this.db.prepare(`UPDATE calls SET ${o} WHERE id=? AND status IN (${u})`).run(...s.map(l=>n[l]??null),t,...i).changes?this.get(t):void 0}list(t={}){let e=Math.min(Math.max(t.limit??50,1),200),n=[],i=[];t.status?.length&&(n.push(`status IN (${t.status.map(()=>"?").join(",")})`),i.push(...t.status)),t.kind&&(n.push("kind=?"),i.push(t.kind));let a=n.length?`WHERE ${n.join(" AND ")}`:"";return this.db.prepare(`SELECT ${A} FROM calls ${a} ORDER BY created_at DESC, rowid DESC LIMIT ?`).all(...i,e)}countSince(t,e){return this.db.prepare("SELECT COUNT(*) AS n FROM calls WHERE agent_id=? AND created_at>?").get(t,e).n}};var T=200,_=1e4,I=10,C=240;function x(r=process.env){return r.AGENTX_TASK_ID?{"X-AgentX-Task":r.AGENTX_TASK_ID}:r.AGENTX_CHANNEL&&r.AGENTX_CHAT_ID?{"X-AgentX-Channel":r.AGENTX_CHANNEL,"X-AgentX-Chat":r.AGENTX_CHAT_ID}:{}}function $(r){return`[Call answered] The owner just picked up the voice call you asked for. You called because: "${r.reason}". Speak first: greet them in a few words and say why you called, then let them answer.`}var D="[Call ended] The owner hung up. In two or three plain sentences, summarise what was said and decided on this call, for your records. Reply with the summary only.",S=class{constructor(t){this.deps=t;this.waiting=t.store.waiting()}widgetSeenAt=0;widget={};waiting;setWaiting(t,e){if(e)this.waiting.set(t,e);else if(!this.waiting.delete(t))return;this.deps.store.setWaiting(t,e)}now(){return this.deps.now?.()??Date.now()}name(t){return this.deps.agentName(t)??t}async request(t,e={}){let n=String(t.agentId??"").trim(),i=String(t.reason??"").replace(/\s+/g," ").trim(),a=t.urgency==="urgent"?"urgent":"normal",s=t.kind==="camera"?"camera":"voice";if(t.kind!==void 0&&t.kind!=="voice"&&t.kind!=="camera")return{ok:!1,status:400,error:"kind must be voice or camera"};if(!n||!i)return{ok:!1,status:400,error:"Required: agentId and reason"};if(i.length>T)return{ok:!1,status:413,error:`reason is over ${T} characters; keep it to one line`};if(this.deps.agentName(n)===null)return{ok:!1,status:404,error:`Unknown agent: ${n}`};if(!this.deps.isRunningTurn(n,e))return{ok:!1,status:403,error:`No running turn of ${n} placed this call. An agent calls from inside its own run.`};let o=this.deps.config();if(!o.allow.includes("*")&&!o.allow.includes(n))return{ok:!1,status:403,error:`${n} ${s==="camera"?"may not ask to see through the owner's camera":"may not call the owner"}. Add it to calls.allow in agentx.json (or: agentx call allow ${n}).`};if(await this.sweep(),this.deps.store.list({status:E,limit:200}).some(m=>m.agentId===n))return{ok:!1,status:409,error:`${n} already has a call in progress`};let u=this.now();if(this.deps.store.countSince(n,u-36e5)>=o.maxPerHour)return{ok:!1,status:429,error:`${n} has placed ${o.maxPerHour} calls in the last hour (calls.maxPerHour)`};let g=s==="camera"?this.deps.turnSession?.(n,e)??e:{},l={id:`call-${k().slice(0,8)}`,agentId:n,kind:s,reason:i,urgency:a,status:"ringing",createdAt:u,ringingSince:u,answeredAt:null,endedAt:null,ringAgainAt:null,note:null,summary:null,channel:g.channel&&g.chatId?g.channel:null,chatId:g.channel&&g.chatId?g.chatId:null};this.deps.store.insert(l),this.deps.log?.(`[calls] ${n} asks ${s==="camera"?"to see":"for a call"} (${a}): ${i}`);let d=await this.ring(l),h=this.deps.store.get(l.id);return d!=="waiting"?{ok:!0,call:h,rang:d}:this.waiting.has(l.id)?{ok:!0,call:h,rang:d,busy:this.busyInfo(l)}:{ok:!0,call:h,rang:"widget"}}async cancel(t,e={}){let n=String(t.agentId??"").trim();if(!n)return{ok:!1,status:400,error:"Required: agentId"};if(!this.deps.isRunningTurn(n,e))return{ok:!1,status:403,error:`No running turn of ${n} asked for this. An agent withdraws its own call from inside its run.`};let i=this.deps.store.list({status:["ringing","later"],kind:"voice",limit:200}).find(s=>s.agentId===n);if(!i)return{ok:!1,status:404,error:`${n} has no call waiting or ringing`};let a=this.deps.store.transition(i.id,["ringing","later"],{status:"ended",endedAt:this.now(),ringingSince:null,ringAgainAt:null,note:N});return a?(this.setWaiting(i.id,null),this.deps.log?.(`[calls] ${i.id} withdrawn by ${n}`),{ok:!0,call:a}):{ok:!1,status:409,error:`call is ${this.deps.store.get(i.id)?.status}`}}async ring(t){let e=(this.deps.focus??w)();return e.active&&t.urgency!=="urgent"?(this.deps.store.transition(t.id,"ringing",{status:"missed",ringingSince:null,note:f(e)})&&await this.notice(t,this.missedTitle(t)),!1):t.kind==="camera"?(await this.notice(t,`${this.name(t.agentId)} wants to see through your camera`,`Open the phone app and tap Show within ${this.deps.config().ringSeconds} s.`),"notify"):this.widgetAlive()?!this.widget.busy&&!this.otherCalls(t).length?"widget":(this.startWaiting(t),await this.callWaiting(t),"waiting"):(await this.notice(t,`${this.name(t.agentId)} is calling`,`Open AgentX Voice to answer within ${this.deps.config().ringSeconds} s.`),"notify")}async notice(t,e,n){try{await this.deps.alert({title:e,message:n?`${t.reason}
|
|
10
|
+
${n}`:t.reason,urgent:t.urgency==="urgent",from:t.agentId})}catch(i){this.deps.log?.(`[calls] notice for ${t.id} failed: ${i?.message??i}`)}}startWaiting(t){let e=this.now();this.setWaiting(t.id,{since:e,toldAt:e}),this.deps.log?.(`[calls] ${t.id} from ${t.agentId} waits: the widget is busy`)}otherCalls(t){return this.deps.store.list({status:["ringing","answered"],kind:"voice",limit:200}).filter(e=>e.id!==t.id).reverse()}async callWaiting(t){let e=this.deps.config().busy;await this.notice(t,e.title.replaceAll("{agent}",this.name(t.agentId)),e.notice)}busyInfo(t){let e=this.deps.config().busy,{since:n,toldAt:i}=this.waiting.get(t.id)??{since:this.now(),toldAt:this.now()},a=this.otherCalls(t),s=a.find(d=>d.status==="answered"),o=a.find(d=>d.id===this.widget.showing)??a.find(d=>!this.waiting.has(d.id)),u=n+e.maxWaitMinutes*6e4+this.deps.config().ringSeconds*1e3,g=i+e.retryMinutes*6e4,l=n+e.maxWaitMinutes*6e4;return{why:s?"call":o?"ringing":"voice",with:s?this.name(s.agentId):o?this.name(o.agentId):null,remindAt:g<l?g:null,waitsUntil:u}}missedTitle(t){return t.kind==="camera"?`${this.name(t.agentId)} asked to see through your camera`:`Missed call from ${this.name(t.agentId)}`}widgetAlive(){return this.now()-this.widgetSeenAt<_}async ringing(t={}){return this.widgetSeenAt=this.now(),this.widget=t,await this.hold(t),await this.sweep(),this.deps.store.list({status:["ringing"],kind:"voice",limit:20}).reverse()}async hold(t){let e=this.now(),n=this.deps.config().busy,i=[],a=this.deps.store.list({status:["ringing"],kind:"voice",limit:20});for(let s of[...this.waiting.keys()])a.some(o=>o.id===s)||this.setWaiting(s,null);for(let s of a){if(s.id===t.showing){this.waiting.has(s.id)&&(this.setWaiting(s.id,null),this.deps.store.transition(s.id,"ringing",{ringingSince:e}));continue}if(!t.busy)continue;let o=this.waiting.get(s.id);if(!(o&&e-o.since>=n.maxWaitMinutes*6e4)&&this.deps.store.transition(s.id,"ringing",{ringingSince:e})){if(!o)this.startWaiting(s);else if(e-o.toldAt>=n.retryMinutes*6e4)this.setWaiting(s.id,{since:o.since,toldAt:e});else continue;i.push(s)}}for(let s of i)await this.callWaiting(s)}async asking(){return await this.sweep(),this.deps.store.list({status:["ringing"],kind:"camera",limit:20}).reverse()}async sweep(){let t=this.now(),e=this.deps.config().ringSeconds*1e3,n=this.deps.config().maxCallMinutes*6e4;for(let i of this.deps.store.list({status:["ringing","later","answered"],limit:200}))if(i.status==="ringing"&&i.ringingSince!==null&&t-i.ringingSince>=e){if(!this.deps.store.transition(i.id,"ringing",{status:"missed",ringingSince:null,note:this.waiting.has(i.id)?"widget busy":"not answered"}))continue;this.deps.log?.(`[calls] ${i.id} from ${i.agentId} missed`),await this.notice(i,this.missedTitle(i))}else if(i.status==="answered"&&i.answeredAt!==null&&t-i.answeredAt>=n){if(!this.deps.store.transition(i.id,"answered",{status:"ended",endedAt:t,note:"no hang-up"}))continue;this.deps.log?.(`[calls] ${i.id} from ${i.agentId} ended: no hang-up in ${this.deps.config().maxCallMinutes} min`)}else if(i.status==="later"&&i.ringAgainAt!==null&&t>=i.ringAgainAt){let a=this.deps.store.transition(i.id,"later",{status:"ringing",ringingSince:t,ringAgainAt:null});if(!a)continue;this.setWaiting(i.id,null),await this.ring(a)}}answer(t){let e=this.deps.store.get(t);if(!e)return c(t);let n=this.deps.store.transition(t,"ringing",{status:"answered",answeredAt:this.now(),ringingSince:null});return n?(this.deps.log?.(`[calls] ${t} answered (${e.agentId})`),n.kind==="camera"?{ok:!0,call:n}:{ok:!0,call:n,opener:$(n)}):{ok:!1,status:409,error:`call is ${this.deps.store.get(t)?.status}, not ringing`}}decline(t){if(!this.deps.store.get(t))return c(t);let n=this.deps.store.transition(t,["ringing","later"],{status:"declined",endedAt:this.now(),ringingSince:null,ringAgainAt:null});return n?{ok:!0,call:n}:{ok:!1,status:409,error:`call is ${this.deps.store.get(t)?.status}`}}later(t,e){if(!this.deps.store.get(t))return c(t);let i=e===void 0?I:Number(e);if(!Number.isInteger(i)||i<1||i>C)return{ok:!1,status:400,error:`minutes must be a whole number from 1 to ${C}`};let a=this.deps.store.transition(t,"ringing",{status:"later",ringingSince:null,ringAgainAt:this.now()+i*6e4});return a?{ok:!0,call:a}:{ok:!1,status:409,error:`call is ${this.deps.store.get(t)?.status}, not ringing`}}hangup(t){let e=this.deps.store.get(t);if(!e)return c(t);let n=this.deps.store.transition(t,"answered",{status:"ended",endedAt:this.now()});if(!n)return{ok:!1,status:409,error:`call is ${e.status}, not answered`};this.deps.log?.(`[calls] ${t} ended (${e.agentId})`);let i=this.deps.summarize;if(n.kind==="camera"||!this.deps.config().summary||!i)return{ok:!0,call:n};let a=(async()=>{try{let s=(await i(n))?.trim();if(!s)return;let o=this.deps.store.update(t,{summary:s});this.deps.file?.(o,s)}catch(s){this.deps.log?.(`[calls] summary for ${t} failed: ${s?.message??s}`)}})();return{ok:!0,call:n,summarized:a}}endCamera(t,e){let n=this.deps.store.get(t);if(!n||n.kind!=="camera")return!1;let i=this.deps.store.transition(t,"answered",{status:"ended",endedAt:this.now(),note:e??null});return i&&this.deps.log?.(`[calls] ${t} ended (${n.agentId}): ${e??"share over"}`),!!i}get(t){return this.deps.store.get(t)}list(t={}){return this.deps.store.list(t)}},N="withdrawn by the caller";function W(r,t=Date.now(),e){let n=a=>`${new Date(a).toLocaleTimeString("en-GB",{hour:"2-digit",minute:"2-digit",hour12:!1,timeZoneName:"short",timeZone:e})} (in ${Math.max(1,Math.round((a-t)/6e4))} min)`;return`${r.why==="call"?`The owner is busy in another call${r.with?` (with ${r.with})`:""}.`:r.why==="ringing"?`The owner is busy: another call${r.with?` (from ${r.with})`:""} is ringing.`:"The owner is busy on the voice widget."} They were told you are calling, and why. Your call waits and rings by itself as soon as they are free: do not call again. `+(r.remindAt!==null?`Still busy at ${n(r.remindAt)}, they are told again that you are waiting. `:"")+`It waits until ${n(r.waitsUntil)} at most, then counts as a missed call.`}var c=r=>({ok:!1,status:404,error:`No such call: ${r}`});export{y as a,x as b,D as c,S as d,W as e};
|
|
11
|
+
//# sourceMappingURL=chunk-3W2BHUGM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/calls/service.ts","../src/calls/store.ts"],"sourcesContent":["// --- An agent rings the owner (#321) ---\n//\n// An agent that needs the owner directly asks for a call. It rings on the\n// desktop widget (AgentX Voice), which polls `ringing()`. Answering starts\n// the widget's hands-free conversation with that agent, which opens with\n// its reason. When the widget is not running, `notify` stands in. While\n// the widget is busy (a turn, the microphone, another call) the call waits\n// for it: its ring time does not run, and the owner gets a call-waiting\n// notice (#408), again every calls.busy.retryMinutes. The caller is told\n// the owner is busy, with whom, and until when its call waits (#969). It\n// rings by itself once the widget is free, oldest first; the caller may\n// withdraw it meanwhile (`cancel`).\n//\n// Checked before anything rings, in this order:\n// 0. the caller is that agent: a turn of it is running now, named by its\n// task id or by its channel and chat (see CallerProof),\n// 1. the owner allows this agent (calls.allow; empty allows nobody),\n// 2. the agent has no other call in progress,\n// 3. it is under calls.maxPerHour,\n// 4. Focus: a non-urgent call does not ring. It becomes a missed call,\n// and its notice goes through notify, which holds it until Focus\n// ends. Urgent calls ring through, as `notify --urgent` does.\n//\n// A camera ask (#325 phase 3, kind \"camera\") goes through the same checks\n// and the same table. It never rings the widget: the notice goes to the\n// phone through notify, and the phone app polls `asking()` to show a\n// Show / Decline bar. Answering it is the owner tapping Show; the camera\n// opens only then, and the share ends the ask (see camera-api.ts).\n\nimport { randomUUID } from \"crypto\"\nimport { focusLabel, readFocus, type FocusState } from \"@/notify/focus\"\nimport type { CallsConfig } from \"@/daemon/config\"\nimport { LIVE, type Call, type CallKind, type CallStore, type CallUrgency, type Waiting } from \"./store\"\n\nexport const REASON_MAX = 200\n/** The widget polls every 2 s; missing this long means it is not running. */\nexport const WIDGET_FRESH_MS = 10_000\nexport const LATER_DEFAULT_MINUTES = 10\nexport const LATER_MAX_MINUTES = 240\n\n/** What a caller is told when the owner cannot take its call now (#969). */\nexport interface BusyInfo {\n /** \"call\": the owner is in a call; \"ringing\": another call is ringing;\n * \"voice\": AgentX Voice is in a turn of the owner's, or recording. */\n why: \"call\" | \"ringing\" | \"voice\"\n /** The agent the owner is in a call with, or whose call is ringing. */\n with: string | null\n /** When the owner is told again that this call waits, if still busy\n * then; null when the wait ends first. Ms since the epoch. */\n remindAt: number | null\n /** The call stops waiting then: still busy, it counts as missed. */\n waitsUntil: number\n}\n\nexport type CallResult =\n | { ok: true; call: Call }\n | { ok: false; status: number; error: string }\n\n/** What the request says about the turn placing it: the run's\n * AGENTX_TASK_ID, or, for a warm process that has none, its\n * AGENTX_CHANNEL and AGENTX_CHAT_ID. Loopback is not an identity. */\nexport interface CallerProof {\n taskId?: string\n channel?: string\n chatId?: string\n}\n\n/** The proof headers for a call placed from inside an agent's run. */\nexport function callerHeaders(env: NodeJS.ProcessEnv = process.env): Record<string, string> {\n if (env.AGENTX_TASK_ID) return { \"X-AgentX-Task\": env.AGENTX_TASK_ID }\n if (env.AGENTX_CHANNEL && env.AGENTX_CHAT_ID) {\n return { \"X-AgentX-Channel\": env.AGENTX_CHANNEL, \"X-AgentX-Chat\": env.AGENTX_CHAT_ID }\n }\n return {}\n}\n\n/** What the widget says with its poll. A widget from before #408 says nothing. */\nexport interface WidgetState {\n /** It cannot ring a new call now. */\n busy?: boolean\n /** The call ringing on it. */\n showing?: string\n}\n\nexport interface CallDeps {\n store: CallStore\n config: () => CallsConfig\n /** The agent's display name, or null when there is no such agent here. */\n agentName: (agentId: string) => string | null\n /** True when `proof` names a running turn of `agentId`. */\n isRunningTurn: (agentId: string, proof: CallerProof) => boolean\n /** The channel and chat of that running turn, when it has one, so a\n * camera ask is answered where it was asked. Unset: only a proof that\n * names them itself is kept. */\n turnSession?: (agentId: string, proof: CallerProof) => { channel?: string; chatId?: string } | null\n /** Tell the owner through notify. `urgent` passes Focus, like notify --urgent. */\n alert: (notice: { title: string; message: string; urgent: boolean; from: string }) => Promise<void>\n /** Ask the agent, in its voice session, to summarise the call. */\n summarize?: (call: Call) => Promise<string | null>\n /** File a finished summary where the dashboard shows it. */\n file?: (call: Call, summary: string) => void\n focus?: () => FocusState\n now?: () => number\n log?: (m: string) => void\n}\n\n/** What the widget sends through /ask when the owner answers, so the\n * agent speaks first and says why it called. */\nexport function openerPrompt(call: Pick<Call, \"reason\">): string {\n return \"[Call answered] The owner just picked up the voice call you asked for. \" +\n `You called because: \"${call.reason}\". Speak first: greet them in a few words ` +\n \"and say why you called, then let them answer.\"\n}\n\nexport const SUMMARY_PROMPT =\n \"[Call ended] The owner hung up. In two or three plain sentences, summarise what \" +\n \"was said and decided on this call, for your records. Reply with the summary only.\"\n\nexport class CallService {\n private widgetSeenAt = 0\n /** What the widget said with its last poll. */\n private widget: WidgetState = {}\n /** Calls waiting for a busy widget: since when, and when the owner was\n * last told. Written through to the store, so a restart keeps them. */\n private waiting: Map<string, Waiting>\n\n constructor(private deps: CallDeps) {\n this.waiting = deps.store.waiting()\n }\n\n private setWaiting(id: string, w: Waiting | null): void {\n if (w) this.waiting.set(id, w)\n else if (!this.waiting.delete(id)) return\n this.deps.store.setWaiting(id, w)\n }\n\n private now(): number { return this.deps.now?.() ?? Date.now() }\n private name(agentId: string): string { return this.deps.agentName(agentId) ?? agentId }\n\n /** An agent asks for a call, or to see (kind \"camera\"). 201 ringing, or\n * waiting for a busy owner (`rang: \"waiting\"`, with `busy`); 202 held\n * as missed (Focus). */\n async request(input: { agentId?: unknown; reason?: unknown; urgency?: unknown; kind?: unknown }, proof: CallerProof = {}): Promise<CallResult & { rang?: Rang; busy?: BusyInfo }> {\n const agentId = String(input.agentId ?? \"\").trim()\n const reason = String(input.reason ?? \"\").replace(/\\s+/g, \" \").trim()\n const urgency: CallUrgency = input.urgency === \"urgent\" ? \"urgent\" : \"normal\"\n const kind: CallKind = input.kind === \"camera\" ? \"camera\" : \"voice\"\n if (input.kind !== undefined && input.kind !== \"voice\" && input.kind !== \"camera\") return { ok: false, status: 400, error: \"kind must be voice or camera\" }\n if (!agentId || !reason) return { ok: false, status: 400, error: \"Required: agentId and reason\" }\n if (reason.length > REASON_MAX) return { ok: false, status: 413, error: `reason is over ${REASON_MAX} characters; keep it to one line` }\n if (this.deps.agentName(agentId) === null) return { ok: false, status: 404, error: `Unknown agent: ${agentId}` }\n if (!this.deps.isRunningTurn(agentId, proof)) {\n return { ok: false, status: 403, error: `No running turn of ${agentId} placed this call. An agent calls from inside its own run.` }\n }\n\n const cfg = this.deps.config()\n if (!cfg.allow.includes(\"*\") && !cfg.allow.includes(agentId)) {\n const verb = kind === \"camera\" ? \"may not ask to see through the owner's camera\" : \"may not call the owner\"\n return { ok: false, status: 403, error: `${agentId} ${verb}. Add it to calls.allow in agentx.json (or: agentx call allow ${agentId}).` }\n }\n await this.sweep()\n if (this.deps.store.list({ status: LIVE, limit: 200 }).some((c) => c.agentId === agentId)) {\n return { ok: false, status: 409, error: `${agentId} already has a call in progress` }\n }\n const now = this.now()\n if (this.deps.store.countSince(agentId, now - 3_600_000) >= cfg.maxPerHour) {\n return { ok: false, status: 429, error: `${agentId} has placed ${cfg.maxPerHour} calls in the last hour (calls.maxPerHour)` }\n }\n\n // Where the ask came from, for kind \"camera\": the turn's own chat, or\n // the one the proof names. A task run has neither.\n const session: { channel?: string; chatId?: string } = kind === \"camera\" ? (this.deps.turnSession?.(agentId, proof) ?? proof) : {}\n const call: Call = {\n id: `call-${randomUUID().slice(0, 8)}`, agentId, kind, reason, urgency, status: \"ringing\", createdAt: now,\n ringingSince: now, answeredAt: null, endedAt: null, ringAgainAt: null, note: null, summary: null,\n channel: session.channel && session.chatId ? session.channel : null,\n chatId: session.channel && session.chatId ? session.chatId : null,\n }\n this.deps.store.insert(call)\n this.deps.log?.(`[calls] ${agentId} asks ${kind === \"camera\" ? \"to see\" : \"for a call\"} (${urgency}): ${reason}`)\n const rang = await this.ring(call)\n const saved = this.deps.store.get(call.id)!\n if (rang !== \"waiting\") return { ok: true, call: saved, rang }\n // The widget may have come free while the owner was being told.\n return this.waiting.has(call.id) ? { ok: true, call: saved, rang, busy: this.busyInfo(call) } : { ok: true, call: saved, rang: \"widget\" }\n }\n\n /** The caller withdraws its own voice call while it waits or rings, for\n * example to send a message instead (#969). */\n async cancel(input: { agentId?: unknown }, proof: CallerProof = {}): Promise<CallResult> {\n const agentId = String(input.agentId ?? \"\").trim()\n if (!agentId) return { ok: false, status: 400, error: \"Required: agentId\" }\n if (!this.deps.isRunningTurn(agentId, proof)) {\n return { ok: false, status: 403, error: `No running turn of ${agentId} asked for this. An agent withdraws its own call from inside its run.` }\n }\n // No sweep first: a call back that is due must not ring just to be withdrawn.\n const mine = this.deps.store.list({ status: [\"ringing\", \"later\"], kind: \"voice\", limit: 200 }).find((c) => c.agentId === agentId)\n if (!mine) return { ok: false, status: 404, error: `${agentId} has no call waiting or ringing` }\n const ended = this.deps.store.transition(mine.id, [\"ringing\", \"later\"], { status: \"ended\", endedAt: this.now(), ringingSince: null, ringAgainAt: null, note: CANCELLED_NOTE })\n if (!ended) return { ok: false, status: 409, error: `call is ${this.deps.store.get(mine.id)?.status}` }\n this.setWaiting(mine.id, null)\n this.deps.log?.(`[calls] ${mine.id} withdrawn by ${agentId}`)\n return { ok: true, call: ended }\n }\n\n /** Ring now, wait for a busy widget, or hold as missed in Focus.\n * Returns how it rang. */\n private async ring(call: Call): Promise<Rang> {\n const focus = (this.deps.focus ?? readFocus)()\n if (focus.active && call.urgency !== \"urgent\") {\n if (!this.deps.store.transition(call.id, \"ringing\", { status: \"missed\", ringingSince: null, note: focusLabel(focus) })) return false\n await this.notice(call, this.missedTitle(call))\n return false\n }\n // A camera ask is answered on the phone, never on the widget.\n if (call.kind === \"camera\") {\n await this.notice(call, `${this.name(call.agentId)} wants to see through your camera`,\n `Open the phone app and tap Show within ${this.deps.config().ringSeconds} s.`)\n return \"notify\"\n }\n if (this.widgetAlive()) {\n // Busy by its last poll, or by a call placed since that it will show first.\n if (!this.widget.busy && !this.otherCalls(call).length) return \"widget\"\n this.startWaiting(call)\n await this.callWaiting(call)\n return \"waiting\"\n }\n await this.notice(call, `${this.name(call.agentId)} is calling`,\n `Open AgentX Voice to answer within ${this.deps.config().ringSeconds} s.`)\n return \"notify\"\n }\n\n private async notice(call: Call, title: string, extra?: string): Promise<void> {\n try {\n await this.deps.alert({\n title, message: extra ? `${call.reason}\\n${extra}` : call.reason,\n urgent: call.urgency === \"urgent\", from: call.agentId,\n })\n } catch (e: any) {\n this.deps.log?.(`[calls] notice for ${call.id} failed: ${e?.message ?? e}`)\n }\n }\n\n /** The call starts waiting for the busy widget. */\n private startWaiting(call: Call): void {\n const now = this.now()\n this.setWaiting(call.id, { since: now, toldAt: now })\n this.deps.log?.(`[calls] ${call.id} from ${call.agentId} waits: the widget is busy`)\n }\n\n /** Voice calls ringing or answered other than `call`, oldest first. */\n private otherCalls(call: Call): Call[] {\n return this.deps.store.list({ status: [\"ringing\", \"answered\"], kind: \"voice\", limit: 200 }).filter((c) => c.id !== call.id).reverse()\n }\n\n private async callWaiting(call: Call): Promise<void> {\n const busy = this.deps.config().busy\n await this.notice(call, busy.title.replaceAll(\"{agent}\", this.name(call.agentId)), busy.notice)\n }\n\n /** Why the owner cannot take `call` now, and how long it waits. */\n private busyInfo(call: Call): BusyInfo {\n const cfg = this.deps.config().busy\n const { since, toldAt } = this.waiting.get(call.id) ?? { since: this.now(), toldAt: this.now() }\n const others = this.otherCalls(call)\n const talking = others.find((c) => c.status === \"answered\")\n // The call on the pill, or the oldest one it has yet to show.\n const showing = others.find((c) => c.id === this.widget.showing) ?? others.find((c) => !this.waiting.has(c.id))\n // After the wait the ring time runs once more before it is missed.\n const waitsUntil = since + cfg.maxWaitMinutes * 60_000 + this.deps.config().ringSeconds * 1000\n const remindAt = toldAt + cfg.retryMinutes * 60_000\n const stopsWaiting = since + cfg.maxWaitMinutes * 60_000\n return {\n why: talking ? \"call\" : showing ? \"ringing\" : \"voice\",\n with: talking ? this.name(talking.agentId) : showing ? this.name(showing.agentId) : null,\n remindAt: remindAt < stopsWaiting ? remindAt : null,\n waitsUntil,\n }\n }\n\n private missedTitle(call: Call): string {\n return call.kind === \"camera\" ? `${this.name(call.agentId)} asked to see through your camera` : `Missed call from ${this.name(call.agentId)}`\n }\n\n /** True when the widget polled recently. */\n widgetAlive(): boolean {\n return this.now() - this.widgetSeenAt < WIDGET_FRESH_MS\n }\n\n /** The widget's poll: voice calls ringing now, oldest first. Marks it alive. */\n async ringing(widget: WidgetState = {}): Promise<Call[]> {\n this.widgetSeenAt = this.now()\n this.widget = widget\n await this.hold(widget)\n await this.sweep()\n return this.deps.store.list({ status: [\"ringing\"], kind: \"voice\", limit: 20 }).reverse()\n }\n\n /** A busy widget cannot ring: the ring time of each call it is not\n * showing starts over at every poll, for calls.busy.maxWaitMinutes at\n * most. The owner is told that the call waits, and again every\n * calls.busy.retryMinutes. The ring time starts over once more when the\n * widget first shows a call that waited. */\n private async hold(widget: WidgetState): Promise<void> {\n const now = this.now()\n const busy = this.deps.config().busy\n // Told after the loop: a poll that overlaps a slow notice must not\n // carry on from a list and a widget state that are out of date.\n const tell: Call[] = []\n const live = this.deps.store.list({ status: [\"ringing\"], kind: \"voice\", limit: 20 })\n for (const id of [...this.waiting.keys()]) if (!live.some((c) => c.id === id)) this.setWaiting(id, null)\n for (const call of live) {\n if (call.id === widget.showing) {\n if (this.waiting.has(call.id)) {\n this.setWaiting(call.id, null)\n this.deps.store.transition(call.id, \"ringing\", { ringingSince: now })\n }\n continue\n }\n if (!widget.busy) continue\n const held = this.waiting.get(call.id)\n if (held && now - held.since >= busy.maxWaitMinutes * 60_000) continue\n if (!this.deps.store.transition(call.id, \"ringing\", { ringingSince: now })) continue\n if (!held) this.startWaiting(call)\n else if (now - held.toldAt >= busy.retryMinutes * 60_000) this.setWaiting(call.id, { since: held.since, toldAt: now })\n else continue\n tell.push(call)\n }\n for (const call of tell) await this.callWaiting(call)\n }\n\n /** The phone app's poll: camera asks waiting for the owner, oldest first.\n * Does not count as the widget being up. */\n async asking(): Promise<Call[]> {\n await this.sweep()\n return this.deps.store.list({ status: [\"ringing\"], kind: \"camera\", limit: 20 }).reverse()\n }\n\n /** Unanswered calls become missed; \"later\" calls that came due ring\n * again; answered calls nobody hung up end after calls.maxCallMinutes. */\n async sweep(): Promise<void> {\n const now = this.now()\n const ringFor = this.deps.config().ringSeconds * 1000\n const talkFor = this.deps.config().maxCallMinutes * 60_000\n for (const call of this.deps.store.list({ status: [\"ringing\", \"later\", \"answered\"], limit: 200 })) {\n // The list was read before any await below: each move re-checks the\n // status in the store, so a call answered meanwhile is left alone.\n if (call.status === \"ringing\" && call.ringingSince !== null && now - call.ringingSince >= ringFor) {\n if (!this.deps.store.transition(call.id, \"ringing\", { status: \"missed\", ringingSince: null, note: this.waiting.has(call.id) ? \"widget busy\" : \"not answered\" })) continue\n this.deps.log?.(`[calls] ${call.id} from ${call.agentId} missed`)\n await this.notice(call, this.missedTitle(call))\n } else if (call.status === \"answered\" && call.answeredAt !== null && now - call.answeredAt >= talkFor) {\n if (!this.deps.store.transition(call.id, \"answered\", { status: \"ended\", endedAt: now, note: \"no hang-up\" })) continue\n this.deps.log?.(`[calls] ${call.id} from ${call.agentId} ended: no hang-up in ${this.deps.config().maxCallMinutes} min`)\n } else if (call.status === \"later\" && call.ringAgainAt !== null && now >= call.ringAgainAt) {\n const again = this.deps.store.transition(call.id, \"later\", { status: \"ringing\", ringingSince: now, ringAgainAt: null })\n if (!again) continue\n // A wait from before the snooze does not carry over.\n this.setWaiting(call.id, null)\n await this.ring(again)\n }\n }\n }\n\n /** The owner picked up, or tapped Show. Returns the call and, for a\n * voice call, the opener for /ask. */\n answer(id: string): CallResult & { opener?: string } {\n const call = this.deps.store.get(id)\n if (!call) return unknown(id)\n const answered = this.deps.store.transition(id, \"ringing\", { status: \"answered\", answeredAt: this.now(), ringingSince: null })\n if (!answered) return { ok: false, status: 409, error: `call is ${this.deps.store.get(id)?.status}, not ringing` }\n this.deps.log?.(`[calls] ${id} answered (${call.agentId})`)\n return answered.kind === \"camera\" ? { ok: true, call: answered } : { ok: true, call: answered, opener: openerPrompt(answered) }\n }\n\n decline(id: string): CallResult {\n const call = this.deps.store.get(id)\n if (!call) return unknown(id)\n const declined = this.deps.store.transition(id, [\"ringing\", \"later\"], { status: \"declined\", endedAt: this.now(), ringingSince: null, ringAgainAt: null })\n return declined ? { ok: true, call: declined } : { ok: false, status: 409, error: `call is ${this.deps.store.get(id)?.status}` }\n }\n\n /** \"Call back in N min\": it rings again then. */\n later(id: string, minutes?: unknown): CallResult {\n const call = this.deps.store.get(id)\n if (!call) return unknown(id)\n const n = minutes === undefined ? LATER_DEFAULT_MINUTES : Number(minutes)\n if (!Number.isInteger(n) || n < 1 || n > LATER_MAX_MINUTES) {\n return { ok: false, status: 400, error: `minutes must be a whole number from 1 to ${LATER_MAX_MINUTES}` }\n }\n const later = this.deps.store.transition(id, \"ringing\", { status: \"later\", ringingSince: null, ringAgainAt: this.now() + n * 60_000 })\n return later ? { ok: true, call: later } : { ok: false, status: 409, error: `call is ${this.deps.store.get(id)?.status}, not ringing` }\n }\n\n /** End an answered call. The summary is written in the background;\n * `summarized` settles when it is filed (tests wait on it). */\n hangup(id: string): CallResult & { summarized?: Promise<void> } {\n const call = this.deps.store.get(id)\n if (!call) return unknown(id)\n const ended = this.deps.store.transition(id, \"answered\", { status: \"ended\", endedAt: this.now() })\n if (!ended) return { ok: false, status: 409, error: `call is ${call.status}, not answered` }\n this.deps.log?.(`[calls] ${id} ended (${call.agentId})`)\n const summarize = this.deps.summarize\n // What the agent saw is already its answer; only a voice call is summarised.\n if (ended.kind === \"camera\" || !this.deps.config().summary || !summarize) return { ok: true, call: ended }\n const summarized = (async () => {\n try {\n const text = (await summarize(ended))?.trim()\n if (!text) return\n const done = this.deps.store.update(id, { summary: text })!\n this.deps.file?.(done, text)\n } catch (e: any) {\n this.deps.log?.(`[calls] summary for ${id} failed: ${e?.message ?? e}`)\n }\n })()\n return { ok: true, call: ended, summarized }\n }\n\n /** A camera ask ends when the share ends; no summary. Only an answered\n * ask ends this way, so a stop before Show leaves it ringing. */\n endCamera(id: string, note?: string): boolean {\n const call = this.deps.store.get(id)\n if (!call || call.kind !== \"camera\") return false\n const ended = this.deps.store.transition(id, \"answered\", { status: \"ended\", endedAt: this.now(), note: note ?? null })\n if (ended) this.deps.log?.(`[calls] ${id} ended (${call.agentId}): ${note ?? \"share over\"}`)\n return !!ended\n }\n\n get(id: string): Call | undefined { return this.deps.store.get(id) }\n\n list(opts: Parameters<CallStore[\"list\"]>[0] = {}): Call[] { return this.deps.store.list(opts) }\n}\n\n/** How a call reached the owner: on the widget, through notify, waiting\n * for a busy widget, or not at all (held as missed). */\nexport type Rang = \"widget\" | \"notify\" | \"waiting\" | false\n\n/** The note on a call its caller withdrew. */\nexport const CANCELLED_NOTE = \"withdrawn by the caller\"\n\n/** What the caller reads when its call waits for a busy owner. Times are\n * in the host's time zone (or `timeZone`), and say which one. */\nexport function busyText(busy: BusyInfo, now: number = Date.now(), timeZone?: string): string {\n const at = (ms: number) => `${new Date(ms).toLocaleTimeString(\"en-GB\", { hour: \"2-digit\", minute: \"2-digit\", hour12: false, timeZoneName: \"short\", timeZone })} (in ${Math.max(1, Math.round((ms - now) / 60_000))} min)`\n const why = busy.why === \"call\" ? `The owner is busy in another call${busy.with ? ` (with ${busy.with})` : \"\"}.`\n : busy.why === \"ringing\" ? `The owner is busy: another call${busy.with ? ` (from ${busy.with})` : \"\"} is ringing.`\n : \"The owner is busy on the voice widget.\"\n return `${why} They were told you are calling, and why. Your call waits and rings by itself as soon as they are free: do not call again. ` +\n (busy.remindAt !== null ? `Still busy at ${at(busy.remindAt)}, they are told again that you are waiting. ` : \"\") +\n `It waits until ${at(busy.waitsUntil)} at most, then counts as a missed call.`\n}\n\nconst unknown = (id: string): CallResult => ({ ok: false, status: 404, error: `No such call: ${id}` })\n","// --- Calls an agent placed to the owner, kept in SQLite ---\n//\n// One row per call, so a missed call is still there after a restart and\n// the dashboard can list them. Nothing else reads this table. A camera\n// ask (#325 phase 3: \"show me\") is a call of kind \"camera\": same\n// allowlist, same hourly limit, same lifecycle, answered on the phone\n// instead of the voice widget.\n\nimport type Database from \"better-sqlite3\"\n\nexport type CallStatus = \"ringing\" | \"answered\" | \"ended\" | \"declined\" | \"missed\" | \"later\"\nexport type CallUrgency = \"normal\" | \"urgent\"\n/** \"voice\": a live voice call on the widget. \"camera\": the agent asks to see through the phone camera. */\nexport type CallKind = \"voice\" | \"camera\"\n\nexport interface Call {\n id: string\n agentId: string\n kind: CallKind\n reason: string\n urgency: CallUrgency\n status: CallStatus\n /** The channel and chat the asking turn ran in, so what the agent sees\n * is answered there; null when the turn had none (a task run). */\n channel: string | null\n chatId: string | null\n /** When the agent asked, ms since the epoch. */\n createdAt: number\n /** When it last started ringing: at the request, or when \"later\" came due. */\n ringingSince: number | null\n answeredAt: number | null\n endedAt: number | null\n /** Status \"later\": when it rings again. */\n ringAgainAt: number | null\n /** Why it ended the way it did, in a few words (\"in Focus\", \"not answered\"). */\n note: string | null\n /** The agent's own summary, written after hang-up. */\n summary: string | null\n}\n\n/** A call waiting for a busy owner: since when, and when the owner was\n * last told. Ms since the epoch. */\nexport interface Waiting {\n since: number\n toldAt: number\n}\n\n/** A call that stops ringing (answered, snoozed, missed...) is no longer\n * waiting: the mark goes in the same write, so a call that rings again\n * later starts a fresh wait, even across a restart. */\nfunction leaving(patch: { status?: CallStatus }): string {\n return patch.status && patch.status !== \"ringing\" ? \", waiting_since=NULL, told_at=NULL\" : \"\"\n}\n\n/** Calls still in progress: an agent has at most one of these. */\nexport const LIVE: CallStatus[] = [\"ringing\", \"answered\", \"later\"]\n\nconst COLUMNS = `id, agent_id AS agentId, kind, reason, urgency, status, created_at AS createdAt,\n ringing_since AS ringingSince, answered_at AS answeredAt, ended_at AS endedAt,\n ring_again_at AS ringAgainAt, note, summary, channel, chat_id AS chatId`\n\nexport class CallStore {\n constructor(private db: Database.Database) {\n db.exec(`CREATE TABLE IF NOT EXISTS calls (\n id TEXT PRIMARY KEY, agent_id TEXT NOT NULL, reason TEXT NOT NULL,\n urgency TEXT NOT NULL, status TEXT NOT NULL, created_at INTEGER NOT NULL,\n ringing_since INTEGER, answered_at INTEGER, ended_at INTEGER,\n ring_again_at INTEGER, note TEXT, summary TEXT);\n CREATE INDEX IF NOT EXISTS calls_recent ON calls(created_at DESC);`)\n // Columns added since the table first shipped (#325 phase 3). A row\n // from before is a voice call from a turn whose chat was not kept.\n const have = new Set((db.prepare(\"PRAGMA table_info(calls)\").all() as Array<{ name: string }>).map((c) => c.name))\n if (!have.has(\"kind\")) db.exec(\"ALTER TABLE calls ADD COLUMN kind TEXT NOT NULL DEFAULT 'voice'\")\n if (!have.has(\"channel\")) db.exec(\"ALTER TABLE calls ADD COLUMN channel TEXT\")\n if (!have.has(\"chat_id\")) db.exec(\"ALTER TABLE calls ADD COLUMN chat_id TEXT\")\n // A call waiting for a busy owner (#969), kept so a restart does not\n // forget it: since when it waits, and when the owner was last told.\n if (!have.has(\"waiting_since\")) db.exec(\"ALTER TABLE calls ADD COLUMN waiting_since INTEGER\")\n if (!have.has(\"told_at\")) db.exec(\"ALTER TABLE calls ADD COLUMN told_at INTEGER\")\n }\n\n /** Ringing calls marked as waiting for a busy owner, by id. */\n waiting(): Map<string, Waiting> {\n const rows = this.db.prepare(\"SELECT id, waiting_since AS since, told_at AS toldAt FROM calls WHERE waiting_since IS NOT NULL AND status='ringing'\")\n .all() as Array<{ id: string } & Waiting>\n return new Map(rows.map(({ id, since, toldAt }) => [id, { since, toldAt: toldAt ?? since }]))\n }\n\n /** Mark a call as waiting, or clear the mark with null. */\n setWaiting(id: string, w: Waiting | null): void {\n this.db.prepare(\"UPDATE calls SET waiting_since=?, told_at=? WHERE id=?\").run(w?.since ?? null, w?.toldAt ?? null, id)\n }\n\n insert(call: Call): void {\n this.db.prepare(`INSERT INTO calls (id, agent_id, kind, reason, urgency, status, created_at, ringing_since,\n answered_at, ended_at, ring_again_at, note, summary, channel, chat_id) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`).run(\n call.id, call.agentId, call.kind, call.reason, call.urgency, call.status, call.createdAt,\n call.ringingSince, call.answeredAt, call.endedAt, call.ringAgainAt, call.note, call.summary, call.channel, call.chatId)\n }\n\n get(id: string): Call | undefined {\n return this.db.prepare(`SELECT ${COLUMNS} FROM calls WHERE id=?`).get(id) as Call | undefined\n }\n\n /** Write the changed fields of one call. */\n update(id: string, patch: Partial<Omit<Call, \"id\" | \"agentId\" | \"createdAt\">>): Call | undefined {\n const cols: Record<string, string> = {\n reason: \"reason\", urgency: \"urgency\", status: \"status\", ringingSince: \"ringing_since\",\n answeredAt: \"answered_at\", endedAt: \"ended_at\", ringAgainAt: \"ring_again_at\", note: \"note\", summary: \"summary\",\n }\n const keys = Object.keys(patch).filter((k) => k in cols)\n if (keys.length) {\n const set = keys.map((k) => `${cols[k]}=?`).join(\", \") + leaving(patch)\n this.db.prepare(`UPDATE calls SET ${set} WHERE id=?`).run(...keys.map((k) => (patch as any)[k] ?? null), id)\n }\n return this.get(id)\n }\n\n /** Change a call only if it is still `from`: the check and the write are\n * one statement, so two sweeps, or a sweep and the owner answering,\n * cannot both move the same call. Undefined when it had moved on. */\n transition(id: string, from: CallStatus | CallStatus[], patch: Partial<Omit<Call, \"id\" | \"agentId\" | \"createdAt\">>): Call | undefined {\n const statuses = Array.isArray(from) ? from : [from]\n const cols: Record<string, string> = {\n status: \"status\", ringingSince: \"ringing_since\", answeredAt: \"answered_at\", endedAt: \"ended_at\",\n ringAgainAt: \"ring_again_at\", note: \"note\", summary: \"summary\",\n }\n const keys = Object.keys(patch).filter((k) => k in cols)\n const set = keys.map((k) => `${cols[k]}=?`).join(\", \") + leaving(patch)\n const marks = statuses.map(() => \"?\").join(\",\")\n const changed = this.db.prepare(`UPDATE calls SET ${set} WHERE id=? AND status IN (${marks})`)\n .run(...keys.map((k) => (patch as any)[k] ?? null), id, ...statuses).changes\n return changed ? this.get(id) : undefined\n }\n\n /** Newest first; only these statuses, and only this kind, when given. */\n list(opts: { status?: CallStatus[]; kind?: CallKind; limit?: number } = {}): Call[] {\n const limit = Math.min(Math.max(opts.limit ?? 50, 1), 200)\n const where: string[] = []\n const args: unknown[] = []\n if (opts.status?.length) {\n where.push(`status IN (${opts.status.map(() => \"?\").join(\",\")})`)\n args.push(...opts.status)\n }\n if (opts.kind) { where.push(\"kind=?\"); args.push(opts.kind) }\n const filter = where.length ? `WHERE ${where.join(\" AND \")}` : \"\"\n return this.db.prepare(`SELECT ${COLUMNS} FROM calls ${filter} ORDER BY created_at DESC, rowid DESC LIMIT ?`)\n .all(...args, limit) as Call[]\n }\n\n /** Calls this agent placed after `since` (ms), for the hourly limit. */\n countSince(agentId: string, since: number): number {\n return (this.db.prepare(\"SELECT COUNT(*) AS n FROM calls WHERE agent_id=? AND created_at>?\")\n .get(agentId, since) as { n: number }).n\n }\n}\n"],"mappings":"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","names":["randomUUID","leaving","patch","LIVE","COLUMNS","CallStore","db","have","c","rows","id","since","toldAt","w","call","cols","keys","k","set","from","statuses","marks","opts","limit","where","args","filter","agentId","REASON_MAX","WIDGET_FRESH_MS","LATER_DEFAULT_MINUTES","LATER_MAX_MINUTES","callerHeaders","env","openerPrompt","call","SUMMARY_PROMPT","CallService","deps","id","w","agentId","input","proof","reason","urgency","kind","cfg","LIVE","c","now","session","randomUUID","rang","saved","mine","ended","CANCELLED_NOTE","focus","readFocus","focusLabel","title","extra","e","busy","since","toldAt","others","talking","showing","waitsUntil","remindAt","stopsWaiting","widget","tell","live","held","ringFor","talkFor","again","unknown","answered","declined","minutes","n","later","summarize","summarized","text","done","note","opts","busyText","timeZone","at","ms"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{Ba as Ju,D as Kn,F as yu,Fa as Xu,Ga as es,Ha as Li,Ia as Ku,Ja as zu,K as wu,Ka as Yu,La as Qu,M as ku,Na as Zu,O as bu,P as Mi,Q as vu,R as He,S as Su,T as zn,Ua as eh,V as Oi,Va as th,W as $u,X as Ni,Y as tt,Z as _u,_ as Mu,a as Ed,b as Ti,ba as nt,ca as Ou,cb as Fi,da as Du,db as Ui,ea as Nu,f as xi,fa as ju,fb as nh,g as Cd,ga as Lu,h as Pd,ha as Fu,i as Ge,j as _d,ja as be,k as Md,ka as Yn,la as ji,m as Od,ma as Uu,na as Kt,o as Dd,oa as Gu,p as zd,pa as Pe,q as Yd,qa as Wu,r as Qd,ra as Hu,s as Zd,sa as Bu,t as eu,ta as Qn,u as tu,ua as Zn,v as nu,va as qu,w as su,wa as Vu,x as ru,y as Pi}from"./chunk-KO6BU3B3.js";import{d as xd,g as Ad,h as Rd}from"./chunk-J7U3BASX.js";import{b as Pu}from"./chunk-7LN4VKDU.js";import{a as Nd}from"./chunk-ZVP6GB4G.js";import{m as Fd,n as Ud,o as Ri,p as Ce,r as Gd,s as ke}from"./chunk-S2GAEH2F.js";import{i as ou,m as _i,n as au,p as cu,q as lu,r as du,s as uu,t as hu,u as gu}from"./chunk-UNIXYV3U.js";import{f as Ei,g as Vd,h as Jn,i as St,k as We,m as Jd,n as Xd,o as Kd,p as Ci,q as Xn}from"./chunk-AKWBDCMU.js";import{a as pu}from"./chunk-PPUN7YHA.js";import{a as fu,b as mu}from"./chunk-VZLIBDLH.js";import{b as Iu,c as Tu,d as Di,e as xu,f as Au,g as Ru,h as Eu,i as Cu}from"./chunk-EKJG63EJ.js";import{b as Vn}from"./chunk-EMGB23H5.js";import{d as Wd}from"./chunk-JAML6222.js";import{g as Hd}from"./chunk-LQE73AZR.js";import{b as Bd,c as qd}from"./chunk-2D3RYIDU.js";import{b as iu}from"./chunk-MRIVENBY.js";import{a as jd,b as Ld}from"./chunk-A5GYG56I.js";import{o as Ai}from"./chunk-Z5IHT6RK.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 pi,c as qc,ca as kd,d as Zc,e as el,f as Jt,h as mi,i as yi,j as wi,k as ki,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-RIYQNADV.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 Ii,x as Bn,y as Yl,z as Ql}from"./chunk-2BEZU5OF.js";import{b as Nl,c as jl,d as Si,f as Wn,g as Ll,h as Fl,i as Ul,j as Xt,k as Hn,l as Gl}from"./chunk-QSKG754X.js";import{a as bi,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 ei}from"./chunk-MUC7MMTT.js";import{b as nc}from"./chunk-SFQUP3BP.js";import{a as Ue}from"./chunk-Y4CYQV7G.js";import{a as Sd,b as vt,c as $d,d as Id,e as Td}from"./chunk-
|
|
1
|
+
import{Ba as Ju,D as Kn,F as yu,Fa as Xu,Ga as es,Ha as Li,Ia as Ku,Ja as zu,K as wu,Ka as Yu,La as Qu,M as ku,Na as Zu,O as bu,P as Mi,Q as vu,R as He,S as Su,T as zn,Ua as eh,V as Oi,Va as th,W as $u,X as Ni,Y as tt,Z as _u,_ as Mu,a as Ed,b as Ti,ba as nt,ca as Ou,cb as Fi,da as Du,db as Ui,ea as Nu,f as xi,fa as ju,fb as nh,g as Cd,ga as Lu,h as Pd,ha as Fu,i as Ge,j as _d,ja as be,k as Md,ka as Yn,la as ji,m as Od,ma as Uu,na as Kt,o as Dd,oa as Gu,p as zd,pa as Pe,q as Yd,qa as Wu,r as Qd,ra as Hu,s as Zd,sa as Bu,t as eu,ta as Qn,u as tu,ua as Zn,v as nu,va as qu,w as su,wa as Vu,x as ru,y as Pi}from"./chunk-KO6BU3B3.js";import{d as xd,g as Ad,h as Rd}from"./chunk-J7U3BASX.js";import{b as Pu}from"./chunk-7LN4VKDU.js";import{a as Nd}from"./chunk-ZVP6GB4G.js";import{m as Fd,n as Ud,o as Ri,p as Ce,r as Gd,s as ke}from"./chunk-S2GAEH2F.js";import{i as ou,m as _i,n as au,p as cu,q as lu,r as du,s as uu,t as hu,u as gu}from"./chunk-UNIXYV3U.js";import{f as Ei,g as Vd,h as Jn,i as St,k as We,m as Jd,n as Xd,o as Kd,p as Ci,q as Xn}from"./chunk-AKWBDCMU.js";import{a as pu}from"./chunk-PPUN7YHA.js";import{a as fu,b as mu}from"./chunk-VZLIBDLH.js";import{b as Iu,c as Tu,d as Di,e as xu,f as Au,g as Ru,h as Eu,i as Cu}from"./chunk-EKJG63EJ.js";import{b as Vn}from"./chunk-EMGB23H5.js";import{d as Wd}from"./chunk-JAML6222.js";import{g as Hd}from"./chunk-LQE73AZR.js";import{b as Bd,c as qd}from"./chunk-2D3RYIDU.js";import{b as iu}from"./chunk-MRIVENBY.js";import{a as jd,b as Ld}from"./chunk-A5GYG56I.js";import{o as Ai}from"./chunk-Z5IHT6RK.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 pi,c as qc,ca as kd,d as Zc,e as el,f as Jt,h as mi,i as yi,j as wi,k as ki,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-RIYQNADV.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 Ii,x as Bn,y as Yl,z as Ql}from"./chunk-2BEZU5OF.js";import{b as Nl,c as jl,d as Si,f as Wn,g as Ll,h as Fl,i as Ul,j as Xt,k as Hn,l as Gl}from"./chunk-QSKG754X.js";import{a as bi,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 ei}from"./chunk-MUC7MMTT.js";import{b as nc}from"./chunk-SFQUP3BP.js";import{a as Ue}from"./chunk-Y4CYQV7G.js";import{a as Sd,b as vt,c as $d,d as Id,e as Td}from"./chunk-3W2BHUGM.js";import{a as kt,b as bt}from"./chunk-SHEUCITK.js";import{a as El,b as vi,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 $i}from"./chunk-JS2LEURJ.js";import{a as Bl,b as ql}from"./chunk-NMYPZR4W.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 pk,m as Fc,n as Ik,q as Uc,v as Gc,y as yt,z as jn}from"./chunk-LMZCB7TT.js";import{a as Qe,b as Vt,f as gi}from"./chunk-72SZI2UW.js";import{a as Bt,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 fi,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 gk}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 $k,D as Rc,E as ci,F as li,H as di,J as Ec,K as Cc,M as qt,N as Pc,O as _c,P as Mc,Q as Oc,R as bk,U as Dc,X as Nc,Y as jc,aa as vk,ca as ui,d as mt,e as dc,ea as Sk,f as ni,g as fk,h as Mn,i as si,j as On,l as ze,m as ri,n as uc,o as mk,p as Dn,q as yk,r as Nn,s as hc,sa as hi,t as wk,ta as Lc,u as gc,v as pc,w as fc,z as xc}from"./chunk-MGM33GOW.js";import{e as Ye,f as mc,g as yc,h as ii}from"./chunk-BRK6M6LA.js";import{a as wc,b as kc,c as bc,e as vc,f as oi,g as Sc,h as $c,i as ai,j as Ic,k as Tc,n as kk}from"./chunk-UIBUZ2N3.js";import{b as ti,e as lc}from"./chunk-2QNT5JDS.js";import{d as ft}from"./chunk-QIJLFPWE.js";import{existsSync as qi,readFileSync as Vi}from"fs";fc();var sh=["answer","narration","line"],rh=5e3;function ih(r){return r==="/voice/queue"||r.startsWith("/voice/queue/")}async function oh(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||!sh.includes(u))return{status:400,body:{error:`Required: text and agentId; kind is ${sh.join(" | ")}`}};if(l.length>rh)return{status:413,body:{error:`text is over ${rh} 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()}:Gi(o);case"front":return r.front(o)?{status:200,body:r.view()}:Gi(o);case"replay":{let l=r.replay(o);return l?{status:202,body:{item:l}}:Gi(o)}default:return{status:404,body:{error:"Not found"}}}}function ah(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 Gi=r=>({status:404,body:{error:`No such line in the queue: ${r}`}});import{resolve as Ji}from"path";Sk();var Tk={name:"agentx",version:"0.1.0"},xk=["2024-11-05","2025-03-26","2025-06-18","2025-11-25"],Ak="2024-11-05";function Rk(r){return typeof r=="string"&&xk.includes(r)?r:Ak}var Ek={tools:{},elicitation:{form:{}}},Ck=0;async function ch(r,e){let t=++Ck;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,Pk="http://localhost:18800",Wi;function _k(){if(process.env.AGENTX_DAEMON_URL)return process.env.AGENTX_DAEMON_URL;for(let r of["agentx.json",".agentx/config.json"])try{let e=Ji(process.cwd(),r);if(!qi(e))continue;let t=JSON.parse(Vi(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 Pk}function X(){return Wi===void 0&&(Wi=_k()),Wi}var Hi;function Mk(r){try{let e=Ji(process.cwd(),".env");if(!qi(e))return"";for(let t of Vi(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 Ok(r=process.env){if(r.AGENTX_TOKEN)return r.AGENTX_TOKEN;if(r.MESH_TOKEN)return r.MESH_TOKEN;let e=Mk("MESH_TOKEN");if(e)return e;for(let t of["agentx.json",".agentx/config.json"])try{let n=Ji(process.cwd(),t);if(!qi(n))continue;let s=JSON.parse(Vi(n,"utf-8"))?.dashboard?.token;if(typeof s=="string"&&s)return s}catch{}return""}function Dk(){return Hi===void 0&&(Hi=Ok()),Hi}var K=(r,e)=>{let t=Dk();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 Bi(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 Nk(){let r=process.env.AGENTX_AGENT_ID,e=Bi();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 jk=1e4,Lk=new Set(["ECONNREFUSED","ENOTFOUND","EAI_AGAIN","EHOSTUNREACH","ENETUNREACH"]);function Fk(r,e){let t=e,n=t?.cause?.code??t?.code;if(n&&Lk.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 Uk=/\x1B\[[0-9;]*[A-Za-z]/g;function $t(r){return r.replace(Uk,"")}async function Gk(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:Wk(i,!!s.hasMore)}]}}function Wk(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(`
|
|
@@ -40,7 +40,7 @@ ${String(b.plan.text).split(`
|
|
|
40
40
|
`)}]}:{content:[{type:"text",text:"No stopped tasks."}]}}if(n!=="stop"&&n!=="resume")return{content:[{type:"text",text:"Error: action must be stop, resume or list."}]};let a=n==="stop"?{taskId:o(e.taskId),agentId:o(e.agentId),channel:o(e.channel),chatId:o(e.chatId),reason:o(e.reason),node:o(e.node),from:s}:{id:o(e.id),reason:o(e.reason),node:o(e.node),from:s},c=await K(`${X()}/api/signals/${n}`,{method:"POST",headers:{"Content-Type":"application/json",...i},body:JSON.stringify(a),signal:AbortSignal.timeout(3e4)}),l=await c.json().catch(()=>({}));return c.ok?{content:[{type:"text",text:n==="stop"?`Stopped task ${l.id} of ${l.agentId}. It is writing its resume plan; resume it with agentx_signal action=resume id=${l.id}.`:`Resumed task ${l.id} of ${l.agentId}. Its answer goes to ${l.delivery??"the chat it came from"}.`}]}:{content:[{type:"text",text:`Signal not sent: ${l.error||`HTTP ${c.status}`}`}]}}case"agentx_crons":{let s=await(await K(`${X()}/crons/health`)).json(),i=`Healthy: ${s.healthy}, Failing: ${s.failing}, Disabled: ${s.disabled}, Missed: ${s.missed}`,o=(s.jobs||[]).map(a=>` ${a.id}: ${a.status}${a.consecutiveErrors?` (${a.consecutiveErrors} errors)`:""}${a.lastError?` \u2014 ${a.lastError.slice(0,100)}`:""}`).join(`
|
|
41
41
|
`);return{content:[{type:"text",text:`${i}
|
|
42
42
|
|
|
43
|
-
${o}`}]}}case"agentx_schedule":{let{runScheduleTool:n,resolveScheduleCaller:s}=await import("./schedule-tool-GUKYTQTY.js");return{content:[{type:"text",text:await n(e,s(e),{notifyOperator:async(o,a)=>{let c=await K(`${X()}/send`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({channel:o.channel,chatId:o.chatId,accountId:o.accountId,text:a}),signal:AbortSignal.timeout(1e4)});if(!c.ok){let l=await c.json().catch(()=>({}));throw new Error(l?.error||`HTTP ${c.status}`)}}})}]}}case"agentx_approval":{let{runApprovalTool:n}=await import("./tool-
|
|
43
|
+
${o}`}]}}case"agentx_schedule":{let{runScheduleTool:n,resolveScheduleCaller:s}=await import("./schedule-tool-GUKYTQTY.js");return{content:[{type:"text",text:await n(e,s(e),{notifyOperator:async(o,a)=>{let c=await K(`${X()}/send`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({channel:o.channel,chatId:o.chatId,accountId:o.accountId,text:a}),signal:AbortSignal.timeout(1e4)});if(!c.ok){let l=await c.json().catch(()=>({}));throw new Error(l?.error||`HTTP ${c.status}`)}}})}]}}case"agentx_approval":{let{runApprovalTool:n}=await import("./tool-GJ75MWV5.js");return{content:[{type:"text",text:await n(e,{daemonUrl:X(),fetch:K})}]}}case"agentx_request":{let{runRequestTool:n}=await import("./tool-DFPOFXGA.js");return{content:[{type:"text",text:await n(e,{daemonUrl:X(),fetch:K})}]}}case"agentx_workflow":{let{runWorkflowTool:n}=await import("./follow-up-tool-M2KCLRQQ.js");return{content:[{type:"text",text:await n(e,{daemonUrl:X()})}]}}case"agentx_wiki_query":{let n=String(e.question||"").trim();if(!n)return{content:[{type:"text",text:"Error: `question` is required."}]};let{WikiHub:s}=await import("./wiki-ISKHNG3P.js"),{agenticQuery:i,sharedQueryEnabled:o}=await import("./query-X4AFOKZ7.js"),{loadQuerySettings:a,toolNoteSourceFor:c}=await import("./query-settings-HV2RFQK7.js"),{resolve:l}=await import("path"),d=e.wiki_dir||l(process.cwd(),".agentx/wiki"),u=new s(d,void 0,"graph"),g=e.agent||process.env.AGENTX_AGENT_ID||"";if(!g){let{existsSync:p}=await import("fs");for(let y of u.listAgents()){let v=l(u.getAgentWiki(y).baseDir,"_index.md");if(p(v)){g=y;break}}}let{QUERY_RUNS_FILE:m,recordQueryRun:f,timedQuery:w}=await import("./query-runs-VXS572PQ.js"),b=l(u.getBaseDir(),m);if(!g)return f(b,{at:new Date().toISOString(),agent:"",status:"no-catalog",source:"tool",wallMs:0}),{content:[{type:"text",text:"Error: no agent has a wiki catalog yet. Run `agentx wiki absorb` first."}]};let k=u.getAgentWiki(g),S=typeof e.max_hops=="number"?Math.min(3,Math.max(0,e.max_hops)):2,I=await Nk(),C=await a(),N=await o()?u.sharedScope(g):void 0,R=await w(b,g,"tool",()=>i(n,k,g,{maxHops:S,shared:N,messagePath:I?.path,...I?.graphWeight!==void 0?{graphWeight:I.graphWeight}:{},method:C.method,summaries:C.summaries,notes:c(C,g)}));if(R.status!=="ok")return{content:[{type:"text",text:`Query returned status "${R.status}"${R.error?`: ${R.error}`:""}`}]};let A=R.citations.map(p=>` - ${p.title} [${p.type||"?"}] (${p.path})`).join(`
|
|
44
44
|
`),M=R.walked.length,j=R.live?.length?`
|
|
45
45
|
|
|
46
46
|
Read live at the source just now:
|
|
@@ -530,7 +530,7 @@ ${l}`)}let S={agentId:o,message:k,context:{channel:w,sender:f?"opencode":"openai
|
|
|
530
530
|
|
|
531
531
|
`)}}function Ig(r){r.writeHead(200,{"Content-Type":"text/event-stream; charset=utf-8","Cache-Control":"no-cache",Connection:"keep-alive","X-Accel-Buffering":"no"})}function Tg(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 xg(r,e,t,n,s,i){let o=i?.usage?.inputTokens??0,a=i?.usage?.outputTokens??Math.ceil(s.length/4);ko(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 ko(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 Jv,statSync as Xv}from"fs";var Ct={version:"0.139.
|
|
533
|
+
`)}function xg(r,e,t,n,s,i){let o=i?.usage?.inputTokens??0,a=i?.usage?.outputTokens??Math.ceil(s.length/4);ko(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 ko(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 Jv,statSync as Xv}from"fs";var Ct={version:"0.139.1",commit:"3a6c682",startedAt:new Date(Date.now()-process.uptime()*1e3).toISOString()};function Kv(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 Rg(){let r=null;try{r=Xv(Jv(process.argv[1])).mtimeMs}catch{}let e=Kh().version;return Kv(Ct,{mtimeMs:r,version:e&&e!=="0.0.0"?e:null})}import{randomUUID as zv}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||!Yv.includes(o.kind))return;let a={...o,rootId:i.rootId,node:i.node};Zv(e,a)&&t(a)}),s=!0;return()=>{s&&(s=!1,this.count--,n())}}publish(e){let t={id:typeof e.id=="string"?e.id:zv(),at:typeof e.at=="string"?e.at:new Date().toISOString(),...e},{type:n,summary:s,ref:i,agentId:o}=Qv(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}},Yv=["run","task","signal","mesh","channel","status"];function Qv(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 Zv(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 Eg(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 Cg}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 Pt(u,d)}if(n==="gitlab"){let d=c("x-gitlab-token");return d?Pt(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 Pt(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&&Cg(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 Pt(g,m)}let u=c("x-odoo-webhook-token")??c("x-webhook-secret");return u?Pt(a,u):"missing X-Odoo-Signature or X-Odoo-Webhook-Token"}let l=c("x-webhook-secret");return l?Pt(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 Pt(r,e){if(r.length!==e.length)return"signature mismatch";let t=Buffer.from(r),n=Buffer.from(e);return Cg(t,n)?null:"signature mismatch"}kk();ac();vc();function eS(r){return{insertTaskHistory:r.prepare(`
|
|
@@ -1176,4 +1176,4 @@ data: ${JSON.stringify(G)}
|
|
|
1176
1176
|
`)}catch{}},D=setInterval(()=>{try{t.write(`: ping
|
|
1177
1177
|
|
|
1178
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(Ww(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=Ur(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()||fA(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 Ag(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=vm(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=ep();if(e==="json")return this.registry.getTodayUsage();let t=Zg(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||!Ht(n)))try{let s=ne(n,".claude","skills");uk(s,{recursive:!0});let i=ne(s,Sm);if(!Ht(i))Qa(i,ea(e)),this.log(` memory-skill: installed remember.md \u2192 ${t.id}`);else{let o=Im(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||!Ht(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 em(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-P7HH25VN.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 Wd(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-P7HH25VN.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 bm({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 fA(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 mA(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)})}gk();function $U(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{PA as a,Xi as b,rt as c,Qt as d,tn as e,ct as f,us as g,Kh as h,Hg as i,dS as j,uS as k,Xg as l,Be as m,dt as n,Yg as o,ut as p,wP as q,kP as r,Hs as s,w_ as t,a$ as u,Jo as v,UM as w,GM as x,WM as y,lm as z,F$ as A,JM as B,XM as C,fn as D,Nt as E,mn as F,bO as G,Gm as H,CI as I,hD as J,ur as K,ga as L,pa as M,mr as N,fe as O,_y as P,br as Q,AT as R,qy as S,ET as T,Ky as U,$r as V,In as W,Ar as X,Er as Y,tw as Z,Ra as _,Je as $,Re as aa,lw as ba,dw as ca,uw as da,hw as ea,Kr as fa,nk as ga,Ka as ha,rk as ia,sA as ja,DL as ka,Za as la,$U as ma};
|
|
1179
|
-
//# sourceMappingURL=chunk-
|
|
1179
|
+
//# sourceMappingURL=chunk-FUJ4KEWS.js.map
|
package/dist/cli.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
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-
|
|
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-X6EKIO24.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
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import{b as h}from"./chunk-HGU5B7IY.js";import"./chunk-RKPAVWXF.js";import{b as I}from"./chunk-
|
|
1
|
+
import{b as h}from"./chunk-HGU5B7IY.js";import"./chunk-RKPAVWXF.js";import{b as I}from"./chunk-3W2BHUGM.js";import"./chunk-SHEUCITK.js";import"./chunk-ANDEMOYE.js";import"./chunk-JS2LEURJ.js";import"./chunk-NMYPZR4W.js";import"./chunk-3UOTLMXD.js";import"./chunk-FCPTN6NF.js";import"./chunk-7I7IEX4S.js";import"./chunk-MGM33GOW.js";import"./chunk-BRK6M6LA.js";import"./chunk-UIBUZ2N3.js";import"./chunk-2QNT5JDS.js";import"./chunk-QIJLFPWE.js";var p=t=>typeof t=="string"?t.trim():"";function $(t){return[`Run ${t.runId} "${t.title}" [${t.status}] on step ${t.step}: waiting on ${t.waitingOn}.`,t.tags?.length?`Tags: ${t.tags.join(", ")}`:"",...(t.steps??[]).slice(-8).map(s=>`- ${String(s.at).slice(0,16).replace("T"," ")} ${s.step} ${s.status}${s.note?`: ${s.note}`:""}`)].filter(Boolean).join(`
|
|
2
2
|
`)}async function T(t,c){let s=c.fetch??fetch,m=c.daemonUrl.replace(/\/$/,""),r=(p(t.action)||"list").toLowerCase(),f=c.env??process.env,w=async i=>{let l=await s(`${m}${i}`,{signal:AbortSignal.timeout(1e4)}),n=await l.json().catch(()=>({}));return{ok:l.ok,status:l.status,data:n}};try{if(r==="list"){let e=await w("/follow-up");if(!e.ok)return`Error: ${e.data?.error||`HTTP ${e.status}`}`;let o=e.data.groups??[];return o.length?o.map(d=>[`${d.tag}:`,...d.rows.map(a=>`- ${a.runId} "${a.title}" on ${a.step}: waiting on ${a.waitingOn} (since ${String(a.since).slice(0,16).replace("T"," ")} UTC)`)].join(`
|
|
3
3
|
`)).join(`
|
|
4
4
|
`):"No follow-up workflows are running."}if(r==="match"){let e=p(t.request)||p(t.title);if(!e)return"Error: match needs `request`: the owner's request in their words.";let o=h(t,f),d=await w(`/follow-up/match?q=${encodeURIComponent(e)}${o?`&agentId=${encodeURIComponent(o.agentId)}`:""}`);if(!d.ok)return`Error: ${d.data?.error||`HTTP ${d.status}`}`;let a=d.data.matches??[];return a.length?["Saved workflows, best fit first (confidence 0-1):",...a.map(u=>`- ${u.workflowId} "${u.title}" (${u.confidence}${u.autoStart?", starts without asking":""}): ${u.steps.map(k=>k.id).join(" \u2192 ")}`),'Start one with {action:"start", workflowId:"<id>", title:"\u2026", tags:["client:<name>"]}. Run its steps through the workflow, not by hand.'].join(`
|
|
5
5
|
`):"No saved workflow fits. If the request has several steps, build one: start with title and steps."}if(r==="status"){let e=p(t.runId);if(!e)return"Error: status needs runId.";let o=await w(`/follow-up/${encodeURIComponent(e)}`);return o.ok?$(o.data.run):`Error: ${o.data?.error||`HTTP ${o.status}`}`}let i=h(t,f);if(!i)return"Error: couldn't tell which agent you are. Pass callerAgentId.";let l=await s(`${m}/follow-up`,{method:"POST",headers:{"Content-Type":"application/json",...I(f)},body:JSON.stringify({action:r,agentId:i.agentId,workflowId:t.workflowId,title:t.title,description:t.description,tags:t.tags,inputs:t.inputs,steps:t.steps,edges:t.edges,approval:t.approval,autoStart:t.autoStart,requestId:t.requestId,runId:t.runId,step:t.step,evidence:t.evidence,note:t.note,reason:t.reason,fromRun:t.fromRun,status:t.status}),signal:AbortSignal.timeout(2e4)}),n=await l.json().catch(()=>({}));if(!l.ok)return`Error: ${n?.error||`HTTP ${l.status}`}`;if(r==="start"){let e=n.run;return[`Started run ${e.runId} "${e.title}"${n.built?" (built from the steps you gave; propose it with fromRun to keep it)":""}.`,n.awaitingApproval?"It waits for the owner's approval before the first step.":`It is on step ${e.step}: ${e.waitingOn}.`,"AgentX follows it from here: it starts each step, reminds whoever is slow, and tells the owner when it ends. Do not run its steps by hand.",...n.warning?[`Note: ${n.warning}`]:[]].join(" ")}return r==="plan"?[`Plan recorded on run ${n.run?.runId??t.runId}:`,...(n.plan??[]).map(e=>`- ${e.id} ${e.title}`),'Report each step with {action:"step", runId, step:"<id>", status:"started"|"done"|"failed"}.'].join(`
|
|
6
6
|
`):r==="step"?`Step recorded. ${n.run?$(n.run):""}`.trim():r==="done"?`Step recorded as done. ${n.run?$(n.run):""}`.trim():r==="blocked"?"The step is marked blocked and the owner is told. Nothing more happens on it until it is sorted.":`Saved ${n.workflowId} switched off, and asked the owner (card ${n.cardId}) whether to keep it as a reusable workflow.`}catch(i){return`Error: couldn't reach the daemon (${i?.message??i}).`}}export{T as runWorkflowTool};
|
|
7
|
-
//# sourceMappingURL=follow-up-tool-
|
|
7
|
+
//# sourceMappingURL=follow-up-tool-M2KCLRQQ.js.map
|
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-FUJ4KEWS.js";import"./chunk-KO6BU3B3.js";import"./chunk-GTJIKXDB.js";import"./chunk-J7U3BASX.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-JAML6222.js";import"./chunk-YMX3QCV7.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-Z5IHT6RK.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-RIYQNADV.js";import"./chunk-2BEZU5OF.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-MUC7MMTT.js";import{a as B,b as M,c as F}from"./chunk-SFQUP3BP.js";import"./chunk-Y4CYQV7G.js";import"./chunk-3W2BHUGM.js";import"./chunk-SHEUCITK.js";import"./chunk-ANDEMOYE.js";import"./chunk-SZDACK6O.js";import"./chunk-HF5EF335.js";import"./chunk-W4YR2RO2.js";import"./chunk-AFRHKA7B.js";import{s as ut}from"./chunk-JS2LEURJ.js";import"./chunk-NMYPZR4W.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-AXHP6TMT.js";import{a as ht}from"./chunk-STIZFWDJ.js";import"./chunk-OWNQ2FD4.js";import"./chunk-KEXBEUSU.js";import"./chunk-ROLSSYXE.js";import"./chunk-HXLB2XCT.js";import"./chunk-SKSYII3K.js";import{f as gt,g as ft}from"./chunk-7KAQHNLT.js";import"./chunk-WPNRDEHW.js";import"./chunk-MWNCS57L.js";import"./chunk-4LWFJ4MZ.js";import"./chunk-BIJHMWXK.js";import"./chunk-JJZVFDG6.js";import{j as it,k as rt,l as at}from"./chunk-LMZCB7TT.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-MGM33GOW.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 Sm,b as Mr,g as Am}from"./chunk-QN2OTBVC.js";import{b as km}from"./chunk-O6WW7ATO.js";import{A as mm,B as um,D as Ta,E as pm,G as fm,H as hm,I as ym,a as Gg,e as Ht,f as Yg,g as Vg,h as Zg,i as em,k as tm,l as om,m as nm,n as Ca,o as sm,t as rm,u as im,v as Oa,w as am,z as gm}from"./chunk-JBYLG3CQ.js";import{n as Ia}from"./chunk-NQGLKWDO.js";import{a as wm,b as $m,d as Ss,e as bm}from"./chunk-B65EC44C.js";import{a as vm,b as xm}from"./chunk-XQVL2RYG.js";import"./chunk-H4NSB54D.js";import"./chunk-MTX4UPNB.js";import{d as Xg,e as Ea,h as Kg,i as Qg}from"./chunk-YXFTUOZR.js";import{a as cm,c as lm,d as dm}from"./chunk-5NTWGUSJ.js";import"./chunk-QAWKN2N4.js";import"./chunk-B2QYMUTD.js";import{a as vt}from"./chunk-EJIY45FZ.js";import{a as Hg,c as zg}from"./chunk-IMUTN7PT.js";import{a as Sa}from"./chunk-LNY6N73E.js";import{a as ia,b as pd}from"./chunk-RKPAVWXF.js";import{$ as Mg,A as pg,B as pa,C as fg,D as hg,E as yg,F as wg,G as $g,H as bg,I as kg,J as vg,K as Tg,L as Rr,M as no,N as wo,O as Rn,P as Nr,Q as jr,R as $a,S as Ig,T as Rg,U as Ng,V as jg,W as _g,X as Dg,Y as Nn,Z as jn,_ as Pg,a as vd,aa as ot,b as xd,ba as _r,ca as xs,da as ba,ea as Lg,fa as Ug,ga as xa,h as yr,ha as Bg,i as Cd,ia as Dr,j as bs,ja as _n,k as wr,ka as Pr,l as Td,la as Aa,m as Id,ma as Jg,n as Wd,o as Bd,p as Sr,q as ga,r as Xd,s as ng,t as sg,u as ig,v as ag,w as dg,x as gg,y as mg,z as ug}from"./chunk-
|
|
1
|
+
import{a as Sm,b as Mr,g as Am}from"./chunk-QN2OTBVC.js";import{b as km}from"./chunk-O6WW7ATO.js";import{A as mm,B as um,D as Ta,E as pm,G as fm,H as hm,I as ym,a as Gg,e as Ht,f as Yg,g as Vg,h as Zg,i as em,k as tm,l as om,m as nm,n as Ca,o as sm,t as rm,u as im,v as Oa,w as am,z as gm}from"./chunk-JBYLG3CQ.js";import{n as Ia}from"./chunk-NQGLKWDO.js";import{a as wm,b as $m,d as Ss,e as bm}from"./chunk-B65EC44C.js";import{a as vm,b as xm}from"./chunk-XQVL2RYG.js";import"./chunk-H4NSB54D.js";import"./chunk-MTX4UPNB.js";import{d as Xg,e as Ea,h as Kg,i as Qg}from"./chunk-YXFTUOZR.js";import{a as cm,c as lm,d as dm}from"./chunk-5NTWGUSJ.js";import"./chunk-QAWKN2N4.js";import"./chunk-B2QYMUTD.js";import{a as vt}from"./chunk-EJIY45FZ.js";import{a as Hg,c as zg}from"./chunk-IMUTN7PT.js";import{a as Sa}from"./chunk-LNY6N73E.js";import{a as ia,b as pd}from"./chunk-RKPAVWXF.js";import{$ as Mg,A as pg,B as pa,C as fg,D as hg,E as yg,F as wg,G as $g,H as bg,I as kg,J as vg,K as Tg,L as Rr,M as no,N as wo,O as Rn,P as Nr,Q as jr,R as $a,S as Ig,T as Rg,U as Ng,V as jg,W as _g,X as Dg,Y as Nn,Z as jn,_ as Pg,a as vd,aa as ot,b as xd,ba as _r,ca as xs,da as ba,ea as Lg,fa as Ug,ga as xa,h as yr,ha as Bg,i as Cd,ia as Dr,j as bs,ja as _n,k as wr,ka as Pr,l as Td,la as Aa,m as Id,ma as Jg,n as Wd,o as Bd,p as Sr,q as ga,r as Xd,s as ng,t as sg,u as ig,v as ag,w as dg,x as gg,y as mg,z as ug}from"./chunk-FUJ4KEWS.js";import{$ as fa,A as tg,D as og,E as Or,J as rg,Pa as Fg,Qa as ka,Ra as qg,Sa as va,Y as ks,aa as Tr,ba as xg,eb as Gt,gb as Wg,ia as yo,ja as Sg,ka as ha,ma as ya,oa as Ag,pa as Eg,qa as vs,ua as Ir,x as eg,xa as Cg,ya as Og,z as ua,za as wa}from"./chunk-KO6BU3B3.js";import"./chunk-GTJIKXDB.js";import{a as Sd,c as ca,e as Ad,f as Ed}from"./chunk-J7U3BASX.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 Nd,e as jd,g as _d,h as Dd,j as Pd,k as Md,l as Ld,m as Fd,p as kr,s as qd,t as Ud}from"./chunk-S2GAEH2F.js";import"./chunk-UNIXYV3U.js";import{a as Kd,b as Wo,c as Qd,d as Ar,e as Zd,f as ma,i as Er,j as Bo,k as Cr}from"./chunk-AKWBDCMU.js";import"./chunk-PPUN7YHA.js";import"./chunk-VZLIBDLH.js";import{a as cg,b as lg}from"./chunk-EKJG63EJ.js";import{b as kt}from"./chunk-EMGB23H5.js";import{c as Jd}from"./chunk-JAML6222.js";import"./chunk-YMX3QCV7.js";import"./chunk-JWDJMFU6.js";import{b as da}from"./chunk-5HLD66CW.js";import"./chunk-IS2IWRP7.js";import{c as Gd,e as vr,g as xr}from"./chunk-LQE73AZR.js";import{a as Hd,b as zd,c as Yd,d as Vd}from"./chunk-2D3RYIDU.js";import"./chunk-MRIVENBY.js";import"./chunk-A5GYG56I.js";import{a as la,p as Od}from"./chunk-Z5IHT6RK.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 na,z as Vl}from"./chunk-RIYQNADV.js";import{F as id,U as ad,W as ld,b as Sn,g as An,h as pr,i as fr,j as En,k as Xl,l as fo,n as qo}from"./chunk-2BEZU5OF.js";import{e as od,f as Uo,k as On}from"./chunk-QSKG754X.js";import{b as Fo}from"./chunk-MEFPXHQJ.js";import"./chunk-MUC7MMTT.js";import"./chunk-SFQUP3BP.js";import{a as oo}from"./chunk-Y4CYQV7G.js";import{b as In,e as ud}from"./chunk-3W2BHUGM.js";import{a as gd,b as md}from"./chunk-SHEUCITK.js";import{a as aa,e as fd,f as hd,h as yd,i as wd,k as $d,l as bd,m as kd}from"./chunk-ANDEMOYE.js";import{c as Ql}from"./chunk-SZDACK6O.js";import{f as Zl,g as ed,h as sa}from"./chunk-HF5EF335.js";import"./chunk-W4YR2RO2.js";import"./chunk-AFRHKA7B.js";import{a as zl,k as td}from"./chunk-JS2LEURJ.js";import{a as hr,b as ge,c as We,d as Tn,e as nd}from"./chunk-NMYPZR4W.js";import{a as sd,b as ra,h as rd}from"./chunk-3UOTLMXD.js";import"./chunk-36AXN6ZB.js";import"./chunk-PE5V2LI7.js";import{a as oa,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 Cn}from"./chunk-GV7S5BAX.js";import{e as Kl}from"./chunk-GSX4NK2N.js";import"./chunk-VWTBHLMX.js";import{a as ta}from"./chunk-AXHP6TMT.js";import"./chunk-STIZFWDJ.js";import{c as Gl}from"./chunk-OWNQ2FD4.js";import"./chunk-KEXBEUSU.js";import"./chunk-ROLSSYXE.js";import"./chunk-HXLB2XCT.js";import"./chunk-SKSYII3K.js";import{g as po,i as ur}from"./chunk-7KAQHNLT.js";import"./chunk-WPNRDEHW.js";import"./chunk-MWNCS57L.js";import"./chunk-4LWFJ4MZ.js";import"./chunk-BIJHMWXK.js";import"./chunk-JJZVFDG6.js";import{a as Xi,b as kl,c as oy,d as lr,e as vl,f as Dl,g as ly,h as Pl,i as dy,m as xn,n as Ll,q as Fl,u as ql,y as Jt,z as $s}from"./chunk-LMZCB7TT.js";import{a as to,b as vn,e as Nl,f as ws}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 ty}from"./chunk-7I7IEX4S.js";import"./chunk-JHCI6XCJ.js";import"./chunk-DSYKYZMT.js";import{Aa as le,D as Ol,E as ry,S as Tl,T as Qi,V as Zi,aa as iy,ba as ea,da as Il,ea as ay,h as xl,i as Sl,j as Al,k as El,l as Cl,m as ny,qa as Rl,ra as dr,sa as O,ta as gr,v as Ki,w as sy}from"./chunk-MGM33GOW.js";import"./chunk-BRK6M6LA.js";import{f as bt,n as ys}from"./chunk-UIBUZ2N3.js";import"./chunk-2QNT5JDS.js";import{g as jl,k as _l,l as cy}from"./chunk-RGDKHYY2.js";import"./chunk-QIJLFPWE.js";import{Command as gy}from"commander";var Em=new gy().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 xd({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 my}from"commander";import{execFileSync as Tt}from"node:child_process";import{cpSync as Cm,existsSync as ja,mkdirSync as Ra,mkdtempSync as uy,readdirSync as py,rmSync as Om,writeFileSync as Tm}from"node:fs";import{dirname as Im,join as nt}from"node:path";import{homedir as Lr,release as fy,tmpdir as hy}from"node:os";import{setTimeout as yy}from"node:timers/promises";function Fr(){let e=Rd(process.platform,process.arch,fy());if(e)throw new Error(e)}function qr(){let e=nt(Lr(),"Library/LaunchAgents"),t=[];try{t=py(e).filter(_d)}catch{}let o=t.flatMap(n=>{let s=nt(e,n);try{return[{file:s,plist:Tt("plutil",["-convert","xml1","-o","-",s],{encoding:"utf8",stdio:["ignore","pipe","ignore"]})}]}catch{return[]}});return Dd(e,o)}function As(e){return`gui/${process.getuid()}/${e.label}`}async function Na(e){try{Tt("launchctl",["bootout",As(e)],{stdio:"pipe"})}catch{}for(let t=0;t<30;t++){try{Tt("launchctl",["print",As(e)],{stdio:"pipe"})}catch{return}await yy(200)}throw new Error("The desktop login service did not stop. Retry agentx desktop stop before installing.")}function Rm(e){if(!ja(e.file))throw new Error("Run agentx desktop install first.");let t=!0;try{Tt("launchctl",["print",As(e)],{stdio:"pipe"})}catch{t=!1}t||Tt("launchctl",["bootstrap",`gui/${process.getuid()}`,e.file],{stdio:"pipe"}),Tt("launchctl",["kickstart",As(e)],{stdio:"pipe"})}function wy(){let e=Fd();if(e&&ja(nt(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 Dn=new my("desktop").description("install and control the macOS desktop assistant");Dn.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=Nd(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=wy(),i=nt(Lr(),"Applications"),{keep:a,remove:c}=qr();if(console.log(`Desktop assistant \u2192 ${nt(i,$r)}
|
|
2
2
|
Computer-use helper \u2192 ${nt(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 tk(e,t){if(e.kind==="path"){let s=e.pts,r=s[Math.fl
|
|
|
493
493
|
Plan ${V.dim(`[${s.state}] by ${s.createdBy}, ${Qn(s.createdAt)}`)}`),gf(n.steps(e)),console.log(`
|
|
494
494
|
Log`);for(let r of n.events(e))console.log(V.dim(` ${Qn(r.at)} ${r.step?`step ${r.step} `:""}${r.kind}: ${r.detail}`))}});mn.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=xi();if(!s)return;let r=df();if(!r){process.exitCode=1;return}let i=Number(t),a=sg(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)")),gf(r.steps(e))});function uf(e,t,o,n){mn.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=xi();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 (${lf[c.state]??c.state})`)),process.exitCode=1;return}console.log(V.green(` \u2713 ${s} ${e==="done"?"closed as done":"dropped"}`))})}uf("done","close a request as finished, with a link to the evidence","--evidence <link>","a finished request needs --evidence <link>");uf("drop","drop a request you no longer want","--reason <text>","");mn.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 tg(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=ua();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??ng;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 Ev}from"commander";import Zn from"chalk";le();var pf=new Ev("request-status").description("show each person the state of their request in the thread where they asked (GitLab, GitHub)").argument("[channel]",Oa.join(" or ")).argument("[state]","on or off").action(async(e,t)=>{if(e!==void 0){t!=="on"&&t!=="off"&&(console.error(Zn.red("\u2717 say on or off, for example: agentx request-status gitlab on")),process.exit(1));let n="",s=await ge(r=>{n=am(r,e,t==="on")});s.success||(console.error(Zn.red(`\u2717 ${s.error}`)),process.exit(1)),console.log(Zn.green(`\u2713 ${n}`)),s.reloaded&&console.log(Zn.dim(" daemon hot-reloaded"))}let o=O().requestStatus.channels.map(n=>n.toLowerCase());for(let n of Oa)console.log(` ${n.padEnd(8)} ${o.includes(n)?Zn.green("on"):Zn.dim("off")}`)});import{Command as Vv}from"commander";import es from"chalk";import ye from"chalk";import zs from"prompts";import{readFileSync as Cv,writeFileSync as Ov,existsSync as Tv}from"fs";import{resolve as ff}from"path";var Iv=/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/;function Rv(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 hf(e=".env"){let t=ff(e);if(!Tv(t))return[];let o=Cv(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(Iv);r?n.push({kind:"kv",key:r[1],value:Rv(r[2]),raw:s}):n.push({kind:"comment",raw:s})}return n.length&&n[n.length-1].kind==="blank"&&n.pop(),n}function Nv(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=ff(o),s=hf(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)}`})),Ov(n,Nv(s))}function _c(e,t=".env"){return hf(t).find(s=>s.kind==="kv"&&s.key===e)?.value}var yf="https://t.me/BotFather";async function jv(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 _v(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 wf(e={}){console.log(),console.log(ye.bold(" Connect Telegram")),console.log(),console.log(ye.dim(` 1. Open ${yf}`)),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(yf);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 jv(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=Tn(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 _v(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 Dv,readdirSync as Pv,readFileSync as $f}from"fs";import{resolve as Pc}from"path";var Mv=".agentx/whatsapp-sessions";function Lv(){let e=Pc(process.cwd(),".agentx/daemon.pid");if(!Mc(e))return!1;try{let t=parseInt($f(e,"utf-8").trim(),10);return t?(process.kill(t,0),!0):!1}catch{return!1}}function Fv(e){if(!Mc(e))return!1;try{return Pv(e).some(t=>t.includes("creds")||t.endsWith(".json"))}catch{return!1}}async function qv(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;Dv(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 bf(e={}){if(console.log(),console.log(Ce.bold(" Connect WhatsApp")),console.log(),Lv()){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($f(t,"utf-8")),n=e.sessionDir||o.channels?.whatsapp?.sessionDir||Mv,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(Fv(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 qv(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 Uv}from"crypto";import Fe from"chalk";import kf from"prompts";var Si="agentx-mesh:",Ai="//join/";function Wv(e){return Buffer.from(e,"utf-8").toString("base64url")}function Bv(e){return Buffer.from(e,"base64url").toString("utf-8")}function Jv(e){return`${Si}${Ai}${Wv(JSON.stringify(e))}`}function Gv(e){if(!e.startsWith(`${Si}${Ai}`))throw new Error(`Not a mesh invite link \u2014 expected ${Si}${Ai}\u2026`);let t=e.slice(Si.length+Ai.length),o=JSON.parse(Bv(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 Hv(){return Uv(32).toString("hex")}function zv(e){let t=e?.node?.bind||"127.0.0.1:18800",[o,n]=t.split(":");return`http://${o}:${n}`}function Yv(e){return e?.node?.name||e?.node?.id||"node"}async function vf(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=Hv(),Hs("MESH_TOKEN",i),console.log(Fe.dim(" Generated new MESH_TOKEN (32 bytes) and saved to .env")));let a=e.url||zv(r);if((a.includes("0.0.0.0")||a.includes("127.0.0.1")||a.includes("localhost"))&&!e.url){let{url:d}=await kf({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=Jv({url:a,token:i,name:Yv(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 xf(e,t={}){let o;try{o=Gv(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 kf({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=Tn(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 ts=new Vv().name("connect").description("connect a channel or mesh peer \u2014 no manual file editing required");ts.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 wf({agent:e.agent,account:e.account,configPath:e.config,skipChatCapture:!!e.skipChatCapture})});ts.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 bf({agent:e.agent,sessionDir:e.sessionDir,skipPair:!!e.skipPair,configPath:e.config})});var Sf=ts.command("mesh").description("mesh invite + join \u2014 replaces manual MESH_TOKEN copy-paste");Sf.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 vf({url:e.url,configPath:e.config})});Sf.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 xf(e,{configPath:t.config})});ts.action(()=>{console.log(),console.log(es.bold(" agentx connect \u2014 which channel?")),console.log(),console.log(` ${es.cyan("agentx connect telegram")} \u2014 pair a Telegram bot`),console.log(` ${es.cyan("agentx connect whatsapp")} \u2014 pair WhatsApp via QR`),console.log(` ${es.cyan("agentx connect mesh invite")} \u2014 emit a mesh join link for another node`),console.log(` ${es.cyan("agentx connect mesh join <link>")} \u2014 accept a mesh invite`),console.log(),console.log(es.dim(" GitLab connect is the next slice.")),console.log()});le();import{Command as Xv}from"commander";import te from"chalk";var un=new Xv().name("usage").description("token usage analysis and reporting");un.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}`))}});un.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}`))}});un.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?"))}});un.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()});un.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-7VHY5RVZ.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 Kv}from"commander";import Pe from"chalk";import{readFileSync as Qv}from"fs";import{resolve as Zv}from"path";le();var Ro=new Kv().name("board").description("kanban board dashboard \u2014 visual work view over configured sources");Ro.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(Zv(process.cwd(),".env"))}catch{}let t=ex(e.port,e.bind),{startBoardDashboard:o}=await import("./board-dashboard-GBEBQUFJ.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 ex(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=ff(o),s=hf(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)}`})),Ov(n,Nv(s))}function _c(e,t=".env"){return hf(t).find(s=>s.kind==="kv"&&s.key===e)?.value}var yf="https://t.me/BotFather";async function jv(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 _v(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 wf(e={}){console.log(),console.log(ye.bold(" Connect Telegram")),console.log(),console.log(ye.dim(` 1. Open ${yf}`)),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(yf);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 jv(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=Tn(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 _v(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 Dv,readdirSync as Pv,readFileSync as $f}from"fs";import{resolve as Pc}from"path";var Mv=".agentx/whatsapp-sessions";function Lv(){let e=Pc(process.cwd(),".agentx/daemon.pid");if(!Mc(e))return!1;try{let t=parseInt($f(e,"utf-8").trim(),10);return t?(process.kill(t,0),!0):!1}catch{return!1}}function Fv(e){if(!Mc(e))return!1;try{return Pv(e).some(t=>t.includes("creds")||t.endsWith(".json"))}catch{return!1}}async function qv(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;Dv(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 bf(e={}){if(console.log(),console.log(Ce.bold(" Connect WhatsApp")),console.log(),Lv()){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($f(t,"utf-8")),n=e.sessionDir||o.channels?.whatsapp?.sessionDir||Mv,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(Fv(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 qv(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 Uv}from"crypto";import Fe from"chalk";import kf from"prompts";var Si="agentx-mesh:",Ai="//join/";function Wv(e){return Buffer.from(e,"utf-8").toString("base64url")}function Bv(e){return Buffer.from(e,"base64url").toString("utf-8")}function Jv(e){return`${Si}${Ai}${Wv(JSON.stringify(e))}`}function Gv(e){if(!e.startsWith(`${Si}${Ai}`))throw new Error(`Not a mesh invite link \u2014 expected ${Si}${Ai}\u2026`);let t=e.slice(Si.length+Ai.length),o=JSON.parse(Bv(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 Hv(){return Uv(32).toString("hex")}function zv(e){let t=e?.node?.bind||"127.0.0.1:18800",[o,n]=t.split(":");return`http://${o}:${n}`}function Yv(e){return e?.node?.name||e?.node?.id||"node"}async function vf(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=Hv(),Hs("MESH_TOKEN",i),console.log(Fe.dim(" Generated new MESH_TOKEN (32 bytes) and saved to .env")));let a=e.url||zv(r);if((a.includes("0.0.0.0")||a.includes("127.0.0.1")||a.includes("localhost"))&&!e.url){let{url:d}=await kf({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=Jv({url:a,token:i,name:Yv(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 xf(e,t={}){let o;try{o=Gv(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 kf({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=Tn(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 ts=new Vv().name("connect").description("connect a channel or mesh peer \u2014 no manual file editing required");ts.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 wf({agent:e.agent,account:e.account,configPath:e.config,skipChatCapture:!!e.skipChatCapture})});ts.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 bf({agent:e.agent,sessionDir:e.sessionDir,skipPair:!!e.skipPair,configPath:e.config})});var Sf=ts.command("mesh").description("mesh invite + join \u2014 replaces manual MESH_TOKEN copy-paste");Sf.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 vf({url:e.url,configPath:e.config})});Sf.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 xf(e,{configPath:t.config})});ts.action(()=>{console.log(),console.log(es.bold(" agentx connect \u2014 which channel?")),console.log(),console.log(` ${es.cyan("agentx connect telegram")} \u2014 pair a Telegram bot`),console.log(` ${es.cyan("agentx connect whatsapp")} \u2014 pair WhatsApp via QR`),console.log(` ${es.cyan("agentx connect mesh invite")} \u2014 emit a mesh join link for another node`),console.log(` ${es.cyan("agentx connect mesh join <link>")} \u2014 accept a mesh invite`),console.log(),console.log(es.dim(" GitLab connect is the next slice.")),console.log()});le();import{Command as Xv}from"commander";import te from"chalk";var un=new Xv().name("usage").description("token usage analysis and reporting");un.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}`))}});un.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}`))}});un.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?"))}});un.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()});un.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-X6EKIO24.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 Kv}from"commander";import Pe from"chalk";import{readFileSync as Qv}from"fs";import{resolve as Zv}from"path";le();var Ro=new Kv().name("board").description("kanban board dashboard \u2014 visual work view over configured sources");Ro.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(Zv(process.cwd(),".env"))}catch{}let t=ex(e.port,e.bind),{startBoardDashboard:o}=await import("./board-dashboard-GBEBQUFJ.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 ex(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}}}}Ro.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)}});Ro.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)}});Ro.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=Ro.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(Qv("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)}});Ro.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 uS}from"commander";import Ke from"chalk";import{writeFileSync as pS,mkdirSync as fS,existsSync as Hi,readFileSync as qh,appendFileSync as Uh}from"fs";import{resolve as ps,dirname as Jh}from"path";import{homedir as pl}from"os";var Gh=new uS().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 uS}from"commander";import Ke from"chalk";import{writeFileSync as pS,mkdirSync as fS,existsSync as Hi,readFileSync as qh,appendFileSync as Uh}from"fs";import{resolve as ps,dirname as Jh}from"path";import{homedir as pl}from"os";var Gh=new uS().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-X6EKIO24.js"),o=await t(),n=String(e.shell||"zsh").toLowerCase(),s;switch(n){case"zsh":s=wS(o);break;case"bash":s=xS(o);break;case"fish":s=SS(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?ps(String(e.output)):hS(n);fS(Jh(r),{recursive:!0}),pS(r,s),console.log(Ke.green(`\u2713 Wrote ${n} completion to:`)),console.log(` ${r}`),console.log(),yS(n,r,!!e.yes)});function hS(e){let t=pl();switch(e){case"zsh":return ps(t,".zsh","completions","_agentx");case"bash":return ps(t,".bash_completion.d","agentx.bash");case"fish":return ps(t,".config","fish","completions","agentx.fish");default:throw new Error(`unsupported shell: ${e}`)}}function yS(e,t,o){if(e==="zsh"){let n=ps(pl(),".zshrc"),s=Jh(t),r=`fpath=(${s} $fpath)`,i="autoload -U compinit && compinit",a=Hi(n)?qh(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&&Hi(n)){let g=["","# agentx completion"];c&&g.push(r),l&&g.push(i),Uh(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=ps(pl(),".bashrc"),s=`[ -f "${t}" ] && source "${t}"`;if((Hi(n)?qh(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&&Hi(n)){Uh(n,`
|
|
763
763
|
# agentx completion
|
|
@@ -773,4 +773,4 @@ ${AS({...t,category:e.category})}`),g&&a.set(l.id,g)}let c=`https://github.com/$
|
|
|
773
773
|
${o.url}`)}catch(t){console.error(hs.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 OS():await CS(e,"utf8"),s=JSON.parse(n),{version:r}=await yr(),i=Vh({...s,fields:s.fields??{}},{model:t.model,version:r??"unknown"});console.log(i)}catch(o){console.error(hs.red(` ${o.message}`)),process.exitCode=1}});async function OS(){let e=[];for await(let t of process.stdin)e.push(t);return Buffer.concat(e).toString("utf8")}async function YM(){let e=await yr(),t=new TS().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=lm(n);dm(s)&&cm("cli",s)});for(let o of[Km,Xm,ts,Dn,zo,It,zg,Yo,Hr,ro,ao,mn,pf,Io,Gr,hh,Eo,Mu,Lu,zu,Us,sp,cp,Du,gp,mp,Tp,jp,_p,Et,un,vh,Is,ko,Gh,ar])t.addCommand(o);for(let o of Kh)t.addCommand(o,{hidden:!0});return t.addHelpText("after",()=>{let o=Kh.map(n=>n.name()).sort();return["",`Advanced (${o.length}, hidden \u2014 run \`agentx <command> --help\` for any of them):`,IS(o,74," "),""].join(`
|
|
774
774
|
`)}),t}var Kh=[J,ss,lo,Ut,js,Vn,Ve,co,Ro,dn,cs,ls,So,ut,Un,ri,ki,ln,uh,ph,so,xt,Em,Xo,rr,Ga,_o,yt,Fn,Ha,Fi,Zt,go,_h,Dh,bh];function IS(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{YM as buildProgram};
|
|
776
|
-
//# sourceMappingURL=program-
|
|
776
|
+
//# sourceMappingURL=program-X6EKIO24.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import{b as h}from"./chunk-HGU5B7IY.js";import"./chunk-RKPAVWXF.js";import{b as l}from"./chunk-
|
|
1
|
+
import{b as h}from"./chunk-HGU5B7IY.js";import"./chunk-RKPAVWXF.js";import{b as l}from"./chunk-3W2BHUGM.js";import"./chunk-SHEUCITK.js";import"./chunk-ANDEMOYE.js";import"./chunk-JS2LEURJ.js";import"./chunk-NMYPZR4W.js";import"./chunk-3UOTLMXD.js";import"./chunk-FCPTN6NF.js";import"./chunk-7I7IEX4S.js";import"./chunk-MGM33GOW.js";import"./chunk-BRK6M6LA.js";import"./chunk-UIBUZ2N3.js";import"./chunk-2QNT5JDS.js";import"./chunk-QIJLFPWE.js";var g=t=>typeof t=="string"?t.trim():"",w={in_progress:"in progress",waiting_owner:"waiting on the owner",waiting_other:"waiting on another agent",needs_attention:"needs attention"};function f(t){return t.length?t.map(e=>{let a=new Date(e.createdAt).toISOString().slice(0,16).replace("T"," "),c=e.state==="waiting_owner"&&e.question?` Question: ${e.question}`:e.state==="needs_attention"&&e.attentionReason?` ${e.attentionReason}`:"",n=String(e.text).replace(/\s+/g," ").trim();return`- ${e.id} [${w[e.state]??e.state}] ${a} UTC, ${e.channel}, ${e.agentId}: ${n.length>160?`${n.slice(0,159)}\u2026`:n}.${c}`}).join(`
|
|
2
2
|
`):"No open requests."}function $(t){return(t??[]).map(e=>`${e.idx}. ${e.name} (${e.agentId}, ${e.kind}) [${e.state}]${e.needsApproval?` approval: ${e.approval}`:""}`)}async function y(t,e){let a=e.fetch??fetch,c=e.daemonUrl.replace(/\/$/,""),n=(g(t.action)||"list").toLowerCase();try{if(n==="list"){let u=await a(`${c}/requests`,{signal:AbortSignal.timeout(1e4)}),r=await u.json().catch(()=>({}));return u.ok?r.enabled?`${r.count} open request${r.count===1?"":"s"}, oldest first.
|
|
3
3
|
${f(r.items??[])}`:"Requests are turned off on this node (requests.enabled), so nothing is recorded.":`Error: ${r?.error||`HTTP ${u.status}`}`}let i=e.env??process.env,p=h(t,i);if(!p)return"Error: couldn't tell which agent you are. Pass callerAgentId.";let d=await a(`${c}/requests`,{method:"POST",headers:{"Content-Type":"application/json",...l(i)},body:JSON.stringify({action:n,agentId:p.agentId,id:t.id,question:t.question,evidence:t.evidence,reason:t.reason,steps:t.steps,step:t.step,status:t.status,note:t.note}),signal:AbortSignal.timeout(1e4)}),s=await d.json().catch(()=>({}));if(!d.ok)return`Error: ${s?.error||`HTTP ${d.status}`}`;let o=s.request;return n==="accept"&&s.plan?[`Request ${o.id} is open with a plan of ${s.steps.length} steps. The daemon hands each step to its agent in order, nudges a step that goes quiet, and tells the owner when it is finished or blocked.`,...$(s.steps),`Steps the owner approves get a decision card now; an approved message is sent by the daemon itself. Report your own steps with {action:"step", id:"${o.id}", step:<n>, status:"progress"|"done"|"blocked"}.`].join(`
|
|
4
4
|
`):n==="step"?[`Step reported. Plan of ${o.id}:`,...$(s.steps)].join(`
|
|
5
5
|
`):n==="accept"?`Request ${o.id} is open and in progress. Close it with done (and a link to the evidence) or decline (and the reason); it stays on the owner's list until then.`:n==="wait"?`Request ${o.id} is waiting on the owner with your question. Ask them the question now, once.`:n==="done"?`Request ${o.id} is closed as done. Evidence: ${o.evidence}`:`Request ${o.id} is closed as declined. Reason: ${o.closeReason}`}catch(i){return`Error: couldn't reach the daemon (${i?.message??i}).`}}export{f as describeOpen,$ as describeSteps,y as runRequestTool};
|
|
6
|
-
//# sourceMappingURL=tool-
|
|
6
|
+
//# sourceMappingURL=tool-DFPOFXGA.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{b as f}from"./chunk-HGU5B7IY.js";import"./chunk-RKPAVWXF.js";import{f as y}from"./chunk-Y4CYQV7G.js";import{b as u}from"./chunk-
|
|
1
|
+
import{b as f}from"./chunk-HGU5B7IY.js";import"./chunk-RKPAVWXF.js";import{f as y}from"./chunk-Y4CYQV7G.js";import{b as u}from"./chunk-3W2BHUGM.js";import"./chunk-SHEUCITK.js";import"./chunk-ANDEMOYE.js";import"./chunk-JS2LEURJ.js";import"./chunk-NMYPZR4W.js";import"./chunk-3UOTLMXD.js";import"./chunk-FCPTN6NF.js";import"./chunk-7I7IEX4S.js";import"./chunk-MGM33GOW.js";import"./chunk-BRK6M6LA.js";import"./chunk-UIBUZ2N3.js";import"./chunk-2QNT5JDS.js";import"./chunk-QIJLFPWE.js";var w=new Set(["cron","heartbeat","api","a2a","mesh","approvals","requests"]);function c(t){return typeof t=="string"?t.trim():""}async function b(t,s){let d=s.fetch??fetch,l=s.daemonUrl.replace(/\/$/,""),i=(c(t.action)||"create").toLowerCase(),o=f(t,s.env??process.env);if(i==="status"){let n=c(t.id);if(!n)return"Error: `id` is required for status (the card id you got when you raised it).";try{let a=await d(`${l}/approvals/${encodeURIComponent(n)}`,{signal:AbortSignal.timeout(1e4)}),r=await a.json().catch(()=>({}));if(!a.ok)return`Error: ${r?.error||`HTTP ${a.status}`}`;let e=r.card;if(o&&e.raised_by!==o.agentId)return`Error: card "${n}" was raised by another agent.`;if(e.status==="pending")return`Card ${e.id} is still waiting for the operator. It expires ${e.expires}; then "${e.if_silent}" applies.`;if(e.status==="decided"){let h=e.verdict==="yes"?y(e).join(`
|
|
2
2
|
`):"";return`Card ${e.id}: the operator said ${String(e.verdict).toUpperCase()}.${e.note?` Note: ${e.note}`:""}${h?`
|
|
3
3
|
${h}`:""}`}return e.status==="resolved"?`Card ${e.id} was closed by ${e.raised_by}: ${e.resolution??"answered outside the card"}.`:`Card ${e.id} expired unanswered; the default applied: ${e.outcome??e.if_silent}.`}catch(a){return`Error: couldn't reach the daemon (${a?.message??a}).`}}if(i==="resolve"){let n=c(t.id),a=c(t.reason);if(!n)return"Error: `id` is required for resolve (the card id you got when you raised it).";if(!a)return'Error: `reason` is required for resolve: where the owner answered and what you did, e.g. "approved in chat, done".';if(!o)return"Error: couldn't tell which agent you are. Pass callerAgentId.";try{let r=await d(`${l}/approvals/${encodeURIComponent(n)}/resolve`,{method:"POST",headers:{"Content-Type":"application/json",...u(s.env??process.env)},body:JSON.stringify({raised_by:o.agentId,reason:a}),signal:AbortSignal.timeout(1e4)}),e=await r.json().catch(()=>({}));return r.ok?`Card ${e.card.id} is closed. It left the operator's inbox, so nobody will be asked about it again.`:`Error: ${e?.error||`HTTP ${r.status}`}`}catch(r){return`Error: couldn't reach the daemon (${r?.message??r}).`}}if(i!=="create")return`Error: unknown action "${i}". Use create, status or resolve.`;if(!o)return"Error: couldn't tell which agent you are. Pass callerAgentId.";let p=o.channel&&o.chatId&&!w.has(o.channel)?{channel:o.channel,chatId:o.chatId,...o.accountId?{accountId:o.accountId}:{}}:void 0,$={title:t.title,ask:t.ask,recommend:t.recommend,if_silent:t.if_silent,expires:t.expires,source:t.source,choices:t.choices,draft:t.draft,say:t.say,context:t.context,raised_by:o.agentId,...p?{reply:p}:{}};try{let n=await d(`${l}/approvals`,{method:"POST",headers:{"Content-Type":"application/json",...u(s.env??process.env)},body:JSON.stringify($),signal:AbortSignal.timeout(1e4)}),a=await n.json().catch(()=>({}));if(!n.ok)return`Error: ${a?.error||`HTTP ${n.status}`}`;let r=a.card;return[`Decision card ${r.id} is in the operator's Approvals inbox.`,`If nobody answers by ${r.expires}, "${r.if_silent}" applies.`,"You will get a message with the result. Tell the requester it is waiting for approval; don't ask the operator again in chat.",`If the operator answers the same thing in chat and you act on it, close the card with {action:"resolve", id:"${r.id}", reason:"approved in chat, done"} so it is not asked again.`].join(" ")}catch(n){return`Error: couldn't reach the daemon (${n?.message??n}).`}}export{b as runApprovalTool};
|
|
4
|
-
//# sourceMappingURL=tool-
|
|
4
|
+
//# sourceMappingURL=tool-GJ75MWV5.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentix-cli",
|
|
3
|
-
"version": "0.139.
|
|
3
|
+
"version": "0.139.1",
|
|
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-Q4XP5TBI.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import{a as w,b as f}from"./chunk-SHEUCITK.js";import{randomUUID as E}from"crypto";var y=["ringing","answered","later"],p=`id, agent_id AS agentId, kind, reason, urgency, status, created_at AS createdAt,
|
|
2
|
-
ringing_since AS ringingSince, answered_at AS answeredAt, ended_at AS endedAt,
|
|
3
|
-
ring_again_at AS ringAgainAt, note, summary, channel, chat_id AS chatId`,A=class{constructor(t){this.db=t;t.exec(`CREATE TABLE IF NOT EXISTS calls (
|
|
4
|
-
id TEXT PRIMARY KEY, agent_id TEXT NOT NULL, reason TEXT NOT NULL,
|
|
5
|
-
urgency TEXT NOT NULL, status TEXT NOT NULL, created_at INTEGER NOT NULL,
|
|
6
|
-
ringing_since INTEGER, answered_at INTEGER, ended_at INTEGER,
|
|
7
|
-
ring_again_at INTEGER, note TEXT, summary TEXT);
|
|
8
|
-
CREATE INDEX IF NOT EXISTS calls_recent ON calls(created_at DESC);`);let s=new Set(t.prepare("PRAGMA table_info(calls)").all().map(e=>e.name));s.has("kind")||t.exec("ALTER TABLE calls ADD COLUMN kind TEXT NOT NULL DEFAULT 'voice'"),s.has("channel")||t.exec("ALTER TABLE calls ADD COLUMN channel TEXT"),s.has("chat_id")||t.exec("ALTER TABLE calls ADD COLUMN chat_id TEXT")}insert(t){this.db.prepare(`INSERT INTO calls (id, agent_id, kind, reason, urgency, status, created_at, ringing_since,
|
|
9
|
-
answered_at, ended_at, ring_again_at, note, summary, channel, chat_id) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`).run(t.id,t.agentId,t.kind,t.reason,t.urgency,t.status,t.createdAt,t.ringingSince,t.answeredAt,t.endedAt,t.ringAgainAt,t.note,t.summary,t.channel,t.chatId)}get(t){return this.db.prepare(`SELECT ${p} FROM calls WHERE id=?`).get(t)}update(t,s){let e={reason:"reason",urgency:"urgency",status:"status",ringingSince:"ringing_since",answeredAt:"answered_at",endedAt:"ended_at",ringAgainAt:"ring_again_at",note:"note",summary:"summary"},n=Object.keys(s).filter(r=>r in e);if(n.length){let r=n.map(i=>`${e[i]}=?`).join(", ");this.db.prepare(`UPDATE calls SET ${r} WHERE id=?`).run(...n.map(i=>s[i]??null),t)}return this.get(t)}transition(t,s,e){let n=Array.isArray(s)?s:[s],r={status:"status",ringingSince:"ringing_since",answeredAt:"answered_at",endedAt:"ended_at",ringAgainAt:"ring_again_at",note:"note",summary:"summary"},i=Object.keys(e).filter(l=>l in r),o=i.map(l=>`${r[l]}=?`).join(", "),u=n.map(()=>"?").join(",");return this.db.prepare(`UPDATE calls SET ${o} WHERE id=? AND status IN (${u})`).run(...i.map(l=>e[l]??null),t,...n).changes?this.get(t):void 0}list(t={}){let s=Math.min(Math.max(t.limit??50,1),200),e=[],n=[];t.status?.length&&(e.push(`status IN (${t.status.map(()=>"?").join(",")})`),n.push(...t.status)),t.kind&&(e.push("kind=?"),n.push(t.kind));let r=e.length?`WHERE ${e.join(" AND ")}`:"";return this.db.prepare(`SELECT ${p} FROM calls ${r} ORDER BY created_at DESC, rowid DESC LIMIT ?`).all(...n,s)}countSince(t,s){return this.db.prepare("SELECT COUNT(*) AS n FROM calls WHERE agent_id=? AND created_at>?").get(t,s).n}};var C=200,S=1e4,I=10,T=240;function L(a=process.env){return a.AGENTX_TASK_ID?{"X-AgentX-Task":a.AGENTX_TASK_ID}:a.AGENTX_CHANNEL&&a.AGENTX_CHAT_ID?{"X-AgentX-Channel":a.AGENTX_CHANNEL,"X-AgentX-Chat":a.AGENTX_CHAT_ID}:{}}function _(a){return`[Call answered] The owner just picked up the voice call you asked for. You called because: "${a.reason}". Speak first: greet them in a few words and say why you called, then let them answer.`}var x="[Call ended] The owner hung up. In two or three plain sentences, summarise what was said and decided on this call, for your records. Reply with the summary only.",k=class{constructor(t){this.deps=t}widgetSeenAt=0;widget={};waiting=new Map;now(){return this.deps.now?.()??Date.now()}name(t){return this.deps.agentName(t)??t}async request(t,s={}){let e=String(t.agentId??"").trim(),n=String(t.reason??"").replace(/\s+/g," ").trim(),r=t.urgency==="urgent"?"urgent":"normal",i=t.kind==="camera"?"camera":"voice";if(t.kind!==void 0&&t.kind!=="voice"&&t.kind!=="camera")return{ok:!1,status:400,error:"kind must be voice or camera"};if(!e||!n)return{ok:!1,status:400,error:"Required: agentId and reason"};if(n.length>C)return{ok:!1,status:413,error:`reason is over ${C} characters; keep it to one line`};if(this.deps.agentName(e)===null)return{ok:!1,status:404,error:`Unknown agent: ${e}`};if(!this.deps.isRunningTurn(e,s))return{ok:!1,status:403,error:`No running turn of ${e} placed this call. An agent calls from inside its own run.`};let o=this.deps.config();if(!o.allow.includes("*")&&!o.allow.includes(e))return{ok:!1,status:403,error:`${e} ${i==="camera"?"may not ask to see through the owner's camera":"may not call the owner"}. Add it to calls.allow in agentx.json (or: agentx call allow ${e}).`};if(await this.sweep(),this.deps.store.list({status:y,limit:200}).some(m=>m.agentId===e))return{ok:!1,status:409,error:`${e} already has a call in progress`};let u=this.now();if(this.deps.store.countSince(e,u-36e5)>=o.maxPerHour)return{ok:!1,status:429,error:`${e} has placed ${o.maxPerHour} calls in the last hour (calls.maxPerHour)`};let d=i==="camera"?this.deps.turnSession?.(e,s)??s:{},l={id:`call-${E().slice(0,8)}`,agentId:e,kind:i,reason:n,urgency:r,status:"ringing",createdAt:u,ringingSince:u,answeredAt:null,endedAt:null,ringAgainAt:null,note:null,summary:null,channel:d.channel&&d.chatId?d.channel:null,chatId:d.channel&&d.chatId?d.chatId:null};this.deps.store.insert(l),this.deps.log?.(`[calls] ${e} asks ${i==="camera"?"to see":"for a call"} (${r}): ${n}`);let g=await this.ring(l),h=this.deps.store.get(l.id);return g!=="waiting"?{ok:!0,call:h,rang:g}:this.waiting.has(l.id)?{ok:!0,call:h,rang:g,busy:this.busyInfo(l)}:{ok:!0,call:h,rang:"widget"}}async cancel(t,s={}){let e=String(t.agentId??"").trim();if(!e)return{ok:!1,status:400,error:"Required: agentId"};if(!this.deps.isRunningTurn(e,s))return{ok:!1,status:403,error:`No running turn of ${e} asked for this. An agent withdraws its own call from inside its run.`};let n=this.deps.store.list({status:["ringing","later"],kind:"voice",limit:200}).find(i=>i.agentId===e);if(!n)return{ok:!1,status:404,error:`${e} has no call waiting or ringing`};let r=this.deps.store.transition(n.id,["ringing","later"],{status:"ended",endedAt:this.now(),ringingSince:null,ringAgainAt:null,note:$});return r?(this.waiting.delete(n.id),this.deps.log?.(`[calls] ${n.id} withdrawn by ${e}`),{ok:!0,call:r}):{ok:!1,status:409,error:`call is ${this.deps.store.get(n.id)?.status}`}}async ring(t){let s=(this.deps.focus??w)();return s.active&&t.urgency!=="urgent"?(this.deps.store.transition(t.id,"ringing",{status:"missed",ringingSince:null,note:f(s)})&&await this.notice(t,this.missedTitle(t)),!1):t.kind==="camera"?(await this.notice(t,`${this.name(t.agentId)} wants to see through your camera`,`Open the phone app and tap Show within ${this.deps.config().ringSeconds} s.`),"notify"):this.widgetAlive()?!this.widget.busy&&!this.otherCalls(t).length?"widget":(this.startWaiting(t),await this.callWaiting(t),"waiting"):(await this.notice(t,`${this.name(t.agentId)} is calling`,`Open AgentX Voice to answer within ${this.deps.config().ringSeconds} s.`),"notify")}async notice(t,s,e){try{await this.deps.alert({title:s,message:e?`${t.reason}
|
|
10
|
-
${e}`:t.reason,urgent:t.urgency==="urgent",from:t.agentId})}catch(n){this.deps.log?.(`[calls] notice for ${t.id} failed: ${n?.message??n}`)}}startWaiting(t){let s=this.now();this.waiting.set(t.id,{since:s,toldAt:s}),this.deps.log?.(`[calls] ${t.id} from ${t.agentId} waits: the widget is busy`)}otherCalls(t){return this.deps.store.list({status:["ringing","answered"],kind:"voice",limit:200}).filter(s=>s.id!==t.id).reverse()}async callWaiting(t){let s=this.deps.config().busy;await this.notice(t,s.title.replaceAll("{agent}",this.name(t.agentId)),s.notice)}busyInfo(t){let s=this.deps.config().busy,{since:e,toldAt:n}=this.waiting.get(t.id)??{since:this.now(),toldAt:this.now()},r=this.otherCalls(t),i=r.find(g=>g.status==="answered"),o=r.find(g=>g.id===this.widget.showing)??r.find(g=>!this.waiting.has(g.id)),u=e+s.maxWaitMinutes*6e4+this.deps.config().ringSeconds*1e3,d=n+s.retryMinutes*6e4,l=e+s.maxWaitMinutes*6e4;return{why:i?"call":o?"ringing":"voice",with:i?this.name(i.agentId):o?this.name(o.agentId):null,remindAt:d<l?d:null,waitsUntil:u}}missedTitle(t){return t.kind==="camera"?`${this.name(t.agentId)} asked to see through your camera`:`Missed call from ${this.name(t.agentId)}`}widgetAlive(){return this.now()-this.widgetSeenAt<S}async ringing(t={}){return this.widgetSeenAt=this.now(),this.widget=t,await this.hold(t),await this.sweep(),this.deps.store.list({status:["ringing"],kind:"voice",limit:20}).reverse()}async hold(t){let s=this.now(),e=this.deps.config().busy,n=[],r=this.deps.store.list({status:["ringing"],kind:"voice",limit:20});for(let i of this.waiting.keys())r.some(o=>o.id===i)||this.waiting.delete(i);for(let i of r){if(i.id===t.showing){this.waiting.delete(i.id)&&this.deps.store.transition(i.id,"ringing",{ringingSince:s});continue}if(!t.busy)continue;let o=this.waiting.get(i.id);if(!(o&&s-o.since>=e.maxWaitMinutes*6e4)&&this.deps.store.transition(i.id,"ringing",{ringingSince:s})){if(!o)this.startWaiting(i);else if(s-o.toldAt>=e.retryMinutes*6e4)o.toldAt=s;else continue;n.push(i)}}for(let i of n)await this.callWaiting(i)}async asking(){return await this.sweep(),this.deps.store.list({status:["ringing"],kind:"camera",limit:20}).reverse()}async sweep(){let t=this.now(),s=this.deps.config().ringSeconds*1e3,e=this.deps.config().maxCallMinutes*6e4;for(let n of this.deps.store.list({status:["ringing","later","answered"],limit:200}))if(n.status==="ringing"&&n.ringingSince!==null&&t-n.ringingSince>=s){if(!this.deps.store.transition(n.id,"ringing",{status:"missed",ringingSince:null,note:this.waiting.has(n.id)?"widget busy":"not answered"}))continue;this.deps.log?.(`[calls] ${n.id} from ${n.agentId} missed`),await this.notice(n,this.missedTitle(n))}else if(n.status==="answered"&&n.answeredAt!==null&&t-n.answeredAt>=e){if(!this.deps.store.transition(n.id,"answered",{status:"ended",endedAt:t,note:"no hang-up"}))continue;this.deps.log?.(`[calls] ${n.id} from ${n.agentId} ended: no hang-up in ${this.deps.config().maxCallMinutes} min`)}else if(n.status==="later"&&n.ringAgainAt!==null&&t>=n.ringAgainAt){let r=this.deps.store.transition(n.id,"later",{status:"ringing",ringingSince:t,ringAgainAt:null});r&&await this.ring(r)}}answer(t){let s=this.deps.store.get(t);if(!s)return c(t);let e=this.deps.store.transition(t,"ringing",{status:"answered",answeredAt:this.now(),ringingSince:null});return e?(this.deps.log?.(`[calls] ${t} answered (${s.agentId})`),e.kind==="camera"?{ok:!0,call:e}:{ok:!0,call:e,opener:_(e)}):{ok:!1,status:409,error:`call is ${this.deps.store.get(t)?.status}, not ringing`}}decline(t){if(!this.deps.store.get(t))return c(t);let e=this.deps.store.transition(t,["ringing","later"],{status:"declined",endedAt:this.now(),ringingSince:null,ringAgainAt:null});return e?{ok:!0,call:e}:{ok:!1,status:409,error:`call is ${this.deps.store.get(t)?.status}`}}later(t,s){if(!this.deps.store.get(t))return c(t);let n=s===void 0?I:Number(s);if(!Number.isInteger(n)||n<1||n>T)return{ok:!1,status:400,error:`minutes must be a whole number from 1 to ${T}`};let r=this.deps.store.transition(t,"ringing",{status:"later",ringingSince:null,ringAgainAt:this.now()+n*6e4});return r?{ok:!0,call:r}:{ok:!1,status:409,error:`call is ${this.deps.store.get(t)?.status}, not ringing`}}hangup(t){let s=this.deps.store.get(t);if(!s)return c(t);let e=this.deps.store.transition(t,"answered",{status:"ended",endedAt:this.now()});if(!e)return{ok:!1,status:409,error:`call is ${s.status}, not answered`};this.deps.log?.(`[calls] ${t} ended (${s.agentId})`);let n=this.deps.summarize;if(e.kind==="camera"||!this.deps.config().summary||!n)return{ok:!0,call:e};let r=(async()=>{try{let i=(await n(e))?.trim();if(!i)return;let o=this.deps.store.update(t,{summary:i});this.deps.file?.(o,i)}catch(i){this.deps.log?.(`[calls] summary for ${t} failed: ${i?.message??i}`)}})();return{ok:!0,call:e,summarized:r}}endCamera(t,s){let e=this.deps.store.get(t);if(!e||e.kind!=="camera")return!1;let n=this.deps.store.transition(t,"answered",{status:"ended",endedAt:this.now(),note:s??null});return n&&this.deps.log?.(`[calls] ${t} ended (${e.agentId}): ${s??"share over"}`),!!n}get(t){return this.deps.store.get(t)}list(t={}){return this.deps.store.list(t)}},$="withdrawn by the caller";function D(a,t=Date.now()){let s=n=>`${new Date(n).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit",hour12:!1})} (in ${Math.max(1,Math.round((n-t)/6e4))} min)`;return`${a.why==="call"?`The owner is busy in another call${a.with?` (with ${a.with})`:""}.`:a.why==="ringing"?`The owner is busy: another call${a.with?` (from ${a.with})`:""} is ringing.`:"The owner is busy on the voice widget."} They were told you are calling, and why. Your call waits and rings by itself as soon as they are free: do not call again. `+(a.remindAt!==null?`Still busy at ${s(a.remindAt)}, they are told again that you are waiting. `:"")+`It waits until ${s(a.waitsUntil)} at most, then counts as a missed call.`}var c=a=>({ok:!1,status:404,error:`No such call: ${a}`});export{A as a,L as b,x as c,k as d,D as e};
|
|
11
|
-
//# sourceMappingURL=chunk-Q4XP5TBI.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/calls/service.ts","../src/calls/store.ts"],"sourcesContent":["// --- An agent rings the owner (#321) ---\n//\n// An agent that needs the owner directly asks for a call. It rings on the\n// desktop widget (AgentX Voice), which polls `ringing()`. Answering starts\n// the widget's hands-free conversation with that agent, which opens with\n// its reason. When the widget is not running, `notify` stands in. While\n// the widget is busy (a turn, the microphone, another call) the call waits\n// for it: its ring time does not run, and the owner gets a call-waiting\n// notice (#408), again every calls.busy.retryMinutes. The caller is told\n// the owner is busy, with whom, and until when its call waits (#969). It\n// rings by itself once the widget is free, oldest first; the caller may\n// withdraw it meanwhile (`cancel`).\n//\n// Checked before anything rings, in this order:\n// 0. the caller is that agent: a turn of it is running now, named by its\n// task id or by its channel and chat (see CallerProof),\n// 1. the owner allows this agent (calls.allow; empty allows nobody),\n// 2. the agent has no other call in progress,\n// 3. it is under calls.maxPerHour,\n// 4. Focus: a non-urgent call does not ring. It becomes a missed call,\n// and its notice goes through notify, which holds it until Focus\n// ends. Urgent calls ring through, as `notify --urgent` does.\n//\n// A camera ask (#325 phase 3, kind \"camera\") goes through the same checks\n// and the same table. It never rings the widget: the notice goes to the\n// phone through notify, and the phone app polls `asking()` to show a\n// Show / Decline bar. Answering it is the owner tapping Show; the camera\n// opens only then, and the share ends the ask (see camera-api.ts).\n\nimport { randomUUID } from \"crypto\"\nimport { focusLabel, readFocus, type FocusState } from \"@/notify/focus\"\nimport type { CallsConfig } from \"@/daemon/config\"\nimport { LIVE, type Call, type CallKind, type CallStore, type CallUrgency } from \"./store\"\n\nexport const REASON_MAX = 200\n/** The widget polls every 2 s; missing this long means it is not running. */\nexport const WIDGET_FRESH_MS = 10_000\nexport const LATER_DEFAULT_MINUTES = 10\nexport const LATER_MAX_MINUTES = 240\n\n/** What a caller is told when the owner cannot take its call now (#969). */\nexport interface BusyInfo {\n /** \"call\": the owner is in a call; \"ringing\": another call is ringing;\n * \"voice\": AgentX Voice is in a turn of the owner's, or recording. */\n why: \"call\" | \"ringing\" | \"voice\"\n /** The agent the owner is in a call with, or whose call is ringing. */\n with: string | null\n /** When the owner is told again that this call waits, if still busy\n * then; null when the wait ends first. Ms since the epoch. */\n remindAt: number | null\n /** The call stops waiting then: still busy, it counts as missed. */\n waitsUntil: number\n}\n\nexport type CallResult =\n | { ok: true; call: Call }\n | { ok: false; status: number; error: string }\n\n/** What the request says about the turn placing it: the run's\n * AGENTX_TASK_ID, or, for a warm process that has none, its\n * AGENTX_CHANNEL and AGENTX_CHAT_ID. Loopback is not an identity. */\nexport interface CallerProof {\n taskId?: string\n channel?: string\n chatId?: string\n}\n\n/** The proof headers for a call placed from inside an agent's run. */\nexport function callerHeaders(env: NodeJS.ProcessEnv = process.env): Record<string, string> {\n if (env.AGENTX_TASK_ID) return { \"X-AgentX-Task\": env.AGENTX_TASK_ID }\n if (env.AGENTX_CHANNEL && env.AGENTX_CHAT_ID) {\n return { \"X-AgentX-Channel\": env.AGENTX_CHANNEL, \"X-AgentX-Chat\": env.AGENTX_CHAT_ID }\n }\n return {}\n}\n\n/** What the widget says with its poll. A widget from before #408 says nothing. */\nexport interface WidgetState {\n /** It cannot ring a new call now. */\n busy?: boolean\n /** The call ringing on it. */\n showing?: string\n}\n\nexport interface CallDeps {\n store: CallStore\n config: () => CallsConfig\n /** The agent's display name, or null when there is no such agent here. */\n agentName: (agentId: string) => string | null\n /** True when `proof` names a running turn of `agentId`. */\n isRunningTurn: (agentId: string, proof: CallerProof) => boolean\n /** The channel and chat of that running turn, when it has one, so a\n * camera ask is answered where it was asked. Unset: only a proof that\n * names them itself is kept. */\n turnSession?: (agentId: string, proof: CallerProof) => { channel?: string; chatId?: string } | null\n /** Tell the owner through notify. `urgent` passes Focus, like notify --urgent. */\n alert: (notice: { title: string; message: string; urgent: boolean; from: string }) => Promise<void>\n /** Ask the agent, in its voice session, to summarise the call. */\n summarize?: (call: Call) => Promise<string | null>\n /** File a finished summary where the dashboard shows it. */\n file?: (call: Call, summary: string) => void\n focus?: () => FocusState\n now?: () => number\n log?: (m: string) => void\n}\n\n/** What the widget sends through /ask when the owner answers, so the\n * agent speaks first and says why it called. */\nexport function openerPrompt(call: Pick<Call, \"reason\">): string {\n return \"[Call answered] The owner just picked up the voice call you asked for. \" +\n `You called because: \"${call.reason}\". Speak first: greet them in a few words ` +\n \"and say why you called, then let them answer.\"\n}\n\nexport const SUMMARY_PROMPT =\n \"[Call ended] The owner hung up. In two or three plain sentences, summarise what \" +\n \"was said and decided on this call, for your records. Reply with the summary only.\"\n\nexport class CallService {\n private widgetSeenAt = 0\n /** What the widget said with its last poll. */\n private widget: WidgetState = {}\n /** Calls waiting for a busy widget: since when, and when the owner was\n * last told. */\n private waiting = new Map<string, { since: number; toldAt: number }>()\n\n constructor(private deps: CallDeps) {}\n\n private now(): number { return this.deps.now?.() ?? Date.now() }\n private name(agentId: string): string { return this.deps.agentName(agentId) ?? agentId }\n\n /** An agent asks for a call, or to see (kind \"camera\"). 201 ringing, or\n * waiting for a busy owner (`rang: \"waiting\"`, with `busy`); 202 held\n * as missed (Focus). */\n async request(input: { agentId?: unknown; reason?: unknown; urgency?: unknown; kind?: unknown }, proof: CallerProof = {}): Promise<CallResult & { rang?: Rang; busy?: BusyInfo }> {\n const agentId = String(input.agentId ?? \"\").trim()\n const reason = String(input.reason ?? \"\").replace(/\\s+/g, \" \").trim()\n const urgency: CallUrgency = input.urgency === \"urgent\" ? \"urgent\" : \"normal\"\n const kind: CallKind = input.kind === \"camera\" ? \"camera\" : \"voice\"\n if (input.kind !== undefined && input.kind !== \"voice\" && input.kind !== \"camera\") return { ok: false, status: 400, error: \"kind must be voice or camera\" }\n if (!agentId || !reason) return { ok: false, status: 400, error: \"Required: agentId and reason\" }\n if (reason.length > REASON_MAX) return { ok: false, status: 413, error: `reason is over ${REASON_MAX} characters; keep it to one line` }\n if (this.deps.agentName(agentId) === null) return { ok: false, status: 404, error: `Unknown agent: ${agentId}` }\n if (!this.deps.isRunningTurn(agentId, proof)) {\n return { ok: false, status: 403, error: `No running turn of ${agentId} placed this call. An agent calls from inside its own run.` }\n }\n\n const cfg = this.deps.config()\n if (!cfg.allow.includes(\"*\") && !cfg.allow.includes(agentId)) {\n const verb = kind === \"camera\" ? \"may not ask to see through the owner's camera\" : \"may not call the owner\"\n return { ok: false, status: 403, error: `${agentId} ${verb}. Add it to calls.allow in agentx.json (or: agentx call allow ${agentId}).` }\n }\n await this.sweep()\n if (this.deps.store.list({ status: LIVE, limit: 200 }).some((c) => c.agentId === agentId)) {\n return { ok: false, status: 409, error: `${agentId} already has a call in progress` }\n }\n const now = this.now()\n if (this.deps.store.countSince(agentId, now - 3_600_000) >= cfg.maxPerHour) {\n return { ok: false, status: 429, error: `${agentId} has placed ${cfg.maxPerHour} calls in the last hour (calls.maxPerHour)` }\n }\n\n // Where the ask came from, for kind \"camera\": the turn's own chat, or\n // the one the proof names. A task run has neither.\n const session: { channel?: string; chatId?: string } = kind === \"camera\" ? (this.deps.turnSession?.(agentId, proof) ?? proof) : {}\n const call: Call = {\n id: `call-${randomUUID().slice(0, 8)}`, agentId, kind, reason, urgency, status: \"ringing\", createdAt: now,\n ringingSince: now, answeredAt: null, endedAt: null, ringAgainAt: null, note: null, summary: null,\n channel: session.channel && session.chatId ? session.channel : null,\n chatId: session.channel && session.chatId ? session.chatId : null,\n }\n this.deps.store.insert(call)\n this.deps.log?.(`[calls] ${agentId} asks ${kind === \"camera\" ? \"to see\" : \"for a call\"} (${urgency}): ${reason}`)\n const rang = await this.ring(call)\n const saved = this.deps.store.get(call.id)!\n if (rang !== \"waiting\") return { ok: true, call: saved, rang }\n // The widget may have come free while the owner was being told.\n return this.waiting.has(call.id) ? { ok: true, call: saved, rang, busy: this.busyInfo(call) } : { ok: true, call: saved, rang: \"widget\" }\n }\n\n /** The caller withdraws its own voice call while it waits or rings, for\n * example to send a message instead (#969). */\n async cancel(input: { agentId?: unknown }, proof: CallerProof = {}): Promise<CallResult> {\n const agentId = String(input.agentId ?? \"\").trim()\n if (!agentId) return { ok: false, status: 400, error: \"Required: agentId\" }\n if (!this.deps.isRunningTurn(agentId, proof)) {\n return { ok: false, status: 403, error: `No running turn of ${agentId} asked for this. An agent withdraws its own call from inside its run.` }\n }\n // No sweep first: a call back that is due must not ring just to be withdrawn.\n const mine = this.deps.store.list({ status: [\"ringing\", \"later\"], kind: \"voice\", limit: 200 }).find((c) => c.agentId === agentId)\n if (!mine) return { ok: false, status: 404, error: `${agentId} has no call waiting or ringing` }\n const ended = this.deps.store.transition(mine.id, [\"ringing\", \"later\"], { status: \"ended\", endedAt: this.now(), ringingSince: null, ringAgainAt: null, note: CANCELLED_NOTE })\n if (!ended) return { ok: false, status: 409, error: `call is ${this.deps.store.get(mine.id)?.status}` }\n this.waiting.delete(mine.id)\n this.deps.log?.(`[calls] ${mine.id} withdrawn by ${agentId}`)\n return { ok: true, call: ended }\n }\n\n /** Ring now, wait for a busy widget, or hold as missed in Focus.\n * Returns how it rang. */\n private async ring(call: Call): Promise<Rang> {\n const focus = (this.deps.focus ?? readFocus)()\n if (focus.active && call.urgency !== \"urgent\") {\n if (!this.deps.store.transition(call.id, \"ringing\", { status: \"missed\", ringingSince: null, note: focusLabel(focus) })) return false\n await this.notice(call, this.missedTitle(call))\n return false\n }\n // A camera ask is answered on the phone, never on the widget.\n if (call.kind === \"camera\") {\n await this.notice(call, `${this.name(call.agentId)} wants to see through your camera`,\n `Open the phone app and tap Show within ${this.deps.config().ringSeconds} s.`)\n return \"notify\"\n }\n if (this.widgetAlive()) {\n // Busy by its last poll, or by a call placed since that it will show first.\n if (!this.widget.busy && !this.otherCalls(call).length) return \"widget\"\n this.startWaiting(call)\n await this.callWaiting(call)\n return \"waiting\"\n }\n await this.notice(call, `${this.name(call.agentId)} is calling`,\n `Open AgentX Voice to answer within ${this.deps.config().ringSeconds} s.`)\n return \"notify\"\n }\n\n private async notice(call: Call, title: string, extra?: string): Promise<void> {\n try {\n await this.deps.alert({\n title, message: extra ? `${call.reason}\\n${extra}` : call.reason,\n urgent: call.urgency === \"urgent\", from: call.agentId,\n })\n } catch (e: any) {\n this.deps.log?.(`[calls] notice for ${call.id} failed: ${e?.message ?? e}`)\n }\n }\n\n /** The call starts waiting for the busy widget. */\n private startWaiting(call: Call): void {\n const now = this.now()\n this.waiting.set(call.id, { since: now, toldAt: now })\n this.deps.log?.(`[calls] ${call.id} from ${call.agentId} waits: the widget is busy`)\n }\n\n /** Voice calls ringing or answered other than `call`, oldest first. */\n private otherCalls(call: Call): Call[] {\n return this.deps.store.list({ status: [\"ringing\", \"answered\"], kind: \"voice\", limit: 200 }).filter((c) => c.id !== call.id).reverse()\n }\n\n private async callWaiting(call: Call): Promise<void> {\n const busy = this.deps.config().busy\n await this.notice(call, busy.title.replaceAll(\"{agent}\", this.name(call.agentId)), busy.notice)\n }\n\n /** Why the owner cannot take `call` now, and how long it waits. */\n private busyInfo(call: Call): BusyInfo {\n const cfg = this.deps.config().busy\n const { since, toldAt } = this.waiting.get(call.id) ?? { since: this.now(), toldAt: this.now() }\n const others = this.otherCalls(call)\n const talking = others.find((c) => c.status === \"answered\")\n // The call on the pill, or the oldest one it has yet to show.\n const showing = others.find((c) => c.id === this.widget.showing) ?? others.find((c) => !this.waiting.has(c.id))\n // After the wait the ring time runs once more before it is missed.\n const waitsUntil = since + cfg.maxWaitMinutes * 60_000 + this.deps.config().ringSeconds * 1000\n const remindAt = toldAt + cfg.retryMinutes * 60_000\n const stopsWaiting = since + cfg.maxWaitMinutes * 60_000\n return {\n why: talking ? \"call\" : showing ? \"ringing\" : \"voice\",\n with: talking ? this.name(talking.agentId) : showing ? this.name(showing.agentId) : null,\n remindAt: remindAt < stopsWaiting ? remindAt : null,\n waitsUntil,\n }\n }\n\n private missedTitle(call: Call): string {\n return call.kind === \"camera\" ? `${this.name(call.agentId)} asked to see through your camera` : `Missed call from ${this.name(call.agentId)}`\n }\n\n /** True when the widget polled recently. */\n widgetAlive(): boolean {\n return this.now() - this.widgetSeenAt < WIDGET_FRESH_MS\n }\n\n /** The widget's poll: voice calls ringing now, oldest first. Marks it alive. */\n async ringing(widget: WidgetState = {}): Promise<Call[]> {\n this.widgetSeenAt = this.now()\n this.widget = widget\n await this.hold(widget)\n await this.sweep()\n return this.deps.store.list({ status: [\"ringing\"], kind: \"voice\", limit: 20 }).reverse()\n }\n\n /** A busy widget cannot ring: the ring time of each call it is not\n * showing starts over at every poll, for calls.busy.maxWaitMinutes at\n * most. The owner is told that the call waits, and again every\n * calls.busy.retryMinutes. The ring time starts over once more when the\n * widget first shows a call that waited. */\n private async hold(widget: WidgetState): Promise<void> {\n const now = this.now()\n const busy = this.deps.config().busy\n // Told after the loop: a poll that overlaps a slow notice must not\n // carry on from a list and a widget state that are out of date.\n const tell: Call[] = []\n const live = this.deps.store.list({ status: [\"ringing\"], kind: \"voice\", limit: 20 })\n for (const id of this.waiting.keys()) if (!live.some((c) => c.id === id)) this.waiting.delete(id)\n for (const call of live) {\n if (call.id === widget.showing) {\n if (this.waiting.delete(call.id)) this.deps.store.transition(call.id, \"ringing\", { ringingSince: now })\n continue\n }\n if (!widget.busy) continue\n const held = this.waiting.get(call.id)\n if (held && now - held.since >= busy.maxWaitMinutes * 60_000) continue\n if (!this.deps.store.transition(call.id, \"ringing\", { ringingSince: now })) continue\n if (!held) this.startWaiting(call)\n else if (now - held.toldAt >= busy.retryMinutes * 60_000) held.toldAt = now\n else continue\n tell.push(call)\n }\n for (const call of tell) await this.callWaiting(call)\n }\n\n /** The phone app's poll: camera asks waiting for the owner, oldest first.\n * Does not count as the widget being up. */\n async asking(): Promise<Call[]> {\n await this.sweep()\n return this.deps.store.list({ status: [\"ringing\"], kind: \"camera\", limit: 20 }).reverse()\n }\n\n /** Unanswered calls become missed; \"later\" calls that came due ring\n * again; answered calls nobody hung up end after calls.maxCallMinutes. */\n async sweep(): Promise<void> {\n const now = this.now()\n const ringFor = this.deps.config().ringSeconds * 1000\n const talkFor = this.deps.config().maxCallMinutes * 60_000\n for (const call of this.deps.store.list({ status: [\"ringing\", \"later\", \"answered\"], limit: 200 })) {\n // The list was read before any await below: each move re-checks the\n // status in the store, so a call answered meanwhile is left alone.\n if (call.status === \"ringing\" && call.ringingSince !== null && now - call.ringingSince >= ringFor) {\n if (!this.deps.store.transition(call.id, \"ringing\", { status: \"missed\", ringingSince: null, note: this.waiting.has(call.id) ? \"widget busy\" : \"not answered\" })) continue\n this.deps.log?.(`[calls] ${call.id} from ${call.agentId} missed`)\n await this.notice(call, this.missedTitle(call))\n } else if (call.status === \"answered\" && call.answeredAt !== null && now - call.answeredAt >= talkFor) {\n if (!this.deps.store.transition(call.id, \"answered\", { status: \"ended\", endedAt: now, note: \"no hang-up\" })) continue\n this.deps.log?.(`[calls] ${call.id} from ${call.agentId} ended: no hang-up in ${this.deps.config().maxCallMinutes} min`)\n } else if (call.status === \"later\" && call.ringAgainAt !== null && now >= call.ringAgainAt) {\n const again = this.deps.store.transition(call.id, \"later\", { status: \"ringing\", ringingSince: now, ringAgainAt: null })\n if (again) await this.ring(again)\n }\n }\n }\n\n /** The owner picked up, or tapped Show. Returns the call and, for a\n * voice call, the opener for /ask. */\n answer(id: string): CallResult & { opener?: string } {\n const call = this.deps.store.get(id)\n if (!call) return unknown(id)\n const answered = this.deps.store.transition(id, \"ringing\", { status: \"answered\", answeredAt: this.now(), ringingSince: null })\n if (!answered) return { ok: false, status: 409, error: `call is ${this.deps.store.get(id)?.status}, not ringing` }\n this.deps.log?.(`[calls] ${id} answered (${call.agentId})`)\n return answered.kind === \"camera\" ? { ok: true, call: answered } : { ok: true, call: answered, opener: openerPrompt(answered) }\n }\n\n decline(id: string): CallResult {\n const call = this.deps.store.get(id)\n if (!call) return unknown(id)\n const declined = this.deps.store.transition(id, [\"ringing\", \"later\"], { status: \"declined\", endedAt: this.now(), ringingSince: null, ringAgainAt: null })\n return declined ? { ok: true, call: declined } : { ok: false, status: 409, error: `call is ${this.deps.store.get(id)?.status}` }\n }\n\n /** \"Call back in N min\": it rings again then. */\n later(id: string, minutes?: unknown): CallResult {\n const call = this.deps.store.get(id)\n if (!call) return unknown(id)\n const n = minutes === undefined ? LATER_DEFAULT_MINUTES : Number(minutes)\n if (!Number.isInteger(n) || n < 1 || n > LATER_MAX_MINUTES) {\n return { ok: false, status: 400, error: `minutes must be a whole number from 1 to ${LATER_MAX_MINUTES}` }\n }\n const later = this.deps.store.transition(id, \"ringing\", { status: \"later\", ringingSince: null, ringAgainAt: this.now() + n * 60_000 })\n return later ? { ok: true, call: later } : { ok: false, status: 409, error: `call is ${this.deps.store.get(id)?.status}, not ringing` }\n }\n\n /** End an answered call. The summary is written in the background;\n * `summarized` settles when it is filed (tests wait on it). */\n hangup(id: string): CallResult & { summarized?: Promise<void> } {\n const call = this.deps.store.get(id)\n if (!call) return unknown(id)\n const ended = this.deps.store.transition(id, \"answered\", { status: \"ended\", endedAt: this.now() })\n if (!ended) return { ok: false, status: 409, error: `call is ${call.status}, not answered` }\n this.deps.log?.(`[calls] ${id} ended (${call.agentId})`)\n const summarize = this.deps.summarize\n // What the agent saw is already its answer; only a voice call is summarised.\n if (ended.kind === \"camera\" || !this.deps.config().summary || !summarize) return { ok: true, call: ended }\n const summarized = (async () => {\n try {\n const text = (await summarize(ended))?.trim()\n if (!text) return\n const done = this.deps.store.update(id, { summary: text })!\n this.deps.file?.(done, text)\n } catch (e: any) {\n this.deps.log?.(`[calls] summary for ${id} failed: ${e?.message ?? e}`)\n }\n })()\n return { ok: true, call: ended, summarized }\n }\n\n /** A camera ask ends when the share ends; no summary. Only an answered\n * ask ends this way, so a stop before Show leaves it ringing. */\n endCamera(id: string, note?: string): boolean {\n const call = this.deps.store.get(id)\n if (!call || call.kind !== \"camera\") return false\n const ended = this.deps.store.transition(id, \"answered\", { status: \"ended\", endedAt: this.now(), note: note ?? null })\n if (ended) this.deps.log?.(`[calls] ${id} ended (${call.agentId}): ${note ?? \"share over\"}`)\n return !!ended\n }\n\n get(id: string): Call | undefined { return this.deps.store.get(id) }\n\n list(opts: Parameters<CallStore[\"list\"]>[0] = {}): Call[] { return this.deps.store.list(opts) }\n}\n\n/** How a call reached the owner: on the widget, through notify, waiting\n * for a busy widget, or not at all (held as missed). */\nexport type Rang = \"widget\" | \"notify\" | \"waiting\" | false\n\n/** The note on a call its caller withdrew. */\nexport const CANCELLED_NOTE = \"withdrawn by the caller\"\n\n/** What the caller reads when its call waits for a busy owner. */\nexport function busyText(busy: BusyInfo, now: number = Date.now()): string {\n const at = (ms: number) => `${new Date(ms).toLocaleTimeString([], { hour: \"2-digit\", minute: \"2-digit\", hour12: false })} (in ${Math.max(1, Math.round((ms - now) / 60_000))} min)`\n const why = busy.why === \"call\" ? `The owner is busy in another call${busy.with ? ` (with ${busy.with})` : \"\"}.`\n : busy.why === \"ringing\" ? `The owner is busy: another call${busy.with ? ` (from ${busy.with})` : \"\"} is ringing.`\n : \"The owner is busy on the voice widget.\"\n return `${why} They were told you are calling, and why. Your call waits and rings by itself as soon as they are free: do not call again. ` +\n (busy.remindAt !== null ? `Still busy at ${at(busy.remindAt)}, they are told again that you are waiting. ` : \"\") +\n `It waits until ${at(busy.waitsUntil)} at most, then counts as a missed call.`\n}\n\nconst unknown = (id: string): CallResult => ({ ok: false, status: 404, error: `No such call: ${id}` })\n","// --- Calls an agent placed to the owner, kept in SQLite ---\n//\n// One row per call, so a missed call is still there after a restart and\n// the dashboard can list them. Nothing else reads this table. A camera\n// ask (#325 phase 3: \"show me\") is a call of kind \"camera\": same\n// allowlist, same hourly limit, same lifecycle, answered on the phone\n// instead of the voice widget.\n\nimport type Database from \"better-sqlite3\"\n\nexport type CallStatus = \"ringing\" | \"answered\" | \"ended\" | \"declined\" | \"missed\" | \"later\"\nexport type CallUrgency = \"normal\" | \"urgent\"\n/** \"voice\": a live voice call on the widget. \"camera\": the agent asks to see through the phone camera. */\nexport type CallKind = \"voice\" | \"camera\"\n\nexport interface Call {\n id: string\n agentId: string\n kind: CallKind\n reason: string\n urgency: CallUrgency\n status: CallStatus\n /** The channel and chat the asking turn ran in, so what the agent sees\n * is answered there; null when the turn had none (a task run). */\n channel: string | null\n chatId: string | null\n /** When the agent asked, ms since the epoch. */\n createdAt: number\n /** When it last started ringing: at the request, or when \"later\" came due. */\n ringingSince: number | null\n answeredAt: number | null\n endedAt: number | null\n /** Status \"later\": when it rings again. */\n ringAgainAt: number | null\n /** Why it ended the way it did, in a few words (\"in Focus\", \"not answered\"). */\n note: string | null\n /** The agent's own summary, written after hang-up. */\n summary: string | null\n}\n\n/** Calls still in progress: an agent has at most one of these. */\nexport const LIVE: CallStatus[] = [\"ringing\", \"answered\", \"later\"]\n\nconst COLUMNS = `id, agent_id AS agentId, kind, reason, urgency, status, created_at AS createdAt,\n ringing_since AS ringingSince, answered_at AS answeredAt, ended_at AS endedAt,\n ring_again_at AS ringAgainAt, note, summary, channel, chat_id AS chatId`\n\nexport class CallStore {\n constructor(private db: Database.Database) {\n db.exec(`CREATE TABLE IF NOT EXISTS calls (\n id TEXT PRIMARY KEY, agent_id TEXT NOT NULL, reason TEXT NOT NULL,\n urgency TEXT NOT NULL, status TEXT NOT NULL, created_at INTEGER NOT NULL,\n ringing_since INTEGER, answered_at INTEGER, ended_at INTEGER,\n ring_again_at INTEGER, note TEXT, summary TEXT);\n CREATE INDEX IF NOT EXISTS calls_recent ON calls(created_at DESC);`)\n // Columns added since the table first shipped (#325 phase 3). A row\n // from before is a voice call from a turn whose chat was not kept.\n const have = new Set((db.prepare(\"PRAGMA table_info(calls)\").all() as Array<{ name: string }>).map((c) => c.name))\n if (!have.has(\"kind\")) db.exec(\"ALTER TABLE calls ADD COLUMN kind TEXT NOT NULL DEFAULT 'voice'\")\n if (!have.has(\"channel\")) db.exec(\"ALTER TABLE calls ADD COLUMN channel TEXT\")\n if (!have.has(\"chat_id\")) db.exec(\"ALTER TABLE calls ADD COLUMN chat_id TEXT\")\n }\n\n insert(call: Call): void {\n this.db.prepare(`INSERT INTO calls (id, agent_id, kind, reason, urgency, status, created_at, ringing_since,\n answered_at, ended_at, ring_again_at, note, summary, channel, chat_id) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`).run(\n call.id, call.agentId, call.kind, call.reason, call.urgency, call.status, call.createdAt,\n call.ringingSince, call.answeredAt, call.endedAt, call.ringAgainAt, call.note, call.summary, call.channel, call.chatId)\n }\n\n get(id: string): Call | undefined {\n return this.db.prepare(`SELECT ${COLUMNS} FROM calls WHERE id=?`).get(id) as Call | undefined\n }\n\n /** Write the changed fields of one call. */\n update(id: string, patch: Partial<Omit<Call, \"id\" | \"agentId\" | \"createdAt\">>): Call | undefined {\n const cols: Record<string, string> = {\n reason: \"reason\", urgency: \"urgency\", status: \"status\", ringingSince: \"ringing_since\",\n answeredAt: \"answered_at\", endedAt: \"ended_at\", ringAgainAt: \"ring_again_at\", note: \"note\", summary: \"summary\",\n }\n const keys = Object.keys(patch).filter((k) => k in cols)\n if (keys.length) {\n const set = keys.map((k) => `${cols[k]}=?`).join(\", \")\n this.db.prepare(`UPDATE calls SET ${set} WHERE id=?`).run(...keys.map((k) => (patch as any)[k] ?? null), id)\n }\n return this.get(id)\n }\n\n /** Change a call only if it is still `from`: the check and the write are\n * one statement, so two sweeps, or a sweep and the owner answering,\n * cannot both move the same call. Undefined when it had moved on. */\n transition(id: string, from: CallStatus | CallStatus[], patch: Partial<Omit<Call, \"id\" | \"agentId\" | \"createdAt\">>): Call | undefined {\n const statuses = Array.isArray(from) ? from : [from]\n const cols: Record<string, string> = {\n status: \"status\", ringingSince: \"ringing_since\", answeredAt: \"answered_at\", endedAt: \"ended_at\",\n ringAgainAt: \"ring_again_at\", note: \"note\", summary: \"summary\",\n }\n const keys = Object.keys(patch).filter((k) => k in cols)\n const set = keys.map((k) => `${cols[k]}=?`).join(\", \")\n const marks = statuses.map(() => \"?\").join(\",\")\n const changed = this.db.prepare(`UPDATE calls SET ${set} WHERE id=? AND status IN (${marks})`)\n .run(...keys.map((k) => (patch as any)[k] ?? null), id, ...statuses).changes\n return changed ? this.get(id) : undefined\n }\n\n /** Newest first; only these statuses, and only this kind, when given. */\n list(opts: { status?: CallStatus[]; kind?: CallKind; limit?: number } = {}): Call[] {\n const limit = Math.min(Math.max(opts.limit ?? 50, 1), 200)\n const where: string[] = []\n const args: unknown[] = []\n if (opts.status?.length) {\n where.push(`status IN (${opts.status.map(() => \"?\").join(\",\")})`)\n args.push(...opts.status)\n }\n if (opts.kind) { where.push(\"kind=?\"); args.push(opts.kind) }\n const filter = where.length ? `WHERE ${where.join(\" AND \")}` : \"\"\n return this.db.prepare(`SELECT ${COLUMNS} FROM calls ${filter} ORDER BY created_at DESC, rowid DESC LIMIT ?`)\n .all(...args, limit) as Call[]\n }\n\n /** Calls this agent placed after `since` (ms), for the hourly limit. */\n countSince(agentId: string, since: number): number {\n return (this.db.prepare(\"SELECT COUNT(*) AS n FROM calls WHERE agent_id=? AND created_at>?\")\n .get(agentId, since) as { n: number }).n\n }\n}\n"],"mappings":"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","names":["randomUUID","LIVE","COLUMNS","CallStore","db","have","c","call","id","patch","cols","keys","k","set","from","statuses","marks","opts","limit","where","args","filter","agentId","since","REASON_MAX","WIDGET_FRESH_MS","LATER_DEFAULT_MINUTES","LATER_MAX_MINUTES","callerHeaders","env","openerPrompt","call","SUMMARY_PROMPT","CallService","deps","agentId","input","proof","reason","urgency","kind","cfg","LIVE","c","now","session","randomUUID","rang","saved","mine","ended","CANCELLED_NOTE","focus","readFocus","focusLabel","title","extra","e","busy","since","toldAt","others","talking","showing","waitsUntil","remindAt","stopsWaiting","widget","tell","live","id","held","ringFor","talkFor","again","unknown","answered","declined","minutes","later","summarize","summarized","text","done","note","opts","busyText","at","ms"]}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|