aicodeman 1.44.0 → 1.46.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 (351) hide show
  1. package/README.md +32 -8
  2. package/dist/agent-codex-identity.d.ts +26 -0
  3. package/dist/agent-codex-identity.d.ts.map +1 -0
  4. package/dist/agent-codex-identity.js +253 -0
  5. package/dist/agent-codex-identity.js.map +1 -0
  6. package/dist/agent-inspection.d.ts +32 -0
  7. package/dist/agent-inspection.d.ts.map +1 -0
  8. package/dist/agent-inspection.js +196 -0
  9. package/dist/agent-inspection.js.map +1 -0
  10. package/dist/agent-staging.d.ts +130 -0
  11. package/dist/agent-staging.d.ts.map +1 -0
  12. package/dist/agent-staging.js +312 -0
  13. package/dist/agent-staging.js.map +1 -0
  14. package/dist/case-rename.d.ts +65 -0
  15. package/dist/case-rename.d.ts.map +1 -0
  16. package/dist/case-rename.js +198 -0
  17. package/dist/case-rename.js.map +1 -0
  18. package/dist/cli-agent.d.ts +91 -3
  19. package/dist/cli-agent.d.ts.map +1 -1
  20. package/dist/cli-agent.js +501 -44
  21. package/dist/cli-agent.js.map +1 -1
  22. package/dist/cli-history.d.ts +20 -0
  23. package/dist/cli-history.d.ts.map +1 -0
  24. package/dist/cli-history.js +61 -0
  25. package/dist/cli-history.js.map +1 -0
  26. package/dist/cli.d.ts.map +1 -1
  27. package/dist/cli.js +6 -1
  28. package/dist/cli.js.map +1 -1
  29. package/dist/codex-rollout-format.d.ts +66 -0
  30. package/dist/codex-rollout-format.d.ts.map +1 -0
  31. package/dist/codex-rollout-format.js +111 -0
  32. package/dist/codex-rollout-format.js.map +1 -0
  33. package/dist/codex-transcript.d.ts +3 -0
  34. package/dist/codex-transcript.d.ts.map +1 -1
  35. package/dist/codex-transcript.js +13 -41
  36. package/dist/codex-transcript.js.map +1 -1
  37. package/dist/config/auth-config.d.ts +16 -1
  38. package/dist/config/auth-config.d.ts.map +1 -1
  39. package/dist/config/auth-config.js +16 -1
  40. package/dist/config/auth-config.js.map +1 -1
  41. package/dist/config/cli-registry/schema.d.ts +12 -7
  42. package/dist/config/cli-registry/schema.d.ts.map +1 -1
  43. package/dist/config/cli-registry/schema.js +26 -0
  44. package/dist/config/cli-registry/schema.js.map +1 -1
  45. package/dist/config/cli-registry/stock.d.ts.map +1 -1
  46. package/dist/config/cli-registry/stock.js +40 -1
  47. package/dist/config/cli-registry/stock.js.map +1 -1
  48. package/dist/config/cli-registry/types.d.ts +57 -0
  49. package/dist/config/cli-registry/types.d.ts.map +1 -1
  50. package/dist/config/server-timing.d.ts +6 -0
  51. package/dist/config/server-timing.d.ts.map +1 -1
  52. package/dist/config/server-timing.js +9 -0
  53. package/dist/config/server-timing.js.map +1 -1
  54. package/dist/daemon-control.d.ts +1 -0
  55. package/dist/daemon-control.d.ts.map +1 -1
  56. package/dist/daemon-control.js +2 -0
  57. package/dist/daemon-control.js.map +1 -1
  58. package/dist/deepseek-transcript.d.ts +9 -7
  59. package/dist/deepseek-transcript.d.ts.map +1 -1
  60. package/dist/deepseek-transcript.js +9 -7
  61. package/dist/deepseek-transcript.js.map +1 -1
  62. package/dist/docker-hosts.d.ts +8 -0
  63. package/dist/docker-hosts.d.ts.map +1 -1
  64. package/dist/docker-hosts.js +29 -0
  65. package/dist/docker-hosts.js.map +1 -1
  66. package/dist/git-fetch-scheduler.d.ts +124 -0
  67. package/dist/git-fetch-scheduler.d.ts.map +1 -0
  68. package/dist/git-fetch-scheduler.js +249 -0
  69. package/dist/git-fetch-scheduler.js.map +1 -0
  70. package/dist/git-fetch.d.ts +241 -0
  71. package/dist/git-fetch.d.ts.map +1 -0
  72. package/dist/git-fetch.js +392 -0
  73. package/dist/git-fetch.js.map +1 -0
  74. package/dist/git-workspace-status.d.ts +36 -1
  75. package/dist/git-workspace-status.d.ts.map +1 -1
  76. package/dist/git-workspace-status.js +42 -5
  77. package/dist/git-workspace-status.js.map +1 -1
  78. package/dist/hooks-config.d.ts +14 -2
  79. package/dist/hooks-config.d.ts.map +1 -1
  80. package/dist/hooks-config.js +39 -3
  81. package/dist/hooks-config.js.map +1 -1
  82. package/dist/host-sensors.d.ts +115 -0
  83. package/dist/host-sensors.d.ts.map +1 -0
  84. package/dist/host-sensors.js +457 -0
  85. package/dist/host-sensors.js.map +1 -0
  86. package/dist/local-history.d.ts +71 -0
  87. package/dist/local-history.d.ts.map +1 -0
  88. package/dist/local-history.js +750 -0
  89. package/dist/local-history.js.map +1 -0
  90. package/dist/mcp-server.d.ts.map +1 -1
  91. package/dist/mcp-server.js +2 -2
  92. package/dist/mcp-server.js.map +1 -1
  93. package/dist/mux-interface.d.ts +5 -2
  94. package/dist/mux-interface.d.ts.map +1 -1
  95. package/dist/prompt-cache-keepwarm.d.ts.map +1 -1
  96. package/dist/prompt-cache-keepwarm.js +5 -3
  97. package/dist/prompt-cache-keepwarm.js.map +1 -1
  98. package/dist/remote-hosts.d.ts +7 -0
  99. package/dist/remote-hosts.d.ts.map +1 -1
  100. package/dist/remote-hosts.js +17 -3
  101. package/dist/remote-hosts.js.map +1 -1
  102. package/dist/remote-pane-history.d.ts.map +1 -1
  103. package/dist/remote-pane-history.js +2 -2
  104. package/dist/remote-pane-history.js.map +1 -1
  105. package/dist/session.d.ts +113 -0
  106. package/dist/session.d.ts.map +1 -1
  107. package/dist/session.js +354 -13
  108. package/dist/session.js.map +1 -1
  109. package/dist/terminal-input-classifier.d.ts +8 -0
  110. package/dist/terminal-input-classifier.d.ts.map +1 -0
  111. package/dist/terminal-input-classifier.js +60 -0
  112. package/dist/terminal-input-classifier.js.map +1 -0
  113. package/dist/tmux-manager.d.ts +30 -4
  114. package/dist/tmux-manager.d.ts.map +1 -1
  115. package/dist/tmux-manager.js +101 -21
  116. package/dist/tmux-manager.js.map +1 -1
  117. package/dist/tmux-prefix-table.d.ts +37 -5
  118. package/dist/tmux-prefix-table.d.ts.map +1 -1
  119. package/dist/tmux-prefix-table.js +40 -5
  120. package/dist/tmux-prefix-table.js.map +1 -1
  121. package/dist/tui/tui-app.d.ts +8 -1
  122. package/dist/tui/tui-app.d.ts.map +1 -1
  123. package/dist/tui/tui-app.js +49 -12
  124. package/dist/tui/tui-app.js.map +1 -1
  125. package/dist/tui/tui-client.d.ts +90 -3
  126. package/dist/tui/tui-client.d.ts.map +1 -1
  127. package/dist/tui/tui-client.js +195 -26
  128. package/dist/tui/tui-client.js.map +1 -1
  129. package/dist/types/session.d.ts +15 -0
  130. package/dist/types/session.d.ts.map +1 -1
  131. package/dist/types/session.js.map +1 -1
  132. package/dist/web/agent-inbox-persistence.d.ts +52 -0
  133. package/dist/web/agent-inbox-persistence.d.ts.map +1 -0
  134. package/dist/web/agent-inbox-persistence.js +139 -0
  135. package/dist/web/agent-inbox-persistence.js.map +1 -0
  136. package/dist/web/agent-inbox.d.ts +140 -0
  137. package/dist/web/agent-inbox.d.ts.map +1 -0
  138. package/dist/web/agent-inbox.js +258 -0
  139. package/dist/web/agent-inbox.js.map +1 -0
  140. package/dist/web/middleware/auth.d.ts +2 -0
  141. package/dist/web/middleware/auth.d.ts.map +1 -1
  142. package/dist/web/middleware/auth.js +145 -52
  143. package/dist/web/middleware/auth.js.map +1 -1
  144. package/dist/web/network-auth-policy.d.ts +13 -3
  145. package/dist/web/network-auth-policy.d.ts.map +1 -1
  146. package/dist/web/network-auth-policy.js +15 -3
  147. package/dist/web/network-auth-policy.js.map +1 -1
  148. package/dist/web/ports/infra-port.d.ts +2 -0
  149. package/dist/web/ports/infra-port.d.ts.map +1 -1
  150. package/dist/web/public/admin-ui.js.gz +0 -0
  151. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  152. package/dist/web/public/app.3093527f.js +43 -0
  153. package/dist/web/public/app.3093527f.js.br +0 -0
  154. package/dist/web/public/app.3093527f.js.gz +0 -0
  155. package/dist/web/public/approvals-ui.js.gz +0 -0
  156. package/dist/web/public/{constants.4866ecc3.js → constants.affcdde6.js} +622 -12
  157. package/dist/web/public/constants.affcdde6.js.br +0 -0
  158. package/dist/web/public/constants.affcdde6.js.gz +0 -0
  159. package/dist/web/public/cron-ui.js.gz +0 -0
  160. package/dist/web/public/entrance-animations.js.gz +0 -0
  161. package/dist/web/public/git-status-ui.js +88 -5
  162. package/dist/web/public/git-status-ui.js.br +0 -0
  163. package/dist/web/public/git-status-ui.js.gz +0 -0
  164. package/dist/web/public/header-divider.eb2d2463.js +1 -0
  165. package/dist/web/public/header-divider.eb2d2463.js.br +0 -0
  166. package/dist/web/public/header-divider.eb2d2463.js.gz +0 -0
  167. package/dist/web/public/home-sessions.js.gz +0 -0
  168. package/dist/web/public/host-wake-ui.js.gz +0 -0
  169. package/dist/web/public/i18n.70265257.js +2 -0
  170. package/dist/web/public/i18n.70265257.js.br +0 -0
  171. package/dist/web/public/i18n.70265257.js.gz +0 -0
  172. package/dist/web/public/image-input.941386e7.js.gz +0 -0
  173. package/dist/web/public/index.html +170 -16
  174. package/dist/web/public/index.html.br +0 -0
  175. package/dist/web/public/index.html.gz +0 -0
  176. package/dist/web/public/input-cjk.8bc46081.js.gz +0 -0
  177. package/dist/web/public/keyboard-accessory.4e8b20ac.js.gz +0 -0
  178. package/dist/web/public/{mobile-handlers.44adaa07.js → mobile-handlers.f7c3f692.js} +26 -2
  179. package/dist/web/public/mobile-handlers.f7c3f692.js.br +0 -0
  180. package/dist/web/public/mobile-handlers.f7c3f692.js.gz +0 -0
  181. package/dist/web/public/mobile-ime-preview.2dbaf262.js.gz +0 -0
  182. package/dist/web/public/mobile-overview.js.gz +0 -0
  183. package/dist/web/public/mobile.1bbd2d67.css +1 -0
  184. package/dist/web/public/mobile.1bbd2d67.css.br +0 -0
  185. package/dist/web/public/mobile.1bbd2d67.css.gz +0 -0
  186. package/dist/web/public/notification-manager.feb82c04.js.gz +0 -0
  187. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  188. package/dist/web/public/{panels-ui.cbb24ba1.js → panels-ui.4a56ee63.js} +76 -70
  189. package/dist/web/public/panels-ui.4a56ee63.js.br +0 -0
  190. package/dist/web/public/panels-ui.4a56ee63.js.gz +0 -0
  191. package/dist/web/public/ralph-panel.b350e2ef.js.gz +0 -0
  192. package/dist/web/public/ralph-wizard.b6c0dd19.js.gz +0 -0
  193. package/dist/web/public/readmymind-ui.js.gz +0 -0
  194. package/dist/web/public/reboot-restore-ui.js.gz +0 -0
  195. package/dist/web/public/respawn-ui.d7adcf6a.js.gz +0 -0
  196. package/dist/web/public/sanitize-html.e7147e76.js.gz +0 -0
  197. package/dist/web/public/session-lineage.js.gz +0 -0
  198. package/dist/web/public/session-ui.186f9419.js +106 -0
  199. package/dist/web/public/session-ui.186f9419.js.br +0 -0
  200. package/dist/web/public/session-ui.186f9419.js.gz +0 -0
  201. package/dist/web/public/settings-ui.a191154e.js +106 -0
  202. package/dist/web/public/settings-ui.a191154e.js.br +0 -0
  203. package/dist/web/public/settings-ui.a191154e.js.gz +0 -0
  204. package/dist/web/public/spreadsheet-preview-worker.js.gz +0 -0
  205. package/dist/web/public/spreadsheet-preview.75c751de.js.gz +0 -0
  206. package/dist/web/public/spreadsheet-xlsx-core.js.gz +0 -0
  207. package/dist/web/public/styles.7238eae8.css +1 -0
  208. package/dist/web/public/styles.7238eae8.css.br +0 -0
  209. package/dist/web/public/styles.7238eae8.css.gz +0 -0
  210. package/dist/web/public/subagent-windows.07277a58.js.gz +0 -0
  211. package/dist/web/public/sw.js +2 -2
  212. package/dist/web/public/sw.js.br +0 -0
  213. package/dist/web/public/sw.js.gz +0 -0
  214. package/dist/web/public/tab-layout-browser.2d6189f7.js.gz +0 -0
  215. package/dist/web/public/{tab-rail-resize.7157e0bd.js → tab-rail-resize.b5cdceaf.js} +1 -1
  216. package/dist/web/public/tab-rail-resize.b5cdceaf.js.br +0 -0
  217. package/dist/web/public/tab-rail-resize.b5cdceaf.js.gz +0 -0
  218. package/dist/web/public/terminal-keycode229-recovery.af09f99a.js.gz +0 -0
  219. package/dist/web/public/terminal-split.js.gz +0 -0
  220. package/dist/web/public/terminal-tile.js +10 -1
  221. package/dist/web/public/terminal-tile.js.br +0 -0
  222. package/dist/web/public/terminal-tile.js.gz +0 -0
  223. package/dist/web/public/terminal-ui.7e787829.js +4 -0
  224. package/dist/web/public/terminal-ui.7e787829.js.br +0 -0
  225. package/dist/web/public/terminal-ui.7e787829.js.gz +0 -0
  226. package/dist/web/public/tile-grid.js.gz +0 -0
  227. package/dist/web/public/ultracode-panel.js.gz +0 -0
  228. package/dist/web/public/ultracode-windows.js.gz +0 -0
  229. package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
  230. package/dist/web/public/vendor/exceljs.min.js.gz +0 -0
  231. package/dist/web/public/vendor/fflate.min.js.gz +0 -0
  232. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  233. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  234. package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
  235. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  236. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  237. package/dist/web/public/vendor/{xterm-predictive-echo.ae5e3d5d.js → xterm-predictive-echo.276b11d9.js} +1 -1
  238. package/dist/web/public/vendor/xterm-predictive-echo.276b11d9.js.br +0 -0
  239. package/dist/web/public/vendor/xterm-predictive-echo.276b11d9.js.gz +0 -0
  240. package/dist/web/public/vendor/xterm-zerolag-input.1be42307.js +5 -0
  241. package/dist/web/public/vendor/xterm-zerolag-input.1be42307.js.br +0 -0
  242. package/dist/web/public/vendor/xterm-zerolag-input.1be42307.js.gz +0 -0
  243. package/dist/web/public/vendor/xterm.css.gz +0 -0
  244. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  245. package/dist/web/public/voice-input.2342c37b.js.gz +0 -0
  246. package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
  247. package/dist/web/public/webview-tabs.js.gz +0 -0
  248. package/dist/web/public/whats-new.json +9 -9
  249. package/dist/web/route-helpers.d.ts +57 -6
  250. package/dist/web/route-helpers.d.ts.map +1 -1
  251. package/dist/web/route-helpers.js +72 -4
  252. package/dist/web/route-helpers.js.map +1 -1
  253. package/dist/web/routes/agent-session-routes.d.ts +20 -0
  254. package/dist/web/routes/agent-session-routes.d.ts.map +1 -0
  255. package/dist/web/routes/agent-session-routes.js +160 -0
  256. package/dist/web/routes/agent-session-routes.js.map +1 -0
  257. package/dist/web/routes/case-routes.d.ts.map +1 -1
  258. package/dist/web/routes/case-routes.js +203 -16
  259. package/dist/web/routes/case-routes.js.map +1 -1
  260. package/dist/web/routes/config-backup-routes.js +2 -2
  261. package/dist/web/routes/config-backup-routes.js.map +1 -1
  262. package/dist/web/routes/git-status-routes.d.ts +21 -7
  263. package/dist/web/routes/git-status-routes.d.ts.map +1 -1
  264. package/dist/web/routes/git-status-routes.js +78 -12
  265. package/dist/web/routes/git-status-routes.js.map +1 -1
  266. package/dist/web/routes/inbox-routes.d.ts +43 -0
  267. package/dist/web/routes/inbox-routes.d.ts.map +1 -0
  268. package/dist/web/routes/inbox-routes.js +148 -0
  269. package/dist/web/routes/inbox-routes.js.map +1 -0
  270. package/dist/web/routes/index.d.ts +2 -0
  271. package/dist/web/routes/index.d.ts.map +1 -1
  272. package/dist/web/routes/index.js +2 -0
  273. package/dist/web/routes/index.js.map +1 -1
  274. package/dist/web/routes/ralph-routes.d.ts.map +1 -1
  275. package/dist/web/routes/ralph-routes.js +7 -16
  276. package/dist/web/routes/ralph-routes.js.map +1 -1
  277. package/dist/web/routes/session-routes.d.ts.map +1 -1
  278. package/dist/web/routes/session-routes.js +120 -74
  279. package/dist/web/routes/session-routes.js.map +1 -1
  280. package/dist/web/routes/system-routes.d.ts.map +1 -1
  281. package/dist/web/routes/system-routes.js +107 -59
  282. package/dist/web/routes/system-routes.js.map +1 -1
  283. package/dist/web/routes/ws-routes.d.ts +5 -0
  284. package/dist/web/routes/ws-routes.d.ts.map +1 -1
  285. package/dist/web/routes/ws-routes.js +84 -47
  286. package/dist/web/routes/ws-routes.js.map +1 -1
  287. package/dist/web/schemas.d.ts +56 -7
  288. package/dist/web/schemas.d.ts.map +1 -1
  289. package/dist/web/schemas.js +88 -1
  290. package/dist/web/schemas.js.map +1 -1
  291. package/dist/web/sensitive-path.d.ts.map +1 -1
  292. package/dist/web/sensitive-path.js +2 -0
  293. package/dist/web/sensitive-path.js.map +1 -1
  294. package/dist/web/server.d.ts +7 -2
  295. package/dist/web/server.d.ts.map +1 -1
  296. package/dist/web/server.js +144 -16
  297. package/dist/web/server.js.map +1 -1
  298. package/dist/web/session-wait-registry.d.ts +48 -4
  299. package/dist/web/session-wait-registry.d.ts.map +1 -1
  300. package/dist/web/session-wait-registry.js +144 -6
  301. package/dist/web/session-wait-registry.js.map +1 -1
  302. package/dist/web/settings-writer.d.ts +47 -0
  303. package/dist/web/settings-writer.d.ts.map +1 -0
  304. package/dist/web/settings-writer.js +110 -0
  305. package/dist/web/settings-writer.js.map +1 -0
  306. package/dist/web/sse-events.d.ts +4 -1
  307. package/dist/web/sse-events.d.ts.map +1 -1
  308. package/dist/web/sse-events.js +4 -1
  309. package/dist/web/sse-events.js.map +1 -1
  310. package/package.json +1 -1
  311. package/skills/codeman/SKILL.md +30 -8
  312. package/skills/codeman/preamble.sh +3 -3
  313. package/skills/codeman/reference/endpoints.md +24 -3
  314. package/skills/codeman/reference/history.md +84 -0
  315. package/skills/codeman/reference/inspection-and-staging.md +152 -0
  316. package/skills/codeman/reference/messaging.md +1 -1
  317. package/skills/codeman/reference/verbs.md +65 -3
  318. package/dist/web/public/app.9314ee60.js +0 -43
  319. package/dist/web/public/app.9314ee60.js.br +0 -0
  320. package/dist/web/public/app.9314ee60.js.gz +0 -0
  321. package/dist/web/public/constants.4866ecc3.js.br +0 -0
  322. package/dist/web/public/constants.4866ecc3.js.gz +0 -0
  323. package/dist/web/public/i18n.22ca902b.js +0 -2
  324. package/dist/web/public/i18n.22ca902b.js.br +0 -0
  325. package/dist/web/public/i18n.22ca902b.js.gz +0 -0
  326. package/dist/web/public/mobile-handlers.44adaa07.js.br +0 -0
  327. package/dist/web/public/mobile-handlers.44adaa07.js.gz +0 -0
  328. package/dist/web/public/mobile.12fb18de.css +0 -1
  329. package/dist/web/public/mobile.12fb18de.css.br +0 -0
  330. package/dist/web/public/mobile.12fb18de.css.gz +0 -0
  331. package/dist/web/public/panels-ui.cbb24ba1.js.br +0 -0
  332. package/dist/web/public/panels-ui.cbb24ba1.js.gz +0 -0
  333. package/dist/web/public/session-ui.9c1f009b.js +0 -104
  334. package/dist/web/public/session-ui.9c1f009b.js.br +0 -0
  335. package/dist/web/public/session-ui.9c1f009b.js.gz +0 -0
  336. package/dist/web/public/settings-ui.fea9ddae.js +0 -106
  337. package/dist/web/public/settings-ui.fea9ddae.js.br +0 -0
  338. package/dist/web/public/settings-ui.fea9ddae.js.gz +0 -0
  339. package/dist/web/public/styles.e096fcd6.css +0 -1
  340. package/dist/web/public/styles.e096fcd6.css.br +0 -0
  341. package/dist/web/public/styles.e096fcd6.css.gz +0 -0
  342. package/dist/web/public/tab-rail-resize.7157e0bd.js.br +0 -0
  343. package/dist/web/public/tab-rail-resize.7157e0bd.js.gz +0 -0
  344. package/dist/web/public/terminal-ui.68dbd352.js +0 -2
  345. package/dist/web/public/terminal-ui.68dbd352.js.br +0 -0
  346. package/dist/web/public/terminal-ui.68dbd352.js.gz +0 -0
  347. package/dist/web/public/vendor/xterm-predictive-echo.ae5e3d5d.js.br +0 -0
  348. package/dist/web/public/vendor/xterm-predictive-echo.ae5e3d5d.js.gz +0 -0
  349. package/dist/web/public/vendor/xterm-zerolag-input.0b69623c.js +0 -4
  350. package/dist/web/public/vendor/xterm-zerolag-input.0b69623c.js.br +0 -0
  351. package/dist/web/public/vendor/xterm-zerolag-input.0b69623c.js.gz +0 -0
