botmux 2.110.0 → 2.111.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 (221) hide show
  1. package/dist/adapters/backend/tmux-backend.d.ts +25 -5
  2. package/dist/adapters/backend/tmux-backend.d.ts.map +1 -1
  3. package/dist/adapters/backend/tmux-backend.js +59 -93
  4. package/dist/adapters/backend/tmux-backend.js.map +1 -1
  5. package/dist/adapters/backend/tmux-pipe-backend.d.ts.map +1 -1
  6. package/dist/adapters/backend/tmux-pipe-backend.js +5 -0
  7. package/dist/adapters/backend/tmux-pipe-backend.js.map +1 -1
  8. package/dist/adapters/backend/zellij-backend.d.ts +30 -0
  9. package/dist/adapters/backend/zellij-backend.d.ts.map +1 -1
  10. package/dist/adapters/backend/zellij-backend.js +80 -13
  11. package/dist/adapters/backend/zellij-backend.js.map +1 -1
  12. package/dist/adapters/backend/zellij-socket-probe.d.ts +18 -0
  13. package/dist/adapters/backend/zellij-socket-probe.d.ts.map +1 -0
  14. package/dist/adapters/backend/zellij-socket-probe.js +146 -0
  15. package/dist/adapters/backend/zellij-socket-probe.js.map +1 -0
  16. package/dist/adapters/cli/claude-code.d.ts.map +1 -1
  17. package/dist/adapters/cli/claude-code.js +5 -0
  18. package/dist/adapters/cli/claude-code.js.map +1 -1
  19. package/dist/adapters/cli/codex-app-turn.d.ts +43 -0
  20. package/dist/adapters/cli/codex-app-turn.d.ts.map +1 -0
  21. package/dist/adapters/cli/codex-app-turn.js +110 -0
  22. package/dist/adapters/cli/codex-app-turn.js.map +1 -0
  23. package/dist/adapters/cli/codex-app.d.ts.map +1 -1
  24. package/dist/adapters/cli/codex-app.js +6 -0
  25. package/dist/adapters/cli/codex-app.js.map +1 -1
  26. package/dist/adapters/cli/codex.d.ts.map +1 -1
  27. package/dist/adapters/cli/codex.js +1 -0
  28. package/dist/adapters/cli/codex.js.map +1 -1
  29. package/dist/adapters/cli/runner-input.d.ts +3 -2
  30. package/dist/adapters/cli/runner-input.d.ts.map +1 -1
  31. package/dist/adapters/cli/runner-input.js +7 -4
  32. package/dist/adapters/cli/runner-input.js.map +1 -1
  33. package/dist/adapters/cli/types.d.ts +16 -0
  34. package/dist/adapters/cli/types.d.ts.map +1 -1
  35. package/dist/bot-registry.d.ts +5 -0
  36. package/dist/bot-registry.d.ts.map +1 -1
  37. package/dist/bot-registry.js +1 -0
  38. package/dist/bot-registry.js.map +1 -1
  39. package/dist/codex-app-runner.js +75 -12
  40. package/dist/codex-app-runner.js.map +1 -1
  41. package/dist/core/command-handler.d.ts +11 -0
  42. package/dist/core/command-handler.d.ts.map +1 -1
  43. package/dist/core/command-handler.js +101 -7
  44. package/dist/core/command-handler.js.map +1 -1
  45. package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
  46. package/dist/core/dashboard-ipc-server.js +69 -11
  47. package/dist/core/dashboard-ipc-server.js.map +1 -1
  48. package/dist/core/doc-comment-prompt.d.ts +57 -0
  49. package/dist/core/doc-comment-prompt.d.ts.map +1 -1
  50. package/dist/core/doc-comment-prompt.js +147 -2
  51. package/dist/core/doc-comment-prompt.js.map +1 -1
  52. package/dist/core/inflight-input-tracker.d.ts +2 -0
  53. package/dist/core/inflight-input-tracker.d.ts.map +1 -1
  54. package/dist/core/inflight-input-tracker.js.map +1 -1
  55. package/dist/core/passthrough-commands.d.ts.map +1 -1
  56. package/dist/core/passthrough-commands.js +1 -1
  57. package/dist/core/passthrough-commands.js.map +1 -1
  58. package/dist/core/persistent-backend.d.ts +4 -0
  59. package/dist/core/persistent-backend.d.ts.map +1 -1
  60. package/dist/core/persistent-backend.js +6 -0
  61. package/dist/core/persistent-backend.js.map +1 -1
  62. package/dist/core/plugins/cli-generation.d.ts +3 -0
  63. package/dist/core/plugins/cli-generation.d.ts.map +1 -1
  64. package/dist/core/plugins/cli-generation.js +1 -0
  65. package/dist/core/plugins/cli-generation.js.map +1 -1
  66. package/dist/core/session-board.d.ts +2 -1
  67. package/dist/core/session-board.d.ts.map +1 -1
  68. package/dist/core/session-board.js +3 -2
  69. package/dist/core/session-board.js.map +1 -1
  70. package/dist/core/session-create.d.ts +38 -0
  71. package/dist/core/session-create.d.ts.map +1 -1
  72. package/dist/core/session-create.js +43 -9
  73. package/dist/core/session-create.js.map +1 -1
  74. package/dist/core/session-manager.d.ts +65 -2
  75. package/dist/core/session-manager.d.ts.map +1 -1
  76. package/dist/core/session-manager.js +285 -68
  77. package/dist/core/session-manager.js.map +1 -1
  78. package/dist/core/session-rename.d.ts +25 -0
  79. package/dist/core/session-rename.d.ts.map +1 -0
  80. package/dist/core/session-rename.js +43 -0
  81. package/dist/core/session-rename.js.map +1 -0
  82. package/dist/core/session-title.d.ts +11 -0
  83. package/dist/core/session-title.d.ts.map +1 -0
  84. package/dist/core/session-title.js +17 -0
  85. package/dist/core/session-title.js.map +1 -0
  86. package/dist/core/trigger-session.d.ts +11 -0
  87. package/dist/core/trigger-session.d.ts.map +1 -1
  88. package/dist/core/trigger-session.js +61 -20
  89. package/dist/core/trigger-session.js.map +1 -1
  90. package/dist/core/types.d.ts +16 -1
  91. package/dist/core/types.d.ts.map +1 -1
  92. package/dist/core/types.js.map +1 -1
  93. package/dist/core/worker-pool.d.ts +6 -2
  94. package/dist/core/worker-pool.d.ts.map +1 -1
  95. package/dist/core/worker-pool.js +48 -15
  96. package/dist/core/worker-pool.js.map +1 -1
  97. package/dist/core/zellij-adopt-discovery.d.ts +17 -0
  98. package/dist/core/zellij-adopt-discovery.d.ts.map +1 -1
  99. package/dist/core/zellij-adopt-discovery.js +24 -4
  100. package/dist/core/zellij-adopt-discovery.js.map +1 -1
  101. package/dist/core/zellij-session-discovery.d.ts +3 -0
  102. package/dist/core/zellij-session-discovery.d.ts.map +1 -1
  103. package/dist/core/zellij-session-discovery.js +1 -0
  104. package/dist/core/zellij-session-discovery.js.map +1 -1
  105. package/dist/daemon.d.ts +3 -0
  106. package/dist/daemon.d.ts.map +1 -1
  107. package/dist/daemon.js +181 -81
  108. package/dist/daemon.js.map +1 -1
  109. package/dist/dashboard/bot-payload.d.ts +1 -0
  110. package/dist/dashboard/bot-payload.d.ts.map +1 -1
  111. package/dist/dashboard/bot-payload.js +1 -0
  112. package/dist/dashboard/bot-payload.js.map +1 -1
  113. package/dist/dashboard/federated-group-core.d.ts +29 -8
  114. package/dist/dashboard/federated-group-core.d.ts.map +1 -1
  115. package/dist/dashboard/federated-group-core.js +68 -6
  116. package/dist/dashboard/federated-group-core.js.map +1 -1
  117. package/dist/dashboard/federation-api.d.ts +10 -9
  118. package/dist/dashboard/federation-api.d.ts.map +1 -1
  119. package/dist/dashboard/federation-api.js +82 -3
  120. package/dist/dashboard/federation-api.js.map +1 -1
  121. package/dist/dashboard/federation-spoke-api.d.ts +8 -9
  122. package/dist/dashboard/federation-spoke-api.d.ts.map +1 -1
  123. package/dist/dashboard/federation-spoke-api.js +7 -2
  124. package/dist/dashboard/federation-spoke-api.js.map +1 -1
  125. package/dist/dashboard/team-group.d.ts +21 -0
  126. package/dist/dashboard/team-group.d.ts.map +1 -1
  127. package/dist/dashboard/team-group.js +16 -0
  128. package/dist/dashboard/team-group.js.map +1 -1
  129. package/dist/dashboard/web/bot-defaults-page.d.ts +10 -0
  130. package/dist/dashboard/web/bot-defaults-page.d.ts.map +1 -1
  131. package/dist/dashboard/web/bot-defaults-page.js +33 -1
  132. package/dist/dashboard/web/bot-defaults-page.js.map +1 -1
  133. package/dist/dashboard/web/bot-defaults.d.ts +1 -0
  134. package/dist/dashboard/web/bot-defaults.d.ts.map +1 -1
  135. package/dist/dashboard/web/bot-defaults.js.map +1 -1
  136. package/dist/dashboard/web/i18n.d.ts.map +1 -1
  137. package/dist/dashboard/web/i18n.js +8 -0
  138. package/dist/dashboard/web/i18n.js.map +1 -1
  139. package/dist/dashboard/web/sessions.d.ts.map +1 -1
  140. package/dist/dashboard/web/sessions.js +5 -1
  141. package/dist/dashboard/web/sessions.js.map +1 -1
  142. package/dist/dashboard/web/ui.d.ts.map +1 -1
  143. package/dist/dashboard/web/ui.js +2 -4
  144. package/dist/dashboard/web/ui.js.map +1 -1
  145. package/dist/dashboard-web/app.js +1 -1
  146. package/dist/dashboard-web/chunks/bot-defaults-page-7T7DPOV6.js +4 -0
  147. package/dist/dashboard-web/chunks/{chunk-SHZ5PUYF.js → chunk-IGXYYRYJ.js} +1 -1
  148. package/dist/dashboard-web/chunks/{chunk-GF7DU2B5.js → chunk-ISOD7VDX.js} +1 -1
  149. package/dist/dashboard-web/chunks/{chunk-TORH53FI.js → chunk-OEJIT66L.js} +1 -1
  150. package/dist/dashboard-web/chunks/{chunk-7UY333A5.js → chunk-PNK3HQBI.js} +2 -2
  151. package/dist/dashboard-web/chunks/{chunk-IAES47WV.js → chunk-YQFXGRSD.js} +1 -1
  152. package/dist/dashboard-web/chunks/{connectors-page-N6GKR6JL.js → connectors-page-UQ4QBE45.js} +1 -1
  153. package/dist/dashboard-web/chunks/{groups-page-IK3GXSQU.js → groups-page-5AJPPJH2.js} +1 -1
  154. package/dist/dashboard-web/chunks/{insights-page-T2PDFPGO.js → insights-page-R3DFECX4.js} +1 -1
  155. package/dist/dashboard-web/chunks/{legacy-workflow-page-YQPLKH74.js → legacy-workflow-page-QMC7OZXH.js} +1 -1
  156. package/dist/dashboard-web/chunks/{monitor-room-7EL3LVJS.js → monitor-room-NLMKJLDY.js} +1 -1
  157. package/dist/dashboard-web/chunks/{monitoring-page-BCADF56L.js → monitoring-page-EHSOX4GV.js} +1 -1
  158. package/dist/dashboard-web/chunks/{office-page-KP7BDLAE.js → office-page-OXJKFJ4R.js} +1 -1
  159. package/dist/dashboard-web/chunks/{overview-page-6GWDHDTI.js → overview-page-GJHIVXJF.js} +1 -1
  160. package/dist/dashboard-web/chunks/{roles-page-2OFR2IBG.js → roles-page-6ZODQISW.js} +1 -1
  161. package/dist/dashboard-web/chunks/{schedules-page-TAWMTSNL.js → schedules-page-DAGMHVCH.js} +1 -1
  162. package/dist/dashboard-web/chunks/{sessions-page-42QZHPPP.js → sessions-page-NNZIDHZM.js} +1 -1
  163. package/dist/dashboard-web/chunks/{settings-page-OVKXUXSV.js → settings-page-LJIXX5LQ.js} +1 -1
  164. package/dist/dashboard-web/chunks/{skills-page-Y2EC3NBI.js → skills-page-J7EVUHXP.js} +1 -1
  165. package/dist/dashboard-web/chunks/{team-federation-page-QYMJUH5F.js → team-federation-page-YM5JBIII.js} +1 -1
  166. package/dist/dashboard-web/chunks/{v3-page-FZJCW5QG.js → v3-page-YLYVHGKD.js} +1 -1
  167. package/dist/dashboard-web/chunks/{whiteboards-page-FBHBBCAL.js → whiteboards-page-75N2U6W4.js} +1 -1
  168. package/dist/dashboard.js +46 -7
  169. package/dist/dashboard.js.map +1 -1
  170. package/dist/i18n/en.d.ts.map +1 -1
  171. package/dist/i18n/en.js +8 -0
  172. package/dist/i18n/en.js.map +1 -1
  173. package/dist/i18n/zh.d.ts.map +1 -1
  174. package/dist/i18n/zh.js +8 -0
  175. package/dist/i18n/zh.js.map +1 -1
  176. package/dist/im/lark/card-handler.d.ts.map +1 -1
  177. package/dist/im/lark/card-handler.js +96 -32
  178. package/dist/im/lark/card-handler.js.map +1 -1
  179. package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
  180. package/dist/im/lark/event-dispatcher.js +16 -5
  181. package/dist/im/lark/event-dispatcher.js.map +1 -1
  182. package/dist/services/bot-config-store.d.ts.map +1 -1
  183. package/dist/services/bot-config-store.js +1 -0
  184. package/dist/services/bot-config-store.js.map +1 -1
  185. package/dist/services/card-prefs-store.d.ts +4 -0
  186. package/dist/services/card-prefs-store.d.ts.map +1 -1
  187. package/dist/services/card-prefs-store.js +8 -0
  188. package/dist/services/card-prefs-store.js.map +1 -1
  189. package/dist/services/default-worktree.d.ts.map +1 -1
  190. package/dist/services/default-worktree.js +4 -1
  191. package/dist/services/default-worktree.js.map +1 -1
  192. package/dist/services/group-creator.d.ts +19 -0
  193. package/dist/services/group-creator.d.ts.map +1 -1
  194. package/dist/services/group-creator.js +62 -28
  195. package/dist/services/group-creator.js.map +1 -1
  196. package/dist/services/groups-store.d.ts +7 -5
  197. package/dist/services/groups-store.d.ts.map +1 -1
  198. package/dist/services/groups-store.js +10 -8
  199. package/dist/services/groups-store.js.map +1 -1
  200. package/dist/setup/ensure-tmux.d.ts +17 -0
  201. package/dist/setup/ensure-tmux.d.ts.map +1 -1
  202. package/dist/setup/ensure-tmux.js +72 -1
  203. package/dist/setup/ensure-tmux.js.map +1 -1
  204. package/dist/types.d.ts +56 -6
  205. package/dist/types.d.ts.map +1 -1
  206. package/dist/utils/child-env.d.ts +27 -0
  207. package/dist/utils/child-env.d.ts.map +1 -1
  208. package/dist/utils/child-env.js +59 -0
  209. package/dist/utils/child-env.js.map +1 -1
  210. package/dist/utils/codex-app-context.d.ts +9 -0
  211. package/dist/utils/codex-app-context.d.ts.map +1 -0
  212. package/dist/utils/codex-app-context.js +47 -0
  213. package/dist/utils/codex-app-context.js.map +1 -0
  214. package/dist/utils/pending-input-queue.d.ts +2 -0
  215. package/dist/utils/pending-input-queue.d.ts.map +1 -1
  216. package/dist/utils/pending-input-queue.js +6 -0
  217. package/dist/utils/pending-input-queue.js.map +1 -1
  218. package/dist/worker.js +197 -38
  219. package/dist/worker.js.map +1 -1
  220. package/package.json +1 -1
  221. package/dist/dashboard-web/chunks/bot-defaults-page-QXZ2VFJJ.js +0 -4
