botmux 3.16.1 → 3.17.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 (595) hide show
  1. package/dist/.runtime-build-id +1 -1
  2. package/dist/adapters/backend/riff-backend.d.ts +84 -1
  3. package/dist/adapters/backend/riff-backend.d.ts.map +1 -1
  4. package/dist/adapters/backend/riff-backend.js +202 -19
  5. package/dist/adapters/backend/riff-backend.js.map +1 -1
  6. package/dist/adapters/backend/sandbox.d.ts +8 -0
  7. package/dist/adapters/backend/sandbox.d.ts.map +1 -1
  8. package/dist/adapters/backend/sandbox.js +15 -0
  9. package/dist/adapters/backend/sandbox.js.map +1 -1
  10. package/dist/adapters/backend/tmux-backend.d.ts +61 -7
  11. package/dist/adapters/backend/tmux-backend.d.ts.map +1 -1
  12. package/dist/adapters/backend/tmux-backend.js +80 -9
  13. package/dist/adapters/backend/tmux-backend.js.map +1 -1
  14. package/dist/adapters/backend/tmux-pipe-backend.d.ts +62 -4
  15. package/dist/adapters/backend/tmux-pipe-backend.d.ts.map +1 -1
  16. package/dist/adapters/backend/tmux-pipe-backend.js +240 -27
  17. package/dist/adapters/backend/tmux-pipe-backend.js.map +1 -1
  18. package/dist/adapters/cli/claude-code.d.ts.map +1 -1
  19. package/dist/adapters/cli/claude-code.js +4 -2
  20. package/dist/adapters/cli/claude-code.js.map +1 -1
  21. package/dist/adapters/cli/coco.d.ts.map +1 -1
  22. package/dist/adapters/cli/coco.js +27 -0
  23. package/dist/adapters/cli/coco.js.map +1 -1
  24. package/dist/adapters/cli/codex-app.d.ts.map +1 -1
  25. package/dist/adapters/cli/codex-app.js +28 -0
  26. package/dist/adapters/cli/codex-app.js.map +1 -1
  27. package/dist/adapters/cli/codex.d.ts.map +1 -1
  28. package/dist/adapters/cli/codex.js +51 -23
  29. package/dist/adapters/cli/codex.js.map +1 -1
  30. package/dist/adapters/cli/dsh-tui.d.ts +24 -0
  31. package/dist/adapters/cli/dsh-tui.d.ts.map +1 -0
  32. package/dist/adapters/cli/dsh-tui.js +99 -0
  33. package/dist/adapters/cli/dsh-tui.js.map +1 -0
  34. package/dist/adapters/cli/dsh.d.ts.map +1 -1
  35. package/dist/adapters/cli/dsh.js +15 -9
  36. package/dist/adapters/cli/dsh.js.map +1 -1
  37. package/dist/adapters/cli/fs-policy.d.ts +98 -1
  38. package/dist/adapters/cli/fs-policy.d.ts.map +1 -1
  39. package/dist/adapters/cli/fs-policy.js +182 -7
  40. package/dist/adapters/cli/fs-policy.js.map +1 -1
  41. package/dist/adapters/cli/grok.js +16 -18
  42. package/dist/adapters/cli/grok.js.map +1 -1
  43. package/dist/adapters/cli/model-catalog-json.d.ts +17 -0
  44. package/dist/adapters/cli/model-catalog-json.d.ts.map +1 -0
  45. package/dist/adapters/cli/model-catalog-json.js +43 -0
  46. package/dist/adapters/cli/model-catalog-json.js.map +1 -0
  47. package/dist/adapters/cli/oh-my-pi.d.ts +2 -0
  48. package/dist/adapters/cli/oh-my-pi.d.ts.map +1 -1
  49. package/dist/adapters/cli/oh-my-pi.js +45 -13
  50. package/dist/adapters/cli/oh-my-pi.js.map +1 -1
  51. package/dist/adapters/cli/opencode.js +1 -1
  52. package/dist/adapters/cli/opencode.js.map +1 -1
  53. package/dist/adapters/cli/registry.d.ts +2 -1
  54. package/dist/adapters/cli/registry.d.ts.map +1 -1
  55. package/dist/adapters/cli/registry.js +6 -1
  56. package/dist/adapters/cli/registry.js.map +1 -1
  57. package/dist/adapters/cli/relay.d.ts.map +1 -1
  58. package/dist/adapters/cli/relay.js +7 -0
  59. package/dist/adapters/cli/relay.js.map +1 -1
  60. package/dist/adapters/cli/seed.d.ts.map +1 -1
  61. package/dist/adapters/cli/seed.js +6 -0
  62. package/dist/adapters/cli/seed.js.map +1 -1
  63. package/dist/adapters/cli/shared-hints.d.ts.map +1 -1
  64. package/dist/adapters/cli/shared-hints.js +20 -14
  65. package/dist/adapters/cli/shared-hints.js.map +1 -1
  66. package/dist/adapters/cli/traex.d.ts +4 -0
  67. package/dist/adapters/cli/traex.d.ts.map +1 -1
  68. package/dist/adapters/cli/traex.js +153 -0
  69. package/dist/adapters/cli/traex.js.map +1 -1
  70. package/dist/adapters/cli/types.d.ts +42 -1
  71. package/dist/adapters/cli/types.d.ts.map +1 -1
  72. package/dist/autostart.d.ts +6 -0
  73. package/dist/autostart.d.ts.map +1 -1
  74. package/dist/autostart.js +21 -1
  75. package/dist/autostart.js.map +1 -1
  76. package/dist/bot-registry.d.ts +27 -0
  77. package/dist/bot-registry.d.ts.map +1 -1
  78. package/dist/bot-registry.js +62 -2
  79. package/dist/bot-registry.js.map +1 -1
  80. package/dist/cli/vc-agent.d.ts.map +1 -1
  81. package/dist/cli/vc-agent.js +21 -3
  82. package/dist/cli/vc-agent.js.map +1 -1
  83. package/dist/cli.d.ts.map +1 -1
  84. package/dist/cli.js +92 -1
  85. package/dist/cli.js.map +1 -1
  86. package/dist/codex-app-runner.js +28 -0
  87. package/dist/codex-app-runner.js.map +1 -1
  88. package/dist/core/bot-steer-directive.d.ts +25 -0
  89. package/dist/core/bot-steer-directive.d.ts.map +1 -0
  90. package/dist/core/bot-steer-directive.js +47 -0
  91. package/dist/core/bot-steer-directive.js.map +1 -0
  92. package/dist/core/command-handler.d.ts.map +1 -1
  93. package/dist/core/command-handler.js +102 -18
  94. package/dist/core/command-handler.js.map +1 -1
  95. package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
  96. package/dist/core/dashboard-ipc-server.js +53 -13
  97. package/dist/core/dashboard-ipc-server.js.map +1 -1
  98. package/dist/core/dashboard-rows.d.ts +3 -0
  99. package/dist/core/dashboard-rows.d.ts.map +1 -1
  100. package/dist/core/dashboard-rows.js +26 -5
  101. package/dist/core/dashboard-rows.js.map +1 -1
  102. package/dist/core/dispatch.d.ts.map +1 -1
  103. package/dist/core/dispatch.js +5 -4
  104. package/dist/core/dispatch.js.map +1 -1
  105. package/dist/core/existing-app-server.d.ts +22 -0
  106. package/dist/core/existing-app-server.d.ts.map +1 -0
  107. package/dist/core/existing-app-server.js +85 -0
  108. package/dist/core/existing-app-server.js.map +1 -0
  109. package/dist/core/native-topic-id.d.ts +9 -0
  110. package/dist/core/native-topic-id.d.ts.map +1 -0
  111. package/dist/core/native-topic-id.js +14 -0
  112. package/dist/core/native-topic-id.js.map +1 -0
  113. package/dist/core/per-bot-env.d.ts.map +1 -1
  114. package/dist/core/per-bot-env.js +10 -0
  115. package/dist/core/per-bot-env.js.map +1 -1
  116. package/dist/core/session-activity.d.ts +11 -0
  117. package/dist/core/session-activity.d.ts.map +1 -1
  118. package/dist/core/session-activity.js +23 -0
  119. package/dist/core/session-activity.js.map +1 -1
  120. package/dist/core/session-group-birth.d.ts.map +1 -1
  121. package/dist/core/session-group-birth.js +23 -8
  122. package/dist/core/session-group-birth.js.map +1 -1
  123. package/dist/core/session-manager.d.ts +1 -4
  124. package/dist/core/session-manager.d.ts.map +1 -1
  125. package/dist/core/session-manager.js +16 -20
  126. package/dist/core/session-manager.js.map +1 -1
  127. package/dist/core/shared-adopt.d.ts +20 -0
  128. package/dist/core/shared-adopt.d.ts.map +1 -0
  129. package/dist/core/shared-adopt.js +26 -0
  130. package/dist/core/shared-adopt.js.map +1 -0
  131. package/dist/core/skills/customize-command.d.ts +7 -0
  132. package/dist/core/skills/customize-command.d.ts.map +1 -0
  133. package/dist/core/skills/customize-command.js +303 -0
  134. package/dist/core/skills/customize-command.js.map +1 -0
  135. package/dist/core/terminal-write-auth.d.ts +15 -0
  136. package/dist/core/terminal-write-auth.d.ts.map +1 -1
  137. package/dist/core/terminal-write-auth.js +15 -0
  138. package/dist/core/terminal-write-auth.js.map +1 -1
  139. package/dist/core/terminal-write-frame.d.ts +30 -0
  140. package/dist/core/terminal-write-frame.d.ts.map +1 -0
  141. package/dist/core/terminal-write-frame.js +44 -0
  142. package/dist/core/terminal-write-frame.js.map +1 -0
  143. package/dist/core/types.d.ts +14 -4
  144. package/dist/core/types.d.ts.map +1 -1
  145. package/dist/core/types.js +6 -8
  146. package/dist/core/types.js.map +1 -1
  147. package/dist/core/worker-pool.d.ts +21 -4
  148. package/dist/core/worker-pool.d.ts.map +1 -1
  149. package/dist/core/worker-pool.js +415 -100
  150. package/dist/core/worker-pool.js.map +1 -1
  151. package/dist/core/worker-startup-retry.d.ts +52 -0
  152. package/dist/core/worker-startup-retry.d.ts.map +1 -0
  153. package/dist/core/worker-startup-retry.js +68 -0
  154. package/dist/core/worker-startup-retry.js.map +1 -0
  155. package/dist/daemon.d.ts +9 -8
  156. package/dist/daemon.d.ts.map +1 -1
  157. package/dist/daemon.js +467 -171
  158. package/dist/daemon.js.map +1 -1
  159. package/dist/dashboard/autostart-api.d.ts +26 -0
  160. package/dist/dashboard/autostart-api.d.ts.map +1 -0
  161. package/dist/dashboard/autostart-api.js +88 -0
  162. package/dist/dashboard/autostart-api.js.map +1 -0
  163. package/dist/dashboard/bot-payload.d.ts +5 -0
  164. package/dist/dashboard/bot-payload.d.ts.map +1 -1
  165. package/dist/dashboard/bot-payload.js +2 -0
  166. package/dist/dashboard/bot-payload.js.map +1 -1
  167. package/dist/dashboard/control-csrf.d.ts +29 -1
  168. package/dist/dashboard/control-csrf.d.ts.map +1 -1
  169. package/dist/dashboard/control-csrf.js +45 -11
  170. package/dist/dashboard/control-csrf.js.map +1 -1
  171. package/dist/dashboard/customization-api.d.ts +23 -0
  172. package/dist/dashboard/customization-api.d.ts.map +1 -0
  173. package/dist/dashboard/customization-api.js +198 -0
  174. package/dist/dashboard/customization-api.js.map +1 -0
  175. package/dist/dashboard/debug-terminal.d.ts +13 -1
  176. package/dist/dashboard/debug-terminal.d.ts.map +1 -1
  177. package/dist/dashboard/debug-terminal.js +18 -4
  178. package/dist/dashboard/debug-terminal.js.map +1 -1
  179. package/dist/dashboard/settings-write-applier.d.ts +0 -23
  180. package/dist/dashboard/settings-write-applier.d.ts.map +1 -1
  181. package/dist/dashboard/settings-write-applier.js +7 -28
  182. package/dist/dashboard/settings-write-applier.js.map +1 -1
  183. package/dist/dashboard/terminal-control-route.d.ts +32 -0
  184. package/dist/dashboard/terminal-control-route.d.ts.map +1 -0
  185. package/dist/dashboard/terminal-control-route.js +70 -0
  186. package/dist/dashboard/terminal-control-route.js.map +1 -0
  187. package/dist/dashboard/terminal-control.d.ts +64 -13
  188. package/dist/dashboard/terminal-control.d.ts.map +1 -1
  189. package/dist/dashboard/terminal-control.js +77 -14
  190. package/dist/dashboard/terminal-control.js.map +1 -1
  191. package/dist/dashboard/terminal-front-proxy.d.ts.map +1 -1
  192. package/dist/dashboard/terminal-front-proxy.js +22 -8
  193. package/dist/dashboard/terminal-front-proxy.js.map +1 -1
  194. package/dist/dashboard/vc-consumer-profiles-api.d.ts +69 -18
  195. package/dist/dashboard/vc-consumer-profiles-api.d.ts.map +1 -1
  196. package/dist/dashboard/vc-consumer-profiles-api.js +149 -29
  197. package/dist/dashboard/vc-consumer-profiles-api.js.map +1 -1
  198. package/dist/dashboard/web/agent-workbench-api.d.ts +34 -2
  199. package/dist/dashboard/web/agent-workbench-api.d.ts.map +1 -1
  200. package/dist/dashboard/web/agent-workbench-api.js +39 -4
  201. package/dist/dashboard/web/agent-workbench-api.js.map +1 -1
  202. package/dist/dashboard/web/agent-workbench-appearance.d.ts +6 -3
  203. package/dist/dashboard/web/agent-workbench-appearance.d.ts.map +1 -1
  204. package/dist/dashboard/web/agent-workbench-appearance.js +7 -4
  205. package/dist/dashboard/web/agent-workbench-appearance.js.map +1 -1
  206. package/dist/dashboard/web/agent-workbench-model.d.ts +2 -0
  207. package/dist/dashboard/web/agent-workbench-model.d.ts.map +1 -1
  208. package/dist/dashboard/web/agent-workbench-model.js.map +1 -1
  209. package/dist/dashboard/web/agent-workbench-page.js +2 -2
  210. package/dist/dashboard/web/agent-workbench-page.js.map +1 -1
  211. package/dist/dashboard/web/agent-workbench-panes.d.ts +59 -4
  212. package/dist/dashboard/web/agent-workbench-panes.d.ts.map +1 -1
  213. package/dist/dashboard/web/agent-workbench-panes.js +493 -85
  214. package/dist/dashboard/web/agent-workbench-panes.js.map +1 -1
  215. package/dist/dashboard/web/agent-workbench-session-list.d.ts +11 -7
  216. package/dist/dashboard/web/agent-workbench-session-list.d.ts.map +1 -1
  217. package/dist/dashboard/web/agent-workbench-session-list.js +24 -31
  218. package/dist/dashboard/web/agent-workbench-session-list.js.map +1 -1
  219. package/dist/dashboard/web/agent-workbench-terminal-control.d.ts +198 -0
  220. package/dist/dashboard/web/agent-workbench-terminal-control.d.ts.map +1 -0
  221. package/dist/dashboard/web/agent-workbench-terminal-control.js +292 -0
  222. package/dist/dashboard/web/agent-workbench-terminal-control.js.map +1 -0
  223. package/dist/dashboard/web/agent-workbench-view.d.ts +3 -2
  224. package/dist/dashboard/web/agent-workbench-view.d.ts.map +1 -1
  225. package/dist/dashboard/web/agent-workbench-view.js +69 -50
  226. package/dist/dashboard/web/agent-workbench-view.js.map +1 -1
  227. package/dist/dashboard/web/app.d.ts.map +1 -1
  228. package/dist/dashboard/web/app.js +58 -7
  229. package/dist/dashboard/web/app.js.map +1 -1
  230. package/dist/dashboard/web/bot-defaults-page.d.ts +24 -0
  231. package/dist/dashboard/web/bot-defaults-page.d.ts.map +1 -1
  232. package/dist/dashboard/web/bot-defaults-page.js +191 -16
  233. package/dist/dashboard/web/bot-defaults-page.js.map +1 -1
  234. package/dist/dashboard/web/bot-defaults.d.ts +17 -0
  235. package/dist/dashboard/web/bot-defaults.d.ts.map +1 -1
  236. package/dist/dashboard/web/bot-defaults.js +51 -2
  237. package/dist/dashboard/web/bot-defaults.js.map +1 -1
  238. package/dist/dashboard/web/bot-onboarding.d.ts.map +1 -1
  239. package/dist/dashboard/web/bot-onboarding.js +32 -8
  240. package/dist/dashboard/web/bot-onboarding.js.map +1 -1
  241. package/dist/dashboard/web/confirm-modal.d.ts +35 -0
  242. package/dist/dashboard/web/confirm-modal.d.ts.map +1 -0
  243. package/dist/dashboard/web/confirm-modal.js +111 -0
  244. package/dist/dashboard/web/confirm-modal.js.map +1 -0
  245. package/dist/dashboard/web/connectors-page.d.ts.map +1 -1
  246. package/dist/dashboard/web/connectors-page.js +2 -1
  247. package/dist/dashboard/web/connectors-page.js.map +1 -1
  248. package/dist/dashboard/web/customization-page.d.ts +3 -0
  249. package/dist/dashboard/web/customization-page.d.ts.map +1 -0
  250. package/dist/dashboard/web/customization-page.js +315 -0
  251. package/dist/dashboard/web/customization-page.js.map +1 -0
  252. package/dist/dashboard/web/dashboard-routes.d.ts.map +1 -1
  253. package/dist/dashboard/web/dashboard-routes.js +1 -0
  254. package/dist/dashboard/web/dashboard-routes.js.map +1 -1
  255. package/dist/dashboard/web/groups-page.d.ts.map +1 -1
  256. package/dist/dashboard/web/groups-page.js +11 -9
  257. package/dist/dashboard/web/groups-page.js.map +1 -1
  258. package/dist/dashboard/web/i18n.d.ts.map +1 -1
  259. package/dist/dashboard/web/i18n.js +226 -42
  260. package/dist/dashboard/web/i18n.js.map +1 -1
  261. package/dist/dashboard/web/monitor-room.d.ts.map +1 -1
  262. package/dist/dashboard/web/monitor-room.js +3 -2
  263. package/dist/dashboard/web/monitor-room.js.map +1 -1
  264. package/dist/dashboard/web/overview-page.d.ts.map +1 -1
  265. package/dist/dashboard/web/overview-page.js +98 -63
  266. package/dist/dashboard/web/overview-page.js.map +1 -1
  267. package/dist/dashboard/web/roles-page.d.ts.map +1 -1
  268. package/dist/dashboard/web/roles-page.js +4 -3
  269. package/dist/dashboard/web/roles-page.js.map +1 -1
  270. package/dist/dashboard/web/schedules-page.d.ts.map +1 -1
  271. package/dist/dashboard/web/schedules-page.js +165 -18
  272. package/dist/dashboard/web/schedules-page.js.map +1 -1
  273. package/dist/dashboard/web/sessions-kanban.d.ts +2 -0
  274. package/dist/dashboard/web/sessions-kanban.d.ts.map +1 -1
  275. package/dist/dashboard/web/sessions-kanban.js +142 -24
  276. package/dist/dashboard/web/sessions-kanban.js.map +1 -1
  277. package/dist/dashboard/web/sessions-page.d.ts.map +1 -1
  278. package/dist/dashboard/web/sessions-page.js +177 -94
  279. package/dist/dashboard/web/sessions-page.js.map +1 -1
  280. package/dist/dashboard/web/sessions.d.ts.map +1 -1
  281. package/dist/dashboard/web/sessions.js +5 -4
  282. package/dist/dashboard/web/sessions.js.map +1 -1
  283. package/dist/dashboard/web/settings-page.d.ts +0 -8
  284. package/dist/dashboard/web/settings-page.d.ts.map +1 -1
  285. package/dist/dashboard/web/settings-page.js +185 -65
  286. package/dist/dashboard/web/settings-page.js.map +1 -1
  287. package/dist/dashboard/web/skills/skill-library-tab.d.ts +2 -0
  288. package/dist/dashboard/web/skills/skill-library-tab.d.ts.map +1 -1
  289. package/dist/dashboard/web/skills/skill-library-tab.js +15 -1
  290. package/dist/dashboard/web/skills/skill-library-tab.js.map +1 -1
  291. package/dist/dashboard/web/skills-page.d.ts.map +1 -1
  292. package/dist/dashboard/web/skills-page.js +5 -4
  293. package/dist/dashboard/web/skills-page.js.map +1 -1
  294. package/dist/dashboard/web/store.d.ts +5 -0
  295. package/dist/dashboard/web/store.d.ts.map +1 -1
  296. package/dist/dashboard/web/store.js +4 -0
  297. package/dist/dashboard/web/store.js.map +1 -1
  298. package/dist/dashboard/web/team-federation-page.d.ts.map +1 -1
  299. package/dist/dashboard/web/team-federation-page.js +3 -2
  300. package/dist/dashboard/web/team-federation-page.js.map +1 -1
  301. package/dist/dashboard/web/toast.d.ts +15 -0
  302. package/dist/dashboard/web/toast.d.ts.map +1 -0
  303. package/dist/dashboard/web/toast.js +70 -0
  304. package/dist/dashboard/web/toast.js.map +1 -0
  305. package/dist/dashboard/web/v3-components.d.ts.map +1 -1
  306. package/dist/dashboard/web/v3-components.js +2 -1
  307. package/dist/dashboard/web/v3-components.js.map +1 -1
  308. package/dist/dashboard/web/vc-consumer-profiles-section.d.ts +10 -12
  309. package/dist/dashboard/web/vc-consumer-profiles-section.d.ts.map +1 -1
  310. package/dist/dashboard/web/vc-consumer-profiles-section.js +219 -200
  311. package/dist/dashboard/web/vc-consumer-profiles-section.js.map +1 -1
  312. package/dist/dashboard/web/whiteboards-page.d.ts.map +1 -1
  313. package/dist/dashboard/web/whiteboards-page.js +2 -1
  314. package/dist/dashboard/web/whiteboards-page.js.map +1 -1
  315. package/dist/dashboard-web/app.js +1 -1
  316. package/dist/dashboard-web/chunks/agent-workbench-dock-page-LZSUOCEQ.js +1 -0
  317. package/dist/dashboard-web/chunks/agent-workbench-page-YONDRJU7.js +1 -0
  318. package/dist/dashboard-web/chunks/bot-defaults-page-X7LV6373.js +1 -0
  319. package/dist/dashboard-web/chunks/chunk-2PVRDXEL.js +1 -0
  320. package/dist/dashboard-web/chunks/chunk-53JUKBU2.js +10 -0
  321. package/dist/dashboard-web/chunks/{chunk-5CJMHTT4.js → chunk-5QX4OYOR.js} +1 -1
  322. package/dist/dashboard-web/chunks/{chunk-NACP7BMR.js → chunk-5VRVJFQR.js} +1 -1
  323. package/dist/dashboard-web/chunks/{chunk-FDZ7WOBW.js → chunk-6ROOZWYV.js} +1 -1
  324. package/dist/dashboard-web/chunks/{chunk-F6KNPF4R.js → chunk-CK22743G.js} +15 -19
  325. package/dist/dashboard-web/chunks/chunk-CVNIMUPY.js +25 -0
  326. package/dist/dashboard-web/chunks/chunk-GJC2TMWS.js +2 -0
  327. package/dist/dashboard-web/chunks/chunk-OT6L6G7R.js +1 -0
  328. package/dist/dashboard-web/chunks/chunk-QITAMHW6.js +1 -0
  329. package/dist/dashboard-web/chunks/chunk-RHVFZC5Z.js +1 -0
  330. package/dist/dashboard-web/chunks/chunk-SOBMN2VY.js +1 -0
  331. package/dist/dashboard-web/chunks/chunk-SZOB6VK2.js +1 -0
  332. package/dist/dashboard-web/chunks/chunk-XVIXNQGC.js +44 -0
  333. package/dist/dashboard-web/chunks/chunk-YUJF34ME.js +1 -0
  334. package/dist/dashboard-web/chunks/connectors-page-LB2HEJ7F.js +1 -0
  335. package/dist/dashboard-web/chunks/customization-page-4GXBYLG6.js +105 -0
  336. package/dist/dashboard-web/chunks/feedback-page-IZQ7VLDG.js +1 -0
  337. package/dist/dashboard-web/chunks/groups-page-4WCP5OEV.js +8 -0
  338. package/dist/dashboard-web/chunks/{insights-page-HJFDYR5N.js → insights-page-TXQHIPQL.js} +1 -1
  339. package/dist/dashboard-web/chunks/{monitor-room-V7PCP47V.js → monitor-room-VBRVWXCM.js} +8 -8
  340. package/dist/dashboard-web/chunks/monitoring-page-QDDZWERE.js +2 -0
  341. package/dist/dashboard-web/chunks/office-page-PENFNYPG.js +1 -0
  342. package/dist/dashboard-web/chunks/overview-page-BZQDP6MP.js +1 -0
  343. package/dist/dashboard-web/chunks/plugin-page-JX4GSWZS.js +1 -0
  344. package/dist/dashboard-web/chunks/roles-page-VPNINZNK.js +1 -0
  345. package/dist/dashboard-web/chunks/schedules-page-BITO6HEA.js +1 -0
  346. package/dist/dashboard-web/chunks/sessions-page-ZKCOVTD2.js +8 -0
  347. package/dist/dashboard-web/chunks/settings-page-UT6JDTS6.js +2 -0
  348. package/dist/dashboard-web/chunks/skills-page-GULXVAJY.js +4 -0
  349. package/dist/dashboard-web/chunks/team-federation-page-CUUJWGEG.js +1 -0
  350. package/dist/dashboard-web/chunks/v3-page-5WVJ5AUR.js +94 -0
  351. package/dist/dashboard-web/chunks/whiteboards-page-FZ2CME4H.js +1 -0
  352. package/dist/dashboard-web/design-tokens.css +236 -0
  353. package/dist/dashboard-web/index.html +1 -0
  354. package/dist/dashboard-web/style.css +1930 -114
  355. package/dist/dashboard.js +360 -279
  356. package/dist/dashboard.js.map +1 -1
  357. package/dist/desktop/renderer/app.js +6 -0
  358. package/dist/desktop/renderer/app.js.map +1 -1
  359. package/dist/dsh-runner.js +139 -17
  360. package/dist/dsh-runner.js.map +1 -1
  361. package/dist/global-config.d.ts +33 -2
  362. package/dist/global-config.d.ts.map +1 -1
  363. package/dist/global-config.js +62 -0
  364. package/dist/global-config.js.map +1 -1
  365. package/dist/i18n/en.d.ts.map +1 -1
  366. package/dist/i18n/en.js +12 -4
  367. package/dist/i18n/en.js.map +1 -1
  368. package/dist/i18n/index.d.ts +27 -3
  369. package/dist/i18n/index.d.ts.map +1 -1
  370. package/dist/i18n/index.js +33 -4
  371. package/dist/i18n/index.js.map +1 -1
  372. package/dist/i18n/zh.d.ts.map +1 -1
  373. package/dist/i18n/zh.js +12 -4
  374. package/dist/i18n/zh.js.map +1 -1
  375. package/dist/im/lark/card-builder.d.ts +2 -0
  376. package/dist/im/lark/card-builder.d.ts.map +1 -1
  377. package/dist/im/lark/card-builder.js +7 -4
  378. package/dist/im/lark/card-builder.js.map +1 -1
  379. package/dist/im/lark/card-handler.d.ts.map +1 -1
  380. package/dist/im/lark/card-handler.js +70 -16
  381. package/dist/im/lark/card-handler.js.map +1 -1
  382. package/dist/im/lark/event-dispatcher.d.ts +38 -3
  383. package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
  384. package/dist/im/lark/event-dispatcher.js +163 -13
  385. package/dist/im/lark/event-dispatcher.js.map +1 -1
  386. package/dist/im/lark/v3-blocked-card-handler.d.ts.map +1 -1
  387. package/dist/im/lark/v3-blocked-card-handler.js +2 -1
  388. package/dist/im/lark/v3-blocked-card-handler.js.map +1 -1
  389. package/dist/im/lark/v3-blocked-card.d.ts +1 -1
  390. package/dist/im/lark/v3-blocked-card.d.ts.map +1 -1
  391. package/dist/im/lark/v3-blocked-card.js +14 -0
  392. package/dist/im/lark/v3-blocked-card.js.map +1 -1
  393. package/dist/index-core-only.js +2 -0
  394. package/dist/index-core-only.js.map +1 -1
  395. package/dist/index-daemon.js +3 -0
  396. package/dist/index-daemon.js.map +1 -1
  397. package/dist/platform/binding.d.ts +11 -1
  398. package/dist/platform/binding.d.ts.map +1 -1
  399. package/dist/platform/binding.js +3 -28
  400. package/dist/platform/binding.js.map +1 -1
  401. package/dist/services/codex-app-runner-protocol.d.ts +5 -0
  402. package/dist/services/codex-app-runner-protocol.d.ts.map +1 -1
  403. package/dist/services/codex-app-runner-protocol.js +5 -0
  404. package/dist/services/codex-app-runner-protocol.js.map +1 -1
  405. package/dist/services/customization-store.d.ts +93 -0
  406. package/dist/services/customization-store.d.ts.map +1 -0
  407. package/dist/services/customization-store.js +542 -0
  408. package/dist/services/customization-store.js.map +1 -0
  409. package/dist/services/file-bridge-path.d.ts.map +1 -1
  410. package/dist/services/file-bridge-path.js +3 -0
  411. package/dist/services/file-bridge-path.js.map +1 -1
  412. package/dist/services/grok-transcript.d.ts.map +1 -1
  413. package/dist/services/grok-transcript.js +44 -9
  414. package/dist/services/grok-transcript.js.map +1 -1
  415. package/dist/services/local-cli-opener.d.ts.map +1 -1
  416. package/dist/services/local-cli-opener.js +12 -3
  417. package/dist/services/local-cli-opener.js.map +1 -1
  418. package/dist/services/model-catalog.d.ts +54 -0
  419. package/dist/services/model-catalog.d.ts.map +1 -0
  420. package/dist/services/model-catalog.js +147 -0
  421. package/dist/services/model-catalog.js.map +1 -0
  422. package/dist/services/omp-transcript.d.ts +27 -0
  423. package/dist/services/omp-transcript.d.ts.map +1 -0
  424. package/dist/services/omp-transcript.js +214 -0
  425. package/dist/services/omp-transcript.js.map +1 -0
  426. package/dist/services/platform-team-store.d.ts +16 -8
  427. package/dist/services/platform-team-store.d.ts.map +1 -1
  428. package/dist/services/platform-team-store.js +17 -10
  429. package/dist/services/platform-team-store.js.map +1 -1
  430. package/dist/services/project-scanner.d.ts.map +1 -1
  431. package/dist/services/project-scanner.js +13 -8
  432. package/dist/services/project-scanner.js.map +1 -1
  433. package/dist/services/resumable-session-discovery.d.ts.map +1 -1
  434. package/dist/services/resumable-session-discovery.js +117 -35
  435. package/dist/services/resumable-session-discovery.js.map +1 -1
  436. package/dist/services/structured-bridge-clis.d.ts +5 -3
  437. package/dist/services/structured-bridge-clis.d.ts.map +1 -1
  438. package/dist/services/structured-bridge-clis.js +5 -1
  439. package/dist/services/structured-bridge-clis.js.map +1 -1
  440. package/dist/services/vc-meeting-action-gate.d.ts.map +1 -1
  441. package/dist/services/vc-meeting-action-gate.js +15 -4
  442. package/dist/services/vc-meeting-action-gate.js.map +1 -1
  443. package/dist/services/vc-meeting-consumer-profile-bootstrap.d.ts +46 -66
  444. package/dist/services/vc-meeting-consumer-profile-bootstrap.d.ts.map +1 -1
  445. package/dist/services/vc-meeting-consumer-profile-bootstrap.js +33 -292
  446. package/dist/services/vc-meeting-consumer-profile-bootstrap.js.map +1 -1
  447. package/dist/services/vc-meeting-consumer-profile-templates.js +3 -3
  448. package/dist/services/vc-meeting-consumer-profile-templates.js.map +1 -1
  449. package/dist/services/vc-meeting-delivery-store.d.ts +18 -0
  450. package/dist/services/vc-meeting-delivery-store.d.ts.map +1 -1
  451. package/dist/services/vc-meeting-delivery-store.js +55 -0
  452. package/dist/services/vc-meeting-delivery-store.js.map +1 -1
  453. package/dist/services/vc-meeting-send-policy.d.ts +21 -1
  454. package/dist/services/vc-meeting-send-policy.d.ts.map +1 -1
  455. package/dist/services/vc-meeting-send-policy.js +30 -2
  456. package/dist/services/vc-meeting-send-policy.js.map +1 -1
  457. package/dist/services/vc-meeting-shared-consumer-catalog-store.d.ts +78 -0
  458. package/dist/services/vc-meeting-shared-consumer-catalog-store.d.ts.map +1 -0
  459. package/dist/services/vc-meeting-shared-consumer-catalog-store.js +227 -0
  460. package/dist/services/vc-meeting-shared-consumer-catalog-store.js.map +1 -0
  461. package/dist/services/vc-meeting-shared-consumer-catalog.d.ts +53 -0
  462. package/dist/services/vc-meeting-shared-consumer-catalog.d.ts.map +1 -0
  463. package/dist/services/vc-meeting-shared-consumer-catalog.js +161 -0
  464. package/dist/services/vc-meeting-shared-consumer-catalog.js.map +1 -0
  465. package/dist/setup/bot-config-editor.d.ts.map +1 -1
  466. package/dist/setup/bot-config-editor.js +1 -0
  467. package/dist/setup/bot-config-editor.js.map +1 -1
  468. package/dist/setup/open-platform-automation.d.ts +20 -0
  469. package/dist/setup/open-platform-automation.d.ts.map +1 -1
  470. package/dist/setup/open-platform-automation.js +27 -0
  471. package/dist/setup/open-platform-automation.js.map +1 -1
  472. package/dist/skills/customization-bundle.d.ts +83 -0
  473. package/dist/skills/customization-bundle.d.ts.map +1 -0
  474. package/dist/skills/customization-bundle.js +214 -0
  475. package/dist/skills/customization-bundle.js.map +1 -0
  476. package/dist/skills/definitions.d.ts.map +1 -1
  477. package/dist/skills/definitions.js +10 -3
  478. package/dist/skills/definitions.js.map +1 -1
  479. package/dist/skills/effective-builtins.d.ts +38 -0
  480. package/dist/skills/effective-builtins.d.ts.map +1 -0
  481. package/dist/skills/effective-builtins.js +96 -0
  482. package/dist/skills/effective-builtins.js.map +1 -0
  483. package/dist/skills/injection-mode.d.ts +4 -1
  484. package/dist/skills/injection-mode.d.ts.map +1 -1
  485. package/dist/skills/injection-mode.js +11 -3
  486. package/dist/skills/injection-mode.js.map +1 -1
  487. package/dist/skills/installer.d.ts.map +1 -1
  488. package/dist/skills/installer.js +24 -1
  489. package/dist/skills/installer.js.map +1 -1
  490. package/dist/skills/prompt-fragments.d.ts +58 -0
  491. package/dist/skills/prompt-fragments.d.ts.map +1 -0
  492. package/dist/skills/prompt-fragments.js +88 -0
  493. package/dist/skills/prompt-fragments.js.map +1 -0
  494. package/dist/types.d.ts +51 -0
  495. package/dist/types.d.ts.map +1 -1
  496. package/dist/utils/cli-usage-limit.d.ts +50 -0
  497. package/dist/utils/cli-usage-limit.d.ts.map +1 -1
  498. package/dist/utils/cli-usage-limit.js +43 -0
  499. package/dist/utils/cli-usage-limit.js.map +1 -1
  500. package/dist/utils/idle-detector.d.ts +12 -0
  501. package/dist/utils/idle-detector.d.ts.map +1 -1
  502. package/dist/utils/idle-detector.js +88 -2
  503. package/dist/utils/idle-detector.js.map +1 -1
  504. package/dist/utils/stuck-detector.d.ts +5 -0
  505. package/dist/utils/stuck-detector.d.ts.map +1 -1
  506. package/dist/utils/stuck-detector.js +41 -0
  507. package/dist/utils/stuck-detector.js.map +1 -1
  508. package/dist/utils/usage-limit-tracker.d.ts +46 -0
  509. package/dist/utils/usage-limit-tracker.d.ts.map +1 -0
  510. package/dist/utils/usage-limit-tracker.js +91 -0
  511. package/dist/utils/usage-limit-tracker.js.map +1 -0
  512. package/dist/utils/user-token.js +1 -1
  513. package/dist/utils/user-token.js.map +1 -1
  514. package/dist/worker.js +542 -106
  515. package/dist/worker.js.map +1 -1
  516. package/dist/workflows/definition.d.ts +14 -14
  517. package/dist/workflows/events/payloads.d.ts +12 -12
  518. package/dist/workflows/events/schema.d.ts +164 -164
  519. package/dist/workflows/migration/v2-to-v3.js +1 -1
  520. package/dist/workflows/migration/v2-to-v3.js.map +1 -1
  521. package/dist/workflows/v3/architect.d.ts.map +1 -1
  522. package/dist/workflows/v3/architect.js +11 -5
  523. package/dist/workflows/v3/architect.js.map +1 -1
  524. package/dist/workflows/v3/artifact-contract-declarations.d.ts +22 -0
  525. package/dist/workflows/v3/artifact-contract-declarations.d.ts.map +1 -0
  526. package/dist/workflows/v3/artifact-contract-declarations.js +95 -0
  527. package/dist/workflows/v3/artifact-contract-declarations.js.map +1 -0
  528. package/dist/workflows/v3/contract.d.ts +1 -0
  529. package/dist/workflows/v3/contract.d.ts.map +1 -1
  530. package/dist/workflows/v3/contract.js.map +1 -1
  531. package/dist/workflows/v3/daemon-run.d.ts +2 -2
  532. package/dist/workflows/v3/daemon-run.d.ts.map +1 -1
  533. package/dist/workflows/v3/daemon-run.js +4 -0
  534. package/dist/workflows/v3/daemon-run.js.map +1 -1
  535. package/dist/workflows/v3/dag.d.ts +7 -0
  536. package/dist/workflows/v3/dag.d.ts.map +1 -1
  537. package/dist/workflows/v3/dag.js +69 -8
  538. package/dist/workflows/v3/dag.js.map +1 -1
  539. package/dist/workflows/v3/ephemeral-pool.js +1 -1
  540. package/dist/workflows/v3/ephemeral-pool.js.map +1 -1
  541. package/dist/workflows/v3/event-contract.d.ts +3 -1
  542. package/dist/workflows/v3/event-contract.d.ts.map +1 -1
  543. package/dist/workflows/v3/host.d.ts.map +1 -1
  544. package/dist/workflows/v3/host.js +5 -1
  545. package/dist/workflows/v3/host.js.map +1 -1
  546. package/dist/workflows/v3/journal.d.ts +1 -1
  547. package/dist/workflows/v3/journal.d.ts.map +1 -1
  548. package/dist/workflows/v3/journal.js.map +1 -1
  549. package/dist/workflows/v3/library-materialize.d.ts.map +1 -1
  550. package/dist/workflows/v3/library-materialize.js +9 -1
  551. package/dist/workflows/v3/library-materialize.js.map +1 -1
  552. package/dist/workflows/v3/library-schema.d.ts +3 -1
  553. package/dist/workflows/v3/library-schema.d.ts.map +1 -1
  554. package/dist/workflows/v3/library-schema.js +20 -4
  555. package/dist/workflows/v3/library-schema.js.map +1 -1
  556. package/dist/workflows/v3/library-store.d.ts.map +1 -1
  557. package/dist/workflows/v3/library-store.js +15 -3
  558. package/dist/workflows/v3/library-store.js.map +1 -1
  559. package/dist/workflows/v3/run-envelope.d.ts +16 -16
  560. package/dist/workflows/v3/shared-node-runtime.d.ts +2 -1
  561. package/dist/workflows/v3/shared-node-runtime.d.ts.map +1 -1
  562. package/dist/workflows/v3/shared-node-runtime.js +48 -7
  563. package/dist/workflows/v3/shared-node-runtime.js.map +1 -1
  564. package/package.json +2 -1
  565. package/dist/dashboard-web/chunks/agent-workbench-dock-page-IKAC5CFL.js +0 -1
  566. package/dist/dashboard-web/chunks/agent-workbench-page-M53UN5EB.js +0 -1
  567. package/dist/dashboard-web/chunks/bot-defaults-page-YKE5SDZF.js +0 -10
  568. package/dist/dashboard-web/chunks/chunk-22WIADUT.js +0 -2
  569. package/dist/dashboard-web/chunks/chunk-24ONYQMR.js +0 -1
  570. package/dist/dashboard-web/chunks/chunk-2W6SCN3M.js +0 -1
  571. package/dist/dashboard-web/chunks/chunk-4XNJX6B3.js +0 -1
  572. package/dist/dashboard-web/chunks/chunk-5THX4DI2.js +0 -66
  573. package/dist/dashboard-web/chunks/chunk-7SZRL6FY.js +0 -1
  574. package/dist/dashboard-web/chunks/chunk-AGCPF6MT.js +0 -1
  575. package/dist/dashboard-web/chunks/chunk-FXW3V4JR.js +0 -1
  576. package/dist/dashboard-web/chunks/chunk-SYC6D5XU.js +0 -1
  577. package/dist/dashboard-web/chunks/connectors-page-VOISXXII.js +0 -1
  578. package/dist/dashboard-web/chunks/feedback-page-RN6HRZMS.js +0 -1
  579. package/dist/dashboard-web/chunks/groups-page-UQT6DCB7.js +0 -8
  580. package/dist/dashboard-web/chunks/monitoring-page-NKEKVQAP.js +0 -2
  581. package/dist/dashboard-web/chunks/office-page-GVJF73XW.js +0 -1
  582. package/dist/dashboard-web/chunks/overview-page-KHLV6VTI.js +0 -1
  583. package/dist/dashboard-web/chunks/plugin-page-J3OF5IY4.js +0 -1
  584. package/dist/dashboard-web/chunks/roles-page-WQIRSOLZ.js +0 -1
  585. package/dist/dashboard-web/chunks/schedules-page-J2DEY2X6.js +0 -1
  586. package/dist/dashboard-web/chunks/sessions-page-7BQFEA5P.js +0 -8
  587. package/dist/dashboard-web/chunks/settings-page-BJEIOFJH.js +0 -2
  588. package/dist/dashboard-web/chunks/skills-page-HGIFR3IU.js +0 -4
  589. package/dist/dashboard-web/chunks/team-federation-page-MR5ZPL4B.js +0 -1
  590. package/dist/dashboard-web/chunks/v3-page-GBR5JWPE.js +0 -94
  591. package/dist/dashboard-web/chunks/whiteboards-page-QAZM4BN3.js +0 -1
  592. package/dist/services/vc-meeting-consumer-profile-store.d.ts +0 -39
  593. package/dist/services/vc-meeting-consumer-profile-store.d.ts.map +0 -1
  594. package/dist/services/vc-meeting-consumer-profile-store.js +0 -191
  595. package/dist/services/vc-meeting-consumer-profile-store.js.map +0 -1
