botmux 2.93.1 → 2.95.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 (290) hide show
  1. package/dist/adapters/backend/tmux-backend.d.ts +18 -2
  2. package/dist/adapters/backend/tmux-backend.d.ts.map +1 -1
  3. package/dist/adapters/backend/tmux-backend.js +45 -3
  4. package/dist/adapters/backend/tmux-backend.js.map +1 -1
  5. package/dist/adapters/backend/tmux-pipe-backend.js +1 -1
  6. package/dist/adapters/backend/tmux-pipe-backend.js.map +1 -1
  7. package/dist/adapters/backend/types.d.ts +8 -0
  8. package/dist/adapters/backend/types.d.ts.map +1 -1
  9. package/dist/adapters/backend/types.js.map +1 -1
  10. package/dist/adapters/backend/zellij-backend.js +1 -1
  11. package/dist/adapters/backend/zellij-backend.js.map +1 -1
  12. package/dist/adapters/cli/claude-code.d.ts.map +1 -1
  13. package/dist/adapters/cli/claude-code.js +6 -1
  14. package/dist/adapters/cli/claude-code.js.map +1 -1
  15. package/dist/bot-registry.d.ts +50 -1
  16. package/dist/bot-registry.d.ts.map +1 -1
  17. package/dist/bot-registry.js +30 -0
  18. package/dist/bot-registry.js.map +1 -1
  19. package/dist/cli/workflow.d.ts.map +1 -1
  20. package/dist/cli/workflow.js +11 -4
  21. package/dist/cli/workflow.js.map +1 -1
  22. package/dist/cli.d.ts.map +1 -1
  23. package/dist/cli.js +146 -1
  24. package/dist/cli.js.map +1 -1
  25. package/dist/core/command-handler.d.ts.map +1 -1
  26. package/dist/core/command-handler.js +28 -3
  27. package/dist/core/command-handler.js.map +1 -1
  28. package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
  29. package/dist/core/dashboard-ipc-server.js +92 -0
  30. package/dist/core/dashboard-ipc-server.js.map +1 -1
  31. package/dist/core/inherit-peer.d.ts +5 -0
  32. package/dist/core/inherit-peer.d.ts.map +1 -1
  33. package/dist/core/inherit-peer.js +3 -0
  34. package/dist/core/inherit-peer.js.map +1 -1
  35. package/dist/core/session-discovery.d.ts +9 -0
  36. package/dist/core/session-discovery.d.ts.map +1 -1
  37. package/dist/core/session-discovery.js +24 -0
  38. package/dist/core/session-discovery.js.map +1 -1
  39. package/dist/core/session-manager.d.ts.map +1 -1
  40. package/dist/core/session-manager.js +7 -6
  41. package/dist/core/session-manager.js.map +1 -1
  42. package/dist/core/terminal-url.d.ts.map +1 -1
  43. package/dist/core/terminal-url.js +15 -0
  44. package/dist/core/terminal-url.js.map +1 -1
  45. package/dist/core/trigger-session.js +2 -2
  46. package/dist/core/trigger-session.js.map +1 -1
  47. package/dist/core/worker-pool.d.ts.map +1 -1
  48. package/dist/core/worker-pool.js +30 -0
  49. package/dist/core/worker-pool.js.map +1 -1
  50. package/dist/daemon.d.ts.map +1 -1
  51. package/dist/daemon.js +296 -23
  52. package/dist/daemon.js.map +1 -1
  53. package/dist/dashboard/aggregator.d.ts +5 -0
  54. package/dist/dashboard/aggregator.d.ts.map +1 -1
  55. package/dist/dashboard/aggregator.js +7 -0
  56. package/dist/dashboard/aggregator.js.map +1 -1
  57. package/dist/dashboard/auth.d.ts.map +1 -1
  58. package/dist/dashboard/auth.js +7 -1
  59. package/dist/dashboard/auth.js.map +1 -1
  60. package/dist/dashboard/bot-payload.d.ts +2 -0
  61. package/dist/dashboard/bot-payload.d.ts.map +1 -1
  62. package/dist/dashboard/bot-payload.js +2 -0
  63. package/dist/dashboard/bot-payload.js.map +1 -1
  64. package/dist/dashboard/connector-api.d.ts.map +1 -1
  65. package/dist/dashboard/connector-api.js +14 -3
  66. package/dist/dashboard/connector-api.js.map +1 -1
  67. package/dist/dashboard/settings-write-applier.d.ts +2 -1
  68. package/dist/dashboard/settings-write-applier.d.ts.map +1 -1
  69. package/dist/dashboard/settings-write-applier.js +7 -0
  70. package/dist/dashboard/settings-write-applier.js.map +1 -1
  71. package/dist/dashboard/v3-runs-api.d.ts +26 -0
  72. package/dist/dashboard/v3-runs-api.d.ts.map +1 -0
  73. package/dist/dashboard/v3-runs-api.js +67 -0
  74. package/dist/dashboard/v3-runs-api.js.map +1 -0
  75. package/dist/dashboard/web/app.js +21 -9
  76. package/dist/dashboard/web/app.js.map +1 -1
  77. package/dist/dashboard/web/bot-defaults.d.ts.map +1 -1
  78. package/dist/dashboard/web/bot-defaults.js +128 -33
  79. package/dist/dashboard/web/bot-defaults.js.map +1 -1
  80. package/dist/dashboard/web/i18n.d.ts.map +1 -1
  81. package/dist/dashboard/web/i18n.js +42 -6
  82. package/dist/dashboard/web/i18n.js.map +1 -1
  83. package/dist/dashboard/web/legacy-workflow-link.d.ts +2 -0
  84. package/dist/dashboard/web/legacy-workflow-link.d.ts.map +1 -0
  85. package/dist/dashboard/web/legacy-workflow-link.js +12 -0
  86. package/dist/dashboard/web/legacy-workflow-link.js.map +1 -0
  87. package/dist/dashboard/web/settings.d.ts.map +1 -1
  88. package/dist/dashboard/web/settings.js +11 -0
  89. package/dist/dashboard/web/settings.js.map +1 -1
  90. package/dist/dashboard/web/v3-terminal.d.ts +11 -0
  91. package/dist/dashboard/web/v3-terminal.d.ts.map +1 -0
  92. package/dist/dashboard/web/v3-terminal.js +143 -0
  93. package/dist/dashboard/web/v3-terminal.js.map +1 -0
  94. package/dist/dashboard/web/v3.d.ts +2 -0
  95. package/dist/dashboard/web/v3.d.ts.map +1 -0
  96. package/dist/dashboard/web/v3.js +564 -0
  97. package/dist/dashboard/web/v3.js.map +1 -0
  98. package/dist/dashboard/web/whiteboards.d.ts.map +1 -1
  99. package/dist/dashboard/web/whiteboards.js +14 -5
  100. package/dist/dashboard/web/whiteboards.js.map +1 -1
  101. package/dist/dashboard/web/workflows.d.ts.map +1 -1
  102. package/dist/dashboard/web/workflows.js +12 -8
  103. package/dist/dashboard/web/workflows.js.map +1 -1
  104. package/dist/dashboard-web/app.js +834 -724
  105. package/dist/dashboard-web/apple-touch-icon.png +0 -0
  106. package/dist/dashboard-web/favicon.png +0 -0
  107. package/dist/dashboard-web/index.html +3 -1
  108. package/dist/dashboard-web/style.css +223 -0
  109. package/dist/dashboard.js +170 -10
  110. package/dist/dashboard.js.map +1 -1
  111. package/dist/global-config.d.ts +10 -0
  112. package/dist/global-config.d.ts.map +1 -1
  113. package/dist/global-config.js +8 -0
  114. package/dist/global-config.js.map +1 -1
  115. package/dist/i18n/en.d.ts.map +1 -1
  116. package/dist/i18n/en.js +23 -3
  117. package/dist/i18n/en.js.map +1 -1
  118. package/dist/i18n/zh.d.ts.map +1 -1
  119. package/dist/i18n/zh.js +23 -3
  120. package/dist/i18n/zh.js.map +1 -1
  121. package/dist/im/lark/card-handler.d.ts +12 -0
  122. package/dist/im/lark/card-handler.d.ts.map +1 -1
  123. package/dist/im/lark/card-handler.js +28 -0
  124. package/dist/im/lark/card-handler.js.map +1 -1
  125. package/dist/im/lark/client.d.ts.map +1 -1
  126. package/dist/im/lark/client.js +3 -12
  127. package/dist/im/lark/client.js.map +1 -1
  128. package/dist/im/lark/event-dispatcher.d.ts +2 -1
  129. package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
  130. package/dist/im/lark/event-dispatcher.js +20 -6
  131. package/dist/im/lark/event-dispatcher.js.map +1 -1
  132. package/dist/im/lark/grant-command.js +4 -4
  133. package/dist/im/lark/grant-command.js.map +1 -1
  134. package/dist/im/lark/message-parser.d.ts +29 -1
  135. package/dist/im/lark/message-parser.d.ts.map +1 -1
  136. package/dist/im/lark/message-parser.js +35 -1
  137. package/dist/im/lark/message-parser.js.map +1 -1
  138. package/dist/im/lark/v3-blocked-card-handler.d.ts +28 -0
  139. package/dist/im/lark/v3-blocked-card-handler.d.ts.map +1 -0
  140. package/dist/im/lark/v3-blocked-card-handler.js +134 -0
  141. package/dist/im/lark/v3-blocked-card-handler.js.map +1 -0
  142. package/dist/im/lark/v3-blocked-card.d.ts +75 -0
  143. package/dist/im/lark/v3-blocked-card.d.ts.map +1 -0
  144. package/dist/im/lark/v3-blocked-card.js +206 -0
  145. package/dist/im/lark/v3-blocked-card.js.map +1 -0
  146. package/dist/im/lark/v3-gate-card-handler.d.ts +31 -0
  147. package/dist/im/lark/v3-gate-card-handler.d.ts.map +1 -0
  148. package/dist/im/lark/v3-gate-card-handler.js +110 -0
  149. package/dist/im/lark/v3-gate-card-handler.js.map +1 -0
  150. package/dist/im/lark/v3-gate-card.d.ts +47 -0
  151. package/dist/im/lark/v3-gate-card.d.ts.map +1 -0
  152. package/dist/im/lark/v3-gate-card.js +119 -0
  153. package/dist/im/lark/v3-gate-card.js.map +1 -0
  154. package/dist/im/lark/v3-loop-grant-card-handler.d.ts +28 -0
  155. package/dist/im/lark/v3-loop-grant-card-handler.d.ts.map +1 -0
  156. package/dist/im/lark/v3-loop-grant-card-handler.js +90 -0
  157. package/dist/im/lark/v3-loop-grant-card-handler.js.map +1 -0
  158. package/dist/im/lark/v3-loop-grant-card.d.ts +46 -0
  159. package/dist/im/lark/v3-loop-grant-card.d.ts.map +1 -0
  160. package/dist/im/lark/v3-loop-grant-card.js +119 -0
  161. package/dist/im/lark/v3-loop-grant-card.js.map +1 -0
  162. package/dist/im/lark/v3-revisit-grant-card-handler.d.ts +29 -0
  163. package/dist/im/lark/v3-revisit-grant-card-handler.d.ts.map +1 -0
  164. package/dist/im/lark/v3-revisit-grant-card-handler.js +96 -0
  165. package/dist/im/lark/v3-revisit-grant-card-handler.js.map +1 -0
  166. package/dist/im/lark/v3-revisit-grant-card.d.ts +52 -0
  167. package/dist/im/lark/v3-revisit-grant-card.d.ts.map +1 -0
  168. package/dist/im/lark/v3-revisit-grant-card.js +123 -0
  169. package/dist/im/lark/v3-revisit-grant-card.js.map +1 -0
  170. package/dist/im/lark/workflow-cards.d.ts.map +1 -1
  171. package/dist/im/lark/workflow-cards.js +5 -1
  172. package/dist/im/lark/workflow-cards.js.map +1 -1
  173. package/dist/im/lark/workflow-progress-card.d.ts +1 -1
  174. package/dist/im/lark/workflow-progress-card.js +1 -1
  175. package/dist/im/lark/workflow-slash-command.d.ts +28 -0
  176. package/dist/im/lark/workflow-slash-command.d.ts.map +1 -1
  177. package/dist/im/lark/workflow-slash-command.js +61 -5
  178. package/dist/im/lark/workflow-slash-command.js.map +1 -1
  179. package/dist/platform/binding.d.ts +6 -0
  180. package/dist/platform/binding.d.ts.map +1 -1
  181. package/dist/platform/binding.js +17 -0
  182. package/dist/platform/binding.js.map +1 -1
  183. package/dist/platform/tunnel-client.d.ts +2 -0
  184. package/dist/platform/tunnel-client.d.ts.map +1 -1
  185. package/dist/platform/tunnel-client.js.map +1 -1
  186. package/dist/services/bot-config-store.d.ts.map +1 -1
  187. package/dist/services/bot-config-store.js +1 -0
  188. package/dist/services/bot-config-store.js.map +1 -1
  189. package/dist/services/card-prefs-store.d.ts +4 -0
  190. package/dist/services/card-prefs-store.d.ts.map +1 -1
  191. package/dist/services/card-prefs-store.js +19 -0
  192. package/dist/services/card-prefs-store.js.map +1 -1
  193. package/dist/services/oncall-store.d.ts +25 -0
  194. package/dist/services/oncall-store.d.ts.map +1 -1
  195. package/dist/services/oncall-store.js +56 -0
  196. package/dist/services/oncall-store.js.map +1 -1
  197. package/dist/services/transcript-resolver.d.ts.map +1 -1
  198. package/dist/services/transcript-resolver.js +21 -4
  199. package/dist/services/transcript-resolver.js.map +1 -1
  200. package/dist/setup/bot-config-editor.d.ts +7 -0
  201. package/dist/setup/bot-config-editor.d.ts.map +1 -1
  202. package/dist/setup/bot-config-editor.js +33 -0
  203. package/dist/setup/bot-config-editor.js.map +1 -1
  204. package/dist/skills/definitions.d.ts.map +1 -1
  205. package/dist/skills/definitions.js +208 -4
  206. package/dist/skills/definitions.js.map +1 -1
  207. package/dist/types.d.ts +1 -0
  208. package/dist/types.d.ts.map +1 -1
  209. package/dist/utils/terminal-favicon.d.ts +2 -0
  210. package/dist/utils/terminal-favicon.d.ts.map +1 -0
  211. package/dist/utils/terminal-favicon.js +5 -0
  212. package/dist/utils/terminal-favicon.js.map +1 -0
  213. package/dist/worker.js +152 -8
  214. package/dist/worker.js.map +1 -1
  215. package/dist/workflows/definition.d.ts +80 -80
  216. package/dist/workflows/events/payloads.d.ts +4 -4
  217. package/dist/workflows/events/schema.d.ts +16 -16
  218. package/dist/workflows/v3/architect.d.ts +37 -0
  219. package/dist/workflows/v3/architect.d.ts.map +1 -0
  220. package/dist/workflows/v3/architect.js +226 -0
  221. package/dist/workflows/v3/architect.js.map +1 -0
  222. package/dist/workflows/v3/bot-resolve.d.ts +21 -0
  223. package/dist/workflows/v3/bot-resolve.d.ts.map +1 -0
  224. package/dist/workflows/v3/bot-resolve.js +48 -0
  225. package/dist/workflows/v3/bot-resolve.js.map +1 -0
  226. package/dist/workflows/v3/cli-run.d.ts +24 -0
  227. package/dist/workflows/v3/cli-run.d.ts.map +1 -0
  228. package/dist/workflows/v3/cli-run.js +265 -0
  229. package/dist/workflows/v3/cli-run.js.map +1 -0
  230. package/dist/workflows/v3/contract.d.ts +302 -0
  231. package/dist/workflows/v3/contract.d.ts.map +1 -0
  232. package/dist/workflows/v3/contract.js +91 -0
  233. package/dist/workflows/v3/contract.js.map +1 -0
  234. package/dist/workflows/v3/daemon-run.d.ts +311 -0
  235. package/dist/workflows/v3/daemon-run.d.ts.map +1 -0
  236. package/dist/workflows/v3/daemon-run.js +775 -0
  237. package/dist/workflows/v3/daemon-run.js.map +1 -0
  238. package/dist/workflows/v3/dag.d.ts +332 -0
  239. package/dist/workflows/v3/dag.d.ts.map +1 -0
  240. package/dist/workflows/v3/dag.js +1040 -0
  241. package/dist/workflows/v3/dag.js.map +1 -0
  242. package/dist/workflows/v3/ephemeral-pool.d.ts +29 -0
  243. package/dist/workflows/v3/ephemeral-pool.d.ts.map +1 -0
  244. package/dist/workflows/v3/ephemeral-pool.js +331 -0
  245. package/dist/workflows/v3/ephemeral-pool.js.map +1 -0
  246. package/dist/workflows/v3/grill-state.d.ts +92 -0
  247. package/dist/workflows/v3/grill-state.d.ts.map +1 -0
  248. package/dist/workflows/v3/grill-state.js +151 -0
  249. package/dist/workflows/v3/grill-state.js.map +1 -0
  250. package/dist/workflows/v3/host.d.ts +88 -0
  251. package/dist/workflows/v3/host.d.ts.map +1 -0
  252. package/dist/workflows/v3/host.js +349 -0
  253. package/dist/workflows/v3/host.js.map +1 -0
  254. package/dist/workflows/v3/human-gate.d.ts +85 -0
  255. package/dist/workflows/v3/human-gate.d.ts.map +1 -0
  256. package/dist/workflows/v3/human-gate.js +153 -0
  257. package/dist/workflows/v3/human-gate.js.map +1 -0
  258. package/dist/workflows/v3/journal.d.ts +207 -0
  259. package/dist/workflows/v3/journal.d.ts.map +1 -0
  260. package/dist/workflows/v3/journal.js +74 -0
  261. package/dist/workflows/v3/journal.js.map +1 -0
  262. package/dist/workflows/v3/manifest.d.ts +17 -0
  263. package/dist/workflows/v3/manifest.d.ts.map +1 -0
  264. package/dist/workflows/v3/manifest.js +266 -0
  265. package/dist/workflows/v3/manifest.js.map +1 -0
  266. package/dist/workflows/v3/ops-projection.d.ts +110 -0
  267. package/dist/workflows/v3/ops-projection.d.ts.map +1 -0
  268. package/dist/workflows/v3/ops-projection.js +0 -0
  269. package/dist/workflows/v3/ops-projection.js.map +1 -0
  270. package/dist/workflows/v3/orchestrator.d.ts +168 -0
  271. package/dist/workflows/v3/orchestrator.d.ts.map +1 -0
  272. package/dist/workflows/v3/orchestrator.js +417 -0
  273. package/dist/workflows/v3/orchestrator.js.map +1 -0
  274. package/dist/workflows/v3/runtime.d.ts +193 -0
  275. package/dist/workflows/v3/runtime.d.ts.map +1 -0
  276. package/dist/workflows/v3/runtime.js +1284 -0
  277. package/dist/workflows/v3/runtime.js.map +1 -0
  278. package/dist/workflows/v3/spec.d.ts +32 -0
  279. package/dist/workflows/v3/spec.d.ts.map +1 -0
  280. package/dist/workflows/v3/spec.js +173 -0
  281. package/dist/workflows/v3/spec.js.map +1 -0
  282. package/dist/workflows/v3/state.d.ts +74 -0
  283. package/dist/workflows/v3/state.d.ts.map +1 -0
  284. package/dist/workflows/v3/state.js +353 -0
  285. package/dist/workflows/v3/state.js.map +1 -0
  286. package/package.json +2 -2
  287. package/dist/dashboard/web/workflow-catalog.d.ts +0 -2
  288. package/dist/dashboard/web/workflow-catalog.d.ts.map +0 -1
  289. package/dist/dashboard/web/workflow-catalog.js +0 -323
  290. package/dist/dashboard/web/workflow-catalog.js.map +0 -1
