botmux 2.46.1 → 2.47.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 (468) hide show
  1. package/README.en.md +5 -10
  2. package/README.md +5 -10
  3. package/dist/adapters/backend/tmux-backend.d.ts.map +1 -1
  4. package/dist/adapters/backend/tmux-backend.js +0 -11
  5. package/dist/adapters/backend/tmux-backend.js.map +1 -1
  6. package/dist/adapters/cli/claude-code.d.ts.map +1 -1
  7. package/dist/adapters/cli/claude-code.js +9 -36
  8. package/dist/adapters/cli/claude-code.js.map +1 -1
  9. package/dist/adapters/cli/coco.d.ts.map +1 -1
  10. package/dist/adapters/cli/coco.js +1 -26
  11. package/dist/adapters/cli/coco.js.map +1 -1
  12. package/dist/adapters/cli/codex.d.ts.map +1 -1
  13. package/dist/adapters/cli/codex.js +1 -6
  14. package/dist/adapters/cli/codex.js.map +1 -1
  15. package/dist/adapters/cli/cursor.d.ts.map +1 -1
  16. package/dist/adapters/cli/cursor.js +12 -58
  17. package/dist/adapters/cli/cursor.js.map +1 -1
  18. package/dist/adapters/cli/gemini.d.ts.map +1 -1
  19. package/dist/adapters/cli/gemini.js +1 -5
  20. package/dist/adapters/cli/gemini.js.map +1 -1
  21. package/dist/adapters/cli/opencode.d.ts.map +1 -1
  22. package/dist/adapters/cli/opencode.js +1 -19
  23. package/dist/adapters/cli/opencode.js.map +1 -1
  24. package/dist/adapters/cli/registry.d.ts +1 -5
  25. package/dist/adapters/cli/registry.d.ts.map +1 -1
  26. package/dist/adapters/cli/registry.js +2 -22
  27. package/dist/adapters/cli/registry.js.map +1 -1
  28. package/dist/adapters/cli/shared-hints.d.ts +1 -1
  29. package/dist/adapters/cli/shared-hints.d.ts.map +1 -1
  30. package/dist/adapters/cli/shared-hints.js +1 -2
  31. package/dist/adapters/cli/shared-hints.js.map +1 -1
  32. package/dist/adapters/cli/types.d.ts +2 -35
  33. package/dist/adapters/cli/types.d.ts.map +1 -1
  34. package/dist/bot-registry.d.ts +0 -59
  35. package/dist/bot-registry.d.ts.map +1 -1
  36. package/dist/bot-registry.js +0 -67
  37. package/dist/bot-registry.js.map +1 -1
  38. package/dist/cli/bots-list-output.d.ts +0 -8
  39. package/dist/cli/bots-list-output.d.ts.map +1 -1
  40. package/dist/cli/bots-list-output.js +0 -9
  41. package/dist/cli/bots-list-output.js.map +1 -1
  42. package/dist/cli.d.ts +1 -15
  43. package/dist/cli.d.ts.map +1 -1
  44. package/dist/cli.js +106 -603
  45. package/dist/cli.js.map +1 -1
  46. package/dist/config.d.ts +2 -11
  47. package/dist/config.d.ts.map +1 -1
  48. package/dist/config.js +4 -17
  49. package/dist/config.js.map +1 -1
  50. package/dist/core/command-handler.d.ts +0 -20
  51. package/dist/core/command-handler.d.ts.map +1 -1
  52. package/dist/core/command-handler.js +313 -762
  53. package/dist/core/command-handler.js.map +1 -1
  54. package/dist/core/dashboard-ipc-server.d.ts +0 -2
  55. package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
  56. package/dist/core/dashboard-ipc-server.js +2 -222
  57. package/dist/core/dashboard-ipc-server.js.map +1 -1
  58. package/dist/core/role-resolver.d.ts +1 -17
  59. package/dist/core/role-resolver.d.ts.map +1 -1
  60. package/dist/core/role-resolver.js +10 -64
  61. package/dist/core/role-resolver.js.map +1 -1
  62. package/dist/core/session-discovery.d.ts.map +1 -1
  63. package/dist/core/session-discovery.js +5 -19
  64. package/dist/core/session-discovery.js.map +1 -1
  65. package/dist/core/session-manager.d.ts +1 -1
  66. package/dist/core/session-manager.d.ts.map +1 -1
  67. package/dist/core/session-manager.js +20 -37
  68. package/dist/core/session-manager.js.map +1 -1
  69. package/dist/core/types.d.ts +0 -5
  70. package/dist/core/types.d.ts.map +1 -1
  71. package/dist/core/types.js.map +1 -1
  72. package/dist/core/worker-pool.d.ts +0 -141
  73. package/dist/core/worker-pool.d.ts.map +1 -1
  74. package/dist/core/worker-pool.js +24 -543
  75. package/dist/core/worker-pool.js.map +1 -1
  76. package/dist/daemon.d.ts.map +1 -1
  77. package/dist/daemon.js +58 -213
  78. package/dist/daemon.js.map +1 -1
  79. package/dist/dashboard/auth.d.ts +1 -6
  80. package/dist/dashboard/auth.d.ts.map +1 -1
  81. package/dist/dashboard/auth.js +1 -9
  82. package/dist/dashboard/auth.js.map +1 -1
  83. package/dist/dashboard/web/app.js +0 -8
  84. package/dist/dashboard/web/app.js.map +1 -1
  85. package/dist/dashboard/web/bot-defaults.d.ts.map +1 -1
  86. package/dist/dashboard/web/bot-defaults.js +21 -205
  87. package/dist/dashboard/web/bot-defaults.js.map +1 -1
  88. package/dist/dashboard/web/i18n.d.ts.map +1 -1
  89. package/dist/dashboard/web/i18n.js +5 -43
  90. package/dist/dashboard/web/i18n.js.map +1 -1
  91. package/dist/dashboard/web/sessions.d.ts.map +1 -1
  92. package/dist/dashboard/web/sessions.js +0 -4
  93. package/dist/dashboard/web/sessions.js.map +1 -1
  94. package/dist/dashboard/web/workflows.js +3 -3
  95. package/dist/dashboard/web/workflows.js.map +1 -1
  96. package/dist/dashboard/workflow-api.d.ts +1 -8
  97. package/dist/dashboard/workflow-api.d.ts.map +1 -1
  98. package/dist/dashboard/workflow-api.js +4 -19
  99. package/dist/dashboard/workflow-api.js.map +1 -1
  100. package/dist/dashboard-web/app.js +375 -539
  101. package/dist/dashboard-web/index.html +1 -3
  102. package/dist/dashboard-web/style.css +0 -22
  103. package/dist/dashboard.js +2 -199
  104. package/dist/dashboard.js.map +1 -1
  105. package/dist/i18n/en.d.ts.map +1 -1
  106. package/dist/i18n/en.js +11 -104
  107. package/dist/i18n/en.js.map +1 -1
  108. package/dist/i18n/zh.d.ts.map +1 -1
  109. package/dist/i18n/zh.js +11 -104
  110. package/dist/i18n/zh.js.map +1 -1
  111. package/dist/im/lark/card-builder.d.ts +3 -108
  112. package/dist/im/lark/card-builder.d.ts.map +1 -1
  113. package/dist/im/lark/card-builder.js +50 -480
  114. package/dist/im/lark/card-builder.js.map +1 -1
  115. package/dist/im/lark/card-handler.d.ts.map +1 -1
  116. package/dist/im/lark/card-handler.js +18 -241
  117. package/dist/im/lark/card-handler.js.map +1 -1
  118. package/dist/im/lark/client.d.ts +0 -83
  119. package/dist/im/lark/client.d.ts.map +1 -1
  120. package/dist/im/lark/client.js +70 -286
  121. package/dist/im/lark/client.js.map +1 -1
  122. package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
  123. package/dist/im/lark/event-dispatcher.js +4 -29
  124. package/dist/im/lark/event-dispatcher.js.map +1 -1
  125. package/dist/im/lark/grant-command.d.ts +1 -2
  126. package/dist/im/lark/grant-command.d.ts.map +1 -1
  127. package/dist/im/lark/grant-command.js +2 -3
  128. package/dist/im/lark/grant-command.js.map +1 -1
  129. package/dist/im/lark/identity-cache.d.ts.map +1 -1
  130. package/dist/im/lark/identity-cache.js +3 -3
  131. package/dist/im/lark/identity-cache.js.map +1 -1
  132. package/dist/im/lark/md-card.d.ts +2 -20
  133. package/dist/im/lark/md-card.d.ts.map +1 -1
  134. package/dist/im/lark/md-card.js +17 -49
  135. package/dist/im/lark/md-card.js.map +1 -1
  136. package/dist/im/lark/message-parser.d.ts.map +1 -1
  137. package/dist/im/lark/message-parser.js +31 -87
  138. package/dist/im/lark/message-parser.js.map +1 -1
  139. package/dist/im/lark/workflow-card-handler.d.ts +2 -2
  140. package/dist/im/lark/workflow-card-handler.d.ts.map +1 -1
  141. package/dist/im/lark/workflow-card-handler.js +1 -12
  142. package/dist/im/lark/workflow-card-handler.js.map +1 -1
  143. package/dist/im/lark/workflow-progress-card.d.ts.map +1 -1
  144. package/dist/im/lark/workflow-progress-card.js +0 -53
  145. package/dist/im/lark/workflow-progress-card.js.map +1 -1
  146. package/dist/services/codex-bridge-queue.d.ts +0 -1
  147. package/dist/services/codex-bridge-queue.d.ts.map +1 -1
  148. package/dist/services/codex-bridge-queue.js +0 -23
  149. package/dist/services/codex-bridge-queue.js.map +1 -1
  150. package/dist/services/codex-transcript.d.ts +0 -1
  151. package/dist/services/codex-transcript.d.ts.map +1 -1
  152. package/dist/services/codex-transcript.js.map +1 -1
  153. package/dist/services/feishu-task-client.d.ts +28 -0
  154. package/dist/services/feishu-task-client.d.ts.map +1 -0
  155. package/dist/services/feishu-task-client.js +123 -0
  156. package/dist/services/feishu-task-client.js.map +1 -0
  157. package/dist/services/grant-store.d.ts +2 -12
  158. package/dist/services/grant-store.d.ts.map +1 -1
  159. package/dist/services/grant-store.js +4 -51
  160. package/dist/services/grant-store.js.map +1 -1
  161. package/dist/services/group-creator.d.ts +0 -10
  162. package/dist/services/group-creator.d.ts.map +1 -1
  163. package/dist/services/group-creator.js +1 -26
  164. package/dist/services/group-creator.js.map +1 -1
  165. package/dist/services/groups-store.d.ts +0 -30
  166. package/dist/services/groups-store.d.ts.map +1 -1
  167. package/dist/services/groups-store.js +12 -85
  168. package/dist/services/groups-store.js.map +1 -1
  169. package/dist/services/project-scanner.d.ts +0 -10
  170. package/dist/services/project-scanner.d.ts.map +1 -1
  171. package/dist/services/project-scanner.js +0 -11
  172. package/dist/services/project-scanner.js.map +1 -1
  173. package/dist/services/session-store.js +1 -1
  174. package/dist/services/session-store.js.map +1 -1
  175. package/dist/services/task-store.d.ts +37 -0
  176. package/dist/services/task-store.d.ts.map +1 -0
  177. package/dist/services/task-store.js +115 -0
  178. package/dist/services/task-store.js.map +1 -0
  179. package/dist/setup/bot-config-editor.d.ts +1 -8
  180. package/dist/setup/bot-config-editor.d.ts.map +1 -1
  181. package/dist/setup/bot-config-editor.js +2 -20
  182. package/dist/setup/bot-config-editor.js.map +1 -1
  183. package/dist/setup/ensure-tmux.d.ts +22 -0
  184. package/dist/setup/ensure-tmux.d.ts.map +1 -1
  185. package/dist/setup/ensure-tmux.js +1 -25
  186. package/dist/setup/ensure-tmux.js.map +1 -1
  187. package/dist/setup/verify-permissions.d.ts.map +1 -1
  188. package/dist/setup/verify-permissions.js +1 -15
  189. package/dist/setup/verify-permissions.js.map +1 -1
  190. package/dist/skills/definitions.d.ts +0 -2
  191. package/dist/skills/definitions.d.ts.map +1 -1
  192. package/dist/skills/definitions.js +12 -178
  193. package/dist/skills/definitions.js.map +1 -1
  194. package/dist/skills/installer.d.ts +0 -34
  195. package/dist/skills/installer.d.ts.map +1 -1
  196. package/dist/skills/installer.js +2 -119
  197. package/dist/skills/installer.js.map +1 -1
  198. package/dist/types.d.ts +0 -25
  199. package/dist/types.d.ts.map +1 -1
  200. package/dist/utils/bot-routing.d.ts +0 -50
  201. package/dist/utils/bot-routing.d.ts.map +1 -1
  202. package/dist/utils/bot-routing.js +0 -83
  203. package/dist/utils/bot-routing.js.map +1 -1
  204. package/dist/utils/user-token.d.ts.map +1 -1
  205. package/dist/utils/user-token.js +2 -0
  206. package/dist/utils/user-token.js.map +1 -1
  207. package/dist/worker.js +27 -198
  208. package/dist/worker.js.map +1 -1
  209. package/dist/workflows/attempt-resume.d.ts.map +1 -1
  210. package/dist/workflows/attempt-resume.js +2 -2
  211. package/dist/workflows/attempt-resume.js.map +1 -1
  212. package/dist/workflows/definition.d.ts +9 -412
  213. package/dist/workflows/definition.d.ts.map +1 -1
  214. package/dist/workflows/definition.js +3 -238
  215. package/dist/workflows/definition.js.map +1 -1
  216. package/dist/workflows/events/payloads.d.ts +11 -114
  217. package/dist/workflows/events/payloads.d.ts.map +1 -1
  218. package/dist/workflows/events/payloads.js +0 -46
  219. package/dist/workflows/events/payloads.js.map +1 -1
  220. package/dist/workflows/events/replay.d.ts +0 -21
  221. package/dist/workflows/events/replay.d.ts.map +1 -1
  222. package/dist/workflows/events/replay.js +0 -103
  223. package/dist/workflows/events/replay.js.map +1 -1
  224. package/dist/workflows/events/schema.d.ts +1017 -1712
  225. package/dist/workflows/events/schema.d.ts.map +1 -1
  226. package/dist/workflows/events/schema.js +1 -37
  227. package/dist/workflows/events/schema.js.map +1 -1
  228. package/dist/workflows/events/types.d.ts +1 -5
  229. package/dist/workflows/events/types.d.ts.map +1 -1
  230. package/dist/workflows/loader.d.ts +0 -14
  231. package/dist/workflows/loader.d.ts.map +1 -1
  232. package/dist/workflows/loader.js +0 -27
  233. package/dist/workflows/loader.js.map +1 -1
  234. package/dist/workflows/loop.js +0 -58
  235. package/dist/workflows/loop.js.map +1 -1
  236. package/dist/workflows/ops-projection.d.ts +0 -58
  237. package/dist/workflows/ops-projection.d.ts.map +1 -1
  238. package/dist/workflows/ops-projection.js +0 -74
  239. package/dist/workflows/ops-projection.js.map +1 -1
  240. package/dist/workflows/orchestrator.d.ts +1 -65
  241. package/dist/workflows/orchestrator.d.ts.map +1 -1
  242. package/dist/workflows/orchestrator.js +74 -486
  243. package/dist/workflows/orchestrator.js.map +1 -1
  244. package/dist/workflows/output-binding.d.ts +1 -8
  245. package/dist/workflows/output-binding.d.ts.map +1 -1
  246. package/dist/workflows/output-binding.js +11 -75
  247. package/dist/workflows/output-binding.js.map +1 -1
  248. package/dist/workflows/runtime.d.ts +1 -1
  249. package/dist/workflows/runtime.d.ts.map +1 -1
  250. package/dist/workflows/runtime.js +4 -39
  251. package/dist/workflows/runtime.js.map +1 -1
  252. package/dist/workflows/wait.d.ts +2 -23
  253. package/dist/workflows/wait.d.ts.map +1 -1
  254. package/dist/workflows/wait.js +17 -39
  255. package/dist/workflows/wait.js.map +1 -1
  256. package/package.json +1 -1
  257. package/dist/adapters/adopt-route.d.ts +0 -63
  258. package/dist/adapters/adopt-route.d.ts.map +0 -1
  259. package/dist/adapters/adopt-route.js +0 -195
  260. package/dist/adapters/adopt-route.js.map +0 -1
  261. package/dist/adapters/cli/codex-app.d.ts +0 -4
  262. package/dist/adapters/cli/codex-app.d.ts.map +0 -1
  263. package/dist/adapters/cli/codex-app.js +0 -72
  264. package/dist/adapters/cli/codex-app.js.map +0 -1
  265. package/dist/adapters/cli/hermes.d.ts +0 -4
  266. package/dist/adapters/cli/hermes.d.ts.map +0 -1
  267. package/dist/adapters/cli/hermes.js +0 -40
  268. package/dist/adapters/cli/hermes.js.map +0 -1
  269. package/dist/adapters/cli/mira.d.ts +0 -4
  270. package/dist/adapters/cli/mira.d.ts.map +0 -1
  271. package/dist/adapters/cli/mira.js +0 -67
  272. package/dist/adapters/cli/mira.js.map +0 -1
  273. package/dist/adapters/cli/mtr.d.ts +0 -5
  274. package/dist/adapters/cli/mtr.d.ts.map +0 -1
  275. package/dist/adapters/cli/mtr.js +0 -62
  276. package/dist/adapters/cli/mtr.js.map +0 -1
  277. package/dist/adapters/hook-command.d.ts +0 -18
  278. package/dist/adapters/hook-command.d.ts.map +0 -1
  279. package/dist/adapters/hook-command.js +0 -38
  280. package/dist/adapters/hook-command.js.map +0 -1
  281. package/dist/adapters/hook-installer.d.ts +0 -14
  282. package/dist/adapters/hook-installer.d.ts.map +0 -1
  283. package/dist/adapters/hook-installer.js +0 -192
  284. package/dist/adapters/hook-installer.js.map +0 -1
  285. package/dist/codex-app-runner.d.ts +0 -3
  286. package/dist/codex-app-runner.d.ts.map +0 -1
  287. package/dist/codex-app-runner.js +0 -512
  288. package/dist/codex-app-runner.js.map +0 -1
  289. package/dist/core/ask-api.d.ts +0 -47
  290. package/dist/core/ask-api.d.ts.map +0 -1
  291. package/dist/core/ask-api.js +0 -139
  292. package/dist/core/ask-api.js.map +0 -1
  293. package/dist/core/ask-args.d.ts +0 -53
  294. package/dist/core/ask-args.d.ts.map +0 -1
  295. package/dist/core/ask-args.js +0 -122
  296. package/dist/core/ask-args.js.map +0 -1
  297. package/dist/core/ask-broker.d.ts +0 -98
  298. package/dist/core/ask-broker.d.ts.map +0 -1
  299. package/dist/core/ask-broker.js +0 -329
  300. package/dist/core/ask-broker.js.map +0 -1
  301. package/dist/core/ask-hook/claude-code.d.ts +0 -50
  302. package/dist/core/ask-hook/claude-code.d.ts.map +0 -1
  303. package/dist/core/ask-hook/claude-code.js +0 -145
  304. package/dist/core/ask-hook/claude-code.js.map +0 -1
  305. package/dist/core/ask-hook/codex.d.ts +0 -43
  306. package/dist/core/ask-hook/codex.d.ts.map +0 -1
  307. package/dist/core/ask-hook/codex.js +0 -69
  308. package/dist/core/ask-hook/codex.js.map +0 -1
  309. package/dist/core/ask-hook/opencode.d.ts +0 -41
  310. package/dist/core/ask-hook/opencode.d.ts.map +0 -1
  311. package/dist/core/ask-hook/opencode.js +0 -108
  312. package/dist/core/ask-hook/opencode.js.map +0 -1
  313. package/dist/core/ask-hook/registry.d.ts +0 -3
  314. package/dist/core/ask-hook/registry.d.ts.map +0 -1
  315. package/dist/core/ask-hook/registry.js +0 -12
  316. package/dist/core/ask-hook/registry.js.map +0 -1
  317. package/dist/core/ask-hook/types.d.ts +0 -26
  318. package/dist/core/ask-hook/types.d.ts.map +0 -1
  319. package/dist/core/ask-hook/types.js +0 -2
  320. package/dist/core/ask-hook/types.js.map +0 -1
  321. package/dist/core/ask-types.d.ts +0 -146
  322. package/dist/core/ask-types.d.ts.map +0 -1
  323. package/dist/core/ask-types.js +0 -18
  324. package/dist/core/ask-types.js.map +0 -1
  325. package/dist/core/trigger-session.d.ts +0 -9
  326. package/dist/core/trigger-session.d.ts.map +0 -1
  327. package/dist/core/trigger-session.js +0 -158
  328. package/dist/core/trigger-session.js.map +0 -1
  329. package/dist/dashboard/connector-api.d.ts +0 -3
  330. package/dist/dashboard/connector-api.d.ts.map +0 -1
  331. package/dist/dashboard/connector-api.js +0 -351
  332. package/dist/dashboard/connector-api.js.map +0 -1
  333. package/dist/dashboard/federated-group-core.d.ts +0 -54
  334. package/dist/dashboard/federated-group-core.d.ts.map +0 -1
  335. package/dist/dashboard/federated-group-core.js +0 -165
  336. package/dist/dashboard/federated-group-core.js.map +0 -1
  337. package/dist/dashboard/federation-api.d.ts +0 -42
  338. package/dist/dashboard/federation-api.d.ts.map +0 -1
  339. package/dist/dashboard/federation-api.js +0 -408
  340. package/dist/dashboard/federation-api.js.map +0 -1
  341. package/dist/dashboard/federation-spoke-api.d.ts +0 -76
  342. package/dist/dashboard/federation-spoke-api.d.ts.map +0 -1
  343. package/dist/dashboard/federation-spoke-api.js +0 -618
  344. package/dist/dashboard/federation-spoke-api.js.map +0 -1
  345. package/dist/dashboard/team-group.d.ts +0 -18
  346. package/dist/dashboard/team-group.d.ts.map +0 -1
  347. package/dist/dashboard/team-group.js +0 -7
  348. package/dist/dashboard/team-group.js.map +0 -1
  349. package/dist/dashboard/trigger-api.d.ts +0 -13
  350. package/dist/dashboard/trigger-api.d.ts.map +0 -1
  351. package/dist/dashboard/trigger-api.js +0 -77
  352. package/dist/dashboard/trigger-api.js.map +0 -1
  353. package/dist/dashboard/web/connectors.d.ts +0 -2
  354. package/dist/dashboard/web/connectors.d.ts.map +0 -1
  355. package/dist/dashboard/web/connectors.js +0 -187
  356. package/dist/dashboard/web/connectors.js.map +0 -1
  357. package/dist/dashboard/web/team-federation.d.ts +0 -3
  358. package/dist/dashboard/web/team-federation.d.ts.map +0 -1
  359. package/dist/dashboard/web/team-federation.js +0 -487
  360. package/dist/dashboard/web/team-federation.js.map +0 -1
  361. package/dist/dashboard/webhook-routes.d.ts +0 -19
  362. package/dist/dashboard/webhook-routes.d.ts.map +0 -1
  363. package/dist/dashboard/webhook-routes.js +0 -321
  364. package/dist/dashboard/webhook-routes.js.map +0 -1
  365. package/dist/im/lark/ask-card.d.ts +0 -55
  366. package/dist/im/lark/ask-card.d.ts.map +0 -1
  367. package/dist/im/lark/ask-card.js +0 -328
  368. package/dist/im/lark/ask-card.js.map +0 -1
  369. package/dist/mira-output.d.ts +0 -3
  370. package/dist/mira-output.d.ts.map +0 -1
  371. package/dist/mira-output.js +0 -136
  372. package/dist/mira-output.js.map +0 -1
  373. package/dist/mira-runner.d.ts +0 -3
  374. package/dist/mira-runner.d.ts.map +0 -1
  375. package/dist/mira-runner.js +0 -534
  376. package/dist/mira-runner.js.map +0 -1
  377. package/dist/services/bot-owner-store.d.ts +0 -28
  378. package/dist/services/bot-owner-store.d.ts.map +0 -1
  379. package/dist/services/bot-owner-store.js +0 -82
  380. package/dist/services/bot-owner-store.js.map +0 -1
  381. package/dist/services/bot-profile-store.d.ts +0 -16
  382. package/dist/services/bot-profile-store.d.ts.map +0 -1
  383. package/dist/services/bot-profile-store.js +0 -98
  384. package/dist/services/bot-profile-store.js.map +0 -1
  385. package/dist/services/brand-store.d.ts +0 -15
  386. package/dist/services/brand-store.d.ts.map +0 -1
  387. package/dist/services/brand-store.js +0 -47
  388. package/dist/services/brand-store.js.map +0 -1
  389. package/dist/services/card-prefs-store.d.ts +0 -20
  390. package/dist/services/card-prefs-store.d.ts.map +0 -1
  391. package/dist/services/card-prefs-store.js +0 -82
  392. package/dist/services/card-prefs-store.js.map +0 -1
  393. package/dist/services/connector-store.d.ts +0 -58
  394. package/dist/services/connector-store.d.ts.map +0 -1
  395. package/dist/services/connector-store.js +0 -79
  396. package/dist/services/connector-store.js.map +0 -1
  397. package/dist/services/deployment-identity.d.ts +0 -22
  398. package/dist/services/deployment-identity.d.ts.map +0 -1
  399. package/dist/services/deployment-identity.js +0 -67
  400. package/dist/services/deployment-identity.js.map +0 -1
  401. package/dist/services/federation-membership-store.d.ts +0 -23
  402. package/dist/services/federation-membership-store.d.ts.map +0 -1
  403. package/dist/services/federation-membership-store.js +0 -66
  404. package/dist/services/federation-membership-store.js.map +0 -1
  405. package/dist/services/federation-roster.d.ts +0 -54
  406. package/dist/services/federation-roster.d.ts.map +0 -1
  407. package/dist/services/federation-roster.js +0 -51
  408. package/dist/services/federation-roster.js.map +0 -1
  409. package/dist/services/federation-store.d.ts +0 -76
  410. package/dist/services/federation-store.d.ts.map +0 -1
  411. package/dist/services/federation-store.js +0 -133
  412. package/dist/services/federation-store.js.map +0 -1
  413. package/dist/services/hermes-transcript.d.ts +0 -7
  414. package/dist/services/hermes-transcript.d.ts.map +0 -1
  415. package/dist/services/hermes-transcript.js +0 -117
  416. package/dist/services/hermes-transcript.js.map +0 -1
  417. package/dist/services/invite-store.d.ts +0 -28
  418. package/dist/services/invite-store.d.ts.map +0 -1
  419. package/dist/services/invite-store.js +0 -85
  420. package/dist/services/invite-store.js.map +0 -1
  421. package/dist/services/pairing-store.d.ts +0 -47
  422. package/dist/services/pairing-store.d.ts.map +0 -1
  423. package/dist/services/pairing-store.js +0 -132
  424. package/dist/services/pairing-store.js.map +0 -1
  425. package/dist/services/relay-picker.d.ts +0 -22
  426. package/dist/services/relay-picker.d.ts.map +0 -1
  427. package/dist/services/relay-picker.js +0 -62
  428. package/dist/services/relay-picker.js.map +0 -1
  429. package/dist/services/send-policy.d.ts +0 -55
  430. package/dist/services/send-policy.d.ts.map +0 -1
  431. package/dist/services/send-policy.js +0 -47
  432. package/dist/services/send-policy.js.map +0 -1
  433. package/dist/services/team-roster.d.ts +0 -38
  434. package/dist/services/team-roster.d.ts.map +0 -1
  435. package/dist/services/team-roster.js +0 -82
  436. package/dist/services/team-roster.js.map +0 -1
  437. package/dist/services/team-store.d.ts +0 -54
  438. package/dist/services/team-store.d.ts.map +0 -1
  439. package/dist/services/team-store.js +0 -156
  440. package/dist/services/team-store.js.map +0 -1
  441. package/dist/services/trigger-log-store.d.ts +0 -46
  442. package/dist/services/trigger-log-store.d.ts.map +0 -1
  443. package/dist/services/trigger-log-store.js +0 -132
  444. package/dist/services/trigger-log-store.js.map +0 -1
  445. package/dist/services/trigger-types.d.ts +0 -57
  446. package/dist/services/trigger-types.d.ts.map +0 -1
  447. package/dist/services/trigger-types.js +0 -28
  448. package/dist/services/trigger-types.js.map +0 -1
  449. package/dist/services/webhook-key.d.ts +0 -16
  450. package/dist/services/webhook-key.d.ts.map +0 -1
  451. package/dist/services/webhook-key.js +0 -123
  452. package/dist/services/webhook-key.js.map +0 -1
  453. package/dist/services/webhook-lifecycle-extractors.d.ts +0 -15
  454. package/dist/services/webhook-lifecycle-extractors.d.ts.map +0 -1
  455. package/dist/services/webhook-lifecycle-extractors.js +0 -59
  456. package/dist/services/webhook-lifecycle-extractors.js.map +0 -1
  457. package/dist/services/webhook-lifecycle-store.d.ts +0 -45
  458. package/dist/services/webhook-lifecycle-store.d.ts.map +0 -1
  459. package/dist/services/webhook-lifecycle-store.js +0 -159
  460. package/dist/services/webhook-lifecycle-store.js.map +0 -1
  461. package/dist/utils/daemon-discovery.d.ts +0 -11
  462. package/dist/utils/daemon-discovery.d.ts.map +0 -1
  463. package/dist/utils/daemon-discovery.js +0 -59
  464. package/dist/utils/daemon-discovery.js.map +0 -1
  465. package/dist/workflows/trigger-from-envelope.d.ts +0 -13
  466. package/dist/workflows/trigger-from-envelope.d.ts.map +0 -1
  467. package/dist/workflows/trigger-from-envelope.js +0 -67
  468. package/dist/workflows/trigger-from-envelope.js.map +0 -1