@@ -1,7 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useCallback, useEffect, useMemo, useRef, useState, } from 'react';
2
+ import { useCallback, useEffect, useMemo, useReducer, useRef, useState, } from 'react';
3
3
  import { clampSplitRatio, formatWorkbenchRelativeTime, paneTreeForLayout, workbenchExternalTerminalHref, workbenchPreviewHref, workbenchSessionTitle, workbenchTerminalHref, } from './agent-workbench-model.js';
4
- import { WorkbenchApiError, } from './agent-workbench-api.js';
4
+ import { WorkbenchApiError, newTerminalAcquisitionId, } from './agent-workbench-api.js';
5
+ // 控制权只有一份状态机(面板侧 + 外层意图),见该文件头部的注释。
6
+ import { WORKBENCH_TERM_WRITE_MESSAGE, claimTerminalWrite, hasLiveTerminalWriteClaim, initialTerminalControlModel, terminalControlBusy, terminalControlMode, terminalControlNeedsMask, terminalControlReducer, terminalControlSettled, } from './agent-workbench-terminal-control.js';
5
7
  // 触屏鉴权契约只有一份实现,终端面板和会话坞共用(P1-17)。
6
8
  import { useTerminalViewLink, useTouchEnvironment } from './agent-workbench-touch.js';
