botmux 3.2.1 → 3.4.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 (277) hide show
  1. package/dist/adapters/backend/sandbox.d.ts +47 -0
  2. package/dist/adapters/backend/sandbox.d.ts.map +1 -1
  3. package/dist/adapters/backend/sandbox.js +137 -0
  4. package/dist/adapters/backend/sandbox.js.map +1 -1
  5. package/dist/adapters/cli/pi.d.ts.map +1 -1
  6. package/dist/adapters/cli/pi.js +4 -0
  7. package/dist/adapters/cli/pi.js.map +1 -1
  8. package/dist/adapters/cli/read-isolation.d.ts +72 -3
  9. package/dist/adapters/cli/read-isolation.d.ts.map +1 -1
  10. package/dist/adapters/cli/read-isolation.js +143 -6
  11. package/dist/adapters/cli/read-isolation.js.map +1 -1
  12. package/dist/adapters/cli/registry.d.ts.map +1 -1
  13. package/dist/adapters/cli/registry.js +8 -2
  14. package/dist/adapters/cli/registry.js.map +1 -1
  15. package/dist/adapters/cli/types.d.ts +8 -1
  16. package/dist/adapters/cli/types.d.ts.map +1 -1
  17. package/dist/cli/deferred-topic-send.d.ts +37 -0
  18. package/dist/cli/deferred-topic-send.d.ts.map +1 -0
  19. package/dist/cli/deferred-topic-send.js +69 -0
  20. package/dist/cli/deferred-topic-send.js.map +1 -0
  21. package/dist/cli/pm2-command.d.ts.map +1 -1
  22. package/dist/cli/pm2-command.js +6 -0
  23. package/dist/cli/pm2-command.js.map +1 -1
  24. package/dist/cli.d.ts.map +1 -1
  25. package/dist/cli.js +173 -34
  26. package/dist/cli.js.map +1 -1
  27. package/dist/core/command-handler.d.ts.map +1 -1
  28. package/dist/core/command-handler.js +17 -12
  29. package/dist/core/command-handler.js.map +1 -1
  30. package/dist/core/dashboard-ipc-server.d.ts +4 -1
  31. package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
  32. package/dist/core/dashboard-ipc-server.js +99 -8
  33. package/dist/core/dashboard-ipc-server.js.map +1 -1
  34. package/dist/core/deferred-schedule-settlement.d.ts +20 -0
  35. package/dist/core/deferred-schedule-settlement.d.ts.map +1 -0
  36. package/dist/core/deferred-schedule-settlement.js +18 -0
  37. package/dist/core/deferred-schedule-settlement.js.map +1 -0
  38. package/dist/core/deferred-topic-binding.d.ts +19 -0
  39. package/dist/core/deferred-topic-binding.d.ts.map +1 -0
  40. package/dist/core/deferred-topic-binding.js +55 -0
  41. package/dist/core/deferred-topic-binding.js.map +1 -0
  42. package/dist/core/device-isolation-activation.d.ts +56 -0
  43. package/dist/core/device-isolation-activation.d.ts.map +1 -0
  44. package/dist/core/device-isolation-activation.js +110 -0
  45. package/dist/core/device-isolation-activation.js.map +1 -0
  46. package/dist/core/device-isolation-daemon.d.ts +84 -0
  47. package/dist/core/device-isolation-daemon.d.ts.map +1 -0
  48. package/dist/core/device-isolation-daemon.js +591 -0
  49. package/dist/core/device-isolation-daemon.js.map +1 -0
  50. package/dist/core/scheduler.d.ts +26 -20
  51. package/dist/core/scheduler.d.ts.map +1 -1
  52. package/dist/core/scheduler.js +114 -56
  53. package/dist/core/scheduler.js.map +1 -1
  54. package/dist/core/session-discovery.d.ts.map +1 -1
  55. package/dist/core/session-discovery.js +47 -15
  56. package/dist/core/session-discovery.js.map +1 -1
  57. package/dist/core/session-manager.d.ts +17 -1
  58. package/dist/core/session-manager.d.ts.map +1 -1
  59. package/dist/core/session-manager.js +178 -84
  60. package/dist/core/session-manager.js.map +1 -1
  61. package/dist/core/types.d.ts +13 -0
  62. package/dist/core/types.d.ts.map +1 -1
  63. package/dist/core/types.js +10 -0
  64. package/dist/core/types.js.map +1 -1
  65. package/dist/core/worker-pool.d.ts +7 -0
  66. package/dist/core/worker-pool.d.ts.map +1 -1
  67. package/dist/core/worker-pool.js +86 -1
  68. package/dist/core/worker-pool.js.map +1 -1
  69. package/dist/daemon.d.ts.map +1 -1
  70. package/dist/daemon.js +95 -5
  71. package/dist/daemon.js.map +1 -1
  72. package/dist/dashboard/schedule-card-model.d.ts +12 -3
  73. package/dist/dashboard/schedule-card-model.d.ts.map +1 -1
  74. package/dist/dashboard/schedule-card-model.js +32 -8
  75. package/dist/dashboard/schedule-card-model.js.map +1 -1
  76. package/dist/dashboard/web/dashboard-components.d.ts +7 -0
  77. package/dist/dashboard/web/dashboard-components.d.ts.map +1 -1
  78. package/dist/dashboard/web/dashboard-components.js +11 -2
  79. package/dist/dashboard/web/dashboard-components.js.map +1 -1
  80. package/dist/dashboard/web/i18n.d.ts.map +1 -1
  81. package/dist/dashboard/web/i18n.js +30 -6
  82. package/dist/dashboard/web/i18n.js.map +1 -1
  83. package/dist/dashboard/web/schedules-page.d.ts +2 -0
  84. package/dist/dashboard/web/schedules-page.d.ts.map +1 -1
  85. package/dist/dashboard/web/schedules-page.js +74 -27
  86. package/dist/dashboard/web/schedules-page.js.map +1 -1
  87. package/dist/dashboard-web/app.js +1 -1
  88. package/dist/dashboard-web/chunks/{bot-defaults-page-O37SAQD5.js → bot-defaults-page-B5AY4B2J.js} +1 -1
  89. package/dist/dashboard-web/chunks/{chunk-6QHMLMIM.js → chunk-CUV65BIQ.js} +3 -3
  90. package/dist/dashboard-web/chunks/{chunk-O2DPQL7C.js → chunk-JNRSPKLS.js} +1 -1
  91. package/dist/dashboard-web/chunks/{chunk-FYALHCPG.js → chunk-P6GO5KT4.js} +1 -1
  92. package/dist/dashboard-web/chunks/chunk-QFMH6ITB.js +1 -0
  93. package/dist/dashboard-web/chunks/{chunk-B42PCL5P.js → chunk-UBOLHQCK.js} +1 -1
  94. package/dist/dashboard-web/chunks/{connectors-page-DZBQ42MH.js → connectors-page-KTC2QWNW.js} +1 -1
  95. package/dist/dashboard-web/chunks/{groups-page-3IEGRZKQ.js → groups-page-B3EKGOLY.js} +1 -1
  96. package/dist/dashboard-web/chunks/{insights-page-X3ADRON3.js → insights-page-X6RXNCWX.js} +1 -1
  97. package/dist/dashboard-web/chunks/{monitor-room-IV36RGQY.js → monitor-room-LLRA5LQW.js} +1 -1
  98. package/dist/dashboard-web/chunks/{monitoring-page-ADFKVON4.js → monitoring-page-7MT5FEOS.js} +1 -1
  99. package/dist/dashboard-web/chunks/{office-page-CALAOWNS.js → office-page-KD46D5TD.js} +1 -1
  100. package/dist/dashboard-web/chunks/{overview-page-SLMGLK3M.js → overview-page-JUWNH32O.js} +1 -1
  101. package/dist/dashboard-web/chunks/{roles-page-L7JJ4FFV.js → roles-page-P6F7NLOI.js} +1 -1
  102. package/dist/dashboard-web/chunks/schedules-page-XIHWZST4.js +1 -0
  103. package/dist/dashboard-web/chunks/{sessions-page-OKJ5YWUZ.js → sessions-page-2VECQ27T.js} +1 -1
  104. package/dist/dashboard-web/chunks/{settings-page-P6MI2ZUE.js → settings-page-HMIUE2JA.js} +1 -1
  105. package/dist/dashboard-web/chunks/{skills-page-FT7BVZ6X.js → skills-page-A46KVWBL.js} +1 -1
  106. package/dist/dashboard-web/chunks/{team-federation-page-DEYCPUFB.js → team-federation-page-SDUC56DF.js} +1 -1
  107. package/dist/dashboard-web/chunks/{v3-page-4IOWREZG.js → v3-page-J5JOTUSQ.js} +1 -1
  108. package/dist/dashboard-web/chunks/{whiteboards-page-2VMBETZH.js → whiteboards-page-U7LSOAWI.js} +1 -1
  109. package/dist/dashboard-web/style.css +34 -4
  110. package/dist/dashboard.js +16 -1
  111. package/dist/dashboard.js.map +1 -1
  112. package/dist/desktop/main/auto-start.d.ts +2 -0
  113. package/dist/desktop/main/auto-start.d.ts.map +1 -1
  114. package/dist/desktop/main/auto-start.js +10 -6
  115. package/dist/desktop/main/auto-start.js.map +1 -1
  116. package/dist/desktop/main/bundled-runtime.d.ts +12 -0
  117. package/dist/desktop/main/bundled-runtime.d.ts.map +1 -0
  118. package/dist/desktop/main/bundled-runtime.js +30 -0
  119. package/dist/desktop/main/bundled-runtime.js.map +1 -0
  120. package/dist/desktop/main/device-status.d.ts +15 -0
  121. package/dist/desktop/main/device-status.d.ts.map +1 -0
  122. package/dist/desktop/main/device-status.js +94 -0
  123. package/dist/desktop/main/device-status.js.map +1 -0
  124. package/dist/desktop/main/external-runtime.d.ts +9 -0
  125. package/dist/desktop/main/external-runtime.d.ts.map +1 -1
  126. package/dist/desktop/main/external-runtime.js +88 -18
  127. package/dist/desktop/main/external-runtime.js.map +1 -1
  128. package/dist/desktop/main/ipc.d.ts.map +1 -1
  129. package/dist/desktop/main/ipc.js +1 -0
  130. package/dist/desktop/main/ipc.js.map +1 -1
  131. package/dist/desktop/main/node-command.d.ts +21 -0
  132. package/dist/desktop/main/node-command.d.ts.map +1 -1
  133. package/dist/desktop/main/node-command.js +41 -0
  134. package/dist/desktop/main/node-command.js.map +1 -1
  135. package/dist/desktop/main/pm2-apps.d.ts +4 -2
  136. package/dist/desktop/main/pm2-apps.d.ts.map +1 -1
  137. package/dist/desktop/main/pm2-apps.js +11 -3
  138. package/dist/desktop/main/pm2-apps.js.map +1 -1
  139. package/dist/desktop/main/runtime-service.d.ts +21 -5
  140. package/dist/desktop/main/runtime-service.d.ts.map +1 -1
  141. package/dist/desktop/main/runtime-service.js +94 -10
  142. package/dist/desktop/main/runtime-service.js.map +1 -1
  143. package/dist/desktop/main/runtime-source.d.ts +2 -0
  144. package/dist/desktop/main/runtime-source.d.ts.map +1 -1
  145. package/dist/desktop/main/runtime-source.js +12 -6
  146. package/dist/desktop/main/runtime-source.js.map +1 -1
  147. package/dist/desktop/main/tray.d.ts +1 -0
  148. package/dist/desktop/main/tray.d.ts.map +1 -1
  149. package/dist/desktop/main/tray.js +3 -3
  150. package/dist/desktop/main/tray.js.map +1 -1
  151. package/dist/desktop/main.js +28 -8
  152. package/dist/desktop/main.js.map +1 -1
  153. package/dist/desktop/preload.js +3 -0
  154. package/dist/desktop/preload.js.map +1 -1
  155. package/dist/desktop/renderer/app.d.ts +2 -1
  156. package/dist/desktop/renderer/app.d.ts.map +1 -1
  157. package/dist/desktop/renderer/app.js +6 -1
  158. package/dist/desktop/renderer/app.js.map +1 -1
  159. package/dist/desktop/renderer/runtime-state.d.ts.map +1 -1
  160. package/dist/desktop/renderer/runtime-state.js +2 -0
  161. package/dist/desktop/renderer/runtime-state.js.map +1 -1
  162. package/dist/desktop/shared/shell-path-probes.d.ts +24 -0
  163. package/dist/desktop/shared/shell-path-probes.d.ts.map +1 -0
  164. package/dist/desktop/shared/shell-path-probes.js +38 -0
  165. package/dist/desktop/shared/shell-path-probes.js.map +1 -0
  166. package/dist/desktop/shared/types.d.ts +21 -1
  167. package/dist/desktop/shared/types.d.ts.map +1 -1
  168. package/dist/desktop/smoke.d.ts +1 -0
  169. package/dist/desktop/smoke.d.ts.map +1 -1
  170. package/dist/desktop/smoke.js +20 -5
  171. package/dist/desktop/smoke.js.map +1 -1
  172. package/dist/i18n/en.d.ts.map +1 -1
  173. package/dist/i18n/en.js +16 -7
  174. package/dist/i18n/en.js.map +1 -1
  175. package/dist/i18n/zh.d.ts.map +1 -1
  176. package/dist/i18n/zh.js +16 -7
  177. package/dist/i18n/zh.js.map +1 -1
  178. package/dist/im/lark/card-handler.d.ts.map +1 -1
  179. package/dist/im/lark/card-handler.js +3 -0
  180. package/dist/im/lark/card-handler.js.map +1 -1
  181. package/dist/im/lark/event-dispatcher.d.ts +1 -0
  182. package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
  183. package/dist/im/lark/event-dispatcher.js +1 -1
  184. package/dist/im/lark/event-dispatcher.js.map +1 -1
  185. package/dist/im/lark/schedules-card.d.ts +2 -2
  186. package/dist/im/lark/schedules-card.d.ts.map +1 -1
  187. package/dist/im/lark/schedules-card.js +50 -34
  188. package/dist/im/lark/schedules-card.js.map +1 -1
  189. package/dist/index-daemon.js +13 -1
  190. package/dist/index-daemon.js.map +1 -1
  191. package/dist/platform/bind.d.ts +4 -1
  192. package/dist/platform/bind.d.ts.map +1 -1
  193. package/dist/platform/bind.js +10 -1
  194. package/dist/platform/bind.js.map +1 -1
  195. package/dist/platform/binding.d.ts.map +1 -1
  196. package/dist/platform/binding.js +15 -9
  197. package/dist/platform/binding.js.map +1 -1
  198. package/dist/platform/device-command.d.ts +35 -0
  199. package/dist/platform/device-command.d.ts.map +1 -0
  200. package/dist/platform/device-command.js +187 -0
  201. package/dist/platform/device-command.js.map +1 -0
  202. package/dist/platform/device-enroll.d.ts +156 -0
  203. package/dist/platform/device-enroll.d.ts.map +1 -0
  204. package/dist/platform/device-enroll.js +781 -0
  205. package/dist/platform/device-enroll.js.map +1 -0
  206. package/dist/platform/device-isolation-activation-client.d.ts +28 -0
  207. package/dist/platform/device-isolation-activation-client.d.ts.map +1 -0
  208. package/dist/platform/device-isolation-activation-client.js +279 -0
  209. package/dist/platform/device-isolation-activation-client.js.map +1 -0
  210. package/dist/platform/device-isolation.d.ts +35 -0
  211. package/dist/platform/device-isolation.d.ts.map +1 -0
  212. package/dist/platform/device-isolation.js +90 -0
  213. package/dist/platform/device-isolation.js.map +1 -0
  214. package/dist/platform/device-paths.d.ts +9 -0
  215. package/dist/platform/device-paths.d.ts.map +1 -0
  216. package/dist/platform/device-paths.js +14 -0
  217. package/dist/platform/device-paths.js.map +1 -0
  218. package/dist/platform/device.d.ts +77 -0
  219. package/dist/platform/device.d.ts.map +1 -0
  220. package/dist/platform/device.js +212 -0
  221. package/dist/platform/device.js.map +1 -0
  222. package/dist/platform/host-command-context.d.ts +15 -0
  223. package/dist/platform/host-command-context.d.ts.map +1 -0
  224. package/dist/platform/host-command-context.js +26 -0
  225. package/dist/platform/host-command-context.js.map +1 -0
  226. package/dist/platform/secure-host-file.d.ts +12 -0
  227. package/dist/platform/secure-host-file.d.ts.map +1 -0
  228. package/dist/platform/secure-host-file.js +182 -0
  229. package/dist/platform/secure-host-file.js.map +1 -0
  230. package/dist/services/schedule-store.d.ts +6 -2
  231. package/dist/services/schedule-store.d.ts.map +1 -1
  232. package/dist/services/schedule-store.js +19 -4
  233. package/dist/services/schedule-store.js.map +1 -1
  234. package/dist/skills/definitions.d.ts.map +1 -1
  235. package/dist/skills/definitions.js +4 -5
  236. package/dist/skills/definitions.js.map +1 -1
  237. package/dist/types.d.ts +45 -8
  238. package/dist/types.d.ts.map +1 -1
  239. package/dist/utils/atomic-write.d.ts +13 -0
  240. package/dist/utils/atomic-write.d.ts.map +1 -1
  241. package/dist/utils/atomic-write.js +89 -9
  242. package/dist/utils/atomic-write.js.map +1 -1
  243. package/dist/utils/child-env.d.ts +40 -1
  244. package/dist/utils/child-env.d.ts.map +1 -1
  245. package/dist/utils/child-env.js +45 -0
  246. package/dist/utils/child-env.js.map +1 -1
  247. package/dist/utils/pending-input-queue.d.ts +10 -0
  248. package/dist/utils/pending-input-queue.d.ts.map +1 -1
  249. package/dist/utils/pending-input-queue.js +15 -0
  250. package/dist/utils/pending-input-queue.js.map +1 -1
  251. package/dist/worker.js +391 -28
  252. package/dist/worker.js.map +1 -1
  253. package/dist/workflows/events/payloads.d.ts +4 -4
  254. package/dist/workflows/events/schema.d.ts +16 -16
  255. package/dist/workflows/hostExecutors/botmux-schedule.d.ts +3 -1
  256. package/dist/workflows/hostExecutors/botmux-schedule.d.ts.map +1 -1
  257. package/dist/workflows/hostExecutors/botmux-schedule.js +14 -3
  258. package/dist/workflows/hostExecutors/botmux-schedule.js.map +1 -1
  259. package/dist/workflows/v3/architect.js +1 -1
  260. package/dist/workflows/v3/architect.js.map +1 -1
  261. package/dist/workflows/v3/contract.d.ts +8 -0
  262. package/dist/workflows/v3/contract.d.ts.map +1 -1
  263. package/dist/workflows/v3/contract.js.map +1 -1
  264. package/dist/workflows/v3/daemon-run.d.ts.map +1 -1
  265. package/dist/workflows/v3/daemon-run.js +2 -0
  266. package/dist/workflows/v3/daemon-run.js.map +1 -1
  267. package/dist/workflows/v3/ephemeral-pool.d.ts.map +1 -1
  268. package/dist/workflows/v3/ephemeral-pool.js +10 -2
  269. package/dist/workflows/v3/ephemeral-pool.js.map +1 -1
  270. package/dist/workflows/v3/run-envelope.d.ts +24 -24
  271. package/dist/workflows/v3/runtime.d.ts +5 -0
  272. package/dist/workflows/v3/runtime.d.ts.map +1 -1
  273. package/dist/workflows/v3/runtime.js +1 -0
  274. package/dist/workflows/v3/runtime.js.map +1 -1
  275. package/package.json +6 -3
  276. package/dist/dashboard-web/chunks/chunk-I35DXGM4.js +0 -1
  277. package/dist/dashboard-web/chunks/schedules-page-Z3672POL.js +0 -1
