agentix-cli 0.154.1 → 0.155.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.
Files changed (138) hide show
  1. package/dist/{App-6T6NVB7O.js → App-P4MLXD46.js} +2 -2
  2. package/dist/{ChatApp-QZBJ4NHC.js → ChatApp-DILDQOJL.js} +2 -2
  3. package/dist/{absorb-eval-EVX7LDXR.js → absorb-eval-DBC7EKU5.js} +2 -2
  4. package/dist/{agent-F2NT6B25.js → agent-OZ3KPI6Y.js} +2 -2
  5. package/dist/{board-dashboard-642BO7GV.js → board-dashboard-MWUCBHDN.js} +239 -233
  6. package/dist/board-dashboard-MWUCBHDN.js.map +1 -0
  7. package/dist/{chunk-KA2NV7H4.js → chunk-3ZAAUY2N.js} +2 -2
  8. package/dist/{chunk-743RAKLP.js → chunk-4RHM4XTI.js} +2 -2
  9. package/dist/{chunk-U4YTTWR6.js → chunk-6CXJAZZD.js} +2 -2
  10. package/dist/{chunk-AKCEMKQM.js → chunk-ACB6GRSP.js} +2 -2
  11. package/dist/{chunk-W2RB5XG7.js → chunk-AHZBWCXI.js} +2 -2
  12. package/dist/chunk-AVAR5KNN.js +36 -0
  13. package/dist/chunk-AVAR5KNN.js.map +1 -0
  14. package/dist/{chunk-E76HREC7.js → chunk-CTXHMJLZ.js} +3 -3
  15. package/dist/{chunk-IA4J3YLB.js → chunk-D65HXPIB.js} +2 -2
  16. package/dist/{chunk-S3EX64LR.js → chunk-EE3ZYIIB.js} +2 -2
  17. package/dist/{chunk-VRF4YW46.js → chunk-FTIJQLBX.js} +2 -2
  18. package/dist/{chunk-YL26FRKJ.js → chunk-GIFUJ3LL.js} +2 -2
  19. package/dist/chunk-H7DLFJBV.js +575 -0
  20. package/dist/chunk-H7DLFJBV.js.map +1 -0
  21. package/dist/{chunk-4CWACHEH.js → chunk-HGF73KFH.js} +2 -2
  22. package/dist/{chunk-JAML6222.js → chunk-HOVJVXEP.js} +2 -2
  23. package/dist/{chunk-GRVTLUQ6.js → chunk-IW4AZVJR.js} +2 -2
  24. package/dist/{chunk-IWWEUXZQ.js → chunk-J3IP3UZX.js} +2 -2
  25. package/dist/{chunk-OKVQYPPF.js → chunk-J432IZXG.js} +333 -336
  26. package/dist/chunk-J432IZXG.js.map +1 -0
  27. package/dist/{chunk-37I654UJ.js → chunk-KYRF2FPT.js} +2 -2
  28. package/dist/{chunk-SD3CC4EX.js → chunk-NJEJU5XN.js} +2 -2
  29. package/dist/{chunk-CUAS4ITR.js → chunk-PI6CKUUH.js} +2 -2
  30. package/dist/{chunk-HUN6COOF.js → chunk-Q7LUTCNO.js} +2 -2
  31. package/dist/chunk-QHRQ3PUX.js +3 -0
  32. package/dist/chunk-QHRQ3PUX.js.map +1 -0
  33. package/dist/chunk-SQTL2CLY.js +76 -0
  34. package/dist/chunk-SQTL2CLY.js.map +1 -0
  35. package/dist/{chunk-EGTANAFI.js → chunk-SSFQWEPJ.js} +2 -2
  36. package/dist/{chunk-MLC5INGD.js → chunk-UK6MICKK.js} +5 -5
  37. package/dist/{chunk-MLC5INGD.js.map → chunk-UK6MICKK.js.map} +1 -1
  38. package/dist/{chunk-YJNUVAM4.js → chunk-WHMX2D2Q.js} +2 -2
  39. package/dist/{chunk-ABCQ6QMJ.js → chunk-YDAFHXDE.js} +2 -2
  40. package/dist/{chunk-GFRAHUAQ.js → chunk-YLAJOO5X.js} +2 -2
  41. package/dist/{chunk-B75BLZ2M.js → chunk-YXBQSLFV.js} +2 -2
  42. package/dist/cli.js +1 -1
  43. package/dist/{client-OCLSGJYQ.js → client-7JINGNNU.js} +2 -2
  44. package/dist/{config-FZTWI3VG.js → config-S4ZZHOUZ.js} +2 -2
  45. package/dist/{config-mutate-X5IKMROK.js → config-mutate-U3KKAXVD.js} +2 -2
  46. package/dist/config-mutator-KKRVNVVV.js +2 -0
  47. package/dist/{contributions-KHGGVSR6.js → contributions-6F3MYSRR.js} +2 -2
  48. package/dist/{doctor-KNCRQ3YO.js → doctor-UDZI2OMU.js} +2 -2
  49. package/dist/{follow-up-tool-TGKEPA5C.js → follow-up-tool-MP6FOIQE.js} +2 -2
  50. package/dist/{hub-HQUAH5H6.js → hub-TSUVK7PH.js} +2 -2
  51. package/dist/index.d.ts +362 -61
  52. package/dist/index.js +1 -1
  53. package/dist/{program-HFN24YLA.js → program-CMWF3BQS.js} +234 -232
  54. package/dist/program-CMWF3BQS.js.map +1 -0
  55. package/dist/{promote-VD4OVQGL.js → promote-YVOYLTXI.js} +2 -2
  56. package/dist/{push-bridge-P7HH25VN.js → push-bridge-CFPGPPO7.js} +2 -2
  57. package/dist/{query-5CPK6G2H.js → query-ZGXXMJ4M.js} +2 -2
  58. package/dist/{query-settings-35GDM3HU.js → query-settings-TNOGVKBX.js} +2 -2
  59. package/dist/{registry-JCHKHCRE.js → registry-R2X4YGUK.js} +2 -2
  60. package/dist/{request-planner-ZIG4YVXC.js → request-planner-QBNGJMF5.js} +2 -2
  61. package/dist/{review-candidates-LPZWOR2K.js → review-candidates-WCDKCFXR.js} +2 -2
  62. package/dist/{routing-BLOYDLU4.js → routing-RRRO2GDA.js} +2 -2
  63. package/dist/schedule-tool-2XZ3I5IL.js +2 -0
  64. package/dist/{scheduler-BEJRDQR3.js → scheduler-7O73TDCG.js} +2 -2
  65. package/dist/{seat-NU3UJC6C.js → seat-CCDGNARC.js} +2 -2
  66. package/dist/{select-IWAFX4XJ.js → select-VPZ4QDHV.js} +2 -2
  67. package/dist/{set-enabled-JF75TSFU.js → set-enabled-VYSPZXPC.js} +2 -2
  68. package/dist/{tool-PQBQJWM5.js → tool-K3PZOFC2.js} +2 -2
  69. package/dist/{tool-WTOOPTGC.js → tool-VPDXU3IZ.js} +2 -2
  70. package/dist/{wiki-DXG655NP.js → wiki-QA7BDTOR.js} +2 -2
  71. package/dist/wiki-enrich-ZIZ5QHQ3.js +2 -0
  72. package/package.json +2 -1
  73. package/dist/board-dashboard-642BO7GV.js.map +0 -1
  74. package/dist/chunk-HSCZSNOX.js +0 -533
  75. package/dist/chunk-HSCZSNOX.js.map +0 -1
  76. package/dist/chunk-OKVQYPPF.js.map +0 -1
  77. package/dist/chunk-VKEH4IBA.js +0 -76
  78. package/dist/chunk-VKEH4IBA.js.map +0 -1
  79. package/dist/chunk-XOUK5ZIQ.js +0 -36
  80. package/dist/chunk-XOUK5ZIQ.js.map +0 -1
  81. package/dist/chunk-YMX3QCV7.js +0 -3
  82. package/dist/chunk-YMX3QCV7.js.map +0 -1
  83. package/dist/config-mutator-C5BMIBZO.js +0 -2
  84. package/dist/program-HFN24YLA.js.map +0 -1
  85. package/dist/schedule-tool-7KFO3NPD.js +0 -2
  86. package/dist/wiki-enrich-5VQCT6BT.js +0 -2
  87. /package/dist/{App-6T6NVB7O.js.map → App-P4MLXD46.js.map} +0 -0
  88. /package/dist/{ChatApp-QZBJ4NHC.js.map → ChatApp-DILDQOJL.js.map} +0 -0
  89. /package/dist/{absorb-eval-EVX7LDXR.js.map → absorb-eval-DBC7EKU5.js.map} +0 -0
  90. /package/dist/{agent-F2NT6B25.js.map → agent-OZ3KPI6Y.js.map} +0 -0
  91. /package/dist/{chunk-KA2NV7H4.js.map → chunk-3ZAAUY2N.js.map} +0 -0
  92. /package/dist/{chunk-743RAKLP.js.map → chunk-4RHM4XTI.js.map} +0 -0
  93. /package/dist/{chunk-U4YTTWR6.js.map → chunk-6CXJAZZD.js.map} +0 -0
  94. /package/dist/{chunk-AKCEMKQM.js.map → chunk-ACB6GRSP.js.map} +0 -0
  95. /package/dist/{chunk-W2RB5XG7.js.map → chunk-AHZBWCXI.js.map} +0 -0
  96. /package/dist/{chunk-E76HREC7.js.map → chunk-CTXHMJLZ.js.map} +0 -0
  97. /package/dist/{chunk-IA4J3YLB.js.map → chunk-D65HXPIB.js.map} +0 -0
  98. /package/dist/{chunk-S3EX64LR.js.map → chunk-EE3ZYIIB.js.map} +0 -0
  99. /package/dist/{chunk-VRF4YW46.js.map → chunk-FTIJQLBX.js.map} +0 -0
  100. /package/dist/{chunk-YL26FRKJ.js.map → chunk-GIFUJ3LL.js.map} +0 -0
  101. /package/dist/{chunk-4CWACHEH.js.map → chunk-HGF73KFH.js.map} +0 -0
  102. /package/dist/{chunk-JAML6222.js.map → chunk-HOVJVXEP.js.map} +0 -0
  103. /package/dist/{chunk-GRVTLUQ6.js.map → chunk-IW4AZVJR.js.map} +0 -0
  104. /package/dist/{chunk-IWWEUXZQ.js.map → chunk-J3IP3UZX.js.map} +0 -0
  105. /package/dist/{chunk-37I654UJ.js.map → chunk-KYRF2FPT.js.map} +0 -0
  106. /package/dist/{chunk-SD3CC4EX.js.map → chunk-NJEJU5XN.js.map} +0 -0
  107. /package/dist/{chunk-CUAS4ITR.js.map → chunk-PI6CKUUH.js.map} +0 -0
  108. /package/dist/{chunk-HUN6COOF.js.map → chunk-Q7LUTCNO.js.map} +0 -0
  109. /package/dist/{chunk-EGTANAFI.js.map → chunk-SSFQWEPJ.js.map} +0 -0
  110. /package/dist/{chunk-YJNUVAM4.js.map → chunk-WHMX2D2Q.js.map} +0 -0
  111. /package/dist/{chunk-ABCQ6QMJ.js.map → chunk-YDAFHXDE.js.map} +0 -0
  112. /package/dist/{chunk-GFRAHUAQ.js.map → chunk-YLAJOO5X.js.map} +0 -0
  113. /package/dist/{chunk-B75BLZ2M.js.map → chunk-YXBQSLFV.js.map} +0 -0
  114. /package/dist/{client-OCLSGJYQ.js.map → client-7JINGNNU.js.map} +0 -0
  115. /package/dist/{config-FZTWI3VG.js.map → config-S4ZZHOUZ.js.map} +0 -0
  116. /package/dist/{config-mutate-X5IKMROK.js.map → config-mutate-U3KKAXVD.js.map} +0 -0
  117. /package/dist/{config-mutator-C5BMIBZO.js.map → config-mutator-KKRVNVVV.js.map} +0 -0
  118. /package/dist/{contributions-KHGGVSR6.js.map → contributions-6F3MYSRR.js.map} +0 -0
  119. /package/dist/{doctor-KNCRQ3YO.js.map → doctor-UDZI2OMU.js.map} +0 -0
  120. /package/dist/{follow-up-tool-TGKEPA5C.js.map → follow-up-tool-MP6FOIQE.js.map} +0 -0
  121. /package/dist/{hub-HQUAH5H6.js.map → hub-TSUVK7PH.js.map} +0 -0
  122. /package/dist/{promote-VD4OVQGL.js.map → promote-YVOYLTXI.js.map} +0 -0
  123. /package/dist/{push-bridge-P7HH25VN.js.map → push-bridge-CFPGPPO7.js.map} +0 -0
  124. /package/dist/{query-5CPK6G2H.js.map → query-ZGXXMJ4M.js.map} +0 -0
  125. /package/dist/{query-settings-35GDM3HU.js.map → query-settings-TNOGVKBX.js.map} +0 -0
  126. /package/dist/{registry-JCHKHCRE.js.map → registry-R2X4YGUK.js.map} +0 -0
  127. /package/dist/{request-planner-ZIG4YVXC.js.map → request-planner-QBNGJMF5.js.map} +0 -0
  128. /package/dist/{review-candidates-LPZWOR2K.js.map → review-candidates-WCDKCFXR.js.map} +0 -0
  129. /package/dist/{routing-BLOYDLU4.js.map → routing-RRRO2GDA.js.map} +0 -0
  130. /package/dist/{schedule-tool-7KFO3NPD.js.map → schedule-tool-2XZ3I5IL.js.map} +0 -0
  131. /package/dist/{scheduler-BEJRDQR3.js.map → scheduler-7O73TDCG.js.map} +0 -0
  132. /package/dist/{seat-NU3UJC6C.js.map → seat-CCDGNARC.js.map} +0 -0
  133. /package/dist/{select-IWAFX4XJ.js.map → select-VPZ4QDHV.js.map} +0 -0
  134. /package/dist/{set-enabled-JF75TSFU.js.map → set-enabled-VYSPZXPC.js.map} +0 -0
  135. /package/dist/{tool-PQBQJWM5.js.map → tool-K3PZOFC2.js.map} +0 -0
  136. /package/dist/{tool-WTOOPTGC.js.map → tool-VPDXU3IZ.js.map} +0 -0
  137. /package/dist/{wiki-DXG655NP.js.map → wiki-QA7BDTOR.js.map} +0 -0
  138. /package/dist/{wiki-enrich-5VQCT6BT.js.map → wiki-enrich-ZIZ5QHQ3.js.map} +0 -0