7
9
  import { postWorkbenchTermAppearance, workbenchTermCanvasStyle, workbenchTermContainerClass, } from './agent-workbench-appearance.js';
@@ -22,6 +24,52 @@ export function readTerminalFrameStatus(frame) {
22
24
  return 'unknown';
23
25
  }
24
26
  }
27
+ /**
28
+ * 生产实现:只读终端页的 `wsHasWrite`——**已经建立的那条 WebSocket** 实际拿到什么
29
+ * 权限(worker 在握手完成时用一条带外控制帧告诉页面,页面落成这个全局)。
30
+ * `null` / 读不到 = 还没有结论,一律 `unknown`。
31
+ *
32
+ * 为什么不拿页面那次 HTTP GET 的 `hasToken` 兜底:两次是**各自独立**的鉴权。页面是
33
+ * HTTP 请求,iOS WebView 会带 Cookie;紧接着的 WebSocket 升级**不带** Cookie,前置
34
+ * 代理给平台所有者补 WRITE grant 这件事也就跟着落空。拿 hasToken 兜底等于在 WS 还
35
+ * 没连上(或永远连不上)时宣称「可以输入」,用户打的字被 worker 原地丢掉,屏幕上
36
+ * 什么都不发生。没有依据时说未知,是这里唯一诚实的答案。
37
+ */
38
+ export function readTerminalFrameWrite(frame) {
39
+ try {
40
+ const view = frame?.contentWindow;
41
+ const settled = view?.wsHasWrite;
42
+ if (settled === true)
43
+ return 'writable';
44
+ if (settled === false)
45
+ return 'readonly';
46
+ return 'unknown';
47
+ }
48
+ catch {
49
+ return 'unknown';
50
+ }
51
+ }
52
+ /**
53
+ * 终端页 → 工作台的一次写权限回报(终端页在 WS 建立后 postMessage 上抛)。
54
+ *
55
+ * 同源读全局已经够用,这条消息解决的是**时机**:不用等下一拍轮询,连上那一刻面板
56
+ * 就能把文案改对;而且万一将来这块 iframe 走到跨文档隔离的场景,同源读会直接抛,
57
+ * 消息通道仍然成立。来源校验沿用外观桥那一套:只认这块 iframe 自己的 contentWindow
58
+ * 发来的、结构完全对得上的消息。
59
+ *
60
+ * `write: null` 是终端页每次**重连**时先发的一条「退回未知」——上一条连接的结论套
61
+ * 不到新连接上。返回 `null` 表示「这压根不是我们的消息」,两者不能混。
62
+ */
63
+ export function readTerminalWriteReport(data) {
64
+ if (!data || typeof data !== 'object')
65
+ return null;
66
+ const message = data;
67
+ if (message.type !== WORKBENCH_TERM_WRITE_MESSAGE)
68
+ return null;
69
+ if (message.write === null)
70
+ return 'unknown';
71
+ return typeof message.write === 'boolean' ? (message.write ? 'writable' : 'readonly') : null;
72
+ }
25
73
  const FRAME_STATUS_POLL_MS = 1_000;
