botmux 3.14.0 → 3.16.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 (812) hide show
  1. package/README.en.md +2 -2
  2. package/README.md +2 -2
  3. package/dist/.runtime-build-id +1 -1
  4. package/dist/adapters/backend/destroy-result.d.ts +159 -0
  5. package/dist/adapters/backend/destroy-result.d.ts.map +1 -0
  6. package/dist/adapters/backend/destroy-result.js +223 -0
  7. package/dist/adapters/backend/destroy-result.js.map +1 -0
  8. package/dist/adapters/backend/herdr-backend.d.ts +4 -0
  9. package/dist/adapters/backend/herdr-backend.d.ts.map +1 -1
  10. package/dist/adapters/backend/herdr-backend.js +23 -1
  11. package/dist/adapters/backend/herdr-backend.js.map +1 -1
  12. package/dist/adapters/backend/mojo-backend.d.ts +577 -0
  13. package/dist/adapters/backend/mojo-backend.d.ts.map +1 -0
  14. package/dist/adapters/backend/mojo-backend.js +2471 -0
  15. package/dist/adapters/backend/mojo-backend.js.map +1 -0
  16. package/dist/adapters/backend/mojo-budgets.d.ts +21 -0
  17. package/dist/adapters/backend/mojo-budgets.d.ts.map +1 -0
  18. package/dist/adapters/backend/mojo-budgets.js +22 -0
  19. package/dist/adapters/backend/mojo-budgets.js.map +1 -0
  20. package/dist/adapters/backend/mojo-isolated-workspace.d.ts +69 -0
  21. package/dist/adapters/backend/mojo-isolated-workspace.d.ts.map +1 -0
  22. package/dist/adapters/backend/mojo-isolated-workspace.js +213 -0
  23. package/dist/adapters/backend/mojo-isolated-workspace.js.map +1 -0
  24. package/dist/adapters/backend/mojo-process-tree.d.ts +292 -0
  25. package/dist/adapters/backend/mojo-process-tree.d.ts.map +1 -0
  26. package/dist/adapters/backend/mojo-process-tree.js +486 -0
  27. package/dist/adapters/backend/mojo-process-tree.js.map +1 -0
  28. package/dist/adapters/backend/mojo-types.d.ts +542 -0
  29. package/dist/adapters/backend/mojo-types.d.ts.map +1 -0
  30. package/dist/adapters/backend/mojo-types.js +682 -0
  31. package/dist/adapters/backend/mojo-types.js.map +1 -0
  32. package/dist/adapters/backend/reproduce-command.js +3 -3
  33. package/dist/adapters/backend/reproduce-command.js.map +1 -1
  34. package/dist/adapters/backend/riff-backend.js +1 -1
  35. package/dist/adapters/backend/riff-backend.js.map +1 -1
  36. package/dist/adapters/backend/sandbox.d.ts +44 -7
  37. package/dist/adapters/backend/sandbox.d.ts.map +1 -1
  38. package/dist/adapters/backend/sandbox.js +30 -7
  39. package/dist/adapters/backend/sandbox.js.map +1 -1
  40. package/dist/adapters/backend/session-backend-selector.d.ts +14 -1
  41. package/dist/adapters/backend/session-backend-selector.d.ts.map +1 -1
  42. package/dist/adapters/backend/session-backend-selector.js +82 -10
  43. package/dist/adapters/backend/session-backend-selector.js.map +1 -1
  44. package/dist/adapters/backend/tmux-pipe-backend.d.ts +30 -0
  45. package/dist/adapters/backend/tmux-pipe-backend.d.ts.map +1 -1
  46. package/dist/adapters/backend/tmux-pipe-backend.js +60 -0
  47. package/dist/adapters/backend/tmux-pipe-backend.js.map +1 -1
  48. package/dist/adapters/backend/types.d.ts +108 -6
  49. package/dist/adapters/backend/types.d.ts.map +1 -1
  50. package/dist/adapters/backend/types.js.map +1 -1
  51. package/dist/adapters/backend/zmx-backend.d.ts +11 -0
  52. package/dist/adapters/backend/zmx-backend.d.ts.map +1 -1
  53. package/dist/adapters/backend/zmx-backend.js +16 -1
  54. package/dist/adapters/backend/zmx-backend.js.map +1 -1
  55. package/dist/adapters/cli/codex-app.d.ts.map +1 -1
  56. package/dist/adapters/cli/codex-app.js +2 -2
  57. package/dist/adapters/cli/codex-app.js.map +1 -1
  58. package/dist/adapters/cli/codex.d.ts.map +1 -1
  59. package/dist/adapters/cli/codex.js +7 -0
  60. package/dist/adapters/cli/codex.js.map +1 -1
  61. package/dist/adapters/cli/copilot.d.ts +3 -1
  62. package/dist/adapters/cli/copilot.d.ts.map +1 -1
  63. package/dist/adapters/cli/copilot.js +23 -5
  64. package/dist/adapters/cli/copilot.js.map +1 -1
  65. package/dist/adapters/cli/cursor.d.ts.map +1 -1
  66. package/dist/adapters/cli/cursor.js +26 -4
  67. package/dist/adapters/cli/cursor.js.map +1 -1
  68. package/dist/adapters/cli/dsh.d.ts.map +1 -1
  69. package/dist/adapters/cli/dsh.js +5 -1
  70. package/dist/adapters/cli/dsh.js.map +1 -1
  71. package/dist/adapters/cli/grok.d.ts.map +1 -1
  72. package/dist/adapters/cli/grok.js +24 -5
  73. package/dist/adapters/cli/grok.js.map +1 -1
  74. package/dist/adapters/cli/kimi.d.ts.map +1 -1
  75. package/dist/adapters/cli/kimi.js +19 -1
  76. package/dist/adapters/cli/kimi.js.map +1 -1
  77. package/dist/adapters/cli/mojo.d.ts +28 -0
  78. package/dist/adapters/cli/mojo.d.ts.map +1 -0
  79. package/dist/adapters/cli/mojo.js +58 -0
  80. package/dist/adapters/cli/mojo.js.map +1 -0
  81. package/dist/adapters/cli/pi.d.ts +20 -5
  82. package/dist/adapters/cli/pi.d.ts.map +1 -1
  83. package/dist/adapters/cli/pi.js +27 -5
  84. package/dist/adapters/cli/pi.js.map +1 -1
  85. package/dist/adapters/cli/read-isolation.d.ts +251 -1
  86. package/dist/adapters/cli/read-isolation.d.ts.map +1 -1
  87. package/dist/adapters/cli/read-isolation.js +254 -1
  88. package/dist/adapters/cli/read-isolation.js.map +1 -1
  89. package/dist/adapters/cli/registry.d.ts +13 -1
  90. package/dist/adapters/cli/registry.d.ts.map +1 -1
  91. package/dist/adapters/cli/registry.js +19 -1
  92. package/dist/adapters/cli/registry.js.map +1 -1
  93. package/dist/adapters/cli/runner-input.d.ts +3 -3
  94. package/dist/adapters/cli/runner-input.d.ts.map +1 -1
  95. package/dist/adapters/cli/runner-input.js +4 -3
  96. package/dist/adapters/cli/runner-input.js.map +1 -1
  97. package/dist/adapters/cli/shared-hints.d.ts +3 -1
  98. package/dist/adapters/cli/shared-hints.d.ts.map +1 -1
  99. package/dist/adapters/cli/shared-hints.js +51 -30
  100. package/dist/adapters/cli/shared-hints.js.map +1 -1
  101. package/dist/adapters/cli/types.d.ts +33 -4
  102. package/dist/adapters/cli/types.d.ts.map +1 -1
  103. package/dist/bot-registry.d.ts +42 -0
  104. package/dist/bot-registry.d.ts.map +1 -1
  105. package/dist/bot-registry.js +54 -4
  106. package/dist/bot-registry.js.map +1 -1
  107. package/dist/cli/arg-utils.d.ts +5 -0
  108. package/dist/cli/arg-utils.d.ts.map +1 -1
  109. package/dist/cli/arg-utils.js +7 -0
  110. package/dist/cli/arg-utils.js.map +1 -1
  111. package/dist/cli/bots-list-output.d.ts +130 -2
  112. package/dist/cli/bots-list-output.d.ts.map +1 -1
  113. package/dist/cli/bots-list-output.js +173 -27
  114. package/dist/cli/bots-list-output.js.map +1 -1
  115. package/dist/cli/daemon-lifecycle-env.d.ts +10 -3
  116. package/dist/cli/daemon-lifecycle-env.d.ts.map +1 -1
  117. package/dist/cli/daemon-lifecycle-env.js +38 -11
  118. package/dist/cli/daemon-lifecycle-env.js.map +1 -1
  119. package/dist/cli/dashboard-command.d.ts +15 -0
  120. package/dist/cli/dashboard-command.d.ts.map +1 -1
  121. package/dist/cli/dashboard-command.js +22 -0
  122. package/dist/cli/dashboard-command.js.map +1 -1
  123. package/dist/cli/log-tail.d.ts +54 -0
  124. package/dist/cli/log-tail.d.ts.map +1 -0
  125. package/dist/cli/log-tail.js +167 -0
  126. package/dist/cli/log-tail.js.map +1 -0
  127. package/dist/cli/pm2-env.d.ts +16 -0
  128. package/dist/cli/pm2-env.d.ts.map +1 -0
  129. package/dist/cli/pm2-env.js +63 -0
  130. package/dist/cli/pm2-env.js.map +1 -0
  131. package/dist/cli/pm2-fleet-lock.d.ts +26 -0
  132. package/dist/cli/pm2-fleet-lock.d.ts.map +1 -0
  133. package/dist/cli/pm2-fleet-lock.js +50 -0
  134. package/dist/cli/pm2-fleet-lock.js.map +1 -0
  135. package/dist/cli/pm2-god-retirement.d.ts +59 -0
  136. package/dist/cli/pm2-god-retirement.d.ts.map +1 -0
  137. package/dist/cli/pm2-god-retirement.js +95 -0
  138. package/dist/cli/pm2-god-retirement.js.map +1 -0
  139. package/dist/cli/session-list-wake.d.ts +41 -0
  140. package/dist/cli/session-list-wake.d.ts.map +1 -0
  141. package/dist/cli/session-list-wake.js +102 -0
  142. package/dist/cli/session-list-wake.js.map +1 -0
  143. package/dist/cli/workflow.d.ts.map +1 -1
  144. package/dist/cli/workflow.js +26 -0
  145. package/dist/cli/workflow.js.map +1 -1
  146. package/dist/cli.d.ts.map +1 -1
  147. package/dist/cli.js +941 -118
  148. package/dist/cli.js.map +1 -1
  149. package/dist/codex-app-runner.js +261 -49
  150. package/dist/codex-app-runner.js.map +1 -1
  151. package/dist/core/cli-runtime-update.d.ts +8 -0
  152. package/dist/core/cli-runtime-update.d.ts.map +1 -1
  153. package/dist/core/cli-runtime-update.js +127 -8
  154. package/dist/core/cli-runtime-update.js.map +1 -1
  155. package/dist/core/close-residual.d.ts +64 -0
  156. package/dist/core/close-residual.d.ts.map +1 -0
  157. package/dist/core/close-residual.js +93 -0
  158. package/dist/core/close-residual.js.map +1 -0
  159. package/dist/core/closed-session-card.d.ts.map +1 -1
  160. package/dist/core/closed-session-card.js +11 -3
  161. package/dist/core/closed-session-card.js.map +1 -1
  162. package/dist/core/command-discovery.d.ts.map +1 -1
  163. package/dist/core/command-discovery.js +4 -1
  164. package/dist/core/command-discovery.js.map +1 -1
  165. package/dist/core/command-handler.d.ts.map +1 -1
  166. package/dist/core/command-handler.js +129 -27
  167. package/dist/core/command-handler.js.map +1 -1
  168. package/dist/core/cost-calculator.d.ts +3 -1
  169. package/dist/core/cost-calculator.d.ts.map +1 -1
  170. package/dist/core/cost-calculator.js +230 -1
  171. package/dist/core/cost-calculator.js.map +1 -1
  172. package/dist/core/daemon-background-close.d.ts +19 -0
  173. package/dist/core/daemon-background-close.d.ts.map +1 -0
  174. package/dist/core/daemon-background-close.js +39 -0
  175. package/dist/core/daemon-background-close.js.map +1 -0
  176. package/dist/core/dashboard-command/overview.d.ts +4 -0
  177. package/dist/core/dashboard-command/overview.d.ts.map +1 -1
  178. package/dist/core/dashboard-command/overview.js +8 -1
  179. package/dist/core/dashboard-command/overview.js.map +1 -1
  180. package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
  181. package/dist/core/dashboard-ipc-server.js +415 -42
  182. package/dist/core/dashboard-ipc-server.js.map +1 -1
  183. package/dist/core/dashboard-rows.d.ts +4 -0
  184. package/dist/core/dashboard-rows.d.ts.map +1 -1
  185. package/dist/core/dashboard-rows.js +3 -0
  186. package/dist/core/dashboard-rows.js.map +1 -1
  187. package/dist/core/dashboard-url.d.ts +37 -0
  188. package/dist/core/dashboard-url.d.ts.map +1 -1
  189. package/dist/core/dashboard-url.js +71 -0
  190. package/dist/core/dashboard-url.js.map +1 -1
  191. package/dist/core/deferred-schedule-settlement.d.ts +15 -1
  192. package/dist/core/deferred-schedule-settlement.d.ts.map +1 -1
  193. package/dist/core/deferred-schedule-settlement.js +4 -1
  194. package/dist/core/deferred-schedule-settlement.js.map +1 -1
  195. package/dist/core/device-isolation-daemon.d.ts +91 -1
  196. package/dist/core/device-isolation-daemon.d.ts.map +1 -1
  197. package/dist/core/device-isolation-daemon.js +346 -2
  198. package/dist/core/device-isolation-daemon.js.map +1 -1
  199. package/dist/core/local-terminal-opener.d.ts.map +1 -1
  200. package/dist/core/local-terminal-opener.js +5 -1
  201. package/dist/core/local-terminal-opener.js.map +1 -1
  202. package/dist/core/loopback-target.d.ts +14 -0
  203. package/dist/core/loopback-target.d.ts.map +1 -0
  204. package/dist/core/loopback-target.js +20 -0
  205. package/dist/core/loopback-target.js.map +1 -0
  206. package/dist/core/maintenance.d.ts.map +1 -1
  207. package/dist/core/maintenance.js +16 -2
  208. package/dist/core/maintenance.js.map +1 -1
  209. package/dist/core/mojo-containment-command.d.ts +20 -0
  210. package/dist/core/mojo-containment-command.d.ts.map +1 -0
  211. package/dist/core/mojo-containment-command.js +266 -0
  212. package/dist/core/mojo-containment-command.js.map +1 -0
  213. package/dist/core/mojo-containment.d.ts +575 -0
  214. package/dist/core/mojo-containment.d.ts.map +1 -0
  215. package/dist/core/mojo-containment.js +1158 -0
  216. package/dist/core/mojo-containment.js.map +1 -0
  217. package/dist/core/mojo-launcher-env-quarantine.d.ts +30 -0
  218. package/dist/core/mojo-launcher-env-quarantine.d.ts.map +1 -0
  219. package/dist/core/mojo-launcher-env-quarantine.js +175 -0
  220. package/dist/core/mojo-launcher-env-quarantine.js.map +1 -0
  221. package/dist/core/mojo-session-identity.d.ts +3 -0
  222. package/dist/core/mojo-session-identity.d.ts.map +1 -0
  223. package/dist/core/mojo-session-identity.js +126 -0
  224. package/dist/core/mojo-session-identity.js.map +1 -0
  225. package/dist/core/persistent-backend.d.ts +32 -8
  226. package/dist/core/persistent-backend.d.ts.map +1 -1
  227. package/dist/core/persistent-backend.js +55 -17
  228. package/dist/core/persistent-backend.js.map +1 -1
  229. package/dist/core/plugins/mcp/gateway.d.ts +14 -1
  230. package/dist/core/plugins/mcp/gateway.d.ts.map +1 -1
  231. package/dist/core/plugins/mcp/gateway.js +70 -3
  232. package/dist/core/plugins/mcp/gateway.js.map +1 -1
  233. package/dist/core/plugins/mcp/host.d.ts +2 -0
  234. package/dist/core/plugins/mcp/host.d.ts.map +1 -1
  235. package/dist/core/plugins/mcp/host.js +2 -0
  236. package/dist/core/plugins/mcp/host.js.map +1 -1
  237. package/dist/core/plugins/pm2.d.ts.map +1 -1
  238. package/dist/core/plugins/pm2.js +16 -3
  239. package/dist/core/plugins/pm2.js.map +1 -1
  240. package/dist/core/plugins/service-manager.d.ts +9 -0
  241. package/dist/core/plugins/service-manager.d.ts.map +1 -1
  242. package/dist/core/plugins/service-manager.js +33 -16
  243. package/dist/core/plugins/service-manager.js.map +1 -1
  244. package/dist/core/preview-port-owner.d.ts +109 -0
  245. package/dist/core/preview-port-owner.d.ts.map +1 -0
  246. package/dist/core/preview-port-owner.js +320 -0
  247. package/dist/core/preview-port-owner.js.map +1 -0
  248. package/dist/core/remote-cli-ids.d.ts +5 -0
  249. package/dist/core/remote-cli-ids.d.ts.map +1 -0
  250. package/dist/core/remote-cli-ids.js +24 -0
  251. package/dist/core/remote-cli-ids.js.map +1 -0
  252. package/dist/core/{riff-shutdown-detach.d.ts → remote-shutdown-detach.d.ts} +40 -39
  253. package/dist/core/remote-shutdown-detach.d.ts.map +1 -0
  254. package/dist/core/{riff-shutdown-detach.js → remote-shutdown-detach.js} +85 -83
  255. package/dist/core/remote-shutdown-detach.js.map +1 -0
  256. package/dist/core/{riff-worker-shutdown-readiness.d.ts → remote-worker-shutdown-readiness.d.ts} +5 -4
  257. package/dist/core/remote-worker-shutdown-readiness.d.ts.map +1 -0
  258. package/dist/core/{riff-worker-shutdown-readiness.js → remote-worker-shutdown-readiness.js} +4 -3
  259. package/dist/core/remote-worker-shutdown-readiness.js.map +1 -0
  260. package/dist/core/reply-target.d.ts +16 -1
  261. package/dist/core/reply-target.d.ts.map +1 -1
  262. package/dist/core/reply-target.js +61 -0
  263. package/dist/core/reply-target.js.map +1 -1
  264. package/dist/core/session-group-birth.d.ts +26 -0
  265. package/dist/core/session-group-birth.d.ts.map +1 -1
  266. package/dist/core/session-group-birth.js +81 -4
  267. package/dist/core/session-group-birth.js.map +1 -1
  268. package/dist/core/session-manager.d.ts +15 -2
  269. package/dist/core/session-manager.d.ts.map +1 -1
  270. package/dist/core/session-manager.js +259 -5
  271. package/dist/core/session-manager.js.map +1 -1
  272. package/dist/core/session-model.d.ts +46 -0
  273. package/dist/core/session-model.d.ts.map +1 -0
  274. package/dist/core/session-model.js +42 -0
  275. package/dist/core/session-model.js.map +1 -0
  276. package/dist/core/session-owner-reminder.d.ts +63 -0
  277. package/dist/core/session-owner-reminder.d.ts.map +1 -0
  278. package/dist/core/session-owner-reminder.js +196 -0
  279. package/dist/core/session-owner-reminder.js.map +1 -0
  280. package/dist/core/session-preview-registry.d.ts +53 -0
  281. package/dist/core/session-preview-registry.d.ts.map +1 -0
  282. package/dist/core/session-preview-registry.js +82 -0
  283. package/dist/core/session-preview-registry.js.map +1 -0
  284. package/dist/core/session-preview.d.ts +114 -0
  285. package/dist/core/session-preview.d.ts.map +1 -0
  286. package/dist/core/session-preview.js +184 -0
  287. package/dist/core/session-preview.js.map +1 -0
  288. package/dist/core/session-rename.d.ts.map +1 -1
  289. package/dist/core/session-rename.js +5 -3
  290. package/dist/core/session-rename.js.map +1 -1
  291. package/dist/core/session-wake-deadline.d.ts +5 -0
  292. package/dist/core/session-wake-deadline.d.ts.map +1 -0
  293. package/dist/core/session-wake-deadline.js +13 -0
  294. package/dist/core/session-wake-deadline.js.map +1 -0
  295. package/dist/core/shutdown-budgets.d.ts +7 -6
  296. package/dist/core/shutdown-budgets.d.ts.map +1 -1
  297. package/dist/core/shutdown-budgets.js +11 -10
  298. package/dist/core/shutdown-budgets.js.map +1 -1
  299. package/dist/core/skills/frontmatter.d.ts.map +1 -1
  300. package/dist/core/skills/frontmatter.js +6 -1
  301. package/dist/core/skills/frontmatter.js.map +1 -1
  302. package/dist/core/terminal-control-grant.d.ts +74 -0
  303. package/dist/core/terminal-control-grant.d.ts.map +1 -0
  304. package/dist/core/terminal-control-grant.js +183 -0
  305. package/dist/core/terminal-control-grant.js.map +1 -0
  306. package/dist/core/terminal-proxy.d.ts.map +1 -1
  307. package/dist/core/terminal-proxy.js +4 -5
  308. package/dist/core/terminal-proxy.js.map +1 -1
  309. package/dist/core/terminal-write-auth.d.ts +4 -11
  310. package/dist/core/terminal-write-auth.d.ts.map +1 -1
  311. package/dist/core/terminal-write-auth.js +19 -16
  312. package/dist/core/terminal-write-auth.js.map +1 -1
  313. package/dist/core/trigger-session.d.ts.map +1 -1
  314. package/dist/core/trigger-session.js +36 -29
  315. package/dist/core/trigger-session.js.map +1 -1
  316. package/dist/core/types.d.ts +88 -10
  317. package/dist/core/types.d.ts.map +1 -1
  318. package/dist/core/types.js +9 -7
  319. package/dist/core/types.js.map +1 -1
  320. package/dist/core/workbench-link.d.ts +81 -0
  321. package/dist/core/workbench-link.d.ts.map +1 -0
  322. package/dist/core/workbench-link.js +109 -0
  323. package/dist/core/workbench-link.js.map +1 -0
  324. package/dist/core/worker-pool.d.ts +312 -11
  325. package/dist/core/worker-pool.d.ts.map +1 -1
  326. package/dist/core/worker-pool.js +2515 -217
  327. package/dist/core/worker-pool.js.map +1 -1
  328. package/dist/daemon.d.ts +6 -0
  329. package/dist/daemon.d.ts.map +1 -1
  330. package/dist/daemon.js +447 -116
  331. package/dist/daemon.js.map +1 -1
  332. package/dist/dashboard/aggregator.d.ts +33 -3
  333. package/dist/dashboard/aggregator.d.ts.map +1 -1
  334. package/dist/dashboard/aggregator.js +93 -17
  335. package/dist/dashboard/aggregator.js.map +1 -1
  336. package/dist/dashboard/auth-session-connections.d.ts +59 -0
  337. package/dist/dashboard/auth-session-connections.d.ts.map +1 -0
  338. package/dist/dashboard/auth-session-connections.js +109 -0
  339. package/dist/dashboard/auth-session-connections.js.map +1 -0
  340. package/dist/dashboard/auth.d.ts +99 -1
  341. package/dist/dashboard/auth.d.ts.map +1 -1
  342. package/dist/dashboard/auth.js +176 -64
  343. package/dist/dashboard/auth.js.map +1 -1
  344. package/dist/dashboard/bot-payload.d.ts +5 -0
  345. package/dist/dashboard/bot-payload.d.ts.map +1 -1
  346. package/dist/dashboard/bot-payload.js +4 -0
  347. package/dist/dashboard/bot-payload.js.map +1 -1
  348. package/dist/dashboard/control-audit.d.ts +112 -0
  349. package/dist/dashboard/control-audit.d.ts.map +1 -0
  350. package/dist/dashboard/control-audit.js +195 -0
  351. package/dist/dashboard/control-audit.js.map +1 -0
  352. package/dist/dashboard/control-csrf.d.ts +123 -0
  353. package/dist/dashboard/control-csrf.d.ts.map +1 -0
  354. package/dist/dashboard/control-csrf.js +392 -0
  355. package/dist/dashboard/control-csrf.js.map +1 -0
  356. package/dist/dashboard/daemon-reconcile.d.ts +26 -0
  357. package/dist/dashboard/daemon-reconcile.d.ts.map +1 -0
  358. package/dist/dashboard/daemon-reconcile.js +46 -0
  359. package/dist/dashboard/daemon-reconcile.js.map +1 -0
  360. package/dist/dashboard/debug-terminal.d.ts.map +1 -1
  361. package/dist/dashboard/debug-terminal.js +18 -4
  362. package/dist/dashboard/debug-terminal.js.map +1 -1
  363. package/dist/dashboard/events-sse.d.ts +60 -0
  364. package/dist/dashboard/events-sse.d.ts.map +1 -0
  365. package/dist/dashboard/events-sse.js +63 -0
  366. package/dist/dashboard/events-sse.js.map +1 -0
  367. package/dist/dashboard/h5-auth.d.ts +256 -0
  368. package/dist/dashboard/h5-auth.d.ts.map +1 -0
  369. package/dist/dashboard/h5-auth.js +825 -0
  370. package/dist/dashboard/h5-auth.js.map +1 -0
  371. package/dist/dashboard/managed-spawn.d.ts +36 -0
  372. package/dist/dashboard/managed-spawn.d.ts.map +1 -0
  373. package/dist/dashboard/managed-spawn.js +169 -0
  374. package/dist/dashboard/managed-spawn.js.map +1 -0
  375. package/dist/dashboard/preview-content-capability.d.ts +41 -0
  376. package/dist/dashboard/preview-content-capability.d.ts.map +1 -0
  377. package/dist/dashboard/preview-content-capability.js +127 -0
  378. package/dist/dashboard/preview-content-capability.js.map +1 -0
  379. package/dist/dashboard/preview-contract.d.ts +85 -0
  380. package/dist/dashboard/preview-contract.d.ts.map +1 -0
  381. package/dist/dashboard/preview-contract.js +198 -0
  382. package/dist/dashboard/preview-contract.js.map +1 -0
  383. package/dist/dashboard/preview-guard-page.d.ts +52 -0
  384. package/dist/dashboard/preview-guard-page.d.ts.map +1 -0
  385. package/dist/dashboard/preview-guard-page.js +162 -0
  386. package/dist/dashboard/preview-guard-page.js.map +1 -0
  387. package/dist/dashboard/preview-interaction.d.ts +57 -0
  388. package/dist/dashboard/preview-interaction.d.ts.map +1 -0
  389. package/dist/dashboard/preview-interaction.js +198 -0
  390. package/dist/dashboard/preview-interaction.js.map +1 -0
  391. package/dist/dashboard/preview-proxy.d.ts +113 -0
  392. package/dist/dashboard/preview-proxy.d.ts.map +1 -0
  393. package/dist/dashboard/preview-proxy.js +543 -0
  394. package/dist/dashboard/preview-proxy.js.map +1 -0
  395. package/dist/dashboard/public-redact.d.ts +23 -0
  396. package/dist/dashboard/public-redact.d.ts.map +1 -1
  397. package/dist/dashboard/public-redact.js +90 -21
  398. package/dist/dashboard/public-redact.js.map +1 -1
  399. package/dist/dashboard/request-identity.d.ts +77 -0
  400. package/dist/dashboard/request-identity.d.ts.map +1 -0
  401. package/dist/dashboard/request-identity.js +72 -0
  402. package/dist/dashboard/request-identity.js.map +1 -0
  403. package/dist/dashboard/session-cleanup.d.ts +5 -0
  404. package/dist/dashboard/session-cleanup.d.ts.map +1 -1
  405. package/dist/dashboard/session-cleanup.js +4 -0
  406. package/dist/dashboard/session-cleanup.js.map +1 -1
  407. package/dist/dashboard/settings-write-applier.d.ts +5 -1
  408. package/dist/dashboard/settings-write-applier.d.ts.map +1 -1
  409. package/dist/dashboard/settings-write-applier.js +14 -0
  410. package/dist/dashboard/settings-write-applier.js.map +1 -1
  411. package/dist/dashboard/standing-link.d.ts +76 -0
  412. package/dist/dashboard/standing-link.d.ts.map +1 -0
  413. package/dist/dashboard/standing-link.js +111 -0
  414. package/dist/dashboard/standing-link.js.map +1 -0
  415. package/dist/dashboard/terminal-control.d.ts +105 -0
  416. package/dist/dashboard/terminal-control.d.ts.map +1 -0
  417. package/dist/dashboard/terminal-control.js +296 -0
  418. package/dist/dashboard/terminal-control.js.map +1 -0
  419. package/dist/dashboard/terminal-front-proxy.d.ts +55 -0
  420. package/dist/dashboard/terminal-front-proxy.d.ts.map +1 -0
  421. package/dist/dashboard/terminal-front-proxy.js +397 -0
  422. package/dist/dashboard/terminal-front-proxy.js.map +1 -0
  423. package/dist/dashboard/terminal-view-capability.d.ts +97 -0
  424. package/dist/dashboard/terminal-view-capability.d.ts.map +1 -0
  425. package/dist/dashboard/terminal-view-capability.js +145 -0
  426. package/dist/dashboard/terminal-view-capability.js.map +1 -0
  427. package/dist/dashboard/web/agent-workbench-api.d.ts +59 -0
  428. package/dist/dashboard/web/agent-workbench-api.d.ts.map +1 -0
  429. package/dist/dashboard/web/agent-workbench-api.js +227 -0
  430. package/dist/dashboard/web/agent-workbench-api.js.map +1 -0
  431. package/dist/dashboard/web/agent-workbench-appearance-menu.d.ts +59 -0
  432. package/dist/dashboard/web/agent-workbench-appearance-menu.d.ts.map +1 -0
  433. package/dist/dashboard/web/agent-workbench-appearance-menu.js +195 -0
  434. package/dist/dashboard/web/agent-workbench-appearance-menu.js.map +1 -0
  435. package/dist/dashboard/web/agent-workbench-appearance.d.ts +222 -0
  436. package/dist/dashboard/web/agent-workbench-appearance.d.ts.map +1 -0
  437. package/dist/dashboard/web/agent-workbench-appearance.js +499 -0
  438. package/dist/dashboard/web/agent-workbench-appearance.js.map +1 -0
  439. package/dist/dashboard/web/agent-workbench-capabilities.d.ts +13 -0
  440. package/dist/dashboard/web/agent-workbench-capabilities.d.ts.map +1 -0
  441. package/dist/dashboard/web/agent-workbench-capabilities.js +24 -0
  442. package/dist/dashboard/web/agent-workbench-capabilities.js.map +1 -0
  443. package/dist/dashboard/web/agent-workbench-chat.d.ts +90 -0
  444. package/dist/dashboard/web/agent-workbench-chat.d.ts.map +1 -0
  445. package/dist/dashboard/web/agent-workbench-chat.js +168 -0
  446. package/dist/dashboard/web/agent-workbench-chat.js.map +1 -0
  447. package/dist/dashboard/web/agent-workbench-dock-page.d.ts +3 -0
  448. package/dist/dashboard/web/agent-workbench-dock-page.d.ts.map +1 -0
  449. package/dist/dashboard/web/agent-workbench-dock-page.js +23 -0
  450. package/dist/dashboard/web/agent-workbench-dock-page.js.map +1 -0
  451. package/dist/dashboard/web/agent-workbench-dock-view.d.ts +22 -0
  452. package/dist/dashboard/web/agent-workbench-dock-view.d.ts.map +1 -0
  453. package/dist/dashboard/web/agent-workbench-dock-view.js +76 -0
  454. package/dist/dashboard/web/agent-workbench-dock-view.js.map +1 -0
  455. package/dist/dashboard/web/agent-workbench-model.d.ts +196 -0
  456. package/dist/dashboard/web/agent-workbench-model.d.ts.map +1 -0
  457. package/dist/dashboard/web/agent-workbench-model.js +529 -0
  458. package/dist/dashboard/web/agent-workbench-model.js.map +1 -0
  459. package/dist/dashboard/web/agent-workbench-page.d.ts +3 -0
  460. package/dist/dashboard/web/agent-workbench-page.d.ts.map +1 -0
  461. package/dist/dashboard/web/agent-workbench-page.js +27 -0
  462. package/dist/dashboard/web/agent-workbench-page.js.map +1 -0
  463. package/dist/dashboard/web/agent-workbench-panes.d.ts +49 -0
  464. package/dist/dashboard/web/agent-workbench-panes.d.ts.map +1 -0
  465. package/dist/dashboard/web/agent-workbench-panes.js +468 -0
  466. package/dist/dashboard/web/agent-workbench-panes.js.map +1 -0
  467. package/dist/dashboard/web/agent-workbench-session-list.d.ts +42 -0
  468. package/dist/dashboard/web/agent-workbench-session-list.d.ts.map +1 -0
  469. package/dist/dashboard/web/agent-workbench-session-list.js +353 -0
  470. package/dist/dashboard/web/agent-workbench-session-list.js.map +1 -0
  471. package/dist/dashboard/web/agent-workbench-storage.d.ts +49 -0
  472. package/dist/dashboard/web/agent-workbench-storage.d.ts.map +1 -0
  473. package/dist/dashboard/web/agent-workbench-storage.js +197 -0
  474. package/dist/dashboard/web/agent-workbench-storage.js.map +1 -0
  475. package/dist/dashboard/web/agent-workbench-touch.d.ts +46 -0
  476. package/dist/dashboard/web/agent-workbench-touch.d.ts.map +1 -0
  477. package/dist/dashboard/web/agent-workbench-touch.js +96 -0
  478. package/dist/dashboard/web/agent-workbench-touch.js.map +1 -0
  479. package/dist/dashboard/web/agent-workbench-view.d.ts +34 -0
  480. package/dist/dashboard/web/agent-workbench-view.d.ts.map +1 -0
  481. package/dist/dashboard/web/agent-workbench-view.js +439 -0
  482. package/dist/dashboard/web/agent-workbench-view.js.map +1 -0
  483. package/dist/dashboard/web/app.d.ts.map +1 -1
  484. package/dist/dashboard/web/app.js +63 -2
  485. package/dist/dashboard/web/app.js.map +1 -1
  486. package/dist/dashboard/web/bot-defaults-page.d.ts +18 -0
  487. package/dist/dashboard/web/bot-defaults-page.d.ts.map +1 -1
  488. package/dist/dashboard/web/bot-defaults-page.js +362 -36
  489. package/dist/dashboard/web/bot-defaults-page.js.map +1 -1
  490. package/dist/dashboard/web/bot-defaults.d.ts +8 -0
  491. package/dist/dashboard/web/bot-defaults.d.ts.map +1 -1
  492. package/dist/dashboard/web/bot-defaults.js.map +1 -1
  493. package/dist/dashboard/web/control-csrf.d.ts +4 -0
  494. package/dist/dashboard/web/control-csrf.d.ts.map +1 -0
  495. package/dist/dashboard/web/control-csrf.js +26 -0
  496. package/dist/dashboard/web/control-csrf.js.map +1 -0
  497. package/dist/dashboard/web/dashboard-routes.d.ts.map +1 -1
  498. package/dist/dashboard/web/dashboard-routes.js +2 -0
  499. package/dist/dashboard/web/dashboard-routes.js.map +1 -1
  500. package/dist/dashboard/web/groups-page.d.ts.map +1 -1
  501. package/dist/dashboard/web/groups-page.js +11 -2
  502. package/dist/dashboard/web/groups-page.js.map +1 -1
  503. package/dist/dashboard/web/i18n.d.ts.map +1 -1
  504. package/dist/dashboard/web/i18n.js +140 -6
  505. package/dist/dashboard/web/i18n.js.map +1 -1
  506. package/dist/dashboard/web/overview-page.d.ts.map +1 -1
  507. package/dist/dashboard/web/overview-page.js +3 -2
  508. package/dist/dashboard/web/overview-page.js.map +1 -1
  509. package/dist/dashboard/web/sessions-page.d.ts.map +1 -1
  510. package/dist/dashboard/web/sessions-page.js +41 -1
  511. package/dist/dashboard/web/sessions-page.js.map +1 -1
  512. package/dist/dashboard/web/sessions.d.ts.map +1 -1
  513. package/dist/dashboard/web/sessions.js +5 -1
  514. package/dist/dashboard/web/sessions.js.map +1 -1
  515. package/dist/dashboard/web/settings-page.d.ts +3 -0
  516. package/dist/dashboard/web/settings-page.d.ts.map +1 -1
  517. package/dist/dashboard/web/settings-page.js +56 -10
  518. package/dist/dashboard/web/settings-page.js.map +1 -1
  519. package/dist/dashboard/web/skills-page.d.ts.map +1 -1
  520. package/dist/dashboard/web/skills-page.js +4 -2
  521. package/dist/dashboard/web/skills-page.js.map +1 -1
  522. package/dist/dashboard/web/store.d.ts +27 -2
  523. package/dist/dashboard/web/store.d.ts.map +1 -1
  524. package/dist/dashboard/web/store.js +43 -4
  525. package/dist/dashboard/web/store.js.map +1 -1
  526. package/dist/dashboard/web/ui.d.ts +3 -0
  527. package/dist/dashboard/web/ui.d.ts.map +1 -1
  528. package/dist/dashboard/web/ui.js +11 -0
  529. package/dist/dashboard/web/ui.js.map +1 -1
  530. package/dist/dashboard/web/update-action.d.ts +12 -0
  531. package/dist/dashboard/web/update-action.d.ts.map +1 -1
  532. package/dist/dashboard/web/update-action.js +11 -0
  533. package/dist/dashboard/web/update-action.js.map +1 -1
  534. package/dist/dashboard/workbench-doctor.d.ts +114 -0
  535. package/dist/dashboard/workbench-doctor.d.ts.map +1 -0
  536. package/dist/dashboard/workbench-doctor.js +620 -0
  537. package/dist/dashboard/workbench-doctor.js.map +1 -0
  538. package/dist/dashboard/workbench-ticket.d.ts +165 -0
  539. package/dist/dashboard/workbench-ticket.d.ts.map +1 -0
  540. package/dist/dashboard/workbench-ticket.js +495 -0
  541. package/dist/dashboard/workbench-ticket.js.map +1 -0
  542. package/dist/dashboard-web/app.js +1 -1
  543. package/dist/dashboard-web/chunks/agent-workbench-dock-page-IKAC5CFL.js +1 -0
  544. package/dist/dashboard-web/chunks/agent-workbench-page-M53UN5EB.js +1 -0
  545. package/dist/dashboard-web/chunks/bot-defaults-page-YKE5SDZF.js +10 -0
  546. package/dist/dashboard-web/chunks/chunk-22WIADUT.js +2 -0
  547. package/dist/dashboard-web/chunks/chunk-24ONYQMR.js +1 -0
  548. package/dist/dashboard-web/chunks/{chunk-6O7KXFD2.js → chunk-2W6SCN3M.js} +1 -1
  549. package/dist/dashboard-web/chunks/chunk-4XNJX6B3.js +1 -0
  550. package/dist/dashboard-web/chunks/{chunk-Q2MT4VWH.js → chunk-F6KNPF4R.js} +11 -9
  551. package/dist/dashboard-web/chunks/chunk-FXW3V4JR.js +1 -0
  552. package/dist/dashboard-web/chunks/chunk-G5ZS2YUS.js +1 -0
  553. package/dist/dashboard-web/chunks/chunk-KTQ7B6ZR.js +1 -0
  554. package/dist/dashboard-web/chunks/{chunk-U3ZFSO7E.js → chunk-NACP7BMR.js} +1 -1
  555. package/dist/dashboard-web/chunks/{connectors-page-2KZO5KSJ.js → connectors-page-VOISXXII.js} +1 -1
  556. package/dist/dashboard-web/chunks/{feedback-page-CLVTOLLO.js → feedback-page-RN6HRZMS.js} +1 -1
  557. package/dist/dashboard-web/chunks/groups-page-UQT6DCB7.js +8 -0
  558. package/dist/dashboard-web/chunks/{insights-page-4RPU2CRU.js → insights-page-HJFDYR5N.js} +3 -3
  559. package/dist/dashboard-web/chunks/{monitor-room-OTKQ2CXK.js → monitor-room-V7PCP47V.js} +1 -1
  560. package/dist/dashboard-web/chunks/{monitoring-page-IJPUB6JT.js → monitoring-page-NKEKVQAP.js} +1 -1
  561. package/dist/dashboard-web/chunks/{office-page-QSR3J44Y.js → office-page-GVJF73XW.js} +1 -1
  562. package/dist/dashboard-web/chunks/overview-page-KHLV6VTI.js +1 -0
  563. package/dist/dashboard-web/chunks/{plugin-page-VEGTCEAS.js → plugin-page-J3OF5IY4.js} +1 -1
  564. package/dist/dashboard-web/chunks/{roles-page-6NWJMOB5.js → roles-page-WQIRSOLZ.js} +1 -1
  565. package/dist/dashboard-web/chunks/{schedules-page-6I3LNXD4.js → schedules-page-J2DEY2X6.js} +1 -1
  566. package/dist/dashboard-web/chunks/sessions-page-7BQFEA5P.js +8 -0
  567. package/dist/dashboard-web/chunks/settings-page-BJEIOFJH.js +2 -0
  568. package/dist/dashboard-web/chunks/{skills-page-M2Q6L5CB.js → skills-page-HGIFR3IU.js} +2 -2
  569. package/dist/dashboard-web/chunks/{team-federation-page-7I7FDE3B.js → team-federation-page-MR5ZPL4B.js} +1 -1
  570. package/dist/dashboard-web/chunks/{v3-page-HQXRL65L.js → v3-page-GBR5JWPE.js} +2 -2
  571. package/dist/dashboard-web/chunks/{whiteboards-page-LQKC4TAZ.js → whiteboards-page-QAZM4BN3.js} +1 -1
  572. package/dist/dashboard-web/index.html +8 -1
  573. package/dist/dashboard-web/style.css +2006 -10
  574. package/dist/dashboard.js +1017 -264
  575. package/dist/dashboard.js.map +1 -1
  576. package/dist/dsh-runner.js +8 -1
  577. package/dist/dsh-runner.js.map +1 -1
  578. package/dist/global-config.d.ts +30 -0
  579. package/dist/global-config.d.ts.map +1 -1
  580. package/dist/global-config.js +33 -0
  581. package/dist/global-config.js.map +1 -1
  582. package/dist/i18n/en.d.ts.map +1 -1
  583. package/dist/i18n/en.js +46 -27
  584. package/dist/i18n/en.js.map +1 -1
  585. package/dist/i18n/zh.d.ts.map +1 -1
  586. package/dist/i18n/zh.js +43 -27
  587. package/dist/i18n/zh.js.map +1 -1
  588. package/dist/im/lark/card-builder.d.ts +1 -1
  589. package/dist/im/lark/card-builder.d.ts.map +1 -1
  590. package/dist/im/lark/card-builder.js +22 -6
  591. package/dist/im/lark/card-builder.js.map +1 -1
  592. package/dist/im/lark/card-handler.d.ts.map +1 -1
  593. package/dist/im/lark/card-handler.js +118 -24
  594. package/dist/im/lark/card-handler.js.map +1 -1
  595. package/dist/im/lark/client.d.ts +15 -0
  596. package/dist/im/lark/client.d.ts.map +1 -1
  597. package/dist/im/lark/client.js +30 -0
  598. package/dist/im/lark/client.js.map +1 -1
  599. package/dist/im/lark/event-dispatcher.d.ts +48 -0
  600. package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
  601. package/dist/im/lark/event-dispatcher.js +138 -41
  602. package/dist/im/lark/event-dispatcher.js.map +1 -1
  603. package/dist/im/lark/identity-cache.d.ts +14 -6
  604. package/dist/im/lark/identity-cache.d.ts.map +1 -1
  605. package/dist/im/lark/identity-cache.js +69 -26
  606. package/dist/im/lark/identity-cache.js.map +1 -1
  607. package/dist/im/lark/lark-hosts.d.ts +13 -0
  608. package/dist/im/lark/lark-hosts.d.ts.map +1 -1
  609. package/dist/im/lark/lark-hosts.js +16 -0
  610. package/dist/im/lark/lark-hosts.js.map +1 -1
  611. package/dist/im/lark/overview-card.d.ts +22 -0
  612. package/dist/im/lark/overview-card.d.ts.map +1 -1
  613. package/dist/im/lark/overview-card.js +53 -3
  614. package/dist/im/lark/overview-card.js.map +1 -1
  615. package/dist/im/lark/sessions-card.d.ts.map +1 -1
  616. package/dist/im/lark/sessions-card.js +26 -3
  617. package/dist/im/lark/sessions-card.js.map +1 -1
  618. package/dist/im/lark/workflow-slash-command.d.ts +6 -0
  619. package/dist/im/lark/workflow-slash-command.d.ts.map +1 -1
  620. package/dist/im/lark/workflow-slash-command.js +7 -0
  621. package/dist/im/lark/workflow-slash-command.js.map +1 -1
  622. package/dist/index-daemon.js +27 -4
  623. package/dist/index-daemon.js.map +1 -1
  624. package/dist/index-dashboard.d.ts +3 -0
  625. package/dist/index-dashboard.d.ts.map +1 -0
  626. package/dist/index-dashboard.js +94 -0
  627. package/dist/index-dashboard.js.map +1 -0
  628. package/dist/platform/team-agents-client.d.ts +199 -0
  629. package/dist/platform/team-agents-client.d.ts.map +1 -0
  630. package/dist/platform/team-agents-client.js +263 -0
  631. package/dist/platform/team-agents-client.js.map +1 -0
  632. package/dist/platform/tunnel-client.d.ts +6 -0
  633. package/dist/platform/tunnel-client.d.ts.map +1 -1
  634. package/dist/platform/tunnel-client.js.map +1 -1
  635. package/dist/services/async-trigger-state.d.ts +7 -3
  636. package/dist/services/async-trigger-state.d.ts.map +1 -1
  637. package/dist/services/async-trigger-state.js +28 -0
  638. package/dist/services/async-trigger-state.js.map +1 -1
  639. package/dist/services/async-trigger-store.d.ts +13 -4
  640. package/dist/services/async-trigger-store.d.ts.map +1 -1
  641. package/dist/services/async-trigger-store.js +55 -8
  642. package/dist/services/async-trigger-store.js.map +1 -1
  643. package/dist/services/bot-config-store.d.ts +1 -11
  644. package/dist/services/bot-config-store.d.ts.map +1 -1
  645. package/dist/services/bot-config-store.js +27 -3
  646. package/dist/services/bot-config-store.js.map +1 -1
  647. package/dist/services/bot-profile-store.d.ts +17 -2
  648. package/dist/services/bot-profile-store.d.ts.map +1 -1
  649. package/dist/services/bot-profile-store.js +93 -12
  650. package/dist/services/bot-profile-store.js.map +1 -1
  651. package/dist/services/bridge-fallback-gate.d.ts +15 -7
  652. package/dist/services/bridge-fallback-gate.d.ts.map +1 -1
  653. package/dist/services/bridge-fallback-gate.js +43 -16
  654. package/dist/services/bridge-fallback-gate.js.map +1 -1
  655. package/dist/services/bridge-turn-queue.d.ts +10 -1
  656. package/dist/services/bridge-turn-queue.d.ts.map +1 -1
  657. package/dist/services/bridge-turn-queue.js +29 -9
  658. package/dist/services/bridge-turn-queue.js.map +1 -1
  659. package/dist/services/claude-transcript.d.ts +19 -0
  660. package/dist/services/claude-transcript.d.ts.map +1 -1
  661. package/dist/services/claude-transcript.js +57 -5
  662. package/dist/services/claude-transcript.js.map +1 -1
  663. package/dist/services/codex-app-threads.d.ts +2 -0
  664. package/dist/services/codex-app-threads.d.ts.map +1 -1
  665. package/dist/services/codex-app-threads.js +1 -1
  666. package/dist/services/codex-app-threads.js.map +1 -1
  667. package/dist/services/codex-reasoning-effort.d.ts +8 -0
  668. package/dist/services/codex-reasoning-effort.d.ts.map +1 -1
  669. package/dist/services/codex-reasoning-effort.js +24 -0
  670. package/dist/services/codex-reasoning-effort.js.map +1 -1
  671. package/dist/services/cursor-resume-policy.d.ts.map +1 -1
  672. package/dist/services/cursor-resume-policy.js +15 -3
  673. package/dist/services/cursor-resume-policy.js.map +1 -1
  674. package/dist/services/open-platform-rename.d.ts.map +1 -1
  675. package/dist/services/open-platform-rename.js +3 -2
  676. package/dist/services/open-platform-rename.js.map +1 -1
  677. package/dist/services/ordinary-turn-recovery.d.ts +76 -0
  678. package/dist/services/ordinary-turn-recovery.d.ts.map +1 -0
  679. package/dist/services/ordinary-turn-recovery.js +349 -0
  680. package/dist/services/ordinary-turn-recovery.js.map +1 -0
  681. package/dist/services/peer-cross-ref-store.d.ts +8 -0
  682. package/dist/services/peer-cross-ref-store.d.ts.map +1 -0
  683. package/dist/services/peer-cross-ref-store.js +95 -0
  684. package/dist/services/peer-cross-ref-store.js.map +1 -0
  685. package/dist/services/pi-transcript.d.ts.map +1 -1
  686. package/dist/services/pi-transcript.js +10 -6
  687. package/dist/services/pi-transcript.js.map +1 -1
  688. package/dist/services/resume-fresh-policy.d.ts +17 -0
  689. package/dist/services/resume-fresh-policy.d.ts.map +1 -0
  690. package/dist/services/resume-fresh-policy.js +24 -0
  691. package/dist/services/resume-fresh-policy.js.map +1 -0
  692. package/dist/services/session-group-title.d.ts +1 -0
  693. package/dist/services/session-group-title.d.ts.map +1 -1
  694. package/dist/services/session-group-title.js +16 -1
  695. package/dist/services/session-group-title.js.map +1 -1
  696. package/dist/services/session-groups-store.d.ts +30 -0
  697. package/dist/services/session-groups-store.d.ts.map +1 -1
  698. package/dist/services/session-groups-store.js +26 -4
  699. package/dist/services/session-groups-store.js.map +1 -1
  700. package/dist/services/session-owner-notification.d.ts +9 -0
  701. package/dist/services/session-owner-notification.d.ts.map +1 -0
  702. package/dist/services/session-owner-notification.js +10 -0
  703. package/dist/services/session-owner-notification.js.map +1 -0
  704. package/dist/services/session-owner-reminder-config-store.d.ts +9 -0
  705. package/dist/services/session-owner-reminder-config-store.d.ts.map +1 -0
  706. package/dist/services/session-owner-reminder-config-store.js +24 -0
  707. package/dist/services/session-owner-reminder-config-store.js.map +1 -0
  708. package/dist/services/session-owner-reminder-store.d.ts +5 -0
  709. package/dist/services/session-owner-reminder-store.d.ts.map +1 -0
  710. package/dist/services/session-owner-reminder-store.js +54 -0
  711. package/dist/services/session-owner-reminder-store.js.map +1 -0
  712. package/dist/services/session-store.d.ts +98 -21
  713. package/dist/services/session-store.d.ts.map +1 -1
  714. package/dist/services/session-store.js +406 -48
  715. package/dist/services/session-store.js.map +1 -1
  716. package/dist/services/structured-bridge-clis.d.ts +15 -2
  717. package/dist/services/structured-bridge-clis.d.ts.map +1 -1
  718. package/dist/services/structured-bridge-clis.js +16 -1
  719. package/dist/services/structured-bridge-clis.js.map +1 -1
  720. package/dist/services/transcript-resolver.d.ts +1 -1
  721. package/dist/services/transcript-resolver.d.ts.map +1 -1
  722. package/dist/services/transcript-resolver.js +6 -1
  723. package/dist/services/transcript-resolver.js.map +1 -1
  724. package/dist/setup/bot-config-editor.d.ts.map +1 -1
  725. package/dist/setup/bot-config-editor.js +7 -2
  726. package/dist/setup/bot-config-editor.js.map +1 -1
  727. package/dist/setup/ensure-herdr-integrations.d.ts +13 -0
  728. package/dist/setup/ensure-herdr-integrations.d.ts.map +1 -1
  729. package/dist/setup/ensure-herdr-integrations.js +21 -3
  730. package/dist/setup/ensure-herdr-integrations.js.map +1 -1
  731. package/dist/setup/open-platform-automation.d.ts +1 -0
  732. package/dist/setup/open-platform-automation.d.ts.map +1 -1
  733. package/dist/setup/open-platform-automation.js +107 -7
  734. package/dist/setup/open-platform-automation.js.map +1 -1
  735. package/dist/skills/definitions.d.ts +17 -0
  736. package/dist/skills/definitions.d.ts.map +1 -1
  737. package/dist/skills/definitions.js +78 -8
  738. package/dist/skills/definitions.js.map +1 -1
  739. package/dist/skills/injection-mode.d.ts +24 -3
  740. package/dist/skills/injection-mode.d.ts.map +1 -1
  741. package/dist/skills/injection-mode.js +64 -15
  742. package/dist/skills/injection-mode.js.map +1 -1
  743. package/dist/skills/installer.d.ts +18 -0
  744. package/dist/skills/installer.d.ts.map +1 -1
  745. package/dist/skills/installer.js +45 -1
  746. package/dist/skills/installer.js.map +1 -1
  747. package/dist/types.d.ts +222 -10
  748. package/dist/types.d.ts.map +1 -1
  749. package/dist/utils/bot-routing.d.ts +1 -1
  750. package/dist/utils/bot-routing.d.ts.map +1 -1
  751. package/dist/utils/bot-routing.js.map +1 -1
  752. package/dist/utils/child-env.d.ts +92 -2
  753. package/dist/utils/child-env.d.ts.map +1 -1
  754. package/dist/utils/child-env.js +195 -1
  755. package/dist/utils/child-env.js.map +1 -1
  756. package/dist/utils/cli-usage-limit.d.ts +25 -14
  757. package/dist/utils/cli-usage-limit.d.ts.map +1 -1
  758. package/dist/utils/cli-usage-limit.js +25 -15
  759. package/dist/utils/cli-usage-limit.js.map +1 -1
  760. package/dist/utils/daemon-rejection-guard.d.ts +35 -0
  761. package/dist/utils/daemon-rejection-guard.d.ts.map +1 -0
  762. package/dist/utils/daemon-rejection-guard.js +101 -0
  763. package/dist/utils/daemon-rejection-guard.js.map +1 -0
  764. package/dist/utils/dashboard-env.d.ts +53 -0
  765. package/dist/utils/dashboard-env.d.ts.map +1 -0
  766. package/dist/utils/dashboard-env.js +116 -0
  767. package/dist/utils/dashboard-env.js.map +1 -0
  768. package/dist/utils/global-install.d.ts.map +1 -1
  769. package/dist/utils/global-install.js +61 -5
  770. package/dist/utils/global-install.js.map +1 -1
  771. package/dist/utils/install-diagnostics.d.ts +14 -7
  772. package/dist/utils/install-diagnostics.d.ts.map +1 -1
  773. package/dist/utils/install-diagnostics.js +20 -11
  774. package/dist/utils/install-diagnostics.js.map +1 -1
  775. package/dist/utils/local-dev-update.d.ts +132 -0
  776. package/dist/utils/local-dev-update.d.ts.map +1 -0
  777. package/dist/utils/local-dev-update.js +176 -0
  778. package/dist/utils/local-dev-update.js.map +1 -0
  779. package/dist/utils/pending-input-queue.d.ts +12 -1
  780. package/dist/utils/pending-input-queue.d.ts.map +1 -1
  781. package/dist/utils/pending-input-queue.js.map +1 -1
  782. package/dist/worker.js +1517 -236
  783. package/dist/worker.js.map +1 -1
  784. package/dist/workflows/definition.d.ts +68 -68
  785. package/dist/workflows/events/payloads.d.ts +14 -14
  786. package/dist/workflows/events/schema.d.ts +312 -312
  787. package/dist/workflows/migration/v2-run-archive-schema.d.ts +40 -40
  788. package/dist/workflows/v3/cli-run.d.ts.map +1 -1
  789. package/dist/workflows/v3/cli-run.js +7 -0
  790. package/dist/workflows/v3/cli-run.js.map +1 -1
  791. package/dist/workflows/v3/goal-cli.d.ts +3 -0
  792. package/dist/workflows/v3/goal-cli.d.ts.map +1 -1
  793. package/dist/workflows/v3/goal-cli.js +11 -0
  794. package/dist/workflows/v3/goal-cli.js.map +1 -1
  795. package/dist/workflows/v3/run-envelope.d.ts +144 -144
  796. package/package.json +7 -2
  797. package/dist/cli/pm2-god-admission.d.ts +0 -8
  798. package/dist/cli/pm2-god-admission.d.ts.map +0 -1
  799. package/dist/cli/pm2-god-admission.js +0 -25
  800. package/dist/cli/pm2-god-admission.js.map +0 -1
  801. package/dist/core/riff-shutdown-detach.d.ts.map +0 -1
  802. package/dist/core/riff-shutdown-detach.js.map +0 -1
  803. package/dist/core/riff-worker-shutdown-readiness.d.ts.map +0 -1
  804. package/dist/core/riff-worker-shutdown-readiness.js.map +0 -1
  805. package/dist/dashboard-web/chunks/bot-defaults-page-4RENHE7J.js +0 -10
  806. package/dist/dashboard-web/chunks/chunk-2WQDS65D.js +0 -1
  807. package/dist/dashboard-web/chunks/chunk-G5XGBCM6.js +0 -1
  808. package/dist/dashboard-web/chunks/chunk-WLIVBPJC.js +0 -1
  809. package/dist/dashboard-web/chunks/groups-page-HLASRTSY.js +0 -8
  810. package/dist/dashboard-web/chunks/overview-page-6OK63V4V.js +0 -1
  811. package/dist/dashboard-web/chunks/sessions-page-B2TXGJDO.js +0 -8
  812. package/dist/dashboard-web/chunks/settings-page-6KJADSC6.js +0 -2
