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
@@ -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, readKeepWarmSettingsOrThrow, readPlanUsageTelemetryEnabled, removeAgentSessionPreamble, } from '../hooks-config.js';
81
+ import { applyWorkspaceHooks, pruneAgentSessionPreambles, readGitFetchSettings, 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,10 +87,16 @@ 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';
92
95
  import { PromptCacheKeepWarmService } from '../prompt-cache-keepwarm.js';
96
+ import { fetchWorkspaceOnce } from '../git-fetch.js';
97
+ import { GitFetchScheduler } from '../git-fetch-scheduler.js';
98
+ import { probePathKind } from '../utils/bounded-path-probe.js';
99
+ import { defaultDockerWorkspaces } from './routes/git-status-routes.js';
93
100
  import { composerIsEmpty, plainFrame } from '../session-submit-verifier.js';
94
101
  import { detectUsageLimitPause } from '../usage-limit-patterns.js';
95
102
  import { stopDeepSeekWeb } from '../deepseek-web-server.js';
@@ -117,6 +124,7 @@ import { MAX_PASTE_IMAGE_BYTES } from '../config/buffer-limits.js';
117
124
  import { resolveTerminalHistoryConfig } from '../config/terminal-history.js';
118
125
  import { SseEvent } from './sse-events.js';
119
126
  import { MAX_ROUTE_PARAM_LENGTH } from './route-helpers.js';
127
+ import { mutateSettingsFile } from './settings-writer.js';
120
128
  import { getLatestPlanUsage, setLatestCodexPlanUsage, setLatestCopilotPlanUsage } from './plan-usage-latest.js';
121
129
  import { telemetrySignature } from '../usage-telemetry.js';
122
130
  import { readCodexPlanUsage, resolveCodexBinaryPath } from '../utils/codex-cli-resolver.js';
@@ -127,9 +135,10 @@ import { isMultiUserMode } from '../config/multiuser.js';
127
135
  import { bootstrapInitialAdmin, hasUsers, resolveClaudeModeForUsername } from '../user-store.js';
128
136
  import { installRouteErrorHandler } from './route-error-handler.js';
129
137
  import { isExplicitlyEnabled, isLoopbackBindHost, buildHostPolicy } from './network-auth-policy.js';
130
- 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, registerConfigBackupRoutes, runConfigBackup, registerWebhookRoutes, registerDoctorRoutes, registerCustomModelRoutes, refreshAllCustomModelHosts, readCustomModelEndpointsEnabled, closeAllLlamaSwapLogTails, detectCustomModelSwapDisplacements, pruneIdleLlamaSwapLogTails, tryWebviewRefererFallback, registerCliRegistryRoutes, } from './routes/index.js';
138
+ import { registerPushRoutes, registerTeamRoutes, registerMuxRoutes, registerFileRoutes, registerHtmlViewRoutes, registerScheduledRoutes, registerHookEventRoutes, registerApprovalRoutes, registerInboxRoutes, registerRebootRestoreRoutes, registerReadMyMindRoutes, registerGitStatusRoutes, registerStatusTelemetryRoutes, registerSystemRoutes, registerCaseRoutes, registerWorktreeRoutes, registerSessionRoutes, registerAgentSessionRoutes, 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';
131
139
  import { isLostWebviewFrameNavigation } from './webview-proxy.js';
132
140
  import { CronService } from '../cron/cron-service.js';
141
+ import { caseSessionSettled } from '../case-rename.js';
133
142
  const __dirname = dirname(fileURLToPath(import.meta.url));
134
143
  // Bounded, predictable shape for SSE client identifiers: alphanumerics, `_`, `-`.
135
144
  // Length range covers crypto.randomUUID() (36 chars) plus any short stable IDs,
@@ -164,7 +173,7 @@ function escapeHtmlText(value) {
164
173
  export function escapeScriptJson(json) {
165
174
  return json.replace(/</g, '\\u003c');
166
175
  }
167
- import { SESSIONS_LIST_CACHE_TTL, SCHEDULED_CLEANUP_INTERVAL, SCHEDULED_RUN_MAX_AGE, SSE_HEARTBEAT_INTERVAL, SESSION_LIMIT_WAIT_MS, ITERATION_PAUSE_MS, STATS_COLLECTION_INTERVAL_MS, INACTIVITY_TIMEOUT_MS, CRON_TICK_INTERVAL, } from '../config/server-timing.js';
176
+ import { SESSIONS_LIST_CACHE_TTL, SCHEDULED_CLEANUP_INTERVAL, SCHEDULED_RUN_MAX_AGE, SSE_HEARTBEAT_INTERVAL, SESSION_LIMIT_WAIT_MS, ITERATION_PAUSE_MS, STATS_COLLECTION_INTERVAL_MS, INACTIVITY_TIMEOUT_MS, CRON_TICK_INTERVAL, GIT_FETCH_TICK_INTERVAL_MS, } from '../config/server-timing.js';
168
177
  /**
169
178
  * Get or generate a self-signed TLS certificate for HTTPS.
170
179
  * Certs are stored in ~/.codeman/certs/ and reused across restarts.
@@ -250,6 +259,38 @@ export class WebServer extends EventEmitter {
250
259
  },
251
260
  log: (message) => console.log(message),
252
261
  });
262
+ // Git fetch on an interval (git-fetch-scheduler.ts): the due-tracking lives in the service; the server
263
+ // lends it the facts a decision needs and stores what it reports on the session. A field initializer for
264
+ // the same reason as the keep-warm one above, and the scheduler reads the app setting FRESH on every tick:
265
+ // that is what makes a live toggle take effect on the next tick, and what makes turning it off actually
266
+ // stop the fetching. Default OFF, so an install that never enabled it spawns nothing at all.
267
+ gitFetchScheduler = new GitFetchScheduler({
268
+ sessionIds: () => this.sessions.keys(),
269
+ getSession: (id) => this.sessions.get(id),
270
+ readSettings: readGitFetchSettings,
271
+ // The same Docker list the panel's Fetch button uses: the rule has to be one rule. It rejects on a list
272
+ // that cannot be read, and the tick then fetches nothing (fail closed).
273
+ dockerWorkspaces: defaultDockerWorkspaces,
274
+ // Bounded probe, DEFAULT options (rule #516): a tick is a bulk scan, so it keeps the stall cap and must
275
+ // never be handed `pastCap`. A folder on a mount that stopped answering is skipped, not fetched.
276
+ probePath: (path) => probePathKind(path),
277
+ // Through the join, so the tick and the panel's Fetch button are the same run: a click on a folder the
278
+ // tick is already fetching joins it rather than starting a second `git fetch` in the same clone, and the
279
+ // run that gets published is the one that ran, with its own trigger.
280
+ fetch: (cwd, opts, trigger) => fetchWorkspaceOnce(cwd, opts, trigger),
281
+ publish: (id, status) => {
282
+ const session = this.sessions.get(id);
283
+ if (!session?.setGitFetchStatus(status))
284
+ return;
285
+ // No new SSE event: the status rides the session state the page already listens for, and the Git
286
+ // panel polls on its own clock. It is written to disk with the rest of the record, but the restore
287
+ // copies only the fields its own config lists and this is not one of them (src/types/session.ts), so
288
+ // what a run found is the panel's truth for the life of the session, not across a restart.
289
+ this.persistSessionState(session);
290
+ this.broadcast(SseEvent.SessionUpdated, this.getSessionStateWithRespawn(session));
291
+ },
292
+ log: (message) => console.log(message),
293
+ });
253
294
  /**
254
295
  * Wake-on-LAN registry, returned by `registerSessionRoutes`. Held for its LIFETIME
255
296
  * only — `drop()` on session cleanup, `stop()` on shutdown. Waking from here would
@@ -305,6 +346,7 @@ export class WebServer extends EventEmitter {
305
346
  qrAuthFailures = null;
306
347
  hookSecretFailures = null;
307
348
  userFailures = null;
349
+ userIpFailures = null;
308
350
  pushStore = new PushSubscriptionStore();
309
351
  /** ntfy / Slack / Discord / generic webhook for the push events; config in webhook.json (0600). */
310
352
  webhookNotifier = new WebhookNotifier(() => readWebhookConfig(getDataDir()), webviewFetch);
@@ -314,12 +356,14 @@ export class WebServer extends EventEmitter {
314
356
  windowTitle;
315
357
  indexHtmlTemplate;
316
358
  allowUnauthenticatedNetwork;
359
+ /** `--allow-unauthenticated-tunnel`; the tunnel guard reads it through the route context (#706). */
360
+ allowUnauthenticatedTunnel;
317
361
  _pasteImageGcStop = null;
318
362
  _eventLoopMonitor = null;
319
363
  /** Opt-in hooks-only listener on the docker bridge gateway (CODEMAN_DOCKER_BRIDGE_HOOKS). */
320
364
  _dockerBridgeServer = null;
321
365
  teamWatcherHandlers = null;
322
- constructor(port = 3000, https = false, testMode = false, host = '127.0.0.1', titleHostname, allowUnauthenticatedNetwork = false, basePath = '') {
366
+ constructor(port = 3000, https = false, testMode = false, host = '127.0.0.1', titleHostname, allowUnauthenticatedNetwork = false, basePath = '', allowUnauthenticatedTunnel = false) {
323
367
  super();
324
368
  this.setMaxListeners(0);
325
369
  this.host = host;
@@ -330,6 +374,7 @@ export class WebServer extends EventEmitter {
330
374
  this.testMode = testMode;
331
375
  this.allowUnauthenticatedNetwork =
332
376
  allowUnauthenticatedNetwork || isExplicitlyEnabled(process.env.CODEMAN_ALLOW_UNAUTHENTICATED_NETWORK);
377
+ this.allowUnauthenticatedTunnel = allowUnauthenticatedTunnel;
333
378
  this.titleHostname = titleHostname || getHostname();
334
379
  this.windowTitle = `codeman:${this.titleHostname}`;
335
380
  this.indexHtmlTemplate = readFileSync(join(__dirname, 'public', 'index.html'), 'utf-8');
@@ -614,6 +659,8 @@ export class WebServer extends EventEmitter {
614
659
  async registerSessionWithLayout(session, placement) {
615
660
  return this.tabLayouts.sessionCreated(ownerLayoutKey(session.owner), session.id, placement, () => {
616
661
  this.sessions.set(session.id, session);
662
+ // In the map now, so a case rename lists (and on request stops) it instead of refusing the move.
663
+ caseSessionSettled(session);
617
664
  return () => {
618
665
  if (this.sessions.get(session.id) === session)
619
666
  this.sessions.delete(session.id);
@@ -685,6 +732,7 @@ export class WebServer extends EventEmitter {
685
732
  scheduledRuns: this.scheduledRuns,
686
733
  teamWatcher: this.teamWatcher,
687
734
  tunnelManager: this.tunnelManager,
735
+ allowUnauthenticatedTunnel: this.allowUnauthenticatedTunnel,
688
736
  pushStore: this.pushStore,
689
737
  startScheduledRun: this.startScheduledRun.bind(this),
690
738
  stopScheduledRun: this.stopScheduledRun.bind(this),
@@ -767,6 +815,7 @@ export class WebServer extends EventEmitter {
767
815
  this.qrAuthFailures = authState.qrAuthFailures;
768
816
  this.hookSecretFailures = authState.hookSecretFailures;
769
817
  this.userFailures = authState.userFailures;
818
+ this.userIpFailures = authState.userIpFailures;
770
819
  }
771
820
  // WebSocket support (terminal I/O — low-latency bidirectional channel)
772
821
  await this.app.register(fastifyWebsocket);
@@ -1030,9 +1079,21 @@ export class WebServer extends EventEmitter {
1030
1079
  registerScheduledRoutes(this.app, ctx);
1031
1080
  registerHookEventRoutes(this.app, ctx);
1032
1081
  registerApprovalRoutes(this.app, ctx);
1082
+ registerInboxRoutes(this.app, ctx);
1033
1083
  registerRebootRestoreRoutes(this.app, ctx);
1034
1084
  registerReadMyMindRoutes(this.app, ctx);
1035
1085
  registerGitStatusRoutes(this.app, ctx);
1086
+ // Git fetch on an interval: this is only the wake-up, the scheduler itself decides what is due. Off in
1087
+ // tests for the same reason as the polls below: a test drives `tick()` directly instead of waiting on
1088
+ // a timer the harness would have to keep alive. The setting is read FRESH inside `tick()`, so turning
1089
+ // it on takes effect on the next tick and turning it off actually stops the fetching.
1090
+ if (!this.testMode) {
1091
+ this.cleanup.setInterval(() => {
1092
+ this.gitFetchScheduler.tick().catch((err) => {
1093
+ console.error('[git fetch] interval tick failed:', getErrorMessage(err));
1094
+ });
1095
+ }, GIT_FETCH_TICK_INTERVAL_MS, { description: 'git fetch interval checker' });
1096
+ }
1036
1097
  registerStatusTelemetryRoutes(this.app, ctx);
1037
1098
  registerSystemRoutes(this.app, ctx);
1038
1099
  registerCaseRoutes(this.app, ctx);
@@ -1040,6 +1101,7 @@ export class WebServer extends EventEmitter {
1040
1101
  // The registry's lifetime is the server's: it drops per-session wake state on every
1041
1102
  // cleanup path and resolves in-flight wakes on shutdown.
1042
1103
  this.remoteWake = registerSessionRoutes(this.app, ctx);
1104
+ registerAgentSessionRoutes(this.app, ctx);
1043
1105
  registerRespawnRoutes(this.app, ctx);
1044
1106
  registerRalphRoutes(this.app, ctx);
1045
1107
  registerPlanRoutes(this.app, ctx);
@@ -1263,6 +1325,9 @@ export class WebServer extends EventEmitter {
1263
1325
  async _doCleanupSession(sessionId, killMux, reason, coordinateLayout = true) {
1264
1326
  const session = this.sessions.get(sessionId);
1265
1327
  this.keepWarmService.remove(sessionId);
1328
+ // A closed session takes its place in the fetch schedule with it: otherwise a detached-and-forgotten id
1329
+ // would still count as tracked, and its next run would land on a session that no longer exists.
1330
+ this.gitFetchScheduler.remove(sessionId);
1266
1331
  const pinned = session?.pinned === true || this.store.getSession(sessionId)?.pinned === true;
1267
1332
  if (coordinateLayout && session && killMux && !pinned) {
1268
1333
  return this.tabLayouts.runSessionDeletion([{ id: sessionId, owner: session.owner }], () => this._doCleanupSession(sessionId, killMux, reason, false));
@@ -1438,6 +1503,12 @@ export class WebServer extends EventEmitter {
1438
1503
  // The 'exit' here is NOT redundant with the PTY-exit listener: listeners are
1439
1504
  // detached a few lines above, before `session.stop()`, so on a delete the
1440
1505
  // session's own exit event never reaches the registry.
1506
+ // The agent inbox goes with a DELETED session only. killMux=false is shutdown or
1507
+ // detach: mail posted before that is still there when the session is re-attached
1508
+ // (while detached, the routes answer 404, so nothing new arrives). Before
1509
+ // cancelAll, so a reader released by it reads an empty inbox.
1510
+ if (killMux)
1511
+ agentInbox.drop(sessionId);
1441
1512
  sessionWaits.notifySignal(sessionId, 'exit');
1442
1513
  sessionWaits.cancelAll(sessionId);
1443
1514
  approvalInbox.resolveForSession(sessionId, 'session_ended');
@@ -1630,6 +1701,13 @@ export class WebServer extends EventEmitter {
1630
1701
  .filter((entry) => entry.capabilities.transcriptPageKeys === true)
1631
1702
  .map((entry) => entry.id);
1632
1703
  html = html.replace('</head>', () => `<script>window.__codemanTranscriptPageKeys=${JSON.stringify(pageKeyClis)};</script>\n</head>`);
1704
+ // Which run modes keep their PTY rows under a phone keyboard (inline renderers,
1705
+ // the `keyboardHold` capability; `_keyboardHoldEligible` in terminal-ui.js). Same
1706
+ // shape and reasoning as the page-key list above.
1707
+ const keyboardHoldClis = enabledClis()
1708
+ .filter((entry) => entry.capabilities.keyboardHold === 'inline')
1709
+ .map((entry) => entry.id);
1710
+ html = html.replace('</head>', () => `<script>window.__codemanKeyboardHoldModes=${JSON.stringify(keyboardHoldClis)};</script>\n</head>`);
1633
1711
  if (!soloSessionId && process.env.CODEMAN_GESTURE === '1') {
1634
1712
  html = html.replace('</head>', () => `<script>window.__codemanGestureAvailable=true;</script>\n</head>`);
1635
1713
  if (settings.gestureControlEnabled === true) {
@@ -1913,6 +1991,12 @@ export class WebServer extends EventEmitter {
1913
1991
  const settings = await this.readSettings();
1914
1992
  return settings.agentSkillEnabled === true;
1915
1993
  }
1994
+ // Whether pending agent-inbox mail is snapshotted to disk (synced
1995
+ // `agentInboxPersistEnabled` setting). Default OFF: an absent key writes nothing.
1996
+ async getAgentInboxPersistEnabled() {
1997
+ const settings = await this.readSettings();
1998
+ return settings.agentInboxPersistEnabled === true;
1999
+ }
1916
2000
  // Whether a Claude session installs Codeman's hooks block into its workspace
1917
2001
  // (synced `workspaceHooksEnabled` setting). Default ON — an absent key means a
1918
2002
  // user who has never seen this setting, and OFF for them would mean no tab
@@ -2674,6 +2758,11 @@ export class WebServer extends EventEmitter {
2674
2758
  // This prevents race conditions where clients connect before state is ready
2675
2759
  // CRITICAL: Skip in test mode to prevent tests from picking up user sessions
2676
2760
  if (!this.testMode) {
2761
+ // Pending agent mail first (opt-in snapshot), so it is in place before any
2762
+ // route can answer. Pruned against the restored sessions in finalizeRestoredState.
2763
+ const restoredMail = agentInboxPersistence.load(await this.getAgentInboxPersistEnabled());
2764
+ if (restoredMail > 0)
2765
+ console.log(`[AgentInbox] restored ${restoredMail} pending message(s)`);
2677
2766
  const restored = await this.restoreMuxSessions();
2678
2767
  await this.finalizeRestoredState(restored);
2679
2768
  // Instance-scoped reaper: after restore, `docker rm -f` managed containers of
@@ -2891,17 +2980,13 @@ export class WebServer extends EventEmitter {
2891
2980
  // Tunnel only starts when user clicks the toggle in the UI — never on boot.
2892
2981
  // Reset persisted tunnelEnabled so the UI toggle reflects actual state.
2893
2982
  if (await this.isTunnelEnabled()) {
2894
- const settingsPath = dataPath('settings.json');
2895
2983
  try {
2896
- const content = await fs.readFile(settingsPath, 'utf-8');
2897
- const settings = JSON.parse(content);
2898
- settings.tunnelEnabled = false;
2899
- await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2));
2984
+ await mutateSettingsFile((settings) => ({ ...settings, tunnelEnabled: false }));
2985
+ console.log('Cloudflare tunnel setting reset (tunnel only starts on explicit UI toggle)');
2900
2986
  }
2901
- catch {
2902
- /* ignore */
2987
+ catch (err) {
2988
+ console.warn('Could not reset the Cloudflare tunnel setting:', getErrorMessage(err));
2903
2989
  }
2904
- console.log('Cloudflare tunnel setting reset (tunnel only starts on explicit UI toggle)');
2905
2990
  }
2906
2991
  // Start team watcher for agent team awareness (always on — lightweight polling)
2907
2992
  this.teamWatcher.start();
@@ -3076,6 +3161,9 @@ export class WebServer extends EventEmitter {
3076
3161
  if (saved.autoClearEnabled !== undefined || saved.autoClearThreshold !== undefined) {
3077
3162
  session.setAutoClear(saved.autoClearEnabled ?? false, saved.autoClearThreshold);
3078
3163
  }
3164
+ if (saved.gitFetchEnabled !== undefined || saved.gitFetchIntervalSeconds !== undefined) {
3165
+ session.setGitFetchSettings(saved.gitFetchEnabled, saved.gitFetchIntervalSeconds);
3166
+ }
3079
3167
  if (saved.autoResumeEnabled) {
3080
3168
  // The stamp is re-armed by default, because a Codeman restart leaves the
3081
3169
  // limit footer un-reprinted and dropping it there would strand the pause.
@@ -3292,6 +3380,11 @@ export class WebServer extends EventEmitter {
3292
3380
  // below, a future `expiresAt` re-arms the timer once the pane is attached.
3293
3381
  lastRealPromptAt: savedState?.lastRealPromptAt,
3294
3382
  keepWarmEnabled: savedState?.keepWarmEnabled,
3383
+ // The session's own fetch switch and interval, carried over rather than reset to the app default:
3384
+ // the choice is the user's and outlives the run that made it. The scheduler reads them fresh
3385
+ // on its next tick, so a restored session is back on its own schedule without a restart.
3386
+ gitFetchEnabled: savedState?.gitFetchEnabled,
3387
+ gitFetchIntervalSeconds: savedState?.gitFetchIntervalSeconds,
3295
3388
  // Conversations this pane provably owned, oldest first. Its tail is
3296
3389
  // the conversation the CLI was on when the server stopped, which is
3297
3390
  // what a re-attach must point the viewer at instead of the launch id.
@@ -3466,6 +3559,7 @@ export class WebServer extends EventEmitter {
3466
3559
  }
3467
3560
  }
3468
3561
  this.sessions.set(session.id, session);
3562
+ caseSessionSettled(session);
3469
3563
  await this.setupSessionListeners(session);
3470
3564
  // Auto-attach PTY to the surviving tmux session immediately.
3471
3565
  // This ensures ALL sessions resume capturing output right away,
@@ -3538,6 +3632,21 @@ export class WebServer extends EventEmitter {
3538
3632
  this.tabLayouts.markRestorationComplete();
3539
3633
  await this.cleanupStaleSessions();
3540
3634
  await this.tabLayouts.reconcileAfterRestoration();
3635
+ // Same gate as the tab layouts: only a restore that succeeded may decide which
3636
+ // inboxes are orphans. A failed one returns above and leaves every message alone.
3637
+ // Kept: live sessions plus every record state.json still holds (a session that
3638
+ // was not re-adopted this boot but is kept for recovery keeps its mail), plus the
3639
+ // sessions on the reboot-restore offer. `cleanupStaleSessions()` above has already
3640
+ // pruned their records, and a rebuild reuses the old id, so without them a restored
3641
+ // session would come back to an empty inbox. The 12 h TTL bounds what is never rebuilt.
3642
+ const keep = new Set([
3643
+ ...this.sessions.keys(),
3644
+ ...Object.keys(this.store.getSessions()),
3645
+ ...rebootRestoreRegistry.list(() => true).map((entry) => entry.sessionId),
3646
+ ]);
3647
+ const pruned = agentInbox.pruneExcept(keep);
3648
+ if (pruned > 0)
3649
+ console.log(`[AgentInbox] dropped ${pruned} inbox(es) of sessions that did not come back`);
3541
3650
  }
3542
3651
  /**
3543
3652
  * Install Codeman's hooks into the workspaces of the sessions just recovered.
@@ -3692,6 +3801,8 @@ export class WebServer extends EventEmitter {
3692
3801
  // port against the next start — the exact EADDRINUSE this feature already
3693
3802
  // got wrong once.
3694
3803
  void stopDeepSeekWeb();
3804
+ // The header's GPU / CPU clock sampler is a Windows child process under WSL, and its idle sweep a timer.
3805
+ stopHostSensors();
3695
3806
  // Same teardown rule: the per-endpoint llama-swap log tails are otherwise closed
3696
3807
  // only by the periodic idle sweep, whose interval is disposed just below.
3697
3808
  closeAllLlamaSwapLogTails();
@@ -3705,8 +3816,14 @@ export class WebServer extends EventEmitter {
3705
3816
  sessionWaits.cancelEverything();
3706
3817
  approvalInbox.stop();
3707
3818
  this.keepWarmService.stop();
3819
+ // Stops the fetch sweep from starting another run while the server is closing; a git process already
3820
+ // running is left to finish, which is what a fetch does to a repository nothing else is touching.
3821
+ this.gitFetchScheduler.stop();
3822
+ // A post within the debounce window before shutdown would otherwise not reach disk.
3823
+ // Flushed again after `app.close()` below, for a post that lands in between.
3824
+ agentInboxPersistence.flush();
3708
3825
  // Same reason as `cancelEverything` above: an in-flight wake is awaited by a request,
3709
- // and `app.close()` (the last line of this method) does not abort in-flight requests —
3826
+ // and `app.close()` (at the end of this method) does not abort in-flight requests,
3710
3827
  // so without this a restart during a wake waits out the readiness poll.
3711
3828
  this.remoteWake?.stop();
3712
3829
  this.lastRecordedTokens.clear();
@@ -3821,16 +3938,27 @@ export class WebServer extends EventEmitter {
3821
3938
  this.userFailures.dispose();
3822
3939
  this.userFailures = null;
3823
3940
  }
3941
+ if (this.userIpFailures) {
3942
+ this.userIpFailures.dispose();
3943
+ this.userIpFailures = null;
3944
+ }
3824
3945
  this.activePlanOrchestrators.clear();
3825
3946
  this.cleaningUp.clear();
3826
3947
  this.killingSessions.clear();
3827
3948
  // Dispose push store (flush pending saves)
3828
3949
  this.pushStore.dispose();
3829
- await this.app.close();
3950
+ try {
3951
+ await this.app.close();
3952
+ }
3953
+ finally {
3954
+ // The routes answer until `app.close()` returns, so a post after the flush above
3955
+ // would otherwise sit in an unref'd debounce timer and never reach disk.
3956
+ agentInboxPersistence.flush();
3957
+ }
3830
3958
  }
3831
3959
  }
3832
- export async function startWebServer(port = 3000, https = false, testMode = false, host = '127.0.0.1', titleHostname, allowUnauthenticatedNetwork = false, basePath = '') {
3833
- const server = new WebServer(port, https, testMode, host, titleHostname, allowUnauthenticatedNetwork, basePath);
3960
+ export async function startWebServer(port = 3000, https = false, testMode = false, host = '127.0.0.1', titleHostname, allowUnauthenticatedNetwork = false, basePath = '', allowUnauthenticatedTunnel = false) {
3961
+ const server = new WebServer(port, https, testMode, host, titleHostname, allowUnauthenticatedNetwork, basePath, allowUnauthenticatedTunnel);
3834
3962
  await server.start();
3835
3963
  return server;
3836
3964
  }