aicodeman 1.43.0 → 1.45.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 (353) hide show
  1. package/README.md +9 -4
  2. package/dist/case-rename.d.ts +65 -0
  3. package/dist/case-rename.d.ts.map +1 -0
  4. package/dist/case-rename.js +198 -0
  5. package/dist/case-rename.js.map +1 -0
  6. package/dist/cli-agent.d.ts +63 -4
  7. package/dist/cli-agent.d.ts.map +1 -1
  8. package/dist/cli-agent.js +233 -19
  9. package/dist/cli-agent.js.map +1 -1
  10. package/dist/config/auth-config.d.ts +16 -1
  11. package/dist/config/auth-config.d.ts.map +1 -1
  12. package/dist/config/auth-config.js +16 -1
  13. package/dist/config/auth-config.js.map +1 -1
  14. package/dist/config/cli-registry/stock.d.ts.map +1 -1
  15. package/dist/config/cli-registry/stock.js +8 -3
  16. package/dist/config/cli-registry/stock.js.map +1 -1
  17. package/dist/config/cli-registry/types.d.ts +2 -2
  18. package/dist/config/map-limits.d.ts +13 -1
  19. package/dist/config/map-limits.d.ts.map +1 -1
  20. package/dist/config/map-limits.js +28 -1
  21. package/dist/config/map-limits.js.map +1 -1
  22. package/dist/config-backup.d.ts +279 -0
  23. package/dist/config-backup.d.ts.map +1 -0
  24. package/dist/config-backup.js +794 -0
  25. package/dist/config-backup.js.map +1 -0
  26. package/dist/cron/cron-service.d.ts +5 -5
  27. package/dist/cron/cron-service.js +6 -6
  28. package/dist/cron/cron-service.js.map +1 -1
  29. package/dist/custom-model-hosts.d.ts +16 -0
  30. package/dist/custom-model-hosts.d.ts.map +1 -1
  31. package/dist/custom-model-hosts.js.map +1 -1
  32. package/dist/hooks-config.d.ts +15 -0
  33. package/dist/hooks-config.d.ts.map +1 -1
  34. package/dist/hooks-config.js +35 -0
  35. package/dist/hooks-config.js.map +1 -1
  36. package/dist/host-sensors.d.ts +115 -0
  37. package/dist/host-sensors.d.ts.map +1 -0
  38. package/dist/host-sensors.js +457 -0
  39. package/dist/host-sensors.js.map +1 -0
  40. package/dist/intent-store.d.ts +2 -0
  41. package/dist/intent-store.d.ts.map +1 -1
  42. package/dist/intent-store.js +9 -0
  43. package/dist/intent-store.js.map +1 -1
  44. package/dist/mux-interface.d.ts +21 -4
  45. package/dist/mux-interface.d.ts.map +1 -1
  46. package/dist/prompt-cache-keepwarm.d.ts +193 -0
  47. package/dist/prompt-cache-keepwarm.d.ts.map +1 -0
  48. package/dist/prompt-cache-keepwarm.js +359 -0
  49. package/dist/prompt-cache-keepwarm.js.map +1 -0
  50. package/dist/remote-hosts.d.ts +7 -0
  51. package/dist/remote-hosts.d.ts.map +1 -1
  52. package/dist/remote-hosts.js +17 -3
  53. package/dist/remote-hosts.js.map +1 -1
  54. package/dist/remote-pane-history.d.ts.map +1 -1
  55. package/dist/remote-pane-history.js +2 -2
  56. package/dist/remote-pane-history.js.map +1 -1
  57. package/dist/respawn-controller.d.ts.map +1 -1
  58. package/dist/respawn-controller.js +6 -5
  59. package/dist/respawn-controller.js.map +1 -1
  60. package/dist/session-auto-ops.d.ts.map +1 -1
  61. package/dist/session-auto-ops.js +6 -1
  62. package/dist/session-auto-ops.js.map +1 -1
  63. package/dist/session-cli-registry-bridge.d.ts +13 -0
  64. package/dist/session-cli-registry-bridge.d.ts.map +1 -1
  65. package/dist/session-cli-registry-bridge.js +20 -3
  66. package/dist/session-cli-registry-bridge.js.map +1 -1
  67. package/dist/session-submit-verifier.d.ts +19 -0
  68. package/dist/session-submit-verifier.d.ts.map +1 -1
  69. package/dist/session-submit-verifier.js +88 -0
  70. package/dist/session-submit-verifier.js.map +1 -1
  71. package/dist/session.d.ts +61 -5
  72. package/dist/session.d.ts.map +1 -1
  73. package/dist/session.js +125 -7
  74. package/dist/session.js.map +1 -1
  75. package/dist/state-store.d.ts +10 -0
  76. package/dist/state-store.d.ts.map +1 -1
  77. package/dist/state-store.js +35 -0
  78. package/dist/state-store.js.map +1 -1
  79. package/dist/tab-layout-service.d.ts +33 -0
  80. package/dist/tab-layout-service.d.ts.map +1 -1
  81. package/dist/tab-layout-service.js +75 -3
  82. package/dist/tab-layout-service.js.map +1 -1
  83. package/dist/tmux-manager.d.ts +57 -11
  84. package/dist/tmux-manager.d.ts.map +1 -1
  85. package/dist/tmux-manager.js +190 -42
  86. package/dist/tmux-manager.js.map +1 -1
  87. package/dist/tmux-prefix-table.d.ts +86 -0
  88. package/dist/tmux-prefix-table.d.ts.map +1 -0
  89. package/dist/tmux-prefix-table.js +172 -0
  90. package/dist/tmux-prefix-table.js.map +1 -0
  91. package/dist/tui/tui-app.d.ts +17 -2
  92. package/dist/tui/tui-app.d.ts.map +1 -1
  93. package/dist/tui/tui-app.js +70 -20
  94. package/dist/tui/tui-app.js.map +1 -1
  95. package/dist/tui/tui-client.d.ts +93 -5
  96. package/dist/tui/tui-client.d.ts.map +1 -1
  97. package/dist/tui/tui-client.js +198 -28
  98. package/dist/tui/tui-client.js.map +1 -1
  99. package/dist/types/app-state.d.ts +6 -0
  100. package/dist/types/app-state.d.ts.map +1 -1
  101. package/dist/types/app-state.js.map +1 -1
  102. package/dist/types/session.d.ts +40 -1
  103. package/dist/types/session.d.ts.map +1 -1
  104. package/dist/types/session.js.map +1 -1
  105. package/dist/usage-telemetry.d.ts +15 -0
  106. package/dist/usage-telemetry.d.ts.map +1 -1
  107. package/dist/usage-telemetry.js +45 -2
  108. package/dist/usage-telemetry.js.map +1 -1
  109. package/dist/utils/copilot-plan-usage.d.ts +46 -0
  110. package/dist/utils/copilot-plan-usage.d.ts.map +1 -0
  111. package/dist/utils/copilot-plan-usage.js +95 -0
  112. package/dist/utils/copilot-plan-usage.js.map +1 -0
  113. package/dist/utils/index.d.ts +2 -0
  114. package/dist/utils/index.d.ts.map +1 -1
  115. package/dist/utils/index.js +2 -0
  116. package/dist/utils/index.js.map +1 -1
  117. package/dist/utils/single-line.d.ts +22 -0
  118. package/dist/utils/single-line.d.ts.map +1 -0
  119. package/dist/utils/single-line.js +30 -0
  120. package/dist/utils/single-line.js.map +1 -0
  121. package/dist/web/agent-inbox-persistence.d.ts +52 -0
  122. package/dist/web/agent-inbox-persistence.d.ts.map +1 -0
  123. package/dist/web/agent-inbox-persistence.js +139 -0
  124. package/dist/web/agent-inbox-persistence.js.map +1 -0
  125. package/dist/web/agent-inbox.d.ts +140 -0
  126. package/dist/web/agent-inbox.d.ts.map +1 -0
  127. package/dist/web/agent-inbox.js +258 -0
  128. package/dist/web/agent-inbox.js.map +1 -0
  129. package/dist/web/approval-inbox.d.ts +6 -0
  130. package/dist/web/approval-inbox.d.ts.map +1 -1
  131. package/dist/web/approval-inbox.js +2 -2
  132. package/dist/web/approval-inbox.js.map +1 -1
  133. package/dist/web/middleware/auth.d.ts +2 -0
  134. package/dist/web/middleware/auth.d.ts.map +1 -1
  135. package/dist/web/middleware/auth.js +145 -52
  136. package/dist/web/middleware/auth.js.map +1 -1
  137. package/dist/web/plan-usage-latest.d.ts +2 -1
  138. package/dist/web/plan-usage-latest.d.ts.map +1 -1
  139. package/dist/web/plan-usage-latest.js +12 -2
  140. package/dist/web/plan-usage-latest.js.map +1 -1
  141. package/dist/web/ports/index.d.ts +1 -0
  142. package/dist/web/ports/index.d.ts.map +1 -1
  143. package/dist/web/ports/keep-warm-port.d.ts +9 -0
  144. package/dist/web/ports/keep-warm-port.d.ts.map +1 -0
  145. package/dist/web/ports/keep-warm-port.js +6 -0
  146. package/dist/web/ports/keep-warm-port.js.map +1 -0
  147. package/dist/web/ports/session-port.d.ts +3 -1
  148. package/dist/web/ports/session-port.d.ts.map +1 -1
  149. package/dist/web/public/admin-ui.js.gz +0 -0
  150. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  151. package/dist/web/public/app.42d0abae.js +43 -0
  152. package/dist/web/public/app.42d0abae.js.br +0 -0
  153. package/dist/web/public/app.42d0abae.js.gz +0 -0
  154. package/dist/web/public/approvals-ui.js.gz +0 -0
  155. package/dist/web/public/{constants.e5e588db.js → constants.0b3cdde9.js} +60 -1
  156. package/dist/web/public/constants.0b3cdde9.js.br +0 -0
  157. package/dist/web/public/constants.0b3cdde9.js.gz +0 -0
  158. package/dist/web/public/cron-ui.js.gz +0 -0
  159. package/dist/web/public/entrance-animations.js.gz +0 -0
  160. package/dist/web/public/git-status-ui.js.gz +0 -0
  161. package/dist/web/public/header-divider.eb2d2463.js +1 -0
  162. package/dist/web/public/header-divider.eb2d2463.js.br +0 -0
  163. package/dist/web/public/header-divider.eb2d2463.js.gz +0 -0
  164. package/dist/web/public/home-sessions.js.gz +0 -0
  165. package/dist/web/public/host-wake-ui.js.gz +0 -0
  166. package/dist/web/public/i18n.1e68220d.js +2 -0
  167. package/dist/web/public/i18n.1e68220d.js.br +0 -0
  168. package/dist/web/public/i18n.1e68220d.js.gz +0 -0
  169. package/dist/web/public/image-input.941386e7.js.gz +0 -0
  170. package/dist/web/public/index.html +162 -14
  171. package/dist/web/public/index.html.br +0 -0
  172. package/dist/web/public/index.html.gz +0 -0
  173. package/dist/web/public/input-cjk.8bc46081.js.gz +0 -0
  174. package/dist/web/public/{keyboard-accessory.c283e827.js → keyboard-accessory.4e8b20ac.js} +23 -5
  175. package/dist/web/public/keyboard-accessory.4e8b20ac.js.br +0 -0
  176. package/dist/web/public/keyboard-accessory.4e8b20ac.js.gz +0 -0
  177. package/dist/web/public/{mobile-handlers.0ef9c311.js → mobile-handlers.1c0cbcd8.js} +50 -16
  178. package/dist/web/public/mobile-handlers.1c0cbcd8.js.br +0 -0
  179. package/dist/web/public/mobile-handlers.1c0cbcd8.js.gz +0 -0
  180. package/dist/web/public/mobile-ime-preview.2dbaf262.js.gz +0 -0
  181. package/dist/web/public/mobile-overview.js.gz +0 -0
  182. package/dist/web/public/mobile.12fb18de.css +1 -0
  183. package/dist/web/public/mobile.12fb18de.css.br +0 -0
  184. package/dist/web/public/mobile.12fb18de.css.gz +0 -0
  185. package/dist/web/public/notification-manager.feb82c04.js.gz +0 -0
  186. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  187. package/dist/web/public/{panels-ui.b7bfb9da.js → panels-ui.78c3defb.js} +19 -13
  188. package/dist/web/public/panels-ui.78c3defb.js.br +0 -0
  189. package/dist/web/public/panels-ui.78c3defb.js.gz +0 -0
  190. package/dist/web/public/ralph-panel.b350e2ef.js.gz +0 -0
  191. package/dist/web/public/ralph-wizard.b6c0dd19.js.gz +0 -0
  192. package/dist/web/public/readmymind-ui.js.gz +0 -0
  193. package/dist/web/public/reboot-restore-ui.js.gz +0 -0
  194. package/dist/web/public/respawn-ui.d7adcf6a.js.gz +0 -0
  195. package/dist/web/public/sanitize-html.e7147e76.js.gz +0 -0
  196. package/dist/web/public/session-lineage.js.gz +0 -0
  197. package/dist/web/public/{session-ui.be076c88.js → session-ui.ea6b1994.js} +33 -27
  198. package/dist/web/public/session-ui.ea6b1994.js.br +0 -0
  199. package/dist/web/public/session-ui.ea6b1994.js.gz +0 -0
  200. package/dist/web/public/settings-ui.085dd202.js +106 -0
  201. package/dist/web/public/settings-ui.085dd202.js.br +0 -0
  202. package/dist/web/public/settings-ui.085dd202.js.gz +0 -0
  203. package/dist/web/public/spreadsheet-preview-worker.js.gz +0 -0
  204. package/dist/web/public/spreadsheet-preview.75c751de.js.gz +0 -0
  205. package/dist/web/public/spreadsheet-xlsx-core.js.gz +0 -0
  206. package/dist/web/public/styles.3b59b41d.css +1 -0
  207. package/dist/web/public/styles.3b59b41d.css.br +0 -0
  208. package/dist/web/public/styles.3b59b41d.css.gz +0 -0
  209. package/dist/web/public/subagent-windows.07277a58.js.gz +0 -0
  210. package/dist/web/public/sw.js +2 -2
  211. package/dist/web/public/sw.js.br +0 -0
  212. package/dist/web/public/sw.js.gz +0 -0
  213. package/dist/web/public/tab-layout-browser.2d6189f7.js.gz +0 -0
  214. package/dist/web/public/tab-rail-resize.7157e0bd.js.gz +0 -0
  215. package/dist/web/public/terminal-keycode229-recovery.af09f99a.js.gz +0 -0
  216. package/dist/web/public/terminal-split.js.gz +0 -0
  217. package/dist/web/public/terminal-tile.js +49 -23
  218. package/dist/web/public/terminal-tile.js.br +0 -0
  219. package/dist/web/public/terminal-tile.js.gz +0 -0
  220. package/dist/web/public/terminal-ui.fb2a27f5.js +2 -0
  221. package/dist/web/public/terminal-ui.fb2a27f5.js.br +0 -0
  222. package/dist/web/public/terminal-ui.fb2a27f5.js.gz +0 -0
  223. package/dist/web/public/tile-grid.js.gz +0 -0
  224. package/dist/web/public/ultracode-panel.js.gz +0 -0
  225. package/dist/web/public/ultracode-windows.js.gz +0 -0
  226. package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
  227. package/dist/web/public/vendor/exceljs.min.js.gz +0 -0
  228. package/dist/web/public/vendor/fflate.min.js.gz +0 -0
  229. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  230. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  231. package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
  232. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  233. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  234. package/dist/web/public/vendor/xterm-predictive-echo.ae5e3d5d.js.gz +0 -0
  235. package/dist/web/public/vendor/xterm-zerolag-input.0b69623c.js.gz +0 -0
  236. package/dist/web/public/vendor/xterm.css.gz +0 -0
  237. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  238. package/dist/web/public/voice-input.2342c37b.js.gz +0 -0
  239. package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
  240. package/dist/web/public/webview-tabs.js.gz +0 -0
  241. package/dist/web/public/whats-new.json +9 -9
  242. package/dist/web/route-helpers.d.ts +57 -6
  243. package/dist/web/route-helpers.d.ts.map +1 -1
  244. package/dist/web/route-helpers.js +72 -4
  245. package/dist/web/route-helpers.js.map +1 -1
  246. package/dist/web/routes/admin-routes.d.ts +2 -1
  247. package/dist/web/routes/admin-routes.d.ts.map +1 -1
  248. package/dist/web/routes/admin-routes.js +3 -0
  249. package/dist/web/routes/admin-routes.js.map +1 -1
  250. package/dist/web/routes/case-routes.d.ts +2 -2
  251. package/dist/web/routes/case-routes.d.ts.map +1 -1
  252. package/dist/web/routes/case-routes.js +263 -4
  253. package/dist/web/routes/case-routes.js.map +1 -1
  254. package/dist/web/routes/config-backup-routes.d.ts +54 -0
  255. package/dist/web/routes/config-backup-routes.d.ts.map +1 -0
  256. package/dist/web/routes/config-backup-routes.js +268 -0
  257. package/dist/web/routes/config-backup-routes.js.map +1 -0
  258. package/dist/web/routes/custom-model-routes.d.ts +17 -1
  259. package/dist/web/routes/custom-model-routes.d.ts.map +1 -1
  260. package/dist/web/routes/custom-model-routes.js +118 -13
  261. package/dist/web/routes/custom-model-routes.js.map +1 -1
  262. package/dist/web/routes/hook-event-routes.d.ts.map +1 -1
  263. package/dist/web/routes/hook-event-routes.js +26 -5
  264. package/dist/web/routes/hook-event-routes.js.map +1 -1
  265. package/dist/web/routes/inbox-routes.d.ts +43 -0
  266. package/dist/web/routes/inbox-routes.d.ts.map +1 -0
  267. package/dist/web/routes/inbox-routes.js +148 -0
  268. package/dist/web/routes/inbox-routes.js.map +1 -0
  269. package/dist/web/routes/index.d.ts +2 -0
  270. package/dist/web/routes/index.d.ts.map +1 -1
  271. package/dist/web/routes/index.js +2 -0
  272. package/dist/web/routes/index.js.map +1 -1
  273. package/dist/web/routes/ralph-routes.js +2 -2
  274. package/dist/web/routes/ralph-routes.js.map +1 -1
  275. package/dist/web/routes/reboot-restore-routes.d.ts.map +1 -1
  276. package/dist/web/routes/reboot-restore-routes.js +2 -0
  277. package/dist/web/routes/reboot-restore-routes.js.map +1 -1
  278. package/dist/web/routes/session-routes.d.ts +2 -2
  279. package/dist/web/routes/session-routes.d.ts.map +1 -1
  280. package/dist/web/routes/session-routes.js +110 -61
  281. package/dist/web/routes/session-routes.js.map +1 -1
  282. package/dist/web/routes/status-telemetry-routes.d.ts +2 -2
  283. package/dist/web/routes/status-telemetry-routes.d.ts.map +1 -1
  284. package/dist/web/routes/status-telemetry-routes.js +8 -4
  285. package/dist/web/routes/status-telemetry-routes.js.map +1 -1
  286. package/dist/web/routes/system-routes.d.ts +2 -2
  287. package/dist/web/routes/system-routes.d.ts.map +1 -1
  288. package/dist/web/routes/system-routes.js +30 -2
  289. package/dist/web/routes/system-routes.js.map +1 -1
  290. package/dist/web/routes/webview-routes.d.ts.map +1 -1
  291. package/dist/web/routes/webview-routes.js +1 -0
  292. package/dist/web/routes/webview-routes.js.map +1 -1
  293. package/dist/web/schemas.d.ts +45 -0
  294. package/dist/web/schemas.d.ts.map +1 -1
  295. package/dist/web/schemas.js +79 -2
  296. package/dist/web/schemas.js.map +1 -1
  297. package/dist/web/sensitive-path.d.ts.map +1 -1
  298. package/dist/web/sensitive-path.js +2 -0
  299. package/dist/web/sensitive-path.js.map +1 -1
  300. package/dist/web/server.d.ts +14 -2
  301. package/dist/web/server.d.ts.map +1 -1
  302. package/dist/web/server.js +176 -7
  303. package/dist/web/server.js.map +1 -1
  304. package/dist/web/session-listener-wiring.d.ts +6 -0
  305. package/dist/web/session-listener-wiring.d.ts.map +1 -1
  306. package/dist/web/session-listener-wiring.js +10 -3
  307. package/dist/web/session-listener-wiring.js.map +1 -1
  308. package/dist/web/session-wait-registry.d.ts +48 -4
  309. package/dist/web/session-wait-registry.d.ts.map +1 -1
  310. package/dist/web/session-wait-registry.js +144 -6
  311. package/dist/web/session-wait-registry.js.map +1 -1
  312. package/dist/web/sse-events.d.ts +9 -3
  313. package/dist/web/sse-events.d.ts.map +1 -1
  314. package/dist/web/sse-events.js +9 -3
  315. package/dist/web/sse-events.js.map +1 -1
  316. package/dist/web/webview-proxy.d.ts +8 -3
  317. package/dist/web/webview-proxy.d.ts.map +1 -1
  318. package/dist/web/webview-proxy.js +23 -5
  319. package/dist/web/webview-proxy.js.map +1 -1
  320. package/package.json +1 -1
  321. package/skills/codeman/SKILL.md +27 -13
  322. package/skills/codeman/preamble.sh +15 -5
  323. package/skills/codeman/reference/endpoints.md +32 -10
  324. package/skills/codeman/reference/recipes.md +1 -1
  325. package/skills/codeman/reference/verbs.md +68 -13
  326. package/dist/web/public/app.ae50029a.js +0 -43
  327. package/dist/web/public/app.ae50029a.js.br +0 -0
  328. package/dist/web/public/app.ae50029a.js.gz +0 -0
  329. package/dist/web/public/constants.e5e588db.js.br +0 -0
  330. package/dist/web/public/constants.e5e588db.js.gz +0 -0
  331. package/dist/web/public/i18n.8f291f06.js +0 -2
  332. package/dist/web/public/i18n.8f291f06.js.br +0 -0
  333. package/dist/web/public/i18n.8f291f06.js.gz +0 -0
  334. package/dist/web/public/keyboard-accessory.c283e827.js.br +0 -0
  335. package/dist/web/public/keyboard-accessory.c283e827.js.gz +0 -0
  336. package/dist/web/public/mobile-handlers.0ef9c311.js.br +0 -0
  337. package/dist/web/public/mobile-handlers.0ef9c311.js.gz +0 -0
  338. package/dist/web/public/mobile.13514863.css +0 -1
  339. package/dist/web/public/mobile.13514863.css.br +0 -0
  340. package/dist/web/public/mobile.13514863.css.gz +0 -0
  341. package/dist/web/public/panels-ui.b7bfb9da.js.br +0 -0
  342. package/dist/web/public/panels-ui.b7bfb9da.js.gz +0 -0
  343. package/dist/web/public/session-ui.be076c88.js.br +0 -0
  344. package/dist/web/public/session-ui.be076c88.js.gz +0 -0
  345. package/dist/web/public/settings-ui.fc4cc50a.js +0 -97
  346. package/dist/web/public/settings-ui.fc4cc50a.js.br +0 -0
  347. package/dist/web/public/settings-ui.fc4cc50a.js.gz +0 -0
  348. package/dist/web/public/styles.dcfa907f.css +0 -1
  349. package/dist/web/public/styles.dcfa907f.css.br +0 -0
  350. package/dist/web/public/styles.dcfa907f.css.gz +0 -0
  351. package/dist/web/public/terminal-ui.ae01a7d1.js +0 -2
  352. package/dist/web/public/terminal-ui.ae01a7d1.js.br +0 -0
  353. package/dist/web/public/terminal-ui.ae01a7d1.js.gz +0 -0
