agentix-cli 0.110.1 → 0.111.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/apps/mac-voice/Sources/AgentXVoice/Panel.swift +77 -30
  2. package/apps/mac-voice/Sources/AgentXVoice/PanelAnswer.swift +3 -2
  3. package/apps/mac-voice/Sources/AgentXVoice/PillForm.swift +55 -0
  4. package/apps/mac-voice/Sources/AgentXVoice/PillMenu.swift +8 -7
  5. package/apps/mac-voice/Sources/AgentXVoice/SettingsLook.swift +2 -2
  6. package/apps/mac-voice/Sources/AgentXVoice/StatusMenu.swift +5 -6
  7. package/apps/mac-voice/Sources/AgentXVoice/main.swift +7 -6
  8. package/dist/{App-4VWAQOVA.js → App-T4Q7SW6M.js} +2 -2
  9. package/dist/{ChatApp-KOTGUDZH.js → ChatApp-BKUJUIDL.js} +2 -2
  10. package/dist/{agent-CGRHC2IS.js → agent-YZVEXFE7.js} +2 -2
  11. package/dist/{board-dashboard-SAYBNLZW.js → board-dashboard-MZVUQ2YO.js} +411 -257
  12. package/dist/board-dashboard-MZVUQ2YO.js.map +1 -0
  13. package/dist/{chunk-SFPMS55Z.js → chunk-3RHFEKFX.js} +49 -48
  14. package/dist/chunk-3RHFEKFX.js.map +1 -0
  15. package/dist/chunk-3RTOS727.js +482 -0
  16. package/dist/chunk-3RTOS727.js.map +1 -0
  17. package/dist/{chunk-UVMCAZL7.js → chunk-4FU4BFCH.js} +2 -2
  18. package/dist/{chunk-NIW75USA.js → chunk-4NFDDTGA.js} +2 -2
  19. package/dist/{chunk-2VGYRETU.js → chunk-53OS4TDU.js} +2 -2
  20. package/dist/{chunk-J32LSOBJ.js → chunk-7UNS34CN.js} +2 -2
  21. package/dist/chunk-AV7T3SYT.js +111 -0
  22. package/dist/chunk-AV7T3SYT.js.map +1 -0
  23. package/dist/chunk-DTQQ4DAY.js +154 -0
  24. package/dist/chunk-DTQQ4DAY.js.map +1 -0
  25. package/dist/{chunk-BFOB6ERR.js → chunk-DXWDVKMX.js} +2 -2
  26. package/dist/{chunk-BZKGQMU2.js → chunk-FBODSJU6.js} +2 -2
  27. package/dist/{chunk-UTV4DRAW.js → chunk-GQMUFSKB.js} +14 -14
  28. package/dist/{chunk-UTV4DRAW.js.map → chunk-GQMUFSKB.js.map} +1 -1
  29. package/dist/{chunk-4UCDWEC4.js → chunk-HCTWKWS3.js} +2 -2
  30. package/dist/chunk-JIT5GPTX.js +4 -0
  31. package/dist/chunk-JIT5GPTX.js.map +1 -0
  32. package/dist/{chunk-GI4RYX4G.js → chunk-K24MYJFP.js} +238 -238
  33. package/dist/chunk-K24MYJFP.js.map +1 -0
  34. package/dist/{chunk-5O6PIO7F.js → chunk-LWYKS5JU.js} +2 -2
  35. package/dist/{chunk-QQAZ6EJP.js → chunk-M2L6S3Z5.js} +2 -2
  36. package/dist/{chunk-6F2E5NQA.js → chunk-QJP37VVA.js} +2 -2
  37. package/dist/chunk-QLWGLRT5.js +3 -0
  38. package/dist/chunk-QLWGLRT5.js.map +1 -0
  39. package/dist/{chunk-QFP4YZII.js → chunk-RS2T53EC.js} +2 -2
  40. package/dist/{chunk-D7M4WQFU.js → chunk-SIOWVLZG.js} +4 -4
  41. package/dist/chunk-SIOWVLZG.js.map +1 -0
  42. package/dist/{chunk-BG6SOGKT.js → chunk-U52IS3AF.js} +2 -2
  43. package/dist/{chunk-RJ7DASOU.js → chunk-UIBUZ2N3.js} +25 -10
  44. package/dist/chunk-UIBUZ2N3.js.map +1 -0
  45. package/dist/{chunk-MJWJ3JVA.js → chunk-WSKQUKCF.js} +2 -2
  46. package/dist/{chunk-S4NZUPIN.js → chunk-WXQQO2PD.js} +2 -2
  47. package/dist/{chunk-CJR26JUI.js → chunk-XEOT2F4C.js} +2 -2
  48. package/dist/{chunk-CJL5OPNQ.js → chunk-Z7L74GGT.js} +2 -2
  49. package/dist/cli.js +1 -1
  50. package/dist/{client-VYMTUZKN.js → client-O7SPKO5X.js} +2 -2
  51. package/dist/config-ZHQYXFOJ.js +2 -0
  52. package/dist/config-mutator-RS2NTDNO.js +2 -0
  53. package/dist/{doctor-GHUPUBET.js → doctor-XP7O45RC.js} +2 -2
  54. package/dist/{grants-Y667GZAU.js → grants-BRAVOGBP.js} +2 -2
  55. package/dist/index.d.ts +68 -15
  56. package/dist/index.js +1 -1
  57. package/dist/{program-RMGWJHT6.js → program-YK3YR2CQ.js} +187 -182
  58. package/dist/program-YK3YR2CQ.js.map +1 -0
  59. package/dist/{promote-KY3Y2PVU.js → promote-UGWANGI2.js} +2 -2
  60. package/dist/{query-BYCRYDM3.js → query-ZET7Z5FT.js} +2 -2
  61. package/dist/registry-UXWOY52B.js +2 -0
  62. package/dist/request-planner-W5YLKCGG.js +6 -0
  63. package/dist/request-planner-W5YLKCGG.js.map +1 -0
  64. package/dist/{review-candidates-56ZPJNXW.js → review-candidates-GU4S5VQH.js} +2 -2
  65. package/dist/{routing-WKS2CZYF.js → routing-GJ4VXKIC.js} +2 -2
  66. package/dist/schedule-tool-HD4LWKVC.js +2 -0
  67. package/dist/scheduler-3LEHZHWJ.js +2 -0
  68. package/dist/seat-L6XJ2BFA.js +2 -0
  69. package/dist/select-T7CKKLC2.js +2 -0
  70. package/dist/{select-73JC2YBN.js.map → select-T7CKKLC2.js.map} +1 -1
  71. package/dist/{set-enabled-WQAO4BFF.js → set-enabled-U2HGNY2O.js} +2 -2
  72. package/dist/{surface-usage-CH3SHO77.js → surface-usage-HCJTUS6Z.js} +2 -2
  73. package/dist/{tool-QDU6MY6B.js → tool-2FMWZB4N.js} +2 -2
  74. package/dist/{tool-GCPMPPUE.js → tool-TX622HEB.js} +2 -2
  75. package/dist/wiki-MYJBOJSM.js +2 -0
  76. package/dist/workspace-setup-HAV4CQV7.js +2 -0
  77. package/package.json +2 -1
  78. package/dist/board-dashboard-SAYBNLZW.js.map +0 -1
  79. package/dist/chunk-3TY6YJHO.js +0 -153
  80. package/dist/chunk-3TY6YJHO.js.map +0 -1
  81. package/dist/chunk-3Z5P2CWS.js +0 -3
  82. package/dist/chunk-3Z5P2CWS.js.map +0 -1
  83. package/dist/chunk-D7M4WQFU.js.map +0 -1
  84. package/dist/chunk-E5OV6RTG.js +0 -4
  85. package/dist/chunk-E5OV6RTG.js.map +0 -1
  86. package/dist/chunk-FHI5WPFO.js +0 -482
  87. package/dist/chunk-FHI5WPFO.js.map +0 -1
  88. package/dist/chunk-GI4RYX4G.js.map +0 -1
  89. package/dist/chunk-RJ7DASOU.js.map +0 -1
  90. package/dist/chunk-SFPMS55Z.js.map +0 -1
  91. package/dist/chunk-UDJAODAB.js +0 -100
  92. package/dist/chunk-UDJAODAB.js.map +0 -1
  93. package/dist/config-J6CNV23P.js +0 -2
  94. package/dist/config-mutator-RGC72OEU.js +0 -2
  95. package/dist/program-RMGWJHT6.js.map +0 -1
  96. package/dist/registry-NDFYNQG4.js +0 -2
  97. package/dist/request-planner-YOUVO3BR.js +0 -2
  98. package/dist/request-planner-YOUVO3BR.js.map +0 -1
  99. package/dist/schedule-tool-4L6QAILN.js +0 -2
  100. package/dist/scheduler-65SE3WJJ.js +0 -2
  101. package/dist/seat-EO3EVBH3.js +0 -2
  102. package/dist/select-73JC2YBN.js +0 -2
  103. package/dist/wiki-CQZE5Z5C.js +0 -2
  104. package/dist/workspace-setup-EA3G42NY.js +0 -2
  105. /package/dist/{App-4VWAQOVA.js.map → App-T4Q7SW6M.js.map} +0 -0
  106. /package/dist/{ChatApp-KOTGUDZH.js.map → ChatApp-BKUJUIDL.js.map} +0 -0
  107. /package/dist/{agent-CGRHC2IS.js.map → agent-YZVEXFE7.js.map} +0 -0
  108. /package/dist/{chunk-UVMCAZL7.js.map → chunk-4FU4BFCH.js.map} +0 -0
  109. /package/dist/{chunk-NIW75USA.js.map → chunk-4NFDDTGA.js.map} +0 -0
  110. /package/dist/{chunk-2VGYRETU.js.map → chunk-53OS4TDU.js.map} +0 -0
  111. /package/dist/{chunk-J32LSOBJ.js.map → chunk-7UNS34CN.js.map} +0 -0
  112. /package/dist/{chunk-BFOB6ERR.js.map → chunk-DXWDVKMX.js.map} +0 -0
  113. /package/dist/{chunk-BZKGQMU2.js.map → chunk-FBODSJU6.js.map} +0 -0
  114. /package/dist/{chunk-4UCDWEC4.js.map → chunk-HCTWKWS3.js.map} +0 -0
  115. /package/dist/{chunk-5O6PIO7F.js.map → chunk-LWYKS5JU.js.map} +0 -0
  116. /package/dist/{chunk-QQAZ6EJP.js.map → chunk-M2L6S3Z5.js.map} +0 -0
  117. /package/dist/{chunk-6F2E5NQA.js.map → chunk-QJP37VVA.js.map} +0 -0
  118. /package/dist/{chunk-QFP4YZII.js.map → chunk-RS2T53EC.js.map} +0 -0
  119. /package/dist/{chunk-BG6SOGKT.js.map → chunk-U52IS3AF.js.map} +0 -0
  120. /package/dist/{chunk-MJWJ3JVA.js.map → chunk-WSKQUKCF.js.map} +0 -0
  121. /package/dist/{chunk-S4NZUPIN.js.map → chunk-WXQQO2PD.js.map} +0 -0
  122. /package/dist/{chunk-CJR26JUI.js.map → chunk-XEOT2F4C.js.map} +0 -0
  123. /package/dist/{chunk-CJL5OPNQ.js.map → chunk-Z7L74GGT.js.map} +0 -0
  124. /package/dist/{client-VYMTUZKN.js.map → client-O7SPKO5X.js.map} +0 -0
  125. /package/dist/{config-J6CNV23P.js.map → config-ZHQYXFOJ.js.map} +0 -0
  126. /package/dist/{config-mutator-RGC72OEU.js.map → config-mutator-RS2NTDNO.js.map} +0 -0
  127. /package/dist/{doctor-GHUPUBET.js.map → doctor-XP7O45RC.js.map} +0 -0
  128. /package/dist/{grants-Y667GZAU.js.map → grants-BRAVOGBP.js.map} +0 -0
  129. /package/dist/{promote-KY3Y2PVU.js.map → promote-UGWANGI2.js.map} +0 -0
  130. /package/dist/{query-BYCRYDM3.js.map → query-ZET7Z5FT.js.map} +0 -0
  131. /package/dist/{registry-NDFYNQG4.js.map → registry-UXWOY52B.js.map} +0 -0
  132. /package/dist/{review-candidates-56ZPJNXW.js.map → review-candidates-GU4S5VQH.js.map} +0 -0
  133. /package/dist/{routing-WKS2CZYF.js.map → routing-GJ4VXKIC.js.map} +0 -0
  134. /package/dist/{schedule-tool-4L6QAILN.js.map → schedule-tool-HD4LWKVC.js.map} +0 -0
  135. /package/dist/{scheduler-65SE3WJJ.js.map → scheduler-3LEHZHWJ.js.map} +0 -0
  136. /package/dist/{seat-EO3EVBH3.js.map → seat-L6XJ2BFA.js.map} +0 -0
  137. /package/dist/{set-enabled-WQAO4BFF.js.map → set-enabled-U2HGNY2O.js.map} +0 -0
  138. /package/dist/{surface-usage-CH3SHO77.js.map → surface-usage-HCJTUS6Z.js.map} +0 -0
  139. /package/dist/{tool-QDU6MY6B.js.map → tool-2FMWZB4N.js.map} +0 -0
  140. /package/dist/{tool-GCPMPPUE.js.map → tool-TX622HEB.js.map} +0 -0
  141. /package/dist/{wiki-CQZE5Z5C.js.map → wiki-MYJBOJSM.js.map} +0 -0
  142. /package/dist/{workspace-setup-EA3G42NY.js.map → workspace-setup-HAV4CQV7.js.map} +0 -0