package/dist/cli.js CHANGED
@@ -29,12 +29,10 @@ import { enableAutostart, disableAutostart, autostartStatus, refreshAutostart }
29
29
  import { tmuxEnv } from './setup/ensure-tmux.js';
30
30
  import { writeBotsJsonAtomic as writeBotsAtomic } from './setup/bots-store.js';
31
31
  import { applyBotConfigEdits, assertUniqueBotProcessNames, botProcessName, normalizeBotConfig, parseBotConfigsJson, parseBotSelection, removeBotConfig, resolveCliId, assertOwnerWhenChatGroups, findInvalidAllowedUserEntries, hasOwnerEntry, } from './setup/bot-config-editor.js';
32
- import { createCliAdapterSync } from './adapters/cli/registry.js';
33
32
  import { logger } from './utils/logger.js';
34
33
  import { invalidWorkingDirs } from './utils/working-dir.js';
35
34
  import { firstPositional } from './cli/arg-utils.js';
36
35
  import { formatBotInfoEntriesForCli, formatChatBotsForCli, } from './cli/bots-list-output.js';
37
- import { buildFooterAddressing, hasKnownBotMention, knownBotOpenIdsFromCrossRef, orderedFooterRecipients, } from './utils/bot-routing.js';
38
36
  import { isLocale, setDefaultLocale, SUPPORTED_LOCALES } from './i18n/index.js';
