botmux 3.11.0 → 3.12.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 (165) hide show
  1. package/dist/.runtime-build-id +1 -1
  2. package/dist/adapters/backend/riff-backend.d.ts.map +1 -1
  3. package/dist/adapters/backend/riff-backend.js +11 -2
  4. package/dist/adapters/backend/riff-backend.js.map +1 -1
  5. package/dist/adapters/cli/coco.js +3 -3
  6. package/dist/adapters/cli/coco.js.map +1 -1
  7. package/dist/adapters/cli/codex.js +2 -2
  8. package/dist/adapters/cli/codex.js.map +1 -1
  9. package/dist/cli/dashboard-command.d.ts +23 -0
  10. package/dist/cli/dashboard-command.d.ts.map +1 -0
  11. package/dist/cli/dashboard-command.js +61 -0
  12. package/dist/cli/dashboard-command.js.map +1 -0
  13. package/dist/cli/dashboard-endpoint.d.ts +3 -3
  14. package/dist/cli/dashboard-endpoint.d.ts.map +1 -1
  15. package/dist/cli/dashboard-endpoint.js +1 -1
  16. package/dist/cli.d.ts.map +1 -1
  17. package/dist/cli.js +36 -22
  18. package/dist/cli.js.map +1 -1
  19. package/dist/codex-rpc-engine.d.ts +33 -3
  20. package/dist/codex-rpc-engine.d.ts.map +1 -1
  21. package/dist/codex-rpc-engine.js +182 -20
  22. package/dist/codex-rpc-engine.js.map +1 -1
  23. package/dist/codex-rpc-lifecycle.d.ts +42 -7
  24. package/dist/codex-rpc-lifecycle.d.ts.map +1 -1
  25. package/dist/codex-rpc-lifecycle.js +73 -7
  26. package/dist/codex-rpc-lifecycle.js.map +1 -1
  27. package/dist/config.d.ts +1 -1
  28. package/dist/config.js +1 -1
  29. package/dist/core/command-handler.d.ts +3 -2
  30. package/dist/core/command-handler.d.ts.map +1 -1
  31. package/dist/core/command-handler.js +3 -2
  32. package/dist/core/command-handler.js.map +1 -1
  33. package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
  34. package/dist/core/dashboard-ipc-server.js +15 -10
  35. package/dist/core/dashboard-ipc-server.js.map +1 -1
  36. package/dist/core/dispatch.d.ts +9 -10
  37. package/dist/core/dispatch.d.ts.map +1 -1
  38. package/dist/core/dispatch.js +17 -10
  39. package/dist/core/dispatch.js.map +1 -1
  40. package/dist/core/role-resolver.d.ts +5 -2
  41. package/dist/core/role-resolver.d.ts.map +1 -1
  42. package/dist/core/role-resolver.js +55 -23
  43. package/dist/core/role-resolver.js.map +1 -1
  44. package/dist/core/session-manager.d.ts +2 -0
  45. package/dist/core/session-manager.d.ts.map +1 -1
  46. package/dist/core/session-manager.js +20 -18
  47. package/dist/core/session-manager.js.map +1 -1
  48. package/dist/core/terminal-write-auth.d.ts +4 -4
  49. package/dist/core/terminal-write-auth.d.ts.map +1 -1
  50. package/dist/core/terminal-write-auth.js +18 -6
  51. package/dist/core/terminal-write-auth.js.map +1 -1
  52. package/dist/daemon.d.ts +1 -0
  53. package/dist/daemon.d.ts.map +1 -1
  54. package/dist/daemon.js +101 -12
  55. package/dist/daemon.js.map +1 -1
  56. package/dist/dashboard/auth.d.ts +21 -11
  57. package/dist/dashboard/auth.d.ts.map +1 -1
  58. package/dist/dashboard/auth.js +39 -25
  59. package/dist/dashboard/auth.js.map +1 -1
  60. package/dist/dashboard/bot-payload.d.ts +0 -1
  61. package/dist/dashboard/bot-payload.d.ts.map +1 -1
  62. package/dist/dashboard/bot-payload.js +0 -1
  63. package/dist/dashboard/bot-payload.js.map +1 -1
  64. package/dist/dashboard/dashboard-summary-endpoint.d.ts +39 -0
  65. package/dist/dashboard/dashboard-summary-endpoint.d.ts.map +1 -0
  66. package/dist/dashboard/dashboard-summary-endpoint.js +50 -0
  67. package/dist/dashboard/dashboard-summary-endpoint.js.map +1 -0
  68. package/dist/dashboard/dashboard-summary.d.ts +62 -0
  69. package/dist/dashboard/dashboard-summary.d.ts.map +1 -0
  70. package/dist/dashboard/dashboard-summary.js +106 -0
  71. package/dist/dashboard/dashboard-summary.js.map +1 -0
  72. package/dist/dashboard/web/feed-group-picker.d.ts +13 -0
  73. package/dist/dashboard/web/feed-group-picker.d.ts.map +1 -0
  74. package/dist/dashboard/web/feed-group-picker.js +45 -0
  75. package/dist/dashboard/web/feed-group-picker.js.map +1 -0
  76. package/dist/dashboard/web/groups-page.d.ts.map +1 -1
  77. package/dist/dashboard/web/groups-page.js +2 -5
  78. package/dist/dashboard/web/groups-page.js.map +1 -1
  79. package/dist/dashboard/web/i18n.d.ts.map +1 -1
  80. package/dist/dashboard/web/i18n.js +8 -14
  81. package/dist/dashboard/web/i18n.js.map +1 -1
  82. package/dist/dashboard/web/roles-page.d.ts.map +1 -1
  83. package/dist/dashboard/web/roles-page.js +36 -4
  84. package/dist/dashboard/web/roles-page.js.map +1 -1
  85. package/dist/dashboard/web/roles.d.ts +3 -1
  86. package/dist/dashboard/web/roles.d.ts.map +1 -1
  87. package/dist/dashboard/web/roles.js +10 -2
  88. package/dist/dashboard/web/roles.js.map +1 -1
  89. package/dist/dashboard/web/schedules-page.d.ts.map +1 -1
  90. package/dist/dashboard/web/schedules-page.js +8 -2
  91. package/dist/dashboard/web/schedules-page.js.map +1 -1
  92. package/dist/dashboard/web/sessions-page.d.ts.map +1 -1
  93. package/dist/dashboard/web/sessions-page.js +2 -2
  94. package/dist/dashboard/web/sessions-page.js.map +1 -1
  95. package/dist/dashboard-web/app.js +1 -1
  96. package/dist/dashboard-web/chunks/{bot-defaults-page-GED4FEMS.js → bot-defaults-page-USC6EZXP.js} +1 -1
  97. package/dist/dashboard-web/chunks/chunk-6ALR252E.js +1 -0
  98. package/dist/dashboard-web/chunks/{chunk-VBXJOJFH.js → chunk-A7VC4BXR.js} +1 -1
  99. package/dist/dashboard-web/chunks/{chunk-HK7NIBJG.js → chunk-KOPISPHN.js} +1 -1
  100. package/dist/dashboard-web/chunks/{chunk-6J5LS5AU.js → chunk-M6BHWQFO.js} +6 -6
  101. package/dist/dashboard-web/chunks/{chunk-KRHWZEXU.js → chunk-RXRJB5SO.js} +1 -1
  102. package/dist/dashboard-web/chunks/{connectors-page-EELM66G3.js → connectors-page-4Z76HWRH.js} +1 -1
  103. package/dist/dashboard-web/chunks/groups-page-C6NSDTIN.js +8 -0
  104. package/dist/dashboard-web/chunks/{insights-page-54UBPPOC.js → insights-page-UTOVYLLY.js} +1 -1
  105. package/dist/dashboard-web/chunks/{monitor-room-PORHVKRH.js → monitor-room-UKTJYTA2.js} +1 -1
  106. package/dist/dashboard-web/chunks/{monitoring-page-XGA7AXNL.js → monitoring-page-Q3CEZIVX.js} +1 -1
  107. package/dist/dashboard-web/chunks/{office-page-AO3AFG45.js → office-page-GYCZOOVJ.js} +1 -1
  108. package/dist/dashboard-web/chunks/{overview-page-F5BUTYR6.js → overview-page-W2VBJ6OH.js} +1 -1
  109. package/dist/dashboard-web/chunks/roles-page-U4SHER4K.js +1 -0
  110. package/dist/dashboard-web/chunks/schedules-page-TNPW3N3Q.js +1 -0
  111. package/dist/dashboard-web/chunks/{sessions-page-YFRPSTIL.js → sessions-page-YWWDDV57.js} +3 -3
  112. package/dist/dashboard-web/chunks/{settings-page-OJAZLZQT.js → settings-page-VKW5ZK5K.js} +1 -1
  113. package/dist/dashboard-web/chunks/{skills-page-EFZQPWV4.js → skills-page-ZCEDFGZZ.js} +1 -1
  114. package/dist/dashboard-web/chunks/{team-federation-page-ZJVAP7HW.js → team-federation-page-OMFGGBT2.js} +1 -1
  115. package/dist/dashboard-web/chunks/{v3-page-GYWTRGO2.js → v3-page-PLXWK4KO.js} +1 -1
  116. package/dist/dashboard-web/chunks/{whiteboards-page-MSDAG6P7.js → whiteboards-page-NJQOEO2P.js} +1 -1
  117. package/dist/dashboard-web/style.css +112 -0
  118. package/dist/dashboard.js +116 -31
  119. package/dist/dashboard.js.map +1 -1
  120. package/dist/desktop/main/ipc.js +1 -1
  121. package/dist/desktop/main/runtime-service.js +2 -2
  122. package/dist/desktop/main/runtime-service.js.map +1 -1
  123. package/dist/desktop/shared/types.d.ts +1 -1
  124. package/dist/i18n/en.d.ts.map +1 -1
  125. package/dist/i18n/en.js +3 -1
  126. package/dist/i18n/en.js.map +1 -1
  127. package/dist/i18n/zh.d.ts.map +1 -1
  128. package/dist/i18n/zh.js +3 -1
  129. package/dist/i18n/zh.js.map +1 -1
  130. package/dist/services/adopt-input-sequence.d.ts +52 -0
  131. package/dist/services/adopt-input-sequence.d.ts.map +1 -0
  132. package/dist/services/adopt-input-sequence.js +50 -0
  133. package/dist/services/adopt-input-sequence.js.map +1 -0
  134. package/dist/services/bridge-fallback-gate.d.ts.map +1 -1
  135. package/dist/services/bridge-fallback-gate.js +34 -11
  136. package/dist/services/bridge-fallback-gate.js.map +1 -1
  137. package/dist/services/codex-bridge-queue.d.ts +103 -7
  138. package/dist/services/codex-bridge-queue.d.ts.map +1 -1
  139. package/dist/services/codex-bridge-queue.js +291 -17
  140. package/dist/services/codex-bridge-queue.js.map +1 -1
  141. package/dist/services/codex-transcript.d.ts +4 -3
  142. package/dist/services/codex-transcript.d.ts.map +1 -1
  143. package/dist/services/codex-transcript.js.map +1 -1
  144. package/dist/services/structured-bridge-clis.d.ts +8 -1
  145. package/dist/services/structured-bridge-clis.d.ts.map +1 -1
  146. package/dist/services/structured-bridge-clis.js +13 -0
  147. package/dist/services/structured-bridge-clis.js.map +1 -1
  148. package/dist/services/submit-confirmation.d.ts +50 -0
  149. package/dist/services/submit-confirmation.d.ts.map +1 -1
  150. package/dist/services/submit-confirmation.js +82 -0
  151. package/dist/services/submit-confirmation.js.map +1 -1
  152. package/dist/utils/async-serial-queue.d.ts +9 -0
  153. package/dist/utils/async-serial-queue.d.ts.map +1 -0
  154. package/dist/utils/async-serial-queue.js +13 -0
  155. package/dist/utils/async-serial-queue.js.map +1 -0
  156. package/dist/utils/runtime-screen-status.d.ts +52 -0
  157. package/dist/utils/runtime-screen-status.d.ts.map +1 -0
  158. package/dist/utils/runtime-screen-status.js +60 -0
  159. package/dist/utils/runtime-screen-status.js.map +1 -0
  160. package/dist/worker.js +1642 -452
  161. package/dist/worker.js.map +1 -1
  162. package/package.json +1 -1
  163. package/dist/dashboard-web/chunks/groups-page-WKUV6E42.js +0 -8
  164. package/dist/dashboard-web/chunks/roles-page-M5GIASJU.js +0 -1
  165. package/dist/dashboard-web/chunks/schedules-page-O232WQRR.js +0 -1