26
74
  /** 连续 8 拍(≈8 秒)都是 disconnected 才提示。终端页自己每 2 秒重连一次,正常网络下
27
75
  * 一两拍就连上了;8 秒还连不上,基本可以断定是这个浏览器环境拦掉了 ws://。 */
@@ -68,6 +116,74 @@ function useTerminalFrameWatch(params) {
68
116
  const onFrameLoad = useCallback(() => setLoadedFrameKey(frameKey), [frameKey]);
69
117
  return { blocked: enabled && blocked, onFrameLoad: enabled ? onFrameLoad : undefined };
70
118
  }
119
+ /** 最多读几拍就收手:`hasToken` 是终端页顶部同步赋的全局,onLoad 时通常已经在了;
120
+ * 读不到基本就是读不到(跨域 / 页面报错),一直转下去没有意义。 */
121
+ const FRAME_WRITE_MAX_TICKS = 5;
122
+ /** 盯着 iframe 自报的写权限。只在需要它当判据的场景启用(触屏那条 viewToken
123
+ * 通道);桌面同源链路的权威是控制权接口本身,多一个来源只会打架。 */
124
+ function useTerminalFrameWrite(params) {
125
+ const { enabled, frameKey } = params;
126
+ const [write, setWrite] = useState('unknown');
127
+ const readRef = useRef(params.read);
128
+ useEffect(() => { readRef.current = params.read; });
129
+ useEffect(() => {
130
+ if (!enabled)
131
+ return undefined;
132
+ setWrite('unknown');
133
+ let ticks = 0;
134
+ const sample = () => {
135
+ const value = readRef.current();
136
+ if (value === 'unknown')
137
+ return false;
138
+ setWrite(value);
139
+ return true;
140
+ };
141
+ if (sample())
142
+ return undefined;
143
+ const timer = setInterval(() => {
144
+ ticks += 1;
145
+ if (sample() || ticks >= FRAME_WRITE_MAX_TICKS)
146
+ clearInterval(timer);
147
+ }, FRAME_STATUS_POLL_MS);
148
+ return () => clearInterval(timer);
149
+ }, [enabled, frameKey]);
150
+ return enabled ? write : 'unknown';
151
+ }
152
+ /**
153
+ * 收终端页在 WS 建立后主动上抛的写权限,并附带「这条上抛通道到底通没通」(`live`)。
154
+ *
155
+ * 只认这块 iframe 自己的 `contentWindow` 发来的消息(与外观桥同一套来源校验):这
156
+ * 页面会被跨 origin 嵌入,别的窗口发来的同名消息一律不作数。换一块 iframe 就把结论
157
+ * 与 `live` 一起清空——上一块的判定套不到新的连接上。
158
+ *
159
+ * `live` 一旦为真,就说明 postMessage 通道有效、终端页的回报是这条 frame 的**权威**:
160
+ * 此后哪怕它回报 `unknown`(重连时先发的 `write:null`),父页也一律以它为准,绝不再
161
+ * 回退到轮询那份可能已经过期的 writable(见下方合并处注释)。首帧确认前、或页面根本
162
+ * 没上抛(取不到父页 origin 时它会放弃 postMessage),`live` 为假,此时才交给轮询兜底。
163
+ */
164
+ function useTerminalFrameWriteReport(params) {
165
+ const { enabled, frameKey } = params;
166
+ const [state, setState] = useState({ write: 'unknown', live: false });
167
+ const frameRef = useRef(params.frame);
168
+ useEffect(() => { frameRef.current = params.frame; });
169
+ useEffect(() => {
170
+ setState({ write: 'unknown', live: false });
171
+ if (!enabled || typeof window === 'undefined' || !window.addEventListener)
172
+ return undefined;
173
+ const onMessage = (event) => {
174
+ const source = frameRef.current()?.contentWindow;
175
+ if (!source || event.source !== source)
176
+ return;
177
+ const reported = readTerminalWriteReport(event.data);
178
+ if (reported === null)
179
+ return;
180
+ setState({ write: reported, live: true });
181
+ };
182
+ window.addEventListener('message', onMessage);
183
+ return () => window.removeEventListener('message', onMessage);
184
+ }, [enabled, frameKey]);
185
+ return enabled ? state : { write: 'unknown', live: false };
186
+ }
71
187
  function browserHref() {
72
188
  try {
73
189
  return typeof window === 'undefined' ? undefined : window.location?.href;
@@ -85,18 +201,89 @@ function apiErrorText(error) {
85
201
  terminal_unavailable: '终端不可用',
86
202
  control_busy: '另一个浏览器正在控制该终端',
87
203
  preview_not_registered: '未注册网页预览',
204
+ control_request_timeout: '控制权请求超时',
88
205
  };
89
206
  return known[error.code] ?? error.code.replaceAll('_', ' ');
90
207
  }
91
208
  return error instanceof Error ? error.message : String(error);
92
209
  }