39
37
  import { readGlobalConfig, setGlobalLocale, globalConfigPath } from './global-config.js';
40
38
  // Resolve the CLI's UI locale once from the global config file, so subsequent
@@ -243,12 +241,6 @@ function ecosystemConfig() {
243
241
  out_file: join(LOG_DIR, 'dashboard-out.log'),
244
242
  merge_logs: true,
245
243
  env: {
246
- // MUST match the bot daemons' SESSION_DATA_DIR: the dashboard shares
247
- // pairings/federations/memberships with them via {dataDir}/*.json. Without
248
- // it the dashboard falls back to an install-relative ../data and reads a
249
- // DIFFERENT store → /pair「配对码无效」, auto-bind hubsSynced:0,
250
- // remote-group not_a_member (cross-deployment 拉群 silently broken).
251
- SESSION_DATA_DIR: DATA_DIR,
252
244
  BOTMUX_DASHBOARD_HOST: process.env.BOTMUX_DASHBOARD_HOST ?? '0.0.0.0',
253
245
  BOTMUX_DASHBOARD_PORT: process.env.BOTMUX_DASHBOARD_PORT ?? '7891',
254
246
  },
@@ -271,63 +263,6 @@ function printInputHelp(title, lines) {
271
263
  console.log(` ${line}`);
272
264
  }
273
265
  }
