botmux 3.16.0 → 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 (619) 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/dashboard-url.d.ts +10 -1
  103. package/dist/core/dashboard-url.d.ts.map +1 -1
  104. package/dist/core/dashboard-url.js +12 -2
  105. package/dist/core/dashboard-url.js.map +1 -1
  106. package/dist/core/dispatch.d.ts.map +1 -1
  107. package/dist/core/dispatch.js +5 -4
  108. package/dist/core/dispatch.js.map +1 -1
  109. package/dist/core/existing-app-server.d.ts +22 -0
  110. package/dist/core/existing-app-server.d.ts.map +1 -0
  111. package/dist/core/existing-app-server.js +85 -0
  112. package/dist/core/existing-app-server.js.map +1 -0
  113. package/dist/core/native-topic-id.d.ts +9 -0
  114. package/dist/core/native-topic-id.d.ts.map +1 -0
  115. package/dist/core/native-topic-id.js +14 -0
  116. package/dist/core/native-topic-id.js.map +1 -0
  117. package/dist/core/per-bot-env.d.ts.map +1 -1
  118. package/dist/core/per-bot-env.js +10 -0
  119. package/dist/core/per-bot-env.js.map +1 -1
  120. package/dist/core/plugins/mcp/gateway.d.ts +27 -3
  121. package/dist/core/plugins/mcp/gateway.d.ts.map +1 -1
  122. package/dist/core/plugins/mcp/gateway.js +272 -26
  123. package/dist/core/plugins/mcp/gateway.js.map +1 -1
  124. package/dist/core/plugins/mcp/host.d.ts +18 -0
  125. package/dist/core/plugins/mcp/host.d.ts.map +1 -1
  126. package/dist/core/plugins/mcp/host.js +80 -16
  127. package/dist/core/plugins/mcp/host.js.map +1 -1
  128. package/dist/core/plugins/mcp/launch-record.d.ts +14 -0
  129. package/dist/core/plugins/mcp/launch-record.d.ts.map +1 -0
  130. package/dist/core/plugins/mcp/launch-record.js +63 -0
  131. package/dist/core/plugins/mcp/launch-record.js.map +1 -0
  132. package/dist/core/session-activity.d.ts +11 -0
  133. package/dist/core/session-activity.d.ts.map +1 -1
  134. package/dist/core/session-activity.js +23 -0
  135. package/dist/core/session-activity.js.map +1 -1
  136. package/dist/core/session-group-birth.d.ts.map +1 -1
  137. package/dist/core/session-group-birth.js +23 -8
  138. package/dist/core/session-group-birth.js.map +1 -1
  139. package/dist/core/session-manager.d.ts +1 -4
  140. package/dist/core/session-manager.d.ts.map +1 -1
  141. package/dist/core/session-manager.js +16 -20
  142. package/dist/core/session-manager.js.map +1 -1
  143. package/dist/core/shared-adopt.d.ts +20 -0
  144. package/dist/core/shared-adopt.d.ts.map +1 -0
  145. package/dist/core/shared-adopt.js +26 -0
  146. package/dist/core/shared-adopt.js.map +1 -0
  147. package/dist/core/skills/customize-command.d.ts +7 -0
  148. package/dist/core/skills/customize-command.d.ts.map +1 -0
  149. package/dist/core/skills/customize-command.js +303 -0
  150. package/dist/core/skills/customize-command.js.map +1 -0
  151. package/dist/core/terminal-write-auth.d.ts +15 -0
  152. package/dist/core/terminal-write-auth.d.ts.map +1 -1
  153. package/dist/core/terminal-write-auth.js +15 -0
  154. package/dist/core/terminal-write-auth.js.map +1 -1
  155. package/dist/core/terminal-write-frame.d.ts +30 -0
  156. package/dist/core/terminal-write-frame.d.ts.map +1 -0
  157. package/dist/core/terminal-write-frame.js +44 -0
  158. package/dist/core/terminal-write-frame.js.map +1 -0
  159. package/dist/core/types.d.ts +14 -4
  160. package/dist/core/types.d.ts.map +1 -1
  161. package/dist/core/types.js +6 -8
  162. package/dist/core/types.js.map +1 -1
  163. package/dist/core/worker-pool.d.ts +31 -6
  164. package/dist/core/worker-pool.d.ts.map +1 -1
  165. package/dist/core/worker-pool.js +444 -102
  166. package/dist/core/worker-pool.js.map +1 -1
  167. package/dist/core/worker-startup-retry.d.ts +52 -0
  168. package/dist/core/worker-startup-retry.d.ts.map +1 -0
  169. package/dist/core/worker-startup-retry.js +68 -0
  170. package/dist/core/worker-startup-retry.js.map +1 -0
  171. package/dist/daemon.d.ts +9 -8
  172. package/dist/daemon.d.ts.map +1 -1
  173. package/dist/daemon.js +467 -171
  174. package/dist/daemon.js.map +1 -1
  175. package/dist/dashboard/autostart-api.d.ts +26 -0
  176. package/dist/dashboard/autostart-api.d.ts.map +1 -0
  177. package/dist/dashboard/autostart-api.js +88 -0
  178. package/dist/dashboard/autostart-api.js.map +1 -0
  179. package/dist/dashboard/bot-payload.d.ts +5 -0
  180. package/dist/dashboard/bot-payload.d.ts.map +1 -1
  181. package/dist/dashboard/bot-payload.js +2 -0
  182. package/dist/dashboard/bot-payload.js.map +1 -1
  183. package/dist/dashboard/control-csrf.d.ts +29 -1
  184. package/dist/dashboard/control-csrf.d.ts.map +1 -1
  185. package/dist/dashboard/control-csrf.js +49 -10
  186. package/dist/dashboard/control-csrf.js.map +1 -1
  187. package/dist/dashboard/customization-api.d.ts +23 -0
  188. package/dist/dashboard/customization-api.d.ts.map +1 -0
  189. package/dist/dashboard/customization-api.js +198 -0
  190. package/dist/dashboard/customization-api.js.map +1 -0
  191. package/dist/dashboard/debug-terminal.d.ts +13 -1
  192. package/dist/dashboard/debug-terminal.d.ts.map +1 -1
  193. package/dist/dashboard/debug-terminal.js +18 -4
  194. package/dist/dashboard/debug-terminal.js.map +1 -1
  195. package/dist/dashboard/settings-write-applier.d.ts +0 -23
  196. package/dist/dashboard/settings-write-applier.d.ts.map +1 -1
  197. package/dist/dashboard/settings-write-applier.js +7 -28
  198. package/dist/dashboard/settings-write-applier.js.map +1 -1
  199. package/dist/dashboard/terminal-control-route.d.ts +32 -0
  200. package/dist/dashboard/terminal-control-route.d.ts.map +1 -0
  201. package/dist/dashboard/terminal-control-route.js +70 -0
  202. package/dist/dashboard/terminal-control-route.js.map +1 -0
  203. package/dist/dashboard/terminal-control.d.ts +64 -13
  204. package/dist/dashboard/terminal-control.d.ts.map +1 -1
  205. package/dist/dashboard/terminal-control.js +77 -14
  206. package/dist/dashboard/terminal-control.js.map +1 -1
  207. package/dist/dashboard/terminal-front-proxy.d.ts.map +1 -1
  208. package/dist/dashboard/terminal-front-proxy.js +41 -9
  209. package/dist/dashboard/terminal-front-proxy.js.map +1 -1
  210. package/dist/dashboard/vc-consumer-profiles-api.d.ts +69 -18
  211. package/dist/dashboard/vc-consumer-profiles-api.d.ts.map +1 -1
  212. package/dist/dashboard/vc-consumer-profiles-api.js +149 -29
  213. package/dist/dashboard/vc-consumer-profiles-api.js.map +1 -1
  214. package/dist/dashboard/web/agent-workbench-api.d.ts +34 -2
  215. package/dist/dashboard/web/agent-workbench-api.d.ts.map +1 -1
  216. package/dist/dashboard/web/agent-workbench-api.js +39 -4
  217. package/dist/dashboard/web/agent-workbench-api.js.map +1 -1
  218. package/dist/dashboard/web/agent-workbench-appearance.d.ts +6 -3
  219. package/dist/dashboard/web/agent-workbench-appearance.d.ts.map +1 -1
  220. package/dist/dashboard/web/agent-workbench-appearance.js +7 -4
  221. package/dist/dashboard/web/agent-workbench-appearance.js.map +1 -1
  222. package/dist/dashboard/web/agent-workbench-model.d.ts +2 -0
  223. package/dist/dashboard/web/agent-workbench-model.d.ts.map +1 -1
  224. package/dist/dashboard/web/agent-workbench-model.js.map +1 -1
  225. package/dist/dashboard/web/agent-workbench-page.js +2 -2
  226. package/dist/dashboard/web/agent-workbench-page.js.map +1 -1
  227. package/dist/dashboard/web/agent-workbench-panes.d.ts +59 -4
  228. package/dist/dashboard/web/agent-workbench-panes.d.ts.map +1 -1
  229. package/dist/dashboard/web/agent-workbench-panes.js +493 -85
  230. package/dist/dashboard/web/agent-workbench-panes.js.map +1 -1
  231. package/dist/dashboard/web/agent-workbench-session-list.d.ts +11 -7
  232. package/dist/dashboard/web/agent-workbench-session-list.d.ts.map +1 -1
  233. package/dist/dashboard/web/agent-workbench-session-list.js +24 -31
  234. package/dist/dashboard/web/agent-workbench-session-list.js.map +1 -1
  235. package/dist/dashboard/web/agent-workbench-terminal-control.d.ts +198 -0
  236. package/dist/dashboard/web/agent-workbench-terminal-control.d.ts.map +1 -0
  237. package/dist/dashboard/web/agent-workbench-terminal-control.js +292 -0
  238. package/dist/dashboard/web/agent-workbench-terminal-control.js.map +1 -0
  239. package/dist/dashboard/web/agent-workbench-view.d.ts +3 -2
  240. package/dist/dashboard/web/agent-workbench-view.d.ts.map +1 -1
  241. package/dist/dashboard/web/agent-workbench-view.js +69 -50
  242. package/dist/dashboard/web/agent-workbench-view.js.map +1 -1
  243. package/dist/dashboard/web/app.d.ts.map +1 -1
  244. package/dist/dashboard/web/app.js +58 -7
  245. package/dist/dashboard/web/app.js.map +1 -1
  246. package/dist/dashboard/web/bot-defaults-page.d.ts +24 -0
  247. package/dist/dashboard/web/bot-defaults-page.d.ts.map +1 -1
  248. package/dist/dashboard/web/bot-defaults-page.js +191 -16
  249. package/dist/dashboard/web/bot-defaults-page.js.map +1 -1
  250. package/dist/dashboard/web/bot-defaults.d.ts +17 -0
  251. package/dist/dashboard/web/bot-defaults.d.ts.map +1 -1
  252. package/dist/dashboard/web/bot-defaults.js +51 -2
  253. package/dist/dashboard/web/bot-defaults.js.map +1 -1
  254. package/dist/dashboard/web/bot-onboarding.d.ts.map +1 -1
  255. package/dist/dashboard/web/bot-onboarding.js +32 -8
  256. package/dist/dashboard/web/bot-onboarding.js.map +1 -1
  257. package/dist/dashboard/web/confirm-modal.d.ts +35 -0
  258. package/dist/dashboard/web/confirm-modal.d.ts.map +1 -0
  259. package/dist/dashboard/web/confirm-modal.js +111 -0
  260. package/dist/dashboard/web/confirm-modal.js.map +1 -0
  261. package/dist/dashboard/web/connectors-page.d.ts.map +1 -1
  262. package/dist/dashboard/web/connectors-page.js +2 -1
  263. package/dist/dashboard/web/connectors-page.js.map +1 -1
  264. package/dist/dashboard/web/customization-page.d.ts +3 -0
  265. package/dist/dashboard/web/customization-page.d.ts.map +1 -0
  266. package/dist/dashboard/web/customization-page.js +315 -0
  267. package/dist/dashboard/web/customization-page.js.map +1 -0
  268. package/dist/dashboard/web/dashboard-routes.d.ts.map +1 -1
  269. package/dist/dashboard/web/dashboard-routes.js +1 -0
  270. package/dist/dashboard/web/dashboard-routes.js.map +1 -1
  271. package/dist/dashboard/web/groups-page.d.ts.map +1 -1
  272. package/dist/dashboard/web/groups-page.js +11 -9
  273. package/dist/dashboard/web/groups-page.js.map +1 -1
  274. package/dist/dashboard/web/i18n.d.ts.map +1 -1
  275. package/dist/dashboard/web/i18n.js +226 -42
  276. package/dist/dashboard/web/i18n.js.map +1 -1
  277. package/dist/dashboard/web/monitor-room.d.ts.map +1 -1
  278. package/dist/dashboard/web/monitor-room.js +3 -2
  279. package/dist/dashboard/web/monitor-room.js.map +1 -1
  280. package/dist/dashboard/web/overview-page.d.ts.map +1 -1
  281. package/dist/dashboard/web/overview-page.js +98 -63
  282. package/dist/dashboard/web/overview-page.js.map +1 -1
  283. package/dist/dashboard/web/roles-page.d.ts.map +1 -1
  284. package/dist/dashboard/web/roles-page.js +4 -3
  285. package/dist/dashboard/web/roles-page.js.map +1 -1
  286. package/dist/dashboard/web/schedules-page.d.ts.map +1 -1
  287. package/dist/dashboard/web/schedules-page.js +165 -18
  288. package/dist/dashboard/web/schedules-page.js.map +1 -1
  289. package/dist/dashboard/web/sessions-kanban.d.ts +2 -0
  290. package/dist/dashboard/web/sessions-kanban.d.ts.map +1 -1
  291. package/dist/dashboard/web/sessions-kanban.js +142 -24
  292. package/dist/dashboard/web/sessions-kanban.js.map +1 -1
  293. package/dist/dashboard/web/sessions-page.d.ts.map +1 -1
  294. package/dist/dashboard/web/sessions-page.js +177 -94
  295. package/dist/dashboard/web/sessions-page.js.map +1 -1
  296. package/dist/dashboard/web/sessions.d.ts.map +1 -1
  297. package/dist/dashboard/web/sessions.js +5 -4
  298. package/dist/dashboard/web/sessions.js.map +1 -1
  299. package/dist/dashboard/web/settings-page.d.ts +0 -8
  300. package/dist/dashboard/web/settings-page.d.ts.map +1 -1
  301. package/dist/dashboard/web/settings-page.js +185 -65
  302. package/dist/dashboard/web/settings-page.js.map +1 -1
  303. package/dist/dashboard/web/skills/skill-library-tab.d.ts +2 -0
  304. package/dist/dashboard/web/skills/skill-library-tab.d.ts.map +1 -1
  305. package/dist/dashboard/web/skills/skill-library-tab.js +15 -1
  306. package/dist/dashboard/web/skills/skill-library-tab.js.map +1 -1
  307. package/dist/dashboard/web/skills-page.d.ts.map +1 -1
  308. package/dist/dashboard/web/skills-page.js +5 -4
  309. package/dist/dashboard/web/skills-page.js.map +1 -1
  310. package/dist/dashboard/web/store.d.ts +5 -0
  311. package/dist/dashboard/web/store.d.ts.map +1 -1
  312. package/dist/dashboard/web/store.js +4 -0
  313. package/dist/dashboard/web/store.js.map +1 -1
  314. package/dist/dashboard/web/team-federation-page.d.ts.map +1 -1
  315. package/dist/dashboard/web/team-federation-page.js +3 -2
  316. package/dist/dashboard/web/team-federation-page.js.map +1 -1
  317. package/dist/dashboard/web/toast.d.ts +15 -0
  318. package/dist/dashboard/web/toast.d.ts.map +1 -0
  319. package/dist/dashboard/web/toast.js +70 -0
  320. package/dist/dashboard/web/toast.js.map +1 -0
  321. package/dist/dashboard/web/v3-components.d.ts.map +1 -1
  322. package/dist/dashboard/web/v3-components.js +2 -1
  323. package/dist/dashboard/web/v3-components.js.map +1 -1
  324. package/dist/dashboard/web/vc-consumer-profiles-section.d.ts +10 -12
  325. package/dist/dashboard/web/vc-consumer-profiles-section.d.ts.map +1 -1
  326. package/dist/dashboard/web/vc-consumer-profiles-section.js +219 -200
  327. package/dist/dashboard/web/vc-consumer-profiles-section.js.map +1 -1
  328. package/dist/dashboard/web/whiteboards-page.d.ts.map +1 -1
  329. package/dist/dashboard/web/whiteboards-page.js +2 -1
  330. package/dist/dashboard/web/whiteboards-page.js.map +1 -1
  331. package/dist/dashboard-web/app.js +1 -1
  332. package/dist/dashboard-web/chunks/agent-workbench-dock-page-LZSUOCEQ.js +1 -0
  333. package/dist/dashboard-web/chunks/agent-workbench-page-YONDRJU7.js +1 -0
  334. package/dist/dashboard-web/chunks/bot-defaults-page-X7LV6373.js +1 -0
  335. package/dist/dashboard-web/chunks/chunk-2PVRDXEL.js +1 -0
  336. package/dist/dashboard-web/chunks/chunk-53JUKBU2.js +10 -0
  337. package/dist/dashboard-web/chunks/{chunk-5CJMHTT4.js → chunk-5QX4OYOR.js} +1 -1
  338. package/dist/dashboard-web/chunks/{chunk-NACP7BMR.js → chunk-5VRVJFQR.js} +1 -1
  339. package/dist/dashboard-web/chunks/{chunk-FDZ7WOBW.js → chunk-6ROOZWYV.js} +1 -1
  340. package/dist/dashboard-web/chunks/{chunk-F6KNPF4R.js → chunk-CK22743G.js} +15 -19
  341. package/dist/dashboard-web/chunks/chunk-CVNIMUPY.js +25 -0
  342. package/dist/dashboard-web/chunks/chunk-GJC2TMWS.js +2 -0
  343. package/dist/dashboard-web/chunks/chunk-OT6L6G7R.js +1 -0
  344. package/dist/dashboard-web/chunks/chunk-QITAMHW6.js +1 -0
  345. package/dist/dashboard-web/chunks/chunk-RHVFZC5Z.js +1 -0
  346. package/dist/dashboard-web/chunks/chunk-SOBMN2VY.js +1 -0
  347. package/dist/dashboard-web/chunks/chunk-SZOB6VK2.js +1 -0
  348. package/dist/dashboard-web/chunks/chunk-XVIXNQGC.js +44 -0
  349. package/dist/dashboard-web/chunks/chunk-YUJF34ME.js +1 -0
  350. package/dist/dashboard-web/chunks/connectors-page-LB2HEJ7F.js +1 -0
  351. package/dist/dashboard-web/chunks/customization-page-4GXBYLG6.js +105 -0
  352. package/dist/dashboard-web/chunks/feedback-page-IZQ7VLDG.js +1 -0
  353. package/dist/dashboard-web/chunks/groups-page-4WCP5OEV.js +8 -0
  354. package/dist/dashboard-web/chunks/{insights-page-HJFDYR5N.js → insights-page-TXQHIPQL.js} +1 -1
  355. package/dist/dashboard-web/chunks/{monitor-room-V7PCP47V.js → monitor-room-VBRVWXCM.js} +8 -8
  356. package/dist/dashboard-web/chunks/monitoring-page-QDDZWERE.js +2 -0
  357. package/dist/dashboard-web/chunks/office-page-PENFNYPG.js +1 -0
  358. package/dist/dashboard-web/chunks/overview-page-BZQDP6MP.js +1 -0
  359. package/dist/dashboard-web/chunks/plugin-page-JX4GSWZS.js +1 -0
  360. package/dist/dashboard-web/chunks/roles-page-VPNINZNK.js +1 -0
  361. package/dist/dashboard-web/chunks/schedules-page-BITO6HEA.js +1 -0
  362. package/dist/dashboard-web/chunks/sessions-page-ZKCOVTD2.js +8 -0
  363. package/dist/dashboard-web/chunks/settings-page-UT6JDTS6.js +2 -0
  364. package/dist/dashboard-web/chunks/skills-page-GULXVAJY.js +4 -0
  365. package/dist/dashboard-web/chunks/team-federation-page-CUUJWGEG.js +1 -0
  366. package/dist/dashboard-web/chunks/v3-page-5WVJ5AUR.js +94 -0
  367. package/dist/dashboard-web/chunks/whiteboards-page-FZ2CME4H.js +1 -0
  368. package/dist/dashboard-web/design-tokens.css +236 -0
  369. package/dist/dashboard-web/index.html +1 -0
  370. package/dist/dashboard-web/style.css +1930 -114
  371. package/dist/dashboard.js +360 -279
  372. package/dist/dashboard.js.map +1 -1
  373. package/dist/desktop/renderer/app.js +6 -0
  374. package/dist/desktop/renderer/app.js.map +1 -1
  375. package/dist/dsh-runner.js +139 -17
  376. package/dist/dsh-runner.js.map +1 -1
  377. package/dist/global-config.d.ts +33 -2
  378. package/dist/global-config.d.ts.map +1 -1
  379. package/dist/global-config.js +62 -0
  380. package/dist/global-config.js.map +1 -1
  381. package/dist/i18n/en.d.ts.map +1 -1
  382. package/dist/i18n/en.js +13 -4
  383. package/dist/i18n/en.js.map +1 -1
  384. package/dist/i18n/index.d.ts +27 -3
  385. package/dist/i18n/index.d.ts.map +1 -1
  386. package/dist/i18n/index.js +33 -4
  387. package/dist/i18n/index.js.map +1 -1
  388. package/dist/i18n/zh.d.ts.map +1 -1
  389. package/dist/i18n/zh.js +13 -4
  390. package/dist/i18n/zh.js.map +1 -1
  391. package/dist/im/lark/card-builder.d.ts +2 -0
  392. package/dist/im/lark/card-builder.d.ts.map +1 -1
  393. package/dist/im/lark/card-builder.js +7 -4
  394. package/dist/im/lark/card-builder.js.map +1 -1
  395. package/dist/im/lark/card-handler.d.ts.map +1 -1
  396. package/dist/im/lark/card-handler.js +135 -20
  397. package/dist/im/lark/card-handler.js.map +1 -1
  398. package/dist/im/lark/event-dispatcher.d.ts +38 -3
  399. package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
  400. package/dist/im/lark/event-dispatcher.js +163 -13
  401. package/dist/im/lark/event-dispatcher.js.map +1 -1
  402. package/dist/im/lark/v3-blocked-card-handler.d.ts.map +1 -1
  403. package/dist/im/lark/v3-blocked-card-handler.js +2 -1
  404. package/dist/im/lark/v3-blocked-card-handler.js.map +1 -1
  405. package/dist/im/lark/v3-blocked-card.d.ts +1 -1
  406. package/dist/im/lark/v3-blocked-card.d.ts.map +1 -1
  407. package/dist/im/lark/v3-blocked-card.js +14 -0
  408. package/dist/im/lark/v3-blocked-card.js.map +1 -1
  409. package/dist/index-core-only.js +2 -0
  410. package/dist/index-core-only.js.map +1 -1
  411. package/dist/index-daemon.js +3 -0
  412. package/dist/index-daemon.js.map +1 -1
  413. package/dist/platform/binding.d.ts +37 -0
  414. package/dist/platform/binding.d.ts.map +1 -1
  415. package/dist/platform/binding.js +15 -0
  416. package/dist/platform/binding.js.map +1 -1
  417. package/dist/services/bridge-turn-journal.d.ts +63 -0
  418. package/dist/services/bridge-turn-journal.d.ts.map +1 -0
  419. package/dist/services/bridge-turn-journal.js +149 -0
  420. package/dist/services/bridge-turn-journal.js.map +1 -0
  421. package/dist/services/bridge-turn-queue.d.ts +8 -1
  422. package/dist/services/bridge-turn-queue.d.ts.map +1 -1
  423. package/dist/services/bridge-turn-queue.js +2 -1
  424. package/dist/services/bridge-turn-queue.js.map +1 -1
  425. package/dist/services/codex-app-runner-protocol.d.ts +5 -0
  426. package/dist/services/codex-app-runner-protocol.d.ts.map +1 -1
  427. package/dist/services/codex-app-runner-protocol.js +5 -0
  428. package/dist/services/codex-app-runner-protocol.js.map +1 -1
  429. package/dist/services/customization-store.d.ts +93 -0
  430. package/dist/services/customization-store.d.ts.map +1 -0
  431. package/dist/services/customization-store.js +542 -0
  432. package/dist/services/customization-store.js.map +1 -0
  433. package/dist/services/file-bridge-path.d.ts.map +1 -1
  434. package/dist/services/file-bridge-path.js +3 -0
  435. package/dist/services/file-bridge-path.js.map +1 -1
  436. package/dist/services/grok-transcript.d.ts.map +1 -1
  437. package/dist/services/grok-transcript.js +44 -9
  438. package/dist/services/grok-transcript.js.map +1 -1
  439. package/dist/services/local-cli-opener.d.ts.map +1 -1
  440. package/dist/services/local-cli-opener.js +12 -3
  441. package/dist/services/local-cli-opener.js.map +1 -1
  442. package/dist/services/model-catalog.d.ts +54 -0
  443. package/dist/services/model-catalog.d.ts.map +1 -0
  444. package/dist/services/model-catalog.js +147 -0
  445. package/dist/services/model-catalog.js.map +1 -0
  446. package/dist/services/omp-transcript.d.ts +27 -0
  447. package/dist/services/omp-transcript.d.ts.map +1 -0
  448. package/dist/services/omp-transcript.js +214 -0
  449. package/dist/services/omp-transcript.js.map +1 -0
  450. package/dist/services/platform-team-store.d.ts +16 -8
  451. package/dist/services/platform-team-store.d.ts.map +1 -1
  452. package/dist/services/platform-team-store.js +17 -10
  453. package/dist/services/platform-team-store.js.map +1 -1
  454. package/dist/services/project-scanner.d.ts.map +1 -1
  455. package/dist/services/project-scanner.js +13 -8
  456. package/dist/services/project-scanner.js.map +1 -1
  457. package/dist/services/resumable-session-discovery.d.ts.map +1 -1
  458. package/dist/services/resumable-session-discovery.js +117 -35
  459. package/dist/services/resumable-session-discovery.js.map +1 -1
  460. package/dist/services/structured-bridge-clis.d.ts +5 -3
  461. package/dist/services/structured-bridge-clis.d.ts.map +1 -1
  462. package/dist/services/structured-bridge-clis.js +5 -1
  463. package/dist/services/structured-bridge-clis.js.map +1 -1
  464. package/dist/services/vc-meeting-action-gate.d.ts.map +1 -1
  465. package/dist/services/vc-meeting-action-gate.js +15 -4
  466. package/dist/services/vc-meeting-action-gate.js.map +1 -1
  467. package/dist/services/vc-meeting-consumer-profile-bootstrap.d.ts +46 -66
  468. package/dist/services/vc-meeting-consumer-profile-bootstrap.d.ts.map +1 -1
  469. package/dist/services/vc-meeting-consumer-profile-bootstrap.js +33 -292
  470. package/dist/services/vc-meeting-consumer-profile-bootstrap.js.map +1 -1
  471. package/dist/services/vc-meeting-consumer-profile-templates.js +3 -3
  472. package/dist/services/vc-meeting-consumer-profile-templates.js.map +1 -1
  473. package/dist/services/vc-meeting-delivery-store.d.ts +18 -0
  474. package/dist/services/vc-meeting-delivery-store.d.ts.map +1 -1
  475. package/dist/services/vc-meeting-delivery-store.js +55 -0
  476. package/dist/services/vc-meeting-delivery-store.js.map +1 -1
  477. package/dist/services/vc-meeting-send-policy.d.ts +21 -1
  478. package/dist/services/vc-meeting-send-policy.d.ts.map +1 -1
  479. package/dist/services/vc-meeting-send-policy.js +30 -2
  480. package/dist/services/vc-meeting-send-policy.js.map +1 -1
  481. package/dist/services/vc-meeting-shared-consumer-catalog-store.d.ts +78 -0
  482. package/dist/services/vc-meeting-shared-consumer-catalog-store.d.ts.map +1 -0
  483. package/dist/services/vc-meeting-shared-consumer-catalog-store.js +227 -0
  484. package/dist/services/vc-meeting-shared-consumer-catalog-store.js.map +1 -0
  485. package/dist/services/vc-meeting-shared-consumer-catalog.d.ts +53 -0
  486. package/dist/services/vc-meeting-shared-consumer-catalog.d.ts.map +1 -0
  487. package/dist/services/vc-meeting-shared-consumer-catalog.js +161 -0
  488. package/dist/services/vc-meeting-shared-consumer-catalog.js.map +1 -0
  489. package/dist/setup/bot-config-editor.d.ts.map +1 -1
  490. package/dist/setup/bot-config-editor.js +1 -0
  491. package/dist/setup/bot-config-editor.js.map +1 -1
  492. package/dist/setup/open-platform-automation.d.ts +20 -0
  493. package/dist/setup/open-platform-automation.d.ts.map +1 -1
  494. package/dist/setup/open-platform-automation.js +27 -0
  495. package/dist/setup/open-platform-automation.js.map +1 -1
  496. package/dist/skills/customization-bundle.d.ts +83 -0
  497. package/dist/skills/customization-bundle.d.ts.map +1 -0
  498. package/dist/skills/customization-bundle.js +214 -0
  499. package/dist/skills/customization-bundle.js.map +1 -0
  500. package/dist/skills/definitions.d.ts.map +1 -1
  501. package/dist/skills/definitions.js +10 -3
  502. package/dist/skills/definitions.js.map +1 -1
  503. package/dist/skills/effective-builtins.d.ts +38 -0
  504. package/dist/skills/effective-builtins.d.ts.map +1 -0
  505. package/dist/skills/effective-builtins.js +96 -0
  506. package/dist/skills/effective-builtins.js.map +1 -0
  507. package/dist/skills/injection-mode.d.ts +4 -1
  508. package/dist/skills/injection-mode.d.ts.map +1 -1
  509. package/dist/skills/injection-mode.js +11 -3
  510. package/dist/skills/injection-mode.js.map +1 -1
  511. package/dist/skills/installer.d.ts.map +1 -1
  512. package/dist/skills/installer.js +24 -1
  513. package/dist/skills/installer.js.map +1 -1
  514. package/dist/skills/prompt-fragments.d.ts +58 -0
  515. package/dist/skills/prompt-fragments.d.ts.map +1 -0
  516. package/dist/skills/prompt-fragments.js +88 -0
  517. package/dist/skills/prompt-fragments.js.map +1 -0
  518. package/dist/types.d.ts +51 -0
  519. package/dist/types.d.ts.map +1 -1
  520. package/dist/utils/cli-usage-limit.d.ts +50 -0
  521. package/dist/utils/cli-usage-limit.d.ts.map +1 -1
  522. package/dist/utils/cli-usage-limit.js +43 -0
  523. package/dist/utils/cli-usage-limit.js.map +1 -1
  524. package/dist/utils/idle-detector.d.ts +12 -0
  525. package/dist/utils/idle-detector.d.ts.map +1 -1
  526. package/dist/utils/idle-detector.js +88 -2
  527. package/dist/utils/idle-detector.js.map +1 -1
  528. package/dist/utils/stuck-detector.d.ts +5 -0
  529. package/dist/utils/stuck-detector.d.ts.map +1 -1
  530. package/dist/utils/stuck-detector.js +41 -0
  531. package/dist/utils/stuck-detector.js.map +1 -1
  532. package/dist/utils/usage-limit-tracker.d.ts +46 -0
  533. package/dist/utils/usage-limit-tracker.d.ts.map +1 -0
  534. package/dist/utils/usage-limit-tracker.js +91 -0
  535. package/dist/utils/usage-limit-tracker.js.map +1 -0
  536. package/dist/utils/user-token.js +1 -1
  537. package/dist/utils/user-token.js.map +1 -1
  538. package/dist/worker.js +812 -125
  539. package/dist/worker.js.map +1 -1
  540. package/dist/workflows/definition.d.ts +14 -14
  541. package/dist/workflows/events/payloads.d.ts +12 -12
  542. package/dist/workflows/events/schema.d.ts +164 -164
  543. package/dist/workflows/migration/v2-to-v3.js +1 -1
  544. package/dist/workflows/migration/v2-to-v3.js.map +1 -1
  545. package/dist/workflows/v3/architect.d.ts.map +1 -1
  546. package/dist/workflows/v3/architect.js +11 -5
  547. package/dist/workflows/v3/architect.js.map +1 -1
  548. package/dist/workflows/v3/artifact-contract-declarations.d.ts +22 -0
  549. package/dist/workflows/v3/artifact-contract-declarations.d.ts.map +1 -0
  550. package/dist/workflows/v3/artifact-contract-declarations.js +95 -0
  551. package/dist/workflows/v3/artifact-contract-declarations.js.map +1 -0
  552. package/dist/workflows/v3/contract.d.ts +1 -0
  553. package/dist/workflows/v3/contract.d.ts.map +1 -1
  554. package/dist/workflows/v3/contract.js.map +1 -1
  555. package/dist/workflows/v3/daemon-run.d.ts +2 -2
  556. package/dist/workflows/v3/daemon-run.d.ts.map +1 -1
  557. package/dist/workflows/v3/daemon-run.js +4 -0
  558. package/dist/workflows/v3/daemon-run.js.map +1 -1
  559. package/dist/workflows/v3/dag.d.ts +7 -0
  560. package/dist/workflows/v3/dag.d.ts.map +1 -1
  561. package/dist/workflows/v3/dag.js +69 -8
  562. package/dist/workflows/v3/dag.js.map +1 -1
  563. package/dist/workflows/v3/ephemeral-pool.js +1 -1
  564. package/dist/workflows/v3/ephemeral-pool.js.map +1 -1
  565. package/dist/workflows/v3/event-contract.d.ts +3 -1
  566. package/dist/workflows/v3/event-contract.d.ts.map +1 -1
  567. package/dist/workflows/v3/host.d.ts.map +1 -1
  568. package/dist/workflows/v3/host.js +5 -1
  569. package/dist/workflows/v3/host.js.map +1 -1
  570. package/dist/workflows/v3/journal.d.ts +1 -1
  571. package/dist/workflows/v3/journal.d.ts.map +1 -1
  572. package/dist/workflows/v3/journal.js.map +1 -1
  573. package/dist/workflows/v3/library-materialize.d.ts.map +1 -1
  574. package/dist/workflows/v3/library-materialize.js +9 -1
  575. package/dist/workflows/v3/library-materialize.js.map +1 -1
  576. package/dist/workflows/v3/library-schema.d.ts +3 -1
  577. package/dist/workflows/v3/library-schema.d.ts.map +1 -1
  578. package/dist/workflows/v3/library-schema.js +20 -4
  579. package/dist/workflows/v3/library-schema.js.map +1 -1
  580. package/dist/workflows/v3/library-store.d.ts.map +1 -1
  581. package/dist/workflows/v3/library-store.js +15 -3
  582. package/dist/workflows/v3/library-store.js.map +1 -1
  583. package/dist/workflows/v3/run-envelope.d.ts +16 -16
  584. package/dist/workflows/v3/shared-node-runtime.d.ts +2 -1
  585. package/dist/workflows/v3/shared-node-runtime.d.ts.map +1 -1
  586. package/dist/workflows/v3/shared-node-runtime.js +48 -7
  587. package/dist/workflows/v3/shared-node-runtime.js.map +1 -1
  588. package/package.json +2 -1
  589. package/dist/dashboard-web/chunks/agent-workbench-dock-page-IKAC5CFL.js +0 -1
  590. package/dist/dashboard-web/chunks/agent-workbench-page-M53UN5EB.js +0 -1
  591. package/dist/dashboard-web/chunks/bot-defaults-page-YKE5SDZF.js +0 -10
  592. package/dist/dashboard-web/chunks/chunk-22WIADUT.js +0 -2
  593. package/dist/dashboard-web/chunks/chunk-24ONYQMR.js +0 -1
  594. package/dist/dashboard-web/chunks/chunk-2W6SCN3M.js +0 -1
  595. package/dist/dashboard-web/chunks/chunk-4XNJX6B3.js +0 -1
  596. package/dist/dashboard-web/chunks/chunk-5THX4DI2.js +0 -66
  597. package/dist/dashboard-web/chunks/chunk-7SZRL6FY.js +0 -1
  598. package/dist/dashboard-web/chunks/chunk-AGCPF6MT.js +0 -1
  599. package/dist/dashboard-web/chunks/chunk-FXW3V4JR.js +0 -1
  600. package/dist/dashboard-web/chunks/chunk-SYC6D5XU.js +0 -1
  601. package/dist/dashboard-web/chunks/connectors-page-VOISXXII.js +0 -1
  602. package/dist/dashboard-web/chunks/feedback-page-RN6HRZMS.js +0 -1
  603. package/dist/dashboard-web/chunks/groups-page-UQT6DCB7.js +0 -8
  604. package/dist/dashboard-web/chunks/monitoring-page-NKEKVQAP.js +0 -2
  605. package/dist/dashboard-web/chunks/office-page-GVJF73XW.js +0 -1
  606. package/dist/dashboard-web/chunks/overview-page-KHLV6VTI.js +0 -1
  607. package/dist/dashboard-web/chunks/plugin-page-J3OF5IY4.js +0 -1
  608. package/dist/dashboard-web/chunks/roles-page-WQIRSOLZ.js +0 -1
  609. package/dist/dashboard-web/chunks/schedules-page-J2DEY2X6.js +0 -1
  610. package/dist/dashboard-web/chunks/sessions-page-7BQFEA5P.js +0 -8
  611. package/dist/dashboard-web/chunks/settings-page-BJEIOFJH.js +0 -2
  612. package/dist/dashboard-web/chunks/skills-page-HGIFR3IU.js +0 -4
  613. package/dist/dashboard-web/chunks/team-federation-page-MR5ZPL4B.js +0 -1
  614. package/dist/dashboard-web/chunks/v3-page-GBR5JWPE.js +0 -94
  615. package/dist/dashboard-web/chunks/whiteboards-page-QAZM4BN3.js +0 -1
  616. package/dist/services/vc-meeting-consumer-profile-store.d.ts +0 -39
  617. package/dist/services/vc-meeting-consumer-profile-store.d.ts.map +0 -1
  618. package/dist/services/vc-meeting-consumer-profile-store.js +0 -191
  619. package/dist/services/vc-meeting-consumer-profile-store.js.map +0 -1
