botmux 2.47.0 → 2.47.2

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 (472) hide show
  1. package/README.en.md +10 -5
  2. package/README.md +10 -5
  3. package/dist/adapters/adopt-route.d.ts +63 -0
  4. package/dist/adapters/adopt-route.d.ts.map +1 -0
  5. package/dist/adapters/adopt-route.js +195 -0
  6. package/dist/adapters/adopt-route.js.map +1 -0
  7. package/dist/adapters/backend/tmux-backend.d.ts.map +1 -1
  8. package/dist/adapters/backend/tmux-backend.js +11 -0
  9. package/dist/adapters/backend/tmux-backend.js.map +1 -1
  10. package/dist/adapters/backend/tmux-pipe-backend.d.ts +11 -0
  11. package/dist/adapters/backend/tmux-pipe-backend.d.ts.map +1 -1
  12. package/dist/adapters/backend/tmux-pipe-backend.js +17 -1
  13. package/dist/adapters/backend/tmux-pipe-backend.js.map +1 -1
  14. package/dist/adapters/cli/claude-code.d.ts.map +1 -1
  15. package/dist/adapters/cli/claude-code.js +36 -9
  16. package/dist/adapters/cli/claude-code.js.map +1 -1
  17. package/dist/adapters/cli/coco.d.ts.map +1 -1
  18. package/dist/adapters/cli/coco.js +26 -1
  19. package/dist/adapters/cli/coco.js.map +1 -1
  20. package/dist/adapters/cli/codex-app.d.ts +4 -0
  21. package/dist/adapters/cli/codex-app.d.ts.map +1 -0
  22. package/dist/adapters/cli/codex-app.js +72 -0
  23. package/dist/adapters/cli/codex-app.js.map +1 -0
  24. package/dist/adapters/cli/codex.d.ts.map +1 -1
  25. package/dist/adapters/cli/codex.js +34 -17
  26. package/dist/adapters/cli/codex.js.map +1 -1
  27. package/dist/adapters/cli/cursor.d.ts.map +1 -1
  28. package/dist/adapters/cli/cursor.js +58 -12
  29. package/dist/adapters/cli/cursor.js.map +1 -1
  30. package/dist/adapters/cli/gemini.d.ts.map +1 -1
  31. package/dist/adapters/cli/gemini.js +5 -1
  32. package/dist/adapters/cli/gemini.js.map +1 -1
  33. package/dist/adapters/cli/hermes.d.ts +4 -0
  34. package/dist/adapters/cli/hermes.d.ts.map +1 -0
  35. package/dist/adapters/cli/hermes.js +40 -0
  36. package/dist/adapters/cli/hermes.js.map +1 -0
  37. package/dist/adapters/cli/mira.d.ts +4 -0
  38. package/dist/adapters/cli/mira.d.ts.map +1 -0
  39. package/dist/adapters/cli/mira.js +67 -0
  40. package/dist/adapters/cli/mira.js.map +1 -0
  41. package/dist/adapters/cli/mtr.d.ts +5 -0
  42. package/dist/adapters/cli/mtr.d.ts.map +1 -0
  43. package/dist/adapters/cli/mtr.js +62 -0
  44. package/dist/adapters/cli/mtr.js.map +1 -0
  45. package/dist/adapters/cli/opencode.d.ts.map +1 -1
  46. package/dist/adapters/cli/opencode.js +19 -1
  47. package/dist/adapters/cli/opencode.js.map +1 -1
  48. package/dist/adapters/cli/registry.d.ts +5 -1
  49. package/dist/adapters/cli/registry.d.ts.map +1 -1
  50. package/dist/adapters/cli/registry.js +22 -2
  51. package/dist/adapters/cli/registry.js.map +1 -1
  52. package/dist/adapters/cli/shared-hints.d.ts +1 -1
  53. package/dist/adapters/cli/shared-hints.d.ts.map +1 -1
  54. package/dist/adapters/cli/shared-hints.js +2 -1
  55. package/dist/adapters/cli/shared-hints.js.map +1 -1
  56. package/dist/adapters/cli/types.d.ts +35 -2
  57. package/dist/adapters/cli/types.d.ts.map +1 -1
  58. package/dist/adapters/hook-command.d.ts +18 -0
  59. package/dist/adapters/hook-command.d.ts.map +1 -0
  60. package/dist/adapters/hook-command.js +38 -0
  61. package/dist/adapters/hook-command.js.map +1 -0
  62. package/dist/adapters/hook-installer.d.ts +14 -0
  63. package/dist/adapters/hook-installer.d.ts.map +1 -0
  64. package/dist/adapters/hook-installer.js +192 -0
  65. package/dist/adapters/hook-installer.js.map +1 -0
  66. package/dist/bot-registry.d.ts +59 -0
  67. package/dist/bot-registry.d.ts.map +1 -1
  68. package/dist/bot-registry.js +67 -0
  69. package/dist/bot-registry.js.map +1 -1
  70. package/dist/cli/bots-list-output.d.ts +8 -0
  71. package/dist/cli/bots-list-output.d.ts.map +1 -1
  72. package/dist/cli/bots-list-output.js +9 -0
  73. package/dist/cli/bots-list-output.js.map +1 -1
  74. package/dist/cli.d.ts +15 -1
  75. package/dist/cli.d.ts.map +1 -1
  76. package/dist/cli.js +603 -106
  77. package/dist/cli.js.map +1 -1
  78. package/dist/codex-app-runner.d.ts +3 -0
  79. package/dist/codex-app-runner.d.ts.map +1 -0
  80. package/dist/codex-app-runner.js +512 -0
  81. package/dist/codex-app-runner.js.map +1 -0
  82. package/dist/config.d.ts +11 -2
  83. package/dist/config.d.ts.map +1 -1
  84. package/dist/config.js +17 -4
  85. package/dist/config.js.map +1 -1
  86. package/dist/core/ask-api.d.ts +47 -0
  87. package/dist/core/ask-api.d.ts.map +1 -0
  88. package/dist/core/ask-api.js +139 -0
  89. package/dist/core/ask-api.js.map +1 -0
  90. package/dist/core/ask-args.d.ts +53 -0
  91. package/dist/core/ask-args.d.ts.map +1 -0
  92. package/dist/core/ask-args.js +122 -0
  93. package/dist/core/ask-args.js.map +1 -0
  94. package/dist/core/ask-broker.d.ts +98 -0
  95. package/dist/core/ask-broker.d.ts.map +1 -0
  96. package/dist/core/ask-broker.js +329 -0
  97. package/dist/core/ask-broker.js.map +1 -0
  98. package/dist/core/ask-hook/claude-code.d.ts +50 -0
  99. package/dist/core/ask-hook/claude-code.d.ts.map +1 -0
  100. package/dist/core/ask-hook/claude-code.js +145 -0
  101. package/dist/core/ask-hook/claude-code.js.map +1 -0
  102. package/dist/core/ask-hook/codex.d.ts +43 -0
  103. package/dist/core/ask-hook/codex.d.ts.map +1 -0
  104. package/dist/core/ask-hook/codex.js +69 -0
  105. package/dist/core/ask-hook/codex.js.map +1 -0
  106. package/dist/core/ask-hook/opencode.d.ts +41 -0
  107. package/dist/core/ask-hook/opencode.d.ts.map +1 -0
  108. package/dist/core/ask-hook/opencode.js +108 -0
  109. package/dist/core/ask-hook/opencode.js.map +1 -0
  110. package/dist/core/ask-hook/registry.d.ts +3 -0
  111. package/dist/core/ask-hook/registry.d.ts.map +1 -0
  112. package/dist/core/ask-hook/registry.js +12 -0
  113. package/dist/core/ask-hook/registry.js.map +1 -0
  114. package/dist/core/ask-hook/types.d.ts +26 -0
  115. package/dist/core/ask-hook/types.d.ts.map +1 -0
  116. package/dist/core/ask-hook/types.js +2 -0
  117. package/dist/core/ask-hook/types.js.map +1 -0
  118. package/dist/core/ask-types.d.ts +146 -0
  119. package/dist/core/ask-types.d.ts.map +1 -0
  120. package/dist/core/ask-types.js +18 -0
  121. package/dist/core/ask-types.js.map +1 -0
  122. package/dist/core/command-handler.d.ts +29 -0
  123. package/dist/core/command-handler.d.ts.map +1 -1
  124. package/dist/core/command-handler.js +787 -312
  125. package/dist/core/command-handler.js.map +1 -1
  126. package/dist/core/dashboard-ipc-server.d.ts +2 -0
  127. package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
  128. package/dist/core/dashboard-ipc-server.js +222 -2
  129. package/dist/core/dashboard-ipc-server.js.map +1 -1
  130. package/dist/core/role-resolver.d.ts +17 -1
  131. package/dist/core/role-resolver.d.ts.map +1 -1
  132. package/dist/core/role-resolver.js +64 -10
  133. package/dist/core/role-resolver.js.map +1 -1
  134. package/dist/core/session-discovery.d.ts.map +1 -1
  135. package/dist/core/session-discovery.js +19 -5
  136. package/dist/core/session-discovery.js.map +1 -1
  137. package/dist/core/session-manager.d.ts +1 -1
  138. package/dist/core/session-manager.d.ts.map +1 -1
  139. package/dist/core/session-manager.js +37 -20
  140. package/dist/core/session-manager.js.map +1 -1
  141. package/dist/core/trigger-session.d.ts +9 -0
  142. package/dist/core/trigger-session.d.ts.map +1 -0
  143. package/dist/core/trigger-session.js +158 -0
  144. package/dist/core/trigger-session.js.map +1 -0
  145. package/dist/core/types.d.ts +5 -0
  146. package/dist/core/types.d.ts.map +1 -1
  147. package/dist/core/types.js.map +1 -1
  148. package/dist/core/worker-pool.d.ts +141 -0
  149. package/dist/core/worker-pool.d.ts.map +1 -1
  150. package/dist/core/worker-pool.js +543 -24
  151. package/dist/core/worker-pool.js.map +1 -1
  152. package/dist/daemon.d.ts.map +1 -1
  153. package/dist/daemon.js +224 -60
  154. package/dist/daemon.js.map +1 -1
  155. package/dist/dashboard/auth.d.ts +6 -1
  156. package/dist/dashboard/auth.d.ts.map +1 -1
  157. package/dist/dashboard/auth.js +9 -1
  158. package/dist/dashboard/auth.js.map +1 -1
  159. package/dist/dashboard/connector-api.d.ts +3 -0
  160. package/dist/dashboard/connector-api.d.ts.map +1 -0
  161. package/dist/dashboard/connector-api.js +351 -0
  162. package/dist/dashboard/connector-api.js.map +1 -0
  163. package/dist/dashboard/federated-group-core.d.ts +54 -0
  164. package/dist/dashboard/federated-group-core.d.ts.map +1 -0
  165. package/dist/dashboard/federated-group-core.js +165 -0
  166. package/dist/dashboard/federated-group-core.js.map +1 -0
  167. package/dist/dashboard/federation-api.d.ts +42 -0
  168. package/dist/dashboard/federation-api.d.ts.map +1 -0
  169. package/dist/dashboard/federation-api.js +408 -0
  170. package/dist/dashboard/federation-api.js.map +1 -0
  171. package/dist/dashboard/federation-spoke-api.d.ts +76 -0
  172. package/dist/dashboard/federation-spoke-api.d.ts.map +1 -0
  173. package/dist/dashboard/federation-spoke-api.js +618 -0
  174. package/dist/dashboard/federation-spoke-api.js.map +1 -0
  175. package/dist/dashboard/team-group.d.ts +18 -0
  176. package/dist/dashboard/team-group.d.ts.map +1 -0
  177. package/dist/dashboard/team-group.js +7 -0
  178. package/dist/dashboard/team-group.js.map +1 -0
  179. package/dist/dashboard/trigger-api.d.ts +13 -0
  180. package/dist/dashboard/trigger-api.d.ts.map +1 -0
  181. package/dist/dashboard/trigger-api.js +77 -0
  182. package/dist/dashboard/trigger-api.js.map +1 -0
  183. package/dist/dashboard/web/app.js +8 -0
  184. package/dist/dashboard/web/app.js.map +1 -1
  185. package/dist/dashboard/web/bot-defaults.d.ts.map +1 -1
  186. package/dist/dashboard/web/bot-defaults.js +205 -21
  187. package/dist/dashboard/web/bot-defaults.js.map +1 -1
  188. package/dist/dashboard/web/connectors.d.ts +2 -0
  189. package/dist/dashboard/web/connectors.d.ts.map +1 -0
  190. package/dist/dashboard/web/connectors.js +187 -0
  191. package/dist/dashboard/web/connectors.js.map +1 -0
  192. package/dist/dashboard/web/i18n.d.ts.map +1 -1
  193. package/dist/dashboard/web/i18n.js +43 -5
  194. package/dist/dashboard/web/i18n.js.map +1 -1
  195. package/dist/dashboard/web/sessions.d.ts.map +1 -1
  196. package/dist/dashboard/web/sessions.js +4 -0
  197. package/dist/dashboard/web/sessions.js.map +1 -1
  198. package/dist/dashboard/web/team-federation.d.ts +3 -0
  199. package/dist/dashboard/web/team-federation.d.ts.map +1 -0
  200. package/dist/dashboard/web/team-federation.js +487 -0
  201. package/dist/dashboard/web/team-federation.js.map +1 -0
  202. package/dist/dashboard/web/workflows.js +3 -3
  203. package/dist/dashboard/web/workflows.js.map +1 -1
  204. package/dist/dashboard/webhook-routes.d.ts +19 -0
  205. package/dist/dashboard/webhook-routes.d.ts.map +1 -0
  206. package/dist/dashboard/webhook-routes.js +321 -0
  207. package/dist/dashboard/webhook-routes.js.map +1 -0
  208. package/dist/dashboard/workflow-api.d.ts +8 -1
  209. package/dist/dashboard/workflow-api.d.ts.map +1 -1
  210. package/dist/dashboard/workflow-api.js +19 -4
  211. package/dist/dashboard/workflow-api.js.map +1 -1
  212. package/dist/dashboard-web/app.js +539 -375
  213. package/dist/dashboard-web/index.html +3 -1
  214. package/dist/dashboard-web/style.css +22 -0
  215. package/dist/dashboard.js +199 -2
  216. package/dist/dashboard.js.map +1 -1
  217. package/dist/i18n/en.d.ts.map +1 -1
  218. package/dist/i18n/en.js +104 -11
  219. package/dist/i18n/en.js.map +1 -1
  220. package/dist/i18n/zh.d.ts.map +1 -1
  221. package/dist/i18n/zh.js +104 -11
  222. package/dist/i18n/zh.js.map +1 -1
  223. package/dist/im/lark/ask-card.d.ts +55 -0
  224. package/dist/im/lark/ask-card.d.ts.map +1 -0
  225. package/dist/im/lark/ask-card.js +328 -0
  226. package/dist/im/lark/ask-card.js.map +1 -0
  227. package/dist/im/lark/card-builder.d.ts +108 -3
  228. package/dist/im/lark/card-builder.d.ts.map +1 -1
  229. package/dist/im/lark/card-builder.js +480 -50
  230. package/dist/im/lark/card-builder.js.map +1 -1
  231. package/dist/im/lark/card-handler.d.ts.map +1 -1
  232. package/dist/im/lark/card-handler.js +241 -18
  233. package/dist/im/lark/card-handler.js.map +1 -1
  234. package/dist/im/lark/client.d.ts +83 -0
  235. package/dist/im/lark/client.d.ts.map +1 -1
  236. package/dist/im/lark/client.js +286 -70
  237. package/dist/im/lark/client.js.map +1 -1
  238. package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
  239. package/dist/im/lark/event-dispatcher.js +29 -4
  240. package/dist/im/lark/event-dispatcher.js.map +1 -1
  241. package/dist/im/lark/grant-command.d.ts +2 -1
  242. package/dist/im/lark/grant-command.d.ts.map +1 -1
  243. package/dist/im/lark/grant-command.js +3 -2
  244. package/dist/im/lark/grant-command.js.map +1 -1
  245. package/dist/im/lark/identity-cache.d.ts.map +1 -1
  246. package/dist/im/lark/identity-cache.js +3 -3
  247. package/dist/im/lark/identity-cache.js.map +1 -1
  248. package/dist/im/lark/md-card.d.ts +20 -2
  249. package/dist/im/lark/md-card.d.ts.map +1 -1
  250. package/dist/im/lark/md-card.js +49 -17
  251. package/dist/im/lark/md-card.js.map +1 -1
  252. package/dist/im/lark/message-parser.d.ts.map +1 -1
  253. package/dist/im/lark/message-parser.js +87 -31
  254. package/dist/im/lark/message-parser.js.map +1 -1
  255. package/dist/im/lark/workflow-card-handler.d.ts +2 -2
  256. package/dist/im/lark/workflow-card-handler.d.ts.map +1 -1
  257. package/dist/im/lark/workflow-card-handler.js +12 -1
  258. package/dist/im/lark/workflow-card-handler.js.map +1 -1
  259. package/dist/im/lark/workflow-progress-card.d.ts.map +1 -1
  260. package/dist/im/lark/workflow-progress-card.js +53 -0
  261. package/dist/im/lark/workflow-progress-card.js.map +1 -1
  262. package/dist/mira-output.d.ts +3 -0
  263. package/dist/mira-output.d.ts.map +1 -0
  264. package/dist/mira-output.js +136 -0
  265. package/dist/mira-output.js.map +1 -0
  266. package/dist/mira-runner.d.ts +3 -0
  267. package/dist/mira-runner.d.ts.map +1 -0
  268. package/dist/mira-runner.js +534 -0
  269. package/dist/mira-runner.js.map +1 -0
  270. package/dist/services/bot-owner-store.d.ts +28 -0
  271. package/dist/services/bot-owner-store.d.ts.map +1 -0
  272. package/dist/services/bot-owner-store.js +82 -0
  273. package/dist/services/bot-owner-store.js.map +1 -0
  274. package/dist/services/bot-profile-store.d.ts +16 -0
  275. package/dist/services/bot-profile-store.d.ts.map +1 -0
  276. package/dist/services/bot-profile-store.js +98 -0
  277. package/dist/services/bot-profile-store.js.map +1 -0
  278. package/dist/services/brand-store.d.ts +15 -0
  279. package/dist/services/brand-store.d.ts.map +1 -0
  280. package/dist/services/brand-store.js +47 -0
  281. package/dist/services/brand-store.js.map +1 -0
  282. package/dist/services/card-prefs-store.d.ts +20 -0
  283. package/dist/services/card-prefs-store.d.ts.map +1 -0
  284. package/dist/services/card-prefs-store.js +82 -0
  285. package/dist/services/card-prefs-store.js.map +1 -0
  286. package/dist/services/codex-bridge-queue.d.ts +1 -0
  287. package/dist/services/codex-bridge-queue.d.ts.map +1 -1
  288. package/dist/services/codex-bridge-queue.js +23 -0
  289. package/dist/services/codex-bridge-queue.js.map +1 -1
  290. package/dist/services/codex-transcript.d.ts +1 -0
  291. package/dist/services/codex-transcript.d.ts.map +1 -1
  292. package/dist/services/codex-transcript.js.map +1 -1
  293. package/dist/services/connector-store.d.ts +58 -0
  294. package/dist/services/connector-store.d.ts.map +1 -0
  295. package/dist/services/connector-store.js +79 -0
  296. package/dist/services/connector-store.js.map +1 -0
  297. package/dist/services/deployment-identity.d.ts +22 -0
  298. package/dist/services/deployment-identity.d.ts.map +1 -0
  299. package/dist/services/deployment-identity.js +67 -0
  300. package/dist/services/deployment-identity.js.map +1 -0
  301. package/dist/services/federation-membership-store.d.ts +23 -0
  302. package/dist/services/federation-membership-store.d.ts.map +1 -0
  303. package/dist/services/federation-membership-store.js +66 -0
  304. package/dist/services/federation-membership-store.js.map +1 -0
  305. package/dist/services/federation-roster.d.ts +54 -0
  306. package/dist/services/federation-roster.d.ts.map +1 -0
  307. package/dist/services/federation-roster.js +51 -0
  308. package/dist/services/federation-roster.js.map +1 -0
  309. package/dist/services/federation-store.d.ts +76 -0
  310. package/dist/services/federation-store.d.ts.map +1 -0
  311. package/dist/services/federation-store.js +133 -0
  312. package/dist/services/federation-store.js.map +1 -0
  313. package/dist/services/grant-store.d.ts +12 -2
  314. package/dist/services/grant-store.d.ts.map +1 -1
  315. package/dist/services/grant-store.js +51 -4
  316. package/dist/services/grant-store.js.map +1 -1
  317. package/dist/services/group-creator.d.ts +10 -0
  318. package/dist/services/group-creator.d.ts.map +1 -1
  319. package/dist/services/group-creator.js +26 -1
  320. package/dist/services/group-creator.js.map +1 -1
  321. package/dist/services/groups-store.d.ts +30 -0
  322. package/dist/services/groups-store.d.ts.map +1 -1
  323. package/dist/services/groups-store.js +85 -12
  324. package/dist/services/groups-store.js.map +1 -1
  325. package/dist/services/hermes-transcript.d.ts +7 -0
  326. package/dist/services/hermes-transcript.d.ts.map +1 -0
  327. package/dist/services/hermes-transcript.js +117 -0
  328. package/dist/services/hermes-transcript.js.map +1 -0
  329. package/dist/services/invite-store.d.ts +28 -0
  330. package/dist/services/invite-store.d.ts.map +1 -0
  331. package/dist/services/invite-store.js +85 -0
  332. package/dist/services/invite-store.js.map +1 -0
  333. package/dist/services/pairing-store.d.ts +47 -0
  334. package/dist/services/pairing-store.d.ts.map +1 -0
  335. package/dist/services/pairing-store.js +132 -0
  336. package/dist/services/pairing-store.js.map +1 -0
  337. package/dist/services/project-scanner.d.ts +10 -0
  338. package/dist/services/project-scanner.d.ts.map +1 -1
  339. package/dist/services/project-scanner.js +11 -0
  340. package/dist/services/project-scanner.js.map +1 -1
  341. package/dist/services/relay-picker.d.ts +22 -0
  342. package/dist/services/relay-picker.d.ts.map +1 -0
  343. package/dist/services/relay-picker.js +62 -0
  344. package/dist/services/relay-picker.js.map +1 -0
  345. package/dist/services/send-policy.d.ts +55 -0
  346. package/dist/services/send-policy.d.ts.map +1 -0
  347. package/dist/services/send-policy.js +47 -0
  348. package/dist/services/send-policy.js.map +1 -0
  349. package/dist/services/session-store.js +1 -1
  350. package/dist/services/session-store.js.map +1 -1
  351. package/dist/services/team-roster.d.ts +38 -0
  352. package/dist/services/team-roster.d.ts.map +1 -0
  353. package/dist/services/team-roster.js +82 -0
  354. package/dist/services/team-roster.js.map +1 -0
  355. package/dist/services/team-store.d.ts +54 -0
  356. package/dist/services/team-store.d.ts.map +1 -0
  357. package/dist/services/team-store.js +156 -0
  358. package/dist/services/team-store.js.map +1 -0
  359. package/dist/services/trigger-log-store.d.ts +46 -0
  360. package/dist/services/trigger-log-store.d.ts.map +1 -0
  361. package/dist/services/trigger-log-store.js +132 -0
  362. package/dist/services/trigger-log-store.js.map +1 -0
  363. package/dist/services/trigger-types.d.ts +57 -0
  364. package/dist/services/trigger-types.d.ts.map +1 -0
  365. package/dist/services/trigger-types.js +28 -0
  366. package/dist/services/trigger-types.js.map +1 -0
  367. package/dist/services/webhook-key.d.ts +16 -0
  368. package/dist/services/webhook-key.d.ts.map +1 -0
  369. package/dist/services/webhook-key.js +123 -0
  370. package/dist/services/webhook-key.js.map +1 -0
  371. package/dist/services/webhook-lifecycle-extractors.d.ts +15 -0
  372. package/dist/services/webhook-lifecycle-extractors.d.ts.map +1 -0
  373. package/dist/services/webhook-lifecycle-extractors.js +59 -0
  374. package/dist/services/webhook-lifecycle-extractors.js.map +1 -0
  375. package/dist/services/webhook-lifecycle-store.d.ts +45 -0
  376. package/dist/services/webhook-lifecycle-store.d.ts.map +1 -0
  377. package/dist/services/webhook-lifecycle-store.js +159 -0
  378. package/dist/services/webhook-lifecycle-store.js.map +1 -0
  379. package/dist/setup/bot-config-editor.d.ts +8 -1
  380. package/dist/setup/bot-config-editor.d.ts.map +1 -1
  381. package/dist/setup/bot-config-editor.js +20 -2
  382. package/dist/setup/bot-config-editor.js.map +1 -1
  383. package/dist/setup/ensure-tmux.d.ts +0 -22
  384. package/dist/setup/ensure-tmux.d.ts.map +1 -1
  385. package/dist/setup/ensure-tmux.js +25 -1
  386. package/dist/setup/ensure-tmux.js.map +1 -1
  387. package/dist/setup/verify-permissions.d.ts.map +1 -1
  388. package/dist/setup/verify-permissions.js +15 -1
  389. package/dist/setup/verify-permissions.js.map +1 -1
  390. package/dist/skills/definitions.d.ts +2 -0
  391. package/dist/skills/definitions.d.ts.map +1 -1
  392. package/dist/skills/definitions.js +178 -12
  393. package/dist/skills/definitions.js.map +1 -1
  394. package/dist/skills/installer.d.ts +34 -0
  395. package/dist/skills/installer.d.ts.map +1 -1
  396. package/dist/skills/installer.js +119 -2
  397. package/dist/skills/installer.js.map +1 -1
  398. package/dist/types.d.ts +29 -0
  399. package/dist/types.d.ts.map +1 -1
  400. package/dist/utils/bot-routing.d.ts +50 -0
  401. package/dist/utils/bot-routing.d.ts.map +1 -1
  402. package/dist/utils/bot-routing.js +83 -0
  403. package/dist/utils/bot-routing.js.map +1 -1
  404. package/dist/utils/daemon-discovery.d.ts +11 -0
  405. package/dist/utils/daemon-discovery.d.ts.map +1 -0
  406. package/dist/utils/daemon-discovery.js +59 -0
  407. package/dist/utils/daemon-discovery.js.map +1 -0
  408. package/dist/utils/user-token.d.ts.map +1 -1
  409. package/dist/utils/user-token.js +0 -2
  410. package/dist/utils/user-token.js.map +1 -1
  411. package/dist/worker.js +233 -51
  412. package/dist/worker.js.map +1 -1
  413. package/dist/workflows/attempt-resume.d.ts.map +1 -1
  414. package/dist/workflows/attempt-resume.js +2 -2
  415. package/dist/workflows/attempt-resume.js.map +1 -1
  416. package/dist/workflows/definition.d.ts +412 -9
  417. package/dist/workflows/definition.d.ts.map +1 -1
  418. package/dist/workflows/definition.js +238 -3
  419. package/dist/workflows/definition.js.map +1 -1
  420. package/dist/workflows/events/payloads.d.ts +114 -11
  421. package/dist/workflows/events/payloads.d.ts.map +1 -1
  422. package/dist/workflows/events/payloads.js +46 -0
  423. package/dist/workflows/events/payloads.js.map +1 -1
  424. package/dist/workflows/events/replay.d.ts +21 -0
  425. package/dist/workflows/events/replay.d.ts.map +1 -1
  426. package/dist/workflows/events/replay.js +103 -0
  427. package/dist/workflows/events/replay.js.map +1 -1
  428. package/dist/workflows/events/schema.d.ts +1301 -606
  429. package/dist/workflows/events/schema.d.ts.map +1 -1
  430. package/dist/workflows/events/schema.js +37 -1
  431. package/dist/workflows/events/schema.js.map +1 -1
  432. package/dist/workflows/events/types.d.ts +5 -1
  433. package/dist/workflows/events/types.d.ts.map +1 -1
  434. package/dist/workflows/loader.d.ts +14 -0
  435. package/dist/workflows/loader.d.ts.map +1 -1
  436. package/dist/workflows/loader.js +27 -0
  437. package/dist/workflows/loader.js.map +1 -1
  438. package/dist/workflows/loop.js +58 -0
  439. package/dist/workflows/loop.js.map +1 -1
  440. package/dist/workflows/ops-projection.d.ts +58 -0
  441. package/dist/workflows/ops-projection.d.ts.map +1 -1
  442. package/dist/workflows/ops-projection.js +74 -0
  443. package/dist/workflows/ops-projection.js.map +1 -1
  444. package/dist/workflows/orchestrator.d.ts +65 -1
  445. package/dist/workflows/orchestrator.d.ts.map +1 -1
  446. package/dist/workflows/orchestrator.js +486 -74
  447. package/dist/workflows/orchestrator.js.map +1 -1
  448. package/dist/workflows/output-binding.d.ts +8 -1
  449. package/dist/workflows/output-binding.d.ts.map +1 -1
  450. package/dist/workflows/output-binding.js +75 -11
  451. package/dist/workflows/output-binding.js.map +1 -1
  452. package/dist/workflows/runtime.d.ts +1 -1
  453. package/dist/workflows/runtime.d.ts.map +1 -1
  454. package/dist/workflows/runtime.js +39 -4
  455. package/dist/workflows/runtime.js.map +1 -1
  456. package/dist/workflows/trigger-from-envelope.d.ts +13 -0
  457. package/dist/workflows/trigger-from-envelope.d.ts.map +1 -0
  458. package/dist/workflows/trigger-from-envelope.js +67 -0
  459. package/dist/workflows/trigger-from-envelope.js.map +1 -0
  460. package/dist/workflows/wait.d.ts +23 -2
  461. package/dist/workflows/wait.d.ts.map +1 -1
  462. package/dist/workflows/wait.js +39 -17
  463. package/dist/workflows/wait.js.map +1 -1
  464. package/package.json +1 -1
  465. package/dist/services/feishu-task-client.d.ts +0 -28
  466. package/dist/services/feishu-task-client.d.ts.map +0 -1
  467. package/dist/services/feishu-task-client.js +0 -123
  468. package/dist/services/feishu-task-client.js.map +0 -1
  469. package/dist/services/task-store.d.ts +0 -37
  470. package/dist/services/task-store.d.ts.map +0 -1
  471. package/dist/services/task-store.js +0 -115
  472. package/dist/services/task-store.js.map +0 -1