274
- /**
275
- * 读取指定 CLI 适配器声明的候选 model 列表 —— 不支持 model 配置的 CLI(aiden/
276
- * antigravity/mtr 等没声明 modelChoices)返回 null,promptModel 据此整段
277
- * 跳过提问。适配器解析失败也按"不支持"处理,避免在 setup 中冒出陌生堆栈。
278
- */
279
- function cliModelChoices(cliId) {
280
- try {
281
- const adapter = createCliAdapterSync(cliId);
282
- return adapter.modelChoices && adapter.modelChoices.length > 0
283
- ? adapter.modelChoices
284
- : null;
285
- }
286
- catch {
287
- return null;
288
- }
289
- }
290
- /**
291
- * 询问"指定 CLI 用哪个 model"。
292
- * - cliId 对应的 adapter 没声明 modelChoices → return undefined(跳过提问)
293
- * - 用户输入序号 → 取候选列表对应项
294
- * - 输入空 + 提供 current → 保留当前值(current 透传出去)
295
- * - 输入空 + 无 current → return undefined(用 CLI 默认)
296
- * - 输入 `-` → return null(语义:清空,调用方据此 delete model 字段)
297
- * - 输入自由文本 → 原样返回
298
- */
299
- async function promptModel(rl, cliId, current) {
300
- const choices = cliModelChoices(cliId);
301
- if (!choices)
302
- return undefined;
303
- const numbered = choices.map((m, i) => `${i + 1}) ${m}`).join(' ');
304
- printInputHelp(`CLI Model(${cliId})`, [
305
- '可选。在 spawn CLI 时注入 model 参数;同一 CLI 配多个 bot 可以跑不同 model。',
306
- `候选: ${numbered} ${choices.length + 1}) Other(自定义输入)`,
307
- current
308
- ? '留空保留当前值;输入 - 清空(回到 CLI 默认)。'
309
- : '留空 = 不设置(用 CLI 默认)。',
310
- ]);
311
- const label = current ? formatOptionalValue(current) : '未设置';
312
- const raw = (await ask(rl, `CLI Model [${label}]: `)).trim();
313
- if (!raw)
314
- return current ?? undefined;
315
- if (raw === '-')
316
- return null;
317
- const numIdx = Number(raw);
318
- if (Number.isInteger(numIdx) && numIdx >= 1 && numIdx <= choices.length) {
319
- return choices[numIdx - 1];
320
- }
321
- if (Number.isInteger(numIdx) && numIdx === choices.length + 1) {
322
- // 选了 Other,进一步要 free-form
323
- const customRaw = (await ask(rl, '请输入 model 名: ')).trim();
324
- if (!customRaw)
325
- return current ?? undefined;
326
- return customRaw;
327
- }
328
- // 直接当成自由输入
329
- return raw;
330
- }
331
266
  // Thin wrapper around setup/bots-store.writeBotsJsonAtomic so call-sites keep
332
267
  // the same name without passing BOTS_JSON_FILE explicitly each time.
