botmux 3.13.0 → 3.15.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 (541) hide show
  1. package/README.en.md +1 -1
  2. package/README.md +25 -1
  3. package/dist/.runtime-build-id +1 -1
  4. package/dist/adapters/adopt-route.d.ts +58 -0
  5. package/dist/adapters/adopt-route.d.ts.map +1 -1
  6. package/dist/adapters/adopt-route.js +136 -0
  7. package/dist/adapters/adopt-route.js.map +1 -1
  8. package/dist/adapters/backend/capabilities.js +1 -1
  9. package/dist/adapters/backend/capabilities.js.map +1 -1
  10. package/dist/adapters/backend/herdr-backend.d.ts +1 -1
  11. package/dist/adapters/backend/herdr-backend.d.ts.map +1 -1
  12. package/dist/adapters/backend/herdr-backend.js +2 -1
  13. package/dist/adapters/backend/herdr-backend.js.map +1 -1
  14. package/dist/adapters/backend/riff-backend.d.ts +97 -0
  15. package/dist/adapters/backend/riff-backend.d.ts.map +1 -1
  16. package/dist/adapters/backend/riff-backend.js +380 -66
  17. package/dist/adapters/backend/riff-backend.js.map +1 -1
  18. package/dist/adapters/backend/sandbox.d.ts +8 -0
  19. package/dist/adapters/backend/sandbox.d.ts.map +1 -1
  20. package/dist/adapters/backend/sandbox.js +16 -2
  21. package/dist/adapters/backend/sandbox.js.map +1 -1
  22. package/dist/adapters/backend/tmux-backend.d.ts +12 -13
  23. package/dist/adapters/backend/tmux-backend.d.ts.map +1 -1
  24. package/dist/adapters/backend/tmux-backend.js +113 -53
  25. package/dist/adapters/backend/tmux-backend.js.map +1 -1
  26. package/dist/adapters/backend/tmux-pipe-backend.d.ts +34 -3
  27. package/dist/adapters/backend/tmux-pipe-backend.d.ts.map +1 -1
  28. package/dist/adapters/backend/tmux-pipe-backend.js +72 -9
  29. package/dist/adapters/backend/tmux-pipe-backend.js.map +1 -1
  30. package/dist/adapters/backend/types.d.ts +7 -0
  31. package/dist/adapters/backend/types.d.ts.map +1 -1
  32. package/dist/adapters/backend/types.js.map +1 -1
  33. package/dist/adapters/backend/zellij-backend.d.ts +2 -3
  34. package/dist/adapters/backend/zellij-backend.d.ts.map +1 -1
  35. package/dist/adapters/backend/zellij-backend.js +6 -13
  36. package/dist/adapters/backend/zellij-backend.js.map +1 -1
  37. package/dist/adapters/backend/zellij-observe-backend.d.ts +2 -1
  38. package/dist/adapters/backend/zellij-observe-backend.d.ts.map +1 -1
  39. package/dist/adapters/backend/zellij-observe-backend.js +6 -3
  40. package/dist/adapters/backend/zellij-observe-backend.js.map +1 -1
  41. package/dist/adapters/backend/zmx-backend.d.ts.map +1 -1
  42. package/dist/adapters/backend/zmx-backend.js +40 -18
  43. package/dist/adapters/backend/zmx-backend.js.map +1 -1
  44. package/dist/adapters/cli/claude-code.d.ts.map +1 -1
  45. package/dist/adapters/cli/claude-code.js +12 -1
  46. package/dist/adapters/cli/claude-code.js.map +1 -1
  47. package/dist/adapters/cli/codex.d.ts.map +1 -1
  48. package/dist/adapters/cli/codex.js +3 -2
  49. package/dist/adapters/cli/codex.js.map +1 -1
  50. package/dist/adapters/cli/cursor.d.ts.map +1 -1
  51. package/dist/adapters/cli/cursor.js +13 -1
  52. package/dist/adapters/cli/cursor.js.map +1 -1
  53. package/dist/adapters/cli/dsh.d.ts +4 -0
  54. package/dist/adapters/cli/dsh.d.ts.map +1 -0
  55. package/dist/adapters/cli/dsh.js +100 -0
  56. package/dist/adapters/cli/dsh.js.map +1 -0
  57. package/dist/adapters/cli/fs-policy.d.ts.map +1 -1
  58. package/dist/adapters/cli/fs-policy.js +5 -0
  59. package/dist/adapters/cli/fs-policy.js.map +1 -1
  60. package/dist/adapters/cli/opencode.d.ts +52 -1
  61. package/dist/adapters/cli/opencode.d.ts.map +1 -1
  62. package/dist/adapters/cli/opencode.js +117 -76
  63. package/dist/adapters/cli/opencode.js.map +1 -1
  64. package/dist/adapters/cli/opencode2.d.ts +4 -0
  65. package/dist/adapters/cli/opencode2.d.ts.map +1 -0
  66. package/dist/adapters/cli/opencode2.js +137 -0
  67. package/dist/adapters/cli/opencode2.js.map +1 -0
  68. package/dist/adapters/cli/pi.d.ts +14 -1
  69. package/dist/adapters/cli/pi.d.ts.map +1 -1
  70. package/dist/adapters/cli/pi.js +21 -1
  71. package/dist/adapters/cli/pi.js.map +1 -1
  72. package/dist/adapters/cli/read-isolation.d.ts +1 -1
  73. package/dist/adapters/cli/read-isolation.d.ts.map +1 -1
  74. package/dist/adapters/cli/read-isolation.js +5 -2
  75. package/dist/adapters/cli/read-isolation.js.map +1 -1
  76. package/dist/adapters/cli/registry.d.ts +7 -1
  77. package/dist/adapters/cli/registry.d.ts.map +1 -1
  78. package/dist/adapters/cli/registry.js +19 -6
  79. package/dist/adapters/cli/registry.js.map +1 -1
  80. package/dist/adapters/cli/shared-hints.d.ts.map +1 -1
  81. package/dist/adapters/cli/shared-hints.js +41 -23
  82. package/dist/adapters/cli/shared-hints.js.map +1 -1
  83. package/dist/adapters/cli/types.d.ts +28 -3
  84. package/dist/adapters/cli/types.d.ts.map +1 -1
  85. package/dist/adapters/hook-command.d.ts +8 -0
  86. package/dist/adapters/hook-command.d.ts.map +1 -1
  87. package/dist/adapters/hook-command.js +11 -0
  88. package/dist/adapters/hook-command.js.map +1 -1
  89. package/dist/adapters/hook-installer.d.ts +16 -1
  90. package/dist/adapters/hook-installer.d.ts.map +1 -1
  91. package/dist/adapters/hook-installer.js +232 -2
  92. package/dist/adapters/hook-installer.js.map +1 -1
  93. package/dist/bot-registry.d.ts +138 -8
  94. package/dist/bot-registry.d.ts.map +1 -1
  95. package/dist/bot-registry.js +76 -4
  96. package/dist/bot-registry.js.map +1 -1
  97. package/dist/cli/pm2-start-transaction.d.ts +14 -0
  98. package/dist/cli/pm2-start-transaction.d.ts.map +1 -1
  99. package/dist/cli/pm2-start-transaction.js +25 -9
  100. package/dist/cli/pm2-start-transaction.js.map +1 -1
  101. package/dist/cli/restart-failure-notification.d.ts +54 -0
  102. package/dist/cli/restart-failure-notification.d.ts.map +1 -0
  103. package/dist/cli/restart-failure-notification.js +142 -0
  104. package/dist/cli/restart-failure-notification.js.map +1 -0
  105. package/dist/cli/restart-failure-owner.d.ts +20 -0
  106. package/dist/cli/restart-failure-owner.d.ts.map +1 -0
  107. package/dist/cli/restart-failure-owner.js +27 -0
  108. package/dist/cli/restart-failure-owner.js.map +1 -0
  109. package/dist/cli/restart-shutdown-preflight.d.ts +24 -0
  110. package/dist/cli/restart-shutdown-preflight.d.ts.map +1 -0
  111. package/dist/cli/restart-shutdown-preflight.js +105 -0
  112. package/dist/cli/restart-shutdown-preflight.js.map +1 -0
  113. package/dist/cli/send-dispatch.d.ts +26 -0
  114. package/dist/cli/send-dispatch.d.ts.map +1 -1
  115. package/dist/cli/send-dispatch.js +30 -0
  116. package/dist/cli/send-dispatch.js.map +1 -1
  117. package/dist/cli.d.ts +4 -1
  118. package/dist/cli.d.ts.map +1 -1
  119. package/dist/cli.js +673 -402
  120. package/dist/cli.js.map +1 -1
  121. package/dist/codex-app-runner.js +38 -15
  122. package/dist/codex-app-runner.js.map +1 -1
  123. package/dist/codex-rpc-lifecycle.d.ts +14 -2
  124. package/dist/codex-rpc-lifecycle.d.ts.map +1 -1
  125. package/dist/codex-rpc-lifecycle.js +13 -3
  126. package/dist/codex-rpc-lifecycle.js.map +1 -1
  127. package/dist/core/ask-broker.d.ts +3 -0
  128. package/dist/core/ask-broker.d.ts.map +1 -1
  129. package/dist/core/ask-broker.js +29 -7
  130. package/dist/core/ask-broker.js.map +1 -1
  131. package/dist/core/ask-hook/registry.d.ts.map +1 -1
  132. package/dist/core/ask-hook/registry.js +4 -0
  133. package/dist/core/ask-hook/registry.js.map +1 -1
  134. package/dist/core/ask-types.d.ts +5 -1
  135. package/dist/core/ask-types.d.ts.map +1 -1
  136. package/dist/core/ask-types.js.map +1 -1
  137. package/dist/core/browser-restart.d.ts +112 -0
  138. package/dist/core/browser-restart.d.ts.map +1 -0
  139. package/dist/core/browser-restart.js +233 -0
  140. package/dist/core/browser-restart.js.map +1 -0
  141. package/dist/core/command-discovery.d.ts.map +1 -1
  142. package/dist/core/command-discovery.js +4 -1
  143. package/dist/core/command-discovery.js.map +1 -1
  144. package/dist/core/command-handler.d.ts +19 -11
  145. package/dist/core/command-handler.d.ts.map +1 -1
  146. package/dist/core/command-handler.js +237 -43
  147. package/dist/core/command-handler.js.map +1 -1
  148. package/dist/core/current-turn-provenance.d.ts.map +1 -1
  149. package/dist/core/current-turn-provenance.js +7 -27
  150. package/dist/core/current-turn-provenance.js.map +1 -1
  151. package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
  152. package/dist/core/dashboard-ipc-server.js +359 -11
  153. package/dist/core/dashboard-ipc-server.js.map +1 -1
  154. package/dist/core/dispatch-registry.d.ts.map +1 -1
  155. package/dist/core/dispatch-registry.js +6 -0
  156. package/dist/core/dispatch-registry.js.map +1 -1
  157. package/dist/core/dispatch-report-binding.d.ts +31 -0
  158. package/dist/core/dispatch-report-binding.d.ts.map +1 -0
  159. package/dist/core/dispatch-report-binding.js +88 -0
  160. package/dist/core/dispatch-report-binding.js.map +1 -0
  161. package/dist/core/doc-comment-prompt.d.ts +2 -0
  162. package/dist/core/doc-comment-prompt.d.ts.map +1 -1
  163. package/dist/core/doc-comment-prompt.js +6 -0
  164. package/dist/core/doc-comment-prompt.js.map +1 -1
  165. package/dist/core/host-overload-alert.d.ts +25 -1
  166. package/dist/core/host-overload-alert.d.ts.map +1 -1
  167. package/dist/core/host-overload-alert.js +69 -8
  168. package/dist/core/host-overload-alert.js.map +1 -1
  169. package/dist/core/local-terminal-opener.js +1 -1
  170. package/dist/core/local-terminal-opener.js.map +1 -1
  171. package/dist/core/managed-origin-capability.d.ts +7 -6
  172. package/dist/core/managed-origin-capability.d.ts.map +1 -1
  173. package/dist/core/managed-origin-capability.js +32 -1
  174. package/dist/core/managed-origin-capability.js.map +1 -1
  175. package/dist/core/passthrough-commands.d.ts.map +1 -1
  176. package/dist/core/passthrough-commands.js +3 -2
  177. package/dist/core/passthrough-commands.js.map +1 -1
  178. package/dist/core/plugins/materializer.d.ts.map +1 -1
  179. package/dist/core/plugins/materializer.js +1 -0
  180. package/dist/core/plugins/materializer.js.map +1 -1
  181. package/dist/core/producer-quiescence.d.ts +70 -0
  182. package/dist/core/producer-quiescence.d.ts.map +1 -0
  183. package/dist/core/producer-quiescence.js +90 -0
  184. package/dist/core/producer-quiescence.js.map +1 -0
  185. package/dist/core/raw-command-write-options.d.ts +10 -0
  186. package/dist/core/raw-command-write-options.d.ts.map +1 -0
  187. package/dist/core/raw-command-write-options.js +23 -0
  188. package/dist/core/raw-command-write-options.js.map +1 -0
  189. package/dist/core/raw-command-writer.d.ts +4 -0
  190. package/dist/core/raw-command-writer.d.ts.map +1 -1
  191. package/dist/core/raw-command-writer.js +15 -6
  192. package/dist/core/raw-command-writer.js.map +1 -1
  193. package/dist/core/reply-target.d.ts +11 -0
  194. package/dist/core/reply-target.d.ts.map +1 -1
  195. package/dist/core/reply-target.js +22 -7
  196. package/dist/core/reply-target.js.map +1 -1
  197. package/dist/core/report-session-relay.d.ts +53 -0
  198. package/dist/core/report-session-relay.d.ts.map +1 -0
  199. package/dist/core/report-session-relay.js +116 -0
  200. package/dist/core/report-session-relay.js.map +1 -0
  201. package/dist/core/session-discovery.d.ts +5 -0
  202. package/dist/core/session-discovery.d.ts.map +1 -1
  203. package/dist/core/session-discovery.js +13 -0
  204. package/dist/core/session-discovery.js.map +1 -1
  205. package/dist/core/session-group-birth.d.ts +7 -0
  206. package/dist/core/session-group-birth.d.ts.map +1 -0
  207. package/dist/core/session-group-birth.js +145 -0
  208. package/dist/core/session-group-birth.js.map +1 -0
  209. package/dist/core/session-manager.d.ts +16 -21
  210. package/dist/core/session-manager.d.ts.map +1 -1
  211. package/dist/core/session-manager.js +140 -21
  212. package/dist/core/session-manager.js.map +1 -1
  213. package/dist/core/skills/frontmatter.d.ts.map +1 -1
  214. package/dist/core/skills/frontmatter.js +6 -1
  215. package/dist/core/skills/frontmatter.js.map +1 -1
  216. package/dist/core/trigger-session.d.ts.map +1 -1
  217. package/dist/core/trigger-session.js +18 -3
  218. package/dist/core/trigger-session.js.map +1 -1
  219. package/dist/core/types.d.ts +18 -0
  220. package/dist/core/types.d.ts.map +1 -1
  221. package/dist/core/types.js.map +1 -1
  222. package/dist/core/worker-pool.d.ts +63 -11
  223. package/dist/core/worker-pool.d.ts.map +1 -1
  224. package/dist/core/worker-pool.js +262 -21
  225. package/dist/core/worker-pool.js.map +1 -1
  226. package/dist/daemon.d.ts +33 -0
  227. package/dist/daemon.d.ts.map +1 -1
  228. package/dist/daemon.js +904 -147
  229. package/dist/daemon.js.map +1 -1
  230. package/dist/dashboard/auth.d.ts +20 -0
  231. package/dist/dashboard/auth.d.ts.map +1 -1
  232. package/dist/dashboard/auth.js +86 -1
  233. package/dist/dashboard/auth.js.map +1 -1
  234. package/dist/dashboard/bot-payload.d.ts +9 -0
  235. package/dist/dashboard/bot-payload.d.ts.map +1 -1
  236. package/dist/dashboard/bot-payload.js +5 -1
  237. package/dist/dashboard/bot-payload.js.map +1 -1
  238. package/dist/dashboard/federation-spoke-api.d.ts.map +1 -1
  239. package/dist/dashboard/federation-spoke-api.js +27 -3
  240. package/dist/dashboard/federation-spoke-api.js.map +1 -1
  241. package/dist/dashboard/feedback-analytics-api.d.ts +6 -0
  242. package/dist/dashboard/feedback-analytics-api.d.ts.map +1 -0
  243. package/dist/dashboard/feedback-analytics-api.js +39 -0
  244. package/dist/dashboard/feedback-analytics-api.js.map +1 -0
  245. package/dist/dashboard/session-action-timeout.d.ts +3 -0
  246. package/dist/dashboard/session-action-timeout.d.ts.map +1 -0
  247. package/dist/dashboard/session-action-timeout.js +12 -0
  248. package/dist/dashboard/session-action-timeout.js.map +1 -0
  249. package/dist/dashboard/vc-consumer-profiles-api.d.ts +9 -3
  250. package/dist/dashboard/vc-consumer-profiles-api.d.ts.map +1 -1
  251. package/dist/dashboard/vc-consumer-profiles-api.js +2 -1
  252. package/dist/dashboard/vc-consumer-profiles-api.js.map +1 -1
  253. package/dist/dashboard/web/app.d.ts.map +1 -1
  254. package/dist/dashboard/web/app.js +48 -5
  255. package/dist/dashboard/web/app.js.map +1 -1
  256. package/dist/dashboard/web/bot-defaults-page.d.ts +11 -0
  257. package/dist/dashboard/web/bot-defaults-page.d.ts.map +1 -1
  258. package/dist/dashboard/web/bot-defaults-page.js +368 -36
  259. package/dist/dashboard/web/bot-defaults-page.js.map +1 -1
  260. package/dist/dashboard/web/bot-defaults.d.ts +8 -0
  261. package/dist/dashboard/web/bot-defaults.d.ts.map +1 -1
  262. package/dist/dashboard/web/bot-defaults.js.map +1 -1
  263. package/dist/dashboard/web/dashboard-routes.d.ts.map +1 -1
  264. package/dist/dashboard/web/dashboard-routes.js +1 -0
  265. package/dist/dashboard/web/dashboard-routes.js.map +1 -1
  266. package/dist/dashboard/web/feedback-page.d.ts +3 -0
  267. package/dist/dashboard/web/feedback-page.d.ts.map +1 -0
  268. package/dist/dashboard/web/feedback-page.js +34 -0
  269. package/dist/dashboard/web/feedback-page.js.map +1 -0
  270. package/dist/dashboard/web/groups-page.d.ts.map +1 -1
  271. package/dist/dashboard/web/groups-page.js +7 -2
  272. package/dist/dashboard/web/groups-page.js.map +1 -1
  273. package/dist/dashboard/web/i18n.d.ts.map +1 -1
  274. package/dist/dashboard/web/i18n.js +122 -22
  275. package/dist/dashboard/web/i18n.js.map +1 -1
  276. package/dist/dashboard/web/skills-page.d.ts.map +1 -1
  277. package/dist/dashboard/web/skills-page.js +4 -2
  278. package/dist/dashboard/web/skills-page.js.map +1 -1
  279. package/dist/dashboard/web/team-federation-page.d.ts.map +1 -1
  280. package/dist/dashboard/web/team-federation-page.js +26 -2
  281. package/dist/dashboard/web/team-federation-page.js.map +1 -1
  282. package/dist/dashboard/web/team-federation.d.ts +19 -0
  283. package/dist/dashboard/web/team-federation.d.ts.map +1 -1
  284. package/dist/dashboard/web/team-federation.js +3 -0
  285. package/dist/dashboard/web/team-federation.js.map +1 -1
  286. package/dist/dashboard/web/update-action.d.ts +8 -0
  287. package/dist/dashboard/web/update-action.d.ts.map +1 -1
  288. package/dist/dashboard/web/update-action.js +10 -0
  289. package/dist/dashboard/web/update-action.js.map +1 -1
  290. package/dist/dashboard/web/vc-consumer-profiles-section.d.ts.map +1 -1
  291. package/dist/dashboard/web/vc-consumer-profiles-section.js +58 -9
  292. package/dist/dashboard/web/vc-consumer-profiles-section.js.map +1 -1
  293. package/dist/dashboard-web/app.js +1 -1
  294. package/dist/dashboard-web/chunks/bot-defaults-page-65SMZ4QY.js +10 -0
  295. package/dist/dashboard-web/chunks/chunk-G5XGBCM6.js +1 -0
  296. package/dist/dashboard-web/chunks/chunk-IKN6WURK.js +1 -0
  297. package/dist/dashboard-web/chunks/{chunk-6JTZJRQJ.js → chunk-VMWNR3SV.js} +15 -15
  298. package/dist/dashboard-web/chunks/{chunk-CU4SUKK3.js → chunk-Z7B3E3US.js} +1 -1
  299. package/dist/dashboard-web/chunks/{chunk-5QJB24I2.js → chunk-Z7DO42GJ.js} +1 -1
  300. package/dist/dashboard-web/chunks/connectors-page-CPH6VVFY.js +1 -0
  301. package/dist/dashboard-web/chunks/feedback-page-J2FKQ3LK.js +1 -0
  302. package/dist/dashboard-web/chunks/groups-page-VDOOLS4M.js +8 -0
  303. package/dist/dashboard-web/chunks/{insights-page-EVGGJI5L.js → insights-page-A2LWTKJD.js} +1 -1
  304. package/dist/dashboard-web/chunks/{monitor-room-HRGR2ENP.js → monitor-room-2YR752E3.js} +1 -1
  305. package/dist/dashboard-web/chunks/monitoring-page-BGYSKZGM.js +2 -0
  306. package/dist/dashboard-web/chunks/office-page-ZZHTEAE4.js +1 -0
  307. package/dist/dashboard-web/chunks/overview-page-7NYPKQSB.js +1 -0
  308. package/dist/dashboard-web/chunks/roles-page-YUC4YLJG.js +1 -0
  309. package/dist/dashboard-web/chunks/schedules-page-2L2NKTDJ.js +1 -0
  310. package/dist/dashboard-web/chunks/{sessions-page-DXBCDWCS.js → sessions-page-DYZZMKFL.js} +1 -1
  311. package/dist/dashboard-web/chunks/settings-page-LIULUSMH.js +2 -0
  312. package/dist/dashboard-web/chunks/{skills-page-2QAVB2SA.js → skills-page-DQPBBI3T.js} +2 -2
  313. package/dist/dashboard-web/chunks/team-federation-page-LQKLDV4K.js +1 -0
  314. package/dist/dashboard-web/chunks/{v3-page-AY35LKCX.js → v3-page-RVKQIMAP.js} +1 -1
  315. package/dist/dashboard-web/chunks/whiteboards-page-S5K55HRZ.js +1 -0
  316. package/dist/dashboard-web/style.css +35 -0
  317. package/dist/dashboard.js +210 -13
  318. package/dist/dashboard.js.map +1 -1
  319. package/dist/desktop/shared/shell-path-probes.d.ts +1 -1
  320. package/dist/desktop/shared/shell-path-probes.js +2 -2
  321. package/dist/desktop/shared/shell-path-probes.js.map +1 -1
  322. package/dist/dsh-runner.d.ts +3 -0
  323. package/dist/dsh-runner.d.ts.map +1 -0
  324. package/dist/dsh-runner.js +673 -0
  325. package/dist/dsh-runner.js.map +1 -0
  326. package/dist/global-config.d.ts +24 -0
  327. package/dist/global-config.d.ts.map +1 -1
  328. package/dist/global-config.js +35 -0
  329. package/dist/global-config.js.map +1 -1
  330. package/dist/i18n/en.d.ts.map +1 -1
  331. package/dist/i18n/en.js +39 -31
  332. package/dist/i18n/en.js.map +1 -1
  333. package/dist/i18n/zh.d.ts.map +1 -1
  334. package/dist/i18n/zh.js +39 -31
  335. package/dist/i18n/zh.js.map +1 -1
  336. package/dist/im/lark/ask-card.d.ts +3 -1
  337. package/dist/im/lark/ask-card.d.ts.map +1 -1
  338. package/dist/im/lark/ask-card.js +61 -5
  339. package/dist/im/lark/ask-card.js.map +1 -1
  340. package/dist/im/lark/card-builder.d.ts.map +1 -1
  341. package/dist/im/lark/card-builder.js +13 -6
  342. package/dist/im/lark/card-builder.js.map +1 -1
  343. package/dist/im/lark/card-handler.d.ts +2 -1
  344. package/dist/im/lark/card-handler.d.ts.map +1 -1
  345. package/dist/im/lark/card-handler.js +145 -5
  346. package/dist/im/lark/card-handler.js.map +1 -1
  347. package/dist/im/lark/event-dispatcher.d.ts +18 -0
  348. package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
  349. package/dist/im/lark/event-dispatcher.js +41 -28
  350. package/dist/im/lark/event-dispatcher.js.map +1 -1
  351. package/dist/im/lark/md-card.d.ts +18 -0
  352. package/dist/im/lark/md-card.d.ts.map +1 -1
  353. package/dist/im/lark/md-card.js +21 -0
  354. package/dist/im/lark/md-card.js.map +1 -1
  355. package/dist/im/lark/relay-target-routing.d.ts.map +1 -1
  356. package/dist/im/lark/relay-target-routing.js +3 -2
  357. package/dist/im/lark/relay-target-routing.js.map +1 -1
  358. package/dist/im/lark/reply-mode-command.d.ts.map +1 -1
  359. package/dist/im/lark/reply-mode-command.js +19 -8
  360. package/dist/im/lark/reply-mode-command.js.map +1 -1
  361. package/dist/im/lark/skill-feedback-card.d.ts +15 -0
  362. package/dist/im/lark/skill-feedback-card.d.ts.map +1 -0
  363. package/dist/im/lark/skill-feedback-card.js +149 -0
  364. package/dist/im/lark/skill-feedback-card.js.map +1 -0
  365. package/dist/services/bot-config-store.d.ts +10 -1
  366. package/dist/services/bot-config-store.d.ts.map +1 -1
  367. package/dist/services/bot-config-store.js +103 -3
  368. package/dist/services/bot-config-store.js.map +1 -1
  369. package/dist/services/bridge-fallback-gate.d.ts +29 -7
  370. package/dist/services/bridge-fallback-gate.d.ts.map +1 -1
  371. package/dist/services/bridge-fallback-gate.js +58 -16
  372. package/dist/services/bridge-fallback-gate.js.map +1 -1
  373. package/dist/services/codex-reasoning-effort.d.ts +9 -0
  374. package/dist/services/codex-reasoning-effort.d.ts.map +1 -0
  375. package/dist/services/codex-reasoning-effort.js +23 -0
  376. package/dist/services/codex-reasoning-effort.js.map +1 -0
  377. package/dist/services/codex-service-tier.d.ts +1 -1
  378. package/dist/services/codex-service-tier.d.ts.map +1 -1
  379. package/dist/services/codex-transcript.d.ts +9 -0
  380. package/dist/services/codex-transcript.d.ts.map +1 -1
  381. package/dist/services/codex-transcript.js +13 -6
  382. package/dist/services/codex-transcript.js.map +1 -1
  383. package/dist/services/feed-group-tagger.d.ts +15 -0
  384. package/dist/services/feed-group-tagger.d.ts.map +1 -0
  385. package/dist/services/feed-group-tagger.js +348 -0
  386. package/dist/services/feed-group-tagger.js.map +1 -0
  387. package/dist/services/feedback-analytics.d.ts +82 -0
  388. package/dist/services/feedback-analytics.d.ts.map +1 -0
  389. package/dist/services/feedback-analytics.js +95 -0
  390. package/dist/services/feedback-analytics.js.map +1 -0
  391. package/dist/services/feedback-outbox.d.ts +24 -0
  392. package/dist/services/feedback-outbox.d.ts.map +1 -0
  393. package/dist/services/feedback-outbox.js +12 -0
  394. package/dist/services/feedback-outbox.js.map +1 -0
  395. package/dist/services/feedback-policy-resolver.d.ts +56 -0
  396. package/dist/services/feedback-policy-resolver.d.ts.map +1 -0
  397. package/dist/services/feedback-policy-resolver.js +156 -0
  398. package/dist/services/feedback-policy-resolver.js.map +1 -0
  399. package/dist/services/feedback-policy.d.ts +41 -0
  400. package/dist/services/feedback-policy.d.ts.map +1 -0
  401. package/dist/services/feedback-policy.js +102 -0
  402. package/dist/services/feedback-policy.js.map +1 -0
  403. package/dist/services/feedback-webhook-dispatcher.d.ts +76 -0
  404. package/dist/services/feedback-webhook-dispatcher.d.ts.map +1 -0
  405. package/dist/services/feedback-webhook-dispatcher.js +237 -0
  406. package/dist/services/feedback-webhook-dispatcher.js.map +1 -0
  407. package/dist/services/grant-prefs-store.d.ts +3 -0
  408. package/dist/services/grant-prefs-store.d.ts.map +1 -1
  409. package/dist/services/grant-prefs-store.js +30 -1
  410. package/dist/services/grant-prefs-store.js.map +1 -1
  411. package/dist/services/groups-store.d.ts +14 -0
  412. package/dist/services/groups-store.d.ts.map +1 -1
  413. package/dist/services/groups-store.js +23 -0
  414. package/dist/services/groups-store.js.map +1 -1
  415. package/dist/services/local-cli-opener.d.ts +1 -1
  416. package/dist/services/local-cli-opener.d.ts.map +1 -1
  417. package/dist/services/local-cli-opener.js +2 -0
  418. package/dist/services/local-cli-opener.js.map +1 -1
  419. package/dist/services/peer-cross-ref-store.d.ts +8 -0
  420. package/dist/services/peer-cross-ref-store.d.ts.map +1 -0
  421. package/dist/services/peer-cross-ref-store.js +95 -0
  422. package/dist/services/peer-cross-ref-store.js.map +1 -0
  423. package/dist/services/pi-transcript.d.ts.map +1 -1
  424. package/dist/services/pi-transcript.js +10 -6
  425. package/dist/services/pi-transcript.js.map +1 -1
  426. package/dist/services/prompt-context-store.d.ts +29 -0
  427. package/dist/services/prompt-context-store.d.ts.map +1 -0
  428. package/dist/services/prompt-context-store.js +205 -0
  429. package/dist/services/prompt-context-store.js.map +1 -0
  430. package/dist/services/session-group-avatar.d.ts +6 -0
  431. package/dist/services/session-group-avatar.d.ts.map +1 -0
  432. package/dist/services/session-group-avatar.js +96 -0
  433. package/dist/services/session-group-avatar.js.map +1 -0
  434. package/dist/services/session-group-title.d.ts +47 -0
  435. package/dist/services/session-group-title.d.ts.map +1 -0
  436. package/dist/services/session-group-title.js +226 -0
  437. package/dist/services/session-group-title.js.map +1 -0
  438. package/dist/services/session-groups-store.d.ts +30 -0
  439. package/dist/services/session-groups-store.d.ts.map +1 -0
  440. package/dist/services/session-groups-store.js +132 -0
  441. package/dist/services/session-groups-store.js.map +1 -0
  442. package/dist/services/session-store.d.ts +58 -0
  443. package/dist/services/session-store.d.ts.map +1 -1
  444. package/dist/services/session-store.js +174 -0
  445. package/dist/services/session-store.js.map +1 -1
  446. package/dist/services/skill-feedback-store.d.ts +252 -0
  447. package/dist/services/skill-feedback-store.d.ts.map +1 -0
  448. package/dist/services/skill-feedback-store.js +795 -0
  449. package/dist/services/skill-feedback-store.js.map +1 -0
  450. package/dist/services/skill-registry-store.d.ts +23 -0
  451. package/dist/services/skill-registry-store.d.ts.map +1 -1
  452. package/dist/services/skill-registry-store.js +93 -7
  453. package/dist/services/skill-registry-store.js.map +1 -1
  454. package/dist/services/structured-bridge-clis.d.ts +13 -4
  455. package/dist/services/structured-bridge-clis.d.ts.map +1 -1
  456. package/dist/services/structured-bridge-clis.js +13 -3
  457. package/dist/services/structured-bridge-clis.js.map +1 -1
  458. package/dist/services/team-store.d.ts +4 -0
  459. package/dist/services/team-store.d.ts.map +1 -1
  460. package/dist/services/team-store.js +16 -0
  461. package/dist/services/team-store.js.map +1 -1
  462. package/dist/services/traex-transcript.d.ts +32 -4
  463. package/dist/services/traex-transcript.d.ts.map +1 -1
  464. package/dist/services/traex-transcript.js +116 -8
  465. package/dist/services/traex-transcript.js.map +1 -1
  466. package/dist/services/trigger-types.d.ts +1 -1
  467. package/dist/services/trigger-types.d.ts.map +1 -1
  468. package/dist/services/trigger-types.js +2 -2
  469. package/dist/services/trigger-types.js.map +1 -1
  470. package/dist/services/turn-completion-events.d.ts +57 -0
  471. package/dist/services/turn-completion-events.d.ts.map +1 -0
  472. package/dist/services/turn-completion-events.js +179 -0
  473. package/dist/services/turn-completion-events.js.map +1 -0
  474. package/dist/services/vc-meeting-consumer-isolation.d.ts +21 -12
  475. package/dist/services/vc-meeting-consumer-isolation.d.ts.map +1 -1
  476. package/dist/services/vc-meeting-consumer-isolation.js +25 -19
  477. package/dist/services/vc-meeting-consumer-isolation.js.map +1 -1
  478. package/dist/services/vc-meeting-consumer-profile-bootstrap.d.ts +8 -2
  479. package/dist/services/vc-meeting-consumer-profile-bootstrap.d.ts.map +1 -1
  480. package/dist/services/vc-meeting-consumer-profile-bootstrap.js +18 -10
  481. package/dist/services/vc-meeting-consumer-profile-bootstrap.js.map +1 -1
  482. package/dist/setup/bot-config-editor.d.ts.map +1 -1
  483. package/dist/setup/bot-config-editor.js +6 -0
  484. package/dist/setup/bot-config-editor.js.map +1 -1
  485. package/dist/setup/cli-availability.d.ts +2 -1
  486. package/dist/setup/cli-availability.d.ts.map +1 -1
  487. package/dist/setup/cli-availability.js +2 -1
  488. package/dist/setup/cli-availability.js.map +1 -1
  489. package/dist/setup/cli-selection.d.ts +8 -0
  490. package/dist/setup/cli-selection.d.ts.map +1 -1
  491. package/dist/setup/cli-selection.js +31 -0
  492. package/dist/setup/cli-selection.js.map +1 -1
  493. package/dist/setup/ensure-fonts.d.ts +5 -0
  494. package/dist/setup/ensure-fonts.d.ts.map +1 -1
  495. package/dist/setup/ensure-fonts.js +26 -1
  496. package/dist/setup/ensure-fonts.js.map +1 -1
  497. package/dist/setup/lark-scopes.json +3 -1
  498. package/dist/setup/open-platform-automation.d.ts +1 -1
  499. package/dist/setup/open-platform-automation.d.ts.map +1 -1
  500. package/dist/setup/open-platform-automation.js +15 -3
  501. package/dist/setup/open-platform-automation.js.map +1 -1
  502. package/dist/skills/definitions.d.ts +1 -1
  503. package/dist/skills/definitions.d.ts.map +1 -1
  504. package/dist/skills/definitions.js +9 -2
  505. package/dist/skills/definitions.js.map +1 -1
  506. package/dist/types.d.ts +11 -4
  507. package/dist/types.d.ts.map +1 -1
  508. package/dist/utils/bot-routing.d.ts +1 -1
  509. package/dist/utils/bot-routing.d.ts.map +1 -1
  510. package/dist/utils/bot-routing.js.map +1 -1
  511. package/dist/utils/user-token.d.ts +38 -0
  512. package/dist/utils/user-token.d.ts.map +1 -1
  513. package/dist/utils/user-token.js +76 -1
  514. package/dist/utils/user-token.js.map +1 -1
  515. package/dist/utils/web-terminal-scroll.d.ts +27 -0
  516. package/dist/utils/web-terminal-scroll.d.ts.map +1 -0
  517. package/dist/utils/web-terminal-scroll.js +60 -0
  518. package/dist/utils/web-terminal-scroll.js.map +1 -0
  519. package/dist/vc-agent/polling-source.d.ts +70 -0
  520. package/dist/vc-agent/polling-source.d.ts.map +1 -1
  521. package/dist/vc-agent/polling-source.js +115 -0
  522. package/dist/vc-agent/polling-source.js.map +1 -1
  523. package/dist/worker.js +469 -86
  524. package/dist/worker.js.map +1 -1
  525. package/dist/workflows/definition.d.ts +22 -22
  526. package/dist/workflows/events/payloads.d.ts +8 -8
  527. package/dist/workflows/events/schema.d.ts +20 -20
  528. package/dist/workflows/migration/v2-run-archive-schema.d.ts +20 -20
  529. package/package.json +5 -1
  530. package/dist/dashboard-web/chunks/bot-defaults-page-NQJFJ45Z.js +0 -10
  531. package/dist/dashboard-web/chunks/chunk-KEEYQ3WF.js +0 -1
  532. package/dist/dashboard-web/chunks/connectors-page-HADHZXXZ.js +0 -1
  533. package/dist/dashboard-web/chunks/groups-page-5TYF46RS.js +0 -8
  534. package/dist/dashboard-web/chunks/monitoring-page-LBU7TTTW.js +0 -2
  535. package/dist/dashboard-web/chunks/office-page-DF6RRBMN.js +0 -1
  536. package/dist/dashboard-web/chunks/overview-page-ZB6QH2EX.js +0 -1
  537. package/dist/dashboard-web/chunks/roles-page-ZBZGHIIF.js +0 -1
  538. package/dist/dashboard-web/chunks/schedules-page-NQVQC3QY.js +0 -1
  539. package/dist/dashboard-web/chunks/settings-page-E7NN4NVQ.js +0 -2
  540. package/dist/dashboard-web/chunks/team-federation-page-VGWL53AE.js +0 -1
  541. package/dist/dashboard-web/chunks/whiteboards-page-BNWUU4KG.js +0 -1
