agentix-cli 0.91.1 → 0.92.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 (116) hide show
  1. package/dist/{App-PBPYFVBE.js → App-NVYQSINX.js} +2 -2
  2. package/dist/{ChatApp-QC7QAQEW.js → ChatApp-CFJY5S7S.js} +2 -2
  3. package/dist/{agent-BQEENICU.js → agent-IKVCR32P.js} +2 -2
  4. package/dist/{board-dashboard-ZLDVYPZ4.js → board-dashboard-F2AY5XAU.js} +287 -227
  5. package/dist/board-dashboard-F2AY5XAU.js.map +1 -0
  6. package/dist/{chunk-NIQEKVWY.js → chunk-2I34WFIT.js} +2 -2
  7. package/dist/{chunk-AVA245WL.js → chunk-2IMQJS53.js} +2 -2
  8. package/dist/{chunk-4L244AW4.js → chunk-3Z5P2CWS.js} +2 -2
  9. package/dist/{chunk-ZVXKCXTP.js → chunk-4UXHHHYH.js} +2 -2
  10. package/dist/{chunk-SH5Y7N4W.js → chunk-5AAJ6BPY.js} +2 -2
  11. package/dist/{chunk-PJZ4ZHQS.js → chunk-7AJY3EMB.js} +2 -2
  12. package/dist/{chunk-HY6ILQ4T.js → chunk-BNUQFUAE.js} +2 -2
  13. package/dist/{chunk-HXM35HRM.js → chunk-D3PXRTOR.js} +57 -55
  14. package/dist/chunk-D3PXRTOR.js.map +1 -0
  15. package/dist/{chunk-3SZ4CU6G.js → chunk-DG5I6PPM.js} +16 -14
  16. package/dist/chunk-DG5I6PPM.js.map +1 -0
  17. package/dist/{chunk-XIYWXCNU.js → chunk-E5OV6RTG.js} +4 -4
  18. package/dist/chunk-E5OV6RTG.js.map +1 -0
  19. package/dist/{chunk-OS34M3FV.js → chunk-EBMU2RD6.js} +2 -2
  20. package/dist/{chunk-4TKGR3XF.js → chunk-EPP6BEUX.js} +2 -2
  21. package/dist/{chunk-24P6UR3L.js → chunk-GIJV3TMB.js} +19 -14
  22. package/dist/chunk-GIJV3TMB.js.map +1 -0
  23. package/dist/{chunk-L73YON3H.js → chunk-HVJ6LG43.js} +4 -4
  24. package/dist/{chunk-ES2NYGS2.js → chunk-I67L4ZXK.js} +2 -2
  25. package/dist/{chunk-EJPQPU5J.js → chunk-IXRAL4CW.js} +2 -2
  26. package/dist/{chunk-EBHHMJZX.js → chunk-KRW2M4RY.js} +2 -2
  27. package/dist/{chunk-4SHWIGTM.js → chunk-LLFQJUZ6.js} +5 -5
  28. package/dist/{chunk-OKT5RCXP.js → chunk-MDCAQYZW.js} +2 -2
  29. package/dist/{chunk-TGN7K7VG.js → chunk-N7HKIMMR.js} +36 -38
  30. package/dist/chunk-N7HKIMMR.js.map +1 -0
  31. package/dist/{chunk-BTTQZWYB.js → chunk-QAIYUMHW.js} +2 -2
  32. package/dist/{chunk-S4JD5FEE.js → chunk-QPRETJO4.js} +2 -2
  33. package/dist/{chunk-SQOBKSP3.js → chunk-ROOZIJOB.js} +2 -2
  34. package/dist/{chunk-RS6BNRVF.js → chunk-WO7HTHMB.js} +2 -2
  35. package/dist/{chunk-URVWZ5FM.js → chunk-XHGXAWV6.js} +2 -2
  36. package/dist/{chunk-VCO46KQZ.js → chunk-YXPJXBEI.js} +6 -6
  37. package/dist/{chunk-VCO46KQZ.js.map → chunk-YXPJXBEI.js.map} +1 -1
  38. package/dist/cli.js +1 -1
  39. package/dist/{client-WFEXHCNH.js → client-VDKYLOWU.js} +2 -2
  40. package/dist/{config-PPYPER75.js → config-STG62DJ3.js} +2 -2
  41. package/dist/config-mutator-MIU2D465.js +2 -0
  42. package/dist/{doctor-NRKGBTVD.js → doctor-FQNJDN4T.js} +2 -2
  43. package/dist/{grants-N7XC7NLS.js → grants-Y667GZAU.js} +2 -2
  44. package/dist/index.js +1 -1
  45. package/dist/processes-W5VHBP7T.js +2 -0
  46. package/dist/{program-TIKUIL2G.js → program-KUPJQVOT.js} +14 -14
  47. package/dist/{promote-PY73YR6C.js → promote-BSIVNB26.js} +2 -2
  48. package/dist/{query-F3R5KXA7.js → query-HGFJXEL7.js} +2 -2
  49. package/dist/{registry-YBMYCRQH.js → registry-YDCHLHWR.js} +2 -2
  50. package/dist/{request-planner-CVNEV6NJ.js → request-planner-XM3BUNKD.js} +2 -2
  51. package/dist/{review-candidates-5QTF5JRG.js → review-candidates-STJAWQ6H.js} +2 -2
  52. package/dist/{routing-BJLETKHZ.js → routing-ZWRKDDF2.js} +2 -2
  53. package/dist/schedule-tool-AGEAWU3E.js +2 -0
  54. package/dist/scheduler-PKQ5W7UW.js +2 -0
  55. package/dist/{seat-OORCKWER.js → seat-K54UKAY7.js} +2 -2
  56. package/dist/{select-OGMDS37Y.js → select-CQNVQYJI.js} +2 -2
  57. package/dist/{set-enabled-IKSHTUUF.js → set-enabled-3UTTNW3W.js} +2 -2
  58. package/dist/{tool-CKIO3NBX.js → tool-CA7EUQMY.js} +2 -2
  59. package/dist/{tool-6FVBXUTV.js → tool-HLPRU4ES.js} +2 -2
  60. package/dist/wiki-JKQJJLUX.js +2 -0
  61. package/package.json +1 -1
  62. package/dist/board-dashboard-ZLDVYPZ4.js.map +0 -1
  63. package/dist/chunk-24P6UR3L.js.map +0 -1
  64. package/dist/chunk-3SZ4CU6G.js.map +0 -1
  65. package/dist/chunk-HXM35HRM.js.map +0 -1
  66. package/dist/chunk-TGN7K7VG.js.map +0 -1
  67. package/dist/chunk-XIYWXCNU.js.map +0 -1
  68. package/dist/config-mutator-QQDBVKWM.js +0 -2
  69. package/dist/processes-MMAVAZAC.js +0 -2
  70. package/dist/schedule-tool-E2PRGDLW.js +0 -2
  71. package/dist/scheduler-53POJWDI.js +0 -2
  72. package/dist/wiki-4CPXUAPW.js +0 -2
  73. /package/dist/{App-PBPYFVBE.js.map → App-NVYQSINX.js.map} +0 -0
  74. /package/dist/{ChatApp-QC7QAQEW.js.map → ChatApp-CFJY5S7S.js.map} +0 -0
  75. /package/dist/{agent-BQEENICU.js.map → agent-IKVCR32P.js.map} +0 -0
  76. /package/dist/{chunk-NIQEKVWY.js.map → chunk-2I34WFIT.js.map} +0 -0
  77. /package/dist/{chunk-AVA245WL.js.map → chunk-2IMQJS53.js.map} +0 -0
  78. /package/dist/{chunk-4L244AW4.js.map → chunk-3Z5P2CWS.js.map} +0 -0
  79. /package/dist/{chunk-ZVXKCXTP.js.map → chunk-4UXHHHYH.js.map} +0 -0
  80. /package/dist/{chunk-SH5Y7N4W.js.map → chunk-5AAJ6BPY.js.map} +0 -0
  81. /package/dist/{chunk-PJZ4ZHQS.js.map → chunk-7AJY3EMB.js.map} +0 -0
  82. /package/dist/{chunk-HY6ILQ4T.js.map → chunk-BNUQFUAE.js.map} +0 -0
  83. /package/dist/{chunk-OS34M3FV.js.map → chunk-EBMU2RD6.js.map} +0 -0
  84. /package/dist/{chunk-4TKGR3XF.js.map → chunk-EPP6BEUX.js.map} +0 -0
  85. /package/dist/{chunk-L73YON3H.js.map → chunk-HVJ6LG43.js.map} +0 -0
  86. /package/dist/{chunk-ES2NYGS2.js.map → chunk-I67L4ZXK.js.map} +0 -0
  87. /package/dist/{chunk-EJPQPU5J.js.map → chunk-IXRAL4CW.js.map} +0 -0
  88. /package/dist/{chunk-EBHHMJZX.js.map → chunk-KRW2M4RY.js.map} +0 -0
  89. /package/dist/{chunk-4SHWIGTM.js.map → chunk-LLFQJUZ6.js.map} +0 -0
  90. /package/dist/{chunk-OKT5RCXP.js.map → chunk-MDCAQYZW.js.map} +0 -0
  91. /package/dist/{chunk-BTTQZWYB.js.map → chunk-QAIYUMHW.js.map} +0 -0
  92. /package/dist/{chunk-S4JD5FEE.js.map → chunk-QPRETJO4.js.map} +0 -0
  93. /package/dist/{chunk-SQOBKSP3.js.map → chunk-ROOZIJOB.js.map} +0 -0
  94. /package/dist/{chunk-RS6BNRVF.js.map → chunk-WO7HTHMB.js.map} +0 -0
  95. /package/dist/{chunk-URVWZ5FM.js.map → chunk-XHGXAWV6.js.map} +0 -0
  96. /package/dist/{client-WFEXHCNH.js.map → client-VDKYLOWU.js.map} +0 -0
  97. /package/dist/{config-PPYPER75.js.map → config-STG62DJ3.js.map} +0 -0
  98. /package/dist/{config-mutator-QQDBVKWM.js.map → config-mutator-MIU2D465.js.map} +0 -0
  99. /package/dist/{doctor-NRKGBTVD.js.map → doctor-FQNJDN4T.js.map} +0 -0
  100. /package/dist/{grants-N7XC7NLS.js.map → grants-Y667GZAU.js.map} +0 -0
  101. /package/dist/{processes-MMAVAZAC.js.map → processes-W5VHBP7T.js.map} +0 -0
  102. /package/dist/{program-TIKUIL2G.js.map → program-KUPJQVOT.js.map} +0 -0
  103. /package/dist/{promote-PY73YR6C.js.map → promote-BSIVNB26.js.map} +0 -0
  104. /package/dist/{query-F3R5KXA7.js.map → query-HGFJXEL7.js.map} +0 -0
  105. /package/dist/{registry-YBMYCRQH.js.map → registry-YDCHLHWR.js.map} +0 -0
  106. /package/dist/{request-planner-CVNEV6NJ.js.map → request-planner-XM3BUNKD.js.map} +0 -0
  107. /package/dist/{review-candidates-5QTF5JRG.js.map → review-candidates-STJAWQ6H.js.map} +0 -0
  108. /package/dist/{routing-BJLETKHZ.js.map → routing-ZWRKDDF2.js.map} +0 -0
  109. /package/dist/{schedule-tool-E2PRGDLW.js.map → schedule-tool-AGEAWU3E.js.map} +0 -0
  110. /package/dist/{scheduler-53POJWDI.js.map → scheduler-PKQ5W7UW.js.map} +0 -0
  111. /package/dist/{seat-OORCKWER.js.map → seat-K54UKAY7.js.map} +0 -0
  112. /package/dist/{select-OGMDS37Y.js.map → select-CQNVQYJI.js.map} +0 -0
  113. /package/dist/{set-enabled-IKSHTUUF.js.map → set-enabled-3UTTNW3W.js.map} +0 -0
  114. /package/dist/{tool-CKIO3NBX.js.map → tool-CA7EUQMY.js.map} +0 -0
  115. /package/dist/{tool-6FVBXUTV.js.map → tool-HLPRU4ES.js.map} +0 -0
  116. /package/dist/{wiki-4CPXUAPW.js.map → wiki-JKQJJLUX.js.map} +0 -0
@@ -1,4 +1,4 @@
1
- var c={};function v(r){c=r}function w(){return c}var y=`<link rel="preconnect" href="https://fonts.googleapis.com">
1
+ var g={};function v(t){g=t}function w(){return g}var y=`<link rel="preconnect" href="https://fonts.googleapis.com">
2
2
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
3
3
  <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700;800&family=Roboto+Mono:wght@400;500;600&display=swap" rel="stylesheet">
4
4
  <script>(function(){try{var t=localStorage.getItem('ax-theme');if(t!=='light'&&t!=='dark'){t='dark';localStorage.setItem('ax-theme',t)}document.documentElement.setAttribute('data-theme',t)}catch(e){document.documentElement.setAttribute('data-theme','dark')}})();</script>`,k=`