333
268
  function writeBotsJsonAtomic(bots) {
@@ -543,7 +478,7 @@ async function promptBotConfig(rl) {
543
478
  return null;
544
479
  }
545
480
  console.log('✅ 凭证有效(tenant_access_token 已成功获取)\n');
546
- console.log('支持的 CLI: 1) claude-code 2) aiden 3) coco 4) codex 5) cursor 6) gemini 7) opencode 8) antigravity 9) mtr 10) hermes 11) codex-app 12) mira');
481
+ console.log('支持的 CLI: 1) claude-code 2) aiden 3) coco 4) codex 5) cursor 6) gemini 7) opencode 8) antigravity');
547
482
  const cliChoice = await ask(rl, 'CLI 适配器 [1]: ');
548
483
  let cliId;
549
484
  try {
@@ -555,7 +490,6 @@ async function promptBotConfig(rl) {
555
490
  return null;
556
491
  }
557
492
  const workingDir = await ask(rl, '默认工作目录 [~]: ');
558
- const modelChoice = await promptModel(rl, cliId);
559
493
  // 不再持久化 brand 字段: setup 阶段 brand=lark 直接被 obtainCredentials 中止,
560
494
  // 落盘的永远是 'feishu', 写进配置是死字段. 等 lark 完整接入再加回来, 那时
561
495
  // 同步打开 daemon 链路的 brand 透传. botBrand() helper 读不到字段会 default
@@ -568,10 +502,6 @@ async function promptBotConfig(rl) {
568
502
  // 在哪儿, 不用去 README 查字段名.
569
503
  workingDir: workingDir.trim() || '~',
570
504
  };
571
- // modelChoice === undefined → 该 CLI 没声明候选 / 用户跳过;不写 model 字段
572
- if (typeof modelChoice === 'string' && modelChoice) {
573
- bot.model = modelChoice;
574
- }
575
505
  // 扫码场景默认填扫码人自己 (registerApp 返回里有 open_id), 天然就是 owner.
576
506
  // 手动 fallback 场景没 open_id —— 必须显式指定 owner, 否则配置无 owner:
577
507
  // allowedUsers 为空时虽然"全开放", 但一旦后续加了 allowedChatGroups 就会变成
@@ -632,7 +562,7 @@ async function promptEditBotConfig(rl, bot) {
632
562
  '留空保留当前值。',
633
563
  ]);
634
564
  input.larkAppSecret = await ask(rl, `LARK_APP_SECRET [保留当前值]: `);
635
- console.log('\n支持的 CLI: 1) claude-code 2) aiden 3) coco 4) codex 5) cursor 6) gemini 7) opencode 8) antigravity 9) mtr 10) hermes 11) codex-app 12) mira');
565
+ console.log('\n支持的 CLI: 1) claude-code 2) aiden 3) coco 4) codex 5) cursor 6) gemini 7) opencode 8) antigravity');
636
566
  printInputHelp('CLI 适配器', [
637
567
  '选择 botmux 需要套用哪一种 CLI 参数协议和会话恢复方式。',
638
568
  '留空保留当前值;可以输入序号,也可以直接输入适配器 ID。',
@@ -644,29 +574,6 @@ async function promptEditBotConfig(rl, bot) {
644
574
  '留空保留当前值;输入 - 清空覆盖,回到 PATH 查 cliId 对应的默认二进制。',
645
575
  ]);
646
576
  input.cliPathOverride = await ask(rl, `CLI 可执行文件路径覆盖 [${formatOptionalValue(bot.cliPathOverride)}]: `);
647
- // promptModel 返回 string | null | undefined,直接灌进 BotConfigEditInput.model:
648
- // undefined = 用户跳过 / adapter 不支持 → applyBotConfigEdits 不改 model
649
- // null = 用户输 `-` 清空 → delete model
650
- // string = 设值
651
- // 用本轮编辑后的 cliId 而非 bot.cliId —— 用户可能刚换了 CLI。
652
- const effectiveCliIdForModel = (resolveCliId(input.cliChoice) ?? bot.cliId ?? 'claude-code');
653
- const cliChanged = !!resolveCliId(input.cliChoice) && resolveCliId(input.cliChoice) !== bot.cliId;
654
- if (cliChanged && !cliModelChoices(effectiveCliIdForModel)) {
655
- // 切到一个不支持 model 的 adapter(例如 aiden / mtr / antigravity):
656
- // 即使原本配了 model 也要主动清空,避免 bots.json 里残留陈旧字段——
657
- // 否则用户后面再换回支持 model 的 adapter 一路回车时,旧 model
658
- // 会被当作"当前值"保留下来误套到新 CLI 上。
659
- console.log('\n⚠️ 新 CLI 不支持 --model 参数,已清空原 model 字段。');
660
- input.model = null;
661
- }
662
- else {
663
- const promptCurrent = cliChanged ? undefined : (typeof bot.model === 'string' ? bot.model : undefined);
664
- const result = await promptModel(rl, effectiveCliIdForModel, promptCurrent);
665
- // 切换 CLI 时哪怕用户留空也要清掉旧 model —— 旧值是上一个 CLI 的 model,
666
- // 套到新 CLI 上没意义。result === undefined 在"未变 CLI"分支等价于"保留旧值",
667
- // 但在 cliChanged 分支等价于"用户没指定,回到新 CLI 默认",必须 force null。
668
- input.model = cliChanged && result === undefined ? null : result;
669
- }
670
577
  printInputHelp('会话后端 backendType', [
671
578
  '可选。pty 更轻量;tmux 支持 adopt 和 Web Terminal 附着。',
672
579
  '留空保留当前值;输入 - 回到自动检测;只接受 pty 或 tmux。',
@@ -2101,17 +2008,9 @@ botmux v${getVersion()} — IM ↔ AI 编程 CLI 桥接
2101
2008
  --images <path> 内联图片(可重复)
2102
2009
  --files <path> 附件(可重复)
2103
2010
  --mention <open_id:name> @提及(可重复)
2104
- --mention-back @回本轮触发消息的发送者(open_id 自动取自会话)
2105
- --no-mention 明确声明本条不@任何人
2106
- --quote <message_id> 指定引用某条消息(普通群,默认引用本轮触发消息)
2107
- --no-quote 不引用,发独立消息(普通群)
2108
2011
  --card | --text 强制卡片 / 纯文本(默认按 md 语法自动判断)
2109
2012
  --top-level 发顶层消息(不回复进当前话题)
2110
2013
  --chat-id <oc_xxx> 指定目标群(默认当前话题所在群)
2111
- @ 硬门:每条回复须三选一 --mention/--mention-back/--no-mention,否则报错不发。
2112
- 按内容价值选:有实质结论要对方看/确认/决策→--mention-back(或--mention点名);
2113
- 纯记录/低优先级进度/简短确认→--no-mention;没信息量的"收到"不如不发。
2114
- (可设 BOTMUX_REQUIRE_MENTION_DECISION=false 关闭硬门)
2115
2014
  bots list 列出当前群聊中的机器人(含 open_id)
2116
2015
  history [--limit N] [--scope session|thread|chat|ambient]
2117
2016
  拉取当前会话的消息历史 (JSON)。默认按 session scope:话题/话题群 → 话题内,普通群 → 整群;
@@ -2556,10 +2455,21 @@ function argValues(args, ...flags) {
2556
2455
  // Card v2 body builder helpers — extracted to im/lark/md-card.ts so the
2557
2456
  // daemon's bridge fallback path can produce identical cards. cmdSend
2558
2457
  // keeps using `buildCardBodyElements` and `hasMarkdown` from there.
2559
- import { buildCardBodyElements, hasMarkdown, brandFooterSegment } from './im/lark/md-card.js';
2560
- import { resolveBrandLabel } from './bot-registry.js';
2561
- import { config } from './config.js';
2562
- import { resolveQuoteTarget, validateMentionDecision } from './services/send-policy.js';
2458
+ import { buildCardBodyElements, hasMarkdown } from './im/lark/md-card.js';
2459
+ /**
2460
+ * Decide who the reply card should @ in its footer.
2461
+ *
2462
+ * Non-oncall chats: `发送给: @<owner>`.
2463
+ * Oncall chats: `发送给: @<last caller>` (falls back to owner if unknown) —
2464
+ * permission is governed by allowedUsers, so there's no per-chat list to cc.
2465
+ */
2466
+ function buildFooterAddressing(s, oncall) {
2467
+ const owner = s.ownerOpenId;
2468
+ const caller = s.lastCallerOpenId ?? owner;
2469
+ if (!oncall)
2470
+ return { sendTo: owner, cc: [] };
2471
+ return { sendTo: caller, cc: [] };
2472
+ }
2563
2473
  async function cmdSend(rest) {
2564
2474
  // Safety gate: a CLI agent running inside a workflow subagent (Slice F)
2565
2475
  // must not chat-post directly — chat-facing side effects are reserved
@@ -2588,13 +2498,6 @@ async function cmdSend(rest) {
2588
2498
  // for streaming-card / progress UI.
2589
2499
  const sendTopLevel = rest.includes('--top-level');
2590
2500
  const overrideChatId = argValue(rest, '--chat-id');
2591
- // Quote chain (chat scope): --quote <message_id> overrides the auto target,
2592
- // --no-quote forces a plain (un-quoted) send.
2593
- const explicitQuote = argValue(rest, '--quote');
2594
- const noQuote = rest.includes('--no-quote');
2595
- // @ hard-gate: every reply must explicitly choose one of these.
2596
- const mentionBack = rest.includes('--mention-back');
2597
- const noMention = rest.includes('--no-mention');
2598
2501
  const sid = sessionIdArg ?? findAncestorSessionId();
2599
2502
  if (!sid) {
2600
2503
  console.error('无法推断 session-id。请在 Lark 话题内的 CLI 会话中运行,或传 --session-id <id>。');
@@ -2620,7 +2523,7 @@ async function cmdSend(rest) {
2620
2523
  content = readFileSync(contentFile, 'utf-8');
2621
2524
  }
2622
2525
  else {
2623
- const pos = positionals(rest, ['--card', '--text', '--top-level', '--no-quote', '--mention-back', '--no-mention']);
2526
+ const pos = positionals(rest, ['--card', '--text', '--top-level']);
2624
2527
  if (pos.length > 0) {
2625
2528
  content = pos.join(' ');
2626
2529
  }
@@ -2646,28 +2549,6 @@ async function cmdSend(rest) {
2646
2549
  mentions.push({ open_id: m.trim(), name: '' });
2647
2550
  }
2648
2551
  }
2649
- // @ hard-gate (config.send.requireMentionDecision, default on): force the
2650
- // model to make an explicit @ decision before sending. --top-level publish
2651
- // is exempt. The error text adapts to who is being replied to (人 / bot).
2652
- const mentionGate = validateMentionDecision({
2653
- enabled: config.send.requireMentionDecision,
2654
- sendTopLevel,
2655
- hasMentionArgs: mentionArgs.length > 0,
2656
- mentionBack,
2657
- noMention,
2658
- hasQuoteTargetSender: !!s.quoteTargetSenderOpenId,
2659
- });
2660
- if (!mentionGate.ok) {
2661
- console.error(mentionGate.error);
2662
- process.exit(2);
2663
- }
2664
- // --mention-back: @ the sender of the message this turn is replying to
2665
- // (open_id from the session — model needn't know it). Bare-name form so it
2666
- // renders as a trailing <at>.
2667
- if (mentionBack && s.quoteTargetSenderOpenId
2668
- && !mentions.some(m => m.open_id === s.quoteTargetSenderOpenId)) {
2669
- mentions.push({ open_id: s.quoteTargetSenderOpenId, name: '' });
2670
- }
2671
2552
  // Validate file paths
2672
2553
  for (const p of [...images, ...files]) {
2673
2554
  if (!existsSync(p)) {
@@ -2682,7 +2563,7 @@ async function cmdSend(rest) {
2682
2563
  registerBot(cfg);
2683
2564
  }
2684
2565
  catch { /* */ }
2685
- const { sendMessage, replyMessage, uploadImage, uploadFile, MessageWithdrawnError } = await import('./im/lark/client.js');
2566
+ const { sendMessage, replyMessage, uploadImage, uploadFile } = await import('./im/lark/client.js');
2686
2567
  const appId = s.larkAppId;
2687
2568
  // Effective target chat for top-level mode (defaults to session's chat)
2688
2569
  const targetChatId = overrideChatId ?? s.chatId;
@@ -2697,36 +2578,10 @@ async function cmdSend(rest) {
2697
2578
  // addressing to go to the last caller in the shared oncall workspace.
2698
2579
  const oncallEntry = !sendTopLevel && !overrideChatId && s.chatId
2699
2580
  ? findOncallChatForAnyBot(s.chatId) : undefined;
2700
- // Dispatch helper: top-level / chat-scope send vs reply-in-thread, single
2701
- // decision point. Used for file attachments (always plain in chat scope).
2581
+ // Dispatch helper: top-level / chat-scope send vs reply-in-thread, single decision point
2702
2582
  const dispatch = (content, msgType) => (sendTopLevel || isChatScope)
2703
2583
  ? sendMessage(appId, targetChatId, content, msgType)
2704
2584
  : replyMessage(appId, s.rootMessageId, content, msgType, true);
2705
- // Quote chain (普通群): the primary message replies to the turn's target so
2706
- // Lark renders a 引用 chain. --quote overrides, --no-quote opts out. Thread
2707
- // scope and --top-level never quote. Withdrawn target → fall back to plain.
2708
- const quoteTargetId = resolveQuoteTarget({
2709
- isChatScope, sendTopLevel, noQuote, explicitQuote,
2710
- sessionQuoteTargetId: s.quoteTargetId,
2711
- });
2712
- let primaryQuotedId = null;
2713
- const dispatchPrimary = async (content, msgType) => {
2714
- if (quoteTargetId) {
2715
- try {
2716
- const id = await replyMessage(appId, quoteTargetId, content, msgType, false);
2717
- primaryQuotedId = quoteTargetId;
2718
- return id;
2719
- }
2720
- catch (err) {
2721
- if (err instanceof MessageWithdrawnError) {
2722
- console.error(`引用目标 ${quoteTargetId} 已撤回,改为普通发送`);
2723
- return sendMessage(appId, targetChatId, content, msgType);
2724
- }
2725
- throw err;
2726
- }
2727
- }
2728
- return dispatch(content, msgType);
2729
- };
2730
2585
  try {
2731
2586
  // Upload images in parallel
2732
2587
  const imageKeys = [];
@@ -2761,76 +2616,51 @@ async function cmdSend(rest) {
2761
2616
  // "获取群组中其他机器人和用户@当前机器人的消息"权限),不再走任何本地
2762
2617
  // 转发——botmux 历史上为绕过 Lark 不投递跨 bot 事件搞过 signal-file,
2763
2618
  // 那套已经在该权限上线后整体下线。
2764
- let botEntries = [];
2765
- let crossRef = {};
2766
2619
  try {
2767
2620
  const dataDir = resolveDataDir();
2768
2621
  const botInfoPath = join(dataDir, 'bots-info.json');
2769
- botEntries = existsSync(botInfoPath) ? JSON.parse(readFileSync(botInfoPath, 'utf-8')) : [];
2622
+ const botEntries = existsSync(botInfoPath) ? JSON.parse(readFileSync(botInfoPath, 'utf-8')) : [];
2770
2623
  const crossRefPath = join(dataDir, `bot-openids-${appId}.json`);
2771
- crossRef = existsSync(crossRefPath)
2624
+ const crossRef = existsSync(crossRefPath)
2772
2625
  ? JSON.parse(readFileSync(crossRefPath, 'utf-8'))
2773
2626
  : {};
2774
- // --no-mention 显式不 @ 任何人:跳过正文 @BotName 的自动注入,否则正文里
2775
- // 出现的 @名字 仍会被注入成 <at>,破坏 --no-mention 语义、还可能误触发对方
2776
- // bot(正是要避免的循环 @)。botEntries/crossRef 仍需加载供 footer 寻址用。
2777
- if (!noMention) {
2778
- const alreadyMentioned = new Set(mentions.map(m => m.open_id));
2779
- // Sort by name length desc so longer names ("Claude分身") win over their
2780
- // prefix ("Claude") when both could match — break-on-first-hit otherwise
2781
- // routes "@Claude分身" to Claude.
2782
- const sortedEntries = [...botEntries].sort((a, b) => (b.botName?.length ?? 0) - (a.botName?.length ?? 0));
2783
- const selfAliases = new Set(botEntries
2784
- .filter(entry => entry.larkAppId === appId)
2785
- .flatMap(entry => [entry.botName, entry.cliId])
2786
- .filter((name) => !!name)
2787
- .map(name => name.toLowerCase()));
2788
- for (const entry of sortedEntries) {
2789
- if (!entry.botName || entry.larkAppId === appId)
2627
+ const alreadyMentioned = new Set(mentions.map(m => m.open_id));
2628
+ // Sort by name length desc so longer names ("Claude分身") win over their
2629
+ // prefix ("Claude") when both could match — break-on-first-hit otherwise
2630
+ // routes "@Claude分身" to Claude.
2631
+ const sortedEntries = [...botEntries].sort((a, b) => (b.botName?.length ?? 0) - (a.botName?.length ?? 0));
2632
+ for (const entry of sortedEntries) {
2633
+ if (!entry.botName || entry.larkAppId === appId)
2634
+ continue;
2635
+ const names = [entry.botName, entry.cliId].filter(Boolean);
2636
+ for (const name of names) {
2637
+ const escName = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
2638
+ // Boundary: lookbehind blocks only ASCII word chars (so `user@Claude`
2639
+ // is rejected but `看看@CoCo` is accepted — CJK prefix is normal in
2640
+ // Chinese text). Lookahead blocks any Unicode letter/digit so
2641
+ // `@Claude2` doesn't match name "Claude" and `@Claude分身好的` doesn't
2642
+ // either-half-match.
2643
+ const re = new RegExp(`(?<![A-Za-z0-9_])@${escName}(?![\\p{L}\\p{N}_])`, 'iu');
2644
+ if (!re.test(text))
2790
2645
  continue;
2791
- const names = [entry.botName, entry.cliId]
2792
- .filter((name) => !!name && !selfAliases.has(name.toLowerCase()));
2793
- for (const name of names) {
2794
- const escName = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
2795
- // Boundary: lookbehind blocks only ASCII word chars (so `user@Claude`
2796
- // is rejected but `看看@CoCo` is accepted — CJK prefix is normal in
2797
- // Chinese text). Lookahead blocks any Unicode letter/digit so
2798
- // `@Claude2` doesn't match name "Claude" and `@Claude分身好的` doesn't
2799
- // either-half-match.
2800
- const re = new RegExp(`(?<![A-Za-z0-9_])@${escName}(?![\\p{L}\\p{N}_])`, 'iu');
2801
- if (!re.test(text))
2802
- continue;
2803
- // Lark open_id is per-app scoped. Use sender-scoped id from cross-ref
2804
- // only — falling back to entry.botOpenId would feed Lark a wrong-scope
2805
- // id (target's self-scoped) and the API would reject it. Skip + warn
2806
- // so the missing cross-ref is observable instead of silently dropped.
2807
- const senderScopedId = crossRef[entry.botName];
2808
- if (!senderScopedId) {
2809
- console.error(`[botmux send] no cross-ref entry for "${entry.botName}" in app ${appId}, skipping auto-mention (cross-ref populates after the sender app first sees the target bot)`);
2810
- break;
2811
- }
2812
- if (alreadyMentioned.has(senderScopedId))
2813
- break;
2814
- mentions.push({ open_id: senderScopedId, name: entry.botName });
2815
- alreadyMentioned.add(senderScopedId);
2646
+ // Lark open_id is per-app scoped. Use sender-scoped id from cross-ref
2647
+ // only — falling back to entry.botOpenId would feed Lark a wrong-scope
2648
+ // id (target's self-scoped) and the API would reject it. Skip + warn
2649
+ // so the missing cross-ref is observable instead of silently dropped.
2650
+ const senderScopedId = crossRef[entry.botName];
2651
+ if (!senderScopedId) {
2652
+ console.error(`[botmux send] no cross-ref entry for "${entry.botName}" in app ${appId}, skipping auto-mention (cross-ref populates after the sender app first sees the target bot)`);
2816
2653
  break;
2817
2654
  }
2655
+ if (alreadyMentioned.has(senderScopedId))
2656
+ break;
2657
+ mentions.push({ open_id: senderScopedId, name: entry.botName });
2658
+ alreadyMentioned.add(senderScopedId);
2659
+ break;
2818
2660
  }
2819
2661
  }
2820
2662
  }
2821
2663
  catch { /* best-effort */ }
2822
- const explicitKnownBotMention = hasKnownBotMention(text, mentions, botEntries, crossRef, appId);
2823
- const knownBotOpenIds = knownBotOpenIdsFromCrossRef(crossRef, botEntries, appId);
2824
- // --no-mention 显式不 @ 任何人 → 连 footer 的"发送给/cc"寻址 <at> 也清空,
2825
- // 否则 footer 仍会 @ 人,与 --no-mention 语义和"未@任何人"输出自相矛盾
2826
- // (Codex review P2)。--top-level 同样无特定收件人。
2827
- const footerAddressing = (sendTopLevel || noMention)
2828
- ? { sendTo: undefined, cc: [] }
2829
- : buildFooterAddressing(s, {
2830
- isOncall: !!oncallEntry,
2831
- hasExplicitBotMention: explicitKnownBotMention,
2832
- knownBotOpenIds,
2833
- });
2834
2664
  // Decide: interactive card (renders markdown) vs. post (plain text).
2835
2665
  // Explicit --card / --text wins; otherwise auto-detect markdown syntax.
2836
2666
  const useCard = forceCard || (!forceText && hasMarkdown(text));
@@ -2865,9 +2695,12 @@ async function cmdSend(rest) {
2865
2695
  return `<at id=${openId}></at>`;
2866
2696
  });
2867
2697
  }
2868
- // Non-inlined mentions are no longer dangled as a trailing @ block at the
2869
- // body bottom they're consolidated onto the footer `发送给:` line below
2870
- // (human addressee first, then explicit targets). See orderedFooterRecipients.
2698
+ const trailingAts = [];
2699
+ for (const m of mentions)
2700
+ if (!usedIds.has(m.open_id))
2701
+ trailingAts.push(`<at id=${m.open_id}></at>`);
2702
+ if (trailingAts.length > 0)
2703
+ md = md ? `${md}\n\n${trailingAts.join(' ')}` : trailingAts.join(' ');
2871
2704
  // Inline images into the markdown via ![](img_key). If caller used an
2872
2705
  // `![alt](img:N)` placeholder, substitute by 0-based index; any remaining
2873
2706
  // images get appended at the end so they flow with the text.
@@ -2891,43 +2724,31 @@ async function cmdSend(rest) {
2891
2724
  const elements = mdWithImages ? buildCardBodyElements(mdWithImages) : [];
2892
2725
  // Footer: de-emphasized markdown (v2 dropped the `note` tag). Use small
2893
2726
  // text size + grey font tag so it reads like a footnote below the hr.
2894
- // Oncall groups usually address whoever triggered this turn (may not be
2895
- // the session owner). Bot recipients are filtered out so footer chrome
2896
- // cannot accidentally wake a sibling bot.
2897
- // Brand segment honours this bot's configured brandLabel (unset
2898
- // default botmux, '' suppressed, else custom). Same resolver/rule as
2899
- // the daemon's card builders so both send paths render identically.
2900
- const footerParts = [];
2901
- const brandSeg = brandFooterSegment(resolveBrandLabel(appId));
2902
- if (brandSeg)
2903
- footerParts.push(brandSeg);
2904
- // All real mentions land on one footer line: human addressee first, then
2905
- // explicit @ targets (incl. handoff bots), then cc. Ids already inlined in
2906
- // the body prose are skipped. Top-level publish keeps sendTo empty.
2907
- const footerRecipients = orderedFooterRecipients({
2908
- sendTo: footerAddressing.sendTo,
2909
- mentionIds: mentions.map(m => m.open_id),
2910
- cc: footerAddressing.cc,
2911
- inlinedIds: usedIds,
2912
- });
2913
- if (footerRecipients.length > 0) {
2914
- footerParts.push(`发送给:${footerRecipients.map(id => `<at id=${id}></at>`).join(' ')}`);
2915
- }
2916
- // Empty brand + no recipients → no footer at all (skip the orphan HR).
2917
- if (footerParts.length > 0) {
2918
- elements.push({ tag: 'hr' });
2919
- elements.push({
2920
- tag: 'markdown',
2921
- text_size: 'notation_small_v2',
2922
- content: `<font color='grey'>${footerParts.join(' · ')}</font>`,
2923
- });
2727
+ // Oncall groups: `发送给` targets whoever triggered this turn (may not
2728
+ // be the session owner). Non-oncall: keep owner-only behaviour.
2729
+ const footerParts = ['[botmux](https://github.com/deepcoldy/botmux)'];
2730
+ // Top-level publish has no specific recipient drop "发送给/cc" addressing
2731
+ // so the message doesn't @ the session owner who isn't even in the target chat.
2732
+ const addressing = sendTopLevel
2733
+ ? { sendTo: undefined, cc: [] }
2734
+ : buildFooterAddressing(s, oncallEntry);
2735
+ if (addressing.sendTo)
2736
+ footerParts.push(`发送给:<at id=${addressing.sendTo}></at>`);
2737
+ if (addressing.cc.length > 0) {
2738
+ footerParts.push(`cc:${addressing.cc.map(id => `<at id=${id}></at>`).join(' ')}`);
2924
2739
  }
2740
+ elements.push({ tag: 'hr' });
2741
+ elements.push({
2742
+ tag: 'markdown',
2743
+ text_size: 'notation_small_v2',
2744
+ content: `<font color='grey'>${footerParts.join(' · ')}</font>`,
2745
+ });
2925
2746
  const cardJson = JSON.stringify({
2926
2747
  schema: '2.0',
2927
2748
  config: { update_multi: true },
2928
2749
  body: { direction: 'vertical', elements },
2929
2750
  });
2930
- messageId = await dispatchPrimary(cardJson, 'interactive');
2751
+ messageId = await dispatch(cardJson, 'interactive');
2931
2752
  }
2932
2753
  else {
2933
2754
  // Plain-text path: build post content, paragraph per line.
@@ -2951,31 +2772,38 @@ async function cmdSend(rest) {
2951
2772
  }) : [];
2952
2773
  for (const key of imageKeys)
2953
2774
  postContent.push([{ tag: 'img', image_key: key }]);
2954
- // Footer: mirror the card layout — all real mentions go on one
2955
- // `发送给:` line (human addressee first, then explicit targets, then cc),
2956
- // separated from the body by a blank paragraph. Ids already inlined in the
2957
- // body prose are skipped. Top-level publish keeps sendTo empty.
2958
- const inlinedIds = new Set();
2959
- for (const para of postContent)
2960
- for (const n of para)
2961
- if (n.tag === 'at')
2962
- inlinedIds.add(n.user_id);
2963
- const footerRecipients = orderedFooterRecipients({
2964
- sendTo: footerAddressing.sendTo,
2965
- mentionIds: mentions.map(m => m.open_id),
2966
- cc: footerAddressing.cc,
2967
- inlinedIds,
2968
- });
2969
- if (footerRecipients.length > 0) {
2775
+ if (mentions.length > 0) {
2776
+ const usedIds = new Set();
2777
+ for (const para of postContent)
2778
+ for (const n of para)
2779
+ if (n.tag === 'at')
2780
+ usedIds.add(n.user_id);
2781
+ const unused = mentions.filter(m => !usedIds.has(m.open_id));
2782
+ if (unused.length > 0) {
2783
+ if (postContent.length === 0)
2784
+ postContent.push([]);
2785
+ for (const m of unused)
2786
+ postContent[postContent.length - 1].push({ tag: 'at', user_id: m.open_id });
2787
+ }
2788
+ }
2789
+ // Footer: mirror the card layout — a blank paragraph separates the body
2790
+ // from the addressing line(s). `发送给: @<caller>` always. Top-level
2791
+ // publish has no specific recipient — skip addressing entirely.
2792
+ const addressing = sendTopLevel
2793
+ ? { sendTo: undefined, cc: [] }
2794
+ : buildFooterAddressing(s, oncallEntry);
2795
+ if (addressing.sendTo || addressing.cc.length > 0) {
2970
2796
  if (postContent.length > 0)
2971
2797
  postContent.push([{ tag: 'text', text: '' }]);
2972
- postContent.push([
2973
- { tag: 'text', text: '发送给:' },
2974
- ...footerRecipients.map(id => ({ tag: 'at', user_id: id })),
2975
- ]);
2798
+ if (addressing.sendTo) {
2799
+ postContent.push([{ tag: 'text', text: '发送给:' }, { tag: 'at', user_id: addressing.sendTo }]);
2800
+ }
2801
+ if (addressing.cc.length > 0) {
2802
+ postContent.push([{ tag: 'text', text: 'cc:' }, ...addressing.cc.map(id => ({ tag: 'at', user_id: id }))]);
2803
+ }
2976
2804
  }
2977
2805
  const postJson = JSON.stringify({ zh_cn: { title: '', content: postContent } });
2978
- messageId = await dispatchPrimary(postJson, 'post');
2806
+ messageId = await dispatch(postJson, 'post');
2979
2807
  }
2980
2808
  // Bridge fallback marker — append-only jsonl per session. The worker
2981
2809
  // gates its non-adopt transcript-driven fallback on whether any send
@@ -3006,17 +2834,7 @@ async function cmdSend(rest) {
3006
2834
  // 不需要 botmux 自己再写本地 signal 文件做转发。outgoing 消息里 @BotName /
3007
2835
  // --mention 的 open_id 解析(在上方 mentions 数组里完成)仍然必要,它让
3008
2836
  // Lark 在消息里渲染真正的 @at 元素,从而触发对方 bot 的 WS 事件投递。
3009
- const atSummary = mentions.length > 0
3010
- ? `@${mentions.map(m => m.name || m.open_id).join(',')}`
3011
- : '未@任何人';
3012
- console.error(`✓ 已发送 ${messageId} | ${primaryQuotedId ? `引用 ${primaryQuotedId}` : '未引用'} | ${atSummary}`);
3013
- console.log(JSON.stringify({
3014
- success: true,
3015
- messageId,
3016
- sessionId: sid,
3017
- quotedMessageId: primaryQuotedId,
3018
- mentioned: mentions.map(m => ({ open_id: m.open_id, name: m.name })),
3019
- }));
2837
+ console.log(JSON.stringify({ success: true, messageId, sessionId: sid }));
3020
2838
  }
3021
2839
  catch (err) {
3022
2840
  console.error(`发送失败: ${err.message}`);
@@ -3198,307 +3016,6 @@ botmux create-group — 用一组机器人新建飞书群
3198
3016
  }
3199
3017
  }
3200
3018
  // ─── Bots subcommand ─────────────────────────────────────────────────────────
3201
- // ─── botmux ask v0.1.7 ───────────────────────────────────────────────────────
3202
- //
3203
- // CLI agent inside a botmux-spawned session calls `botmux ask buttons
3204
- // --options "..." "<prompt>"`. Daemon sends a Lark card; user clicks; CLI
3205
- // process unblocks with the selected key (or exit 124 on timeout, exit 3 if
3206
- // the daemon dies). See /tmp/botmux-ask.md (or design memory).
3207
- /**
3208
- * postAsk: 找到 daemon → POST /api/asks → 返回 AskResult。
3209
- * 连接失败 / HTTP 错误时抛出带 exitCode 属性的 Error:
3210
- * - exitCode=3:daemon 不可达或 HTTP 非 400
3211
- * - exitCode=2:400 + no_approvers
3212
- */
3213
- async function postAsk(body) {
3214
- const larkAppId = body.larkAppId;
3215
- const daemon = findDaemon(larkAppId);
3216
- if (!daemon) {
3217
- const err = new Error(`botmux ask: 找不到 daemon (larkAppId=${larkAppId})。daemon 已停?exit 3.`);
3218
- err.exitCode = 3;
3219
- throw err;
3220
- }
3221
- let res;
3222
- try {
3223
- res = await fetch(`http://127.0.0.1:${daemon.ipcPort}/api/asks`, {
3224
- method: 'POST',
3225
- headers: { 'content-type': 'application/json' },
3226
- body: JSON.stringify(body),
3227
- // No client-side timeout — broker enforces `timeoutMs` and will respond
3228
- // with `kind:'timedOut'` so this fetch always settles.
3229
- });
3230
- }
3231
- catch (fetchErr) {
3232
- const msg = fetchErr instanceof Error ? fetchErr.message : String(fetchErr);
3233
- const err = new Error(`botmux ask: 无法连接 daemon (port=${daemon.ipcPort}): ${msg}`);
3234
- err.exitCode = 3;
3235
- throw err;
3236
- }
3237
- if (!res.ok) {
3238
- let errBody = '';
3239
- try {
3240
- errBody = (await res.text()).slice(0, 200);
3241
- }
3242
- catch { /* */ }
3243
- if (res.status === 400 && /no_approvers/.test(errBody)) {
3244
- const err = new Error('botmux ask: 当前会话没有可批准者(session.owner 不在 bot.allowedUsers 里,且 --approver 未指定)');
3245
- err.exitCode = 2;
3246
- throw err;
3247
- }
3248
- const err = new Error(`botmux ask: daemon HTTP ${res.status}: ${errBody}`);
3249
- err.exitCode = 3;
3250
- throw err;
3251
- }
3252
- try {
3253
- return (await res.json());
3254
- }
3255
- catch (jsonErr) {
3256
- const err = new Error(`botmux ask: daemon 返回非 JSON: ${jsonErr}`);
3257
- err.exitCode = 3;
3258
- throw err;
3259
- }
3260
- }
3261
- async function cmdAsk(sub, rest) {
3262
- // Workflow-subagent safety gate (same posture as cmdSend): a CLI running
3263
- // inside a workflow subagent (Slice F) must not surface chat UI. Workflow
3264
- // approvals belong in humanGate / decision nodes so the choice is part of
3265
- // the run's event log; an ad-hoc `botmux ask` would bypass that audit
3266
- // trail entirely.
3267
- if (process.env.BOTMUX_WORKFLOW === '1') {
3268
- const runId = process.env.BOTMUX_WORKFLOW_RUN_ID ?? '?';
3269
- const nodeId = process.env.BOTMUX_WORKFLOW_NODE_ID ?? '?';
3270
- console.error(`botmux ask refused inside workflow subagent (run=${runId} node=${nodeId}).\n` +
3271
- `Workflow subagents must surface approvals via humanGate / decision nodes\n` +
3272
- `so the resolution is recorded in the run's event log; ask would bypass it.`);
3273
- process.exit(2);
3274
- }
3275
- // Only `buttons` shipped in v0.1.7. The bare alias (`botmux ask --options`)
3276
- // routes here with sub='' — accept it and behave identically. `ask text` /
3277
- // `ask confirm` are reserved for later versions.
3278
- if (sub && sub !== 'buttons') {
3279
- console.error(`botmux ask: 未知 subcommand "${sub}"(v0.1.7 仅支持 \`buttons\` 或省略)`);
3280
- process.exit(2);
3281
- }
3282
- const { findMissingAskEnv, parseAskOptions, parseAskTimeoutSeconds, AskArgsError } = await import('./core/ask-args.js');
3283
- const { toLegacySelected } = await import('./core/ask-types.js');
3284
- const missing = findMissingAskEnv(process.env);
3285
- if (missing) {
3286
- console.error(`botmux ask: 缺少必需环境变量 ${missing}。` +
3287
- ` 请在 botmux daemon spawn 的 CLI 会话内运行。`);
3288
- process.exit(2);
3289
- }
3290
- const optionsRaw = argValue(rest, '--options');
3291
- const timeoutRaw = argValue(rest, '--timeout');
3292
- const useJson = rest.includes('--json');
3293
- const approverArgs = argValues(rest, '--approver');
3294
- const positionalArgs = positionals(rest, ['--json']);
3295
- let options;
3296
- let timeoutMs;
3297
- try {
3298
- options = parseAskOptions(optionsRaw);
3299
- timeoutMs = parseAskTimeoutSeconds(timeoutRaw);
3300
- }
3301
- catch (err) {
3302
- if (err instanceof AskArgsError) {
3303
- console.error(`botmux ask: ${err.message}`);
3304
- process.exit(2);
3305
- }
3306
- throw err;
3307
- }
3308
- const prompt = positionalArgs.join(' ').trim();
3309
- if (!prompt) {
3310
- console.error('botmux ask: 缺少 prompt。用法: botmux ask buttons --options "yes,no" "继续发版吗?"');
3311
- process.exit(2);
3312
- }
3313
- const larkAppId = process.env.BOTMUX_LARK_APP_ID;
3314
- const body = {
3315
- sessionId: process.env.BOTMUX_SESSION_ID,
3316
- chatId: process.env.BOTMUX_CHAT_ID,
3317
- larkAppId,
3318
- rootMessageId: process.env.BOTMUX_ROOT_MESSAGE_ID || null,
3319
- options,
3320
- prompt,
3321
- timeoutMs,
3322
- approvers: approverArgs,
3323
- };
3324
- let result;
3325
- try {
3326
- result = await postAsk(body);
3327
- }
3328
- catch (err) {
3329
- const code = err.exitCode ?? 3;
3330
- console.error(err.message);
3331
- process.exit(code);
3332
- }
3333
- // result.kind==='answered' 时用 toLegacySelected 取回旧的 string(单问单选)
3334
- const selected = toLegacySelected(result);
3335
- if (useJson) {
3336
- const out = {
3337
- selected,
3338
- answers: result.kind === 'answered' ? result.answers : null,
3339
- by: result.kind === 'answered' ? result.by : null,
3340
- comment: null,
3341
- timedOut: result.kind === 'timedOut',
3342
- };
3343
- process.stdout.write(JSON.stringify(out) + '\n');
3344
- }
3345
- else if (result.kind === 'answered') {
3346
- // 非 JSON 模式:输出 selected key(单问单选),多选/多问输出空字符串
3347
- process.stdout.write((selected ?? '') + '\n');
3348
- }
3349
- switch (result.kind) {
3350
- case 'answered':
3351
- process.exit(0);
3352
- case 'timedOut':
3353
- console.error(`botmux ask: 超时(${timeoutMs / 1000}s),无回复`);
3354
- process.exit(124);
3355
- case 'invalidated':
3356
- console.error(`botmux ask: 已失效 (${result.reason})`);
3357
- process.exit(3);
3358
- }
3359
- }
3360
- // ─── botmux hook <cliId> ──────────────────────────────────────────────────────
3361
- //
3362
- // hook 模式:各 CLI hook 配置调用 `botmux hook <cliId>`,stdin 注入 hook payload,
3363
- // 本命令解析问题 → POST /api/asks → 等结果 → 写 directive 到 stdout。
3364
- // 任何失败(daemon 不可达、env 缺失、解析错误)均输出 passthrough directive 并 exit 0,
3365
- // 绝不挂死,保证 CLI 可以继续原生终端提问。
3366
- /**
3367
- * runHook: hook 命令的纯业务逻辑,接受已解析的 payload/env/postAskFn,
3368
- * 返回应写到 stdout 的字符串。通过依赖注入使单元测试无需真实 daemon/env。
3369
- *
3370
- * @param payload 已经 JSON.parse 的 hook payload 对象
3371
- * @param env 包含 BOTMUX_* 环境变量的字典
3372
- * @param postAskFn 替代真实 postAsk 的可注入函数(测试用)
3373
- * @param cliId CLI 适配器 ID
3374
- * @param resolveAdoptRouteFn 可选:替代真实 adopt 路由解析的注入函数(测试用);
3375
- * 缺省时使用真实 resolveAdoptRoute(查祖先 PID → daemon)
3376
- * @returns { stdout: string } 应写到 stdout 的内容
3377
- */
3378
- export async function runHook(payload, env, postAskFn, cliId, resolveAdoptRouteFn) {
3379
- const { getHookAdapter } = await import('./core/ask-hook/registry.js');
3380
- // 未知 cliId → 无 adapter,输出空字符串静默放行
3381
- const adapter = getHookAdapter(cliId);
3382
- if (!adapter) {
3383
- return { stdout: '' };
3384
- }
3385
- // Workflow-subagent 安全门:workflow 子 agent 内直接 passthrough
3386
- if (env.BOTMUX_WORKFLOW === '1') {
3387
- return { stdout: adapter.passthrough(payload) };
3388
- }
3389
- // 解析问题:非 askUserQuestion 类事件 → passthrough 放行
3390
- const parsed = adapter.parseQuestions(payload);
3391
- if (!parsed) {
3392
- return { stdout: adapter.passthrough(payload) };
3393
- }
3394
- // 检查必需的 BOTMUX_* env
3395
- const sessionId = env.BOTMUX_SESSION_ID;
3396
- const chatId = env.BOTMUX_CHAT_ID;
3397
- const larkAppId = env.BOTMUX_LARK_APP_ID;
3398
- // 路由变量:优先用 env,env 缺失时尝试 adopt 路由
3399
- let routeSessionId = sessionId;
3400
- let routeChatId = chatId;
3401
- let routeLarkAppId = larkAppId;
3402
- let routeRoot = env.BOTMUX_ROOT_MESSAGE_ID || null;
3403
- if (!sessionId || !chatId || !larkAppId) {
3404
- // env 缺失 → 尝试通过祖先 PID 匹配在线 adopt 会话
3405
- const resolver = resolveAdoptRouteFn ?? (() => {
3406
- // 延迟 import 避免冷启动开销
3407
- return import('./adapters/adopt-route.js').then(({ resolveAdoptRoute, queryAdoptSession }) => resolveAdoptRoute({
3408
- startPid: process.pid,
3409
- listDaemons: listOnlineDaemons,
3410
- queryDaemon: queryAdoptSession,
3411
- }));
3412
- });
3413
- let adopt = null;
3414
- try {
3415
- adopt = await resolver();
3416
- }
3417
- catch {
3418
- // 解析失败 → 视作真非 botmux 会话,passthrough 放行
3419
- }
3420
- if (!adopt) {
3421
- // 真非 botmux 会话 → passthrough 放行
3422
- return { stdout: adapter.passthrough(payload) };
3423
- }
3424
- // adopt 命中 → 使用 adopt 路由信息
3425
- routeSessionId = adopt.sessionId;
3426
- routeChatId = adopt.chatId;
3427
- routeLarkAppId = adopt.larkAppId;
3428
- routeRoot = adopt.rootMessageId;
3429
- }
3430
- // 解析 timeoutMs:默认 1 小时,可由 BOTMUX_ASK_TIMEOUT_MS 覆盖
3431
- const DEFAULT_TIMEOUT_MS = 3_600_000;
3432
- let timeoutMs = DEFAULT_TIMEOUT_MS;
3433
- const timeoutEnv = env.BOTMUX_ASK_TIMEOUT_MS;
3434
- if (timeoutEnv) {
3435
- const parsed_timeout = parseInt(timeoutEnv, 10);
3436
- if (Number.isInteger(parsed_timeout) && parsed_timeout > 0) {
3437
- timeoutMs = parsed_timeout;
3438
- }
3439
- }
3440
- const body = {
3441
- sessionId: routeSessionId,
3442
- chatId: routeChatId,
3443
- larkAppId: routeLarkAppId,
3444
- rootMessageId: routeRoot,
3445
- questions: parsed.questions,
3446
- timeoutMs,
3447
- approvers: [],
3448
- };
3449
- let result;
3450
- try {
3451
- result = await postAskFn(body);
3452
- }
3453
- catch {
3454
- // 任何失败(daemon 不可达、HTTP 错误等)→ passthrough 放行
3455
- return { stdout: adapter.passthrough(payload) };
3456
- }
3457
- if (result.kind === 'answered') {
3458
- return { stdout: adapter.formatAnswer(result.answers, parsed) };
3459
- }
3460
- // timedOut / invalidated → passthrough 放行
3461
- return { stdout: adapter.passthrough(payload) };
3462
- }
3463
- /**
3464
- * cmdHook: `botmux hook <cliId>` 入口。
3465
- * 读取 stdin 全文 → JSON.parse → runHook → 写 stdout,exit 0。
3466
- */
3467
- async function cmdHook(cliId) {
3468
- // 读取 stdin 全文
3469
- let stdinText = '';
3470
- try {
3471
- const chunks = [];
3472
- for await (const chunk of process.stdin) {
3473
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
3474
- }
3475
- stdinText = Buffer.concat(chunks).toString('utf-8');
3476
- }
3477
- catch {
3478
- // stdin 读取失败 → 无法处理,静默退出
3479
- process.exit(0);
3480
- }
3481
- // JSON.parse 失败 → 输出空并退出(不挂死)
3482
- let payload;
3483
- try {
3484
- payload = JSON.parse(stdinText);
3485
- }
3486
- catch {
3487
- process.exit(0);
3488
- }
3489
- const { getHookAdapter } = await import('./core/ask-hook/registry.js');
3490
- const adapter = getHookAdapter(cliId);
3491
- // 未知 cliId → 静默放行
3492
- if (!adapter) {
3493
- process.exit(0);
3494
- }
3495
- const env = process.env;
3496
- const result = await runHook(payload, env, postAsk, cliId);
3497
- if (result.stdout) {
3498
- console.log(result.stdout);
3499
- }
3500
- process.exit(0);
3501
- }
3502
3019
  async function cmdBots(sub, rest) {
3503
3020
  process.env.SESSION_DATA_DIR ??= resolveDataDir();
3504
3021
  if (sub !== 'list' && sub !== 'ls' && sub !== '') {
@@ -3727,20 +3244,6 @@ switch (command) {
3727
3244
  case 'schedule':
3728
3245
  await cmdSchedule(process.argv[3] ?? '', process.argv.slice(4));
3729
3246
  break;
3730
- case 'ask': {
3731
- // `botmux ask buttons --options ...` → sub='buttons', rest=['--options', ...]
3732
- // `botmux ask --options ...` → sub='', rest=['--options', ...] (bare alias)
3733
- const { normalizeAskDispatch } = await import('./core/ask-args.js');
3734
- const { sub, rest } = normalizeAskDispatch(process.argv.slice(3));
3735
- await cmdAsk(sub, rest);
3736
- break;
3737
- }
3738
- case 'hook': {
3739
- // `botmux hook <cliId>` — hook 客户端,stdin 读 payload,stdout 写 directive
3740
- const cliId = process.argv[3] ?? '';
3741
- await cmdHook(cliId);
3742
- break;
3743
- }
3744
3247
  case 'workflow': {
3745
3248
  const { cmdWorkflow } = await import('./cli/workflow.js');
3746
3249
  await cmdWorkflow(process.argv[3] ?? '', process.argv.slice(4));