Binary file
@@ -4,6 +4,8 @@
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
6
  <title>botmux dashboard</title>
7
+ <link rel="icon" type="image/png" href="/assets/favicon.png">
8
+ <link rel="apple-touch-icon" href="/assets/apple-touch-icon.png">
7
9
  <link rel="stylesheet" href="/assets/style.css">
8
10
  </head>
9
11
  <body>
@@ -22,7 +24,7 @@
22
24
  <a href="#/" data-route="overview"><svg viewBox="0 0 16 16" aria-hidden="true"><rect x="1.5" y="1.5" width="5.4" height="5.4" rx="1.5"/><rect x="9.1" y="1.5" width="5.4" height="5.4" rx="1.5"/><rect x="1.5" y="9.1" width="5.4" height="5.4" rx="1.5"/><rect x="9.1" y="9.1" width="5.4" height="5.4" rx="1.5"/></svg><span data-i18n="nav.overview">总览</span></a>
23
25
  <a href="#/sessions" data-route="sessions"><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M2 3.5h12v7H6l-3 3v-3H2z"/></svg><span data-i18n="nav.sessions">会话</span></a>
24
26
  <a href="#/insights" data-route="insights"><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M2 2v12h12M5 11V7M8.5 11V4.5M12 11V8.5"/></svg><span data-i18n="nav.insights">洞察</span></a>