@@ -44,6 +44,7 @@ import { looksLikeHostReboot, newestPersistedActivity, planRebootRestore } from
44
44
  import { rebootRestoreRegistry } from './reboot-restore-registry.js';
45
45
  import { dataPath, getDataDir, CODEMAN_INSTANCE } from '../config/instance.js';
46
46
  import { WebhookNotifier, readWebhookConfig } from '../webhook-notify.js';
47
+ import { stopHostSensors } from '../host-sensors.js';
47
48
  import { webviewFetch } from './webview-egress.js';
48
49
  import { readRemoteHosts, rehydrateRemoteHostFields } from '../remote-hosts.js';
49
50
  import { normalizeBasePath, stripBasePath, joinBasePath } from '../config/base-path.js';
@@ -77,7 +78,7 @@ import { createRequire } from 'node:module';
77
78
  import { RunSummaryTracker } from '../run-summary.js';
78
79
  import { OrchestratorLoop } from '../orchestrator-loop.js';
79
80
  import { getLifecycleLog } from '../session-lifecycle-log.js';
80
- import { applyWorkspaceHooks, pruneAgentSessionPreambles, removeAgentSessionPreamble } from '../hooks-config.js';
81
+ import { applyWorkspaceHooks, pruneAgentSessionPreambles, readKeepWarmSettingsOrThrow, readPlanUsageTelemetryEnabled, removeAgentSessionPreamble, } from '../hooks-config.js';
81
82
  import { PushSubscriptionStore } from '../push-store.js';
