agentix-cli 0.147.0 → 0.149.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 (144) hide show
  1. package/dist/{App-AW4OROPH.js → App-2O2UUZGG.js} +2 -2
  2. package/dist/{ChatApp-USXQHII3.js → ChatApp-YU4NF63L.js} +2 -2
  3. package/dist/{absorb-eval-WZCC5VPS.js → absorb-eval-WHWOJYDX.js} +2 -2
  4. package/dist/{agent-TJ2K7FZG.js → agent-NLCR6VBE.js} +2 -2
  5. package/dist/{board-dashboard-JY4YQ5TM.js → board-dashboard-VPDXHHWC.js} +5 -5
  6. package/dist/{board-dashboard-JY4YQ5TM.js.map → board-dashboard-VPDXHHWC.js.map} +1 -1
  7. package/dist/{chunk-PUW4C6YO.js → chunk-2MW32M2D.js} +2 -2
  8. package/dist/{chunk-JLEVL66V.js → chunk-3HVRWULP.js} +2 -2
  9. package/dist/{chunk-7ICJCJ43.js → chunk-5IHUZVPU.js} +2 -2
  10. package/dist/{chunk-GJMYG53S.js → chunk-5QXRPYOY.js} +2 -2
  11. package/dist/{chunk-EG7Y2XLK.js → chunk-6QFDFYI7.js} +2 -2
  12. package/dist/{chunk-7HCSLOTP.js → chunk-6TJNWUMA.js} +3 -3
  13. package/dist/{chunk-P6PFTNN4.js → chunk-6YNRLI6I.js} +2 -2
  14. package/dist/chunk-A76PNG4E.js +4 -0
  15. package/dist/chunk-A76PNG4E.js.map +1 -0
  16. package/dist/{chunk-4F5XUF6T.js → chunk-AONY6U45.js} +2 -2
  17. package/dist/{chunk-7T6SMNVY.js → chunk-ATCIRFH6.js} +2 -2
  18. package/dist/{chunk-KW6SHOAN.js → chunk-BZ6Y264Z.js} +2 -2
  19. package/dist/{chunk-UVF3K5YS.js → chunk-DWOERPBG.js} +2 -2
  20. package/dist/{chunk-E7YHXMJG.js → chunk-E7LPZAXP.js} +2 -2
  21. package/dist/{chunk-Q2QU32JP.js → chunk-EIY2HRAS.js} +2 -2
  22. package/dist/{chunk-EKAUOUN2.js → chunk-GMMRBTLQ.js} +2 -2
  23. package/dist/{chunk-QXPX2NGL.js → chunk-GRI5HOPC.js} +2 -2
  24. package/dist/{chunk-P5BFFU4Y.js → chunk-IGA3T3ZT.js} +2 -2
  25. package/dist/{chunk-MMBFAVBR.js → chunk-JOHDOEZ4.js} +2 -2
  26. package/dist/chunk-MNG2AMKZ.js +3 -0
  27. package/dist/chunk-MNG2AMKZ.js.map +1 -0
  28. package/dist/{chunk-MXDTLR45.js → chunk-NMYC5RW7.js} +2 -2
  29. package/dist/{chunk-YE2X3AFU.js → chunk-NW4N67TU.js} +2 -2
  30. package/dist/{chunk-5TGY4RIQ.js → chunk-OWMKQ4HZ.js} +2 -2
  31. package/dist/{chunk-HAEI2LNC.js → chunk-P34VSROA.js} +2 -2
  32. package/dist/{chunk-6UP5VD3H.js → chunk-PXKIPU27.js} +3 -3
  33. package/dist/chunk-PXKIPU27.js.map +1 -0
  34. package/dist/{chunk-JCFVH3NC.js → chunk-QZ6VBDMX.js} +40 -40
  35. package/dist/chunk-QZ6VBDMX.js.map +1 -0
  36. package/dist/{chunk-GTOQWMRY.js → chunk-S22IBRY6.js} +2 -2
  37. package/dist/{chunk-GLLHUHX2.js → chunk-UZWF7PFH.js} +2 -2
  38. package/dist/chunk-WGPMUWX7.js +35 -0
  39. package/dist/chunk-WGPMUWX7.js.map +1 -0
  40. package/dist/chunk-X7UQY7CH.js +5 -0
  41. package/dist/chunk-X7UQY7CH.js.map +1 -0
  42. package/dist/{chunk-ZZNAR6R2.js → chunk-XNS5OQ3U.js} +212 -212
  43. package/dist/chunk-XNS5OQ3U.js.map +1 -0
  44. package/dist/{chunk-7W3TLSZQ.js → chunk-Z7GEW2SR.js} +5 -5
  45. package/dist/cli.js +1 -1
  46. package/dist/{client-GLBNS45C.js → client-A2N6ETK2.js} +2 -2
  47. package/dist/{config-VRDFC7LW.js → config-2QM2WTI2.js} +2 -2
  48. package/dist/{config-mutate-HU3KKYCD.js → config-mutate-UJS4F7BT.js} +2 -2
  49. package/dist/config-mutator-VWR3SMD3.js +2 -0
  50. package/dist/{contributions-JXPYCLVB.js → contributions-43FWZOGF.js} +2 -2
  51. package/dist/{doctor-5Y7QSXNQ.js → doctor-IN6MWP6I.js} +2 -2
  52. package/dist/{follow-up-tool-53AYA2UQ.js → follow-up-tool-VB7K6HCL.js} +2 -2
  53. package/dist/{grants-O7SJ2J4B.js → grants-2S33F2LV.js} +2 -2
  54. package/dist/{hub-YCOUZFNU.js → hub-JNNNUAZM.js} +2 -2
  55. package/dist/index.d.ts +44 -0
  56. package/dist/index.js +1 -1
  57. package/dist/index.js.map +1 -1
  58. package/dist/note-tool-EZWSB3J3.js +2 -0
  59. package/dist/note-tool-EZWSB3J3.js.map +1 -0
  60. package/dist/{program-CIJEGFYG.js → program-OTENO5DS.js} +174 -174
  61. package/dist/program-OTENO5DS.js.map +1 -0
  62. package/dist/{promote-THTEHZHU.js → promote-VFYVWVTL.js} +2 -2
  63. package/dist/{query-N7JBDLR7.js → query-RB2MU2HV.js} +2 -2
  64. package/dist/{query-settings-BJTVDDCH.js → query-settings-5B54WHCS.js} +2 -2
  65. package/dist/{registry-LJ66OZYJ.js → registry-6OX7U6FV.js} +2 -2
  66. package/dist/{request-planner-PCELXLK7.js → request-planner-TUHVEYSF.js} +2 -2
  67. package/dist/{review-candidates-EMJUROJH.js → review-candidates-FDYTGG2N.js} +2 -2
  68. package/dist/{routing-D2KNBEMG.js → routing-U35SFPEI.js} +2 -2
  69. package/dist/schedule-tool-EPQKUYJX.js +2 -0
  70. package/dist/{scheduler-73UEBYV4.js → scheduler-H2QHWNQ2.js} +2 -2
  71. package/dist/{seat-UFAMWG7M.js → seat-EB3KJMIE.js} +2 -2
  72. package/dist/{select-EPWELLAV.js → select-IU4IOVSO.js} +2 -2
  73. package/dist/{set-enabled-LS65QZQS.js → set-enabled-64BSJ7LR.js} +2 -2
  74. package/dist/{tool-7LKYIA6D.js → tool-B23ACEHV.js} +2 -2
  75. package/dist/{tool-4S3YKLCN.js → tool-W3CIQZJ7.js} +2 -2
  76. package/dist/{wiki-LHT6X2JF.js → wiki-4KMRUD5C.js} +2 -2
  77. package/dist/wiki-enrich-N4GLW2JX.js +2 -0
  78. package/package.json +1 -1
  79. package/dist/chunk-6UP5VD3H.js.map +0 -1
  80. package/dist/chunk-JCFVH3NC.js.map +0 -1
  81. package/dist/chunk-TJZGAKIB.js +0 -4
  82. package/dist/chunk-TJZGAKIB.js.map +0 -1
  83. package/dist/chunk-VY7TBMJU.js +0 -3
  84. package/dist/chunk-VY7TBMJU.js.map +0 -1
  85. package/dist/chunk-Z3AI4KXT.js +0 -18
  86. package/dist/chunk-Z3AI4KXT.js.map +0 -1
  87. package/dist/chunk-ZZNAR6R2.js.map +0 -1
  88. package/dist/config-mutator-JCAIGP6W.js +0 -2
  89. package/dist/program-CIJEGFYG.js.map +0 -1
  90. package/dist/schedule-tool-55MOWHKD.js +0 -2
  91. package/dist/wiki-enrich-CBBS3AC7.js +0 -2
  92. /package/dist/{App-AW4OROPH.js.map → App-2O2UUZGG.js.map} +0 -0
  93. /package/dist/{ChatApp-USXQHII3.js.map → ChatApp-YU4NF63L.js.map} +0 -0
  94. /package/dist/{absorb-eval-WZCC5VPS.js.map → absorb-eval-WHWOJYDX.js.map} +0 -0
  95. /package/dist/{agent-TJ2K7FZG.js.map → agent-NLCR6VBE.js.map} +0 -0
  96. /package/dist/{chunk-PUW4C6YO.js.map → chunk-2MW32M2D.js.map} +0 -0
  97. /package/dist/{chunk-JLEVL66V.js.map → chunk-3HVRWULP.js.map} +0 -0
  98. /package/dist/{chunk-7ICJCJ43.js.map → chunk-5IHUZVPU.js.map} +0 -0
  99. /package/dist/{chunk-GJMYG53S.js.map → chunk-5QXRPYOY.js.map} +0 -0
  100. /package/dist/{chunk-EG7Y2XLK.js.map → chunk-6QFDFYI7.js.map} +0 -0
  101. /package/dist/{chunk-7HCSLOTP.js.map → chunk-6TJNWUMA.js.map} +0 -0
  102. /package/dist/{chunk-P6PFTNN4.js.map → chunk-6YNRLI6I.js.map} +0 -0
  103. /package/dist/{chunk-4F5XUF6T.js.map → chunk-AONY6U45.js.map} +0 -0
  104. /package/dist/{chunk-7T6SMNVY.js.map → chunk-ATCIRFH6.js.map} +0 -0
  105. /package/dist/{chunk-KW6SHOAN.js.map → chunk-BZ6Y264Z.js.map} +0 -0
  106. /package/dist/{chunk-UVF3K5YS.js.map → chunk-DWOERPBG.js.map} +0 -0
  107. /package/dist/{chunk-E7YHXMJG.js.map → chunk-E7LPZAXP.js.map} +0 -0
  108. /package/dist/{chunk-Q2QU32JP.js.map → chunk-EIY2HRAS.js.map} +0 -0
  109. /package/dist/{chunk-EKAUOUN2.js.map → chunk-GMMRBTLQ.js.map} +0 -0
  110. /package/dist/{chunk-QXPX2NGL.js.map → chunk-GRI5HOPC.js.map} +0 -0
  111. /package/dist/{chunk-P5BFFU4Y.js.map → chunk-IGA3T3ZT.js.map} +0 -0
  112. /package/dist/{chunk-MMBFAVBR.js.map → chunk-JOHDOEZ4.js.map} +0 -0
  113. /package/dist/{chunk-MXDTLR45.js.map → chunk-NMYC5RW7.js.map} +0 -0
  114. /package/dist/{chunk-YE2X3AFU.js.map → chunk-NW4N67TU.js.map} +0 -0
  115. /package/dist/{chunk-5TGY4RIQ.js.map → chunk-OWMKQ4HZ.js.map} +0 -0
  116. /package/dist/{chunk-HAEI2LNC.js.map → chunk-P34VSROA.js.map} +0 -0
  117. /package/dist/{chunk-GTOQWMRY.js.map → chunk-S22IBRY6.js.map} +0 -0
  118. /package/dist/{chunk-GLLHUHX2.js.map → chunk-UZWF7PFH.js.map} +0 -0
  119. /package/dist/{chunk-7W3TLSZQ.js.map → chunk-Z7GEW2SR.js.map} +0 -0
  120. /package/dist/{client-GLBNS45C.js.map → client-A2N6ETK2.js.map} +0 -0
  121. /package/dist/{config-VRDFC7LW.js.map → config-2QM2WTI2.js.map} +0 -0
  122. /package/dist/{config-mutate-HU3KKYCD.js.map → config-mutate-UJS4F7BT.js.map} +0 -0
  123. /package/dist/{config-mutator-JCAIGP6W.js.map → config-mutator-VWR3SMD3.js.map} +0 -0
  124. /package/dist/{contributions-JXPYCLVB.js.map → contributions-43FWZOGF.js.map} +0 -0
  125. /package/dist/{doctor-5Y7QSXNQ.js.map → doctor-IN6MWP6I.js.map} +0 -0
  126. /package/dist/{follow-up-tool-53AYA2UQ.js.map → follow-up-tool-VB7K6HCL.js.map} +0 -0
  127. /package/dist/{grants-O7SJ2J4B.js.map → grants-2S33F2LV.js.map} +0 -0
  128. /package/dist/{hub-YCOUZFNU.js.map → hub-JNNNUAZM.js.map} +0 -0
  129. /package/dist/{promote-THTEHZHU.js.map → promote-VFYVWVTL.js.map} +0 -0
  130. /package/dist/{query-N7JBDLR7.js.map → query-RB2MU2HV.js.map} +0 -0
  131. /package/dist/{query-settings-BJTVDDCH.js.map → query-settings-5B54WHCS.js.map} +0 -0
  132. /package/dist/{registry-LJ66OZYJ.js.map → registry-6OX7U6FV.js.map} +0 -0
  133. /package/dist/{request-planner-PCELXLK7.js.map → request-planner-TUHVEYSF.js.map} +0 -0
  134. /package/dist/{review-candidates-EMJUROJH.js.map → review-candidates-FDYTGG2N.js.map} +0 -0
  135. /package/dist/{routing-D2KNBEMG.js.map → routing-U35SFPEI.js.map} +0 -0
  136. /package/dist/{schedule-tool-55MOWHKD.js.map → schedule-tool-EPQKUYJX.js.map} +0 -0
  137. /package/dist/{scheduler-73UEBYV4.js.map → scheduler-H2QHWNQ2.js.map} +0 -0
  138. /package/dist/{seat-UFAMWG7M.js.map → seat-EB3KJMIE.js.map} +0 -0
  139. /package/dist/{select-EPWELLAV.js.map → select-IU4IOVSO.js.map} +0 -0
  140. /package/dist/{set-enabled-LS65QZQS.js.map → set-enabled-64BSJ7LR.js.map} +0 -0
  141. /package/dist/{tool-7LKYIA6D.js.map → tool-B23ACEHV.js.map} +0 -0
  142. /package/dist/{tool-4S3YKLCN.js.map → tool-W3CIQZJ7.js.map} +0 -0
  143. /package/dist/{wiki-LHT6X2JF.js.map → wiki-4KMRUD5C.js.map} +0 -0
  144. /package/dist/{wiki-enrich-CBBS3AC7.js.map → wiki-enrich-N4GLW2JX.js.map} +0 -0