package/dist/session.js CHANGED
@@ -49,6 +49,9 @@ import { DEFAULT_TMUX_HISTORY_LIMIT } from './config/terminal-history.js';
49
49
  import { EXEC_TIMEOUT_MS } from './config/exec-timeout.js';
50
50
  import { getCli } from './config/cli-registry/registry.js';
51
51
  import { SubmitVerifier } from './session-submit-verifier.js';
52
+ import { guardedComposerChrome, promptSha256, readGuardedSnapshotDetails, } from './agent-staging.js';
53
+ import { isKeyboardTerminalInput } from './terminal-input-classifier.js';
54
+ import { stripAnsi } from './utils/regex-patterns.js';
52
55
  import { KEEP_WARM_NUDGE } from './prompt-cache-keepwarm.js';
53
56
  import { compileVersionRegex } from './config/cli-registry/patterns.js';
54
57
  import { resolveSessionCliVersion } from './utils/cli-resolver.js';
@@ -61,6 +64,7 @@ import { isGeneratedSessionName, SubmittedPromptTracker } from './session-auto-n
61
64
  import { parseTerminalAttachmentRequests } from './attachment-magic.js';
62
65
  import { sanitizeAttachmentHistory, upsertAttachmentHistory as upsertAttachmentHistoryList, } from './session-attachment-history.js';