82
83
  import webpush from 'web-push';
83
84
  import { SseStreamManager } from './sse-stream-manager.js';
@@ -86,9 +87,14 @@ import { deriveWebviewSseHint } from './webview-sse.js';
86
87
  import { createSessionListeners, attachSessionListeners, detachSessionListeners, } from './session-listener-wiring.js';
87
88
  import { sessionWaits } from './session-wait-registry.js';
88
89
  import { intentStore } from '../intent-store.js';
90
+ import { agentInbox } from './agent-inbox.js';
91
+ import { agentInboxPersistence } from './agent-inbox-persistence.js';
89
92
  import { AI_CHECK_MODEL } from '../config/ai-defaults.js';
90
93
  import { approvalInbox } from './approval-inbox.js';
91
94
  import { SCREEN_BLOCKED_SOURCE } from './screen-blocked.js';
95
+ import { PromptCacheKeepWarmService } from '../prompt-cache-keepwarm.js';
96
+ import { composerIsEmpty, plainFrame } from '../session-submit-verifier.js';
97
+ import { detectUsageLimitPause } from '../usage-limit-patterns.js';
92
98
  import { stopDeepSeekWeb } from '../deepseek-web-server.js';
93
99
  import { wireRespawnListeners, setupTimedRespawn, restoreRespawnController, saveRespawnConfig, } from './respawn-event-wiring.js';