package/dist/daemon.js CHANGED
@@ -36,6 +36,7 @@ import { delay } from './utils/timing.js';
36
36
  import { BoundedMap } from './utils/bounded-map.js';
37
37
  import { checkAllowedChatGroupsConfig } from './services/allowed-chat-groups.js';
38
38
  import { ensureCjkFontsInstalled } from './utils/font-installer.js';
39
+ import { scrubTmuxServerGlobalEnv } from './setup/ensure-tmux.js';
39
40
  import { invalidWorkingDirs } from './utils/working-dir.js';
40
41
  import { validateWorkingDir } from './core/working-dir.js';
41
42
  import { sessionKey, sessionAnchorId } from './core/types.js';
@@ -48,18 +49,19 @@ import { isLocalCliOpenReady } from './services/local-cli-opener.js';
48
49
  import { RECEIVED_REACTION_EMOJI_TYPE, SUBSTITUTE_RECEIVED_REACTION_EMOJI_TYPE } from './core/pending-response.js';
49
50
  import { t as tr, botLocale, localeForBot } from './i18n/index.js';
50
51
  import { createCliAdapterSync } from './adapters/cli/registry.js';
51
- import { initWorkerPool, setActiveSessionsRegistry, forkWorker, killWorker, reapOrphanWorkers, scheduleCardPatch, setCurrentCliVersion, getCurrentCliVersion, CARD_POSTING_SENTINEL, parkStreamCard, closeSession as closeSessionHelper, ensureCliEnv, sweepGlobalBotmuxSkills, writableTerminalLinkFor, } from './core/worker-pool.js';
52
+ import { initWorkerPool, setActiveSessionsRegistry, forkWorker, sendWorkerInput, killWorker, reapOrphanWorkers, scheduleCardPatch, setCurrentCliVersion, getCurrentCliVersion, CARD_POSTING_SENTINEL, parkStreamCard, closeSession as closeSessionHelper, ensureCliEnv, sweepGlobalBotmuxSkills, writableTerminalLinkFor, } from './core/worker-pool.js';
52
53
  import { ipcRoute, jsonRes, readJsonBody, setBotName, setLarkAppId, startIpcServer, setWorkflowRunner, setBotRenamer } from './core/dashboard-ipc-server.js';