63
66
  import { resolveConfigModel } from './model-config-resolvers.js';
67
+ import { caseSessionSettled, isCasePathLocked, noteCaseSessionStarting } from './case-rename.js';
64
68
  import { legacyConfigForMode } from './session-cli-registry-bridge.js';
65
69
  import { launchModelFor, readScreenModel, resolveDisplayModel, restoredReportedModel, sanitizeModelName, } from './session-display-model.js';
66
70
  /** Line buffer flush interval (100ms) - forces processing of partial lines */
@@ -298,6 +302,16 @@ export function resolveMuxAttachCwd(workingDir, remote, docker) {
298
302
  // wrapper pane never needs the workspace as its cwd, so launch it in /tmp.
299
303
  return remote || docker ? '/tmp' : workingDir;
300
304
  }
305
+ /**
306
+ * A write that a guarded submit's lease could not hold, because the per-lease hold
307
+ * queue (`Session.MAX_GUARDED_HELD_INPUTS`) was full. Nothing was written; retry it.
308
+ */
309
+ export class GuardedInputBusyError extends Error {
310
+ constructor() {
311
+ super('A guarded submit is in progress on this session; nothing was written. Retry the input.');
312
+ this.name = 'GuardedInputBusyError';
313
+ }
314
+ }
301
315
  /**
302
316
  * Whether a programmatic write leaves the keep-alive turn running: the nudge itself, or a bare
303
317
  * Enter (which submits nothing new). Anything else carrying an Enter starts a turn of its own.
@@ -449,6 +463,12 @@ export class Session extends EventEmitter {
449
463
  _keepWarmEnabled;
450
464
  /** Last status the keep-warm service published for this session; ephemeral. */