94
100
  import { reconcileUpdateOnBoot } from './self-update.js';
@@ -114,23 +120,31 @@ import { MAX_PASTE_IMAGE_BYTES } from '../config/buffer-limits.js';
114
120
  import { resolveTerminalHistoryConfig } from '../config/terminal-history.js';
115
121
  import { SseEvent } from './sse-events.js';
116
122
  import { MAX_ROUTE_PARAM_LENGTH } from './route-helpers.js';
117
- import { getLatestPlanUsage, setLatestCodexPlanUsage } from './plan-usage-latest.js';
123
+ import { getLatestPlanUsage, setLatestCodexPlanUsage, setLatestCopilotPlanUsage } from './plan-usage-latest.js';
118
124
  import { telemetrySignature } from '../usage-telemetry.js';
119
125
  import { readCodexPlanUsage, resolveCodexBinaryPath } from '../utils/codex-cli-resolver.js';
126
+ import { isCopilotAvailable } from '../utils/copilot-cli-resolver.js';
127
+ import { readCopilotPlanUsage, readCopilotPlanUsageIfEnabled, resolveCopilotToken, } from '../utils/copilot-plan-usage.js';
120
128
  import { registerAuthMiddleware, registerSecurityHeaders, registerHostGuard, isLostWebviewRootFrame, sendLostWebviewFramePage, } from './middleware/auth.js';