package/dist/daemon.js CHANGED
@@ -4,6 +4,7 @@ import { readFileSync, existsSync, mkdirSync, unlinkSync } from 'node:fs';
4
4
  import { installDaemonRejectionGuard } from './utils/daemon-rejection-guard.js';
5
5
  import { atomicWriteFileSync } from './utils/atomic-write.js';
6
6
  import { readPeerCrossRef } from './services/peer-cross-ref-store.js';
7
+ import { parseBotSteerDirective } from './core/bot-steer-directive.js';
7
8
  import { readAllowedUsersResolveCache, writeAllowedUsersResolveCache } from './utils/allowed-users-cache.js';
8
9
  import { join, dirname } from 'node:path';
9
10
  import { homedir, loadavg, cpus, totalmem, freemem } from 'node:os';
@@ -114,6 +115,7 @@ import { runIdempotencyFailClose, runWithdrawAutoClose, } from './core/daemon-ba
114
115
  import { runDetachedBotTurnMutation, tryWithBotTurnMutation, withBotTurnAdmission, withBotTurnMutation, } from './core/bot-turn-mutation-gate.js';
115
116
  import { claimInitialUserTurn, isInitialUserTurnPending, releaseInitialUserTurn } from './core/initial-user-turn.js';
116
117
  import { applyQueuedCodexAppLegacyFallback, mergeQueuedCodexAppTurn } from './core/session-create.js';