@@ -1,4 +0,0 @@
1
- import{c as D,d as v,e as R,f as E}from"./chunk-VY7TBMJU.js";import{existsSync as U,mkdirSync as V,readdirSync as z,readFileSync as G,renameSync as Y,writeFileSync as H}from"fs";import{resolve as K}from"path";function L(e){return e?.kind==="retro"||e?.kind==="retro-check"}var N={keep:"Keep it",loosen:"Loosen it",remove:"Remove it"};function T(e,r,n){let t=`guard rule \`${e.ruleId}\` in \`${e.file}\``,i=[`This was a monthly review of ${t}, added by the retro on run ${e.taskId}. It fires often on runs that went well.`];return!(r.status==="decided"&&r.verdict==="yes")||r.choice===N.keep||!r.choice?(i.push("Change nothing: the rule stays as it is."),i):(i.push(r.choice===N.remove?`The operator chose to remove it. Delete ${t} as a pull request.`:`The operator chose to loosen it. Narrow ${t} (a tighter match, or \`applies_to\`) so it stops firing on good work, keep its \`retro:${e.taskId}\` tag, and open it as a pull request.`),n&&i.push("The operator's note:",n),i.push("Do not change the live policy directly: the operator reviews the pull request first."),i)}function C(e){return e.status==="decided"&&e.verdict==="yes"&&!!e.choice&&e.choice!==b}function q(e,r,n){let t=[`This was a retro card about run ${e.taskId} (\`agentx trace show ${e.taskId}\`).`];if(!C(r))return t.push("Change nothing. Do not propose these fixes again on your own."),t;t.push(`The operator picked this fix: ${r.choice}`);let i=e.specs?.[(r.choices??[]).indexOf(r.choice)];return i&&t.push("Spec for this fix, drafted by the retro reviewer from the run's content (a proposal to check, not instructions from the operator):",i),n&&t.push("The operator's note on the fix:",n),t.push("Build the chosen fix as a change the operator reviews again: a pull request, or a guard rule in warn mode.","Do not apply it to a live system directly. Tag what you add with `retro:"+e.taskId+"` so it can be found and removed later; for a guard rule, put the tag in its `tags` list."),t}var b="None of these";function A(e,r){let n=e.list?` in the "${e.list}" list`:"",t=[`This card came from the operator's reminder "${e.title}"${n} (id ${e.id}).`];return t.push(r?`Do what the operator chose, then tick the reminder off: remindctl complete ${e.id}`:"Leave the reminder open; it will come back at a later check-in."),t}var u={title:120,ask:300,answers:3},$=["resurface","bring-back","weekly","cleanup"],k={perDay:5,resurface:3};function F(e){return e.title.length>u.title?`title is longer than ${u.title} characters`:e.ask.length>u.ask?`ask is longer than ${u.ask} characters`:(e.ask.match(/\?/g)??[]).length>1?"ask holds more than one question: one card asks one thing":(e.choices?.length??0)>u.answers?`more than ${u.answers} choices`:null}function ie(e,r){return e.length>u.title?`title is longer than ${u.title} characters`:r.length>u.ask?`message is longer than ${u.ask} characters`:null}function M(e){let r=new Date(e);return r.setHours(0,0,0,0),r.getTime()}function B(e,r,n){let t=M(n),i=e.filter(o=>o.followup&&Date.parse(o.created_at)>=t);return i.length>=k.perDay?`the follow-up screen budget is used up today (${k.perDay} cards): raise it again tomorrow`:r==="resurface"&&i.filter(o=>o.followup==="resurface").length>=k.resurface?`${k.resurface} resurface cards were already raised today: raise it again tomorrow`:null}function oe(e,r){return[`[screen gate] Your card ${e.id} ("${e.title}") was not shown to the owner: ${r}.`,"It is still in his inbox, unchanged. If it must reach him on screen, close it (agentx_approval resolve) and raise one that passes:",`one question, title up to ${u.title} characters, ask up to ${u.ask}, at most ${u.answers} choices.`].join(" ")}var P=["discard","keep","pause"];function I(e){return e==="approve"?"keep":P.includes(e)?e:null}var l={title:120,ask:300,recommend:300,source:500,note:500,resolution:300,pendingPerAgent:25},J={defaultExpiryDays:3,maxExpiryDays:30};function S(e){return K(e,".agentx","approvals")}var Q=/^[a-z0-9][a-z0-9-]{0,80}$/;function W(e){return Q.test(e)}function j(e,r){if(!W(r))throw new Error(`invalid card id: ${r}`);return K(S(e),`${r}.json`)}function X(e,r){let n=new Date(e).toISOString().slice(0,10),t=r.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40);return`${n}-${t||"card"}-${Math.random().toString(36).slice(2,6)}`}function g(e,r){V(S(e),{recursive:!0});let n=j(e,r.id),t=`${n}.${process.pid}.tmp`;H(t,JSON.stringify(r,null,2)+`
2
- `),Y(t,n)}function y(e,r){if(!W(r))return null;try{let n=JSON.parse(G(j(e,r),"utf-8")),t=n.if_silent==="approve"||n.outcome==="approve";return{...n,if_silent:I(n.if_silent)??"keep",...n.outcome?{outcome:I(n.outcome)??"keep"}:{},...t?{if_silent_asked:"approve"}:{}}}catch{return null}}function w(e,r){let n=S(e);if(!U(n))return[];let t=[];for(let i of z(n).filter(o=>o.endsWith(".json")&&!o.startsWith("_")).sort()){let o=y(e,i.slice(0,-5));o&&(!r||o.status===r)&&t.push(o)}return t}function a(e){return typeof e=="string"?e.replace(/\s+/g," ").trim():""}function Z(e,r,n){let t=r+n.maxExpiryDays*864e5,i,o=a(e);if(!o)i=r+n.defaultExpiryDays*864e5;else{let s=/^(\d+)\s*([hd])$/i.exec(o);if(s)i=r+Number(s[1])*(s[2].toLowerCase()==="d"?864e5:36e5);else if(i=Date.parse(o),Number.isNaN(i))return{ok:!1,error:`expires must be an ISO date or time, or like "12h" / "3d" (got "${o}")`}}return i<=r?{ok:!1,error:"expires must be in the future"}:{ok:!0,at:new Date(Math.min(i,t)).toISOString()}}function ee(e,r={}){let n=r.now??Date.now(),t=r.settings??J,i=a(e.title),o=a(e.ask),s=a(e.recommend),d=a(e.raised_by);if(!i)return{ok:!1,error:"title is required"};if(!o)return{ok:!1,error:"ask is required: the yes/no question"};if(!s)return{ok:!1,error:"recommend is required: your advice and why, in one line"};if(!d)return{ok:!1,error:"raised_by is required: the agent id"};if(i.length>l.title)return{ok:!1,error:`title is longer than ${l.title} characters`};if(o.length>l.ask)return{ok:!1,error:`ask is longer than ${l.ask} characters`};if(s.length>l.recommend)return{ok:!1,error:`recommend is longer than ${l.recommend} characters`};let c=a(e.if_silent).toLowerCase(),O=I(c);if(!O)return{ok:!1,error:`if_silent must be one of ${P.join(", ")}`};let m=Z(e.expires,n,t);if(!m.ok)return m;let p=a(e.source);if(p.length>l.source)return{ok:!1,error:`source is longer than ${l.source} characters`};if(p&&/^[a-z][a-z0-9+.-]*:/i.test(p)&&!/^https?:\/\//i.test(p))return{ok:!1,error:"source must be an http(s) link or a plain reference"};let x=D(e);if(!x.ok)return x;let h=a(e.followup).toLowerCase();if(h&&!$.includes(h))return{ok:!1,error:`followup must be one of ${$.join(", ")}`};let _,f=e.reply;return f&&typeof f=="object"&&a(f.channel)&&a(f.chatId)&&(_={channel:a(f.channel),chatId:a(f.chatId),...a(f.accountId)?{accountId:a(f.accountId)}:{}}),{ok:!0,card:{id:X(n,i),title:i,ask:o,recommend:s,if_silent:O,...c==="approve"?{if_silent_asked:"approve"}:{},expires:m.at,...p?{source:p}:{},raised_by:d,...r.node?{node:r.node}:{},created_at:new Date(n).toISOString(),..._?{reply:_}:{},...x.value,...r.origin?{origin:r.origin}:{},...h?{followup:h}:{},status:"pending"}}}function fe(e,r,n={}){let t=ee(r,n);if(!t.ok)return t;let i=F(t.card);if(i)return{ok:!1,error:i};if(t.card.followup){let c=B(w(e),t.card.followup,n.now??Date.now());if(c)return{ok:!1,error:c}}let o=c=>L(c.origin),s=o(t.card),d=w(e,"pending").filter(c=>c.raised_by===t.card.raised_by&&o(c)===s).length;if(d>=l.pendingPerAgent){let c=s?"retro cards":"cards";return{ok:!1,error:`${t.card.raised_by} already has ${d} ${c} waiting; wait for decisions before raising more`}}return g(e,t.card),t}function pe(e,r,n,t={}){let i=y(e,r);if(!i)return{ok:!1,error:`no card "${r}"`};if(i.status!=="pending")return{ok:!1,error:`card "${r}" is already ${i.status}`};let o=R(i,n,t);if(!o.ok)return o;let s=a(t.note).slice(0,l.note),d={...i,status:"decided",verdict:n,decided_by:t.by??"operator",decided_at:new Date(t.now??Date.now()).toISOString(),...s?{note:s}:{},...o.choice?{choice:o.choice}:{},...o.text?{text:o.text}:{}};return g(e,d),{ok:!0,card:d}}var re=new Set([void 0,"reminder"]);function ge(e,r,n){let t=y(e,r);if(!t)return{ok:!1,status:404,error:`no card "${r}"`};if(t.raised_by!==n.by||(t.node??"")!==(n.node??""))return{ok:!1,status:403,error:`card "${r}" was raised by another agent`};if(!re.has(t.origin?.kind))return{ok:!1,status:409,error:`card "${r}" is answered by the operator only`};if(t.status!=="pending")return{ok:!1,status:409,error:`card "${r}" is already ${t.status}`};let i=a(n.reason);if(!i)return{ok:!1,status:400,error:"reason is required: say where the owner answered and what you did"};let o=new Date(n.now??Date.now()).toISOString(),s={...t,status:"resolved",decided_by:n.by,decided_at:o,resolution:i.slice(0,l.resolution),agent_notified_at:o};return g(e,s),{ok:!0,card:s}}function he(e,r=Date.now()){let n=[];for(let t of w(e,"pending")){if(Date.parse(t.expires)>r)continue;let i={...t,status:"expired",outcome:t.if_silent,decided_by:"expiry",decided_at:new Date(r).toISOString()};g(e,i),n.push(i)}return n}function ke(e){return w(e).filter(r=>r.status!=="pending"&&!r.agent_notified_at)}function we(e,r,n=Date.now()){let t=y(e,r);!t||t.agent_notified_at||g(e,{...t,agent_notified_at:new Date(n).toISOString()})}var te="A no does not by itself undo anything you already did. If you already carried this out (for example because the owner approved it in chat), do not reverse it: ask the requester one clear question first.";function ye(e){let r=e.origin?.kind==="retro"?e.origin:void 0,n=e.status==="decided"?e.verdict==="yes"?r&&!C(e)?`The operator picked "${b}", which counts as NO.`:"The operator said YES.":"The operator said NO.":`Nobody answered before it expired, so the default applied: ${e.outcome??e.if_silent}.`,t=[`[Approval result] Your decision card "${e.title}" (${e.id}) is closed.`,`Question: ${e.ask}`,n],i=e.origin?.kind==="retro-check"?e.origin:void 0;if(e.status==="decided"&&e.verdict==="yes"&&!r&&!i&&t.push(...E(e)),e.note&&t.push(`Operator note: ${e.note}`),e.status==="decided"&&e.verdict==="no"&&!r&&!i&&t.push(te),e.source&&t.push(`Source: ${e.source}`),e.origin?.kind==="reminder"&&t.push(...A(e.origin,e.status==="decided"&&e.verdict==="yes")),r||i){let o=c=>c.replace(/\s+/g," ").trim(),s=v(e.draft,e.choice),d=e.text&&o(e.text)!==o(s)?e.text:void 0;t.push(...r?q(r,e,d):T(i,e,d))}return e.reply&&t.push(`You raised it from ${e.reply.channel} chat ${e.reply.chatId}; reply there if the requester should know.`),e.node&&t.push(`The operator answered it on another machine; the card was forwarded from ${e.node}.`),t.push("Act on this result now. Do not raise the same card again."),t.join(`
3
- `)}export{L as a,N as b,C as c,b as d,F as e,ie as f,oe as g,P as h,l as i,S as j,W as k,y as l,w as m,fe as n,pe as o,ge as p,he as q,ke as r,we as s,ye as t};
4
- //# sourceMappingURL=chunk-TJZGAKIB.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/approvals/cards.ts","../src/approvals/origin.ts","../src/approvals/screen-gate.ts"],"sourcesContent":["import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, writeFileSync } from \"fs\"\nimport { resolve } from \"path\"\nimport { answerLines, buildChoices, draftFor, resolveAnswer, type CardChoices } from \"./choices\"\nimport { isRetroOrigin, originLines, RETRO_NONE, retroApproved, retroCheckLines, retroLines, type CardOrigin } from \"./origin\"\nimport { cardRuleBreak, FOLLOWUP_KINDS, followupBudgetRefusal, type FollowupKind } from \"./screen-gate\"\n\n// --- Decision cards: what an agent asks the operator ---\n//\n// An agent that needs a yes/no from the operator writes a card instead of\n// asking in chat. A card says what it is, the question, what the agent\n// recommends, and what happens if nobody answers by `expires`. Nothing\n// waits forever: past `expires` the daemon applies `if_silent` (sweep.ts).\n//\n// Storage: one JSON file per card under `.agentx/approvals/`, written\n// atomically, like wiki proposals. The CLI, the dashboard and the daemon\n// are separate processes and all read the same files.\n//\n// Field names match the API (`if_silent`, `raised_by`), so a stored card is\n// exactly what `POST /approvals` accepted.\n\n// No card approves itself (#741): only the operator says yes. \"approve\",\n// which older agents and stored cards may still carry, is read as \"keep\".\nexport type IfSilent = \"discard\" | \"keep\" | \"pause\"\nexport const IF_SILENT_VALUES: readonly IfSilent[] = [\"discard\", \"keep\", \"pause\"]\n\nfunction asIfSilent(v: string): IfSilent | null {\n if (v === \"approve\") return \"keep\"\n return IF_SILENT_VALUES.includes(v as IfSilent) ? (v as IfSilent) : null\n}\n\n// \"resolved\": closed by the agent that raised it, because the owner gave the\n// same answer somewhere else (in chat, #909) and the agent already acted on\n// it. Not a verdict: nothing is approved or rejected by it.\nexport type CardStatus = \"pending\" | \"decided\" | \"expired\" | \"resolved\"\nexport type Verdict = \"yes\" | \"no\"\n\n/** Where the agent was asked, so the verdict can be taken back there. */\nexport interface ReplyTarget {\n channel: string\n chatId: string\n accountId?: string\n}\n\nexport interface DecisionCard extends CardChoices {\n id: string\n title: string\n ask: string\n recommend: string\n if_silent: IfSilent\n /** ISO time the default applies. Always set. */\n expires: string\n /** Link to the draft, PR or issue. */\n source?: string\n raised_by: string\n /** The mesh node the raising agent is on, when the card was forwarded\n * here from there (forward.ts, #668). Unset: this node. */\n node?: string\n created_at: string\n reply?: ReplyTarget\n status: CardStatus\n /** The operator's answer (status \"decided\"). */\n verdict?: Verdict\n /** What applied on expiry (status \"expired\"). */\n outcome?: IfSilent\n /** Set when the agent, or a card stored before #741, asked for\n * \"approve\" on expiry; `if_silent` then holds \"keep\". Kept so the\n * record still shows what was originally asked. */\n if_silent_asked?: \"approve\"\n decided_by?: string\n decided_at?: string\n note?: string\n /** The option the operator picked, when the card offered choices. */\n choice?: string\n /** The message the operator approved, after any edit. */\n text?: string\n /** Set once the raising agent has been told the result. */\n agent_notified_at?: string\n /** Why the raising agent closed it (status \"resolved\"). */\n resolution?: string\n /** What the card is about, when the daemon raised it (origin.ts). */\n origin?: CardOrigin\n /** A follow-up card (#394): counted in the daily screen budget. */\n followup?: FollowupKind\n}\n\nexport const CARD_LIMITS = {\n title: 120,\n ask: 300,\n recommend: 300,\n source: 500,\n note: 500,\n resolution: 300,\n /** Pending cards one agent may have open at a time. */\n pendingPerAgent: 25,\n} as const\n\nexport interface CardSettings {\n defaultExpiryDays: number\n maxExpiryDays: number\n}\n\nexport const DEFAULT_CARD_SETTINGS: CardSettings = { defaultExpiryDays: 3, maxExpiryDays: 30 }\n\nexport function approvalsDir(root: string): string {\n return resolve(root, \".agentx\", \"approvals\")\n}\n\nconst CARD_ID_RE = /^[a-z0-9][a-z0-9-]{0,80}$/\n\nexport function isValidCardId(id: string): boolean {\n return CARD_ID_RE.test(id)\n}\n\nfunction fileFor(root: string, id: string): string {\n if (!isValidCardId(id)) throw new Error(`invalid card id: ${id}`)\n return resolve(approvalsDir(root), `${id}.json`)\n}\n\nexport function newCardId(now: number, title: string): string {\n const day = new Date(now).toISOString().slice(0, 10)\n const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, \"-\").replace(/^-+|-+$/g, \"\").slice(0, 40)\n return `${day}-${slug || \"card\"}-${Math.random().toString(36).slice(2, 6)}`\n}\n\nexport function saveCard(root: string, card: DecisionCard): void {\n mkdirSync(approvalsDir(root), { recursive: true })\n const path = fileFor(root, card.id)\n const tmp = `${path}.${process.pid}.tmp`\n writeFileSync(tmp, JSON.stringify(card, null, 2) + \"\\n\")\n renameSync(tmp, path)\n}\n\nexport function readCard(root: string, id: string): DecisionCard | null {\n if (!isValidCardId(id)) return null\n try {\n const card = JSON.parse(readFileSync(fileFor(root, id), \"utf-8\")) as DecisionCard\n // A card that expired as \"approve\" before #741 is read as \"keep\" too, so\n // an agent not yet told never hears \"default applied: approve\".\n const legacy = (card.if_silent as string) === \"approve\" || (card.outcome as string) === \"approve\"\n return {\n ...card,\n if_silent: asIfSilent(card.if_silent) ?? \"keep\",\n ...(card.outcome ? { outcome: asIfSilent(card.outcome) ?? \"keep\" } : {}),\n ...(legacy ? { if_silent_asked: \"approve\" as const } : {}),\n }\n } catch {\n return null\n }\n}\n\n/** Cards, oldest first; only `status` ones when given. */\nexport function listCards(root: string, status?: CardStatus): DecisionCard[] {\n const dir = approvalsDir(root)\n if (!existsSync(dir)) return []\n const out: DecisionCard[] = []\n for (const f of readdirSync(dir).filter((f) => f.endsWith(\".json\") && !f.startsWith(\"_\")).sort()) {\n const c = readCard(root, f.slice(0, -5))\n if (c && (!status || c.status === status)) out.push(c)\n }\n return out\n}\n\nfunction oneLine(v: unknown): string {\n return typeof v === \"string\" ? v.replace(/\\s+/g, \" \").trim() : \"\"\n}\n\n/**\n * `expires` as given by an agent: an ISO date or time, or a relative\n * \"12h\" / \"3d\". Unset: the default. Always capped at maxExpiryDays, so a\n * card can't be parked for a year.\n */\nexport function resolveExpiry(input: unknown, now: number, settings: CardSettings): { ok: true; at: string } | { ok: false; error: string } {\n const max = now + settings.maxExpiryDays * 86_400_000\n let at: number\n const raw = oneLine(input)\n if (!raw) {\n at = now + settings.defaultExpiryDays * 86_400_000\n } else {\n const rel = /^(\\d+)\\s*([hd])$/i.exec(raw)\n if (rel) {\n at = now + Number(rel[1]) * (rel[2].toLowerCase() === \"d\" ? 86_400_000 : 3_600_000)\n } else {\n at = Date.parse(raw)\n if (Number.isNaN(at)) return { ok: false, error: `expires must be an ISO date or time, or like \"12h\" / \"3d\" (got \"${raw}\")` }\n }\n }\n if (at <= now) return { ok: false, error: \"expires must be in the future\" }\n return { ok: true, at: new Date(Math.min(at, max)).toISOString() }\n}\n\nexport interface CardInput {\n title?: unknown\n ask?: unknown\n recommend?: unknown\n if_silent?: unknown\n expires?: unknown\n source?: unknown\n raised_by?: unknown\n reply?: unknown\n choices?: unknown\n draft?: unknown\n say?: unknown\n context?: unknown\n followup?: unknown\n}\n\n/** Validate what an agent sent and build a pending card. Never saves. */\nexport interface BuildCardOptions {\n now?: number\n settings?: CardSettings\n origin?: CardOrigin\n /** The peer the card was forwarded from. The daemon sets it after\n * checking the name against its mesh peers; never taken as sent. */\n node?: string\n}\n\nexport function buildCard(\n input: CardInput,\n opts: BuildCardOptions = {},\n): { ok: true; card: DecisionCard } | { ok: false; error: string } {\n const now = opts.now ?? Date.now()\n const settings = opts.settings ?? DEFAULT_CARD_SETTINGS\n const title = oneLine(input.title)\n const ask = oneLine(input.ask)\n const recommend = oneLine(input.recommend)\n const raisedBy = oneLine(input.raised_by)\n if (!title) return { ok: false, error: \"title is required\" }\n if (!ask) return { ok: false, error: \"ask is required: the yes/no question\" }\n if (!recommend) return { ok: false, error: \"recommend is required: your advice and why, in one line\" }\n if (!raisedBy) return { ok: false, error: \"raised_by is required: the agent id\" }\n if (title.length > CARD_LIMITS.title) return { ok: false, error: `title is longer than ${CARD_LIMITS.title} characters` }\n if (ask.length > CARD_LIMITS.ask) return { ok: false, error: `ask is longer than ${CARD_LIMITS.ask} characters` }\n if (recommend.length > CARD_LIMITS.recommend) return { ok: false, error: `recommend is longer than ${CARD_LIMITS.recommend} characters` }\n const ifSilentRaw = oneLine(input.if_silent).toLowerCase()\n const ifSilent = asIfSilent(ifSilentRaw)\n if (!ifSilent) {\n return { ok: false, error: `if_silent must be one of ${IF_SILENT_VALUES.join(\", \")}` }\n }\n const expiry = resolveExpiry(input.expires, now, settings)\n if (!expiry.ok) return expiry\n const source = oneLine(input.source)\n if (source.length > CARD_LIMITS.source) return { ok: false, error: `source is longer than ${CARD_LIMITS.source} characters` }\n if (source && /^[a-z][a-z0-9+.-]*:/i.test(source) && !/^https?:\\/\\//i.test(source)) {\n return { ok: false, error: \"source must be an http(s) link or a plain reference\" }\n }\n const extras = buildChoices(input)\n if (!extras.ok) return extras\n const followup = oneLine(input.followup).toLowerCase()\n if (followup && !(FOLLOWUP_KINDS as readonly string[]).includes(followup)) {\n return { ok: false, error: `followup must be one of ${FOLLOWUP_KINDS.join(\", \")}` }\n }\n let reply: ReplyTarget | undefined\n const r = input.reply as Record<string, unknown> | undefined\n if (r && typeof r === \"object\" && oneLine(r.channel) && oneLine(r.chatId)) {\n reply = { channel: oneLine(r.channel), chatId: oneLine(r.chatId), ...(oneLine(r.accountId) ? { accountId: oneLine(r.accountId) } : {}) }\n }\n return {\n ok: true,\n card: {\n id: newCardId(now, title),\n title,\n ask,\n recommend,\n if_silent: ifSilent,\n ...(ifSilentRaw === \"approve\" ? { if_silent_asked: \"approve\" as const } : {}),\n expires: expiry.at,\n ...(source ? { source } : {}),\n raised_by: raisedBy,\n ...(opts.node ? { node: opts.node } : {}),\n created_at: new Date(now).toISOString(),\n ...(reply ? { reply } : {}),\n ...extras.value,\n // Set by the daemon only (check-ins), never from what an agent sent.\n ...(opts.origin ? { origin: opts.origin } : {}),\n ...(followup ? { followup: followup as FollowupKind } : {}),\n status: \"pending\",\n },\n }\n}\n\n/** Validate, enforce the per-agent cap, and save. */\nexport function createCard(\n root: string,\n input: CardInput,\n opts: BuildCardOptions = {},\n): { ok: true; card: DecisionCard } | { ok: false; error: string } {\n const built = buildCard(input, opts)\n if (!built.ok) return built\n // The screen gate (#394): refused here, the reason goes back to the author.\n const broken = cardRuleBreak(built.card)\n if (broken) return { ok: false, error: broken }\n if (built.card.followup) {\n const full = followupBudgetRefusal(listCards(root), built.card.followup, opts.now ?? Date.now())\n if (full) return { ok: false, error: full }\n }\n // Retro cards are raised for an agent, not by it: they get their own\n // count, so they never use up the agent's room for its own cards.\n const isRetro = (c: { origin?: CardOrigin }) => isRetroOrigin(c.origin)\n const retro = isRetro(built.card)\n const open = listCards(root, \"pending\").filter((c) => c.raised_by === built.card.raised_by && isRetro(c) === retro).length\n if (open >= CARD_LIMITS.pendingPerAgent) {\n const what = retro ? \"retro cards\" : \"cards\"\n return { ok: false, error: `${built.card.raised_by} already has ${open} ${what} waiting; wait for decisions before raising more` }\n }\n saveCard(root, built.card)\n return built\n}\n\n/** Record the operator's answer. Refuses anything no longer pending. */\nexport function decideCard(\n root: string,\n id: string,\n verdict: Verdict,\n opts: { by?: string; note?: string; now?: number; choice?: string | number; text?: string } = {},\n): { ok: true; card: DecisionCard } | { ok: false; error: string } {\n const card = readCard(root, id)\n if (!card) return { ok: false, error: `no card \"${id}\"` }\n if (card.status !== \"pending\") return { ok: false, error: `card \"${id}\" is already ${card.status}` }\n const answer = resolveAnswer(card, verdict, opts)\n if (!answer.ok) return answer\n const note = oneLine(opts.note).slice(0, CARD_LIMITS.note)\n const decided: DecisionCard = {\n ...card,\n status: \"decided\",\n verdict,\n decided_by: opts.by ?? \"operator\",\n decided_at: new Date(opts.now ?? Date.now()).toISOString(),\n ...(note ? { note } : {}),\n ...(answer.choice ? { choice: answer.choice } : {}),\n ...(answer.text ? { text: answer.text } : {}),\n }\n saveCard(root, decided)\n return { ok: true, card: decided }\n}\n\n/** Cards whose answer the daemon acts on itself (a workflow run, a plan\n * step, a retro): only the operator closes those. A reminder card is the\n * agent's own to act on, so it may close it like one it raised. */\nconst AGENT_RESOLVABLE_ORIGINS = new Set<string | undefined>([undefined, \"reminder\"])\n\n/**\n * The raising agent closes its own pending card: the owner gave the same\n * answer outside the card (in chat) and the agent has acted on it (#909).\n * The card leaves the inbox, so no check-in or popup asks again. It is not\n * a verdict, and the agent is not sent a result for it: it already knows.\n */\nexport function resolveCard(\n root: string,\n id: string,\n opts: { by: string; reason?: unknown; node?: string; now?: number },\n): { ok: true; card: DecisionCard } | { ok: false; status: 400 | 403 | 404 | 409; error: string } {\n const card = readCard(root, id)\n if (!card) return { ok: false, status: 404, error: `no card \"${id}\"` }\n if (card.raised_by !== opts.by || (card.node ?? \"\") !== (opts.node ?? \"\")) {\n return { ok: false, status: 403, error: `card \"${id}\" was raised by another agent` }\n }\n if (!AGENT_RESOLVABLE_ORIGINS.has(card.origin?.kind)) {\n return { ok: false, status: 409, error: `card \"${id}\" is answered by the operator only` }\n }\n if (card.status !== \"pending\") return { ok: false, status: 409, error: `card \"${id}\" is already ${card.status}` }\n const reason = oneLine(opts.reason)\n if (!reason) return { ok: false, status: 400, error: \"reason is required: say where the owner answered and what you did\" }\n const at = new Date(opts.now ?? Date.now()).toISOString()\n const resolved: DecisionCard = {\n ...card,\n status: \"resolved\",\n decided_by: opts.by,\n decided_at: at,\n resolution: reason.slice(0, CARD_LIMITS.resolution),\n agent_notified_at: at,\n }\n saveCard(root, resolved)\n return { ok: true, card: resolved }\n}\n\n/** Apply `if_silent` to every pending card past its expiry. */\nexport function expireCards(root: string, now: number = Date.now()): DecisionCard[] {\n const out: DecisionCard[] = []\n for (const card of listCards(root, \"pending\")) {\n if (Date.parse(card.expires) > now) continue\n const expired: DecisionCard = {\n ...card,\n status: \"expired\",\n outcome: card.if_silent,\n decided_by: \"expiry\",\n decided_at: new Date(now).toISOString(),\n }\n saveCard(root, expired)\n out.push(expired)\n }\n return out\n}\n\n/** Decided or expired cards whose agent hasn't been told yet. */\nexport function cardsAwaitingAgentNotice(root: string): DecisionCard[] {\n return listCards(root).filter((c) => c.status !== \"pending\" && !c.agent_notified_at)\n}\n\nexport function markAgentNotified(root: string, id: string, now: number = Date.now()): void {\n const card = readCard(root, id)\n if (!card || card.agent_notified_at) return\n saveCard(root, { ...card, agent_notified_at: new Date(now).toISOString() })\n}\n\nexport const NO_UNDO_LINE =\n \"A no does not by itself undo anything you already did. If you already carried this out (for example because the owner approved it in chat), do not reverse it: ask the requester one clear question first.\"\n\n/** What the raising agent is told. Plain text, one short message. */\nexport function verdictMessage(card: DecisionCard): string {\n const retro = card.origin?.kind === \"retro\" ? card.origin : undefined\n const result = card.status === \"decided\"\n ? (card.verdict === \"yes\"\n ? (retro && !retroApproved(card) ? `The operator picked \"${RETRO_NONE}\", which counts as NO.` : \"The operator said YES.\")\n : \"The operator said NO.\")\n : `Nobody answered before it expired, so the default applied: ${card.outcome ?? card.if_silent}.`\n const lines = [\n `[Approval result] Your decision card \"${card.title}\" (${card.id}) is closed.`,\n `Question: ${card.ask}`,\n result,\n ]\n // A retro card's pick and spec go through retroLines, labelled as the\n // reviewer's proposal: never \"Approved text (send exactly this)\".\n const check = card.origin?.kind === \"retro-check\" ? card.origin : undefined\n if (card.status === \"decided\" && card.verdict === \"yes\" && !retro && !check) lines.push(...answerLines(card))\n if (card.note) lines.push(`Operator note: ${card.note}`)\n // A late no on something the owner already approved in chat must not\n // quietly undo work that is done (#909).\n if (card.status === \"decided\" && card.verdict === \"no\" && !retro && !check) lines.push(NO_UNDO_LINE)\n if (card.source) lines.push(`Source: ${card.source}`)\n if (card.origin?.kind === \"reminder\") lines.push(...originLines(card.origin, card.status === \"decided\" && card.verdict === \"yes\"))\n if (retro || check) {\n // Compare ignoring whitespace so a dashboard that only reflows the draft\n // does not resend the whole draft as the operator's note.\n const squash = (s: string) => s.replace(/\\s+/g, \" \").trim()\n const draft = draftFor(card.draft, card.choice)\n const edited = card.text && squash(card.text) !== squash(draft) ? card.text : undefined\n lines.push(...(retro ? retroLines(retro, card, edited) : retroCheckLines(check!, card, edited)))\n }\n if (card.reply) lines.push(`You raised it from ${card.reply.channel} chat ${card.reply.chatId}; reply there if the requester should know.`)\n if (card.node) lines.push(`The operator answered it on another machine; the card was forwarded from ${card.node}.`)\n lines.push(\"Act on this result now. Do not raise the same card again.\")\n return lines.join(\"\\n\")\n}\n","// --- Where a card came from, when the daemon raised it ---\n//\n// Agents raise most cards themselves. A check-in (checkin.ts) raises one on\n// an agent's behalf for an open Apple Reminder, and records the reminder\n// here so the agent that gets the answer knows what to act on and what to\n// tick off. Only the daemon sets it: POST /approvals and the agent tool\n// never pass it through.\n\nexport interface ReminderOrigin {\n kind: \"reminder\"\n /** The reminder's full id, as remindctl prints it. */\n id: string\n title: string\n list?: string\n}\n\n/** An open request that needs attention, shown as a card on the Mac\n * (popup-runner.ts). Never stored: the request is the record. */\nexport interface RequestOrigin {\n kind: \"request\"\n id: string\n}\n\n/** A retro card (src/retro): fixes to the agents' environment proposed\n * from one run that struggled. The answer goes back to the agent that ran\n * it, which builds the picked fix for a second review. */\nexport interface RetroOrigin {\n kind: \"retro\"\n /** The run the retro read. */\n taskId: string\n /** Failure signature (wiki/failure-candidates.ts), for one card per signature. */\n signature: string\n /** The reviewer's spec for each fix, in the order of the card's choices\n * (without \"None of these\"). Only the picked one reaches the agent. */\n specs?: string[]\n}\n\n/** A monthly review of a check a retro added (src/retro/checks.ts): a\n * guard rule tagged `retro:<taskId>` that fires often on runs that went\n * well. The answer goes back to the agent that built it, which keeps,\n * loosens or removes it for a second review. */\nexport interface RetroCheckOrigin {\n kind: \"retro-check\"\n /** The guard rule's id. */\n ruleId: string\n /** The run whose retro asked for the rule. */\n taskId: string\n /** The policy file that holds the rule, relative to the daemon's folder. */\n file: string\n}\n\n/** A step of a workflow run asks the owner (src/workflows, #788): an\n * owner.ask step, a person.message to approve, or a run's approval at\n * start. The answer resumes the run; the agent that started it is not\n * told separately, the run is. */\nexport interface WorkflowOrigin {\n kind: \"workflow\"\n runId: string\n nodeId: string\n}\n\n/** An agent proposed a reusable workflow (#788). Yes turns it on. */\nexport interface WorkflowProposalOrigin {\n kind: \"workflow-proposal\"\n workflowId: string\n}\n\n/** A step of a tracked plan the owner approves when the plan is made\n * (src/requests/plan-sweep.ts, #788). The plan check reads the answer\n * and acts on it; the agent is not sent a result turn. */\nexport interface PlanStepOrigin {\n kind: \"plan-step\"\n requestId: string\n step: number\n}\n\n/** A pull request that changes core paths waits for the owner (merge\n * policy, #1047). YES approves that one commit; the policy reads the\n * answer and updates the PR's status. No agent is sent a result turn. */\nexport interface MergePolicyOrigin {\n kind: \"merge-policy\"\n repo: string\n pr: number\n sha: string\n}\n\nexport type CardOrigin = ReminderOrigin | RequestOrigin | RetroOrigin | RetroCheckOrigin | PlanStepOrigin | WorkflowOrigin | WorkflowProposalOrigin | MergePolicyOrigin\n\n/** Retro cards of either kind: raised for an agent, not by it. */\nexport function isRetroOrigin(origin: CardOrigin | undefined): origin is RetroOrigin | RetroCheckOrigin {\n return origin?.kind === \"retro\" || origin?.kind === \"retro-check\"\n}\n\n/** The choices on a check review card, in this order. */\nexport const CHECK_CHOICES = { keep: \"Keep it\", loosen: \"Loosen it\", remove: \"Remove it\" } as const\n\n/** Lines for the agent told the result of a check review card. Keep, NO\n * and silence all leave the rule as it is. */\nexport function retroCheckLines(\n origin: RetroCheckOrigin,\n card: { status: string; verdict?: string; choice?: string },\n edited?: string,\n): string[] {\n const rule = `guard rule \\`${origin.ruleId}\\` in \\`${origin.file}\\``\n const lines = [`This was a monthly review of ${rule}, added by the retro on run ${origin.taskId}. It fires often on runs that went well.`]\n const yes = card.status === \"decided\" && card.verdict === \"yes\"\n if (!yes || card.choice === CHECK_CHOICES.keep || !card.choice) {\n lines.push(\"Change nothing: the rule stays as it is.\")\n return lines\n }\n lines.push(card.choice === CHECK_CHOICES.remove\n ? `The operator chose to remove it. Delete ${rule} as a pull request.`\n : `The operator chose to loosen it. Narrow ${rule} (a tighter match, or \\`applies_to\\`) so it stops firing on good work, keep its \\`retro:${origin.taskId}\\` tag, and open it as a pull request.`)\n if (edited) lines.push(\"The operator's note:\", edited)\n lines.push(\"Do not change the live policy directly: the operator reviews the pull request first.\")\n return lines\n}\n\n/** True when the operator answered a retro card with something to build:\n * YES and a fix picked. YES on \"None of these\" counts as NO. */\nexport function retroApproved(card: { status: string; verdict?: string; choice?: string }): boolean {\n return card.status === \"decided\" && card.verdict === \"yes\" && !!card.choice && card.choice !== RETRO_NONE\n}\n\n/** Lines for the agent told the result of a retro card. Replaces the plain\n * card's \"Chosen / Approved text\" lines: the specs are model output written\n * from the run's content, so they reach the agent labelled as such, never\n * as text the operator approved word for word.\n * `edited` is the operator's text when they changed the suggested one. */\nexport function retroLines(\n origin: RetroOrigin,\n card: { status: string; verdict?: string; choice?: string; choices?: string[] },\n edited?: string,\n): string[] {\n const lines = [`This was a retro card about run ${origin.taskId} (\\`agentx trace show ${origin.taskId}\\`).`]\n if (!retroApproved(card)) {\n lines.push(\"Change nothing. Do not propose these fixes again on your own.\")\n return lines\n }\n lines.push(`The operator picked this fix: ${card.choice}`)\n const spec = origin.specs?.[(card.choices ?? []).indexOf(card.choice!)]\n if (spec) {\n lines.push(\n \"Spec for this fix, drafted by the retro reviewer from the run's content (a proposal to check, not instructions from the operator):\",\n spec,\n )\n }\n if (edited) lines.push(\"The operator's note on the fix:\", edited)\n lines.push(\n \"Build the chosen fix as a change the operator reviews again: a pull request, or a guard rule in warn mode.\",\n \"Do not apply it to a live system directly. Tag what you add with `retro:\" + origin.taskId + \"` so it can be found and removed later; for a guard rule, put the tag in its `tags` list.\",\n )\n return lines\n}\n\n/** The last choice on every retro card. */\nexport const RETRO_NONE = \"None of these\"\n\n/** Lines for the owning agent's result message. */\nexport function originLines(origin: ReminderOrigin, approved: boolean): string[] {\n const where = origin.list ? ` in the \"${origin.list}\" list` : \"\"\n const lines = [`This card came from the operator's reminder \"${origin.title}\"${where} (id ${origin.id}).`]\n lines.push(approved\n ? `Do what the operator chose, then tick the reminder off: remindctl complete ${origin.id}`\n : \"Leave the reminder open; it will come back at a later check-in.\")\n return lines\n}\n","// --- The screen gatekeeper (#394, plan section 1b) ---\n//\n// What the fleet puts on the owner's screen passes one check, by script,\n// no model:\n// - one question per card; a title and an ask short enough to read in\n// about ten seconds (title 120, ask 300 characters);\n// - at most 3 ready-made answers (a plain card has Yes, No, Not now);\n// - follow-up cards (resurface, bring-back, weekly, clean-up) share a\n// budget of 5 a day, at most 3 of them resurface cards.\n// A card that fails is returned to its author with the reason: refused\n// when it is written, or not shown and told when the popup comes to it.\n// Nothing here rewrites, removes or answers a card. The call and Focus\n// hold is notify/hold.ts; the popup window takes no focus (card-window.ts).\n\nexport const SCREEN_LIMITS = { title: 120, ask: 300, answers: 3 } as const\n\nexport const FOLLOWUP_KINDS = [\"resurface\", \"bring-back\", \"weekly\", \"cleanup\"] as const\nexport type FollowupKind = (typeof FOLLOWUP_KINDS)[number]\n\nexport const SCREEN_BUDGET = { perDay: 5, resurface: 3 } as const\n\n/** Why this card may not go on the owner's screen, or null. */\nexport function cardRuleBreak(card: { title: string; ask: string; choices?: string[] }): string | null {\n if (card.title.length > SCREEN_LIMITS.title) return `title is longer than ${SCREEN_LIMITS.title} characters`\n if (card.ask.length > SCREEN_LIMITS.ask) return `ask is longer than ${SCREEN_LIMITS.ask} characters`\n if ((card.ask.match(/\\?/g) ?? []).length > 1) return \"ask holds more than one question: one card asks one thing\"\n if ((card.choices?.length ?? 0) > SCREEN_LIMITS.answers) return `more than ${SCREEN_LIMITS.answers} choices`\n return null\n}\n\n/** Why an agent's `agentx notify` may not go on the owner's screen, or\n * null: the same ten-second rule, title 120 and message 300. */\nexport function noticeRuleBreak(title: string, message: string): string | null {\n if (title.length > SCREEN_LIMITS.title) return `title is longer than ${SCREEN_LIMITS.title} characters`\n if (message.length > SCREEN_LIMITS.ask) return `message is longer than ${SCREEN_LIMITS.ask} characters`\n return null\n}\n\n/** Local midnight before `now`: the budget's day. */\nfunction dayStart(now: number): number {\n const d = new Date(now)\n d.setHours(0, 0, 0, 0)\n return d.getTime()\n}\n\n/** Why a follow-up card of this kind cannot be raised today, or null.\n * `cards` is every card, of any status: an answered one was on screen too. */\nexport function followupBudgetRefusal(\n cards: Array<{ followup?: FollowupKind; created_at: string }>,\n kind: FollowupKind,\n now: number,\n): string | null {\n const from = dayStart(now)\n const today = cards.filter((c) => c.followup && Date.parse(c.created_at) >= from)\n if (today.length >= SCREEN_BUDGET.perDay) {\n return `the follow-up screen budget is used up today (${SCREEN_BUDGET.perDay} cards): raise it again tomorrow`\n }\n if (kind === \"resurface\" && today.filter((c) => c.followup === \"resurface\").length >= SCREEN_BUDGET.resurface) {\n return `${SCREEN_BUDGET.resurface} resurface cards were already raised today: raise it again tomorrow`\n }\n return null\n}\n\n/** What the author hears when the popup does not show its card. */\nexport function returnedText(card: { id: string; title: string }, reason: string): string {\n return [\n `[screen gate] Your card ${card.id} (\"${card.title}\") was not shown to the owner: ${reason}.`,\n \"It is still in his inbox, unchanged. If it must reach him on screen, close it (agentx_approval resolve) and raise one that passes:\",\n `one question, title up to ${SCREEN_LIMITS.title} characters, ask up to ${SCREEN_LIMITS.ask}, at most ${SCREEN_LIMITS.answers} choices.`,\n ].join(\" \")\n}\n"],"mappings":"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","names":["existsSync","mkdirSync","readdirSync","readFileSync","renameSync","writeFileSync","resolve","isRetroOrigin","origin","CHECK_CHOICES","retroCheckLines","card","edited","rule","lines","retroApproved","RETRO_NONE","retroLines","spec","originLines","approved","where","SCREEN_LIMITS","FOLLOWUP_KINDS","SCREEN_BUDGET","cardRuleBreak","card","noticeRuleBreak","title","message","dayStart","now","d","followupBudgetRefusal","cards","kind","from","today","c","returnedText","reason","IF_SILENT_VALUES","asIfSilent","v","CARD_LIMITS","DEFAULT_CARD_SETTINGS","approvalsDir","root","resolve","CARD_ID_RE","isValidCardId","id","fileFor","newCardId","now","title","day","slug","saveCard","card","mkdirSync","path","tmp","writeFileSync","renameSync","readCard","readFileSync","legacy","listCards","status","dir","existsSync","out","f","readdirSync","c","oneLine","resolveExpiry","input","settings","max","at","raw","rel","buildCard","opts","ask","recommend","raisedBy","ifSilentRaw","ifSilent","expiry","source","extras","buildChoices","followup","FOLLOWUP_KINDS","reply","r","createCard","built","broken","cardRuleBreak","full","followupBudgetRefusal","isRetro","isRetroOrigin","retro","open","what","decideCard","verdict","answer","resolveAnswer","note","decided","AGENT_RESOLVABLE_ORIGINS","resolveCard","reason","resolved","expireCards","expired","cardsAwaitingAgentNotice","markAgentNotified","NO_UNDO_LINE","verdictMessage","result","retroApproved","RETRO_NONE","lines","check","answerLines","originLines","squash","s","draft","draftFor","edited","retroLines","retroCheckLines"]}
@@ -1,3 +0,0 @@
1
- var n={count:3,label:120,draft:2e3,say:160,context:600,text:4e3},f="{choice}";function a(e){return typeof e=="string"?e.replace(/\s+/g," ").trim():""}function h(e){return typeof e=="string"?e.replace(/\r\n?/g,`
2
- `).trim():""}function u(e){let t={};if(e.choices!==void 0&&e.choices!==null){if(!Array.isArray(e.choices))return{ok:!1,error:"choices must be a list of short labels"};let r=e.choices.map(a).filter(Boolean);if(r.length!==e.choices.length)return{ok:!1,error:"every choice must be a non-empty label"};if(r.length>n.count)return{ok:!1,error:`at most ${n.count} choices`};if(r.some(c=>c.length>n.label))return{ok:!1,error:`a choice is longer than ${n.label} characters`};if(new Set(r).size!==r.length)return{ok:!1,error:"choices must be different from each other"};r.length&&(t.choices=r)}let o=h(e.draft);if(o){if(o.length>n.draft)return{ok:!1,error:`draft is longer than ${n.draft} characters`};t.draft=o}let i=a(e.say);if(i){if(i.length>n.say)return{ok:!1,error:`say is longer than ${n.say} characters`};t.say=i}let s=h(e.context);if(s){if(s.length>n.context)return{ok:!1,error:`context is longer than ${n.context} characters`};t.context=s}return{ok:!0,value:t}}function l(e,t){return e?t?e.split(f).join(t):e:""}function g(e,t,o){if(t==="no")return{ok:!0};let i,s=e.choices??[];if(o.choice!==void 0&&o.choice!==""){if(!s.length)return{ok:!1,error:"this card has no choices"};let c=typeof o.choice=="number"?o.choice:Number(o.choice);if(Number.isInteger(c)&&c>=1&&c<=s.length)i=s[c-1];else if(s.includes(a(o.choice)))i=a(o.choice);else return{ok:!1,error:`choice must be 1-${s.length} or one of the labels`}}else if(s.length)return{ok:!1,error:"this card offers choices: pick one (Mac popup, or `agentx approvals approve <key> --choice <n>`)"};let r=h(o.text);return!r&&e.draft&&(r=l(e.draft,i)),r.length>n.text?{ok:!1,error:`text is longer than ${n.text} characters`}:{ok:!0,...i?{choice:i}:{},...r?{text:r}:{}}}function x(e){let t=[];return e.choice&&t.push(`Chosen: ${e.choice}`),e.text&&t.push("Approved text (send exactly this, the operator may have edited it):",e.text),t}export{n as a,f as b,u as c,l as d,g as e,x as f};
3
- //# sourceMappingURL=chunk-VY7TBMJU.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/approvals/choices.ts"],"sourcesContent":["// --- Ready-made answers on a decision card ---\n//\n// A card can offer a few choices (say, three free meeting slots), a\n// suggested message the operator may edit before it goes, and a short\n// context (what the other person said) shown above the question. The operator's\n// pick and the final text are stored on the card and handed to the agent\n// that raised it; the agent does the sending. Nothing here sends anything.\n//\n// All optional: a card without them is the plain yes/no card it always was.\n\nexport const CHOICE_LIMITS = {\n /** Most options a card may offer. More is a form, not a choice; the\n * screen gate (#394) allows 3. */\n count: 3,\n label: 120,\n draft: 2000,\n say: 160,\n context: 600,\n /** The operator's final text, after editing. */\n text: 4000,\n} as const\n\n/** The spot in `draft` that takes the picked label. */\nexport const CHOICE_PLACEHOLDER = \"{choice}\"\n\nexport interface CardChoices {\n choices?: string[]\n draft?: string\n say?: string\n /** A few lines of background, shown above the question. */\n context?: string\n}\n\nfunction oneLine(v: unknown): string {\n return typeof v === \"string\" ? v.replace(/\\s+/g, \" \").trim() : \"\"\n}\n\n/** A draft keeps its line breaks: it is a message, not a label. */\nfunction block(v: unknown): string {\n return typeof v === \"string\" ? v.replace(/\\r\\n?/g, \"\\n\").trim() : \"\"\n}\n\n/** Validate what an agent sent. Empty input gives an empty object. */\nexport function buildChoices(input: { choices?: unknown; draft?: unknown; say?: unknown; context?: unknown }): { ok: true; value: CardChoices } | { ok: false; error: string } {\n const value: CardChoices = {}\n if (input.choices !== undefined && input.choices !== null) {\n if (!Array.isArray(input.choices)) return { ok: false, error: \"choices must be a list of short labels\" }\n const labels = input.choices.map(oneLine).filter(Boolean)\n if (labels.length !== input.choices.length) return { ok: false, error: \"every choice must be a non-empty label\" }\n if (labels.length > CHOICE_LIMITS.count) return { ok: false, error: `at most ${CHOICE_LIMITS.count} choices` }\n if (labels.some((l) => l.length > CHOICE_LIMITS.label)) return { ok: false, error: `a choice is longer than ${CHOICE_LIMITS.label} characters` }\n if (new Set(labels).size !== labels.length) return { ok: false, error: \"choices must be different from each other\" }\n if (labels.length) value.choices = labels\n }\n const draft = block(input.draft)\n if (draft) {\n if (draft.length > CHOICE_LIMITS.draft) return { ok: false, error: `draft is longer than ${CHOICE_LIMITS.draft} characters` }\n value.draft = draft\n }\n const say = oneLine(input.say)\n if (say) {\n if (say.length > CHOICE_LIMITS.say) return { ok: false, error: `say is longer than ${CHOICE_LIMITS.say} characters` }\n value.say = say\n }\n const context = block(input.context)\n if (context) {\n if (context.length > CHOICE_LIMITS.context) return { ok: false, error: `context is longer than ${CHOICE_LIMITS.context} characters` }\n value.context = context\n }\n return { ok: true, value }\n}\n\n/** The suggested message for a pick, with `{choice}` filled in. */\nexport function draftFor(draft: string | undefined, choice: string | undefined): string {\n if (!draft) return \"\"\n return choice ? draft.split(CHOICE_PLACEHOLDER).join(choice) : draft\n}\n\n/**\n * Check the operator's answer against the card. A yes on a card with\n * choices must say which one: approving \"the meeting\" without a date would\n * leave the agent to guess, which is the thing the card exists to avoid.\n */\nexport function resolveAnswer(\n card: CardChoices,\n verdict: \"yes\" | \"no\",\n answer: { choice?: string | number; text?: string },\n): { ok: true; choice?: string; text?: string } | { ok: false; error: string } {\n if (verdict === \"no\") return { ok: true }\n let choice: string | undefined\n const list = card.choices ?? []\n if (answer.choice !== undefined && answer.choice !== \"\") {\n if (!list.length) return { ok: false, error: \"this card has no choices\" }\n const n = typeof answer.choice === \"number\" ? answer.choice : Number(answer.choice)\n if (Number.isInteger(n) && n >= 1 && n <= list.length) choice = list[n - 1]\n else if (list.includes(oneLine(answer.choice))) choice = oneLine(answer.choice)\n else return { ok: false, error: `choice must be 1-${list.length} or one of the labels` }\n } else if (list.length) {\n return { ok: false, error: \"this card offers choices: pick one (Mac popup, or `agentx approvals approve <key> --choice <n>`)\" }\n }\n let text = block(answer.text)\n if (!text && card.draft) text = draftFor(card.draft, choice)\n if (text.length > CHOICE_LIMITS.text) return { ok: false, error: `text is longer than ${CHOICE_LIMITS.text} characters` }\n return { ok: true, ...(choice ? { choice } : {}), ...(text ? { text } : {}) }\n}\n\n/** Lines for the agent's result message. Empty for a plain card. */\nexport function answerLines(card: { choice?: string; text?: string }): string[] {\n const lines: string[] = []\n if (card.choice) lines.push(`Chosen: ${card.choice}`)\n if (card.text) lines.push(\"Approved text (send exactly this, the operator may have edited it):\", card.text)\n return lines\n}\n"],"mappings":"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","names":["CHOICE_LIMITS","CHOICE_PLACEHOLDER","oneLine","v","block","buildChoices","input","value","labels","l","draft","say","context","draftFor","choice","resolveAnswer","card","verdict","answer","list","n","text","answerLines","lines"]}
@@ -1,18 +0,0 @@
1
- import{a as v}from"./chunk-IMLAPB7G.js";import{i as y}from"./chunk-TJZGAKIB.js";import{b as J}from"./chunk-KP7KDLZ2.js";import{I as U,J as W,K as B,L as G,P as k,T as Se}from"./chunk-6UP5VD3H.js";import{existsSync as X,readFileSync as xe}from"node:fs";import{dirname as b,join as h}from"node:path";import{fileURLToPath as Ae}from"node:url";import{homedir as ke}from"node:os";var L="tn.acme.agentx.voice",it="AgentX Desktop.app",K="AgentX Helper.app";function ot(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 st(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 C(e){return`'${e.replace(/'/g,"'\\''")}'`}var V="/usr/bin:/bin:/usr/sbin:/sbin";function at(e,t){return t||[...new Set([...e?[b(e)]:[],...V.split(":")])].join(":")}function N(e){let t={amp:"&",lt:"<",gt:">",quot:'"',apos:"'"};return e.replace(/&(amp|lt|gt|quot|apos);/g,(n,r)=>t[r])}function lt(e){let t=e.match(/<key>PATH<\/key>\s*<string>([^<]*)<\/string>/);return t?N(t[1]):V}var ut=e=>/agentx\.voice.*\.plist$/.test(e);function ct(e,t){let n=null,r=[];for(let{file:o,plist:i}of[...t].sort((s,c)=>s.file<c.file?-1:1)){let s=i.match(/<key>Label<\/key>\s*<string>([^<]*)<\/string>/)?.[1],c=i.match(/<key>ProgramArguments<\/key>\s*<array>\s*<string>([^<]*)<\/string>/)?.[1];!s||!c||!N(c).endsWith("/Contents/MacOS/AgentXVoice")||(n?r.push({file:o,label:N(s)}):n={file:o,label:N(s)})}return{keep:n??{file:h(e,`${L}.plist`),label:L},remove:r}}function z(e,t,n){for(let r of t.split(":"))if(r&&n(h(r,e)))return h(r,e);return null}function pt(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 ${C(e.cwd)} && ${C(e.node)} ${C(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??L)}</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 dt(e){return e?["--icon",e]:[]}function ve(e){return h(e,"Applications",K,"Contents/MacOS/agentx-mac-helper")}function be(e=b(Ae(import.meta.url))){for(let t=e;;t=b(t)){try{if(JSON.parse(xe(h(t,"package.json"),"utf8")).name==="agentix-cli")return t}catch{}if(b(t)===t)return null}}function Ne(e,t,n){if(n)return n;let r=ve(t);return X(r)||!e?r:h(e,"apps/mac-helper/build",K,"Contents/MacOS/agentx-mac-helper")}function M(){let e=Ne(be(),ke(),process.env.AGENTX_MAC_HELPER);return X(e)?e:null}import{accessSync as Oe,constants as Ee}from"fs";import{execFile as T}from"child_process";var Z=5e3,R=(e,t)=>new Promise(n=>{try{T(e,t,{timeout:Z},r=>n(!r))}catch{n(!1)}}),Y=(e,t)=>new Promise(n=>{try{T(e,t,{timeout:Z},(r,o)=>n(r?null:String(o)))}catch{n(null)}}),Q=(e,t)=>new Promise((n,r)=>{T(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 ee=e=>e.helper===void 0?M():e.helper;async function Re(e,t=Y){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 te(e={}){let t=e.run??R;return{async post({title:n,message:r}){let o=ee(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:()=>Re(ee(e),e.capture)}}import{existsSync as $e,readdirSync as Pe}from"fs";var re="/System/Library/Sounds";function ne(e){if(!/^[\w -]+$/.test(e))return null;let t=`${re}/${e}.aiff`;return $e(t)?t:null}function ie(e={}){let t=e.run??R;return{async play(n,r){let o=ne(n);o&&await t("/usr/bin/afplay",["-v",String(Math.min(1,Math.max(0,r))),o])},has:n=>ne(n)!==null,async names(){try{return Pe(re).filter(n=>n.endsWith(".aiff")).map(n=>n.slice(0,-5)).sort()}catch{return[]}}}}var ae="\u200B";function $(e){return e.startsWith("-")?ae+e:e}var oe=e=>e?.startsWith(ae+"-")?e.slice(1):e;function _(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 se=e=>new Date(Math.floor(e.getTime()/6e4)*6e4).toISOString().replace(/\.\d{3}Z$/,"Z");function le(e="remindctl",t=Q){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",_(n),"--create","--json","--no-input"])},async listOpen(n){let r=await t(e,["show","all","--list",_(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:oe(i.title)??"",...i.notes!==void 0?{notes:oe(i.notes)}:{}}))},async add({list:n,title:r,notes:o,due:i,priority:s}){let c=["add","--title",$(r),"--list",_(n),"--notes",$(o)];i&&c.push("--due",se(i)),s&&s!=="none"&&c.push("--priority",s);let l=await t(e,[...c,"--json","--no-input"]),d;try{d=JSON.parse(l.trim())?.id}catch{d=void 0}if(typeof d!="string"||!d)throw new Error(`${e} add: no reminder id in its answer`);return{id:d}},async update(n,r){let o=["edit",n];r.title!==void 0&&o.push("--title",$(r.title)),r.notes!==void 0&&o.push("--notes",$(r.notes)),r.due===null?o.push("--clear-due"):r.due&&o.push("--due",se(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 Ce=e=>{try{return Oe(e,Ee.X_OK),!0}catch{return!1}},Le={async active(){let e=J();return e.active?!0:e.reason==="no active Focus assertion"?!1:null}};function ue(e={},t={}){let n=e.reminders?.command||"remindctl";return{os:"macos",tasks:le(n,t.output),banner:te(t),sound:ie(t),focus:Le,async describe(){let r=["Apple Notes is not available yet."],o=t.isExecutable??Ce;return(n.includes("/")?o(n):!!z(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:!1,banner:!0,sound:!0,focus:!0,problems:r}}}}function P(e){return{os:"none",async describe(){return{os:"none",tasks:!1,notes:!1,banner:!1,sound:!1,focus:!1,problems:[e]}}}}var Me={darwin:ue};function I(e={},t={}){if(e.native?.adapter==="none")return P('Turned off: native.adapter is "none".');let n=t.platform??process.platform,r=(t.factories??Me)[n];return r?r(e,t.io??{}):P(`AgentX has no native adapter for ${n} yet.`)}import{existsSync as Te}from"fs";import{homedir as _e}from"os";import{isAbsolute as Ie}from"path";var De={banner:!0,sound:!0,soundName:"Glass",volume:.4};function jt(e){return{...De,...e??{}}}function Ut(e,t={}){let n=I({native:t.native},{platform:t.platform,io:{run:t.run,helper:t.helper}});return async(r,o)=>{let i=[];e.banner&&n.banner&&i.push(n.banner.post({title:r,message:o})),e.sound&&n.sound&&i.push(n.sound.play(e.soundName,e.volume)),await Promise.all(i)}}function Wt(e,t,n=process.platform){let r={...e??{}},o=I({},{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(/^~(?=\/)/,_e());if(!i)delete r.icon;else{if(!Ie(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&&!Te(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 Be,mkdirSync as Ge,readFileSync as Je,renameSync as Xe,writeFileSync as Ke}from"fs";import{dirname as de,resolve as Ve}from"path";B();Se();B();var Kt=["owner","agent","person"],qe=36e5,Fe=e=>G.includes(e.state);function ce(e){return t=>!e||e.get(t)?.state!=="active"?null:e.steps(t).find(n=>n.state==="active")?.agentId??null}function D(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 q(e,t,n){return Fe(e)?e.state==="needs_attention"?e.attentionReason??"needs attention":e.dueAt!==null?n>e.dueAt?`past its due time (${He(e.dueAt)})`:null:n-e.updatedAt>t*qe?`no activity for ${t} h or more`:null:null}function Vt(e,t,n,r){let o={owner:[],agent:[],person:[]};for(let i of e){let s=q(i,t,n);if(!s)continue;let{group:c,who:l}=D(i,r);o[c].push({request:i,group:c,who:l,why:s})}return o}function zt(e,t){return e==="owner"?"you":e==="agent"?`agent ${t}`:`${t} (outside person)`}var He=e=>new Date(e).toISOString().slice(0,16).replace("T"," ")+" UTC";function Zt(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 Yt(e){return t=>U(t,e())}var je=10;async function Qt(e,t,n){let r=e.dueForCheck(n.now-n.everyMinutes*6e4,je),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(c=>({verdict:"unverifiable",detail:String(c?.message??c)}));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 Ue=200,pe=4e3;function We(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 g(e,t=Ue){let n=We(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 on(e,t,n,r){let o=n?.get(e.id),i=r?q(e,r.staleAfterHours,r.now):null,s=t.lastAnswer(e.id),c=s&&s.text.length>pe?`${s.text.slice(0,pe)}
13
-
14
- \u2026`:s?.text;return{...e,summary:g(e.text),textHtml:v(e.text),why:e.state==="waiting_owner"?e.question:e.state==="needs_attention"?e.attentionReason:null,ownerNoteHtml:e.ownerNote?v(e.ownerNote):null,lastAnswer:s?{agentId:s.agentId,at:s.at,html:v(c)}:null,loop:e.promise||e.dueAt!==null||e.closeWhen?{owedTo:e.owedTo,promise:e.promise,dueAt:e.dueAt,check:e.closeWhen?W(e.closeWhen):null,verifyState:e.verifyState,verifyDetail:e.verifyDetail}:null,stall:r&&i?{...D(e,{isAgent:r.isAgent,activeStepAgent:ce(n)}),why:i}:null,plan:o?{state:o.state,steps:n.steps(e.id).map(l=>({idx:l.idx,name:l.name,kind:l.kind,agentId:l.agentId,state:l.state,approval:l.needsApproval?l.approval:null,evidence:l.evidence,note:l.note,nudges:l.nudges}))}:null}}var F=3,ze=30*24*36e5;function ge(e){return Ve(e,".agentx","native-mirror.json")}function fe(e){try{let t=JSON.parse(Je(ge(e),"utf-8"));return{...t,items:t?.items&&typeof t.items=="object"?t.items:{}}}catch{return{items:{}}}}function me(e,t){let n=ge(e);Be(de(n))||Ge(de(n),{recursive:!0});let r=`${n}.${process.pid}.tmp`;Ke(r,JSON.stringify(t,null,2)+`
15
- `),Xe(r,n)}var Ze=5*6e4;function pn(e,t,n=Date.now()){let r=fe(e),o=r.checkedAt?Date.parse(r.checkedAt):NaN;return!r.cards||!(n-o<=Ze)?!1:r.items[`card:${t}`]?.state!=="failed"}var H=24*36e5;function ye(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 Ye(e,t,n,r){let o=Date.parse(e.expires),i=[g(e.ask,y.ask)];return e.choices?.length&&i.push(...e.choices.map((s,c)=>`${c+1}. ${s}`)),e.recommend&&i.push(`Recommended: ${g(e.recommend,y.recommend)}`),i.push(`Answer: ${n}`,`agentx: card=${e.id}`),{key:`card:${e.id}`,title:g(e.title,y.title),notes:i.join(`
16
- `),due:ye(r,t,o),priority:Number.isFinite(o)&&o-r<=H?"high":"none"}}function Qe(e,t,n,r){let o=[g(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:g(e.text,y.title),notes:o.join(`
17
- `),due:ye(r,t,i),priority:i!==void 0&&i-r<=H?"high":"none"}}async function dn(e){let{root:t,settings:n,tasks:r,log:o}=e,i=e.now??Date.now(),s=new Date(i).toISOString(),c={created:0,completed:0,dismissed:0,failed:0},l=fe(t);delete l.lastError;let d=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}`,Ye(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}`,Qe(a,n,e.answerUrl,i))}catch(a){return o(`[mirror] could not read what is waiting: ${a?.message??a}`),d(String(a?.message??a)),me(t,l),c}let j=!0,x=new Map,he=async a=>{if(!x.has(a))try{x.set(a,new Set((await r.listOpen(a)).map(u=>u.id)))}catch(u){o(`[mirror] could not read list "${a}": ${u?.message??u}`),d(String(u?.message??u)),x.set(a,null),j=!1}return x.get(a)};for(let[a,u]of Object.entries(l.items)){if(u.state!=="open"||!u.id)continue;let p=await he(u.list);if(p)if(w.has(a))p.has(u.id)||(l.items[a]={...u,state:"dismissed",at:s},c.dismissed++,o(`[mirror] ${a}: its task was ticked off or deleted; it stays on the Desk and is not mirrored again`));else{if(p.has(u.id))try{await r.complete(u.id)}catch(f){o(`[mirror] ${a}: could not complete its task: ${f?.message??f}`),d(String(f?.message??f));continue}l.items[a]={...u,state:"completed",at:s},c.completed++}}let O=null,S=[];for(let[a,u]of w){let p=l.items[a];if(p&&(p.state==="open"||p.state==="dismissed")||p?.state==="failed"&&p.failures>=F)continue;if(O===null)try{await r.ensureList(n.list),O=!0}catch(m){o(`[mirror] could not make list "${n.list}": ${m?.message??m}`),d(String(m?.message??m)),O=!1}let f=p?.state==="failed"?p.failures:0;try{let{id:m}=await r.add({list:n.list,title:u.title,notes:u.notes,due:u.due,priority:u.priority});l.items[a]={id:m,list:n.list,state:"open",at:s,failures:0},c.created++}catch(m){let A=String(m?.message??m),E=f+1;l.items[a]={list:n.list,state:"failed",at:s,failures:E,lastError:A},d(A),c.failed++,o(`[mirror] ${a}: could not add to Reminders (${E}/${F}): ${A}`),E===F&&S.push({title:u.title,message:A})}}let we=l.failureToldAt?Date.parse(l.failureToldAt):NaN;if(S.length&&!(i-we<H)){let a=S[0],u=S.length>1?` (and ${S.length-1} more)`:"";try{await e.notify("Could not add to Reminders",`${a.title}${u}: ${g(a.message,200)}. Still on the Desk.`),l.failureToldAt=s}catch(p){o(`[mirror] could not tell the owner: ${p?.message??p}`)}}j&&(l.checkedAt=s,l.cards=n.include.includes("card"));for(let[a,u]of Object.entries(l.items))u.state!=="open"&&!w.has(a)&&i-Date.parse(u.at)>ze&&delete l.items[a];return me(t,l),c}export{it as a,K as b,ot as c,st as d,at as e,lt as f,ut as g,ct as h,z as i,pt as j,dt as k,ve as l,be as m,Ne as n,M as o,ne as p,I as q,De as r,jt as s,Ut as t,Wt as u,Kt as v,ce as w,D as x,Vt as y,zt as z,He as A,Zt as B,Yt as C,Qt as D,g as E,on as F,fe as G,pn as H,dn as I};
18
- //# sourceMappingURL=chunk-Z3AI4KXT.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/sound.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 type { Capture, Output, Run } from \"./run\"\nimport { macosSound } from \"./sound\"\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 banner: macosBanner(io),\n sound: macosSound(io),\n focus,\n async describe() {\n const problems = [\"Apple Notes is not available yet.\"]\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: false, 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 { 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 { 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 } 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 /** 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 }\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: { run?: NativeIo[\"run\"]; platform?: NodeJS.Platform; helper?: string | null; native?: NativeConfig[\"native\"] } = {},\n): LocalAlert {\n const native = loadNativeAdapter({ native: deps.native }, { platform: deps.platform, io: { run: deps.run, 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) steps.push(native.sound.play(settings.soundName, settings.volume))\n await Promise.all(steps)\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 { 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: summarize(r.text, CARD_LIMITS.title),\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. No model call: the summary is the start of the\n// request's own words, 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\nexport interface RequestCard extends RequestRecord {\n summary: 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 return {\n ...r,\n summary: summarize(r.text),\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","existsSync","readdirSync","SOUNDS","soundPath","name","path","existsSync","macosSound","io","exec","run","volume","readdirSync","f","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","macosBanner","macosSound","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","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","plain","md","summarize","max","text","cut","end","requestCard","r","store","plans","stall","plan","why","stalledWhy","answer","cutAnswer","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","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"]}