210
+ /**
211
+ * 写请求(takeover / release)的硬超时。
212
+ *
213
+ * 没有它时,一个**永远不回执**的 POST 会把这块面板整个焊死:`pending` 一直挂着 →
214
+ * `busy` 恒为真 → 标题栏和行内按钮永久禁用;轮询读数一律被在途写丢弃,界面停在
215
+ * 「正在接管输入…」;连卸载补偿也等不到那个 promise。半开连接、被代理吞掉的回执
216
+ * 都是这个形状,而且它不会自己好。
217
+ *
218
+ * 到点就按失败处理:进 `unknown`(**不是**只读——服务端很可能已经受理了)、遮罩
219
+ * 挡住盲输入、立刻发一次权威复核 GET 把状态收敛回来。
220
+ */
221
+ export const TERMINAL_WRITE_TIMEOUT_MS = 10_000;
222
+ /**
223
+ * 跑一次写请求,超时就自己判负。
224
+ *
225
+ * 两件事一起做,缺一不可:
226
+ * ① `abort()` 掐掉请求本身——不然这条 fetch 会带着一个没人看的写租约继续飞;
227
+ * ② 竞速 reject——调用方拿到的必须是一个**会结束**的 promise。只靠 abort 不够:
228
+ * 注入的 api 替身(以及某些不认 signal 的 fetch polyfill)可能压根不理会
229
+ * signal,那样 promise 依旧永久 pending,超时也就形同虚设。
230
+ */
231
+ async function withWriteTimeout(run, timeoutMs = TERMINAL_WRITE_TIMEOUT_MS) {
232
+ const controller = new AbortController();
233
+ let timer;
234
+ const expiry = new Promise((_resolve, reject) => {
235
+ timer = setTimeout(() => {
236
+ try {
237
+ controller.abort();
238
+ }
239
+ catch { /* 环境不支持 abort:超时判定照旧成立 */ }
240
+ reject(new WorkbenchApiError(504, 'control_request_timeout'));
241
+ }, timeoutMs);
242
+ });
243
+ // 被丢下的那一半 promise 若稍后 reject,没人接就会冒成 unhandled rejection。
244
+ expiry.catch(() => { });
245
+ try {
246
+ return await Promise.race([run(controller.signal), expiry]);
247
+ }
248
+ finally {
249
+ if (timer !== undefined)
250
+ clearTimeout(timer);
251
+ }
252
+ }
253
+ /**
254
+ * 控制权未知时顶替终端 iframe 的那一屏。
255
+ *
256
+ * 它**不是**盖在 iframe 上的一层提示,而是把 iframe 整个换掉:真实 Chromium 里量
257
+ * 过——焦点已经落在 iframe 内部时,父文档往上盖一个 `position:absolute` 的层只吃
258
+ * 得到指针事件,`keyboard.type` 照旧一个字不落地打进终端。要真的挡住盲输入,只有
259
+ * 让那块可能可写的 iframe 从 DOM 里消失(焦点随之回到 document.body)。
260
+ *
261
+ * 自己再收一次焦点是兜底:将来若有调用方出于别的理由把 iframe 留着,至少焦点先
262
+ * 离开它;对读屏用户来说,这一屏也确实是此刻该被读出来的东西。
263
+ */
264
+ function TerminalControlUnknown(props) {
265
+ const ref = useRef(null);
266
+ useEffect(() => {
267
+ try {
268
+ ref.current?.focus?.();
269
+ }
270
+ catch { /* 没有 DOM 的宿主(SSR / 组件测试) */ }
271
+ }, []);
272
+ return (_jsxs("div", { ref: ref, tabIndex: -1, className: "wb-pane-empty wb-control-unknown", role: "status", children: [_jsx("span", { "aria-hidden": "true", children: "\u25C7" }), _jsx("strong", { children: "\u63A7\u5236\u6743\u72B6\u6001\u672A\u77E5" }), _jsx("p", { children: "\u6CA1\u6709\u62FF\u5230\u786E\u8BA4\uFF0C\u8FD9\u5757\u7EC8\u7AEF\u53EF\u80FD\u4ECD\u7136\u53EF\u5199\u3002\u5DF2\u65AD\u5F00\u7EC8\u7AEF\u753B\u9762\u6321\u4F4F\u8F93\u5165\uFF0C\u6B63\u5728\u91CD\u65B0\u786E\u8BA4\u3002" }), _jsx("button", { type: "button", onClick: props.onRetry, children: "\u7ACB\u5373\u91CD\u8BD5" })] }));
273
+ }
93
274
  export function TerminalPane(props) {
94
- const [control, setControl] = useState(null);
95
- const [phase, setPhase] = useState('loading');
96
- const [error, setError] = useState('');
97
- const [frameGeneration, setFrameGeneration] = useState(0);
98
- const controlRef = useRef(null);
99
- const requestGeneration = useRef(0);
275
+ // 控制权的唯一状态机。散着的 useState 版本让观察轮询和写操作共用一个
276
+ // generation,15 秒一发的轮询会把在途写的回执丢掉;失败又一律乐观压成只读。
277
+ const [model, dispatch] = useReducer(terminalControlReducer, undefined, initialTerminalControlModel);
278
+ /** 观察与写各自一份 epoch:写只被更新的写作废,轮询作废不了它。 */
279
+ const observeEpoch = useRef(0);
280
+ const writeEpoch = useRef(0);
281
+ /** 卸载后到达的写回执要走补偿路径,不能再 dispatch。 */
282
+ const mounted = useRef(true);
283
+ useEffect(() => {
284
+ mounted.current = true;
285
+ return () => { mounted.current = false; };
286
+ }, []);
100
287
  const terminalUrl = workbenchTerminalHref(props.session, props.location);
101
288
  const externalTerminalUrl = workbenchExternalTerminalHref(props.session);
102
289
  // 触屏和桌面在鉴权方式上彻底分道,详见下面那段注释。
@@ -128,108 +315,265 @@ export function TerminalPane(props) {
128
315
  const frameUrl = !externalTerminalUrl && touch
129
316
  ? viewLinkUrl
130
317
  : props.authenticated ? terminalUrl : viewLinkUrl;
131
- const applyControl = useCallback((next, reloadOnDowngrade = true) => {
132
- const previous = controlRef.current;
133
- controlRef.current = next;
134
- setControl(next);
135
- if (reloadOnDowngrade
136
- && previous?.mode === 'controlled' && previous.owned
137
- && (next.mode !== 'controlled' || !next.owned)) {
138
- setFrameGeneration(value => value + 1);
139
- }
140
- }, []);
141
- const refresh = useCallback(async (source, signal) => {
142
- const generation = ++requestGeneration.current;
318
+ const sessionId = props.session.sessionId;
319
+ /** 一次观察(首屏 / 15s 轮询 / 写失败后的复核)。回执带着自己的 epoch 回来,
320
+ * 过期的、或有写在途的,都在 reducer 里被丢掉。 */
321
+ const observe = useCallback(async (source, signal) => {
143
322
  // Without a Dashboard cookie the control API can only 401, and taking over
144
323
  // is not on offer anyway — settle into read-only instead of surfacing an
145
324
  // authentication error the viewer cannot act on.
146
325
  if (!terminalUrl || externalTerminalUrl || !props.authenticated) {
147
- setPhase('ready');
148
- applyControl({ mode: 'readonly', owned: false });
326
+ dispatch({ type: 'settle-readonly' });
149
327
  return;
150
328
  }
151
- if (source === 'load')
152
- setPhase('loading');
329
+ const epoch = ++observeEpoch.current;
330
+ dispatch({ type: 'observe-start', epoch, source });
153
331
  try {
154
- const next = await props.api.getTerminalControl(props.session.sessionId, signal);
155
- if (generation !== requestGeneration.current)
156
- return;
157
- applyControl(next);
158
- setError('');
159
- setPhase('ready');
332
+ const next = await props.api.getTerminalControl(sessionId, signal);
333
+ dispatch({ type: 'observe-settled', epoch, control: next });
160
334
  }
161
335
  catch (cause) {
162
- if (signal?.aborted || generation !== requestGeneration.current)
336
+ if (signal?.aborted)
163
337
  return;
164
- applyControl({ mode: 'readonly', owned: false });
165
- setError(apiErrorText(cause));
166
- setPhase('error');
338
+ dispatch({
339
+ type: 'observe-failed',
340
+ epoch,
341
+ error: apiErrorText(cause),
342
+ // 触屏走的是 viewToken 只读通道,没有租约可言;无 canControl 的身份连
343
+ // takeover 都会 403。这两类读失败落只读才是事实。
344
+ canHoldLease: props.capabilities.canControl && !touch,
345
+ });
167
346
  }
168
- }, [applyControl, externalTerminalUrl, props.api, props.authenticated, props.session.sessionId, terminalUrl]);
347
+ }, [externalTerminalUrl, props.api, props.authenticated, props.capabilities.canControl, sessionId, terminalUrl, touch]);
169
348
  useEffect(() => {
170
349
  const controller = new AbortController();
171
- void refresh('load', controller.signal);
172
- const timer = setInterval(() => { void refresh('poll', controller.signal); }, 15_000);
350
+ void observe('load', controller.signal);
351
+ const timer = setInterval(() => { void observe('poll', controller.signal); }, 15_000);
173
352
  return () => {
174
- requestGeneration.current += 1;
353
+ // 换会话 / 卸载:作废在途观察。写不走这个计数器——它有自己的 epoch,
354
+ // 卸载后的写回执要留到补偿逻辑里处理,不能在这里"顺手"丢掉。
355
+ observeEpoch.current += 1;
175
356
  clearInterval(timer);
176
357
  controller.abort();
177
358
  };
178
- }, [refresh]);
359
+ }, [observe]);
360
+ const leaseExpiresAt = model.authoritative?.expiresAt;
179
361
  useEffect(() => {
180
- if (control?.mode !== 'controlled' || !control.owned || !control.expiresAt)
362
+ if (model.phase !== 'controlled' || !leaseExpiresAt)
181
363
  return undefined;
182
- const timer = setTimeout(() => { void refresh('poll'); }, Math.max(0, control.expiresAt - Date.now()) + 25);
364
+ const timer = setTimeout(() => { void observe('poll'); }, Math.max(0, leaseExpiresAt - Date.now()) + 25);
183
365
  return () => clearTimeout(timer);
184
- }, [control?.expiresAt, control?.mode, control?.owned, refresh]);
185
- // Fires once per session for the "open writable" shortcut. A platform owner
186
- // (`fixed`) already writes, and an unauthenticated viewer cannot take over at
187
- // all, so neither needs the request. 触屏也跳过:那边的 iframe 走 viewToken 只读通道,
188
- // 抢来的写权限自己用不上,反而会把别人电脑上的输入权顶掉。canControl=false
189
- // 身份(平台 teammate/guest 等)同样跳过——那个 POST 只会 403(P1-4)。
190
- const autoTakeoverDone = useRef(null);
366
+ }, [leaseExpiresAt, model.phase, observe]);
367
+ // 写失败 立刻复核一次。P1-3:release 失败绝不能乐观宣称只读,真实的写租约
368
+ // 可能还在服务端挂着;这一发 GET 是把 unknown 收敛回权威读数的唯一途径
369
+ // (期间可写 iframe 由下面那层遮罩挡住)。
370
+ useEffect(() => {
371
+ if (model.reconcileNonce === 0)
372
+ return undefined;
373
+ const controller = new AbortController();
374
+ void observe('reconcile', controller.signal);
375
+ return () => controller.abort();
376
+ }, [model.reconcileNonce, observe]);
377
+ // 开场只读意图只兑现一次,在这块面板第一次拿到**权威**读数的时候:发现自己还
378
+ // 攥着上一轮留下的租约(标题栏接管过,行内点「终端」把它降回只读),就把它还
379
+ // 回去。这是「打开只读终端」名副其实的关键:不真的释放,只读就只是外层记了一笔,
380
+ // 面板照样可输入,用户点完「终端」看到的还是一个可写终端。
381
+ // 它必须是一次性的——兑现之后控制权只由标题栏的「接管输入 / 释放输入」驱动,
382
+ // 否则只读面板会把用户刚在标题栏按下的接管在同一拍里撤销回去。
383
+ // 平台所有者(`fixed`)恒可写、既接管不了也释放不了,直接跳过;未登录 / 触屏 /
384
+ // canControl=false(平台 teammate/guest 等)同样跳过——它们压根没有租约可还,
385
+ // 那个 POST 只会 401/403(P1-4),触屏那边更是走 viewToken 只读通道。
386
+ const openIntentApplied = useRef(null);
191
387
  useEffect(() => {
192
- if (!props.autoTakeControl || !props.authenticated || !props.capabilities.canControl
193
- || touch || phase !== 'ready')
388
+ if (props.autoTakeControl === undefined)
389
+ return;
390
+ if (!props.authenticated || !props.capabilities.canControl || touch)
194
391
  return;
195
- if (!control || control.mode !== 'readonly' || control.fixed)
392
+ if (!terminalControlSettled(model))
196
393
  return;
197
- if (autoTakeoverDone.current === props.session.sessionId)
394
+ const control = model.authoritative;
395
+ if (!control || control.fixed)
198
396
  return;
199
- autoTakeoverDone.current = props.session.sessionId;
200
- void mutate('takeover');
397
+ if (openIntentApplied.current === sessionId)
398
+ return;
399
+ openIntentApplied.current = sessionId;
400
+ if (control.mode === 'controlled' && control.owned)
401
+ void mutate('release');
201
402
  });