118
+ import { fillNativeTopicId } from './core/native-topic-id.js';
117
119
  import { findOnlineDaemon, listOnlineDaemons } from './utils/daemon-discovery.js';
118
120
  import { beginReplyTargetTurn, buildTurnParticipantsFrom, fallbackTurnId, isSubstituteTurn, resolveInboundReplyTarget, resolveSessionReplyTarget, syncReplyTargetState } from './core/reply-target.js';
119
121
  import { readDeferredTopicBinding } from './core/deferred-topic-binding.js';
@@ -188,7 +190,7 @@ function republishResolvedAllowedUsers(larkAppId, resolved) {
188
190
  catch { /* best effort */ }
189
191
  }
190
192
  let vcMeetingTerminalReconciler;
191
- import { isBotMentioned, probeBotOpenId, startLarkEventDispatcher, markForwardFollowupsSessionsReady, writeBotInfoFile, canOperate, canRunDaemonCommand, evaluateTalk, evaluateBotTalk, evaluateAskAnswerTalk, askCustomReplyCandidate, grantCommandRestriction, isKnownPeerBot, checkRequiredScopes } from './im/lark/event-dispatcher.js';
193
+ import { isBotMentioned, probeBotOpenId, startLarkEventDispatcher, markForwardFollowupsSessionsReady, writeBotInfoFile, canOperate, canRunDaemonCommand, evaluateTalk, evaluateBotTalk, evaluateAskAnswerTalk, askCustomReplyCandidate, grantCommandRestriction, isKnownPeerBot, resolveSiblingBotNameByUnionId, checkRequiredScopes, ensureVcMeetingEventsSubscribed } from './im/lark/event-dispatcher.js';
192
194
  import { getDocSubscription, listAllDocSubscriptions, putDocSubscription, removeDocSubscription, setDocCommentPollCursor } from './services/doc-subs-store.js';
193
195
  import { BOT_REPLY_SENTINEL, subscribeDocFile, unsubscribeDocFile, addCommentReaction, removeCommentReaction, hasBotSentinel, isBotAuthoredReply, listDocComments } from './im/lark/doc-comment.js';
194
196
  import { learnFromMentions, resolveSender, flushIdentityCacheSync } from './im/lark/identity-cache.js';
@@ -200,7 +202,7 @@ import { shutdownBackendDisposition } from './core/persistent-backend.js';
200
202
  import { abortRemoteShutdownFleet, canAbortVerifiedExitedRemotePreparation, collectUniqueDaemonShutdownSessions, commitPreparedRemoteShutdown, isPreparedRemoteSessionCurrent, persistPreparedRemoteShutdownFleet, prepareRemoteFleetForShutdown, } from './core/remote-shutdown-detach.js';
201
203
  import { BOT_TURN_MUTATION_SHUTDOWN_ACQUIRE_TIMEOUT_MS, DAEMON_SHUTDOWN_MAX_MS, DAEMON_WORKER_EXIT_GRACE_MS, } from './core/shutdown-budgets.js';
202
204
  import { evaluateVcMeetingConsumerIsolation } from './services/vc-meeting-consumer-isolation.js';
203
- import { markSessionActivity, announcePendingRepoSession, publishAttentionPatch, publishLastInputFromBotPatch, publishSessionMessagePreviewPatch, publishClosedSessionPatch, clearAgentAttention, } from './core/session-activity.js';
205
+ import { markSessionActivity, announcePendingRepoSession, publishAttentionPatch, publishLastInputFromBotPatch, publishNativeTopicLinkPatch, publishSessionMessagePreviewPatch, publishClosedSessionPatch, clearAgentAttention, } from './core/session-activity.js';
204
206
  import { emitSessionLifecycleHook } from './services/session-lifecycle-hooks.js';
205
207
  import { botAutoWorktreeEnabled } from './services/default-worktree.js';
206
208
  import { setCardDispatcher as setAskCardDispatcher, setCanTalkChecker as setAskCanTalkChecker, setAskPersistStore as setAskPersistStoreBroker, registerAsk as registerAskBroker, restorePersistedAsks as restorePersistedAsksBroker, findPendingAskByAnchor, submitCustomReply, } from './core/ask-broker.js';