121
129
  import { isMultiUserMode } from '../config/multiuser.js';
122
130
  import { bootstrapInitialAdmin, hasUsers, resolveClaudeModeForUsername } from '../user-store.js';
123
131
  import { installRouteErrorHandler } from './route-error-handler.js';
124
132
  import { isExplicitlyEnabled, isLoopbackBindHost, buildHostPolicy } from './network-auth-policy.js';
125
- import { registerPushRoutes, registerTeamRoutes, registerMuxRoutes, registerFileRoutes, registerHtmlViewRoutes, registerScheduledRoutes, registerHookEventRoutes, registerApprovalRoutes, registerRebootRestoreRoutes, registerReadMyMindRoutes, registerGitStatusRoutes, registerStatusTelemetryRoutes, registerSystemRoutes, registerCaseRoutes, registerWorktreeRoutes, registerSessionRoutes, registerRespawnRoutes, registerRalphRoutes, registerPlanRoutes, registerClipboardRoutes, registerSearchRoutes, registerOrchestratorRoutes, registerCronRoutes, registerMeRoutes, registerAdminRoutes, registerWsRoutes, registerVoiceRoutes, registerWebviewRoutes, registerTabLayoutRoutes, registerMcpSyncRoutes, registerWebhookRoutes, registerDoctorRoutes, registerCustomModelRoutes, refreshAllCustomModelHosts, readCustomModelEndpointsEnabled, closeAllLlamaSwapLogTails, detectCustomModelSwapDisplacements, pruneIdleLlamaSwapLogTails, tryWebviewRefererFallback, registerCliRegistryRoutes, } from './routes/index.js';
133
+ import { registerPushRoutes, registerTeamRoutes, registerMuxRoutes, registerFileRoutes, registerHtmlViewRoutes, registerScheduledRoutes, registerHookEventRoutes, registerApprovalRoutes, registerInboxRoutes, registerRebootRestoreRoutes, registerReadMyMindRoutes, registerGitStatusRoutes, registerStatusTelemetryRoutes, registerSystemRoutes, registerCaseRoutes, registerWorktreeRoutes, registerSessionRoutes, registerRespawnRoutes, registerRalphRoutes, registerPlanRoutes, registerClipboardRoutes, registerSearchRoutes, registerOrchestratorRoutes, registerCronRoutes, registerMeRoutes, registerAdminRoutes, registerWsRoutes, registerVoiceRoutes, registerWebviewRoutes, registerTabLayoutRoutes, registerMcpSyncRoutes, registerConfigBackupRoutes, runConfigBackup, registerWebhookRoutes, registerDoctorRoutes, registerCustomModelRoutes, refreshAllCustomModelHosts, readCustomModelEndpointsEnabled, closeAllLlamaSwapLogTails, detectCustomModelSwapDisplacements, pruneIdleLlamaSwapLogTails, tryWebviewRefererFallback, registerCliRegistryRoutes, } from './routes/index.js';
126
134
  import { isLostWebviewFrameNavigation } from './webview-proxy.js';
127
135
  import { CronService } from '../cron/cron-service.js';
136
+ import { caseSessionSettled } from '../case-rename.js';
128
137
  const __dirname = dirname(fileURLToPath(import.meta.url));
129
138
  // Bounded, predictable shape for SSE client identifiers: alphanumerics, `_`, `-`.
130
139
  // Length range covers crypto.randomUUID() (36 chars) plus any short stable IDs,
131
140
  // while capping growth of `sseClientsById` and blocking pathological inputs.
132
141
  const SSE_CLIENT_ID_RE = /^[A-Za-z0-9_-]{8,64}$/;
133
142
  const CODEX_USAGE_POLL_INTERVAL_MS = 5 * 60_000;
143
+ // Copilot's quota is read over the network from GitHub, and a monthly figure moves slowly.
144
+ const COPILOT_USAGE_POLL_INTERVAL_MS = 10 * 60_000;
145
+ // Config backups: one cheap read-and-hash of a handful of small files; a snapshot is written only when
146
+ // their content changed since the latest one, so this is one entry per real change, not per tick.
147
+ const CONFIG_BACKUP_INTERVAL_MS = 5 * 60_000;
134
148
  const CUSTOM_MODEL_REDISCOVER_INTERVAL_MS = 5 * 60_000;
135
149
  // Much shorter than the model-LIST refresh above on purpose: this catches an actual
136
150
  // eviction (a session's model no longer loaded, silently swapped out by another