25
- <a href="#/workflows" data-route="workflows"><svg viewBox="0 0 16 16" aria-hidden="true"><circle cx="3.4" cy="8" r="1.9"/><circle cx="12.6" cy="3.6" r="1.9"/><circle cx="12.6" cy="12.4" r="1.9"/><path d="M5.2 7.2l5.6-2.8M5.2 8.8l5.6 2.8"/></svg><span data-i18n="nav.workflows">工作流(beta)</span></a>
27
+ <a href="#/workflows" data-route="workflows"><svg viewBox="0 0 16 16" aria-hidden="true"><circle cx="3.4" cy="3.6" r="1.9"/><circle cx="3.4" cy="12.4" r="1.9"/><circle cx="12.6" cy="8" r="1.9"/><path d="M5.2 4.4l5.6 2.8M5.2 11.6l5.6-2.8"/></svg><span data-i18n="nav.workflows">工作流</span></a>
26
28
  <a href="#/groups" data-route="groups"><svg viewBox="0 0 16 16" aria-hidden="true"><circle cx="5.6" cy="5.8" r="2.4"/><circle cx="11" cy="6.8" r="1.9"/><path d="M1.8 13.2c.5-2.4 2-3.6 3.8-3.6s3.3 1.2 3.8 3.6M9.8 12.6c.4-1.7 1.5-2.6 2.8-2.6 1 0 1.9.5 2.4 1.6"/></svg><span data-i18n="nav.groups">群组</span></a>
