agentix-cli 0.79.4 → 0.80.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{App-PFMTQ7J5.js → App-2UCGXCX3.js} +2 -2
- package/dist/{ChatApp-ZBSU2F6J.js → ChatApp-IRPSBYC7.js} +2 -2
- package/dist/{agent-AMU6E27Z.js → agent-UPTK3HFR.js} +2 -2
- package/dist/{board-dashboard-OJSOA4OL.js → board-dashboard-GAUAYKWV.js} +337 -215
- package/dist/board-dashboard-GAUAYKWV.js.map +1 -0
- package/dist/{chunk-7QRCAHPX.js → chunk-2YJ2ZIYC.js} +2 -2
- package/dist/{chunk-T7GFKM63.js → chunk-36TJTWG3.js} +193 -197
- package/dist/chunk-36TJTWG3.js.map +1 -0
- package/dist/{chunk-EAYHYAF3.js → chunk-3GYCFRBK.js} +2 -2
- package/dist/chunk-445P5QPS.js +15 -0
- package/dist/{chunk-YKPISR5J.js.map → chunk-445P5QPS.js.map} +1 -1
- package/dist/{chunk-MGCTX2BB.js → chunk-4NUF3SXN.js} +2 -2
- package/dist/chunk-6TLQ4I32.js +57 -0
- package/dist/chunk-6TLQ4I32.js.map +1 -0
- package/dist/{chunk-XFCSJUGY.js → chunk-7EIUNFDF.js} +2 -2
- package/dist/{chunk-HTKSZZXK.js → chunk-AFSM3IY3.js} +2 -2
- package/dist/{chunk-M4XUZA3O.js → chunk-AIHWGU4P.js} +2 -2
- package/dist/{chunk-RD5EKHGZ.js → chunk-BBI3HG77.js} +2 -2
- package/dist/{chunk-WVYOXM45.js → chunk-BIJB7CNG.js} +2 -2
- package/dist/{chunk-R7WZUUYO.js → chunk-D3FDPA74.js} +2 -2
- package/dist/{chunk-ROIPXJ6L.js → chunk-ECEDX6C6.js} +191 -190
- package/dist/chunk-ECEDX6C6.js.map +1 -0
- package/dist/chunk-GTJIKXDB.js +2 -0
- package/dist/chunk-GTJIKXDB.js.map +1 -0
- package/dist/{chunk-EUVMKZUC.js → chunk-I2Z6D3MI.js} +2 -2
- package/dist/{chunk-LNQOJC57.js → chunk-KAFUQILA.js} +2 -2
- package/dist/{chunk-X6WZ54UJ.js → chunk-KBSBMOOL.js} +79 -79
- package/dist/chunk-KBSBMOOL.js.map +1 -0
- package/dist/chunk-NMFXY3QU.js +2 -0
- package/dist/chunk-NMFXY3QU.js.map +1 -0
- package/dist/{chunk-CNSPC4BZ.js → chunk-O67KV3DX.js} +2 -2
- package/dist/chunk-OVN5LVJY.js +11 -0
- package/dist/chunk-OVN5LVJY.js.map +1 -0
- package/dist/{chunk-WNI2P7S6.js → chunk-PHFOYJUB.js} +2 -2
- package/dist/{chunk-LPDQ2KK4.js → chunk-PT5Z5WW4.js} +2 -2
- package/dist/{chunk-6SJTBTDG.js → chunk-PXIGWFIQ.js} +2 -2
- package/dist/chunk-SHEUCITK.js +2 -0
- package/dist/chunk-SHEUCITK.js.map +1 -0
- package/dist/{chunk-2ZQ42Q4L.js → chunk-Z522XZBL.js} +2 -2
- package/dist/{chunk-5RRORS5S.js → chunk-Z6G26FWC.js} +57 -57
- package/dist/chunk-Z6G26FWC.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/client-ABDGDO7S.js +2 -0
- package/dist/config-XPFCUOQT.js +2 -0
- package/dist/config-mutator-WYHQV2XF.js +2 -0
- package/dist/{doctor-DST4SVW4.js → doctor-XLLNQRRT.js} +2 -2
- package/dist/{facts-2K6UX5GD.js → facts-YY2364CP.js} +2 -2
- package/dist/{facts-2K6UX5GD.js.map → facts-YY2364CP.js.map} +1 -1
- package/dist/index.d.ts +74 -12
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/program-KZOBDSXQ.js +2 -0
- package/dist/{promote-CQCRZCYB.js → promote-YUZL3ZU2.js} +2 -2
- package/dist/query-3XH7I6WX.js +2 -0
- package/dist/registry-5OD2IN5G.js +2 -0
- package/dist/{request-planner-TNDIQMHS.js → request-planner-RNXA2QAH.js} +2 -2
- package/dist/{review-candidates-SXVIYCVE.js → review-candidates-THKWXS7S.js} +2 -2
- package/dist/routing-X45VX723.js +2 -0
- package/dist/schedule-tool-ZTL6UZCC.js +2 -0
- package/dist/scheduler-VH7IJ4VA.js +2 -0
- package/dist/seat-LQ47SSNN.js +2 -0
- package/dist/{select-WKMMUWLU.js → select-PRQP2B6G.js} +2 -2
- package/dist/{set-enabled-RIE3PLNJ.js → set-enabled-E2ML2E33.js} +2 -2
- package/dist/tool-4WPUUUE4.js +4 -0
- package/dist/tool-4WPUUUE4.js.map +1 -0
- package/dist/{tool-TAMRBONN.js → tool-67JK7SK6.js} +2 -2
- package/dist/tool-67JK7SK6.js.map +1 -0
- package/dist/wiki-AS5YHSR7.js +2 -0
- package/package.json +1 -1
- package/dist/board-dashboard-OJSOA4OL.js.map +0 -1
- package/dist/chunk-2ACZWM7F.js +0 -22
- package/dist/chunk-2ACZWM7F.js.map +0 -1
- package/dist/chunk-5RRORS5S.js.map +0 -1
- package/dist/chunk-7XB2CVC3.js +0 -2
- package/dist/chunk-7XB2CVC3.js.map +0 -1
- package/dist/chunk-ROIPXJ6L.js.map +0 -1
- package/dist/chunk-T7GFKM63.js.map +0 -1
- package/dist/chunk-X6WZ54UJ.js.map +0 -1
- package/dist/chunk-YKPISR5J.js +0 -15
- package/dist/client-XAE7NLE2.js +0 -2
- package/dist/config-5HAWFIEL.js +0 -2
- package/dist/config-mutator-Y7KAC4AC.js +0 -2
- package/dist/program-SSCPEHTW.js +0 -2
- package/dist/query-WSLKGCQN.js +0 -2
- package/dist/registry-6HKQKIBD.js +0 -2
- package/dist/routing-GQWG65DQ.js +0 -2
- package/dist/schedule-tool-WXRMJMW6.js +0 -2
- package/dist/scheduler-YW6JFH3Z.js +0 -2
- package/dist/seat-K2JSQIZ4.js +0 -2
- package/dist/tool-TAMRBONN.js.map +0 -1
- package/dist/wiki-WBRXHY7J.js +0 -2
- /package/dist/{App-PFMTQ7J5.js.map → App-2UCGXCX3.js.map} +0 -0
- /package/dist/{ChatApp-ZBSU2F6J.js.map → ChatApp-IRPSBYC7.js.map} +0 -0
- /package/dist/{agent-AMU6E27Z.js.map → agent-UPTK3HFR.js.map} +0 -0
- /package/dist/{chunk-7QRCAHPX.js.map → chunk-2YJ2ZIYC.js.map} +0 -0
- /package/dist/{chunk-EAYHYAF3.js.map → chunk-3GYCFRBK.js.map} +0 -0
- /package/dist/{chunk-MGCTX2BB.js.map → chunk-4NUF3SXN.js.map} +0 -0
- /package/dist/{chunk-XFCSJUGY.js.map → chunk-7EIUNFDF.js.map} +0 -0
- /package/dist/{chunk-HTKSZZXK.js.map → chunk-AFSM3IY3.js.map} +0 -0
- /package/dist/{chunk-M4XUZA3O.js.map → chunk-AIHWGU4P.js.map} +0 -0
- /package/dist/{chunk-RD5EKHGZ.js.map → chunk-BBI3HG77.js.map} +0 -0
- /package/dist/{chunk-WVYOXM45.js.map → chunk-BIJB7CNG.js.map} +0 -0
- /package/dist/{chunk-R7WZUUYO.js.map → chunk-D3FDPA74.js.map} +0 -0
- /package/dist/{chunk-EUVMKZUC.js.map → chunk-I2Z6D3MI.js.map} +0 -0
- /package/dist/{chunk-LNQOJC57.js.map → chunk-KAFUQILA.js.map} +0 -0
- /package/dist/{chunk-CNSPC4BZ.js.map → chunk-O67KV3DX.js.map} +0 -0
- /package/dist/{chunk-WNI2P7S6.js.map → chunk-PHFOYJUB.js.map} +0 -0
- /package/dist/{chunk-LPDQ2KK4.js.map → chunk-PT5Z5WW4.js.map} +0 -0
- /package/dist/{chunk-6SJTBTDG.js.map → chunk-PXIGWFIQ.js.map} +0 -0
- /package/dist/{chunk-2ZQ42Q4L.js.map → chunk-Z522XZBL.js.map} +0 -0
- /package/dist/{client-XAE7NLE2.js.map → client-ABDGDO7S.js.map} +0 -0
- /package/dist/{config-5HAWFIEL.js.map → config-XPFCUOQT.js.map} +0 -0
- /package/dist/{config-mutator-Y7KAC4AC.js.map → config-mutator-WYHQV2XF.js.map} +0 -0
- /package/dist/{doctor-DST4SVW4.js.map → doctor-XLLNQRRT.js.map} +0 -0
- /package/dist/{program-SSCPEHTW.js.map → program-KZOBDSXQ.js.map} +0 -0
- /package/dist/{promote-CQCRZCYB.js.map → promote-YUZL3ZU2.js.map} +0 -0
- /package/dist/{query-WSLKGCQN.js.map → query-3XH7I6WX.js.map} +0 -0
- /package/dist/{registry-6HKQKIBD.js.map → registry-5OD2IN5G.js.map} +0 -0
- /package/dist/{request-planner-TNDIQMHS.js.map → request-planner-RNXA2QAH.js.map} +0 -0
- /package/dist/{review-candidates-SXVIYCVE.js.map → review-candidates-THKWXS7S.js.map} +0 -0
- /package/dist/{routing-GQWG65DQ.js.map → routing-X45VX723.js.map} +0 -0
- /package/dist/{schedule-tool-WXRMJMW6.js.map → schedule-tool-ZTL6UZCC.js.map} +0 -0
- /package/dist/{scheduler-YW6JFH3Z.js.map → scheduler-VH7IJ4VA.js.map} +0 -0
- /package/dist/{seat-K2JSQIZ4.js.map → seat-LQ47SSNN.js.map} +0 -0
- /package/dist/{select-WKMMUWLU.js.map → select-PRQP2B6G.js.map} +0 -0
- /package/dist/{set-enabled-RIE3PLNJ.js.map → set-enabled-E2ML2E33.js.map} +0 -0
- /package/dist/{wiki-WBRXHY7J.js.map → wiki-AS5YHSR7.js.map} +0 -0
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var a=new Set(["telegram","whatsapp","slack","discord","gitlab","github","app","voice","dashboard","webrtc"]),s=new Set(["a2a","mcp","mesh","api","cron","workflow","events","reminder","heartbeat","system","internal","business"]),l=["agent:","cron:","workflow:","mesh:","system:","internal:"];function r(n){if(!n||typeof n!="object")return null;let t=n;if(t.kind!=="human"&&t.kind!=="agent"||typeof t.channel!="string"||!t.channel)return null;let e={kind:t.kind,channel:t.channel.slice(0,40)};return typeof t.chatId=="string"&&(e.chatId=t.chatId.slice(0,200)),typeof t.sender=="string"&&(e.sender=t.sender.slice(0,120)),typeof t.agentId=="string"&&(e.agentId=t.agentId.slice(0,80)),e}function f(n){return r(n?.initiator)}function u(n){if(typeof n!="string")return!1;let t=n.trim().toLowerCase();return t==="agent"||l.some(e=>t.startsWith(e))}function o(n){let t=r(n?.initiator);if(t)return t.kind;let e=(n?.channel||"api").toLowerCase();return s.has(e)||u(n?.sender)?"agent":a.has(e)?"human":"agent"}function c(n,t){let e=r(n?.initiator);if(e)return e;let i={kind:o(n),channel:n?.channel||"api"};return n?.chatId&&(i.chatId=String(n.chatId)),n?.sender&&(i.sender=String(n.sender)),t&&(i.agentId=t),i}function d(n){return n?r(n.initiator)!==null||!!n.delegation:!1}function g(n){return!n||d(n)?!1:o(n)==="human"}export{f as a,u as b,c,d,g as e};
|
|
2
|
+
//# sourceMappingURL=chunk-NMFXY3QU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/a2a/initiator.ts"],"sourcesContent":["// --- Who started this work: a person or an agent ---\n//\n// One place answers \"is the root of this chain a human?\" so every caller\n// (A2A delegation today, the activity graph and ledger next) agrees. Keep\n// all human-vs-agent detection here; do not re-derive it from\n// `senderAgentId` at call sites, because relays such as the phone app set\n// fields that look agent-shaped for a turn a person started.\n//\n// The rules, in order:\n// 1. A propagated root (`context.initiator`) wins. It is stamped on the\n// first hop of a delegation and travels with the context, including\n// across mesh peers (sendTask forwards the context verbatim).\n// 2. Machine channels (cron, workflow, a2a, mcp, mesh, api, ...) are agent.\n// 3. A machine-shaped sender (\"agent:x\", \"cron:y\", ...) is agent. This is\n// how router bot-to-bot chains and callback turns are marked, and how\n// the GitHub/GitLab adapters mark an agent's comment posted with a\n// person's account (forgeSender in channels/outbound-marker.ts).\n// 4. Channels a person types or speaks on are human.\n// 5. Anything else is agent: unknown traffic keeps today's behaviour.\n\nexport type InitiatorKind = \"human\" | \"agent\"\n\n/** The root of a chain, carried in `context.initiator` on every hop after\n * the first. Only small identifying fields: it crosses the mesh. */\nexport interface RootInitiator {\n kind: InitiatorKind\n /** Channel the root turn arrived on (telegram, app, cron, ...). */\n channel: string\n chatId?: string\n sender?: string\n /** The agent that was talking to the person when the chain started. */\n agentId?: string\n}\n\n/** The subset of AgentTask.context this module reads. */\nexport interface InitiatorContext {\n channel?: string\n chatId?: string\n sender?: string\n initiator?: unknown\n delegation?: unknown\n [k: string]: unknown\n}\n\n/** Channels a person writes or speaks on. */\nexport const HUMAN_CHANNELS: ReadonlySet<string> = new Set([\n \"telegram\", \"whatsapp\", \"slack\", \"discord\",\n \"gitlab\", \"github\",\n \"app\", \"voice\", \"dashboard\", \"webrtc\",\n])\n\n/** Channels only software writes on. `api` is here on purpose: a bare API\n * call has no conversation to come back to. */\nexport const AGENT_CHANNELS: ReadonlySet<string> = new Set([\n \"a2a\", \"mcp\", \"mesh\", \"api\",\n \"cron\", \"workflow\", \"events\", \"reminder\", \"heartbeat\",\n \"system\", \"internal\", \"business\",\n])\n\nconst AGENT_SENDER_PREFIXES = [\"agent:\", \"cron:\", \"workflow:\", \"mesh:\", \"system:\", \"internal:\"]\n\nfunction validRoot(v: unknown): RootInitiator | null {\n if (!v || typeof v !== \"object\") return null\n const o = v as Record<string, unknown>\n if (o.kind !== \"human\" && o.kind !== \"agent\") return null\n if (typeof o.channel !== \"string\" || !o.channel) return null\n const root: RootInitiator = { kind: o.kind, channel: o.channel.slice(0, 40) }\n if (typeof o.chatId === \"string\") root.chatId = o.chatId.slice(0, 200)\n if (typeof o.sender === \"string\") root.sender = o.sender.slice(0, 120)\n if (typeof o.agentId === \"string\") root.agentId = o.agentId.slice(0, 80)\n return root\n}\n\n/** The root a delegated hop carries in `context.initiator`, or null on a\n * turn that is itself a root (read-only views such as the activity map). */\nexport function propagatedRootOf(ctx: InitiatorContext | undefined | null): RootInitiator | null {\n return validRoot(ctx?.initiator)\n}\n\n/** True for senders that name software rather than a person. */\nexport function isAgentSender(sender: unknown): boolean {\n if (typeof sender !== \"string\") return false\n const s = sender.trim().toLowerCase()\n return s === \"agent\" || AGENT_SENDER_PREFIXES.some((p) => s.startsWith(p))\n}\n\n/** Human or agent, for the chain this turn belongs to. */\nexport function classifyInitiator(ctx: InitiatorContext | undefined | null): InitiatorKind {\n const root = validRoot(ctx?.initiator)\n if (root) return root.kind\n const channel = (ctx?.channel || \"api\").toLowerCase()\n if (AGENT_CHANNELS.has(channel)) return \"agent\"\n if (isAgentSender(ctx?.sender)) return \"agent\"\n if (HUMAN_CHANNELS.has(channel)) return \"human\"\n return \"agent\"\n}\n\n/** The root to stamp on the next hop. An existing root is passed on\n * unchanged; otherwise this turn is the root. */\nexport function rootInitiatorOf(ctx: InitiatorContext | undefined | null, agentId?: string): RootInitiator {\n const existing = validRoot(ctx?.initiator)\n if (existing) return existing\n const root: RootInitiator = { kind: classifyInitiator(ctx), channel: ctx?.channel || \"api\" }\n if (ctx?.chatId) root.chatId = String(ctx.chatId)\n if (ctx?.sender) root.sender = String(ctx.sender)\n if (agentId) root.agentId = agentId\n return root\n}\n\n/** True for a delegated hop (it carries a valid root) or a callback turn\n * (it carries `delegation`). Neither may hand work off asynchronously:\n * a hop has a caller waiting on its answer, and a callback must not fan\n * out into another callback. */\nexport function isInsideDelegation(ctx: InitiatorContext | undefined | null): boolean {\n if (!ctx) return false\n return validRoot(ctx.initiator) !== null || !!ctx.delegation\n}\n\n/** Is this turn the one talking to the person? Only that turn may hand\n * work off and end: a delegated turn (it carries a root) has a caller\n * waiting on its answer, and a callback turn (it carries `delegation`)\n * must not fan out again. */\nexport function isHumanFacingTurn(ctx: InitiatorContext | undefined | null): boolean {\n if (!ctx || isInsideDelegation(ctx)) return false\n return classifyInitiator(ctx) === \"human\"\n}\n"],"mappings":"AA6CO,IAAMA,EAAsC,IAAI,IAAI,CACzD,WAAY,WAAY,QAAS,UACjC,SAAU,SACV,MAAO,QAAS,YAAa,QAC/B,CAAC,EAIYC,EAAsC,IAAI,IAAI,CACzD,MAAO,MAAO,OAAQ,MACtB,OAAQ,WAAY,SAAU,WAAY,YAC1C,SAAU,WAAY,UACxB,CAAC,EAEKC,EAAwB,CAAC,SAAU,QAAS,YAAa,QAAS,UAAW,WAAW,EAE9F,SAASC,EAAUC,EAAkC,CACnD,GAAI,CAACA,GAAK,OAAOA,GAAM,SAAU,OAAO,KACxC,IAAMC,EAAID,EAEV,GADIC,EAAE,OAAS,SAAWA,EAAE,OAAS,SACjC,OAAOA,EAAE,SAAY,UAAY,CAACA,EAAE,QAAS,OAAO,KACxD,IAAMC,EAAsB,CAAE,KAAMD,EAAE,KAAM,QAASA,EAAE,QAAQ,MAAM,EAAG,EAAE,CAAE,EAC5E,OAAI,OAAOA,EAAE,QAAW,WAAUC,EAAK,OAASD,EAAE,OAAO,MAAM,EAAG,GAAG,GACjE,OAAOA,EAAE,QAAW,WAAUC,EAAK,OAASD,EAAE,OAAO,MAAM,EAAG,GAAG,GACjE,OAAOA,EAAE,SAAY,WAAUC,EAAK,QAAUD,EAAE,QAAQ,MAAM,EAAG,EAAE,GAChEC,CACT,CAIO,SAASC,EAAiBC,EAAgE,CAC/F,OAAOL,EAAUK,GAAK,SAAS,CACjC,CAGO,SAASC,EAAcC,EAA0B,CACtD,GAAI,OAAOA,GAAW,SAAU,MAAO,GACvC,IAAMC,EAAID,EAAO,KAAK,EAAE,YAAY,EACpC,OAAOC,IAAM,SAAWT,EAAsB,KAAMU,GAAMD,EAAE,WAAWC,CAAC,CAAC,CAC3E,CAGO,SAASC,EAAkBL,EAAyD,CACzF,IAAMF,EAAOH,EAAUK,GAAK,SAAS,EACrC,GAAIF,EAAM,OAAOA,EAAK,KACtB,IAAMQ,GAAWN,GAAK,SAAW,OAAO,YAAY,EAEpD,OADIP,EAAe,IAAIa,CAAO,GAC1BL,EAAcD,GAAK,MAAM,EAAU,QACnCR,EAAe,IAAIc,CAAO,EAAU,QACjC,OACT,CAIO,SAASC,EAAgBP,EAA0CQ,EAAiC,CACzG,IAAMC,EAAWd,EAAUK,GAAK,SAAS,EACzC,GAAIS,EAAU,OAAOA,EACrB,IAAMX,EAAsB,CAAE,KAAMO,EAAkBL,CAAG,EAAG,QAASA,GAAK,SAAW,KAAM,EAC3F,OAAIA,GAAK,SAAQF,EAAK,OAAS,OAAOE,EAAI,MAAM,GAC5CA,GAAK,SAAQF,EAAK,OAAS,OAAOE,EAAI,MAAM,GAC5CQ,IAASV,EAAK,QAAUU,GACrBV,CACT,CAMO,SAASY,EAAmBV,EAAmD,CACpF,OAAKA,EACEL,EAAUK,EAAI,SAAS,IAAM,MAAQ,CAAC,CAACA,EAAI,WADjC,EAEnB,CAMO,SAASW,EAAkBX,EAAmD,CACnF,MAAI,CAACA,GAAOU,EAAmBV,CAAG,EAAU,GACrCK,EAAkBL,CAAG,IAAM,OACpC","names":["HUMAN_CHANNELS","AGENT_CHANNELS","AGENT_SENDER_PREFIXES","validRoot","v","o","root","propagatedRootOf","ctx","isAgentSender","sender","s","p","classifyInitiator","channel","rootInitiatorOf","agentId","existing","isInsideDelegation","isHumanFacingTurn"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import{f as j,i as x,p as O,r as I}from"./chunk-S2GAEH2F.js";import{
|
|
1
|
+
import{f as j,i as x,p as O,r as I}from"./chunk-S2GAEH2F.js";import{F as S,I as _}from"./chunk-445P5QPS.js";_();import{Command as q}from"commander";import f from"chalk";import{execFileSync as h}from"child_process";import{accessSync as E,constants as T,existsSync as y,readFileSync as b,readdirSync as $,statSync as z}from"fs";import{join as P,resolve as m}from"path";import{homedir as X}from"os";import{createRequire as B}from"module";import{execFile as L}from"child_process";var M=(n,t)=>new Promise(e=>{try{L(n,t,{timeout:5e3},(i,s)=>e(i?null:String(s)))}catch{e(null)}}),R="Open System Settings \u203A Notifications \u203A AgentX Helper, turn on Allow notifications and choose Banners.";async function N(n={}){if((n.platform??process.platform)!=="darwin")return{state:"unsupported",message:"Desktop banners are only shown on a Mac."};let t=n.helper===void 0?I():n.helper;if(!t)return{state:"missing",message:"AgentX Helper is not installed, so banners come from Script Editor and show its icon.",fix:'Run agentx desktop install, then run agentx notify "Hello" --title "Test" and click Allow.'};let e=await(n.run??M)(t,["notify-status"]),i=null;try{i=e?JSON.parse(e):null}catch{i=null}return i?i.authorization==="notDetermined"?{state:"not-asked",path:t,message:"macOS has not asked yet whether AgentX Helper may notify; banners show the Script Editor icon until it does.",fix:'Run agentx notify "Hello" --title "Test" and click Allow when macOS asks.'}:i.authorization==="denied"?{state:"not-allowed",path:t,message:"macOS does not allow AgentX Helper to notify, so banners show the Script Editor icon.",fix:R}:i.alertStyle==="none"?{state:"hidden",path:t,message:"AgentX Helper may notify, but its banner style is None, so nothing appears on screen.",fix:R}:{state:"ready",path:t,message:"AgentX Helper posts banners with the AgentX icon."}:{state:"outdated",path:t,message:"AgentX Helper is too old to report whether it may notify.",fix:"Run agentx desktop install to update it."}}async function F(n={}){let t=[];await Z(t),U(t);let e=K(t);return e&&Q(t,e),e&&Y(t,e),e&&ee(t,e),e&&te(t,e),e&&oe(t,e),e&&await J(t,e),e&&G(t,e),n.running!==!1&&e&&await ie(t,e),{checks:t,summary:H(t)}}var be=new q().name("doctor").description("pre-flight health check: Node, config, credentials, daemon").option("--no-running","skip the live daemon probe").option("--json","emit machine-readable JSON (stable shape for CI)").action(async n=>{let{checks:t,summary:e}=await F({running:n.running!==!1});n.json?process.stdout.write(JSON.stringify({checks:t,summary:e},null,2)+`
|
|
2
2
|
`):se(t),process.exit(e.errors>0?1:0)});async function J(n,t,e=N){if(!O(t.notifications?.local).banner)return;let i=await e();i.state!=="unsupported"&&n.push({severity:i.state==="ready"?"ok":"warn",group:"Notifications",title:i.message,detail:i.path,fix:i.fix})}function G(n,t,e=process.platform,i=s=>{try{return E(s,T.X_OK),!0}catch{return!1}}){let s=t.reminders;if(!s?.enabled)return;if(e!=="darwin"){n.push({severity:"warn",group:"Reminders",title:"Reminders hand-back unavailable on this machine",detail:"reminders.enabled is set, but Apple Reminders exists only on macOS. The daemon starts without it.",fix:"Set reminders.enabled to false here, or enable it on the Mac node instead."});return}let r=s.command||"remindctl",a=r.includes("/")?i(r)?r:null:x(r,process.env.PATH||"",i);n.push(a?{severity:"ok",group:"Reminders",title:`Watching ${(s.lists||[]).join(", ")} for due reminders`,detail:a}:{severity:"fail",group:"Reminders",title:`${r} not found`,detail:"The daemon reads Apple Reminders through remindctl.",fix:"brew install steipete/tap/remindctl, or set reminders.command to its full path."})}function V(n){let[t,e]=n.split(".").map(Number);return t===22&&e>=19||t>=23&&t<=26}async function Z(n){let t=V(process.versions.node);n.push({severity:t?"ok":"fail",group:"Environment",title:`Node.js ${process.versions.node}`,detail:t?void 0:"AgentX requires Node 22.19 or newer, up to Node 26.",fix:t?void 0:"Install Node 22 (nvm install 22 && nvm use 22)"});let e=l=>{try{return h(process.platform==="win32"?"where":"which",[l],{encoding:"utf-8"}).trim().split(`
|
|
3
3
|
`)[0]}catch{return null}},i=e("npm");n.push({severity:i?"ok":"fail",group:"Environment",title:i?"npm on PATH":"npm not found",detail:i||void 0});let s=e("claude");if(s){let l;try{l=h("claude",["--version"],{encoding:"utf-8",timeout:3e3}).trim()}catch{}n.push({severity:"ok",group:"Environment",title:`claude CLI ${l||"installed"}`,detail:s})}else n.push({severity:"warn",group:"Environment",title:"claude CLI not on PATH",detail:"Only required for agents on the claude-code tier.",fix:"See https://docs.anthropic.com/en/docs/claude-code"});let r=e("codex");if(r){let l;try{l=h("codex",["--version"],{encoding:"utf-8",timeout:3e3}).trim()}catch{}n.push({severity:"ok",group:"Environment",title:`codex CLI ${l||"installed"}`,detail:r})}else n.push({severity:"warn",group:"Environment",title:"codex CLI not on PATH",detail:"Only required for agents on the codex-cli tier.",fix:"Install with `npm i -g @openai/codex`."});let a=e("opencode");if(a){let l;try{l=h("opencode",["--version"],{encoding:"utf-8",timeout:3e3}).trim()}catch{}n.push({severity:"ok",group:"Environment",title:`opencode CLI ${l||"installed"}`,detail:a})}else n.push({severity:"warn",group:"Environment",title:"opencode CLI not on PATH",detail:"Only required for agents on the opencode tier.",fix:"Install from https://opencode.ai/docs/."});try{B(import.meta.url)("better-sqlite3"),n.push({severity:"ok",group:"Environment",title:`better-sqlite3 loads for Node ${process.version}`,detail:`NODE_MODULE_VERSION ${process.versions.modules}`})}catch(l){n.push({severity:"fail",group:"Environment",title:"better-sqlite3 native binding does not load",detail:l?.message??String(l),fix:`Run "pnpm rebuild better-sqlite3" with the same Node used by the daemon (${process.version}, modules ${process.versions.modules}).`})}}function U(n){if(process.platform!=="darwin")return;let t=P(X(),"Library/LaunchAgents"),e=[];try{e=$(t).filter(s=>/agentx\.voice.*\.plist$/.test(s))}catch{return}let i=s=>{try{return E(s,T.X_OK),!0}catch{return!1}};for(let s of e){let r=j(b(P(t,s),"utf-8")),a=x("ffmpeg",r,i);n.push(a?{severity:"ok",group:"Desktop",title:"ffmpeg reachable by the desktop app",detail:`${a} (${s})`}:{severity:"warn",group:"Desktop",title:"ffmpeg not reachable by the desktop app",detail:`Local Whisper transcription fails without it. ${s} sets PATH to ${r}.`,fix:"Install ffmpeg (brew install ffmpeg), then rerun agentx desktop install."})}}function K(n){let t=m(process.cwd(),"agentx.json");if(!y(t))return n.push({severity:"fail",group:"Config",title:"agentx.json not found",detail:`Looked in ${t}`,fix:"Run `agentx setup` to create one via the web wizard."}),null;let e;try{e=JSON.parse(b(t,"utf-8"))}catch(a){return n.push({severity:"fail",group:"Config",title:"agentx.json is not valid JSON",detail:a.message}),null}try{S(t),n.push({severity:"ok",group:"Config",title:`agentx.json valid (${Object.keys(e.agents||{}).length} agents, ${Object.keys(e.crons||{}).length} schedules)`})}catch(a){n.push({severity:"fail",group:"Config",title:"agentx.json failed schema validation",detail:a.message,fix:"Open /admin \u2192 Advanced tab to edit and re-save."})}let i=Object.entries(e.agents||{}).filter(([,a])=>a.tier==="claude-code");i.length>0&&((()=>{try{return h(process.platform==="win32"?"where":"which",["claude"],{stdio:"ignore"}),!0}catch{return!1}})()||n.push({severity:"fail",group:"Config",title:`${i.length} agent(s) use claude-code tier but claude CLI is missing`,detail:i.map(([l])=>l).join(", "),fix:"Install Claude Code (https://docs.anthropic.com/en/docs/claude-code) or switch these agents to tier=sdk."}));let s=Object.entries(e.agents||{}).filter(([,a])=>a.tier==="codex-cli");s.length>0&&((()=>{try{return h(process.platform==="win32"?"where":"which",["codex"],{stdio:"ignore"}),!0}catch{return!1}})()||n.push({severity:"fail",group:"Config",title:`${s.length} agent(s) use codex-cli tier but codex CLI is missing`,detail:s.map(([l])=>l).join(", "),fix:"Install Codex CLI with `npm i -g @openai/codex` or switch these agents to tier=claude-code."}));let r=Object.entries(e.agents||{}).filter(([,a])=>a.tier==="opencode");return r.length>0&&((()=>{try{return h(process.platform==="win32"?"where":"which",["opencode"],{stdio:"ignore"}),!0}catch{return!1}})()||n.push({severity:"fail",group:"Config",title:`${r.length} agent(s) use opencode tier but OpenCode CLI is missing`,detail:r.map(([l])=>l).join(", "),fix:"Install OpenCode from https://opencode.ai/docs/ or switch these agents to another tier."})),e}function Q(n,t){let e=new Set;if(C(t,e),e.size===0){n.push({severity:"ok",group:"Secrets",title:"No ${VAR} references in agentx.json"});return}let i=m(process.cwd(),".env"),s={};if(y(i))for(let l of b(i,"utf-8").split(/\r?\n/)){let u=l.match(/^\s*([A-Z_][A-Z0-9_]*)\s*=\s*(.*)$/i);u&&(s[u[1]]=u[2])}let r=[],a=[];for(let l of e){let u=process.env[l]??s[l];u===void 0?r.push(l):(!u.trim()||/^["']?\s*["']?$/.test(u))&&a.push(l)}r.length===0&&a.length===0?n.push({severity:"ok",group:"Secrets",title:`${e.size} env reference(s) resolved`}):(r.length>0&&n.push({severity:"fail",group:"Secrets",title:`${r.length} env var(s) referenced in agentx.json but missing`,detail:r.join(", "),fix:`Add to .env \u2014 e.g. ${r[0]}=<value>`}),a.length>0&&n.push({severity:"warn",group:"Secrets",title:`${a.length} env var(s) set but empty`,detail:a.join(", ")}))}function C(n,t){if(typeof n=="string")for(let e of n.matchAll(/\$\{([A-Z_][A-Z0-9_]*)\}/g))t.add(e[1]);else if(Array.isArray(n))for(let e of n)C(e,t);else if(n&&typeof n=="object")for(let e of Object.values(n))C(e,t)}function Y(n,t){let e=[];for(let[i,s]of Object.entries(t.agents||{})){if(!s.workspace)continue;let r=s.workspace.startsWith("/")?s.workspace:m(process.cwd(),s.workspace);try{z(r).isDirectory()||e.push(`${i}: ${r} (exists but not a directory)`)}catch{e.push(`${i}: ${r}`)}}e.length===0?n.push({severity:"ok",group:"Workspaces",title:"All agent workspace folders exist"}):n.push({severity:"warn",group:"Workspaces",title:`${e.length} agent workspace(s) missing`,detail:e.join(`
|
|
4
4
|
`),fix:"The daemon will create missing folders on first task, but you won't see CLAUDE.md / skills until you populate them."})}function ee(n,t){let e=/\$\{[A-Za-z_][A-Za-z0-9_]*\}/g,i=[];for(let[s,r]of Object.entries(t.agents||{})){if(!r.workspace)continue;let a=r.workspace.startsWith("/")?r.workspace:m(process.cwd(),r.workspace);for(let l of["settings.json","settings.local.json"]){let u=m(a,".claude",l);if(!y(u))continue;let w;try{w=JSON.parse(b(u,"utf-8"))}catch(d){i.push({severity:"fail",agent:s,msg:`${l}: JSON parse error \u2014 ${d.message}`});continue}let k=w.env||{};for(let[d,p]of Object.entries(k)){if(typeof p!="string")continue;let g=p.match(e);g&&i.push({severity:"fail",agent:s,msg:`${l} env.${d} contains unexpanded ${[...new Set(g)].join(",")} \u2014 Claude Code does NOT expand \${VAR} in settings.json (use a fully-literal value)`})}let o=k.PATH;if(typeof o=="string"){let d=o.split(":").filter(Boolean);d.includes("/usr/bin")||i.push({severity:"fail",agent:s,msg:`${l} env.PATH missing /usr/bin \u2014 cat/head/sh/which will fail`}),d.includes("/bin")||i.push({severity:"fail",agent:s,msg:`${l} env.PATH missing /bin \u2014 ls/cp/mv will fail`})}let c=w.permissions;if(c&&typeof c=="object"&&"allow"in c){let d=c.allow,p=c.deny;Array.isArray(d)&&d.length===0&&Array.isArray(p)&&p.length>0&&i.push({severity:"warn",agent:s,msg:`${l} permissions.allow is explicitly [] with ${p.length} deny rules \u2014 agent may be locked out (omit the key to use defaults instead)`})}}}if(i.length===0){n.push({severity:"ok",group:"Workspace settings",title:`All ${Object.keys(t.agents||{}).length} agent .claude/settings.json files clean`});return}for(let s of i)n.push({severity:s.severity,group:"Workspace settings",title:`${s.agent}: ${s.msg}`,fix:s.severity==="fail"?"Edit .claude/settings.json \u2014 replace ${VAR} with the literal value or omit the key to inherit.":"Either remove the empty allow list or populate it."})}function te(n,t){let e=t.webhooks??[],i=[],s=m(process.cwd(),t.workflows?.dir||".agentx/workflows"),r=new Set;try{if(y(s))for(let o of $(s))o.endsWith(".json")&&!o.startsWith("_")&&!o.endsWith(".disabled.json")&&r.add(o.replace(/\.json$/,""))}catch{}let a=new Set(["github","gitlab"]);for(let o of e){if(!o.enabled||!a.has(o.source))continue;!(o.triggers&&Object.keys(o.triggers).length>0)&&!o.defaultWorkflow&&i.push({severity:"warn",title:`webhook "${o.id}" (${o.source} -> ${o.agentId}) has no triggers map; every event-type collapses to the bound agent`,fix:'Add a triggers map: { "issues.opened": "wf-id", "pull_request.synchronize": "wf-id" } \u2014 or set defaultWorkflow.'})}for(let o of e)if(!(!o.enabled||!o.triggers)){for(let[c,d]of Object.entries(o.triggers))r.size>0&&!r.has(d)&&i.push({severity:"fail",title:`webhook "${o.id}".triggers["${c}"] -> "${d}" \u2014 no such workflow under ${t.workflows?.dir||".agentx/workflows"}`,fix:"Create the workflow file or fix the id in agentx.json"});o.defaultWorkflow&&r.size>0&&!r.has(o.defaultWorkflow)&&i.push({severity:"fail",title:`webhook "${o.id}".defaultWorkflow -> "${o.defaultWorkflow}" \u2014 no such workflow`,fix:"Create the workflow file or fix the id in agentx.json"})}try{if(y(s)){for(let o of $(s))if(!(!o.endsWith(".json")||o.startsWith("_")||o.endsWith(".disabled.json")))try{let c=JSON.parse(b(m(s,o),"utf-8"));c?.status==="active"&&c?.state==="quarantined"&&i.push({severity:"warn",title:`workflow "${c.id??o}" is status=active but state=quarantined \u2014 it looks live but will never dispatch`,fix:'Resolve the conflict the detector flagged, then set state back to "active" (or set status to "review" while parked).'})}catch{}}}catch{}let l=new Set((t.mesh?.peers??[]).map(o=>o.name));for(let o of e)!o.enabled||!o.node||l.has(o.node)||i.push({severity:"fail",title:`webhook "${o.id}" routes to mesh peer "${o.node}" which is not in mesh.peers`,fix:"Add the peer to mesh.peers or remove the node field on the webhook."});let u=new Map;for(let o of e){if(!o.enabled)continue;let c=`${o.source}:${o.agentId}`,d=u.get(c);d?i.push({severity:"warn",title:`webhook entries "${d}" and "${o.id}" both enabled for ${o.source} -> ${o.agentId}; only the first is dispatched`,fix:"Disable one entry, or merge their triggers maps into a single entry."}):u.set(c,o.id)}let w=new Set(Object.keys(t.agents??{}));for(let o of t.channels?.gitlab?.agentMappings??[])if(o?.agentId){if(o.node){l.has(o.node)||i.push({severity:"fail",title:`gitlab.agentMappings entry agentId="${o.agentId}" routes to mesh peer "${o.node}" which is not in mesh.peers`,fix:"Add the peer to mesh.peers or drop the node field on the mapping."});continue}w.has(o.agentId)||i.push({severity:"warn",title:`gitlab.agentMappings entry agentId="${o.agentId}" \u2014 no such agent in agents.* (and no node field, so it's not a cross-mesh route)`,fix:'Add the agent to agents.*, set node:"<peer>" if the agent lives remotely, or remove the mapping.'})}for(let[o,c]of Object.entries(t.channels?.telegram?.accounts??{}))c.agentBinding?w.has(c.agentBinding)||i.push({severity:"fail",title:`telegram account "${o}".agentBinding -> "${c.agentBinding}" \u2014 no such agent in agents.*`,fix:"Fix the agentBinding or define the agent in agents.*."}):i.push({severity:"fail",title:`telegram account "${o}" has no agentBinding; every DM to this bot will drop`,fix:"Set agentBinding to a valid agent id."});let k=new Set(["telegram-bot","whatsapp","slack-bot","discord-bot","gitlab-user","github-user","gmail","hotmail","smtp","hubspot","salesforce","pipedrive","odoo","trello","linear","asana","jira","notion","stripe","sentry","vercel","twilio","custom"]);for(let[o,c]of Object.entries(t.agents??{})){let d=c.integrations??[];if(!d.length)continue;let p=new Set;for(let g of d){let A=`${g.kind}::${g.label}`;p.has(A)&&i.push({severity:"warn",title:`agents.${o}.integrations: duplicate (${g.kind}, ${g.label})`,fix:"Remove the duplicate or rename one. (kind, label) must be unique per agent."}),p.add(A),k.has(g.kind)||i.push({severity:"warn",title:`agents.${o}.integrations: unknown kind "${g.kind}" on integration "${g.label}"`,fix:'Use a canonical kind from INTEGRATION_KINDS, or "custom" for ad-hoc services.'});let W=g.credentials||{};for(let[D,v]of Object.entries(W))typeof v=="string"&&/^[A-Z][A-Z0-9_]*$/.test(v)&&!process.env[v]&&i.push({severity:"warn",title:`agents.${o}.integrations[${g.kind}/${g.label}].${D} -> $${v} but env var is unset (in this shell)`,fix:`Set ${v} on the daemon environment. The daemon's process.env is what matters; this warning fires from your local shell.`})}}if(i.length===0){n.push({severity:"ok",group:"Routing",title:`${e.length} webhook(s), ${Object.keys(t.channels?.telegram?.accounts??{}).length} telegram account(s) \u2014 all routing references resolve`});return}for(let o of i)n.push({severity:o.severity,group:"Routing",title:o.title,fix:o.fix})}function ne(n){return typeof n?.canMeasure!="boolean"?null:n.canMeasure?{severity:"ok",group:"Runtime",title:"Daemon finds ffmpeg to measure phone recordings"}:{severity:"warn",group:"Runtime",title:n.allowUnmeasured?"Daemon can't find ffmpeg: phone recordings go unmeasured (voice.allowUnmeasured)":"Daemon can't find ffmpeg: phone voice input is refused",fix:"Install ffmpeg (brew install ffmpeg / sudo apt install ffmpeg), or set AGENTX_FFMPEG in the service's environment. The daemon's PATH is shorter than your shell's."}}async function ie(n,t){let e=t.dashboard?.daemonUrl?.replace(/\/+$/,"")||"http://127.0.0.1:18800",i={};t.dashboard?.token&&(i.Authorization=`Bearer ${t.dashboard.token}`);try{let s=new AbortController,r=setTimeout(()=>s.abort(),2e3),a=await fetch(e+"/health",{headers:i,signal:s.signal});if(clearTimeout(r),!a.ok){n.push({severity:"warn",group:"Runtime",title:`Daemon responded ${a.status}`,detail:e});return}let l=await a.json();n.push({severity:"ok",group:"Runtime",title:`Daemon healthy \u2014 ${l.agents?.length??0} agents live, uptime ${Math.round((l.uptime??0)/60)}m`,detail:e});let u=ne(l.voice);u&&n.push(u)}catch{n.push({severity:"warn",group:"Runtime",title:"Daemon not reachable (not running?)",detail:e,fix:"Run `agentx daemon start`. Skip this check with `agentx doctor --no-running`."})}}function oe(n,t){let e=Object.entries(t.agents||{}).filter(([,s])=>s?.codegraph===!0&&(s.tier==="claude-code"||s.tier==="codex-cli"));if(e.length===0)return;let i=null;try{i=h(process.platform==="win32"?"where":"which",["codegraph"],{encoding:"utf-8"}).trim().split(`
|
|
5
5
|
`)[0]||null}catch{i=null}if(i){let s;try{s=h("codegraph",["--version"],{encoding:"utf-8",timeout:3e3}).trim()}catch{}n.push({severity:"ok",group:"CodeGraph",title:`codegraph CLI ${s||"installed"}`,detail:i})}else n.push({severity:"fail",group:"CodeGraph",title:"codegraph CLI not on PATH",detail:`${e.length} agent(s) have codegraph:true but the binary isn't available.`,fix:"Install with `npm i -g @colbymchenry/codegraph`."});for(let[s,r]of e){if(!r.workspace||!y(r.workspace)){n.push({severity:"warn",group:"CodeGraph",title:`${s}: workspace missing`,detail:`Workspace path ${r.workspace} does not exist \u2014 codegraph will skip this agent.`});continue}let a=m(r.workspace,".codegraph","codegraph.db");y(a)?n.push({severity:"ok",group:"CodeGraph",title:`${s}: index present`,detail:a}):n.push({severity:"warn",group:"CodeGraph",title:`${s}: index not built yet`,detail:".codegraph/ missing \u2014 daemon will background-index this workspace on next boot.",fix:`Run \`codegraph init --index ${r.workspace}\` to build it now.`})}}function H(n){return{errors:n.filter(t=>t.severity==="fail").length,warnings:n.filter(t=>t.severity==="warn").length,ok:n.filter(t=>t.severity==="ok").length}}function se(n){console.log(),console.log(f.bold(" agentx doctor")),console.log();let t=new Map;for(let i of n)t.has(i.group)||t.set(i.group,[]),t.get(i.group).push(i);for(let[i,s]of t){console.log(f.bold(" "+i));for(let r of s){let a=r.severity==="ok"?f.green("\u2713"):r.severity==="warn"?f.yellow("!"):f.red("\u2717");console.log(` ${a} ${r.title}`),r.detail&&console.log(f.dim(" "+r.detail.replace(/\n/g,`
|
|
6
6
|
`))),r.fix&&console.log(f.dim(" \u2192 "+r.fix))}console.log()}let e=H(n);e.errors>0?console.log(f.red.bold(` ${e.errors} error${e.errors===1?"":"s"}`)+(e.warnings?f.yellow(`, ${e.warnings} warning${e.warnings===1?"":"s"}`):"")+`. Fix the error${e.errors===1?"":"s"} and rerun.`):e.warnings>0?console.log(f.yellow.bold(` ${e.warnings} warning${e.warnings===1?"":"s"}.`)+f.dim(" Review but not blocking.")):console.log(f.green.bold(` All checks passed (${e.ok}).`)),console.log()}export{N as a,F as b,be as c,J as d,G as e,V as f,ne as g};
|
|
7
|
-
//# sourceMappingURL=chunk-
|
|
7
|
+
//# sourceMappingURL=chunk-O67KV3DX.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import{a as h,b as m}from"./chunk-SHEUCITK.js";import{randomUUID as y}from"crypto";var A=["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`,f=class{constructor(e){this.db=e;e.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(e.prepare("PRAGMA table_info(calls)").all().map(t=>t.name));s.has("kind")||e.exec("ALTER TABLE calls ADD COLUMN kind TEXT NOT NULL DEFAULT 'voice'"),s.has("channel")||e.exec("ALTER TABLE calls ADD COLUMN channel TEXT"),s.has("chat_id")||e.exec("ALTER TABLE calls ADD COLUMN chat_id TEXT")}insert(e){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(e.id,e.agentId,e.kind,e.reason,e.urgency,e.status,e.createdAt,e.ringingSince,e.answeredAt,e.endedAt,e.ringAgainAt,e.note,e.summary,e.channel,e.chatId)}get(e){return this.db.prepare(`SELECT ${p} FROM calls WHERE id=?`).get(e)}update(e,s){let t={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 t);if(n.length){let r=n.map(a=>`${t[a]}=?`).join(", ");this.db.prepare(`UPDATE calls SET ${r} WHERE id=?`).run(...n.map(a=>s[a]??null),e)}return this.get(e)}transition(e,s,t){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"},a=Object.keys(t).filter(o=>o in r),l=a.map(o=>`${r[o]}=?`).join(", "),g=n.map(()=>"?").join(",");return this.db.prepare(`UPDATE calls SET ${l} WHERE id=? AND status IN (${g})`).run(...a.map(o=>t[o]??null),e,...n).changes?this.get(e):void 0}list(e={}){let s=Math.min(Math.max(e.limit??50,1),200),t=[],n=[];e.status?.length&&(t.push(`status IN (${e.status.map(()=>"?").join(",")})`),n.push(...e.status)),e.kind&&(t.push("kind=?"),n.push(e.kind));let r=t.length?`WHERE ${t.join(" AND ")}`:"";return this.db.prepare(`SELECT ${p} FROM calls ${r} ORDER BY created_at DESC, rowid DESC LIMIT ?`).all(...n,s)}countSince(e,s){return this.db.prepare("SELECT COUNT(*) AS n FROM calls WHERE agent_id=? AND created_at>?").get(e,s).n}};var w=200,k=1e4,S=10,E=240;function b(i=process.env){return i.AGENTX_TASK_ID?{"X-AgentX-Task":i.AGENTX_TASK_ID}:i.AGENTX_CHANNEL&&i.AGENTX_CHAT_ID?{"X-AgentX-Channel":i.AGENTX_CHANNEL,"X-AgentX-Chat":i.AGENTX_CHAT_ID}:{}}function _(i){return`[Call answered] The owner just picked up the voice call you asked for. You called because: "${i.reason}". Speak first: greet them in a few words and say why you called, then let them answer.`}var v="[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.",T=class{constructor(e){this.deps=e}widgetSeenAt=0;now(){return this.deps.now?.()??Date.now()}name(e){return this.deps.agentName(e)??e}async request(e,s={}){let t=String(e.agentId??"").trim(),n=String(e.reason??"").replace(/\s+/g," ").trim(),r=e.urgency==="urgent"?"urgent":"normal",a=e.kind==="camera"?"camera":"voice";if(e.kind!==void 0&&e.kind!=="voice"&&e.kind!=="camera")return{ok:!1,status:400,error:"kind must be voice or camera"};if(!t||!n)return{ok:!1,status:400,error:"Required: agentId and reason"};if(n.length>w)return{ok:!1,status:413,error:`reason is over ${w} characters; keep it to one line`};if(this.deps.agentName(t)===null)return{ok:!1,status:404,error:`Unknown agent: ${t}`};if(!this.deps.isRunningTurn(t,s))return{ok:!1,status:403,error:`No running turn of ${t} placed this call. An agent calls from inside its own run.`};let l=this.deps.config();if(!l.allow.includes("*")&&!l.allow.includes(t))return{ok:!1,status:403,error:`${t} ${a==="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 ${t}).`};if(await this.sweep(),this.deps.store.list({status:A,limit:200}).some(c=>c.agentId===t))return{ok:!1,status:409,error:`${t} already has a call in progress`};let g=this.now();if(this.deps.store.countSince(t,g-36e5)>=l.maxPerHour)return{ok:!1,status:429,error:`${t} has placed ${l.maxPerHour} calls in the last hour (calls.maxPerHour)`};let d=a==="camera"?this.deps.turnSession?.(t,s)??s:{},o={id:`call-${y().slice(0,8)}`,agentId:t,kind:a,reason:n,urgency:r,status:"ringing",createdAt:g,ringingSince:g,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(o),this.deps.log?.(`[calls] ${t} asks ${a==="camera"?"to see":"for a call"} (${r}): ${n}`);let C=await this.ring(o);return{ok:!0,call:this.deps.store.get(o.id),rang:C}}async ring(e){let s=(this.deps.focus??h)();return s.active&&e.urgency!=="urgent"?(this.deps.store.transition(e.id,"ringing",{status:"missed",ringingSince:null,note:m(s)})&&await this.notice(e,this.missedTitle(e)),!1):e.kind==="camera"?(await this.notice(e,`${this.name(e.agentId)} wants to see through your camera`,`Open the phone app and tap Show within ${this.deps.config().ringSeconds} s.`),"notify"):this.widgetAlive()?"widget":(await this.notice(e,`${this.name(e.agentId)} is calling`,`Open AgentX Voice to answer within ${this.deps.config().ringSeconds} s.`),"notify")}async notice(e,s,t){try{await this.deps.alert({title:s,message:t?`${e.reason}
|
|
10
|
+
${t}`:e.reason,urgent:e.urgency==="urgent",from:e.agentId})}catch(n){this.deps.log?.(`[calls] notice for ${e.id} failed: ${n?.message??n}`)}}missedTitle(e){return e.kind==="camera"?`${this.name(e.agentId)} asked to see through your camera`:`Missed call from ${this.name(e.agentId)}`}widgetAlive(){return this.now()-this.widgetSeenAt<k}async ringing(){return this.widgetSeenAt=this.now(),await this.sweep(),this.deps.store.list({status:["ringing"],kind:"voice",limit:20}).reverse()}async asking(){return await this.sweep(),this.deps.store.list({status:["ringing"],kind:"camera",limit:20}).reverse()}async sweep(){let e=this.now(),s=this.deps.config().ringSeconds*1e3,t=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&&e-n.ringingSince>=s){if(!this.deps.store.transition(n.id,"ringing",{status:"missed",ringingSince:null,note:"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&&e-n.answeredAt>=t){if(!this.deps.store.transition(n.id,"answered",{status:"ended",endedAt:e,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&&e>=n.ringAgainAt){let r=this.deps.store.transition(n.id,"later",{status:"ringing",ringingSince:e,ringAgainAt:null});r&&await this.ring(r)}}answer(e){let s=this.deps.store.get(e);if(!s)return u(e);let t=this.deps.store.transition(e,"ringing",{status:"answered",answeredAt:this.now(),ringingSince:null});return t?(this.deps.log?.(`[calls] ${e} answered (${s.agentId})`),t.kind==="camera"?{ok:!0,call:t}:{ok:!0,call:t,opener:_(t)}):{ok:!1,status:409,error:`call is ${this.deps.store.get(e)?.status}, not ringing`}}decline(e){if(!this.deps.store.get(e))return u(e);let t=this.deps.store.transition(e,["ringing","later"],{status:"declined",endedAt:this.now(),ringingSince:null,ringAgainAt:null});return t?{ok:!0,call:t}:{ok:!1,status:409,error:`call is ${this.deps.store.get(e)?.status}`}}later(e,s){if(!this.deps.store.get(e))return u(e);let n=s===void 0?S:Number(s);if(!Number.isInteger(n)||n<1||n>E)return{ok:!1,status:400,error:`minutes must be a whole number from 1 to ${E}`};let r=this.deps.store.transition(e,"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(e)?.status}, not ringing`}}hangup(e){let s=this.deps.store.get(e);if(!s)return u(e);let t=this.deps.store.transition(e,"answered",{status:"ended",endedAt:this.now()});if(!t)return{ok:!1,status:409,error:`call is ${s.status}, not answered`};this.deps.log?.(`[calls] ${e} ended (${s.agentId})`);let n=this.deps.summarize;if(t.kind==="camera"||!this.deps.config().summary||!n)return{ok:!0,call:t};let r=(async()=>{try{let a=(await n(t))?.trim();if(!a)return;let l=this.deps.store.update(e,{summary:a});this.deps.file?.(l,a)}catch(a){this.deps.log?.(`[calls] summary for ${e} failed: ${a?.message??a}`)}})();return{ok:!0,call:t,summarized:r}}endCamera(e,s){let t=this.deps.store.get(e);if(!t||t.kind!=="camera")return!1;let n=this.deps.store.transition(e,"answered",{status:"ended",endedAt:this.now(),note:s??null});return n&&this.deps.log?.(`[calls] ${e} ended (${t.agentId}): ${s??"share over"}`),!!n}get(e){return this.deps.store.get(e)}list(e={}){return this.deps.store.list(e)}},u=i=>({ok:!1,status:404,error:`No such call: ${i}`});export{f as a,b,v as c,T as d};
|
|
11
|
+
//# sourceMappingURL=chunk-OVN5LVJY.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.\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\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\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\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,\n * 202 held as missed (Focus). */\n async request(input: { agentId?: unknown; reason?: unknown; urgency?: unknown; kind?: unknown }, proof: CallerProof = {}): Promise<CallResult & { rang?: \"widget\" | \"notify\" | false }> {\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 return { ok: true, call: saved, rang }\n }\n\n /** Ring now, or hold as missed in Focus. Returns how it rang. */\n private async ring(call: Call): Promise<\"widget\" | \"notify\" | false> {\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()) return \"widget\"\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 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(): Promise<Call[]> {\n this.widgetSeenAt = this.now()\n await this.sweep()\n return this.deps.store.list({ status: [\"ringing\"], kind: \"voice\", limit: 20 }).reverse()\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: \"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\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","now","session","randomUUID","rang","focus","readFocus","focusLabel","title","extra","e","ringFor","talkFor","again","id","unknown","answered","declined","minutes","later","ended","summarize","summarized","text","done","note","opts"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import{a as O,b as $,c as U,d as T,e as j}from"./chunk-WPNRDEHW.js";import{t as R,v as E,w as W}from"./chunk-7QRCAHPX.js";import{a as B,b as re}from"./chunk-FX3D7YON.js";import{a as z,b as C,f as V}from"./chunk-72SZI2UW.js";import{o as J,p as te}from"./chunk-5MUJZODV.js";V();var we="wiki-rerank",be=30,se=900;function xe(a){let e=a.excerptChars??se;return{query:a.query,candidates:a.candidates.map(t=>({id:t.id,title:t.title,tags:t.tags?.length?t.tags.join(", "):null,excerpt:ne(t.excerpt,e)}))}}function Se(a){let e={};for(let t of a)e[t.id]=t.title;return{best:C(e,"Which candidate answers the query?"),answerable:z("Do any of the candidates answer the query?")}}function Ne(a,e){let t=a.best,r={};for(let i of e)r[i.id]=t.probabilities[i.id]??0;let n=new Map(e.map((i,s)=>[i.id,s]));return{ranked:[...e.map(i=>i.id)].sort((i,s)=>{let c=r[s]-r[i];return c!==0?c:(n.get(i)??0)-(n.get(s)??0)}),scores:r,answerable:a.answerable.noul,confidence:t.confidence}}function ne(a,e){let t=String(a??"").replace(/\s+/g," ").trim();return t.length>e?t.slice(0,e):t}var x={version:2,levels:[{id:"category",description:"Top-level kind of work. Closed set \u2014 pick the closest fit.",axes:[{name:"kind",type:"enum",values:["code","ops","support","admin","knowledge","social","system"],description:"Which category this work belongs in."}]},{id:"verb",description:"Specific intent \u2014 verb-level only, NOT client/project-bound.",axes:[{name:"name",type:"free",description:"Lower-kebab dot-namespaced verb. Examples: review.merge-request, fix.bug, investigate.error, deploy.staging, deploy.production, rollback.release, spec.feature, triage.issue, document.feature, chat.greeting, chat.support-request, schedule.add, config.change, ingest.contact, transcribe.audio, brief.daily, summarize.thread, plan.work."}]}],leafInput:{name:"input",type:"enum",values:["telegram","whatsapp","slack","discord","gitlab-mr","gitlab-issue","github-issue","github-pr","cron","webhook","mesh","http"],description:"Where the message entered the system."},leafOutput:{name:"output",type:"free",description:"One short sentence describing what the agent is expected to produce. Generic \u2014 refers to the verb's outcome, not the specific client/project (those are on the event metadata already)."}},G=[{category:"code",verb:"review.merge-request",description:"Review a GitLab MR or GitHub PR"},{category:"code",verb:"review.code-change",description:"General code review (snippet, diff)"},{category:"code",verb:"fix.bug",description:"Fix a reported bug"},{category:"code",verb:"implement.feature",description:"Build a new feature"},{category:"code",verb:"investigate.error",description:"Investigate a failing test, exception, pipeline"},{category:"code",verb:"refactor.code",description:"Restructure existing code without behaviour change"},{category:"code",verb:"spec.feature",description:"Write or update a feature spec / RFC"},{category:"code",verb:"triage.issue",description:"Classify, label, or assign an issue"},{category:"code",verb:"answer.code-question",description:"Answer a code-related question"},{category:"ops",verb:"deploy.staging",description:"Deploy to a staging/preview environment"},{category:"ops",verb:"deploy.production",description:"Deploy to production"},{category:"ops",verb:"rollback.release",description:"Roll back a release"},{category:"ops",verb:"investigate.incident",description:"Investigate a production incident"},{category:"ops",verb:"monitor.system",description:"Check system status / metrics"},{category:"ops",verb:"rotate.credential",description:"Rotate a token, key, certificate"},{category:"ops",verb:"audit.security",description:"Review for security issues"},{category:"support",verb:"chat.greeting",description:"A casual hello, no actionable request"},{category:"support",verb:"chat.support-request",description:"End-user / customer asking for help"},{category:"support",verb:"chat.casual",description:"Informal back-and-forth chat"},{category:"support",verb:"answer.question",description:"Answer an open-ended question"},{category:"support",verb:"route.request",description:"Forward a request to the right agent"},{category:"admin",verb:"config.change",description:"Change agentx.json / environment / config"},{category:"admin",verb:"schedule.add",description:"Add a new cron / scheduled task"},{category:"admin",verb:"schedule.remove",description:"Remove or disable a schedule"},{category:"admin",verb:"token.create",description:"Mint a scoped API token"},{category:"admin",verb:"agent.add",description:"Configure a new agent"},{category:"admin",verb:"channel.add",description:"Wire up a new channel"},{category:"knowledge",verb:"document.feature",description:"Write or update documentation"},{category:"knowledge",verb:"summarize.thread",description:"Summarize a long conversation/thread"},{category:"knowledge",verb:"research.topic",description:"Investigate / gather info on a topic"},{category:"knowledge",verb:"wiki.absorb",description:"Roll raw entries into typed wiki articles"},{category:"social",verb:"brief.daily",description:"Compose a daily brief / digest"},{category:"social",verb:"draft.post",description:"Draft a marketing post / announcement"},{category:"social",verb:"report.weekly",description:"Compose a weekly status report"},{category:"system",verb:"classify.intent",description:"Internal: graph-classifier sub-call"}];import{readFileSync as I,writeFileSync as M,appendFileSync as ie,existsSync as S,mkdirSync as F,renameSync as oe,statSync as Q}from"fs";import{resolve as A,dirname as X}from"path";import{createHash as ae}from"crypto";var _=class{baseDir;log;parsed=new Map;constructor(e={}){this.baseDir=e.baseDir??A(process.cwd(),".agentx/graph"),this.log=e.log??console.error.bind(console,"[graph]"),F(this.baseDir,{recursive:!0})}schemaPath(){return A(this.baseDir,"schema.json")}nodesPath(){return A(this.baseDir,"nodes.json")}indexPath(){return A(this.baseDir,"index.json")}classificationsPath(){return A(this.baseDir,"classifications.jsonl")}loadSchema(){let e=this.schemaPath();if(!S(e))return this.saveSchema(x),this.log("seeded starter schema at",e),x;let t=I(e,"utf-8"),r=O.safeParse(JSON.parse(t));if(!r.success)throw new Error(`Invalid ${e}: ${r.error.issues.map(n=>n.message).join("; ")}`);if((r.data.version??1)<(x.version??2)){let n=e.replace(/\.json$/,`.v${r.data.version??1}.bak.json`);try{M(n,t),this.log(`migrated graph schema v${r.data.version??1} -> v${x.version}; old saved at ${n}`)}catch(i){this.log(`could not back up old schema: ${i?.message??i}`)}this.saveSchema(x);let o=this.nodesPath();if(S(o)){let i=o.replace(/\.json$/,".v1.bak.json");try{M(i,I(o,"utf-8")),this.log(`archived v1 nodes.json -> ${i}`)}catch(s){this.log(`could not back up old nodes: ${s?.message??s}`)}try{let s=K();this.saveNodes(s)}catch(s){this.log(`could not seed v2 nodes: ${s?.message??s}`)}}return x}return r.data}saveSchema(e){let t=O.safeParse(e);if(!t.success)throw new Error(`Refusing to save invalid schema: ${t.error.issues.map(r=>r.message).join("; ")}`);Y(this.schemaPath(),t.data)}loadNodes(){let e=this.nodesPath();if(!S(e)){let t=K();return this.saveNodes(t),t}return structuredClone(this.readParsed(e,t=>{let r=T.safeParse(JSON.parse(t));if(!r.success)throw new Error(`Invalid ${e}: ${r.error.issues.map(n=>n.message).join("; ")}`);return r.data}))}saveNodes(e){let t=T.safeParse(e);if(!t.success)throw new Error(`Refusing to save invalid nodes file: ${t.error.issues.map(r=>r.message).join("; ")}`);this.writeParsed(this.nodesPath(),t.data)}readParsed(e,t){let{mtimeMs:r,size:n}=Q(e),o=this.parsed.get(e);if(o&&o.mtimeMs===r&&o.size===n)return o.data;let i=t(I(e,"utf-8"));return this.parsed.set(e,{mtimeMs:r,size:n,data:i}),i}writeParsed(e,t){this.parsed.delete(e),Y(e,t);let{mtimeMs:r,size:n}=Q(e);this.parsed.set(e,{mtimeMs:r,size:n,data:t})}validateNode(e,t,r){let n=t.levels.find(s=>s.id===e.level);if(!n)throw new Error(`Unknown level: "${e.level}"`);let o=n.axes.filter(s=>!s.optional).filter(s=>!(s.name in e.axes));if(o.length)throw new Error(`Node ${e.id} (${e.level}) missing required axes: ${o.map(s=>s.name).join(", ")}`);for(let s of n.axes){let c=e.axes[s.name];if(c!=null){if(s.type==="enum"&&s.values&&!s.values.includes(c))throw new Error(`Node ${e.id}: axis ${s.name}="${c}" not in enum ${JSON.stringify(s.values)}`);if(s.type==="ref"){if(!r.some(l=>l.id===c))throw new Error(`Node ${e.id}: axis ${s.name}="${c}" refers to unknown node`);if(s.refLevel){let l=r.find(h=>h.id===c);if(l.level!==s.refLevel)throw new Error(`Node ${e.id}: axis ${s.name} expected level "${s.refLevel}" but got "${l.level}"`)}}}}let i=t.levels.findIndex(s=>s.id===e.level);if(i===0){if(e.parentId!==null)throw new Error(`Root-level node ${e.id} must have parentId: null`)}else{if(!e.parentId)throw new Error(`Non-root node ${e.id} must have a parentId`);let s=r.find(l=>l.id===e.parentId);if(!s)throw new Error(`Node ${e.id}: parent "${e.parentId}" not found`);let c=t.levels.findIndex(l=>l.id===s.level);if(c<0||c>=i)throw new Error(`Node ${e.id} at level "${e.level}" has parent at level "${s.level}" \u2014 parent must be at a higher level`)}}commitNodesAlongPath(e,t,r,n){let o=[],i=[],s=new Date().toISOString();for(let c=0;c<e.length;c++){let l=e[c],h=this.loadNodes().nodes;if(h.find(p=>p.id===l)){i.push(l);continue}let g=0;for(let p=c-1;p>=0;p--){let y=e[p],N=h.find(w=>w.id===y)||o.find(w=>w.id===y);if(N){g=r.levels.findIndex(w=>w.id===N.level)+1;break}}let m=t[l]??{},d=null;for(let p=g;p<r.levels.length;p++){let y=r.levels[p];if(y.axes.filter(w=>!w.optional).filter(w=>!(w.name in m)).length===0){d=y.id,g=p+1;break}}if(!d&&g<r.levels.length&&(d=r.levels[g].id,g++),!d)throw new Error(`Can't place node ${l} \u2014 no schema level left (cursor=${g}, levels=${r.levels.length})`);let f=null,v=r.levels.findIndex(p=>p.id===d);for(let p=c-1;p>=0;p--){let y=e[p],N=h.find(k=>k.id===y)||o.find(k=>k.id===y);if(!N)continue;let w=r.levels.findIndex(k=>k.id===N.level);if(w>=0&&w<v){f=y;break}}let u={id:l,level:d,parentId:f,axes:m,createdAt:s,createdBy:n};this.addNode(u),o.push(u)}return{added:o,skipped:i}}addNode(e){let t=this.loadSchema(),r=this.loadNodes();if(r.nodes.some(n=>n.id===e.id))throw new Error(`Node id already exists: ${e.id}`);return this.validateNode(e,t,[...r.nodes,e]),r.nodes.push(e),this.saveNodes(r),e}appendClassification(e){let t=$.safeParse(e);if(!t.success){let o=t.error.issues.map(i=>`${i.path.join(".")}: ${i.message}`).join(" \xB7 ");return this.log(`skipped invalid classification \u2014 ${o} \xB7`,`path=${JSON.stringify(e.path)}`,`axes-keys=${JSON.stringify(Object.keys(e.proposedAxes||{}))}`),!1}let r=JSON.stringify(t.data)+`
|
|
1
|
+
import{a as O,b as $,c as U,d as T,e as j}from"./chunk-WPNRDEHW.js";import{t as R,v as E,w as W}from"./chunk-2YJ2ZIYC.js";import{a as z,b as C,f as V}from"./chunk-72SZI2UW.js";import{a as B,b as re}from"./chunk-FX3D7YON.js";import{o as J,p as te}from"./chunk-5MUJZODV.js";V();var we="wiki-rerank",be=30,se=900;function xe(a){let e=a.excerptChars??se;return{query:a.query,candidates:a.candidates.map(t=>({id:t.id,title:t.title,tags:t.tags?.length?t.tags.join(", "):null,excerpt:ne(t.excerpt,e)}))}}function Se(a){let e={};for(let t of a)e[t.id]=t.title;return{best:C(e,"Which candidate answers the query?"),answerable:z("Do any of the candidates answer the query?")}}function Ne(a,e){let t=a.best,r={};for(let i of e)r[i.id]=t.probabilities[i.id]??0;let n=new Map(e.map((i,s)=>[i.id,s]));return{ranked:[...e.map(i=>i.id)].sort((i,s)=>{let c=r[s]-r[i];return c!==0?c:(n.get(i)??0)-(n.get(s)??0)}),scores:r,answerable:a.answerable.noul,confidence:t.confidence}}function ne(a,e){let t=String(a??"").replace(/\s+/g," ").trim();return t.length>e?t.slice(0,e):t}var x={version:2,levels:[{id:"category",description:"Top-level kind of work. Closed set \u2014 pick the closest fit.",axes:[{name:"kind",type:"enum",values:["code","ops","support","admin","knowledge","social","system"],description:"Which category this work belongs in."}]},{id:"verb",description:"Specific intent \u2014 verb-level only, NOT client/project-bound.",axes:[{name:"name",type:"free",description:"Lower-kebab dot-namespaced verb. Examples: review.merge-request, fix.bug, investigate.error, deploy.staging, deploy.production, rollback.release, spec.feature, triage.issue, document.feature, chat.greeting, chat.support-request, schedule.add, config.change, ingest.contact, transcribe.audio, brief.daily, summarize.thread, plan.work."}]}],leafInput:{name:"input",type:"enum",values:["telegram","whatsapp","slack","discord","gitlab-mr","gitlab-issue","github-issue","github-pr","cron","webhook","mesh","http"],description:"Where the message entered the system."},leafOutput:{name:"output",type:"free",description:"One short sentence describing what the agent is expected to produce. Generic \u2014 refers to the verb's outcome, not the specific client/project (those are on the event metadata already)."}},G=[{category:"code",verb:"review.merge-request",description:"Review a GitLab MR or GitHub PR"},{category:"code",verb:"review.code-change",description:"General code review (snippet, diff)"},{category:"code",verb:"fix.bug",description:"Fix a reported bug"},{category:"code",verb:"implement.feature",description:"Build a new feature"},{category:"code",verb:"investigate.error",description:"Investigate a failing test, exception, pipeline"},{category:"code",verb:"refactor.code",description:"Restructure existing code without behaviour change"},{category:"code",verb:"spec.feature",description:"Write or update a feature spec / RFC"},{category:"code",verb:"triage.issue",description:"Classify, label, or assign an issue"},{category:"code",verb:"answer.code-question",description:"Answer a code-related question"},{category:"ops",verb:"deploy.staging",description:"Deploy to a staging/preview environment"},{category:"ops",verb:"deploy.production",description:"Deploy to production"},{category:"ops",verb:"rollback.release",description:"Roll back a release"},{category:"ops",verb:"investigate.incident",description:"Investigate a production incident"},{category:"ops",verb:"monitor.system",description:"Check system status / metrics"},{category:"ops",verb:"rotate.credential",description:"Rotate a token, key, certificate"},{category:"ops",verb:"audit.security",description:"Review for security issues"},{category:"support",verb:"chat.greeting",description:"A casual hello, no actionable request"},{category:"support",verb:"chat.support-request",description:"End-user / customer asking for help"},{category:"support",verb:"chat.casual",description:"Informal back-and-forth chat"},{category:"support",verb:"answer.question",description:"Answer an open-ended question"},{category:"support",verb:"route.request",description:"Forward a request to the right agent"},{category:"admin",verb:"config.change",description:"Change agentx.json / environment / config"},{category:"admin",verb:"schedule.add",description:"Add a new cron / scheduled task"},{category:"admin",verb:"schedule.remove",description:"Remove or disable a schedule"},{category:"admin",verb:"token.create",description:"Mint a scoped API token"},{category:"admin",verb:"agent.add",description:"Configure a new agent"},{category:"admin",verb:"channel.add",description:"Wire up a new channel"},{category:"knowledge",verb:"document.feature",description:"Write or update documentation"},{category:"knowledge",verb:"summarize.thread",description:"Summarize a long conversation/thread"},{category:"knowledge",verb:"research.topic",description:"Investigate / gather info on a topic"},{category:"knowledge",verb:"wiki.absorb",description:"Roll raw entries into typed wiki articles"},{category:"social",verb:"brief.daily",description:"Compose a daily brief / digest"},{category:"social",verb:"draft.post",description:"Draft a marketing post / announcement"},{category:"social",verb:"report.weekly",description:"Compose a weekly status report"},{category:"system",verb:"classify.intent",description:"Internal: graph-classifier sub-call"}];import{readFileSync as I,writeFileSync as M,appendFileSync as ie,existsSync as S,mkdirSync as F,renameSync as oe,statSync as Q}from"fs";import{resolve as A,dirname as X}from"path";import{createHash as ae}from"crypto";var _=class{baseDir;log;parsed=new Map;constructor(e={}){this.baseDir=e.baseDir??A(process.cwd(),".agentx/graph"),this.log=e.log??console.error.bind(console,"[graph]"),F(this.baseDir,{recursive:!0})}schemaPath(){return A(this.baseDir,"schema.json")}nodesPath(){return A(this.baseDir,"nodes.json")}indexPath(){return A(this.baseDir,"index.json")}classificationsPath(){return A(this.baseDir,"classifications.jsonl")}loadSchema(){let e=this.schemaPath();if(!S(e))return this.saveSchema(x),this.log("seeded starter schema at",e),x;let t=I(e,"utf-8"),r=O.safeParse(JSON.parse(t));if(!r.success)throw new Error(`Invalid ${e}: ${r.error.issues.map(n=>n.message).join("; ")}`);if((r.data.version??1)<(x.version??2)){let n=e.replace(/\.json$/,`.v${r.data.version??1}.bak.json`);try{M(n,t),this.log(`migrated graph schema v${r.data.version??1} -> v${x.version}; old saved at ${n}`)}catch(i){this.log(`could not back up old schema: ${i?.message??i}`)}this.saveSchema(x);let o=this.nodesPath();if(S(o)){let i=o.replace(/\.json$/,".v1.bak.json");try{M(i,I(o,"utf-8")),this.log(`archived v1 nodes.json -> ${i}`)}catch(s){this.log(`could not back up old nodes: ${s?.message??s}`)}try{let s=K();this.saveNodes(s)}catch(s){this.log(`could not seed v2 nodes: ${s?.message??s}`)}}return x}return r.data}saveSchema(e){let t=O.safeParse(e);if(!t.success)throw new Error(`Refusing to save invalid schema: ${t.error.issues.map(r=>r.message).join("; ")}`);Y(this.schemaPath(),t.data)}loadNodes(){let e=this.nodesPath();if(!S(e)){let t=K();return this.saveNodes(t),t}return structuredClone(this.readParsed(e,t=>{let r=T.safeParse(JSON.parse(t));if(!r.success)throw new Error(`Invalid ${e}: ${r.error.issues.map(n=>n.message).join("; ")}`);return r.data}))}saveNodes(e){let t=T.safeParse(e);if(!t.success)throw new Error(`Refusing to save invalid nodes file: ${t.error.issues.map(r=>r.message).join("; ")}`);this.writeParsed(this.nodesPath(),t.data)}readParsed(e,t){let{mtimeMs:r,size:n}=Q(e),o=this.parsed.get(e);if(o&&o.mtimeMs===r&&o.size===n)return o.data;let i=t(I(e,"utf-8"));return this.parsed.set(e,{mtimeMs:r,size:n,data:i}),i}writeParsed(e,t){this.parsed.delete(e),Y(e,t);let{mtimeMs:r,size:n}=Q(e);this.parsed.set(e,{mtimeMs:r,size:n,data:t})}validateNode(e,t,r){let n=t.levels.find(s=>s.id===e.level);if(!n)throw new Error(`Unknown level: "${e.level}"`);let o=n.axes.filter(s=>!s.optional).filter(s=>!(s.name in e.axes));if(o.length)throw new Error(`Node ${e.id} (${e.level}) missing required axes: ${o.map(s=>s.name).join(", ")}`);for(let s of n.axes){let c=e.axes[s.name];if(c!=null){if(s.type==="enum"&&s.values&&!s.values.includes(c))throw new Error(`Node ${e.id}: axis ${s.name}="${c}" not in enum ${JSON.stringify(s.values)}`);if(s.type==="ref"){if(!r.some(l=>l.id===c))throw new Error(`Node ${e.id}: axis ${s.name}="${c}" refers to unknown node`);if(s.refLevel){let l=r.find(h=>h.id===c);if(l.level!==s.refLevel)throw new Error(`Node ${e.id}: axis ${s.name} expected level "${s.refLevel}" but got "${l.level}"`)}}}}let i=t.levels.findIndex(s=>s.id===e.level);if(i===0){if(e.parentId!==null)throw new Error(`Root-level node ${e.id} must have parentId: null`)}else{if(!e.parentId)throw new Error(`Non-root node ${e.id} must have a parentId`);let s=r.find(l=>l.id===e.parentId);if(!s)throw new Error(`Node ${e.id}: parent "${e.parentId}" not found`);let c=t.levels.findIndex(l=>l.id===s.level);if(c<0||c>=i)throw new Error(`Node ${e.id} at level "${e.level}" has parent at level "${s.level}" \u2014 parent must be at a higher level`)}}commitNodesAlongPath(e,t,r,n){let o=[],i=[],s=new Date().toISOString();for(let c=0;c<e.length;c++){let l=e[c],h=this.loadNodes().nodes;if(h.find(p=>p.id===l)){i.push(l);continue}let g=0;for(let p=c-1;p>=0;p--){let y=e[p],N=h.find(w=>w.id===y)||o.find(w=>w.id===y);if(N){g=r.levels.findIndex(w=>w.id===N.level)+1;break}}let m=t[l]??{},d=null;for(let p=g;p<r.levels.length;p++){let y=r.levels[p];if(y.axes.filter(w=>!w.optional).filter(w=>!(w.name in m)).length===0){d=y.id,g=p+1;break}}if(!d&&g<r.levels.length&&(d=r.levels[g].id,g++),!d)throw new Error(`Can't place node ${l} \u2014 no schema level left (cursor=${g}, levels=${r.levels.length})`);let f=null,v=r.levels.findIndex(p=>p.id===d);for(let p=c-1;p>=0;p--){let y=e[p],N=h.find(k=>k.id===y)||o.find(k=>k.id===y);if(!N)continue;let w=r.levels.findIndex(k=>k.id===N.level);if(w>=0&&w<v){f=y;break}}let u={id:l,level:d,parentId:f,axes:m,createdAt:s,createdBy:n};this.addNode(u),o.push(u)}return{added:o,skipped:i}}addNode(e){let t=this.loadSchema(),r=this.loadNodes();if(r.nodes.some(n=>n.id===e.id))throw new Error(`Node id already exists: ${e.id}`);return this.validateNode(e,t,[...r.nodes,e]),r.nodes.push(e),this.saveNodes(r),e}appendClassification(e){let t=$.safeParse(e);if(!t.success){let o=t.error.issues.map(i=>`${i.path.join(".")}: ${i.message}`).join(" \xB7 ");return this.log(`skipped invalid classification \u2014 ${o} \xB7`,`path=${JSON.stringify(e.path)}`,`axes-keys=${JSON.stringify(Object.keys(e.proposedAxes||{}))}`),!1}let r=JSON.stringify(t.data)+`
|
|
2
2
|
`,n=this.classificationsPath();return S(n)||F(X(n),{recursive:!0}),ie(n,r),!0}listPendingClassifications(e=200){return this.listByStatus("pending",e)}listAllClassifications(){return this.readAllCollapsed()}listByStatus(e,t=200){return this.readAllCollapsed().filter(n=>n.status===e).slice(0,t)}readAllCollapsed(){let e=this.classificationsPath();if(!S(e))return[];let r=I(e,"utf-8").split(`
|
|
3
3
|
`).filter(Boolean),n=new Map;for(let o of r)try{let i=$.safeParse(JSON.parse(o));if(!i.success)continue;n.set(i.data.msgHash,i.data)}catch{}return Array.from(n.values()).sort((o,i)=>o.ts<i.ts?1:-1)}updateClassificationStatus(e,t){let n=this.readAllCollapsed().find(i=>i.msgHash===e);if(!n)return null;let o={...n,status:t.status,ts:new Date().toISOString()};if(t.reviewer||t.reviewReason){let i=` \xB7 ${t.reviewer?`reviewer=${t.reviewer}`:""}${t.reviewReason?` \xB7 ${t.reviewReason}`:""}`;o.preview=((n.preview||"")+i).slice(0,500)}return this.appendClassification(o),o}readRecentClassifications(e=50){let t=this.classificationsPath();if(!S(t))return[];let n=I(t,"utf-8").split(`
|
|
4
4
|
`).filter(Boolean),o=[];for(let i=n.length-1;i>=0&&o.length<e;i--)try{let s=$.safeParse(JSON.parse(n[i]));s.success&&o.push(s.data)}catch{}return o}fingerprint(e){let r=[ce(e.text),e.channel??""].join("");return ae("sha256").update(r).digest("hex").slice(0,32)}loadIndex(){return structuredClone(this.index())}saveIndex(e){let t=j.safeParse(e);if(!t.success)throw new Error(`Refusing to save invalid index: ${t.error.issues.map(r=>r.message).join("; ")}`);this.writeParsed(this.indexPath(),t.data)}index(){let e=this.indexPath();return S(e)?this.readParsed(e,t=>{let r=j.safeParse(JSON.parse(t));if(!r.success)throw new Error(`Invalid ${e}: ${r.error.issues.map(n=>n.message).join("; ")}`);return r.data}):{version:1,entries:{}}}getFingerprint(e){let t=this.index().entries[e];return t&&structuredClone(t)}setFingerprint(e,t){let r=U.safeParse({...t,fingerprint:e,updatedAt:new Date().toISOString()});if(!r.success)throw new Error(`Refusing to save invalid index entry: ${r.error.issues.map(o=>o.message).join("; ")}`);let n=this.index();this.writeParsed(this.indexPath(),{...n,entries:{...n.entries,[e]:r.data}})}updateNodeAxes(e,t){let r=this.loadSchema(),n=this.loadNodes(),o=n.nodes.find(s=>s.id===e);if(!o)throw new Error(`Node not found: ${e}`);let i={...o,axes:t};return this.validateNode(i,r,n.nodes.map(s=>s.id===e?i:s)),n.nodes=n.nodes.map(s=>s.id===e?i:s),this.saveNodes(n),i}deleteNode(e){let t=this.loadNodes(),r=t.nodes.find(o=>o.parentId===e);if(r)throw new Error(`Cannot delete "${e}" \u2014 child node "${r.id}" still points to it`);let n=t.nodes.find(o=>Object.values(o.axes).some(i=>i===e));if(n)throw new Error(`Cannot delete "${e}" \u2014 node "${n.id}" references it via an axis`);t.nodes=t.nodes.filter(o=>o.id!==e),this.saveNodes(t)}approveClassification(e){if(this.getFingerprint(e.msgHash))return;let t=this.loadSchema(),r=new Set(this.loadNodes().nodes.map(n=>n.id));for(let n=0;n<e.path.length;n++){let o=e.path[n];if(r.has(o))continue;let i=t.levels[n]?.id;if(!i)break;let s=e.proposedAxes?.[o]??{},c=n===0?null:e.path[n-1];this.addNode({id:o,level:i,parentId:c,axes:s,createdAt:new Date().toISOString(),createdBy:e.agentId}),r.add(o)}this.setFingerprint(e.msgHash,{path:e.path,leaf:e.leaf}),this.appendClassification({...e,status:"approved",ts:new Date().toISOString()})}rejectClassification(e){this.appendClassification({...e,status:"rejected",ts:new Date().toISOString()})}};function Y(a,e){F(X(a),{recursive:!0});let t=`${a}.tmp-${process.pid}`;M(t,JSON.stringify(e,null,2)),oe(t,a)}function K(){let a=new Date().toISOString(),e=[],t=new Set;for(let r of G)t.has(r.category)||(e.push({id:r.category,level:"category",parentId:null,axes:{kind:r.category},createdAt:a,createdBy:"starter"}),t.add(r.category));for(let r of G)e.push({id:r.verb,level:"verb",parentId:r.category,axes:{name:r.verb,...r.description?{description:r.description}:{}},createdAt:a,createdBy:"starter"});return{version:1,nodes:e}}function ce(a){return a.toLowerCase().replace(/https?:\/\/\S+/g," url ").replace(/[@#][\w.\/-]+/g," ref ").replace(/\b[0-9a-f]{7,}\b/g," id ").replace(/\d+/g," num ").replace(/[^\p{L}\p{N}\s]+/gu," ").replace(/\s+/g," ").trim().slice(0,400)}re();te();W();import{createHash as me}from"crypto";V();W();var P="intent-path",de=.5,le=.3;function pe(a){return{message:fe(a.message,2e3),channel:a.channel??null,sender:a.sender??null,agent:a.agent??null}}function Z(a,e){let t={};for(let r of a)t[r.id]=he(r);return C(t,e)}function ge(a){return{category:Z(a,"Which category of work does this message ask for or report on? Pick the one a reviewer would file it under.")}}function ue(a){return{verb:Z(a,"Within that category, which specific action does the message ask for or report on? Prefer the most specific verb that fits; a casual or ambiguous message belongs to the most general one.")}}async function D(a,e,t={}){if(R(P)==="off")return null;let r=e.filter(m=>m.parentId===null);if(r.length<2)return null;let n=pe(a),o={agent:a.agent??"unknown",channel:a.channel??"unknown"},i=a.agent?[{kind:"agent",id:a.agent}]:void 0,s=await E(P,n,ge(r),{incumbent:t.incumbent?.[0]?{category:t.incumbent[0]}:void 0,features:{...o,stage:"category"},links:i});if(!s)return null;let c=s.answers.category,l=c.pMax,h=c.pMax>=de,b=[c.choice],g=e.filter(m=>m.parentId===c.choice);if(g.length>=2){let m=t.incumbent?.[0]===c.choice,d=await E(P,{...n,category:c.choice},ue(g),{incumbent:m&&t.incumbent?.[1]?{verb:t.incumbent[1]}:void 0,features:{...o,stage:"verb",category:c.choice},links:i});if(!d)return null;let f=d.answers.verb;l=Math.min(l,f.pMax),h=h&&f.pMax>=le,b.push(f.choice)}else g.length===1&&b.push(g[0].id);return{path:b,confidence:l,confident:h,mode:s.mode}}function he(a){let e=a.axes?.description?.trim()||void 0,t=a.axes?.name?.trim(),r=t&&t!==a.id?t:void 0;return e&&r?`${r}: ${e}`:e??r??null}function fe(a,e){return a.length>e?`${a.slice(0,e)}\u2026`:a}var L=class{store;daemonUrl;token;draftAgent;autoApprove;autoApproveStructure;classifierModel;classifierProviderName;providerInstance;log;constructor(e){this.store=e.store,this.daemonUrl=e.daemonUrl.replace(/\/+$/,""),this.token=e.token,this.draftAgent=e.draftAgent,this.autoApprove=e.autoApproveConfidence??1,this.autoApproveStructure=e.autoApproveStructure??"extend-leaves",this.classifierModel=e.classifierModel??"claude-haiku-4-5-20251001",this.classifierProviderName=e.classifierProvider??"claude-code",this.log=e.log??console.error.bind(console,"[classifier]")}getProvider(){return this.providerInstance||(this.providerInstance=J(this.classifierProviderName)),this.providerInstance}async classify(e){let t=this.store.fingerprint({text:e.text,channel:e.channel,sender:e.sender}),r=this.store.getFingerprint(t);if(r)return this.store.appendClassification({ts:new Date().toISOString(),msgHash:t,agentId:e.agentId,channel:e.channel,sender:e.sender,chatId:e.chatId,path:r.path,proposedAxes:{},leaf:r.leaf,source:"cache",status:"approved",preview:e.text.slice(0,200)}),{path:r.path,pathId:H(r.path),pathLabel:q(r.path,this.store.loadNodes().nodes),axes:{},leaf:r.leaf,source:"cache",status:"approved"};let n=this.store.loadSchema(),o=this.store.loadNodes().nodes,i=await this.propose(e,n,o);if(!i)return null;let{path:s,proposedAxes:c,confidence:l,leaf:h}=i;if(s.length===0)return null;let b={ts:new Date().toISOString(),msgHash:t,agentId:e.agentId,channel:e.channel,sender:e.sender,chatId:e.chatId,path:s,proposedAxes:c,leaf:h,source:i.source,status:"pending",confidence:l,preview:e.text.slice(0,200)},g=new Set(o.map(u=>u.id)),m=s.map((u,p)=>g.has(u)?-1:p).filter(u=>u>=0),d=!1;if(this.autoApproveStructure==="any")d=!0;else if(this.autoApproveStructure==="extend-leaves"){let u=m.length===0,p=m.length===1&&m[0]===s.length-1;d=u||p}let f=l!==void 0&&l>=this.autoApprove,v="pending";if(d||f)try{this.commitNewNodes(s,c,n,o,e.sender),this.store.setFingerprint(t,{path:s,leaf:h}),v="approved"}catch(u){this.log("auto-approve failed \u2014 staying pending \xB7",`path=${JSON.stringify(s)}`,`reason=${(u?.message||u).toString().slice(0,200)}`)}return b.status=v,this.store.appendClassification(b),{path:s,pathId:H(s),pathLabel:q(s,this.store.loadNodes().nodes),axes:c,leaf:h,source:i.source,status:v,confidence:l}}async propose(e,t,r){let n=R(P),o={message:e.text,channel:e.channel,sender:e.sender,agent:e.agentId};if(n==="active"){let s=await D(o,r).catch(c=>(this.log("intent-path seat failed:",c?.message||c),null));if(s?.confident)return{path:s.path,proposedAxes:{},leaf:{},confidence:s.confidence,source:"seat"}}let i=await this.proposePath(e,t,r).catch(s=>(this.log("LLM proposal failed:",s?.message||s),null));return n==="shadow"&&i&&D(o,r,{incumbent:i.path}).catch(()=>{}),i?{...i,source:"llm"}:null}async proposePath(e,t,r){let n=r.map(d=>({id:d.id,level:d.level,parentId:d.parentId,axes:d.axes})),o=["You are the intent classifier for AgentX. You answer ONE question:",'"what kind of work does this message describe?" \u2014 verb-level only.',"","IMPORTANT \u2014 what NOT to encode in the path:","- Client / company / project / team names. Those are already on the event"," metadata (project, channel, agentId). Embedding them in the path causes"," duplicate nodes (one per client) for the same verb.","- The specific subject (issue number, file name, person name). Same reason.","- The agent's name or role. Pick the verb the *requester* intended.","","SCHEMA (fixed levels \u2014 pick one node per level):","```json",JSON.stringify(t,null,2),"```","","EXISTING NODES (prefer reusing these; only invent a new node when no","existing one fits):","```json",JSON.stringify(n,null,2),"```","","MESSAGE:","```",e.text.slice(0,2e3),"```",`channel: ${e.channel??"?"}`,`sender: ${e.sender??"?"}`,"","TASK:","1. Classify into 'category' (closed enum): code, ops, support, admin,"," knowledge, social, system. Pick the closest fit.","2. Pick or propose a 'verb' node. Verb ids are dot-namespaced lower-kebab,",' e.g. "review.merge-request", "deploy.staging", "investigate.error",',' "chat.greeting", "fix.bug". Reuse an existing verb when the message is'," the same kind of work as something already classified, even when the"," client / project / subject differs.","","Examples (right vs wrong):",'- "Please review MR #957 on globex/system" \u2192 ["code", "review.merge-request"]',' NOT ["business", "acme", "globex-v2", "review-mr-957-system"]','- "Deploy initech-v2 to staging please" \u2192 ["ops", "deploy.staging"]',' NOT ["business", "acme", "initech-v2", "deploy-initech-v2-to-staging"]','- "Hello Atlas" \u2192 ["support", "chat.greeting"]',"","NODE ID RULES (strict \u2014 invalid ids get dropped):","- lowercase-kebab + dots only: [a-z0-9][a-z0-9._-]*","- no spaces, no uppercase, no Arabic/other non-Latin",'- verb ids should use a "category.specific" or "verb.modifier" shape',"","Return ONE JSON object on one line, no prose, no fences:",' { "path": string[], "proposedAxes": { [nodeId]: { [axisName]: string } }, "leaf": { "input"?: string, "output"?: string }, "confidence": number }',"confidence in [0,1]. Prefer low confidence over guessing."].join(`
|
|
5
5
|
`),i=await this.callProvider(o),s=B(i);if(!s||!Array.isArray(s.path))return null;let c=/^[a-z0-9][a-z0-9._-]*$/,l=new Map,h=s.path.filter(d=>typeof d=="string"&&d.length>0).map(d=>{let f=d.trim(),v=ee(f);return f!==v&&l.set(f,v),v}).filter(d=>c.test(d));if(h.length===0)return null;let b={};if(s.proposedAxes&&typeof s.proposedAxes=="object")for(let[d,f]of Object.entries(s.proposedAxes)){if(!f||typeof f!="object")continue;let v=l.get(d)??ee(d);if(!v)continue;let u={};for(let[p,y]of Object.entries(f))typeof y=="string"&&(u[p]=y);!u.name&&d!==v&&(u.name=d.trim()),b[v]=u}let g={};s.leaf&&typeof s.leaf=="object"&&(typeof s.leaf.input=="string"&&(g.input=s.leaf.input),typeof s.leaf.output=="string"&&(g.output=s.leaf.output));let m=typeof s.confidence=="number"?Math.max(0,Math.min(1,s.confidence)):void 0;return{path:h,proposedAxes:b,confidence:m,leaf:g}}commitNewNodes(e,t,r,n,o){this.store.commitNodesAlongPath(e,t,r,o)}async callProvider(e){let t=this.getProvider(),r=new AbortController,n=setTimeout(()=>r.abort(),3e4);try{return(await t.generate([{role:"system",content:"You are a taxonomy classifier. Respond with a single JSON object on one line. No prose, no code fences."},{role:"user",content:e}],{model:this.classifierModel,maxTokens:800,abortSignal:r.signal,bare:!0})).content||""}finally{clearTimeout(n)}}};function ee(a){let e=a.trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/^[-_]+|[-_]+$/g,"").replace(/-{2,}/g,"-");return e?/^[a-z0-9]/.test(e)?e:`n-${e}`.replace(/[-_]+$/,""):""}function H(a){return me("sha1").update(a.join("")).digest("hex").slice(0,16)}function q(a,e){return a.map(t=>{let r=e.find(o=>o.id===t);return r&&(r.axes?.name||r.axes?.what)||t}).join(" \u203A ")}function ve(a,e){if(a.length===0||e.length===0)return 0;let t=0;for(let n=0;n<Math.min(a.length,e.length)&&a[n]===e[n];n++)t++;if(t===0)return 0;let r=Math.max(a.length,e.length);return t/r}export{we as a,be as b,xe as c,Se as d,Ne as e,x as f,_ as g,L as h,q as i,ve as j};
|
|
6
|
-
//# sourceMappingURL=chunk-
|
|
6
|
+
//# sourceMappingURL=chunk-PHFOYJUB.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{c as te,d as ne,e as F,f as C,g as j,h as B}from"./chunk-O5U5NVLR.js";import{a as ee}from"./chunk-CL53UVOF.js";import{a as Q,b as Z}from"./chunk-
|
|
1
|
+
import{c as te,d as ne,e as F,f as C,g as j,h as B}from"./chunk-O5U5NVLR.js";import{a as ee}from"./chunk-CL53UVOF.js";import{a as Q,b as Z}from"./chunk-AFSM3IY3.js";import{a as V}from"./chunk-AIHWGU4P.js";import{C as O,E as G,F as _,I as M}from"./chunk-445P5QPS.js";import{existsSync as W,readFileSync as ce,writeFileSync as pe,appendFileSync as Ae,mkdirSync as Se}from"fs";import{dirname as Ce,resolve as P}from"path";import{fileURLToPath as Ie}from"url";import{hostname as Ee}from"os";import{spawn as Te}from"child_process";M();import{existsSync as re,readFileSync as ae,writeFileSync as L,copyFileSync as ye}from"fs";import{resolve as ie}from"path";function oe(e,t={}){let n=t.configPath||ie(process.cwd(),"agentx.json");if(!re(n))throw new Error(`agentx.json not found at ${n}`);let a=JSON.parse(ae(n,"utf-8")),r=e(a),i=typeof r=="string"?{summary:r}:r,s=JSON.stringify(a,null,2)+`
|
|
2
2
|
`,l=n+".tmp";L(l,s,"utf-8");try{JSON.parse(ae(l,"utf-8"))}catch(c){throw new Error(`config did not round-trip cleanly: ${c.message}`)}let o;return t.skipBackup||(o=`${n}.bak.${Date.now()}`,ye(n,o)),L(n,s,"utf-8"),t.skipReload||ke().catch(()=>{}),{summary:i.summary,payload:i.payload,backupPath:o}}function se(e,t={}){let n=t.configPath||ie(process.cwd(),"agentx.json");if(re(n)&&!t.overwrite)throw new Error(`agentx.json already exists at ${n}. Pass overwrite=true to replace it.`);return L(n,JSON.stringify(e,null,2)+`
|
|
3
3
|
`,"utf-8"),{file:n}}async function ke(){try{let t=_().dashboard.daemonUrl?.replace(/\/+$/,"")||"http://127.0.0.1:18800";await fetch(`${t}/reload`,{method:"POST"}).catch(()=>null)}catch{}}function le(e,t){let n=e.configExists?e.agentCount===0?"Add your first agent":"Add another agent":"Set up your team",a=`<header class="ax-topbar">
|
|
4
4
|
<div class="ax-topbar__left">
|
|
@@ -737,4 +737,4 @@ tr:hover td { background: var(--bg-hover); }
|
|
|
737
737
|
<h3><a href="${l}/article/${encodeURIComponent(c.path)}">${d(c.meta.title)}</a></h3>
|
|
738
738
|
</div>`;return $(`${t} \xB7 ${n}`,I(r),o)}function U(e,t,n){let a=e.rebuildIndex(),r=J(a.articles.map(s=>({title:s.title,path:s.path,tags:s.tags}))),i='<div class="nav-section">';i+=`<div class="nav-section-title">${d(t)}</div>`,i+=`<a class="nav-item${n?"":" active"}" href="/">Home</a>`,i+='<a class="nav-item" href="/lint">Health check</a>',i+="</div>";for(let[s,l]of r){i+='<div class="nav-section">',i+=`<div class="nav-section-title">${q(s)}</div>`;for(let o of l){let c=o.path===n;i+=`<a class="nav-item${c?" active":""}" href="/article/${encodeURIComponent(o.path)}">${d(o.title)}</a>`}i+="</div>"}return i}function Ye(e){let{wikiDir:t,agentFilter:n,peerUrls:a=[],mode:r="graph",pathPrefix:i=""}=e,s=i.replace(/\/+$/,""),l=new V(t,void 0,r),o=a.length>0?new D(a):null;return async(c,g)=>{let x=new URL(c.url||"/","http://localhost"),p=decodeURIComponent(x.pathname),f=s&&p.startsWith(s)?p.slice(s.length)||"/":p,h=null,T=200;try{if(f.startsWith("/remote/")&&o){let y=f.slice(8).split("/article/");if(y.length===2){let[b,v]=y,m=b.indexOf("/"),k=decodeURIComponent(b.slice(0,m)),u=b.slice(m+1),w=await o.getRemoteArticle(k,u,v);if(w){let z=await o.getRemoteAgents(),N=[...l.summary(),...z.map(S=>({...S,agentId:`${S.agentId} @${S.nodeId}`}))],A=`<div class="breadcrumb"><a href="/">Hub</a> / <span class="tag">remote</span> ${d(u)} @ ${d(k)}</div>`;A+=`<h1>${d(w.meta.title)}</h1>`,A+='<dl class="meta">',A+=`<dt>Tags</dt><dd>${(w.meta.tags||[]).map(S=>`<span class="tag">${S}</span>`).join(" ")}</dd>`,A+=`<dt>Owner</dt><dd>${d(w.meta.owner)}</dd>`,A+=`<dt>Source</dt><dd><span class="tag">remote</span> ${d(k)}</dd>`,A+=`<dt>Updated</dt><dd>${w.meta.lastUpdated}</dd>`,A+="</dl>";let E=new Map;A+=H(w.content,E),h=$(w.meta.title,I(N),A)}}}if(!h&&n){let y=l.getAgentWiki(n);if(f==="/"||f===""){let b=y.rebuildIndex(),v=l.getAgentEntries(n),m=`<h1>${d(n)}</h1>`;if(m+='<div class="stats">',m+=`<div class="stat-card"><div class="number">${b.articles.length}</div><div class="label">Articles</div></div>`,m+=`<div class="stat-card"><div class="number">${v.length}</div><div class="label">Entries</div></div>`,m+="</div>",b.articles.length>0){m+="<h2>Articles</h2><table><tr><th>Title</th><th>Type</th><th>Updated</th></tr>";for(let k of b.articles)m+=`<tr><td><a href="/article/${encodeURIComponent(k.path)}">${d(k.title)}</a></td><td><span class="tag">${(k.tags||[]).slice(0,3).join(", ")}</span></td><td>${k.lastUpdated||""}</td></tr>`;m+="</table>"}h=$(n,U(y,n),m)}else if(f.startsWith("/article/")){let b=f.slice(9),v=y.readArticle(b);if(v){let m=y.rebuildIndex(),k=new Map;for(let w of m.articles)k.set(w.title,w.path);let u=`<h1>${d(v.meta.title)}</h1>`;u+='<dl class="meta">',u+=`<dt>Tags</dt><dd>${(v.meta.tags||[]).map(w=>`<span class="tag">${w}</span>`).join(" ")}</dd>`,u+=`<dt>Updated</dt><dd>${v.meta.lastUpdated}</dd>`,v.meta.sources?.length&&(u+=`<dt>Sources</dt><dd>${v.meta.sources.length} entries</dd>`),u+="</dl>",u+=H(v.content,k),h=$(v.meta.title,U(y,n,b),u)}}else if(f==="/lint"){let b=y.lint(),v="<h1>Health Check</h1>";if(b.length===0)v+='<p style="color:green;">No issues.</p>';else{v+="<table><tr><th>Type</th><th>Article</th><th>Issue</th></tr>";for(let m of b)v+=`<tr><td><span class="tag">${m.type}</span></td><td>${d(m.article)}</td><td>${d(m.message)}</td></tr>`;v+="</table>"}h=$("Health Check",U(y,n),v)}else if(f==="/search"){let b=x.searchParams.get("q")||"",v=await y.findRelevantReranked(b,void 0,20),m=`<h1>Search: "${d(b)}"</h1>`;for(let k of v)m+=`<div class="entry-card"><h3><a href="/article/${encodeURIComponent(k.path)}">${d(k.meta.title)}</a></h3><p>${d(k.content.slice(0,200))}...</p></div>`;v.length===0&&(m+="<p>No results.</p>"),h=$(`Search: ${b}`,U(y,n),m)}else if(f.startsWith("/tag/")){let b=decodeURIComponent(f.slice(5)),v=y.findByTags([b],void 0,100),m=`<h1>Tag: <a class="tag" href="#">${d(b)}</a></h1>`;m+=`<p>${v.length} article${v.length===1?"":"s"} tagged <code>${d(b)}</code></p>`;for(let k of v)m+=`<div class="entry-card"><h3><a href="/article/${encodeURIComponent(k.path)}">${d(k.meta.title)}</a></h3><div class="meta-line">${R((k.meta.tags||[]).slice(0,6))}</div></div>`;v.length===0&&(m+="<p>No articles with this tag.</p>"),h=$(`Tag: ${b}`,U(y,n),m)}}else{let y=o?await o.getRemoteAgents():[],b=l.summary(),v=new Map(b.map(u=>[u.agentId,u])),m=[],k=new Set;for(let u of b){let w=y.find(z=>z.agentId===u.agentId);w&&w.totalArticles>u.totalArticles?m.push({...w,totalEntries:Math.max(u.totalEntries,w.totalEntries),unabsorbed:Math.max(u.unabsorbed,w.unabsorbed)}):m.push(u),k.add(u.agentId)}for(let u of y)k.has(u.agentId)||(m.push(u),k.add(u.agentId));if(f==="/"||f==="")h=Me(l,m,y);else if(f==="/entries")h=Le(l,m);else if(f==="/search")h=Je(l,x.searchParams.get("q")||"",m);else if(f.startsWith("/tag/"))h=qe(l,decodeURIComponent(f.slice(5)),m);else if(f.match(/^\/agent\/[^/]+\/tag\/.+/)){let u=f.match(/^\/agent\/([^/]+)\/tag\/(.+)$/);h=Ke(l,decodeURIComponent(u[1]),decodeURIComponent(u[2]),m)}else if(f.startsWith("/agent/")){let u=f.slice(7),w=u.indexOf("/article/");if(w===-1)h=Fe(l,u,m);else{let z=u.slice(0,w),N=u.slice(w+9);if(h=je(l,z,N,m),!h&&o){let A=y.find(E=>E.agentId===z);if(A){let E=await o.getRemoteArticle(A.peerUrl,z,N);if(E){let S=`<div class="breadcrumb"><a href="/">Hub</a> / <span class="tag">remote @ ${d(A.nodeId)}</span> / ${d(z)}</div>`;S+=`<h1>${d(E.meta.title)}</h1>`,S+='<dl class="meta">',S+=`<dt>Tags</dt><dd>${(E.meta.tags||[]).map(be=>`<span class="tag">${be}</span>`).join(" ")}</dd>`,S+=`<dt>Source</dt><dd><span class="tag">remote</span> ${d(A.nodeId)}</dd>`,S+=`<dt>Updated</dt><dd>${E.meta.lastUpdated}</dd>`,S+="</dl>",S+=H(E.content,new Map),h=$(E.meta.title,I(m),S)}}}}}}if(!h){T=404;let y=n?U(l.getAgentWiki(n),n):I(l.summary());h=$("Not Found",y,"<h1>Page not found</h1>")}}catch(y){T=500,h=$("Error","",`<h1>Error</h1><pre>${d(y.message)}</pre>`)}g.writeHead(T,{"Content-Type":"text/html; charset=utf-8"}),g.end(s?Xe(h,s):h)}}function Xe(e,t){e=e.replace(/(\s(?:href|action)=")\/(?!\/)/g,`$1${t}/`),e=e.replace(new RegExp(`(${t})${t}/`,"g"),`${t}/`);let n=`<a href="/admin" style="position:fixed;top:10px;right:14px;z-index:9999;padding:5px 11px;background:rgba(0,0,0,0.55);color:#fff;font:12px/1.2 'IBM Plex Sans',system-ui,sans-serif;text-decoration:none;border-radius:6px;border:1px solid rgba(255,255,255,0.18);backdrop-filter:blur(6px)">\u2190 Dashboard</a>`;return e=e.replace(/<body([^>]*)>/,`<body$1>${n}`),e}function Kt(e,t=4200,n,a=[],r="graph"){let i=Ye({wikiDir:e,agentFilter:n,peerUrls:a,mode:r});Ne((l,o)=>{i(l,o)}).listen(t)}import{createCipheriv as Ge,createDecipheriv as Ve,createHash as Qe,randomBytes as K,randomInt as Ze,scryptSync as et,timingSafeEqual as tt}from"crypto";import{existsSync as nt,mkdirSync as at,readFileSync as rt,renameSync as it,writeFileSync as ot}from"fs";import{dirname as st,resolve as lt}from"path";var Y="23456789ABCDEFGHJKMNPQRSTUVWXYZ",X=8,dt=10*60*1e3,ct=".agentx/pair-codes.json",pt={N:16384,r:8,p:1};function gt(){let e="";for(let t=0;t<X;t++)e+=Y[Ze(Y.length)];return e}function Qt(e){return`${e.slice(0,4)}-${e.slice(4)}`}function mt(e){if(typeof e!="string"||e.length>64)return null;let t=e.toUpperCase().replace(/[\s\-‐-―]/g,"");if(t.length!==X)return null;for(let n of t)if(!Y.includes(n))return null;return t}var ve=class{constructor(t=process.cwd(),n=ct,a=Date.now){this.now=a;this.file=lt(t,n)}file;create(t){let n=this.load(),a=gt(),{id:r,key:i}=xe(a,n.salt),s=this.now(),l=new Date(s+dt).toISOString();return n.codes=n.codes.filter(o=>Date.parse(o.expiresAt)>s),n.codes.push({id:r,tokenId:t.tokenId,name:t.name,sealed:ut(i,t.token),createdAt:new Date(s).toISOString(),expiresAt:l}),this.save(n),{code:a,expiresAt:l}}redeem(t){let n=this.load(),a=mt(t),{id:r,key:i}=xe(a??"-".repeat(X),n.salt);if(!a)return null;let s=Buffer.from(r),l=n.codes.find(c=>c.id.length===r.length&&tt(Buffer.from(c.id),s));if(!l||Date.parse(l.expiresAt)<=this.now())return null;let o=ft(i,l.sealed);return o?(n.codes=n.codes.filter(c=>c!==l&&Date.parse(c.expiresAt)>this.now()),this.save(n),{token:o,tokenId:l.tokenId,name:l.name}):null}load(){if(nt(this.file))try{let t=JSON.parse(rt(this.file,"utf-8"));if(t&&typeof t.salt=="string"&&Array.isArray(t.codes))return t}catch{}return{salt:K(16).toString("hex"),codes:[]}}save(t){at(st(this.file),{recursive:!0});let n=`${this.file}.${process.pid}.${K(4).toString("hex")}.tmp`;ot(n,JSON.stringify(t,null,2)+`
|
|
739
739
|
`,{encoding:"utf-8",mode:384}),it(n,this.file)}};function xe(e,t){let n=et(e,Buffer.from(t,"hex"),64,pt);return{id:"sha256:"+Qe("sha256").update(n.subarray(0,32)).digest("hex"),key:n.subarray(32)}}function ut(e,t){let n=K(12),a=Ge("aes-256-gcm",e,n),r=Buffer.concat([a.update(t,"utf-8"),a.final()]);return Buffer.concat([n,a.getAuthTag(),r]).toString("base64")}function ft(e,t){try{let n=Buffer.from(t,"base64"),a=Ve("aes-256-gcm",e,n.subarray(0,12));return a.setAuthTag(n.subarray(12,28)),Buffer.concat([a.update(n.subarray(28)),a.final()]).toString("utf-8")}catch{return null}}var ht="https://ntfy.sh";function en(e){return{enabled:!!e?.enabled,server:e?.server||ht,topicSet:!!e?.topic,tokenSet:!!e?.token}}function tn(e,t){let n={...e??{}};if("enabled"in t&&(n.enabled=!!t.enabled),"server"in t){let a=String(t.server).trim().replace(/\/+$/,"");if(!/^https?:\/\/\S+$/.test(a))throw new Error("ntfy server must be an http(s) URL");n.server=a}if("topic"in t){let a=String(t.topic).trim();if(!a)throw new Error("ntfy topic cannot be empty");n.topic=a}if("token"in t){let a=String(t.token??"").trim();a?n.token=a:delete n.token}if(n.enabled&&!n.topic)throw new Error("set a topic before enabling ntfy");return n}export{oe as a,ge as b,Pt as c,Rt as d,Ut as e,dt as f,Qt as g,ve as h,Dt as i,Ht as j,Ot as k,en as l,tn as m,Ye as n,Kt as o};
|
|
740
|
-
//# sourceMappingURL=chunk-
|
|
740
|
+
//# sourceMappingURL=chunk-PT5Z5WW4.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{b as S,c as O}from"./chunk-
|
|
1
|
+
import{b as S,c as O}from"./chunk-AFSM3IY3.js";import{a as I,b as X}from"./chunk-FCPTN6NF.js";import{a as F,b as oe}from"./chunk-7I7IEX4S.js";import{d as M}from"./chunk-QIJLFPWE.js";import{execFile as Le}from"child_process";import{writeFileSync as ee,mkdirSync as w,existsSync as b,readFileSync as $e}from"fs";import{resolve as _}from"path";var ce=["off","shadow","authoritative"],ae={telegram:"INTENT_LEDGER_MODE_TELEGRAM",slack:"INTENT_LEDGER_MODE_SLACK",whatsapp:"INTENT_LEDGER_MODE_WHATSAPP",discord:"INTENT_LEDGER_MODE_DISCORD",gitlab:"INTENT_LEDGER_MODE_GITLAB",github:"INTENT_LEDGER_MODE_GITHUB",workflow:"INTENT_LEDGER_MODE_WORKFLOW",cron:"INTENT_LEDGER_MODE_CRON",mesh:"INTENT_LEDGER_MODE_MESH"},de="INTENT_LEDGER_MODE";function B(n,e=process.env){let t=P(e[ae[n]]);if(t)return t;let i=P(e[de]);return i||"off"}function P(n){if(typeof n!="string")return null;let e=n.trim().toLowerCase();return ce.includes(e)?e:null}X();import ue from"better-sqlite3";import{existsSync as Pe,mkdirSync as le}from"fs";import{dirname as ge,resolve as me}from"path";var T=class{db;path;constructor(e={}){this.path=me(process.cwd(),e.path??".agentx/intent/ledger.sqlite"),le(ge(this.path),{recursive:!0}),this.db=new ue(this.path),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),fe(this.db)}close(){this.db.close()}schemaVersion(){return this.db.prepare("SELECT MAX(v) AS v FROM schema_version").get().v??0}recordEvent(e){if(e.sourceEventId!==null){let i=this.findEventBySourceId(e.source,e.sourceEventId);if(i)return i}let t={id:e.id??I(e.ts),ts:e.ts,source:e.source,sourceEventId:e.sourceEventId,project:e.project,subject:e.subject,intent:e.intent,rawJson:e.rawJson};return this.db.prepare(`INSERT INTO intent_events (id, ts, source, source_event_id, project, subject, intent, raw_json)
|
|
2
2
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(t.id,t.ts,t.source,t.sourceEventId,t.project,t.subject,t.intent,t.rawJson),t}findEventBySourceId(e,t){let i=this.db.prepare(`SELECT id, ts, source, source_event_id, project, subject, intent, raw_json
|
|
3
3
|
FROM intent_events WHERE source = ? AND source_event_id = ?`).get(e,t);return i?U(i):null}getEventById(e){let t=this.db.prepare(`SELECT id, ts, source, source_event_id, project, subject, intent, raw_json
|
|
4
4
|
FROM intent_events WHERE id = ?`).get(e);return t?U(t):null}recordDecision(e){this.db.prepare(`INSERT INTO intent_decisions (event_id, decided_at, decided_by, agent_id, outcome, reason)
|
|
@@ -123,4 +123,4 @@ ${r.prompt}`,r.maxOutputTokens),agentId:r.agent,autonomy:r.autonomy,timeoutMinut
|
|
|
123
123
|
`);return t.length<=e?t.join(`
|
|
124
124
|
`):t.slice(-e).join(`
|
|
125
125
|
`)}function Ce(n,e,t){return new Promise((i,r)=>{Le("/bin/sh",["-c",n],{timeout:e,maxBuffer:8*1024*1024,...t?{env:t}:{}},(s,c,o)=>{if(s&&s.killed){r(new Error(`command timed out after ${Math.round(e/1e3)}s`));return}i({stdout:String(c??""),stderr:String(o??""),code:s?typeof s.code=="number"?s.code:1:0})})})}export{T as a,G as b,it as c,De as d,rt as e,st as f,$ as g,Q as h,B as i,je as j,V as k,K as l,at as m,Ae as n,ne as o,ie as p,re as q,we as r,C as s};
|
|
126
|
-
//# sourceMappingURL=chunk-
|
|
126
|
+
//# sourceMappingURL=chunk-PXIGWFIQ.js.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{readFileSync as i}from"fs";import{homedir as c}from"os";import{join as u}from"path";var d=u(c(),"Library","DoNotDisturb","DB","Assertions.json"),l=u(c(),".agentx","focus.json");function S(e=d,t=l){let r=f(t);return r.active?r:m(e)}function f(e=l){try{if(JSON.parse(i(e,"utf-8"))?.active===!0)return{active:!0,mode:"widget",reason:"holding \u2014 switched on from the widget"}}catch{}return{active:!1,mode:null,reason:"widget hold is off"}}function m(e=d){let t;try{t=i(e,"utf-8")}catch(a){return a?.code==="EPERM"||a?.code==="EACCES"?{active:!1,mode:null,reason:"Focus database not readable \u2014 grant Full Disk Access to the app running agentx"}:{active:!1,mode:null,reason:"no Focus database on this machine"}}let r;try{r=JSON.parse(t)}catch{return{active:!1,mode:null,reason:"Focus database was not readable JSON"}}let n=(Array.isArray(r?.data)?r.data[0]:null)?.storeAssertionRecords;if(!Array.isArray(n)||n.length===0)return{active:!1,mode:null,reason:"no active Focus assertion"};let s=n[0],o=s?.assertionDetails?.assertionDetailsModeIdentifier??s?.assertionDetails?.assertionDetailsModeID??null;return{active:!0,mode:typeof o=="string"?o:null,reason:`Focus assertion present${o?` (${o})`:""}`}}function y(e){return e.active?e.mode==="widget"?"holding (widget)":e.mode?`in ${(e.mode.split(".").pop()??e.mode).replace(/-/g," ")}`:"in Focus":"not in Focus"}export{S as a,y as b};
|
|
2
|
+
//# sourceMappingURL=chunk-SHEUCITK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/notify/focus.ts"],"sourcesContent":["import { readFileSync } from \"fs\"\nimport { homedir } from \"os\"\nimport { join } from \"path\"\n\n// Is the person currently in Do Not Disturb / a Focus mode?\n//\n// macOS keeps the answer in a JSON file rather than behind an API a\n// non-sandboxed CLI can reach. An ACTIVE focus appears as an entry under\n// `data[0].storeAssertionRecords`; when nothing is active the key is not\n// merely empty, it is absent, and the file instead holds the invalidation\n// records for the focus that was just switched off.\n//\n// Verified on this machine at both ends:\n//\n// focus off data[0] = { storeInvalidationRequestRecords,\n// storeInvalidationRecords } (no assertions)\n// focus on data[0] gains storeAssertionRecords with one entry per\n// active mode\n//\n// Reading a private file is a compromise and it is worth naming. The\n// alternative is to ignore Focus entirely and interrupt someone who has\n// explicitly asked not to be, which is worse. The failure mode is chosen\n// to match: anything unexpected — file missing, shape changed after an OS\n// update, unreadable — is reported as NOT in focus, so notifications keep\n// flowing. A notification that arrives during Focus is a small annoyance;\n// one that is swallowed forever because a file moved is a broken feature.\n\nexport interface FocusState {\n /** True when a Focus / Do Not Disturb mode is on. */\n active: boolean\n /** The mode's identifier when macOS names one, e.g. com.apple.sleep.sleep-mode. */\n mode: string | null\n /** Why we believe this — recorded so a wrong answer is diagnosable. */\n reason: string\n}\n\nconst ASSERTIONS = join(homedir(), \"Library\", \"DoNotDisturb\", \"DB\", \"Assertions.json\")\n\n/** Where the widget records a hold the person switched on themselves. */\nconst WIDGET_HOLD = join(homedir(), \".agentx\", \"focus.json\")\n\n/**\n * Focus, from either source: the widget's own toggle or the system's.\n *\n * The widget toggle exists because macOS Focus is a heavy instrument —\n * turning it on silences everything on every device, which is more than\n * someone wants when they only mean \"let me finish this\". A switch on the\n * widget is a hold you can flick on for twenty minutes without negotiating\n * with the operating system.\n *\n * Either source being on is enough. They are not ranked: both are the\n * same person saying the same thing by different means, and a rule that\n * let one override the other would mean ignoring an explicit instruction\n * because it arrived through the wrong switch.\n */\nexport function readFocus(path = ASSERTIONS, holdPath = WIDGET_HOLD): FocusState {\n const held = readWidgetHold(holdPath)\n if (held.active) return held\n return readSystemFocus(path)\n}\n\n/** The widget's own toggle. Absent file means not held. */\nexport function readWidgetHold(path = WIDGET_HOLD): FocusState {\n try {\n const parsed = JSON.parse(readFileSync(path, \"utf-8\"))\n if (parsed?.active === true) {\n return { active: true, mode: \"widget\", reason: \"holding — switched on from the widget\" }\n }\n } catch {\n /* no file, or unreadable: not held */\n }\n return { active: false, mode: null, reason: \"widget hold is off\" }\n}\n\nexport function readSystemFocus(path = ASSERTIONS): FocusState {\n let raw: string\n try {\n raw = readFileSync(path, \"utf-8\")\n } catch (e: any) {\n // The file exists on every recent macOS but is private: without Full\n // Disk Access for the process reading it, the answer is \"denied\", and\n // saying so is the difference between a fixable setup and a mystery.\n if (e?.code === \"EPERM\" || e?.code === \"EACCES\") {\n return { active: false, mode: null, reason: \"Focus database not readable — grant Full Disk Access to the app running agentx\" }\n }\n return { active: false, mode: null, reason: \"no Focus database on this machine\" }\n }\n\n let parsed: any\n try {\n parsed = JSON.parse(raw)\n } catch {\n return { active: false, mode: null, reason: \"Focus database was not readable JSON\" }\n }\n\n const record = Array.isArray(parsed?.data) ? parsed.data[0] : null\n const assertions = record?.storeAssertionRecords\n if (!Array.isArray(assertions) || assertions.length === 0) {\n return { active: false, mode: null, reason: \"no active Focus assertion\" }\n }\n\n // The mode identifier lives a few levels down and its exact path has\n // moved between OS versions, so it is treated as a nicety: the presence\n // of the assertion is what decides, the name only improves the message.\n const first = assertions[0]\n const mode =\n first?.assertionDetails?.assertionDetailsModeIdentifier ??\n first?.assertionDetails?.assertionDetailsModeID ??\n null\n\n return {\n active: true,\n mode: typeof mode === \"string\" ? mode : null,\n reason: `Focus assertion present${mode ? ` (${mode})` : \"\"}`,\n }\n}\n\n/** A short phrase for logs and for telling someone why a message waited. */\nexport function focusLabel(state: FocusState): string {\n if (!state.active) return \"not in Focus\"\n if (state.mode === \"widget\") return \"holding (widget)\"\n if (!state.mode) return \"in Focus\"\n // com.apple.sleep.sleep-mode -> sleep mode\n const tail = state.mode.split(\".\").pop() ?? state.mode\n return `in ${tail.replace(/-/g, \" \")}`\n}\n"],"mappings":"AAAA,OAAS,gBAAAA,MAAoB,KAC7B,OAAS,WAAAC,MAAe,KACxB,OAAS,QAAAC,MAAY,OAkCrB,IAAMC,EAAaD,EAAKD,EAAQ,EAAG,UAAW,eAAgB,KAAM,iBAAiB,EAG/EG,EAAcF,EAAKD,EAAQ,EAAG,UAAW,YAAY,EAgBpD,SAASI,EAAUC,EAAOH,EAAYI,EAAWH,EAAyB,CAC/E,IAAMI,EAAOC,EAAeF,CAAQ,EACpC,OAAIC,EAAK,OAAeA,EACjBE,EAAgBJ,CAAI,CAC7B,CAGO,SAASG,EAAeH,EAAOF,EAAyB,CAC7D,GAAI,CAEF,GADe,KAAK,MAAMJ,EAAaM,EAAM,OAAO,CAAC,GACzC,SAAW,GACrB,MAAO,CAAE,OAAQ,GAAM,KAAM,SAAU,OAAQ,4CAAwC,CAE3F,MAAE,CAEF,CACA,MAAO,CAAE,OAAQ,GAAO,KAAM,KAAM,OAAQ,oBAAqB,CACnE,CAEO,SAASI,EAAgBJ,EAAOH,EAAwB,CAC7D,IAAIQ,EACJ,GAAI,CACFA,EAAMX,EAAaM,EAAM,OAAO,CAClC,OAASM,EAAP,CAIA,OAAIA,GAAG,OAAS,SAAWA,GAAG,OAAS,SAC9B,CAAE,OAAQ,GAAO,KAAM,KAAM,OAAQ,qFAAiF,EAExH,CAAE,OAAQ,GAAO,KAAM,KAAM,OAAQ,mCAAoC,CAClF,CAEA,IAAIC,EACJ,GAAI,CACFA,EAAS,KAAK,MAAMF,CAAG,CACzB,MAAE,CACA,MAAO,CAAE,OAAQ,GAAO,KAAM,KAAM,OAAQ,sCAAuC,CACrF,CAGA,IAAMG,GADS,MAAM,QAAQD,GAAQ,IAAI,EAAIA,EAAO,KAAK,CAAC,EAAI,OACnC,sBAC3B,GAAI,CAAC,MAAM,QAAQC,CAAU,GAAKA,EAAW,SAAW,EACtD,MAAO,CAAE,OAAQ,GAAO,KAAM,KAAM,OAAQ,2BAA4B,EAM1E,IAAMC,EAAQD,EAAW,CAAC,EACpBE,EACJD,GAAO,kBAAkB,gCACzBA,GAAO,kBAAkB,wBACzB,KAEF,MAAO,CACL,OAAQ,GACR,KAAM,OAAOC,GAAS,SAAWA,EAAO,KACxC,OAAQ,0BAA0BA,EAAO,KAAKA,KAAU,IAC1D,CACF,CAGO,SAASC,EAAWC,EAA2B,CACpD,OAAKA,EAAM,OACPA,EAAM,OAAS,SAAiB,mBAC/BA,EAAM,KAGJ,OADMA,EAAM,KAAK,MAAM,GAAG,EAAE,IAAI,GAAKA,EAAM,MAChC,QAAQ,KAAM,GAAG,IAHX,WAFE,cAM5B","names":["readFileSync","homedir","join","ASSERTIONS","WIDGET_HOLD","readFocus","path","holdPath","held","readWidgetHold","readSystemFocus","raw","e","parsed","assertions","first","mode","focusLabel","state"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{q as _,r as W}from"./chunk-2AXFYDWF.js";import{b as Y,c as I,d as O}from"./chunk-AQASR6KR.js";import{b as F}from"./chunk-DP7RTNDM.js";import{a as E}from"./chunk-BBI3HG77.js";import{b as L}from"./chunk-CECHWYJ2.js";import{createHash as ee}from"crypto";import{existsSync as H,readdirSync as te,readFileSync as re,writeFileSync as ne}from"fs";import{resolve as P}from"path";function be(n,r,s,t,o,i,c=""){return Z(r,s,t,o,i,c)}function Z(n,r,s,t,o,i=""){let c=r?`
|
|
2
2
|
## Worldview
|
|
3
3
|
|
|
4
4
|
${r}
|
|
@@ -240,4 +240,4 @@ ${g}`}var x="memory-promoter",oe=["project","reference","feedback"],K="_memory-p
|
|
|
240
240
|
Re-emit the FULL corrected JSON object \u2014 same structure, fixed issues, valid JSON only.`:n,y=await t(`${a}/task`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({agent:r.viaAgent,message:h,freshSession:s===null,context:{channel:"api",chatId:r.chatId??"memory-promote"}}),signal:i.signal});if(!y.ok){let f=await y.text().catch(()=>"");throw new Error(`agent /task ${y.status}: ${f.slice(0,500)}`)}let w=await y.json();if(w.error)throw new Error(`agent reported error: ${w.error}`);let b=String(w.content??"").trim();if(!b)throw new Error("agent returned empty content");return b}if(!r.model)throw new Error("callPromotionLlm: viaAgent or model is required");let p=process.env.ANTHROPIC_API_KEY;if(!p)throw new Error("ANTHROPIC_API_KEY not set; use --via <agentId> instead");let u=[{role:"user",content:n}];s&&(u.push({role:"assistant",content:"(previous attempt; will retry with corrections)"}),u.push({role:"user",content:`Your previous response failed validation: ${s}
|
|
241
241
|
Re-emit the FULL corrected JSON object.`}));let g=await t(me,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":p,"anthropic-version":ge},body:JSON.stringify({model:r.model,max_tokens:8e3,messages:u}),signal:i.signal});if(!g.ok){let a=await g.text().catch(()=>"");throw new Error(`anthropic API ${g.status}: ${a.slice(0,500)}`)}let l=await g.json(),e=(Array.isArray(l.content)?l.content:[]).filter(a=>a?.type==="text").map(a=>a.text).join(`
|
|
242
242
|
`).trim();if(!e)throw new Error("anthropic API returned no text content");return e}finally{clearTimeout(c)}}async function Se(n={}){let r=n.wikiDir??P(process.cwd(),".agentx","wiki"),s=n.memoryRoot??P(process.cwd(),".agentx"),t=n.log??(()=>{}),o=n.now??Date.now(),i={candidates:[],clusters:[],proposed:[],skipped:[],gaps:[],warnings:[],errors:[],dryRun:!n.commit},c=new E(r,(...m)=>t(m.map(String).join(" "))),p=c.rebuildIndex(),u=z(r),g=[...ce(s),...n.extraCandidates??[]];if(i.candidates=de(g,p,u,{types:n.types,sinceMs:n.sinceMs,agentFilter:n.agentFilter,now:o,max:n.max}),i.candidates.length===0)return t("no unpromoted memories in window"),i;if(i.clusters=le(i.candidates),i.dryRun)return i;let l=p.articles.map(m=>({title:m.title,path:m.path,type:m.type})),d=c.getWorldview()??"",e=n.budgetTokens??ue,a=i.clusters,h=D(x,a,l,d);for(;R(h)>e&&a.length>1;)a=a.slice(0,-1),h=D(x,a,l,d);if(R(h)>e)return i.errors.push(`judge prompt (~${R(h)} tokens) exceeds the ${e}-token budget even with one cluster`),i;a.length<i.clusters.length&&i.warnings.push(`token budget: judged ${a.length} of ${i.clusters.length} clusters; the rest wait for the next run`);let y=a.flatMap(m=>m.candidates),w=new Set(y.map(m=>m.stamp)),b=n.chatId??`memory-promote-${new Date(o).toISOString().slice(0,10)}`,f=B(await G(h,{...n,chatId:b}),w);if("error"in f&&(t(`parse failed (${f.error}) \u2014 retrying with feedback`),f=B(await G(h,{...n,chatId:b},f.error),w)),"error"in f)return i.errors.push(`LLM response unusable after retry: ${f.error}`),i;i.warnings.push(...f.warnings),i.gaps=f.gaps;let v=new Date(o).toISOString(),k=[],T=new Map(y.map(m=>[m.stamp,m]));for(let m of f.articles){let A=c.readArticle(m.path);if(A&&!c.canWrite(A.meta,x)){i.errors.push(`write denied for "${m.path}" (owned by ${A.meta.owner??"?"})`);continue}let M=fe(m.promotedFrom,T),S={id:Y(o,m.path),createdAt:v,status:"pending",article:m,evidence:M,...A?{replaces:{lastUpdated:A.meta.lastUpdated,contentHash:V(A.content)}}:{}};I(r,S),i.proposed.push({id:S.id,path:m.path,title:m.title,type:m.type,stamps:m.promotedFrom,agents:M.agents,occurrences:M.occurrences});for(let X of m.promotedFrom)k.push({stamp:X,decision:"proposed",article:m.path,proposal:S.id,at:v})}for(let m of f.skipped)k.push({stamp:m.stamp,decision:"skipped",reason:m.reason,at:v}),i.skipped.push(m);return k.length&&C(r,k),i}var ue=6e4;function R(n){return Math.ceil(n.length/4)}function V(n){return ee("sha256").update(n).digest("hex").slice(0,16)}var he=600;function ye(n){return n.startsWith("review:")?"review":n.startsWith(j)?"failure":"memory"}function fe(n,r){let s=[];for(let t of n){let o=r.get(t);o&&s.push({stamp:t,kind:ye(t),agentId:o.agentId,type:o.memory.type,name:o.memory.name,description:o.memory.description,...o.occurrences?{occurrences:o.occurrences}:{},...o.sessions?.length?{sessions:o.sessions}:{},...o.tasks?.length?{tasks:o.tasks}:{},...o.failure?{failure:o.failure}:{},...o.memory.author?{author:o.memory.author}:{},...o.memory.taskId?{taskId:o.memory.taskId}:{},updatedAt:o.memory.updatedAt,excerpt:o.memory.body.slice(0,he)})}return{agents:[...new Set(s.map(t=>t.agentId))].sort(),occurrences:Math.max(1,...s.map(t=>t.occurrences??1)),sources:s}}function Ee(n,r,s={}){let t=O(n,r);if(!t)return{ok:!1,error:`no proposal "${r}"`};if(t.status!=="pending")return{ok:!1,error:`proposal "${r}" is already ${t.status}`};let o=s.log??(()=>{}),i=new E(n,(...a)=>o(a.map(String).join(" "))),{article:c}=t,p=i.readArticle(c.path);if(!s.force){if(p&&!t.replaces)return{ok:!1,error:`"${c.path}" was created after this proposal; review it, then approve with --force`};if(p&&t.replaces&&V(p.content)!==t.replaces.contentHash)return{ok:!1,error:`"${c.path}" changed since this proposal; review it, then approve with --force`}}let u=s.now??Date.now(),g=new Date(u).toISOString().slice(0,10);if(!i.writeArticle(c.path,{title:c.title,type:L(c.type)?c.type:void 0,related:c.related,tags:c.tags,owner:x,access:"public",created:p?.meta.created??g,lastUpdated:g,sources:ae(p?.meta.sources,c.promotedFrom)},c.content,x))return{ok:!1,error:`write denied for "${c.path}" (owned by ${p?.meta.owner??"?"})`};let d=new Date(u).toISOString();C(n,c.promotedFrom.map(a=>({stamp:a,decision:"promoted",article:c.path,proposal:r,at:d}))),i.rebuildIndex();let e={...t,status:"approved",decidedBy:s.by??"operator",decidedAt:d};return I(n,e),{ok:!0,proposal:e}}function Oe(n,r,s={}){let t=O(n,r);if(!t)return{ok:!1,error:`no proposal "${r}"`};if(t.status!=="pending")return{ok:!1,error:`proposal "${r}" is already ${t.status}`};let o=new Date(s.now??Date.now()).toISOString();C(n,t.article.promotedFrom.map(c=>({stamp:c,decision:"rejected",article:t.article.path,proposal:r,reason:s.reason,at:o})));let i={...t,status:"rejected",decidedBy:s.by??"operator",decidedAt:o,...s.reason?{reason:s.reason}:{}};return I(n,i),{ok:!0,proposal:i}}export{be as a,x as b,oe as c,K as d,q as e,ie as f,$ as g,ae as h,ce as i,z as j,C as k,le as l,B as m,de as n,G as o,Se as p,R as q,ye as r,fe as s,Ee as t,Oe as u};
|
|
243
|
-
//# sourceMappingURL=chunk-
|
|
243
|
+
//# sourceMappingURL=chunk-Z522XZBL.js.map
|