@@ -0,0 +1,825 @@
1
+ import { createHash, randomBytes } from 'node:crypto';
2
+ import { larkHosts } from '../im/lark/lark-hosts.js';
3
+ import { controlAuditRecord, } from './control-audit.js';
4
+ import { logger } from '../utils/logger.js';
5
+ export const DASHBOARD_SESSION_COOKIE = 'botmux_dashboard_session';
6
+ export const DEFAULT_DASHBOARD_SESSION_TTL_MS = 30 * 60_000;
7
+ export const DASHBOARD_H5_CLIENT_TIMEOUT_MS = 8_000;
8
+ const MAX_AUTH_BODY_BYTES = 4 * 1024;
9
+ const DEFAULT_EXCHANGE_TIMEOUT_MS = 8_000;
10
+ /** Upper bound for the configured reverse-proxy hop count. Deployments chain a
11
+ * handful of proxies at most; a typo of `99` must not turn into "trust the
12
+ * whole client-supplied chain". */
13
+ export const DASHBOARD_H5_MAX_TRUSTED_PROXY_HOPS = 8;
14
+ function tokenHash(token) {
15
+ return createHash('sha256').update('botmux-dashboard-h5-session-v1\0').update(token).digest('base64url');
16
+ }
17
+ /** Single-flight map key. Codes are one-time secrets — only digests may be
18
+ * used as (short-lived) map keys, mirroring the session-token policy above. */
19
+ function exchangeCodeKey(code) {
20
+ return createHash('sha256').update('botmux-dashboard-h5-exchange-code-v1\0').update(code).digest('base64url');
21
+ }
22
+ function validTtl(ttlMs) {
23
+ return Number.isSafeInteger(ttlMs) && ttlMs >= 60_000 && ttlMs <= 24 * 60 * 60_000;
24
+ }
25
+ /**
26
+ * In-memory fixed-expiry Dashboard sessions. Only SHA-256 token digests are
27
+ * retained; the opaque browser cookie value exists only during Set-Cookie and
28
+ * request parsing. A dashboard restart intentionally signs everyone out.
29
+ */
30
+ export class DashboardSessionStore {
31
+ sessionsByHash = new Map();
32
+ hashesById = new Map();
33
+ listeners = new Set();
34
+ ttlMs;
35
+ now;
36
+ randomToken;
37
+ schedule;
38
+ cancel;
39
+ constructor(opts = {}) {
40
+ this.ttlMs = opts.ttlMs ?? DEFAULT_DASHBOARD_SESSION_TTL_MS;
41
+ if (!validTtl(this.ttlMs))
42
+ throw new RangeError('dashboard session TTL must be between 1 minute and 24 hours');
43
+ this.now = opts.now ?? Date.now;
44
+ this.randomToken = opts.randomToken ?? (() => randomBytes(32).toString('base64url'));
45
+ this.schedule = opts.setTimer ?? setTimeout;
46
+ this.cancel = opts.clearTimer ?? clearTimeout;
47
+ }
48
+ create(userId) {
49
+ if (!validOpenId(userId))
50
+ throw new Error('invalid dashboard session user');
51
+ const now = this.now();
52
+ const token = this.randomToken();
53
+ if (!/^[A-Za-z0-9_-]{32,256}$/.test(token))
54
+ throw new Error('invalid dashboard session token source');
55
+ const identity = {
56
+ kind: 'feishu-h5',
57
+ userId,
58
+ authSessionId: randomBytes(18).toString('base64url'),
59
+ issuedAt: now,
60
+ expiresAt: now + this.ttlMs,
61
+ };
62
+ const hash = tokenHash(token);
63
+ const stored = { ...identity, tokenHash: hash };
64
+ stored.timer = this.schedule(() => { this.expireHash(hash); }, this.ttlMs);
65
+ stored.timer.unref?.();
66
+ this.sessionsByHash.set(hash, stored);
67
+ this.hashesById.set(identity.authSessionId, hash);
68
+ return { token, identity };
69
+ }
70
+ resolveToken(token) {
71
+ if (!token || token.length > 512 || !/^[A-Za-z0-9_-]+$/.test(token))
72
+ return null;
73
+ const hash = tokenHash(token);
74
+ const stored = this.sessionsByHash.get(hash);
75
+ if (!stored)
76
+ return null;
77
+ if (this.now() >= stored.expiresAt) {
78
+ this.expireHash(hash);
79
+ return null;
80
+ }
81
+ return this.publicIdentity(stored);
82
+ }
83
+ revokeToken(token) {
84
+ if (!token || token.length > 512 || !/^[A-Za-z0-9_-]+$/.test(token))
85
+ return false;
86
+ return this.endHash(tokenHash(token), 'logout');
87
+ }
88
+ revokeAuthSession(authSessionId, reason = 'logout') {
89
+ const hash = this.hashesById.get(authSessionId);
90
+ return hash ? this.endHash(hash, reason) : false;
91
+ }
92
+ /** Whether this auth session is still alive (present and unexpired). Used as
93
+ * the revocation check for capabilities minted under the session (P1-5); a
94
+ * due-but-unswept session is expired inline so the answer is authoritative. */
95
+ liveAuthSession(authSessionId) {
96
+ const hash = this.hashesById.get(authSessionId);
97
+ if (!hash)
98
+ return false;
99
+ const stored = this.sessionsByHash.get(hash);
100
+ if (!stored)
101
+ return false;
102
+ if (this.now() >= stored.expiresAt) {
103
+ this.expireHash(hash);
104
+ return false;
105
+ }
106
+ return true;
107
+ }
108
+ sweepExpired() {
109
+ const before = this.sessionsByHash.size;
110
+ for (const [hash, session] of this.sessionsByHash) {
111
+ if (this.now() >= session.expiresAt)
112
+ this.expireHash(hash);
113
+ }
114
+ return before - this.sessionsByHash.size;
115
+ }
116
+ onEnd(listener) {
117
+ this.listeners.add(listener);
118
+ return () => this.listeners.delete(listener);
119
+ }
120
+ expireHash(hash) {
121
+ return this.endHash(hash, 'expired');
122
+ }
123
+ endHash(hash, reason) {
124
+ const stored = this.sessionsByHash.get(hash);
125
+ if (!stored)
126
+ return false;
127
+ this.sessionsByHash.delete(hash);
128
+ this.hashesById.delete(stored.authSessionId);
129
+ if (stored.timer)
130
+ this.cancel(stored.timer);
131
+ const identity = this.publicIdentity(stored);
132
+ for (const listener of this.listeners) {
133
+ try {
134
+ listener(identity, reason);
135
+ }
136
+ catch { /* listeners are isolation boundaries */ }
137
+ }
138
+ return true;
139
+ }
140
+ publicIdentity(stored) {
141
+ return {
142
+ kind: 'feishu-h5',
143
+ userId: stored.userId,
144
+ authSessionId: stored.authSessionId,
145
+ issuedAt: stored.issuedAt,
146
+ expiresAt: stored.expiresAt,
147
+ };
148
+ }
149
+ }
150
+ export function validOpenId(value) {
151
+ return value.length >= 3 && value.length <= 256 && !/[\s\0]/.test(value);
152
+ }
153
+ export function parseNamedCookie(header, name) {
154
+ if (!header)
155
+ return undefined;
156
+ for (const part of header.split(';')) {
157
+ const eq = part.indexOf('=');
158
+ if (eq < 0 || part.slice(0, eq).trim() !== name)
159
+ continue;
160
+ return part.slice(eq + 1).trim() || undefined;
161
+ }
162
+ return undefined;
163
+ }
164
+ export function dashboardSessionCookie(token, ttlMs, secure = false) {
165
+ const maxAge = Math.max(1, Math.floor(ttlMs / 1000));
166
+ return `${DASHBOARD_SESSION_COOKIE}=${token}; HttpOnly; SameSite=Lax; Path=/; Max-Age=${maxAge}${secure ? '; Secure' : ''}`;
167
+ }
168
+ export function clearDashboardSessionCookie(secure = false) {
169
+ return `${DASHBOARD_SESSION_COOKIE}=; HttpOnly; SameSite=Lax; Path=/; Max-Age=0${secure ? '; Secure' : ''}`;
170
+ }
171
+ export function resolveDashboardH5AuthConfig(env = process.env) {
172
+ const entryCandidate = env.BOTMUX_DASHBOARD_FEISHU_H5_ENTRY_PATH?.trim() || '/auth/feishu';
173
+ const entryPath = /^\/[A-Za-z0-9/_-]{1,127}$/.test(entryCandidate) && !entryCandidate.includes('//')
174
+ ? entryCandidate.replace(/\/$/, '')
175
+ : '/auth/feishu';
176
+ const rawTtl = Number(env.BOTMUX_DASHBOARD_FEISHU_H5_SESSION_TTL_MS);
177
+ const sessionTtlMs = validTtl(rawTtl) ? rawTtl : DEFAULT_DASHBOARD_SESSION_TTL_MS;
178
+ const publicUrl = env.BOTMUX_PUBLIC_URL?.trim() ?? '';
179
+ // Absent/blank/garbage all mean "no trusted proxy" — the value that makes
180
+ // `x-forwarded-for` unspoofable because it is not read at all.
181
+ const rawHops = Number(env.BOTMUX_DASHBOARD_FEISHU_H5_TRUSTED_PROXY_HOPS);
182
+ const trustedProxyHops = Number.isSafeInteger(rawHops)
183
+ && rawHops > 0 && rawHops <= DASHBOARD_H5_MAX_TRUSTED_PROXY_HOPS
184
+ ? rawHops
185
+ : 0;
186
+ return {
187
+ enabled: (env.BOTMUX_DASHBOARD_FEISHU_H5_ENABLED ?? 'false').toLowerCase() === 'true',
188
+ brand: env.BOTMUX_DASHBOARD_FEISHU_H5_BRAND === 'lark' ? 'lark' : 'feishu',
189
+ appId: env.BOTMUX_DASHBOARD_FEISHU_H5_APP_ID?.trim() || '',
190
+ appSecret: env.BOTMUX_DASHBOARD_FEISHU_H5_APP_SECRET?.trim() || '',
191
+ allowedOpenIds: (env.BOTMUX_DASHBOARD_FEISHU_H5_ALLOWED_OPEN_IDS ?? '')
192
+ .split(',').map(value => value.trim()).filter(validOpenId),
193
+ entryPath,
194
+ sessionTtlMs,
195
+ secureCookies: publicUrl.startsWith('https://')
196
+ || (env.BOTMUX_DASHBOARD_FEISHU_H5_SECURE_COOKIE ?? '').toLowerCase() === 'true',
197
+ trustedProxyHops,
198
+ };
199
+ }
200
+ /**
201
+ * Feishu H5 `requestAccess` / `requestAuthCode` compatible exchange. Tokens
202
+ * from Feishu stay in local variables long enough to fetch `open_id` and are
203
+ * never cached, logged, returned, or copied into the Dashboard session.
204
+ */
205
+ export function createFeishuH5CodeExchanger(config, opts = {}) {
206
+ const fetchImpl = opts.fetchImpl ?? fetch;
207
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_EXCHANGE_TIMEOUT_MS;
208
+ const base = larkHosts(config.brand).openApi;
209
+ return {
210
+ async exchange(code, outerSignal) {
211
+ if (!config.appId || !config.appSecret)
212
+ throw new Error('auth_not_configured');
213
+ const controller = new AbortController();
214
+ const onAbort = () => controller.abort();
215
+ outerSignal?.addEventListener('abort', onAbort, { once: true });
216
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
217
+ timer.unref?.();
218
+ try {
219
+ const appResponse = await fetchImpl(`${base}/open-apis/auth/v3/app_access_token/internal`, {
220
+ method: 'POST',
221
+ headers: { 'content-type': 'application/json; charset=utf-8' },
222
+ body: JSON.stringify({ app_id: config.appId, app_secret: config.appSecret }),
223
+ signal: controller.signal,
224
+ });
225
+ const appBody = await appResponse.json().catch(() => null);
226
+ const appAccessToken = appBody?.code === 0 && typeof appBody.app_access_token === 'string'
227
+ ? appBody.app_access_token
228
+ : undefined;
229
+ if (!appResponse.ok || !appAccessToken)
230
+ throw new Error('provider_rejected');
231
+ const userResponse = await fetchImpl(`${base}/open-apis/authen/v1/access_token`, {
232
+ method: 'POST',
233
+ headers: {
234
+ 'content-type': 'application/json; charset=utf-8',
235
+ authorization: `Bearer ${appAccessToken}`,
236
+ },
237
+ body: JSON.stringify({ grant_type: 'authorization_code', code }),
238
+ signal: controller.signal,
239
+ });
240
+ const userBody = await userResponse.json().catch(() => null);
241
+ if (!userResponse.ok || userBody?.code !== 0 || !userBody.data)
242
+ throw new Error('provider_rejected');
243
+ const directOpenId = userBody.data.open_id;
244
+ if (typeof directOpenId === 'string' && validOpenId(directOpenId))
245
+ return { openId: directOpenId };
246
+ // Some compatible responses only return user_access_token. Resolve the
247
+ // identity and immediately discard that token as well.
248
+ const userAccessToken = userBody.data.access_token;
249
+ if (typeof userAccessToken !== 'string' || !userAccessToken)
250
+ throw new Error('provider_identity_missing');
251
+ const infoResponse = await fetchImpl(`${base}/open-apis/authen/v1/user_info`, {
252
+ headers: {
253
+ authorization: `Bearer ${userAccessToken}`,
254
+ 'content-type': 'application/json; charset=utf-8',
255
+ },
256
+ signal: controller.signal,
257
+ });
258
+ const infoBody = await infoResponse.json().catch(() => null);
259
+ const openId = infoBody?.data?.open_id;
260
+ if (!infoResponse.ok || infoBody?.code !== 0 || typeof openId !== 'string' || !validOpenId(openId)) {
261
+ throw new Error('provider_identity_missing');
262
+ }
263
+ return { openId };
264
+ }
265
+ finally {
266
+ clearTimeout(timer);
267
+ outerSignal?.removeEventListener('abort', onAbort);
268
+ }
269
+ },
270
+ };
271
+ }
272
+ export const DASHBOARD_H5_EXCHANGE_WINDOW_MS = 60_000;
273
+ export const DASHBOARD_H5_EXCHANGE_MAX_PER_IP_PER_WINDOW = 10;
274
+ export const DASHBOARD_H5_EXCHANGE_MAX_CONCURRENT = 4;
275
+ /** Ceiling for the endpoint as a whole, across every source address. The
276
+ * per-IP window alone bounds one client; this bounds the endpoint when the
277
+ * attacker has many source addresses (or one trusted proxy in front of many
278
+ * forged client addresses). */
279
+ export const DASHBOARD_H5_EXCHANGE_MAX_GLOBAL_PER_WINDOW = 60;
280
+ /** Hard cap on tracked per-IP buckets. Reaching it evicts the least recently
281
+ * used bucket (O(1)); it never degrades into a per-request full-table scan. */
282
+ export const DASHBOARD_H5_EXCHANGE_MAX_TRACKED_IPS = 4_096;
283
+ const EXCHANGE_BUSY_RETRY_MS = 1_000;
284
+ const EXCHANGE_GATE_MAX_SPENT_CODES = 4_096;
285
+ function normalizeIpCandidate(value) {
286
+ const trimmed = (value ?? '').trim();
287
+ if (!trimmed)
288
+ return '';
289
+ // IPv4-mapped IPv6 prefixes are stripped to match the dashboard's other
290
+ // remote-address handling (dashboard.ts verifyCliRequest).
291
+ return trimmed.replace(/^::ffff:/i, '').slice(0, 100);
292
+ }
293
+ /**
294
+ * Rate-limit bucket key for the public exchange endpoint, resolved under a
295
+ * TRUSTED-PROXY discipline (`proxy-addr` semantics, zero dependencies).
296
+ *
297
+ * `x-forwarded-for` is client-supplied text: anyone can send it, and each
298
+ * proxy only appends. Trusting the leftmost hop therefore hands an attacker an
299
+ * unlimited supply of distinct bucket keys — a per-IP limiter you can opt out
300
+ * of by typing a new IP, plus an attacker-driven tracking table.
301
+ *
302
+ * So the chain is read from the end WE control: `[socket peer, ...forwarded
303
+ * reversed]`, and `trustedProxyHops` says how many of those leading entries
304
+ * were written by infrastructure we trust. The first entry past them is the
305
+ * client. With the default `0` the header is never consulted at all; with `1`
306
+ * only the rightmost `x-forwarded-for` entry counts — the one the single
307
+ * trusted proxy wrote itself — so any value the client prepended sits beyond
308
+ * the trusted window and can never become a bucket key. A shorter-than-
309
+ * configured chain falls back to the furthest address actually present, which
310
+ * is at worst the direct peer.
311
+ *
312
+ * The value is only ever an opaque bucket key: it grants nothing.
313
+ */
314
+ export function dashboardH5ClientIp(req, trustedProxyHops = 0) {
315
+ const socketAddress = normalizeIpCandidate(req.socket?.remoteAddress);
316
+ const hops = Number.isSafeInteger(trustedProxyHops)
317
+ ? Math.min(Math.max(trustedProxyHops, 0), DASHBOARD_H5_MAX_TRUSTED_PROXY_HOPS)
318
+ : 0;
319
+ if (hops === 0)
320
+ return socketAddress || 'unknown';
321
+ const header = req.headers['x-forwarded-for'];
322
+ // Repeated headers are one logical list in wire order, so join before split.
323
+ const forwarded = (Array.isArray(header) ? header.join(',') : header ?? '')
324
+ .split(',')
325
+ .map(normalizeIpCandidate)
326
+ .filter(Boolean);
327
+ const chain = [socketAddress, ...forwarded.reverse()].filter(Boolean);
328
+ if (chain.length === 0)
329
+ return 'unknown';
330
+ return chain[Math.min(hops, chain.length - 1)];
331
+ }
332
+ /**
333
+ * In-process brakes for the public, unauthenticated H5 exchange endpoint.
334
+ * One exchange can cost up to three open-platform requests, so several
335
+ * independent limits apply (LocateRateLimiter-style, zero dependencies).
336
+ *
337
+ * The admission half (`admit`/`prune`) is deliberately generic and is reused by
338
+ * the other pre-auth public surface, `GET /workbench-ticket/<ticket>` (see
339
+ * dashboard/workbench-ticket.ts), with its own budgets; only the code-specific
340
+ * halves (`share`/`markSpent`) are exchange-only:
341
+ *
342
+ * - per-IP sliding window (`admit`) — only admitted hits consume slots, so
343
+ * a refused burst cannot lock a NAT'd office out forever;
344
+ * - global sliding window (`admit`) — the same endpoint-wide ceiling in
345
+ * requests-per-window, so "many source addresses" is not a way around the
346
+ * per-IP budget. It is checked AFTER the per-IP budget and consumed only on
347
+ * admission, so one noisy client can spend at most its own per-IP quota out
348
+ * of the shared pool. Implemented as a fixed-size ring of admission
349
+ * timestamps: exact sliding-window semantics with an honest Retry-After, in
350
+ * O(1) per request and O(cap) memory;
351
+ * - bounded IP tracking — the table never exceeds `maxTrackedIps`; a new
352
+ * bucket past the cap evicts the least recently used one in O(1). Pruning
353
+ * idle buckets (`prune`) is a memory tidy-up on a timer, NOT the bound, so
354
+ * a saturated table can never turn into a full-table scan per request;
355
+ * - single-flight per code plus a global in-flight cap (`share`) —
356
+ * concurrent duplicates of one code join the same upstream flight, and
357
+ * distinct codes beyond the cap are fast-rejected rather than queued so
358
+ * spam cannot stack pending exchanges on the dashboard event loop;
359
+ * - spent-code memory (`markSpent`/`isSpent`) — a code that already minted a
360
+ * session is refused outright afterwards, so "one-time" holds for a
361
+ * sequential replay too, not just for the concurrent window. Only the code
362
+ * DIGEST is kept (never the code, never the session token), with a TTL and
363
+ * its own bounded LRU.
364
+ *
365
+ * The clock is injectable for tests.
366
+ */
367
+ export class DashboardH5ExchangeGate {
368
+ /** Insertion-ordered ⇒ iteration starts at the least recently used bucket. */
369
+ hitsByIp = new Map();
370
+ inFlightByKey = new Map();
371
+ /** code digest → expiry. Uniform TTL ⇒ insertion order is also expiry order. */
372
+ spentByKey = new Map();
373
+ globalHits;
374
+ globalNext = 0;
375
+ globalFilled = 0;
376
+ now;
377
+ windowMs;
378
+ maxPerIpPerWindow;
379
+ maxTrackedIps;
380
+ maxSpentCodes;
381
+ spentCodeTtlMs;
382
+ maxConcurrent;
383
+ pruneIntervalMs;
384
+ lastPruneAt;
385
+ sweeps = 0;
386
+ constructor(opts = {}) {
387
+ this.now = opts.now ?? Date.now;
388
+ this.windowMs = opts.windowMs ?? DASHBOARD_H5_EXCHANGE_WINDOW_MS;
389
+ this.maxPerIpPerWindow = opts.maxPerIpPerWindow ?? DASHBOARD_H5_EXCHANGE_MAX_PER_IP_PER_WINDOW;
390
+ this.maxConcurrent = opts.maxConcurrent ?? DASHBOARD_H5_EXCHANGE_MAX_CONCURRENT;
391
+ this.maxTrackedIps = Math.max(1, opts.maxTrackedIps ?? DASHBOARD_H5_EXCHANGE_MAX_TRACKED_IPS);
392
+ this.maxSpentCodes = Math.max(1, opts.maxSpentCodes ?? EXCHANGE_GATE_MAX_SPENT_CODES);
393
+ this.spentCodeTtlMs = Math.max(1, opts.spentCodeTtlMs ?? this.windowMs);
394
+ const globalCap = Math.max(1, Math.min(opts.maxGlobalPerWindow ?? DASHBOARD_H5_EXCHANGE_MAX_GLOBAL_PER_WINDOW, 100_000));
395
+ this.globalHits = new Array(globalCap).fill(0);
396
+ this.pruneIntervalMs = opts.pruneIntervalMs ?? this.windowMs;
397
+ this.lastPruneAt = this.now();
398
+ }
399
+ /**
400
+ * Per-IP then endpoint-wide sliding-window admission. Refusals carry an
401
+ * honest Retry-After and consume nothing — neither budget, and (for an
402
+ * unknown IP) not a tracking-table slot either.
403
+ */
404
+ admit(ip) {
405
+ const now = this.now();
406
+ // Time-based only. The table's hard bound is the LRU eviction in
407
+ // `remember()`, so a saturated table never makes this a per-request scan.
408
+ if (now - this.lastPruneAt >= this.pruneIntervalMs)
409
+ this.prune();
410
+ const cutoff = now - this.windowMs;
411
+ const known = this.hitsByIp.get(ip);
412
+ const hits = known ? known.filter(at => at > cutoff) : [];
413
+ if (hits.length >= this.maxPerIpPerWindow) {
414
+ this.remember(ip, hits);
415
+ return { ok: false, retryAfterMs: Math.max(1, hits[0] + this.windowMs - now) };
416
+ }
417
+ const globalRetryAfterMs = this.globalRetryAfterMs(now);
418
+ if (globalRetryAfterMs !== null) {
419
+ // Keep an already-tracked bucket tidy, but never let a refused request
420
+ // from an unknown address create (and evict) a tracking entry.
421
+ if (known)
422
+ this.remember(ip, hits);
423
+ return { ok: false, retryAfterMs: globalRetryAfterMs };
424
+ }
425
+ this.globalHits[this.globalNext] = now;
426
+ this.globalNext = (this.globalNext + 1) % this.globalHits.length;
427
+ if (this.globalFilled < this.globalHits.length)
428
+ this.globalFilled += 1;
429
+ hits.push(now);
430
+ this.remember(ip, hits);
431
+ return { ok: true };
432
+ }
433
+ /** `null` when the endpoint-wide window still has room. */
434
+ globalRetryAfterMs(now) {
435
+ if (this.globalFilled < this.globalHits.length)
436
+ return null;
437
+ const oldest = this.globalHits[this.globalNext];
438
+ const retryAfterMs = oldest + this.windowMs - now;
439
+ return retryAfterMs > 0 ? retryAfterMs : null;
440
+ }
441
+ /** Store a bucket at the young end of the LRU, evicting the oldest if the
442
+ * table is full. Constant time: `delete` + `set` re-inserts, and at most one
443
+ * entry is dropped per new key. */
444
+ remember(ip, hits) {
445
+ if (!this.hitsByIp.delete(ip) && this.hitsByIp.size >= this.maxTrackedIps) {
446
+ const oldest = this.hitsByIp.keys().next();
447
+ if (!oldest.done)
448
+ this.hitsByIp.delete(oldest.value);
449
+ }
450
+ this.hitsByIp.set(ip, hits);
451
+ }
452
+ /** Remember that this code digest already minted a session. */
453
+ markSpent(key) {
454
+ if (!this.spentByKey.delete(key) && this.spentByKey.size >= this.maxSpentCodes) {
455
+ const oldest = this.spentByKey.keys().next();
456
+ if (!oldest.done)
457
+ this.spentByKey.delete(oldest.value);
458
+ }
459
+ this.spentByKey.set(key, this.now() + this.spentCodeTtlMs);
460
+ }
461
+ /** Whether this code digest already minted a session (within the TTL). */
462
+ isSpent(key) {
463
+ const expiresAt = this.spentByKey.get(key);
464
+ if (expiresAt === undefined)
465
+ return false;
466
+ if (this.now() >= expiresAt) {
467
+ this.spentByKey.delete(key);
468
+ return false;
469
+ }
470
+ return true;
471
+ }
472
+ /**
473
+ * Single-flight + global concurrency. A key already in flight joins the
474
+ * existing promise (never re-hits the open platform, exempt from the cap);
475
+ * a new key beyond `maxConcurrent` is refused for a fast 429.
476
+ */
477
+ share(key, start) {
478
+ const existing = this.inFlightByKey.get(key);
479
+ if (existing)
480
+ return { ok: true, result: existing };
481
+ if (this.inFlightByKey.size >= this.maxConcurrent) {
482
+ return { ok: false, retryAfterMs: EXCHANGE_BUSY_RETRY_MS };
483
+ }
484
+ const flight = start();
485
+ this.inFlightByKey.set(key, flight);
486
+ const settle = () => { this.inFlightByKey.delete(key); };
487
+ // then(settle, settle) — a .finally() chain would itself reject on
488
+ // failure and surface as an unhandled rejection.
489
+ flight.then(settle, settle);
490
+ return { ok: true, result: flight };
491
+ }
492
+ /** Drop IP buckets and spent-code digests that have left their window. The
493
+ * one full-table pass in this class — on a timer, never per request. */
494
+ prune() {
495
+ const now = this.now();
496
+ const cutoff = now - this.windowMs;
497
+ for (const [ip, hits] of this.hitsByIp) {
498
+ const live = hits.filter(at => at > cutoff);
499
+ if (live.length === 0)
500
+ this.hitsByIp.delete(ip);
501
+ else if (live.length !== hits.length)
502
+ this.hitsByIp.set(ip, live);
503
+ }
504
+ for (const [key, expiresAt] of this.spentByKey) {
505
+ if (now >= expiresAt)
506
+ this.spentByKey.delete(key);
507
+ }
508
+ this.sweeps += 1;
509
+ this.lastPruneAt = now;
510
+ }
511
+ trackedIpCount() { return this.hitsByIp.size; }
512
+ inFlightCount() { return this.inFlightByKey.size; }
513
+ spentCodeCount() { return this.spentByKey.size; }
514
+ /** Full-table sweeps performed so far. Diagnostic: this must stay flat as
515
+ * request volume grows, otherwise admission has become O(table). */
516
+ sweepCount() { return this.sweeps; }
517
+ }
518
+ function rateLimited(res, error, retryAfterMs) {
519
+ const retryAfterSeconds = Math.max(1, Math.ceil(retryAfterMs / 1000));
520
+ json(res, 429, { ok: false, error, retryAfterSeconds }, { 'retry-after': String(retryAfterSeconds) });
521
+ }
522
+ function json(res, status, value, headers = {}) {
523
+ res.writeHead(status, {
524
+ 'content-type': 'application/json; charset=utf-8',
525
+ 'cache-control': 'no-store',
526
+ 'referrer-policy': 'no-referrer',
527
+ ...headers,
528
+ });
529
+ res.end(JSON.stringify(value));
530
+ }
531
+ async function readCode(req) {
532
+ const chunks = [];
533
+ let bytes = 0;
534
+ for await (const raw of req) {
535
+ const chunk = Buffer.isBuffer(raw) ? raw : Buffer.from(raw);
536
+ bytes += chunk.byteLength;
537
+ if (bytes > MAX_AUTH_BODY_BYTES)
538
+ return null;
539
+ chunks.push(chunk);
540
+ }
541
+ let parsed;
542
+ try {
543
+ parsed = JSON.parse(Buffer.concat(chunks).toString('utf8'));
544
+ }
545
+ catch {
546
+ return null;
547
+ }
548
+ const code = parsed && typeof parsed === 'object' && !Array.isArray(parsed)
549
+ ? parsed.code
550
+ : undefined;
551
+ return typeof code === 'string' && code.length >= 4 && code.length <= 2_048 && !/[\s\0]/.test(code)
552
+ ? code
553
+ : null;
554
+ }
555
+ export function safeDashboardH5ReturnTo(value) {
556
+ if (!value || value.length > 1_024 || !value.startsWith('/#/agent-workbench'))
557
+ return '/';
558
+ const hash = value.slice(1).split('?')[0].replace(/\/+$/, '');
559
+ const bases = ['#/agent-workbench-dock', '#/agent-workbench'];
560
+ for (const base of bases) {
561
+ if (hash === base)
562
+ return `/${base}`;
563
+ if (!hash.startsWith(`${base}/`))
564
+ continue;
565
+ const encoded = hash.slice(base.length + 1);
566
+ let decoded;
567
+ try {
568
+ decoded = decodeURIComponent(encoded);
569
+ }
570
+ catch {
571
+ return '/';
572
+ }
573
+ if (!decoded || decoded.length > 512 || /[\u0000-\u001f\u007f]/.test(decoded))
574
+ return '/';
575
+ return `/${base}/${encodeURIComponent(decoded)}`;
576
+ }
577
+ return '/';
578
+ }
579
+ function h5EntryHtml(appId, exchangePath, returnTo = '/') {
580
+ const safeAppId = JSON.stringify(appId).replace(/</g, '\\u003c');
581
+ const safeExchangePath = JSON.stringify(exchangePath).replace(/</g, '\\u003c');
582
+ const safeReturnTo = JSON.stringify(safeDashboardH5ReturnTo(returnTo)).replace(/</g, '\\u003c');
583
+ return `<!doctype html>
584
+ <html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
585
+ <meta name="referrer" content="no-referrer"><title>Botmux Dashboard 登录</title>
586
+ <style>body{margin:0;min-height:100vh;display:grid;place-items:center;background:#f5f6f8;font:15px/1.6 system-ui;color:#1f2329}.card{max-width:420px;margin:24px;padding:28px;border:1px solid #d0d3d8;border-radius:4px;background:#fff}h1{font-size:20px;margin:0 0 8px}p{color:#646a73}button{border:1px solid #3370ff;border-radius:2px;padding:9px 18px;background:#fff;color:#245bdb}#error{color:#d54941}</style>
587
+ </head>
588
+ <body><main class="card"><h1>正在验证飞书身份</h1><p id="status">请在飞书客户端中打开此入口。</p><p id="error" role="alert"></p><button id="retry" hidden>重试</button></main>
589
+ <script>(function(){
590
+ var appId=${safeAppId},endpoint=${safeExchangePath},returnTo=${safeReturnTo},status=document.getElementById('status'),error=document.getElementById('error'),retry=document.getElementById('retry'),attempt=0,timer=0,controller=null,sdkScript=null,timeoutMs=${DASHBOARD_H5_CLIENT_TIMEOUT_MS},sdkUrl='https://lf-scm-cn.feishucdn.com/lark/op/h5-js-sdk-1.5.44.js';
591
+ function fail(id){if(id!==undefined&&id!==attempt)return;if(controller){controller.abort();controller=null}if(sdkScript){sdkScript.onload=sdkScript.onerror=null;sdkScript.remove();sdkScript=null}clearTimeout(timer);timer=0;attempt++;status.textContent='未能完成免登。';error.textContent='请确认正在飞书客户端中打开,或联系管理员检查入口配置。';retry.hidden=false}
592
+ function exchange(code,id){if(id!==attempt)return;controller=typeof AbortController==='function'?new AbortController():null;fetch(endpoint,{method:'POST',credentials:'same-origin',headers:{'content-type':'application/json'},body:JSON.stringify({code:code}),signal:controller?controller.signal:undefined}).then(function(r){if(!r.ok)throw new Error('denied');return r.json()}).then(function(){if(id!==attempt)return;clearTimeout(timer);timer=0;controller=null;location.replace(returnTo)}).catch(function(){fail(id)})}
593
+ function auth(id){if(id!==attempt)return;if(!window.tt){fail(id);return}var fallback=function(){if(id!==attempt)return;try{window.tt.requestAuthCode({appId:appId,success:function(r){exchange(r&&r.code,id)},fail:function(){fail(id)}})}catch(e){fail(id)}};try{if(window.tt.requestAccess){window.tt.requestAccess({appID:appId,scopeList:[],success:function(r){exchange(r&&r.code,id)},fail:function(e){if(e&&e.errno===103)fallback();else fail(id)}})}else fallback()}catch(e){fail(id)}}
594
+ function ready(id){if(id!==attempt)return;try{if(window.h5sdk&&window.h5sdk.ready)window.h5sdk.ready(function(){auth(id)});else auth(id)}catch(e){fail(id)}}
595
+ function loadSdk(id){if(window.tt||window.h5sdk){ready(id);return}sdkScript=document.createElement('script');sdkScript.src=sdkUrl;sdkScript.async=true;sdkScript.referrerPolicy='no-referrer';sdkScript.onload=function(){sdkScript=null;ready(id)};sdkScript.onerror=function(){sdkScript=null;fail(id)};document.head.appendChild(sdkScript)}
596
+ function begin(){var id=++attempt;if(controller){controller.abort();controller=null}if(sdkScript){sdkScript.onload=sdkScript.onerror=null;sdkScript.remove();sdkScript=null}clearTimeout(timer);error.textContent='';retry.hidden=true;status.textContent='正在验证飞书身份…';timer=setTimeout(function(){fail(id)},timeoutMs);loadSdk(id)}
597
+ retry.addEventListener('click',begin);begin();
598
+ })();</script></body></html>`;
599
+ }
600
+ /**
601
+ * 本地环节(审计落盘、会话创建)失败的标记,与「飞书上游换取 open_id 失败」
602
+ * 严格区分:两者的排查方向完全相反——上游失败要去查飞书应用配置与网络,本地
603
+ * 失败是磁盘满 / 审计路径不可写。把两者都报成 `feishu_exchange_failed`,会让
604
+ * 一块写满的磁盘看起来像飞书应用配错了。
605
+ */
606
+ class DashboardH5LocalFailure extends Error {
607
+ stage;
608
+ reason;
609
+ constructor(stage, reason) {
610
+ super(`dashboard h5 local failure at ${stage}`);
611
+ this.stage = stage;
612
+ this.reason = reason;
613
+ this.name = 'DashboardH5LocalFailure';
614
+ }
615
+ }
616
+ /** 只进本机 daemon 日志的错因摘要——绝不进 HTTP 响应体(错误串可能带审计文件
617
+ * 路径 / errno,那是运维信息,不是给客户端看的)。 */
618
+ function describeFailure(reason) {
619
+ return reason instanceof Error ? `${reason.name}: ${reason.message}` : String(reason);
620
+ }
621
+ export function createDashboardH5AuthController(opts) {
622
+ const { config, sessions, audit } = opts;
623
+ const exchanger = opts.exchanger ?? createFeishuH5CodeExchanger(config);
624
+ const exchangeGate = opts.exchangeGate ?? new DashboardH5ExchangeGate();
625
+ const allowed = new Set(config.allowedOpenIds);
626
+ const entryPath = config.entryPath;
627
+ const exchangePath = `${entryPath}/exchange`;
628
+ const sessionPath = `${entryPath}/session`;
629
+ const logoutPath = `${entryPath}/logout`;
630
+ const resolve = (req) => sessions.resolveToken(parseNamedCookie(req.headers.cookie, DASHBOARD_SESSION_COOKIE));
631
+ /** 落日志前抹掉 appSecret:错因只进本机 daemon 日志,但上游客户端的错误串万一
632
+ * 带上凭据,也不能就这么写进日志文件。 */
633
+ const describeForLog = (reason) => {
634
+ const text = describeFailure(reason);
635
+ return config.appSecret ? text.split(config.appSecret).join('***') : text;
636
+ };
637
+ /** 审计写不进去 = fail closed,但要让调用方认得出这是本地故障而非上游故障。 */
638
+ const auditOrFail = (userId, action) => {
639
+ try {
640
+ audit.append(controlAuditRecord(userId, 'dashboard', action));
641
+ }
642
+ catch (error) {
643
+ throw new DashboardH5LocalFailure(`audit ${action}`, error);
644
+ }
645
+ };
646
+ /**
647
+ * The whole login decision for one code, run exactly once under the gate's
648
+ * single-flight: provider call, allowlist check, session creation and the
649
+ * audit record. Everything lives inside so that N concurrent replays produce
650
+ * ONE upstream call, ONE session and ONE audit line — not N of each.
651
+ */
652
+ const exchangeOnce = async (codeKey, code) => {
653
+ let openId;
654
+ try {
655
+ ({ openId } = await exchanger.exchange(code));
656
+ }
657
+ catch (error) {
658
+ // 上游的错因优先:审计再写不进去也不能把它盖掉,否则日志里只剩本地错误、
659
+ // 看不出飞书那边到底怎么了。这条路径本来就要抛,没有任何状态要 fail closed。
660
+ try {
661
+ audit.append(controlAuditRecord('unknown', 'dashboard', 'auth.login_denied'));
662
+ }
663
+ catch (auditError) {
664
+ logger.error(`[dashboard-h5] auth.login_denied 审计写入失败:${describeForLog(auditError)}`);
665
+ }
666
+ throw error;
667
+ }
668
+ if (!allowed.has(openId)) {
669
+ auditOrFail(openId, 'auth.login_denied');
670
+ return { ok: false, openId };
671
+ }
672
+ // 审计先行,与 terminal-control 的 takeover 同一套 fail-closed 顺序:durable
673
+ // 审计写不进去就一条不可逆状态都不落。反过来(先建 session、先烧 code、再写
674
+ // 审计)一旦写失败,会留下浏览器永远拿不到的孤儿 session,还白白烧掉这个一
675
+ // 次性 code,让用户连重试都换不回登录。
676
+ // openId 已过 allowlist(配置侧按 validOpenId 过滤),create 只会因非法
677
+ // openId/token 抛错,此处不会在审计落地后再失败。
678
+ auditOrFail(openId, 'auth.login');
679
+ let created;
680
+ try {
681
+ created = sessions.create(openId);
682
+ }
683
+ catch (error) {
684
+ throw new DashboardH5LocalFailure('session create', error);
685
+ }
686
+ // Burn the code the moment it becomes a session: a later, non-concurrent
687
+ // replay must not mint a second one even if the provider would answer
688
+ // again. Only the digest is retained — never the code, never the token.
689
+ exchangeGate.markSpent(codeKey);
690
+ return { ok: true, openId, token: created.token, expiresAt: created.identity.expiresAt };
691
+ };
692
+ const handle = async (req, res, url) => {
693
+ const relevant = url.pathname === entryPath || url.pathname === exchangePath
694
+ || url.pathname === sessionPath || url.pathname === logoutPath;
695
+ if (!relevant)
696
+ return false;
697
+ if (!config.enabled) {
698
+ json(res, 404, { ok: false, error: 'h5_auth_disabled' });
699
+ return true;
700
+ }
701
+ if (!config.appId || !config.appSecret || allowed.size === 0) {
702
+ json(res, 503, { ok: false, error: 'h5_auth_not_configured' });
703
+ return true;
704
+ }
705
+ if ((req.method === 'GET' || req.method === 'HEAD') && url.pathname === entryPath) {
706
+ const body = h5EntryHtml(config.appId, exchangePath, url.searchParams.get('returnTo') ?? '/');
707
+ res.writeHead(200, {
708
+ 'content-type': 'text/html; charset=utf-8',
709
+ 'cache-control': 'no-store',
710
+ 'referrer-policy': 'no-referrer',
711
+ 'x-content-type-options': 'nosniff',
712
+ 'content-security-policy': "default-src 'none'; script-src 'self' 'unsafe-inline' https://lf-scm-cn.feishucdn.com; style-src 'unsafe-inline'; connect-src 'self'; img-src 'none'; base-uri 'none'; frame-ancestors 'self' https://*.feishu.cn https://*.larksuite.com",
713
+ });
714
+ if (req.method === 'HEAD')
715
+ res.end();
716
+ else
717
+ res.end(body);
718
+ return true;
719
+ }
720
+ if (req.method === 'POST' && url.pathname === exchangePath) {
721
+ // Requiring JSON makes cross-origin browser submission non-simple: a
722
+ // hostile origin must pass a CORS preflight, and this controller exposes
723
+ // no CORS permission. Plain HTML forms/text bodies therefore cannot turn
724
+ // a one-time code into a login-CSRF cookie.
725
+ const contentType = String(req.headers['content-type'] ?? '').split(';')[0].trim().toLowerCase();
726
+ if (contentType !== 'application/json') {
727
+ json(res, 415, { ok: false, error: 'unsupported_media_type' });
728
+ return true;
729
+ }
730
+ // Public endpoint, and each exchange can cost up to three open-platform
731
+ // requests: rate-limit per IP before even reading the body. The slot is
732
+ // consumed on attempt (invalid bodies included), like LocateRateLimiter.
733
+ const admission = exchangeGate.admit(dashboardH5ClientIp(req, config.trustedProxyHops));
734
+ if (!admission.ok) {
735
+ rateLimited(res, 'rate_limited', admission.retryAfterMs);
736
+ return true;
737
+ }
738
+ const code = await readCode(req);
739
+ if (!code) {
740
+ json(res, 400, { ok: false, error: 'invalid_authorization_code' });
741
+ return true;
742
+ }
743
+ const codeKey = exchangeCodeKey(code);
744
+ // The spent check and the single-flight lookup must observe the same
745
+ // tick — no `await` between them. Otherwise a code whose flight settles
746
+ // in the gap would be seen as neither spent nor in flight, and the
747
+ // replay would open a second flight and mint a second session.
748
+ if (exchangeGate.isSpent(codeKey)) {
749
+ json(res, 409, { ok: false, error: 'authorization_code_already_used' });
750
+ return true;
751
+ }
752
+ // Concurrent duplicates of one code share a single upstream flight AND
753
+ // its single session; distinct codes beyond the global cap 429
754
+ // immediately (never queue, so spam cannot stack pending exchanges on
755
+ // the event loop).
756
+ const flight = exchangeGate.share(codeKey, () => exchangeOnce(codeKey, code));
757
+ if (!flight.ok) {
758
+ rateLimited(res, 'exchange_busy', flight.retryAfterMs);
759
+ return true;
760
+ }
761
+ let outcome;
762
+ try {
763
+ outcome = await flight.result;
764
+ }
765
+ catch (error) {
766
+ // 错因分流:本地故障(审计不可写 / 建会话失败)报 503,飞书上游故障才
767
+ // 报 502。原始错因只落 daemon 日志——曾经这里是裸 catch,磁盘写满会让
768
+ // 全量登录静默失败并谎称是飞书的问题,日志里一条线索都没有。
769
+ if (error instanceof DashboardH5LocalFailure) {
770
+ logger.error(`[dashboard-h5] 登录本地失败(${error.stage}):${describeForLog(error.reason)}`);
771
+ json(res, 503, { ok: false, error: 'login_unavailable' });
772
+ return true;
773
+ }
774
+ logger.warn(`[dashboard-h5] 飞书换取 open_id 失败:${describeForLog(error)}`);
775
+ json(res, 502, { ok: false, error: 'feishu_exchange_failed' });
776
+ return true;
777
+ }
778
+ if (!outcome.ok) {
779
+ json(res, 403, { ok: false, error: 'open_id_not_allowed' });
780
+ return true;
781
+ }
782
+ json(res, 200, {
783
+ ok: true,
784
+ user: { openId: outcome.openId },
785
+ expiresAt: outcome.expiresAt,
786
+ redirectTo: '/',
787
+ }, { 'set-cookie': dashboardSessionCookie(outcome.token, config.sessionTtlMs, config.secureCookies) });
788
+ return true;
789
+ }
790
+ if (req.method === 'GET' && url.pathname === sessionPath) {
791
+ const identity = resolve(req);
792
+ if (!identity) {
793
+ json(res, 401, { ok: false, error: 'authentication_required' });
794
+ }
795
+ else {
796
+ json(res, 200, { ok: true, user: { openId: identity.userId }, expiresAt: identity.expiresAt });
797
+ }
798
+ return true;
799
+ }
800
+ if (req.method === 'POST' && url.pathname === logoutPath) {
801
+ const identity = resolve(req);
802
+ if (!identity) {
803
+ json(res, 401, { ok: false, error: 'authentication_required' });
804
+ return true;
805
+ }
806
+ const token = parseNamedCookie(req.headers.cookie, DASHBOARD_SESSION_COOKIE);
807
+ // 撤销在前(登出的安全动作已经完成、不可撤回),审计写失败不能再把清
808
+ // Cookie 的成功响应吞掉:否则浏览器留着一枚已死的 cookie,页面还以为自己
809
+ // 没登出。审计失败单独落日志,运维照样看得见。
810
+ sessions.revokeToken(token);
811
+ try {
812
+ audit.append(controlAuditRecord(identity.userId, 'dashboard', 'auth.logout'));
813
+ }
814
+ catch (error) {
815
+ logger.error(`[dashboard-h5] auth.logout 审计写入失败(会话已撤销):${describeForLog(error)}`);
816
+ }
817
+ json(res, 200, { ok: true }, { 'set-cookie': clearDashboardSessionCookie(config.secureCookies) });
818
+ return true;
819
+ }
820
+ json(res, 405, { ok: false, error: 'method_not_allowed' });
821
+ return true;
822
+ };
823
+ return { entryPath, exchangePath, sessionPath, logoutPath, resolve, handle };
824
+ }
825
+ //# sourceMappingURL=h5-auth.js.map