@@ -1,36 +0,0 @@
1
- import{a as N}from"./chunk-IMLAPB7G.js";import{k as y}from"./chunk-LQS27HSV.js";import{b as V}from"./chunk-KP7KDLZ2.js";import{I as B,J,K as X,L as K,P as k,T as Pe}from"./chunk-VKEH4IBA.js";import{existsSync as z,readFileSync as $e}from"node:fs";import{dirname as b,join as h}from"node:path";import{fileURLToPath as Oe}from"node:url";import{homedir as Ee}from"node:os";var M="tn.acme.agentx.voice",dt="AgentX Desktop.app",Y="AgentX Helper.app";function mt(e,t,n){return e!=="darwin"||t!=="arm64"||Number(n.split(".")[0])<23?"AgentX Desktop currently requires Apple Silicon and macOS 14 or newer.":null}function ft(e,t){if(t&&!Object.hasOwn(e,t))throw new Error(`Unknown agent: ${t}. Run agentx agent list.`);let n=t||Object.keys(e)[0];if(!n)throw new Error("No agents configured. Run agentx setup first.");return n}function I(e){return`'${e.replace(/'/g,"'\\''")}'`}var Z="/usr/bin:/bin:/usr/sbin:/sbin";function gt(e,t){return t||[...new Set([...e?[b(e)]:[],...Z.split(":")])].join(":")}function A(e){let t={amp:"&",lt:"<",gt:">",quot:'"',apos:"'"};return e.replace(/&(amp|lt|gt|quot|apos);/g,(n,r)=>t[r])}function yt(e){let t=e.match(/<key>PATH<\/key>\s*<string>([^<]*)<\/string>/);return t?A(t[1]):Z}var ht=e=>/agentx\.voice.*\.plist$/.test(e);function wt(e,t){let n=null,r=[];for(let{file:o,plist:i}of[...t].sort((s,u)=>s.file<u.file?-1:1)){let s=i.match(/<key>Label<\/key>\s*<string>([^<]*)<\/string>/)?.[1],u=i.match(/<key>ProgramArguments<\/key>\s*<array>\s*<string>([^<]*)<\/string>/)?.[1];!s||!u||!A(u).endsWith("/Contents/MacOS/AgentXVoice")||(n?r.push({file:o,label:A(s)}):n={file:o,label:A(s)})}return{keep:n??{file:h(e,`${M}.plist`),label:M},remove:r}}function Q(e,t,n){for(let r of t.split(":"))if(r&&n(h(r,e)))return h(r,e);return null}function St(e){let t=r=>r.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&apos;"),n={PATH:e.path,...e.agent?{AGENTX_VOICE_AGENT:e.agent}:{},AGENTX_DAEMON_URL:e.url,AGENTX_MAC_HELPER:e.helper,AGENTX_PASTE_COMMAND:`cd ${I(e.cwd)} && ${I(e.node)} ${I(e.cli)} paste`};return`<?xml version="1.0" encoding="UTF-8"?>
2
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
- <plist version="1.0"><dict>
4
- <key>Label</key><string>${t(e.label??M)}</string>
5
- <key>ProgramArguments</key><array><string>${t(e.executable)}</string></array>
6
- <key>WorkingDirectory</key><string>${t(e.cwd)}</string>
7
- <key>EnvironmentVariables</key><dict>${Object.entries(n).map(([r,o])=>`<key>${r}</key><string>${t(o)}</string>`).join("")}</dict>
8
- <key>RunAtLoad</key><true/><key>KeepAlive</key><true/>
9
- <key>ProcessType</key><string>Interactive</string>
10
- <key>StandardErrorPath</key><string>${t(e.log)}</string>
11
- </dict></plist>
12
- `}function xt(e){return e?["--icon",e]:[]}function Ce(e){return h(e,"Applications",Y,"Contents/MacOS/agentx-mac-helper")}function Te(e=b(Oe(import.meta.url))){for(let t=e;;t=b(t)){try{if(JSON.parse($e(h(t,"package.json"),"utf8")).name==="agentix-cli")return t}catch{}if(b(t)===t)return null}}function Ie(e,t,n){if(n)return n;let r=Ce(t);return z(r)||!e?r:h(e,"apps/mac-helper/build",Y,"Contents/MacOS/agentx-mac-helper")}function _(){let e=Ie(Te(),Ee(),process.env.AGENTX_MAC_HELPER);return z(e)?e:null}import{accessSync as Fe,constants as He}from"fs";import{execFile as L}from"child_process";var ee=5e3,R=(e,t)=>new Promise(n=>{try{L(e,t,{timeout:ee},r=>n(!r))}catch{n(!1)}}),P=(e,t)=>new Promise(n=>{try{L(e,t,{timeout:ee},(r,o)=>n(r?null:String(o)))}catch{n(null)}}),$=(e,t)=>new Promise((n,r)=>{L(e,t,{timeout:3e4,maxBuffer:8*1024*1024},(o,i,s)=>{o?r(new Error(`${e} ${t[0]}: ${(s||o.message).trim()}`)):n(i)})});var te=e=>e.helper===void 0?_():e.helper;async function Me(e,t=P){if(!e)return"missing";let n=await t(e,["notify-status"]),r=null;try{r=n?JSON.parse(n):null}catch{r=null}return r?r.authorization==="notDetermined"?"not-asked":r.authorization==="denied"?"not-allowed":r.alertStyle==="none"?"hidden":"ready":"outdated"}function ne(e={}){let t=e.run??R;return{async post({title:n,message:r}){let o=te(e);return o&&await t(o,["notify","--title",n,"--message",r])||await t("/usr/bin/osascript",["-e","on run argv","-e","display notification (item 2 of argv) with title (item 1 of argv)","-e","end run",n,r])?"posted":"failed"},status:()=>Me(te(e),e.capture)}}var _e=`
13
- function run(argv) {
14
- var folderName = argv[0], title = argv[1], body = argv[2]
15
- var Notes = Application("Notes")
16
- var esc = function (s) { return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;") }
17
- var folder = Notes.folders.whose({ name: folderName })()[0]
18
- if (!folder) {
19
- folder = Notes.Folder({ name: folderName })
20
- Notes.folders.push(folder)
21
- folder = Notes.folders.whose({ name: folderName })()[0]
22
- }
23
- var lines = body.split("\\n").map(function (l) { return "<div>" + (l ? esc(l) : "<br>") + "</div>" })
24
- // Notes takes the title from the first line: a name as well would show it twice.
25
- var note = Notes.Note({ body: "<div><h1>" + esc(title) + "</h1></div>" + lines.join("") })
26
- folder.notes.push(note)
27
- return note.id()
28
- }
29
- `;function re(e=$){return{async add({folder:t,title:n,body:r}){let i=(await e("/usr/bin/osascript",["-l","JavaScript","-e",_e,t,n,r])).trim();if(!i)throw new Error("Notes did not return the new note's id");return{id:i}}}}import{existsSync as Le,readdirSync as De}from"fs";var oe="/System/Library/Sounds";function ie(e){if(!/^[\w -]+$/.test(e))return null;let t=`${oe}/${e}.aiff`;return Le(t)?t:null}function se(e={}){let t=e.run??R;return{async play(n,r){let o=ie(n);o&&await t("/usr/bin/afplay",["-v",String(Math.min(1,Math.max(0,r))),o])},has:n=>ie(n)!==null,async names(){try{return De(oe).filter(n=>n.endsWith(".aiff")).map(n=>n.slice(0,-5)).sort()}catch{return[]}}}}var ae={say:"a spoken line (say)",afplay:"a sound playing (afplay)"};function qe(e,t,n=process.pid){for(let r of e.split(`
30
- `)){let o=r.trim().match(/^(\d+)\s+(\d+)\s+(\S+)/);if(!o)continue;let[i,s,u]=[Number(o[1]),Number(o[2]),o[3]],l=u.slice(u.lastIndexOf("/")+1);if(!(!(l in ae)||i===n||t.includes(s)))return ae[l]}return null}function ue(e={}){let t=e.capture??P;return{async othersSpeaking(n={}){let r=await t("/bin/ps",["-axo","pid=,ppid=,comm="]);return r?qe(r,n.exceptParents??[]):null}}}var pe="\u200B";function O(e){return e.startsWith("-")?pe+e:e}var le=e=>e?.startsWith(pe+"-")?e.slice(1):e;function D(e){if(!e.trim()||e.startsWith("-"))throw new Error(`Reminders list name "${e}" is not usable: it must not be empty or start with "-"`);return e}var ce=e=>new Date(Math.floor(e.getTime()/6e4)*6e4).toISOString().replace(/\.\d{3}Z$/,"Z");function de(e="remindctl",t=$){return{async ensureList(n){let r=await t(e,["list","--json","--no-input"]),o=JSON.parse(r.trim()||"[]");if(!Array.isArray(o))throw new Error(`${e} list: expected a JSON array`);o.some(i=>i?.title===n)||await t(e,["list",D(n),"--create","--json","--no-input"])},async listOpen(n){let r=await t(e,["show","all","--list",D(n),"--json","--no-input"]),o=JSON.parse(r.trim()||"[]");if(!Array.isArray(o))throw new Error(`${e} show: expected a JSON array`);return o.filter(i=>i&&typeof i.id=="string"&&!i.isCompleted).map(i=>({...i,title:le(i.title)??"",...i.notes!==void 0?{notes:le(i.notes)}:{}}))},async add({list:n,title:r,notes:o,due:i,priority:s}){let u=["add","--title",O(r),"--list",D(n),"--notes",O(o)];i&&u.push("--due",ce(i)),s&&s!=="none"&&u.push("--priority",s);let l=await t(e,[...u,"--json","--no-input"]),p;try{p=JSON.parse(l.trim())?.id}catch{p=void 0}if(typeof p!="string"||!p)throw new Error(`${e} add: no reminder id in its answer`);return{id:p}},async update(n,r){let o=["edit",n];r.title!==void 0&&o.push("--title",O(r.title)),r.notes!==void 0&&o.push("--notes",O(r.notes)),r.due===null?o.push("--clear-due"):r.due&&o.push("--due",ce(r.due)),o.length!==2&&await t(e,[...o,"--json","--no-input"])},async complete(n){await t(e,["complete",n,"--json","--no-input"])},async remove(n){await t(e,["delete",n,"--force","--json","--no-input"])}}}var We=e=>{try{return Fe(e,He.X_OK),!0}catch{return!1}},je={async active(){let e=V();return e.active?!0:e.reason==="no active Focus assertion"?!1:null}};function me(e={},t={}){let n=e.reminders?.command||"remindctl";return{os:"macos",tasks:de(n,t.output),notes:re(t.output),banner:ne(t),sound:se(t),focus:je,speech:ue(t),async describe(){let r=[],o=t.isExecutable??We;return(n.includes("/")?o(n):!!Q(n,process.env.PATH||"",o))||r.push(`Reminders: ${n} not found (brew install steipete/tap/remindctl, or set reminders.command).`),{os:"macos",tasks:!0,notes:!0,banner:!0,sound:!0,focus:!0,problems:r}}}}function E(e){return{os:"none",async describe(){return{os:"none",tasks:!1,notes:!1,banner:!1,sound:!1,focus:!1,problems:[e]}}}}var Ue={darwin:me};function q(e={},t={}){if(e.native?.adapter==="none")return E('Turned off: native.adapter is "none".');let n=t.platform??process.platform,r=(t.factories??Ue)[n];return r?r(e,t.io??{}):E(`AgentX has no native adapter for ${n} yet.`)}import{existsSync as Ge}from"fs";import{homedir as Be}from"os";import{isAbsolute as Je}from"path";var fe={banner:!0,sound:!0,soundName:"Glass",volume:.4,quietWaitSeconds:15};function tn(e){return{...fe,...e??{}}}function nn(e,t={}){let n=q({native:t.native},{platform:t.platform,io:{run:t.run,capture:t.capture,helper:t.helper}});return async(r,o)=>{let i=[];if(e.banner&&n.banner&&i.push(n.banner.post({title:r,message:o})),e.sound&&n.sound){let s=n.sound;i.push(Xe(n.speech,(e.quietWaitSeconds??fe.quietWaitSeconds)*1e3,t.pollMs??500).then(u=>u?s.play(e.soundName,e.volume):void 0))}await Promise.all(i)}}async function Xe(e,t,n){if(!e||t<=0)return!0;let r=Date.now()+t;for(;;){if(!await e.othersSpeaking())return!0;if(Date.now()>=r)return!1;await new Promise(o=>setTimeout(o,n))}}function rn(e,t,n=process.platform){let r={...e??{}},o=q({},{platform:n});if("banner"in t&&(r.banner=!!t.banner),"sound"in t&&(r.sound=!!t.sound),"soundName"in t){let i=String(t.soundName).trim();if(!(o.sound?o.sound.has(i):/^[\w -]+$/.test(i)))throw new Error(`no system sound named "${i}" on this machine`);r.soundName=i}if("icon"in t){let i=String(t.icon??"").trim().replace(/^~(?=\/)/,Be());if(!i)delete r.icon;else{if(!Je(i)||!/\.(png|jpe?g|icns)$/i.test(i))throw new Error("icon must be the full path to a .png, .jpg or .icns file");if(o.banner&&!Ge(i))throw new Error(`no icon file at ${i}`);r.icon=i}}if("volume"in t){let i=Number(t.volume);if(!Number.isFinite(i)||i<0||i>1)throw new Error("volume must be between 0 and 1");r.volume=i}return r}import{existsSync as Ze,mkdirSync as Qe,readFileSync as et,renameSync as tt,writeFileSync as nt}from"fs";import{dirname as xe,resolve as rt}from"path";X();Pe();X();var ln=["owner","agent","person"],Ke=36e5,Ve=e=>K.includes(e.state);function ge(e){return t=>!e||e.get(t)?.state!=="active"?null:e.steps(t).find(n=>n.state==="active")?.agentId??null}function F(e,t){return e.state==="waiting_owner"?{group:"owner",who:k}:e.promise&&e.owedTo&&e.owedTo!==k&&!t.isAgent(e.owedTo)?{group:"person",who:e.owedTo}:e.state==="needs_attention"?{group:"owner",who:k}:{group:"agent",who:t.activeStepAgent?.(e.id)??e.agentId}}function H(e,t,n){return Ve(e)?e.state==="needs_attention"?e.attentionReason??"needs attention":e.dueAt!==null?n>e.dueAt?`past its due time (${ze(e.dueAt)})`:null:n-e.updatedAt>t*Ke?`no activity for ${t} h or more`:null:null}function cn(e,t,n,r){let o={owner:[],agent:[],person:[]};for(let i of e){let s=H(i,t,n);if(!s)continue;let{group:u,who:l}=F(i,r);o[u].push({request:i,group:u,who:l,why:s})}return o}function pn(e,t){return e==="owner"?"you":e==="agent"?`agent ${t}`:`${t} (outside person)`}var ze=e=>new Date(e).toISOString().slice(0,16).replace("T"," ")+" UTC";function dn(e){if(e==null||e==="")return;let t=typeof e=="number"?e:typeof e=="string"?Date.parse(e.trim()):NaN;return!Number.isFinite(t)||t<=0?"due must be an ISO date (2030-01-31T17:00:00Z) or epoch milliseconds":t}function mn(e){return t=>B(t,e())}var Ye=10;async function fn(e,t,n){let r=e.dueForCheck(n.now-n.everyMinutes*6e4,Ye),o=0;return await Promise.all(r.map(async i=>{e.markChecked(i.id,i.verifyState,i.verifyDetail??"",n.now);let s=await t(i.closeWhen,i).catch(u=>({verdict:"unverifiable",detail:String(u?.message??u)}));if(s.verdict==="met"){e.closeVerified(i.id,s.detail,n.now)&&(o++,n.log(`[requests] ${i.id} closed by its check: ${s.detail}`));return}e.markChecked(i.id,s.verdict==="unverifiable"?"unverified":i.verifyState==="failed"?"failed":null,s.detail,n.now)})),{checked:r.length,closed:o}}var we=200,ye=4e3,he="Nothing, just so you know.";function W(e,t=we){let n=e.headline?e.headline.length>t?`${e.headline.slice(0,t-1)}\u2026`:e.headline:f(e.text,t),r=e.state==="needs_attention"?`Hand it back to ${e.agentId}, or drop it.`:e.state==="waiting_owner"?e.need??(e.question?`Answer: ${f(e.question,150)}`:"Your answer."):e.state==="candidate"||e.state==="in_progress"||e.state==="waiting_other"?e.need??he:he;return{headline:n,need:r,said:Se(e.text)}}function Se(e){return e.replace(/```[\s\S]*?```/g," ").replace(/!?\[([^\]]*)\]\([^)]*\)/g,"$1").replace(/^\s{0,3}(#{1,6}|>|[-*+]|\d+[.)])\s+/gm,"").replace(/[*_~`]+/g,"").replace(/\s+/g," ").trim()}function f(e,t=we){let n=Se(e);if(n.length<=t)return n;let r=n.slice(0,t),o=Math.max(r.lastIndexOf(". "),r.lastIndexOf("? "),r.lastIndexOf("! "));return o>t/2?r.slice(0,o+1):`${r.slice(0,t-1).trimEnd()}\u2026`}function Sn(e,t,n,r){let o=n?.get(e.id),i=r?H(e,r.staleAfterHours,r.now):null,s=t.lastAnswer(e.id),u=s&&s.text.length>ye?`${s.text.slice(0,ye)}
31
-
32
- \u2026`:s?.text,l=W(e);return{...e,summary:l.headline,needed:l.need,textHtml:N(e.text),why:e.state==="waiting_owner"?e.question:e.state==="needs_attention"?e.attentionReason:null,ownerNoteHtml:e.ownerNote?N(e.ownerNote):null,lastAnswer:s?{agentId:s.agentId,at:s.at,html:N(u)}:null,loop:e.promise||e.dueAt!==null||e.closeWhen?{owedTo:e.owedTo,promise:e.promise,dueAt:e.dueAt,check:e.closeWhen?J(e.closeWhen):null,verifyState:e.verifyState,verifyDetail:e.verifyDetail}:null,stall:r&&i?{...F(e,{isAgent:r.isAgent,activeStepAgent:ge(n)}),why:i}:null,plan:o?{state:o.state,steps:n.steps(e.id).map(p=>({idx:p.idx,name:p.name,kind:p.kind,agentId:p.agentId,state:p.state,approval:p.needsApproval?p.approval:null,evidence:p.evidence,note:p.note,nudges:p.nudges}))}:null}}var j=3,it=30*24*36e5;function ke(e){return rt(e,".agentx","native-mirror.json")}function Ne(e){try{let t=JSON.parse(et(ke(e),"utf-8"));return{...t,items:t?.items&&typeof t.items=="object"?t.items:{}}}catch{return{items:{}}}}function ve(e,t){let n=ke(e);Ze(xe(n))||Qe(xe(n),{recursive:!0});let r=`${n}.${process.pid}.tmp`;nt(r,JSON.stringify(t,null,2)+`
33
- `),tt(r,n)}var ot=5*6e4;function An(e,t,n=Date.now()){let r=Ne(e),o=r.checkedAt?Date.parse(r.checkedAt):NaN;return!r.cards||!(n-o<=ot)?!1:r.items[`card:${t}`]?.state!=="failed"}var U=24*36e5;function be(e,t,n){let r=e+t.dueAfterMinutes*6e4;return new Date(n!==void 0&&Number.isFinite(n)?Math.max(e,Math.min(r,n)):r)}function st(e,t,n,r){let o=Date.parse(e.expires),i=[f(e.ask,y.ask)];return e.choices?.length&&i.push(...e.choices.map((s,u)=>`${u+1}. ${s}`)),e.recommend&&i.push(`Recommended: ${f(e.recommend,y.recommend)}`),i.push(`Answer: ${n}`,`agentx: card=${e.id}`),{key:`card:${e.id}`,title:f(e.title,y.title),notes:i.join(`
34
- `),due:be(r,t,o),priority:Number.isFinite(o)&&o-r<=U?"high":"none"}}function at(e,t,n,r){let o=[f(e.question||e.text,y.ask),`Asked of ${e.agentId}.`,`Answer: ${n}`,`agentx: request=${e.id}`],i=e.dueAt??void 0;return{key:`request:${e.id}`,title:W(e,y.title).headline,notes:o.join(`
35
- `),due:be(r,t,i),priority:i!==void 0&&i-r<=U?"high":"none"}}async function Rn(e){let{root:t,settings:n,tasks:r,log:o}=e,i=e.now??Date.now(),s=new Date(i).toISOString(),u={created:0,completed:0,dismissed:0,failed:0},l=Ne(t);delete l.lastError;let p=a=>{l.lastError={at:s,message:a}},w=new Map;try{if(n.include.includes("card"))for(let a of e.cards())a.status==="pending"&&Date.parse(a.expires)>i&&w.set(`card:${a.id}`,st(a,n,e.answerUrl,i));if(n.include.includes("request"))for(let a of e.requests())a.state==="waiting_owner"&&w.set(`request:${a.id}`,at(a,n,e.answerUrl,i))}catch(a){return o(`[mirror] could not read what is waiting: ${a?.message??a}`),p(String(a?.message??a)),ve(t,l),u}let G=!0,x=new Map,Ae=async a=>{if(!x.has(a))try{x.set(a,new Set((await r.listOpen(a)).map(c=>c.id)))}catch(c){o(`[mirror] could not read list "${a}": ${c?.message??c}`),p(String(c?.message??c)),x.set(a,null),G=!1}return x.get(a)};for(let[a,c]of Object.entries(l.items)){if(c.state!=="open"||!c.id)continue;let d=await Ae(c.list);if(d)if(w.has(a))d.has(c.id)||(l.items[a]={...c,state:"dismissed",at:s},u.dismissed++,o(`[mirror] ${a}: its task was ticked off or deleted; it stays on the Desk and is not mirrored again`));else{if(d.has(c.id))try{await r.complete(c.id)}catch(g){o(`[mirror] ${a}: could not complete its task: ${g?.message??g}`),p(String(g?.message??g));continue}l.items[a]={...c,state:"completed",at:s},u.completed++}}let C=null,S=[];for(let[a,c]of w){let d=l.items[a];if(d&&(d.state==="open"||d.state==="dismissed")||d?.state==="failed"&&d.failures>=j)continue;if(C===null)try{await r.ensureList(n.list),C=!0}catch(m){o(`[mirror] could not make list "${n.list}": ${m?.message??m}`),p(String(m?.message??m)),C=!1}let g=d?.state==="failed"?d.failures:0;try{let{id:m}=await r.add({list:n.list,title:c.title,notes:c.notes,due:c.due,priority:c.priority});l.items[a]={id:m,list:n.list,state:"open",at:s,failures:0},u.created++}catch(m){let v=String(m?.message??m),T=g+1;l.items[a]={list:n.list,state:"failed",at:s,failures:T,lastError:v},p(v),u.failed++,o(`[mirror] ${a}: could not add to Reminders (${T}/${j}): ${v}`),T===j&&S.push({title:c.title,message:v})}}let Re=l.failureToldAt?Date.parse(l.failureToldAt):NaN;if(S.length&&!(i-Re<U)){let a=S[0],c=S.length>1?` (and ${S.length-1} more)`:"";try{await e.notify("Could not add to Reminders",`${a.title}${c}: ${f(a.message,200)}. Still on the Desk.`),l.failureToldAt=s}catch(d){o(`[mirror] could not tell the owner: ${d?.message??d}`)}}G&&(l.checkedAt=s,l.cards=n.include.includes("card"));for(let[a,c]of Object.entries(l.items))c.state!=="open"&&!w.has(a)&&i-Date.parse(c.at)>it&&delete l.items[a];return ve(t,l),u}export{ln as a,ge as b,F as c,cn as d,pn as e,ze as f,dn as g,mn as h,fn as i,W as j,Sn as k,dt as l,Y as m,mt as n,ft as o,gt as p,yt as q,ht as r,wt as s,Q as t,St as u,xt as v,Ce as w,Te as x,Ie as y,_ as z,ie as A,q as B,fe as C,tn as D,nn as E,rn as F,Ne as G,An as H,Rn as I};
36
- //# sourceMappingURL=chunk-XOUK5ZIQ.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/desktop/install.ts","../src/native/macos/index.ts","../src/native/macos/run.ts","../src/native/macos/banner.ts","../src/native/macos/notes.ts","../src/native/macos/sound.ts","../src/native/macos/speech.ts","../src/native/macos/tasks.ts","../src/native/none.ts","../src/native/index.ts","../src/notify/local.ts","../src/approvals/mirror.ts","../src/requests/card-view.ts","../src/requests/loops.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { homedir } from 'node:os'\n\nexport const DESKTOP_LABEL = 'tn.acme.agentx.voice'\nexport const DESKTOP_APP = 'AgentX Desktop.app'\nexport const HELPER_APP = 'AgentX Helper.app'\nexport function desktopPlatformError(platform: string, arch: string, release: string): string | null {\n if (platform !== 'darwin' || arch !== 'arm64' || Number(release.split('.')[0]) < 23)\n return 'AgentX Desktop currently requires Apple Silicon and macOS 14 or newer.'\n return null\n}\nexport function selectDesktopAgent(agents: Record<string, unknown>, requested?: string): string {\n if (requested && !Object.hasOwn(agents, requested)) throw new Error(`Unknown agent: ${requested}. Run agentx agent list.`)\n const id = requested || Object.keys(agents)[0]\n if (!id) throw new Error('No agents configured. Run agentx setup first.')\n return id\n}\nexport function shellQuote(value: string): string { return `'${value.replace(/'/g, \"'\\\\''\")}'` }\n/** launchd's default PATH, which is all a login item gets unless the plist sets one. */\nexport const LAUNCHD_PATH = '/usr/bin:/bin:/usr/sbin:/sbin'\n/** PATH for the login item: AGENTX_VOICE_PATH when set, else the folder of\n * the ffmpeg found at install time (mlx_whisper needs it) ahead of launchd's default. */\nexport function desktopPath(ffmpeg: string | null, override?: string): string {\n if (override) return override\n return [...new Set([...(ffmpeg ? [dirname(ffmpeg)] : []), ...LAUNCHD_PATH.split(':')])].join(':')\n}\nfunction unxml(s: string): string {\n const ents: Record<string, string> = { amp: '&', lt: '<', gt: '>', quot: '\"', apos: \"'\" }\n return s.replace(/&(amp|lt|gt|quot|apos);/g, (_, e) => ents[e])\n}\n/** The PATH a login item plist sets, or launchd's default when it sets none. */\nexport function plistPathEnv(plist: string): string {\n const m = plist.match(/<key>PATH<\\/key>\\s*<string>([^<]*)<\\/string>/)\n return m ? unxml(m[1]) : LAUNCHD_PATH\n}\n/** Login item files the voice app may have been installed under. */\nexport const isVoiceLoginItemFile = (name: string) => /agentx\\.voice.*\\.plist$/.test(name)\nexport type LoginItem = { file: string; label: string }\n/** One login item per Mac. The label was renamed once and\n * apps/mac-voice/install.sh writes its own, so adopt the first existing\n * `*agentx.voice*` item that runs the voice app (any bundle name) and\n * retire the others; with none, use DESKTOP_LABEL. Plists are XML text. */\nexport function adoptLoginItem(dir: string, items: { file: string; plist: string }[]): { keep: LoginItem; remove: LoginItem[] } {\n let keep: LoginItem | null = null\n const remove: LoginItem[] = []\n for (const { file, plist } of [...items].sort((a, b) => (a.file < b.file ? -1 : 1))) {\n const label = plist.match(/<key>Label<\\/key>\\s*<string>([^<]*)<\\/string>/)?.[1]\n const program = plist.match(/<key>ProgramArguments<\\/key>\\s*<array>\\s*<string>([^<]*)<\\/string>/)?.[1]\n if (!label || !program || !unxml(program).endsWith('/Contents/MacOS/AgentXVoice')) continue\n if (keep) remove.push({ file, label: unxml(label) })\n else keep = { file, label: unxml(label) }\n }\n return { keep: keep ?? { file: join(dir, `${DESKTOP_LABEL}.plist`), label: DESKTOP_LABEL }, remove }\n}\n/** The file that a process with this PATH would run, or null. */\nexport function findOnPath(bin: string, path: string, isExecutable: (file: string) => boolean): string | null {\n for (const dir of path.split(':')) if (dir && isExecutable(join(dir, bin))) return join(dir, bin)\n return null\n}\n/** Without `agent` the app talks to the agent picked in its menu-bar\n * icon; with it, that agent is pinned and the menu cannot switch. */\nexport function desktopPlist(opts: { label?: string; executable: string; cwd: string; agent?: string; url: string; helper: string; cli: string; node: string; log: string; path: string }): string {\n const xml = (s: string) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;').replace(/'/g, '&apos;')\n const env = {\n PATH: opts.path,\n ...(opts.agent ? { AGENTX_VOICE_AGENT: opts.agent } : {}),\n AGENTX_DAEMON_URL: opts.url,\n AGENTX_MAC_HELPER: opts.helper,\n AGENTX_PASTE_COMMAND: `cd ${shellQuote(opts.cwd)} && ${shellQuote(opts.node)} ${shellQuote(opts.cli)} paste`,\n }\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\"><dict>\n<key>Label</key><string>${xml(opts.label ?? DESKTOP_LABEL)}</string>\n<key>ProgramArguments</key><array><string>${xml(opts.executable)}</string></array>\n<key>WorkingDirectory</key><string>${xml(opts.cwd)}</string>\n<key>EnvironmentVariables</key><dict>${Object.entries(env).map(([k,v]) => `<key>${k}</key><string>${xml(v)}</string>`).join('')}</dict>\n<key>RunAtLoad</key><true/><key>KeepAlive</key><true/>\n<key>ProcessType</key><string>Interactive</string>\n<key>StandardErrorPath</key><string>${xml(opts.log)}</string>\n</dict></plist>\\n`\n}\n/** build.sh arguments for the helper: the banner icon from\n * notifications.local.icon, or none for the bundled AgentX logo. */\nexport function helperBuildArgs(icon?: string): string[] {\n return icon ? ['--icon', icon] : []\n}\nexport function installedHelper(home: string): string {\n return join(home, 'Applications', HELPER_APP, 'Contents/MacOS/agentx-mac-helper')\n}\n/** The AgentX package folder, found by walking up from this file (dist/\n * when installed, src/ in a checkout) — never from the caller's cwd, so\n * `agentx notify` finds a repo-built helper from any folder. */\nexport function packageRoot(from = dirname(fileURLToPath(import.meta.url))): string | null {\n for (let dir = from; ; dir = dirname(dir)) {\n try {\n if (JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')).name === 'agentix-cli') return dir\n } catch { /* no package.json here — keep walking up */ }\n if (dirname(dir) === dir) return null\n }\n}\n/** The helper to run: an explicit path, else the one `agentx desktop\n * install` put in ~/Applications, else one built in the package. */\nexport function resolveHelper(root: string | null, home: string, override?: string): string {\n if (override) return override\n const installed = installedHelper(home)\n if (existsSync(installed) || !root) return installed\n return join(root, 'apps/mac-helper/build', HELPER_APP, 'Contents/MacOS/agentx-mac-helper')\n}\n/** The AgentX Helper binary when one is built or installed, else null.\n * AGENTX_MAC_HELPER points at a helper somewhere else. */\nexport function findHelper(): string | null {\n const path = resolveHelper(packageRoot(), homedir(), process.env.AGENTX_MAC_HELPER)\n return existsSync(path) ? path : null\n}\n","import { accessSync, constants } from \"fs\"\nimport { findOnPath } from \"@/desktop/install\"\nimport { readSystemFocus } from \"@/notify/focus\"\nimport type { NativeAdapter, NativeFocus } from \"../types\"\nimport { macosBanner } from \"./banner\"\nimport { macosNotes } from \"./notes\"\nimport type { Capture, Output, Run } from \"./run\"\nimport { macosSound } from \"./sound\"\nimport { macosSpeech } from \"./speech\"\nimport { macosTasks } from \"./tasks\"\n\n/** Programs the adapter runs, so tests can stand in for them. */\nexport interface MacosIo {\n run?: Run\n capture?: Capture\n output?: Output\n /** The AgentX Helper binary; null means none. Unset: looked up on use. */\n helper?: string | null\n /** Is this file an executable? Used to find remindctl. */\n isExecutable?: (file: string) => boolean\n}\n\nconst executable = (file: string): boolean => {\n try { accessSync(file, constants.X_OK); return true } catch { return false }\n}\n\n/** System Focus from the private Focus database (notify/focus.ts). That\n * reader answers \"not in Focus\" when it can't read the file, so here\n * anything but a clean answer is null: the OS could not tell. */\nconst focus: NativeFocus = {\n async active() {\n const s = readSystemFocus()\n if (s.active) return true\n return s.reason === \"no active Focus assertion\" ? false : null\n },\n}\n\nexport function macosAdapter(cfg: { reminders?: { command?: string } } = {}, io: MacosIo = {}): NativeAdapter {\n const command = cfg.reminders?.command || \"remindctl\"\n return {\n os: \"macos\",\n tasks: macosTasks(command, io.output),\n notes: macosNotes(io.output),\n banner: macosBanner(io),\n sound: macosSound(io),\n focus,\n speech: macosSpeech(io),\n async describe() {\n const problems: string[] = []\n const isExec = io.isExecutable ?? executable\n const found = command.includes(\"/\") ? isExec(command) : !!findOnPath(command, process.env.PATH || \"\", isExec)\n if (!found) problems.push(`Reminders: ${command} not found (brew install steipete/tap/remindctl, or set reminders.command).`)\n return { os: \"macos\", tasks: true, notes: true, banner: true, sound: true, focus: true, problems }\n },\n }\n}\n","import { execFile } from \"child_process\"\n\n/** Runs a program; resolves true when it exited 0. Never rejects. */\nexport type Run = (file: string, args: string[]) => Promise<boolean>\n\n/** Runs a program; resolves its stdout, or rejects with its stderr. */\nexport type Output = (file: string, args: string[]) => Promise<string>\n\n/** Runs a program; resolves its stdout, or null when it failed. */\nexport type Capture = (file: string, args: string[]) => Promise<string | null>\n\n/** A banner or a sound may not hold a caller hostage: afplay on a long\n * custom sound, or osascript stuck on a permission prompt, is cut off. */\nconst STEP_TIMEOUT_MS = 5_000\n\nexport const run: Run = (file, args) =>\n new Promise((done) => {\n try {\n execFile(file, args, { timeout: STEP_TIMEOUT_MS }, (err) => done(!err))\n } catch {\n done(false) // the program is not on this machine: not worth reporting\n }\n })\n\nexport const capture: Capture = (file, args) =>\n new Promise((done) => {\n try {\n execFile(file, args, { timeout: STEP_TIMEOUT_MS }, (err, stdout) => done(err ? null : String(stdout)))\n } catch {\n done(null)\n }\n })\n\nexport const output: Output = (file, args) =>\n new Promise((resolve, reject) => {\n execFile(file, args, { timeout: 30_000, maxBuffer: 8 * 1024 * 1024 }, (err, stdout, stderr) => {\n if (err) reject(new Error(`${file} ${args[0]}: ${(stderr || err.message).trim()}`))\n else resolve(stdout)\n })\n })\n","import { findHelper } from \"@/desktop/install\"\nimport type { BannerStatus, NativeBanner } from \"../types\"\nimport { capture as defaultCapture, run as defaultRun, type Capture, type Run } from \"./run\"\n\n// The banner is posted by the AgentX Helper app when it is installed, so it\n// carries the helper's icon (the AgentX logo by default). Without the\n// helper, or before macOS has allowed it to notify, osascript posts it, and\n// macOS shows it as coming from Script Editor.\n\nexport interface BannerIo {\n run?: Run\n capture?: Capture\n /** The helper binary; null means none. Unset: looked up on each use. */\n helper?: string | null\n}\n\nconst helperOf = (io: BannerIo): string | null => (io.helper === undefined ? findHelper() : io.helper)\n\n/** What the helper reports about its notification permission. Never shows\n * the macOS \"allow notifications\" prompt and never posts a banner. */\nexport async function helperBannerStatus(path: string | null, capture: Capture = defaultCapture): Promise<BannerStatus> {\n if (!path) return \"missing\"\n const out = await capture(path, [\"notify-status\"])\n let status: { authorization?: string; alertStyle?: string } | null = null\n try { status = out ? JSON.parse(out) : null } catch { status = null }\n if (!status) return \"outdated\"\n if (status.authorization === \"notDetermined\") return \"not-asked\"\n if (status.authorization === \"denied\") return \"not-allowed\"\n if (status.alertStyle === \"none\") return \"hidden\"\n return \"ready\"\n}\n\nexport function macosBanner(io: BannerIo = {}): NativeBanner {\n const exec = io.run ?? defaultRun\n return {\n async post({ title, message }) {\n const helper = helperOf(io)\n // A non-zero exit is usually \"not allowed to notify yet\" (the first\n // banner asks), so this one goes through osascript instead.\n if (helper && await exec(helper, [\"notify\", \"--title\", title, \"--message\", message])) return \"posted\"\n // Title and message travel as argv, never spliced into the script,\n // so a quote in either cannot turn into AppleScript.\n const ok = await exec(\"/usr/bin/osascript\", [\n \"-e\", \"on run argv\",\n \"-e\", \"display notification (item 2 of argv) with title (item 1 of argv)\",\n \"-e\", \"end run\",\n title, message,\n ])\n return ok ? \"posted\" : \"failed\"\n },\n status: () => helperBannerStatus(helperOf(io), io.capture),\n }\n}\n","import type { NativeNotes } from \"../types\"\nimport { output as defaultOutput, type Output } from \"./run\"\n\n// Apple Notes through JXA (osascript -l JavaScript). The folder, title\n// and body travel as argv and are read by the script, never spliced into\n// it, so nothing in them can run as code. Notes stores a body as HTML:\n// the script escapes the text and keeps its line breaks. The first call\n// from the daemon's process asks for the macOS Automation permission\n// (System Settings › Privacy & Security › Automation › Notes).\n\nexport const NOTES_SCRIPT = `\nfunction run(argv) {\n var folderName = argv[0], title = argv[1], body = argv[2]\n var Notes = Application(\"Notes\")\n var esc = function (s) { return s.replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\") }\n var folder = Notes.folders.whose({ name: folderName })()[0]\n if (!folder) {\n folder = Notes.Folder({ name: folderName })\n Notes.folders.push(folder)\n folder = Notes.folders.whose({ name: folderName })()[0]\n }\n var lines = body.split(\"\\\\n\").map(function (l) { return \"<div>\" + (l ? esc(l) : \"<br>\") + \"</div>\" })\n // Notes takes the title from the first line: a name as well would show it twice.\n var note = Notes.Note({ body: \"<div><h1>\" + esc(title) + \"</h1></div>\" + lines.join(\"\") })\n folder.notes.push(note)\n return note.id()\n}\n`\n\nexport function macosNotes(exec: Output = defaultOutput): NativeNotes {\n return {\n async add({ folder, title, body }) {\n const raw = await exec(\"/usr/bin/osascript\", [\"-l\", \"JavaScript\", \"-e\", NOTES_SCRIPT, folder, title, body])\n const id = raw.trim()\n if (!id) throw new Error(\"Notes did not return the new note's id\")\n return { id }\n },\n }\n}\n","import { existsSync, readdirSync } from \"fs\"\nimport type { NativeSound } from \"../types\"\nimport { run as defaultRun, type Run } from \"./run\"\n\nconst SOUNDS = \"/System/Library/Sounds\"\n\n/** The system sound file for a name, or null when there is none. Names\n * only: a path in the config must not reach afplay. */\nexport function soundPath(name: string): string | null {\n if (!/^[\\w -]+$/.test(name)) return null\n const path = `${SOUNDS}/${name}.aiff`\n return existsSync(path) ? path : null\n}\n\nexport function macosSound(io: { run?: Run } = {}): NativeSound {\n const exec = io.run ?? defaultRun\n return {\n async play(name, volume) {\n const path = soundPath(name)\n if (!path) return\n await exec(\"/usr/bin/afplay\", [\"-v\", String(Math.min(1, Math.max(0, volume))), path])\n },\n has: (name) => soundPath(name) !== null,\n async names() {\n try {\n return readdirSync(SOUNDS).filter((f) => f.endsWith(\".aiff\")).map((f) => f.slice(0, -5)).sort()\n } catch {\n return []\n }\n },\n }\n}\n","import type { NativeSpeech } from \"../types\"\nimport { capture as defaultCapture, type Capture } from \"./run\"\n\n// --- Is something else speaking on this Mac? (#1126) ---\n//\n// A script that narrates with `say`, or plays a recording with `afplay`,\n// talks to the person at this Mac without going through AgentX's speaking\n// queue. Before AgentX speaks or chimes, it looks at the process list:\n// one such program running, not started by AgentX itself, means wait.\n\n/** Programs that speak or play sound, and how the owner would say it. */\nconst SPEAKERS: Record<string, string> = { say: \"a spoken line (say)\", afplay: \"a sound playing (afplay)\" }\n\n/** \"pid ppid command\" lines from ps, the speakers among them. */\nexport function speakersIn(ps: string, exceptParents: number[], self = process.pid): string | null {\n for (const line of ps.split(\"\\n\")) {\n const m = line.trim().match(/^(\\d+)\\s+(\\d+)\\s+(\\S+)/)\n if (!m) continue\n const [pid, ppid, comm] = [Number(m[1]), Number(m[2]), m[3]]\n const name = comm.slice(comm.lastIndexOf(\"/\") + 1)\n if (!(name in SPEAKERS) || pid === self || exceptParents.includes(ppid)) continue\n return SPEAKERS[name]\n }\n return null\n}\n\nexport function macosSpeech(io: { capture?: Capture } = {}): NativeSpeech {\n const capture = io.capture ?? defaultCapture\n return {\n async othersSpeaking(opts = {}) {\n const ps = await capture(\"/bin/ps\", [\"-axo\", \"pid=,ppid=,comm=\"])\n return ps ? speakersIn(ps, opts.exceptParents ?? []) : null\n },\n }\n}\n","import type { NativeTask, NativeTasks } from \"../types\"\nimport { output as defaultOutput, type Output } from \"./run\"\n\n// Apple Reminders through remindctl (Homebrew). It is the same tool the\n// mac-pim skill uses, so the daemon and the skill see the same lists. The\n// first call from the daemon's process triggers the macOS Reminders\n// permission prompt.\n//\n// Every value travels as its own argv entry, never through a shell, so\n// quotes and newlines arrive as typed. remindctl 0.1.1 reads any value\n// that starts with \"-\" as an option (and a value equal to --help or\n// --version runs that), and has no \"--opt=value\" form, so such a value\n// gets an invisible zero-width space in front. Reminders keeps that\n// character; listOpen takes it off again. A list name is a lookup key and\n// cannot carry it, so a list name that starts with \"-\" is refused.\n//\n// Reminders keeps a due time to the minute, in this Mac's time zone:\n// seconds are dropped.\n\nconst ZWSP = \"\\u200b\"\n\n/** A value remindctl takes literally. */\nexport function argValue(v: string): string {\n return v.startsWith(\"-\") ? ZWSP + v : v\n}\n\nconst unArg = (v: string | undefined): string | undefined => (v?.startsWith(ZWSP + \"-\") ? v.slice(1) : v)\n\nfunction listName(list: string): string {\n if (!list.trim() || list.startsWith(\"-\")) throw new Error(`Reminders list name \"${list}\" is not usable: it must not be empty or start with \"-\"`)\n return list\n}\n\n/** ISO to the minute, which is all Reminders keeps. */\nconst isoDue = (d: Date): string => new Date(Math.floor(d.getTime() / 60_000) * 60_000).toISOString().replace(/\\.\\d{3}Z$/, \"Z\")\n\nexport function macosTasks(command = \"remindctl\", exec: Output = defaultOutput): NativeTasks {\n return {\n async ensureList(list) {\n const raw = await exec(command, [\"list\", \"--json\", \"--no-input\"])\n const lists = JSON.parse(raw.trim() || \"[]\")\n if (!Array.isArray(lists)) throw new Error(`${command} list: expected a JSON array`)\n if (lists.some((l: { title?: string }) => l?.title === list)) return\n await exec(command, [\"list\", listName(list), \"--create\", \"--json\", \"--no-input\"])\n },\n async listOpen(list) {\n const raw = await exec(command, [\"show\", \"all\", \"--list\", listName(list), \"--json\", \"--no-input\"])\n const items = JSON.parse(raw.trim() || \"[]\")\n if (!Array.isArray(items)) throw new Error(`${command} show: expected a JSON array`)\n return items\n .filter((r: NativeTask) => r && typeof r.id === \"string\" && !r.isCompleted)\n .map((r: NativeTask) => ({ ...r, title: unArg(r.title) ?? \"\", ...(r.notes !== undefined ? { notes: unArg(r.notes) } : {}) }))\n },\n async add({ list, title, notes, due, priority }) {\n const args = [\"add\", \"--title\", argValue(title), \"--list\", listName(list), \"--notes\", argValue(notes)]\n if (due) args.push(\"--due\", isoDue(due))\n if (priority && priority !== \"none\") args.push(\"--priority\", priority)\n const raw = await exec(command, [...args, \"--json\", \"--no-input\"])\n let id: unknown\n try { id = JSON.parse(raw.trim())?.id } catch { id = undefined }\n if (typeof id !== \"string\" || !id) throw new Error(`${command} add: no reminder id in its answer`)\n return { id }\n },\n async update(id, patch) {\n const args = [\"edit\", id]\n if (patch.title !== undefined) args.push(\"--title\", argValue(patch.title))\n if (patch.notes !== undefined) args.push(\"--notes\", argValue(patch.notes))\n if (patch.due === null) args.push(\"--clear-due\")\n else if (patch.due) args.push(\"--due\", isoDue(patch.due))\n // remindctl refuses an edit that changes nothing; so does this.\n if (args.length === 2) return\n await exec(command, [...args, \"--json\", \"--no-input\"])\n },\n async complete(id) {\n await exec(command, [\"complete\", id, \"--json\", \"--no-input\"])\n },\n async remove(id) {\n await exec(command, [\"delete\", id, \"--force\", \"--json\", \"--no-input\"])\n },\n }\n}\n","import type { NativeAdapter } from \"./types\"\n\n/** The adapter for a machine AgentX has no OS layer for, or where the owner\n * turned it off. Every capability is absent; describe() says why. */\nexport function noneAdapter(reason: string): NativeAdapter {\n return {\n os: \"none\",\n async describe() {\n return { os: \"none\", tasks: false, notes: false, banner: false, sound: false, focus: false, problems: [reason] }\n },\n }\n}\n","import { macosAdapter, type MacosIo } from \"./macos\"\nimport { noneAdapter } from \"./none\"\nimport type { NativeAdapter } from \"./types\"\n\nexport * from \"./types\"\nexport { noneAdapter } from \"./none\"\n\nexport type NativeAdapterChoice = \"auto\" | \"none\"\n\nexport type NativeConfig = { native?: { adapter?: NativeAdapterChoice }; reminders?: { command?: string } }\n\n/** Programs an adapter runs, so tests can stand in for them. */\nexport type NativeIo = MacosIo\n\n/** Builds the adapter for one OS. Each OS adapter registers here. */\nexport type NativeAdapterFactory = (cfg: NativeConfig, io: NativeIo) => NativeAdapter\n\nconst FACTORIES: Partial<Record<NodeJS.Platform, NativeAdapterFactory>> = {\n darwin: macosAdapter,\n}\n\n/** The adapter for this machine. `native.adapter: \"none\"` forces none on\n * any OS; \"auto\" (the default) picks by platform. */\nexport function loadNativeAdapter(\n cfg: NativeConfig = {},\n opts: { platform?: NodeJS.Platform; factories?: Partial<Record<NodeJS.Platform, NativeAdapterFactory>>; io?: NativeIo } = {},\n): NativeAdapter {\n if (cfg.native?.adapter === \"none\") return noneAdapter(\"Turned off: native.adapter is \\\"none\\\".\")\n const platform = opts.platform ?? process.platform\n const make = (opts.factories ?? FACTORIES)[platform]\n return make ? make(cfg, opts.io ?? {}) : noneAdapter(`AgentX has no native adapter for ${platform} yet.`)\n}\n","import { existsSync } from \"fs\"\nimport { homedir } from \"os\"\nimport { isAbsolute } from \"path\"\nimport { loadNativeAdapter, type NativeConfig, type NativeIo, type NativeSpeech } from \"@/native\"\n\nexport { findHelper } from \"@/desktop/install\"\n\n// What happens on THIS machine when a notification is delivered: a banner\n// on screen and a short sound. The push goes to the phone; these are for\n// the person sitting at the computer that raised the event. Both go\n// through the native adapter (src/native); a machine without one skips\n// them.\n//\n// Both are awaited rather than fired and forgotten. A caller that exits\n// straight after notifying — a `launchctl submit` job that removes itself,\n// a cron one-liner — used to take the sound down with it before it\n// played. Waiting costs about a second and makes the step reliable.\n//\n// Never fatal: a notification whose banner failed still arrived, and no\n// caller should have to handle an audio error to say a build finished.\n\nexport interface LocalSettings {\n /** Show a desktop banner (macOS only; other platforms skip it). */\n banner: boolean\n /** Play a sound (macOS only). */\n sound: boolean\n /** A system sound, by name (on a Mac, one of the sounds in System Settings › Sound). */\n soundName: string\n /** 0 (silent) … 1 (full). */\n volume: number\n /** Seconds the sound waits while something else speaks on this machine\n * (#1126). Still speaking after that: no sound, the banner is enough.\n * 0: never wait. */\n quietWaitSeconds?: number\n /** Image for the helper's app icon, which macOS puts on its banners.\n * Unset: the AgentX logo. Applied when `agentx desktop install` builds\n * the helper, since macOS reads the icon from the app, not the banner. */\n icon?: string\n}\n\n/** Glass is the gentlest system sound that is still audible over a\n * keyboard — Basso and Sosumi are alerts, Funk and Frog are jokes, and\n * Ping is the one every other app already uses. */\nexport const DEFAULT_LOCAL: LocalSettings = { banner: true, sound: true, soundName: \"Glass\", volume: 0.4, quietWaitSeconds: 15 }\n\n/** Shows the banner and plays the sound for one delivered notification. */\nexport type LocalAlert = (title: string, message: string) => Promise<void>\n\n/** Fill gaps in a partial `notifications.local` block with the defaults. */\nexport function localSettings(cfg: Partial<LocalSettings> | undefined): LocalSettings {\n return { ...DEFAULT_LOCAL, ...(cfg ?? {}) }\n}\n\nexport function localAlert(\n settings: LocalSettings,\n deps: {\n run?: NativeIo[\"run\"]; capture?: NativeIo[\"capture\"]; platform?: NodeJS.Platform; helper?: string | null; native?: NativeConfig[\"native\"]\n /** For tests: the poll between two looks at who is speaking. */\n pollMs?: number\n } = {},\n): LocalAlert {\n const native = loadNativeAdapter({ native: deps.native }, { platform: deps.platform, io: { run: deps.run, capture: deps.capture, helper: deps.helper } })\n return async (title, message) => {\n const steps: Promise<unknown>[] = []\n if (settings.banner && native.banner) steps.push(native.banner.post({ title, message }))\n if (settings.sound && native.sound) {\n const sound = native.sound\n // A chime over someone speaking is the collision #1126 is about:\n // the banner goes up at once, the sound waits for quiet.\n steps.push(quiet(native.speech, (settings.quietWaitSeconds ?? DEFAULT_LOCAL.quietWaitSeconds!) * 1000, deps.pollMs ?? 500)\n .then((ok) => (ok ? sound.play(settings.soundName, settings.volume) : undefined)))\n }\n await Promise.all(steps)\n }\n}\n\n/** True once nothing else speaks on this machine, false when something\n * still does after `waitMs`. Without a way to tell, true at once. The\n * daemon's own speech counts too (no `exceptParents` here, unlike the\n * speaking queue): a chime over a spoken reply is the same interruption,\n * so it waits, and past the wait the banner shows without the sound. */\nasync function quiet(speech: NativeSpeech | undefined, waitMs: number, pollMs: number): Promise<boolean> {\n if (!speech || waitMs <= 0) return true\n const until = Date.now() + waitMs\n for (;;) {\n if (!(await speech.othersSpeaking())) return true\n if (Date.now() >= until) return false\n await new Promise((r) => setTimeout(r, pollMs))\n }\n}\n\n/**\n * Apply a change to a stored `notifications.local` block, the one rule\n * both the CLI and the dashboard write through. Returns the new block;\n * throws on a value that would not load. Only a machine with a native\n * adapter has sounds to check a name against; elsewhere the setting is\n * inert, so it need only be well-formed.\n */\nexport function patchLocal(\n current: Partial<LocalSettings> | undefined,\n patch: Record<string, unknown>,\n platform: NodeJS.Platform = process.platform,\n): Partial<LocalSettings> {\n const next = { ...(current ?? {}) }\n const native = loadNativeAdapter({}, { platform })\n if (\"banner\" in patch) next.banner = Boolean(patch.banner)\n if (\"sound\" in patch) next.sound = Boolean(patch.sound)\n if (\"soundName\" in patch) {\n const name = String(patch.soundName).trim()\n const ok = native.sound ? native.sound.has(name) : /^[\\w -]+$/.test(name)\n if (!ok) throw new Error(`no system sound named \"${name}\" on this machine`)\n next.soundName = name\n }\n if (\"icon\" in patch) {\n const icon = String(patch.icon ?? \"\").trim().replace(/^~(?=\\/)/, homedir())\n if (!icon) delete next.icon\n else {\n if (!isAbsolute(icon) || !/\\.(png|jpe?g|icns)$/i.test(icon)) throw new Error(\"icon must be the full path to a .png, .jpg or .icns file\")\n if (native.banner && !existsSync(icon)) throw new Error(`no icon file at ${icon}`)\n next.icon = icon\n }\n }\n if (\"volume\" in patch) {\n const v = Number(patch.volume)\n if (!Number.isFinite(v) || v < 0 || v > 1) throw new Error(\"volume must be between 0 and 1\")\n next.volume = v\n }\n return next\n}\n","import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from \"fs\"\nimport { dirname, resolve } from \"path\"\nimport type { NativePriority, NativeTasks } from \"@/native\"\nimport { cardWords, summarize } from \"@/requests/card-view\"\nimport type { RequestRecord } from \"@/requests/store\"\nimport { CARD_LIMITS, type DecisionCard } from \"./cards\"\n\n// --- The Reminders mirror (#1062 step 5) ---\n//\n// Each pending decision card, and each request waiting on the owner,\n// becomes one task in the OS task app (Apple Reminders on a Mac) with a due\n// time. Calendar shows it, and the system alert comes back when it falls\n// due. Answering stays on the Desk, the Approvals page or the CLI: a task\n// cannot carry choices or a draft.\n//\n// Runs once a minute beside the approvals sweep, on the node that holds the\n// cards (forwarded cards live on the owner's node, so that is where they\n// are mirrored). Off by default.\n//\n// Rules:\n// - one task per card or request, created once;\n// - the card is answered, expires or is closed, or the request stops\n// waiting on the owner: the task is completed;\n// - the owner ticks the task off or deletes it while the item still\n// waits: the item stays on the Desk and is never mirrored again.\n// Ticking a task is not an answer;\n// - three failed adds for one item: the owner is told once and that\n// item is not tried again;\n// - every task ends with `agentx: card=<id>` or `agentx: request=<id>`,\n// which check-ins and the reminders hand-back skip, so a mirrored\n// task never turns back into a card or an agent's job.\n//\n// State lives in its own file, written only by the daemon, not in the card\n// files that the CLI and the dashboard also write.\n\nexport interface MirrorSettings {\n enabled: boolean\n list: string\n dueAfterMinutes: number\n include: (\"card\" | \"request\")[]\n /** Where the owner answers. Unset: the local dashboard's Approvals page. */\n answerUrl?: string\n}\n\nexport type MirrorState = \"open\" | \"completed\" | \"dismissed\" | \"failed\"\n\nexport interface MirrorEntry {\n /** The task's id in the OS task app, once added. */\n id?: string\n list: string\n state: MirrorState\n /** ISO time of the last change. */\n at: string\n failures: number\n lastError?: string\n}\n\nexport interface MirrorFile {\n /** Keyed `card:<id>` or `request:<id>`, the inbox's keys. */\n items: Record<string, MirrorEntry>\n /** ISO time of the last pass that read every list it needed. Stale\n * (the mirror was turned off, or the list can't be read) means the\n * mirror no longer covers anything, and the Mac popup is back. */\n checkedAt?: string\n /** That pass mirrored cards (include has \"card\"). */\n cards?: boolean\n /** ISO time the owner was last told adds are failing: once a day. */\n failureToldAt?: string\n /** The latest pass's last error, for doctor. */\n lastError?: { at: string; message: string }\n}\n\nexport const MAX_ADD_FAILURES = 3\n/** Closed entries are kept this long, then forgotten. */\nconst KEEP_CLOSED_MS = 30 * 24 * 3_600_000\n\nexport function mirrorPath(root: string): string {\n return resolve(root, \".agentx\", \"native-mirror.json\")\n}\n\nexport function readMirror(root: string): MirrorFile {\n try {\n const parsed = JSON.parse(readFileSync(mirrorPath(root), \"utf-8\"))\n return { ...parsed, items: parsed?.items && typeof parsed.items === \"object\" ? parsed.items : {} }\n } catch {\n return { items: {} }\n }\n}\n\nfunction writeMirror(root: string, file: MirrorFile): void {\n const path = mirrorPath(root)\n if (!existsSync(dirname(path))) mkdirSync(dirname(path), { recursive: true })\n const tmp = `${path}.${process.pid}.tmp`\n writeFileSync(tmp, JSON.stringify(file, null, 2) + \"\\n\")\n renameSync(tmp, path)\n}\n\n/** A pass older than this means the mirror is not running. */\nexport const MIRROR_FRESH_MS = 5 * 60_000\n\n/** True when the mirror is running and covers this card: it has a task,\n * or gets one on the next pass, or the owner ticked its task off. The Mac\n * popup then leaves it alone, so the owner is not alerted twice. A card\n * whose task could not be added is not covered. */\nexport function mirrorCovers(root: string, cardId: string, now = Date.now()): boolean {\n const file = readMirror(root)\n const checked = file.checkedAt ? Date.parse(file.checkedAt) : NaN\n if (!file.cards || !(now - checked <= MIRROR_FRESH_MS)) return false\n return file.items[`card:${cardId}`]?.state !== \"failed\"\n}\n\n/** What one task says. */\nexport interface MirrorItem {\n key: string\n title: string\n notes: string\n due: Date\n priority: NativePriority\n}\n\nconst DAY_MS = 24 * 3_600_000\n\nfunction dueFor(now: number, settings: MirrorSettings, limit?: number): Date {\n const due = now + settings.dueAfterMinutes * 60_000\n return new Date(limit !== undefined && Number.isFinite(limit) ? Math.max(now, Math.min(due, limit)) : due)\n}\n\nexport function cardItem(card: DecisionCard, settings: MirrorSettings, answerUrl: string, now: number): MirrorItem {\n const expires = Date.parse(card.expires)\n const lines = [summarize(card.ask, CARD_LIMITS.ask)]\n if (card.choices?.length) lines.push(...card.choices.map((c, i) => `${i + 1}. ${c}`))\n if (card.recommend) lines.push(`Recommended: ${summarize(card.recommend, CARD_LIMITS.recommend)}`)\n lines.push(`Answer: ${answerUrl}`, `agentx: card=${card.id}`)\n return {\n key: `card:${card.id}`,\n title: summarize(card.title, CARD_LIMITS.title),\n notes: lines.join(\"\\n\"),\n due: dueFor(now, settings, expires),\n priority: Number.isFinite(expires) && expires - now <= DAY_MS ? \"high\" : \"none\",\n }\n}\n\nexport function requestItem(r: RequestRecord, settings: MirrorSettings, answerUrl: string, now: number): MirrorItem {\n const lines = [summarize(r.question || r.text, CARD_LIMITS.ask), `Asked of ${r.agentId}.`, `Answer: ${answerUrl}`, `agentx: request=${r.id}`]\n const dueAt = r.dueAt ?? undefined\n return {\n key: `request:${r.id}`,\n title: cardWords(r, CARD_LIMITS.title).headline,\n notes: lines.join(\"\\n\"),\n due: dueFor(now, settings, dueAt),\n priority: dueAt !== undefined && dueAt - now <= DAY_MS ? \"high\" : \"none\",\n }\n}\n\nexport interface MirrorDeps {\n root: string\n settings: MirrorSettings\n tasks: NativeTasks\n /** Pending cards on this node. */\n cards: () => DecisionCard[]\n /** Requests in waiting_owner. */\n requests: () => RequestRecord[]\n answerUrl: string\n /** Tell the owner, through the usual notification. */\n notify: (title: string, message: string) => Promise<void>\n log: (msg: string) => void\n now?: number\n}\n\nexport interface MirrorResult {\n created: number\n completed: number\n dismissed: number\n failed: number\n}\n\n/** One pass of the mirror. Never throws. */\nexport async function runMirror(deps: MirrorDeps): Promise<MirrorResult> {\n const { root, settings, tasks, log } = deps\n const now = deps.now ?? Date.now()\n const at = new Date(now).toISOString()\n const result: MirrorResult = { created: 0, completed: 0, dismissed: 0, failed: 0 }\n const file = readMirror(root)\n // lastError says how the latest pass went: a clean pass clears it.\n delete file.lastError\n const fail = (message: string) => { file.lastError = { at, message } }\n\n const waiting = new Map<string, MirrorItem>()\n try {\n if (settings.include.includes(\"card\")) {\n for (const c of deps.cards()) if (c.status === \"pending\" && Date.parse(c.expires) > now) waiting.set(`card:${c.id}`, cardItem(c, settings, deps.answerUrl, now))\n }\n if (settings.include.includes(\"request\")) {\n for (const r of deps.requests()) if (r.state === \"waiting_owner\") waiting.set(`request:${r.id}`, requestItem(r, settings, deps.answerUrl, now))\n }\n } catch (e: any) {\n log(`[mirror] could not read what is waiting: ${e?.message ?? e}`)\n fail(String(e?.message ?? e))\n writeMirror(root, file)\n return result\n }\n\n // Close or notice dismissal for what is already mirrored. The open list\n // is read once per list; when it can't be read, nothing is closed or\n // dismissed this pass, so a hiccup never loses a task.\n let allRead = true\n const openIds = new Map<string, Set<string> | null>()\n const openIn = async (list: string): Promise<Set<string> | null> => {\n if (!openIds.has(list)) {\n try {\n openIds.set(list, new Set((await tasks.listOpen(list)).map((t) => t.id)))\n } catch (e: any) {\n log(`[mirror] could not read list \"${list}\": ${e?.message ?? e}`)\n fail(String(e?.message ?? e))\n openIds.set(list, null)\n allRead = false\n }\n }\n return openIds.get(list)!\n }\n\n for (const [key, entry] of Object.entries(file.items)) {\n if (entry.state !== \"open\" || !entry.id) continue\n const open = await openIn(entry.list)\n if (!open) continue\n if (!waiting.has(key)) {\n if (open.has(entry.id)) {\n try {\n await tasks.complete(entry.id)\n } catch (e: any) {\n log(`[mirror] ${key}: could not complete its task: ${e?.message ?? e}`)\n fail(String(e?.message ?? e))\n continue\n }\n }\n file.items[key] = { ...entry, state: \"completed\", at }\n result.completed++\n } else if (!open.has(entry.id)) {\n file.items[key] = { ...entry, state: \"dismissed\", at }\n result.dismissed++\n log(`[mirror] ${key}: its task was ticked off or deleted; it stays on the Desk and is not mirrored again`)\n }\n }\n\n let listReady: boolean | null = null\n const gaveUp: { title: string; message: string }[] = []\n for (const [key, item] of waiting) {\n const entry = file.items[key]\n if (entry && (entry.state === \"open\" || entry.state === \"dismissed\")) continue\n if (entry?.state === \"failed\" && entry.failures >= MAX_ADD_FAILURES) continue\n // The list is made on the first add of a pass, when it is missing.\n if (listReady === null) {\n try {\n await tasks.ensureList(settings.list)\n listReady = true\n } catch (e: any) {\n log(`[mirror] could not make list \"${settings.list}\": ${e?.message ?? e}`)\n fail(String(e?.message ?? e))\n listReady = false\n }\n }\n const failures = entry?.state === \"failed\" ? entry.failures : 0\n try {\n const { id } = await tasks.add({ list: settings.list, title: item.title, notes: item.notes, due: item.due, priority: item.priority })\n file.items[key] = { id, list: settings.list, state: \"open\", at, failures: 0 }\n result.created++\n } catch (e: any) {\n const message = String(e?.message ?? e)\n const n = failures + 1\n file.items[key] = { list: settings.list, state: \"failed\", at, failures: n, lastError: message }\n fail(message)\n result.failed++\n log(`[mirror] ${key}: could not add to Reminders (${n}/${MAX_ADD_FAILURES}): ${message}`)\n if (n === MAX_ADD_FAILURES) gaveUp.push({ title: item.title, message })\n }\n }\n\n // One notification a day at most, however many items gave up: a missing\n // permission fails every card the same way.\n const told = file.failureToldAt ? Date.parse(file.failureToldAt) : NaN\n if (gaveUp.length && !(now - told < DAY_MS)) {\n const first = gaveUp[0]\n const more = gaveUp.length > 1 ? ` (and ${gaveUp.length - 1} more)` : \"\"\n try {\n await deps.notify(\"Could not add to Reminders\", `${first.title}${more}: ${summarize(first.message, 200)}. Still on the Desk.`)\n file.failureToldAt = at\n } catch (err: any) {\n log(`[mirror] could not tell the owner: ${err?.message ?? err}`)\n }\n }\n\n if (allRead) {\n file.checkedAt = at\n file.cards = settings.include.includes(\"card\")\n }\n\n for (const [key, entry] of Object.entries(file.items)) {\n if (entry.state !== \"open\" && !waiting.has(key) && now - Date.parse(entry.at) > KEEP_CLOSED_MS) delete file.items[key]\n }\n writeMirror(root, file)\n return result\n}\n","import { markdownToHtml } from \"@/utils/markdown-html\"\nimport type { RequestRecord, RequestStore } from \"./store\"\nimport type { PlanState, PlanStore, StepState } from \"./plan-store\"\nimport { describeCloseWhen } from \"./checkers\"\nimport { activeStepAgentOf, stalledWhy, waitsOn, type StallGroup } from \"./loops\"\n\n// --- One open request as a card the owner can read in seconds (#459) ---\n//\n// The Approvals page shows each open request as a card: a short plain\n// summary, where it stands and why it is not finished, then the words the\n// owner used and the agent's last answer, both rendered. markdownToHtml\n// escapes before it marks up, so nothing an agent or a channel wrote\n// reaches the page as HTML. The summary is the request's headline (#1126);\n// a request without one yet falls back to the start of its own words,\n// without the markdown marks.\n\nexport const SUMMARY_MAX = 200\n/** An answer of pages is cut: the card is for reading quickly. */\nexport const ANSWER_MAX = 4000\n\n/** What a request needs from the owner when nothing does. */\nexport const NOTHING_NEEDED = \"Nothing, just so you know.\"\n\n/** A request as every surface titles it (#1126): what it is, the one\n * thing needed from the owner, and the owner's own words. The need\n * follows where it stands: a request that needs attention or waits on\n * the owner's answer says so, whatever the agent wrote earlier. */\nexport function cardWords(\n r: Pick<RequestRecord, \"text\" | \"headline\" | \"need\" | \"state\" | \"question\" | \"agentId\">,\n max = SUMMARY_MAX,\n): { headline: string; need: string; said: string } {\n const headline = r.headline ? (r.headline.length > max ? `${r.headline.slice(0, max - 1)}…` : r.headline) : summarize(r.text, max)\n const need = r.state === \"needs_attention\" ? `Hand it back to ${r.agentId}, or drop it.`\n : r.state === \"waiting_owner\" ? (r.need ?? (r.question ? `Answer: ${summarize(r.question, 150)}` : \"Your answer.\"))\n : r.state === \"candidate\" || r.state === \"in_progress\" || r.state === \"waiting_other\" ? (r.need ?? NOTHING_NEEDED)\n : NOTHING_NEEDED\n return { headline, need, said: plain(r.text) }\n}\n\nexport interface RequestCard extends RequestRecord {\n summary: string\n /** The one thing needed from the owner, in words: the agent's `need`\n * or what follows from where the request stands (cardWords). */\n needed: string\n /** The request in the owner's words (the transcript, when spoken). */\n textHtml: string\n /** Why it is not finished, or what is being asked. Null: work is under way. */\n why: string | null\n ownerNoteHtml: string | null\n lastAnswer: { agentId: string; at: number; html: string } | null\n /** The open loop (#1039): who it is owed to, when it is due, what\n * closes it. Null on a request with none of it. */\n loop: {\n owedTo: string | null\n promise: boolean\n dueAt: number | null\n /** What its check waits for, in words. */\n check: string | null\n verifyState: string | null\n verifyDetail: string | null\n } | null\n /** Stalled, and what it waits on. Null: not stalled, or not asked. */\n stall: { group: StallGroup; who: string; why: string } | null\n /** Its tracked plan (#788), step by step. Null: no plan. */\n plan: {\n state: PlanState\n steps: Array<{ idx: number; name: string; kind: string; agentId: string; state: StepState; approval: string | null; evidence: string | null; note: string | null; nudges: number }>\n } | null\n}\n\n/** Markdown to the words alone, on one line. */\nexport function plain(md: string): string {\n return md\n .replace(/```[\\s\\S]*?```/g, \" \")\n .replace(/!?\\[([^\\]]*)\\]\\([^)]*\\)/g, \"$1\")\n .replace(/^\\s{0,3}(#{1,6}|>|[-*+]|\\d+[.)])\\s+/gm, \"\")\n .replace(/[*_~`]+/g, \"\")\n .replace(/\\s+/g, \" \")\n .trim()\n}\n\n/** The start of the request, cut at a sentence when one ends in reach. */\nexport function summarize(md: string, max = SUMMARY_MAX): string {\n const text = plain(md)\n if (text.length <= max) return text\n const cut = text.slice(0, max)\n const end = Math.max(cut.lastIndexOf(\". \"), cut.lastIndexOf(\"? \"), cut.lastIndexOf(\"! \"))\n return end > max / 2 ? cut.slice(0, end + 1) : `${cut.slice(0, max - 1).trimEnd()}…`\n}\n\n/** What the stall view needs: the quiet time, the clock, and which ids are agents. */\nexport interface StallLookup { staleAfterHours: number; now: number; isAgent: (id: string) => boolean }\n\nexport function requestCard(\n r: RequestRecord,\n store: Pick<RequestStore, \"lastAnswer\">,\n plans?: Pick<PlanStore, \"get\" | \"steps\"> | null,\n stall?: StallLookup,\n): RequestCard {\n const plan = plans?.get(r.id)\n const why = stall ? stalledWhy(r, stall.staleAfterHours, stall.now) : null\n const answer = store.lastAnswer(r.id)\n const cutAnswer = answer && answer.text.length > ANSWER_MAX ? `${answer.text.slice(0, ANSWER_MAX)}\\n\\n…` : answer?.text\n const words = cardWords(r)\n return {\n ...r,\n summary: words.headline,\n needed: words.need,\n textHtml: markdownToHtml(r.text),\n why: r.state === \"waiting_owner\" ? r.question : r.state === \"needs_attention\" ? r.attentionReason : null,\n ownerNoteHtml: r.ownerNote ? markdownToHtml(r.ownerNote) : null,\n lastAnswer: answer ? { agentId: answer.agentId, at: answer.at, html: markdownToHtml(cutAnswer!) } : null,\n loop: r.promise || r.dueAt !== null || r.closeWhen ? {\n owedTo: r.owedTo, promise: r.promise, dueAt: r.dueAt, check: r.closeWhen ? describeCloseWhen(r.closeWhen) : null,\n verifyState: r.verifyState, verifyDetail: r.verifyDetail,\n } : null,\n stall: stall && why ? { ...waitsOn(r, { isAgent: stall.isAgent, activeStepAgent: activeStepAgentOf(plans) }), why } : null,\n plan: plan ? {\n state: plan.state,\n steps: plans!.steps(r.id).map((s) => ({\n idx: s.idx, name: s.name, kind: s.kind, agentId: s.agentId, state: s.state,\n approval: s.needsApproval ? s.approval : null, evidence: s.evidence, note: s.note, nudges: s.nudges,\n })),\n } : null,\n }\n}\n","import { OPEN_STATES, OWED_TO_OWNER, type RequestRecord, type RequestStore } from \"./store\"\nimport { runCheck, type CheckContext, type CheckResult, type CloseWhen } from \"./checkers\"\n\n// --- Open loops: who each one waits on, and when it is stalled (#1039) ---\n//\n// A loop is an open request: one the owner asked for, or one an agent\n// promised (action promise). It is stalled when it is past its due time,\n// or, without one, when nothing has happened for staleAfterHours. A loop\n// that needs attention is stalled too: nothing is retrying it.\n//\n// Stalled loops are grouped by what they wait on:\n// owner the owner's answer (waiting_owner), or the owner's decision\n// on one that needs attention\n// agent an agent: the one that has it, a plan step's agent, or the\n// agent it is owed to\n// person an outside person: a promise owed to a known person\n// The requests panel, `agentx requests --stalled` and the sweep's notice\n// all use this one grouping.\n\nexport const STALL_GROUPS = [\"owner\", \"agent\", \"person\"] as const\nexport type StallGroup = (typeof STALL_GROUPS)[number]\n\nexport interface StalledLoop {\n request: RequestRecord\n group: StallGroup\n /** Who it waits on: \"owner\", an agent id or a person id. */\n who: string\n /** Why it counts as stalled, in words. */\n why: string\n}\n\nexport interface LoopLookups {\n isAgent: (id: string) => boolean\n /** The agent of the active step of the request's plan, if it has one. */\n activeStepAgent?: (requestId: string) => string | null\n}\n\nconst HOUR = 3_600_000\nconst isOpen = (r: RequestRecord) => (OPEN_STATES as readonly string[]).includes(r.state)\n\n/** The agent of each request's active plan step, from the plan store. */\nexport function activeStepAgentOf(plans: { get(id: string): { state: string } | null; steps(id: string): Array<{ state: string; agentId: string }> } | null | undefined) {\n return (requestId: string): string | null => {\n if (!plans || plans.get(requestId)?.state !== \"active\") return null\n return plans.steps(requestId).find((s) => s.state === \"active\")?.agentId ?? null\n }\n}\n\n/** What an open loop waits on. */\nexport function waitsOn(r: RequestRecord, look: LoopLookups): { group: StallGroup; who: string } {\n if (r.state === \"waiting_owner\") return { group: \"owner\", who: OWED_TO_OWNER }\n if (r.promise && r.owedTo && r.owedTo !== OWED_TO_OWNER && !look.isAgent(r.owedTo)) return { group: \"person\", who: r.owedTo }\n if (r.state === \"needs_attention\") return { group: \"owner\", who: OWED_TO_OWNER }\n const step = look.activeStepAgent?.(r.id)\n return { group: \"agent\", who: step ?? r.agentId }\n}\n\n/** Why an open loop is stalled, or null when it is not. */\nexport function stalledWhy(r: RequestRecord, staleAfterHours: number, now: number): string | null {\n if (!isOpen(r)) return null\n if (r.state === \"needs_attention\") return r.attentionReason ?? \"needs attention\"\n if (r.dueAt !== null) return now > r.dueAt ? `past its due time (${when(r.dueAt)})` : null\n return now - r.updatedAt > staleAfterHours * HOUR ? `no activity for ${staleAfterHours} h or more` : null\n}\n\n/** Stalled loops among `items`, oldest first, grouped. */\nexport function stalledLoops(items: RequestRecord[], staleAfterHours: number, now: number, look: LoopLookups): Record<StallGroup, StalledLoop[]> {\n const out: Record<StallGroup, StalledLoop[]> = { owner: [], agent: [], person: [] }\n for (const r of items) {\n const why = stalledWhy(r, staleAfterHours, now)\n if (!why) continue\n const { group, who } = waitsOn(r, look)\n out[group].push({ request: r, group, who, why })\n }\n return out\n}\n\n/** The group, in words, for people. */\nexport function waitsOnWords(group: StallGroup, who: string): string {\n return group === \"owner\" ? \"you\" : group === \"agent\" ? `agent ${who}` : `${who} (outside person)`\n}\n\nexport const when = (ms: number) => new Date(ms).toISOString().slice(0, 16).replace(\"T\", \" \") + \" UTC\"\n\n/** A due time an agent or the owner gave: epoch ms, or an ISO date.\n * Undefined when absent, a string when it is not a time. */\nexport function parseDue(v: unknown): number | undefined | string {\n if (v === undefined || v === null || v === \"\") return undefined\n const at = typeof v === \"number\" ? v : typeof v === \"string\" ? Date.parse(v.trim()) : NaN\n if (!Number.isFinite(at) || at <= 0) return \"due must be an ISO date (2030-01-31T17:00:00Z) or epoch milliseconds\"\n return at\n}\n\n// ── Running the checkers from the requests check ─────────────────────────\n\n/** Runs a loop's checker. The daemon's own; tests give their own. */\nexport type LoopChecker = (spec: CloseWhen, request: RequestRecord) => Promise<CheckResult>\n\nexport function loopChecker(ctx: () => CheckContext): LoopChecker {\n return (spec) => runCheck(spec, ctx())\n}\n\n/** How many checkers run at once, and how many loops one check takes on. */\nconst CHECK_BATCH = 10\n\n/** Run the checker of every open loop whose last check is older than\n * `everyMinutes`. Met closes it as done with the checker's evidence;\n * unverifiable marks it unverified; not met leaves it as it is. */\nexport async function checkLoops(\n store: RequestStore,\n check: LoopChecker,\n opts: { everyMinutes: number; now: number; log: (msg: string) => void },\n): Promise<{ checked: number; closed: number }> {\n const due = store.dueForCheck(opts.now - opts.everyMinutes * 60_000, CHECK_BATCH)\n let closed = 0\n await Promise.all(due.map(async (r) => {\n // Marked before the call: a check that hangs is not started again\n // at the next minute.\n store.markChecked(r.id, r.verifyState, r.verifyDetail ?? \"\", opts.now)\n const res = await check(r.closeWhen!, r).catch((e: any): CheckResult => ({ verdict: \"unverifiable\", detail: String(e?.message ?? e) }))\n if (res.verdict === \"met\") {\n if (store.closeVerified(r.id, res.detail, opts.now)) {\n closed++\n opts.log(`[requests] ${r.id} closed by its check: ${res.detail}`)\n }\n return\n }\n // A refused done stays \"failed\" until the check says otherwise.\n store.markChecked(r.id, res.verdict === \"unverifiable\" ? \"unverified\" : r.verifyState === \"failed\" ? \"failed\" : null, res.detail, opts.now)\n }))\n return { checked: due.length, closed }\n}\n"],"mappings":"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","names":["existsSync","readFileSync","dirname","join","fileURLToPath","homedir","DESKTOP_LABEL","DESKTOP_APP","HELPER_APP","desktopPlatformError","platform","arch","release","selectDesktopAgent","agents","requested","id","shellQuote","value","LAUNCHD_PATH","desktopPath","ffmpeg","override","unxml","s","ents","_","e","plistPathEnv","plist","m","isVoiceLoginItemFile","name","adoptLoginItem","dir","items","keep","remove","file","a","b","label","program","findOnPath","bin","path","isExecutable","desktopPlist","opts","xml","env","k","v","helperBuildArgs","icon","installedHelper","home","packageRoot","from","resolveHelper","root","installed","findHelper","accessSync","constants","execFile","STEP_TIMEOUT_MS","run","file","args","done","err","capture","stdout","output","resolve","reject","stderr","helperOf","io","findHelper","helperBannerStatus","path","capture","out","status","macosBanner","exec","run","title","message","helper","NOTES_SCRIPT","macosNotes","exec","output","folder","title","body","id","existsSync","readdirSync","SOUNDS","soundPath","name","path","existsSync","macosSound","io","exec","run","volume","readdirSync","f","SPEAKERS","speakersIn","ps","exceptParents","self","line","m","pid","ppid","comm","name","macosSpeech","io","capture","opts","ZWSP","argValue","v","unArg","listName","list","isoDue","d","macosTasks","command","exec","output","raw","lists","l","items","r","title","notes","due","priority","args","id","patch","executable","file","accessSync","constants","focus","s","readSystemFocus","macosAdapter","cfg","io","command","macosTasks","macosNotes","macosBanner","macosSound","macosSpeech","problems","isExec","findOnPath","noneAdapter","reason","FACTORIES","macosAdapter","loadNativeAdapter","cfg","opts","noneAdapter","platform","make","existsSync","homedir","isAbsolute","DEFAULT_LOCAL","localSettings","cfg","localAlert","settings","deps","native","loadNativeAdapter","title","message","steps","sound","quiet","ok","speech","waitMs","pollMs","until","r","patchLocal","current","patch","platform","next","name","icon","homedir","isAbsolute","existsSync","v","existsSync","mkdirSync","readFileSync","renameSync","writeFileSync","dirname","resolve","init_checkers","init_store","init_checkers","STALL_GROUPS","HOUR","isOpen","r","OPEN_STATES","activeStepAgentOf","plans","requestId","s","waitsOn","look","OWED_TO_OWNER","stalledWhy","staleAfterHours","now","when","stalledLoops","items","out","why","group","who","waitsOnWords","ms","parseDue","v","at","loopChecker","ctx","spec","runCheck","CHECK_BATCH","checkLoops","store","check","opts","due","closed","res","e","SUMMARY_MAX","ANSWER_MAX","NOTHING_NEEDED","cardWords","r","max","headline","summarize","need","plain","md","text","cut","end","requestCard","store","plans","stall","plan","why","stalledWhy","answer","cutAnswer","words","markdownToHtml","describeCloseWhen","waitsOn","activeStepAgentOf","s","MAX_ADD_FAILURES","KEEP_CLOSED_MS","mirrorPath","root","resolve","readMirror","parsed","readFileSync","writeMirror","file","path","existsSync","dirname","mkdirSync","tmp","writeFileSync","renameSync","MIRROR_FRESH_MS","mirrorCovers","cardId","now","checked","DAY_MS","dueFor","settings","limit","due","cardItem","card","answerUrl","expires","lines","summarize","CARD_LIMITS","c","i","requestItem","r","dueAt","cardWords","runMirror","deps","tasks","log","at","result","fail","message","waiting","e","allRead","openIds","openIn","list","t","key","entry","open","listReady","gaveUp","item","failures","id","n","told","first","more","err"]}
@@ -1,3 +0,0 @@
1
- import{a as f}from"./chunk-JWDJMFU6.js";import{mkdirSync as D,readFileSync as F,renameSync as H,writeFileSync as K}from"fs";import{dirname as J,resolve as M}from"path";function k(e,t=160){if(!e)return e;let r=e.replace(/\s+/g," ").trim();return r.length>t?r.slice(0,t-1)+"\u2026":r}function j(e){let t=new Map;for(let n of e.cronRuns??[]){let s=t.get(n.jobId);(!s||s.startedAt<n.startedAt)&&t.set(n.jobId,n)}let r={success:0,failed:0};for(let n of e.cronRuns??[])n.status==="success"?r.success++:r.failed++;return{id:e.id,name:e.name,url:e.url,reachable:e.reachable,error:k(e.error),uptimeSec:e.uptimeSec,inflight:e.inflight,restart:e.restart?.state&&e.restart.state!=="none"?e.restart:void 0,agents:e.agents.map(n=>({id:n.id,name:n.name,tier:n.tier,active:n.active,errors:n.errors,lastActive:n.lastActive,last:n.lastSummary?{text:k(n.lastSummary.text),at:n.lastSummary.at,ok:n.lastSummary.ok}:void 0})),crons:{today:r,items:(e.crons??[]).map(n=>{let s=t.get(n.id);return{id:n.id,enabled:n.enabled,schedule:n.schedule,agent:n.agent,nextRun:n.nextRun,consecutiveErrors:n.consecutiveErrors,last:s?{status:s.status,at:s.startedAt,text:k(s.status==="success"?s.responseSummary:s.errorSummary)}:void 0}})}}}function E(e){let t=[];for(let r of e)for(let n of r.agents)for(let s of n.runningTasks??[])t.push({node:r.url,nodeName:r.name,agentId:n.id,agentName:n.name,taskId:s.id,preview:k(s.messagePreview),channel:s.channel,startedAt:s.startedAt});return t.sort((r,n)=>r.startedAt<n.startedAt?1:-1)}var O=/^\/api\/app\/tasks\/(cancel|followup)$/;async function _(e,t,r,n,s,o){let a=new URL(e.url||r,"http://localhost");if(n==="GET"&&(r==="/api/app/fleet"||r==="/api/app/activity")){let c=$(a.searchParams.get("timezone")),l=await o.snapshot({date:U(c),timezone:c});return r==="/api/app/activity"?b(t,200,{ts:l.ts,tasks:E(l.nodes)}):b(t,200,{ts:l.ts,nodes:l.nodes.map(j)})}if(n==="GET"&&r==="/api/app/approvals")return b(t,200,{nodes:await o.approvals()});if(n!=="POST")return!1;let p=r.match(O);if(!(p||["/api/app/nodes/restart","/api/app/nodes/reload","/api/app/crons/toggle","/api/app/approvals/decide"].includes(r)))return!1;let i;try{i=await P(e)}catch(c){return b(t,400,{error:c.message})}let m=A(i.node);if(!m)return b(t,400,{error:"node is required"});let w=`operator (phone: ${s})`,u;if(p){let c=A(i.taskId);if(!c)return b(t,400,{error:"taskId is required"});let l=`/api/tasks/${encodeURIComponent(c)}/${p[1]}`;if(p[1]==="cancel")u=await o.nodePost(m,l,{reason:`cancelled by ${w}`});else{let y=A(i.message);if(!y)return b(t,400,{error:"message is required"});u=await o.nodePost(m,l,{message:y,sender:w})}}else if(r==="/api/app/nodes/restart")u=await o.nodePost(m,i.cancel===!0?"/daemon/restart/cancel":"/daemon/restart",{});else if(r==="/api/app/nodes/reload")u=await o.nodePost(m,"/reload",{});else if(r==="/api/app/crons/toggle"){let c=A(i.cronId);if(!c||typeof i.enabled!="boolean")return b(t,400,{error:"cronId and enabled (true or false) are required"});u=await o.nodePost(m,`/crons/${encodeURIComponent(c)}/enabled`,{enabled:i.enabled})}else{let c=A(i.key),l=i.action;if(!c||l!=="yes"&&l!=="no"&&l!=="later")return b(t,400,{error:"key and action (yes, no or later) are required"});let y=typeof i.choice=="number"?i.choice:A(i.choice),d=typeof i.text=="string"&&i.text.trim()?i.text:"",C={...y!==""?{choice:y}:{},...d?{text:d}:{}};u=await o.decide(m,c,l,w,C)}return b(t,u.status,u.body)}function A(e){return typeof e=="string"?e.trim():""}function $(e){if(!e)return"UTC";try{return new Intl.DateTimeFormat("en-US",{timeZone:e}),e}catch{return"UTC"}}function U(e,t=new Date){return new Intl.DateTimeFormat("en-CA",{timeZone:e,year:"numeric",month:"2-digit",day:"2-digit"}).format(t)}async function P(e,t=16*1024){let r=0,n=[];for await(let a of e){if(r+=a.length,r>t)throw new Error("body too large");n.push(a)}let s=Buffer.concat(n).toString("utf-8").trim();if(!s)return{};let o=JSON.parse(s);if(!o||typeof o!="object"||Array.isArray(o))throw new Error("expected a JSON object");return o}function b(e,t,r){return e.writeHead(t,{"Content-Type":"application/json","Cache-Control":"no-store"}),e.end(JSON.stringify(r)),!0}function I(e){return e==="/api/app/push"||e.startsWith("/api/app/push/")||e==="/api/app/alerts"}var G=2048,N=/^[A-Za-z0-9_-]{8,256}$/;async function Y(e,t,r,n,s,o){if(!I(r))return!1;let a={};if(n==="POST")try{a=await P(e)}catch(i){return R(t,400,{error:i.message})}let p={path:r,method:n,body:a,device:{id:s.id,name:s.name}};if(!o.forward){let i=B(p,o);return R(t,i.status,i.body)}let h;try{h=await o.forward(p),S.note(p,h)}catch(i){h=z(p,`${o.reason??"Can't reach the computer that sends notifications"}: ${i?.message??i}`)}return R(t,h.status,h.body)}var v=class{phones=new Map;file=null;persistTo(t){if(this.file!==t){this.file=t;try{let r=JSON.parse(F(t,"utf-8"));for(let[n,s]of Object.entries(r??{}))s&&typeof s=="object"&&typeof s.subscribed=="boolean"&&this.phones.set(n,{subscribed:s.subscribed,prefs:q(s.prefs)})}catch{}}}note(t,r){if(r.status!==200||!r.body||typeof r.body!="object")return;let n=r.body,s=this.phones.get(t.device.id)??{subscribed:!1,prefs:{}};if(t.method==="GET"&&t.path==="/api/app/push")s.subscribed=n.available===!0&&Number(n.subscriptions)>0,s.prefs=x(n);else if(t.path==="/api/app/push/prefs")s.prefs={...s.prefs,...x(n)};else if(t.path==="/api/app/push/subscribe")s.subscribed=!0;else if(t.path==="/api/app/push/unsubscribe")n.removed&&(s.subscribed=!1);else return;this.phones.set(t.device.id,s),this.save()}save(){if(this.file)try{D(J(this.file),{recursive:!0});let t=`${this.file}.${process.pid}.tmp`;K(t,JSON.stringify(Object.fromEntries(this.phones))+`
2
- `,{encoding:"utf-8",mode:384}),H(t,this.file)}catch{}}on(t,r){return this.phones.get(t)?.prefs[f[r].field]??f[r].default}finishOn(t){return!!this.phones.get(t)?.subscribed&&this.on(t,"finish")}},S=new v;function ee(){return S.persistTo(M(process.cwd(),".agentx/push-relayed.json")),S}function x(e){let t={};for(let r of Object.values(f))typeof e[r.field]=="boolean"&&(t[r.field]=e[r.field]);return t}function q(e){return e&&typeof e=="object"&&!Array.isArray(e)?x(e):{}}function z(e,t){return e.method==="GET"&&e.path==="/api/app/push"?{status:200,body:{available:!1,reason:t,publicKey:null,subscriptions:0,...T()}}:e.method==="GET"&&e.path==="/api/app/alerts"?{status:200,body:{items:[],error:t}}:{status:502,body:{error:t}}}function B(e,t){let{path:r,method:n,body:s,device:o}=e;if(!I(r))return{status:404,body:{error:"not found"}};let a=t.store(),p=a?t.publicKey():null,h=a?p?void 0:"This computer has no push keys yet. On it, run: agentx app push-keys":t.reason??"Notifications are not set up on this computer.";if(n==="GET"&&r==="/api/app/push")return g(200,{available:!h,reason:h??null,publicKey:p,subscriptions:a&&p?a.list(o.id).filter(u=>u.publicKey===p).length:0,...a?a.prefs.all(o.id):T()});if(n==="GET"&&r==="/api/app/alerts")return g(200,{items:a?a.recent(t.keepRecent,o.id):[]});if(n!=="POST"||r!=="/api/app/push/subscribe"&&r!=="/api/app/push/unsubscribe"&&r!=="/api/app/push/prefs")return g(404,{error:"not found"});if(!a||h)return g(503,{error:h});if(r==="/api/app/push/prefs"){let u=Object.keys(f).filter(d=>s[f[d].field]!==void 0),c=u.map(d=>f[d].field).join(", "),l=Object.values(f).map(d=>d.field).join(" or ");if(u.length===0)return g(400,{error:`send ${l}, true or false`});if(u.some(d=>typeof s[f[d].field]!="boolean"))return g(400,{error:`${c} must be true or false`});let y={};for(let d of u)a.prefs.set(o.id,d,s[f[d].field]),y[f[d].field]=s[f[d].field];return g(200,{ok:!0,...y})}let i=L(s.endpoint,t.allowedHosts);if(!i){let u="";try{u=new URL(String(s.endpoint)).hostname}catch{}return g(400,{error:`not a known push service${u?`: ${u}`:""} (see channels.push.allowedHosts)`})}if(r==="/api/app/push/unsubscribe")return g(200,{ok:!0,removed:a.unsubscribe(i,o.id)});let m=s.keys?.p256dh,w=s.keys?.auth;return typeof m!="string"||!N.test(m)||typeof w!="string"||!N.test(w)?g(400,{error:"keys.p256dh and keys.auth are required"}):s.publicKey!==void 0&&s.publicKey!==p?g(409,{error:"this computer's push keys changed; turn notifications on again"}):(a.subscribe({endpoint:i,p256dh:m,auth:w,deviceId:o.id,deviceName:o.name,publicKey:p}),g(200,{ok:!0}))}function g(e,t){return{status:e,body:t}}function T(){let e={};for(let t of Object.values(f))e[t.field]=t.default;return e}function L(e,t){if(typeof e!="string"||e.length>G)return null;try{let r=new URL(e);if(r.protocol!=="https:"||r.port||r.username||r.password)return null;let n=r.hostname.toLowerCase();return t.some(o=>{let a=o.toLowerCase().replace(/^\.+|\.+$/g,"");return!!a&&(n===a||n.endsWith("."+a))})?e:null}catch{return null}}function R(e,t,r){return e.writeHead(t,{"Content-Type":"application/json","Cache-Control":"no-store"}),e.end(JSON.stringify(r)),!0}export{_ as a,P as b,I as c,Y as d,S as e,ee as f,B as g};
3
- //# sourceMappingURL=chunk-YMX3QCV7.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/daemon/app-push.ts","../src/daemon/app-fleet.ts"],"sourcesContent":["import type { IncomingMessage, ServerResponse } from \"http\"\nimport { mkdirSync, readFileSync, renameSync, writeFileSync } from \"fs\"\nimport { dirname, resolve } from \"path\"\nimport type { TokenRecord } from \"./token-store\"\nimport type { PushStore } from \"@/channels/push-store\"\nimport { PUSH_PREFS, type PushPrefName } from \"@/channels/push-prefs\"\nimport { readJson } from \"./app-fleet\"\n\n// --- Phone app: notifications (/api/app/push*, /api/app/alerts) ---\n//\n// Phase 4 of the mobile epic. Runs behind the device-token check in\n// app-routes.ts. A phone subscribes here; the daemon's PushAdapter reads the\n// same table to deliver. Each subscription is tied to the phone's device\n// token, so `agentx app revoke` also stops that phone's notifications.\n//\n// On a node that relays pushes (channels.push.relayTo), `forward` is set and\n// every call goes to the push host instead, through this node's daemon and\n// the mesh (push-bridge.ts), so a phone paired with any node can turn\n// notifications on (#711).\n\nexport interface AppPushDeps {\n /** Null when this node doesn't send pushes itself; `reason` says why. */\n store: () => PushStore | null\n /** VAPID public key the browser subscribes with, or null without keys. */\n publicKey: () => string | null\n keepRecent: number\n /** channels.push.allowedHosts: push services a phone may subscribe with. */\n allowedHosts: string[]\n reason?: string\n /** Set on a relaying node: sends each call to the push host. */\n forward?: (call: AppPushCall) => Promise<AppPushResult>\n}\n\n/** One phone-app push request, as handled here or by the push host. */\nexport interface AppPushCall {\n path: string\n method: string\n body: Record<string, any>\n device: { id: string; name: string }\n}\n\nexport interface AppPushResult {\n status: number\n body: unknown\n}\n\n/** The routes this module answers. */\nexport function isAppPushPath(path: string): boolean {\n return path === \"/api/app/push\" || path.startsWith(\"/api/app/push/\") || path === \"/api/app/alerts\"\n}\n\nconst MAX_ENDPOINT = 2048\nconst B64URL = /^[A-Za-z0-9_-]{8,256}$/\n\nexport async function handleAppPush(\n req: IncomingMessage,\n res: ServerResponse,\n path: string,\n method: string,\n device: TokenRecord,\n deps: AppPushDeps,\n): Promise<boolean> {\n if (!isAppPushPath(path)) return false\n let body: Record<string, any> = {}\n if (method === \"POST\") {\n try { body = await readJson(req) } catch (e: any) { return json(res, 400, { error: e.message }) }\n }\n const call: AppPushCall = { path, method, body, device: { id: device.id, name: device.name } }\n if (!deps.forward) {\n const out = runAppPush(call, deps)\n return json(res, out.status, out.body)\n }\n let out: AppPushResult\n try {\n out = await deps.forward(call)\n relayedPush.note(call, out)\n } catch (e: any) {\n out = unreachable(call, `${deps.reason ?? \"Can't reach the computer that sends notifications\"}: ${e?.message ?? e}`)\n }\n return json(res, out.status, out.body)\n}\n\n/** On a relaying node: what the push host last said about each phone, for\n * the checks that must answer at once (a finish notification, the\n * announcement switch). Filled from the phone's own calls on their way\n * through, so it is as fresh as the phone's last look at Alerts. */\nexport class RelayedPushState {\n private phones = new Map<string, { subscribed: boolean; prefs: Record<string, boolean> }>()\n private file: string | null = null\n\n /** Keeps the state in `file` too, so a dashboard restart doesn't stop\n * finish notifications until the phone next opens Alerts. Loads it the\n * first time; later calls with the same file do nothing. */\n persistTo(file: string): void {\n if (this.file === file) return\n this.file = file\n try {\n const saved = JSON.parse(readFileSync(file, \"utf-8\")) as Record<string, any>\n for (const [id, p] of Object.entries(saved ?? {})) {\n if (p && typeof p === \"object\" && typeof p.subscribed === \"boolean\") {\n this.phones.set(id, { subscribed: p.subscribed, prefs: pickPrefsFrom(p.prefs) })\n }\n }\n } catch { /* none yet, or unreadable: start empty */ }\n }\n\n note(call: AppPushCall, out: AppPushResult): void {\n if (out.status !== 200 || !out.body || typeof out.body !== \"object\") return\n const body = out.body as Record<string, any>\n const phone = this.phones.get(call.device.id) ?? { subscribed: false, prefs: {} }\n if (call.method === \"GET\" && call.path === \"/api/app/push\") {\n phone.subscribed = body.available === true && Number(body.subscriptions) > 0\n phone.prefs = pickPrefs(body)\n } else if (call.path === \"/api/app/push/prefs\") {\n phone.prefs = { ...phone.prefs, ...pickPrefs(body) }\n } else if (call.path === \"/api/app/push/subscribe\") {\n phone.subscribed = true\n } else if (call.path === \"/api/app/push/unsubscribe\") {\n if (body.removed) phone.subscribed = false\n } else {\n return\n }\n this.phones.set(call.device.id, phone)\n this.save()\n }\n\n private save(): void {\n if (!this.file) return\n try {\n mkdirSync(dirname(this.file), { recursive: true })\n const tmp = `${this.file}.${process.pid}.tmp`\n writeFileSync(tmp, JSON.stringify(Object.fromEntries(this.phones)) + \"\\n\", { encoding: \"utf-8\", mode: 0o600 })\n renameSync(tmp, this.file)\n } catch { /* kept in memory; written again at the next change */ }\n }\n\n /** A switch as last seen, or its default. */\n on(deviceId: string, name: PushPrefName): boolean {\n return this.phones.get(deviceId)?.prefs[PUSH_PREFS[name].field] ?? PUSH_PREFS[name].default\n }\n\n /** Subscribed and wanting finish notifications, as far as last seen. */\n finishOn(deviceId: string): boolean {\n return !!this.phones.get(deviceId)?.subscribed && this.on(deviceId, \"finish\")\n }\n}\n\n/** One per dashboard process. */\nexport const relayedPush = new RelayedPushState()\n\n/** relayedPush, kept in .agentx/ of the folder the dashboard runs in. */\nexport function relayedPushHere(): RelayedPushState {\n relayedPush.persistTo(resolve(process.cwd(), \".agentx/push-relayed.json\"))\n return relayedPush\n}\n\nfunction pickPrefs(body: Record<string, any>): Record<string, boolean> {\n const out: Record<string, boolean> = {}\n for (const p of Object.values(PUSH_PREFS)) if (typeof body[p.field] === \"boolean\") out[p.field] = body[p.field]\n return out\n}\n\nfunction pickPrefsFrom(v: unknown): Record<string, boolean> {\n return v && typeof v === \"object\" && !Array.isArray(v) ? pickPrefs(v as Record<string, any>) : {}\n}\n\n/** What a relaying node answers when the push host can't be reached: the\n * Alerts card says why instead of failing to load. */\nfunction unreachable(call: AppPushCall, reason: string): AppPushResult {\n if (call.method === \"GET\" && call.path === \"/api/app/push\") {\n return { status: 200, body: { available: false, reason, publicKey: null, subscriptions: 0, ...prefDefaults() } }\n }\n if (call.method === \"GET\" && call.path === \"/api/app/alerts\") return { status: 200, body: { items: [], error: reason } }\n return { status: 502, body: { error: reason } }\n}\n\n/** Answers one call against this node's own subscriptions table. Used for\n * a phone paired here, and by the push host for a phone paired with a\n * relaying node (push-bridge.ts), whose device id is then scoped to it. */\nexport function runAppPush(call: AppPushCall, deps: AppPushDeps): AppPushResult {\n const { path, method, body, device } = call\n if (!isAppPushPath(path)) return { status: 404, body: { error: \"not found\" } }\n const store = deps.store()\n const publicKey = store ? deps.publicKey() : null\n const unavailable = !store\n ? deps.reason ?? \"Notifications are not set up on this computer.\"\n : !publicKey ? \"This computer has no push keys yet. On it, run: agentx app push-keys\" : undefined\n\n if (method === \"GET\" && path === \"/api/app/push\") {\n return ok(200, {\n available: !unavailable,\n reason: unavailable ?? null,\n publicKey,\n // Only rows made with the current key count, so a phone subscribed\n // before `push-keys --force` sees 0 and subscribes again.\n subscriptions: store && publicKey ? store.list(device.id).filter((s) => s.publicKey === publicKey).length : 0,\n // Per-phone switches (push-prefs.ts): chatFinish, announce.\n ...(store ? store.prefs.all(device.id) : prefDefaults()),\n })\n }\n if (method === \"GET\" && path === \"/api/app/alerts\") {\n return ok(200, { items: store ? store.recent(deps.keepRecent, device.id) : [] })\n }\n if (method !== \"POST\" || (path !== \"/api/app/push/subscribe\" && path !== \"/api/app/push/unsubscribe\" && path !== \"/api/app/push/prefs\")) {\n return ok(404, { error: \"not found\" })\n }\n if (!store || unavailable) return ok(503, { error: unavailable })\n\n if (path === \"/api/app/push/prefs\") {\n // Any of the switches, each true or false; answers with those it set.\n const changes = (Object.keys(PUSH_PREFS) as PushPrefName[]).filter((n) => body[PUSH_PREFS[n].field] !== undefined)\n const fields = changes.map((n) => PUSH_PREFS[n].field).join(\", \")\n const all = Object.values(PUSH_PREFS).map((p) => p.field).join(\" or \")\n if (changes.length === 0) return ok(400, { error: `send ${all}, true or false` })\n if (changes.some((n) => typeof body[PUSH_PREFS[n].field] !== \"boolean\")) return ok(400, { error: `${fields} must be true or false` })\n const out: Record<string, boolean> = {}\n for (const n of changes) {\n store.prefs.set(device.id, n, body[PUSH_PREFS[n].field])\n out[PUSH_PREFS[n].field] = body[PUSH_PREFS[n].field]\n }\n return ok(200, { ok: true, ...out })\n }\n const endpoint = validEndpoint(body.endpoint, deps.allowedHosts)\n if (!endpoint) {\n let host = \"\"\n try { host = new URL(String(body.endpoint)).hostname } catch { /* not a URL */ }\n return ok(400, { error: `not a known push service${host ? `: ${host}` : \"\"} (see channels.push.allowedHosts)` })\n }\n\n if (path === \"/api/app/push/unsubscribe\") {\n return ok(200, { ok: true, removed: store.unsubscribe(endpoint, device.id) })\n }\n const p256dh = body.keys?.p256dh\n const auth = body.keys?.auth\n if (typeof p256dh !== \"string\" || !B64URL.test(p256dh) || typeof auth !== \"string\" || !B64URL.test(auth)) {\n return ok(400, { error: \"keys.p256dh and keys.auth are required\" })\n }\n // An endpoint belongs to one browser. If another device had it (a phone\n // re-paired under a new name), the latest pairing takes it over.\n // The browser subscribed with the key GET /api/app/push just handed it;\n // `publicKey` in the body must match it, so a stale page can't store a\n // subscription made with replaced keys.\n if (body.publicKey !== undefined && body.publicKey !== publicKey) {\n return ok(409, { error: \"this computer's push keys changed; turn notifications on again\" })\n }\n store.subscribe({ endpoint, p256dh, auth, deviceId: device.id, deviceName: device.name, publicKey: publicKey! })\n return ok(200, { ok: true })\n}\n\nfunction ok(status: number, body: unknown): AppPushResult {\n return { status, body }\n}\n\nfunction prefDefaults(): Record<string, boolean> {\n const out: Record<string, boolean> = {}\n for (const p of Object.values(PUSH_PREFS)) out[p.field] = p.default\n return out\n}\n\n/** The endpoint if it is https on an allowed push-service host (or a\n * subdomain of one). The daemon POSTs to whatever is stored, so a name\n * that merely looks public (and may resolve inside the network) is refused. */\nexport function validEndpoint(v: unknown, allowedHosts: string[]): string | null {\n if (typeof v !== \"string\" || v.length > MAX_ENDPOINT) return null\n try {\n const u = new URL(v)\n if (u.protocol !== \"https:\" || u.port || u.username || u.password) return null\n const host = u.hostname.toLowerCase()\n const ok = allowedHosts.some((h) => {\n const allowed = h.toLowerCase().replace(/^\\.+|\\.+$/g, \"\")\n return !!allowed && (host === allowed || host.endsWith(\".\" + allowed))\n })\n return ok ? v : null\n } catch {\n return null\n }\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, 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; traceId?: 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 /** Cards: the ready-made answers; yes must pick one (#743). */\n choices?: string[]\n /** Cards: the suggested message, `{choice}` not filled in yet. */\n draft?: string\n /** A short excerpt of what is being decided, for the Desk (#959). */\n detail?: string\n createdAt?: string\n}\n\n/** A card's pick (1-based number or label) and the message as edited. */\nexport interface ChoiceAnswer { choice?: string | number; text?: string }\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, answer?: ChoiceAnswer): 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 const choice = typeof body.choice === \"number\" ? body.choice : str(body.choice)\n const text = typeof body.text === \"string\" && body.text.trim() ? body.text : \"\"\n const answer: ChoiceAnswer = { ...(choice !== \"\" ? { choice } : {}), ...(text ? { text } : {}) }\n reply = await deps.decide(node, key, action, by, answer)\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"],"mappings":"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","names":["mkdirSync","readFileSync","renameSync","writeFileSync","dirname","resolve","clip","text","max","flat","summarizeNode","n","lastRun","r","prev","today","a","c","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","choice","answer","v","tz","now","limit","size","chunks","chunk","raw","parsed","status","isAppPushPath","path","MAX_ENDPOINT","B64URL","handleAppPush","req","res","method","device","deps","body","readJson","e","json","call","out","runAppPush","relayedPush","unreachable","RelayedPushState","file","saved","readFileSync","id","p","pickPrefsFrom","phone","pickPrefs","mkdirSync","dirname","tmp","writeFileSync","renameSync","deviceId","name","PUSH_PREFS","relayedPushHere","resolve","v","reason","prefDefaults","store","publicKey","unavailable","ok","s","changes","n","fields","all","endpoint","validEndpoint","host","p256dh","auth","status","allowedHosts","u","h","allowed"]}
@@ -1,2 +0,0 @@
1
- import{a,b,c,d,e}from"./chunk-GRVTLUQ6.js";import"./chunk-VKEH4IBA.js";import"./chunk-BRK6M6LA.js";import"./chunk-UIBUZ2N3.js";import"./chunk-2QNT5JDS.js";import"./chunk-QIJLFPWE.js";export{b as applyConfigMutation,a as findConfigPath,d as getAtPath,c as setAtPath,e as unsetAtPath};
2
- //# sourceMappingURL=config-mutator-C5BMIBZO.js.map