@@ -4,10 +4,14 @@ const cliDisplayNames = {
4
4
  'aiden': 'Aiden',
5
5
  'coco': 'CoCo',
6
6
  'codex': 'Codex',
7
+ 'codex-app': 'Codex App',
7
8
  'cursor': 'Cursor',
8
9
  'gemini': 'Gemini',
9
10
  'opencode': 'OpenCode',
10
11
  'antigravity': 'Antigravity',
12
+ 'mtr': 'MTR',
13
+ 'hermes': 'Hermes',
14
+ 'mira': 'Mira',
11
15
  };
12
16
  export function getCliDisplayName(cliId) {
13
17
  return cliDisplayNames[cliId] ?? cliId;
@@ -129,14 +133,70 @@ export function buildSessionClosedCard(sessionId, rootId, title, cliId, workingD
129
133
  };
130
134
  return JSON.stringify(card);
131
135
  }
136
+ /**
137
+ * Build a frozen-snapshot card to PATCH onto the source-chat streaming card
138
+ * after `/relay` moves the session elsewhere.
139
+ *
140
+ * Why this exists: a live streaming card carries action buttons (close /
141
+ * toggle display / get write link). Those buttons identify their session by
142
+ * `session_id` in the value payload, so clicking them after relay still
143
+ * reaches the now-relocated session — closing it, toggling its display
144
+ * mode, etc. — but the visible feedback all lands on the NEW card in the
145
+ * target chat, not this one. The source-chat card then looks like a "live
146
+ * console" while actually being a footgun. PATCH it to an inert snapshot
147
+ * so the user sees clearly it's historical.
148
+ *
149
+ * Last-frame rendering:
150
+ * - imageKey present (session was in 'screenshot' / expanded mode at
151
+ * relay time) → embed the same img element the live card had.
152
+ * img_key is a Lark server resource independent of the card it lived
153
+ * on, so the PATCHed card can still reference it.
154
+ * - imageKey absent (hidden / collapsed mode) → render nothing extra.
155
+ * The header + body notice already convey the state; raw tmux pane
156
+ * text as a code-block is too long and noisy (王皓 caught this in
157
+ * testing).
158
+ *
159
+ * No action buttons are rendered in either case.
160
+ */
161
+ export function buildRelayedFrozenCard(title, cliId, imageKey, locale) {
162
+ const cliName = getCliDisplayName(cliId ?? 'claude-code');
163
+ const body = `**${escapeMd(title || cliName)}**\n` +
164
+ `${t('card.body.relay_frozen', undefined, locale)}`;
165
+ const elements = [
166
+ { tag: 'markdown', content: body },
167
+ ];
168
+ if (imageKey) {
169
+ elements.push({
170
+ tag: 'img',
171
+ img_key: imageKey,
172
+ alt: { tag: 'plain_text', content: '' },
173
+ mode: 'fit_horizontal',
174
+ preview: true,
175
+ });
176
+ }
177
+ const card = {
178
+ config: { wide_screen_mode: true },
179
+ header: {
180
+ title: { tag: 'plain_text', content: t('card.status.relay_frozen', undefined, locale) },
181
+ template: 'grey',
182
+ },
183
+ elements,
184
+ };
185
+ return JSON.stringify(card);
186
+ }
132
187
  /**
133
188
  * Feishu card API rejects payloads exceeding ~109 KB (error 230025).
134
189
  * Cap markdown content byte size with headroom for card JSON overhead.
135
190
  */