package/dist/cli.js CHANGED
@@ -12,6 +12,7 @@
12
12
  * botmux logs [--lines] — view daemon logs
13
13
  * botmux status — show daemon status
14
14
  * botmux upgrade|update — upgrade to latest version
15
+ * botmux device enroll|status|logout — manage the host desktop device credential
15
16
  * botmux list — interactive session picker (TUI), attach to tmux
16
17
  * botmux list --plain — plain table output (for piping / scripts)
17
18
  * botmux delete <id> — close a session by ID prefix
@@ -46,11 +47,13 @@ import { buildBotFromAddFlags, editInputFromFlags, isScriptedSetupInvocation, ma
46
47
  import { interactiveSelect, pickChoice, pickCliSelection } from './setup/interactive-select.js';
47
48
  import { buildPreset, serializePreset, presetFilename } from './setup/agent-preset.js';
48
49
  import { logger } from './utils/logger.js';
50
+ import { scrubSessionCliHomeEnv } from './utils/child-env.js';
49
51
  import { scheduleTimeZone } from './utils/timezone.js';
50
52
  import { expandHomePath, invalidWorkingDirs } from './utils/working-dir.js';
51
53
  import { firstPositional } from './cli/arg-utils.js';
52
54
  import { isColdResumeDormant, sessionListDisposition } from './cli/session-list-liveness.js';
53
55
  import { dispatchPrimaryMessage, findStdinAliasAttachment, normalizeInteractiveCardInput, sendFileAttachments, sendVideoAttachments, shouldSendAsPureVideo, validateVideoAttachments } from './cli/send-dispatch.js';
56
+ import { dispatchDeferredTopicSend } from './cli/deferred-topic-send.js';
54
57
  import { resolveDaemonExternalHostEnv } from './cli/daemon-lifecycle-env.js';
55
58
  import { buildPm2SpawnCommand } from './cli/pm2-command.js';
56
59
  import { callDashboard } from './cli/dashboard-endpoint.js';
@@ -146,7 +149,18 @@ function pm2Bin() {
146
149
  }
147
150
  /** Env for pm2 invocations with an isolated PM2_HOME. */
148
151
  function pm2Env(home = PM2_HOME) {
149
- return { ...process.env, PM2_HOME: home };
152
+ const env = { ...process.env, PM2_HOME: home };
153
+ // pm2 persists the caller's env into every managed app (and into dump.pm2
154
+ // for resurrect), so a `botmux start/restart` invoked from ANY process that
155
+ // carries a session-level CLI home pointer — most commonly a bot's own
156
+ // session during self-upgrade, whose env holds its injected
157
+ // CLAUDE_CONFIG_DIR — would poison ALL workers, making every non-isolated
158
+ // bot read/write a sibling bot's home. Strip at this boundary so the daemon
159
+ // stays session-agnostic; daemon/worker boot scrub the same keys against
160
+ // stale dumps (see SESSION_CLI_HOME_ENV_KEYS for the full story, including
161
+ // why GROK_HOME is exempt and why deleting beats pinning a default).
162
+ scrubSessionCliHomeEnv(env);
163
+ return env;
150
164
  }
151
165
  function listPm2GodDaemonPids(home = PM2_HOME) {
152
166
  if (process.platform !== 'linux')
@@ -325,6 +339,10 @@ function ecosystemConfig() {
325
339
  const externalHostEnv = resolveDaemonExternalHostEnv(process.env, existsSync(ENV_FILE) ? readFileSync(ENV_FILE, 'utf-8') : undefined);
326
340
  const baseApp = {
327
341
  script: daemonScript,
342
+ // Pin every managed core process to the Node that invoked this CLI. This
343
+ // keeps GUI/launchd starts independent from PATH and lets Desktop replace
344
+ // an external fleet without also killing unrelated plugin services.
345
+ interpreter: process.execPath,
328
346
  cwd: CONFIG_DIR,
329
347
  autorestart: true,
330
348
  max_restarts: 10,
@@ -370,6 +388,7 @@ function ecosystemConfig() {
370
388
  apps.push({
371
389
  name: 'botmux-dashboard',
372
390
  script: join(PKG_ROOT, 'dist', 'dashboard.js'),
391
+ interpreter: process.execPath,
373
392
  cwd: PKG_ROOT,
374
393
  autorestart: true,
375
394
  max_restarts: 10,
@@ -4057,6 +4076,9 @@ async function cmdResume() {
4057
4076
  else if (errCode === 'adopt_unsupported') {
4058
4077
  console.error('❌ adopt 接管会话不支持 resume。');
4059
4078
  }
4079
+ else if (errCode === 'deferred_unmaterialized') {
4080
+ console.error('❌ 该静默定时轮次未创建话题,隐藏会话只保留审计记录,不能 resume。');
4081
+ }
4060
4082
  else {
4061
4083
  console.error(`❌ 恢复失败: ${errCode}`);
4062
4084
  }
@@ -4182,6 +4204,8 @@ botmux v${getVersion()} — IM ↔ AI 编程 CLI 桥接
4182
4204
  status 查看 daemon 状态
4183
4205
  upgrade 升级到最新版本(别名:update)
4184
4206
  dashboard 打印新的 Web Dashboard 一次性登录 URL(旧 token 同时失效)
4207
+ device enroll|status|logout
4208
+ 在宿主终端注册、查看或清除 desktop device 凭证(AI CLI 会话内拒绝)
4185
4209
  list 列出活跃会话(交互式选择并连接 tmux)
4186
4210
  --plain 纯文本表格输出(管道/脚本场景)
4187
4211
  delete <id> 关闭指定会话(支持 ID 前缀匹配)
@@ -4231,8 +4255,10 @@ botmux v${getVersion()} — IM ↔ AI 编程 CLI 桥接
4231
4255
  定时任务(可在 CLI 会话内自动推断 chat):
4232
4256
  schedule list 列出所有任务
4233
4257
  schedule add <schedule> <prompt> 添加任务(ex: "30m" / "every 2h" / "每日9:00" / "0 9 * * *")
4234
- --new-topic 每次触发在同群开一个新话题、起独立会话(不续旧话题)
4235
- --silent 静默执行:不发「执行中」提示,模型判断是否 botmux send 报警(与 --new-topic 互斥)
4258
+ --top-level 在群消息顶层执行(后续会话形态跟随普通群会话模式)
4259
+ --topic --root-msg-id <om_...> 固定在指定话题下执行
4260
+ --new-topic [--topic-title ...] 每次创建新话题和独立会话
4261
+ --silent 静默执行:不发「执行中」提示,模型判断是否 botmux send 报警
4236
4262
  schedule remove <id> 删除任务
4237
4263
  schedule pause|resume <id> 暂停/恢复
4238
4264
  schedule run <id> 标记立即执行
@@ -4711,9 +4737,9 @@ async function cmdSchedule(sub, rest) {
4711
4737
  return;
4712
4738
  }
4713
4739
  if (sub === 'add') {
4714
- const [rawSchedule, ...promptParts] = positionals(rest, ['--new-topic', '--silent']);
4740
+ const [rawSchedule, ...promptParts] = positionals(rest, ['--new-topic', '--top-level', '--topic', '--silent']);
4715
4741
  if (!rawSchedule) {
4716
- console.error('用法: botmux schedule add <schedule> <prompt> [--name NAME] [--chat-id CHAT] [--root-msg-id ROOT] [--lark-app-id APP] [--workdir DIR] [--new-topic] [--silent]');
4742
+ console.error('用法: botmux schedule add <schedule> <prompt> [--name NAME] [--chat-id CHAT] [--top-level | --topic --root-msg-id ROOT | --new-topic [--topic-title TITLE]] [--lark-app-id APP] [--workdir DIR] [--silent]');
4717
4743
  process.exit(1);
4718
4744
  }
4719
4745
  // prompt may come from positional or --prompt flag
@@ -4724,25 +4750,50 @@ async function cmdSchedule(sub, rest) {
4724
4750
  }
4725
4751
  const cur = detectCurrentSession();
4726
4752
  const chatId = argValue(rest, '--chat-id') ?? cur?.chatId;
4727
- const rootMessageId = argValue(rest, '--root-msg-id') ?? cur?.rootMessageId;
4753
+ const explicitRootMessageId = argValue(rest, '--root-msg-id');
4754
+ const rootMessageId = explicitRootMessageId
4755
+ ?? (chatId && chatId === cur?.chatId ? cur.rootMessageId : undefined);
4728
4756
  const larkAppId = argValue(rest, '--lark-app-id') ?? cur?.larkAppId;
4729
4757
  const workingDir = argValue(rest, '--workdir') ?? cur?.workingDir ?? process.cwd();
4730
4758
  const name = argValue(rest, '--name') ?? (promptArg.length > 20 ? promptArg.slice(0, 20) + '…' : promptArg);
4731
- // --new-topic: every fire opens a brand-new topic in a fresh session.
4732
- const deliver = rest.includes('--new-topic')
4733
- ? 'new-topic'
4734
- : (argValue(rest, '--deliver') ?? 'origin');
4759
+ const legacyDeliver = argValue(rest, '--deliver');
4760
+ const wantsNewTopic = rest.includes('--new-topic') || legacyDeliver === 'new-topic';
4761
+ const wantsTopLevel = rest.includes('--top-level');
4762
+ const wantsTopic = rest.includes('--topic');
4763
+ if ([wantsNewTopic, wantsTopLevel, wantsTopic].filter(Boolean).length > 1) {
4764
+ console.error('--top-level、--topic 与 --new-topic 只能选择一个。');
4765
+ process.exit(1);
4766
+ }
4767
+ const requestedDeliver = legacyDeliver;
4768
+ const deliver = requestedDeliver === 'local' ? 'local' : 'origin';
4735
4769
  // --silent: fires post no "执行中" banner; the spawned turn stays quiet and
4736
4770
  // the model only `botmux send`s when the alert condition in the prompt is met.
4737
4771
  const silent = rest.includes('--silent');
4738
- if (silent && deliver === 'new-topic') {
4739
- console.error('--silent 与 --new-topic 不能同时使用:新话题必须由首条消息开启。要「有异常才开新话题」,请在 prompt 里指示模型报警时用 botmux send 顶层发送。');
4740
- process.exit(1);
4741
- }
4742
4772
  if (!chatId) {
4743
4773
  console.error('无法推断 chat-id。请加上 --chat-id <CHAT_ID>,或从 Lark 话题内的 CLI 会话中运行本命令。');
4744
4774
  process.exit(1);
4745
4775
  }
4776
+ const executionPosition = wantsNewTopic
4777
+ ? 'new-topic'
4778
+ : wantsTopLevel
4779
+ ? 'top-level'
4780
+ : wantsTopic
4781
+ ? 'topic'
4782
+ : cur?.scope === 'chat' ? 'top-level' : rootMessageId ? 'topic' : 'top-level';
4783
+ const scope = executionPosition === 'topic' ? 'thread' : 'chat';
4784
+ if (scope === 'thread' && !rootMessageId) {
4785
+ console.error('话题下执行需要 --root-msg-id <ROOT_MESSAGE_ID>,或从 Lark 话题会话中运行。');
4786
+ process.exit(1);
4787
+ }
4788
+ const topicTitle = argValue(rest, '--topic-title');
4789
+ if (topicTitle && executionPosition !== 'new-topic') {
4790
+ console.error('--topic-title 仅可与 --new-topic 一起使用。');
4791
+ process.exit(1);
4792
+ }
4793
+ if (topicTitle && Array.from(topicTitle.trim()).length > 200) {
4794
+ console.error('--topic-title 最多 200 个字符。');
4795
+ process.exit(1);
4796
+ }
4746
4797
  let parsed;
4747
4798
  try {
4748
4799
  parsed = scheduler.parseSchedule(rawSchedule);
@@ -4764,7 +4815,9 @@ async function cmdSchedule(sub, rest) {
4764
4815
  creatorRootMessageId: cur?.rootMessageId,
4765
4816
  creatorLarkAppId: cur?.larkAppId,
4766
4817
  chatType: cur?.chatType === 'p2p' ? 'p2p' : 'topic_group',
4767
- scope: cur?.scope,
4818
+ scope,
4819
+ executionPosition,
4820
+ topicTitle,
4768
4821
  deliver,
4769
4822
  silent,
4770
4823
  });
@@ -4773,7 +4826,9 @@ async function cmdSchedule(sub, rest) {
4773
4826
  console.log(` 规则: ${parsed.display}`);
4774
4827
  console.log(` 下次执行: ${next}`);
4775
4828
  console.log(` 工作目录: ${workingDir}`);
4776
- console.log(` 话题: ${deliver === 'new-topic' ? '(每次新开话题,独立会话)' : rootMessageId ?? '(将新开)'}`);
4829
+ console.log(` 执行位置: ${executionPosition === 'new-topic' ? '每次新话题' : executionPosition === 'top-level' ? '群消息顶层' : '话题下'}`);
4830
+ if (executionPosition === 'new-topic' && topicTitle?.trim())
4831
+ console.log(` 新话题标题: ${topicTitle.trim()}`);
4777
4832
  if (silent)
4778
4833
  console.log(' 静默: 触发时不发「执行中」提示,由模型判断是否需要 botmux send 报警');
4779
4834
  return;
@@ -5417,6 +5472,10 @@ function riffModeSession(opts = {}) {
5417
5472
  const scopeEnv = process.env.BOTMUX_SESSION_SCOPE;
5418
5473
  const scope = scopeEnv === 'chat' || scopeEnv === 'thread' ? scopeEnv : (rootMessageId ? 'thread' : 'chat');
5419
5474
  const ownerOpenId = process.env.BOTMUX_OWNER_OPEN_ID;
5475
+ const deferredTaskId = process.env.BOTMUX_DEFERRED_SCHEDULE_TASK_ID;
5476
+ const deferredTurnId = process.env.BOTMUX_DEFERRED_SCHEDULE_TURN_ID;
5477
+ const deferredRoutingAnchor = process.env.BOTMUX_DEFERRED_SCHEDULE_ROUTING_ANCHOR;
5478
+ const deferredCreatedAt = process.env.BOTMUX_DEFERRED_SCHEDULE_CREATED_AT;
5420
5479
  const botConfig = {
5421
5480
  larkAppId: appId,
5422
5481
  larkAppSecret: appSecret,
@@ -5434,6 +5493,19 @@ function riffModeSession(opts = {}) {
5434
5493
  larkAppId: appId,
5435
5494
  scope,
5436
5495
  ownerOpenId,
5496
+ ...(deferredTaskId && deferredTurnId && deferredRoutingAnchor && deferredCreatedAt
5497
+ ? {
5498
+ deferredScheduleRun: {
5499
+ taskId: deferredTaskId,
5500
+ turnId: deferredTurnId,
5501
+ routingAnchor: deferredRoutingAnchor,
5502
+ ...(process.env.BOTMUX_DEFERRED_SCHEDULE_TOPIC_TITLE
5503
+ ? { topicTitle: process.env.BOTMUX_DEFERRED_SCHEDULE_TOPIC_TITLE }
5504
+ : {}),
5505
+ createdAt: deferredCreatedAt,
5506
+ },
5507
+ }
5508
+ : {}),
5437
5509
  // 刻意不设 quoteTargetSenderOpenId:env 是任务创建时冻结的,follow-up 轮
5438
5510
  // 换了触发人后 --mention-back 会错误 @ 最初 owner。riff routing 明确禁用
5439
5511
  // mention-back(@ 硬门会拒绝并提示 agent 改用 --mention <本轮 sender>)。
@@ -5707,6 +5779,8 @@ async function cmdSend(rest) {
5707
5779
  console.error(`session ${sid} 缺少 larkAppId`);
5708
5780
  process.exit(1);
5709
5781
  }
5782
+ let deferredMaterializedByThisCommand = false;
5783
+ let deferredTopicRootMessageIdForOutput;
5710
5784
  // 本轮的回复锚点:优先按 turnId 精确取 per-turn 落点(排队/并发轮次不再被
5711
5785
  // 后到轮次覆盖 currentReplyTarget 而塌成群顶层 plain),无记录再退回单槽。
5712
5786
  const turnReplyTarget = pickTurnReplyTarget(s, currentTurnId);
@@ -5855,23 +5929,45 @@ async function cmdSend(rest) {
5855
5929
  }
5856
5930
  else {
5857
5931
  revalidateVcMeetingManagedSend();
5858
- const canonicalTarget = resolveSendTarget({
5859
- into: sendInto,
5860
- topLevel: sendTopLevel,
5861
- chatScope: s.scope === 'chat',
5862
- chatId: canonicalOutput.targetChatId,
5863
- rootMessageId: s.rootMessageId,
5864
- replyTargetRootId: turnReplyTarget?.rootMessageId,
5865
- replyTargetTurnId: turnReplyTarget?.turnId,
5866
- replyTargetQuoteOnly: turnReplyTarget?.quoteOnly,
5867
- currentTurnId,
5868
- });
5869
5932
  const managedProviderOptions = prepared
5870
5933
  ? { suppressHook: true }
5871
5934
  : undefined;
5872
- messageId = canonicalTarget.mode === 'plain'
5873
- ? await sendMessage(appId, canonicalTarget.chatId, canonicalOutput.content, canonicalOutput.msgType, prepared?.providerKey, undefined, managedProviderOptions)
5874
- : await replyMessage(appId, canonicalTarget.rootMessageId, canonicalOutput.content, canonicalOutput.msgType, canonicalTarget.mode === 'thread', prepared?.providerKey, undefined, managedProviderOptions);
5935
+ const deferred = !sendInto && (!overrideChatId || overrideChatId === s.chatId)
5936
+ ? await dispatchDeferredTopicSend({
5937
+ dataDir: resolveDataDir(),
5938
+ session: s,
5939
+ currentTurnId,
5940
+ explicitTopLevel: sendTopLevel,
5941
+ reuseBoundRootWhenTopLevel: deferredMaterializedByThisCommand,
5942
+ content: canonicalOutput.content,
5943
+ msgType: canonicalOutput.msgType,
5944
+ uuid: prepared?.providerKey,
5945
+ sendRoot: (body, type, uuid) => sendMessage(appId, targetChatId, body, type, uuid, undefined, managedProviderOptions),
5946
+ sendTitleSeed: (title, uuid) => sendMessage(appId, targetChatId, title, 'text', uuid),
5947
+ replyRoot: (root, body, type, uuid) => replyMessage(appId, root, body, type, true, uuid, undefined, managedProviderOptions),
5948
+ })
5949
+ : { handled: false };
5950
+ if (deferred.handled && deferred.messageId) {
5951
+ deferredMaterializedByThisCommand ||= deferred.materializedNow === true;
5952
+ deferredTopicRootMessageIdForOutput = deferred.rootMessageId;
5953
+ messageId = deferred.messageId;
5954
+ }
5955
+ else {
5956
+ const canonicalTarget = resolveSendTarget({
5957
+ into: sendInto,
5958
+ topLevel: sendTopLevel,
5959
+ chatScope: s.scope === 'chat',
5960
+ chatId: canonicalOutput.targetChatId,
5961
+ rootMessageId: s.rootMessageId,
5962
+ replyTargetRootId: turnReplyTarget?.rootMessageId,
5963
+ replyTargetTurnId: turnReplyTarget?.turnId,
5964
+ replyTargetQuoteOnly: turnReplyTarget?.quoteOnly,
5965
+ currentTurnId,
5966
+ });
5967
+ messageId = canonicalTarget.mode === 'plain'
5968
+ ? await sendMessage(appId, canonicalTarget.chatId, canonicalOutput.content, canonicalOutput.msgType, prepared?.providerKey, undefined, managedProviderOptions)
5969
+ : await replyMessage(appId, canonicalTarget.rootMessageId, canonicalOutput.content, canonicalOutput.msgType, canonicalTarget.mode === 'thread', prepared?.providerKey, undefined, managedProviderOptions);
5970
+ }
5875
5971
  if (prepared?.kind === 'send' || prepared?.kind === 'succeeded') {
5876
5972
  finishVcMeetingImReply(resolveDataDir(), prepared.ref, messageId);
5877
5973
  }
@@ -5879,7 +5975,9 @@ async function cmdSend(rest) {
5879
5975
  recordVcMeetingPrimaryOutput(messageId, canonicalOutput.targetChatId);
5880
5976
  // 语音也是一次回复:写 bridge fallback marker,否则本轮会被判为"没发 botmux send"
5881
5977
  // 而触发兜底,多补一张文本卡。与文本/卡片路径同口径:仅同话题回复才记。
5882
- if (!sendTopLevel && !overrideChatId && !sendInto) {
5978
+ if ((!sendTopLevel || !!deferredTopicRootMessageIdForOutput)
5979
+ && (!overrideChatId || overrideChatId === s.chatId)
5980
+ && !sendInto) {
5883
5981
  try {
5884
5982
  const markerDir = join(resolveDataDir(), 'turn-sends');
5885
5983
  if (!existsSync(markerDir))
@@ -5889,7 +5987,16 @@ async function cmdSend(rest) {
5889
5987
  catch { /* best-effort:漏记只多一条兜底,不致命 */ }
5890
5988
  }
5891
5989
  console.error(`✓ 已发送语音 ${messageId} | ${Math.round(out.durationMs / 1000)}s`);
5892
- console.log(JSON.stringify({ success: true, messageId, sessionId: sid, kind: 'voice', durationMs: out.durationMs }));
5990
+ console.log(JSON.stringify({
5991
+ success: true,
5992
+ messageId,
5993
+ sessionId: sid,
5994
+ kind: 'voice',
5995
+ durationMs: out.durationMs,
5996
+ ...(deferredTopicRootMessageIdForOutput
5997
+ ? { deferredTopicRootMessageId: deferredTopicRootMessageIdForOutput, turnId: currentTurnId }
5998
+ : {}),
5999
+ }));
5893
6000
  }
5894
6001
  catch (e) {
5895
6002
  console.error(`语音发送失败:${e?.message ?? e}`);
@@ -6108,10 +6215,33 @@ async function cmdSend(rest) {
6108
6215
  // Dispatch helper: top-level / chat-scope send vs reply-in-thread, single
6109
6216
  // decision point. Used for file attachments (always plain in chat scope).
6110
6217
  const sendTarget = resolveSendTarget({ into: sendInto, topLevel: sendTopLevel, chatScope: isChatScope, chatId: targetChatId, rootMessageId: s.rootMessageId, replyTargetRootId: turnReplyTarget?.rootMessageId, replyTargetTurnId: turnReplyTarget?.turnId, replyTargetQuoteOnly: turnReplyTarget?.quoteOnly, currentTurnId });
6111
- const dispatch = (content, msgType, uuid, suppressHook) => {
6218
+ const dispatch = async (content, msgType, uuid, suppressHook) => {
6112
6219
  // This closure also carries attachments, so every Lark call re-checks the
6113
6220
  // exact durable attempt/member instead of inheriting the early cmd gate.
6114
6221
  revalidateVcMeetingManagedSend();
6222
+ if (!sendInto && (!overrideChatId || overrideChatId === s.chatId)) {
6223
+ const deferred = await dispatchDeferredTopicSend({
6224
+ dataDir: resolveDataDir(),
6225
+ session: s,
6226
+ currentTurnId,
6227
+ explicitTopLevel: sendTopLevel,
6228
+ reuseBoundRootWhenTopLevel: deferredMaterializedByThisCommand,
6229
+ content,
6230
+ msgType,
6231
+ uuid,
6232
+ sendRoot: (body, type, rootUuid) => sendMessage(appId, targetChatId, body, type, rootUuid, hookContext, suppressHook ? { suppressHook: true } : undefined),
6233
+ // The optional title is the root seed and the actual alert follows as
6234
+ // its first reply. Do not emit a user outbound hook for presentation-
6235
+ // only seed text; the alert itself still goes through the hook path.
6236
+ sendTitleSeed: (title, rootUuid) => sendMessage(appId, targetChatId, title, 'text', rootUuid),
6237
+ replyRoot: (root, body, type, replyUuid) => replyMessage(appId, root, body, type, true, replyUuid, hookContext, suppressHook ? { suppressHook: true } : undefined),
6238
+ });
6239
+ if (deferred.handled && deferred.messageId) {
6240
+ deferredMaterializedByThisCommand ||= deferred.materializedNow === true;
6241
+ deferredTopicRootMessageIdForOutput = deferred.rootMessageId;
6242
+ return deferred.messageId;
6243
+ }
6244
+ }
6115
6245
  return sendTarget.mode === 'plain'
6116
6246
  ? sendMessage(appId, sendTarget.chatId, content, msgType, uuid, hookContext, suppressHook ? { suppressHook: true } : undefined)
6117
6247
  : replyMessage(appId, sendTarget.rootMessageId, content, msgType, sendTarget.mode === 'thread', uuid, hookContext, suppressHook ? { suppressHook: true } : undefined);
@@ -6538,8 +6668,9 @@ async function cmdSend(rest) {
6538
6668
  // sends can suppress transcript fallback when their content appears to
6539
6669
  // cover the same final answer; detoured sends suppress only when they
6540
6670
  // closed a pending response card for this turn.
6541
- if (shouldRecordBridgeMarker)
6671
+ if (shouldRecordBridgeMarker || deferredTopicRootMessageIdForOutput) {
6542
6672
  recordBridgeSendMarker(sentAtMs, messageId, text);
6673
+ }
6543
6674
  // Send attachments as separate messages — best-effort. The primary message
6544
6675
  // is already delivered above; a failing attachment must not throw out to the
6545
6676
  // catch below (which would report total failure / exit 1 for an already-sent
@@ -6619,6 +6750,9 @@ async function cmdSend(rest) {
6619
6750
  sessionId: sid,
6620
6751
  quotedMessageId: primaryQuotedId,
6621
6752
  mentioned: mentions.map(m => ({ open_id: m.open_id, name: m.name })),
6753
+ ...(deferredTopicRootMessageIdForOutput
6754
+ ? { deferredTopicRootMessageId: deferredTopicRootMessageIdForOutput, turnId: currentTurnId }
6755
+ : {}),
6622
6756
  ...(attention.requested ? { attentionRaised, attentionError } : {}),
6623
6757
  ...(failedAttachments.length > 0
6624
6758
  ? { failedAttachments: failedAttachments.map(f => f.path) }
@@ -8615,6 +8749,11 @@ switch (command) {
8615
8749
  await cmdBind(process.argv.slice(3));
8616
8750
  break;
8617
8751
  }
8752
+ case 'device': {
8753
+ const { runDeviceCommand } = await import('./platform/device-command.js');
8754
+ process.exitCode = await runDeviceCommand(process.argv.slice(3));
8755
+ break;
8756
+ }
8618
8757
  case 'list':
8619
8758
  case 'ls':
8620
8759
  await cmdList();