package/dist/worker.js CHANGED
@@ -23,18 +23,20 @@ import { evaluateCredentialOnlyIsolationGate, credentialIsolationRequired, devic
23
23
  import { buildFsPolicy, compileToSeatbelt, migrateLegacySandboxFields, resolveRedirectedAdapterAuthPaths, FsPolicyConfigError } from './adapters/cli/fs-policy.js';
24
24
  import { killPersistentBackendTarget, killPersistentSession, probePersistentBackendTarget, probePersistentSession, shouldRejectPersistentPostKillProbe } from './core/persistent-backend.js';
25
25
  import { finalizeRawCommandDelivery, writeRawCommandLine } from './core/raw-command-writer.js';
26
+ import { rawCommandWriteOptionsFor } from './core/raw-command-write-options.js';
26
27
  import { publishCliSessionIdToDaemon } from './core/cli-session-id-publisher.js';
27
28
  import { readProcessStartIdentity } from './core/session-marker.js';
28
29
  import { roleLibraryRoot, roleLibrarySubtree } from './core/role-library.js';
29
30
  import { drainTranscript, joinAssistantText, trailingAssistantText, findJsonlContainingFingerprint, findJsonlsContainingExactContent, findLatestJsonl, extractLastAssistantTurn, extractTurnStartText, splitTranscriptEventsByCutoff, isTranscriptRateLimitEvent, apiErrorMessageText } from './services/claude-transcript.js';
30
31
  import { BridgeTurnQueue, makeFingerprint, normaliseForFingerprint } from './services/bridge-turn-queue.js';
31
- import { bridgePostText, isBridgeNothingToSendFinal, shouldEmitEmptyCompletedBridgeFallback, shouldEmitFailedBridgeFallback, shouldSuppressBridgeEmit, stripTrailingBridgeSentinelLine } from './services/bridge-fallback-gate.js';
32
+ import { bridgePostText, isBridgeNothingToSendFinal, shouldSuppressBridgeEmit, structuredFallbackKind, stripTrailingOaiMemoryCitation } from './services/bridge-fallback-gate.js';
32
33
  import { buildSubmitMessagePreview } from './services/submit-notification.js';
33
34
  import { decideHardTimeoutAction, decidePostHookPromptEvidence, decideSettleMarkReady, shouldArmPostHookPromptEvidenceFallback, shouldReleaseFirstPromptTimeout, shouldWaitForPostSessionStartPromptEvidence, shouldWriteNow, POST_HOOK_EVIDENCE_FALLBACK_MS, POST_HOOK_EVIDENCE_RETRY_MS, } from './utils/input-gate.js';
34
35
  import { decideCodexRunnerFreshness, CodexRunnerFreshnessInputQueue, shouldHoldCodexRunnerInput, } from './services/codex-runner-freshness.js';
35
36
  import { canStartInjectionFlush, shouldDeferUserFlush, shouldFlushInjectionsFirst } from './core/inject-queue-policy.js';
36
37
  import { decideRestartFollowup, settleDurableTurnForRestart } from './core/restart-followup-policy.js';
37
38
  import { stripAnsiForLog, tailChars } from './utils/crash-log.js';
39
+ import { parseReadOnlyRemoteScrollPayload, READ_ONLY_REMOTE_SCROLL_SESSION_BUDGET, READ_ONLY_REMOTE_SCROLL_WINDOW_MS, ReadOnlyRemoteScrollLimiter, } from './utils/web-terminal-scroll.js';
38
40
  import { CodexUpdateDialogGuard } from './utils/codex-update-dialog.js';
39
41
  import { EffortConfirmDialogGuard } from './utils/effort-confirm-dialog.js';
40
42
  import { installStdioEpipeGuard, isIgnorableStreamError } from './utils/stdio-epipe-guard.js';
@@ -63,7 +65,7 @@ import { CodexBridgeQueue, pruneExpiredPreStartHeadsAndEmit } from './services/c
63
65
  import { detectCodexComposerState } from './services/codex-composer-state.js';
64
66
  import { generateCodexAppThreadTitle, readCodexAppThreadMetadata, setCodexAppThreadName, } from './services/codex-app-threads.js';
65
67
  import { buildBotmuxLarkNativeSessionTitle } from './core/session-title.js';
66
- import { CODEX_AUTH_ERROR_CODE, CODEX_CONNECTION_ERROR_CODE, CODEX_INVALID_REQUEST_ERROR_CODE, CODEX_RATE_LIMIT_ERROR_CODE, drainCodexRollout, findCodexRolloutBySessionId, findCodexRolloutByPid, findCodexRolloutSetByPid, codexHistorySidIsOwned, splitCodexEventsByCutoff, extractLastCodexTurn, codexSessionIdFromRolloutPath, isCodexRateLimitEvent, scanCodexThreadSettings, readLatestCodexRuntime } from './services/codex-transcript.js';
68
+ import { CODEX_AUTH_ERROR_CODE, CODEX_CONNECTION_ERROR_CODE, CODEX_INVALID_REQUEST_ERROR_CODE, drainCodexRollout, findCodexRolloutBySessionId, findCodexRolloutByPid, findCodexRolloutSetByPid, codexHistorySidIsOwned, splitCodexEventsByCutoff, extractLastCodexTurn, codexSessionIdFromRolloutPath, isCodexRateLimitEvent, scanCodexThreadSettings, readLatestCodexRuntime } from './services/codex-transcript.js';
67
69
  import { CodexServiceTierTracker, resolveCodexServiceTierSnapshot } from './services/codex-service-tier.js';
68
70
  import { WORKER_IPC_HANDLER_READY_EVENT } from './worker-ipc-preload.js';
69
71
  import { drainTraexRollout, findTraexRolloutBySessionId, findTraexRolloutByPid, findTraexRolloutSetByPid, readLatestTraexRuntime, traexHistorySidIsOwned } from './services/traex-transcript.js';
@@ -91,9 +93,9 @@ import { t, setDefaultLocale } from './i18n/index.js';
91
93
  import { TerminalRenderer } from './utils/terminal-renderer.js';
92
94
  import { DEFAULT_RENDER_COLS, DEFAULT_RENDER_ROWS, MAX_RENDER_COLS, MAX_RENDER_ROWS, MIN_RENDER_COLS, MIN_RENDER_ROWS, clamp, resolveRenderDimensions, } from './utils/render-dimensions.js';
93
95
  import { createCliAdapterSync, locateOnPath } from './adapters/cli/registry.js';
94
- import { buildWrappedLaunch, parseWrapperCli, isTtadkWrapper } from './setup/cli-selection.js';
96
+ import { buildWrappedLaunch, parseWrapperCli, isTtadkWrapper, wrapperLaunchEnv } from './setup/cli-selection.js';
95
97
  import { cliUnavailableMessage } from './setup/cli-availability.js';
96
- import { findLaunchedCliPid, scheduleWrapperRealCliPid, readComm, isBareShellComm, bareShellLaunchKind, settleLaunchComm, } from './core/session-discovery.js';
98
+ import { findLaunchedCliPid, scheduleWrapperRealCliPid, readComm, isBareShellComm, bareShellLaunchKind, bareShellLaunchGuidance, settleLaunchComm, } from './core/session-discovery.js';
97
99
  import { CODEX_RPC_TERMINAL_HYDRATION_DELAYS_MS, RpcEngagementFence, codexRpcEligible, paneRunsRemoteTui, orchestrateCodexRpcInit, rolloutUserTurnMatches, decideStartupDialogAction, shouldQueueInitialPrompt, shouldPreMarkFirstTurn, killAndVerifyPersistentPane, rpcTranscriptIngestBlockedByAwaitingActivation } from './codex-rpc-lifecycle.js';
98
100
  import { delay } from './utils/timing.js';
99
101
  import { claudeJsonlPathForSession, resolveJsonlFromPid, findOpenClaudeSessionIds, syncClaudeResumeTargetToCwd, DEFAULT_CLAUDE_DATA_DIR } from './adapters/cli/claude-code.js';
@@ -143,7 +145,7 @@ import { withCodexAppContext } from './utils/codex-app-context.js';
143
145
  import { resolveCodexAppFinalTurnIdentity } from './adapters/cli/codex-app-turn.js';
144
146
  import { RunnerControlDecoder } from './adapters/cli/runner-control-channel.js';
145
147
  import { normalizeCodexAppLifecycleEvent, normalizeFinalUsage, } from './services/codex-app-runner-protocol.js';
146
- import { hasMatchingManagedOriginCapability, ensureManagedOriginAttestationDirectory, ensureManagedOriginCapabilityLeafSafe, ensureManagedOriginDataRootProbe, ensureManagedOriginIsolationSentinel, ensureManagedOriginRootLocator, managedOriginLegacyIsolationProbeAccess, managedOriginDataRootProbeAccess, managedOriginIsolationSentinelAccess, managedOriginAttestationDirectory, managedOriginCapabilityPath, managedOriginRootLocatorPath, readManagedOriginAuthorityFile, RELAY_ORIGIN_CAPABILITY_BASENAME, replaceManagedOriginCapabilityFile, sweepManagedOriginAttestationProofs, } from './core/managed-origin-capability.js';
148
+ import { hasMatchingManagedOriginCapability, ensureManagedOriginAttestationDirectory, ensureManagedOriginCapabilityLeafSafe, ensureManagedOriginDataRootProbe, ensureManagedOriginIsolationSentinel, ensureManagedOriginRootLocator, managedOriginLegacyIsolationProbeAccess, managedOriginDataRootProbeAccess, managedOriginIsolationSentinelAccess, managedOriginAttestationDirectory, managedOriginCapabilityDirectory, managedOriginCapabilityPath, managedOriginRootLocatorPath, readManagedOriginAuthorityFile, RELAY_ORIGIN_CAPABILITY_BASENAME, replaceManagedOriginCapabilityFile, sweepManagedOriginAttestationProofs, } from './core/managed-origin-capability.js';
147
149
  import { CodexRpcEngine, } from './codex-rpc-engine.js';
148
150
  import { beginRuntimeWriteCycle, isCliBackendGenerationCurrent, PtyOutputGeneration, projectRuntimeScreenStatus, } from './utils/runtime-screen-status.js';
149
151
  import { AsyncSerialQueue } from './utils/async-serial-queue.js';
@@ -422,14 +424,22 @@ function persistentPaneInfo(backendType, sessionId) {
422
424
  : HerdrBackend.hasSession(name);
423
425
  return { name, live };
424
426
  }
425
- function persistentPaneLive(backendType, name) {
426
- if (backendType === 'tmux')
427
- return TmuxBackend.hasSession(name);
428
- if (backendType === 'zellij')
429
- return ZellijBackend.hasSession(name);
430
- if (backendType === 'herdr')
431
- return HerdrBackend.hasSession(name);
432
- return ZmxBackend.hasSession(name);
427
+ /** Tri-state pane liveness, for callers that must not read "no answer" as an
428
+ * answer. The `hasSession()` helpers collapse an unanswered probe into
429
+ * `false`, which is safe where a wrong guess just means "treat as fresh" but
430
+ * NOT where the boolean is consumed as proof that a kill succeeded. Every
431
+ * backend already classifies this correctly; only the boolean wrapper threw
432
+ * the distinction away. */
433
+ function persistentPaneProbe(backendType, name) {
434
+ const probe = backendType === 'tmux' ? TmuxBackend.probeSession(name)
435
+ : backendType === 'zellij' ? ZellijBackend.probeSession(name)
436
+ : backendType === 'herdr' ? HerdrBackend.probeSession(name)
437
+ : ZmxBackend.probeSession(name);
438
+ if (probe === 'exists')
439
+ return 'live';
440
+ if (probe === 'missing')
441
+ return 'gone';
442
+ return 'unknown';
433
443
  }
434
444
  function probeOwnedZmxSession(name, sessionId, expectedPid) {
435
445
  const managed = ZmxBackend.probeManagedSession(name, sessionId);
@@ -459,7 +469,7 @@ function probeOwnedZmxSession(name, sessionId, expectedPid) {
459
469
  async function killPersistentSessionVerified(backendType, name, sessionId) {
460
470
  return killAndVerifyPersistentPane(name, {
461
471
  kill: (resolvedName) => killPersistentSession(backendType, resolvedName, sessionId),
462
- isLive: (resolvedName) => persistentPaneLive(backendType, resolvedName),
472
+ probeLive: (resolvedName) => persistentPaneProbe(backendType, resolvedName),
463
473
  wait: delay,
464
474
  });
465
475
  }
@@ -1389,6 +1399,25 @@ let spawnArgvInitialPromptBusy = false;
1389
1399
  * arm above; quiescence argv adapters seed working then idle at first ready.
1390
1400
  */
1391
1401
  let spawnArgvNeedsWorkingSeed = false;
1402
+ /**
1403
+ * Startup-window evidence for an argv-baked first prompt: true once the turn
1404
+ * has VISIBLY started — busyPattern seen in the raw PTY stream since spawn, or
1405
+ * a live structured-transcript user/final event ingested. Pi's TUI renders its
1406
+ * input box seconds before it begins consuming the argv prompt (extension /
1407
+ * model loading), and during that gap the screen shows no busy marker and the
1408
+ * transcript has no user record — quiescence then reports a FALSE first idle
1409
+ * ("not started yet", not "turn complete"). Until this latches,
1410
+ * markPromptReady holds ready + re-arms instead of seeding working→idle.
1411
+ */
1412
+ let spawnArgvTurnStartEvidenceSeen = false;
1413
+ /** Rolling ANSI-stripped PTY tail scanned for busyPattern while the argv
1414
+ * first-turn evidence gate is armed; dropped once evidence latches. */
1415
+ let spawnArgvTurnStartBusyScanTail = '';
1416
+ /** Fail-open deadline for the evidence gate: past this instant the gate stops
1417
+ * holding, so an argv prompt the CLI never consumed (e.g. dropped on a
1418
+ * restart) cannot park the card at 工作中 forever. */
1419
+ let spawnArgvTurnStartEvidenceDeadlineMs = 0;
1420
+ let spawnArgvTurnStartFailOpenTimer = null;
1392
1421
  let idleDetector = null;
1393
1422
  let isTmuxMode = false;
1394
1423
  /** True once a crash diagnostic tmux shell (bmx-diag-<sid>) is live. */
@@ -1439,6 +1468,10 @@ const authedClients = new WeakSet();
1439
1468
  const clientPtys = new Map();
1440
1469
  /** Managed-Herdr viewers survive an in-worker /restart while backend changes. */
1441
1470
  const herdrWebBindings = new Map();
1471
+ const readOnlyRemoteScrollLimiter = new ReadOnlyRemoteScrollLimiter({
1472
+ budget: READ_ONLY_REMOTE_SCROLL_SESSION_BUDGET,
1473
+ windowMs: READ_ONLY_REMOTE_SCROLL_WINDOW_MS,
1474
+ });
1442
1475
  // Standalone/test fallback. Production replaces this after init with a stable
1443
1476
  // per-session HMAC derived from the host-only dashboard secret, so an
1444
1477
  // already-issued 「操作链接」/write link survives a worker restart (a silent
@@ -1511,7 +1544,7 @@ let closeRequested = false;
1511
1544
  let capturedSpawnCommand = null;
1512
1545
  let deferredTopicOutputTail = '';
1513
1546
  const reportedDeferredTopicRoots = new Set();
1514
- const CLI_DISPLAY_NAMES = { 'claude-code': 'Claude', seed: 'Seed', relay: 'Relay', aiden: 'Aiden', coco: 'CoCo', codex: 'Codex', 'codex-app': 'Codex App', cursor: 'Cursor', gemini: 'Gemini', genius: 'Genius', opencode: 'OpenCode', antigravity: 'Antigravity', mtr: 'MTR', hermes: 'Hermes', mira: 'Mira', mir: 'Mir CLI', traex: 'TRAE', pi: 'Pi', copilot: 'Copilot', 'oh-my-pi': 'Oh My Pi', kimi: 'Kimi', grok: 'Grok Build', 'kiro-cli': 'Kiro', riff: 'Riff', reasonix: 'Reasonix' };
1547
+ const CLI_DISPLAY_NAMES = { 'claude-code': 'Claude', seed: 'Seed', relay: 'Relay', aiden: 'Aiden', coco: 'CoCo', codex: 'Codex', 'codex-app': 'Codex App', cursor: 'Cursor', gemini: 'Gemini', genius: 'Genius', opencode: 'OpenCode', opencode2: 'OpenCode 2', antigravity: 'Antigravity', mtr: 'MTR', hermes: 'Hermes', mira: 'Mira', mir: 'Mir CLI', traex: 'TRAE', pi: 'Pi', copilot: 'Copilot', 'oh-my-pi': 'Oh My Pi', kimi: 'Kimi', grok: 'Grok Build', 'kiro-cli': 'Kiro', riff: 'Riff', reasonix: 'Reasonix', dsh: 'DeepSeek Harness' };
1515
1548
  function cliName() {
1516
1549
  return (lastInitConfig?.cliRuntime?.source === 'configured'
1517
1550
  ? (lastInitConfig.cliRuntime.displayName?.trim() || lastInitConfig.cliRuntime.id)
@@ -1677,17 +1710,14 @@ function releaseReadyGate(reason, opts) {
1677
1710
  * the next command, capped so a slow/redrawing command can't stall the queue. */
1678
1711
  const STARTUP_CMD_QUIET_MS = 500;
1679
1712
  const STARTUP_CMD_CAP_MS = 4_000;
1680
- /** Inter-keystroke spacing when typing a slash command into CoCo char-by-char.
1681
- * CoCo (Trae CLI, Ink TUI) treats "several bytes delivered in one PTY read" as a
1682
- * paste, which skips command mode + the slash picker — so each char must land as
1683
- * its own keystroke. 40ms is comfortably above CoCo's coalescing window (verified
1684
- * against Trae CLI 0.120.45) while keeping a short command sub-second. */
1685
- const COCO_SLASH_TYPE_THROTTLE_MS = 40;
1686
1713
  /** Type one literal input LINE into the CLI exactly like a passthrough slash
1687
- * command: raw text a 200ms beat (so the TUI's slash-command picker registers
1688
- * the match before submit) a separate Enter. Non-TUI backends fall back to a
1689
- * single write + CR. Shared by the `raw_input` IPC handler and runStartupCommands
1690
- * so both stay in lockstep. */
1714
+ * command. Delivery is adapter-derived: adapters that declare a paste-line
1715
+ * input mode (e.g. OpenCode) paste the line up front, then wait out the
1716
+ * adapter's settle window before pressing Enter; generic backends keep the
1717
+ * classic raw text → a 200ms beat (so the TUI's slash-command picker
1718
+ * registers the match before submit) → a separate Enter, or fall back to a
1719
+ * single write + CR. Shared by the `raw_input` IPC handler and
1720
+ * runStartupCommands so both stay in lockstep. */
1691
1721
  async function sendRawCommandLine(be, content) {
1692
1722
  // PR #597 extracted the CoCo/pty keystroke choreography into the shared
1693
1723
  // writeRawCommandLine helper (unit-tested in raw-command-writer.ts). It
@@ -1695,11 +1725,7 @@ async function sendRawCommandLine(be, content) {
1695
1725
  // recovery-fence transaction detects a failed submission by a thrown error, so
1696
1726
  // bridge the two: turn an explicit `false` into the same throw the inline
1697
1727
  // implementation used, letting runAmbiguousSubmissionTransaction cancel the WAL.
1698
- const accepted = await writeRawCommandLine(be, content, {
1699
- coco: lastInitConfig?.cliId === 'coco',
1700
- cocoThrottleMs: COCO_SLASH_TYPE_THROTTLE_MS,
1701
- submitBeatMs: 200,
1702
- });
1728
+ const accepted = await writeRawCommandLine(be, content, rawCommandWriteOptionsFor(cliAdapter ?? undefined, lastInitConfig?.cliId));
1703
1729
  if (accepted === false) {
1704
1730
  throw new Error('backend rejected command text or submit key input');
1705
1731
  }
@@ -2090,6 +2116,7 @@ let currentBotmuxDispatchAttempt;
2090
2116
  let currentVcMeetingImTurnOrigin;
2091
2117
  let durableTurnInFlight = false;
2092
2118
  function publishSandboxRelayCapability(opts = {}) {
2119
+ const daemonIpcPort = parseDaemonIpcPort(process.env.BOTMUX_DAEMON_IPC_PORT);
2093
2120
  const capability = {
2094
2121
  token: randomBytes(32).toString('hex'),
2095
2122
  ...(currentBotmuxTurnId ? { turnId: currentBotmuxTurnId } : {}),
@@ -2104,6 +2131,21 @@ function publishSandboxRelayCapability(opts = {}) {
2104
2131
  body: JSON.stringify({ token: capability.token }),
2105
2132
  }]
2106
2133
  : []),
2134
+ ...(readIsolationOriginChannelId
2135
+ ? [{
2136
+ path: managedOriginCapabilityPath(process.env.SESSION_DATA_DIR ?? config.session.dataDir, sessionId, readIsolationOriginChannelId),
2137
+ body: JSON.stringify({
2138
+ sessionId,
2139
+ channelId: readIsolationOriginChannelId,
2140
+ capability: capability.token,
2141
+ ...(capability.turnId ? { turnId: capability.turnId } : {}),
2142
+ ...(capability.dispatchAttempt !== undefined
2143
+ ? { dispatchAttempt: capability.dispatchAttempt }
2144
+ : {}),
2145
+ ...(daemonIpcPort !== undefined ? { ipcPort: daemonIpcPort } : {}),
2146
+ }),
2147
+ }]
2148
+ : []),
2107
2149
  ];
2108
2150
  let publishError;
2109
2151
  for (const file of files) {
@@ -3482,7 +3524,8 @@ function failedBridgeFallbackContent(errorCode, summary, partialText) {
3482
3524
  ? 'worker.empty_final_failed_connection'
3483
3525
  : 'worker.empty_final_failed';
3484
3526
  const failure = t(key, { cliName: cliName(), reason });
3485
- return partialText?.trim() ? `${partialText.trim()}\n\n${failure}` : failure;
3527
+ const visiblePartialText = stripTrailingOaiMemoryCitation(partialText ?? '').trim();
3528
+ return visiblePartialText ? `${visiblePartialText}\n\n${failure}` : failure;
3486
3529
  }
3487
3530
  // ─── Bridge fallback marker (non-adopt) ────────────────────────────────────
3488
3531
  //
@@ -4800,8 +4843,11 @@ function currentHermesBridgeDbPath() {
4800
4843
  function structuredBridgeIngestPath(path, offset) {
4801
4844
  if (structuredBridgeIsCodex())
4802
4845
  return drainCodexRollout(path, offset);
4803
- if (structuredBridgeIsTraex())
4804
- return drainTraexRollout(path, offset);
4846
+ // adoptMode gates the drainer's bare-sentinel synthesis: adopt posts
4847
+ // transcript text verbatim, so a synthesised token would leak into Lark.
4848
+ if (structuredBridgeIsTraex()) {
4849
+ return drainTraexRollout(path, offset, { adoptMode: lastInitConfig?.adoptMode === true });
4850
+ }
4805
4851
  if (codexBridgeIsCursor())
4806
4852
  return drainCursorTranscript(path, offset);
4807
4853
  if (structuredBridgeIsPi())
@@ -5599,6 +5645,9 @@ function codexBridgeIngest(opts = {}) {
5599
5645
  if (result.events.length > 0)
5600
5646
  lastStructuredBridgeActivityAtMs = Date.now();
5601
5647
  codexBridgeQueue.ingest(result.events);
5648
+ // After ingest so the latch's delivery re-kick observes the started turn —
5649
+ // the flush's own bridge mark must queue behind it, not ahead of it.
5650
+ noteSpawnArgvTurnStartTranscriptEvidence(result.events);
5602
5651
  pruneExpiredStructuredHeadsAndEmit('structured ingest');
5603
5652
  // Transcript-driven idle: a normal `assistant_final` or no-output
5604
5653
  // `turn_aborted` is Codex declaring end-of-turn, far more reliable than the screen-pattern heuristic
@@ -6020,13 +6069,17 @@ function emitReadyCodexTurns() {
6020
6069
  isLocal: turn.isLocal,
6021
6070
  finalText: turn.finalText,
6022
6071
  terminalStatus: turn.terminalStatus,
6072
+ terminalErrorCode: turn.terminalErrorCode,
6023
6073
  };
6024
- const content = turn.terminalErrorCode !== CODEX_RATE_LIMIT_ERROR_CODE
6025
- && shouldEmitFailedBridgeFallback(gateInput, nextBoundaryMs, markers, adoptMode)
6074
+ // The rate-limit skip is narrowed to CLIs with a dedicated structured
6075
+ // rate-limit chain (Codex): TRAE 429 has no such chain, so skipping the
6076
+ // generic failed fallback would post nothing at all.
6077
+ const fallbackKind = structuredFallbackKind(gateInput, nextBoundaryMs, markers, adoptMode, structuredBridgeIsCodex());
6078
+ const content = fallbackKind === 'failed'
6026
6079
  ? failedBridgeFallbackContent(turn.terminalErrorCode, turn.terminalErrorSummary, turn.finalText)
6027
- : turn.finalText && turn.finalText.trim()
6028
- ? turn.finalText
6029
- : shouldEmitEmptyCompletedBridgeFallback(gateInput, nextBoundaryMs, markers, adoptMode)
6080
+ : fallbackKind === 'final'
6081
+ ? turn.finalText ?? ''
6082
+ : fallbackKind === 'empty_completed'
6030
6083
  ? emptyCompletedBridgeFallbackContent()
6031
6084
  : '';
6032
6085
  if (!content)
@@ -6295,6 +6348,10 @@ function restoreHerdrWebBindings() {
6295
6348
  applyHerdrWebBindingResult(ws, binding.restore());
6296
6349
  }
6297
6350
  }
6351
+ function canHandleReadOnlyRemoteScroll() {
6352
+ return cliAdapter?.readOnlyRemoteScroll === true
6353
+ && !(lastInitConfig?.adoptMode && lastInitConfig.adoptZellijPaneId);
6354
+ }
6298
6355
  function wireHerdrWebTerminalRelays(be) {
6299
6356
  be.onSnapshot(relayHerdrWebSnapshot);
6300
6357
  be.onWebTerminalCursor(relayHerdrWebCursor);
@@ -7548,11 +7605,11 @@ function handleVisibleStartupInteraction(data) {
7548
7605
  }
7549
7606
  return true;
7550
7607
  }
7551
- // Mira/Mir still send terminal OSC control messages. Codex App deliberately
7608
+ // Mira/Mir/dsh send terminal OSC control messages. Codex App deliberately
7552
7609
  // does not (PR #597): its signed Unix-socket channel is independent of the
7553
7610
  // terminal/backend rendering (including Herdr and Zellij), so it is no longer
7554
7611
  // in the terminal-OSC decode set.
7555
- const APP_RUNNER_OSC_CLI_IDS = new Set(['mira', 'mir']);
7612
+ const APP_RUNNER_OSC_CLI_IDS = new Set(['mira', 'mir', 'dsh']);
7556
7613
  const appRunnerControlDecoder = new RunnerControlDecoder();
7557
7614
  let kiroSessionIdCaptureArmed = false;
7558
7615
  let kiroSessionIdCaptureBuffer = '';
@@ -7908,12 +7965,12 @@ async function handleTrustedCodexAppMarker(kind, payload, control) {
7908
7965
  log(`${cliName()} native turn ${nativeTurnId.substring(0, 12)} mapped to botmux turn ${turnId.substring(0, 12)}`);
7909
7966
  }
7910
7967
  let suppressDelivery = false;
7911
- // What actually reaches Lark: strip a trailing sentinel line so the literal
7912
- // token never posts. `finalContent` stays RAW above (the steer_superseded
7913
- // validator asserts finalContent==='' and must see the unstripped payload).
7914
- // If nothing remains after stripping, this was a pure-silence final → treat
7915
- // as suppressed so the daemon persists the FIFO advance without delivering.
7916
- const deliverableContent = stripTrailingBridgeSentinelLine(finalContent);
7968
+ // What actually reaches Lark: strip internal memory attribution and a
7969
+ // trailing sentinel line. `finalContent` stays RAW above (the
7970
+ // steer_superseded validator asserts finalContent==='' and the fallback gate
7971
+ // must see the unstripped payload). If nothing remains, this was metadata or
7972
+ // a pure-silence final persist the FIFO advance without delivering.
7973
+ const deliverableContent = bridgePostText(finalContent, false);
7917
7974
  if (deliverableContent.trim().length === 0 && finalContent.trim().length > 0) {
7918
7975
  suppressDelivery = true;
7919
7976
  }
@@ -8327,6 +8384,20 @@ function onPtyData(data) {
8327
8384
  maybeReportDeferredTopicMaterialization(data);
8328
8385
  maybeCaptureKiroSessionId(data);
8329
8386
  captureWorkflowTranscript(data);
8387
+ // Argv first-turn start evidence (Pi): latch the busy marker from the raw
8388
+ // PTY stream. The viewport-based defer cannot see a marker that has not
8389
+ // rendered yet, and a fast turn can paint and clear `Working...` between
8390
+ // screen samples — the stream sees every byte exactly once.
8391
+ if (spawnArgvNeedsWorkingSeed
8392
+ && !spawnArgvTurnStartEvidenceSeen
8393
+ && structuredBridgeIsPi()
8394
+ && cliAdapter?.busyPattern) {
8395
+ spawnArgvTurnStartBusyScanTail = tailChars(spawnArgvTurnStartBusyScanTail + stripAnsiForLog(data), 500);
8396
+ if (cliAdapter.busyPattern.test(spawnArgvTurnStartBusyScanTail)) {
8397
+ spawnArgvTurnStartEvidenceSeen = true;
8398
+ spawnArgvTurnStartBusyScanTail = '';
8399
+ }
8400
+ }
8330
8401
  renderer?.write(data);
8331
8402
  // In tmux-attach mode, each web client has its own tmux attach PTY —
8332
8403
  // no relay needed. In non-tmux mode AND in pipe mode (adopt-bridge),
@@ -8569,6 +8640,125 @@ function scheduleStructuredStartGraceRecheck(remainingMs) {
8569
8640
  }, Math.max(1, remainingMs + 1));
8570
8641
  structuredStartGraceRecheckTimer.unref?.();
8571
8642
  }
8643
+ /** How long the argv first-turn evidence gate may hold ready with no start
8644
+ * evidence at all. Pi's startup gap (extension/model loading before it
8645
+ * consumes the argv prompt) is typically 3–9s; 90s covers slow hosts while
8646
+ * keeping a never-consumed argv prompt from parking the card at 工作中
8647
+ * forever. */
8648
+ const SPAWN_ARGV_TURN_START_EVIDENCE_GRACE_MS = 90_000;
8649
+ /** True while markPromptReady must hold the argv-baked first prompt's ready:
8650
+ * no "turn started" evidence yet and the bounded grace has not expired.
8651
+ * Scoped to Pi — the only quiescence-argv adapter with BOTH evidence channels
8652
+ * (busyPattern + always-on structured transcript); Gemini/OpenCode/MTR keep
8653
+ * their historical first-idle behavior. Never applies to the Grok-class busy
8654
+ * arm, whose first ready is handled by spawnArgvInitialPromptBusy. */
8655
+ function spawnArgvTurnStartGateHolds() {
8656
+ if (!spawnArgvNeedsWorkingSeed || spawnArgvInitialPromptBusy)
8657
+ return false;
8658
+ if (!structuredBridgeIsPi())
8659
+ return false;
8660
+ if (spawnArgvTurnStartEvidenceSeen)
8661
+ return false;
8662
+ return Date.now() < spawnArgvTurnStartEvidenceDeadlineMs;
8663
+ }
8664
+ /** Latch "the argv first turn actually began" from live structured-transcript
8665
+ * events. The latch must happen at INGEST time, not lazily at gate time: a
8666
+ * fast first turn can be started AND drained/emitted before markPromptReady
8667
+ * runs, leaving no started turn to observe in the queue. A user record is the
8668
+ * authoritative start signal; a final implies start too (covers a drain that
8669
+ * sees both lines at once).
8670
+ *
8671
+ * The latch is also a DELIVERY DRIVER, not just a boolean: when evidence
8672
+ * lands later than the 15s first-prompt timeout, every markPromptReady-based
8673
+ * driver has already fired rejected and stopped (probe one-shot, timeout
8674
+ * short-circuit) and the 90s fail-open stands down on evidenceSeen — with a
8675
+ * silent PTY nothing else would ever deliver a startup-queued message. The
8676
+ * user record hitting disk is itself the proof the TUI accepts input, so
8677
+ * re-kick immediately. (The PTY busyPattern latch needs no re-kick: busy
8678
+ * output implies a later quiescence edge that re-drives markPromptReady.) */
8679
+ function noteSpawnArgvTurnStartTranscriptEvidence(events) {
8680
+ // The whole evidence mechanism is Pi-scoped (matching the onPtyData busy
8681
+ // scan and spawnArgvTurnStartGateHolds); this generic ingest path also
8682
+ // serves Grok — another argv-baked + structured-bridge + type-ahead CLI —
8683
+ // so without this guard the flush side effect below would add a new
8684
+ // startup-window write for Grok (whose first ready is owned by the
8685
+ // SessionStart busy arm, not by this gate).
8686
+ if (!structuredBridgeIsPi())
8687
+ return;
8688
+ if (spawnArgvTurnStartEvidenceSeen || !spawnArgvNeedsWorkingSeed)
8689
+ return;
8690
+ if (!events.some(ev => ev.kind === 'user' || ev.kind === 'assistant_final'))
8691
+ return;
8692
+ spawnArgvTurnStartEvidenceSeen = true;
8693
+ spawnArgvTurnStartBusyScanTail = '';
8694
+ if (!isPromptReady && pendingMessages.length > 0) {
8695
+ log('Argv first-turn start evidence landed via transcript — releasing startup-queued input');
8696
+ flushQueuedInputAfterTurnStartEvidence();
8697
+ }
8698
+ }
8699
+ function stopSpawnArgvTurnStartFailOpen() {
8700
+ if (!spawnArgvTurnStartFailOpenTimer)
8701
+ return;
8702
+ clearTimeout(spawnArgvTurnStartFailOpenTimer);
8703
+ spawnArgvTurnStartFailOpenTimer = null;
8704
+ }
8705
+ /** Turn-start evidence in hand ⇒ startup-queued input becomes deliverable.
8706
+ *
8707
+ * Messages that arrive during startup (awaitingFirstPrompt) skip
8708
+ * handleMessage's type-ahead write (shouldWriteNow holds them, "no input box
8709
+ * yet") and historically relied on markPromptReady()'s tail flush for
8710
+ * delivery. When a Pi argv first turn never lands its terminal record
8711
+ * (terminate:true gap), every ready driver dies rejected: the structured
8712
+ * lifecycle block refuses quiescence idles, the queued-message busy probe
8713
+ * and the 15s first-prompt timeout both short-circuit through the same
8714
+ * rejected markPromptReady(), and the 90s fail-open stands down once
8715
+ * evidence is latched. Without an explicit re-kick the message sits in
8716
+ * pendingMessages forever (card pinned at 工作中, the next transcript user
8717
+ * event never appears, HOL-drop never fires).
8718
+ *
8719
+ * Two call sites, one safety argument — input may only be re-kicked once the
8720
+ * TUI provably booted far enough to accept it:
8721
+ * 1. markPromptReady()'s lifecycle-block rejection (a started turn /
8722
+ * confirmed submit exists);
8723
+ * 2. the transcript-evidence latch in codexBridgeIngest (the user record
8724
+ * just hit disk — the ONLY driver left when evidence lands later than
8725
+ * every probe/timeout and the PTY stays silent).
8726
+ * The argv turn-start evidence gate must NOT re-kick — before any evidence
8727
+ * the TUI may still be loading extensions/models and a type-ahead paste
8728
+ * could be dropped or land in a half-drawn screen (flushPending() has no
8729
+ * awaitingFirstPrompt gate of its own; shouldWriteNow()'s hold is the only
8730
+ * startup input protection).
8731
+ *
8732
+ * flushPending() still re-checks its own admission gates (type-ahead
8733
+ * allowance, durable HOL, injection serialization, restart fences,
8734
+ * ready-gate settle), so a non-type-ahead adapter keeps holding until a real
8735
+ * ready edge; this is a re-kick, never a bypass of those gates. */
8736
+ function flushQueuedInputAfterTurnStartEvidence() {
8737
+ if (pendingMessages.length === 0)
8738
+ return;
8739
+ flushPending();
8740
+ }
8741
+ /** Re-drive the held first idle if no start evidence ever appears. Without
8742
+ * this, a swallowed quiescence idle followed by a fully silent PTY (argv
8743
+ * prompt never consumed) would leave no later signal to re-check the gate.
8744
+ * The re-driven idle still passes deferPromptReadyWhileBusy, so a turn whose
8745
+ * evidence was merely missed keeps deferring on a busy viewport. */
8746
+ function scheduleSpawnArgvTurnStartFailOpen() {
8747
+ if (spawnArgvTurnStartFailOpenTimer)
8748
+ return;
8749
+ const backendAtSchedule = backend;
8750
+ const cliGenerationAtSchedule = cliSpawnGeneration;
8751
+ spawnArgvTurnStartFailOpenTimer = setTimeout(() => {
8752
+ spawnArgvTurnStartFailOpenTimer = null;
8753
+ if (backend !== backendAtSchedule || cliSpawnGeneration !== cliGenerationAtSchedule)
8754
+ return;
8755
+ if (isPromptReady || !spawnArgvNeedsWorkingSeed || spawnArgvTurnStartEvidenceSeen)
8756
+ return;
8757
+ log('Argv-baked first prompt showed no start evidence within grace — failing open to quiescence idle');
8758
+ idleDetector?.fireIdle();
8759
+ }, Math.max(1, spawnArgvTurnStartEvidenceDeadlineMs - Date.now()));
8760
+ spawnArgvTurnStartFailOpenTimer.unref?.();
8761
+ }
8572
8762
  function markPromptReady() {
8573
8763
  if (bareShellLaunchBlocked) {
8574
8764
  log('Ignoring non-PTY prompt-ready while bare-shell launch block is active');
@@ -8644,6 +8834,29 @@ function markPromptReady() {
8644
8834
  }
8645
8835
  if (freshnessAction === 'ignore')
8646
8836
  return;
8837
+ // Argv-baked first prompt start gate (Pi): the TUI paints its input box
8838
+ // seconds before the CLI begins consuming the argv prompt, and that startup
8839
+ // window has no busyPattern on screen and no transcript user record —
8840
+ // quiescence then produces a FALSE first idle ("not started yet", not "turn
8841
+ // complete"). Letting it through would set isPromptReady for the whole turn
8842
+ // and seed working→idle mid-turn (premature ✅ DONE + card flip to 等待输入).
8843
+ // Hold ready and re-arm until the turn visibly starts; bounded fail-open via
8844
+ // SPAWN_ARGV_TURN_START_EVIDENCE_GRACE_MS restores the historical quiescence
8845
+ // behavior if evidence never appears.
8846
+ // Deliberately NO flush re-kick here (unlike the lifecycle block below):
8847
+ // with zero turn-start evidence the TUI may still be booting, and a
8848
+ // type-ahead paste could be dropped or land in a half-drawn screen —
8849
+ // shouldWriteNow()'s awaitingFirstPrompt hold is the only startup input
8850
+ // protection and flushPending() does not re-check it. Queued messages are
8851
+ // delivered the moment evidence appears (the transcript latch re-kicks, a
8852
+ // busy-marker latch implies a later quiescence edge) or by the 90s
8853
+ // fail-open.
8854
+ if (spawnArgvTurnStartGateHolds()) {
8855
+ log('Argv-baked first prompt has not visibly started (no busy marker or transcript user event since spawn) — re-arming idle detector');
8856
+ idleDetector?.reset();
8857
+ scheduleSpawnArgvTurnStartFailOpen();
8858
+ return;
8859
+ }
8647
8860
  // Screen prompt/quiescence is only a UI heuristic. Structured transcript
8648
8861
  // bridges have the stronger lifecycle signal: a transcript-started turn
8649
8862
  // without assistant_final is still running even if the TUI redraw exposes a
@@ -8660,6 +8873,7 @@ function markPromptReady() {
8660
8873
  idleDetector?.reset();
8661
8874
  if (remainingMs !== undefined)
8662
8875
  scheduleStructuredStartGraceRecheck(remainingMs);
8876
+ flushQueuedInputAfterTurnStartEvidence();
8663
8877
  return;
8664
8878
  }
8665
8879
  structuredRejectedReadyEvidenceGeneration = undefined;
@@ -8706,7 +8920,17 @@ function markPromptReady() {
8706
8920
  // make the CLI busy, so the idle state is transient and shouldn't appear
8707
8921
  // in the card. This avoids a false "就绪" flash on daemon restart
8708
8922
  // (where the initial prompt is queued before the CLI becomes idle).
8709
- if (renderer && pendingMessages.length === 0 && pendingAdoptMessages.length === 0 && pendingRawInputs.length === 0 && pendingSessionRename === null && !isFlushing) {
8923
+ //
8924
+ // ALSO skip when the Grok-class busy arm is pending (spawnArgvInitialPromptBusy):
8925
+ // for these adapters the FIRST ready is a pre-execution SessionStart edge, not a
8926
+ // turn boundary — the argv-baked first prompt is still running. isPromptReady was
8927
+ // just set true above, so this generic snapshot would project 'idle' and reach the
8928
+ // daemon BEFORE the busy arm below re-publishes 'working'. Combined with the
8929
+ // first-turn working already sent by startScreenUpdates, the daemon would then see
8930
+ // working→idle and fire finishTurnReactions() — a premature ✅ DONE mid-turn (and a
8931
+ // 「工作中→等待输入→工作中」 flicker on the open card). The busy arm below owns the
8932
+ // correct 'working' publish for this path, so this idle must not escape first.
8933
+ if (renderer && !spawnArgvInitialPromptBusy && pendingMessages.length === 0 && pendingAdoptMessages.length === 0 && pendingRawInputs.length === 0 && pendingSessionRename === null && !isFlushing) {
8710
8934
  const { content } = renderer.snapshot();
8711
8935
  send({
8712
8936
  type: 'screen_update',
@@ -9079,7 +9303,10 @@ async function detectBareShellLaunch() {
9079
9303
  // rcfile may still finish and exec the CLI after this bounded check.
9080
9304
  const launchShell = (lastInitConfig?.launchShell || process.env.SHELL || '').trim();
9081
9305
  const expectedShell = launchShell ? basename(launchShell) : '';
9082
- const trampolined = bareShellLaunchKind(comm, expectedShell) === 'trampoline';
9306
+ if (!comm)
9307
+ return false;
9308
+ const shellComm = comm;
9309
+ const trampolined = bareShellLaunchKind(shellComm, expectedShell) === 'trampoline';
9083
9310
  bareShellLaunchBlocked = true;
9084
9311
  // Injection-first flushes can enter while isPromptReady is still true. Make
9085
9312
  // this a real busy/blocked transition so a later PTY-ready event re-enters
@@ -9094,17 +9321,18 @@ async function detectBareShellLaunch() {
9094
9321
  const cli = cliName();
9095
9322
  let message;
9096
9323
  if (trampolined) {
9324
+ const guidance = bareShellLaunchGuidance(shellComm, expectedShell);
9097
9325
  message =
9098
- `⚠️ 会话没能启动:pane 里现在是裸 \`${comm}\`,${cli} 没真正跑起来——所以我没把你的消息打进去(否则会被当 shell 命令执行,报 \`parse error\`)。\n\n` +
9099
- `最可能原因:botmux 用 \`${expectedShell}\` 启动 CLI,但 pane 落到了 \`${comm}\`。通常是 rc 文件(如 \`~/.${expectedShell}rc\`)里有 \`exec ${comm}\` 这类跳转——\`${expectedShell} -i\` 会 source rc,于是 shell 被顶替,CLI 的启动命令没机会跑。\n\n` +
9326
+ `⚠️ 会话没能启动:pane 里现在是裸 \`${shellComm}\`,${cli} 没真正跑起来——所以我没把你的消息打进去(否则会被当 shell 命令执行,报 \`parse error\`)。\n\n` +
9327
+ `最可能原因:botmux 用 \`${expectedShell}\` 启动 CLI,但 pane 落到了 \`${shellComm}\`。通常是 rc 文件(如 \`${guidance.rcFileHint}\`)里有 \`exec ${shellComm}\` 这类跳转——\`${expectedShell} -i\` 会 source rc,于是 shell 被顶替,CLI 的启动命令没机会跑。\n\n` +
9100
9328
  `两种修法(任选其一,改完重启 daemon 再发一条消息):\n` +
9101
- `① 给那行加守卫,只在手动开终端时切:\`[ -z "$BASH_EXECUTION_STRING" ] && [ -t 1 ] && exec ${comm}\`(注意 PATH/nvm 等导出放在它之前)\n` +
9102
- `② 给这个 bot 配 \`launchShell: ${comm}\`(dashboard 机器人配置,或 \`/config launchShell ${comm}\`),直接用 \`${comm}\` 启动绕开 \`${expectedShell}\` 的 rc——但要确保 PATH/nvm 在 \`${comm}\` 的 rc 里。`;
9329
+ `① 给那行加守卫,只在手动开终端时切:\`${guidance.manualTerminalGuard}\`(注意 PATH/nvm 等导出放在它之前)\n` +
9330
+ `② 给这个 bot 配 \`launchShell: ${shellComm}\`(dashboard 机器人配置,或 \`/config launchShell ${shellComm}\`),直接用 \`${shellComm}\` 启动绕开 \`${expectedShell}\` 的 rc——但要确保 PATH/nvm 在 \`${shellComm}\` 的 rc 里。`;
9103
9331
  }
9104
9332
  else {
9105
9333
  message =
9106
- `⚠️ ${cli} 启动时间较长:pane 里暂时仍是 \`${comm}\`。我没有把消息写进 shell,消息还在队列里;检测到真实输入框后会自动继续投递,无需重发。\n\n` +
9107
- `仅凭进程仍是 \`${comm}\` 无法判断具体原因,可能只是 rc 文件或机器负载让启动变慢。若长时间没有恢复,请打开 Web 终端查看当前提示,处理后等待自动继续,或使用 \`/restart\` 重启会话。`;
9334
+ `⚠️ ${cli} 启动时间较长:pane 里暂时仍是 \`${shellComm}\`。我没有把消息写进 shell,消息还在队列里;检测到真实输入框后会自动继续投递,无需重发。\n\n` +
9335
+ `仅凭进程仍是 \`${shellComm}\` 无法判断具体原因,可能只是 rc 文件或机器负载让启动变慢。若长时间没有恢复,请打开 Web 终端查看当前提示,处理后等待自动继续,或使用 \`/restart\` 重启会话。`;
9108
9336
  }
9109
9337
  const pendingTurn = pendingMessages[0];
9110
9338
  send({
@@ -9985,8 +10213,48 @@ function startScreenUpdates() {
9985
10213
  // watermark — there we must capture every tick (see shouldCaptureScreen).
9986
10214
  let lastSnapshotPtyActivity = -1;
9987
10215
  screenUpdateTimer = setInterval(() => {
9988
- if (awaitingFirstPrompt)
10216
+ if (awaitingFirstPrompt) {
10217
+ // First-turn 「工作中」 publisher. The async sampler below is fully gated
10218
+ // until the first turn ends (markPromptReady flips awaitingFirstPrompt) or
10219
+ // the 15s soft timeout, so an argv-baked first prompt (Pi/Grok/…, delivered
10220
+ // via the launch command — it never goes through flushPending) would emit
10221
+ // NO screen_update for the whole turn: the daemon card sits at 'starting'
10222
+ // and jumps straight to 「等待输入」 on the first idle, skipping 「工作中」.
10223
+ //
10224
+ // The status projection already reads 'working' during turn one (isPromptReady
10225
+ // is still false — same rule that makes every post-submit turn working), so we
10226
+ // simply PUBLISH that projection once instead of scraping the screen for a busy
10227
+ // marker. This is the general "sent to the CLI ⇒ working" model: it fires for
10228
+ // every argv-baked first prompt (spawnArgvNeedsWorkingSeed), not just CLIs that
10229
+ // happen to render a detectable busyPattern.
10230
+ //
10231
+ // Gate on spawnArgvNeedsWorkingSeed so it stays a no-op for a NON-argv first
10232
+ // turn (Claude/Codex/type-ahead): there the prompt is still queued and the CLI
10233
+ // is genuinely booting — not working — until flushPending() submits it after
10234
+ // the ready edge. Empty-prompt adopt sessions also don't arm the flag, so an
10235
+ // attach never falsely claims working.
10236
+ //
10237
+ // Pure publisher: it only send()s a status and updates the local lastSentStatus
10238
+ // dedup — it never touches isPromptReady, the idle detector, or the argv seed
10239
+ // flags (spawnArgvInitialPromptBusy / -NeedsWorkingSeed), so the first-prompt
10240
+ // evidence machinery and the end-of-turn seed are unchanged.
10241
+ if (spawnArgvNeedsWorkingSeed && lastSentStatus !== 'working' && renderer) {
10242
+ const projected = projectedRuntimeScreenStatus();
10243
+ if (projected === 'working') {
10244
+ const { content } = renderer.snapshot();
10245
+ lastSentStatus = 'working';
10246
+ send({
10247
+ type: 'screen_update',
10248
+ content,
10249
+ status: 'working',
10250
+ turnId: currentBotmuxTurnId,
10251
+ dispatchAttempt: currentBotmuxDispatchAttempt,
10252
+ });
10253
+ log('Argv-baked first prompt in flight — publishing projected working before first prompt');
10254
+ }
10255
+ }
9989
10256
  return;
10257
+ }
9990
10258
  void (async () => {
9991
10259
  const { snapshot, status } = await snapshotWithLatestRuntimeStatus(async () => {
9992
10260
  let content = lastContent;
@@ -10708,6 +10976,8 @@ async function spawnCli(cfg, opts = {}) {
10708
10976
  // Feishu. Thread the flag so the reconstructed config keeps the boundary.
10709
10977
  if (cfg.apiOnly)
10710
10978
  sessionEnv.BOTMUX_API_ONLY = '1';
10979
+ if (cfg.feedback)
10980
+ sessionEnv.BOTMUX_FEEDBACK_POLICY = JSON.stringify(cfg.feedback);
10711
10981
  // Session scope for `botmux send` inside the sandbox. Thread sessions
10712
10982
  // anchor on a real om_ message (reply_in_thread); chat-scope sessions use
10713
10983
  // the chat id as anchor (sessionAnchorId), which is NOT a message id —
@@ -10744,6 +11014,12 @@ async function spawnCli(cfg, opts = {}) {
10744
11014
  // Per-bot env (bots.json `env`) takes precedence over session context;
10745
11015
  // explicit riff config.env takes precedence over both.
10746
11016
  const mergedEnv = { ...sessionEnv, ...sanitizePerBotEnv(cfg.env), ...cfg.backendConfig.env };
11017
+ // The effective policy is a host-resolved snapshot, not a user-overridable
11018
+ // backend env knob. Re-freeze it after config.env/per-bot env merge.
11019
+ if (cfg.feedback)
11020
+ mergedEnv.BOTMUX_FEEDBACK_POLICY = JSON.stringify(cfg.feedback);
11021
+ else
11022
+ delete mergedEnv.BOTMUX_FEEDBACK_POLICY;
10747
11023
  // Re-freeze the no-transport capability keys AFTER the merge: a stale or
10748
11024
  // attacker-shaped backendConfig.env / per-bot env merges LAST and would
10749
11025
  // otherwise override the frozen values, restoring send capability for a
@@ -10896,6 +11172,19 @@ async function spawnCli(cfg, opts = {}) {
10896
11172
  resolvedBin: canonicalPolicyPath(cliAdapter.resolvedBin),
10897
11173
  })
10898
11174
  : undefined;
11175
+ const managedOriginChannelRequired = willReadIsolate
11176
+ || credentialOnlySeatbelt
11177
+ || credentialOnlyBwrap;
11178
+ const managedOriginChannelPolicyDigest = (willReadIsolate || willWriteSandbox)
11179
+ ? darwinIsolationPolicyDigest
11180
+ : managedOriginChannelRequired
11181
+ ? createHash('sha256').update(JSON.stringify({
11182
+ domain: 'botmux.credential-origin-channel.v1',
11183
+ platform: process.platform,
11184
+ configuredBotmuxHome: canonicalPolicyPath(configuredBotmuxHome),
11185
+ defaultBotmuxHome: canonicalPolicyPath(defaultBotmuxHome),
11186
+ })).digest('hex')
11187
+ : undefined;
10899
11188
  let mcpRuntimeManifest = readSessionMcpRuntimeManifest(cfg.sessionId, config.session.dataDir);
10900
11189
  const hasMcpRuntimeEntries = !!cliAdapter.mcpGateway && !!mcpRuntimeManifest?.entries.length;
10901
11190
  const reuseRecordedHerdrTarget = !sandboxRequested && !hasMcpRuntimeEntries;
@@ -11052,8 +11341,7 @@ async function spawnCli(cfg, opts = {}) {
11052
11341
  if (paneLive) {
11053
11342
  const markerPath = join(isolationRuntimeDataDir, 'read-isolation', `${cfg.sessionId}.boot`);
11054
11343
  const marker = readManagedOriginAuthorityFile(markerPath);
11055
- const darwinPolicyExpected = process.platform === 'darwin'
11056
- && (willReadIsolate || willWriteSandbox);
11344
+ const originChannelPolicyExpected = !!managedOriginChannelPolicyDigest;
11057
11345
  // A stamped pane must match even when the new policy is OFF. Otherwise a
11058
11346
  // disable followed by restart could reattach the still-confined process
11059
11347
  // without rebuilding its authority/profile. An unsafe planted marker
@@ -11062,16 +11350,16 @@ async function spawnCli(cfg, opts = {}) {
11062
11350
  ? isolatedPaneReattachSafe(marker, {
11063
11351
  requiredCapabilities: appliedIsolationCapabilities,
11064
11352
  exactCapabilities: true,
11065
- ...(darwinPolicyExpected ? {
11353
+ ...(originChannelPolicyExpected ? {
11066
11354
  readIsolation: willReadIsolate,
11067
11355
  writeSandbox: willWriteSandbox,
11068
11356
  requireOriginChannel: true,
11069
- policyDigest: darwinIsolationPolicyDigest,
11357
+ policyDigest: managedOriginChannelPolicyDigest,
11070
11358
  } : {}),
11071
11359
  })
11072
11360
  : marker === null && !hostEntryExistsNoFollow(markerPath);
11073
11361
  if (policyMatches) {
11074
- if (darwinPolicyExpected) {
11362
+ if (originChannelPolicyExpected) {
11075
11363
  persistentPaneOriginChannelId = isolatedPaneOriginChannel(marker);
11076
11364
  }
11077
11365
  // Pane was spawned under the current isolation policy → still confined
@@ -11132,7 +11420,7 @@ async function spawnCli(cfg, opts = {}) {
11132
11420
  }
11133
11421
  }
11134
11422
  }
11135
- readIsolationOriginChannelId = willReadIsolate
11423
+ readIsolationOriginChannelId = managedOriginChannelRequired
11136
11424
  ? (persistentPaneOriginChannelId ?? randomBytes(32).toString('hex'))
11137
11425
  : null;
11138
11426
  if (readIsolationOriginChannelId) {
@@ -11463,6 +11751,14 @@ async function spawnCli(cfg, opts = {}) {
11463
11751
  injectsReadyHook: cliAdapter.injectsReadyHook === true,
11464
11752
  reliableTurnTerminal: cliAdapter.reliableTurnTerminal === true,
11465
11753
  });
11754
+ // Fresh evidence window for the argv first-turn start gate (Pi): the new
11755
+ // generation must not inherit a previous spawn's latch or fail-open timer.
11756
+ stopSpawnArgvTurnStartFailOpen();
11757
+ spawnArgvTurnStartEvidenceSeen = false;
11758
+ spawnArgvTurnStartBusyScanTail = '';
11759
+ spawnArgvTurnStartEvidenceDeadlineMs = spawnArgvNeedsWorkingSeed
11760
+ ? Date.now() + SPAWN_ARGV_TURN_START_EVIDENCE_GRACE_MS
11761
+ : 0;
11466
11762
  if (deferInitialPrompt && preparedDeferredInput) {
11467
11763
  piInitialPromptAdditionalArgs = [...(preparedDeferredInput.additionalArgs ?? [])];
11468
11764
  piInitialPromptEnv = { ...(preparedDeferredInput.env ?? {}) };
@@ -11479,7 +11775,7 @@ async function spawnCli(cfg, opts = {}) {
11479
11775
  try {
11480
11776
  const credPath = sendCredFilePath(process.env.SESSION_DATA_DIR, cfg.larkAppId);
11481
11777
  mkdirSync(dirname(credPath), { recursive: true });
11482
- writeFileSync(credPath, JSON.stringify({ larkAppId: cfg.larkAppId, larkAppSecret: cfg.larkAppSecret, brand: cfg.brand, apiOnly: cfg.apiOnly }), { mode: 0o600 });
11778
+ writeFileSync(credPath, JSON.stringify({ larkAppId: cfg.larkAppId, larkAppSecret: cfg.larkAppSecret, brand: cfg.brand, apiOnly: cfg.apiOnly, feedback: cfg.feedback }), { mode: 0o600 });
11483
11779
  }
11484
11780
  catch (e) {
11485
11781
  log(`[sandbox] WARN could not write send-cred file: ${e.message}`);
@@ -11516,6 +11812,8 @@ async function spawnCli(cfg, opts = {}) {
11516
11812
  larkAppId: cfg.larkAppId,
11517
11813
  locale: cfg.locale,
11518
11814
  model: ttadkGateway ? undefined : cfg.model,
11815
+ // dsh runner only; other adapters ignore the field.
11816
+ turnTimeoutMs: cfg.turnTimeoutMs,
11519
11817
  reasoningEffort: cfg.reasoningEffort,
11520
11818
  disableCliBypass: cfg.disableCliBypass === true,
11521
11819
  // Codex-family hook-trust bypass: global toggle (default ON) so a headless
@@ -11772,8 +12070,19 @@ async function spawnCli(cfg, opts = {}) {
11772
12070
  throw new Error('[read-isolation] origin channel is unavailable');
11773
12071
  }
11774
12072
  childEnv.BOTMUX_READ_ISOLATED = '1';
12073
+ }
12074
+ if (readIsolationOriginChannelId) {
11775
12075
  childEnv.BOTMUX_ORIGIN_CHANNEL_ID = readIsolationOriginChannelId;
11776
12076
  }
12077
+ // Credential-only children have no writable relay outbox. Publish the same
12078
+ // rotating managed-origin claim through a host-owned per-channel directory
12079
+ // before the Seatbelt/bwrap wrapper is compiled, then expose only that
12080
+ // directory read-only below.
12081
+ if (readIsolationOriginChannelId && !sandboxRequested) {
12082
+ readIsolationOriginCapabilityFile = managedOriginCapabilityPath(isolationRuntimeDataDir, cfg.sessionId, readIsolationOriginChannelId);
12083
+ ensureManagedOriginCapabilityLeafSafe(readIsolationOriginCapabilityFile);
12084
+ publishSandboxRelayCapability({ failClosed: true });
12085
+ }
11777
12086
  // Per-bot env (bots.json `env`): extra vars for THIS bot's CLI only — e.g.
11778
12087
  // ANTHROPIC_BASE_URL/ANTHROPIC_AUTH_TOKEN to run a bot on GLM/a 3rd-party
11779
12088
  // provider, an HTTPS_PROXY, or a CLI feature flag. Passed as injectEnv (NOT
@@ -11948,6 +12257,11 @@ async function spawnCli(cfg, opts = {}) {
11948
12257
  writeFileSync(tsFile, '');
11949
12258
  }
11950
12259
  catch { /* */ }
12260
+ // UserPromptSubmit sidecar 目录(#794):daemon 逐 turn 写入,沙盒内 hook 只读。
12261
+ try {
12262
+ mkdirSync(join(dataDir, 'prompt-ctx', cfg.sessionId), { recursive: true, mode: 0o700 });
12263
+ }
12264
+ catch { /* */ }
11951
12265
  try {
11952
12266
  mkdirSync(join(dataDir, 'attachments', cfg.larkAppId), { recursive: true });
11953
12267
  }
@@ -12002,7 +12316,7 @@ async function spawnCli(cfg, opts = {}) {
12002
12316
  if (readIsolationOriginCapabilityFile) {
12003
12317
  ensureManagedOriginCapabilityLeafSafe(readIsolationOriginCapabilityFile);
12004
12318
  publishSandboxRelayCapability({ failClosed: true });
12005
- mandatoryReadOnlyPaths.push(readIsolationOriginCapabilityFile);
12319
+ mandatoryReadOnlyPaths.push(managedOriginCapabilityDirectory(dataDir, cfg.sessionId, readIsolationOriginChannelId));
12006
12320
  mandatoryReadOnlyPaths.push(managedOriginAttestationDirectory(dataDir, cfg.sessionId, readIsolationOriginChannelId));
12007
12321
  mandatoryReadOnlyPaths.push(managedOriginRootLocatorPath(userInfo().homedir, cfg.sessionId));
12008
12322
  }
@@ -12288,12 +12602,12 @@ async function spawnCli(cfg, opts = {}) {
12288
12602
  try {
12289
12603
  const markerDir = join(isolationRuntimeDataDir, 'read-isolation');
12290
12604
  mkdirSync(markerDir, { recursive: true });
12291
- replaceManagedOriginCapabilityFile(join(markerDir, `${cfg.sessionId}.boot`), isolationPaneMarkerContent(cfg.daemonBootId ?? '', appliedIsolationCapabilities, willReadIsolate || willWriteSandbox
12605
+ replaceManagedOriginCapabilityFile(join(markerDir, `${cfg.sessionId}.boot`), isolationPaneMarkerContent(cfg.daemonBootId ?? '', appliedIsolationCapabilities, managedOriginChannelPolicyDigest
12292
12606
  ? {
12293
12607
  originChannelId: persistentPaneOriginChannelId,
12294
12608
  readIsolation: willReadIsolate,
12295
12609
  writeSandbox: willWriteSandbox,
12296
- policyDigest: darwinIsolationPolicyDigest,
12610
+ policyDigest: managedOriginChannelPolicyDigest,
12297
12611
  }
12298
12612
  : undefined));
12299
12613
  }
@@ -12337,11 +12651,13 @@ async function spawnCli(cfg, opts = {}) {
12337
12651
  // the input box). cjadk's own botmux integration (`cjadk feishu`, see its
12338
12652
  // botmux-wrapper-writer) sets CJADK_INTERACTIVE=0 to disable all of that.
12339
12653
  // We mirror it here so a `cjadk <agent>` wrapperCli is driven the way
12340
- // cjadk intends — no selector, clean soft-newline input. Keyed on the
12341
- // wrapper's leading token so only cjadk launches are affected.
12342
- if (parseWrapperCli(cfg.wrapperCli)[0] === 'cjadk') {
12343
- childEnv.CJADK_INTERACTIVE = '0';
12344
- log('cjadk launcher: set CJADK_INTERACTIVE=0 (non-interactive, mirrors cjadk feishu wrapper)');
12654
+ // cjadk intends — no selector, clean soft-newline input. The env set
12655
+ // comes from wrapperLaunchEnv the shared single source of truth also
12656
+ // used by one-shot children (session-group AI titling).
12657
+ const wrapperEnv = wrapperLaunchEnv(cfg.wrapperCli);
12658
+ if (wrapperEnv) {
12659
+ Object.assign(childEnv, wrapperEnv);
12660
+ log(`wrapper launcher env applied: ${Object.keys(wrapperEnv).join(', ')} (mirrors the wrapper's own non-interactive integration)`);
12345
12661
  }
12346
12662
  }
12347
12663
  }
@@ -12379,8 +12695,9 @@ async function spawnCli(cfg, opts = {}) {
12379
12695
  });
12380
12696
  const profileDir = join(isolationRuntimeDataDir, 'read-isolation');
12381
12697
  mkdirSync(profileDir, { recursive: true });
12698
+ const originDirectory = managedOriginCapabilityDirectory(isolationRuntimeDataDir, cfg.sessionId, readIsolationOriginChannelId);
12382
12699
  const profilePath = join(profileDir, `${cfg.sessionId}.sb`);
12383
- replaceManagedOriginCapabilityFile(profilePath, buildSeatbeltProfile(rules.denyPaths.map(canonical), [], [], [], rules.denyRegexes, undefined, {
12700
+ replaceManagedOriginCapabilityFile(profilePath, buildSeatbeltProfile([...rules.denyPaths.map(canonical), canonical(profileDir)], [canonical(originDirectory)], [], [canonical(profileDir)], rules.denyRegexes, undefined, {
12384
12701
  denyWritePaths: [...new Set([
12385
12702
  ...rules.denyWritePaths.map(canonical),
12386
12703
  canonical(profileDir),
@@ -12482,7 +12799,10 @@ async function spawnCli(cfg, opts = {}) {
12482
12799
  const credentialSandbox = prepareCredentialOnlySandbox({
12483
12800
  hideDirectories: [...hideDirectories],
12484
12801
  hideFiles: [...hideFiles],
12485
- readonlyPaths: [realpathSync(panePolicyDir)],
12802
+ privateReadonlyDirectories: [{
12803
+ parent: realpathSync(panePolicyDir),
12804
+ directory: realpathSync(managedOriginCapabilityDirectory(isolationRuntimeDataDir, cfg.sessionId, readIsolationOriginChannelId)),
12805
+ }],
12486
12806
  workingDir: spawnCwd,
12487
12807
  cliBin: credentialCliBin,
12488
12808
  cliArgs: spawnArgs,
@@ -13319,6 +13639,8 @@ function killCli(opts = {}) {
13319
13639
  stopReattachIdleProbe();
13320
13640
  stopBusyPatternIdleProbe();
13321
13641
  stopStructuredStartGraceRecheck();
13642
+ stopSpawnArgvTurnStartFailOpen();
13643
+ spawnArgvTurnStartBusyScanTail = '';
13322
13644
  structuredRejectedReadyEvidenceGeneration = undefined;
13323
13645
  ptyOutputGeneration.reset();
13324
13646
  // Cancel any pending ready-gate fallback / settle timers; spawnCli re-arms on respawn.
@@ -13590,6 +13912,7 @@ function startWebServer(host, preferredPort) {
13590
13912
  // normal buffer until resize causes a redraw. Preserve that mode so
13591
13913
  // scroll gestures continue to target the CLI's own transcript.
13592
13914
  const forceRemoteScroll = effectiveBackendType === 'herdr' && cliAdapter?.altScreen === true;
13915
+ const allowReadOnlyRemoteScroll = canHandleReadOnlyRemoteScroll();
13593
13916
  // The wheel burst cap throttles backends where a forwarded wheel is
13594
13917
  // EXPENSIVE or has no local terminal to drive:
13595
13918
  // • Herdr — each forwarded wheel → pane send-text + snapshot re-render.
@@ -13607,7 +13930,7 @@ function startWebServer(host, preferredPort) {
13607
13930
  || effectiveBackendType === 'tmux'
13608
13931
  || effectiveBackendType === 'zellij';
13609
13932
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
13610
- res.end(getTerminalHtml(hasWrite, platformReadonly, loginUrl, forceRemoteScroll, localTerminalBackend));
13933
+ res.end(getTerminalHtml(hasWrite, platformReadonly, loginUrl, forceRemoteScroll, localTerminalBackend, allowReadOnlyRemoteScroll));
13611
13934
  });
13612
13935
  wss = new WebSocketServer({
13613
13936
  server: httpServer,
@@ -13639,6 +13962,7 @@ function startWebServer(host, preferredPort) {
13639
13962
  return;
13640
13963
  }
13641
13964
  const { hasWrite } = resolveTerminalAccessForReq(req, url);
13965
+ const allowReadOnlyRemoteScroll = canHandleReadOnlyRemoteScroll();
13642
13966
  if (hasWrite)
13643
13967
  authedClients.add(ws);
13644
13968
  log(`WS client connected (total: ${wsClients.size}, write: ${hasWrite})`);
@@ -13864,8 +14188,17 @@ function startWebServer(host, preferredPort) {
13864
14188
  scrollback,
13865
14189
  onError: log,
13866
14190
  });
13867
- if (seed.length > 0) {
13868
- ws.send(seed + herdrWebCursorSequence());
14191
+ // A capture-pane seed carries screen cells but no DECSET state: a fresh
14192
+ // client xterm never learns the CLI enabled mouse tracking (grok build:
14193
+ // 1003+1006), so clicks/double-clicks are silently swallowed instead of
14194
+ // reported. Re-assert the pane's live input modes after the seed —
14195
+ // write clients only: a read-only viewer forwards no input anyway, and
14196
+ // mouse-mode xterm would break its plain select-to-copy. Raw-scrollback
14197
+ // seeds already contain the original DECSET bytes; backends that can't
14198
+ // be queried (herdr/zmx/pty) simply don't implement the hook.
14199
+ const modeSeed = hasWrite ? (backend?.capturePaneInputModes?.() ?? '') : '';
14200
+ if (seed.length > 0 || modeSeed.length > 0) {
14201
+ ws.send(seed + modeSeed + herdrWebCursorSequence());
13869
14202
  }
13870
14203
  ws.on('message', (raw) => {
13871
14204
  try {
@@ -13890,6 +14223,18 @@ function startWebServer(host, preferredPort) {
13890
14223
  }
13891
14224
  backend?.write(msg.data);
13892
14225
  }
14226
+ else if (msg.type === 'scroll' && typeof msg.data === 'string') {
14227
+ if (!allowReadOnlyRemoteScroll || authedClients.has(ws))
14228
+ return;
14229
+ const parsed = parseReadOnlyRemoteScrollPayload(msg.data);
14230
+ if (!parsed)
14231
+ return;
14232
+ if (!readOnlyRemoteScrollLimiter.tryConsume(parsed.eventCount))
14233
+ return;
14234
+ if (usesHerdrSnapshotWebHistory())
14235
+ herdrWebScrollDirection = parsed.direction;
14236
+ backend?.write(msg.data);
14237
+ }
13893
14238
  }
13894
14239
  catch { /* ignore non-JSON or bad messages */ }
13895
14240
  });
@@ -13912,7 +14257,7 @@ function startWebServer(host, preferredPort) {
13912
14257
  .then(resolve, reject);
13913
14258
  });
13914
14259
  }
13915
- function getTerminalHtml(hasWrite, platformReadonly = false, loginUrl = '', forceRemoteScroll = false, localTerminalBackend = false) {
14260
+ function getTerminalHtml(hasWrite, platformReadonly = false, loginUrl = '', forceRemoteScroll = false, localTerminalBackend = false, allowReadOnlyRemoteScroll = false) {
13916
14261
  const label = sessionId.substring(0, 8);
13917
14262
  return `<!DOCTYPE html>
13918
14263
  <html>
@@ -14050,6 +14395,7 @@ var hasToken=${hasWrite};
14050
14395
  var platformReadonly=${platformReadonly};
14051
14396
  var remoteScroll=${forceRemoteScroll};
14052
14397
  var localTerminalBackend=${localTerminalBackend};
14398
+ var readOnlyRemoteScroll=${allowReadOnlyRemoteScroll};
14053
14399
  if(!hasToken){
14054
14400
  if(platformReadonly){var _lb=document.getElementById('login-banner');_lb.classList.add('show');}
14055
14401
  else{var _rb=document.getElementById('readonly-banner');_rb.classList.add('show');_rb.addEventListener('click',function(){_rb.classList.remove('show')});}
@@ -14253,13 +14599,34 @@ if(hasToken && !/[?&]imefix=0\\b/.test(location.search)){(function(){
14253
14599
 
14254
14600
  // ── WebSocket ──
14255
14601
  var ws_=null,el=document.getElementById('status');
14602
+ function _sendInput(d){if(ws_&&ws_.readyState===1)ws_.send(JSON.stringify({type:'input',data:d}))}
14603
+ // Pure pointer-motion reports (SGR button code 35 + shift/alt/ctrl modifier
14604
+ // bits). Emitted by xterm once the seed re-asserts DECSET 1003 (grok build) —
14605
+ // one per cell crossed. Each forwarded report costs a synchronous tmux
14606
+ // send-keys exec in the worker, so an unthrottled sweep across a 120-col pane
14607
+ // would stall the relay for every viewer. Clicks/drags/wheel stay immediate.
14608
+ var _MOTION_RE=/^(?:\\x1b\\[<(?:35|39|43|47|51|55|59|63);\\d+;\\d+[Mm])+$/;
14609
+ var _motionPend=null,_motionT=0;
14256
14610
  term.onData(function(d){
14257
14611
  if(!hasToken){
14258
14612
  // Mouse escape sequences are input too: a TUI can bind clicks or wheel
14259
14613
  // events to actions. View links never forward terminal bytes.
14260
14614
  _showReadonlyToast();return;
14261
14615
  }
14262
- if(ws_&&ws_.readyState===1)ws_.send(JSON.stringify({type:'input',data:d}));
14616
+ if(_MOTION_RE.test(d)){
14617
+ // Trailing throttle: keep only the LATEST motion, flush every 90ms. Hover
14618
+ // feedback survives; the send-keys exec rate is bounded (~11/s).
14619
+ _motionPend=d;
14620
+ if(!_motionT)_motionT=setTimeout(function(){
14621
+ _motionT=0;if(_motionPend){_sendInput(_motionPend);_motionPend=null}
14622
+ },90);
14623
+ return;
14624
+ }
14625
+ // A press/release/drag/key supersedes any pending motion (it carries its own
14626
+ // coordinates); dropping it preserves event ordering for the TUI.
14627
+ if(_motionT){clearTimeout(_motionT);_motionT=0}
14628
+ _motionPend=null;
14629
+ _sendInput(d);
14263
14630
  });
14264
14631
  var fixedSize=false,_lastC=0,_lastR=0,_rzT=0;
14265
14632
  function sendResize(){
@@ -14346,8 +14713,9 @@ window.addEventListener('resize',onViewportResize);
14346
14713
  // Alt-screen + mouse-mode CLIs (e.g. Claude Code) keep NO scrollback in xterm OR
14347
14714
  // tmux — their whole transcript is redrawn by the app inside the fixed alt-screen
14348
14715
  // grid, so term.scrollLines() reveals nothing. In the alternate buffer we forward
14349
- // scrolling as SGR mouse-wheel events so the CLI scrolls its own transcript and
14350
- // repaints. This is write-capability only; view links stay locally scrollable.
14716
+ // scrolling as SGR mouse-wheel events so the CLI scrolls its own transcript.
14717
+ // Read-only viewers get this remote path only for adapters that explicitly opt
14718
+ // in; the server then accepts only validated wheel events, never general input.
14351
14719
  // Normal-buffer CLIs keep xterm's native scrollback scroll. Capture-phase +
14352
14720
  // stopPropagation pre-empts xterm's own handler. Skipped for pure tmux/zellij
14353
14721
  // ATTACH (gate), where the attach client owns scrolling via copy-mode.
@@ -14407,7 +14775,7 @@ function _cellAt(clientX,clientY){
14407
14775
  return col+';'+row;
14408
14776
  }
14409
14777
  function _fwdScroll(px,coord){
14410
- if(!hasToken||!ws_||ws_.readyState!==1||!px)return;
14778
+ if((!hasToken&&!readOnlyRemoteScroll)||!ws_||ws_.readyState!==1||!px)return;
14411
14779
  coord=coord||(((term.cols>>1)+1)+';'+((term.rows>>1)+1)); // never (1,1)
14412
14780
  var dir=px<0?-1:1;
14413
14781
  if(_scrollBurstDir&&dir!==_scrollBurstDir){_scrollAccum=0;_scrollBurstTicks=0;}
@@ -14421,7 +14789,7 @@ function _fwdScroll(px,coord){
14421
14789
  _scrollAccum+=up?_SCROLL_STEP:-_SCROLL_STEP;n++;_scrollBurstTicks++;
14422
14790
  }
14423
14791
  if(_scrollBurstTicks>=_SCROLL_BURST_MAX)_scrollAccum=0;
14424
- if(data)ws_.send(JSON.stringify({type:'input',data:data}));
14792
+ if(data)ws_.send(JSON.stringify({type:hasToken?'input':'scroll',data:data}));
14425
14793
  }
14426
14794
  if(!${isTmuxMode && !isPipeMode}){
14427
14795
  document.getElementById('terminal').addEventListener('wheel',function(e){
@@ -14434,7 +14802,9 @@ if(!${isTmuxMode && !isPipeMode}){
14434
14802
  return;
14435
14803
  }
14436
14804
  if(!hasToken){
14437
- e.preventDefault();e.stopPropagation();term.scrollLines(e.deltaY>0?3:-3);return;
14805
+ e.preventDefault();e.stopPropagation();
14806
+ if(readOnlyRemoteScroll)_fwdScroll(px,_cellAt(e.clientX,e.clientY));
14807
+ else term.scrollLines(e.deltaY>0?3:-3);return;
14438
14808
  }
14439
14809
  e.preventDefault();e.stopPropagation();
14440
14810
  _fwdScroll(px,_cellAt(e.clientX,e.clientY)); // report the cell under the pointer
@@ -14755,8 +15125,7 @@ if(!${isTmuxMode && !isPipeMode}){
14755
15125
  if(e.touches.length===1)_tLastY=e.touches[0].clientY;
14756
15126
  },{capture:true,passive:true});
14757
15127
  _tTerm.addEventListener('touchmove',function(e){
14758
- // View links never forward input; let xterm/browser handle local scrolling.
14759
- if(!hasToken||_tLastY===null||e.touches.length!==1)return;
15128
+ if((!hasToken&&!readOnlyRemoteScroll)||_tLastY===null||e.touches.length!==1)return;
14760
15129
  e.preventDefault();e.stopPropagation();
14761
15130
  var y=e.touches[0].clientY;
14762
15131
  var px=_tLastY-y;
@@ -15934,7 +16303,17 @@ process.on('message', async (raw) => {
15934
16303
  break;
15935
16304
  }
15936
16305
  closeRequested = true;
15937
- send({ type: 'session_close_ready', sessionId });
16306
+ // This ACK is what resolves the daemon's close fence (worker-pool
16307
+ // resolveCloseFence) so `/close` can return. It MUST be flushed: a plain
16308
+ // send() only queues on process.send's async IPC buffer, and the fully
16309
+ // synchronous teardown + process.exit(0) below never yield the event loop
16310
+ // to drain it. Worse, process.exit(0) can wedge in node-pty's native
16311
+ // reader-thread join whenever a web-terminal client PTY was attached — so
16312
+ // the queued ACK would be lost entirely and the fence would only resolve
16313
+ // on the daemon's 7s SIGKILL backstop (the ~7s dashboard/card close stall).
16314
+ // closeRequested already fences bridge-marker reads, so ACKing before
16315
+ // teardown stays safe; sendAndFlush yields so the ACK truly departs.
16316
+ await sendAndFlush({ type: 'session_close_ready', sessionId });
15938
16317
  stopScreenshotLoop();
15939
16318
  stopBridgeWatcher();
15940
16319
  stopCodexBridge();
@@ -16115,7 +16494,11 @@ process.on('message', async (raw) => {
16115
16494
  lastAbortedCloseRequestId = null;
16116
16495
  backend?.commitDestroySession?.();
16117
16496
  closeRequested = true;
16118
- send({ type: 'session_close_ready', sessionId });
16497
+ // Flush the fence-resolving ACK before the synchronous teardown +
16498
+ // process.exit(0); see the local-close case for the node-pty exit-wedge
16499
+ // rationale (a queued send() would be dropped and strand `/close` behind
16500
+ // the daemon's 7s SIGKILL backstop).
16501
+ await sendAndFlush({ type: 'session_close_ready', sessionId });
16119
16502
  stopScreenshotLoop();
16120
16503
  stopBridgeWatcher();
16121
16504
  stopCodexBridge();