@@ -268,32 +268,34 @@ var c={};function v(r){c=r}function w(){return c}var y=`<link rel="preconnect" h
268
268
  document.addEventListener('DOMContentLoaded', wire);
269
269
  } else { wire(); }
270
270
  })();
271
- </script>`;function S(r){let t=o=>typeof o=="string"&&o?o:null;return{node:t(r?.node?.id),version:t(r?.version),commit:t(r?.commit),startedAt:t(r?.startedAt),diskNewer:r?.build?.newer===!0}}var b='<span class="ax-build" hidden></span>';function E(r){let t=r.peers&&r.peers.length>0?r.peers:[{id:"local",name:"this daemon",dashboardUrl:"",primary:!0}],o=r.currentPeerId||t.find(e=>e.primary)?.id||t[0].id,p=t.find(e=>e.id===o)||t[0],u=t.map(e=>{let s=e.id===o,n=e.primary?' <span style="color:var(--ax-muted);font-size:10px">\xB7 primary</span>':"",i=!!e.tokenScope&&!e.primary,l=i?"#":e.dashboardUrl?`${e.dashboardUrl.replace(/\/+$/,"")}/admin`:"#",d=i?` data-peer-id="${a(e.id)}" data-peer-action="proxy"`:e.primary?' data-peer-id="primary" data-peer-action="proxy"':"",m=i?'<div class="url" style="color:var(--ax-accent)">manage from this dashboard \u2713</div>':e.tokenScope?`<div class="url">${a(e.tokenScope)}</div>`:e.dashboardUrl?'<div class="url" style="color:var(--ax-muted)">(open peer dashboard \u2197)</div>':"";return`<a href="${a(l)}" class="${s?"is-current":""}"${d}>
272
- <div class="row"><span class="dot"></span><span>${a(e.name)}${n}</span></div>
273
- ${e.dashboardUrl?`<div class="url">${a(e.dashboardUrl)}</div>`:""}
274
- ${m}
271
+ </script>`;function $(t){let a=e=>typeof e=="string"&&e?e:null;return{node:a(t?.node?.id),version:a(t?.version),commit:a(t?.commit),startedAt:a(t?.startedAt),diskNewer:t?.build?.newer===!0}}var f='<span class="ax-build" hidden></span>';function S(t){let a=t.peers&&t.peers.length>0?t.peers:[{id:"local",name:"this daemon",dashboardUrl:"",primary:!0}],e=t.currentPeerId||a.find(r=>r.primary)?.id||a[0].id,c=a.find(r=>r.id===e)||a[0],h=a.map(r=>{let p=r.id===e,o=r.primary?' <span style="color:var(--ax-muted);font-size:10px">\xB7 primary</span>':"",l=!!r.tokenScope&&!r.primary,m=l?"#":r.dashboardUrl?`${r.dashboardUrl.replace(/\/+$/,"")}/admin`:"#",u=l?` data-peer-id="${d(r.id)}" data-peer-action="proxy"`:r.primary?' data-peer-id="primary" data-peer-action="proxy"':"",b=l?'<div class="url" style="color:var(--ax-accent)">manage from this dashboard \u2713</div>':r.tokenScope?`<div class="url">${d(r.tokenScope)}</div>`:r.dashboardUrl?'<div class="url" style="color:var(--ax-muted)">(open peer dashboard \u2197)</div>':"";return`<a href="${d(m)}" class="${p?"is-current":""}"${u}>
272
+ <div class="row"><span class="dot"></span><span>${d(r.name)}${o}</span></div>
273
+ ${r.dashboardUrl?`<div class="url">${d(r.dashboardUrl)}</div>`:""}
274
+ ${b}
275
275
  </a>`}).join(""),x=`<div class="ax-mesh-sel" aria-haspopup="menu">
276
276
  <span class="lbl">managing</span>
277
- <span class="name">${a(p.name)}</span>
277
+ <span class="name">${d(c.name)}</span>
278
278
  <span class="chev">\u25BE</span>
279
- <div class="ax-mesh-menu" role="menu">${u}</div>
280
- </div>`,h=[{name:"Mesh",tabs:[{id:"live",label:"Live",href:"/live"},{id:"mesh",label:"Operations",href:"/mesh"},{id:"monitor",label:"Monitor",href:"/monitor"},{id:"approvals",label:"Approvals",href:"/approvals"},{id:"people",label:"People",href:"/people"},{id:"activity",label:"Activity",href:"/activity"},{id:"workflows",label:"Workflows",href:"/workflows"},{id:"admin",label:"Settings",href:"/admin"}]}].map(e=>{let s=e.tabs.map(n=>{let i=n.id===r.activeTab?"ax-topbar__tab is-active":"ax-topbar__tab",l=n.external?' target="_blank" rel="noopener"':"",d=n.external?' <span style="font-size:9px;opacity:0.6">\u2197</span>':"";return`<a href="${n.href}" class="${i}"${l}>${n.label}${d}</a>`}).join("");return`<div class="ax-topbar__group" title="${a(e.name)} tier"><span class="ax-topbar__group-label">${a(e.name)}</span>${s}</div>`}).join("");return`<header class="ax-topbar">
279
+ <div class="ax-mesh-menu" role="menu">${h}</div>
280
+ </div>`,i=[{name:"Mesh",tabs:[{id:"live",label:"Live",href:"/live"},{id:"mesh",label:"Operations",href:"/mesh"},{id:"monitor",label:"Monitor",href:"/monitor"},{id:"approvals",label:"Approvals",href:"/approvals"},{id:"people",label:"People",href:"/people"},{id:"activity",label:"Activity",href:"/activity"},{id:"workflows",label:"Workflows",href:"/workflows"},{id:"admin",label:"Settings",href:"/admin"}]}].map(r=>{let p=r.tabs.map(o=>{let l=o.id===t.activeTab?"ax-topbar__tab is-active":"ax-topbar__tab",m=o.external?' target="_blank" rel="noopener"':"",u=o.external?' <span style="font-size:9px;opacity:0.6">\u2197</span>':"";return`<a href="${o.href}" class="${l}"${m}>${o.label}${u}</a>`}).join("");return`<div class="ax-topbar__group" title="${d(r.name)} tier"><span class="ax-topbar__group-label">${d(r.name)}</span>${p}</div>`}).join("");return`<header class="ax-topbar">
281
281
  <div class="ax-topbar__left">
282
282
  <div class="ax-brand">
283
283
  <span class="ax-brand__mark">AX</span>
284
284
  <span class="ax-brand__name">AgentX</span>
285
- <span class="ax-brand__subtitle">${a(r.subtitle)}</span>
285
+ <span class="ax-brand__subtitle">${d(t.subtitle)}</span>
286
286
  </div>
287
- <nav class="ax-topbar__tabs">${h}</nav>
287
+ <nav class="ax-topbar__tabs">${i}</nav>
288
288
  </div>
289
289
  <div class="ax-topbar__right">
290
- ${r.rightExtras||""}
291
- ${b}
290
+ ${t.rightExtras||""}
291
+ ${f}
292
292
  ${x}
293
293
  <div class="ax-theme-switch" role="tablist" aria-label="Theme">
294
294
  <button data-theme-opt="dark">Dark</button>
295
295
  <button data-theme-opt="light">Light</button>
296
296
  </div>
297
297
  </div>
298
- </header>${r.subheader?`<div class="ax-subheader">${r.subheader}</div>`:""}`}function a(r){return String(r||"").replace(/[&<>"']/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[t])}function A(r){return String(r??"").replace(/[&<>"']/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[t])}export{A as a,v as b,w as c,y as d,k as e,_ as f,S as g,E as h};
299
- //# sourceMappingURL=chunk-3SZ4CU6G.js.map
298
+ </header>${t.subheader?`<div class="ax-subheader">${t.subheader}</div>`:""}`}function d(t){return String(t||"").replace(/[&<>"']/g,a=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[a])}function P(t,a={}){let e=String(t??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;");e=e.replace(/```(\w*)\n([\s\S]*?)```/g,(s,i,n)=>`<pre><code class="lang-${i}">${n.trim()}</code></pre>`),e=e.replace(/`([^`]+)`/g,"<code>$1</code>");let c=[],h=[],x=a.images?"axpic"+Math.random().toString(36).slice(2,10)+"n":"";if(a.images){let s=typeof a.images=="number"?a.images:8;e=e.replace(/<code[^>]*>[\s\S]*?<\/code>|!\[([^\]\n]*)\]\(([^)\s]+)\)/g,(i,n,r)=>n===void 0||r===void 0?i:/^https?:\/\/[^\s]+$/i.test(r)?c.length>=s?`[${n||r}](${r})`:(c.push(`<img src="${r}" alt="${n}" loading="lazy" referrerpolicy="no-referrer">`),h.push(n),x+(c.length-1)+"z"):n)}if(e=e.replace(/^(\|.+\|)\n(\|[-| :]+\|)\n((?:\|.+\|\n?)*)/gm,(s,i,n,r)=>{let p=i.split("|").filter(l=>l.trim()).map(l=>`<th>${l.trim()}</th>`).join(""),o=r.trim().split(`
299
+ `).map(l=>`<tr>${l.split("|").filter(u=>u.trim()).map(u=>`<td>${u.trim()}</td>`).join("")}</tr>`).join("");return`<table><thead><tr>${p}</tr></thead><tbody>${o}</tbody></table>`}),e=e.replace(/^#### (.+)$/gm,"<h4>$1</h4>"),e=e.replace(/^### (.+)$/gm,"<h3>$1</h3>"),e=e.replace(/^## (.+)$/gm,"<h2>$1</h2>"),e=e.replace(/^# (.+)$/gm,"<h1>$1</h1>"),e=e.replace(/\*\*(.+?)\*\*/g,"<strong>$1</strong>"),e=e.replace(/\*(.+?)\*/g,"<em>$1</em>"),e=e.replace(/\[\[([^\]]+)\]\]/g,(s,i)=>{let[n,r]=i.split("|",2),p=n.trim().replace(/^\//,""),o=(r||n).trim();return a.wikilink?a.wikilink(p,o):o}),e=e.replace(/\[([^\]]+)\]\(([^)]+)\)/g,'<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>'),e=e.replace(/^(>\s?.*(?:\n>\s?.*)*)/gm,s=>`<blockquote>${s.split(`
300
+ `).map(n=>n.replace(/^>\s?/,"")).join("<br>")}</blockquote>`),e=e.replace(/^[-*] \[( |x|X)\] (.+)$/gm,(s,i,n)=>`<li class="task">${i.toLowerCase()==="x"?"\u2611":"\u2610"} ${n}</li>`),e=e.replace(/^[-*] (.+)$/gm,"<li>$1</li>"),e=e.replace(/^\d+\. (.+)$/gm,'<li data-ordered="1">$1</li>'),e=e.replace(/(<li data-ordered="1">.*?<\/li>(?:\n?<li data-ordered="1">.*?<\/li>)*)/g,"<ol>$1</ol>"),e=e.replace(/((?:<li(?! data-ordered)[^>]*>.*?<\/li>\n?)+)/g,"<ul>$1</ul>"),e=e.replace(/ data-ordered="1"/g,""),e=e.replace(/^---$/gm,"<hr>"),e=e.replace(/\n\n+/g,"</p><p>"),e=`<p>${e}</p>`,e=e.replace(/<p>\s*(<(?:h[1-6]|ul|ol|blockquote|table|pre|hr)\b[^>]*>[\s\S]*?<\/(?:h[1-6]|ul|ol|blockquote|table|pre)>)\s*<\/p>/g,"$1"),e=e.replace(/<p>\s*<hr>\s*<\/p>/g,"<hr>"),e=e.replace(/<p>\s*<\/p>/g,""),e=e.replace(/<p>(<(?:h[1-4]|pre|table|ul|hr|div))/g,"$1"),e=e.replace(/(<\/(?:h[1-4]|pre|table|ul|hr|div)>)<\/p>/g,"$1"),e=e.replace(/<p>\s*<\/p>/g,""),c.length){let s=new RegExp(x+"(\\d+)z","g");e=e.split(/(<[^>]*>)/).map(i=>i.charAt(0)==="<"?i.replace(s,(n,r)=>h[Number(r)]):i.replace(s,(n,r)=>c[Number(r)])).join("")}return e}function A(t){return String(t??"").replace(/[&<>"']/g,a=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[a])}export{P as a,A as b,v as c,w as d,y as e,k as f,_ as g,$ as h,S as i};
301
+ //# sourceMappingURL=chunk-DG5I6PPM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/daemon/topbar.ts","../src/utils/markdown-html.ts","../src/daemon/ui/util.ts"],"sourcesContent":["// --- Shared top-chrome for every dashboard surface ---\n//\n// Four pages render in different files (live + kanban + glossary in\n// board-dashboard.ts, settings in admin-panel.ts) but must share: design\n// tokens, fonts, theme switcher, brand, primary tab nav, and a mesh-node\n// selector. This module exports the CSS + HTML snippets so each page\n// composes exactly the same chrome and we never drift them again.\n//\n// Nothing here is page-specific — callers pass in { activeTab, subtitle,\n// subheader? } and compose their own <main> below.\n\nexport type TopbarTab = \"monitor\" | \"activity\" | \"live\" | \"mesh\" | \"boards\" | \"admin\" | \"graph\" | \"glossary\" | \"workflows\" | \"health\" | \"cost\" | \"wiki\" | \"procedures\" | \"inbox\" | \"projects\" | \"approvals\" | \"people\"\n\nexport interface TopbarPeer {\n /** Stable id: primary node id, or URL for configured daemons */\n id: string\n name: string\n /** Absolute URL of the peer's dashboard (http://host:port) — selector\n * navigates to <url>/admin when switched. */\n dashboardUrl: string\n /** True for the node hosting this dashboard (locks the default). */\n primary?: boolean\n /** Optional; shown in the dropdown as \"managing via token\" hint. */\n tokenScope?: string\n}\n\nexport interface TopbarFeatures {\n /** Show the Team/Business Settings sub-tabs — config.business.enabled. */\n business?: boolean\n}\n\n// Set once at server start (board-dashboard + daemon both call it).\n//\n// This used to carry `boards` and `workflows` flags that gated top-level\n// tabs. The nav is now fixed (Live, Operations, Settings), so those flags had\n// nothing left to gate and were removed rather than left describing chrome\n// that no longer exists.\nlet topbarFeatures: TopbarFeatures = {}\n\nexport function setTopbarFeatures(features: TopbarFeatures): void {\n topbarFeatures = features\n}\n\nexport function getTopbarFeatures(): TopbarFeatures {\n return topbarFeatures\n}\n\nexport interface TopbarOpts {\n activeTab: TopbarTab\n /** Text after the brand, e.g. \"Live\" / \"Settings\" / \"Boards\". */\n subtitle: string\n /** Optional HTML inserted as a second row under the topbar. */\n subheader?: string\n /** Peers to list in the mesh selector. */\n peers?: TopbarPeer[]\n /** id of the peer currently being managed — defaults to the primary. */\n currentPeerId?: string\n /** Optional right-side HTML added before the theme switcher (clocks,\n * connection dots, etc.). */\n rightExtras?: string\n}\n\n/**\n * Font link tags + tiny inline bootstrap script. Callers drop this in <head>.\n */\n// Fonts degrade to the system stack when the box is offline or air-gapped —\n// the tokens list real fallbacks, so a failed font fetch costs typography,\n// never layout.\n//\n// The theme bootstrap runs before first paint to avoid a flash. A stored\n// \"crt\" from before that theme was removed falls back to dark rather than\n// leaving data-theme set to a value no stylesheet defines.\nexport const TOPBAR_HEAD = `<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n<link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n<link href=\"https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700;800&family=Roboto+Mono:wght@400;500;600&display=swap\" rel=\"stylesheet\">\n<script>(function(){try{var t=localStorage.getItem('ax-theme');if(t!=='light'&&t!=='dark'){t='dark';localStorage.setItem('ax-theme',t)}document.documentElement.setAttribute('data-theme',t)}catch(e){document.documentElement.setAttribute('data-theme','dark')}})();</script>`\n\n/**\n * CSS for topbar + subheader + theme switch + mesh selector. No token\n * definitions — those are per-page because each page's CSS block owns its\n * own :root overrides.\n */\nexport const TOPBAR_CSS = `\n.ax-topbar{display:flex;align-items:center;justify-content:space-between;\n padding:8px 18px;border-bottom:1px solid var(--ax-border);\n background:var(--ax-bg-elev);position:sticky;top:0;z-index:20;gap:10px;flex-wrap:nowrap}\n.ax-topbar__left{display:flex;align-items:center;gap:18px;min-width:0}\n.ax-brand{display:flex;align-items:center;gap:10px;font-weight:600;letter-spacing:-0.01em;flex-shrink:0}\n.ax-brand__mark{font-family:var(--ax-mono);font-size:12px;padding:2px 8px;\n border:1px solid var(--ax-border-2);color:var(--ax-accent);border-radius:4px}\n.ax-brand__name{font-size:14px}\n.ax-brand__subtitle{font-size:11px;color:var(--ax-muted);\n border-left:1px solid var(--ax-border);padding-left:12px;margin-left:4px}\n.ax-topbar__tabs{display:flex;gap:14px;min-width:0;overflow-x:auto;align-items:center}\n/* Tier groups (System / Processes / Procedures) — visual separators\n * mirror the docs/architecture/three-tier.md hierarchy. */\n.ax-topbar__group{display:flex;align-items:center;gap:0;padding:0 4px;\n border-left:1px solid var(--ax-border);position:relative}\n.ax-topbar__group:first-child{border-left:none;padding-left:0}\n.ax-topbar__group-label{font:inherit;font-family:var(--ax-mono);font-size:9px;\n letter-spacing:0.08em;text-transform:uppercase;color:var(--ax-muted);\n padding:0 8px;white-space:nowrap;opacity:0.7}\n.ax-topbar__tab{background:transparent;border:none;color:var(--ax-text-2);\n padding:8px 12px;font:inherit;cursor:pointer;font-size:12px;text-decoration:none;\n border-bottom:2px solid transparent;letter-spacing:-0.005em;white-space:nowrap}\n.ax-topbar__tab:hover{color:var(--ax-text)}\n.ax-topbar__tab.is-active{color:var(--ax-text);border-bottom-color:var(--ax-accent)}\n.ax-topbar__right{display:flex;align-items:center;gap:10px;font-size:11px;\n color:var(--ax-muted);flex-shrink:0}\n.ax-topbar__right .ax-mono{color:var(--ax-text-2)}\n\n/* Running build: version, commit, running since; warns when the code on\n * disk has moved on. */\n.ax-build{display:inline-flex;align-items:center;gap:8px;white-space:nowrap}\n.ax-build[hidden]{display:none}\n/* Two short lines, so the tabs keep their room on a laptop screen. */\n.ax-build__lines{display:flex;flex-direction:column;font-size:10px;line-height:1.3}\n.ax-build__stale{color:var(--ax-warn,var(--ax-accent));border:1px solid currentColor;\n border-radius:4px;padding:1px 6px}\n\n/* Mesh selector */\n.ax-mesh-sel{position:relative;display:inline-flex;align-items:center;gap:6px;\n padding:4px 10px;border:1px solid var(--ax-border-2);border-radius:4px;\n background:var(--ax-surface);cursor:pointer;font:inherit;font-size:11px;\n color:var(--ax-text-2);white-space:nowrap}\n.ax-mesh-sel:hover{border-color:var(--ax-accent);color:var(--ax-text)}\n.ax-mesh-sel.is-proxy{background:color-mix(in oklch,var(--ax-accent) 14%,var(--ax-surface));\n border-color:color-mix(in oklch,var(--ax-accent) 45%,var(--ax-border-2));color:var(--ax-accent)}\n.ax-mesh-sel .lbl{font-size:9px;letter-spacing:0.06em;text-transform:uppercase;color:var(--ax-muted);margin-right:2px}\n.ax-mesh-sel .name{font-weight:500;color:var(--ax-text)}\n.ax-mesh-sel .chev{font-size:9px;opacity:0.6;margin-left:2px}\n.ax-mesh-menu{position:absolute;top:calc(100% + 4px);right:0;min-width:240px;\n background:var(--ax-surface);border:1px solid var(--ax-border-2);\n border-radius:6px;box-shadow:0 8px 24px rgba(0,0,0,0.4);padding:4px;z-index:30;display:none}\n.ax-mesh-sel.is-open .ax-mesh-menu{display:block}\n.ax-mesh-menu a{display:block;padding:7px 10px;border-radius:4px;text-decoration:none;\n color:var(--ax-text-2);font-size:12px}\n.ax-mesh-menu a:hover{background:var(--ax-surface-2);color:var(--ax-text)}\n.ax-mesh-menu a.is-current{background:color-mix(in oklch,var(--ax-accent) 12%,var(--ax-surface));\n color:var(--ax-accent);border:1px solid color-mix(in oklch,var(--ax-accent) 30%,var(--ax-border-2))}\n.ax-mesh-menu a .row{display:flex;align-items:center;gap:8px}\n.ax-mesh-menu a .row .dot{width:6px;height:6px;border-radius:50%;background:var(--ax-accent)}\n.ax-mesh-menu a.is-offline .row .dot{background:var(--ax-border-2)}\n.ax-mesh-menu a .url{font-family:var(--ax-mono);font-size:10px;color:var(--ax-muted);margin-top:2px}\n\n/* Theme switcher segmented control */\n.ax-theme-switch{display:inline-flex;border:1px solid var(--ax-border-2);\n border-radius:4px;overflow:hidden}\n.ax-theme-switch button{background:transparent;border:none;color:var(--ax-muted);\n padding:3px 9px;font:inherit;font-size:10px;cursor:pointer;letter-spacing:0.04em;\n text-transform:uppercase;font-family:var(--ax-mono);border-right:1px solid var(--ax-border-2)}\n.ax-theme-switch button:last-child{border-right:none}\n.ax-theme-switch button:hover{color:var(--ax-text);background:var(--ax-surface-2,var(--ax-surface))}\n.ax-theme-switch button.is-active{color:var(--ax-accent);\n background:color-mix(in oklch,var(--ax-accent) 12%,var(--ax-surface))}\n\n/* Subheader — page-specific toolbar below the main topbar. Kanban uses it\n * for the board picker + search + filters; others can use it for context\n * bars if they want. */\n.ax-subheader{display:flex;align-items:center;gap:10px;padding:10px 18px;\n background:var(--ax-bg-elev);border-bottom:1px solid var(--ax-border);\n flex-wrap:wrap}\n.ax-subheader > * {flex-shrink:0}\n.ax-subheader .spacer{flex:1}`\n\n/**\n * Shared click wiring for the theme switcher + mesh selector. Safe to call\n * multiple times — only binds once per element.\n */\nexport const TOPBAR_SCRIPT = `<script>\n(function(){\n function wireTheme(){\n var sw = document.querySelectorAll('[data-theme-opt]');\n if (!sw.length) return;\n var current = document.documentElement.getAttribute('data-theme') || 'dark';\n sw.forEach(function(b){\n if (b.dataset.wired) return; b.dataset.wired = '1';\n if (b.getAttribute('data-theme-opt') === current) b.classList.add('is-active');\n b.addEventListener('click', function(){\n var t = b.getAttribute('data-theme-opt');\n document.documentElement.setAttribute('data-theme', t);\n try { localStorage.setItem('ax-theme', t); } catch (e) {}\n sw.forEach(function(x){ x.classList.toggle('is-active', x === b); });\n });\n });\n }\n function currentPeer(){\n try { return localStorage.getItem('ax-peer') || 'primary'; } catch { return 'primary'; }\n }\n function setCurrentPeer(id){\n try {\n if (id && id !== 'primary') localStorage.setItem('ax-peer', id);\n else localStorage.removeItem('ax-peer');\n } catch {}\n }\n function reflectPeerPill(){\n var sel = document.querySelector('.ax-mesh-sel');\n if (!sel) return;\n var cur = currentPeer();\n sel.classList.toggle('is-proxy', cur !== 'primary');\n // If a proxy is active, update the visible pill label + highlight.\n var picked = null;\n sel.querySelectorAll('.ax-mesh-menu a').forEach(function(a){\n a.classList.toggle('is-current', a.getAttribute('data-peer-id') === cur || (cur === 'primary' && a.getAttribute('data-peer-id') === 'primary'));\n if (a.getAttribute('data-peer-id') === cur) picked = a;\n });\n if (picked) {\n var name = picked.querySelector('span'); // first <span> inside .row\n var visibleName = sel.querySelector('.name');\n if (name && visibleName) visibleName.textContent = name.textContent.replace(/ · primary/, '');\n }\n }\n function wireMesh(){\n var sel = document.querySelector('.ax-mesh-sel');\n if (!sel || sel.dataset.wired) return; sel.dataset.wired = '1';\n sel.addEventListener('click', function(e){\n var link = e.target.closest('.ax-mesh-menu a[data-peer-action=\"proxy\"]');\n if (link) {\n e.preventDefault();\n setCurrentPeer(link.getAttribute('data-peer-id'));\n reflectPeerPill();\n sel.classList.remove('is-open');\n // Reload so the admin panel re-reads state from the selected peer.\n location.reload();\n return;\n }\n if (e.target.closest('.ax-mesh-menu a')) return; // plain navigate anchor\n sel.classList.toggle('is-open');\n });\n document.addEventListener('click', function(e){\n if (!sel.contains(e.target)) sel.classList.remove('is-open');\n });\n reflectPeerPill();\n }\n function wireHostRewrite(){\n try {\n var h = location.hostname || 'localhost';\n document.querySelectorAll('.ax-topbar__tab[href*=\"__HOST__\"]').forEach(function(a){\n a.href = a.getAttribute('href').replace(/__HOST__/g, h);\n });\n } catch (e) {}\n }\n /**\n * Make the topbar peer selector actually drive page content. When the\n * operator picks a non-primary peer, every /api/admin/* request gets an\n * X-Agentx-Peer header (and every EventSource on that path gets a ?peer=\n * query, since EventSource can't set headers). The dashboard's existing\n * proxy handler picks it up and forwards to the chosen peer.\n *\n * Without this hook, the activity-graph (and other admin pages) silently\n * keep showing only the local node's data even after the operator picks\n * a different peer in the topbar.\n */\n function wirePeerProxy(){\n if (window.__axPeerWired) return; window.__axPeerWired = true;\n // Path-prefix allowlist for the peer-proxy interceptor. Extend here\n // when a new admin-side surface needs to follow the mesh selector.\n // /api/workflows is the workflow dashboard's read/write API — it\n // lives outside /api/admin for legacy reasons, but the page is part\n // of the admin surface and should proxy when a non-primary peer is\n // selected (otherwise the workflows page on Mac shows local-only\n // YAMLs while the runtime lives on peer).\n function shouldProxy(url){\n if (!url) return false;\n // Admin-side surfaces that follow the mesh selector. Each entry\n // is a path-prefix match. Extend when a new admin surface lands\n // outside /api/admin/* but should still proxy when the operator\n // picks a non-primary peer.\n // /api/admin/ — original admin surface (activity, ledger, …)\n // /api/workflows — workflow editor + observability page\n // /api/agents — agent dropdowns inside the workflow editor +\n // anywhere else the page asks \"which agents\n // exist on the selected node?\"\n return url.indexOf('/api/admin/') >= 0\n || url.indexOf('/api/workflows') >= 0\n || url.indexOf('/api/agents') >= 0;\n }\n var origFetch = window.fetch.bind(window);\n window.fetch = function(input, init){\n try {\n var peer = currentPeer();\n if (peer && peer !== 'primary') {\n var url = typeof input === 'string' ? input : (input && input.url) || '';\n if (shouldProxy(url)) {\n init = init || {};\n init.headers = new Headers(init.headers || {});\n init.headers.set('X-Agentx-Peer', peer);\n }\n }\n } catch (e) {}\n return origFetch(input, init);\n };\n var OrigES = window.EventSource;\n if (OrigES) {\n window.EventSource = function(url, cfg){\n try {\n var peer = currentPeer();\n if (peer && peer !== 'primary' && typeof url === 'string' && shouldProxy(url)) {\n url += (url.indexOf('?') >= 0 ? '&' : '?') + 'peer=' + encodeURIComponent(peer);\n }\n } catch (e) {}\n return new OrigES(url, cfg);\n };\n window.EventSource.prototype = OrigES.prototype;\n window.EventSource.CONNECTING = OrigES.CONNECTING;\n window.EventSource.OPEN = OrigES.OPEN;\n window.EventSource.CLOSED = OrigES.CLOSED;\n }\n }\n // wirePeerProxy() is synchronous and DOM-independent — it just wraps\n // window.fetch + window.EventSource. Fire it IMMEDIATELY (before\n // DOMContentLoaded) so deferred page bundles that issue their first\n // fetch on bundle-parse (e.g. the workflow editor's React useEffect)\n // see the wrapped fetch and pick up the X-Agentx-Peer header. Without\n // this, the workflow editor's load on Mac:4202 hit local /api/workflows\n // (instead of proxying to the selected peer), got a 404, and silently\n // fell back to blank-graph state — looking like a \"new workflow\".\n wirePeerProxy();\n // Which build the daemon is running and since when, on every page; says\n // so when the code on disk is newer. Asked again every minute, so a\n // rebuild or a restart shows without a reload.\n function wireBuild(){\n var el = document.querySelector('.ax-build');\n if (!el || el.dataset.wired) return; el.dataset.wired = '1';\n function span(cls, text){ var s = document.createElement('span'); if (cls) s.className = cls; s.textContent = text; return s; }\n function load(){\n fetch('/api/node/build').then(function(r){ return r.ok ? r.json() : null; }).then(function(b){\n if (!b || !b.version) return;\n el.textContent = '';\n var lines = span('ax-build__lines', '');\n lines.appendChild(span('ax-mono', b.version + (b.commit ? ' · ' + b.commit : '')));\n var d = b.startedAt ? new Date(b.startedAt) : null;\n if (d && !isNaN(d.getTime())) {\n lines.appendChild(span('', 'since ' + d.toLocaleString([], { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' })));\n }\n el.appendChild(lines);\n if (b.diskNewer) {\n var w = span('ax-build__stale', 'restart pending');\n w.setAttribute('role', 'status');\n el.appendChild(w);\n }\n el.title = (b.diskNewer\n ? 'The code on disk is newer than the running daemon. Restart the daemon to load it.'\n : 'The build the daemon is running, and when it started.') + (b.node ? ' Node: ' + b.node : '');\n el.hidden = false;\n }).catch(function(){});\n }\n load();\n setInterval(load, 60000);\n }\n function wire(){ wireHostRewrite(); wireTheme(); wireMesh(); wireBuild(); /* wirePeerProxy already ran */ }\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', wire);\n } else { wire(); }\n})();\n</script>`\n\n/**\n * What the header shows of the daemon's GET /health (served to the page as\n * GET /api/node/build). The dashboard is its own process, so its own build\n * and start time would be the wrong answer.\n */\nexport function headerBuild(health: any): { node: string | null; version: string | null; commit: string | null; startedAt: string | null; diskNewer: boolean } {\n const text = (v: unknown) => (typeof v === \"string\" && v ? v : null)\n return {\n node: text(health?.node?.id),\n version: text(health?.version),\n commit: text(health?.commit),\n startedAt: text(health?.startedAt),\n diskNewer: health?.build?.newer === true,\n }\n}\n\n/** Where the page script (wireBuild) puts the daemon's build. Empty and\n * hidden until the daemon has answered. */\nexport const BUILD_PILL = '<span class=\"ax-build\" hidden></span>'\n\n/**\n * Render the topbar element. Caller places this at the start of <body>.\n * Emits a matching .ax-subheader row only when `subheader` HTML is provided.\n */\nexport function renderTopbar(opts: TopbarOpts): string {\n const peers = opts.peers && opts.peers.length > 0 ? opts.peers : [{ id: \"local\", name: \"this daemon\", dashboardUrl: \"\", primary: true }]\n const currentId = opts.currentPeerId || peers.find((p) => p.primary)?.id || peers[0].id\n const current = peers.find((p) => p.id === currentId) || peers[0]\n\n const meshMenu = peers.map((p) => {\n const isCurrent = p.id === currentId\n const primaryMark = p.primary ? ' <span style=\"color:var(--ax-muted);font-size:10px\">· primary</span>' : \"\"\n const proxyable = !!p.tokenScope && !p.primary\n // Proxyable peers: use data-peer-id so the click handler switches locally\n // via localStorage + reload. Un-proxyable (no token): legacy navigate.\n const href = proxyable\n ? \"#\"\n : p.dashboardUrl\n ? `${p.dashboardUrl.replace(/\\/+$/, \"\")}/admin`\n : \"#\"\n const dataAttr = proxyable ? ` data-peer-id=\"${esc(p.id)}\" data-peer-action=\"proxy\"` : p.primary ? ` data-peer-id=\"primary\" data-peer-action=\"proxy\"` : \"\"\n const hint = proxyable\n ? '<div class=\"url\" style=\"color:var(--ax-accent)\">manage from this dashboard ✓</div>'\n : p.tokenScope\n ? `<div class=\"url\">${esc(p.tokenScope)}</div>`\n : p.dashboardUrl\n ? '<div class=\"url\" style=\"color:var(--ax-muted)\">(open peer dashboard ↗)</div>'\n : \"\"\n return `<a href=\"${esc(href)}\" class=\"${isCurrent ? \"is-current\" : \"\"}\"${dataAttr}>\n <div class=\"row\"><span class=\"dot\"></span><span>${esc(p.name)}${primaryMark}</span></div>\n ${p.dashboardUrl ? `<div class=\"url\">${esc(p.dashboardUrl)}</div>` : \"\"}\n ${hint}\n </a>`\n }).join(\"\")\n\n const meshSelector = `<div class=\"ax-mesh-sel\" aria-haspopup=\"menu\">\n <span class=\"lbl\">managing</span>\n <span class=\"name\">${esc(current.name)}</span>\n <span class=\"chev\">▾</span>\n <div class=\"ax-mesh-menu\" role=\"menu\">${meshMenu}</div>\n </div>`\n\n // Minimal, mesh-first nav. The primary group answers the four ops\n // questions in order: who's alive and what are they doing (Live),\n // what happened (Ledger), what did it cost (Cost), how is it wired\n // (Settings). A second \"Work\" group appears only when the operator\n // actually configured that surface (boards / workflows) — flags are\n // set once per process via setTopbarFeatures().\n //\n // Deliberately NOT in the nav (all still routable by URL):\n // /admin/ledger /admin/cost what happened, what it cost\n // /workflows /inbox BPM surfaces\n // / (boards) Kanban\n // /admin/health /admin/graph /admin/projects\n // /procedures /admin/wiki /glossary /processes\n //\n // The nav is the set of surfaces an operator opens on purpose, not a site\n // map. Workflows earned its way back: automation that stops running reports\n // nothing anywhere else, so it needs somewhere to be found. Every page still\n // listed above is one link away from Live or Settings, and none of them is\n // where an operator starts their day —\n // \"who is alive and what are they doing\" (Live) and \"how is this wired\"\n // (Settings) are. A tab bar that lists every surface an app HAS is a site\n // map, not navigation; it makes the two answers people actually want\n // harder to find, which is the opposite of what chrome is for.\n //\n // Removing a tab does not remove the page. If view counts later show a\n // surface earning its place, adding a tab back is one line.\n type Tab = { id: TopbarTab; label: string; href: string; external?: boolean }\n const groups: Array<{ name: string; tabs: Tab[] }> = [\n {\n name: \"Mesh\",\n tabs: [\n { id: \"live\", label: \"Live\", href: \"/live\" },\n { id: \"mesh\", label: \"Operations\", href: \"/mesh\" },\n { id: \"monitor\", label: \"Monitor\", href: \"/monitor\" },\n { id: \"approvals\", label: \"Approvals\", href: \"/approvals\" },\n { id: \"people\", label: \"People\", href: \"/people\" },\n { id: \"activity\", label: \"Activity\", href: \"/activity\" },\n { id: \"workflows\", label: \"Workflows\", href: \"/workflows\" },\n { id: \"admin\", label: \"Settings\", href: \"/admin\" },\n ],\n },\n ]\n\n const tabs = groups.map((g) => {\n const inner = g.tabs.map((t) => {\n const cls = t.id === opts.activeTab ? \"ax-topbar__tab is-active\" : \"ax-topbar__tab\"\n const extAttrs = t.external ? ` target=\"_blank\" rel=\"noopener\"` : \"\"\n const extGlyph = t.external ? ' <span style=\"font-size:9px;opacity:0.6\">↗</span>' : \"\"\n return `<a href=\"${t.href}\" class=\"${cls}\"${extAttrs}>${t.label}${extGlyph}</a>`\n }).join(\"\")\n return `<div class=\"ax-topbar__group\" title=\"${esc(g.name)} tier\"><span class=\"ax-topbar__group-label\">${esc(g.name)}</span>${inner}</div>`\n }).join(\"\")\n\n const header = `<header class=\"ax-topbar\">\n <div class=\"ax-topbar__left\">\n <div class=\"ax-brand\">\n <span class=\"ax-brand__mark\">AX</span>\n <span class=\"ax-brand__name\">AgentX</span>\n <span class=\"ax-brand__subtitle\">${esc(opts.subtitle)}</span>\n </div>\n <nav class=\"ax-topbar__tabs\">${tabs}</nav>\n </div>\n <div class=\"ax-topbar__right\">\n ${opts.rightExtras || \"\"}\n ${BUILD_PILL}\n ${meshSelector}\n <div class=\"ax-theme-switch\" role=\"tablist\" aria-label=\"Theme\">\n <button data-theme-opt=\"dark\">Dark</button>\n <button data-theme-opt=\"light\">Light</button>\n </div>\n </div>\n</header>${opts.subheader ? `<div class=\"ax-subheader\">${opts.subheader}</div>` : \"\"}`\n return header\n}\n\nfunction esc(s: string): string {\n return String(s || \"\").replace(/[&<>\"']/g, (c) => ({ \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\", '\"': \"&quot;\", \"'\": \"&#39;\" }[c] as string))\n}\n","// --- Markdown to HTML, shared -------------------------------------------\n//\n// One renderer for the wiki, the dashboard's ask-an-agent drawer and the task\n// conversation view. It escapes first and only then adds markup, because\n// everything it renders — wiki articles, agent replies — is written by\n// someone or something other than the person reading it.\n//\n// Deliberately small: headings, bold/italic, code, links, lists, tables,\n// blockquotes and rules, and pictures for a caller that asks. Anything it\n// does not know stays as text.\n\nexport interface MarkdownOptions {\n /** Resolve [[wikilinks]]. Omitted, they render as their display text. */\n wikilink?: (target: string, display: string) => string\n /** Render `![alt](https://…)` as a picture: `true` for up to 8, or the\n * most to show. Web addresses only; pictures past the limit become links.\n * Omitted, a picture stays a link, as it always was. */\n images?: boolean | number\n}\n\nexport function markdownToHtml(text: string, opts: MarkdownOptions = {}): string {\n // Strip section-tag HTML comments before escaping so they don't render\n // as literal \"<!-- tags: ... -->\" in the output.\n // Inlined rather than a module-scope helper: this function is stringified\n // and shipped to the browser, where the minifier renames such a helper and\n // module scope does not travel with the source.\n let html = String(text ?? \"\")\n .replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\").replace(/'/g, \"&#39;\")\n\n // Code blocks (fenced)\n html = html.replace(/```(\\w*)\\n([\\s\\S]*?)```/g, (_m, lang, code) =>\n `<pre><code class=\"lang-${lang}\">${code.trim()}</code></pre>`)\n\n // Inline code\n html = html.replace(/`([^`]+)`/g, '<code>$1</code>')\n\n // Pictures, only for a caller that opts in. Code is left alone. Each\n // picture waits as a placeholder of plain letters and digits until every\n // other rule has run, so no later rule can take its markup apart; one\n // that ends up inside a tag (say, a link's address) becomes its alt text.\n // The text is escaped already, so the address and alt hold no quotes.\n const pics: string[] = []\n const alts: string[] = []\n const mark = opts.images ? \"axpic\" + Math.random().toString(36).slice(2, 10) + \"n\" : \"\"\n if (opts.images) {\n const most = typeof opts.images === \"number\" ? opts.images : 8\n html = html.replace(/<code[^>]*>[\\s\\S]*?<\\/code>|!\\[([^\\]\\n]*)\\]\\(([^)\\s]+)\\)/g, (m: string, alt?: string, src?: string) => {\n if (alt === undefined || src === undefined) return m\n if (!/^https?:\\/\\/[^\\s]+$/i.test(src)) return alt\n if (pics.length >= most) return `[${alt || src}](${src})`\n pics.push(`<img src=\"${src}\" alt=\"${alt}\" loading=\"lazy\" referrerpolicy=\"no-referrer\">`)\n alts.push(alt)\n return mark + (pics.length - 1) + \"z\"\n })\n }\n\n // Tables\n html = html.replace(/^(\\|.+\\|)\\n(\\|[-| :]+\\|)\\n((?:\\|.+\\|\\n?)*)/gm, (_m, header, _sep, body) => {\n const ths = header.split(\"|\").filter((c: string) => c.trim()).map((c: string) => `<th>${c.trim()}</th>`).join(\"\")\n const rows = body.trim().split(\"\\n\").map((row: string) => {\n const tds = row.split(\"|\").filter((c: string) => c.trim()).map((c: string) => `<td>${c.trim()}</td>`).join(\"\")\n return `<tr>${tds}</tr>`\n }).join(\"\")\n return `<table><thead><tr>${ths}</tr></thead><tbody>${rows}</tbody></table>`\n })\n\n // Headings\n html = html.replace(/^#### (.+)$/gm, '<h4>$1</h4>')\n html = html.replace(/^### (.+)$/gm, '<h3>$1</h3>')\n html = html.replace(/^## (.+)$/gm, '<h2>$1</h2>')\n html = html.replace(/^# (.+)$/gm, '<h1>$1</h1>')\n\n // Bold and italic\n html = html.replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>')\n html = html.replace(/\\*(.+?)\\*/g, '<em>$1</em>')\n\n // Wikilinks — support three forms for the [[...]] body:\n // [[Title]] exact article title\n // [[path/to/article]] path without .md (e.g. \"people/alex\")\n // [[path/to/article.md]] full relative path\n // [[Title|custom display text]] optional display override (Obsidian-style)\n html = html.replace(/\\[\\[([^\\]]+)\\]\\]/g, (_m, inner: string) => {\n const [rawTarget, rawDisplay] = inner.split(\"|\", 2)\n const target = rawTarget.trim().replace(/^\\//, \"\")\n const display = (rawDisplay || rawTarget).trim()\n // Without a resolver a wikilink is just words — better than a dead link.\n return opts.wikilink ? opts.wikilink(target, display) : display\n })\n\n // External links\n html = html.replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, '<a href=\"$2\" target=\"_blank\" rel=\"noopener noreferrer\">$1</a>')\n\n // Blockquotes (consecutive `> ` lines → one <blockquote>)\n html = html.replace(/^(>\\s?.*(?:\\n>\\s?.*)*)/gm, (block: string) => {\n const inner = block.split(\"\\n\").map(l => l.replace(/^>\\s?/, \"\")).join(\"<br>\")\n return `<blockquote>${inner}</blockquote>`\n })\n\n // Task-list items (render as plain text with unicode checkbox)\n html = html.replace(/^[-*] \\[( |x|X)\\] (.+)$/gm, (_m, chk, rest) =>\n `<li class=\"task\">${chk.toLowerCase() === \"x\" ? \"☑\" : \"☐\"} ${rest}</li>`)\n\n // Unordered lists\n html = html.replace(/^[-*] (.+)$/gm, '<li>$1</li>')\n\n // Ordered lists — match lines like \"1. foo\" (number, dot, space)\n html = html.replace(/^\\d+\\. (.+)$/gm, '<li data-ordered=\"1\">$1</li>')\n\n // Wrap consecutive ordered <li>s into <ol>. Keep the data-ordered marker\n // on each <li> inside the <ol> so the subsequent <ul> regex below can\n // correctly skip them via negative lookahead. Marker is stripped once all\n // list-wrapping is done.\n html = html.replace(/(<li data-ordered=\"1\">.*?<\\/li>(?:\\n?<li data-ordered=\"1\">.*?<\\/li>)*)/g, '<ol>$1</ol>')\n\n // Wrap consecutive unordered <li>s — the negative lookahead `(?! data-ordered)`\n // ensures ordered <li>s inside the <ol> above are NOT re-wrapped in <ul>.\n html = html.replace(/((?:<li(?! data-ordered)[^>]*>.*?<\\/li>\\n?)+)/g, '<ul>$1</ul>')\n\n // Clean up the ordered marker now that wrapping is settled.\n html = html.replace(/ data-ordered=\"1\"/g, \"\")\n\n // Horizontal rules\n html = html.replace(/^---$/gm, '<hr>')\n\n // Paragraphs — only wrap non-block lines. A paragraph is a run of\n // text that isn't already wrapped in a block-level tag.\n html = html.replace(/\\n\\n+/g, '</p><p>')\n html = `<p>${html}</p>`\n // Unwrap <p>...</p> that now contain block-level elements.\n html = html.replace(/<p>\\s*(<(?:h[1-6]|ul|ol|blockquote|table|pre|hr)\\b[^>]*>[\\s\\S]*?<\\/(?:h[1-6]|ul|ol|blockquote|table|pre)>)\\s*<\\/p>/g, \"$1\")\n html = html.replace(/<p>\\s*<hr>\\s*<\\/p>/g, \"<hr>\")\n // Drop empty paragraphs that remain.\n html = html.replace(/<p>\\s*<\\/p>/g, \"\")\n\n html = html.replace(/<p>(<(?:h[1-4]|pre|table|ul|hr|div))/g, '$1')\n html = html.replace(/(<\\/(?:h[1-4]|pre|table|ul|hr|div)>)<\\/p>/g, '$1')\n html = html.replace(/<p>\\s*<\\/p>/g, '')\n\n if (pics.length) {\n const at = new RegExp(mark + \"(\\\\d+)z\", \"g\")\n html = html.split(/(<[^>]*>)/).map((part) => part.charAt(0) === \"<\"\n ? part.replace(at, (_m, i) => alts[Number(i)])\n : part.replace(at, (_m, i) => pics[Number(i)])).join(\"\")\n }\n\n return html\n}\n","// Shared small utilities for UI renderers.\n\n/**\n * HTML-escape a string so it's safe to interpolate inside attributes or text\n * content. Every page used to ship its own `esc` / `escapeHtml` / `safe` —\n * this is the canonical one.\n */\nexport function esc(s: unknown): string {\n return String(s ?? \"\").replace(/[&<>\"']/g, (c) => ({\n \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\", '\"': \"&quot;\", \"'\": \"&#39;\",\n }[c] as string))\n}\n\n/** Join class names, dropping falsy values. */\nexport function cx(...parts: Array<string | false | null | undefined>): string {\n return parts.filter(Boolean).join(\" \")\n}\n"],"mappings":"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","names":["topbarFeatures","setTopbarFeatures","features","getTopbarFeatures","TOPBAR_HEAD","TOPBAR_CSS","TOPBAR_SCRIPT","headerBuild","health","text","v","BUILD_PILL","renderTopbar","opts","peers","currentId","p","current","meshMenu","isCurrent","primaryMark","proxyable","href","dataAttr","esc","hint","meshSelector","tabs","g","inner","t","cls","extAttrs","extGlyph","s","c","markdownToHtml","text","opts","html","_m","lang","code","pics","alts","mark","most","m","alt","src","header","_sep","body","ths","c","rows","row","inner","rawTarget","rawDisplay","target","display","block","l","chk","rest","at","part","i","esc","s","c"]}
@@ -1,4 +1,4 @@
1
- import{c as A,e as _,f as D}from"./chunk-Y4CYQV7G.js";import{existsSync as O,mkdirSync as M,readdirSync as L,readFileSync as U,renameSync as j,writeFileSync as F}from"fs";import{resolve as P}from"path";function $(e,t){let n=e.list?` in the "${e.list}" list`:"",r=[`This card came from the operator's reminder "${e.title}"${n} (id ${e.id}).`];return r.push(t?`Do what the operator chose, then tick the reminder off: remindctl complete ${e.id}`:"Leave the reminder open; it will come back at a later check-in."),r}var R=["discard","keep","pause","approve"],f={title:120,ask:300,recommend:300,source:500,note:500,pendingPerAgent:25},q={defaultExpiryDays:3,maxExpiryDays:30};function x(e){return P(e,".agentx","approvals")}var z=/^[a-z0-9][a-z0-9-]{0,80}$/;function N(e){return z.test(e)}function T(e,t){if(!N(t))throw new Error(`invalid card id: ${t}`);return P(x(e),`${t}.json`)}function B(e,t){let n=new Date(e).toISOString().slice(0,10),r=t.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40);return`${n}-${r||"card"}-${Math.random().toString(36).slice(2,6)}`}function w(e,t){M(x(e),{recursive:!0});let n=T(e,t.id),r=`${n}.${process.pid}.tmp`;F(r,JSON.stringify(t,null,2)+`
2
- `),j(r,n)}function S(e,t){if(!N(t))return null;try{return JSON.parse(U(T(e,t),"utf-8"))}catch{return null}}function C(e,t){let n=x(e);if(!O(n))return[];let r=[];for(let i of L(n).filter(s=>s.endsWith(".json")&&!s.startsWith("_")).sort()){let s=S(e,i.slice(0,-5));s&&(!t||s.status===t)&&r.push(s)}return r}function l(e){return typeof e=="string"?e.replace(/\s+/g," ").trim():""}function J(e,t,n){let r=t+n.maxExpiryDays*864e5,i,s=l(e);if(!s)i=t+n.defaultExpiryDays*864e5;else{let a=/^(\d+)\s*([hd])$/i.exec(s);if(a)i=t+Number(a[1])*(a[2].toLowerCase()==="d"?864e5:36e5);else if(i=Date.parse(s),Number.isNaN(i))return{ok:!1,error:`expires must be an ISO date or time, or like "12h" / "3d" (got "${s}")`}}return i<=t?{ok:!1,error:"expires must be in the future"}:{ok:!0,at:new Date(Math.min(i,r)).toISOString()}}function V(e,t={}){let n=t.now??Date.now(),r=t.settings??q,i=l(e.title),s=l(e.ask),a=l(e.recommend),c=l(e.raised_by);if(!i)return{ok:!1,error:"title is required"};if(!s)return{ok:!1,error:"ask is required: the yes/no question"};if(!a)return{ok:!1,error:"recommend is required: your advice and why, in one line"};if(!c)return{ok:!1,error:"raised_by is required: the agent id"};if(i.length>f.title)return{ok:!1,error:`title is longer than ${f.title} characters`};if(s.length>f.ask)return{ok:!1,error:`ask is longer than ${f.ask} characters`};if(a.length>f.recommend)return{ok:!1,error:`recommend is longer than ${f.recommend} characters`};let y=l(e.if_silent).toLowerCase();if(!R.includes(y))return{ok:!1,error:`if_silent must be one of ${R.join(", ")}`};let d=J(e.expires,n,r);if(!d.ok)return d;let u=l(e.source);if(u.length>f.source)return{ok:!1,error:`source is longer than ${f.source} characters`};if(u&&/^[a-z][a-z0-9+.-]*:/i.test(u)&&!/^https?:\/\//i.test(u))return{ok:!1,error:"source must be an http(s) link or a plain reference"};let h=A(e);if(!h.ok)return h;let p,o=e.reply;return o&&typeof o=="object"&&l(o.channel)&&l(o.chatId)&&(p={channel:l(o.channel),chatId:l(o.chatId),...l(o.accountId)?{accountId:l(o.accountId)}:{}}),{ok:!0,card:{id:B(n,i),title:i,ask:s,recommend:a,if_silent:y,expires:d.at,...u?{source:u}:{},raised_by:c,created_at:new Date(n).toISOString(),...p?{reply:p}:{},...h.value,...t.origin?{origin:t.origin}:{},status:"pending"}}}function oe(e,t,n={}){let r=V(t,n);if(!r.ok)return r;let i=C(e,"pending").filter(s=>s.raised_by===r.card.raised_by).length;return i>=f.pendingPerAgent?{ok:!1,error:`${r.card.raised_by} already has ${i} cards waiting; wait for decisions before raising more`}:(w(e,r.card),r)}function ae(e,t,n,r={}){let i=S(e,t);if(!i)return{ok:!1,error:`no card "${t}"`};if(i.status!=="pending")return{ok:!1,error:`card "${t}" is already ${i.status}`};let s=_(i,n,r);if(!s.ok)return s;let a=l(r.note).slice(0,f.note),c={...i,status:"decided",verdict:n,decided_by:r.by??"operator",decided_at:new Date(r.now??Date.now()).toISOString(),...a?{note:a}:{},...s.choice?{choice:s.choice}:{},...s.text?{text:s.text}:{}};return w(e,c),{ok:!0,card:c}}function ce(e,t=Date.now()){let n=[];for(let r of C(e,"pending")){if(Date.parse(r.expires)>t)continue;let i={...r,status:"expired",outcome:r.if_silent,decided_by:"expiry",decided_at:new Date(t).toISOString()};w(e,i),n.push(i)}return n}function de(e){return C(e).filter(t=>t.status!=="pending"&&!t.agent_notified_at)}function ue(e,t,n=Date.now()){let r=S(e,t);!r||r.agent_notified_at||w(e,{...r,agent_notified_at:new Date(n).toISOString()})}function le(e){let t=e.status==="decided"?e.verdict==="yes"?"The operator said YES.":"The operator said NO.":`Nobody answered before it expired, so the default applied: ${e.outcome??e.if_silent}.`,n=[`[Approval result] Your decision card "${e.title}" (${e.id}) is closed.`,`Question: ${e.ask}`,t];return e.status==="decided"&&e.verdict==="yes"&&n.push(...D(e)),e.note&&n.push(`Operator note: ${e.note}`),e.source&&n.push(`Source: ${e.source}`),e.origin&&n.push(...$(e.origin,e.status==="decided"&&e.verdict==="yes")),e.reply&&n.push(`You raised it from ${e.reply.channel} chat ${e.reply.chatId}; reply there if the requester should know.`),n.push("Act on this result now. Do not raise the same card again."),n.join(`
3
- `)}function b(e,t=160){if(!e)return e;let n=e.replace(/\s+/g," ").trim();return n.length>t?n.slice(0,t-1)+"\u2026":n}function W(e){let t=new Map;for(let r of e.cronRuns??[]){let i=t.get(r.jobId);(!i||i.startedAt<r.startedAt)&&t.set(r.jobId,r)}let n={success:0,failed:0};for(let r of e.cronRuns??[])r.status==="success"?n.success++:n.failed++;return{id:e.id,name:e.name,url:e.url,reachable:e.reachable,error:b(e.error),uptimeSec:e.uptimeSec,inflight:e.inflight,restart:e.restart?.state&&e.restart.state!=="none"?e.restart:void 0,agents:e.agents.map(r=>({id:r.id,name:r.name,tier:r.tier,active:r.active,errors:r.errors,lastActive:r.lastActive,last:r.lastSummary?{text:b(r.lastSummary.text),at:r.lastSummary.at,ok:r.lastSummary.ok}:void 0})),crons:{today:n,items:(e.crons??[]).map(r=>{let i=t.get(r.id);return{id:r.id,enabled:r.enabled,schedule:r.schedule,agent:r.agent,nextRun:r.nextRun,consecutiveErrors:r.consecutiveErrors,last:i?{status:i.status,at:i.startedAt,text:b(i.status==="success"?i.responseSummary:i.errorSummary)}:void 0}})}}}function G(e){let t=[];for(let n of e)for(let r of n.agents)for(let i of r.runningTasks??[])t.push({node:n.url,nodeName:n.name,agentId:r.id,agentName:r.name,taskId:i.id,preview:b(i.messagePreview),channel:i.channel,startedAt:i.startedAt});return t.sort((n,r)=>n.startedAt<r.startedAt?1:-1)}var H=/^\/api\/app\/tasks\/(cancel|followup)$/;async function ge(e,t,n,r,i,s){let a=new URL(e.url||n,"http://localhost");if(r==="GET"&&(n==="/api/app/fleet"||n==="/api/app/activity")){let o=Y(a.searchParams.get("timezone")),g=await s.snapshot({date:Z(o),timezone:o});return n==="/api/app/activity"?m(t,200,{ts:g.ts,tasks:G(g.nodes)}):m(t,200,{ts:g.ts,nodes:g.nodes.map(W)})}if(r==="GET"&&n==="/api/app/approvals")return m(t,200,{nodes:await s.approvals()});if(r!=="POST")return!1;let c=n.match(H);if(!(c||["/api/app/nodes/restart","/api/app/nodes/reload","/api/app/crons/toggle","/api/app/approvals/decide"].includes(n)))return!1;let d;try{d=await I(e)}catch(o){return m(t,400,{error:o.message})}let u=k(d.node);if(!u)return m(t,400,{error:"node is required"});let h=`operator (phone: ${i})`,p;if(c){let o=k(d.taskId);if(!o)return m(t,400,{error:"taskId is required"});let g=`/api/tasks/${encodeURIComponent(o)}/${c[1]}`;if(c[1]==="cancel")p=await s.nodePost(u,g,{reason:`cancelled by ${h}`});else{let v=k(d.message);if(!v)return m(t,400,{error:"message is required"});p=await s.nodePost(u,g,{message:v,sender:h})}}else if(n==="/api/app/nodes/restart")p=await s.nodePost(u,d.cancel===!0?"/daemon/restart/cancel":"/daemon/restart",{});else if(n==="/api/app/nodes/reload")p=await s.nodePost(u,"/reload",{});else if(n==="/api/app/crons/toggle"){let o=k(d.cronId);if(!o||typeof d.enabled!="boolean")return m(t,400,{error:"cronId and enabled (true or false) are required"});p=await s.nodePost(u,`/crons/${encodeURIComponent(o)}/enabled`,{enabled:d.enabled})}else{let o=k(d.key),g=d.action;if(!o||g!=="yes"&&g!=="no"&&g!=="later")return m(t,400,{error:"key and action (yes, no or later) are required"});p=await s.decide(u,o,g,h)}return m(t,p.status,p.body)}function k(e){return typeof e=="string"?e.trim():""}function Y(e){if(!e)return"UTC";try{return new Intl.DateTimeFormat("en-US",{timeZone:e}),e}catch{return"UTC"}}function Z(e,t=new Date){return new Intl.DateTimeFormat("en-CA",{timeZone:e,year:"numeric",month:"2-digit",day:"2-digit"}).format(t)}async function I(e,t=16*1024){let n=0,r=[];for await(let a of e){if(n+=a.length,n>t)throw new Error("body too large");r.push(a)}let i=Buffer.concat(r).toString("utf-8").trim();if(!i)return{};let s=JSON.parse(i);if(!s||typeof s!="object"||Array.isArray(s))throw new Error("expected a JSON object");return s}function m(e,t,n){return e.writeHead(t,{"Content-Type":"application/json","Cache-Control":"no-store"}),e.end(JSON.stringify(n)),!0}var K={perClient:5,global:10,windowMs:10*60*1e3,lockoutMs:5*60*1e3},E=class{constructor(t=K,n=Date.now){this.opts=t;this.now=n}clients=new Map;all={failures:[],lockedUntil:0};retryAfter(t){let n=this.now(),r=Math.max(this.all.lockedUntil,this.clients.get(t)?.lockedUntil??0);return r>n?Math.ceil((r-n)/1e3):0}fail(t){let n=this.clients.get(t);n||this.clients.set(t,n={failures:[],lockedUntil:0}),this.bump(n,this.opts.perClient),this.bump(this.all,this.opts.global)}succeed(t){this.clients.delete(t)}bump(t,n){let r=this.now();t.failures=t.failures.filter(i=>r-i<this.opts.windowMs),t.failures.push(r),t.failures.length>=n&&(t.lockedUntil=r+this.opts.lockoutMs)}},Q="That code didn't work. Check it, or run agentx app pair for a new one.",X=400;async function ye(e,t){let n=Date.now(),r=t.log??(d=>console.log(d)),i=e.socket.remoteAddress||"unknown",s=t.limiter.retryAfter(i);if(s>0)return r(`[app] pair-code refused from ${i}: locked out for ${s}s more`),{status:429,body:{error:"too many attempts",retryAfter:s},retryAfter:s};let a=null;try{a=(await I(e,1024)).code}catch{}let c=t.codes.redeem(a),y=c?t.verify(c.token):null;return await ee(n,t.minMs??X),!c||!y?(t.limiter.fail(i),r(`[app] pair-code failed from ${i}`),{status:401,body:{error:Q}}):(t.limiter.succeed(i),r(`[app] pair-code paired ${c.tokenId} (${y}) from ${i}`),{status:200,body:{device:y},token:c.token})}async function ee(e,t){let n=e+t-Date.now();n>0&&await new Promise(r=>setTimeout(r,n))}export{R as a,f as b,x as c,N as d,S as e,C as f,oe as g,ae as h,ce as i,de as j,ue as k,le as l,ge as m,I as n,E as o,Q as p,ye as q};
4
- //# sourceMappingURL=chunk-XIYWXCNU.js.map
1
+ import{c as A,e as _,f as D}from"./chunk-Y4CYQV7G.js";import{existsSync as E,mkdirSync as M,readdirSync as L,readFileSync as U,renameSync as q,writeFileSync as j}from"fs";import{resolve as P}from"path";function $(e,t){let n=e.list?` in the "${e.list}" list`:"",r=[`This card came from the operator's reminder "${e.title}"${n} (id ${e.id}).`];return r.push(t?`Do what the operator chose, then tick the reminder off: remindctl complete ${e.id}`:"Leave the reminder open; it will come back at a later check-in."),r}var R=["discard","keep","pause","approve"],f={title:120,ask:300,recommend:300,source:500,note:500,pendingPerAgent:25},F={defaultExpiryDays:3,maxExpiryDays:30};function x(e){return P(e,".agentx","approvals")}var z=/^[a-z0-9][a-z0-9-]{0,80}$/;function N(e){return z.test(e)}function O(e,t){if(!N(t))throw new Error(`invalid card id: ${t}`);return P(x(e),`${t}.json`)}function B(e,t){let n=new Date(e).toISOString().slice(0,10),r=t.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40);return`${n}-${r||"card"}-${Math.random().toString(36).slice(2,6)}`}function w(e,t){M(x(e),{recursive:!0});let n=O(e,t.id),r=`${n}.${process.pid}.tmp`;j(r,JSON.stringify(t,null,2)+`
2
+ `),q(r,n)}function S(e,t){if(!N(t))return null;try{return JSON.parse(U(O(e,t),"utf-8"))}catch{return null}}function C(e,t){let n=x(e);if(!E(n))return[];let r=[];for(let i of L(n).filter(s=>s.endsWith(".json")&&!s.startsWith("_")).sort()){let s=S(e,i.slice(0,-5));s&&(!t||s.status===t)&&r.push(s)}return r}function l(e){return typeof e=="string"?e.replace(/\s+/g," ").trim():""}function J(e,t,n){let r=t+n.maxExpiryDays*864e5,i,s=l(e);if(!s)i=t+n.defaultExpiryDays*864e5;else{let a=/^(\d+)\s*([hd])$/i.exec(s);if(a)i=t+Number(a[1])*(a[2].toLowerCase()==="d"?864e5:36e5);else if(i=Date.parse(s),Number.isNaN(i))return{ok:!1,error:`expires must be an ISO date or time, or like "12h" / "3d" (got "${s}")`}}return i<=t?{ok:!1,error:"expires must be in the future"}:{ok:!0,at:new Date(Math.min(i,r)).toISOString()}}function V(e,t={}){let n=t.now??Date.now(),r=t.settings??F,i=l(e.title),s=l(e.ask),a=l(e.recommend),c=l(e.raised_by);if(!i)return{ok:!1,error:"title is required"};if(!s)return{ok:!1,error:"ask is required: the yes/no question"};if(!a)return{ok:!1,error:"recommend is required: your advice and why, in one line"};if(!c)return{ok:!1,error:"raised_by is required: the agent id"};if(i.length>f.title)return{ok:!1,error:`title is longer than ${f.title} characters`};if(s.length>f.ask)return{ok:!1,error:`ask is longer than ${f.ask} characters`};if(a.length>f.recommend)return{ok:!1,error:`recommend is longer than ${f.recommend} characters`};let y=l(e.if_silent).toLowerCase();if(!R.includes(y))return{ok:!1,error:`if_silent must be one of ${R.join(", ")}`};let d=J(e.expires,n,r);if(!d.ok)return d;let u=l(e.source);if(u.length>f.source)return{ok:!1,error:`source is longer than ${f.source} characters`};if(u&&/^[a-z][a-z0-9+.-]*:/i.test(u)&&!/^https?:\/\//i.test(u))return{ok:!1,error:"source must be an http(s) link or a plain reference"};let h=A(e);if(!h.ok)return h;let p,o=e.reply;return o&&typeof o=="object"&&l(o.channel)&&l(o.chatId)&&(p={channel:l(o.channel),chatId:l(o.chatId),...l(o.accountId)?{accountId:l(o.accountId)}:{}}),{ok:!0,card:{id:B(n,i),title:i,ask:s,recommend:a,if_silent:y,expires:d.at,...u?{source:u}:{},raised_by:c,created_at:new Date(n).toISOString(),...p?{reply:p}:{},...h.value,...t.origin?{origin:t.origin}:{},status:"pending"}}}function oe(e,t,n={}){let r=V(t,n);if(!r.ok)return r;let i=C(e,"pending").filter(s=>s.raised_by===r.card.raised_by).length;return i>=f.pendingPerAgent?{ok:!1,error:`${r.card.raised_by} already has ${i} cards waiting; wait for decisions before raising more`}:(w(e,r.card),r)}function ae(e,t,n,r={}){let i=S(e,t);if(!i)return{ok:!1,error:`no card "${t}"`};if(i.status!=="pending")return{ok:!1,error:`card "${t}" is already ${i.status}`};let s=_(i,n,r);if(!s.ok)return s;let a=l(r.note).slice(0,f.note),c={...i,status:"decided",verdict:n,decided_by:r.by??"operator",decided_at:new Date(r.now??Date.now()).toISOString(),...a?{note:a}:{},...s.choice?{choice:s.choice}:{},...s.text?{text:s.text}:{}};return w(e,c),{ok:!0,card:c}}function ce(e,t=Date.now()){let n=[];for(let r of C(e,"pending")){if(Date.parse(r.expires)>t)continue;let i={...r,status:"expired",outcome:r.if_silent,decided_by:"expiry",decided_at:new Date(t).toISOString()};w(e,i),n.push(i)}return n}function de(e){return C(e).filter(t=>t.status!=="pending"&&!t.agent_notified_at)}function ue(e,t,n=Date.now()){let r=S(e,t);!r||r.agent_notified_at||w(e,{...r,agent_notified_at:new Date(n).toISOString()})}function le(e){let t=e.status==="decided"?e.verdict==="yes"?"The operator said YES.":"The operator said NO.":`Nobody answered before it expired, so the default applied: ${e.outcome??e.if_silent}.`,n=[`[Approval result] Your decision card "${e.title}" (${e.id}) is closed.`,`Question: ${e.ask}`,t];return e.status==="decided"&&e.verdict==="yes"&&n.push(...D(e)),e.note&&n.push(`Operator note: ${e.note}`),e.source&&n.push(`Source: ${e.source}`),e.origin?.kind==="reminder"&&n.push(...$(e.origin,e.status==="decided"&&e.verdict==="yes")),e.reply&&n.push(`You raised it from ${e.reply.channel} chat ${e.reply.chatId}; reply there if the requester should know.`),n.push("Act on this result now. Do not raise the same card again."),n.join(`
3
+ `)}function b(e,t=160){if(!e)return e;let n=e.replace(/\s+/g," ").trim();return n.length>t?n.slice(0,t-1)+"\u2026":n}function W(e){let t=new Map;for(let r of e.cronRuns??[]){let i=t.get(r.jobId);(!i||i.startedAt<r.startedAt)&&t.set(r.jobId,r)}let n={success:0,failed:0};for(let r of e.cronRuns??[])r.status==="success"?n.success++:n.failed++;return{id:e.id,name:e.name,url:e.url,reachable:e.reachable,error:b(e.error),uptimeSec:e.uptimeSec,inflight:e.inflight,restart:e.restart?.state&&e.restart.state!=="none"?e.restart:void 0,agents:e.agents.map(r=>({id:r.id,name:r.name,tier:r.tier,active:r.active,errors:r.errors,lastActive:r.lastActive,last:r.lastSummary?{text:b(r.lastSummary.text),at:r.lastSummary.at,ok:r.lastSummary.ok}:void 0})),crons:{today:n,items:(e.crons??[]).map(r=>{let i=t.get(r.id);return{id:r.id,enabled:r.enabled,schedule:r.schedule,agent:r.agent,nextRun:r.nextRun,consecutiveErrors:r.consecutiveErrors,last:i?{status:i.status,at:i.startedAt,text:b(i.status==="success"?i.responseSummary:i.errorSummary)}:void 0}})}}}function G(e){let t=[];for(let n of e)for(let r of n.agents)for(let i of r.runningTasks??[])t.push({node:n.url,nodeName:n.name,agentId:r.id,agentName:r.name,taskId:i.id,preview:b(i.messagePreview),channel:i.channel,startedAt:i.startedAt});return t.sort((n,r)=>n.startedAt<r.startedAt?1:-1)}var H=/^\/api\/app\/tasks\/(cancel|followup)$/;async function ge(e,t,n,r,i,s){let a=new URL(e.url||n,"http://localhost");if(r==="GET"&&(n==="/api/app/fleet"||n==="/api/app/activity")){let o=Y(a.searchParams.get("timezone")),g=await s.snapshot({date:Z(o),timezone:o});return n==="/api/app/activity"?m(t,200,{ts:g.ts,tasks:G(g.nodes)}):m(t,200,{ts:g.ts,nodes:g.nodes.map(W)})}if(r==="GET"&&n==="/api/app/approvals")return m(t,200,{nodes:await s.approvals()});if(r!=="POST")return!1;let c=n.match(H);if(!(c||["/api/app/nodes/restart","/api/app/nodes/reload","/api/app/crons/toggle","/api/app/approvals/decide"].includes(n)))return!1;let d;try{d=await I(e)}catch(o){return m(t,400,{error:o.message})}let u=k(d.node);if(!u)return m(t,400,{error:"node is required"});let h=`operator (phone: ${i})`,p;if(c){let o=k(d.taskId);if(!o)return m(t,400,{error:"taskId is required"});let g=`/api/tasks/${encodeURIComponent(o)}/${c[1]}`;if(c[1]==="cancel")p=await s.nodePost(u,g,{reason:`cancelled by ${h}`});else{let v=k(d.message);if(!v)return m(t,400,{error:"message is required"});p=await s.nodePost(u,g,{message:v,sender:h})}}else if(n==="/api/app/nodes/restart")p=await s.nodePost(u,d.cancel===!0?"/daemon/restart/cancel":"/daemon/restart",{});else if(n==="/api/app/nodes/reload")p=await s.nodePost(u,"/reload",{});else if(n==="/api/app/crons/toggle"){let o=k(d.cronId);if(!o||typeof d.enabled!="boolean")return m(t,400,{error:"cronId and enabled (true or false) are required"});p=await s.nodePost(u,`/crons/${encodeURIComponent(o)}/enabled`,{enabled:d.enabled})}else{let o=k(d.key),g=d.action;if(!o||g!=="yes"&&g!=="no"&&g!=="later")return m(t,400,{error:"key and action (yes, no or later) are required"});p=await s.decide(u,o,g,h)}return m(t,p.status,p.body)}function k(e){return typeof e=="string"?e.trim():""}function Y(e){if(!e)return"UTC";try{return new Intl.DateTimeFormat("en-US",{timeZone:e}),e}catch{return"UTC"}}function Z(e,t=new Date){return new Intl.DateTimeFormat("en-CA",{timeZone:e,year:"numeric",month:"2-digit",day:"2-digit"}).format(t)}async function I(e,t=16*1024){let n=0,r=[];for await(let a of e){if(n+=a.length,n>t)throw new Error("body too large");r.push(a)}let i=Buffer.concat(r).toString("utf-8").trim();if(!i)return{};let s=JSON.parse(i);if(!s||typeof s!="object"||Array.isArray(s))throw new Error("expected a JSON object");return s}function m(e,t,n){return e.writeHead(t,{"Content-Type":"application/json","Cache-Control":"no-store"}),e.end(JSON.stringify(n)),!0}var K={perClient:5,global:10,windowMs:10*60*1e3,lockoutMs:5*60*1e3},T=class{constructor(t=K,n=Date.now){this.opts=t;this.now=n}clients=new Map;all={failures:[],lockedUntil:0};retryAfter(t){let n=this.now(),r=Math.max(this.all.lockedUntil,this.clients.get(t)?.lockedUntil??0);return r>n?Math.ceil((r-n)/1e3):0}fail(t){let n=this.clients.get(t);n||this.clients.set(t,n={failures:[],lockedUntil:0}),this.bump(n,this.opts.perClient),this.bump(this.all,this.opts.global)}succeed(t){this.clients.delete(t)}bump(t,n){let r=this.now();t.failures=t.failures.filter(i=>r-i<this.opts.windowMs),t.failures.push(r),t.failures.length>=n&&(t.lockedUntil=r+this.opts.lockoutMs)}},Q="That code didn't work. Check it, or run agentx app pair for a new one.",X=400;async function ye(e,t){let n=Date.now(),r=t.log??(d=>console.log(d)),i=e.socket.remoteAddress||"unknown",s=t.limiter.retryAfter(i);if(s>0)return r(`[app] pair-code refused from ${i}: locked out for ${s}s more`),{status:429,body:{error:"too many attempts",retryAfter:s},retryAfter:s};let a=null;try{a=(await I(e,1024)).code}catch{}let c=t.codes.redeem(a),y=c?t.verify(c.token):null;return await ee(n,t.minMs??X),!c||!y?(t.limiter.fail(i),r(`[app] pair-code failed from ${i}`),{status:401,body:{error:Q}}):(t.limiter.succeed(i),r(`[app] pair-code paired ${c.tokenId} (${y}) from ${i}`),{status:200,body:{device:y},token:c.token})}async function ee(e,t){let n=e+t-Date.now();n>0&&await new Promise(r=>setTimeout(r,n))}export{R as a,f as b,x as c,N as d,S as e,C as f,oe as g,ae as h,ce as i,de as j,ue as k,le as l,ge as m,I as n,T as o,Q as p,ye as q};
4
+ //# sourceMappingURL=chunk-E5OV6RTG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/approvals/cards.ts","../src/approvals/origin.ts","../src/daemon/app-fleet.ts","../src/daemon/app-pair-code.ts"],"sourcesContent":["import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, writeFileSync } from \"fs\"\nimport { resolve } from \"path\"\nimport { answerLines, buildChoices, resolveAnswer, type CardChoices } from \"./choices\"\nimport { originLines, type CardOrigin } from \"./origin\"\n\n// --- Decision cards: what an agent asks the operator ---\n//\n// An agent that needs a yes/no from the operator writes a card instead of\n// asking in chat. A card says what it is, the question, what the agent\n// recommends, and what happens if nobody answers by `expires`. Nothing\n// waits forever: past `expires` the daemon applies `if_silent` (sweep.ts).\n//\n// Storage: one JSON file per card under `.agentx/approvals/`, written\n// atomically, like wiki proposals. The CLI, the dashboard and the daemon\n// are separate processes and all read the same files.\n//\n// Field names match the API (`if_silent`, `raised_by`), so a stored card is\n// exactly what `POST /approvals` accepted.\n\nexport type IfSilent = \"discard\" | \"keep\" | \"pause\" | \"approve\"\nexport const IF_SILENT_VALUES: readonly IfSilent[] = [\"discard\", \"keep\", \"pause\", \"approve\"]\n\nexport type CardStatus = \"pending\" | \"decided\" | \"expired\"\nexport type Verdict = \"yes\" | \"no\"\n\n/** Where the agent was asked, so the verdict can be taken back there. */\nexport interface ReplyTarget {\n channel: string\n chatId: string\n accountId?: string\n}\n\nexport interface DecisionCard extends CardChoices {\n id: string\n title: string\n ask: string\n recommend: string\n if_silent: IfSilent\n /** ISO time the default applies. Always set. */\n expires: string\n /** Link to the draft, PR or issue. */\n source?: string\n raised_by: string\n created_at: string\n reply?: ReplyTarget\n status: CardStatus\n /** The operator's answer (status \"decided\"). */\n verdict?: Verdict\n /** What applied on expiry (status \"expired\"). */\n outcome?: IfSilent\n decided_by?: string\n decided_at?: string\n note?: string\n /** The option the operator picked, when the card offered choices. */\n choice?: string\n /** The message the operator approved, after any edit. */\n text?: string\n /** Set once the raising agent has been told the result. */\n agent_notified_at?: string\n /** What the card is about, when the daemon raised it (origin.ts). */\n origin?: CardOrigin\n}\n\nexport const CARD_LIMITS = {\n title: 120,\n ask: 300,\n recommend: 300,\n source: 500,\n note: 500,\n /** Pending cards one agent may have open at a time. */\n pendingPerAgent: 25,\n} as const\n\nexport interface CardSettings {\n defaultExpiryDays: number\n maxExpiryDays: number\n}\n\nexport const DEFAULT_CARD_SETTINGS: CardSettings = { defaultExpiryDays: 3, maxExpiryDays: 30 }\n\nexport function approvalsDir(root: string): string {\n return resolve(root, \".agentx\", \"approvals\")\n}\n\nconst CARD_ID_RE = /^[a-z0-9][a-z0-9-]{0,80}$/\n\nexport function isValidCardId(id: string): boolean {\n return CARD_ID_RE.test(id)\n}\n\nfunction fileFor(root: string, id: string): string {\n if (!isValidCardId(id)) throw new Error(`invalid card id: ${id}`)\n return resolve(approvalsDir(root), `${id}.json`)\n}\n\nexport function newCardId(now: number, title: string): string {\n const day = new Date(now).toISOString().slice(0, 10)\n const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, \"-\").replace(/^-+|-+$/g, \"\").slice(0, 40)\n return `${day}-${slug || \"card\"}-${Math.random().toString(36).slice(2, 6)}`\n}\n\nexport function saveCard(root: string, card: DecisionCard): void {\n mkdirSync(approvalsDir(root), { recursive: true })\n const path = fileFor(root, card.id)\n const tmp = `${path}.${process.pid}.tmp`\n writeFileSync(tmp, JSON.stringify(card, null, 2) + \"\\n\")\n renameSync(tmp, path)\n}\n\nexport function readCard(root: string, id: string): DecisionCard | null {\n if (!isValidCardId(id)) return null\n try {\n return JSON.parse(readFileSync(fileFor(root, id), \"utf-8\")) as DecisionCard\n } catch {\n return null\n }\n}\n\n/** Cards, oldest first; only `status` ones when given. */\nexport function listCards(root: string, status?: CardStatus): DecisionCard[] {\n const dir = approvalsDir(root)\n if (!existsSync(dir)) return []\n const out: DecisionCard[] = []\n for (const f of readdirSync(dir).filter((f) => f.endsWith(\".json\") && !f.startsWith(\"_\")).sort()) {\n const c = readCard(root, f.slice(0, -5))\n if (c && (!status || c.status === status)) out.push(c)\n }\n return out\n}\n\nfunction oneLine(v: unknown): string {\n return typeof v === \"string\" ? v.replace(/\\s+/g, \" \").trim() : \"\"\n}\n\n/**\n * `expires` as given by an agent: an ISO date or time, or a relative\n * \"12h\" / \"3d\". Unset: the default. Always capped at maxExpiryDays, so a\n * card can't be parked for a year.\n */\nexport function resolveExpiry(input: unknown, now: number, settings: CardSettings): { ok: true; at: string } | { ok: false; error: string } {\n const max = now + settings.maxExpiryDays * 86_400_000\n let at: number\n const raw = oneLine(input)\n if (!raw) {\n at = now + settings.defaultExpiryDays * 86_400_000\n } else {\n const rel = /^(\\d+)\\s*([hd])$/i.exec(raw)\n if (rel) {\n at = now + Number(rel[1]) * (rel[2].toLowerCase() === \"d\" ? 86_400_000 : 3_600_000)\n } else {\n at = Date.parse(raw)\n if (Number.isNaN(at)) return { ok: false, error: `expires must be an ISO date or time, or like \"12h\" / \"3d\" (got \"${raw}\")` }\n }\n }\n if (at <= now) return { ok: false, error: \"expires must be in the future\" }\n return { ok: true, at: new Date(Math.min(at, max)).toISOString() }\n}\n\nexport interface CardInput {\n title?: unknown\n ask?: unknown\n recommend?: unknown\n if_silent?: unknown\n expires?: unknown\n source?: unknown\n raised_by?: unknown\n reply?: unknown\n choices?: unknown\n draft?: unknown\n say?: unknown\n context?: unknown\n}\n\n/** Validate what an agent sent and build a pending card. Never saves. */\nexport function buildCard(\n input: CardInput,\n opts: { now?: number; settings?: CardSettings; origin?: CardOrigin } = {},\n): { ok: true; card: DecisionCard } | { ok: false; error: string } {\n const now = opts.now ?? Date.now()\n const settings = opts.settings ?? DEFAULT_CARD_SETTINGS\n const title = oneLine(input.title)\n const ask = oneLine(input.ask)\n const recommend = oneLine(input.recommend)\n const raisedBy = oneLine(input.raised_by)\n if (!title) return { ok: false, error: \"title is required\" }\n if (!ask) return { ok: false, error: \"ask is required: the yes/no question\" }\n if (!recommend) return { ok: false, error: \"recommend is required: your advice and why, in one line\" }\n if (!raisedBy) return { ok: false, error: \"raised_by is required: the agent id\" }\n if (title.length > CARD_LIMITS.title) return { ok: false, error: `title is longer than ${CARD_LIMITS.title} characters` }\n if (ask.length > CARD_LIMITS.ask) return { ok: false, error: `ask is longer than ${CARD_LIMITS.ask} characters` }\n if (recommend.length > CARD_LIMITS.recommend) return { ok: false, error: `recommend is longer than ${CARD_LIMITS.recommend} characters` }\n const ifSilent = oneLine(input.if_silent).toLowerCase()\n if (!IF_SILENT_VALUES.includes(ifSilent as IfSilent)) {\n return { ok: false, error: `if_silent must be one of ${IF_SILENT_VALUES.join(\", \")}` }\n }\n const expiry = resolveExpiry(input.expires, now, settings)\n if (!expiry.ok) return expiry\n const source = oneLine(input.source)\n if (source.length > CARD_LIMITS.source) return { ok: false, error: `source is longer than ${CARD_LIMITS.source} characters` }\n if (source && /^[a-z][a-z0-9+.-]*:/i.test(source) && !/^https?:\\/\\//i.test(source)) {\n return { ok: false, error: \"source must be an http(s) link or a plain reference\" }\n }\n const extras = buildChoices(input)\n if (!extras.ok) return extras\n let reply: ReplyTarget | undefined\n const r = input.reply as Record<string, unknown> | undefined\n if (r && typeof r === \"object\" && oneLine(r.channel) && oneLine(r.chatId)) {\n reply = { channel: oneLine(r.channel), chatId: oneLine(r.chatId), ...(oneLine(r.accountId) ? { accountId: oneLine(r.accountId) } : {}) }\n }\n return {\n ok: true,\n card: {\n id: newCardId(now, title),\n title,\n ask,\n recommend,\n if_silent: ifSilent as IfSilent,\n expires: expiry.at,\n ...(source ? { source } : {}),\n raised_by: raisedBy,\n created_at: new Date(now).toISOString(),\n ...(reply ? { reply } : {}),\n ...extras.value,\n // Set by the daemon only (check-ins), never from what an agent sent.\n ...(opts.origin ? { origin: opts.origin } : {}),\n status: \"pending\",\n },\n }\n}\n\n/** Validate, enforce the per-agent cap, and save. */\nexport function createCard(\n root: string,\n input: CardInput,\n opts: { now?: number; settings?: CardSettings; origin?: CardOrigin } = {},\n): { ok: true; card: DecisionCard } | { ok: false; error: string } {\n const built = buildCard(input, opts)\n if (!built.ok) return built\n const open = listCards(root, \"pending\").filter((c) => c.raised_by === built.card.raised_by).length\n if (open >= CARD_LIMITS.pendingPerAgent) {\n return { ok: false, error: `${built.card.raised_by} already has ${open} cards waiting; wait for decisions before raising more` }\n }\n saveCard(root, built.card)\n return built\n}\n\n/** Record the operator's answer. Refuses anything no longer pending. */\nexport function decideCard(\n root: string,\n id: string,\n verdict: Verdict,\n opts: { by?: string; note?: string; now?: number; choice?: string | number; text?: string } = {},\n): { ok: true; card: DecisionCard } | { ok: false; error: string } {\n const card = readCard(root, id)\n if (!card) return { ok: false, error: `no card \"${id}\"` }\n if (card.status !== \"pending\") return { ok: false, error: `card \"${id}\" is already ${card.status}` }\n const answer = resolveAnswer(card, verdict, opts)\n if (!answer.ok) return answer\n const note = oneLine(opts.note).slice(0, CARD_LIMITS.note)\n const decided: DecisionCard = {\n ...card,\n status: \"decided\",\n verdict,\n decided_by: opts.by ?? \"operator\",\n decided_at: new Date(opts.now ?? Date.now()).toISOString(),\n ...(note ? { note } : {}),\n ...(answer.choice ? { choice: answer.choice } : {}),\n ...(answer.text ? { text: answer.text } : {}),\n }\n saveCard(root, decided)\n return { ok: true, card: decided }\n}\n\n/** Apply `if_silent` to every pending card past its expiry. */\nexport function expireCards(root: string, now: number = Date.now()): DecisionCard[] {\n const out: DecisionCard[] = []\n for (const card of listCards(root, \"pending\")) {\n if (Date.parse(card.expires) > now) continue\n const expired: DecisionCard = {\n ...card,\n status: \"expired\",\n outcome: card.if_silent,\n decided_by: \"expiry\",\n decided_at: new Date(now).toISOString(),\n }\n saveCard(root, expired)\n out.push(expired)\n }\n return out\n}\n\n/** Decided or expired cards whose agent hasn't been told yet. */\nexport function cardsAwaitingAgentNotice(root: string): DecisionCard[] {\n return listCards(root).filter((c) => c.status !== \"pending\" && !c.agent_notified_at)\n}\n\nexport function markAgentNotified(root: string, id: string, now: number = Date.now()): void {\n const card = readCard(root, id)\n if (!card || card.agent_notified_at) return\n saveCard(root, { ...card, agent_notified_at: new Date(now).toISOString() })\n}\n\n/** What the raising agent is told. Plain text, one short message. */\nexport function verdictMessage(card: DecisionCard): string {\n const result = card.status === \"decided\"\n ? (card.verdict === \"yes\" ? \"The operator said YES.\" : \"The operator said NO.\")\n : `Nobody answered before it expired, so the default applied: ${card.outcome ?? card.if_silent}.`\n const lines = [\n `[Approval result] Your decision card \"${card.title}\" (${card.id}) is closed.`,\n `Question: ${card.ask}`,\n result,\n ]\n if (card.status === \"decided\" && card.verdict === \"yes\") lines.push(...answerLines(card))\n if (card.note) lines.push(`Operator note: ${card.note}`)\n if (card.source) lines.push(`Source: ${card.source}`)\n if (card.origin?.kind === \"reminder\") lines.push(...originLines(card.origin, card.status === \"decided\" && card.verdict === \"yes\"))\n if (card.reply) lines.push(`You raised it from ${card.reply.channel} chat ${card.reply.chatId}; reply there if the requester should know.`)\n lines.push(\"Act on this result now. Do not raise the same card again.\")\n return lines.join(\"\\n\")\n}\n","// --- Where a card came from, when the daemon raised it ---\n//\n// Agents raise most cards themselves. A check-in (checkin.ts) raises one on\n// an agent's behalf for an open Apple Reminder, and records the reminder\n// here so the agent that gets the answer knows what to act on and what to\n// tick off. Only the daemon sets it: POST /approvals and the agent tool\n// never pass it through.\n\nexport interface ReminderOrigin {\n kind: \"reminder\"\n /** The reminder's full id, as remindctl prints it. */\n id: string\n title: string\n list?: string\n}\n\n/** An open request that needs attention, shown as a card on the Mac\n * (popup-runner.ts). Never stored: the request is the record. */\nexport interface RequestOrigin {\n kind: \"request\"\n id: string\n}\n\nexport type CardOrigin = ReminderOrigin | RequestOrigin\n\n/** Lines for the owning agent's result message. */\nexport function originLines(origin: ReminderOrigin, approved: boolean): string[] {\n const where = origin.list ? ` in the \"${origin.list}\" list` : \"\"\n const lines = [`This card came from the operator's reminder \"${origin.title}\"${where} (id ${origin.id}).`]\n lines.push(approved\n ? `Do what the operator chose, then tick the reminder off: remindctl complete ${origin.id}`\n : \"Leave the reminder open; it will come back at a later check-in.\")\n return lines\n}\n","import type { IncomingMessage, ServerResponse } from \"http\"\n\n// --- Phone app: fleet, activity and control (/api/app/fleet, …) ---\n//\n// Phase 3 of the mobile epic. Runs behind the device-token check in\n// app-routes.ts, so every handler here may assume a paired phone.\n//\n// Reads reuse the dashboard's live snapshot (every node the dashboard can\n// see, including peers found through /mesh) and trim it to what a phone\n// shows: short previews, never full prompts, replies or errors. Writes go\n// to a node daemon through the dashboard's allowlisted, token-carrying POST\n// (AppFleetDeps.nodePost), the same path the desktop task and restart\n// buttons use, so the phone gets no power the dashboard doesn't already have.\n\nexport const PREVIEW_CHARS = 160\n\nexport interface SnapshotNode {\n id: string\n name: string\n url: string\n reachable: boolean\n error?: string\n uptimeSec?: number\n inflight?: number\n restart?: { state: string; requestedAt?: string; deadline?: string }\n agents: Array<{\n id: string\n name: string\n tier?: string\n color?: string\n active: number\n errors: number\n lastActive?: string\n lastSummary?: { text: string; at: string; ok: boolean }\n runningTasks?: Array<{ id: string; messagePreview: string; channel: string; chatId?: string; sender?: string; startedAt: string }>\n }>\n crons?: Array<{ id: string; enabled: boolean; schedule: string; agent: string; kind?: string; nextRun?: string; consecutiveErrors: number }>\n cronRuns?: Array<{ jobId: string; startedAt: string; status: string; responseSummary?: string; errorSummary?: string; isRetry?: boolean }>\n}\n\nexport interface ApprovalItem {\n key: string\n title: string\n ask?: string\n recommend?: string\n /** What yes and no do, in words. */\n yes?: string\n no?: string\n raisedBy?: string\n expires?: string\n}\n\nexport interface NodeApprovals {\n node: string\n nodeName: string\n items: ApprovalItem[]\n error?: string\n}\n\nexport interface NodeReply { status: number; body: any }\n\nexport interface AppFleetDeps {\n snapshot(day?: { date: string; timezone: string }): Promise<{ ts: string; nodes: SnapshotNode[] }>\n /** POST to a node daemon on the dashboard's allowlist, with its token. */\n nodePost(nodeUrl: string, path: string, body: unknown): Promise<NodeReply>\n approvals(): Promise<NodeApprovals[]>\n /** `by` names the phone, e.g. \"operator (phone: My phone)\". */\n decide(nodeUrl: string, key: string, action: \"yes\" | \"no\" | \"later\", by: string): Promise<NodeReply>\n}\n\nexport function clip(text: string | undefined, max = PREVIEW_CHARS): string | undefined {\n if (!text) return text\n const flat = text.replace(/\\s+/g, \" \").trim()\n return flat.length > max ? flat.slice(0, max - 1) + \"…\" : flat\n}\n\n/** One node, trimmed for the Fleet tab. Cron outcomes come from the node's\n * persisted run files (cronRuns), never the scheduler's counters. */\nexport function summarizeNode(n: SnapshotNode) {\n const lastRun = new Map<string, NonNullable<SnapshotNode[\"cronRuns\"]>[number]>()\n for (const r of n.cronRuns ?? []) {\n const prev = lastRun.get(r.jobId)\n if (!prev || prev.startedAt < r.startedAt) lastRun.set(r.jobId, r)\n }\n const today = { success: 0, failed: 0 }\n for (const r of n.cronRuns ?? []) {\n if (r.status === \"success\") today.success++\n else today.failed++\n }\n return {\n id: n.id,\n name: n.name,\n url: n.url,\n reachable: n.reachable,\n error: clip(n.error),\n uptimeSec: n.uptimeSec,\n inflight: n.inflight,\n restart: n.restart?.state && n.restart.state !== \"none\" ? n.restart : undefined,\n agents: n.agents.map((a) => ({\n id: a.id,\n name: a.name,\n tier: a.tier,\n active: a.active,\n errors: a.errors,\n lastActive: a.lastActive,\n last: a.lastSummary ? { text: clip(a.lastSummary.text), at: a.lastSummary.at, ok: a.lastSummary.ok } : undefined,\n })),\n crons: {\n today,\n items: (n.crons ?? []).map((c) => {\n const r = lastRun.get(c.id)\n return {\n id: c.id,\n enabled: c.enabled,\n schedule: c.schedule,\n agent: c.agent,\n nextRun: c.nextRun,\n consecutiveErrors: c.consecutiveErrors,\n last: r ? { status: r.status, at: r.startedAt, text: clip(r.status === \"success\" ? r.responseSummary : r.errorSummary) } : undefined,\n }\n }),\n },\n }\n}\n\n/** Every running task across the fleet, newest first. */\nexport function summarizeActivity(nodes: SnapshotNode[]) {\n const tasks = []\n for (const n of nodes) {\n for (const a of n.agents) {\n for (const t of a.runningTasks ?? []) {\n tasks.push({\n node: n.url,\n nodeName: n.name,\n agentId: a.id,\n agentName: a.name,\n taskId: t.id,\n preview: clip(t.messagePreview),\n channel: t.channel,\n startedAt: t.startedAt,\n })\n }\n }\n }\n return tasks.sort((x, y) => (x.startedAt < y.startedAt ? 1 : -1))\n}\n\nconst TASK_ACTION = /^\\/api\\/app\\/tasks\\/(cancel|followup)$/\n\n/** Handles /api/app/{fleet,activity,tasks,nodes,crons,approvals}. Returns\n * false for any other path so the caller can fall through. */\nexport async function handleAppFleet(\n req: IncomingMessage,\n res: ServerResponse,\n path: string,\n method: string,\n device: string,\n deps: AppFleetDeps,\n): Promise<boolean> {\n const url = new URL(req.url || path, \"http://localhost\")\n\n if (method === \"GET\" && (path === \"/api/app/fleet\" || path === \"/api/app/activity\")) {\n const timezone = validTimezone(url.searchParams.get(\"timezone\"))\n const snap = await deps.snapshot({ date: dateIn(timezone), timezone })\n if (path === \"/api/app/activity\") return json(res, 200, { ts: snap.ts, tasks: summarizeActivity(snap.nodes) })\n return json(res, 200, { ts: snap.ts, nodes: snap.nodes.map(summarizeNode) })\n }\n\n if (method === \"GET\" && path === \"/api/app/approvals\") {\n return json(res, 200, { nodes: await deps.approvals() })\n }\n\n if (method !== \"POST\") return false\n const taskAction = path.match(TASK_ACTION)\n const known = taskAction || [\n \"/api/app/nodes/restart\", \"/api/app/nodes/reload\", \"/api/app/crons/toggle\", \"/api/app/approvals/decide\",\n ].includes(path)\n if (!known) return false\n\n let body: Record<string, unknown>\n try { body = await readJson(req) } catch (e: any) { return json(res, 400, { error: e.message }) }\n const node = str(body.node)\n if (!node) return json(res, 400, { error: \"node is required\" })\n const by = `operator (phone: ${device})`\n\n let reply: NodeReply\n if (taskAction) {\n const taskId = str(body.taskId)\n if (!taskId) return json(res, 400, { error: \"taskId is required\" })\n const upstream = `/api/tasks/${encodeURIComponent(taskId)}/${taskAction[1]}`\n if (taskAction[1] === \"cancel\") {\n reply = await deps.nodePost(node, upstream, { reason: `cancelled by ${by}` })\n } else {\n const message = str(body.message)\n if (!message) return json(res, 400, { error: \"message is required\" })\n reply = await deps.nodePost(node, upstream, { message, sender: by })\n }\n } else if (path === \"/api/app/nodes/restart\") {\n reply = await deps.nodePost(node, body.cancel === true ? \"/daemon/restart/cancel\" : \"/daemon/restart\", {})\n } else if (path === \"/api/app/nodes/reload\") {\n reply = await deps.nodePost(node, \"/reload\", {})\n } else if (path === \"/api/app/crons/toggle\") {\n const cronId = str(body.cronId)\n if (!cronId || typeof body.enabled !== \"boolean\") return json(res, 400, { error: \"cronId and enabled (true or false) are required\" })\n reply = await deps.nodePost(node, `/crons/${encodeURIComponent(cronId)}/enabled`, { enabled: body.enabled })\n } else {\n const key = str(body.key)\n const action = body.action\n if (!key || (action !== \"yes\" && action !== \"no\" && action !== \"later\")) {\n return json(res, 400, { error: \"key and action (yes, no or later) are required\" })\n }\n reply = await deps.decide(node, key, action, by)\n }\n return json(res, reply.status, reply.body)\n}\n\nfunction str(v: unknown): string {\n return typeof v === \"string\" ? v.trim() : \"\"\n}\n\nfunction validTimezone(tz: string | null): string {\n if (!tz) return \"UTC\"\n try { new Intl.DateTimeFormat(\"en-US\", { timeZone: tz }); return tz } catch { return \"UTC\" }\n}\n\n/** YYYY-MM-DD for \"today\" in the phone's time zone. */\nexport function dateIn(timezone: string, now = new Date()): string {\n return new Intl.DateTimeFormat(\"en-CA\", { timeZone: timezone, year: \"numeric\", month: \"2-digit\", day: \"2-digit\" }).format(now)\n}\n\nexport async function readJson(req: IncomingMessage, limit = 16 * 1024): Promise<Record<string, unknown>> {\n let size = 0\n const chunks: Buffer[] = []\n for await (const chunk of req) {\n size += (chunk as Buffer).length\n if (size > limit) throw new Error(\"body too large\")\n chunks.push(chunk as Buffer)\n }\n const raw = Buffer.concat(chunks).toString(\"utf-8\").trim()\n if (!raw) return {}\n const parsed = JSON.parse(raw)\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) throw new Error(\"expected a JSON object\")\n return parsed\n}\n\nfunction json(res: ServerResponse, status: number, body: unknown): true {\n res.writeHead(status, { \"Content-Type\": \"application/json\", \"Cache-Control\": \"no-store\" })\n res.end(JSON.stringify(body))\n return true\n}\n","import type { IncomingMessage } from \"http\"\nimport { PairCodeStore } from \"./pair-codes\"\nimport { readJson } from \"./app-fleet\"\n\n// --- POST /api/app/pair-code: trade a one-time code for the session ---\n//\n// Public by necessity (the phone isn't paired yet), so it is guarded three\n// ways: an 8-symbol code (31^8 ≈ 850 billion), a per-client and a GLOBAL\n// failure limit with lockout, and one answer for every failure. Behind\n// `tailscale serve` every request arrives from 127.0.0.1, so the per-client\n// limit alone would see one client; the global limit is what really bounds\n// guessing: at most 10 wrong codes in any 10 minutes, whoever sends them.\n//\n// Every attempt is padded to the same minimum duration, so wrong, expired\n// and used codes can't be told apart by timing either.\n\nexport interface PairLimitOptions {\n perClient: number // failures per window before that client is locked out\n global: number // failures per window, all clients together\n windowMs: number\n lockoutMs: number\n}\n\nexport const DEFAULT_PAIR_LIMITS: PairLimitOptions = {\n perClient: 5,\n global: 10,\n windowMs: 10 * 60 * 1000,\n lockoutMs: 5 * 60 * 1000,\n}\n\ninterface Bucket { failures: number[]; lockedUntil: number }\n\n/** In-memory failure counter. A dashboard restart clears it; codes still\n * expire after 10 minutes, so that grants an attacker nothing lasting. */\nexport class PairAttemptLimiter {\n private clients = new Map<string, Bucket>()\n private all: Bucket = { failures: [], lockedUntil: 0 }\n\n constructor(private opts: PairLimitOptions = DEFAULT_PAIR_LIMITS, private now: () => number = Date.now) {}\n\n /** Seconds until this client may try again, or 0 when it may try now. */\n retryAfter(client: string): number {\n const now = this.now()\n const until = Math.max(this.all.lockedUntil, this.clients.get(client)?.lockedUntil ?? 0)\n return until > now ? Math.ceil((until - now) / 1000) : 0\n }\n\n fail(client: string): void {\n let bucket = this.clients.get(client)\n if (!bucket) this.clients.set(client, (bucket = { failures: [], lockedUntil: 0 }))\n this.bump(bucket, this.opts.perClient)\n this.bump(this.all, this.opts.global)\n }\n\n succeed(client: string): void {\n this.clients.delete(client)\n }\n\n private bump(bucket: Bucket, limit: number): void {\n const now = this.now()\n bucket.failures = bucket.failures.filter((t) => now - t < this.opts.windowMs)\n bucket.failures.push(now)\n if (bucket.failures.length >= limit) bucket.lockedUntil = now + this.opts.lockoutMs\n }\n}\n\nexport interface PairCodeDeps {\n codes: PairCodeStore\n limiter: PairAttemptLimiter\n /** Checks the redeemed token is still an active `app` token; returns its device name. */\n verify: (token: string) => string | null\n log?: (line: string) => void\n minMs?: number\n}\n\nexport type PairCodeResult =\n | { status: 200; body: { device: string }; token: string }\n | { status: 401; body: { error: string } }\n | { status: 429; body: { error: string; retryAfter: number }; retryAfter: number }\n\nexport const PAIR_CODE_FAILED = \"That code didn't work. Check it, or run agentx app pair for a new one.\"\nconst MIN_MS = 400\n\nexport async function redeemPairCode(req: IncomingMessage, deps: PairCodeDeps): Promise<PairCodeResult> {\n const started = Date.now()\n const log = deps.log ?? ((line: string) => console.log(line))\n const client = req.socket.remoteAddress || \"unknown\"\n\n const wait = deps.limiter.retryAfter(client)\n if (wait > 0) {\n log(`[app] pair-code refused from ${client}: locked out for ${wait}s more`)\n return { status: 429, body: { error: \"too many attempts\", retryAfter: wait }, retryAfter: wait }\n }\n\n let code: unknown = null\n try { code = (await readJson(req, 1024)).code } catch { /* counts as a wrong code */ }\n const redeemed = deps.codes.redeem(code)\n const device = redeemed ? deps.verify(redeemed.token) : null\n await padTo(started, deps.minMs ?? MIN_MS)\n\n if (!redeemed || !device) {\n deps.limiter.fail(client)\n log(`[app] pair-code failed from ${client}`)\n return { status: 401, body: { error: PAIR_CODE_FAILED } }\n }\n deps.limiter.succeed(client)\n log(`[app] pair-code paired ${redeemed.tokenId} (${device}) from ${client}`)\n return { status: 200, body: { device }, token: redeemed.token }\n}\n\nasync function padTo(started: number, minMs: number): Promise<void> {\n const left = started + minMs - Date.now()\n if (left > 0) await new Promise((r) => setTimeout(r, left))\n}\n"],"mappings":"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","names":["existsSync","mkdirSync","readdirSync","readFileSync","renameSync","writeFileSync","resolve","originLines","origin","approved","where","lines","IF_SILENT_VALUES","CARD_LIMITS","DEFAULT_CARD_SETTINGS","approvalsDir","root","resolve","CARD_ID_RE","isValidCardId","id","fileFor","newCardId","now","title","day","slug","saveCard","card","mkdirSync","path","tmp","writeFileSync","renameSync","readCard","readFileSync","listCards","status","dir","existsSync","out","f","readdirSync","c","oneLine","v","resolveExpiry","input","settings","max","at","raw","rel","buildCard","opts","ask","recommend","raisedBy","ifSilent","expiry","source","extras","buildChoices","reply","r","createCard","built","open","decideCard","verdict","answer","resolveAnswer","note","decided","expireCards","expired","cardsAwaitingAgentNotice","markAgentNotified","verdictMessage","result","lines","answerLines","originLines","clip","text","max","flat","summarizeNode","n","lastRun","prev","today","a","c","r","summarizeActivity","nodes","tasks","t","x","y","TASK_ACTION","handleAppFleet","req","res","path","method","device","deps","url","timezone","validTimezone","snap","dateIn","json","taskAction","body","readJson","e","node","str","by","reply","taskId","upstream","message","cronId","key","action","v","tz","now","limit","size","chunks","chunk","raw","parsed","status","DEFAULT_PAIR_LIMITS","PairAttemptLimiter","opts","now","client","until","bucket","limit","t","PAIR_CODE_FAILED","MIN_MS","redeemPairCode","req","deps","started","log","line","wait","code","readJson","redeemed","device","padTo","minMs","left"]}
@@ -1,6 +1,6 @@
1
- import{a as O,b as $,c as U,d as T,e as j}from"./chunk-WPNRDEHW.js";import{t as R,v as E,w as W}from"./chunk-ZVXKCXTP.js";import{a as z,b as C,f as V}from"./chunk-72SZI2UW.js";import{a as B,b as re}from"./chunk-FX3D7YON.js";import{o as J,p as te}from"./chunk-5MUJZODV.js";V();var we="wiki-rerank",be=30,se=900;function xe(a){let e=a.excerptChars??se;return{query:a.query,candidates:a.candidates.map(t=>({id:t.id,title:t.title,tags:t.tags?.length?t.tags.join(", "):null,excerpt:ne(t.excerpt,e)}))}}function Se(a){let e={};for(let t of a)e[t.id]=t.title;return{best:C(e,"Which candidate answers the query?"),answerable:z("Do any of the candidates answer the query?")}}function Ne(a,e){let t=a.best,r={};for(let i of e)r[i.id]=t.probabilities[i.id]??0;let n=new Map(e.map((i,s)=>[i.id,s]));return{ranked:[...e.map(i=>i.id)].sort((i,s)=>{let c=r[s]-r[i];return c!==0?c:(n.get(i)??0)-(n.get(s)??0)}),scores:r,answerable:a.answerable.noul,confidence:t.confidence}}function ne(a,e){let t=String(a??"").replace(/\s+/g," ").trim();return t.length>e?t.slice(0,e):t}var x={version:2,levels:[{id:"category",description:"Top-level kind of work. Closed set \u2014 pick the closest fit.",axes:[{name:"kind",type:"enum",values:["code","ops","support","admin","knowledge","social","system"],description:"Which category this work belongs in."}]},{id:"verb",description:"Specific intent \u2014 verb-level only, NOT client/project-bound.",axes:[{name:"name",type:"free",description:"Lower-kebab dot-namespaced verb. Examples: review.merge-request, fix.bug, investigate.error, deploy.staging, deploy.production, rollback.release, spec.feature, triage.issue, document.feature, chat.greeting, chat.support-request, schedule.add, config.change, ingest.contact, transcribe.audio, brief.daily, summarize.thread, plan.work."}]}],leafInput:{name:"input",type:"enum",values:["telegram","whatsapp","slack","discord","gitlab-mr","gitlab-issue","github-issue","github-pr","cron","webhook","mesh","http"],description:"Where the message entered the system."},leafOutput:{name:"output",type:"free",description:"One short sentence describing what the agent is expected to produce. Generic \u2014 refers to the verb's outcome, not the specific client/project (those are on the event metadata already)."}},G=[{category:"code",verb:"review.merge-request",description:"Review a GitLab MR or GitHub PR"},{category:"code",verb:"review.code-change",description:"General code review (snippet, diff)"},{category:"code",verb:"fix.bug",description:"Fix a reported bug"},{category:"code",verb:"implement.feature",description:"Build a new feature"},{category:"code",verb:"investigate.error",description:"Investigate a failing test, exception, pipeline"},{category:"code",verb:"refactor.code",description:"Restructure existing code without behaviour change"},{category:"code",verb:"spec.feature",description:"Write or update a feature spec / RFC"},{category:"code",verb:"triage.issue",description:"Classify, label, or assign an issue"},{category:"code",verb:"answer.code-question",description:"Answer a code-related question"},{category:"ops",verb:"deploy.staging",description:"Deploy to a staging/preview environment"},{category:"ops",verb:"deploy.production",description:"Deploy to production"},{category:"ops",verb:"rollback.release",description:"Roll back a release"},{category:"ops",verb:"investigate.incident",description:"Investigate a production incident"},{category:"ops",verb:"monitor.system",description:"Check system status / metrics"},{category:"ops",verb:"rotate.credential",description:"Rotate a token, key, certificate"},{category:"ops",verb:"audit.security",description:"Review for security issues"},{category:"support",verb:"chat.greeting",description:"A casual hello, no actionable request"},{category:"support",verb:"chat.support-request",description:"End-user / customer asking for help"},{category:"support",verb:"chat.casual",description:"Informal back-and-forth chat"},{category:"support",verb:"answer.question",description:"Answer an open-ended question"},{category:"support",verb:"route.request",description:"Forward a request to the right agent"},{category:"admin",verb:"config.change",description:"Change agentx.json / environment / config"},{category:"admin",verb:"schedule.add",description:"Add a new cron / scheduled task"},{category:"admin",verb:"schedule.remove",description:"Remove or disable a schedule"},{category:"admin",verb:"token.create",description:"Mint a scoped API token"},{category:"admin",verb:"agent.add",description:"Configure a new agent"},{category:"admin",verb:"channel.add",description:"Wire up a new channel"},{category:"knowledge",verb:"document.feature",description:"Write or update documentation"},{category:"knowledge",verb:"summarize.thread",description:"Summarize a long conversation/thread"},{category:"knowledge",verb:"research.topic",description:"Investigate / gather info on a topic"},{category:"knowledge",verb:"wiki.absorb",description:"Roll raw entries into typed wiki articles"},{category:"social",verb:"brief.daily",description:"Compose a daily brief / digest"},{category:"social",verb:"draft.post",description:"Draft a marketing post / announcement"},{category:"social",verb:"report.weekly",description:"Compose a weekly status report"},{category:"system",verb:"classify.intent",description:"Internal: graph-classifier sub-call"}];import{readFileSync as I,writeFileSync as M,appendFileSync as ie,existsSync as S,mkdirSync as F,renameSync as oe,statSync as Q}from"fs";import{resolve as A,dirname as X}from"path";import{createHash as ae}from"crypto";var _=class{baseDir;log;parsed=new Map;constructor(e={}){this.baseDir=e.baseDir??A(process.cwd(),".agentx/graph"),this.log=e.log??console.error.bind(console,"[graph]"),F(this.baseDir,{recursive:!0})}schemaPath(){return A(this.baseDir,"schema.json")}nodesPath(){return A(this.baseDir,"nodes.json")}indexPath(){return A(this.baseDir,"index.json")}classificationsPath(){return A(this.baseDir,"classifications.jsonl")}loadSchema(){let e=this.schemaPath();if(!S(e))return this.saveSchema(x),this.log("seeded starter schema at",e),x;let t=I(e,"utf-8"),r=O.safeParse(JSON.parse(t));if(!r.success)throw new Error(`Invalid ${e}: ${r.error.issues.map(n=>n.message).join("; ")}`);if((r.data.version??1)<(x.version??2)){let n=e.replace(/\.json$/,`.v${r.data.version??1}.bak.json`);try{M(n,t),this.log(`migrated graph schema v${r.data.version??1} -> v${x.version}; old saved at ${n}`)}catch(i){this.log(`could not back up old schema: ${i?.message??i}`)}this.saveSchema(x);let o=this.nodesPath();if(S(o)){let i=o.replace(/\.json$/,".v1.bak.json");try{M(i,I(o,"utf-8")),this.log(`archived v1 nodes.json -> ${i}`)}catch(s){this.log(`could not back up old nodes: ${s?.message??s}`)}try{let s=K();this.saveNodes(s)}catch(s){this.log(`could not seed v2 nodes: ${s?.message??s}`)}}return x}return r.data}saveSchema(e){let t=O.safeParse(e);if(!t.success)throw new Error(`Refusing to save invalid schema: ${t.error.issues.map(r=>r.message).join("; ")}`);Y(this.schemaPath(),t.data)}loadNodes(){let e=this.nodesPath();if(!S(e)){let t=K();return this.saveNodes(t),t}return structuredClone(this.readParsed(e,t=>{let r=T.safeParse(JSON.parse(t));if(!r.success)throw new Error(`Invalid ${e}: ${r.error.issues.map(n=>n.message).join("; ")}`);return r.data}))}saveNodes(e){let t=T.safeParse(e);if(!t.success)throw new Error(`Refusing to save invalid nodes file: ${t.error.issues.map(r=>r.message).join("; ")}`);this.writeParsed(this.nodesPath(),t.data)}readParsed(e,t){let{mtimeMs:r,size:n}=Q(e),o=this.parsed.get(e);if(o&&o.mtimeMs===r&&o.size===n)return o.data;let i=t(I(e,"utf-8"));return this.parsed.set(e,{mtimeMs:r,size:n,data:i}),i}writeParsed(e,t){this.parsed.delete(e),Y(e,t);let{mtimeMs:r,size:n}=Q(e);this.parsed.set(e,{mtimeMs:r,size:n,data:t})}validateNode(e,t,r){let n=t.levels.find(s=>s.id===e.level);if(!n)throw new Error(`Unknown level: "${e.level}"`);let o=n.axes.filter(s=>!s.optional).filter(s=>!(s.name in e.axes));if(o.length)throw new Error(`Node ${e.id} (${e.level}) missing required axes: ${o.map(s=>s.name).join(", ")}`);for(let s of n.axes){let c=e.axes[s.name];if(c!=null){if(s.type==="enum"&&s.values&&!s.values.includes(c))throw new Error(`Node ${e.id}: axis ${s.name}="${c}" not in enum ${JSON.stringify(s.values)}`);if(s.type==="ref"){if(!r.some(l=>l.id===c))throw new Error(`Node ${e.id}: axis ${s.name}="${c}" refers to unknown node`);if(s.refLevel){let l=r.find(h=>h.id===c);if(l.level!==s.refLevel)throw new Error(`Node ${e.id}: axis ${s.name} expected level "${s.refLevel}" but got "${l.level}"`)}}}}let i=t.levels.findIndex(s=>s.id===e.level);if(i===0){if(e.parentId!==null)throw new Error(`Root-level node ${e.id} must have parentId: null`)}else{if(!e.parentId)throw new Error(`Non-root node ${e.id} must have a parentId`);let s=r.find(l=>l.id===e.parentId);if(!s)throw new Error(`Node ${e.id}: parent "${e.parentId}" not found`);let c=t.levels.findIndex(l=>l.id===s.level);if(c<0||c>=i)throw new Error(`Node ${e.id} at level "${e.level}" has parent at level "${s.level}" \u2014 parent must be at a higher level`)}}commitNodesAlongPath(e,t,r,n){let o=[],i=[],s=new Date().toISOString();for(let c=0;c<e.length;c++){let l=e[c],h=this.loadNodes().nodes;if(h.find(p=>p.id===l)){i.push(l);continue}let g=0;for(let p=c-1;p>=0;p--){let y=e[p],N=h.find(w=>w.id===y)||o.find(w=>w.id===y);if(N){g=r.levels.findIndex(w=>w.id===N.level)+1;break}}let m=t[l]??{},d=null;for(let p=g;p<r.levels.length;p++){let y=r.levels[p];if(y.axes.filter(w=>!w.optional).filter(w=>!(w.name in m)).length===0){d=y.id,g=p+1;break}}if(!d&&g<r.levels.length&&(d=r.levels[g].id,g++),!d)throw new Error(`Can't place node ${l} \u2014 no schema level left (cursor=${g}, levels=${r.levels.length})`);let f=null,v=r.levels.findIndex(p=>p.id===d);for(let p=c-1;p>=0;p--){let y=e[p],N=h.find(k=>k.id===y)||o.find(k=>k.id===y);if(!N)continue;let w=r.levels.findIndex(k=>k.id===N.level);if(w>=0&&w<v){f=y;break}}let u={id:l,level:d,parentId:f,axes:m,createdAt:s,createdBy:n};this.addNode(u),o.push(u)}return{added:o,skipped:i}}addNode(e){let t=this.loadSchema(),r=this.loadNodes();if(r.nodes.some(n=>n.id===e.id))throw new Error(`Node id already exists: ${e.id}`);return this.validateNode(e,t,[...r.nodes,e]),r.nodes.push(e),this.saveNodes(r),e}appendClassification(e){let t=$.safeParse(e);if(!t.success){let o=t.error.issues.map(i=>`${i.path.join(".")}: ${i.message}`).join(" \xB7 ");return this.log(`skipped invalid classification \u2014 ${o} \xB7`,`path=${JSON.stringify(e.path)}`,`axes-keys=${JSON.stringify(Object.keys(e.proposedAxes||{}))}`),!1}let r=JSON.stringify(t.data)+`
1
+ import{a as O,b as $,c as U,d as T,e as j}from"./chunk-WPNRDEHW.js";import{t as R,v as E,w as W}from"./chunk-4UXHHHYH.js";import{a as z,b as C,f as V}from"./chunk-72SZI2UW.js";import{a as B,b as re}from"./chunk-FX3D7YON.js";import{o as J,p as te}from"./chunk-5MUJZODV.js";V();var we="wiki-rerank",be=30,se=900;function xe(a){let e=a.excerptChars??se;return{query:a.query,candidates:a.candidates.map(t=>({id:t.id,title:t.title,tags:t.tags?.length?t.tags.join(", "):null,excerpt:ne(t.excerpt,e)}))}}function Se(a){let e={};for(let t of a)e[t.id]=t.title;return{best:C(e,"Which candidate answers the query?"),answerable:z("Do any of the candidates answer the query?")}}function Ne(a,e){let t=a.best,r={};for(let i of e)r[i.id]=t.probabilities[i.id]??0;let n=new Map(e.map((i,s)=>[i.id,s]));return{ranked:[...e.map(i=>i.id)].sort((i,s)=>{let c=r[s]-r[i];return c!==0?c:(n.get(i)??0)-(n.get(s)??0)}),scores:r,answerable:a.answerable.noul,confidence:t.confidence}}function ne(a,e){let t=String(a??"").replace(/\s+/g," ").trim();return t.length>e?t.slice(0,e):t}var x={version:2,levels:[{id:"category",description:"Top-level kind of work. Closed set \u2014 pick the closest fit.",axes:[{name:"kind",type:"enum",values:["code","ops","support","admin","knowledge","social","system"],description:"Which category this work belongs in."}]},{id:"verb",description:"Specific intent \u2014 verb-level only, NOT client/project-bound.",axes:[{name:"name",type:"free",description:"Lower-kebab dot-namespaced verb. Examples: review.merge-request, fix.bug, investigate.error, deploy.staging, deploy.production, rollback.release, spec.feature, triage.issue, document.feature, chat.greeting, chat.support-request, schedule.add, config.change, ingest.contact, transcribe.audio, brief.daily, summarize.thread, plan.work."}]}],leafInput:{name:"input",type:"enum",values:["telegram","whatsapp","slack","discord","gitlab-mr","gitlab-issue","github-issue","github-pr","cron","webhook","mesh","http"],description:"Where the message entered the system."},leafOutput:{name:"output",type:"free",description:"One short sentence describing what the agent is expected to produce. Generic \u2014 refers to the verb's outcome, not the specific client/project (those are on the event metadata already)."}},G=[{category:"code",verb:"review.merge-request",description:"Review a GitLab MR or GitHub PR"},{category:"code",verb:"review.code-change",description:"General code review (snippet, diff)"},{category:"code",verb:"fix.bug",description:"Fix a reported bug"},{category:"code",verb:"implement.feature",description:"Build a new feature"},{category:"code",verb:"investigate.error",description:"Investigate a failing test, exception, pipeline"},{category:"code",verb:"refactor.code",description:"Restructure existing code without behaviour change"},{category:"code",verb:"spec.feature",description:"Write or update a feature spec / RFC"},{category:"code",verb:"triage.issue",description:"Classify, label, or assign an issue"},{category:"code",verb:"answer.code-question",description:"Answer a code-related question"},{category:"ops",verb:"deploy.staging",description:"Deploy to a staging/preview environment"},{category:"ops",verb:"deploy.production",description:"Deploy to production"},{category:"ops",verb:"rollback.release",description:"Roll back a release"},{category:"ops",verb:"investigate.incident",description:"Investigate a production incident"},{category:"ops",verb:"monitor.system",description:"Check system status / metrics"},{category:"ops",verb:"rotate.credential",description:"Rotate a token, key, certificate"},{category:"ops",verb:"audit.security",description:"Review for security issues"},{category:"support",verb:"chat.greeting",description:"A casual hello, no actionable request"},{category:"support",verb:"chat.support-request",description:"End-user / customer asking for help"},{category:"support",verb:"chat.casual",description:"Informal back-and-forth chat"},{category:"support",verb:"answer.question",description:"Answer an open-ended question"},{category:"support",verb:"route.request",description:"Forward a request to the right agent"},{category:"admin",verb:"config.change",description:"Change agentx.json / environment / config"},{category:"admin",verb:"schedule.add",description:"Add a new cron / scheduled task"},{category:"admin",verb:"schedule.remove",description:"Remove or disable a schedule"},{category:"admin",verb:"token.create",description:"Mint a scoped API token"},{category:"admin",verb:"agent.add",description:"Configure a new agent"},{category:"admin",verb:"channel.add",description:"Wire up a new channel"},{category:"knowledge",verb:"document.feature",description:"Write or update documentation"},{category:"knowledge",verb:"summarize.thread",description:"Summarize a long conversation/thread"},{category:"knowledge",verb:"research.topic",description:"Investigate / gather info on a topic"},{category:"knowledge",verb:"wiki.absorb",description:"Roll raw entries into typed wiki articles"},{category:"social",verb:"brief.daily",description:"Compose a daily brief / digest"},{category:"social",verb:"draft.post",description:"Draft a marketing post / announcement"},{category:"social",verb:"report.weekly",description:"Compose a weekly status report"},{category:"system",verb:"classify.intent",description:"Internal: graph-classifier sub-call"}];import{readFileSync as I,writeFileSync as M,appendFileSync as ie,existsSync as S,mkdirSync as F,renameSync as oe,statSync as Q}from"fs";import{resolve as A,dirname as X}from"path";import{createHash as ae}from"crypto";var _=class{baseDir;log;parsed=new Map;constructor(e={}){this.baseDir=e.baseDir??A(process.cwd(),".agentx/graph"),this.log=e.log??console.error.bind(console,"[graph]"),F(this.baseDir,{recursive:!0})}schemaPath(){return A(this.baseDir,"schema.json")}nodesPath(){return A(this.baseDir,"nodes.json")}indexPath(){return A(this.baseDir,"index.json")}classificationsPath(){return A(this.baseDir,"classifications.jsonl")}loadSchema(){let e=this.schemaPath();if(!S(e))return this.saveSchema(x),this.log("seeded starter schema at",e),x;let t=I(e,"utf-8"),r=O.safeParse(JSON.parse(t));if(!r.success)throw new Error(`Invalid ${e}: ${r.error.issues.map(n=>n.message).join("; ")}`);if((r.data.version??1)<(x.version??2)){let n=e.replace(/\.json$/,`.v${r.data.version??1}.bak.json`);try{M(n,t),this.log(`migrated graph schema v${r.data.version??1} -> v${x.version}; old saved at ${n}`)}catch(i){this.log(`could not back up old schema: ${i?.message??i}`)}this.saveSchema(x);let o=this.nodesPath();if(S(o)){let i=o.replace(/\.json$/,".v1.bak.json");try{M(i,I(o,"utf-8")),this.log(`archived v1 nodes.json -> ${i}`)}catch(s){this.log(`could not back up old nodes: ${s?.message??s}`)}try{let s=K();this.saveNodes(s)}catch(s){this.log(`could not seed v2 nodes: ${s?.message??s}`)}}return x}return r.data}saveSchema(e){let t=O.safeParse(e);if(!t.success)throw new Error(`Refusing to save invalid schema: ${t.error.issues.map(r=>r.message).join("; ")}`);Y(this.schemaPath(),t.data)}loadNodes(){let e=this.nodesPath();if(!S(e)){let t=K();return this.saveNodes(t),t}return structuredClone(this.readParsed(e,t=>{let r=T.safeParse(JSON.parse(t));if(!r.success)throw new Error(`Invalid ${e}: ${r.error.issues.map(n=>n.message).join("; ")}`);return r.data}))}saveNodes(e){let t=T.safeParse(e);if(!t.success)throw new Error(`Refusing to save invalid nodes file: ${t.error.issues.map(r=>r.message).join("; ")}`);this.writeParsed(this.nodesPath(),t.data)}readParsed(e,t){let{mtimeMs:r,size:n}=Q(e),o=this.parsed.get(e);if(o&&o.mtimeMs===r&&o.size===n)return o.data;let i=t(I(e,"utf-8"));return this.parsed.set(e,{mtimeMs:r,size:n,data:i}),i}writeParsed(e,t){this.parsed.delete(e),Y(e,t);let{mtimeMs:r,size:n}=Q(e);this.parsed.set(e,{mtimeMs:r,size:n,data:t})}validateNode(e,t,r){let n=t.levels.find(s=>s.id===e.level);if(!n)throw new Error(`Unknown level: "${e.level}"`);let o=n.axes.filter(s=>!s.optional).filter(s=>!(s.name in e.axes));if(o.length)throw new Error(`Node ${e.id} (${e.level}) missing required axes: ${o.map(s=>s.name).join(", ")}`);for(let s of n.axes){let c=e.axes[s.name];if(c!=null){if(s.type==="enum"&&s.values&&!s.values.includes(c))throw new Error(`Node ${e.id}: axis ${s.name}="${c}" not in enum ${JSON.stringify(s.values)}`);if(s.type==="ref"){if(!r.some(l=>l.id===c))throw new Error(`Node ${e.id}: axis ${s.name}="${c}" refers to unknown node`);if(s.refLevel){let l=r.find(h=>h.id===c);if(l.level!==s.refLevel)throw new Error(`Node ${e.id}: axis ${s.name} expected level "${s.refLevel}" but got "${l.level}"`)}}}}let i=t.levels.findIndex(s=>s.id===e.level);if(i===0){if(e.parentId!==null)throw new Error(`Root-level node ${e.id} must have parentId: null`)}else{if(!e.parentId)throw new Error(`Non-root node ${e.id} must have a parentId`);let s=r.find(l=>l.id===e.parentId);if(!s)throw new Error(`Node ${e.id}: parent "${e.parentId}" not found`);let c=t.levels.findIndex(l=>l.id===s.level);if(c<0||c>=i)throw new Error(`Node ${e.id} at level "${e.level}" has parent at level "${s.level}" \u2014 parent must be at a higher level`)}}commitNodesAlongPath(e,t,r,n){let o=[],i=[],s=new Date().toISOString();for(let c=0;c<e.length;c++){let l=e[c],h=this.loadNodes().nodes;if(h.find(p=>p.id===l)){i.push(l);continue}let g=0;for(let p=c-1;p>=0;p--){let y=e[p],N=h.find(w=>w.id===y)||o.find(w=>w.id===y);if(N){g=r.levels.findIndex(w=>w.id===N.level)+1;break}}let m=t[l]??{},d=null;for(let p=g;p<r.levels.length;p++){let y=r.levels[p];if(y.axes.filter(w=>!w.optional).filter(w=>!(w.name in m)).length===0){d=y.id,g=p+1;break}}if(!d&&g<r.levels.length&&(d=r.levels[g].id,g++),!d)throw new Error(`Can't place node ${l} \u2014 no schema level left (cursor=${g}, levels=${r.levels.length})`);let f=null,v=r.levels.findIndex(p=>p.id===d);for(let p=c-1;p>=0;p--){let y=e[p],N=h.find(k=>k.id===y)||o.find(k=>k.id===y);if(!N)continue;let w=r.levels.findIndex(k=>k.id===N.level);if(w>=0&&w<v){f=y;break}}let u={id:l,level:d,parentId:f,axes:m,createdAt:s,createdBy:n};this.addNode(u),o.push(u)}return{added:o,skipped:i}}addNode(e){let t=this.loadSchema(),r=this.loadNodes();if(r.nodes.some(n=>n.id===e.id))throw new Error(`Node id already exists: ${e.id}`);return this.validateNode(e,t,[...r.nodes,e]),r.nodes.push(e),this.saveNodes(r),e}appendClassification(e){let t=$.safeParse(e);if(!t.success){let o=t.error.issues.map(i=>`${i.path.join(".")}: ${i.message}`).join(" \xB7 ");return this.log(`skipped invalid classification \u2014 ${o} \xB7`,`path=${JSON.stringify(e.path)}`,`axes-keys=${JSON.stringify(Object.keys(e.proposedAxes||{}))}`),!1}let r=JSON.stringify(t.data)+`
2
2
  `,n=this.classificationsPath();return S(n)||F(X(n),{recursive:!0}),ie(n,r),!0}listPendingClassifications(e=200){return this.listByStatus("pending",e)}listAllClassifications(){return this.readAllCollapsed()}listByStatus(e,t=200){return this.readAllCollapsed().filter(n=>n.status===e).slice(0,t)}readAllCollapsed(){let e=this.classificationsPath();if(!S(e))return[];let r=I(e,"utf-8").split(`
3
3
  `).filter(Boolean),n=new Map;for(let o of r)try{let i=$.safeParse(JSON.parse(o));if(!i.success)continue;n.set(i.data.msgHash,i.data)}catch{}return Array.from(n.values()).sort((o,i)=>o.ts<i.ts?1:-1)}updateClassificationStatus(e,t){let n=this.readAllCollapsed().find(i=>i.msgHash===e);if(!n)return null;let o={...n,status:t.status,ts:new Date().toISOString()};if(t.reviewer||t.reviewReason){let i=` \xB7 ${t.reviewer?`reviewer=${t.reviewer}`:""}${t.reviewReason?` \xB7 ${t.reviewReason}`:""}`;o.preview=((n.preview||"")+i).slice(0,500)}return this.appendClassification(o),o}readRecentClassifications(e=50){let t=this.classificationsPath();if(!S(t))return[];let n=I(t,"utf-8").split(`
4
4
  `).filter(Boolean),o=[];for(let i=n.length-1;i>=0&&o.length<e;i--)try{let s=$.safeParse(JSON.parse(n[i]));s.success&&o.push(s.data)}catch{}return o}fingerprint(e){let r=[ce(e.text),e.channel??""].join("");return ae("sha256").update(r).digest("hex").slice(0,32)}loadIndex(){return structuredClone(this.index())}saveIndex(e){let t=j.safeParse(e);if(!t.success)throw new Error(`Refusing to save invalid index: ${t.error.issues.map(r=>r.message).join("; ")}`);this.writeParsed(this.indexPath(),t.data)}index(){let e=this.indexPath();return S(e)?this.readParsed(e,t=>{let r=j.safeParse(JSON.parse(t));if(!r.success)throw new Error(`Invalid ${e}: ${r.error.issues.map(n=>n.message).join("; ")}`);return r.data}):{version:1,entries:{}}}getFingerprint(e){let t=this.index().entries[e];return t&&structuredClone(t)}setFingerprint(e,t){let r=U.safeParse({...t,fingerprint:e,updatedAt:new Date().toISOString()});if(!r.success)throw new Error(`Refusing to save invalid index entry: ${r.error.issues.map(o=>o.message).join("; ")}`);let n=this.index();this.writeParsed(this.indexPath(),{...n,entries:{...n.entries,[e]:r.data}})}updateNodeAxes(e,t){let r=this.loadSchema(),n=this.loadNodes(),o=n.nodes.find(s=>s.id===e);if(!o)throw new Error(`Node not found: ${e}`);let i={...o,axes:t};return this.validateNode(i,r,n.nodes.map(s=>s.id===e?i:s)),n.nodes=n.nodes.map(s=>s.id===e?i:s),this.saveNodes(n),i}deleteNode(e){let t=this.loadNodes(),r=t.nodes.find(o=>o.parentId===e);if(r)throw new Error(`Cannot delete "${e}" \u2014 child node "${r.id}" still points to it`);let n=t.nodes.find(o=>Object.values(o.axes).some(i=>i===e));if(n)throw new Error(`Cannot delete "${e}" \u2014 node "${n.id}" references it via an axis`);t.nodes=t.nodes.filter(o=>o.id!==e),this.saveNodes(t)}approveClassification(e){if(this.getFingerprint(e.msgHash))return;let t=this.loadSchema(),r=new Set(this.loadNodes().nodes.map(n=>n.id));for(let n=0;n<e.path.length;n++){let o=e.path[n];if(r.has(o))continue;let i=t.levels[n]?.id;if(!i)break;let s=e.proposedAxes?.[o]??{},c=n===0?null:e.path[n-1];this.addNode({id:o,level:i,parentId:c,axes:s,createdAt:new Date().toISOString(),createdBy:e.agentId}),r.add(o)}this.setFingerprint(e.msgHash,{path:e.path,leaf:e.leaf}),this.appendClassification({...e,status:"approved",ts:new Date().toISOString()})}rejectClassification(e){this.appendClassification({...e,status:"rejected",ts:new Date().toISOString()})}};function Y(a,e){F(X(a),{recursive:!0});let t=`${a}.tmp-${process.pid}`;M(t,JSON.stringify(e,null,2)),oe(t,a)}function K(){let a=new Date().toISOString(),e=[],t=new Set;for(let r of G)t.has(r.category)||(e.push({id:r.category,level:"category",parentId:null,axes:{kind:r.category},createdAt:a,createdBy:"starter"}),t.add(r.category));for(let r of G)e.push({id:r.verb,level:"verb",parentId:r.category,axes:{name:r.verb,...r.description?{description:r.description}:{}},createdAt:a,createdBy:"starter"});return{version:1,nodes:e}}function ce(a){return a.toLowerCase().replace(/https?:\/\/\S+/g," url ").replace(/[@#][\w.\/-]+/g," ref ").replace(/\b[0-9a-f]{7,}\b/g," id ").replace(/\d+/g," num ").replace(/[^\p{L}\p{N}\s]+/gu," ").replace(/\s+/g," ").trim().slice(0,400)}re();te();W();import{createHash as me}from"crypto";V();W();var P="intent-path",de=.5,le=.3;function pe(a){return{message:fe(a.message,2e3),channel:a.channel??null,sender:a.sender??null,agent:a.agent??null}}function Z(a,e){let t={};for(let r of a)t[r.id]=he(r);return C(t,e)}function ge(a){return{category:Z(a,"Which category of work does this message ask for or report on? Pick the one a reviewer would file it under.")}}function ue(a){return{verb:Z(a,"Within that category, which specific action does the message ask for or report on? Prefer the most specific verb that fits; a casual or ambiguous message belongs to the most general one.")}}async function D(a,e,t={}){if(R(P)==="off")return null;let r=e.filter(m=>m.parentId===null);if(r.length<2)return null;let n=pe(a),o={agent:a.agent??"unknown",channel:a.channel??"unknown"},i=a.agent?[{kind:"agent",id:a.agent}]:void 0,s=await E(P,n,ge(r),{incumbent:t.incumbent?.[0]?{category:t.incumbent[0]}:void 0,features:{...o,stage:"category"},links:i});if(!s)return null;let c=s.answers.category,l=c.pMax,h=c.pMax>=de,b=[c.choice],g=e.filter(m=>m.parentId===c.choice);if(g.length>=2){let m=t.incumbent?.[0]===c.choice,d=await E(P,{...n,category:c.choice},ue(g),{incumbent:m&&t.incumbent?.[1]?{verb:t.incumbent[1]}:void 0,features:{...o,stage:"verb",category:c.choice},links:i});if(!d)return null;let f=d.answers.verb;l=Math.min(l,f.pMax),h=h&&f.pMax>=le,b.push(f.choice)}else g.length===1&&b.push(g[0].id);return{path:b,confidence:l,confident:h,mode:s.mode}}function he(a){let e=a.axes?.description?.trim()||void 0,t=a.axes?.name?.trim(),r=t&&t!==a.id?t:void 0;return e&&r?`${r}: ${e}`:e??r??null}function fe(a,e){return a.length>e?`${a.slice(0,e)}\u2026`:a}var L=class{store;daemonUrl;token;draftAgent;autoApprove;autoApproveStructure;classifierModel;classifierProviderName;providerInstance;log;constructor(e){this.store=e.store,this.daemonUrl=e.daemonUrl.replace(/\/+$/,""),this.token=e.token,this.draftAgent=e.draftAgent,this.autoApprove=e.autoApproveConfidence??1,this.autoApproveStructure=e.autoApproveStructure??"extend-leaves",this.classifierModel=e.classifierModel??"claude-haiku-4-5-20251001",this.classifierProviderName=e.classifierProvider??"claude-code",this.log=e.log??console.error.bind(console,"[classifier]")}getProvider(){return this.providerInstance||(this.providerInstance=J(this.classifierProviderName)),this.providerInstance}async classify(e){let t=this.store.fingerprint({text:e.text,channel:e.channel,sender:e.sender}),r=this.store.getFingerprint(t);if(r)return this.store.appendClassification({ts:new Date().toISOString(),msgHash:t,agentId:e.agentId,channel:e.channel,sender:e.sender,chatId:e.chatId,path:r.path,proposedAxes:{},leaf:r.leaf,source:"cache",status:"approved",preview:e.text.slice(0,200)}),{path:r.path,pathId:H(r.path),pathLabel:q(r.path,this.store.loadNodes().nodes),axes:{},leaf:r.leaf,source:"cache",status:"approved"};let n=this.store.loadSchema(),o=this.store.loadNodes().nodes,i=await this.propose(e,n,o);if(!i)return null;let{path:s,proposedAxes:c,confidence:l,leaf:h}=i;if(s.length===0)return null;let b={ts:new Date().toISOString(),msgHash:t,agentId:e.agentId,channel:e.channel,sender:e.sender,chatId:e.chatId,path:s,proposedAxes:c,leaf:h,source:i.source,status:"pending",confidence:l,preview:e.text.slice(0,200)},g=new Set(o.map(u=>u.id)),m=s.map((u,p)=>g.has(u)?-1:p).filter(u=>u>=0),d=!1;if(this.autoApproveStructure==="any")d=!0;else if(this.autoApproveStructure==="extend-leaves"){let u=m.length===0,p=m.length===1&&m[0]===s.length-1;d=u||p}let f=l!==void 0&&l>=this.autoApprove,v="pending";if(d||f)try{this.commitNewNodes(s,c,n,o,e.sender),this.store.setFingerprint(t,{path:s,leaf:h}),v="approved"}catch(u){this.log("auto-approve failed \u2014 staying pending \xB7",`path=${JSON.stringify(s)}`,`reason=${(u?.message||u).toString().slice(0,200)}`)}return b.status=v,this.store.appendClassification(b),{path:s,pathId:H(s),pathLabel:q(s,this.store.loadNodes().nodes),axes:c,leaf:h,source:i.source,status:v,confidence:l}}async propose(e,t,r){let n=R(P),o={message:e.text,channel:e.channel,sender:e.sender,agent:e.agentId};if(n==="active"){let s=await D(o,r).catch(c=>(this.log("intent-path seat failed:",c?.message||c),null));if(s?.confident)return{path:s.path,proposedAxes:{},leaf:{},confidence:s.confidence,source:"seat"}}let i=await this.proposePath(e,t,r).catch(s=>(this.log("LLM proposal failed:",s?.message||s),null));return n==="shadow"&&i&&D(o,r,{incumbent:i.path}).catch(()=>{}),i?{...i,source:"llm"}:null}async proposePath(e,t,r){let n=r.map(d=>({id:d.id,level:d.level,parentId:d.parentId,axes:d.axes})),o=["You are the intent classifier for AgentX. You answer ONE question:",'"what kind of work does this message describe?" \u2014 verb-level only.',"","IMPORTANT \u2014 what NOT to encode in the path:","- Client / company / project / team names. Those are already on the event"," metadata (project, channel, agentId). Embedding them in the path causes"," duplicate nodes (one per client) for the same verb.","- The specific subject (issue number, file name, person name). Same reason.","- The agent's name or role. Pick the verb the *requester* intended.","","SCHEMA (fixed levels \u2014 pick one node per level):","```json",JSON.stringify(t,null,2),"```","","EXISTING NODES (prefer reusing these; only invent a new node when no","existing one fits):","```json",JSON.stringify(n,null,2),"```","","MESSAGE:","```",e.text.slice(0,2e3),"```",`channel: ${e.channel??"?"}`,`sender: ${e.sender??"?"}`,"","TASK:","1. Classify into 'category' (closed enum): code, ops, support, admin,"," knowledge, social, system. Pick the closest fit.","2. Pick or propose a 'verb' node. Verb ids are dot-namespaced lower-kebab,",' e.g. "review.merge-request", "deploy.staging", "investigate.error",',' "chat.greeting", "fix.bug". Reuse an existing verb when the message is'," the same kind of work as something already classified, even when the"," client / project / subject differs.","","Examples (right vs wrong):",'- "Please review MR #957 on globex/system" \u2192 ["code", "review.merge-request"]',' NOT ["business", "acme", "globex-v2", "review-mr-957-system"]','- "Deploy initech-v2 to staging please" \u2192 ["ops", "deploy.staging"]',' NOT ["business", "acme", "initech-v2", "deploy-initech-v2-to-staging"]','- "Hello Atlas" \u2192 ["support", "chat.greeting"]',"","NODE ID RULES (strict \u2014 invalid ids get dropped):","- lowercase-kebab + dots only: [a-z0-9][a-z0-9._-]*","- no spaces, no uppercase, no Arabic/other non-Latin",'- verb ids should use a "category.specific" or "verb.modifier" shape',"","Return ONE JSON object on one line, no prose, no fences:",' { "path": string[], "proposedAxes": { [nodeId]: { [axisName]: string } }, "leaf": { "input"?: string, "output"?: string }, "confidence": number }',"confidence in [0,1]. Prefer low confidence over guessing."].join(`
5
5
  `),i=await this.callProvider(o),s=B(i);if(!s||!Array.isArray(s.path))return null;let c=/^[a-z0-9][a-z0-9._-]*$/,l=new Map,h=s.path.filter(d=>typeof d=="string"&&d.length>0).map(d=>{let f=d.trim(),v=ee(f);return f!==v&&l.set(f,v),v}).filter(d=>c.test(d));if(h.length===0)return null;let b={};if(s.proposedAxes&&typeof s.proposedAxes=="object")for(let[d,f]of Object.entries(s.proposedAxes)){if(!f||typeof f!="object")continue;let v=l.get(d)??ee(d);if(!v)continue;let u={};for(let[p,y]of Object.entries(f))typeof y=="string"&&(u[p]=y);!u.name&&d!==v&&(u.name=d.trim()),b[v]=u}let g={};s.leaf&&typeof s.leaf=="object"&&(typeof s.leaf.input=="string"&&(g.input=s.leaf.input),typeof s.leaf.output=="string"&&(g.output=s.leaf.output));let m=typeof s.confidence=="number"?Math.max(0,Math.min(1,s.confidence)):void 0;return{path:h,proposedAxes:b,confidence:m,leaf:g}}commitNewNodes(e,t,r,n,o){this.store.commitNodesAlongPath(e,t,r,o)}async callProvider(e){let t=this.getProvider(),r=new AbortController,n=setTimeout(()=>r.abort(),3e4);try{return(await t.generate([{role:"system",content:"You are a taxonomy classifier. Respond with a single JSON object on one line. No prose, no code fences."},{role:"user",content:e}],{model:this.classifierModel,maxTokens:800,abortSignal:r.signal,bare:!0})).content||""}finally{clearTimeout(n)}}};function ee(a){let e=a.trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/^[-_]+|[-_]+$/g,"").replace(/-{2,}/g,"-");return e?/^[a-z0-9]/.test(e)?e:`n-${e}`.replace(/[-_]+$/,""):""}function H(a){return me("sha1").update(a.join("")).digest("hex").slice(0,16)}function q(a,e){return a.map(t=>{let r=e.find(o=>o.id===t);return r&&(r.axes?.name||r.axes?.what)||t}).join(" \u203A ")}function ve(a,e){if(a.length===0||e.length===0)return 0;let t=0;for(let n=0;n<Math.min(a.length,e.length)&&a[n]===e[n];n++)t++;if(t===0)return 0;let r=Math.max(a.length,e.length);return t/r}export{we as a,be as b,xe as c,Se as d,Ne as e,x as f,_ as g,L as h,q as i,ve as j};
6
- //# sourceMappingURL=chunk-OS34M3FV.js.map
6
+ //# sourceMappingURL=chunk-EBMU2RD6.js.map
@@ -1,2 +1,2 @@
1
- import{a as o}from"./chunk-AVA245WL.js";import{resolve as a}from"path";import{existsSync as d,readdirSync as c,mkdirSync as l}from"fs";var g=class{baseDir;agentsDir;mode;sharedStore;agentStores=new Map;log;constructor(t=a(process.cwd(),".agentx/wiki"),e=console.error.bind(console,"[wiki-hub]"),r="graph"){this.baseDir=t,this.agentsDir=a(t,"agents"),this.mode=r,this.log=e,l(this.agentsDir,{recursive:!0}),this.sharedStore=new o(t,e)}getMode(){return this.mode}getAgentWiki(t){let e=`${t}:${this.mode}`;if(this.agentStores.has(e))return this.agentStores.get(e);let r=a(this.agentsDir,t,this.mode),i=new o(r,this.log);return this.agentStores.set(e,i),i}getBaseDir(){return this.baseDir}getSharedStore(){return this.sharedStore}listAgents(){let t=new Set,e=this.sharedStore.listEntries();for(let r of e)t.add(r.agentId);if(d(this.agentsDir))for(let r of c(this.agentsDir))!r.startsWith("_")&&!r.startsWith(".")&&t.add(r);return[...t].sort()}getAgentEntries(t){return this.sharedStore.listEntries({agentId:t})}getUnabsorbedEntries(t){let e=this.getAgentWiki(t),r=this.getAgentEntries(t),i=new Set,n=e.rebuildIndex();for(let s of n.articles)if(s.sources)for(let h of s.sources)i.add(h);return r.filter(s=>!i.has(s.id))}summary(){return this.listAgents().map(e=>{let r=this.getAgentEntries(e),n=this.getAgentWiki(e).rebuildIndex(),s=this.getUnabsorbedEntries(e);return{agentId:e,totalEntries:r.length,totalArticles:n.articles.length,unabsorbed:s.length,articles:n.articles}})}};export{g as a};
2
- //# sourceMappingURL=chunk-4TKGR3XF.js.map
1
+ import{a as o}from"./chunk-2IMQJS53.js";import{resolve as a}from"path";import{existsSync as d,readdirSync as c,mkdirSync as l}from"fs";var g=class{baseDir;agentsDir;mode;sharedStore;agentStores=new Map;log;constructor(t=a(process.cwd(),".agentx/wiki"),e=console.error.bind(console,"[wiki-hub]"),r="graph"){this.baseDir=t,this.agentsDir=a(t,"agents"),this.mode=r,this.log=e,l(this.agentsDir,{recursive:!0}),this.sharedStore=new o(t,e)}getMode(){return this.mode}getAgentWiki(t){let e=`${t}:${this.mode}`;if(this.agentStores.has(e))return this.agentStores.get(e);let r=a(this.agentsDir,t,this.mode),i=new o(r,this.log);return this.agentStores.set(e,i),i}getBaseDir(){return this.baseDir}getSharedStore(){return this.sharedStore}listAgents(){let t=new Set,e=this.sharedStore.listEntries();for(let r of e)t.add(r.agentId);if(d(this.agentsDir))for(let r of c(this.agentsDir))!r.startsWith("_")&&!r.startsWith(".")&&t.add(r);return[...t].sort()}getAgentEntries(t){return this.sharedStore.listEntries({agentId:t})}getUnabsorbedEntries(t){let e=this.getAgentWiki(t),r=this.getAgentEntries(t),i=new Set,n=e.rebuildIndex();for(let s of n.articles)if(s.sources)for(let h of s.sources)i.add(h);return r.filter(s=>!i.has(s.id))}summary(){return this.listAgents().map(e=>{let r=this.getAgentEntries(e),n=this.getAgentWiki(e).rebuildIndex(),s=this.getUnabsorbedEntries(e);return{agentId:e,totalEntries:r.length,totalArticles:n.articles.length,unabsorbed:s.length,articles:n.articles}})}};export{g as a};
2
+ //# sourceMappingURL=chunk-EPP6BEUX.js.map