403
+ /**
404
+ * 这块面板**可能已经提交**的接管集合(第 16 点)。
405
+ *
406
+ * 为什么是集合而不是单值:单值每次 takeover 都覆盖上一次,于是「acq1 提交但回执
407
+ * 丢 → unknown → 用户立即重试 acq2、acq2 在到服务端之前就失败」时,客户端只剩
408
+ * acq2,服务端却仍持有 acq1;卸载补偿只 release(acq2),acq1 一路泄漏到 TTL。改成
409
+ * 集合后,本 pane POST 过的每一次 acquisition 都留着,卸载时逐个按各自的 id 做 CAS
410
+ * (服务端最多匹配其中一条,其余全部落空即无害)。
411
+ *
412
+ * 键是 acquisition id:客户端在 POST **之前**自己生成,不依赖回执——回执可能根本
413
+ * 回不来,而那恰恰是最需要补偿的场景。手动释放成功 / 恒可写身份确认后清空整份
414
+ * 集合。每条都连着 sessionId:面板换会话是重挂,但卸载清理跑在 React 之外,必须
415
+ * 自带「这是哪个会话的哪一次」才不会还错。
416
+ */
417
+ const heldAcquisitions = useRef(new Map());
418
+ /** 快照并清空「可能已提交」集合,返回待补偿的全部条目。快照 + 清空同步完成,两条
419
+ * 在途补偿就不会各自拿到同一批条目重复发请求(CAS 本身幂等,这里只是少发无谓的)。 */
420
+ const drainHeldAcquisitions = () => {
421
+ const entries = [...heldAcquisitions.current.values()];
422
+ heldAcquisitions.current.clear();
423
+ return entries;
424
+ };
425
+ /**
426
+ * 收掉一批**没人管的**接管:逐个按各自的 acquisition 做条件释放。
427
+ *
428
+ * 每一条的两条判据缺一不可:
429
+ * ① 这份文档里还有活面板攥着同一把租约 → 它不是孤儿,别动。租约挂在
430
+ * (会话 × 登录)上,第二块面板 takeover 拿到的是同一把,服务端看来两次
431
+ * 请求完全同形,只有这里分得清(同标签页内的判据)。
432
+ * ② 释放带上**那一次自己的** acquisition id,服务端按它 CAS。跨标签页 / 跨设备
433
+ * 的抢先接管只有服务端看得见,不匹配时它拒绝,用户正在打字的那块终端不受伤。
434
+ *
435
+ * 这里**不再**去 GET 一次「当前 marker」再拿它释放:事后读到的是此刻活着的那一次
436
+ * acquisition,跨标签页接管之后正是别人的那一把,拿它补偿等于精准误删。
437
+ * 幂等:租约已经没了就是 `released:false`,被别人接管过就是 `control_lease_superseded`,
438
+ * 服务端最多匹配集合里一条、其余全部落空即无害,所以随后 socket 断开再走一遍也不冲突。
439
+ */
440
+ const compensateAcquisitions = useCallback(async (targets) => {
441
+ for (const target of targets) {
442
+ if (hasLiveTerminalWriteClaim(target.sessionId))
443
+ continue;
444
+ await props.api
445
+ .releaseTerminal(target.sessionId, undefined, target.acquisitionId)
446
+ .catch(() => { });
447
+ }
448
+ }, [props.api]);
449
+ /** 在途写的计数。卸载补偿据此让路:在途那一条自己会在回执(或失败)之后补偿,
450
+ * 它比卸载那一刻知道得更多,两边都发只是多一次必然被拒的请求。 */
451
+ const writesInFlight = useRef(0);
202
452
  const mutate = async (action) => {
203
- const generation = ++requestGeneration.current;
204
- setPhase('busy');
453
+ const epoch = ++writeEpoch.current;
454
+ // POST **之前**就把这一次接管的 id 定下来,并记进「可能已提交」集合。回执丢了也
455
+ // 不影响补偿的精确度;单值覆盖会漏掉更早那次尚未收敛的接管(第 16 点)。
456
+ const acquisitionId = action === 'takeover' ? newTerminalAcquisitionId() : undefined;
457
+ if (acquisitionId)
458
+ heldAcquisitions.current.set(acquisitionId, { sessionId, acquisitionId });
459
+ writesInFlight.current += 1;
460
+ dispatch({ type: 'write-start', epoch, action });
205
461
  try {
206
- const next = action === 'takeover'
207
- ? await props.api.takeoverTerminal(props.session.sessionId)
208
- : await props.api.releaseTerminal(props.session.sessionId);
209
- if (generation !== requestGeneration.current)
462
+ // 硬超时:永久 pending POST 会把 busy / 按钮 / 卸载补偿一起焊死。
463
+ const next = await withWriteTimeout(signal => (action === 'takeover'
464
+ ? props.api.takeoverTerminal(sessionId, signal, acquisitionId)
465
+ : props.api.releaseTerminal(sessionId, signal)));
466
+ // 手动释放成功(租约已还清)/ 恒可写身份(压根没有租约)→ 整份集合清空,卸载
467
+ // 时也就没什么要补偿。takeover 成功则保留——它是本 pane 此刻握着的那把活租约,
468
+ // 连同更早提交但回执丢失的几次一并留到卸载时逐个 CAS(服务端最多匹配一条)。
469
+ if (action === 'release' || next.fixed === true)
470
+ heldAcquisitions.current.clear();
471
+ if (!mounted.current) {
472
+ // 服务端刚刚把写租约发给了这次调用,面板却已经关掉 / 换会话 / 离开工作台:把
473
+ // 集合里每一次 acquisition 都逐个 CAS 释放(release 成功已清空 → 这里空转)。
474
+ void compensateAcquisitions(drainHeldAcquisitions());
210
475
  return;
211
- applyControl(next, false);
212
- setError('');
213
- setFrameGeneration(value => value + 1);
214
- setPhase('ready');
476
+ }
477
+ dispatch({ type: 'write-settled', epoch, control: next });
215
478
  }
216
479
  catch (cause) {
217
- if (generation !== requestGeneration.current)
480
+ if (!mounted.current) {
481
+ // 失败不代表服务端没受理——超时、断连都只是丢了回执。这里**不能**裸 return,
482
+ // 也不能去问「现在的租约是谁的」,只能认自己 POST 前生成的那些 id:把本 pane
483
+ // 全部「可能已提交」的 acquisition 逐个条件释放(含这次 attempt 与更早提交但
484
+ // 回执丢失的那几次),服务端最多匹配其中一条。
485
+ void compensateAcquisitions(drainHeldAcquisitions());
218
486
  return;
219
- applyControl({ mode: 'readonly', owned: false });
220
- setError(apiErrorText(cause));
221
- setPhase('error');
487
+ }
488
+ dispatch({ type: 'write-failed', epoch, error: apiErrorText(cause) });
489
+ }
490
+ finally {
491
+ writesInFlight.current -= 1;
222
492
  }
223
493
  };