@@ -222,7 +224,7 @@ import { scheduleSessionGroupTitle } from './services/session-group-title.js';
222
224
  import { setChatReplyMode } from './services/chat-reply-mode-store.js';
223
225
  import { hasVcMeetingEndedTombstone, listVcMeetingRuntimeSessionsByListenerAndAgent, listVcMeetingRuntimeSessions, pruneExpiredVcMeetingRuntimeSessions, recordVcMeetingEndedTombstone, recordVcMeetingRuntimeSession, removeVcMeetingRuntimeSession, } from './services/vc-meeting-runtime-store.js';
224
226
  import { computeVcMeetingConsumerProfileHash, normalizeVcMeetingProfileInstructions, } from './services/vc-meeting-profile-instructions.js';
225
- import { bootstrapVcMeetingDefaultConsumerProfile } from './services/vc-meeting-consumer-profile-bootstrap.js';
227
+ import { bindVcMeetingConsumerCatalogToBot } from './services/vc-meeting-shared-consumer-catalog.js';
226
228
  import { getVcMeetingPreparation, normalizeVcMeetingNumber, } from './services/vc-meeting-preparations-store.js';
227
229
  import { abandonPoisonedVcMeetingDelivery, getVcMeetingDeliveryStatus, handleVcMeetingTurnTerminal, handleVcMeetingWorkerGenerationExit, receiveVcMeetingDelivery, registerVcMeetingMember, retryPoisonedVcMeetingDelivery, } from './services/vc-meeting-delivery-receiver.js';
228
230
  import { abandonVcMeetingDeliveryStream, applyVcMeetingMemberProjection, expireVcMeetingDeliveryLeases, findVcMeetingDeliveryByKey, getVcMeetingMemberProjection, listVcMeetingMemberProjections, reconcileVcMeetingDeliveriesOnBoot, } from './services/vc-meeting-delivery-store.js';