27
29
  <a href="#/schedules" data-route="schedules"><svg viewBox="0 0 16 16" aria-hidden="true"><circle cx="8" cy="8" r="6.2"/><path d="M8 4.5V8l2.4 1.6"/></svg><span data-i18n="nav.schedules">定时</span></a>
28
30
  <a href="#/whiteboards" data-route="whiteboards"><svg viewBox="0 0 16 16" aria-hidden="true"><rect x="2.2" y="2.2" width="11.6" height="11.6" rx="2"/><path d="M4.8 5.2h6.4M4.8 8h6.4M4.8 10.8h4"/></svg><span>白板</span></a>
@@ -3121,6 +3121,17 @@ input.row-select,
3121
3121
  .wf-terminal-frame { display: block; width: 100%; height: 460px; border: 0; border-top: 1px solid var(--border); background: #0d1117; }
3122
3122
  .wf-error-msg { display: block; margin-top: 0.15rem; white-space: pre-wrap; overflow-wrap: anywhere; font-size: 12px; }
3123
3123
 
3124
+ /* v3 per-node terminal panel (live iframe / replay) */
3125
+ .v3-terminal-panel { border: 1px solid var(--border); border-radius: 6px; overflow: hidden; background: #0d1117; }
3126
+ .v3-terminal-head { display: flex; align-items: center; gap: 0.5rem; padding: 0.4rem 0.6rem; background: var(--surface); border-bottom: 1px solid var(--border); font-size: 12px; }
3127
+ .v3-terminal-head .muted { margin-left: 0; }
3128
+ .v3-terminal-head .btn-link { margin-left: auto; }
3129
+ .v3-terminal-dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; background: var(--muted); }
3130
+ .v3-terminal-dot.live { background: #1a73e8; box-shadow: 0 0 0 3px rgba(26,115,232,0.25); }
3131
+ .v3-terminal-dot.replay { background: #f9ab00; }
3132
+ .v3-terminal-dot.closed { background: #188038; }
3133
+ .v3-terminal-empty { padding: 1.5rem 0.75rem; text-align: center; border: 1px dashed var(--border); border-radius: 6px; }
3134
+
3124
3135
  /* Workflow catalog */
3125
3136
  .catalog-head, .catalog-detail-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 0.8rem; margin-bottom: 0.8rem; }
3126
3137
  .catalog-head h2, .catalog-detail-head h2 { margin: 0 0 0.15rem; font-size: 18px; line-height: 1.25; }
@@ -6919,6 +6930,218 @@ body.cp-fx-slice main { animation: cp-fx-slice 0.32s steps(2, end); }
6919
6930
  .skin-intro { display: none; }
6920
6931
  }
6921
6932
 
6933
+ /* ════════════════════════════════════════════════════════════════════════
6934
+ v3 runs page — "flight recorder" visual language (v3.ts).
6935
+ Status hues derive from theme tokens so light/dark/skins all inherit;
6936
+ gateWaiting gets its own violet (approval ≠ blocked). All motion is
6937
+ CSS-only and disabled under prefers-reduced-motion.
6938
+ (`.v3-terminal-*` above is the codex-owned terminal slot — untouched.)
6939
+ ════════════════════════════════════════════════════════════════════════ */
6940
+ :root {
6941
+ --v3r-pending: var(--faint);
6942
+ --v3r-running: var(--accent);
6943
+ --v3r-done: var(--success);
6944
+ --v3r-blocked: var(--warning);
6945
+ --v3r-failed: var(--danger);
6946
+ --v3r-gate: #7c5cd6;
6947
+ /* edge-activation: 分支未选中的中性终态 — 比 pending 更"收起",绝不能像
6948
+ 失败(它是 by-design 的非事件) */
6949
+ --v3r-skipped: var(--faint);
6950
+ }
6951
+ :root[data-theme="dark"] { --v3r-gate: #a78bfa; }
6952
+
6953
+ .v3r-title { display: inline-block; margin-left: .5rem; font-family: var(--mono); font-size: 1.18rem; letter-spacing: -0.01em; }
6954
+ .v3r-runid { font-family: var(--mono); font-size: 12.5px; }
6955
+
6956
+ /* status pill — shared by list rows, panel header, run badge */
6957
+ .v3r-pill {
6958
+ display: inline-flex; align-items: center; gap: .38em;
6959
+ padding: .14em .62em; border-radius: 999px;
6960
+ font-size: 12px; font-weight: 600; line-height: 1.5;
6961
+ font-variant-numeric: tabular-nums;
6962
+ background: color-mix(in srgb, var(--ns, var(--muted)) 14%, transparent);
6963
+ color: var(--ns, var(--muted));
6964
+ border: 1px solid color-mix(in srgb, var(--ns, var(--muted)) 32%, transparent);
6965
+ }
6966
+ .v3r-pill .dot, .v3r-inst .dot {
6967
+ width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto;
6968
+ background: var(--ns, var(--muted));
6969
+ }
6970
+ .st-pending { --ns: var(--v3r-pending); }
6971
+ .st-running { --ns: var(--v3r-running); }
6972
+ .st-gateWaiting { --ns: var(--v3r-gate); }
6973
+ .st-done { --ns: var(--v3r-done); }
6974
+ .st-skipped { --ns: var(--v3r-skipped); }
6975
+ .st-cancelled { --ns: var(--v3r-skipped); }
6976
+ .st-blocked { --ns: var(--v3r-blocked); }
6977
+ .st-failed { --ns: var(--v3r-failed); }
6978
+ /* skipped 整体降透明度 + 虚线盒 — 一眼读出"这条路没走",与 pending 区分 */
6979
+ .v3r-node.st-skipped, .v3r-node.st-cancelled { opacity: .55; }
6980
+ .v3r-node.st-skipped .v3r-box, .v3r-node.st-cancelled .v3r-box { stroke-dasharray: 4 3; }
6981
+ .rs-running { --ns: var(--v3r-running); }
6982
+ .rs-succeeded { --ns: var(--v3r-done); }
6983
+ .rs-blocked { --ns: var(--v3r-blocked); }
6984
+ .rs-failed { --ns: var(--v3r-failed); }
6985
+
6986
+ .v3r-wrap { display: flex; flex-direction: column; gap: 1rem; align-items: stretch; }
6987
+ .v3r-graph-card {
6988
+ border: 1px solid var(--border); border-radius: 10px;
6989
+ background:
6990
+ radial-gradient(circle, color-mix(in srgb, var(--fg) 7%, transparent) 1px, transparent 1px) 0 0 / 22px 22px,
6991
+ var(--surface);
6992
+ }
6993
+ .v3r-graph { max-height: 48vh; overflow: auto; }
6994
+ .v3r-legend {
6995
+ display: flex; flex-wrap: wrap; gap: .9rem; align-items: center;
6996
+ padding: .45rem .9rem; border-top: 1px solid var(--border-soft);
6997
+ font-size: 11.5px; color: var(--muted);
6998
+ }
6999
+ .v3r-legend .lg { display: inline-flex; align-items: center; gap: .35em; }
7000
+ .v3r-legend .lg::before {
7001
+ content: ""; width: 8px; height: 8px; border-radius: 50%;
7002
+ background: var(--ns, var(--muted));
7003
+ }
7004
+ .v3r-legend .lg-loop::before { content: none; }
7005
+ .v3r-legend .lg-loop { font-family: var(--mono); }
7006
+
7007
+ /* ── graph nodes (SVG) ── */
7008
+ .v3r-node { cursor: pointer; }
7009
+ .v3r-node .v3r-box {
7010
+ fill: color-mix(in srgb, var(--ns) 9%, var(--surface-raised));
7011
+ stroke: color-mix(in srgb, var(--ns) 52%, var(--border));
7012
+ stroke-width: 1.2;
7013
+ transition: fill .2s ease, stroke .2s ease;
7014
+ }
7015
+ .v3r-node:hover .v3r-box { fill: color-mix(in srgb, var(--ns) 16%, var(--surface-raised)); }
7016
+ .v3r-node.sel .v3r-box { stroke: var(--accent-strong); stroke-width: 2; }
7017
+ .v3r-node .v3r-bar { fill: var(--ns); }
7018
+ .v3r-node .v3r-nid {
7019
+ fill: var(--fg); font-family: var(--mono); font-size: 12px; font-weight: 600;
7020
+ }
7021
+ .v3r-node .v3r-nstatus {
7022
+ fill: var(--ns); font-size: 10.5px; font-weight: 600;
7023
+ font-variant-numeric: tabular-nums;
7024
+ }
7025
+ .v3r-node.st-running .v3r-box { animation: v3r-breathe 2.2s ease-in-out infinite; }
7026
+
7027
+ /* loop capsule: dashed outer frame = composite-container affordance */
7028
+ .v3r-loopnode .v3r-cap {
7029
+ fill: none; stroke: color-mix(in srgb, var(--ns) 45%, var(--border));
7030
+ stroke-width: 1.2; stroke-dasharray: 5 4;
7031
+ }
7032
+ .v3r-loopnode.sel .v3r-cap { stroke: var(--accent-strong); stroke-dasharray: none; }
7033
+ .v3r-spin {
7034
+ fill: var(--ns); font-size: 13px; font-weight: 700;
7035
+ transform-box: fill-box; transform-origin: center;
7036
+ }
7037
+ .st-running .v3r-spin { animation: v3r-spin 2.4s linear infinite; }
7038
+ .v3r-dot.done { fill: var(--ns); }
7039
+ .v3r-dot.cur { fill: var(--ns); stroke: color-mix(in srgb, var(--ns) 40%, transparent); stroke-width: 3; }
7040
+ .v3r-dot.cur.live { animation: v3r-breathe 1.4s ease-in-out infinite; }
7041
+ .v3r-dot.todo { fill: none; stroke: var(--faint); stroke-width: 1.2; }
7042
+ .v3r-dot.grant { stroke: var(--accent-strong); stroke-width: 1.6; }
7043
+ .v3r-dot.grant.done, .v3r-dot.grant.cur { fill: var(--accent-strong); }
7044
+ .v3r-dots-verdict { font-size: 11px; font-weight: 700; }
7045
+ .v3r-dots-verdict.ok { fill: var(--v3r-done); }
7046
+ .v3r-dots-verdict.warn { fill: var(--v3r-blocked); }
7047
+
7048
+ /* edges: quiet by default, marching-ants into live nodes */
7049
+ .v3r-edge { fill: none; stroke: color-mix(in srgb, var(--fg) 26%, transparent); stroke-width: 1.5; }
7050
+ .v3r-edge.live {
7051
+ stroke: var(--v3r-running); stroke-width: 1.8;
7052
+ stroke-dasharray: 6 5; animation: v3r-flow 0.9s linear infinite;
7053
+ }
7054
+
7055
+ /* ── node panel ── */
7056
+ .v3r-panel {
7057
+ border: 1px solid var(--border); border-radius: 10px;
7058
+ padding: 1rem; background: var(--surface);
7059
+ }
7060
+ .v3r-panel-head { display: flex; align-items: center; gap: .6rem; margin-bottom: .55rem; }
7061
+ .v3r-loop-mark { font-size: 17px; font-weight: 700; color: var(--accent); }
7062
+ .v3r-nodeid { font-family: var(--mono); font-size: 15px; font-weight: 700; letter-spacing: -0.01em; }
7063
+ .v3r-crumb { margin: .2rem 0 .55rem; font-size: 12.5px; color: var(--muted); display: flex; align-items: center; gap: .45em; }
7064
+ .v3r-crumb .sep { color: var(--faint); }
7065
+ .v3r-goal {
7066
+ margin: .45rem 0; padding: .5rem .7rem; font-size: 12.5px; color: var(--muted);
7067
+ border-left: 3px solid var(--border); background: var(--surface-muted);
7068
+ border-radius: 0 6px 6px 0; white-space: pre-wrap;
7069
+ }
7070
+ .v3r-deps { margin: .4rem 0; font-size: 12px; color: var(--muted); display: flex; align-items: center; gap: .4em; flex-wrap: wrap; }
7071
+ .v3r-dep {
7072
+ font-family: var(--mono); font-size: 11.5px; padding: .1em .5em;
7073
+ border: 1px solid var(--border); border-radius: 6px; background: var(--surface-muted);
7074
+ }
7075
+ .v3r-err { margin: .4rem 0; font-size: 12.5px; color: var(--v3r-blocked); }
7076
+ .v3r-term-slot { margin-top: .75rem; }
7077
+
7078
+ /* ── loop iteration timeline ── */
7079
+ .v3r-loop-sec { margin-top: .65rem; }
7080
+ .v3r-loop-meter {
7081
+ display: flex; align-items: center; gap: .7rem; margin-bottom: .55rem;
7082
+ font-size: 13px; color: var(--muted);
7083
+ }
7084
+ .v3r-loop-meter .num { font-variant-numeric: tabular-nums; }
7085
+ .v3r-loop-meter .num b { font-family: var(--mono); font-size: 15px; color: var(--fg); }
7086
+ .v3r-granted-tag {
7087
+ font-size: 11px; font-weight: 700; padding: .1em .5em; border-radius: 999px;
7088
+ color: var(--accent-strong);
7089
+ background: color-mix(in srgb, var(--accent) 14%, transparent);
7090
+ border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
7091
+ }
7092
+ .v3r-rounds {
7093
+ display: flex; flex-direction: column; gap: .45rem;
7094
+ max-height: 380px; overflow-y: auto; padding: .15rem;
7095
+ }
7096
+ .v3r-round {
7097
+ border: 1px solid var(--border); border-radius: 9px;
7098
+ padding: .45rem .6rem; background: var(--surface-muted);
7099
+ }
7100
+ .v3r-round.cur { border-color: color-mix(in srgb, var(--accent) 55%, var(--border)); box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 18%, transparent); }
7101
+ .v3r-round.vd-exit { border-color: color-mix(in srgb, var(--v3r-done) 50%, var(--border)); }
7102
+ .v3r-round.vd-exhausted { border-color: color-mix(in srgb, var(--v3r-blocked) 55%, var(--border)); }
7103
+ .v3r-round-head { display: flex; align-items: center; gap: .45em; margin-bottom: .15rem; }
7104
+ .v3r-round-head .rn { font-family: var(--mono); font-size: 12.5px; font-weight: 700; color: var(--fg); }
7105
+ .v3r-verdict { font-size: 11px; font-weight: 700; white-space: nowrap; }
7106
+ .v3r-verdict.vd-exit { color: var(--v3r-done); }
7107
+ .v3r-verdict.vd-continue { color: var(--muted); }
7108
+ .v3r-verdict.vd-exhausted { color: var(--v3r-blocked); }
7109
+ .v3r-verdict.vd-live { color: var(--v3r-running); animation: v3r-blink 1.6s ease-in-out infinite; }
7110
+ /* round mini-dag: body instances drawn like real graph nodes (template gives
7111
+ the shape; undispatched slots are dashed ghosts) */
7112
+ .v3r-mini-svg { display: block; }
7113
+ .v3r-mini .v3r-nid { font-size: 11px; }
7114
+ .v3r-mini .v3r-mini-dot { fill: var(--ns); }
7115
+ .v3r-mini.st-running .v3r-mini-dot { animation: v3r-breathe 1.4s ease-in-out infinite; }
7116
+ .v3r-mini.sel .v3r-box { stroke: var(--accent-strong); stroke-width: 2; }
7117
+ .v3r-mini.ghost .v3r-box { stroke-dasharray: 3 3; fill: transparent; }
7118
+ .v3r-mini.ghost .v3r-nid, .v3r-mini.ghost .v3r-mini-dot { opacity: .55; }
7119
+ /* the strip naming which instance's terminal is mounted below the timeline */
7120
+ .v3r-inst-strip {
7121
+ display: flex; align-items: center; gap: .55rem; flex-wrap: wrap;
7122
+ margin-top: .7rem; padding: .45rem .65rem;
7123
+ border: 1px solid var(--border-soft); border-radius: 8px 8px 0 0; border-bottom: none;
7124
+ background: var(--surface-muted); font-size: 12px; color: var(--muted);
7125
+ }
7126
+ .v3r-inst-strip .lbl { font-weight: 700; color: var(--faint); letter-spacing: .04em; }
7127
+ .v3r-nodeid.sm { font-size: 12.5px; }
7128
+ #v3-inst-strip:not(:empty) + .v3r-term-slot { margin-top: 0; }
7129
+ .v3r-cta {
7130
+ margin-top: .55rem; padding: .5rem .7rem; font-size: 12.5px;
7131
+ color: var(--v3r-blocked); border-radius: 8px;
7132
+ background: color-mix(in srgb, var(--v3r-blocked) 10%, transparent);
7133
+ border: 1px solid color-mix(in srgb, var(--v3r-blocked) 35%, transparent);
7134
+ }
7135
+ .v3r-cta code { font-family: var(--mono); }
7136
+
7137
+ @keyframes v3r-spin { to { transform: rotate(360deg); } }
7138
+ @keyframes v3r-flow { to { stroke-dashoffset: -11; } }
7139
+ @keyframes v3r-breathe { 0%, 100% { opacity: 1; } 50% { opacity: .55; } }
7140
+ @keyframes v3r-blink { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }
7141
+ @media (prefers-reduced-motion: reduce) {
7142
+ .st-running .v3r-spin, .v3r-edge.live, .v3r-node.st-running .v3r-box,
7143
+ .v3r-dot.cur.live, .v3r-verdict.vd-live { animation: none; }
7144
+ }
6922
7145
  /* ── Insight workbench (owner-only #/insights page) ───────────────────────────
6923
7146
  Visual layer ported from the reference app (agent-session-insights, html.ts
6924
7147
  STYLE) so this page reads as that read-only "工作台", not generic dashboard
package/dist/dashboard.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/dashboard.ts
2
- import { createServer, get as httpGet } from 'node:http';
3
- import { createServer as createTcpServer } from 'node:net';
2
+ import { createServer, get as httpGet, request as httpRequest } from 'node:http';
3
+ import { createServer as createTcpServer, connect as netConnect } from 'node:net';
4
4
  import { readFileSync, existsSync, mkdirSync, statSync, createReadStream, } from 'node:fs';
5
5
  import { atomicWriteFileSync } from './utils/atomic-write.js';
6
6
  import { join, dirname, extname, resolve, relative, isAbsolute } from 'node:path';
@@ -16,6 +16,8 @@ import { Aggregator, subscribeDaemon } from './dashboard/aggregator.js';
16
16
  import { pickCreatorForGroup } from './dashboard/operator-selector.js';
17
17
  import { planGroupCreator } from './dashboard/team-group.js';
18
18
  import { handleWorkflowApi, jsonRes } from './dashboard/workflow-api.js';
19
+ import { handleV3RunsApi } from './dashboard/v3-runs-api.js';
20
+ import { defaultRunsDir as v3RunsDir } from './workflows/v3/ops-projection.js';
19
21
  import { handleDashboardTriggerApi } from './dashboard/trigger-api.js';
20
22
  import { handleConnectorApi } from './dashboard/connector-api.js';
21
23
  import { redactGroupsForPublic, redactSchedulesForPublic } from './dashboard/public-redact.js';
@@ -176,6 +178,7 @@ function resolveDashboardSettings() {
176
178
  maintenance: global.maintenance ?? {},
177
179
  localDevInstall: isLocalDevInstall(),
178
180
  whiteboard: { enabled: global.whiteboard?.enabled === true },
181
+ remoteAccess: global.remoteAccess === true,
179
182
  };
180
183
  }
181
184
  // Single shared deps object for `applySettingsWrite` — both the browser
@@ -831,6 +834,15 @@ async function dashboardSkillReferences(skillName) {
831
834
  bots: [...refs.values()].sort((a, b) => a.botName.localeCompare(b.botName)),
832
835
  };
833
836
  }
837
+ /** Extract the sessionId from a terminal path `/s/<sessionId>[/...]`. Returns
838
+ * the first path segment after `/s/` (stops at the next `/`; query/hash are
839
+ * already stripped by URL.pathname). undefined when there's no segment. */
840
+ function parseTerminalSessionId(pathname) {
841
+ if (!pathname.startsWith('/s/'))
842
+ return undefined;
843
+ const seg = pathname.slice(3).split('/')[0];
844
+ return seg || undefined;
845
+ }
834
846
  const server = createServer(async (req, res) => {
835
847
  try {
836
848
  const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);
@@ -845,6 +857,35 @@ const server = createServer(async (req, res) => {
845
857
  res.writeHead(200, { 'content-type': 'text/plain' });
846
858
  return res.end(DASHBOARD_SELF_NONCE);
847
859
  }
860
+ // Web terminal reverse-proxy: `/s/<sessionId>/*` → the owning bot daemon's
861
+ // terminal proxy. The central platform only tunnels the dashboard port, so
862
+ // terminal links served under the machine subdomain
863
+ // (`https://m-<id>.<host>/s/<sessionId>`) land here. The dashboard is the
864
+ // aggregator process (it fronts many bot daemons, each with its own terminal
865
+ // proxy on proxyBasePort+idx), so we resolve the session's owning daemon's
866
+ // proxy port from the aggregator rows and forward there, streaming the
867
+ // response straight back. Mounted before the dashboard auth gate because the
868
+ // terminal proxy / worker enforces its own write gate (read-only without
869
+ // token / platform role).
870
+ if (url.pathname === '/s' || url.pathname.startsWith('/s/')) {
871
+ const sessionId = parseTerminalSessionId(url.pathname);
872
+ const tport = sessionId ? aggregator.terminalProxyPortOf(sessionId) : undefined;
873
+ if (!tport) {
874
+ res.writeHead(404, { 'content-type': 'text/plain; charset=utf-8' });
875
+ return res.end('session terminal not available');
876
+ }
877
+ const upstream = httpRequest({ host: '127.0.0.1', port: tport, method: req.method, path: req.url, headers: req.headers }, (up) => {
878
+ res.writeHead(up.statusCode ?? 502, up.headers);
879
+ up.pipe(res);
880
+ });
881
+ upstream.on('error', () => {
882
+ if (!res.headersSent)
883
+ res.writeHead(502, { 'content-type': 'text/plain; charset=utf-8' });
884
+ res.end('terminal proxy error');
885
+ });
886
+ req.pipe(upstream);
887
+ return;
888
+ }
848
889
  if (await handleWebhookRoute(req, res, url, {
849
890
  proxyToDaemon,
850
891
  createLifecycleGroup: createLifecycleGroupForWebhook,
@@ -1089,7 +1130,14 @@ const server = createServer(async (req, res) => {
1089
1130
  // `lang` is the global UI locale (single source of truth shared with
1090
1131
  // `botmux lang` and the Feishu cards) — the web UI reads it as its
1091
1132
  // authoritative initial language when set.
1092
- return jsonRes(res, 200, { settings: dashboardSettings, lang: readGlobalConfig().lang ?? null, authed });
1133
+ // `bound` reflects central-platform binding; the Settings UI only shows the
1134
+ // 远程访问 toggle when bound (the central URLs are meaningless otherwise).
1135
+ return jsonRes(res, 200, {
1136
+ settings: dashboardSettings,
1137
+ lang: readGlobalConfig().lang ?? null,
1138
+ authed,
1139
+ bound: readPlatformBinding() !== null,
1140
+ });
1093
1141
  }
1094
1142
  if (req.method === 'PUT' && url.pathname === '/api/settings') {
1095
1143
  let parsed;
@@ -1593,6 +1641,11 @@ const server = createServer(async (req, res) => {
1593
1641
  }, authed)) {
1594
1642
  return;
1595
1643
  }
1644
+ // v3 workflow runs (read-only DAG + per-node terminal projection). Reads
1645
+ // the v3 run dirs directly; no daemon proxy (v3 runs are plain files).
1646
+ if (await handleV3RunsApi(req, res, url, { runsDir: v3RunsDir() }, authed)) {
1647
+ return;
1648
+ }
1596
1649
  // ─── Groups (Phase B) ────────────────────────────────────────────────────
1597
1650
  if (req.method === 'GET' && url.pathname === '/api/groups') {
1598
1651
  // Fan out via the shared `buildGroupsMatrix` helper so the browser
@@ -1874,6 +1927,25 @@ const server = createServer(async (req, res) => {
1874
1927
  res.end(await upstream.text());
1875
1928
  return;
1876
1929
  }
1930
+ // PUT /api/bots/:appId/working-dir-mode — proxy to that bot's daemon. Body
1931
+ // `{ mode: 'off'|'default'|'oncall', workingDir }` — sets the 3-way
1932
+ // mutually-exclusive default-dir mode (defaultWorkingDir vs defaultOncall).
1933
+ let mBotWdMode;
1934
+ if (req.method === 'PUT' && (mBotWdMode = url.pathname.match(/^\/api\/bots\/([^/]+)\/working-dir-mode$/))) {
1935
+ const appId = decodeURIComponent(mBotWdMode[1]);
1936
+ const chunks = [];
1937
+ for await (const c of req)
1938
+ chunks.push(c);
1939
+ const raw = Buffer.concat(chunks).toString('utf8') || '{}';
1940
+ const upstream = await proxyToDaemon(appId, `/api/bot-working-dir-mode`, {
1941
+ method: 'PUT',
1942
+ headers: { 'content-type': 'application/json' },
1943
+ body: raw,
1944
+ });
1945
+ res.writeHead(upstream.status, { 'content-type': 'application/json' });
1946
+ res.end(await upstream.text());
1947
+ return;
1948
+ }
1877
1949
  // PUT /api/bots/:appId/skills — proxy to that bot's daemon. Body accepts
1878
1950
  // `{ action:'attach'|'detach', name }` or `{ action:'set', policy|null }`.
1879
1951
  let mBotSkills;
@@ -1928,6 +2000,24 @@ const server = createServer(async (req, res) => {
1928
2000
  res.end(await upstream.text());
1929
2001
  return;
1930
2002
  }
2003
+ // PUT /api/bots/:appId/launch-shell — proxy to that bot's daemon. Body
2004
+ // `{ launchShell: string }` (shell name or absolute path; '' = clear → $SHELL).
2005
+ let mBotLaunchShell;
2006
+ if (req.method === 'PUT' && (mBotLaunchShell = url.pathname.match(/^\/api\/bots\/([^/]+)\/launch-shell$/))) {
2007
+ const appId = decodeURIComponent(mBotLaunchShell[1]);
2008
+ const chunks = [];
2009
+ for await (const c of req)
2010
+ chunks.push(c);
2011
+ const raw = Buffer.concat(chunks).toString('utf8') || '{}';
2012
+ const upstream = await proxyToDaemon(appId, `/api/bot-launch-shell`, {
2013
+ method: 'PUT',
2014
+ headers: { 'content-type': 'application/json' },
2015
+ body: raw,
2016
+ });
2017
+ res.writeHead(upstream.status, { 'content-type': 'application/json' });
2018
+ res.end(await upstream.text());
2019
+ return;
2020
+ }
1931
2021
  // PUT /api/bots/:appId/env — proxy to that bot's daemon. Body
1932
2022
  // `{ env: string }` (raw JSON text; '' = clear).
1933
2023
  let mBotEnv;
@@ -2300,6 +2390,59 @@ const server = createServer(async (req, res) => {
2300
2390
  jsonRes(res, 500, { error: String(err) });
2301
2391
  }
2302
2392
  });
2393
+ // Web terminal WebSocket reverse-proxy: bridge `/s/*` upgrade requests through to
2394
+ // the local terminal proxy (which in turn bridges to the session worker). Raw
2395
+ // socket-to-socket bridge: dial 127.0.0.1:<terminalProxyPort>, replay the upgrade
2396
+ // request line + headers verbatim, then pipe both directions. Mirrors how
2397
+ // terminal-proxy.ts bridges to the worker. Non-`/s/*` upgrades are dropped (the
2398
+ // dashboard SPA uses SSE, not WebSocket).
2399
+ server.on('upgrade', (req, clientSocket, head) => {
2400
+ try {
2401
+ const rawUrl = req.url ?? '/';
2402
+ if (!(rawUrl === '/s' || rawUrl.startsWith('/s/') || rawUrl.startsWith('/s?'))) {
2403
+ return clientSocket.destroy();
2404
+ }
2405
+ // Strip query/hash before extracting the sessionId path segment.
2406
+ const pathname = rawUrl.split(/[?#]/)[0];
2407
+ const sessionId = parseTerminalSessionId(pathname);
2408
+ const tport = sessionId ? aggregator.terminalProxyPortOf(sessionId) : undefined;
2409
+ if (!tport)
2410
+ return clientSocket.destroy();
2411
+ const upstream = netConnect(tport, '127.0.0.1', () => {
2412
+ // rawHeaders is a flat [k, v, k, v, ...] list — preserves casing/duplicates.
2413
+ const lines = [`${req.method} ${req.url} HTTP/1.1`];
2414
+ const rh = req.rawHeaders;
2415
+ for (let i = 0; i + 1 < rh.length; i += 2)
2416
+ lines.push(`${rh[i]}: ${rh[i + 1]}`);
2417
+ lines.push('', '');
2418
+ upstream.write(lines.join('\r\n'));
2419
+ if (head?.length)
2420
+ upstream.write(head);
2421
+ upstream.pipe(clientSocket);
2422
+ clientSocket.pipe(upstream);
2423
+ });
2424
+ const cleanup = () => {
2425
+ try {
2426
+ upstream.destroy();
2427
+ }
2428
+ catch { /* ignore */ }
2429
+ try {
2430
+ clientSocket.destroy();
2431
+ }
2432
+ catch { /* ignore */ }
2433
+ };
2434
+ upstream.on('error', cleanup);
2435
+ clientSocket.on('error', cleanup);
2436
+ upstream.on('close', () => clientSocket.destroy());
2437
+ clientSocket.on('close', () => upstream.destroy());
2438
+ }
2439
+ catch {
2440
+ try {
2441
+ clientSocket.destroy();
2442
+ }
2443
+ catch { /* ignore */ }
2444
+ }
2445
+ });
2303
2446
  // Probe upward on EADDRINUSE rather than crashing with an unhandled 'error':
2304
2447
  // a second botmux instance on this host (or a stray process) holding the
2305
2448
  // configured port would otherwise tear the dashboard process down on bind.
@@ -2389,14 +2532,31 @@ function readPlatformBotsInfo() {
2389
2532
  const entries = JSON.parse(readFileSync(fp, 'utf8'));
2390
2533
  if (!Array.isArray(entries))
2391
2534
  return [];
2535
+ // Merge per-bot team-visibility config (showInTeam) from bots.json by
2536
+ // larkAppId so the platform team page can hide bots. Default: showInTeam =
2537
+ // true (shown). bots.json may be unreadable from the dashboard process →
2538
+ // fall back to the default.
2539
+ const cfgByAppId = new Map();
2540
+ try {
2541
+ for (const cfg of loadBotConfigs()) {
2542
+ cfgByAppId.set(cfg.larkAppId, { showInTeam: cfg.showInTeam });
2543
+ }
2544
+ }
2545
+ catch {
2546
+ /* defaults below */
2547
+ }
2392
2548
  return entries
2393
- .map((e) => ({
2394
- appId: e.larkAppId || '',
2395
- openId: e.botOpenId ?? null,
2396
- name: e.botName || e.larkAppId || 'bot',
2397
- avatar: e.botAvatarUrl || undefined,
2398
- cli: e.cliId,
2399
- }))
2549
+ .map((e) => {
2550
+ const cfg = cfgByAppId.get(e.larkAppId || '');
2551
+ return {
2552
+ appId: e.larkAppId || '',
2553
+ openId: e.botOpenId ?? null,
2554
+ name: e.botName || e.larkAppId || 'bot',
2555
+ avatar: e.botAvatarUrl || undefined,
2556
+ cli: e.cliId,
2557
+ showInTeam: cfg?.showInTeam !== false, // default true
2558
+ };
2559
+ })
2400
2560
  .filter((b) => b.appId);
2401
2561
  }
2402
2562
  catch {