494
+ // 恒可写身份没有租约可还(服务端会 403),别把它记成「握着接管」。
495
+ useEffect(() => {
496
+ if (model.authoritative?.fixed === true)
497
+ heldAcquisitions.current.clear();
498
+ }, [model.authoritative]);
499
+ /**
500
+ * 关面板 / 换会话 = 放弃本 pane 可能已提交的每一次接管(第 15 / 16 点)。
501
+ *
502
+ * 接管已经回执、model 也已经 controlled,但新的 iframe / WS 还没连上就把终端关掉
503
+ * 时:没有在途 promise 可补偿,也没有已注册的 socket 会在断开时触发服务端那条
504
+ * disconnect —— 旧实现只撤掉本地认领,租约一路挂到 TTL 到期,同一个会话在那之前
505
+ * 谁也接管不了。这里把「可能已提交」集合里的每一次 acquisition 都做一次条件释放,
506
+ * 与 socket 随后断开触发的清理天然可重入(服务端每一条都是同一把 CAS)。
507
+ *
508
+ * 走微任务是必需的:这块面板自己那份写租约认领由另一个 effect 的清理撤销,React
509
+ * 按 effect 定义顺序跑清理,同步做判断有可能看见的是**自己**的认领而白白放弃补偿。
510
+ * `compensateRef` 同理——把函数写进依赖会让 api 引用一变就"卸载"一次。
511
+ */
512
+ const compensateRef = useRef(compensateAcquisitions);
513
+ useEffect(() => { compensateRef.current = compensateAcquisitions; });
514
+ useEffect(() => () => {
515
+ // 有写在途时让路:那一条自己会在回执 / 失败之后补偿整份集合,而且那时它知道得更多。
516
+ if (writesInFlight.current > 0)
517
+ return;
518
+ const targets = [...heldAcquisitions.current.values()];
519
+ if (targets.length === 0)
520
+ return;
521
+ heldAcquisitions.current.clear();
522
+ void Promise.resolve().then(() => compensateRef.current(targets));
523
+ }, [sessionId]);
524
+ // 只要这块面板**可能**握着写租约,就在登记簿上留个名:别的面板的迟到补偿据此
525
+ // 知道「这把租约还有人在管」。卸载时 effect 的清理先跑,补偿的续行在其后的微
526
+ // 任务里,所以补偿看到的永远是**别人**的认领,不是自己的。
527
+ const writeClaim = useRef({});
528
+ useEffect(() => {
529
+ if (!model.mayWrite)
530
+ return undefined;
531
+ return claimTerminalWrite(sessionId, writeClaim.current);
532
+ }, [model.mayWrite, sessionId]);
224
533
  const frameRef = useRef(null);
225
- const frameKey = `${props.session.sessionId}-${frameGeneration}`;
534
+ /**
535
+ * 链接换代计数。短时 viewToken 到期前会**换一条链接**,那是一条全新的连接、全新的
536
+ * 授权;不把它算进 iframe / watcher 的 key,React 就会复用同一块 iframe 与同一个
537
+ * WindowProxy,上一条连接回报的写权限一路留着,UI 继续说「可输入」。
538
+ * 存的是计数不是链接本身:key 会进 DOM,token 绝不能跟着进去。
539
+ */
540
+ const linkGeneration = useRef(0);
541
+ const lastFrameUrl = useRef(undefined);
542
+ if (lastFrameUrl.current !== frameUrl) {
543
+ if (lastFrameUrl.current !== undefined)
544
+ linkGeneration.current += 1;
545
+ lastFrameUrl.current = frameUrl;
546
+ }
547
+ const frameKey = `${sessionId}-${model.frameGeneration}-l${linkGeneration.current}`;
226
548
  const readFrameStatus = props.readFrameStatus ?? readTerminalFrameStatus;
227
549
  const frameWatch = useTerminalFrameWatch({
228
550
  enabled: touch,
229
- sessionId: props.session.sessionId,
551
+ sessionId,
230
552
  frameKey,
231
553
  read: () => readFrameStatus(frameRef.current),
232
554
  });
555
+ // 触屏那条 viewToken 通道到底能不能写,只有终端页自己知道(前置代理会给平台
556
+ // owner 的 viewToken 请求补 WRITE grant,而补不补又取决于这个 WebView 带不带
557
+ // Cookie)。桌面不启用:那边的权威是控制权接口,多一个来源只会两边打架。
558
+ const readFrameWrite = props.readFrameWrite ?? readTerminalFrameWrite;
559
+ const polledFrameWrite = useTerminalFrameWrite({
560
+ enabled: touch,
561
+ frameKey,
562
+ read: () => readFrameWrite(frameRef.current),
563
+ });
564
+ // 终端页连上 WS 那一刻主动上抛的结论。它比轮询早、也比轮询确定(轮询读的是同一
565
+ // 份全局,只是要等下一拍),所以有它就以它为准。
566
+ const reportedFrameWrite = useTerminalFrameWriteReport({
567
+ enabled: touch,
568
+ frameKey,
569
+ frame: () => frameRef.current,
570
+ });
571
+ // 上抛通道一旦通过(`live`),它就是这条 frame 的权威判据:哪怕它此刻回报 `unknown`
572
+ // (重连时终端页先发的 `write:null`),也一律以它为准,**绝不**回退到轮询那份可能已
573
+ // 经过期的 writable —— 本连接在自己的新首帧确认前一律「未知」(换链/重连都算未确认)。
574
+ // 只有上抛通道还没通(首帧前、或页面取不到父页 origin 放弃了 postMessage)才交给轮询
575
+ // 兜底;轮询本身也只认 wsHasWrite,读不到同样是「未知」,不会拿 HTTP hasToken 顶替。
576
+ const frameWrite = reportedFrameWrite.live ? reportedFrameWrite.write : polledFrameWrite;
233
577
  // 终端渲染风格:容器 class 管几何(行距 / 内边距),xterm 的配色住在跨文档的
234
578
  // 终端页里,父页换 class 它收不到 —— 必须把 theme 推过去,否则会出现「工作台
235
579
  // 换了配色、终端还是旧色」的半截状态。切换和重挂 iframe 时各推一次。
@@ -244,27 +588,91 @@ export function TerminalPane(props) {
244
588
  frameWatch.onFrameLoad?.();
245
589
  pushTermAppearance();
246
590
  }, [frameWatch, pushTermAppearance]);
247
- // 触屏挂的是 viewToken 只读通道:控制权接口哪怕报「已接管」或平台所有者(fixed),这块
248
- // iframe 也送不进输入,徽标必须跟着说只读,否则和下面那行反馈自相矛盾。
249
- const controlled = !touch && control?.mode === 'controlled' && control.owned;
250
- const status = controlled ? '可输入' : '只读';
591
+ const control = model.authoritative;
592
+ const busy = terminalControlBusy(model);
593
+ const fixed = control?.fixed === true;
594
+ // 标题栏能不能切模式 —— 也就是这块面板有没有第二种状态可去。触屏(viewToken
595
+ // 通道没有租约可接管)、无 canControl 能力、恒可写身份、外部终端都没有。
596
+ const canSwitchMode = !externalTerminalUrl && !!terminalUrl && props.authenticated
597
+ && props.capabilities.canControl && !touch && !fixed;
598
+ // 触屏这块 iframe 到底能不能写,最终以终端页自己的结论为准。它还没给出结论时
599
+ // 别急着猜:非恒可写身份走的 viewToken 通道**必定**只读(前置代理只对平台所有者
600
+ // 补 WRITE grant),照实说只读;恒可写身份则取决于这个 WebView 带不带 Cookie,
601
+ // 只能先说未知,等页面自己说话——这总好过写死一句与实际能力相反的「只读」。
602
+ const touchWrite = frameWrite !== 'unknown' ? frameWrite : fixed ? 'unknown' : 'readonly';
603
+ // 用户眼里的模式。触屏不看租约:那条通道上「控制权接口说什么」和「这一页能不能
604
+ // 输入」本来就是两件事,照抄接口就会得出「UI 说只读、链路其实可写」的分裂表述
605
+ // (P1-4)。
606
+ const paneMode = touch
607
+ ? (touchWrite === 'writable' ? 'controlled' : touchWrite === 'readonly' ? 'readonly' : 'unknown')
608
+ : terminalControlMode(model);
609
+ const controlled = paneMode === 'controlled';
610
+ // 徽标只说有依据的话。首屏 GET 还在飞时说「只读」是最容易被信以为真的一句假话:
611
+ // 同一个登录留下的写租约会让这块 iframe 真的可以打字(详见状态机文件里 loading
612
+ // 那条注释),所以 loading 有自己的说法,不借用只读。
613
+ const status = controlled ? '可输入'
614
+ : paneMode === 'unknown' ? '未知'
615
+ : paneMode === 'loading' ? '检查中' : '只读';
616
+ const chipClass = controlled ? 'is-controlled'
617
+ : paneMode === 'unknown' ? 'is-unknown'
618
+ : paneMode === 'loading' ? 'is-checking' : 'is-readonly';
619
+ const chipGlyph = controlled ? '◆'
620
+ : paneMode === 'unknown' ? '◇'
621
+ : paneMode === 'loading' ? '⋯' : '◌';
622
+ // 未知且这块 iframe 可能仍然可写 → 盖住它。这一刻既不能说「已接管」也不能说
623
+ // 「只读」,而盲输入会真的打进终端(P1-3)。复核 GET 回来后遮罩自动撤掉。
624
+ const masked = !touch && terminalControlNeedsMask(model);
625
+ /**
626
+ * 这条通道有没有可能握着写租约。租约挂在(会话 × 登录)上,**不**挂在面板上:
627
+ * 同一个登录里上一块面板接管过、这块面板刚挂上来时,服务端那把写租约仍在,前置
628
+ * 代理照旧会给这个 iframe 补 WRITE grant —— 也就是说它**真的能打字**。
629
+ * `canControl=false`(平台 teammate/guest)与无凭证的只读通道则永远握不到。
630
+ */
631
+ const leaseCapableChannel = !externalTerminalUrl && !!terminalUrl && !touch
632
+ && props.authenticated && props.capabilities.canControl;
633
+ /**
634
+ * 首屏 GET 还没回来(第 9 / 9b 点):这一刻我们**什么都不知道**,而上面那条判据
635
+ * 说明它可能可写。iframe 一旦挂上去就能收键盘,等 GET 回来再撤已经晚了 —— 所以
636
+ * 能握租约的通道在 loading 期间干脆不挂。随后才会被识别为 fixed 的平台 owner 也
637
+ * 走这条:识别完成前它同样是 authenticated + canControl。
638
+ */
639
+ const withheldWhileChecking = model.phase === 'loading' && leaseCapableChannel;
640
+ const frameWithheld = masked || withheldWhileChecking;
641
+ // 把徽标看到的这份结论原样回执给外层。回执的是**渲染出来的模式**(含触屏那层
642
+ // 覆盖),不是接口原始读数——外层拿它决定行内按钮的开关语义,两边看到的必须是
643
+ // 同一个「用户眼里的模式」。sessionId 一并带上:面板换会话是重挂,回执路上不该
644
+ // 有把上一块面板的结论套到新会话头上的缝。
645
+ const onControlModeChange = props.onControlModeChange;
646
+ const toggleOnly = !canSwitchMode;
647
+ useEffect(() => {
648
+ onControlModeChange?.({ sessionId, mode: paneMode, fixed, toggleOnly, busy });
649
+ }, [busy, fixed, onControlModeChange, paneMode, sessionId, toggleOnly]);
251
650
  const expires = controlled && control?.expiresAt