@@ -231,7 +233,7 @@ import { buildVcMeetingDeliveryEntries, renderVcMeetingDeliveryItem, sealVcMeeti
231
233
  import { applyVcMeetingHubMemberProjection, freezeVcMeetingHubDeliveryAssignment, getVcMeetingHubCloseState, getVcMeetingHubDeliveryAssignment, getVcMeetingHubMember, listVcMeetingHubMembers, observeVcMeetingHubReceiverReceipt, updateVcMeetingHubCloseState, } from './services/vc-meeting-delivery-hub-store.js';
232
234
  import { getVcMeetingFeedMetadataState, ingestVcMeetingFeedMetadata, } from './services/vc-meeting-feed-metadata-store.js';
233
235
  import { VcMeetingTerminalReconciler } from './services/vc-meeting-terminal-reconciler.js';
234
- import { resolveVcMeetingImTurnOrigin, verifyVcMeetingManagedOriginClaim, } from './services/vc-meeting-send-policy.js';
236
+ import { resolveVcMeetingImTurnOrigin, verifyVcMeetingManagedOriginClaim, evaluateVcMeetingManagedSend, } from './services/vc-meeting-send-policy.js';
235
237
  import { ensureVcMeetingListenerTopicRoot, } from './services/vc-meeting-listener-topic-store.js';
236
238
  import { finishVcMeetingManagedActionProvider, finishVcMeetingManagedApprovalCard, requestVcMeetingManagedImAction, requestVcMeetingManagedAction, resolveVcMeetingManagedActionApproval, } from './services/vc-meeting-action-gate.js';
237
239
  import { claimVcMeetingApprovalCardAttempt, findVcMeetingAction, listVcMeetingActionScopes, listVcMeetingActions, reconcileVcMeetingActionsOnBoot, } from './services/vc-meeting-action-store.js';
@@ -1129,14 +1131,34 @@ function vcMeetingConsumerRecoveryResolutionGraceMs() {
1129
1131
  const vcMeetingClosingConsumerSessions = new Map();
1130
1132
  let vcMeetingAgentGlobalEnabledOverrideForTest;
1131
1133
  let vcMeetingAgentGlobalListenerBotAppIdOverrideForTest;
1134
+ let vcMeetingListenerPinDeprecationWarned = false;
1132
1135
  function vcMeetingAgentGlobalEnabled() {
1133
1136
  return vcMeetingAgentGlobalEnabledOverrideForTest ?? isVcMeetingAgentGloballyEnabled();
1134
1137
  }
1138
+ /**
1139
+ * RETIRED (2026-08): the global VC listener pin is gone — every bot with
1140
+ * `vcMeetingAgent.enabled` now handles the meeting events IT receives, so a
1141
+ * meeting can run multiple bots (each with its own presets) instead of routing
1142
+ * everything through one designated listener. Always returns undefined so the
1143
+ * `listenerAppId &&` guards at every call site short-circuit to the
1144
+ * every-bot-handles-its-own behaviour (which was already the semantics when
1145
+ * `listenerBotAppId` was unset). A legacy `vcMeetingAgent.listenerBotAppId`
1146
+ * still in config is ignored with a one-time warning.
1147
+ *
1148
+ * The test override is retained ONLY so the test harness can still call the
1149
+ * setter without a signature change; it no longer re-pins routing.
1150
+ */
1135
1151
  function vcMeetingAgentGlobalListenerAppId() {
1136
- if (vcMeetingAgentGlobalListenerBotAppIdOverrideForTest !== undefined) {
1137
- return vcMeetingAgentGlobalListenerBotAppIdOverrideForTest ?? undefined;
1152
+ const legacyPin = vcMeetingAgentGlobalListenerBotAppIdOverrideForTest !== undefined
1153
+ ? (vcMeetingAgentGlobalListenerBotAppIdOverrideForTest ?? undefined)
1154
+ : vcMeetingAgentGlobalListenerBotAppId();
1155
+ if (legacyPin && !vcMeetingListenerPinDeprecationWarned) {
1156
+ vcMeetingListenerPinDeprecationWarned = true;
1157
+ logger.warn(`[vc-agent] vcMeetingAgent.listenerBotAppId=${legacyPin} is deprecated and ignored: `
1158
+ + 'every bot with vcMeetingAgent.enabled now handles its own meeting invites. '
1159
+ + 'Remove this field from the global config.');
1138
1160
  }
1139
- return vcMeetingAgentGlobalListenerBotAppId();
1161
+ return undefined;
1140
1162
  }
1141
1163
  function vcMeetingPushIsTrackedLifecycleEvent(ctx) {
1142
1164
  const key = ctx.meeting.id ? vcMeetingSessionKey(ctx.larkAppId, ctx.meeting.id) : '';
@@ -1151,7 +1173,11 @@ const DEFAULT_VC_MEETING_FLUSH_INTERVAL_MS = 30_000;
1151
1173
  const DEFAULT_VC_MEETING_TIME_ZONE = 'Asia/Shanghai';
1152
1174
  const DEFAULT_VC_MEETING_CONSUMER_SELECTION_TIMEOUT_MS = 20_000;
1153
1175
  const DEFAULT_VC_MEETING_CONSUMER_INJECT_INTERVAL_MS = 30_000;
1154
- const DEFAULT_VC_MEETING_CONSUMER_MIN_BATCH_CHARS = 400;
1176
+ // Effectively no char-accumulation gate: with minBatchItems=1 a single
1177
+ // finalized transcript segment already qualifies, and the operator explicitly
1178
+ // asked for no "wait until enough text piles up" behavior (esp. at meeting
1179
+ // start). Kept as a config knob for bots that want to raise it.
1180
+ const DEFAULT_VC_MEETING_CONSUMER_MIN_BATCH_CHARS = 1;
1155
1181
  const VC_MEETING_CONSUMER_DELIVERY_MAX_ITEMS = 100;
1156
1182
  const VC_MEETING_CONSUMER_DELIVERY_MAX_RENDERED_CHARS = 20_000;
1157
1183
  // By default, keep the selected agent as fresh as the listener group: one new
@@ -1345,11 +1371,42 @@ function vcMeetingConsumerMinBatchItems(cfg) {
1345
1371
  function vcMeetingConsumerMaxInjectIntervalMs(cfg) {
1346
1372
  return cfg.meetingConsumer?.maxInjectIntervalMs ?? DEFAULT_VC_MEETING_CONSUMER_MAX_INJECT_INTERVAL_MS;
1347
1373
  }
1348
- function defaultVcMeetingTextOutputPolicy() {
1349
- return 'approval';
1374
+ function defaultVcMeetingTextOutputPolicy(cfg) {
1375
+ // In-meeting text now sends without per-message approval by default; an
1376
+ // operator can restore the review gate per-bot via
1377
+ // meetingConsumer.textOutputPolicy = 'approval' (or 'deny').
1378
+ const configured = cfg?.meetingConsumer?.textOutputPolicy;
1379
+ if (configured === 'approval' || configured === 'deny' || configured === 'allow')
1380
+ return configured;
1381
+ return 'allow';
1350
1382
  }
1351
1383
  function defaultVcMeetingVoiceOutputPolicy(cfg) {
1352
- return cfg.realtimeVoice?.enabled === true ? 'approval' : 'deny';
1384
+ // Voice is denied only when realtime voice is explicitly OFF. Realtime voice is
1385
+ // now ON BY DEFAULT (vcMeetingRealtimeVoiceEnabled: unset ⇒ enabled), so unless
1386
+ // an operator set realtimeVoice.enabled:false, voice falls through to the
1387
+ // default below. ⚠ That default is 'allow' — matching text — meaning a bot
1388
+ // pulled into a meeting speaks aloud WITHOUT per-utterance approval by default.
1389
+ // Tighten per-bot to 'approval'/'deny' via meetingConsumer.voiceOutputPolicy
1390
+ // (dashboard-editable). 'allow' only drops the per-send approval step; the
1391
+ // managed request-output/action gate + capability check still apply.
1392
+ if (!vcMeetingRealtimeVoiceEnabled(cfg))
1393
+ return 'deny';
1394
+ const configured = cfg.meetingConsumer?.voiceOutputPolicy;
1395
+ if (configured === 'approval' || configured === 'deny' || configured === 'allow')
1396
+ return configured;
1397
+ return 'allow';
1398
+ }
1399
+ /**
1400
+ * 实时语音能力现在**默认开启**:`realtimeVoice.enabled` 未配 = 视为开,只有显式
1401
+ * `false` 才关。scope 已 fleet 级预授(实测含 vc:meeting.bot.realtime:write),所以
1402
+ * 语音关着只是保守默认、不是权限卡。
1403
+ *
1404
+ * 注意「能力开」不等于「入会即建连」:语音 WebSocket 只在这个 bot 真要发言时
1405
+ * (deliverVcMeetingVoiceOutput → ensureVcMeetingRealtimeVoiceSession)按需建立,
1406
+ * 纯监听 / 从不发言的 bot 不会挂空闲连接。见 maybeStartVcMeetingRealtimeVoice。
1407
+ */
1408
+ function vcMeetingRealtimeVoiceEnabled(cfg) {
1409
+ return cfg?.realtimeVoice?.enabled !== false;
1353
1410
  }
1354
1411
  function vcMeetingOutputReviewTimeoutMs(channel) {
1355
1412
  return channel === 'voice' ? DEFAULT_VC_MEETING_VOICE_REVIEW_TIMEOUT_MS : DEFAULT_VC_MEETING_TEXT_REVIEW_TIMEOUT_MS;
@@ -1616,36 +1673,29 @@ async function ensureVcMeetingReceiverSession(request, existingSessionId, receiv
1616
1673
  if (!existing) {
1617
1674
  throw new Error(`registered receiver session is not active or lacks managed side-effect isolation: ${existingSessionId}`);
1618
1675
  }
1676
+ // Plan B: the meeting agent is an ordinary chat-scope session, so a chat can
1677
+ // legitimately host several meeting projections in one transcript. The old
1678
+ // strict "one session ⇔ one exact meeting/member/epoch" identity check no
1679
+ // longer holds. Only require that the resolved session is the right listener
1680
+ // chat under the right agent app (verified via the resolver binding above)
1681
+ // and stamp/refresh the delivery-identity metadata for this member epoch so
1682
+ // later meeting-directed output (Stage 6) can find it. The marker is pure
1683
+ // metadata now — it does not affect routing.
1619
1684
  const ds = [...activeSessions.values()].find(candidate => candidate.session.sessionId === existingSessionId) ?? findActiveBySessionId(existingSessionId);
1620
- const identity = ds?.session.vcMeetingReceiver;
1621
- if (!identity
1622
- || identity.listenerAppId !== request.meeting.listenerAppId
1623
- || identity.meetingId !== request.meeting.meetingId
1624
- || identity.memberId !== request.member.memberId
1625
- || identity.memberEpoch !== request.member.epoch) {
1626
- throw new Error('registered receiver session is not dedicated to this meeting member epoch');
1685
+ if (ds && ds.chatId === request.outputRoute.chatId) {
1686
+ stampVcMeetingBinding(ds, request);
1627
1687
  }
1628
1688
  return existing;
1629
1689
  }
1630
- // A receiver session is a dedicated conversation. It deliberately shares
1631
- // the listener chat only as an output route; its activeSessions key is based
1632
- // on sessionId (activeSessionKey), so an ordinary chat-scope session or a
1633
- // second meeting/member cannot collapse into the same CLI transcript.
1690
+ // Plan B core cut: a meeting agent is a normal chat-scope session in its
1691
+ // listener group, keyed by the ordinary `(chatId, appId)` slot. Plain IM and
1692
+ // meeting transcripts therefore fold into the SAME session (transcripts are
1693
+ // delivered by target.sessionId; IM is routed by the chat anchor). Reuse an
1694
+ // existing chat-scope session at that slot if present (whether it was opened
1695
+ // by IM or by an earlier meeting), else create a cold `worker:null` session
1696
+ // shaped exactly like an auto-created IM session so triggerSessionTurn's
1697
+ // cold-fork path spawns it on first delivery.
1634
1698
  const chatId = request.outputRoute.chatId;
1635
- const matching = [...activeSessions.values()].find((candidate) => {
1636
- const identity = candidate.session.vcMeetingReceiver;
1637
- return candidate.larkAppId === selfAppId
1638
- && identity?.listenerAppId === request.meeting.listenerAppId
1639
- && identity.meetingId === request.meeting.meetingId
1640
- && identity.memberId === request.member.memberId
1641
- && identity.memberEpoch === request.member.epoch;
1642
- });
1643
- if (matching) {
1644
- const binding = resolveVcMeetingReceiverSession(matching.session.sessionId);
1645
- if (!binding)
1646
- throw new Error('existing dedicated receiver session has an unsupported CLI adapter');
1647
- return binding;
1648
- }
1649
1699
  const bot = getBot(selfAppId);
1650
1700
  const isolation = vcMeetingConsumerIsolationForBot(bot.config);
1651
1701
  if (!isolation.decision.ok) {
@@ -1657,56 +1707,84 @@ async function ensureVcMeetingReceiverSession(request, existingSessionId, receiv
1657
1707
  // whose environment/working dir was never set up for bwrap, and the CLI would
1658
1708
  // fail to spawn or be unable to work (the "joined but never replies" bug).
1659
1709
  const receiverSandboxed = isolation.decision.isolated;
1660
- const rawWorkingDir = findOncallChat(selfAppId, chatId)?.workingDir
1661
- ?? effectiveDefaultWorkingDir(bot.config)
1662
- ?? bot.config.workingDir
1663
- ?? '~';
1664
- const workingDir = validateWorkingDir(rawWorkingDir, localeForBot(selfAppId));
1665
- if (!workingDir.ok)
1666
- throw new Error(workingDir.error);
1667
- const session = sessionStore.createSession(chatId, chatId, `[Meeting] ${request.meeting.meetingId}`.slice(0, 50), 'group');
1668
- const now = Date.now();
1669
- session.larkAppId = selfAppId;
1670
- session.scope = 'chat';
1671
- session.vcMeetingReceiver = {
1710
+ const key = sessionKey(chatId, selfAppId);
1711
+ const bound = await withActiveSessionKeyLock(activeSessions, key, () => {
1712
+ const current = activeSessions.get(key);
1713
+ if (current) {
1714
+ // A chat-scope session already occupies this slot. Adopt it as the meeting
1715
+ // agent by stamping the delivery-identity metadata; do NOT create a second
1716
+ // universe. The session keeps whatever launch/sandbox decision it already
1717
+ // froze we never retroactively weaken or strengthen a live session here.
1718
+ stampVcMeetingBinding(current, request);
1719
+ return current;
1720
+ }
1721
+ const rawWorkingDir = findOncallChat(selfAppId, chatId)?.workingDir
1722
+ ?? effectiveDefaultWorkingDir(bot.config)
1723
+ ?? bot.config.workingDir
1724
+ ?? '~';
1725
+ const workingDir = validateWorkingDir(rawWorkingDir, localeForBot(selfAppId));
1726
+ if (!workingDir.ok)
1727
+ throw new Error(workingDir.error);
1728
+ const session = sessionStore.createSession(chatId, chatId, `[Meeting] ${request.meeting.meetingId}`.slice(0, 50), 'group');
1729
+ const now = Date.now();
1730
+ session.larkAppId = selfAppId;
1731
+ session.scope = 'chat';
1732
+ session.vcMeetingReceiver = {
1733
+ listenerAppId: request.meeting.listenerAppId,
1734
+ meetingId: request.meeting.meetingId,
1735
+ memberId: request.member.memberId,
1736
+ memberEpoch: request.member.epoch,
1737
+ };
1738
+ session.lastMessageAt = new Date(now).toISOString();
1739
+ session.workingDir = workingDir.resolvedPath;
1740
+ session.cliId = bot.config.cliId;
1741
+ // Freeze the security-critical launch decision at creation. A later live
1742
+ // Bot-config edit must neither weaken this session nor make an old
1743
+ // unisolated session appear eligible retroactively. Under plan B the frozen
1744
+ // value follows the bot's opt-in, not a hardcoded true.
1745
+ session.sandbox = receiverSandboxed;
1746
+ session.sandboxHidePaths = receiverSandboxed ? (bot.config.sandboxHidePaths ?? []) : [];
1747
+ session.sandboxReadonlyPaths = receiverSandboxed ? (bot.config.sandboxReadonlyPaths ?? []) : [];
1748
+ session.sandboxNetwork = receiverSandboxed ? (bot.config.sandboxNetwork !== false) : true;
1749
+ session.backendType = isolation.backendType;
1750
+ sessionStore.updateSession(session);
1751
+ const ds = {
1752
+ session,
1753
+ worker: null,
1754
+ workerPort: null,
1755
+ workerToken: null,
1756
+ larkAppId: selfAppId,
1757
+ chatId,
1758
+ chatType: 'group',
1759
+ scope: 'chat',
1760
+ spawnedAt: Date.parse(session.createdAt) || now,
1761
+ cliVersion: getCurrentCliVersion(),
1762
+ lastMessageAt: now,
1763
+ hasHistory: false,
1764
+ workingDir: workingDir.resolvedPath,
1765
+ };
1766
+ activeSessions.set(key, ds);
1767
+ return ds;
1768
+ });
1769
+ const binding = resolveVcMeetingReceiverSession(bound.session.sessionId);
1770
+ if (!binding)
1771
+ throw new Error('meeting agent session has an unsupported CLI adapter');
1772
+ return binding;
1773
+ }
1774
+ /** Stamp/refresh the VC meeting delivery-identity metadata onto an ordinary
1775
+ * chat-scope session (Plan B). This binds the session as the delivery + future
1776
+ * meeting-output target for this member epoch WITHOUT changing routing: the
1777
+ * session is still keyed by its normal chat slot. The most-recent meeting/member
1778
+ * epoch wins when one chat hosts several meetings, matching the transcript
1779
+ * delivery model (targets pick the session by id, not by this marker). */
1780
+ function stampVcMeetingBinding(ds, request) {
1781
+ ds.session.vcMeetingReceiver = {
1672
1782
  listenerAppId: request.meeting.listenerAppId,
1673
1783
  meetingId: request.meeting.meetingId,
1674
1784
  memberId: request.member.memberId,
1675
1785
  memberEpoch: request.member.epoch,
1676
1786
  };
1677
- session.lastMessageAt = new Date(now).toISOString();
1678
- session.workingDir = workingDir.resolvedPath;
1679
- session.cliId = bot.config.cliId;
1680
- // Freeze the security-critical launch decision at receiver creation. A
1681
- // later live Bot-config edit must neither weaken this session nor make an
1682
- // old unisolated receiver appear eligible retroactively. Under plan B the
1683
- // frozen value follows the bot's opt-in, not a hardcoded true.
1684
- session.sandbox = receiverSandboxed;
1685
- session.sandboxHidePaths = receiverSandboxed ? (bot.config.sandboxHidePaths ?? []) : [];
1686
- session.sandboxReadonlyPaths = receiverSandboxed ? (bot.config.sandboxReadonlyPaths ?? []) : [];
1687
- session.sandboxNetwork = receiverSandboxed ? (bot.config.sandboxNetwork !== false) : true;
1688
- session.backendType = isolation.backendType;
1689
- sessionStore.updateSession(session);
1690
- const ds = {
1691
- session,
1692
- worker: null,
1693
- workerPort: null,
1694
- workerToken: null,
1695
- larkAppId: selfAppId,
1696
- chatId,
1697
- chatType: 'group',
1698
- scope: 'chat',
1699
- spawnedAt: Date.parse(session.createdAt) || now,
1700
- cliVersion: getCurrentCliVersion(),
1701
- lastMessageAt: now,
1702
- hasHistory: false,
1703
- workingDir: workingDir.resolvedPath,
1704
- };
1705
- activeSessions.set(activeSessionKey(ds), ds);
1706
- const binding = resolveVcMeetingReceiverSession(session.sessionId);
1707
- if (!binding)
1708
- throw new Error('created receiver session has an unsupported CLI adapter');
1709
- return binding;
1787
+ sessionStore.updateSession(ds.session);
1710
1788
  }
1711
1789
  function vcMeetingDeliveryReceiverDeps(receiverAppId) {
1712
1790
  const selfAppId = receiverAppId ?? selfDaemonLarkAppId;
@@ -1722,6 +1800,18 @@ function vcMeetingDeliveryReceiverDeps(receiverAppId) {
1722
1800
  const target = findActiveBySessionId(request.target.sessionId ?? '');
1723
1801
  if (target)
1724
1802
  target.vcMeetingImTurnOrigin = undefined;
1803
+ // VC transcript delivery turns dispatch straight through triggerSessionTurn,
1804
+ // which (unlike the ordinary IM path) never calls beginNewTurn — so without
1805
+ // this the meeting agent's streaming-card lifecycle would never arm and no
1806
+ // live card would post for a transcript-driven turn. Under Plan B the
1807
+ // meeting agent is an ordinary chat-scope session whose card should surface
1808
+ // like any group session (the "看不到流式卡片" report), so arm it for every
1809
+ // delivery turn. A silent-responseMode turn still suppresses only its final
1810
+ // output (managedFinalOutputSuppressed), not the live card.
1811
+ if (target?.session.vcMeetingReceiver && context.stableTurnId) {
1812
+ const title = target.currentTurnTitle || target.session.title || '会议监听';
1813
+ beginNewTurn(target, title, context.stableTurnId);
1814
+ }
1725
1815
  return triggerSessionTurn(request, { larkAppId: selfAppId, activeSessions }, {
1726
1816
  stableTurnId: context.stableTurnId,
1727
1817
  beforeDispatch: context.beforeDispatch,
@@ -1926,7 +2016,13 @@ async function maybeCatchUpVcMeetingConsumerBeforeTurn(data, ctx) {
1926
2016
  logger.warn(`[vc-agent] IM follow-up routed with possibly stale context meeting=${caughtUp.candidate.meetingId} `
1927
2017
  + `agent=${ctx.larkAppId} status=${caughtUp.catchUpStatus} error=${caughtUp.catchUpError ?? '-'}`);
1928
2018
  }
1929
- return { anchorOverride: `vc-receiver:${caughtUp.candidate.receiverSessionId}` };
2019
+ // Plan B: the meeting agent is an ordinary chat-scope session keyed by the
2020
+ // normal `(chatId, appId)` slot, so the natural chat anchor already resolves
2021
+ // it — no `vc-receiver:` anchor override is needed (or correct) anymore. This
2022
+ // hook now only (a) blocks on ambiguous/undeliverable meeting context and
2023
+ // (b) stamps vcMeetingImTurnOrigin so an @mention follow-up carries the
2024
+ // meeting delivery identity for catch-up/echo purposes.
2025
+ return;
1930
2026
  }
1931
2027
  async function pinVcMeetingConsumerChatReplyMode(larkAppId, chatId) {
1932
2028
  if (vcMeetingConsumerUsesLocalReceiver(larkAppId)) {
@@ -2282,7 +2378,14 @@ export async function noteTurnReceived(ds, triggerMessageId, _prompt, _sender, _
2282
2378
  // message — not a worker status edge — means type-ahead / busy-batched messages
2283
2379
  // each get their own ✋. `finishTurnReactions` flips every pending ✋ to ✅ when
2284
2380
  // the worker next goes idle.
2285
- if (ds.session.vcMeetingReceiver)
2381
+ // Plan B: a meeting agent is an ordinary chat-scope session that hosts plain
2382
+ // user turns. A plain user message should get the ✋→✅ progress reaction like
2383
+ // any card-off session. Only a stamped meeting @mention follow-up stays
2384
+ // reaction-free (it is meeting-driven and routes through the audited listener
2385
+ // action, not the ordinary progress-reaction channel). Transcript deliveries
2386
+ // never reach this inbound-message acceptance point at all.
2387
+ if (ds.session.vcMeetingReceiver
2388
+ && resolveVcMeetingImTurnOrigin(ds.session, triggerMessageId) !== undefined)
2286
2389
  return;
2287
2390
  // Turn-exact card-off check: the reaction ack belongs to THIS message's turn,
2288
2391
  // not to whichever turn most recently overwrote currentReplyTarget.
@@ -4194,7 +4297,8 @@ workflowDaemonMutationRoute('retry', async (reply, params, body, identity) => {
4194
4297
  error: outcome.reason === 'missing' ? 'unknown_run'
4195
4298
  : outcome.reason === 'loop-node' ? 'loop_node_use_grant'
4196
4299
  : outcome.reason === 'host-effect-uncertain' ? 'host_effect_reconcile_required'
4197
- : 'not_blocked',
4300
+ : outcome.reason === 'revise-workflow-required' ? 'workflow_revision_required'
4301
+ : 'not_blocked',
4198
4302
  });
4199
4303
  }
4200
4304
  // requested / already-requested → make sure the run is moving.
@@ -5185,8 +5289,66 @@ ipcRoute('POST', '/api/vc-meetings/action-request', async (req, res) => {
5185
5289
  claimedTurnId: typeof body.originTurnId === 'string' ? body.originTurnId : undefined,
5186
5290
  claimedDispatchAttempt: claimedAttempt,
5187
5291
  });
5188
- if (!verified.ok)
5189
- return jsonRes(res, 403, verified);
5292
+ // Plan B: the meeting agent is an ordinary chat-scope session that interleaves
5293
+ // plain user IM turns with meeting deliveries and reaches idle between them.
5294
+ // The in-memory managedTurnOrigin is cleared at each delivery turn's terminal
5295
+ // edge, so a request-output the model runs AFTER the delivery turn goes idle
5296
+ // (or after an intervening IM turn) finds no live origin and would fail
5297
+ // `origin_unproven` — even though the durable delivery receipt still fully
5298
+ // authorizes it. Fall back to the DURABLE receipt for a claimed delivery origin
5299
+ // (turnId + dispatchAttempt): evaluateVcMeetingManagedSend re-derives authority
5300
+ // from the on-disk receipt exactly as the final-output path does — receipt must
5301
+ // exist for THIS receiver session, the attempt must match, its status must be
5302
+ // dispatched/completed, the projection must still be active, and a silent
5303
+ // delivery is refused. This never authorizes anything the receipt itself
5304
+ // wouldn't, so it does not weaken the boundary; it only survives the idle gap.
5305
+ //
5306
+ // The fallback intentionally does NOT require the live origin to be absent:
5307
+ // live verification proves origin via the rotating worker capability only,
5308
+ // and non-sandboxed sessions have no origin-channel transport for that
5309
+ // capability (managedOriginChannelRequired is darwin-isolate/credential-only
5310
+ // bwrap), so their live check ALWAYS fails — including while the delivery
5311
+ // turn is still executing. Gating the fallback on "live origin cleared"
5312
+ // hard-bricked exactly that case (in-turn speech from a non-sandboxed
5313
+ // meeting agent). The durable receipt check is the same strength either way,
5314
+ // so the fallback runs whenever live verification failed.
5315
+ let effectiveVerified = verified;
5316
+ if (!verified.ok
5317
+ && typeof body.originTurnId === 'string'
5318
+ && body.originTurnId.trim()
5319
+ && claimedAttempt !== undefined) {
5320
+ const claimedDeliveryTurnId = body.originTurnId.trim();
5321
+ const durable = evaluateVcMeetingManagedSend(config.session.dataDir, {
5322
+ receiverSessionId,
5323
+ receiverSession: true,
5324
+ turnId: claimedDeliveryTurnId,
5325
+ dispatchAttempt: claimedAttempt,
5326
+ allowTerminalReceipt: true,
5327
+ // In-meeting output channel: silent responseMode gates only listener-group
5328
+ // auto-post, not in-meeting speech (the hub applies capability +
5329
+ // textOutputPolicy/voiceOutputPolicy). Prove receipt identity/liveness here
5330
+ // but do not apply the silent veto.
5331
+ forInMeetingOutput: true,
5332
+ });
5333
+ if (durable.ok && durable.kind === 'listener_thread') {
5334
+ logger.info(`[vc-agent] request-output authorized via durable receipt fallback `
5335
+ + `(live origin ${ds.managedTurnOrigin ? 'present but unverifiable — no capability transport' : 'cleared at turn terminal'}) `
5336
+ + `session=${receiverSessionId.slice(0, 8)} `
5337
+ + `turn=${claimedDeliveryTurnId.slice(0, 12)} attempt=${claimedAttempt}`);
5338
+ effectiveVerified = {
5339
+ ok: true,
5340
+ origin: {
5341
+ receiverSessionId,
5342
+ turnId: claimedDeliveryTurnId,
5343
+ dispatchAttempt: claimedAttempt,
5344
+ currentImTurnId: undefined,
5345
+ currentImTurnOrigin: undefined,
5346
+ },
5347
+ };
5348
+ }
5349
+ }
5350
+ if (!effectiveVerified.ok)
5351
+ return jsonRes(res, 403, effectiveVerified);
5190
5352
  const channel = body.channel === 'text' || body.channel === 'voice' ? body.channel : undefined;
5191
5353
  const content = sanitizeVcMeetingOutputContent(body.content, 'content');
5192
5354
  const reason = sanitizeVcMeetingOutputContent(body.reason, 'reason');
@@ -5198,13 +5360,13 @@ ipcRoute('POST', '/api/vc-meetings/action-request', async (req, res) => {
5198
5360
  ? body.expectedListenerAppId.trim()
5199
5361
  : '';
5200
5362
  const expectedMeetingId = typeof body.expectedMeetingId === 'string' ? body.expectedMeetingId.trim() : '';
5201
- if (verified.origin.dispatchAttempt === undefined) {
5202
- const imOrigin = resolveVcMeetingImTurnOrigin(ds.session, verified.origin.turnId);
5203
- if (!verified.origin.turnId
5204
- || verified.origin.currentImTurnId !== verified.origin.turnId
5363
+ if (effectiveVerified.origin.dispatchAttempt === undefined) {
5364
+ const imOrigin = resolveVcMeetingImTurnOrigin(ds.session, effectiveVerified.origin.turnId);
5365
+ if (!effectiveVerified.origin.turnId
5366
+ || effectiveVerified.origin.currentImTurnId !== effectiveVerified.origin.turnId
5205
5367
  || !imOrigin
5206
5368
  || imOrigin.receiverSessionId !== receiverSessionId
5207
- || imOrigin.larkMessageId !== verified.origin.turnId) {
5369
+ || imOrigin.larkMessageId !== effectiveVerified.origin.turnId) {
5208
5370
  return jsonRes(res, 409, {
5209
5371
  ok: false,
5210
5372
  errorCode: 'im_turn_origin_mismatch',
@@ -5233,14 +5395,14 @@ ipcRoute('POST', '/api/vc-meetings/action-request', async (req, res) => {
5233
5395
  });
5234
5396
  return jsonRes(res, upstream.status, upstream.body);
5235
5397
  }
5236
- if (!verified.origin.turnId) {
5398
+ if (!effectiveVerified.origin.turnId) {
5237
5399
  return jsonRes(res, 409, {
5238
5400
  ok: false,
5239
5401
  errorCode: 'delivery_origin_mismatch',
5240
5402
  error: 'durable delivery action has no stable turn id',
5241
5403
  });
5242
5404
  }
5243
- const lookup = findVcMeetingDeliveryByKey(config.session.dataDir, verified.origin.turnId, {
5405
+ const lookup = findVcMeetingDeliveryByKey(config.session.dataDir, effectiveVerified.origin.turnId, {
5244
5406
  receiverSessionId,
5245
5407
  });
5246
5408
  if (!lookup) {
@@ -5271,8 +5433,8 @@ ipcRoute('POST', '/api/vc-meetings/action-request', async (req, res) => {
5271
5433
  body: JSON.stringify({
5272
5434
  agentAppId: ds.larkAppId,
5273
5435
  receiverSessionId,
5274
- stableTurnId: verified.origin.turnId,
5275
- dispatchAttempt: verified.origin.dispatchAttempt,
5436
+ stableTurnId: effectiveVerified.origin.turnId,
5437
+ dispatchAttempt: effectiveVerified.origin.dispatchAttempt,
5276
5438
  channel,
5277
5439
  content,
5278
5440
  ...(reason ? { reason } : {}),
@@ -5428,7 +5590,48 @@ function effectiveVcMeetingAgentConfig(larkAppId) {
5428
5590
  // `restoreVcMeetingRuntimeSessionsForBot`, whose call site sits OUTSIDE the
5429
5591
  // `!cfg.apiOnly` boot block — so a migrated bots.json (normal VC bot flipped to
5430
5592
  // apiOnly, stale runtime record on disk) can no longer re-spawn lark-cli at boot.
5431
- return vcMeetingAgentConfigActive(getBot(larkAppId)?.config);
5593
+ //
5594
+ // 这是 daemon 侧唯一一处产出 VcMeetingAgentConfig 的地方,所以角色预设的绑定也在
5595
+ // 这里做:没有自己 consumerProfiles 的 bot 继承 fleet 共享目录,且**所有**预设的
5596
+ // 执行方一律重绑为 larkAppId 本人——「拉 A 进会却拉 B 进群」在这一层被彻底切断,
5597
+ // 历史配置里写歪的 agentAppId 也在读路径上被纠正,不需要迁移写盘。
5598
+ const cfg = vcMeetingAgentConfigActive(getBot(larkAppId)?.config);
5599
+ if (!cfg)
5600
+ return cfg;
5601
+ return withDefaultVcMeetingJoinProfile(larkAppId, bindVcMeetingConsumerCatalogToBot(larkAppId, cfg));
5602
+ }
5603
+ /**
5604
+ * 没配 `larkCliProfile` 的 bot,入会身份默认用它**自己的 appId** 作为 lark-cli
5605
+ * profile 名。
5606
+ *
5607
+ * 背景:入会门禁 {@link ensureVcMeetingJoinProfile} 要求 `larkCliProfile` 非空,
5608
+ * 否则以 `no_profile` 拒绝入会。而这个字段过去只有 Dashboard 的「配置权限」按钮
5609
+ * 会落盘(见 preflightVcMeetingBot),于是 fleet 里绝大多数从没点过那颗按钮的 bot
5610
+ * 被拉进会一律被拒——正是「拉别的 bot 进会还是不行」。
5611
+ *
5612
+ * 把默认值放在**读路径**而不是要求人工点按钮,是因为这个默认值是纯确定性的:
5613
+ * - 就是 bot **自己的 appId**,既不是跨 app 身份、也不含任何 secret,app-scoped,
5614
+ * 不触碰 owner 身份边界;
5615
+ * - profile 一旦有名字,{@link ensureLarkCliBotProfile} 会用该 bot **自己**保存的
5616
+ * appSecret 走 `--app-secret-stdin`(不进 argv、只落 lark-cli 加密库、只在受信
5617
+ * daemon 里)自动注册它——PR #392 当年之所以 fail-closed(“no faked profile”),
5618
+ * 是因为那时没有自动注册、写个名字会 ringing;PR #782 补上自动注册后这个顾虑
5619
+ * 已消除。
5620
+ *
5621
+ * 真正需要人在场的是**开放平台权限开通 / 事件订阅**(可能要扫码登录),那部分仍留在
5622
+ * 「配置权限」按钮里;权限/secret 真缺时,入会会在后面以 `missing_secret` /
5623
+ * `add_failed` / scope 校验等**可操作**错误失败,而不是这里这条造出来的 `no_profile`。
5624
+ *
5625
+ * apiOnly bot 在 {@link vcMeetingAgentConfigActive} 已返回 undefined,走不到这里,
5626
+ * 所以默认只加给结构上真能入会的 bot。纯函数,不改入参、不写盘。
5627
+ */
5628
+ function withDefaultVcMeetingJoinProfile(larkAppId, cfg) {
5629
+ if (cfg.larkCliProfile?.trim())
5630
+ return cfg;
5631
+ const appId = larkAppId.trim();
5632
+ if (!appId)
5633
+ return cfg;
5634
+ return { ...cfg, larkCliProfile: appId };
5432
5635
  }
5433
5636
  function configuredVcMeetingListenerChatId(cfg) {
5434
5637
  return cfg.listenerChatId ?? cfg.notificationChatId;
@@ -5557,7 +5760,7 @@ function restoreVcMeetingRuntimeSessionsForBot(larkAppId, cfg) {
5557
5760
  session.consumerPaused = migratePreparedConsumer ? false : record.consumerPaused;
5558
5761
  if (session.consumerMode === 'agent')
5559
5762
  session.consumerLastInjectedAtMs = undefined;
5560
- session.textOutputPolicy = record.textOutputPolicy ?? defaultVcMeetingTextOutputPolicy();
5763
+ session.textOutputPolicy = record.textOutputPolicy ?? defaultVcMeetingTextOutputPolicy(cfg);
5561
5764
  session.voiceOutputPolicy = record.voiceOutputPolicy ?? defaultVcMeetingVoiceOutputPolicy(cfg);
5562
5765
  session.syncIntervalMs = record.syncIntervalMs;
5563
5766
  session.consumerSelectionExpiresAt = record.consumerSelectionExpiresAt;
@@ -5727,32 +5930,36 @@ function restoreVcMeetingRuntimeSessionsForBot(larkAppId, cfg) {
5727
5930
  }
5728
5931
  async function maybeStartVcMeetingRealtimeVoice(larkAppId, session, cfg) {
5729
5932
  const voiceCfg = cfg.realtimeVoice;
5730
- if (voiceCfg?.enabled !== true)
5933
+ if (!vcMeetingRealtimeVoiceEnabled(cfg))
5934
+ return;
5935
+ // 按需建连:实时语音能力默认开,但入会时**不**急着开语音 WS——真正要发言时
5936
+ // deliverVcMeetingVoiceOutput 会按需 ensureVcMeetingRealtimeVoiceSession 建连,
5937
+ // 纯监听 / 从不发言的 bot 不挂空闲连接。唯一在入会时就预热的情况是显式配了
5938
+ // testSpeakOnStartText 的 dogfood 场景(要在入会瞬间说一句自检话)。
5939
+ if (!voiceCfg?.testSpeakOnStartText || session.realtimeVoiceTestUtteranceSent)
5731
5940
  return;
5732
5941
  try {
5733
5942
  const voice = await ensureVcMeetingRealtimeVoiceSession(larkAppId, session, cfg);
5734
- if (voiceCfg.testSpeakOnStartText && !session.realtimeVoiceTestUtteranceSent) {
5735
- session.realtimeVoiceTestUtteranceSent = true;
5736
- void (async () => {
5737
- let sent = false;
5738
- try {
5739
- const r = await voice.speak(voiceCfg.testSpeakOnStartText);
5740
- sent = true;
5741
- logger.info(`[vc-agent] realtime voice test utterance sent meeting=${session.state.meeting.id} frames=${r.frames} durationMs=${r.durationMs}`);
5742
- await delay(DEFAULT_VC_REALTIME_TEST_SPEAK_CLOSE_GRACE_MS);
5743
- }
5744
- catch (err) {
5745
- logger.warn(`[vc-agent] realtime voice test utterance failed meeting=${session.state.meeting.id}: ${err instanceof Error ? err.message : String(err)}`);
5746
- }
5747
- finally {
5748
- await voice.stop(sent ? 'test-speak-finished' : 'test-speak-failed').catch((err) => {
5749
- logger.warn(`[vc-agent] realtime voice test utterance stop failed meeting=${session.state.meeting.id}: ${err instanceof Error ? err.message : String(err)}`);
5750
- });
5751
- if (session.realtimeVoice === voice)
5752
- session.realtimeVoice = undefined;
5753
- }
5754
- })();
5755
- }
5943
+ session.realtimeVoiceTestUtteranceSent = true;
5944
+ void (async () => {
5945
+ let sent = false;
5946
+ try {
5947
+ const r = await voice.speak(voiceCfg.testSpeakOnStartText);
5948
+ sent = true;
5949
+ logger.info(`[vc-agent] realtime voice test utterance sent meeting=${session.state.meeting.id} frames=${r.frames} durationMs=${r.durationMs}`);
5950
+ await delay(DEFAULT_VC_REALTIME_TEST_SPEAK_CLOSE_GRACE_MS);
5951
+ }
5952
+ catch (err) {
5953
+ logger.warn(`[vc-agent] realtime voice test utterance failed meeting=${session.state.meeting.id}: ${err instanceof Error ? err.message : String(err)}`);
5954
+ }
5955
+ finally {
5956
+ await voice.stop(sent ? 'test-speak-finished' : 'test-speak-failed').catch((err) => {
5957
+ logger.warn(`[vc-agent] realtime voice test utterance stop failed meeting=${session.state.meeting.id}: ${err instanceof Error ? err.message : String(err)}`);
5958
+ });
5959
+ if (session.realtimeVoice === voice)
5960
+ session.realtimeVoice = undefined;
5961
+ }
5962
+ })();
5756
5963
  }
5757
5964
  catch (err) {
5758
5965
  await session.realtimeVoice?.stop('start-failed').catch(() => { });
@@ -5761,9 +5968,9 @@ async function maybeStartVcMeetingRealtimeVoice(larkAppId, session, cfg) {
5761
5968
  }
5762
5969
  }
5763
5970
  async function ensureVcMeetingRealtimeVoiceSession(larkAppId, session, cfg) {
5764
- const voiceCfg = cfg.realtimeVoice;
5765
- if (voiceCfg?.enabled !== true)
5971
+ if (!vcMeetingRealtimeVoiceEnabled(cfg))
5766
5972
  throw new Error('realtime voice is disabled');
5973
+ const voiceCfg = cfg.realtimeVoice;
5767
5974
  if (session.realtimeVoice && (session.realtimeVoice.status === 'failed' || session.realtimeVoice.status === 'stopped')) {
5768
5975
  await session.realtimeVoice.stop('rebuild-stale-session').catch((err) => {
5769
5976
  logger.warn(`[vc-agent] stale realtime voice cleanup failed meeting=${session.state.meeting.id}: ${err instanceof Error ? err.message : String(err)}`);
@@ -5780,9 +5987,9 @@ async function ensureVcMeetingRealtimeVoiceSession(larkAppId, session, cfg) {
5780
5987
  protocol: createProtoRealtimeVoiceProtocol(),
5781
5988
  transport,
5782
5989
  audioFormat: {
5783
- ...(voiceCfg.sampleRate !== undefined ? { sampleRate: voiceCfg.sampleRate } : {}),
5784
- ...(voiceCfg.channels !== undefined ? { channels: voiceCfg.channels } : {}),
5785
- ...(voiceCfg.frameMs !== undefined ? { frameMs: voiceCfg.frameMs } : {}),
5990
+ ...(voiceCfg?.sampleRate !== undefined ? { sampleRate: voiceCfg.sampleRate } : {}),
5991
+ ...(voiceCfg?.channels !== undefined ? { channels: voiceCfg.channels } : {}),
5992
+ ...(voiceCfg?.frameMs !== undefined ? { frameMs: voiceCfg.frameMs } : {}),
5786
5993
  },
5787
5994
  });
5788
5995
  }
@@ -5907,7 +6114,7 @@ function getOrCreateVcMeetingDaemonSession(larkAppId, meeting, cfg, opts = {}) {
5907
6114
  monitoringStarted: false,
5908
6115
  ...(listenerChatId ? { listenerChatId } : {}),
5909
6116
  pendingItems: [],
5910
- textOutputPolicy: defaultVcMeetingTextOutputPolicy(),
6117
+ textOutputPolicy: defaultVcMeetingTextOutputPolicy(cfg),
5911
6118
  voiceOutputPolicy: defaultVcMeetingVoiceOutputPolicy(cfg),
5912
6119
  pendingOutputRequests: {},
5913
6120
  consumerPendingItems: [],
@@ -5937,7 +6144,7 @@ function getOrCreateVcMeetingDaemonSession(larkAppId, meeting, cfg, opts = {}) {
5937
6144
  session.listenerChatId = listenerChatId;
5938
6145
  session.state.notificationChatId = listenerChatId;
5939
6146
  }
5940
- session.textOutputPolicy ??= defaultVcMeetingTextOutputPolicy();
6147
+ session.textOutputPolicy ??= defaultVcMeetingTextOutputPolicy(cfg);
5941
6148
  session.voiceOutputPolicy ??= defaultVcMeetingVoiceOutputPolicy(cfg);
5942
6149
  session.pendingOutputRequests ??= {};
5943
6150
  session.consumerPendingItems ??= [];
@@ -8322,7 +8529,11 @@ function vcMeetingConsumerHasFastSignal(session, cfg, items) {
8322
8529
  : item.type === 'transcript_received'
8323
8530
  ? item.speaker
8324
8531
  : undefined;
8325
- if (isInstructionSource(actor) && /[??]/.test(text))
8532
+ // Any speech/chat from an instruction source (the authorizing user) is a
8533
+ // fast signal — not only questions. Waiting out the regular flush tick on
8534
+ // the operator's own words made the agent feel unresponsive in-meeting;
8535
+ // other participants still batch on the normal cadence.
8536
+ if (isInstructionSource(actor))
8326
8537
  return true;
8327
8538
  }
8328
8539
  return false;
@@ -11699,6 +11910,42 @@ async function restoreVcMeetingMonitoringAfterObservedJoin(key, session, cfg, oc
11699
11910
  await resolveVcMeetingListenerRejoinCard(session, cfg, 'rejoined');
11700
11911
  logger.info(`[vc-agent] listener monitoring restored from own join event meeting=${session.state.meeting.id}`);
11701
11912
  }
11913
+ /**
11914
+ * 入会即预热会议 agent 的 CLI 会话——复用「进群自动开工」(handleBotAdded)而不是走
11915
+ * VC delivery 冷 fork,把 bot 刚进监听群时就把会话冷起来待命,首条字幕/弹幕来了秒回
11916
+ * (否则要等选择卡默认生效 + 首次投递,实测 ~28s)。
11917
+ *
11918
+ * 为什么安全复用 handleBotAdded:
11919
+ * - Plan B 下会议 agent 本就是这个监听群的普通 chat-scope 会话,key = sessionKey(
11920
+ * chatId, appId);handleBotAdded 普通群 chat-scope 会话同 key。预热出来的正是后面
11921
+ * VC 投递 `ensureVcMeetingReceiverSession` 会 adopt 的那个(它见已有会话只 stamp
11922
+ * binding、不另建),零重复会话、零 delivery 机制改动。
11923
+ * - forcePrompt='' 绕过 autoStartOnGroupJoin 开关(入会预热是我们主动的),D7(群里
11924
+ * 要有 allowedUser)照跑——监听群有 owner,天然过。空 prompt = 模型自己读群上下文
11925
+ * (与场景① D8 一致),不塞合成指令。
11926
+ *
11927
+ * **只在会落到 chat-scope 时预热**:若这个 bot 的普通群回复模式是 new-topic,
11928
+ * handleBotAdded 会给监听群开一个话题(thread-scope,锚点漂移),就和 VC 固定的
11929
+ * chat key 岔开、可能双开会话。这种情况跳过预热,退回老的懒启动(无回归)。
11930
+ * best-effort:任何失败只 warn,会话仍会在首次投递时冷 fork。
11931
+ */
11932
+ async function warmSpawnVcMeetingAgentSession(larkAppId, listenerChatId, meeting) {
11933
+ try {
11934
+ // 监听群是新建的普通群(createGroupWithBots),mode='group';只有当这个 bot 的普通群
11935
+ // 回复模式是 new-topic 时 handleBotAdded 才会开话题,那时跳过以免与 VC chat key 岔开。
11936
+ const regularGroupMode = resolveRegularGroupMode(larkAppId, listenerChatId);
11937
+ if (externalEventOpensOwnTopic('group', regularGroupMode)) {
11938
+ logger.info(`[vc-agent] skip warm-spawn for meeting=${meeting.id} chat=${listenerChatId}: `
11939
+ + `bot reply mode opens a topic (would diverge from the chat-scope meeting key)`);
11940
+ return;
11941
+ }
11942
+ await handleBotAdded(listenerChatId, undefined, larkAppId, { forcePrompt: '' });
11943
+ }
11944
+ catch (err) {
11945
+ logger.warn(`[vc-agent] warm-spawn on join failed meeting=${meeting.id} chat=${listenerChatId}: `
11946
+ + `${err instanceof Error ? err.message : String(err)} (falling back to lazy spawn on first delivery)`);
11947
+ }
11948
+ }
11702
11949
  async function startVcMeetingMonitoring(input) {
11703
11950
  if (!input.forceJoin
11704
11951
  && input.session.monitoringStarted
@@ -11786,6 +12033,13 @@ async function startVcMeetingMonitoring(input) {
11786
12033
  persistVcMeetingRuntimeSession(session, input.cfg);
11787
12034
  scheduleVcMeetingListenerFlush(currentKey, input.cfg);
11788
12035
  void maybeStartVcMeetingRealtimeVoice(input.larkAppId, session, input.cfg);
12036
+ // 入会即预热 CLI:监听群一建好就把这个 bot 的会话冷起来待命,而不是等第一条
12037
+ // 字幕/弹幕投递才冷 fork(实测那要 ~28s)。复用「进群自动开工」的 handleBotAdded
12038
+ // ——不碰 VC delivery/attribution 机制,且 Plan B 下会议 agent 本就是这个监听群的
12039
+ // 普通 chat-scope 会话(sessionKey(chatId,appId)),与 handleBotAdded 普通群 chat
12040
+ // 会话同 key:预热出来的正是后面 VC 投递会 adopt 的那个,零重复会话。best-effort,
12041
+ // 失败只 warn(会话仍会在首次投递时按老路径冷 fork)。
12042
+ void warmSpawnVcMeetingAgentSession(input.larkAppId, listenerChatId, meeting);
11789
12043
  await sendMessage(input.larkAppId, listenerChatId, formatVcMeetingStartMessage(meeting, input.cfg), 'text', `vc_${meeting.id.slice(-12)}_start`).catch((err) => {
11790
12044
  logger.warn(`[vc-agent] listener start message failed meeting=${meeting.id}: ${err instanceof Error ? err.message : String(err)}`);
11791
12045
  });
@@ -13363,6 +13617,8 @@ export const __vcMeetingAgentTest = {
13363
13617
  if (cfg)
13364
13618
  restoreVcMeetingRuntimeSessionsForBot(larkAppId, cfg);
13365
13619
  },
13620
+ /** 测试用:读某个 bot 的有效 VC 配置(含共享目录绑定 + larkCliProfile 默认值)。 */
13621
+ effectiveConfig: (larkAppId) => effectiveVcMeetingAgentConfig(larkAppId),
13366
13622
  reset: () => {
13367
13623
  for (const session of vcMeetingSessions.values()) {
13368
13624
  if (session.flushTimer)
@@ -14102,6 +14358,7 @@ async function startInitialPassthroughSession(args) {
14102
14358
  session.quoteTargetSenderIsBot = resolvedSenderIsBot;
14103
14359
  session.lastMessageAt = new Date(now).toISOString();
14104
14360
  session.scope = scope;
14361
+ fillNativeTopicId(session, scope, parsed.threadId);
14105
14362
  sessionStore.updateSession(session);
14106
14363
  const { pinnedWorkingDir, oncallEntry, inheritedFrom, pinnedFromBotDefault } = await resolvePinnedWorkingDir({ scope, anchor, chatId, chatType, larkAppId });
14107
14364
  // Auto-worktree: register PENDING (router buffers, no force-fork) and build the
@@ -14286,19 +14543,20 @@ async function handleNewTopic(data, ctx) {
14286
14543
  * (handleNewTopicAdmitted + handleThreadReplyAdmitted), so a plain-human turn is
14287
14544
  * authorized identically whether it opens a new topic or continues one (R6/R7-B1).
14288
14545
  *
14289
- * FAIL-CLOSED by construction (R7-B1): authorization requires a POSITIVE
14290
- * `humanSender` (senderType === 'user' AND not a known peer bot) AND the absence
14291
- * of every control-rewrite / dedicated-receiver signal. Excluding a list of
14292
- * known non-human sources is NOT enough an exclude-list is never complete, so
14293
- * any un-enumerated non-user source would fail OPEN. The positive humanSender
14294
- * gate makes the default deny: anything that is not provably a real human turn
14295
- * with no special semantics stays forced-serial.
14546
+ * FAIL-CLOSED by construction (R7-B1): authorization requires either a POSITIVE
14547
+ * `humanSender` (senderType === 'user' AND not a known peer bot), or a known peer
14548
+ * bot carrying the explicit `@steer` directive. Both lanes must also have no
14549
+ * control-rewrite / dedicated-receiver signal. Anything not positively matched
14550
+ * by one of those lanes stays forced-serial.
14296
14551
  */
14297
14552
  function computeCodexAppSteerable(facts) {
14298
- return facts.humanSender
14299
- && !facts.adopted
14553
+ const plainHuman = facts.humanSender
14300
14554
  && !facts.isForeignBot
14301
- && !facts.isBotSenderType
14555
+ && !facts.isBotSenderType;
14556
+ const explicitBotSteer = facts.explicitBotSteer
14557
+ && facts.isForeignBot;
14558
+ return (plainHuman || explicitBotSteer)
14559
+ && !facts.adopted
14302
14560
  && !facts.substituteTrigger
14303
14561
  && !facts.controlRewrite
14304
14562
  && !facts.messageListener
@@ -14409,6 +14667,19 @@ async function handleNewTopicAdmitted(data, ctx) {
14409
14667
  // every event that flows through us teaches the cache without touching
14410
14668
  // the contact API. Must run before any await on the sender resolver.
14411
14669
  learnFromMentions(larkAppId, parsed.mentions);
14670
+ const senderOpenId = data.sender?.sender_id?.open_id;
14671
+ const isBotSenderType = data.sender?.sender_type === 'app' || data.sender?.sender_type === 'bot';
14672
+ const isForeignBotSender = isBotSenderType
14673
+ || (!!senderOpenId && senderOpenId !== getBot(larkAppId).botOpenId
14674
+ && isKnownPeerBot(config.session.dataDir, larkAppId, senderOpenId));
14675
+ // `@steer` is a generic bot-to-bot transport directive, not model content.
14676
+ // Consume it only from a positively classified bot sender; a human writing
14677
+ // the same text keeps the literal body (human turns are already steerable).
14678
+ const botSteerDirective = isForeignBotSender
14679
+ ? parseBotSteerDirective(parsed.content)
14680
+ : { requested: false, content: parsed.content };
14681
+ if (botSteerDirective.requested)
14682
+ parsed.content = botSteerDirective.content;
14412
14683
  const followupContent = parsed.content.trim();
14413
14684
  let content = composeForwardFollowupContent(forwardSeedContent, followupContent);
14414
14685
  // Strip leading @<bot> mentions so "@bot /oncall bind" is recognized as a command.
@@ -14420,7 +14691,6 @@ async function handleNewTopicAdmitted(data, ctx) {
14420
14691
  // (already thread-scope) it's just a prefix strip — no routing change.
14421
14692
  // Empty prompt is allowed: later setup chooses either a repo picker or a
14422
14693
  // visible thread that waits for the first real task, without an empty worker.
14423
- const senderOpenId = data.sender?.sender_id?.open_id;
14424
14694
  const forceTopic = parseForceTopicInvocation(cmdContent);
14425
14695
  if (forceTopic) {
14426
14696
  if (await replyGrantRestrictionIfNeeded(larkAppId, chatId, senderOpenId, anchor, forceTopicCommandLabel(cmdContent))) {
@@ -14449,7 +14719,6 @@ async function handleNewTopicAdmitted(data, ctx) {
14449
14719
  // 飞书盖章的 bot 发送方。既锁 union 信任腿(下面的 teamTrustUnionId),也决定
14450
14720
  // talk 复查走哪个谓词(evaluateBotTalk vs evaluateTalk)——两者必须同源,见
14451
14721
  // enforceMessageQuotaForCliInput 的 botSender 说明。
14452
- const isBotSenderType = data.sender?.sender_type === 'app' || data.sender?.sender_type === 'bot';
14453
14722
  const teamTrustUnionId = isBotSenderType ? senderUnionId : undefined;
14454
14723
  // A bot-sent new topic (e.g. a message-listener match on a third-party alert
14455
14724
  // bot's card) must NOT make that bot the session owner: daemon-generated
@@ -14458,9 +14727,6 @@ async function handleNewTopicAdmitted(data, ctx) {
14458
14727
  // surfaces (restart/report/cards) to it. Mirror the handleThreadReply +
14459
14728
  // auto-create paths (isForeignBot → ownerOpenId/ownerUnionId undefined); keep
14460
14729
  // creatorOpenId + quoteTarget* so botmux report / first-turn quote still work.
14461
- const isForeignBotSender = isBotSenderType
14462
- || (!!senderOpenId && senderOpenId !== getBot(larkAppId).botOpenId
14463
- && isKnownPeerBot(config.session.dataDir, larkAppId, senderOpenId));
14464
14730
  const ownerOpenIdForSession = isForeignBotSender ? undefined : senderOpenId;
14465
14731
  const ownerUnionIdForSession = isForeignBotSender ? undefined : senderUnionId;
14466
14732
  const botCfg = getBot(larkAppId).config;
@@ -14682,6 +14948,7 @@ async function handleNewTopicAdmitted(data, ctx) {
14682
14948
  session.lastCallerOpenId = senderOpenId;
14683
14949
  session.lastMessageAt = new Date(now).toISOString();
14684
14950
  session.scope = scope;
14951
+ fillNativeTopicId(session, scope, parsed.threadId);
14685
14952
  // First-message `/repo`: seed the same pending-repo state the card flow
14686
14953
  // uses, so the `/repo` handler launches the CLI straight away —
14687
14954
  // `/repo <arg>` in that repo, bare `/repo` in the default workingDir —
@@ -14890,6 +15157,7 @@ async function handleNewTopicAdmitted(data, ctx) {
14890
15157
  session.quoteTargetSenderIsBot = isForeignBotSender || parsed.senderType === 'app' || parsed.senderType === 'bot';
14891
15158
  session.lastMessageAt = new Date(now).toISOString();
14892
15159
  session.scope = scope;
15160
+ fillNativeTopicId(session, scope, parsed.threadId);
14893
15161
  session.nativeSessionTitle = buildBotmuxLarkNativeSessionTitle(parsed.content, parsed.mentions, groupChatName);
14894
15162
  sessionStore.updateSession(session);
14895
15163
  // Control card compensates for the card-less (avatar-style) chat-scope
@@ -14927,8 +15195,10 @@ async function handleNewTopicAdmitted(data, ctx) {
14927
15195
  pendingSubstituteTrigger: substituteTrigger,
14928
15196
  pendingSubstituteControlCard: shouldSendSubstituteControlCard,
14929
15197
  pendingSender: newTopicSender,
14930
- // R6-B1: freeze the plain-human steer authorization into the NEW-TOPIC opening
14931
- // metadata from inbound source facts (the same helper the follow-up twin uses).
15198
+ // Freeze steer authorization into the NEW-TOPIC opening metadata from
15199
+ // inbound source facts (the same helper the follow-up twin uses). The two
15200
+ // allowed lanes are a plain human turn and a foreign bot carrying the
15201
+ // explicit, language-neutral `@steer` directive; ordinary bot traffic is serial.
14932
15202
  // A brand-new session has no VC receiver yet; foreign-bot maps to the bot-sender
14933
15203
  // fact here. buildReservedInitialInput COPIES this onto the opening payload;
14934
15204
  // forkReservedInitialSession never infers it.
@@ -14944,6 +15214,7 @@ async function handleNewTopicAdmitted(data, ctx) {
14944
15214
  isForeignBot: isBotSenderType
14945
15215
  || isKnownPeerBot(config.session.dataDir, larkAppId, senderOpenId),
14946
15216
  isBotSenderType,
15217
+ explicitBotSteer: botSteerDirective.requested,
14947
15218
  substituteTrigger: !!substituteTrigger,
14948
15219
  controlRewrite: !!newTopicGrill,
14949
15220
  messageListener: !!messageListener,
@@ -15579,18 +15850,29 @@ async function handleBotAdded(chatId, operatorOpenId, larkAppId, opts) {
15579
15850
  }
15580
15851
  }
15581
15852
  export const __testOnly_handleBotAdded = handleBotAdded;
15582
- /** Reverse-lookup a foreign bot's display name for a sender open_id observed on
15583
- * this app's WS events. Priority:
15853
+ /** Reverse-lookup a foreign bot's display name for a sender observed on this
15854
+ * app's WS events. Priority:
15855
+ * 0) union_id → current sibling name — rename-proof. The sender's tenant-
15856
+ * stable union_id (stamped on the event) maps to the one locally-configured
15857
+ * sibling that learned it, whose bots-info.json botName is rewritten by its
15858
+ * OWN daemon on every rename. This is the only source that reflects a rename
15859
+ * when the renamed bot merely SENDS to us (no @ of it flows through us, so
15860
+ * the by-name cross-ref below never refreshes on that event).
15584
15861
  * 1) bot-openids-${larkAppId}.json — per-app cross-ref populated by
15585
15862
  * updateBotOpenIdCrossRef when @mentions go through us. Open_id is
15586
15863
  * per-app scoped, so this is the authoritative map for this larkAppId.
15864
+ * (The writer now evicts pre-rename aliases, so a stale name here self-heals
15865
+ * the next time that bot is @-mentioned by its new name.)
15587
15866
  * 2) bots-info.json — fallback for bots not yet in our cross-ref but
15588
15867
  * registered as botmux peers (matches by their self-reported open_id;
15589
15868
  * only works when the peer's app id space coincides with ours).
15590
- * Returns "Bot" if neither lookup hits — keeps the prefix readable rather
15591
- * than blocking the message.
15869
+ * Returns "Bot" if nothing hits — keeps the prefix readable rather than
15870
+ * blocking the message.
15592
15871
  */
15593
- function lookupForeignBotName(senderOpenId, larkAppId) {
15872
+ function lookupForeignBotName(senderOpenId, larkAppId, senderUnionId) {
15873
+ const currentName = resolveSiblingBotNameByUnionId(config.session.dataDir, larkAppId, senderUnionId);
15874
+ if (currentName)
15875
+ return currentName;
15594
15876
  for (const [name, openId] of Object.entries(readPeerCrossRef(config.session.dataDir, larkAppId))) {
15595
15877
  if (openId === senderOpenId)
15596
15878
  return name;
@@ -15660,7 +15942,13 @@ async function handleThreadReplyAdmitted(data, ctx, prepared) {
15660
15942
  senderOpenIdForPrefix !== selfBotOpenId &&
15661
15943
  (isBotSenderType ||
15662
15944
  isKnownPeerBot(config.session.dataDir, larkAppId, senderOpenIdForPrefix));
15663
- const foreignBotName = isForeignBot ? lookupForeignBotName(senderOpenIdForPrefix, larkAppId) : undefined;
15945
+ const botSteerDirective = isForeignBot
15946
+ ? parseBotSteerDirective(parsed.content)
15947
+ : { requested: false, content: parsed.content };
15948
+ if (botSteerDirective.requested)
15949
+ parsed.content = botSteerDirective.content;
15950
+ const senderUnionIdForPrefix = parsed.senderUnionId || data?.sender?.sender_id?.union_id;
15951
+ const foreignBotName = isForeignBot ? lookupForeignBotName(senderOpenIdForPrefix, larkAppId, senderUnionIdForPrefix) : undefined;
15664
15952
  const botSenderPrefix = isForeignBot
15665
15953
  ? `${tr('daemon.foreign_bot_mention_prefix', { botName: foreignBotName }, localeForBot(larkAppId))}\n`
15666
15954
  : '';
@@ -15971,6 +16259,7 @@ async function handleThreadReplyAdmitted(data, ctx, prepared) {
15971
16259
  session.lastCallerOpenId = threadSenderOpenId;
15972
16260
  session.lastMessageAt = new Date(now).toISOString();
15973
16261
  session.scope = scope;
16262
+ fillNativeTopicId(session, scope, parsed.threadId);
15974
16263
  let cmdPending;
15975
16264
  if (cmd === '/repo') {
15976
16265
  const { pinnedWorkingDir } = await resolvePinnedWorkingDir({ scope, anchor, chatId: threadChatId, chatType: ctxChatType, larkAppId });
@@ -16063,6 +16352,10 @@ async function handleThreadReplyAdmitted(data, ctx, prepared) {
16063
16352
  }
16064
16353
  logger.info(`Reply in ${scope}-scope session ${anchor.substring(0, 12)}: ${content.substring(0, 100)} (resources: ${resources.length})`);
16065
16354
  let ds = activeSessions.get(sessionKey(anchor, larkAppId));
16355
+ if (ds && fillNativeTopicId(ds.session, scope, parsed.threadId)) {
16356
+ sessionStore.updateSession(ds.session);
16357
+ publishNativeTopicLinkPatch(ds);
16358
+ }
16066
16359
  // If another bot already owns this anchor, ignore unmentioned replies here as a
16067
16360
  // second line of defense. Explicit @mentions are still allowed to spin up/take over.
16068
16361
  // For chat-scope: another bot's session in the same chat is keyed by its own chatId.
@@ -16193,14 +16486,15 @@ async function handleThreadReplyAdmitted(data, ctx, prepared) {
16193
16486
  // opening path — would silently drop it. When a claim token IS held (a
16194
16487
  // concurrent first turn owns the opening) or a queued-activation ACK is
16195
16488
  // pending, a live worker MUST keep buffering to preserve FIFO submission order.
16196
- // Codex App steer authorization (Blocking 1, decision A) — computed ONCE here,
16489
+ // Codex App steer authorization — computed ONCE here,
16197
16490
  // BEFORE every admission/fork branch below (R5-B1-1): the earliest tail-admit
16198
16491
  // (initialStartPending follower), pending-repo follower, worker-null refork,
16199
16492
  // and new-topic auto-create paths all need the same frozen value, and several
16200
16493
  // of them return before the later existing-owner branch. Explicit positive
16201
- // ONLY for a plain-human-interactive turn (real human sender; none of
16202
- // foreign-bot / bot-sender / substitute-rewrite / v3-grill / message-listener /
16203
- // VC receiver / VC origin / adopt). Never inferred from the delivery sink;
16494
+ // only for a plain-human-interactive turn or a foreign bot carrying an
16495
+ // explicit `@steer` directive. Plain @mentions, reports, other bot traffic,
16496
+ // substitute-rewrite, v3-grill, message-listener, VC and adopt stay serial.
16497
+ // Never inferred from the delivery sink;
16204
16498
  // ignored by acceptCodexAppDispatch for non-codex-app CLIs. A new-topic root
16205
16499
  // must FREEZE this into its opening payload — forkReservedInitialSession is
16206
16500
  // shared by bot-added / scheduler / system bootstrap and only COPIES an
@@ -16212,6 +16506,7 @@ async function handleThreadReplyAdmitted(data, ctx, prepared) {
16212
16506
  adopted: !!ds?.adoptedFrom,
16213
16507
  isForeignBot,
16214
16508
  isBotSenderType,
16509
+ explicitBotSteer: botSteerDirective.requested,
16215
16510
  substituteTrigger: !!substituteTrigger,
16216
16511
  controlRewrite: !!threadGrill,
16217
16512
  messageListener: !!ctx.messageListener,
@@ -16521,6 +16816,7 @@ async function handleThreadReplyAdmitted(data, ctx, prepared) {
16521
16816
  session.quoteTargetSenderIsBot = isForeignBot;
16522
16817
  session.lastMessageAt = new Date(now).toISOString();
16523
16818
  session.scope = scope;
16819
+ fillNativeTopicId(session, scope, parsed.threadId);
16524
16820
  const groupChatName = await groupChatNamePromise;
16525
16821
  if (groupChatName)
16526
16822
  session.chatDisplayName = groupChatName;
@@ -17813,23 +18109,15 @@ export async function startDaemon(botIndex) {
17813
18109
  botConfigs = loadBotConfigs();
17814
18110
  cfg = loadBotConfigAtIndex(idx);
17815
18111
  }
17816
- // One-time, lock-protected catalog bootstrap. This runs only after the
17817
- // complete bots.json has parsed successfully, and the helper re-reads the
17818
- // latest file under its lock before deciding. Explicit [] and legacy agent
17819
- // policy are durable opt-outs. Reload the selected config after a write so
17820
- // this daemon exposes the seeded selection card immediately on the same boot.
18112
+ // 这里曾经有一次「给本 bot 播种默认会议角色预设」的启动写盘。已退役:角色预设
18113
+ // 改成全 fleet 共享目录 + 读路径内置默认(services/vc-meeting-shared-consumer-
18114
+ // catalog.ts),没有任何 bot 还需要自己那份 per-bot 拷贝。退役的两个理由:
18115
+ // 1. 播种出来的 per-bot `consumerProfiles` 会永久遮蔽共享目录——操作者在
18116
+ // Dashboard 改共享预设,被播种过的 bot 完全不跟随;
18117
+ // 2. 那次写盘还会把「另一个 bot」的 appId 焊进预设(旧的换人兜底),正是
18118
+ // 「拉 A 进会却把 B 拉进监听群」的源头。
18119
+ // 现在启动路径对 VC 预设零写盘,fleet 里几十个 daemon 同时启动也不再有写竞争。
17821
18120
  const selectedAppId = cfg.larkAppId;
17822
- const profileBootstrap = await bootstrapVcMeetingDefaultConsumerProfile(selectedAppId);
17823
- if (profileBootstrap.ok) {
17824
- if (profileBootstrap.seeded) {
17825
- logger.info(`[vc-agent] seeded default meeting minutes profile listener=${selectedAppId} `
17826
- + `agent=${profileBootstrap.agentAppId}`);
17827
- }
17828
- }
17829
- else if (!profileBootstrap.ok) {
17830
- logger.warn(`[vc-agent] default consumer profile bootstrap skipped: ${profileBootstrap.reason}`
17831
- + `${profileBootstrap.error ? ` (${profileBootstrap.error})` : ''}`);
17832
- }
17833
18121
  // A bootstrap failure is not authority to keep an earlier in-memory config.
17834
18122
  // Every explicit PM2 daemon must prove its same raw slot and App identity
17835
18123
  // immediately before registerBot, regardless of the bootstrap result.
@@ -18622,6 +18910,14 @@ export async function startDaemon(botIndex) {
18622
18910
  checkRequiredScopes(cfg.larkAppId).catch(err => {
18623
18911
  logger.debug(`[${cfg.larkAppId}] required-scope check failed: ${err?.message ?? err}`);
18624
18912
  });
18913
+ // Ensure VC meeting events are subscribed so ANY invited bot can receive
18914
+ // meeting invites (bot-agnostic auto-join). Check-first + best-effort: a
18915
+ // read-only probe over the cached web session, auto-subscribing only when
18916
+ // events are missing. Never blocks boot; the fn itself skips VC-inactive
18917
+ // bots via vcMeetingAgentConfigActive.
18918
+ ensureVcMeetingEventsSubscribed(cfg.larkAppId).catch(err => {
18919
+ logger.debug(`[${cfg.larkAppId}] VC event subscription check failed: ${err?.message ?? err}`);
18920
+ });
18625
18921
  }
18626
18922
  // 主动开工 — 场景①: the bot.added event can't be self-verified via API, and
18627
18923
  // if it isn't subscribed the handler simply never fires (no runtime signal).