53
54
  import { saveFrozenCards } from './services/frozen-card-store.js';
54
- import { DAEMON_COMMANDS, SESSIONLESS_DAEMON_COMMANDS, resolvePassthroughCommands, resolveAdapterDefaultPassthroughCommands, handleCommand, handleCardCommand, handleTermLinkCommand, parseSlashCommandInvocation, parseForceTopicInvocation } from './core/command-handler.js';
55
+ import { DAEMON_COMMANDS, SESSIONLESS_DAEMON_COMMANDS, EXISTING_SESSION_ONLY_DAEMON_COMMANDS, resolvePassthroughCommands, resolveAdapterDefaultPassthroughCommands, handleCommand, handleCardCommand, handleTermLinkCommand, parseSlashCommandInvocation, parseForceTopicInvocation } from './core/command-handler.js';
55
56
  import { docWatchCommandNeedsSession } from './core/doc-watch-command.js';
56
57
  import { SLASH_COMMAND_SHAPE } from './core/passthrough-commands.js';
57
58
  import { findInheritablePeer } from './core/inherit-peer.js';
58
59
  import { isCallbackUrl, handleCallbackUrl } from './utils/user-token.js';
59
60
  import { consumeQuota, removeChatGrant, removeGlobalGrant } from './services/grant-store.js';
60
61
  import { abortCharge, commitCharge, beginCharge } from './services/quota-dedup.js';
61
- import { getSessionWorkingDir, getProjectScanDirs, expandHome, downloadResources, formatAttachmentsHint, buildNewTopicPrompt, buildFollowUpContent, buildBridgeInputContent, buildReforkPrompt, getAvailableBots, restoreActiveSessions, executeScheduledTask, persistStreamCardState, rememberLastCliInput, ensureTerminalWorkerPort, ensureSessionWhiteboard, } from './core/session-manager.js';
62
+ import { getSessionWorkingDir, getProjectScanDirs, expandHome, downloadResources, formatAttachmentsHint, buildNewTopicCliInput, buildFollowUpCliInput, buildBridgeInputContent, buildReforkCliInput, getAvailableBots, restoreActiveSessions, executeScheduledTask, persistStreamCardState, rememberLastCliInput, ensureTerminalWorkerPort, ensureSessionWhiteboard, } from './core/session-manager.js';
62
63
  import { triggerSessionTurn } from './core/trigger-session.js';
64
+ import { applyQueuedCodexAppLegacyFallback, mergeQueuedCodexAppTurn } from './core/session-create.js';
63
65
  import { findOnlineDaemon, listOnlineDaemons } from './utils/daemon-discovery.js';
64
66
  import { beginReplyTargetTurn, fallbackTurnId, resolveSessionReplyTarget, syncReplyTargetState } from './core/reply-target.js';
65
67
  import { sweepOrphanSandboxes } from './adapters/backend/sandbox.js';