@@ -196,6 +210,50 @@ export class WebServer extends EventEmitter {
196
210
  _customModelDisplacedNotified = new Set();
197
211
  /** Cron service (assigned in setupRoutes). */
198
212
  cronService;
213
+ // Prompt-cache keep-warm (prompt-cache-keepwarm.ts): the rulebook and timers live in the
214
+ // service; the server only lends it the facts a decision needs. A field initializer, not a
215
+ // setupRoutes() step: cleanup and listener wiring reach it on servers that never set up routes
216
+ // (the pane-exit and discard tests build a bare WebServer), and every port reads `this.*` lazily.
217
+ keepWarmService = new PromptCacheKeepWarmService({
218
+ getSession: (id) => this.sessions.get(id),
219
+ sessionIds: () => this.sessions.keys(),
220
+ readSettings: readKeepWarmSettingsOrThrow,
221
+ isAutomationActive: (id) => {
222
+ const session = this.sessions.get(id);
223
+ if (!session)
224
+ return true;
225
+ return ((this.respawnControllers.get(id)?.isRunning ?? false) ||
226
+ session.ralphTracker.loopState.active ||
227
+ session.currentTaskId !== null ||
228
+ sessionWaits.signalWaiterCount(id) > 0);
229
+ },
230
+ hasPendingApproval: (id) => approvalInbox.listPending().some((item) => item.sessionId === id && item.kind !== 'idle'),
231
+ readPane: (view) => {
232
+ const session = view;
233
+ const muxName = session.muxName;
234
+ // `capture-pane -p -e`: the visible frame with its styles, which is how `composerIsEmpty()`
235
+ // tells Claude Code's faint prompt suggestion from a typed draft.
236
+ const text = muxName ? (this.mux.capturePaneText?.(muxName, undefined, { styles: true }) ?? null) : null;
237
+ if (!text)
238
+ return { composerEmpty: null, limitShown: false };
239
+ const limit = detectUsageLimitPause(plainFrame(text));
240
+ return {
241
+ composerEmpty: composerIsEmpty(text, getCli(session.mode)?.capabilities.workDetect?.promptGlyph ?? '') ?? null,
242
+ limitShown: limit !== null && limit.resetAt > Date.now(),
243
+ };
244
+ },
245
+ publish: (id, status) => {
246
+ const session = this.sessions.get(id);
247
+ if (!session?.setKeepWarmStatus(status))
248
+ return;
249
+ this.broadcast(SseEvent.SessionKeepWarm, { sessionId: id, ...status });
250
+ // Change-only, so one line per transition names whichever guard holds it now.
251
+ if (status.state !== 'off' || status.reason !== 'off') {
252
+ console.log(`[KeepWarm] ${id.slice(0, 8)}: ${status.state}${status.reason ? ` (${status.reason})` : ''}`);
253
+ }
254
+ },
255
+ log: (message) => console.log(message),
256
+ });
199
257
  /**
200
258
  * Wake-on-LAN registry, returned by `registerSessionRoutes`. Held for its LIFETIME
201
259
  * only — `drop()` on session cleanup, `stop()` on shutdown. Waking from here would
@@ -232,6 +290,8 @@ export class WebServer extends EventEmitter {
232
290
  lastRecordedTokens = new Map();
233
291
  codexUsageRefreshInFlight = false;
234
292
  lastCodexUsageSignature = null;
293
+ copilotUsageRefreshInFlight = false;
294
+ lastCopilotUsageSignature = null;
235
295
  // Server startup time for respawn grace period calculation
236
296
  serverStartTime = Date.now();
237
297
  // Pending respawn start timers (for cleanup on shutdown)
@@ -249,6 +309,7 @@ export class WebServer extends EventEmitter {
249
309
  qrAuthFailures = null;
250
310
  hookSecretFailures = null;
251
311
  userFailures = null;
312
+ userIpFailures = null;
252
313
  pushStore = new PushSubscriptionStore();
253
314
  /** ntfy / Slack / Discord / generic webhook for the push events; config in webhook.json (0600). */
254
315
  webhookNotifier = new WebhookNotifier(() => readWebhookConfig(getDataDir()), webviewFetch);
@@ -315,6 +376,7 @@ export class WebServer extends EventEmitter {
315
376
  // Fresh, not the 2s cache: the settings PUT does not invalidate it, and a
316
377
  // flip must apply to the very next spawn.
317
378
  childrenFollowParent: async () => (await this.readSettings(true)).spawnedTabsFollowParent === true,
379
+ caseDefaults: this.store,
318
380
  });
319
381
  if (this.testMode)
320
382
  this.tabLayouts.markRestorationSkipped();