252
651
  ? formatWorkbenchRelativeTime(control.expiresAt, props.now, 'zh-CN')
253
652
  : null;
254
653
  return (_jsxs("section", { className: `wb-pane wb-terminal-pane ${workbenchTermContainerClass(appearance.termStyle)}`,
255
654
  // 外壳那圈安全边距的底色 = 这一刻真正渲染出来的 xterm 底色(同一个
256
655
  // workbenchTermTheme 也在往 iframe 里推),经典渲染下才不会露出一圈更黑的边。
257
- style: workbenchTermCanvasStyle(appearance.termStyle, skin), "aria-label": "\u7EC8\u7AEF\u9762\u677F", children: [_jsxs("header", { className: "wb-pane-titlebar", children: [_jsxs("div", { className: "wb-pane-identity", children: [_jsx("span", { className: "wb-pane-glyph", "aria-hidden": "true", children: "\u203A_" }), _jsx("strong", { children: "\u7EC8\u7AEF" }), _jsxs("span", { className: `wb-mode-chip ${controlled ? 'is-controlled' : 'is-readonly'}`, children: [_jsx("span", { "aria-hidden": "true", children: controlled ? '◆' : '◌' }), status] }), expires ? _jsx("span", { className: "wb-lease-time", children: `${expires}到期` }) : null] }), _jsx(WorkbenchTermStyleSegment, { termStyle: appearance.termStyle }), _jsxs("div", { className: "wb-pane-actions", children: [frameUrl ? _jsx("a", { href: frameUrl, target: "_blank", rel: "noopener noreferrer", children: "\u65B0\u6807\u7B7E\u9875\u6253\u5F00" }) : null, !externalTerminalUrl && terminalUrl && props.authenticated
258
- && props.capabilities.canControl && !touch && !control?.fixed ? (controlled
259
- ? _jsx("button", { type: "button", disabled: phase === 'busy', onClick: () => void mutate('release'), children: "\u91CA\u653E\u8F93\u5165" })
260
- : _jsx("button", { type: "button", disabled: phase === 'busy', onClick: () => void mutate('takeover'), children: "\u63A5\u7BA1\u8F93\u5165" })) : null] })] }), _jsx("div", { className: "wb-pane-feedback", role: "status", "aria-live": "polite", children: touch ? '只读查看中。手机端为只读视图,需要输入请在电脑上操作。'
261
- : phase === 'loading' ? '正在检查终端权限…' : error || (control?.fixed
262
- ? '平台所有者身份已登录,可直接输入。'
263
- : controlled ? '已接管,可键盘输入。'
264
- // 没有接管能力的身份别劝人去点一个根本没渲染的按钮。
265
- : props.authenticated && props.capabilities.canControl ? '只读查看中,点「接管输入」可操作。'
266
- : props.authenticated ? '只读查看中,当前身份不可接管输入。'
267
- : '只读查看。登录 Dashboard 后可接管。') }), _jsx("div", { className: "wb-pane-frame-shell", children: externalTerminalUrl ? (_jsxs("div", { className: "wb-pane-empty", children: [_jsx("span", { "aria-hidden": "true", children: "\u2197" }), _jsx("strong", { children: "\u5916\u90E8 Riff \u7EC8\u7AEF" }), _jsx("p", { children: "\u8BE5\u540E\u7AEF\u4E0D\u5728\u5DE5\u4F5C\u53F0\u9762\u677F\u5185\u5C55\u793A\u3002" }), _jsx("a", { href: externalTerminalUrl, target: "_blank", rel: "noopener noreferrer", children: "\u6253\u5F00\u5916\u90E8\u7EC8\u7AEF" })] })) : frameUrl ? (_jsxs(_Fragment, { children: [_jsx("iframe", { ref: frameRef, className: "wb-pane-frame", src: frameUrl, title: `终端 — ${workbenchSessionTitle(props.session)}`, allow: "clipboard-read; clipboard-write", referrerPolicy: "no-referrer", onLoad: onFrameLoad }, frameKey), frameWatch.blocked ? (_jsxs("div", { className: "wb-pane-empty wb-ws-blocked", role: "status", children: [_jsx("span", { "aria-hidden": "true", children: "\u26A0" }), _jsx("strong", { children: "\u7EC8\u7AEF\u5B9E\u65F6\u8FDE\u63A5\u672A\u5EFA\u7ACB" }), _jsx("p", { children: "\u5F53\u524D\u6D4F\u89C8\u5668\u73AF\u5883\u9650\u5236\u4E86\u975E\u52A0\u5BC6\u7684\u5B9E\u65F6\u8FDE\u63A5\uFF08iOS \u5185\u5E38\u89C1\uFF09\u3002\u53EF\u5C1D\u8BD5\u7528\u7CFB\u7EDF\u6D4F\u89C8\u5668\u6253\u5F00\uFF0C\u6216\u5728\u7535\u8111\u4E0A\u67E5\u770B\u3002" }), _jsx("a", { href: frameUrl, target: "_blank", rel: "noopener", children: "\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00" }), _jsx("p", { children: "\u98DE\u4E66\u5185\u53EF\u901A\u8FC7\u53F3\u4E0A\u89D2 \u22EF \u83DC\u5355\u9009\u62E9\u300C\u5728\u6D4F\u89C8\u5668\u6253\u5F00\u300D\u3002" })] })) : null] })) : (touch || !props.authenticated) && !externalTerminalUrl && terminalUrl ? (
656
+ style: workbenchTermCanvasStyle(appearance.termStyle, skin), "aria-label": "\u7EC8\u7AEF\u9762\u677F", children: [_jsxs("header", { className: "wb-pane-titlebar", children: [_jsxs("div", { className: "wb-pane-identity", children: [_jsx("span", { className: "wb-pane-glyph", "aria-hidden": "true", children: "\u203A_" }), _jsx("strong", { children: "\u7EC8\u7AEF" }), _jsxs("span", { className: `wb-mode-chip ${chipClass}`, children: [_jsx("span", { "aria-hidden": "true", children: chipGlyph }), status] }), expires ? _jsx("span", { className: "wb-lease-time", children: `${expires}到期` }) : null] }), _jsx(WorkbenchTermStyleSegment, { termStyle: appearance.termStyle }), _jsxs("div", { className: "wb-pane-actions", children: [frameUrl ? _jsx("a", { href: frameUrl, target: "_blank", rel: "noopener noreferrer", children: "\u65B0\u6807\u7B7E\u9875\u6253\u5F00" }) : null, canSwitchMode ? (controlled
657
+ ? _jsx("button", { type: "button", disabled: busy, onClick: () => void mutate('release'), children: "\u91CA\u653E\u8F93\u5165" })
658
+ : _jsx("button", { type: "button", disabled: busy, onClick: () => void mutate('takeover'), children: "\u63A5\u7BA1\u8F93\u5165" })) : null] })] }), _jsx("div", { className: "wb-pane-feedback", role: "status", "aria-live": "polite", children: touch ? (controlled
659
+ ? '当前身份在手机端也可直接输入。'
660
+ : paneMode === 'unknown'
661
+ ? '只读链接查看中,能否输入以终端页内的提示为准。'
662
+ : '只读查看中。手机端为只读视图,需要输入请在电脑上操作。')
663
+ : model.phase === 'loading' ? '正在检查终端权限…'
664
+ : model.phase === 'taking-over' ? '正在接管输入…'
665
+ : model.phase === 'releasing' ? '正在释放输入…'
666
+ // 失败要连着「现在怎么办」一起说:只报错不说状态,用户会以为界面
667
+ // 上写的只读就是真的(P1-3)。
668
+ : model.error ? `${model.error.text}${model.phase === 'unknown' ? ';控制权状态未知,正在重新确认…' : ''}`
669
+ : model.phase === 'unknown' ? '控制权状态未知,正在重新确认…'
670
+ : control?.fixed ? '平台所有者身份已登录,可直接输入。'
671
+ : controlled ? '已接管,可键盘输入。'
672
+ // 没有接管能力的身份别劝人去点一个根本没渲染的按钮。
673
+ : props.authenticated && props.capabilities.canControl ? '只读查看中,点「接管输入」可操作。'
674
+ : props.authenticated ? '只读查看中,当前身份不可接管输入。'
675
+ : '只读查看。登录 Dashboard 后可接管。' }), _jsx("div", { className: "wb-pane-frame-shell", children: externalTerminalUrl ? (_jsxs("div", { className: "wb-pane-empty", children: [_jsx("span", { "aria-hidden": "true", children: "\u2197" }), _jsx("strong", { children: "\u5916\u90E8 Riff \u7EC8\u7AEF" }), _jsx("p", { children: "\u8BE5\u540E\u7AEF\u4E0D\u5728\u5DE5\u4F5C\u53F0\u9762\u677F\u5185\u5C55\u793A\u3002" }), _jsx("a", { href: externalTerminalUrl, target: "_blank", rel: "noopener noreferrer", children: "\u6253\u5F00\u5916\u90E8\u7EC8\u7AEF" })] })) : frameUrl ? (_jsxs(_Fragment, { children: [frameWithheld ? null : (_jsx("iframe", { ref: frameRef, className: "wb-pane-frame", src: frameUrl, title: `终端 — ${workbenchSessionTitle(props.session)}`, allow: "clipboard-read; clipboard-write", referrerPolicy: "no-referrer", onLoad: onFrameLoad }, frameKey)), frameWatch.blocked && !frameWithheld ? (_jsxs("div", { className: "wb-pane-empty wb-ws-blocked", role: "status", children: [_jsx("span", { "aria-hidden": "true", children: "\u26A0" }), _jsx("strong", { children: "\u7EC8\u7AEF\u5B9E\u65F6\u8FDE\u63A5\u672A\u5EFA\u7ACB" }), _jsx("p", { children: "\u5F53\u524D\u6D4F\u89C8\u5668\u73AF\u5883\u9650\u5236\u4E86\u975E\u52A0\u5BC6\u7684\u5B9E\u65F6\u8FDE\u63A5\uFF08iOS \u5185\u5E38\u89C1\uFF09\u3002\u53EF\u5C1D\u8BD5\u7528\u7CFB\u7EDF\u6D4F\u89C8\u5668\u6253\u5F00\uFF0C\u6216\u5728\u7535\u8111\u4E0A\u67E5\u770B\u3002" }), _jsx("a", { href: frameUrl, target: "_blank", rel: "noopener", children: "\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00" }), _jsx("p", { children: "\u98DE\u4E66\u5185\u53EF\u901A\u8FC7\u53F3\u4E0A\u89D2 \u22EF \u83DC\u5355\u9009\u62E9\u300C\u5728\u6D4F\u89C8\u5668\u6253\u5F00\u300D\u3002" })] })) : null, masked ? _jsx(TerminalControlUnknown, { onRetry: () => void observe('reconcile') }) : null, !masked && withheldWhileChecking ? (_jsxs("div", { className: "wb-pane-empty wb-control-checking", role: "status", children: [_jsx("span", { "aria-hidden": "true", children: "\u22EF" }), _jsx("strong", { children: "\u6B63\u5728\u786E\u8BA4\u7EC8\u7AEF\u63A7\u5236\u6743" }), _jsx("p", { children: "\u786E\u8BA4\u4E4B\u524D\u5148\u4E0D\u63A5\u5165\u7EC8\u7AEF\u753B\u9762\uFF0C\u907F\u514D\u5728\u4E0D\u77E5\u9053\u80FD\u4E0D\u80FD\u5199\u7684\u60C5\u51B5\u4E0B\u628A\u8F93\u5165\u6253\u8FDB\u53BB\u3002" })] })) : null] })) : (touch || !props.authenticated) && !externalTerminalUrl && terminalUrl ? (
268
676
  // A terminal exists, we just have no credential for it yet. Say so
269
677
  // instead of framing a URL that would answer "Forbidden". 触屏即使已登录也走
270
678
  // 这里:它等的是 viewToken 链接,同源地址对它等同于没有凭证。