@@ -85,7 +87,7 @@ import { getDocSubscription, listAllDocSubscriptions, removeDocSubscription, set
85
87
  import { BOT_REPLY_SENTINEL, subscribeDocFile, unsubscribeDocFile, addCommentReaction, hasBotSentinel, isBotAuthoredReply, listDocComments } from './im/lark/doc-comment.js';
86
88
  import { learnFromMentions, resolveSender, flushIdentityCacheSync } from './im/lark/identity-cache.js';
87
89
  import { normalizeBrand } from './im/lark/lark-hosts.js';
88
- import { buildDocCommentPrompt, buildDocWatchWarmupPrompt } from './core/doc-comment-prompt.js';
90
+ import { buildDocCommentTurnInput, buildDocWatchWarmupTurnInput } from './core/doc-comment-prompt.js';
89
91
  import { advanceDocCommentCursor, docCommentRepliesAfterCursor, latestDocCommentPollCursor } from './core/doc-comment-poller.js';
90
92
  import { renderBufferedSenderBlock } from './core/session-manager.js';
91
93
  import { shutdownBackendDisposition } from './core/persistent-backend.js';
@@ -1764,12 +1766,12 @@ async function prewarmDocCommentSession(ds, sub) {
1764
1766
  const bot = getBot(ds.larkAppId);
1765
1767
  const botCfg = bot.config;
1766
1768
  const loc = localeForBot(ds.larkAppId);
1767
- const promptContent = buildDocWatchWarmupPrompt({
1769
+ const warmupInput = {
1768
1770
  fileToken: sub.fileToken,
1769
1771
  fileType: sub.fileType,
1770
1772
  brand: normalizeBrand(botCfg.brand),
1771
1773
  locale: loc,
1772
- });
1774
+ };
1773
1775
  const title = `[Doc Watch] ${sub.fileToken.slice(0, 12)}`;
1774
1776
  const turnId = `doc-watch-${Date.now()}-${sub.fileToken.slice(0, 8)}`;
1775
1777
  const sender = sub.ownerOpenId ? await resolveSender(ds.larkAppId, sub.ownerOpenId, 'user') : undefined;
@@ -1779,34 +1781,39 @@ async function prewarmDocCommentSession(ds, sub) {
1779
1781
  if (sub.workingDir && (!ds.worker || ds.worker.killed)) {
1780
1782
  ds.workingDir = sub.workingDir;
1781
1783
  ds.session.workingDir = sub.workingDir;
1784
+ // An explicit doc-watch cwd replaces the previous repo selection. Keeping
1785
+ // a multi-Riff stamp would make the cold refork ignore this new directory.
1786
+ ds.session.riffRepoDirs = undefined;
1782
1787
  }
1783
1788
  if (ds.worker && !ds.worker.killed) {
1784
1789
  ensureSessionWhiteboard(ds);
1785
- const msgContent = buildFollowUpContent(promptContent, ds.session.sessionId, {
1786
- isAdoptMode: false,
1787
- cliId: botCfg.cliId,
1788
- cliPathOverride: botCfg.cliPathOverride,
1790
+ const { promptContent, cliInput } = buildDocWatchWarmupTurnInput({
1791
+ ds,
1792
+ promptInput: warmupInput,
1793
+ botCliId: botCfg.cliId,
1794
+ botCliPathOverride: botCfg.cliPathOverride,
1789
1795
  sender,
1790
- larkAppId: ds.larkAppId,
1791
- chatId: ds.session.chatId,
1792
- whiteboardId: ds.session.whiteboardId,
1796
+ mode: 'live',
1793
1797
  });
1794
- rememberLastCliInput(ds, promptContent, msgContent);
1798
+ rememberLastCliInput(ds, promptContent, cliInput);
1795
1799
  sessionStore.updateSession(ds.session);
1796
- ds.worker.send({ type: 'message', content: msgContent, turnId });
1800
+ sendWorkerInput(ds, cliInput, turnId);
1797
1801
  markSessionActivity(ds);
1798
1802
  }
1799
1803
  else {
1800
1804
  ensureSessionWhiteboard(ds);
1801
- const wrappedPrompt = buildReforkPrompt(ds, promptContent, {
1802
- cliId: botCfg.cliId,
1803
- cliPathOverride: botCfg.cliPathOverride,
1804
- selfMention: { name: bot.botName, openId: bot.botOpenId },
1805
+ const { promptContent, cliInput: wrappedInput } = buildDocWatchWarmupTurnInput({
1806
+ ds,
1807
+ promptInput: warmupInput,
1808
+ botCliId: botCfg.cliId,
1809
+ botCliPathOverride: botCfg.cliPathOverride,
1810
+ botIdentity: { name: bot.botName, openId: bot.botOpenId },
1805
1811
  sender,
1812
+ mode: 'refork',
1806
1813
  });
1807
- rememberLastCliInput(ds, promptContent, wrappedPrompt);
1814
+ rememberLastCliInput(ds, promptContent, wrappedInput);
1808
1815
  sessionStore.updateSession(ds.session);
1809
- forkWorker(ds, wrappedPrompt, ds.hasHistory);
1816
+ forkWorker(ds, wrappedInput, ds.hasHistory);
1810
1817
  }
1811
1818
  logger.info(`[${tag(ds)}] doc-comment watch prewarm injected file=${sub.fileToken.slice(0, 12)}`);
1812
1819
  }
@@ -5342,6 +5349,12 @@ async function resolvePinnedWorkingDir(ctx) {
5342
5349
  return { pinnedWorkingDir, oncallEntry, inheritedFrom, pinnedFromBotDefault };
5343
5350
  }
5344
5351
  export const __testOnly_resolvePinnedWorkingDir = resolvePinnedWorkingDir;
5352
+ // Production message routes (function declarations hoist, so the references
5353
+ // are valid here). Exposed for route-level regression tests — e.g. asserting
5354
+ // that `/rename` in a fresh topic/thread does NOT pre-create a phantom session,
5355
+ // which unit tests calling handleCommand directly can never catch.
5356
+ export const __testOnly_handleNewTopic = (data, ctx) => handleNewTopic(data, ctx);
5357
+ export const __testOnly_handleThreadReply = (data, ctx) => handleThreadReply(data, ctx);
5345
5358
  /**
5346
5359
  * 该新会话是否要走「仅默认目录 + 自动建 worktree」:pinned dir 来自本 bot 自己的
5347
5360
  * defaultWorkingDir (layer 3) 且开关打开。为真时,spawn 路径不再同步 fork,而是把会话登记
@@ -5663,6 +5676,17 @@ async function handleNewTopic(data, ctx) {
5663
5676
  fireSessionlessCommandDetached(cmd, anchor, { ...parsed, content: commandContent, chatId }, larkAppId);
5664
5677
  return;
5665
5678
  }
5679
+ // `/rename` renames an EXISTING session; a brand-new topic has none. Route
5680
+ // straight to handleCommand (its `!ds` branch replies no_active_session)
5681
+ // so the pre-create block below doesn't spawn a worker:null phantom
5682
+ // session just to rename it. Same phantom-session concern as the /card
5683
+ // and /term special cases, but UNLIKE those (which carry their own
5684
+ // permission gates inside their handlers) this branch MUST stay after
5685
+ // the canOperate gate above — the /rename handler itself has no gate.
5686
+ if (EXISTING_SESSION_ONLY_DAEMON_COMMANDS.has(cmd)) {
5687
+ await handleCommand(cmd, anchor, { ...parsed, content: commandContent }, commandDeps, larkAppId);
5688
+ return;
5689
+ }
5666
5690
  // Same rootMessageId reasoning as below in the main spawn path:
5667
5691
  // thread-scope MUST anchor on the thread root or sessionAnchorId() will
5668
5692
  // disagree with activeSessions's key and downstream card buttons silently
@@ -5735,7 +5759,8 @@ async function handleNewTopic(data, ctx) {
5735
5759
  // weight on first turns. `content` (post force-topic-strip) is what the
5736
5760
  // worker will see; promptContent wraps it for prompt-building paths but
5737
5761
  // leaves `content` untouched for title / log substring uses.
5738
- const promptContent = buildQuoteHint(parsed, scope, anchor, localeForBot(larkAppId)) + content;
5762
+ const codexAppMessageContext = buildQuoteHint(parsed, scope, anchor, localeForBot(larkAppId));
5763
+ const promptContent = codexAppMessageContext + content;
5739
5764
  // Resolve sender identity for <sender> tag injection. The first call to
5740
5765
  // resolveSender for an unseen open_id may await contact.v3.user.get with a
5741
5766
  // short budget; subsequent calls hit the cache and are sync-fast.
@@ -5792,6 +5817,8 @@ async function handleNewTopic(data, ctx) {
5792
5817
  hasHistory: false,
5793
5818
  pendingRepo: !pinnedWorkingDir || autoWt,
5794
5819
  pendingPrompt: promptContent,
5820
+ pendingCodexAppText: content,
5821
+ pendingCodexAppMessageContext: codexAppMessageContext,
5795
5822
  pendingAttachments: attachments.length > 0 ? attachments : undefined,
5796
5823
  pendingMentions: parsed.mentions,
5797
5824
  pendingSubstituteTrigger: substituteTrigger,
@@ -5822,9 +5849,9 @@ async function handleNewTopic(data, ctx) {
5822
5849
  return;
5823
5850
  const selfBot = getBot(larkAppId);
5824
5851
  ensureSessionWhiteboard(ds);
5825
- const prompt = buildNewTopicPrompt(promptContent, session.sessionId, botCfg.cliId, botCfg.cliPathOverride, attachments, parsed.mentions, await getAvailableBots(larkAppId, chatId), undefined, { name: selfBot.botName, openId: selfBot.botOpenId }, localeForBot(larkAppId), newTopicSender, { larkAppId, chatId, whiteboardId: ds.session.whiteboardId, substituteTrigger });
5826
- rememberLastCliInput(ds, promptContent, prompt);
5852
+ const prompt = buildNewTopicCliInput(promptContent, session.sessionId, botCfg.cliId, botCfg.cliPathOverride, attachments, parsed.mentions, await getAvailableBots(larkAppId, chatId), undefined, { name: selfBot.botName, openId: selfBot.botOpenId }, localeForBot(larkAppId), newTopicSender, { larkAppId, chatId, whiteboardId: ds.session.whiteboardId, substituteTrigger, codexAppText: content, codexAppMessageContext });
5827
5853
  await noteTurnReceived(ds, messageId, content, newTopicSender, messageId, substituteTrigger ? SUBSTITUTE_RECEIVED_REACTION_EMOJI_TYPE : undefined);
5854
+ rememberLastCliInput(ds, promptContent, prompt);
5828
5855
  forkWorker(ds, prompt);
5829
5856
  const reason = oncallEntry
5830
5857
  ? `oncall-bound chat ${chatId}`
@@ -5855,9 +5882,9 @@ async function handleNewTopic(data, ctx) {
5855
5882
  ds.pendingRepo = false;
5856
5883
  const selfBot = getBot(larkAppId);
5857
5884
  ensureSessionWhiteboard(ds);
5858
- const prompt = buildNewTopicPrompt(promptContent, session.sessionId, botCfg.cliId, botCfg.cliPathOverride, attachments, parsed.mentions, await getAvailableBots(larkAppId, chatId), undefined, { name: selfBot.botName, openId: selfBot.botOpenId }, localeForBot(larkAppId), newTopicSender, { larkAppId, chatId, whiteboardId: ds.session.whiteboardId, substituteTrigger });
5859
- rememberLastCliInput(ds, promptContent, prompt);
5885
+ const prompt = buildNewTopicCliInput(promptContent, session.sessionId, botCfg.cliId, botCfg.cliPathOverride, attachments, parsed.mentions, await getAvailableBots(larkAppId, chatId), undefined, { name: selfBot.botName, openId: selfBot.botOpenId }, localeForBot(larkAppId), newTopicSender, { larkAppId, chatId, whiteboardId: ds.session.whiteboardId, substituteTrigger, codexAppText: content, codexAppMessageContext });
5860
5886
  await noteTurnReceived(ds, messageId, content, newTopicSender, messageId, substituteTrigger ? SUBSTITUTE_RECEIVED_REACTION_EMOJI_TYPE : undefined);
5887
+ rememberLastCliInput(ds, promptContent, prompt);
5861
5888
  forkWorker(ds, prompt);
5862
5889
  logger.info(`Session ${session.sessionId} ready (no projects to select), total active: ${getActiveCount()}`);
5863
5890
  }
@@ -5963,6 +5990,11 @@ async function handleBotAdded(chatId, operatorOpenId, larkAppId) {
5963
5990
  const mode = await getChatMode(larkAppId, chatId, { forceRefresh: true });
5964
5991
  const promptBody = resolveGroupJoinPrompt(botCfg.autoStartOnGroupJoinPrompt);
5965
5992
  const title = (promptBody || tr('daemon.auto_start_join_title', undefined, localeForBot(larkAppId))).substring(0, 50);
5993
+ // D8 deliberately keeps the legacy prompt empty when no join prompt is
5994
+ // configured, so the agent can inspect group context without receiving a
5995
+ // synthetic instruction. Codex App still needs a non-blank visible
5996
+ // UserMessage; reuse the localized session title only in its clean sidecar.
5997
+ const codexAppText = promptBody || title;
5966
5998
  // Pick scope + anchor. 话题群 → seed a topic and anchor thread-scope there;
5967
5999
  // 普通群 → chat-scope anchored at chatId.
5968
6000
  let scope;
@@ -6010,6 +6042,7 @@ async function handleBotAdded(chatId, operatorOpenId, larkAppId) {
6010
6042
  hasHistory: false,
6011
6043
  pendingRepo: !pinnedWorkingDir || autoWt,
6012
6044
  pendingPrompt: promptBody,
6045
+ pendingCodexAppText: botCfg.cliId === 'codex-app' ? codexAppText : undefined,
6013
6046
  ownerOpenId: operatorOpenId,
6014
6047
  currentTurnTitle: title,
6015
6048
  workingDir: pinnedWorkingDir,
@@ -6019,7 +6052,7 @@ async function handleBotAdded(chatId, operatorOpenId, larkAppId) {
6019
6052
  // even in 话题群 (where dsKey is the seed id, not chatId).
6020
6053
  groupJoinAnchorByChat.set(chatLiveKey, dsKey);
6021
6054
  const selfBot = getBot(larkAppId);
6022
- const buildPrompt = async () => buildNewTopicPrompt(promptBody, session.sessionId, botCfg.cliId, botCfg.cliPathOverride, undefined, undefined, await getAvailableBots(larkAppId, chatId), undefined, { name: selfBot.botName, openId: selfBot.botOpenId }, localeForBot(larkAppId), undefined, { larkAppId, chatId, whiteboardId: ds.session.whiteboardId });
6055
+ const buildPrompt = async () => buildNewTopicCliInput(promptBody, session.sessionId, botCfg.cliId, botCfg.cliPathOverride, undefined, undefined, await getAvailableBots(larkAppId, chatId), undefined, { name: selfBot.botName, openId: selfBot.botOpenId }, localeForBot(larkAppId), undefined, { larkAppId, chatId, whiteboardId: ds.session.whiteboardId, codexAppText });
6023
6056
  // Auto-worktree: register PENDING, build worktree off-path, commit+fork later.
6024
6057
  if (pinnedWorkingDir && autoWt) {
6025
6058
  if (await replyInvalidWorkingDirs(anchor, larkAppId, ds))
@@ -6033,8 +6066,8 @@ async function handleBotAdded(chatId, operatorOpenId, larkAppId) {
6033
6066
  return;
6034
6067
  ensureSessionWhiteboard(ds);
6035
6068
  const prompt = await buildPrompt();
6036
- rememberLastCliInput(ds, promptBody, prompt);
6037
6069
  await noteTurnReceived(ds, anchor, promptBody);
6070
+ rememberLastCliInput(ds, promptBody, prompt);
6038
6071
  forkWorker(ds, prompt);
6039
6072
  logger.info(`[auto-start:入群] ${chatId.substring(0, 12)} 自动开工(${mode}/${scope}),workingDir=${pinnedWorkingDir}`);
6040
6073
  return;
@@ -6055,8 +6088,8 @@ async function handleBotAdded(chatId, operatorOpenId, larkAppId) {
6055
6088
  ds.pendingRepo = false;
6056
6089
  ensureSessionWhiteboard(ds);
6057
6090
  const prompt = await buildPrompt();
6058
- rememberLastCliInput(ds, promptBody, prompt);
6059
6091
  await noteTurnReceived(ds, anchor, promptBody);
6092
+ rememberLastCliInput(ds, promptBody, prompt);
6060
6093
  forkWorker(ds, prompt);
6061
6094
  logger.info(`[auto-start:入群] ${chatId.substring(0, 12)} 无默认目录且无可选项目,直接开工`);
6062
6095
  }
@@ -6136,7 +6169,9 @@ async function handleThreadReply(data, ctx) {
6136
6169
  : '';
6137
6170
  // `let` (not const): the v3 grill gate below may replace this with a
6138
6171
  // skill-trigger prompt when the user sends `/workflow [new] <目标>` mid-thread.
6139
- let promptContent = buildQuoteHint(parsed, scope, anchor, localeForBot(larkAppId)) + botSenderPrefix + parsed.content;
6172
+ const initialCodexAppMessageContext = buildQuoteHint(parsed, scope, anchor, localeForBot(larkAppId)) + botSenderPrefix;
6173
+ const initialPromptContent = initialCodexAppMessageContext + parsed.content;
6174
+ let promptContent = initialPromptContent;
6140
6175
  const existingHookSession = activeSessions.get(sessionKey(anchor, larkAppId));
6141
6176
  emitHookEvent('thread.reply', {
6142
6177
  larkAppId,
@@ -6340,8 +6375,12 @@ async function handleThreadReply(data, ctx) {
6340
6375
  // Without a session, handleCommand gets ds=undefined and `/repo` (and other
6341
6376
  // session commands) fall through to the repo-select card. Create the session
6342
6377
  // first, mirroring handleNewTopic's first-message `/repo` pendingRepo setup.
6343
- // Session-less commands (/group /g) don't need one.
6344
- if (!existingDs && threadChatId && !isSessionlessCommandInvocation(cmd, commandContent)) {
6378
+ // Session-less commands (/group /g) don't need one; existing-session-only
6379
+ // commands (/rename) must NOT get one — a pre-created worker:null session
6380
+ // would be a phantom conversation that only exists to be renamed. Let
6381
+ // handleCommand's `!ds` branch reply no_active_session instead.
6382
+ if (!existingDs && threadChatId && !isSessionlessCommandInvocation(cmd, commandContent)
6383
+ && !EXISTING_SESSION_ONLY_DAEMON_COMMANDS.has(cmd)) {
6345
6384
  const session = sessionStore.createSession(threadChatId, anchor, cmdContent.substring(0, 50), ctxChatType);
6346
6385
  const now = Date.now();
6347
6386
  if (ctxChatType === 'p2p') {
@@ -6442,6 +6481,13 @@ async function handleThreadReply(data, ctx) {
6442
6481
  if (!await enforceMessageQuotaForCliInput(larkAppId, ctxChatId ?? data?.message?.chat_id, quotaSenderOpenId, parsed.messageId, anchor, threadTeamTrustUnionId, threadSenderUnionId)) {
6443
6482
  return;
6444
6483
  }
6484
+ // When a command path rewrites the model prompt (for example /workflow),
6485
+ // keep the Lark-authored bytes visible and move the rewritten instruction
6486
+ // into hidden untrusted context. Simple quote/bot prefixes use only the
6487
+ // prefix as context, avoiding a duplicate copy of the user text.
6488
+ const codexAppMessageContext = promptContent === initialPromptContent
6489
+ ? initialCodexAppMessageContext
6490
+ : promptContent;
6445
6491
  // Download attachments
6446
6492
  const effectiveAppId = ds?.larkAppId ?? larkAppId;
6447
6493
  const { attachments, needLogin } = await downloadResources(effectiveAppId, parsed.messageId, resources);
@@ -6472,15 +6518,23 @@ async function handleThreadReply(data, ctx) {
6472
6518
  // If waiting for repo selection, buffer the message and remind user
6473
6519
  if (ds?.pendingRepo) {
6474
6520
  // Enrich content with attachment hints and mention metadata (same as normal send)
6475
- let enriched = attachments.length > 0
6476
- ? `${promptContent}${formatAttachmentsHint(attachments)}`
6521
+ const codexAppFollowUpContextParts = [];
6522
+ if (codexAppMessageContext)
6523
+ codexAppFollowUpContextParts.push(codexAppMessageContext);
6524
+ const attachmentHint = attachments.length > 0 ? formatAttachmentsHint(attachments) : '';
6525
+ let enriched = attachmentHint
6526
+ ? `${promptContent}${attachmentHint}`
6477
6527
  : promptContent;
6528
+ if (attachmentHint)
6529
+ codexAppFollowUpContextParts.push(attachmentHint);
6478
6530
  if (parsed.mentions && parsed.mentions.length > 0) {
6479
6531
  const mentionLines = parsed.mentions.map(m => {
6480
6532
  const idPart = m.openId ? ` → open_id: ${m.openId}` : '';
6481
6533
  return `- @${m.name}${idPart}`;
6482
6534
  });
6483
- enriched += `\n\n${tr('daemon.enriched_mentions_label', undefined, localeForBot(larkAppId))}\n${mentionLines.join('\n')}`;
6535
+ const mentionContext = `${tr('daemon.enriched_mentions_label', undefined, localeForBot(larkAppId))}\n${mentionLines.join('\n')}`;
6536
+ enriched += `\n\n${mentionContext}`;
6537
+ codexAppFollowUpContextParts.push(mentionContext);
6484
6538
  }
6485
6539
  // Stamp a buffered follow-up with its own <sender> tag ONLY when it comes
6486
6540
  // from a different user than the first message (ds.pendingSender) — the
@@ -6496,12 +6550,20 @@ async function handleThreadReply(data, ctx) {
6496
6550
  // guard (the builder's own note only covers ds.pendingSender's top-level
6497
6551
  // tag, and is absent entirely when pendingSender is undefined).
6498
6552
  const followUpSenderBlock = renderBufferedSenderBlock(followUpSender, getBot(larkAppId).config.cliId, localeForBot(larkAppId));
6499
- if (followUpSenderBlock)
6553
+ if (followUpSenderBlock) {
6500
6554
  enriched = `${followUpSenderBlock}\n${enriched}`;
6555
+ codexAppFollowUpContextParts.unshift(followUpSenderBlock);
6556
+ }
6501
6557
  }
6502
6558
  if (!ds.pendingFollowUps)
6503
6559
  ds.pendingFollowUps = [];
6504
6560
  ds.pendingFollowUps.push(enriched);
6561
+ if (!ds.pendingCodexAppFollowUps)
6562
+ ds.pendingCodexAppFollowUps = [];
6563
+ ds.pendingCodexAppFollowUps.push(parsed.content);
6564
+ if (!ds.pendingCodexAppFollowUpContexts)
6565
+ ds.pendingCodexAppFollowUpContexts = [];
6566
+ ds.pendingCodexAppFollowUpContexts.push(codexAppFollowUpContextParts.join('\n\n'));
6505
6567
  // Auto-worktree pending (worktreeCreating) has no repo card to point at — the
6506
6568
  // message IS buffered (folded on commit), so just say "hold on, building worktree"
6507
6569
  // instead of the misleading "pick a repo from the card above".
@@ -6583,6 +6645,8 @@ async function handleThreadReply(data, ctx) {
6583
6645
  hasHistory: false,
6584
6646
  pendingRepo: !pinnedWorkingDir || autoWt,
6585
6647
  pendingPrompt: promptContent,
6648
+ pendingCodexAppText: parsed.content,
6649
+ pendingCodexAppMessageContext: codexAppMessageContext,
6586
6650
  pendingAttachments: attachments.length > 0 ? attachments : undefined,
6587
6651
  pendingMentions: parsed.mentions,
6588
6652
  pendingSubstituteTrigger: substituteTrigger,
@@ -6612,9 +6676,9 @@ async function handleThreadReply(data, ctx) {
6612
6676
  return;
6613
6677
  const selfBot = getBot(larkAppId);
6614
6678
  ensureSessionWhiteboard(newDs);
6615
- const prompt = buildNewTopicPrompt(promptContent, session.sessionId, botCfg.cliId, botCfg.cliPathOverride, attachments, parsed.mentions, await getAvailableBots(larkAppId, autoCreateChatId), undefined, { name: selfBot.botName, openId: selfBot.botOpenId }, localeForBot(larkAppId), autoCreateSender, { larkAppId, chatId: autoCreateChatId, whiteboardId: newDs.session.whiteboardId, substituteTrigger });
6616
- rememberLastCliInput(newDs, promptContent, prompt);
6679
+ const prompt = buildNewTopicCliInput(promptContent, session.sessionId, botCfg.cliId, botCfg.cliPathOverride, attachments, parsed.mentions, await getAvailableBots(larkAppId, autoCreateChatId), undefined, { name: selfBot.botName, openId: selfBot.botOpenId }, localeForBot(larkAppId), autoCreateSender, { larkAppId, chatId: autoCreateChatId, whiteboardId: newDs.session.whiteboardId, substituteTrigger, codexAppText: parsed.content, codexAppMessageContext });
6617
6680
  await noteTurnReceived(newDs, parsed.messageId, parsed.content, autoCreateSender, parsed.messageId, substituteTrigger ? SUBSTITUTE_RECEIVED_REACTION_EMOJI_TYPE : undefined);
6681
+ rememberLastCliInput(newDs, promptContent, prompt);
6618
6682
  forkWorker(newDs, prompt);
6619
6683
  const reason = oncallEntry
6620
6684
  ? `oncall-bound chat ${autoCreateChatId}`
@@ -6645,9 +6709,9 @@ async function handleThreadReply(data, ctx) {
6645
6709
  newDs.pendingRepo = false;
6646
6710
  const selfBot = getBot(larkAppId);
6647
6711
  ensureSessionWhiteboard(newDs);
6648
- const prompt = buildNewTopicPrompt(promptContent, session.sessionId, botCfg.cliId, botCfg.cliPathOverride, attachments, parsed.mentions, await getAvailableBots(larkAppId, autoCreateChatId), undefined, { name: selfBot.botName, openId: selfBot.botOpenId }, localeForBot(larkAppId), autoCreateSender, { larkAppId, chatId: autoCreateChatId, whiteboardId: newDs.session.whiteboardId, substituteTrigger });
6649
- rememberLastCliInput(newDs, promptContent, prompt);
6712
+ const prompt = buildNewTopicCliInput(promptContent, session.sessionId, botCfg.cliId, botCfg.cliPathOverride, attachments, parsed.mentions, await getAvailableBots(larkAppId, autoCreateChatId), undefined, { name: selfBot.botName, openId: selfBot.botOpenId }, localeForBot(larkAppId), autoCreateSender, { larkAppId, chatId: autoCreateChatId, whiteboardId: newDs.session.whiteboardId, substituteTrigger, codexAppText: parsed.content, codexAppMessageContext });
6650
6713
  await noteTurnReceived(newDs, parsed.messageId, parsed.content, autoCreateSender, parsed.messageId, substituteTrigger ? SUBSTITUTE_RECEIVED_REACTION_EMOJI_TYPE : undefined);
6714
+ rememberLastCliInput(newDs, promptContent, prompt);
6651
6715
  forkWorker(newDs, prompt);
6652
6716
  }
6653
6717
  return;
@@ -6667,28 +6731,31 @@ async function handleThreadReply(data, ctx) {
6667
6731
  const selfBot = getBot(ds.larkAppId);
6668
6732
  if (!isBridge)
6669
6733
  ensureSessionWhiteboard(ds);
6670
- const msgContent = isBridge
6671
- ? buildBridgeInputContent(promptContent, {
6672
- attachments,
6673
- mentions: parsed.mentions,
6674
- selfMention: { name: selfBot.botName, openId: selfBot.botOpenId },
6675
- })
6676
- : buildFollowUpContent(promptContent, ds.session.sessionId, {
6734
+ const effectiveCliId = ds.session.cliId ?? dsBotCfgForMsg.cliId;
6735
+ const cliInput = isBridge
6736
+ ? { content: buildBridgeInputContent(promptContent, {
6737
+ attachments,
6738
+ mentions: parsed.mentions,
6739
+ selfMention: { name: selfBot.botName, openId: selfBot.botOpenId },
6740
+ }) }
6741
+ : buildFollowUpCliInput(promptContent, ds.session.sessionId, {
6677
6742
  attachments,
6678
6743
  mentions: parsed.mentions,
6679
6744
  isAdoptMode: false,
6680
- cliId: dsBotCfgForMsg.cliId,
6681
- cliPathOverride: dsBotCfgForMsg.cliPathOverride,
6745
+ cliId: effectiveCliId,
6746
+ cliPathOverride: ds.session.cliPathOverride ?? dsBotCfgForMsg.cliPathOverride,
6682
6747
  sender: await getThreadSender(),
6683
6748
  larkAppId,
6684
6749
  chatId: ds.session.chatId,
6685
6750
  whiteboardId: ds.session.whiteboardId,
6686
6751
  substituteTrigger,
6752
+ codexAppText: parsed.content,
6753
+ codexAppMessageContext,
6687
6754
  });
6688
6755
  beginNewTurn(ds, parsed.content);
6689
- rememberLastCliInput(ds, promptContent, msgContent);
6690
6756
  await noteTurnReceived(ds, parsed.messageId, parsed.content, await getThreadSender(), parsed.messageId, substituteTrigger ? SUBSTITUTE_RECEIVED_REACTION_EMOJI_TYPE : undefined);
6691
- ds.worker.send({ type: 'message', content: msgContent, turnId: parsed.messageId });
6757
+ rememberLastCliInput(ds, promptContent, cliInput);
6758
+ sendWorkerInput(ds, cliInput, parsed.messageId);
6692
6759
  }
6693
6760
  else {
6694
6761
  // Worker not running — re-fork with resume. This is a NEW turn, so drop
@@ -6739,21 +6806,44 @@ async function handleThreadReply(data, ctx) {
6739
6806
  // 待办池(queued)会话:CLI 从没起过,暂存的任务内容(queuedPrompt,已按角色包装好)
6740
6807
  // 必须当首轮发出去——否则群里来的这第一条消息会顶替掉它、把用户分配的任务丢掉。
6741
6808
  // 把暂存任务前置、用户这条消息拼在后面,一并作为首轮。forkWorker 随后清 queued。
6742
- const reforkContent = ds.session.queued && ds.session.queuedPrompt
6809
+ const queuedDashboardTurn = !!(ds.session.queued && ds.session.queuedPrompt);
6810
+ const reforkContent = queuedDashboardTurn
6743
6811
  ? `${ds.session.queuedPrompt}\n\n${promptContent}`
6744
6812
  : promptContent;
6745
- const wrappedPrompt = buildReforkPrompt(ds, reforkContent, {
6813
+ const queuedCodexAppText = ds.session.queuedCodexAppText ?? ds.pendingCodexAppText;
6814
+ const reforkCodexApp = mergeQueuedCodexAppTurn({
6815
+ queued: queuedDashboardTurn,
6816
+ queuedText: queuedCodexAppText,
6817
+ queuedMessageContext: ds.session.queuedCodexAppMessageContext ?? ds.pendingCodexAppMessageContext,
6818
+ currentText: parsed.content,
6819
+ currentMessageContext: codexAppMessageContext,
6820
+ });
6821
+ const builtReforkInput = buildReforkCliInput(ds, reforkContent, {
6746
6822
  attachments,
6747
6823
  mentions: parsed.mentions,
6748
- cliId: dsBotCfgForFork.cliId,
6749
- cliPathOverride: dsBotCfgForFork.cliPathOverride,
6824
+ cliId: ds.session.cliId ?? dsBotCfgForFork.cliId,
6825
+ cliPathOverride: ds.session.cliPathOverride ?? dsBotCfgForFork.cliPathOverride,
6750
6826
  selfMention: { name: selfBot.botName, openId: selfBot.botOpenId },
6751
6827
  sender: await getThreadSender(),
6828
+ substituteTrigger,
6829
+ codexAppText: reforkCodexApp.text,
6830
+ codexAppMessageContext: reforkCodexApp.messageContext,
6752
6831
  });
6753
- rememberLastCliInput(ds, promptContent, wrappedPrompt);
6754
- await noteTurnReceived(ds, parsed.messageId, parsed.content, await getThreadSender(), parsed.messageId);
6832
+ const wrappedInput = applyQueuedCodexAppLegacyFallback(builtReforkInput, {
6833
+ queued: queuedDashboardTurn,
6834
+ queuedText: queuedCodexAppText,
6835
+ });
6836
+ if (wrappedInput !== builtReforkInput && dsBotCfgForFork.codexAppCleanInput === true) {
6837
+ // Backlog sessions persisted before clean-input have no raw queued text.
6838
+ // Keep this activation entirely legacy: reforkContent already contains
6839
+ // queuedPrompt + the current reply, whereas a structured turn could only
6840
+ // contain the reply and would silently discard the original task.
6841
+ logger.warn(`[${tag(ds)}] Legacy queued dashboard task has no clean-input text; using the full legacy activation prompt`);
6842
+ }
6843
+ await noteTurnReceived(ds, parsed.messageId, parsed.content, await getThreadSender(), parsed.messageId, substituteTrigger ? SUBSTITUTE_RECEIVED_REACTION_EMOJI_TYPE : undefined);
6844
+ rememberLastCliInput(ds, promptContent, wrappedInput);
6755
6845
  sessionStore.updateSession(ds.session);
6756
- forkWorker(ds, wrappedPrompt, ds.hasHistory);
6846
+ forkWorker(ds, wrappedInput, ds.hasHistory);
6757
6847
  }
6758
6848
  }
6759
6849
  /**
@@ -6878,7 +6968,7 @@ async function handleDocComment(ctx) {
6878
6968
  const sender = ctx.authorOpenId ? await resolveSender(larkAppId, ctx.authorOpenId, 'user') : undefined;
6879
6969
  const authorName = sender?.name || ctx.authorOpenId?.slice(0, 8) || '?';
6880
6970
  const dsBotCfg = getBot(ds.larkAppId).config;
6881
- const promptContent = buildDocCommentPrompt({
6971
+ const promptInput = {
6882
6972
  fileToken: sub.fileToken,
6883
6973
  fileType: sub.fileType,
6884
6974
  question: text,
@@ -6890,7 +6980,7 @@ async function handleDocComment(ctx) {
6890
6980
  })),
6891
6981
  brand: normalizeBrand(dsBotCfg.brand),
6892
6982
  locale: loc,
6893
- });
6983
+ };
6894
6984
  // 记录本轮回评论的落点。两条路都要覆盖:
6895
6985
  // • ds.docCommentTurns(内存,按 turnId)→ deliverFinalOutput「兜底」分流用
6896
6986
  // • session.docCommentTargets(磁盘,per-turn map)→ `botmux send`「主回复」分流用
@@ -6910,25 +7000,21 @@ async function handleDocComment(ctx) {
6910
7000
  const isBridge = !!ds.adoptedFrom;
6911
7001
  if (!isBridge)
6912
7002
  ensureSessionWhiteboard(ds);
6913
- const msgContent = isBridge
6914
- ? buildBridgeInputContent(promptContent, {
6915
- selfMention: { name: selfBot.botName, openId: selfBot.botOpenId },
6916
- })
6917
- : buildFollowUpContent(promptContent, ds.session.sessionId, {
6918
- isAdoptMode: false,
6919
- cliId: dsBotCfg.cliId,
6920
- cliPathOverride: dsBotCfg.cliPathOverride,
6921
- sender,
6922
- larkAppId,
6923
- chatId: ds.session.chatId,
6924
- whiteboardId: ds.session.whiteboardId,
6925
- });
7003
+ const { promptContent, cliInput } = buildDocCommentTurnInput({
7004
+ ds,
7005
+ promptInput,
7006
+ botCliId: dsBotCfg.cliId,
7007
+ botCliPathOverride: dsBotCfg.cliPathOverride,
7008
+ botIdentity: { name: selfBot.botName, openId: selfBot.botOpenId },
7009
+ sender,
7010
+ mode: 'live',
7011
+ });
6926
7012
  beginNewTurn(ds, text);
6927
7013
  (ds.session.docCommentTargets ??= {})[turnId] = docTarget; // per-turn map,不覆盖其他并发轮
6928
- rememberLastCliInput(ds, promptContent, msgContent);
6929
7014
  sessionStore.updateSession(ds.session); // 先落盘,botmux send 子进程才读得到落点
6930
7015
  await noteTurnReceived(ds, commentId, text, sender, turnId);
6931
- ds.worker.send({ type: 'message', content: msgContent, turnId });
7016
+ rememberLastCliInput(ds, promptContent, cliInput);
7017
+ sendWorkerInput(ds, cliInput, turnId);
6932
7018
  logger.info(`[${tag(ds)}] doc-comment turn injected (turn ${turnId.slice(0, 8)})`);
6933
7019
  }
6934
7020
  else {
@@ -6950,17 +7036,20 @@ async function handleDocComment(ctx) {
6950
7036
  // the live-worker branch above (if (!isBridge) ensure…).
6951
7037
  if (!ds.adoptedFrom)
6952
7038
  ensureSessionWhiteboard(ds);
6953
- const wrappedPrompt = buildReforkPrompt(ds, promptContent, {
6954
- cliId: dsBotCfg.cliId,
6955
- cliPathOverride: dsBotCfg.cliPathOverride,
6956
- selfMention: { name: selfBot.botName, openId: selfBot.botOpenId },
7039
+ const { promptContent, cliInput: wrappedInput } = buildDocCommentTurnInput({
7040
+ ds,
7041
+ promptInput,
7042
+ botCliId: dsBotCfg.cliId,
7043
+ botCliPathOverride: dsBotCfg.cliPathOverride,
7044
+ botIdentity: { name: selfBot.botName, openId: selfBot.botOpenId },
6957
7045
  sender,
7046
+ mode: 'refork',
6958
7047
  });
6959
7048
  (ds.session.docCommentTargets ??= {})[turnId] = docTarget; // per-turn map,不覆盖其他并发轮
6960
- rememberLastCliInput(ds, promptContent, wrappedPrompt);
6961
7049
  await noteTurnReceived(ds, commentId, text, sender, turnId);
7050
+ rememberLastCliInput(ds, promptContent, wrappedInput);
6962
7051
  sessionStore.updateSession(ds.session);
6963
- forkWorker(ds, wrappedPrompt, { resume: ds.hasHistory, turnId });
7052
+ forkWorker(ds, wrappedInput, { resume: ds.hasHistory, turnId });
6964
7053
  }
6965
7054
  return true;
6966
7055
  }
@@ -7133,6 +7222,17 @@ function dashboardUrlForReport() {
7133
7222
  }
7134
7223
  }
7135
7224
  export async function startDaemon(botIndex) {
7225
+ // Repair a shared tmux server polluted by an older botmux immediately on
7226
+ // daemon startup. This must not depend on restoring/spawning a bmx-* session:
7227
+ // a user-held tmux server can outlive every botmux pane and still leak stale
7228
+ // routing/profile env into newly-created user panes.
7229
+ const tmuxEnvScrub = scrubTmuxServerGlobalEnv();
7230
+ if (tmuxEnvScrub.removed.length > 0) {
7231
+ logger.info(`[tmux] scrubbed ${tmuxEnvScrub.removed.length} server-global env key(s): ${tmuxEnvScrub.removed.join(', ')}`);
7232
+ }
7233
+ if (tmuxEnvScrub.failed.length > 0) {
7234
+ logger.warn(`[tmux] failed to scrub server-global env key(s): ${tmuxEnvScrub.failed.join(', ')}`);
7235
+ }
7136
7236
  // 首次启动时后台尝试安装 CJK 字体(Debian/Ubuntu),避免截图中文显示豆腐块。
7137
7237
  // 不阻塞:首张截图可能仍是豆腐块,装完重启 daemon 即可正常。
7138
7238
  ensureCjkFontsInstalled();