@@ -1,4 +0,0 @@
1
- import{c as A,e as _,f as D}from"./chunk-Y4CYQV7G.js";import{existsSync as E,mkdirSync as M,readdirSync as L,readFileSync as U,renameSync as q,writeFileSync as j}from"fs";import{resolve as P}from"path";function $(e,t){let n=e.list?` in the "${e.list}" list`:"",r=[`This card came from the operator's reminder "${e.title}"${n} (id ${e.id}).`];return r.push(t?`Do what the operator chose, then tick the reminder off: remindctl complete ${e.id}`:"Leave the reminder open; it will come back at a later check-in."),r}var R=["discard","keep","pause","approve"],f={title:120,ask:300,recommend:300,source:500,note:500,pendingPerAgent:25},F={defaultExpiryDays:3,maxExpiryDays:30};function x(e){return P(e,".agentx","approvals")}var z=/^[a-z0-9][a-z0-9-]{0,80}$/;function N(e){return z.test(e)}function O(e,t){if(!N(t))throw new Error(`invalid card id: ${t}`);return P(x(e),`${t}.json`)}function B(e,t){let n=new Date(e).toISOString().slice(0,10),r=t.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40);return`${n}-${r||"card"}-${Math.random().toString(36).slice(2,6)}`}function w(e,t){M(x(e),{recursive:!0});let n=O(e,t.id),r=`${n}.${process.pid}.tmp`;j(r,JSON.stringify(t,null,2)+`
2
- `),q(r,n)}function S(e,t){if(!N(t))return null;try{return JSON.parse(U(O(e,t),"utf-8"))}catch{return null}}function C(e,t){let n=x(e);if(!E(n))return[];let r=[];for(let i of L(n).filter(s=>s.endsWith(".json")&&!s.startsWith("_")).sort()){let s=S(e,i.slice(0,-5));s&&(!t||s.status===t)&&r.push(s)}return r}function l(e){return typeof e=="string"?e.replace(/\s+/g," ").trim():""}function J(e,t,n){let r=t+n.maxExpiryDays*864e5,i,s=l(e);if(!s)i=t+n.defaultExpiryDays*864e5;else{let a=/^(\d+)\s*([hd])$/i.exec(s);if(a)i=t+Number(a[1])*(a[2].toLowerCase()==="d"?864e5:36e5);else if(i=Date.parse(s),Number.isNaN(i))return{ok:!1,error:`expires must be an ISO date or time, or like "12h" / "3d" (got "${s}")`}}return i<=t?{ok:!1,error:"expires must be in the future"}:{ok:!0,at:new Date(Math.min(i,r)).toISOString()}}function V(e,t={}){let n=t.now??Date.now(),r=t.settings??F,i=l(e.title),s=l(e.ask),a=l(e.recommend),c=l(e.raised_by);if(!i)return{ok:!1,error:"title is required"};if(!s)return{ok:!1,error:"ask is required: the yes/no question"};if(!a)return{ok:!1,error:"recommend is required: your advice and why, in one line"};if(!c)return{ok:!1,error:"raised_by is required: the agent id"};if(i.length>f.title)return{ok:!1,error:`title is longer than ${f.title} characters`};if(s.length>f.ask)return{ok:!1,error:`ask is longer than ${f.ask} characters`};if(a.length>f.recommend)return{ok:!1,error:`recommend is longer than ${f.recommend} characters`};let y=l(e.if_silent).toLowerCase();if(!R.includes(y))return{ok:!1,error:`if_silent must be one of ${R.join(", ")}`};let d=J(e.expires,n,r);if(!d.ok)return d;let u=l(e.source);if(u.length>f.source)return{ok:!1,error:`source is longer than ${f.source} characters`};if(u&&/^[a-z][a-z0-9+.-]*:/i.test(u)&&!/^https?:\/\//i.test(u))return{ok:!1,error:"source must be an http(s) link or a plain reference"};let h=A(e);if(!h.ok)return h;let p,o=e.reply;return o&&typeof o=="object"&&l(o.channel)&&l(o.chatId)&&(p={channel:l(o.channel),chatId:l(o.chatId),...l(o.accountId)?{accountId:l(o.accountId)}:{}}),{ok:!0,card:{id:B(n,i),title:i,ask:s,recommend:a,if_silent:y,expires:d.at,...u?{source:u}:{},raised_by:c,created_at:new Date(n).toISOString(),...p?{reply:p}:{},...h.value,...t.origin?{origin:t.origin}:{},status:"pending"}}}function oe(e,t,n={}){let r=V(t,n);if(!r.ok)return r;let i=C(e,"pending").filter(s=>s.raised_by===r.card.raised_by).length;return i>=f.pendingPerAgent?{ok:!1,error:`${r.card.raised_by} already has ${i} cards waiting; wait for decisions before raising more`}:(w(e,r.card),r)}function ae(e,t,n,r={}){let i=S(e,t);if(!i)return{ok:!1,error:`no card "${t}"`};if(i.status!=="pending")return{ok:!1,error:`card "${t}" is already ${i.status}`};let s=_(i,n,r);if(!s.ok)return s;let a=l(r.note).slice(0,f.note),c={...i,status:"decided",verdict:n,decided_by:r.by??"operator",decided_at:new Date(r.now??Date.now()).toISOString(),...a?{note:a}:{},...s.choice?{choice:s.choice}:{},...s.text?{text:s.text}:{}};return w(e,c),{ok:!0,card:c}}function ce(e,t=Date.now()){let n=[];for(let r of C(e,"pending")){if(Date.parse(r.expires)>t)continue;let i={...r,status:"expired",outcome:r.if_silent,decided_by:"expiry",decided_at:new Date(t).toISOString()};w(e,i),n.push(i)}return n}function de(e){return C(e).filter(t=>t.status!=="pending"&&!t.agent_notified_at)}function ue(e,t,n=Date.now()){let r=S(e,t);!r||r.agent_notified_at||w(e,{...r,agent_notified_at:new Date(n).toISOString()})}function le(e){let t=e.status==="decided"?e.verdict==="yes"?"The operator said YES.":"The operator said NO.":`Nobody answered before it expired, so the default applied: ${e.outcome??e.if_silent}.`,n=[`[Approval result] Your decision card "${e.title}" (${e.id}) is closed.`,`Question: ${e.ask}`,t];return e.status==="decided"&&e.verdict==="yes"&&n.push(...D(e)),e.note&&n.push(`Operator note: ${e.note}`),e.source&&n.push(`Source: ${e.source}`),e.origin?.kind==="reminder"&&n.push(...$(e.origin,e.status==="decided"&&e.verdict==="yes")),e.reply&&n.push(`You raised it from ${e.reply.channel} chat ${e.reply.chatId}; reply there if the requester should know.`),n.push("Act on this result now. Do not raise the same card again."),n.join(`
3
- `)}function b(e,t=160){if(!e)return e;let n=e.replace(/\s+/g," ").trim();return n.length>t?n.slice(0,t-1)+"\u2026":n}function W(e){let t=new Map;for(let r of e.cronRuns??[]){let i=t.get(r.jobId);(!i||i.startedAt<r.startedAt)&&t.set(r.jobId,r)}let n={success:0,failed:0};for(let r of e.cronRuns??[])r.status==="success"?n.success++:n.failed++;return{id:e.id,name:e.name,url:e.url,reachable:e.reachable,error:b(e.error),uptimeSec:e.uptimeSec,inflight:e.inflight,restart:e.restart?.state&&e.restart.state!=="none"?e.restart:void 0,agents:e.agents.map(r=>({id:r.id,name:r.name,tier:r.tier,active:r.active,errors:r.errors,lastActive:r.lastActive,last:r.lastSummary?{text:b(r.lastSummary.text),at:r.lastSummary.at,ok:r.lastSummary.ok}:void 0})),crons:{today:n,items:(e.crons??[]).map(r=>{let i=t.get(r.id);return{id:r.id,enabled:r.enabled,schedule:r.schedule,agent:r.agent,nextRun:r.nextRun,consecutiveErrors:r.consecutiveErrors,last:i?{status:i.status,at:i.startedAt,text:b(i.status==="success"?i.responseSummary:i.errorSummary)}:void 0}})}}}function G(e){let t=[];for(let n of e)for(let r of n.agents)for(let i of r.runningTasks??[])t.push({node:n.url,nodeName:n.name,agentId:r.id,agentName:r.name,taskId:i.id,preview:b(i.messagePreview),channel:i.channel,startedAt:i.startedAt});return t.sort((n,r)=>n.startedAt<r.startedAt?1:-1)}var H=/^\/api\/app\/tasks\/(cancel|followup)$/;async function ge(e,t,n,r,i,s){let a=new URL(e.url||n,"http://localhost");if(r==="GET"&&(n==="/api/app/fleet"||n==="/api/app/activity")){let o=Y(a.searchParams.get("timezone")),g=await s.snapshot({date:Z(o),timezone:o});return n==="/api/app/activity"?m(t,200,{ts:g.ts,tasks:G(g.nodes)}):m(t,200,{ts:g.ts,nodes:g.nodes.map(W)})}if(r==="GET"&&n==="/api/app/approvals")return m(t,200,{nodes:await s.approvals()});if(r!=="POST")return!1;let c=n.match(H);if(!(c||["/api/app/nodes/restart","/api/app/nodes/reload","/api/app/crons/toggle","/api/app/approvals/decide"].includes(n)))return!1;let d;try{d=await I(e)}catch(o){return m(t,400,{error:o.message})}let u=k(d.node);if(!u)return m(t,400,{error:"node is required"});let h=`operator (phone: ${i})`,p;if(c){let o=k(d.taskId);if(!o)return m(t,400,{error:"taskId is required"});let g=`/api/tasks/${encodeURIComponent(o)}/${c[1]}`;if(c[1]==="cancel")p=await s.nodePost(u,g,{reason:`cancelled by ${h}`});else{let v=k(d.message);if(!v)return m(t,400,{error:"message is required"});p=await s.nodePost(u,g,{message:v,sender:h})}}else if(n==="/api/app/nodes/restart")p=await s.nodePost(u,d.cancel===!0?"/daemon/restart/cancel":"/daemon/restart",{});else if(n==="/api/app/nodes/reload")p=await s.nodePost(u,"/reload",{});else if(n==="/api/app/crons/toggle"){let o=k(d.cronId);if(!o||typeof d.enabled!="boolean")return m(t,400,{error:"cronId and enabled (true or false) are required"});p=await s.nodePost(u,`/crons/${encodeURIComponent(o)}/enabled`,{enabled:d.enabled})}else{let o=k(d.key),g=d.action;if(!o||g!=="yes"&&g!=="no"&&g!=="later")return m(t,400,{error:"key and action (yes, no or later) are required"});p=await s.decide(u,o,g,h)}return m(t,p.status,p.body)}function k(e){return typeof e=="string"?e.trim():""}function Y(e){if(!e)return"UTC";try{return new Intl.DateTimeFormat("en-US",{timeZone:e}),e}catch{return"UTC"}}function Z(e,t=new Date){return new Intl.DateTimeFormat("en-CA",{timeZone:e,year:"numeric",month:"2-digit",day:"2-digit"}).format(t)}async function I(e,t=16*1024){let n=0,r=[];for await(let a of e){if(n+=a.length,n>t)throw new Error("body too large");r.push(a)}let i=Buffer.concat(r).toString("utf-8").trim();if(!i)return{};let s=JSON.parse(i);if(!s||typeof s!="object"||Array.isArray(s))throw new Error("expected a JSON object");return s}function m(e,t,n){return e.writeHead(t,{"Content-Type":"application/json","Cache-Control":"no-store"}),e.end(JSON.stringify(n)),!0}var K={perClient:5,global:10,windowMs:10*60*1e3,lockoutMs:5*60*1e3},T=class{constructor(t=K,n=Date.now){this.opts=t;this.now=n}clients=new Map;all={failures:[],lockedUntil:0};retryAfter(t){let n=this.now(),r=Math.max(this.all.lockedUntil,this.clients.get(t)?.lockedUntil??0);return r>n?Math.ceil((r-n)/1e3):0}fail(t){let n=this.clients.get(t);n||this.clients.set(t,n={failures:[],lockedUntil:0}),this.bump(n,this.opts.perClient),this.bump(this.all,this.opts.global)}succeed(t){this.clients.delete(t)}bump(t,n){let r=this.now();t.failures=t.failures.filter(i=>r-i<this.opts.windowMs),t.failures.push(r),t.failures.length>=n&&(t.lockedUntil=r+this.opts.lockoutMs)}},Q="That code didn't work. Check it, or run agentx app pair for a new one.",X=400;async function ye(e,t){let n=Date.now(),r=t.log??(d=>console.log(d)),i=e.socket.remoteAddress||"unknown",s=t.limiter.retryAfter(i);if(s>0)return r(`[app] pair-code refused from ${i}: locked out for ${s}s more`),{status:429,body:{error:"too many attempts",retryAfter:s},retryAfter:s};let a=null;try{a=(await I(e,1024)).code}catch{}let c=t.codes.redeem(a),y=c?t.verify(c.token):null;return await ee(n,t.minMs??X),!c||!y?(t.limiter.fail(i),r(`[app] pair-code failed from ${i}`),{status:401,body:{error:Q}}):(t.limiter.succeed(i),r(`[app] pair-code paired ${c.tokenId} (${y}) from ${i}`),{status:200,body:{device:y},token:c.token})}async function ee(e,t){let n=e+t-Date.now();n>0&&await new Promise(r=>setTimeout(r,n))}export{R as a,f as b,x as c,N as d,S as e,C as f,oe as g,ae as h,ce as i,de as j,ue as k,le as l,ge as m,I as n,T as o,Q as p,ye as q};
4
- //# sourceMappingURL=chunk-E5OV6RTG.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/approvals/cards.ts","../src/approvals/origin.ts","../src/daemon/app-fleet.ts","../src/daemon/app-pair-code.ts"],"sourcesContent":["import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, writeFileSync } from \"fs\"\nimport { resolve } from \"path\"\nimport { answerLines, buildChoices, resolveAnswer, type CardChoices } from \"./choices\"\nimport { originLines, type CardOrigin } from \"./origin\"\n\n// --- Decision cards: what an agent asks the operator ---\n//\n// An agent that needs a yes/no from the operator writes a card instead of\n// asking in chat. A card says what it is, the question, what the agent\n// recommends, and what happens if nobody answers by `expires`. Nothing\n// waits forever: past `expires` the daemon applies `if_silent` (sweep.ts).\n//\n// Storage: one JSON file per card under `.agentx/approvals/`, written\n// atomically, like wiki proposals. The CLI, the dashboard and the daemon\n// are separate processes and all read the same files.\n//\n// Field names match the API (`if_silent`, `raised_by`), so a stored card is\n// exactly what `POST /approvals` accepted.\n\nexport type IfSilent = \"discard\" | \"keep\" | \"pause\" | \"approve\"\nexport const IF_SILENT_VALUES: readonly IfSilent[] = [\"discard\", \"keep\", \"pause\", \"approve\"]\n\nexport type CardStatus = \"pending\" | \"decided\" | \"expired\"\nexport type Verdict = \"yes\" | \"no\"\n\n/** Where the agent was asked, so the verdict can be taken back there. */\nexport interface ReplyTarget {\n channel: string\n chatId: string\n accountId?: string\n}\n\nexport interface DecisionCard extends CardChoices {\n id: string\n title: string\n ask: string\n recommend: string\n if_silent: IfSilent\n /** ISO time the default applies. Always set. */\n expires: string\n /** Link to the draft, PR or issue. */\n source?: string\n raised_by: string\n created_at: string\n reply?: ReplyTarget\n status: CardStatus\n /** The operator's answer (status \"decided\"). */\n verdict?: Verdict\n /** What applied on expiry (status \"expired\"). */\n outcome?: IfSilent\n decided_by?: string\n decided_at?: string\n note?: string\n /** The option the operator picked, when the card offered choices. */\n choice?: string\n /** The message the operator approved, after any edit. */\n text?: string\n /** Set once the raising agent has been told the result. */\n agent_notified_at?: string\n /** What the card is about, when the daemon raised it (origin.ts). */\n origin?: CardOrigin\n}\n\nexport const CARD_LIMITS = {\n title: 120,\n ask: 300,\n recommend: 300,\n source: 500,\n note: 500,\n /** Pending cards one agent may have open at a time. */\n pendingPerAgent: 25,\n} as const\n\nexport interface CardSettings {\n defaultExpiryDays: number\n maxExpiryDays: number\n}\n\nexport const DEFAULT_CARD_SETTINGS: CardSettings = { defaultExpiryDays: 3, maxExpiryDays: 30 }\n\nexport function approvalsDir(root: string): string {\n return resolve(root, \".agentx\", \"approvals\")\n}\n\nconst CARD_ID_RE = /^[a-z0-9][a-z0-9-]{0,80}$/\n\nexport function isValidCardId(id: string): boolean {\n return CARD_ID_RE.test(id)\n}\n\nfunction fileFor(root: string, id: string): string {\n if (!isValidCardId(id)) throw new Error(`invalid card id: ${id}`)\n return resolve(approvalsDir(root), `${id}.json`)\n}\n\nexport function newCardId(now: number, title: string): string {\n const day = new Date(now).toISOString().slice(0, 10)\n const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, \"-\").replace(/^-+|-+$/g, \"\").slice(0, 40)\n return `${day}-${slug || \"card\"}-${Math.random().toString(36).slice(2, 6)}`\n}\n\nexport function saveCard(root: string, card: DecisionCard): void {\n mkdirSync(approvalsDir(root), { recursive: true })\n const path = fileFor(root, card.id)\n const tmp = `${path}.${process.pid}.tmp`\n writeFileSync(tmp, JSON.stringify(card, null, 2) + \"\\n\")\n renameSync(tmp, path)\n}\n\nexport function readCard(root: string, id: string): DecisionCard | null {\n if (!isValidCardId(id)) return null\n try {\n return JSON.parse(readFileSync(fileFor(root, id), \"utf-8\")) as DecisionCard\n } catch {\n return null\n }\n}\n\n/** Cards, oldest first; only `status` ones when given. */\nexport function listCards(root: string, status?: CardStatus): DecisionCard[] {\n const dir = approvalsDir(root)\n if (!existsSync(dir)) return []\n const out: DecisionCard[] = []\n for (const f of readdirSync(dir).filter((f) => f.endsWith(\".json\") && !f.startsWith(\"_\")).sort()) {\n const c = readCard(root, f.slice(0, -5))\n if (c && (!status || c.status === status)) out.push(c)\n }\n return out\n}\n\nfunction oneLine(v: unknown): string {\n return typeof v === \"string\" ? v.replace(/\\s+/g, \" \").trim() : \"\"\n}\n\n/**\n * `expires` as given by an agent: an ISO date or time, or a relative\n * \"12h\" / \"3d\". Unset: the default. Always capped at maxExpiryDays, so a\n * card can't be parked for a year.\n */\nexport function resolveExpiry(input: unknown, now: number, settings: CardSettings): { ok: true; at: string } | { ok: false; error: string } {\n const max = now + settings.maxExpiryDays * 86_400_000\n let at: number\n const raw = oneLine(input)\n if (!raw) {\n at = now + settings.defaultExpiryDays * 86_400_000\n } else {\n const rel = /^(\\d+)\\s*([hd])$/i.exec(raw)\n if (rel) {\n at = now + Number(rel[1]) * (rel[2].toLowerCase() === \"d\" ? 86_400_000 : 3_600_000)\n } else {\n at = Date.parse(raw)\n if (Number.isNaN(at)) return { ok: false, error: `expires must be an ISO date or time, or like \"12h\" / \"3d\" (got \"${raw}\")` }\n }\n }\n if (at <= now) return { ok: false, error: \"expires must be in the future\" }\n return { ok: true, at: new Date(Math.min(at, max)).toISOString() }\n}\n\nexport interface CardInput {\n title?: unknown\n ask?: unknown\n recommend?: unknown\n if_silent?: unknown\n expires?: unknown\n source?: unknown\n raised_by?: unknown\n reply?: unknown\n choices?: unknown\n draft?: unknown\n say?: unknown\n context?: unknown\n}\n\n/** Validate what an agent sent and build a pending card. Never saves. */\nexport function buildCard(\n input: CardInput,\n opts: { now?: number; settings?: CardSettings; origin?: CardOrigin } = {},\n): { ok: true; card: DecisionCard } | { ok: false; error: string } {\n const now = opts.now ?? Date.now()\n const settings = opts.settings ?? DEFAULT_CARD_SETTINGS\n const title = oneLine(input.title)\n const ask = oneLine(input.ask)\n const recommend = oneLine(input.recommend)\n const raisedBy = oneLine(input.raised_by)\n if (!title) return { ok: false, error: \"title is required\" }\n if (!ask) return { ok: false, error: \"ask is required: the yes/no question\" }\n if (!recommend) return { ok: false, error: \"recommend is required: your advice and why, in one line\" }\n if (!raisedBy) return { ok: false, error: \"raised_by is required: the agent id\" }\n if (title.length > CARD_LIMITS.title) return { ok: false, error: `title is longer than ${CARD_LIMITS.title} characters` }\n if (ask.length > CARD_LIMITS.ask) return { ok: false, error: `ask is longer than ${CARD_LIMITS.ask} characters` }\n if (recommend.length > CARD_LIMITS.recommend) return { ok: false, error: `recommend is longer than ${CARD_LIMITS.recommend} characters` }\n const ifSilent = oneLine(input.if_silent).toLowerCase()\n if (!IF_SILENT_VALUES.includes(ifSilent as IfSilent)) {\n return { ok: false, error: `if_silent must be one of ${IF_SILENT_VALUES.join(\", \")}` }\n }\n const expiry = resolveExpiry(input.expires, now, settings)\n if (!expiry.ok) return expiry\n const source = oneLine(input.source)\n if (source.length > CARD_LIMITS.source) return { ok: false, error: `source is longer than ${CARD_LIMITS.source} characters` }\n if (source && /^[a-z][a-z0-9+.-]*:/i.test(source) && !/^https?:\\/\\//i.test(source)) {\n return { ok: false, error: \"source must be an http(s) link or a plain reference\" }\n }\n const extras = buildChoices(input)\n if (!extras.ok) return extras\n let reply: ReplyTarget | undefined\n const r = input.reply as Record<string, unknown> | undefined\n if (r && typeof r === \"object\" && oneLine(r.channel) && oneLine(r.chatId)) {\n reply = { channel: oneLine(r.channel), chatId: oneLine(r.chatId), ...(oneLine(r.accountId) ? { accountId: oneLine(r.accountId) } : {}) }\n }\n return {\n ok: true,\n card: {\n id: newCardId(now, title),\n title,\n ask,\n recommend,\n if_silent: ifSilent as IfSilent,\n expires: expiry.at,\n ...(source ? { source } : {}),\n raised_by: raisedBy,\n created_at: new Date(now).toISOString(),\n ...(reply ? { reply } : {}),\n ...extras.value,\n // Set by the daemon only (check-ins), never from what an agent sent.\n ...(opts.origin ? { origin: opts.origin } : {}),\n status: \"pending\",\n },\n }\n}\n\n/** Validate, enforce the per-agent cap, and save. */\nexport function createCard(\n root: string,\n input: CardInput,\n opts: { now?: number; settings?: CardSettings; origin?: CardOrigin } = {},\n): { ok: true; card: DecisionCard } | { ok: false; error: string } {\n const built = buildCard(input, opts)\n if (!built.ok) return built\n const open = listCards(root, \"pending\").filter((c) => c.raised_by === built.card.raised_by).length\n if (open >= CARD_LIMITS.pendingPerAgent) {\n return { ok: false, error: `${built.card.raised_by} already has ${open} cards waiting; wait for decisions before raising more` }\n }\n saveCard(root, built.card)\n return built\n}\n\n/** Record the operator's answer. Refuses anything no longer pending. */\nexport function decideCard(\n root: string,\n id: string,\n verdict: Verdict,\n opts: { by?: string; note?: string; now?: number; choice?: string | number; text?: string } = {},\n): { ok: true; card: DecisionCard } | { ok: false; error: string } {\n const card = readCard(root, id)\n if (!card) return { ok: false, error: `no card \"${id}\"` }\n if (card.status !== \"pending\") return { ok: false, error: `card \"${id}\" is already ${card.status}` }\n const answer = resolveAnswer(card, verdict, opts)\n if (!answer.ok) return answer\n const note = oneLine(opts.note).slice(0, CARD_LIMITS.note)\n const decided: DecisionCard = {\n ...card,\n status: \"decided\",\n verdict,\n decided_by: opts.by ?? \"operator\",\n decided_at: new Date(opts.now ?? Date.now()).toISOString(),\n ...(note ? { note } : {}),\n ...(answer.choice ? { choice: answer.choice } : {}),\n ...(answer.text ? { text: answer.text } : {}),\n }\n saveCard(root, decided)\n return { ok: true, card: decided }\n}\n\n/** Apply `if_silent` to every pending card past its expiry. */\nexport function expireCards(root: string, now: number = Date.now()): DecisionCard[] {\n const out: DecisionCard[] = []\n for (const card of listCards(root, \"pending\")) {\n if (Date.parse(card.expires) > now) continue\n const expired: DecisionCard = {\n ...card,\n status: \"expired\",\n outcome: card.if_silent,\n decided_by: \"expiry\",\n decided_at: new Date(now).toISOString(),\n }\n saveCard(root, expired)\n out.push(expired)\n }\n return out\n}\n\n/** Decided or expired cards whose agent hasn't been told yet. */\nexport function cardsAwaitingAgentNotice(root: string): DecisionCard[] {\n return listCards(root).filter((c) => c.status !== \"pending\" && !c.agent_notified_at)\n}\n\nexport function markAgentNotified(root: string, id: string, now: number = Date.now()): void {\n const card = readCard(root, id)\n if (!card || card.agent_notified_at) return\n saveCard(root, { ...card, agent_notified_at: new Date(now).toISOString() })\n}\n\n/** What the raising agent is told. Plain text, one short message. */\nexport function verdictMessage(card: DecisionCard): string {\n const result = card.status === \"decided\"\n ? (card.verdict === \"yes\" ? \"The operator said YES.\" : \"The operator said NO.\")\n : `Nobody answered before it expired, so the default applied: ${card.outcome ?? card.if_silent}.`\n const lines = [\n `[Approval result] Your decision card \"${card.title}\" (${card.id}) is closed.`,\n `Question: ${card.ask}`,\n result,\n ]\n if (card.status === \"decided\" && card.verdict === \"yes\") lines.push(...answerLines(card))\n if (card.note) lines.push(`Operator note: ${card.note}`)\n if (card.source) lines.push(`Source: ${card.source}`)\n if (card.origin?.kind === \"reminder\") lines.push(...originLines(card.origin, card.status === \"decided\" && card.verdict === \"yes\"))\n if (card.reply) lines.push(`You raised it from ${card.reply.channel} chat ${card.reply.chatId}; reply there if the requester should know.`)\n lines.push(\"Act on this result now. Do not raise the same card again.\")\n return lines.join(\"\\n\")\n}\n","// --- Where a card came from, when the daemon raised it ---\n//\n// Agents raise most cards themselves. A check-in (checkin.ts) raises one on\n// an agent's behalf for an open Apple Reminder, and records the reminder\n// here so the agent that gets the answer knows what to act on and what to\n// tick off. Only the daemon sets it: POST /approvals and the agent tool\n// never pass it through.\n\nexport interface ReminderOrigin {\n kind: \"reminder\"\n /** The reminder's full id, as remindctl prints it. */\n id: string\n title: string\n list?: string\n}\n\n/** An open request that needs attention, shown as a card on the Mac\n * (popup-runner.ts). Never stored: the request is the record. */\nexport interface RequestOrigin {\n kind: \"request\"\n id: string\n}\n\nexport type CardOrigin = ReminderOrigin | RequestOrigin\n\n/** Lines for the owning agent's result message. */\nexport function originLines(origin: ReminderOrigin, approved: boolean): string[] {\n const where = origin.list ? ` in the \"${origin.list}\" list` : \"\"\n const lines = [`This card came from the operator's reminder \"${origin.title}\"${where} (id ${origin.id}).`]\n lines.push(approved\n ? `Do what the operator chose, then tick the reminder off: remindctl complete ${origin.id}`\n : \"Leave the reminder open; it will come back at a later check-in.\")\n return lines\n}\n","import type { IncomingMessage, ServerResponse } from \"http\"\n\n// --- Phone app: fleet, activity and control (/api/app/fleet, …) ---\n//\n// Phase 3 of the mobile epic. Runs behind the device-token check in\n// app-routes.ts, so every handler here may assume a paired phone.\n//\n// Reads reuse the dashboard's live snapshot (every node the dashboard can\n// see, including peers found through /mesh) and trim it to what a phone\n// shows: short previews, never full prompts, replies or errors. Writes go\n// to a node daemon through the dashboard's allowlisted, token-carrying POST\n// (AppFleetDeps.nodePost), the same path the desktop task and restart\n// buttons use, so the phone gets no power the dashboard doesn't already have.\n\nexport const PREVIEW_CHARS = 160\n\nexport interface SnapshotNode {\n id: string\n name: string\n url: string\n reachable: boolean\n error?: string\n uptimeSec?: number\n inflight?: number\n restart?: { state: string; requestedAt?: string; deadline?: string }\n agents: Array<{\n id: string\n name: string\n tier?: string\n color?: string\n active: number\n errors: number\n lastActive?: string\n lastSummary?: { text: string; at: string; ok: boolean }\n runningTasks?: Array<{ id: string; messagePreview: string; channel: string; chatId?: string; sender?: string; startedAt: string }>\n }>\n crons?: Array<{ id: string; enabled: boolean; schedule: string; agent: string; kind?: string; nextRun?: string; consecutiveErrors: number }>\n cronRuns?: Array<{ jobId: string; startedAt: string; status: string; responseSummary?: string; errorSummary?: string; isRetry?: boolean }>\n}\n\nexport interface ApprovalItem {\n key: string\n title: string\n ask?: string\n recommend?: string\n /** What yes and no do, in words. */\n yes?: string\n no?: string\n raisedBy?: string\n expires?: string\n}\n\nexport interface NodeApprovals {\n node: string\n nodeName: string\n items: ApprovalItem[]\n error?: string\n}\n\nexport interface NodeReply { status: number; body: any }\n\nexport interface AppFleetDeps {\n snapshot(day?: { date: string; timezone: string }): Promise<{ ts: string; nodes: SnapshotNode[] }>\n /** POST to a node daemon on the dashboard's allowlist, with its token. */\n nodePost(nodeUrl: string, path: string, body: unknown): Promise<NodeReply>\n approvals(): Promise<NodeApprovals[]>\n /** `by` names the phone, e.g. \"operator (phone: My phone)\". */\n decide(nodeUrl: string, key: string, action: \"yes\" | \"no\" | \"later\", by: string): Promise<NodeReply>\n}\n\nexport function clip(text: string | undefined, max = PREVIEW_CHARS): string | undefined {\n if (!text) return text\n const flat = text.replace(/\\s+/g, \" \").trim()\n return flat.length > max ? flat.slice(0, max - 1) + \"…\" : flat\n}\n\n/** One node, trimmed for the Fleet tab. Cron outcomes come from the node's\n * persisted run files (cronRuns), never the scheduler's counters. */\nexport function summarizeNode(n: SnapshotNode) {\n const lastRun = new Map<string, NonNullable<SnapshotNode[\"cronRuns\"]>[number]>()\n for (const r of n.cronRuns ?? []) {\n const prev = lastRun.get(r.jobId)\n if (!prev || prev.startedAt < r.startedAt) lastRun.set(r.jobId, r)\n }\n const today = { success: 0, failed: 0 }\n for (const r of n.cronRuns ?? []) {\n if (r.status === \"success\") today.success++\n else today.failed++\n }\n return {\n id: n.id,\n name: n.name,\n url: n.url,\n reachable: n.reachable,\n error: clip(n.error),\n uptimeSec: n.uptimeSec,\n inflight: n.inflight,\n restart: n.restart?.state && n.restart.state !== \"none\" ? n.restart : undefined,\n agents: n.agents.map((a) => ({\n id: a.id,\n name: a.name,\n tier: a.tier,\n active: a.active,\n errors: a.errors,\n lastActive: a.lastActive,\n last: a.lastSummary ? { text: clip(a.lastSummary.text), at: a.lastSummary.at, ok: a.lastSummary.ok } : undefined,\n })),\n crons: {\n today,\n items: (n.crons ?? []).map((c) => {\n const r = lastRun.get(c.id)\n return {\n id: c.id,\n enabled: c.enabled,\n schedule: c.schedule,\n agent: c.agent,\n nextRun: c.nextRun,\n consecutiveErrors: c.consecutiveErrors,\n last: r ? { status: r.status, at: r.startedAt, text: clip(r.status === \"success\" ? r.responseSummary : r.errorSummary) } : undefined,\n }\n }),\n },\n }\n}\n\n/** Every running task across the fleet, newest first. */\nexport function summarizeActivity(nodes: SnapshotNode[]) {\n const tasks = []\n for (const n of nodes) {\n for (const a of n.agents) {\n for (const t of a.runningTasks ?? []) {\n tasks.push({\n node: n.url,\n nodeName: n.name,\n agentId: a.id,\n agentName: a.name,\n taskId: t.id,\n preview: clip(t.messagePreview),\n channel: t.channel,\n startedAt: t.startedAt,\n })\n }\n }\n }\n return tasks.sort((x, y) => (x.startedAt < y.startedAt ? 1 : -1))\n}\n\nconst TASK_ACTION = /^\\/api\\/app\\/tasks\\/(cancel|followup)$/\n\n/** Handles /api/app/{fleet,activity,tasks,nodes,crons,approvals}. Returns\n * false for any other path so the caller can fall through. */\nexport async function handleAppFleet(\n req: IncomingMessage,\n res: ServerResponse,\n path: string,\n method: string,\n device: string,\n deps: AppFleetDeps,\n): Promise<boolean> {\n const url = new URL(req.url || path, \"http://localhost\")\n\n if (method === \"GET\" && (path === \"/api/app/fleet\" || path === \"/api/app/activity\")) {\n const timezone = validTimezone(url.searchParams.get(\"timezone\"))\n const snap = await deps.snapshot({ date: dateIn(timezone), timezone })\n if (path === \"/api/app/activity\") return json(res, 200, { ts: snap.ts, tasks: summarizeActivity(snap.nodes) })\n return json(res, 200, { ts: snap.ts, nodes: snap.nodes.map(summarizeNode) })\n }\n\n if (method === \"GET\" && path === \"/api/app/approvals\") {\n return json(res, 200, { nodes: await deps.approvals() })\n }\n\n if (method !== \"POST\") return false\n const taskAction = path.match(TASK_ACTION)\n const known = taskAction || [\n \"/api/app/nodes/restart\", \"/api/app/nodes/reload\", \"/api/app/crons/toggle\", \"/api/app/approvals/decide\",\n ].includes(path)\n if (!known) return false\n\n let body: Record<string, unknown>\n try { body = await readJson(req) } catch (e: any) { return json(res, 400, { error: e.message }) }\n const node = str(body.node)\n if (!node) return json(res, 400, { error: \"node is required\" })\n const by = `operator (phone: ${device})`\n\n let reply: NodeReply\n if (taskAction) {\n const taskId = str(body.taskId)\n if (!taskId) return json(res, 400, { error: \"taskId is required\" })\n const upstream = `/api/tasks/${encodeURIComponent(taskId)}/${taskAction[1]}`\n if (taskAction[1] === \"cancel\") {\n reply = await deps.nodePost(node, upstream, { reason: `cancelled by ${by}` })\n } else {\n const message = str(body.message)\n if (!message) return json(res, 400, { error: \"message is required\" })\n reply = await deps.nodePost(node, upstream, { message, sender: by })\n }\n } else if (path === \"/api/app/nodes/restart\") {\n reply = await deps.nodePost(node, body.cancel === true ? \"/daemon/restart/cancel\" : \"/daemon/restart\", {})\n } else if (path === \"/api/app/nodes/reload\") {\n reply = await deps.nodePost(node, \"/reload\", {})\n } else if (path === \"/api/app/crons/toggle\") {\n const cronId = str(body.cronId)\n if (!cronId || typeof body.enabled !== \"boolean\") return json(res, 400, { error: \"cronId and enabled (true or false) are required\" })\n reply = await deps.nodePost(node, `/crons/${encodeURIComponent(cronId)}/enabled`, { enabled: body.enabled })\n } else {\n const key = str(body.key)\n const action = body.action\n if (!key || (action !== \"yes\" && action !== \"no\" && action !== \"later\")) {\n return json(res, 400, { error: \"key and action (yes, no or later) are required\" })\n }\n reply = await deps.decide(node, key, action, by)\n }\n return json(res, reply.status, reply.body)\n}\n\nfunction str(v: unknown): string {\n return typeof v === \"string\" ? v.trim() : \"\"\n}\n\nfunction validTimezone(tz: string | null): string {\n if (!tz) return \"UTC\"\n try { new Intl.DateTimeFormat(\"en-US\", { timeZone: tz }); return tz } catch { return \"UTC\" }\n}\n\n/** YYYY-MM-DD for \"today\" in the phone's time zone. */\nexport function dateIn(timezone: string, now = new Date()): string {\n return new Intl.DateTimeFormat(\"en-CA\", { timeZone: timezone, year: \"numeric\", month: \"2-digit\", day: \"2-digit\" }).format(now)\n}\n\nexport async function readJson(req: IncomingMessage, limit = 16 * 1024): Promise<Record<string, unknown>> {\n let size = 0\n const chunks: Buffer[] = []\n for await (const chunk of req) {\n size += (chunk as Buffer).length\n if (size > limit) throw new Error(\"body too large\")\n chunks.push(chunk as Buffer)\n }\n const raw = Buffer.concat(chunks).toString(\"utf-8\").trim()\n if (!raw) return {}\n const parsed = JSON.parse(raw)\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) throw new Error(\"expected a JSON object\")\n return parsed\n}\n\nfunction json(res: ServerResponse, status: number, body: unknown): true {\n res.writeHead(status, { \"Content-Type\": \"application/json\", \"Cache-Control\": \"no-store\" })\n res.end(JSON.stringify(body))\n return true\n}\n","import type { IncomingMessage } from \"http\"\nimport { PairCodeStore } from \"./pair-codes\"\nimport { readJson } from \"./app-fleet\"\n\n// --- POST /api/app/pair-code: trade a one-time code for the session ---\n//\n// Public by necessity (the phone isn't paired yet), so it is guarded three\n// ways: an 8-symbol code (31^8 ≈ 850 billion), a per-client and a GLOBAL\n// failure limit with lockout, and one answer for every failure. Behind\n// `tailscale serve` every request arrives from 127.0.0.1, so the per-client\n// limit alone would see one client; the global limit is what really bounds\n// guessing: at most 10 wrong codes in any 10 minutes, whoever sends them.\n//\n// Every attempt is padded to the same minimum duration, so wrong, expired\n// and used codes can't be told apart by timing either.\n\nexport interface PairLimitOptions {\n perClient: number // failures per window before that client is locked out\n global: number // failures per window, all clients together\n windowMs: number\n lockoutMs: number\n}\n\nexport const DEFAULT_PAIR_LIMITS: PairLimitOptions = {\n perClient: 5,\n global: 10,\n windowMs: 10 * 60 * 1000,\n lockoutMs: 5 * 60 * 1000,\n}\n\ninterface Bucket { failures: number[]; lockedUntil: number }\n\n/** In-memory failure counter. A dashboard restart clears it; codes still\n * expire after 10 minutes, so that grants an attacker nothing lasting. */\nexport class PairAttemptLimiter {\n private clients = new Map<string, Bucket>()\n private all: Bucket = { failures: [], lockedUntil: 0 }\n\n constructor(private opts: PairLimitOptions = DEFAULT_PAIR_LIMITS, private now: () => number = Date.now) {}\n\n /** Seconds until this client may try again, or 0 when it may try now. */\n retryAfter(client: string): number {\n const now = this.now()\n const until = Math.max(this.all.lockedUntil, this.clients.get(client)?.lockedUntil ?? 0)\n return until > now ? Math.ceil((until - now) / 1000) : 0\n }\n\n fail(client: string): void {\n let bucket = this.clients.get(client)\n if (!bucket) this.clients.set(client, (bucket = { failures: [], lockedUntil: 0 }))\n this.bump(bucket, this.opts.perClient)\n this.bump(this.all, this.opts.global)\n }\n\n succeed(client: string): void {\n this.clients.delete(client)\n }\n\n private bump(bucket: Bucket, limit: number): void {\n const now = this.now()\n bucket.failures = bucket.failures.filter((t) => now - t < this.opts.windowMs)\n bucket.failures.push(now)\n if (bucket.failures.length >= limit) bucket.lockedUntil = now + this.opts.lockoutMs\n }\n}\n\nexport interface PairCodeDeps {\n codes: PairCodeStore\n limiter: PairAttemptLimiter\n /** Checks the redeemed token is still an active `app` token; returns its device name. */\n verify: (token: string) => string | null\n log?: (line: string) => void\n minMs?: number\n}\n\nexport type PairCodeResult =\n | { status: 200; body: { device: string }; token: string }\n | { status: 401; body: { error: string } }\n | { status: 429; body: { error: string; retryAfter: number }; retryAfter: number }\n\nexport const PAIR_CODE_FAILED = \"That code didn't work. Check it, or run agentx app pair for a new one.\"\nconst MIN_MS = 400\n\nexport async function redeemPairCode(req: IncomingMessage, deps: PairCodeDeps): Promise<PairCodeResult> {\n const started = Date.now()\n const log = deps.log ?? ((line: string) => console.log(line))\n const client = req.socket.remoteAddress || \"unknown\"\n\n const wait = deps.limiter.retryAfter(client)\n if (wait > 0) {\n log(`[app] pair-code refused from ${client}: locked out for ${wait}s more`)\n return { status: 429, body: { error: \"too many attempts\", retryAfter: wait }, retryAfter: wait }\n }\n\n let code: unknown = null\n try { code = (await readJson(req, 1024)).code } catch { /* counts as a wrong code */ }\n const redeemed = deps.codes.redeem(code)\n const device = redeemed ? deps.verify(redeemed.token) : null\n await padTo(started, deps.minMs ?? MIN_MS)\n\n if (!redeemed || !device) {\n deps.limiter.fail(client)\n log(`[app] pair-code failed from ${client}`)\n return { status: 401, body: { error: PAIR_CODE_FAILED } }\n }\n deps.limiter.succeed(client)\n log(`[app] pair-code paired ${redeemed.tokenId} (${device}) from ${client}`)\n return { status: 200, body: { device }, token: redeemed.token }\n}\n\nasync function padTo(started: number, minMs: number): Promise<void> {\n const left = started + minMs - Date.now()\n if (left > 0) await new Promise((r) => setTimeout(r, left))\n}\n"],"mappings":"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","names":["existsSync","mkdirSync","readdirSync","readFileSync","renameSync","writeFileSync","resolve","originLines","origin","approved","where","lines","IF_SILENT_VALUES","CARD_LIMITS","DEFAULT_CARD_SETTINGS","approvalsDir","root","resolve","CARD_ID_RE","isValidCardId","id","fileFor","newCardId","now","title","day","slug","saveCard","card","mkdirSync","path","tmp","writeFileSync","renameSync","readCard","readFileSync","listCards","status","dir","existsSync","out","f","readdirSync","c","oneLine","v","resolveExpiry","input","settings","max","at","raw","rel","buildCard","opts","ask","recommend","raisedBy","ifSilent","expiry","source","extras","buildChoices","reply","r","createCard","built","open","decideCard","verdict","answer","resolveAnswer","note","decided","expireCards","expired","cardsAwaitingAgentNotice","markAgentNotified","verdictMessage","result","lines","answerLines","originLines","clip","text","max","flat","summarizeNode","n","lastRun","prev","today","a","c","r","summarizeActivity","nodes","tasks","t","x","y","TASK_ACTION","handleAppFleet","req","res","path","method","device","deps","url","timezone","validTimezone","snap","dateIn","json","taskAction","body","readJson","e","node","str","by","reply","taskId","upstream","message","cronId","key","action","v","tz","now","limit","size","chunks","chunk","raw","parsed","status","DEFAULT_PAIR_LIMITS","PairAttemptLimiter","opts","now","client","until","bucket","limit","t","PAIR_CODE_FAILED","MIN_MS","redeemPairCode","req","deps","started","log","line","wait","code","readJson","redeemed","device","padTo","minMs","left"]}