@@ -1 +1 @@
1
- import{c as B}from"./chunk-HK7NIBJG.js";import{c as W,k as C}from"./chunk-5CJMHTT4.js";import"./chunk-6J5LS5AU.js";import{a as P}from"./chunk-SYC6D5XU.js";import{a as O,d as T}from"./chunk-5THX4DI2.js";import{c as R}from"./chunk-7SZRL6FY.js";var r=R(O(),1);var e=R(T(),1);function x(t){let a=Date.parse(t);if(!a)return t||"-";let o=Math.max(0,Math.floor((Date.now()-a)/1e3));if(o<60)return`${o}s ago`;let i=Math.floor(o/60);if(i<60)return`${i}m ago`;let n=Math.floor(i/60);return n<24?`${n}h ago`:`${Math.floor(n/24)}d ago`}function F(t){return t.chatId?.trim()||"__local__"}function G(t,a){if(t==="__local__")return"\u672A\u7ED1\u5B9A\u7FA4 / \u672C\u5730\u767D\u677F";let o=a.get(t);return o&&o!==t?`${o} (${t})`:t}function K(t,a){let o=new Map;for(let i of t){let n=F(i),c=o.get(n)??[];c.push(i),o.set(n,c)}return[...o.entries()].map(([i,n])=>({chatId:i,label:G(i,a),rows:[...n].sort((c,b)=>b.updatedAt.localeCompare(c.updatedAt))})).sort((i,n)=>i.label.localeCompare(n.label))}function z(){return decodeURIComponent((location.hash.match(/^#\/whiteboards\/([^/]+)/)?.[1]??"").trim())}async function E(t,a){let o=await fetch(`/api/whiteboards/${encodeURIComponent(t)}`),i=await o.json().catch(()=>({}));if(o.ok)return{id:t,content:String(i.content??""),row:a.find(n=>n.id===t)}}async function J(t){let a=new Map;if(!t?.ok)return a;let o=await t.json().catch(()=>({})),i=Array.isArray(o.chats)?o.chats:[];for(let n of i)n.chatId&&a.set(String(n.chatId),String(n.name||n.chatId));return a}function Q(t){let a=t.row,o=a.title||a.id;return(0,e.jsxs)("a",{className:`wb-item${t.active?" active":""}`,"data-whiteboard-id":a.id,href:`#/whiteboards/${encodeURIComponent(a.id)}`,onClick:i=>{i.preventDefault(),t.onSelect(a.id)},children:[(0,e.jsx)("div",{className:"wb-item-head",children:(0,e.jsxs)("div",{className:"wb-item-main",children:[(0,e.jsx)("strong",{children:(0,e.jsx)(C,{text:o,textClassName:"wb-title-scroll"})}),(0,e.jsx)("span",{className:"wb-id-text",title:a.id,children:a.id})]})}),(0,e.jsxs)("div",{className:"wb-item-foot",children:[(0,e.jsxs)("div",{className:"wb-item-meta",children:[(0,e.jsx)("span",{children:x(a.updatedAt)}),(0,e.jsx)("span",{children:"\xB7"}),(0,e.jsxs)("span",{children:["log ",a.logCount]})]}),(0,e.jsx)("span",{className:"wb-scope",title:a.scope,children:a.scope})]})]})}function p(t){let a=t.value||"-";return(0,e.jsxs)("div",{className:"wb-meta-card",title:a,children:[(0,e.jsx)("span",{children:t.label}),(0,e.jsx)("strong",{children:a})]})}function V(t){let a=t.selected;if(!a)return(0,e.jsx)("p",{className:"empty",children:"\u9009\u62E9\u5DE6\u4FA7\u767D\u677F\u67E5\u770B meta \u548C board.md\u3002"});let o=a.row,i=o?.chatId?G(o.chatId,t.groupNames):"\u672A\u7ED1\u5B9A\u7FA4 / \u672C\u5730\u767D\u677F",n=o?.title||a.id;return(0,e.jsxs)(e.Fragment,{children:[(0,e.jsxs)("div",{className:"wb-detail-head",children:[(0,e.jsxs)("div",{className:"wb-detail-title",children:[(0,e.jsx)("p",{className:"eyebrow",children:"WHITEBOARD"}),(0,e.jsx)("h2",{title:n,children:n}),(0,e.jsx)("code",{title:a.id,children:a.id})]}),(0,e.jsx)("button",{type:"button",className:"danger","data-delete-whiteboard":!0,onClick:t.onDelete,children:"\u5220\u9664\u767D\u677F"})]}),(0,e.jsxs)("div",{className:"wb-meta-grid",children:[(0,e.jsx)(p,{label:"\u6240\u5C5E\u7FA4",value:i}),(0,e.jsx)(p,{label:"\u8303\u56F4",value:o?.scope??"-"}),(0,e.jsx)(p,{label:"\u6700\u8FD1\u66F4\u65B0",value:o?.updatedAt?x(o.updatedAt):"-"}),(0,e.jsx)(p,{label:"\u6765\u6E90\u76EE\u5F55",value:o?.workingDir??"-"})]}),(0,e.jsxs)("details",{className:"wb-admin-info",children:[(0,e.jsx)("summary",{children:"\u7BA1\u7406\u4FE1\u606F / \u6587\u4EF6\u8DEF\u5F84"}),(0,e.jsx)("code",{children:o?.path??""})]}),(0,e.jsxs)("section",{className:"wb-board-panel",children:[(0,e.jsxs)("div",{className:"wb-board-panel-head",children:[(0,e.jsx)("strong",{children:"board.md"}),(0,e.jsx)("span",{children:"\u53EA\u8BFB\u9884\u89C8"})]}),(0,e.jsx)("pre",{children:a.content||"\uFF08\u6682\u65E0\u5185\u5BB9\uFF09"})]})]})}function X(t){let a=t.selected.row?.title||t.selected.id;return(0,e.jsx)("div",{className:"wb-delete-backdrop","data-delete-modal":!0,onClick:o=>{o.target===o.currentTarget&&t.onCancel()},children:(0,e.jsxs)("div",{className:"wb-delete-dialog",role:"dialog","aria-modal":"true","aria-labelledby":"wb-delete-title",children:[(0,e.jsxs)("div",{className:"wb-delete-body",children:[(0,e.jsx)("div",{className:"wb-delete-mark","aria-hidden":"true",children:"!"}),(0,e.jsxs)("div",{children:[(0,e.jsx)("h3",{id:"wb-delete-title",children:"\u5220\u9664\u767D\u677F\uFF1F"}),(0,e.jsxs)("p",{children:["\u5C06\u5220\u9664 ",(0,e.jsx)("strong",{children:a}),"\uFF08",(0,e.jsx)("code",{children:t.selected.id}),"\uFF09\u7684 board\u3001log\u3001meta\uFF0C\u5E76\u6E05\u7406\u9ED8\u8BA4\u7ED1\u5B9A\u548C\u4F1A\u8BDD\u5F15\u7528\u3002\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002"]})]})]}),(0,e.jsxs)("div",{className:"actions wb-delete-actions",children:[(0,e.jsx)("button",{type:"button","data-delete-cancel":!0,onClick:t.onCancel,disabled:t.deleting,children:"\u53D6\u6D88"}),(0,e.jsx)("button",{type:"button",className:"danger","data-delete-confirm":!0,onClick:t.onConfirm,disabled:t.deleting,children:t.deleting?"\u5220\u9664\u4E2D...":"\u786E\u8BA4\u5220\u9664"})]})]})})}function Y(){let t=B(),a=(0,r.useRef)(!1),o=(0,r.useRef)(0),[i,n]=(0,r.useState)(!1),[c,b]=(0,r.useState)([]),[g,_]=(0,r.useState)(()=>new Map),[m,f]=(0,r.useState)(),[H,S]=(0,r.useState)(!0),[k,M]=(0,r.useState)(null),[u,h]=(0,r.useState)(null),[L,D]=(0,r.useState)(!1),I=(0,r.useMemo)(()=>K(c,g),[c,g]);(0,r.useEffect)(()=>{a.current=!0;let s=z();async function d(){S(!0);try{let[l,y]=await Promise.all([fetch("/api/whiteboards"),fetch("/api/groups").catch(()=>null)]),w=await l.json().catch(()=>({}));if(!l.ok)throw new Error(w?.error??`HTTP ${l.status}`);let q=await J(y),$=Array.isArray(w.whiteboards)?w.whiteboards:[],A;if(s&&(A=await E(s,$)),!a.current)return;n(w.enabled===!0),b($),_(q),f(A),M(null)}catch(l){a.current&&M(l instanceof Error?l.message:String(l))}finally{a.current&&S(!1)}}return d(),()=>{a.current=!1,o.current+=1}},[]);async function U(s){let d=o.current+1;o.current=d;let l=await E(s,c);!a.current||o.current!==d||!l||(f(l),window.history.replaceState(null,"",`#/whiteboards/${encodeURIComponent(s)}`))}async function j(){if(u){D(!0);try{let s=await fetch(`/api/whiteboards/${encodeURIComponent(u.id)}`,{method:"DELETE"}),d=await s.json().catch(()=>({}));if(!s.ok||d.ok===!1)throw new Error(d?.error??`HTTP ${s.status}`);if(!a.current)return;b(l=>l.filter(y=>y.id!==u.id)),m?.id===u.id&&(f(void 0),window.history.replaceState(null,"","#/whiteboards")),h(null)}catch(s){a.current&&window.alert(`\u5220\u9664\u5931\u8D25\uFF1A${s instanceof Error?s.message:String(s)}`)}finally{a.current&&D(!1)}}}let v="\u767D\u677F\u80FD\u529B\u5F53\u524D\u5173\u95ED\uFF1A\u4E0D\u4F1A\u81EA\u52A8\u521B\u5EFA/\u7ED1\u5B9A\u767D\u677F\uFF0C\u4E5F\u4E0D\u4F1A\u6CE8\u5165\u5230 agent prompt\u3002\u5386\u53F2\u767D\u677F\u4EC5\u5728 dashboard \u4E2D\u53EA\u8BFB\u53EF\u89C1\uFF0C\u53EF\u5728\u6B64\u6E05\u7406\u3002",N=(s=!1)=>(0,e.jsx)("div",{className:"page-heading",children:(0,e.jsxs)("div",{children:[(0,e.jsx)("p",{className:"eyebrow",children:t("nav.whiteboards")}),(0,e.jsx)("h1",{children:t("nav.whiteboards")}),s?(0,e.jsx)("span",{className:"wb-disabled-pill",title:v,"aria-label":v,children:v}):null]})});return k?(0,e.jsxs)("section",{className:"page whiteboards-page",children:[N(),(0,e.jsxs)("p",{className:"hint-warn",children:["\u52A0\u8F7D\u767D\u677F\u5931\u8D25\uFF1A",k]})]}):H?(0,e.jsxs)("section",{className:"page whiteboards-page","data-whiteboards-host":!0,children:[N(),(0,e.jsx)(W,{label:t("common.loading")})]}):(0,e.jsxs)("section",{className:"page whiteboards-page",children:[N(!i),(0,e.jsxs)("div",{className:"wb-split",children:[(0,e.jsx)("section",{className:"overview-block wb-list-block",children:(0,e.jsx)("div",{className:"wb-list-panel",children:I.length===0?(0,e.jsx)("p",{className:"empty wb-empty",children:"\u6682\u65E0\u767D\u677F\u3002\u6253\u5F00\u80FD\u529B\u540E\uFF0C\u6BCF\u4E2A\u7FA4\u9996\u6B21\u9700\u8981\u767D\u677F\u65F6\u624D\u4F1A\u521B\u5EFA\u9ED8\u8BA4\u767D\u677F\u3002"}):I.map(s=>(0,e.jsxs)("details",{className:"wb-group",open:!0,children:[(0,e.jsxs)("summary",{children:[(0,e.jsx)("span",{className:"wb-group-title",children:(0,e.jsx)(C,{text:s.label,textClassName:"wb-group-title-scroll"})}),(0,e.jsx)("small",{children:s.rows.length})]}),(0,e.jsx)("div",{className:"wb-group-items",children:s.rows.map(d=>(0,e.jsx)(Q,{row:d,active:m?.id===d.id,onSelect:l=>{U(l)}},d.id))})]},s.chatId))})}),(0,e.jsx)("section",{className:"overview-block wb-detail-block",children:(0,e.jsx)("div",{className:"wb-detail-panel",id:"whiteboard-detail",children:(0,e.jsx)(V,{selected:m,groupNames:g,onDelete:()=>{m&&h(m)}})})})]}),u?(0,e.jsx)(X,{selected:u,deleting:L,onCancel:()=>h(null),onConfirm:()=>{j()}}):null]})}function ae(t){return P(t,(0,e.jsx)(Y,{}))}export{ae as renderWhiteboardsPage};
1
+ import{c as B}from"./chunk-KOPISPHN.js";import{c as W,k as C}from"./chunk-5CJMHTT4.js";import"./chunk-M6BHWQFO.js";import{a as P}from"./chunk-SYC6D5XU.js";import{a as O,d as T}from"./chunk-5THX4DI2.js";import{c as R}from"./chunk-7SZRL6FY.js";var r=R(O(),1);var e=R(T(),1);function x(t){let a=Date.parse(t);if(!a)return t||"-";let o=Math.max(0,Math.floor((Date.now()-a)/1e3));if(o<60)return`${o}s ago`;let i=Math.floor(o/60);if(i<60)return`${i}m ago`;let n=Math.floor(i/60);return n<24?`${n}h ago`:`${Math.floor(n/24)}d ago`}function F(t){return t.chatId?.trim()||"__local__"}function G(t,a){if(t==="__local__")return"\u672A\u7ED1\u5B9A\u7FA4 / \u672C\u5730\u767D\u677F";let o=a.get(t);return o&&o!==t?`${o} (${t})`:t}function K(t,a){let o=new Map;for(let i of t){let n=F(i),c=o.get(n)??[];c.push(i),o.set(n,c)}return[...o.entries()].map(([i,n])=>({chatId:i,label:G(i,a),rows:[...n].sort((c,b)=>b.updatedAt.localeCompare(c.updatedAt))})).sort((i,n)=>i.label.localeCompare(n.label))}function z(){return decodeURIComponent((location.hash.match(/^#\/whiteboards\/([^/]+)/)?.[1]??"").trim())}async function E(t,a){let o=await fetch(`/api/whiteboards/${encodeURIComponent(t)}`),i=await o.json().catch(()=>({}));if(o.ok)return{id:t,content:String(i.content??""),row:a.find(n=>n.id===t)}}async function J(t){let a=new Map;if(!t?.ok)return a;let o=await t.json().catch(()=>({})),i=Array.isArray(o.chats)?o.chats:[];for(let n of i)n.chatId&&a.set(String(n.chatId),String(n.name||n.chatId));return a}function Q(t){let a=t.row,o=a.title||a.id;return(0,e.jsxs)("a",{className:`wb-item${t.active?" active":""}`,"data-whiteboard-id":a.id,href:`#/whiteboards/${encodeURIComponent(a.id)}`,onClick:i=>{i.preventDefault(),t.onSelect(a.id)},children:[(0,e.jsx)("div",{className:"wb-item-head",children:(0,e.jsxs)("div",{className:"wb-item-main",children:[(0,e.jsx)("strong",{children:(0,e.jsx)(C,{text:o,textClassName:"wb-title-scroll"})}),(0,e.jsx)("span",{className:"wb-id-text",title:a.id,children:a.id})]})}),(0,e.jsxs)("div",{className:"wb-item-foot",children:[(0,e.jsxs)("div",{className:"wb-item-meta",children:[(0,e.jsx)("span",{children:x(a.updatedAt)}),(0,e.jsx)("span",{children:"\xB7"}),(0,e.jsxs)("span",{children:["log ",a.logCount]})]}),(0,e.jsx)("span",{className:"wb-scope",title:a.scope,children:a.scope})]})]})}function p(t){let a=t.value||"-";return(0,e.jsxs)("div",{className:"wb-meta-card",title:a,children:[(0,e.jsx)("span",{children:t.label}),(0,e.jsx)("strong",{children:a})]})}function V(t){let a=t.selected;if(!a)return(0,e.jsx)("p",{className:"empty",children:"\u9009\u62E9\u5DE6\u4FA7\u767D\u677F\u67E5\u770B meta \u548C board.md\u3002"});let o=a.row,i=o?.chatId?G(o.chatId,t.groupNames):"\u672A\u7ED1\u5B9A\u7FA4 / \u672C\u5730\u767D\u677F",n=o?.title||a.id;return(0,e.jsxs)(e.Fragment,{children:[(0,e.jsxs)("div",{className:"wb-detail-head",children:[(0,e.jsxs)("div",{className:"wb-detail-title",children:[(0,e.jsx)("p",{className:"eyebrow",children:"WHITEBOARD"}),(0,e.jsx)("h2",{title:n,children:n}),(0,e.jsx)("code",{title:a.id,children:a.id})]}),(0,e.jsx)("button",{type:"button",className:"danger","data-delete-whiteboard":!0,onClick:t.onDelete,children:"\u5220\u9664\u767D\u677F"})]}),(0,e.jsxs)("div",{className:"wb-meta-grid",children:[(0,e.jsx)(p,{label:"\u6240\u5C5E\u7FA4",value:i}),(0,e.jsx)(p,{label:"\u8303\u56F4",value:o?.scope??"-"}),(0,e.jsx)(p,{label:"\u6700\u8FD1\u66F4\u65B0",value:o?.updatedAt?x(o.updatedAt):"-"}),(0,e.jsx)(p,{label:"\u6765\u6E90\u76EE\u5F55",value:o?.workingDir??"-"})]}),(0,e.jsxs)("details",{className:"wb-admin-info",children:[(0,e.jsx)("summary",{children:"\u7BA1\u7406\u4FE1\u606F / \u6587\u4EF6\u8DEF\u5F84"}),(0,e.jsx)("code",{children:o?.path??""})]}),(0,e.jsxs)("section",{className:"wb-board-panel",children:[(0,e.jsxs)("div",{className:"wb-board-panel-head",children:[(0,e.jsx)("strong",{children:"board.md"}),(0,e.jsx)("span",{children:"\u53EA\u8BFB\u9884\u89C8"})]}),(0,e.jsx)("pre",{children:a.content||"\uFF08\u6682\u65E0\u5185\u5BB9\uFF09"})]})]})}function X(t){let a=t.selected.row?.title||t.selected.id;return(0,e.jsx)("div",{className:"wb-delete-backdrop","data-delete-modal":!0,onClick:o=>{o.target===o.currentTarget&&t.onCancel()},children:(0,e.jsxs)("div",{className:"wb-delete-dialog",role:"dialog","aria-modal":"true","aria-labelledby":"wb-delete-title",children:[(0,e.jsxs)("div",{className:"wb-delete-body",children:[(0,e.jsx)("div",{className:"wb-delete-mark","aria-hidden":"true",children:"!"}),(0,e.jsxs)("div",{children:[(0,e.jsx)("h3",{id:"wb-delete-title",children:"\u5220\u9664\u767D\u677F\uFF1F"}),(0,e.jsxs)("p",{children:["\u5C06\u5220\u9664 ",(0,e.jsx)("strong",{children:a}),"\uFF08",(0,e.jsx)("code",{children:t.selected.id}),"\uFF09\u7684 board\u3001log\u3001meta\uFF0C\u5E76\u6E05\u7406\u9ED8\u8BA4\u7ED1\u5B9A\u548C\u4F1A\u8BDD\u5F15\u7528\u3002\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002"]})]})]}),(0,e.jsxs)("div",{className:"actions wb-delete-actions",children:[(0,e.jsx)("button",{type:"button","data-delete-cancel":!0,onClick:t.onCancel,disabled:t.deleting,children:"\u53D6\u6D88"}),(0,e.jsx)("button",{type:"button",className:"danger","data-delete-confirm":!0,onClick:t.onConfirm,disabled:t.deleting,children:t.deleting?"\u5220\u9664\u4E2D...":"\u786E\u8BA4\u5220\u9664"})]})]})})}function Y(){let t=B(),a=(0,r.useRef)(!1),o=(0,r.useRef)(0),[i,n]=(0,r.useState)(!1),[c,b]=(0,r.useState)([]),[g,_]=(0,r.useState)(()=>new Map),[m,f]=(0,r.useState)(),[H,S]=(0,r.useState)(!0),[k,M]=(0,r.useState)(null),[u,h]=(0,r.useState)(null),[L,D]=(0,r.useState)(!1),I=(0,r.useMemo)(()=>K(c,g),[c,g]);(0,r.useEffect)(()=>{a.current=!0;let s=z();async function d(){S(!0);try{let[l,y]=await Promise.all([fetch("/api/whiteboards"),fetch("/api/groups").catch(()=>null)]),w=await l.json().catch(()=>({}));if(!l.ok)throw new Error(w?.error??`HTTP ${l.status}`);let q=await J(y),$=Array.isArray(w.whiteboards)?w.whiteboards:[],A;if(s&&(A=await E(s,$)),!a.current)return;n(w.enabled===!0),b($),_(q),f(A),M(null)}catch(l){a.current&&M(l instanceof Error?l.message:String(l))}finally{a.current&&S(!1)}}return d(),()=>{a.current=!1,o.current+=1}},[]);async function U(s){let d=o.current+1;o.current=d;let l=await E(s,c);!a.current||o.current!==d||!l||(f(l),window.history.replaceState(null,"",`#/whiteboards/${encodeURIComponent(s)}`))}async function j(){if(u){D(!0);try{let s=await fetch(`/api/whiteboards/${encodeURIComponent(u.id)}`,{method:"DELETE"}),d=await s.json().catch(()=>({}));if(!s.ok||d.ok===!1)throw new Error(d?.error??`HTTP ${s.status}`);if(!a.current)return;b(l=>l.filter(y=>y.id!==u.id)),m?.id===u.id&&(f(void 0),window.history.replaceState(null,"","#/whiteboards")),h(null)}catch(s){a.current&&window.alert(`\u5220\u9664\u5931\u8D25\uFF1A${s instanceof Error?s.message:String(s)}`)}finally{a.current&&D(!1)}}}let v="\u767D\u677F\u80FD\u529B\u5F53\u524D\u5173\u95ED\uFF1A\u4E0D\u4F1A\u81EA\u52A8\u521B\u5EFA/\u7ED1\u5B9A\u767D\u677F\uFF0C\u4E5F\u4E0D\u4F1A\u6CE8\u5165\u5230 agent prompt\u3002\u5386\u53F2\u767D\u677F\u4EC5\u5728 dashboard \u4E2D\u53EA\u8BFB\u53EF\u89C1\uFF0C\u53EF\u5728\u6B64\u6E05\u7406\u3002",N=(s=!1)=>(0,e.jsx)("div",{className:"page-heading",children:(0,e.jsxs)("div",{children:[(0,e.jsx)("p",{className:"eyebrow",children:t("nav.whiteboards")}),(0,e.jsx)("h1",{children:t("nav.whiteboards")}),s?(0,e.jsx)("span",{className:"wb-disabled-pill",title:v,"aria-label":v,children:v}):null]})});return k?(0,e.jsxs)("section",{className:"page whiteboards-page",children:[N(),(0,e.jsxs)("p",{className:"hint-warn",children:["\u52A0\u8F7D\u767D\u677F\u5931\u8D25\uFF1A",k]})]}):H?(0,e.jsxs)("section",{className:"page whiteboards-page","data-whiteboards-host":!0,children:[N(),(0,e.jsx)(W,{label:t("common.loading")})]}):(0,e.jsxs)("section",{className:"page whiteboards-page",children:[N(!i),(0,e.jsxs)("div",{className:"wb-split",children:[(0,e.jsx)("section",{className:"overview-block wb-list-block",children:(0,e.jsx)("div",{className:"wb-list-panel",children:I.length===0?(0,e.jsx)("p",{className:"empty wb-empty",children:"\u6682\u65E0\u767D\u677F\u3002\u6253\u5F00\u80FD\u529B\u540E\uFF0C\u6BCF\u4E2A\u7FA4\u9996\u6B21\u9700\u8981\u767D\u677F\u65F6\u624D\u4F1A\u521B\u5EFA\u9ED8\u8BA4\u767D\u677F\u3002"}):I.map(s=>(0,e.jsxs)("details",{className:"wb-group",open:!0,children:[(0,e.jsxs)("summary",{children:[(0,e.jsx)("span",{className:"wb-group-title",children:(0,e.jsx)(C,{text:s.label,textClassName:"wb-group-title-scroll"})}),(0,e.jsx)("small",{children:s.rows.length})]}),(0,e.jsx)("div",{className:"wb-group-items",children:s.rows.map(d=>(0,e.jsx)(Q,{row:d,active:m?.id===d.id,onSelect:l=>{U(l)}},d.id))})]},s.chatId))})}),(0,e.jsx)("section",{className:"overview-block wb-detail-block",children:(0,e.jsx)("div",{className:"wb-detail-panel",id:"whiteboard-detail",children:(0,e.jsx)(V,{selected:m,groupNames:g,onDelete:()=>{m&&h(m)}})})})]}),u?(0,e.jsx)(X,{selected:u,deleting:L,onCancel:()=>h(null),onConfirm:()=>{j()}}):null]})}function ae(t){return P(t,(0,e.jsx)(Y,{}))}export{ae as renderWhiteboardsPage};
@@ -13640,6 +13640,16 @@ tbody tr[data-chat]:hover {
13640
13640
  white-space: nowrap;
13641
13641
  }
13642
13642
 
13643
+ .schedule-chip-strip span.schedule-chat-chip {
13644
+ display: inline-block;
13645
+ max-width: min(320px, 100%);
13646
+ overflow: hidden;
13647
+ text-overflow: ellipsis;
13648
+ white-space: nowrap;
13649
+ line-height: 22px;
13650
+ text-align: left;
13651
+ }
13652
+
13643
13653
  .schedule-chip-strip span.schedule-error-chip {
13644
13654
  border-color: color-mix(in srgb, var(--danger) 35%, var(--border-soft));
13645
13655
  background: color-mix(in srgb, var(--danger) 10%, var(--surface));
@@ -26024,3 +26034,105 @@ button.ui-refresh-button.is-loading .ui-action-icon {
26024
26034
  .cs-advanced-fields .cs-feed-group > legend {
26025
26035
  padding: 0;
26026
26036
  }
26037
+
26038
+ .feed-group-picker {
26039
+ position: relative;
26040
+ width: 100%;
26041
+ }
26042
+
26043
+ .feed-group-picker > summary {
26044
+ box-sizing: border-box;
26045
+ display: flex;
26046
+ align-items: center;
26047
+ min-height: var(--button-height);
26048
+ padding: 0 42px 0 var(--button-padding-x);
26049
+ border: 1px solid color-mix(in srgb, var(--accent) 38%, var(--border-soft));
26050
+ border-radius: var(--radius-full);
26051
+ background: var(--surface);
26052
+ color: var(--fg);
26053
+ cursor: pointer;
26054
+ list-style: none;
26055
+ border-color: color-mix(in srgb, var(--accent) 38%, var(--border-soft));
26056
+ box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 10%, transparent);
26057
+ }
26058
+
26059
+ .feed-group-picker > summary::-webkit-details-marker { display: none; }
26060
+
26061
+ .feed-group-picker > summary > span,
26062
+ .feed-group-picker-pop > button {
26063
+ min-width: 0;
26064
+ overflow: hidden;
26065
+ text-overflow: ellipsis;
26066
+ white-space: nowrap;
26067
+ }
26068
+
26069
+ .feed-group-picker > summary::after {
26070
+ content: "";
26071
+ position: absolute;
26072
+ right: 18px;
26073
+ width: 8px;
26074
+ height: 8px;
26075
+ border-style: solid;
26076
+ border-width: 0 2px 2px 0;
26077
+ border-color: var(--accent-strong);
26078
+ transform: translateY(-2px) rotate(45deg);
26079
+ }
26080
+
26081
+ .feed-group-picker[open] > summary::after { transform: translateY(2px) rotate(225deg); }
26082
+ .feed-group-picker.is-disabled > summary { cursor: not-allowed; opacity: .58; }
26083
+
26084
+ .feed-group-picker-pop {
26085
+ position: absolute;
26086
+ z-index: 120;
26087
+ left: 0;
26088
+ right: 0;
26089
+ top: calc(100% + 6px);
26090
+ display: grid;
26091
+ max-height: min(420px, calc(100vh - 180px));
26092
+ overflow-y: auto;
26093
+ padding: 8px;
26094
+ border: 1px solid var(--border-soft);
26095
+ border-radius: 14px;
26096
+ background: var(--surface-elevated, var(--surface));
26097
+ box-shadow: 0 16px 40px rgb(0 0 0 / 28%);
26098
+ }
26099
+
26100
+ .feed-group-picker-new {
26101
+ position: sticky;
26102
+ z-index: 1;
26103
+ top: 0;
26104
+ display: grid;
26105
+ gap: 6px;
26106
+ padding: 4px 4px 10px;
26107
+ border-bottom: 1px solid var(--border-soft);
26108
+ background: var(--surface-elevated, var(--surface));
26109
+ }
26110
+
26111
+ .feed-group-picker-new > span { color: var(--muted); font-size: 12px; font-weight: 700; }
26112
+ .feed-group-picker-new > input {
26113
+ box-sizing: border-box;
26114
+ width: 100%;
26115
+ min-height: 38px;
26116
+ padding: 0 12px;
26117
+ border: 1px solid var(--border-soft);
26118
+ border-radius: 9px;
26119
+ background: var(--surface);
26120
+ color: var(--fg);
26121
+ }
26122
+
26123
+ .feed-group-picker-pop > button {
26124
+ min-height: 38px;
26125
+ padding: 0 12px;
26126
+ border: 0;
26127
+ border-radius: 8px;
26128
+ background: transparent;
26129
+ color: var(--fg);
26130
+ text-align: left;
26131
+ cursor: pointer;
26132
+ }
26133
+
26134
+ .feed-group-picker-pop > button:hover,
26135
+ .feed-group-picker-pop > button[aria-current="true"] {
26136
+ background: var(--accent-soft);
26137
+ color: var(--accent-strong);
26138
+ }
package/dist/dashboard.js CHANGED
@@ -11,7 +11,7 @@ import { logger } from './utils/logger.js';
11
11
  import { gracefulProcessExitCode } from './pm2-graceful-exit.js';
12
12
  import { config, isWildcardBindHost } from './config.js';
13
13
  import { listenWithProbe } from './utils/listen-with-probe.js';
14
- import { generateToken, parseCookie, buildSetCookie, verifyHmac, cliAuthBind, decideDashboardAuth, loadPersistedToken, loadOrCreatePersistedToken, persistToken, loadDashboardSecret, loadOrCreateDashboardSecret, } from './dashboard/auth.js';
14
+ import { parseCookie, buildSetCookie, verifyHmac, cliAuthBind, decideDashboardAuth, loadPersistedToken, loadOrCreatePersistedToken, rotatePersistedToken, loadDashboardSecret, loadOrCreateDashboardSecret, } from './dashboard/auth.js';
15
15
  import { DaemonRegistry, botsRosterSignature } from './dashboard/registry.js';
16
16
  import { Aggregator, subscribeDaemon } from './dashboard/aggregator.js';
17
17
  import { createSessionPresentationCoordinator } from './dashboard/session-presentation.js';
@@ -104,6 +104,8 @@ import { resolveEffectivePluginIds, updateBotPluginOverride } from './core/plugi
104
104
  import { assertPluginBindingTransition, describePluginDependencyError } from './core/plugins/dependencies.js';
105
105
  import { inspectGatewayEntry } from './core/plugins/mcp/gateway-installer.js';
106
106
  import { fetchDaemonIpc } from './core/daemon-ipc-auth.js';
107
+ import { buildDashboardSummary, parseDashboardSummaryRows, } from './dashboard/dashboard-summary.js';
108
+ import { createDashboardSummaryEndpoint } from './dashboard/dashboard-summary-endpoint.js';
107
109
  const SECRET_PATH = dashboardSecretPath();
108
110
  const TOKEN_PATH = join(homedir(), '.botmux', '.dashboard-token');
109
111
  /** Per-daemon budget for the cross-daemon insight overview fan-out — bounds
@@ -113,7 +115,8 @@ const BOTS_JSON_PATH = join(homedir(), '.botmux', 'bots.json');
113
115
  const REGISTRY_DIR = join(resolveBotmuxDataDir(), 'dashboard-daemons');
114
116
  // The dashboard probes upward if its configured port is busy (e.g. a second
115
117
  // botmux instance on this host). The actually-bound port is persisted here so
116
- // the `botmux dashboard` CLI can reach /__cli/rotate without guessing.
118
+ // the `botmux dashboard` CLI can reach /__cli/current, /__cli/ensure, and
119
+ // /__cli/rotate without guessing.
117
120
  const PORT_PATH = join(homedir(), '.botmux', '.dashboard-port');
118
121
  function loadOrCreateSecret() {
119
122
  let existing;
@@ -137,15 +140,21 @@ function loadOrCreateSecret() {
137
140
  process.exit(1);
138
141
  }
139
142
  }
140
- // The active dashboard token is persisted to disk so a previously-issued
141
- // dashboard URL survives `botmux restart`. A platform-bound dashboard creates
142
- // the first token on startup; only `botmux dashboard` (the /__cli/rotate
143
- // endpoint) replaces it and thereby invalidates the old link.
144
- // The start/restart hint reads it via the non-rotating /__cli/current endpoint
145
- // so it can show the live link without invalidating it.
146
- let activeToken = loadPersistedToken(TOKEN_PATH);
143
+ // The persisted file is the active-token authority. Reading it at each use
144
+ // keeps multiple dashboard processes coherent: first creation converges under
145
+ // a file lock, and an explicit rotation is observed by every process without a
146
+ // restart. `/__cli/current` remains a strictly read-only probe.
147
+ function currentDashboardToken() {
148
+ try {
149
+ return loadPersistedToken(TOKEN_PATH);
150
+ }
151
+ catch (error) {
152
+ logger.warn(`[dashboard] Failed to read token from ${TOKEN_PATH}: ${error.message}`);
153
+ return null;
154
+ }
155
+ }
147
156
  // The port we actually bound (may differ from config.dashboard.port after an
148
- // EADDRINUSE probe). Used for the rotation-URL and persisted for the CLI.
157
+ // EADDRINUSE probe). Used for token-bearing URLs and persisted for the CLI.
149
158
  let boundDashboardPort = config.dashboard.port;
150
159
  const SECRET = loadOrCreateSecret();
151
160
  function tcpPortAvailable(host, port) {
@@ -220,7 +229,7 @@ aggregator.on(sessionPresentation.onEvent);
220
229
  // 调试终端(owner-only 裸 bash)。默认工作目录取当前所有 session 的工作目录去重,
221
230
  // 让 owner 从熟悉的目录起终端复现问题;都没有时模块内退回 homedir。
222
231
  const debugTerminalManager = createDebugTerminalManager({
223
- getActiveToken: () => activeToken,
232
+ getActiveToken: currentDashboardToken,
224
233
  defaultWorkingDirs: () => {
225
234
  const dirs = new Set();
226
235
  for (const s of aggregator.getSessions()) {
@@ -1032,9 +1041,9 @@ const groupsActionDeps = {
1032
1041
  // ─── PR2 C8: Route B internal API (`/__daemon/*`) ───────────────────────────
1033
1042
  // HMAC + loopback + ts ±60s + nonce TTL, signed-request envelope = full
1034
1043
  // (ts, nonce, method, pathWithQuery, sha256(body)). Reuses `.dashboard-secret`
1035
- // for the HMAC key — the same secret `/__cli/rotate` already uses — but the
1036
- // signing material is wider so a `/__cli/rotate` signature cannot be replayed
1037
- // here and vice versa (different protocols, same secret, no cross-replay).
1044
+ // for the HMAC key — the same secret the `/__cli/*` protocol uses — but the
1045
+ // signing material is wider so a CLI signature cannot be replayed here and
1046
+ // vice versa (different protocols, same secret, no cross-replay).
1038
1047
  //
1039
1048
  // SECRET fail-closed: `loadOrCreateSecret()` returns a 32-byte base64url
1040
1049
  // string and never empty; we still guard below at server-startup time.
@@ -1751,7 +1760,7 @@ async function handlePluginManagementApi(req, res, url) {
1751
1760
  return false;
1752
1761
  }
1753
1762
  // ─── HTTP routing ────────────────────────────────────────────────────────────
1754
- function authedToken(req, url) {
1763
+ function authedToken(req, url, activeToken) {
1755
1764
  const q = url.searchParams.get('t');
1756
1765
  if (q && q === activeToken)
1757
1766
  return q;
@@ -2393,6 +2402,52 @@ function parseTerminalSessionId(pathname) {
2393
2402
  const seg = pathname.slice(3).split('/')[0];
2394
2403
  return seg || undefined;
2395
2404
  }
2405
+ /**
2406
+ * Read every currently-online daemon directly for the public dashboard
2407
+ * summary. The regular aggregator intentionally retains offline
2408
+ * rows for operator history, so using it here would make an offline bot's old
2409
+ * sessions or schedules look live. A failed or malformed daemon snapshot
2410
+ * rejects the whole projection instead of silently turning missing data into
2411
+ * zeroes.
2412
+ */
2413
+ async function liveDashboardSummary() {
2414
+ const daemons = registry.list();
2415
+ const configuredBots = loadBotConfigs();
2416
+ const snapshots = await Promise.all(daemons.map(async (daemon) => {
2417
+ const [sessionsResponse, schedulesResponse] = await Promise.all([
2418
+ fetchDaemonIpc(daemon.ipcPort, '/api/sessions', {
2419
+ signal: AbortSignal.timeout(2_000),
2420
+ }),
2421
+ fetchDaemonIpc(daemon.ipcPort, '/api/schedules', {
2422
+ signal: AbortSignal.timeout(2_000),
2423
+ }),
2424
+ ]);
2425
+ if (!sessionsResponse.ok || !schedulesResponse.ok) {
2426
+ throw new Error('daemon_snapshot_http_error');
2427
+ }
2428
+ const [sessionsBody, schedulesBody] = await Promise.all([
2429
+ sessionsResponse.json(),
2430
+ schedulesResponse.json(),
2431
+ ]);
2432
+ return parseDashboardSummaryRows({
2433
+ sessions: sessionsBody.sessions,
2434
+ schedules: schedulesBody.schedules,
2435
+ });
2436
+ }));
2437
+ return buildDashboardSummary({
2438
+ generatedAt: new Date(),
2439
+ configuredBotAppIds: configuredBots.map(bot => bot.larkAppId),
2440
+ onlineBotAppIds: daemons.map(daemon => daemon.larkAppId),
2441
+ sessions: snapshots.flatMap(snapshot => snapshot.sessions),
2442
+ schedules: snapshots.flatMap(snapshot => snapshot.schedules),
2443
+ });
2444
+ }
2445
+ const dashboardSummaryEndpoint = createDashboardSummaryEndpoint({
2446
+ load: liveDashboardSummary,
2447
+ onError: error => {
2448
+ logger.warn(`[dashboard-summary] live snapshot unavailable: ${error instanceof Error ? error.message : String(error)}`);
2449
+ },
2450
+ });
2396
2451
  const server = createServer(async (req, res) => {
2397
2452
  try {
2398
2453
  const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);
@@ -2411,7 +2466,8 @@ const server = createServer(async (req, res) => {
2411
2466
  // outside the browser auth gate so packaged desktop apps can decide whether
2412
2467
  // this runtime speaks their dashboard protocol before loading the SPA.
2413
2468
  if (req.method === 'GET' && url.pathname === '/__desktop/compat') {
2414
- const presentedToken = authedToken(req, url);
2469
+ const activeToken = currentDashboardToken();
2470
+ const presentedToken = authedToken(req, url, activeToken);
2415
2471
  const boundMachineId = activeToken && presentedToken === activeToken
2416
2472
  ? readPlatformBinding()?.machineId
2417
2473
  : null;
@@ -2473,32 +2529,53 @@ const server = createServer(async (req, res) => {
2473
2529
  if (await daemonInternalApi.handle(req, res, url)) {
2474
2530
  return;
2475
2531
  }
2476
- // CLI rotate (HMAC + loopback only) — for `botmux dashboard`. Mints a fresh
2477
- // token, invalidating any previously-issued link.
2532
+ // CLI rotate (HMAC + loopback only) — for `botmux dashboard rotate`.
2533
+ // Publish the new token only after a durable write succeeds.
2478
2534
  if (req.method === 'POST' && url.pathname === '/__cli/rotate') {
2479
2535
  const gate = verifyCliRequest(req, url.pathname);
2480
2536
  if (!gate.ok)
2481
2537
  return jsonRes(res, gate.status, gate.body);
2482
- activeToken = generateToken();
2483
2538
  try {
2484
- persistToken(TOKEN_PATH, activeToken);
2539
+ const token = rotatePersistedToken(TOKEN_PATH);
2540
+ return jsonRes(res, 200, dashboardUrlsFor(token));
2485
2541
  }
2486
2542
  catch (e) {
2487
2543
  logger.warn(`[dashboard] Failed to persist token to ${TOKEN_PATH}: ${e.message}`);
2544
+ return jsonRes(res, 500, { error: 'token_persist_failed' });
2545
+ }
2546
+ }
2547
+ // CLI get-or-create URL (HMAC + loopback only). Existing links survive
2548
+ // untouched; the first caller atomically creates and persists a token.
2549
+ if (req.method === 'POST' && url.pathname === '/__cli/ensure') {
2550
+ const gate = verifyCliRequest(req, url.pathname);
2551
+ if (!gate.ok)
2552
+ return jsonRes(res, gate.status, gate.body);
2553
+ try {
2554
+ const token = loadOrCreatePersistedToken(TOKEN_PATH);
2555
+ return jsonRes(res, 200, dashboardUrlsFor(token));
2556
+ }
2557
+ catch (e) {
2558
+ logger.warn(`[dashboard] Failed to ensure token at ${TOKEN_PATH}: ${e.message}`);
2559
+ return jsonRes(res, 500, { error: 'token_persist_failed' });
2488
2560
  }
2489
- return jsonRes(res, 200, dashboardUrlsFor(activeToken));
2490
2561
  }
2491
- // CLI read current URL (HMAC + loopback only) — for the start/restart hint.
2492
- // Unlike /__cli/rotate this does NOT mint a token, so an already-issued
2493
- // dashboard link survives restart untouched. 404 → no token has ever been
2494
- // minted (caller falls back to suggesting `botmux dashboard`).
2562
+ // CLI read current URL (HMAC + loopback only) — for start/restart hints and
2563
+ // safe port discovery. This never mints a token.
2495
2564
  if (req.method === 'POST' && url.pathname === '/__cli/current') {
2496
2565
  const gate = verifyCliRequest(req, url.pathname);
2497
2566
  if (!gate.ok)
2498
2567
  return jsonRes(res, gate.status, gate.body);
2499
- if (!activeToken)
2568
+ let token;
2569
+ try {
2570
+ token = loadPersistedToken(TOKEN_PATH);
2571
+ }
2572
+ catch (e) {
2573
+ logger.warn(`[dashboard] Failed to read token from ${TOKEN_PATH}: ${e.message}`);
2574
+ return jsonRes(res, 500, { error: 'token_unavailable' });
2575
+ }
2576
+ if (!token)
2500
2577
  return jsonRes(res, 404, { error: 'no_active_token' });
2501
- return jsonRes(res, 200, dashboardUrlsFor(activeToken));
2578
+ return jsonRes(res, 200, dashboardUrlsFor(token));
2502
2579
  }
2503
2580
  // CLI 通知绑定变化(HMAC + loopback)——`botmux bind` 写完绑定后捅一下,立即重连平台,
2504
2581
  // 无需重启 daemon,也不依赖 fs.watch。
@@ -2521,7 +2598,8 @@ const server = createServer(async (req, res) => {
2521
2598
  startPlatformTunnelIfBound();
2522
2599
  return jsonRes(res, 200, { ok: true });
2523
2600
  }
2524
- const presentedToken = authedToken(req, url);
2601
+ const activeToken = currentDashboardToken();
2602
+ const presentedToken = authedToken(req, url, activeToken);
2525
2603
  const globalDashboardConfig = readGlobalConfig().dashboard;
2526
2604
  const decision = decideDashboardAuth({
2527
2605
  method: req.method ?? 'GET',
@@ -2659,6 +2737,12 @@ const server = createServer(async (req, res) => {
2659
2737
  return jsonRes(res, 200, startHd2dDownload());
2660
2738
  }
2661
2739
  // ─── Public API (cookie/token already validated above) ──────────────────
2740
+ if (req.method === 'GET' && url.pathname === '/api/dashboard/v1/summary') {
2741
+ const result = await dashboardSummaryEndpoint.get({ authenticated: authed });
2742
+ for (const [name, value] of Object.entries(result.headers))
2743
+ res.setHeader(name, value);
2744
+ return jsonRes(res, result.status, result.body);
2745
+ }
2662
2746
  if (await handleResourceMonitorApi(req, res, url, resourceMonitor)) {
2663
2747
  return;
2664
2748
  }
@@ -5482,15 +5566,16 @@ function startPlatformTunnelIfBound() {
5482
5566
  const binding = readPlatformBinding();
5483
5567
  if (!binding)
5484
5568
  return;
5485
- if (!activeToken) {
5486
- activeToken = loadOrCreatePersistedToken(TOKEN_PATH);
5569
+ const existingToken = currentDashboardToken();
5570
+ loadOrCreatePersistedToken(TOKEN_PATH);
5571
+ if (!existingToken) {
5487
5572
  logger.info('[platform-tunnel] 已初始化 dashboard token');
5488
5573
  }
5489
5574
  const version = readBotmuxVersion();
5490
5575
  platformTunnel = startPlatformTunnelClient({
5491
5576
  binding,
5492
5577
  getDashboardPort: () => boundDashboardPort,
5493
- getDashboardToken: () => activeToken,
5578
+ getDashboardToken: currentDashboardToken,
5494
5579
  getVersion: () => version,
5495
5580
  getBots: () => readPlatformBotsInfo(),
5496
5581
  getTeamSyncRev: () => getPlatformTeamSyncRev(config.session.dataDir),