451
465
  _keepWarm = null;
466
+ /** Git fetch on an interval for this session; undefined = follow the app-wide default. Persisted. */
467
+ _gitFetchEnabled;
468
+ /** Seconds between this session's fetch runs; undefined = follow the app-wide default. Persisted. */
469
+ _gitFetchIntervalSeconds;
470
+ /** Last status the fetch scheduler published for this session; ephemeral. */
471
+ _gitFetch = null;
452
472
  /** Until when the current turn counts as Codeman's own keep-alive (hooks open pre-acknowledged). */
453
473
  _keepAliveTurnUntil = 0;
454
474
  /** Last keystroke a person sent through a `fromUser` write. */
@@ -655,6 +675,16 @@ export class Session extends EventEmitter {
655
675
  static EXPENSIVE_PROCESS_INTERVAL_MS = 150; // Process at most every 150ms
656
676
  constructor(config) {
657
677
  super();
678
+ // A case folder being renamed (src/case-rename.ts) must not gain a session mid-move: its pane would
679
+ // start in a directory that is about to have a different path.
680
+ if (config.workingDir && isCasePathLocked(config.workingDir)) {
681
+ throw new Error(`The case folder ${config.workingDir} is being renamed. Try again in a moment.`);
682
+ }
683
+ // And a rename must not start until this session is in the server's map, where it can be listed and
684
+ // stopped: until registerSessionWithLayout() (or stop()) settles it, the rename route refuses the move.
685
+ // A remote session's workingDir is the remote path, which the route never counts as the folder's.
686
+ if (config.workingDir && !config.remote)
687
+ noteCaseSessionStarting(this, config.workingDir);
658
688
  this.setMaxListeners(25);
659
689
  // Default error handler prevents unhandled 'error' events from crashing the process.
660
690
  // Server attaches its own handler after construction — this is a safety net for the gap.
@@ -710,6 +740,10 @@ export class Session extends EventEmitter {
710
740
  // from before the field has only `lastSubmitAt`, and every submit then was a real one.
711
741
  this._lastRealPromptAt = config.lastRealPromptAt ?? config.lastSubmitAt ?? 0;
712
742
  this._keepWarmEnabled = config.keepWarmEnabled;
743
+ // Restored, like the keep-warm choice: the interval is the user's decision about their repositories
744
+ // and outlives the run that made it, so a restart keeps it rather than falling back to the app default.
745
+ this._gitFetchEnabled = config.gitFetchEnabled;
746
+ this._gitFetchIntervalSeconds = config.gitFetchIntervalSeconds;
713
747
  // Restored chain: its tail is the conversation the CLI was actually on when
714
748
  // the server stopped, which outranks the launch id seeded just above. The
715
749
  // FIRST-HAND flag is deliberately NOT restored — a persisted claim is not a
@@ -969,6 +1003,8 @@ export class Session extends EventEmitter {
969
1003
  }
970
1004
  /** Set the owning username (used by recovery to restore ownership). */
971
1005
  set owner(username) {
1006
+ if (this._owner !== username)
1007
+ this._guardedPaneRevision++;
972
1008
  this._owner = username;
973
1009
  }
974
1010
  // Adopt a Claude conversation ID observed from an external source (e.g. hook
@@ -1070,10 +1106,13 @@ export class Session extends EventEmitter {
1070
1106
  * tmux session whose record is about to be deleted (Ark0N/Codeman#446).
1071
1107
  */
1072
1108
  markClosing(closing) {
1109
+ if (this._closing !== closing)
1110
+ this._guardedPaneRevision++;
1073
1111
  this._closing = closing;
1074
1112
  }
1075
1113
  /** Run one pane start, attach or relaunch with {@link paneLifecycleInFlight} raised. */
1076
1114
  async _withPaneLifecycle(op) {
1115
+ this._guardedPaneRevision++;
1077
1116
  this._paneLifecycleOps++;
1078
1117
  try {
1079
1118
  return await op();
@@ -1100,6 +1139,7 @@ export class Session extends EventEmitter {
1100
1139
  * Ark0N/Codeman#446 review; both now persist on their success path.
1101
1140
  */
1102
1141
  clearPaneExitForNewPane() {
1142
+ this._guardedPaneRevision++;
1103
1143
  this.setPaneExit(undefined);
1104
1144
  if (this._muxSession)
1105
1145
  this._mux?.clearPaneExit?.(this._muxSession.muxName);
@@ -1222,6 +1262,32 @@ export class Session extends EventEmitter {
1222
1262
  this._keepWarm = status;
1223
1263
  return changed;
1224
1264
  }
1265
+ /** Git fetch override for this session; undefined means the app-wide setting applies. */
1266
+ get gitFetchEnabled() {
1267
+ return this._gitFetchEnabled;
1268
+ }
1269
+ /** Interval override for this session; undefined means the app-wide setting applies. */
1270
+ get gitFetchIntervalSeconds() {
1271
+ return this._gitFetchIntervalSeconds;
1272
+ }
1273
+ /**
1274
+ * Set the session's override. The Session Options rows save on their own, so a caller passes what it has
1275
+ * and leaves the other half as it was; `undefined` on either means the app-wide setting applies again.
1276
+ */
1277
+ setGitFetchSettings(enabled, intervalSeconds) {
1278
+ this._gitFetchEnabled = enabled;
1279
+ this._gitFetchIntervalSeconds = intervalSeconds;
1280
+ }
1281
+ /** What the fetch scheduler last reported for this session, or null before its first run. */
1282
+ get gitFetch() {
1283
+ return this._gitFetch;
1284
+ }
1285
+ /** Replace the fetch status; true when it differs from the previous one. */
1286
+ setGitFetchStatus(status) {
1287
+ const changed = JSON.stringify(status) !== JSON.stringify(this._gitFetch);
1288
+ this._gitFetch = status;
1289
+ return changed;
1290
+ }
1225
1291
  /**
1226
1292
  * True while the running turn is Codeman's own keep-alive nudge: its stop hook and idle
1227
1293
  * prompt open pre-acknowledged, and its submit moves `lastSubmitAt` (it IS a submit, and the
@@ -1688,6 +1754,9 @@ export class Session extends EventEmitter {
1688
1754
  statusLineExporter: this.statusLineExporter,
1689
1755
  keepWarmEnabled: this._keepWarmEnabled,
1690
1756
  keepWarm: this._keepWarm ?? undefined,
1757
+ gitFetchEnabled: this._gitFetchEnabled,
1758
+ gitFetchIntervalSeconds: this._gitFetchIntervalSeconds,
1759
+ gitFetch: this._gitFetch ?? undefined,
1691
1760
  imageWatcherEnabled: this._imageWatcherEnabled,
1692
1761
  pinned: this._pinned || undefined,
1693
1762
  pinnedAt: this._pinned ? (this._pinnedAt ?? undefined) : undefined,
@@ -3694,6 +3763,11 @@ export class Session extends EventEmitter {
3694
3763
  * ```
3695
3764
  */
3696
3765
  async runPrompt(prompt, options) {
3766
+ if (this.guardedInputHeld) {
3767
+ this._guardedInputRevision++;
3768
+ throw new Error('Guarded submission in progress');
3769
+ }
3770
+ this._guardedPaneRevision++;
3697
3771
  return new Promise((resolve, reject) => {
3698
3772
  if (this.ptyProcess) {
3699
3773
  reject(new Error('Session already has a running process'));
@@ -4132,6 +4206,236 @@ export class Session extends EventEmitter {
4132
4206
  }
4133
4207
  }
4134
4208
  // Note: checkAutoCompact/checkAutoClear moved to SessionAutoOps (this._autoOps)
4209
+ _inspectionInstance = uuidv4();
4210
+ _guardedPaneRevision = 0;
4211
+ _guardedGeometryRevision = 0;
4212
+ _guardedInputRevision = 0;
4213
+ _guardedPendingWrites = 0;
4214
+ _guardedSubmitInFlight = false;
4215
+ /**
4216
+ * Input that arrived while a guarded submit held the lease, in arrival order. Each
4217
+ * entry runs when the lease is released; an entry that returns a promise (a mux
4218
+ * write, a named key) is waited for before the next runs, so nothing overtakes it.
4219
+ */
4220
+ _guardedHeld = [];
4221
+ _guardedHeldBytes = 0;
4222
+ /** True while the drain waits for a held asynchronous write to finish. */
4223
+ _guardedHeldDraining = false;
4224
+ /** Held entries per lease. A lease lasts milliseconds; past this the caller backs off. */
4225
+ static MAX_GUARDED_HELD_INPUTS = 256;
4226
+ /** Held bytes per lease (16 browser frames at the 64 KiB frame cap). */
4227
+ static MAX_GUARDED_HELD_BYTES = 16 * 64 * 1024;
4228
+ /** Ephemeral identity, never persisted or based on a potentially reused pid alone. */
4229
+ get inspectionGeneration() {
4230
+ return `${this._inspectionInstance}:${this._guardedPaneRevision}:${this.owner ?? ''}`;
4231
+ }
4232
+ /** Staging additionally binds geometry; transcript cursors survive a terminal resize. */
4233
+ get guardedInputGeneration() {
4234
+ return `${this.inspectionGeneration}:${this._guardedGeometryRevision}`;
4235
+ }
4236
+ get guardedInputPendingWrites() {
4237
+ return this._guardedPendingWrites;
4238
+ }
4239
+ /**
4240
+ * True while a guarded submit holds the input lease (a few milliseconds). Input that
4241
+ * arrives in that span is held and written after it (see {@link guardedInputHeld}).
4242
+ */
4243
+ get guardedSubmitInFlight() {
4244
+ return this._guardedSubmitInFlight;
4245
+ }
4246
+ /**
4247
+ * True while input is being held behind a guarded submit: the lease itself, or held
4248
+ * input not yet written after it. A write that is not routed through
4249
+ * {@link holdForGuardedLease} must refuse in this window, or it overtakes held input.
4250
+ * `write()` is the known exception: it refuses during the lease only, since the drain
4251
+ * writes held input through it, so a direct `write()` caller (cron's `paste` mode in
4252
+ * `cron/cron-service.ts`) can still land ahead of input that is draining.
4253
+ */
4254
+ get guardedInputHeld() {
4255
+ return this._guardedSubmitInFlight || this._guardedHeld.length > 0 || this._guardedHeldDraining;
4256
+ }
4257
+ /**
4258
+ * Hold input behind a guarded submit's lease instead of refusing it.
4259
+ *
4260
+ * `free`: nothing is held, so the caller writes inline as it always did. `held`: `run`
4261
+ * is queued and runs, in arrival order with every other held input, once the lease is
4262
+ * released (whatever the submit's outcome). `full`: the per-lease cap is reached and
4263
+ * nothing was queued; the caller must stop delivering from that source until it can
4264
+ * resend in order. Refusing instead lost input: the browser's WebSocket path does not
4265
+ * wait for ACKs, so a refused seq N was overtaken by N+1 and its redelivery was then
4266
+ * rejected as a duplicate. `data`, when given, is observed as input on arrival, so a
4267
+ * keystroke held during the lease still moves the input revision at once.
4268
+ */
4269
+ holdForGuardedLease(run, bytes = 0, data) {
4270
+ if (!this.guardedInputHeld)
4271
+ return 'free';
4272
+ if (data !== undefined)
4273
+ this._observeGuardedKeyboardInput(data);
4274
+ if (this._guardedHeld.length >= Session.MAX_GUARDED_HELD_INPUTS ||
4275
+ this._guardedHeldBytes + bytes > Session.MAX_GUARDED_HELD_BYTES)
4276
+ return 'full';
4277
+ this._guardedHeld.push({ run, bytes });
4278
+ this._guardedHeldBytes += bytes;
4279
+ return 'held';
4280
+ }
4281
+ /**
4282
+ * Run an asynchronous write now, or in its turn behind a guarded submit's lease. The
4283
+ * turn lasts until the write settles, so later held input waits for it. Resolves
4284
+ * `whenFull` without running anything when the hold queue is full.
4285
+ */
4286
+ _inGuardedTurn(write, whenFull) {
4287
+ if (!this.guardedInputHeld)
4288
+ return write();
4289
+ return new Promise((resolve, reject) => {
4290
+ const held = this.holdForGuardedLease(() => {
4291
+ const running = write();
4292
+ running.then(resolve, reject);
4293
+ return running;
4294
+ });
4295
+ if (held === 'full') {
4296
+ try {
4297
+ resolve(whenFull());
4298
+ }
4299
+ catch (err) {
4300
+ reject(err);
4301
+ }
4302
+ }
4303
+ });
4304
+ }
4305
+ /** Run held input in arrival order until it is all written or a new lease is taken. */
4306
+ _drainGuardedHeld() {
4307
+ while (this._guardedHeld.length > 0 && !this._guardedSubmitInFlight && !this._guardedHeldDraining) {
4308
+ const entry = this._guardedHeld.shift();
4309
+ this._guardedHeldBytes -= entry.bytes;
4310
+ let running;
4311
+ try {
4312
+ running = entry.run();
4313
+ }
4314
+ catch (err) {
4315
+ console.error(`[Session ${this.id}] held input failed after a guarded submit:`, getErrorMessage(err));
4316
+ continue;
4317
+ }
4318
+ if (running && typeof running.then === 'function') {
4319
+ this._guardedHeldDraining = true;
4320
+ void running
4321
+ .catch(() => { })
4322
+ .then(() => {
4323
+ this._guardedHeldDraining = false;
4324
+ this._drainGuardedHeld();
4325
+ });
4326
+ }
4327
+ }
4328
+ }
4329
+ /** Expose only the path selection needed for attributed Codex evidence, never credentials. */
4330
+ getCodexHomeOverride() {
4331
+ return this._envOverrides?.CODEX_HOME || undefined;
4332
+ }
4333
+ _observeGuardedKeyboardInput(data) {
4334
+ if (data && isKeyboardTerminalInput(data))
4335
+ this._guardedInputRevision++;
4336
+ }
4337
+ guardedInputSnapshot() {
4338
+ return this._readGuardedInputSnapshot(false);
4339
+ }
4340
+ _readGuardedInputSnapshot(allowLease) {
4341
+ if (this._isStopped ||
4342
+ this._closing ||
4343
+ this.paneLifecycleInFlight ||
4344
+ this._guardedPendingWrites ||
4345
+ (!allowLease && this.guardedInputHeld) ||
4346
+ this.remote ||
4347
+ this.docker ||
4348
+ this.paneExit ||
4349
+ !this._muxSession)
4350
+ return null;
4351
+ // Only a CLI whose registry entry describes its composer chrome takes guarded input.
4352
+ const chrome = guardedComposerChrome(getCli(this.mode));
4353
+ if (!chrome)
4354
+ return null;
4355
+ const capture = {};
4356
+ const screen = this._mux?.capturePaneBuffer?.(this._muxSession.muxName, undefined, capture);
4357
+ if (!screen)
4358
+ return null;
4359
+ // tmux's replay has CUP addresses rather than line breaks. Read the SAME
4360
+ // fresh frame for dialog/work chrome and exact composer/caret proof.
4361
+ // eslint-disable-next-line no-control-regex
4362
+ const plain = stripAnsi(screen.replace(/\x1b\[\d+;\d+H/g, '\n'));
4363
+ const working = this._workingLinePattern();
4364
+ working.lastIndex = 0;
4365
+ if (this._paneShowsBlocked(plain) || working.test(plain) || this._paneAwaitsWorkers(plain))
4366
+ return null;
4367
+ const composer = readGuardedSnapshotDetails(screen, chrome);
4368
+ return composer === null
4369
+ ? null
4370
+ : {
4371
+ generation: this.guardedInputGeneration,
4372
+ revision: this._guardedInputRevision,
4373
+ composerSha: promptSha256(composer.composer),
4374
+ availableColumns: Number.isFinite(capture.capturedGeometry?.cols)
4375
+ ? capture.capturedGeometry.cols - composer.prefixWidth
4376
+ : 0,
4377
+ };
4378
+ }
4379
+ /** Stage through ordinary delivery, after cancelling any older Enter retry timer. */
4380
+ async stageGuardedInput(message) {
4381
+ if (/[\r\n]/.test(message))
4382
+ return false;
4383
+ if (this._guardedPendingWrites || this.guardedInputHeld) {
4384
+ this._guardedInputRevision++;
4385
+ return false;
4386
+ }
4387
+ this._submitVerifier?.cancel();
4388
+ return this.writeViaMux(message, { fromUser: true });
4389
+ }
4390
+ /** One conditional Enter. An ambiguous acknowledgement is never replayed. */
4391
+ async submitGuardedInput(expected) {
4392
+ const matches = (current) => !!current &&
4393
+ current.generation === expected.generation &&
4394
+ current.revision === expected.revision &&
4395
+ current.composerSha === expected.composerSha &&
4396
+ current.availableColumns === expected.availableColumns;
4397
+ if (!matches(this.guardedInputSnapshot()) || !this._mux || !this._muxSession)
4398
+ return false;
4399
+ this._guardedSubmitInFlight = true;
4400
+ let dispatched = false;
4401
+ try {
4402
+ const sent = await this._mux.sendInput(this.id, '\r', {
4403
+ beforeDispatch: () => {
4404
+ if (!matches(this._readGuardedInputSnapshot(true)))
4405
+ return false;
4406
+ this._guardedInputRevision++;
4407
+ dispatched = true;
4408
+ return true;
4409
+ },
4410
+ });
4411
+ if (sent && dispatched)
4412
+ this._emitSubmittedPrompt(this._trackSubmit('\r', { fromUser: true }));
4413
+ return sent && dispatched;
4414
+ }
4415
+ finally {
4416
+ this._guardedSubmitInFlight = false;
4417
+ this._drainGuardedHeld();
4418
+ }
4419
+ }
4420
+ /**
4421
+ * Lease/pending boundary for named keys whose bytes bypass the ordinary write methods.
4422
+ * During a guarded submit the operation waits for the lease like any other write;
4423
+ * only a full hold queue refuses it, with {@link GuardedInputBusyError}.
4424
+ */
4425
+ async performAuxiliaryInput(operation) {
4426
+ this._guardedInputRevision++;
4427
+ return this._inGuardedTurn(async () => {
4428
+ this._guardedPendingWrites++;
4429
+ try {
4430
+ await operation();
4431
+ }
4432
+ finally {
4433
+ this._guardedPendingWrites--;
4434
+ }
4435
+ }, () => {
4436
+ throw new GuardedInputBusyError();
4437
+ });
4438
+ }
4135
4439
  /**
4136
4440
  * Sends input directly to the PTY process.
4137
4441
  *
@@ -4151,6 +4455,10 @@ export class Session extends EventEmitter {
4151
4455
  * input could disappear while the caller believed it had been delivered.
4152
4456
  */
4153
4457
  write(data, options = {}) {
4458
+ this._observeGuardedKeyboardInput(data);
4459
+ // The lease only, not the drain, which writes held input through here (see guardedInputHeld).
4460
+ if (this._guardedSubmitInFlight)
4461
+ return false;
4154
4462
  const submittedPrompt = this._trackSubmit(data, options);
4155
4463
  if (!this.ptyProcess)
4156
4464
  return false;
@@ -4198,6 +4506,7 @@ export class Session extends EventEmitter {
4198
4506
  * separator. Reports submissions like a write would (a line feed never is one).
4199
4507
  */
4200
4508
  trackUserInput(data) {
4509
+ this._observeGuardedKeyboardInput(data);
4201
4510
  if (!this._acceptsPrompts)
4202
4511
  return;
4203
4512
  this._emitSubmittedPrompt(this._submittedPromptTracker.feed(data));
@@ -4218,6 +4527,7 @@ export class Session extends EventEmitter {
4218
4527
  * their prompt must count as real and alert normally.
4219
4528
  */
4220
4529
  markPromptSubmitted(prompt) {
4530
+ this._guardedInputRevision++;
4221
4531
  if (prompt !== undefined && prompt.trim() !== KEEP_WARM_NUDGE)
4222
4532
  this._keepAliveTurnUntil = 0;
4223
4533
  this._stampSubmit();
@@ -4312,22 +4622,36 @@ export class Session extends EventEmitter {
4312
4622
  * ```
4313
4623
  */
4314
4624
  async writeViaMux(data, options = {}) {
4315
- const submittedPrompt = this._trackSubmit(data, options);
4316
- if (this._mux && this._muxSession) {
4317
- const sent = await this._mux.sendInput(this.id, data);
4318
- if (sent) {
4625
+ this._observeGuardedKeyboardInput(data);
4626
+ // A guarded submit's lease lasts for its Enter only: wait for it, in arrival order,
4627
+ // rather than refusing, so an approval answer or a programmatic prompt is not
4628
+ // dropped. Without a lease the write starts synchronously, so the pending count
4629
+ // rises before any await and no lease can be taken while it is in flight.
4630
+ return this._inGuardedTurn(() => this._writeViaMuxNow(data, options), () => false);
4631
+ }
4632
+ async _writeViaMuxNow(data, options) {
4633
+ this._guardedPendingWrites++;
4634
+ try {
4635
+ const submittedPrompt = this._trackSubmit(data, options);
4636
+ if (this._mux && this._muxSession) {
4637
+ const sent = await this._mux.sendInput(this.id, data);
4638
+ if (sent) {
4639
+ this._emitSubmittedPrompt(submittedPrompt);
4640
+ this._verifySubmitted(data);
4641
+ }
4642
+ return sent;
4643
+ }
4644
+ // Fallback to PTY write
4645
+ if (this.ptyProcess) {
4646
+ this.ptyProcess.write(data);
4319
4647
  this._emitSubmittedPrompt(submittedPrompt);
4320
- this._verifySubmitted(data);
4648
+ return true;
4321
4649
  }
4322
- return sent;
4650
+ return false;
4323
4651
  }
4324
- // Fallback to PTY write
4325
- if (this.ptyProcess) {
4326
- this.ptyProcess.write(data);
4327
- this._emitSubmittedPrompt(submittedPrompt);
4328
- return true;
4652
+ finally {
4653
+ this._guardedPendingWrites--;
4329
4654
  }
4330
- return false;
4331
4655
  }
4332
4656
  /**
4333
4657
  * Arm the composer check for a prompt that went out some other way than
@@ -4354,7 +4678,16 @@ export class Session extends EventEmitter {
4354
4678
  capture: () => this._isStopped || !this._mux || !this._muxSession
4355
4679
  ? null
4356
4680
  : this._mux.capturePaneText?.(this._muxSession.muxName),
4357
- sendEnter: () => this._mux?.sendInput(this.id, '\r'),
4681
+ sendEnter: () => {
4682
+ // A retry Enter is never held behind a guarded submit: once that submit's
4683
+ // Enter went out, a re-press would submit whatever is on the composer next.
4684
+ if (this.guardedInputHeld) {
4685
+ this._guardedInputRevision++;
4686
+ this._submitVerifier?.cancel();
4687
+ return Promise.resolve(false);
4688
+ }
4689
+ return this.writeViaMux('\r');
4690
+ },
4358
4691
  // ⚠ NO fallback glyph here, unlike the screen-reading probe elsewhere in this file.
4359
4692
  // Only claude, codex, pi, opencode, omp, gemini and antigravity declare a promptGlyph; the other modes would fall back
4360
4693
  // to claude's `❯`, which is ALSO starship's default shell prompt (and pure's, and
@@ -4381,6 +4714,7 @@ export class Session extends EventEmitter {
4381
4714
  * `resize()` skipped a real resize that happened to match them.
4382
4715
  */
4383
4716
  _notePtySpawnGeometry(cols, rows) {
4717
+ this._guardedPaneRevision++;
4384
4718
  this._ptyCols = cols;
4385
4719
  this._ptyRows = rows;
4386
4720
  }
@@ -4493,6 +4827,7 @@ export class Session extends EventEmitter {
4493
4827
  }
4494
4828
  const dimsChanged = cols !== this._ptyCols || rows !== this._ptyRows;
4495
4829
  if (this.ptyProcess && (dimsChanged || options.force)) {
4830
+ this._guardedGeometryRevision++;
4496
4831
  this._ptyCols = cols;
4497
4832
  this._ptyRows = rows;
4498
4833
  if (!IS_TEST_MODE && this._mux && this._muxSession) {
@@ -4507,6 +4842,10 @@ export class Session extends EventEmitter {
4507
4842
  }
4508
4843
  // Legacy method for sending input - wraps runPrompt
4509
4844
  async sendInput(input) {
4845
+ if (this.guardedInputHeld) {
4846
+ this._guardedInputRevision++;
4847
+ throw new Error('Guarded submission in progress');
4848
+ }
4510
4849
  this._status = 'busy';
4511
4850
  this._markActivity(true);
4512
4851
  this.runPrompt(input).catch((err) => {
@@ -4580,6 +4919,8 @@ export class Session extends EventEmitter {
4580
4919
  async stop(killMux = true) {
4581
4920
  // Set stopped flag first to prevent new timers from being created
4582
4921
  this._isStopped = true;
4922
+ // A session stopped before it was registered (a create that failed) no longer holds up a case rename.
4923
+ caseSessionSettled(this);
4583
4924
  // A pane that is gone is watching nothing. Nothing probes a stopped session, so
4584
4925
  // without this the last chip it drew would ride along on its row forever.
4585
4926
  this._watching = null;