136
191
  const MAX_CONTENT_BYTES = 100_000;
137
- /** Truncate content to fit within MAX_CONTENT_BYTES, keeping the tail (most recent output). */
138
- export function truncateContent(content, locale) {
139
- if (Buffer.byteLength(content, 'utf-8') <= MAX_CONTENT_BYTES)
192
+ /**
193
+ * Truncate content to fit within `maxBytes`, keeping the tail (most recent
194
+ * output). Defaults to {@link MAX_CONTENT_BYTES}; callers that wrap the content
195
+ * in additional card JSON (e.g. the private snapshot's code fence) pass a
196
+ * tighter budget so the whole card stays under Feishu's ~109 KB hard limit.
197
+ */
198
+ export function truncateContent(content, locale, maxBytes = MAX_CONTENT_BYTES) {
199
+ if (Buffer.byteLength(content, 'utf-8') <= maxBytes)
140
200
  return content;
141
201
  // Binary search for the longest suffix that fits
142
202
  const lines = content.split('\n');
@@ -145,7 +205,7 @@ export function truncateContent(content, locale) {
145
205
  while (lo < hi) {
146
206
  const mid = Math.floor((lo + hi) / 2);
147
207
  const candidate = lines.slice(mid).join('\n');
148
- if (Buffer.byteLength(candidate, 'utf-8') <= MAX_CONTENT_BYTES - 30) {
208
+ if (Buffer.byteLength(candidate, 'utf-8') <= maxBytes - 30) {
149
209
  hi = mid;
150
210
  }
151
211
  else {
@@ -154,36 +214,29 @@ export function truncateContent(content, locale) {
154
214
  }
155
215
  return `${t('card.status.truncated_prefix', undefined, locale)}\n${lines.slice(lo).join('\n')}`;
156
216
  }
157
- /**
158
- * Build a Feishu streaming card that shows live terminal output + controls.
159
- * This card is PATCHed in-place as the CLI works.
160
- *
161
- * displayMode:
162
- * - 'hidden' — body collapsed; only header + main controls visible.
163
- * - 'screenshot' img element (rendered server-side, uploaded for img_key).
164
- *
165
- * Quick-action buttons (Esc, ^C, Tab, Space, Enter, ←↑↓→, ½屏 ↑/↓) appear
166
- * whenever displayMode !== 'hidden'.
167
- */
168
- export function buildStreamingCard(sessionId, rootId, terminalUrl, title, screenContent, status, cliId, displayMode = 'hidden', cardNonce, imageKey, adoptMode, showTakeover, locale, usageLimit) {
169
- const effectiveCliId = cliId ?? 'claude-code';
170
- const cliName = getCliDisplayName(effectiveCliId);
171
- const actionBase = { root_id: rootId, session_id: sessionId, cli_id: effectiveCliId, ...(cardNonce ? { card_nonce: cardNonce } : {}) };
172
- const displayStatus = status === 'limited' && usageLimit?.retryReady ? 'retry_ready' : status;
173
- const templateMap = { starting: 'yellow', working: 'blue', idle: 'green', analyzing: 'purple', limited: 'red', retry_ready: 'green' };
174
- const statusLabel = (s) => {
175
- switch (s) {
176
- case 'starting': return t('card.status.starting', undefined, locale);
177
- case 'working': return t('card.status.working', undefined, locale);
178
- case 'idle': return t('card.status.idle', undefined, locale);
179
- case 'analyzing': return t('card.status.analyzing', undefined, locale);
180
- case 'limited': return usageLimit?.retryReady
181
- ? t('card.status.retry_ready', undefined, locale)
182
- : t('card.status.limited', undefined, locale);
183
- }
184
- };
185
- const elements = [];
186
- // ── Output body ─────────────────────────────────────────────────────────
217
+ /** Byte budget for the private snapshot's text fallback. Well under the ~109 KB
218
+ * card limit, leaving room for JSON escaping + the card's structural overhead. */
219
+ const PRIVATE_SNAPSHOT_TEXT_MAX = 50_000;
220
+ const STREAM_TEMPLATE_MAP = {
221
+ starting: 'yellow', working: 'blue', idle: 'green', analyzing: 'purple', limited: 'red', retry_ready: 'green',
222
+ };
223
+ /** Header status label for a streaming/snapshot card. Shared by the live card
224
+ * and the private snapshot so the two never drift. */
225
+ function streamStatusLabel(status, usageLimit, locale) {
226
+ switch (status) {
227
+ case 'starting': return t('card.status.starting', undefined, locale);
228
+ case 'working': return t('card.status.working', undefined, locale);
229
+ case 'idle': return t('card.status.idle', undefined, locale);
230
+ case 'analyzing': return t('card.status.analyzing', undefined, locale);
231
+ case 'limited': return usageLimit?.retryReady
232
+ ? t('card.status.retry_ready', undefined, locale)
233
+ : t('card.status.limited', undefined, locale);
234
+ }
235
+ }
236
+ /** Push the shared "output body" elements (usage-limit notice + screenshot) used
237
+ * by both {@link buildStreamingCard} and {@link buildPrivateSnapshotCard}. */
238
+ function pushStreamBody(elements, opts) {
239
+ const { status, usageLimit, displayMode, imageKey, cliName, locale } = opts;
187
240
  if (status === 'limited' && usageLimit) {
188
241
  elements.push({
189
242
  tag: 'markdown',
@@ -195,19 +248,33 @@ export function buildStreamingCard(sessionId, rootId, terminalUrl, title, screen
195
248
  }
196
249
  if (displayMode === 'screenshot') {
197
250
  if (imageKey) {
198
- elements.push({
199
- tag: 'img',
200
- img_key: imageKey,
201
- alt: { tag: 'plain_text', content: '' },
202
- mode: 'fit_horizontal',
203
- preview: true,
204
- });
251
+ elements.push({ tag: 'img', img_key: imageKey, alt: { tag: 'plain_text', content: '' }, mode: 'fit_horizontal', preview: true });
205
252
  }
206
253
  else {
207
254
  elements.push({ tag: 'markdown', content: t('card.status.waiting_screenshot', undefined, locale) });
208
255
  }
209
256
  elements.push({ tag: 'hr' });
210
257
  }
258
+ }
259
+ /**
260
+ * Build a Feishu streaming card that shows live terminal output + controls.
261
+ * This card is PATCHed in-place as the CLI works.
262
+ *
263
+ * displayMode:
264
+ * - 'hidden' — body collapsed; only header + main controls visible.
265
+ * - 'screenshot' — img element (rendered server-side, uploaded for img_key).
266
+ *
267
+ * Quick-action buttons (Esc, ^C, Tab, Space, Enter, ←↑↓→, ½屏 ↑/↓) appear
268
+ * whenever displayMode !== 'hidden'.
269
+ */
270
+ export function buildStreamingCard(sessionId, rootId, terminalUrl, title, screenContent, status, cliId, displayMode = 'hidden', cardNonce, imageKey, adoptMode, showTakeover, locale, usageLimit, writableTerminalUrl) {
271
+ const effectiveCliId = cliId ?? 'claude-code';
272
+ const cliName = getCliDisplayName(effectiveCliId);
273
+ const actionBase = { root_id: rootId, session_id: sessionId, cli_id: effectiveCliId, ...(cardNonce ? { card_nonce: cardNonce } : {}) };
274
+ const displayStatus = status === 'limited' && usageLimit?.retryReady ? 'retry_ready' : status;
275
+ const elements = [];
276
+ // ── Output body (shared with the private snapshot card) ──────────────────
277
+ pushStreamBody(elements, { status, usageLimit, displayMode, imageKey, cliName, locale });
211
278
  // ── Main control row: display toggle, mode toggle, terminal, manage ─────
212
279
  const headerActions = [];
213
280
  headerActions.push({
@@ -277,6 +344,16 @@ export function buildStreamingCard(sessionId, rootId, terminalUrl, title, screen
277
344
  });
278
345
  }
279
346
  elements.push({ tag: 'action', actions: headerActions });
347
+ // ── Writable terminal link (opt-in) ─────────────────────────────────────
348
+ // When the bot enables `writableTerminalLinkInCard`, embed the token-bearing
349
+ // link right in the card so anyone here can open a writable terminal without
350
+ // the get-write-link → DM round-trip. The link is intentionally group-visible.
351
+ if (writableTerminalUrl) {
352
+ elements.push({
353
+ tag: 'markdown',
354
+ content: t('card.writable_terminal_link', { url: writableTerminalUrl }, locale),
355
+ });
356
+ }
280
357
  // ── Quick-action keys (only when the screenshot is visible — in text mode
281
358
  // there's no visible cursor/input, so these keys would fire blindly) ──
282
359
  if (displayMode === 'screenshot') {
@@ -311,8 +388,94 @@ export function buildStreamingCard(sessionId, rootId, terminalUrl, title, screen
311
388
  const card = {
312
389
  config: { wide_screen_mode: true },
313
390
  header: {
314
- title: { tag: 'plain_text', content: `🖥️ ${cliName} · ${escapeMd(title)} — ${statusLabel(status)}` },
315
- template: templateMap[displayStatus],
391
+ title: { tag: 'plain_text', content: `🖥️ ${cliName} · ${escapeMd(title)} — ${streamStatusLabel(status, usageLimit, locale)}` },
392
+ template: STREAM_TEMPLATE_MAP[displayStatus],
393
+ },
394
+ elements,
395
+ };
396
+ return JSON.stringify(card);
397
+ }
398
+ /**
399
+ * Build a static "private snapshot" card for `/card` in private mode — sent via
400
+ * the ephemeral API to one user at a time. Unlike {@link buildStreamingCard} it
401
+ * is **never PATCH-updated** (ephemeral cards can't be), so it carries only a
402
+ * one-shot snapshot of the terminal screenshot plus three buttons:
403
+ * • read-only "open terminal" link (a plain URL button — no callback);
404
+ * • "get write link", whose callback DMs the writable link to the clicker;
405
+ * • "close session", whose callback kills the session and (in private mode)
406
+ * sends the "closed" card ephemeral to the owner audience too — so the
407
+ * session title / CLI name / workingDir on it don't leak to the group.
408
+ * The last two have callbacks but neither patches THIS card (one DMs, the other
409
+ * sends a fresh card), so both work fine on an ephemeral card. Both are
410
+ * `canOperate`-gated in the handler — talk-only viewers who tap them are denied.
411
+ * The patch-driven controls (toggle/refresh/export/term keys) and the inline
412
+ * writable link are still omitted: those need to update this card, which
413
+ * ephemeral cards can't do.
414
+ */
415
+ export function buildPrivateSnapshotCard(terminalUrl, title, status, cliId, imageKey, screenContent, sessionId, rootId, locale, usageLimit) {
416
+ const effectiveCliId = cliId ?? 'claude-code';
417
+ const cliName = getCliDisplayName(effectiveCliId);
418
+ const displayStatus = status === 'limited' && usageLimit?.retryReady ? 'retry_ready' : status;
419
+ // `visibility: 'private'` pins this card's privacy intent onto the action
420
+ // itself, so a later callback (notably `close`) keeps sending ephemeral even
421
+ // if the bot's `privateCard` config is toggled off after the card was sent —
422
+ // otherwise the closed card (session title / workingDir / resume command)
423
+ // could leak to the group. See the `close` handler in card-handler.ts.
424
+ const actionBase = { root_id: rootId, session_id: sessionId, cli_id: effectiveCliId, visibility: 'private' };
425
+ const elements = [];
426
+ // Show the terminal once: prefer the rendered screenshot when present;
427
+ // otherwise fall back to a code-block of the latest screen text so the
428
+ // snapshot isn't empty (common when the bot has the streaming card disabled
429
+ // or display mode never flipped to screenshot — `lastScreenContent` is still
430
+ // kept up to date regardless). pushStreamBody also emits the usage-limit
431
+ // notice, which applies in either case.
432
+ pushStreamBody(elements, {
433
+ status, usageLimit, displayMode: imageKey ? 'screenshot' : 'hidden', imageKey, cliName, locale,
434
+ });
435
+ if (!imageKey) {
436
+ const text = (screenContent ?? '').replace(/[ \t\r\n]+$/, '');
437
+ if (text) {
438
+ const body = truncateContent(text, locale, PRIVATE_SNAPSHOT_TEXT_MAX);
439
+ // Fence must be longer than the longest backtick run in the body, else
440
+ // terminal output containing ``` would break out of the code block.
441
+ const maxRun = (body.match(/`+/g) ?? []).reduce((m, r) => Math.max(m, r.length), 0);
442
+ const fence = '`'.repeat(Math.max(3, maxRun + 1));
443
+ elements.push({ tag: 'markdown', content: `${fence}\n${body}\n${fence}` });
444
+ elements.push({ tag: 'hr' });
445
+ }
446
+ }
447
+ elements.push({
448
+ tag: 'action',
449
+ actions: [
450
+ {
451
+ tag: 'button',
452
+ text: { tag: 'plain_text', content: t('card.btn.open_terminal', undefined, locale) },
453
+ type: 'primary',
454
+ multi_url: { url: terminalUrl, pc_url: terminalUrl, android_url: terminalUrl, ios_url: terminalUrl },
455
+ },
456
+ {
457
+ tag: 'button',
458
+ text: { tag: 'plain_text', content: t('card.btn.get_write_link', undefined, locale) },
459
+ type: 'default',
460
+ value: { action: 'get_write_link', ...actionBase },
461
+ },
462
+ {
463
+ tag: 'button',
464
+ text: { tag: 'plain_text', content: t('card.btn.close_session', undefined, locale) },
465
+ type: 'danger',
466
+ value: { action: 'close', ...actionBase },
467
+ },
468
+ ],
469
+ });
470
+ elements.push({
471
+ tag: 'note',
472
+ elements: [{ tag: 'lark_md', content: t('card.private.snapshot_note', undefined, locale) }],
473
+ });
474
+ const card = {
475
+ config: { wide_screen_mode: true },
476
+ header: {
477
+ title: { tag: 'plain_text', content: `🔒 ${cliName} · ${escapeMd(title)} — ${streamStatusLabel(status, usageLimit, locale)}` },
478
+ template: STREAM_TEMPLATE_MAP[displayStatus],
316
479
  },
317
480
  elements,
318
481
  };
@@ -385,19 +548,22 @@ export function buildGrantCard(o, locale) {
385
548
  ? t('card.grant.body_request', { name: escapeMd(o.requesterName), owner: o.ownerOpenId }, locale)
386
549
  : t('card.grant.body_owner', { name: escapeMd(o.requesterName), owner: o.ownerOpenId }, locale);
387
550
  const v = { target_open_id: o.requesterOpenId, chat_id: o.chatId, nonce: o.nonce };
551
+ // 「全局授权对话」只在 owner 主动发卡时出现:owner 一眼明确要给全局;request 模式(成员
552
+ // 自助申请)只提供「本群」,避免成员把自己申请到全局。两个授权按钮都是 talk-only。
553
+ const grantButtons = [
554
+ { tag: 'button', type: 'primary', text: { tag: 'plain_text', content: t('card.grant.btn_chat', undefined, locale) }, value: { action: 'grant_chat', ...v } },
555
+ ];
556
+ if (o.mode === 'owner') {
557
+ grantButtons.push({ tag: 'button', type: 'default', text: { tag: 'plain_text', content: t('card.grant.btn_global', undefined, locale) }, value: { action: 'grant_global', ...v } });
558
+ }
559
+ grantButtons.push({ tag: 'button', type: 'danger', text: { tag: 'plain_text', content: t('card.grant.btn_deny', undefined, locale) }, value: { action: 'grant_deny', ...v } });
388
560
  const card = {
389
561
  config: { wide_screen_mode: true },
390
562
  header: { template: 'orange', title: { tag: 'plain_text', content: t('card.grant.title', undefined, locale) } },
391
563
  elements: [
392
564
  { tag: 'div', text: { tag: 'lark_md', content: body } },
393
565
  { tag: 'hr' },
394
- {
395
- tag: 'action',
396
- actions: [
397
- { tag: 'button', type: 'primary', text: { tag: 'plain_text', content: t('card.grant.btn_chat', undefined, locale) }, value: { action: 'grant_chat', ...v } },
398
- { tag: 'button', type: 'danger', text: { tag: 'plain_text', content: t('card.grant.btn_deny', undefined, locale) }, value: { action: 'grant_deny', ...v } },
399
- ],
400
- },
566
+ { tag: 'action', actions: grantButtons },
401
567
  { tag: 'note', elements: [{ tag: 'lark_md', content: t('card.grant.note', undefined, locale) }] },
402
568
  ],
403
569
  };
@@ -570,6 +736,270 @@ function formatDuration(ms) {
570
736
  const d = Math.floor(h / 24);
571
737
  return `${d}d${h % 24}h`;
572
738
  }
739
+ function relayPickerTypeTag(mode, locale) {
740
+ switch (mode) {
741
+ case 'p2p': return t('card.relay.type_p2p', undefined, locale);
742
+ case 'topic': return t('card.relay.type_topic', undefined, locale);
743
+ default: return t('card.relay.type_group', undefined, locale); // 'group' or undefined
744
+ }
745
+ }
746
+ const RELAY_PICKER_PAGE_SIZE = 5;
747
+ const RELAY_SEARCH_FIELD = 'search';
748
+ /**
749
+ * Match against title / chatLabel / workingDir / cliId. Case-insensitive
750
+ * substring. Empty / whitespace query matches everything.
751
+ */
752
+ function relayPickerFilter(entries, query) {
753
+ const q = (query ?? '').trim().toLowerCase();
754
+ if (!q)
755
+ return entries;
756
+ return entries.filter((e) => {
757
+ const haystack = [e.title, e.chatLabel, e.workingDir, e.cliId]
758
+ .filter(Boolean)
759
+ .join(' ')
760
+ .toLowerCase();
761
+ return haystack.includes(q);
762
+ });
763
+ }
764
+ /**
765
+ * Card listing the operator's relayable sessions, paginated 5 per page with
766
+ * a search box at the top and a confirm button at the bottom. Layout:
767
+ *
768
+ * ┌──────────────────────────────────────┐
769
+ * │ 📋 选择要接力的会话 │ header
770
+ * ├──────────────────────────────────────┤
771
+ * │ 🔍 [______________] [搜索] │ form: input + submit button
772
+ * ├──────────────────────────────────────┤
773
+ * │ [interactive_container 1] │ current page (≤5 cards),
774
+ * │ [interactive_container 2] │ each clickable for selection
775
+ * │ ... │
776
+ * ├──────────────────────────────────────┤
777
+ * │ [← 上一页] 1 / 4 [下一页 →] │ paginator row
778
+ * ├──────────────────────────────────────┤
779
+ * │ [确认接力到本群] │ primary button (only when
780
+ * │ │ a selected session is on
781
+ * │ │ the current filtered set)
782
+ * └──────────────────────────────────────┘
783
+ *
784
+ * State (search / page / selected) is propagated entirely via the value
785
+ * objects on each button and container — Lark cards are stateless, so any
786
+ * server-side re-render must reconstruct from what the click sent us.
787
+ * That's why every interactive value here carries `search`, `page`,
788
+ * `target_chat_id`, `root_id`.
789
+ *
790
+ * Note: typing into the search box without clicking 搜索 does NOT update
791
+ * the in-callback state — container/paginator clicks use whatever search
792
+ * was applied at card-render time. To apply a new filter, click 搜索.
793
+ */
794
+ export function buildRelayPickerCard(entries, targetChatId, targetRootMessageId, invokerOpenId, locale, state) {
795
+ const searchQuery = state?.searchQuery ?? '';
796
+ const requestedPage = state?.page ?? 0;
797
+ const selectedSessionId = state?.selectedSessionId;
798
+ const elements = [];
799
+ // ─── Filter & paginate ───────────────────────────────────────────────
800
+ const filtered = relayPickerFilter(entries, searchQuery);
801
+ const totalPages = Math.max(1, Math.ceil(filtered.length / RELAY_PICKER_PAGE_SIZE));
802
+ const page = Math.min(Math.max(0, requestedPage), totalPages - 1);
803
+ const start = page * RELAY_PICKER_PAGE_SIZE;
804
+ const visible = filtered.slice(start, start + RELAY_PICKER_PAGE_SIZE);
805
+ // Common state object carried by every interactive value so re-renders
806
+ // can reconstruct what the user was looking at. `invoker_open_id` pins the
807
+ // card to the user who originally summoned it — card-handler refuses
808
+ // re-render / confirm clicks from anyone else, so the menu doesn't get
809
+ // silently swapped to a passer-by's session list.
810
+ const stateValue = {
811
+ target_chat_id: targetChatId,
812
+ root_id: targetRootMessageId,
813
+ invoker_open_id: invokerOpenId,
814
+ search: searchQuery,
815
+ page,
816
+ selected: selectedSessionId ?? '',
817
+ };
818
+ // ─── Search box ─────────────────────────────────────────────────────
819
+ // v2 input supports `behaviors` natively — pressing Enter or clicking
820
+ // the built-in submit icon inside the input fires the callback. No
821
+ // separate 搜索 button needed (王皓 reported that button rendered as
822
+ // "..." due to cramped column width; the auto-submit input avoids the
823
+ // problem entirely AND removes the manual click).
824
+ //
825
+ // On submit the callback delivers `action.input_value` (the typed
826
+ // string) and `action.value` (our state object). card-handler reads
827
+ // input_value to update search, resets page to 0 and clears selection.
828
+ elements.push({
829
+ tag: 'input',
830
+ name: RELAY_SEARCH_FIELD,
831
+ placeholder: { tag: 'plain_text', content: t('card.relay.search_placeholder', undefined, locale) },
832
+ default_value: searchQuery,
833
+ width: 'fill',
834
+ behaviors: [
835
+ {
836
+ type: 'callback',
837
+ value: { action: 'relay_search', ...stateValue, selected: '' /* new search → reset selection */ },
838
+ },
839
+ ],
840
+ });
841
+ elements.push({ tag: 'hr' });
842
+ // ─── Empty / no-match notice ────────────────────────────────────────
843
+ if (entries.length === 0) {
844
+ elements.push({ tag: 'markdown', content: t('card.relay.empty', undefined, locale) });
845
+ return JSON.stringify(wrapCard(elements, locale));
846
+ }
847
+ if (filtered.length === 0) {
848
+ elements.push({
849
+ tag: 'markdown',
850
+ content: t('card.relay.empty_filtered', { query: searchQuery }, locale),
851
+ });
852
+ return JSON.stringify(wrapCard(elements, locale));
853
+ }
854
+ // ─── Session cards (current page) ───────────────────────────────────
855
+ const p2pLocationLabel = t('card.relay.type_p2p', undefined, locale);
856
+ const labelType = t('card.relay.field_type', undefined, locale);
857
+ const labelLocation = t('card.relay.field_location', undefined, locale);
858
+ const labelTime = t('card.relay.field_time', undefined, locale);
859
+ const selectedTag = t('card.relay.selected_tag', undefined, locale);
860
+ const hasValidSelection = !!selectedSessionId && filtered.some(e => e.sessionId === selectedSessionId);
861
+ visible.forEach((e) => {
862
+ const isSelected = e.sessionId === selectedSessionId;
863
+ const typeTag = relayPickerTypeTag(e.chatMode, locale);
864
+ const locationLine = e.chatMode === 'p2p' ? p2pLocationLabel : e.chatLabel;
865
+ const titleLine = isSelected
866
+ ? `**✅ ${escapeMd(e.title)}** \`${selectedTag}\``
867
+ : `**${escapeMd(e.title)}**`;
868
+ const lines = [
869
+ titleLine,
870
+ `${labelType}: ${typeTag}`,
871
+ `${labelLocation}: ${escapeMd(locationLine)}`,
872
+ ];
873
+ if (e.lastMessageAt) {
874
+ lines.push(`${labelTime}: ${formatDuration(Date.now() - e.lastMessageAt)}`);
875
+ }
876
+ elements.push({
877
+ tag: 'interactive_container',
878
+ width: 'fill',
879
+ padding: '8px 12px',
880
+ background_style: isSelected ? 'laser' : 'default',
881
+ has_border: true,
882
+ border_color: isSelected ? 'blue-500' : 'grey-200',
883
+ corner_radius: '8px',
884
+ behaviors: [
885
+ {
886
+ type: 'callback',
887
+ value: { action: 'relay_select', session_id: e.sessionId, ...stateValue },
888
+ },
889
+ ],
890
+ elements: [{ tag: 'markdown', content: lines.join('\n') }],
891
+ });
892
+ });
893
+ // ─── Paginator (only when more than one page) ───────────────────────
894
+ if (totalPages > 1) {
895
+ elements.push({
896
+ tag: 'column_set',
897
+ flex_mode: 'none',
898
+ horizontal_spacing: 'default',
899
+ columns: [
900
+ {
901
+ tag: 'column',
902
+ width: 'weighted',
903
+ weight: 1,
904
+ vertical_align: 'center',
905
+ elements: [
906
+ {
907
+ tag: 'button',
908
+ text: { tag: 'plain_text', content: t('card.relay.btn_prev_page', undefined, locale) },
909
+ type: 'default',
910
+ disabled: page === 0,
911
+ behaviors: [
912
+ {
913
+ type: 'callback',
914
+ value: { action: 'relay_page', ...stateValue, page: Math.max(0, page - 1) },
915
+ },
916
+ ],
917
+ },
918
+ ],
919
+ },
920
+ {
921
+ tag: 'column',
922
+ width: 'weighted',
923
+ weight: 2,
924
+ vertical_align: 'center',
925
+ elements: [
926
+ {
927
+ tag: 'markdown',
928
+ text_align: 'center',
929
+ content: t('card.relay.page_indicator', { current: page + 1, total: totalPages }, locale),
930
+ },
931
+ ],
932
+ },
933
+ {
934
+ tag: 'column',
935
+ width: 'weighted',
936
+ weight: 1,
937
+ vertical_align: 'center',
938
+ elements: [
939
+ {
940
+ tag: 'button',
941
+ text: { tag: 'plain_text', content: t('card.relay.btn_next_page', undefined, locale) },
942
+ type: 'default',
943
+ disabled: page === totalPages - 1,
944
+ behaviors: [
945
+ {
946
+ type: 'callback',
947
+ value: { action: 'relay_page', ...stateValue, page: Math.min(totalPages - 1, page + 1) },
948
+ },
949
+ ],
950
+ },
951
+ ],
952
+ },
953
+ ],
954
+ });
955
+ }
956
+ // ─── Confirm button or hint ─────────────────────────────────────────
957
+ elements.push({ tag: 'hr' });
958
+ if (hasValidSelection) {
959
+ elements.push({
960
+ tag: 'column_set',
961
+ flex_mode: 'none',
962
+ columns: [
963
+ {
964
+ tag: 'column',
965
+ width: 'weighted',
966
+ weight: 1,
967
+ elements: [
968
+ {
969
+ tag: 'button',
970
+ text: { tag: 'plain_text', content: t('card.relay.btn_confirm', undefined, locale) },
971
+ type: 'primary',
972
+ behaviors: [
973
+ {
974
+ type: 'callback',
975
+ value: { action: 'relay_confirm', session_id: selectedSessionId, ...stateValue },
976
+ },
977
+ ],
978
+ },
979
+ ],
980
+ },
981
+ ],
982
+ });
983
+ }
984
+ else {
985
+ elements.push({
986
+ tag: 'markdown',
987
+ content: `<font color='grey'>${t('card.relay.hint_pick_first', undefined, locale)}</font>`,
988
+ });
989
+ }
990
+ return JSON.stringify(wrapCard(elements, locale));
991
+ }
992
+ function wrapCard(elements, locale) {
993
+ return {
994
+ schema: '2.0',
995
+ config: { update_multi: true },
996
+ header: {
997
+ title: { tag: 'plain_text', content: t('card.relay.title', undefined, locale) },
998
+ template: 'blue',
999
+ },
1000
+ body: { direction: 'vertical', elements },
1001
+ };
1002
+ }
573
1003
  export function buildAdoptSelectCard(sessions, rootMessageId, locale) {
574
1004
  const unknownUptime = t('card.adopt.uptime_unknown', undefined, locale);
575
1005
  const options = sessions.map((s) => {