@@ -551,12 +613,14 @@ export class WebServer extends EventEmitter {
551
613
  /**
552
614
  * Add a tentative session only after its owner layout accepts the creation.
553
615
  * The session enters the live map inside the layout service's owner lock, so
554
- * the placement it asked for (`tabGroupId`) lands in the same versioned write
555
- * that first records it.
616
+ * the placement it asked for (`tabGroupId`, else its case's default group)
617
+ * lands in the same versioned write that first records it.
556
618
  */
557
619
  async registerSessionWithLayout(session, placement) {
558
620
  return this.tabLayouts.sessionCreated(ownerLayoutKey(session.owner), session.id, placement, () => {
559
621
  this.sessions.set(session.id, session);
622
+ // In the map now, so a case rename lists (and on request stops) it instead of refusing the move.
623
+ caseSessionSettled(session);
560
624
  return () => {
561
625
  if (this.sessions.get(session.id) === session)
562
626
  this.sessions.delete(session.id);
@@ -573,6 +637,7 @@ export class WebServer extends EventEmitter {
573
637
  return {
574
638
  // SessionPort
575
639
  sessions: this.sessions,
640
+ keepWarm: this.keepWarmService,
576
641
  addSession: this.registerSessionWithLayout.bind(this),
577
642
  tabLayouts: this.tabLayouts,
578
643
  cleanupSession: this.cleanupSession.bind(this),
@@ -709,6 +774,7 @@ export class WebServer extends EventEmitter {
709
774
  this.qrAuthFailures = authState.qrAuthFailures;
710
775
  this.hookSecretFailures = authState.hookSecretFailures;
711
776
  this.userFailures = authState.userFailures;
777
+ this.userIpFailures = authState.userIpFailures;
712
778
  }
713
779
  // WebSocket support (terminal I/O — low-latency bidirectional channel)
714
780
  await this.app.register(fastifyWebsocket);
@@ -972,6 +1038,7 @@ export class WebServer extends EventEmitter {
972
1038
  registerScheduledRoutes(this.app, ctx);
973
1039
  registerHookEventRoutes(this.app, ctx);
974
1040
  registerApprovalRoutes(this.app, ctx);
1041
+ registerInboxRoutes(this.app, ctx);
975
1042
  registerRebootRestoreRoutes(this.app, ctx);
976
1043
  registerReadMyMindRoutes(this.app, ctx);
977
1044
  registerGitStatusRoutes(this.app, ctx);
@@ -993,6 +1060,7 @@ export class WebServer extends EventEmitter {
993
1060
  registerWebviewRoutes(this.app, ctx, this.basePath);
994
1061
  registerTabLayoutRoutes(this.app, ctx);
995
1062
  registerMcpSyncRoutes(this.app);
1063
+ registerConfigBackupRoutes(this.app);
996
1064
  registerWebhookRoutes(this.app, {
997
1065
  notifier: this.webhookNotifier,
998
1066
  configDir: getDataDir(),
@@ -1203,6 +1271,7 @@ export class WebServer extends EventEmitter {
1203
1271
  }
1204
1272
  async _doCleanupSession(sessionId, killMux, reason, coordinateLayout = true) {
1205
1273
  const session = this.sessions.get(sessionId);
1274
+ this.keepWarmService.remove(sessionId);
1206
1275
  const pinned = session?.pinned === true || this.store.getSession(sessionId)?.pinned === true;
1207
1276
  if (coordinateLayout && session && killMux && !pinned) {
1208
1277
  return this.tabLayouts.runSessionDeletion([{ id: sessionId, owner: session.owner }], () => this._doCleanupSession(sessionId, killMux, reason, false));
@@ -1378,6 +1447,12 @@ export class WebServer extends EventEmitter {
1378
1447
  // The 'exit' here is NOT redundant with the PTY-exit listener: listeners are
1379
1448
  // detached a few lines above, before `session.stop()`, so on a delete the
1380
1449
  // session's own exit event never reaches the registry.
1450
+ // The agent inbox goes with a DELETED session only. killMux=false is shutdown or
1451
+ // detach: mail posted before that is still there when the session is re-attached
1452
+ // (while detached, the routes answer 404, so nothing new arrives). Before
1453
+ // cancelAll, so a reader released by it reads an empty inbox.
1454
+ if (killMux)
1455
+ agentInbox.drop(sessionId);
1381
1456
  sessionWaits.notifySignal(sessionId, 'exit');
1382
1457
  sessionWaits.cancelAll(sessionId);
1383
1458
  approvalInbox.resolveForSession(sessionId, 'session_ended');
@@ -1625,6 +1700,9 @@ export class WebServer extends EventEmitter {
1625
1700
  });
1626
1701
  }
1627
1702
  async setupSessionListeners(session) {
1703
+ // First keep-warm evaluation: publishes the effective opt-in for the Session Options
1704
+ // readout even before any cache report arrives.
1705
+ void this.keepWarmService.schedule(session.id);
1628
1706
  // Idempotent: the wiring exit handler detaches ALL listeners on every PTY exit
1629
1707
  // (removeSessionListenerRefs), so the re-attach routes (/interactive,
1630
1708
  // /interactive-respawn, /shell) call this again to restore observability
@@ -1653,6 +1731,7 @@ export class WebServer extends EventEmitter {
1653
1731
  buildSessionListenerDeps() {
1654
1732
  return {
1655
1733
  broadcast: this.broadcast.bind(this),
1734
+ keepWarmSchedule: (id) => void this.keepWarmService.schedule(id),
1656
1735
  batchTerminalData: this.batchTerminalData.bind(this),
1657
1736
  batchTaskUpdate: this.batchTaskUpdate.bind(this),
1658
1737
  broadcastSessionStateDebounced: this.broadcastSessionStateDebounced.bind(this),
@@ -1849,6 +1928,12 @@ export class WebServer extends EventEmitter {
1849
1928
  const settings = await this.readSettings();
1850
1929
  return settings.agentSkillEnabled === true;
1851
1930
  }
1931
+ // Whether pending agent-inbox mail is snapshotted to disk (synced
1932
+ // `agentInboxPersistEnabled` setting). Default OFF: an absent key writes nothing.
1933
+ async getAgentInboxPersistEnabled() {
1934
+ const settings = await this.readSettings();
1935
+ return settings.agentInboxPersistEnabled === true;
1936
+ }
1852
1937
  // Whether a Claude session installs Codeman's hooks block into its workspace
1853
1938
  // (synced `workspaceHooksEnabled` setting). Default ON — an absent key means a
1854
1939
  // user who has never seen this setting, and OFF for them would mean no tab
@@ -2552,6 +2637,36 @@ export class WebServer extends EventEmitter {
2552
2637
  this.codexUsageRefreshInFlight = false;
2553
2638
  }
2554
2639
  }
2640
+ /**
2641
+ * Copilot's monthly premium-request quota for the shared plan-usage chip. Host-scoped and read-only
2642
+ * like the Codex poll, and additionally gated by `readCopilotPlanUsageIfEnabled()`: only while
2643
+ * plan-usage display is on (so turning the chip off also stops the outbound request), only with a
2644
+ * sign-in token and only with the Copilot CLI installed; nothing is sent to GitHub otherwise.
2645
+ */
2646
+ async refreshCopilotPlanUsage() {
2647
+ if (this.copilotUsageRefreshInFlight)
2648
+ return;
2649
+ this.copilotUsageRefreshInFlight = true;
2650
+ try {
2651
+ // A null clears the row, so switching the chip off also takes the last quota off the page.
2652
+ const usage = await readCopilotPlanUsageIfEnabled({
2653
+ planUsageEnabled: readPlanUsageTelemetryEnabled,
2654
+ resolveToken: () => resolveCopilotToken(),
2655
+ copilotInstalled: isCopilotAvailable,
2656
+ read: (token) => readCopilotPlanUsage({ token }),
2657
+ });
2658
+ const signature = usage ? telemetrySignature(usage) : '';
2659
+ if (signature === this.lastCopilotUsageSignature)
2660
+ return;
2661
+ this.lastCopilotUsageSignature = signature;
2662
+ const snapshot = setLatestCopilotPlanUsage(usage);
2663
+ this.cachedLightState = null;
2664
+ this.broadcast(SseEvent.SessionStatusTelemetry, snapshot);
2665
+ }
2666
+ finally {
2667
+ this.copilotUsageRefreshInFlight = false;
2668
+ }
2669
+ }
2555
2670
  async start() {
2556
2671
  // Multi-user first boot: create the initial admin from CODEMAN_USERNAME/PASSWORD
2557
2672
  // if there are no users yet, else refuse to start (there would be no way in).
@@ -2580,6 +2695,11 @@ export class WebServer extends EventEmitter {
2580
2695
  // This prevents race conditions where clients connect before state is ready
2581
2696
  // CRITICAL: Skip in test mode to prevent tests from picking up user sessions
2582
2697
  if (!this.testMode) {
2698
+ // Pending agent mail first (opt-in snapshot), so it is in place before any
2699
+ // route can answer. Pruned against the restored sessions in finalizeRestoredState.
2700
+ const restoredMail = agentInboxPersistence.load(await this.getAgentInboxPersistEnabled());
2701
+ if (restoredMail > 0)
2702
+ console.log(`[AgentInbox] restored ${restoredMail} pending message(s)`);
2583
2703
  const restored = await this.restoreMuxSessions();
2584
2704
  await this.finalizeRestoredState(restored);
2585
2705
  // Instance-scoped reaper: after restore, `docker rm -f` managed containers of
@@ -2683,6 +2803,14 @@ export class WebServer extends EventEmitter {
2683
2803
  // Ensure the COD-54 hook secret exists on disk before any session exports
2684
2804
  // $CODEMAN_HOOK_SECRET_FILE — hook curls cat that path at execution time.
2685
2805
  getHookSecret();
2806
+ // Config backups: a snapshot at start (the state the process found) and then on change.
2807
+ if (!this.testMode) {
2808
+ const backup = (reason) => void runConfigBackup(reason).catch((err) => console.warn('[config-backup] snapshot failed:', err instanceof Error ? err.message : err));
2809
+ backup('startup');
2810
+ this.cleanup.setInterval(() => backup('auto'), CONFIG_BACKUP_INTERVAL_MS, {
2811
+ description: 'Config backup',
2812
+ });
2813
+ }
2686
2814
  // Main Codex subscription limits come from the signed-in local CLI. Keep
2687
2815
  // this read-only and host-scoped; multi-user SSE routing makes it admin-only.
2688
2816
  if (!this.testMode) {
@@ -2690,6 +2818,10 @@ export class WebServer extends EventEmitter {
2690
2818
  this.cleanup.setInterval(() => void this.refreshCodexPlanUsage(), CODEX_USAGE_POLL_INTERVAL_MS, {
2691
2819
  description: 'Codex plan-usage refresh',
2692
2820
  });
2821
+ void this.refreshCopilotPlanUsage();
2822
+ this.cleanup.setInterval(() => void this.refreshCopilotPlanUsage(), COPILOT_USAGE_POLL_INTERVAL_MS, {
2823
+ description: 'Copilot plan-usage refresh',
2824
+ });
2693
2825
  }
2694
2826
  // Custom Model Endpoint Profiles (docs/custom-model-endpoints-plan.md): keeps
2695
2827
  // each saved endpoint's discovered model list current with no manual
@@ -3182,6 +3314,10 @@ export class WebServer extends EventEmitter {
3182
3314
  // the launch conversation until the user types again, even though
3183
3315
  // the re-attached CLI is on a post-`/clear` one.
3184
3316
  lastSubmitAt: savedState?.lastSubmitAt,
3317
+ // The keep-warm choice and its cap's clock; with the restored `promptCache`
3318
+ // below, a future `expiresAt` re-arms the timer once the pane is attached.
3319
+ lastRealPromptAt: savedState?.lastRealPromptAt,
3320
+ keepWarmEnabled: savedState?.keepWarmEnabled,
3185
3321
  // Conversations this pane provably owned, oldest first. Its tail is
3186
3322
  // the conversation the CLI was on when the server stopped, which is
3187
3323
  // what a re-attach must point the viewer at instead of the launch id.
@@ -3356,6 +3492,7 @@ export class WebServer extends EventEmitter {
3356
3492
  }
3357
3493
  }
3358
3494
  this.sessions.set(session.id, session);
3495
+ caseSessionSettled(session);
3359
3496
  await this.setupSessionListeners(session);
3360
3497
  // Auto-attach PTY to the surviving tmux session immediately.
3361
3498
  // This ensures ALL sessions resume capturing output right away,
@@ -3428,6 +3565,21 @@ export class WebServer extends EventEmitter {
3428
3565
  this.tabLayouts.markRestorationComplete();
3429
3566
  await this.cleanupStaleSessions();
3430
3567
  await this.tabLayouts.reconcileAfterRestoration();
3568
+ // Same gate as the tab layouts: only a restore that succeeded may decide which
3569
+ // inboxes are orphans. A failed one returns above and leaves every message alone.
3570
+ // Kept: live sessions plus every record state.json still holds (a session that
3571
+ // was not re-adopted this boot but is kept for recovery keeps its mail), plus the
3572
+ // sessions on the reboot-restore offer. `cleanupStaleSessions()` above has already
3573
+ // pruned their records, and a rebuild reuses the old id, so without them a restored
3574
+ // session would come back to an empty inbox. The 12 h TTL bounds what is never rebuilt.
3575
+ const keep = new Set([
3576
+ ...this.sessions.keys(),
3577
+ ...Object.keys(this.store.getSessions()),
3578
+ ...rebootRestoreRegistry.list(() => true).map((entry) => entry.sessionId),
3579
+ ]);
3580
+ const pruned = agentInbox.pruneExcept(keep);
3581
+ if (pruned > 0)
3582
+ console.log(`[AgentInbox] dropped ${pruned} inbox(es) of sessions that did not come back`);
3431
3583
  }
3432
3584
  /**
3433
3585
  * Install Codeman's hooks into the workspaces of the sessions just recovered.
@@ -3582,6 +3734,8 @@ export class WebServer extends EventEmitter {
3582
3734
  // port against the next start — the exact EADDRINUSE this feature already
3583
3735
  // got wrong once.
3584
3736
  void stopDeepSeekWeb();
3737
+ // The header's GPU / CPU clock sampler is a Windows child process under WSL, and its idle sweep a timer.
3738
+ stopHostSensors();
3585
3739
  // Same teardown rule: the per-endpoint llama-swap log tails are otherwise closed
3586
3740
  // only by the periodic idle sweep, whose interval is disposed just below.
3587
3741
  closeAllLlamaSwapLogTails();
@@ -3594,8 +3748,12 @@ export class WebServer extends EventEmitter {
3594
3748
  // response), so without this a 10-minute wait holds shutdown open.
3595
3749
  sessionWaits.cancelEverything();
3596
3750
  approvalInbox.stop();
3751
+ this.keepWarmService.stop();
3752
+ // A post within the debounce window before shutdown would otherwise not reach disk.
3753
+ // Flushed again after `app.close()` below, for a post that lands in between.
3754
+ agentInboxPersistence.flush();
3597
3755
  // Same reason as `cancelEverything` above: an in-flight wake is awaited by a request,
3598
- // and `app.close()` (the last line of this method) does not abort in-flight requests —
3756
+ // and `app.close()` (at the end of this method) does not abort in-flight requests,
3599
3757
  // so without this a restart during a wake waits out the readiness poll.
3600
3758
  this.remoteWake?.stop();
3601
3759
  this.lastRecordedTokens.clear();
@@ -3710,12 +3868,23 @@ export class WebServer extends EventEmitter {
3710
3868
  this.userFailures.dispose();
3711
3869
  this.userFailures = null;
3712
3870
  }
3871
+ if (this.userIpFailures) {
3872
+ this.userIpFailures.dispose();
3873
+ this.userIpFailures = null;
3874
+ }
3713
3875
  this.activePlanOrchestrators.clear();
3714
3876
  this.cleaningUp.clear();
3715
3877
  this.killingSessions.clear();
3716
3878
  // Dispose push store (flush pending saves)
3717
3879
  this.pushStore.dispose();
3718
- await this.app.close();
3880
+ try {
3881
+ await this.app.close();
3882
+ }
3883
+ finally {
3884
+ // The routes answer until `app.close()` returns, so a post after the flush above
3885
+ // would otherwise sit in an unref'd debounce timer and never reach disk.
3886
+ agentInboxPersistence.flush();
3887
+ }
3719
3888
  }
3720
3889
  }
3721
3890
  export async function startWebServer(port = 3000, https = false, testMode = false, host = '127.0.0.1', titleHostname, allowUnauthenticatedNetwork = false, basePath = '') {