aicodeman 1.42.0 → 1.44.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 (350) hide show
  1. package/README.md +13 -12
  2. package/dist/claude-session-title.d.ts +6 -4
  3. package/dist/claude-session-title.d.ts.map +1 -1
  4. package/dist/claude-session-title.js +6 -4
  5. package/dist/claude-session-title.js.map +1 -1
  6. package/dist/cli-agent.d.ts +3 -3
  7. package/dist/cli-agent.d.ts.map +1 -1
  8. package/dist/cli-agent.js +8 -6
  9. package/dist/cli-agent.js.map +1 -1
  10. package/dist/config/cli-registry/patterns.d.ts +1 -1
  11. package/dist/config/cli-registry/patterns.d.ts.map +1 -1
  12. package/dist/config/cli-registry/patterns.js +3 -0
  13. package/dist/config/cli-registry/patterns.js.map +1 -1
  14. package/dist/config/cli-registry/schema.d.ts +1 -0
  15. package/dist/config/cli-registry/schema.d.ts.map +1 -1
  16. package/dist/config/cli-registry/schema.js +5 -3
  17. package/dist/config/cli-registry/schema.js.map +1 -1
  18. package/dist/config/cli-registry/stock.d.ts.map +1 -1
  19. package/dist/config/cli-registry/stock.js +263 -4
  20. package/dist/config/cli-registry/stock.js.map +1 -1
  21. package/dist/config/cli-registry/types.d.ts +13 -5
  22. package/dist/config/cli-registry/types.d.ts.map +1 -1
  23. package/dist/config/map-limits.d.ts +13 -1
  24. package/dist/config/map-limits.d.ts.map +1 -1
  25. package/dist/config/map-limits.js +28 -1
  26. package/dist/config/map-limits.js.map +1 -1
  27. package/dist/config-backup.d.ts +279 -0
  28. package/dist/config-backup.d.ts.map +1 -0
  29. package/dist/config-backup.js +794 -0
  30. package/dist/config-backup.js.map +1 -0
  31. package/dist/cron/cron-service.d.ts +5 -5
  32. package/dist/cron/cron-service.js +6 -6
  33. package/dist/cron/cron-service.js.map +1 -1
  34. package/dist/custom-model-hosts.d.ts +16 -0
  35. package/dist/custom-model-hosts.d.ts.map +1 -1
  36. package/dist/custom-model-hosts.js.map +1 -1
  37. package/dist/hooks-config.d.ts +24 -0
  38. package/dist/hooks-config.d.ts.map +1 -1
  39. package/dist/hooks-config.js +47 -0
  40. package/dist/hooks-config.js.map +1 -1
  41. package/dist/intent-store.d.ts +2 -0
  42. package/dist/intent-store.d.ts.map +1 -1
  43. package/dist/intent-store.js +9 -0
  44. package/dist/intent-store.js.map +1 -1
  45. package/dist/mcp-sync-targets.d.ts +6 -3
  46. package/dist/mcp-sync-targets.d.ts.map +1 -1
  47. package/dist/mcp-sync-targets.js +6 -3
  48. package/dist/mcp-sync-targets.js.map +1 -1
  49. package/dist/mux-interface.d.ts +28 -7
  50. package/dist/mux-interface.d.ts.map +1 -1
  51. package/dist/prompt-cache-keepwarm.d.ts +193 -0
  52. package/dist/prompt-cache-keepwarm.d.ts.map +1 -0
  53. package/dist/prompt-cache-keepwarm.js +359 -0
  54. package/dist/prompt-cache-keepwarm.js.map +1 -0
  55. package/dist/respawn-controller.d.ts.map +1 -1
  56. package/dist/respawn-controller.js +6 -5
  57. package/dist/respawn-controller.js.map +1 -1
  58. package/dist/session-auto-ops.d.ts.map +1 -1
  59. package/dist/session-auto-ops.js +6 -1
  60. package/dist/session-auto-ops.js.map +1 -1
  61. package/dist/session-cli-registry-bridge.d.ts +15 -1
  62. package/dist/session-cli-registry-bridge.d.ts.map +1 -1
  63. package/dist/session-cli-registry-bridge.js +20 -3
  64. package/dist/session-cli-registry-bridge.js.map +1 -1
  65. package/dist/session-submit-verifier.d.ts +19 -0
  66. package/dist/session-submit-verifier.d.ts.map +1 -1
  67. package/dist/session-submit-verifier.js +88 -0
  68. package/dist/session-submit-verifier.js.map +1 -1
  69. package/dist/session.d.ts +83 -12
  70. package/dist/session.d.ts.map +1 -1
  71. package/dist/session.js +150 -16
  72. package/dist/session.js.map +1 -1
  73. package/dist/state-store.d.ts +10 -0
  74. package/dist/state-store.d.ts.map +1 -1
  75. package/dist/state-store.js +35 -0
  76. package/dist/state-store.js.map +1 -1
  77. package/dist/tab-layout-service.d.ts +33 -0
  78. package/dist/tab-layout-service.d.ts.map +1 -1
  79. package/dist/tab-layout-service.js +75 -3
  80. package/dist/tab-layout-service.js.map +1 -1
  81. package/dist/tmux-manager.d.ts +52 -9
  82. package/dist/tmux-manager.d.ts.map +1 -1
  83. package/dist/tmux-manager.js +148 -30
  84. package/dist/tmux-manager.js.map +1 -1
  85. package/dist/tmux-prefix-table.d.ts +54 -0
  86. package/dist/tmux-prefix-table.d.ts.map +1 -0
  87. package/dist/tmux-prefix-table.js +137 -0
  88. package/dist/tmux-prefix-table.js.map +1 -0
  89. package/dist/tui/tui-app.d.ts +9 -1
  90. package/dist/tui/tui-app.d.ts.map +1 -1
  91. package/dist/tui/tui-app.js +21 -8
  92. package/dist/tui/tui-app.js.map +1 -1
  93. package/dist/tui/tui-client.d.ts +3 -2
  94. package/dist/tui/tui-client.d.ts.map +1 -1
  95. package/dist/tui/tui-client.js +3 -2
  96. package/dist/tui/tui-client.js.map +1 -1
  97. package/dist/types/app-state.d.ts +6 -0
  98. package/dist/types/app-state.d.ts.map +1 -1
  99. package/dist/types/app-state.js.map +1 -1
  100. package/dist/types/session.d.ts +72 -5
  101. package/dist/types/session.d.ts.map +1 -1
  102. package/dist/types/session.js +2 -1
  103. package/dist/types/session.js.map +1 -1
  104. package/dist/usage-telemetry.d.ts +15 -0
  105. package/dist/usage-telemetry.d.ts.map +1 -1
  106. package/dist/usage-telemetry.js +45 -2
  107. package/dist/usage-telemetry.js.map +1 -1
  108. package/dist/utils/cli-installed-probes.d.ts +1 -1
  109. package/dist/utils/cli-installed-probes.d.ts.map +1 -1
  110. package/dist/utils/cli-installed-probes.js +4 -2
  111. package/dist/utils/cli-installed-probes.js.map +1 -1
  112. package/dist/utils/copilot-cli-resolver.d.ts +48 -0
  113. package/dist/utils/copilot-cli-resolver.d.ts.map +1 -0
  114. package/dist/utils/copilot-cli-resolver.js +112 -0
  115. package/dist/utils/copilot-cli-resolver.js.map +1 -0
  116. package/dist/utils/copilot-plan-usage.d.ts +46 -0
  117. package/dist/utils/copilot-plan-usage.d.ts.map +1 -0
  118. package/dist/utils/copilot-plan-usage.js +95 -0
  119. package/dist/utils/copilot-plan-usage.js.map +1 -0
  120. package/dist/utils/index.d.ts +3 -0
  121. package/dist/utils/index.d.ts.map +1 -1
  122. package/dist/utils/index.js +3 -0
  123. package/dist/utils/index.js.map +1 -1
  124. package/dist/utils/single-line.d.ts +22 -0
  125. package/dist/utils/single-line.d.ts.map +1 -0
  126. package/dist/utils/single-line.js +30 -0
  127. package/dist/utils/single-line.js.map +1 -0
  128. package/dist/web/approval-inbox.d.ts +6 -0
  129. package/dist/web/approval-inbox.d.ts.map +1 -1
  130. package/dist/web/approval-inbox.js +2 -2
  131. package/dist/web/approval-inbox.js.map +1 -1
  132. package/dist/web/case-path.d.ts +4 -2
  133. package/dist/web/case-path.d.ts.map +1 -1
  134. package/dist/web/case-path.js +13 -4
  135. package/dist/web/case-path.js.map +1 -1
  136. package/dist/web/plan-usage-latest.d.ts +2 -1
  137. package/dist/web/plan-usage-latest.d.ts.map +1 -1
  138. package/dist/web/plan-usage-latest.js +12 -2
  139. package/dist/web/plan-usage-latest.js.map +1 -1
  140. package/dist/web/ports/index.d.ts +1 -0
  141. package/dist/web/ports/index.d.ts.map +1 -1
  142. package/dist/web/ports/keep-warm-port.d.ts +9 -0
  143. package/dist/web/ports/keep-warm-port.d.ts.map +1 -0
  144. package/dist/web/ports/keep-warm-port.js +6 -0
  145. package/dist/web/ports/keep-warm-port.js.map +1 -0
  146. package/dist/web/ports/session-port.d.ts +3 -1
  147. package/dist/web/ports/session-port.d.ts.map +1 -1
  148. package/dist/web/public/admin-ui.js.gz +0 -0
  149. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  150. package/dist/web/public/app.9314ee60.js +43 -0
  151. package/dist/web/public/app.9314ee60.js.br +0 -0
  152. package/dist/web/public/app.9314ee60.js.gz +0 -0
  153. package/dist/web/public/approvals-ui.js.gz +0 -0
  154. package/dist/web/public/{constants.12b0c247.js → constants.4866ecc3.js} +205 -3
  155. package/dist/web/public/constants.4866ecc3.js.br +0 -0
  156. package/dist/web/public/constants.4866ecc3.js.gz +0 -0
  157. package/dist/web/public/cron-ui.js.gz +0 -0
  158. package/dist/web/public/entrance-animations.js.gz +0 -0
  159. package/dist/web/public/git-status-ui.js.gz +0 -0
  160. package/dist/web/public/home-sessions.js.gz +0 -0
  161. package/dist/web/public/host-wake-ui.js.gz +0 -0
  162. package/dist/web/public/i18n.22ca902b.js +2 -0
  163. package/dist/web/public/i18n.22ca902b.js.br +0 -0
  164. package/dist/web/public/i18n.22ca902b.js.gz +0 -0
  165. package/dist/web/public/{image-input.fcdcb46c.js → image-input.941386e7.js} +24 -14
  166. package/dist/web/public/image-input.941386e7.js.br +0 -0
  167. package/dist/web/public/image-input.941386e7.js.gz +0 -0
  168. package/dist/web/public/index.html +110 -16
  169. package/dist/web/public/index.html.br +0 -0
  170. package/dist/web/public/index.html.gz +0 -0
  171. package/dist/web/public/input-cjk.8bc46081.js.gz +0 -0
  172. package/dist/web/public/{keyboard-accessory.b42f539b.js → keyboard-accessory.4e8b20ac.js} +51 -22
  173. package/dist/web/public/keyboard-accessory.4e8b20ac.js.br +0 -0
  174. package/dist/web/public/keyboard-accessory.4e8b20ac.js.gz +0 -0
  175. package/dist/web/public/{mobile-handlers.0ef9c311.js → mobile-handlers.44adaa07.js} +48 -16
  176. package/dist/web/public/mobile-handlers.44adaa07.js.br +0 -0
  177. package/dist/web/public/mobile-handlers.44adaa07.js.gz +0 -0
  178. package/dist/web/public/mobile-ime-preview.2dbaf262.js.gz +0 -0
  179. package/dist/web/public/mobile-overview.js +1 -0
  180. package/dist/web/public/mobile-overview.js.br +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.73ec0137.js → panels-ui.cbb24ba1.js} +66 -66
  188. package/dist/web/public/panels-ui.cbb24ba1.js.br +0 -0
  189. package/dist/web/public/panels-ui.cbb24ba1.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/security-notices.json +21 -0
  197. package/dist/web/public/session-lineage.js.gz +0 -0
  198. package/dist/web/public/{session-ui.d52ff6aa.js → session-ui.9c1f009b.js} +8 -4
  199. package/dist/web/public/session-ui.9c1f009b.js.br +0 -0
  200. package/dist/web/public/session-ui.9c1f009b.js.gz +0 -0
  201. package/dist/web/public/settings-ui.fea9ddae.js +106 -0
  202. package/dist/web/public/settings-ui.fea9ddae.js.br +0 -0
  203. package/dist/web/public/settings-ui.fea9ddae.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.e096fcd6.css +1 -0
  208. package/dist/web/public/styles.e096fcd6.css.br +0 -0
  209. package/dist/web/public/styles.e096fcd6.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.gz +0 -0
  216. package/dist/web/public/terminal-keycode229-recovery.af09f99a.js.gz +0 -0
  217. package/dist/web/public/terminal-split.js.gz +0 -0
  218. package/dist/web/public/terminal-tile.js +41 -22
  219. package/dist/web/public/terminal-tile.js.br +0 -0
  220. package/dist/web/public/terminal-tile.js.gz +0 -0
  221. package/dist/web/public/terminal-ui.68dbd352.js +2 -0
  222. package/dist/web/public/terminal-ui.68dbd352.js.br +0 -0
  223. package/dist/web/public/terminal-ui.68dbd352.js.gz +0 -0
  224. package/dist/web/public/tile-grid.js +3 -6
  225. package/dist/web/public/tile-grid.js.br +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.gz +0 -0
  238. package/dist/web/public/vendor/xterm-zerolag-input.0b69623c.js.gz +0 -0
  239. package/dist/web/public/vendor/xterm.css.gz +0 -0
  240. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  241. package/dist/web/public/voice-input.2342c37b.js.gz +0 -0
  242. package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
  243. package/dist/web/public/webview-tabs.js.gz +0 -0
  244. package/dist/web/public/whats-new.json +9 -9
  245. package/dist/web/response-viewer-transcript.d.ts +6 -0
  246. package/dist/web/response-viewer-transcript.d.ts.map +1 -1
  247. package/dist/web/response-viewer-transcript.js +134 -1
  248. package/dist/web/response-viewer-transcript.js.map +1 -1
  249. package/dist/web/routes/admin-routes.d.ts +2 -1
  250. package/dist/web/routes/admin-routes.d.ts.map +1 -1
  251. package/dist/web/routes/admin-routes.js +3 -0
  252. package/dist/web/routes/admin-routes.js.map +1 -1
  253. package/dist/web/routes/case-routes.d.ts +2 -2
  254. package/dist/web/routes/case-routes.d.ts.map +1 -1
  255. package/dist/web/routes/case-routes.js +81 -3
  256. package/dist/web/routes/case-routes.js.map +1 -1
  257. package/dist/web/routes/config-backup-routes.d.ts +54 -0
  258. package/dist/web/routes/config-backup-routes.d.ts.map +1 -0
  259. package/dist/web/routes/config-backup-routes.js +268 -0
  260. package/dist/web/routes/config-backup-routes.js.map +1 -0
  261. package/dist/web/routes/custom-model-routes.d.ts +17 -1
  262. package/dist/web/routes/custom-model-routes.d.ts.map +1 -1
  263. package/dist/web/routes/custom-model-routes.js +118 -13
  264. package/dist/web/routes/custom-model-routes.js.map +1 -1
  265. package/dist/web/routes/hook-event-routes.d.ts.map +1 -1
  266. package/dist/web/routes/hook-event-routes.js +26 -5
  267. package/dist/web/routes/hook-event-routes.js.map +1 -1
  268. package/dist/web/routes/index.d.ts +1 -0
  269. package/dist/web/routes/index.d.ts.map +1 -1
  270. package/dist/web/routes/index.js +1 -0
  271. package/dist/web/routes/index.js.map +1 -1
  272. package/dist/web/routes/mcp-sync-routes.d.ts +4 -4
  273. package/dist/web/routes/mcp-sync-routes.js +4 -4
  274. package/dist/web/routes/ralph-routes.js +2 -2
  275. package/dist/web/routes/ralph-routes.js.map +1 -1
  276. package/dist/web/routes/reboot-restore-routes.d.ts.map +1 -1
  277. package/dist/web/routes/reboot-restore-routes.js +2 -0
  278. package/dist/web/routes/reboot-restore-routes.js.map +1 -1
  279. package/dist/web/routes/session-routes.d.ts +14 -4
  280. package/dist/web/routes/session-routes.d.ts.map +1 -1
  281. package/dist/web/routes/session-routes.js +131 -30
  282. package/dist/web/routes/session-routes.js.map +1 -1
  283. package/dist/web/routes/status-telemetry-routes.d.ts +2 -2
  284. package/dist/web/routes/status-telemetry-routes.d.ts.map +1 -1
  285. package/dist/web/routes/status-telemetry-routes.js +8 -4
  286. package/dist/web/routes/status-telemetry-routes.js.map +1 -1
  287. package/dist/web/routes/system-routes.d.ts +2 -2
  288. package/dist/web/routes/system-routes.d.ts.map +1 -1
  289. package/dist/web/routes/system-routes.js +18 -0
  290. package/dist/web/routes/system-routes.js.map +1 -1
  291. package/dist/web/routes/webview-routes.d.ts.map +1 -1
  292. package/dist/web/routes/webview-routes.js +1 -0
  293. package/dist/web/routes/webview-routes.js.map +1 -1
  294. package/dist/web/schemas.d.ts +32 -0
  295. package/dist/web/schemas.d.ts.map +1 -1
  296. package/dist/web/schemas.js +64 -4
  297. package/dist/web/schemas.js.map +1 -1
  298. package/dist/web/server.d.ts +12 -2
  299. package/dist/web/server.d.ts.map +1 -1
  300. package/dist/web/server.js +127 -6
  301. package/dist/web/server.js.map +1 -1
  302. package/dist/web/session-listener-wiring.d.ts +6 -0
  303. package/dist/web/session-listener-wiring.d.ts.map +1 -1
  304. package/dist/web/session-listener-wiring.js +10 -3
  305. package/dist/web/session-listener-wiring.js.map +1 -1
  306. package/dist/web/sse-events.d.ts +5 -2
  307. package/dist/web/sse-events.d.ts.map +1 -1
  308. package/dist/web/sse-events.js +5 -2
  309. package/dist/web/sse-events.js.map +1 -1
  310. package/dist/web/webview-proxy.d.ts +8 -3
  311. package/dist/web/webview-proxy.d.ts.map +1 -1
  312. package/dist/web/webview-proxy.js +23 -5
  313. package/dist/web/webview-proxy.js.map +1 -1
  314. package/package.json +1 -1
  315. package/skills/codeman/SKILL.md +23 -11
  316. package/skills/codeman/preamble.sh +12 -2
  317. package/skills/codeman/reference/endpoints.md +16 -15
  318. package/skills/codeman/reference/messaging.md +5 -5
  319. package/skills/codeman/reference/recipes.md +2 -2
  320. package/skills/codeman/reference/verbs.md +25 -17
  321. package/dist/web/public/app.f04bb2a5.js +0 -43
  322. package/dist/web/public/app.f04bb2a5.js.br +0 -0
  323. package/dist/web/public/app.f04bb2a5.js.gz +0 -0
  324. package/dist/web/public/constants.12b0c247.js.br +0 -0
  325. package/dist/web/public/constants.12b0c247.js.gz +0 -0
  326. package/dist/web/public/i18n.06659676.js +0 -2
  327. package/dist/web/public/i18n.06659676.js.br +0 -0
  328. package/dist/web/public/i18n.06659676.js.gz +0 -0
  329. package/dist/web/public/image-input.fcdcb46c.js.br +0 -0
  330. package/dist/web/public/image-input.fcdcb46c.js.gz +0 -0
  331. package/dist/web/public/keyboard-accessory.b42f539b.js.br +0 -0
  332. package/dist/web/public/keyboard-accessory.b42f539b.js.gz +0 -0
  333. package/dist/web/public/mobile-handlers.0ef9c311.js.br +0 -0
  334. package/dist/web/public/mobile-handlers.0ef9c311.js.gz +0 -0
  335. package/dist/web/public/mobile.2dff1580.css +0 -1
  336. package/dist/web/public/mobile.2dff1580.css.br +0 -0
  337. package/dist/web/public/mobile.2dff1580.css.gz +0 -0
  338. package/dist/web/public/panels-ui.73ec0137.js.br +0 -0
  339. package/dist/web/public/panels-ui.73ec0137.js.gz +0 -0
  340. package/dist/web/public/session-ui.d52ff6aa.js.br +0 -0
  341. package/dist/web/public/session-ui.d52ff6aa.js.gz +0 -0
  342. package/dist/web/public/settings-ui.3a22bd2a.js +0 -97
  343. package/dist/web/public/settings-ui.3a22bd2a.js.br +0 -0
  344. package/dist/web/public/settings-ui.3a22bd2a.js.gz +0 -0
  345. package/dist/web/public/styles.ba6a9b84.css +0 -1
  346. package/dist/web/public/styles.ba6a9b84.css.br +0 -0
  347. package/dist/web/public/styles.ba6a9b84.css.gz +0 -0
  348. package/dist/web/public/terminal-ui.0a7ccc29.js +0 -2
  349. package/dist/web/public/terminal-ui.0a7ccc29.js.br +0 -0
  350. package/dist/web/public/terminal-ui.0a7ccc29.js.gz +0 -0
@@ -3,7 +3,7 @@
3
3
  * Covers session CRUD, input/output, terminal buffer, quick-start, quick-run,
4
4
  * auto-clear, auto-compact, image watcher, flicker filter, and logout.
5
5
  */
6
- import { join, dirname, extname, basename } from 'node:path';
6
+ import { join, dirname, extname, basename, resolve as resolvePath } from 'node:path';
7
7
  import { homedir } from 'node:os';
8
8
  import { existsSync, statSync, mkdirSync, writeFileSync } from 'node:fs';
9
9
  import { execFile } from 'node:child_process';
@@ -17,7 +17,7 @@ import { Session, cliTakesSessionModel, isAltScreenStripMode, isExternalCliMode,
17
17
  import { SseEvent } from '../sse-events.js';
18
18
  import { webviewCapabilities } from '../../webview-capabilities.js';
19
19
  import { htmlViewCapabilities } from '../../html-view-capabilities.js';
20
- import { CreateSessionSchema, SessionNameSchema, SessionColorSchema, RunPromptSchema, SessionInputWithLimitSchema, ResizeSchema, AutoClearSchema, AutoCompactSchema, AutoResumeSchema, PinSessionSchema, ImageWatcherSchema, FlickerFilterSchema, QuickRunSchema, QuickStartSchema, InteractiveStartSchema, SessionOrderUpdateSchema, SessionWaitQuerySchema, SessionWaitOutputQuerySchema, CustomModelSelectionSchema, } from '../schemas.js';
20
+ import { CreateSessionSchema, SessionNameSchema, SessionColorSchema, RunPromptSchema, SessionInputWithLimitSchema, ResizeSchema, AutoClearSchema, AutoCompactSchema, AutoResumeSchema, KeepWarmSchema, PinSessionSchema, ImageWatcherSchema, FlickerFilterSchema, QuickRunSchema, QuickStartSchema, InteractiveStartSchema, SessionOrderUpdateSchema, SessionWaitQuerySchema, SessionWaitOutputQuerySchema, CustomModelSelectionSchema, } from '../schemas.js';
21
21
  import { readCustomModelHosts } from '../../custom-model-hosts.js';
22
22
  import { applyCustomModelInjection, removeConfigDir } from '../../custom-model-injection-apply.js';
23
23
  import { getLlamaSwapStatus, triggerLlamaSwapLoad, exceedsSafeContextFloor, CLAUDE_MIN_SAFE_CONTEXT_TOKENS, } from './custom-model-routes.js';
@@ -27,7 +27,7 @@ import { TabLayoutValidationError } from '../../tab-layout.js';
27
27
  import { sessionWaits, resolveWaitSignals, sessionHookOptions, signalForStatus, WaitCapacityError, } from '../session-wait-registry.js';
28
28
  import { RemoteWakeRegistry, REMOTE_WAKE_REQUEST_READY_TIMEOUT_MS, createDefaultRemoteWakeDeps, isProbeable, } from '../../remote-wake.js';
29
29
  import { clampWaitMs, MAX_BUFFER_SCAN_BYTES } from '../../config/agent-wait.js';
30
- import { autoConfigureRalph, canAccessOwned, CASES_DIR, findPersistedSessionOrFail, findSessionOrFail, getAuthUser, isAdmin, isPlainPromptInput, isWorkingDirAllowed, ownerFor, parseBody, persistAndBroadcastSession, resolveAgentCaseOrigin, resolveCasesDir, resolveParentSessionId, sessionCapacityMessage, SETTINGS_PATH, validatePathWithinBase, } from '../route-helpers.js';
30
+ import { autoConfigureRalph, canAccessOwned, CASES_DIR, findPersistedSessionOrFail, findSessionOrFail, getAuthUser, isAdmin, isPlainPromptInput, isWorkingDirAllowed, ownerFor, parseBody, persistAndBroadcastSession, readJsonConfig, resolveAgentCaseOrigin, resolveCasesDir, resolveParentSessionId, sessionCapacityMessage, SETTINGS_PATH, validatePathWithinBase, } from '../route-helpers.js';
31
31
  import { buildAgentCaseMarker, writeAgentCaseMarker } from '../../agent-case-marker.js';
32
32
  import { canUsernameRunPrivilegedCommands, resolveClaudeModeForUsername } from '../../user-store.js';
33
33
  import { clampEnvOverridesForOwner } from '../../session-env-clamp.js';
@@ -58,13 +58,48 @@ import { describeUnknownPath, probePathKind } from '../../utils/index.js';
58
58
  import { findLatestOmpSessionId } from '../../utils/omp-session-resolver.js';
59
59
  import { scanOmpSessionsHistory } from '../../omp-transcript.js';
60
60
  import { scanCodexSessionsHistory, codexThreadBySessionId } from '../../codex-transcript.js';
61
- import { getLastTranscriptResponse, isExternalCliTranscriptMode, parseExternalCliTranscript, selectLastAnsweredTurn, } from '../response-viewer-transcript.js';
61
+ import { transcriptNeedsScreenCapture, getLastTranscriptResponse, isExternalCliTranscriptMode, parseExternalCliTranscript, selectLastAnsweredTurn, } from '../response-viewer-transcript.js';
62
62
  import { readDeepSeekLastResponse } from '../../deepseek-transcript.js';
63
63
  import { appendClaudeCustomTitle } from '../../claude-session-title.js';
64
64
  import { UPLOADS_DIR } from '../paste-image-gc.js';
65
65
  // Path to linked-cases registry (same file used by case-routes resolveCasePath)
66
66
  const LINKED_CASES_FILE = dataPath('linked-cases.json');
67
67
  const CODEMAN_CONFIG_DIR = getDataDir();
68
+ const SAFE_CASE_NAME = /^[a-zA-Z0-9_-]+$/;
69
+ /** `<repo>/.claude/worktrees/<name>`: the layout Claude Code's own worktree feature creates. */
70
+ const CLAUDE_WORKTREE_PATH = /^(.*)\/\.claude\/worktrees\/([^/]+)\/?$/;
71
+ /** Did the committed layout put this session in a named group (not Ungrouped)? */
72
+ function landedInGroup(layout, sessionId) {
73
+ return !!layout?.groups.some((group) => group.refs.some((ref) => ref.kind === 'session' && ref.id === sessionId));
74
+ }
75
+ /**
76
+ * The case a POST /api/sessions `workingDir` belongs to, for its default session
77
+ * group: a direct child of the caller's case space, or (admin / single-user, as
78
+ * GET /api/cases lists them) a linked case's exact path, or a Claude worktree
79
+ * (`<case>/.claude/worktrees/<name>`) of either. Lexical only: the browser sends
80
+ * the path the case list gave it, and this must never touch a possibly-dead
81
+ * mount. Anything else is no case, which keeps today's placement.
82
+ */
83
+ async function inferLocalCaseName(req, workingDir) {
84
+ // The Git worktrees modal launches Claude through this route in the worktree, and every other CLI
85
+ // through quick-start with the case name: both are sessions of that case.
86
+ const resolved = resolvePath(workingDir);
87
+ const target = CLAUDE_WORKTREE_PATH.exec(resolved)?.[1] || resolved;
88
+ const user = getAuthUser(req);
89
+ if (dirname(target) === resolvePath(resolveCasesDir(user))) {
90
+ const name = basename(target);
91
+ if (SAFE_CASE_NAME.test(name))
92
+ return name;
93
+ }
94
+ if (isMultiUserMode() && !isAdmin(req))
95
+ return undefined;
96
+ const linked = await readJsonConfig(LINKED_CASES_FILE, 'linked cases', {});
97
+ for (const [name, path] of Object.entries(linked)) {
98
+ if (SAFE_CASE_NAME.test(name) && typeof path === 'string' && resolvePath(path) === target)
99
+ return name;
100
+ }
101
+ return undefined;
102
+ }
68
103
  // Pre-compiled regex for terminal buffer cleaning (avoids per-request compilation)
69
104
  // eslint-disable-next-line no-control-regex
70
105
  const CLAUDE_BANNER_PATTERN = /\x1b\[1mClaud/;
@@ -189,6 +224,22 @@ export function stripInkRedrawBloat(buffer) {
189
224
  * (valid UTF-8, no NUL byte) rather than on a signature, since plain text has none.
190
225
  */
191
226
  const TEXT_UPLOAD_EXTS = new Set(['.txt', '.md', '.csv', '.tsv', '.json', '.log', '.yaml', '.yml', '.xml']);
227
+ /**
228
+ * Documents a user hands an agent to read: the office formats plus the text
229
+ * family. Curated on purpose, never SUPPORTED_ATTACHMENT_EXTENSIONS or
230
+ * EDITABLE_EXTENSIONS, which carry sh/py/js/html; a prompt upload is a thing
231
+ * to read, not a thing to run or serve. Each is judged on its bytes by
232
+ * imageMagicMatchesExt (signature, or UTF-8 without NUL for text). The browser
233
+ * keeps its own copies (test/upload-document-list-parity.test.ts pins them).
234
+ */
235
+ export const ALLOWED_DOCUMENT_EXTS = new Set([
236
+ '.pdf',
237
+ '.docx',
238
+ '.pptx',
239
+ '.xlsx',
240
+ '.zip',
241
+ ...TEXT_UPLOAD_EXTS,
242
+ ]);
192
243
  /**
193
244
  * Validate upload bytes against a declared extension. Sniffs the first ~12 bytes
194
245
  * for a known magic-number signature (a text file is instead required to be
@@ -456,7 +507,7 @@ async function clampExternalCliBypassForOwner(owner, configs) {
456
507
  * named individually, so `privilegedParams` on anything outside that list was declared but
457
508
  * unreachable.
458
509
  */
459
- export async function _clampExternalCliBypassForOwner(owner, codexConfig, geminiConfig, antigravityConfig, piConfig, grokConfig, deepSeekConfig) {
510
+ export async function _clampExternalCliBypassForOwner(owner, codexConfig, geminiConfig, antigravityConfig, piConfig, grokConfig, deepSeekConfig, copilotConfig) {
460
511
  const out = await clampExternalCliBypassForOwner(owner, {
461
512
  codexConfig,
462
513
  geminiConfig,
@@ -464,6 +515,7 @@ export async function _clampExternalCliBypassForOwner(owner, codexConfig, gemini
464
515
  piConfig,
465
516
  grokConfig,
466
517
  deepSeekConfig,
518
+ copilotConfig,
467
519
  });
468
520
  return out;
469
521
  }
@@ -1102,7 +1154,7 @@ options = {}) {
1102
1154
  // Section 6.3: force non-granted users to a classifier-guarded mode.
1103
1155
  const effectiveClaudeMode = await resolveClaudeModeForUsername(claudeModeConfig.claudeMode, owner);
1104
1156
  // Section 6.3: clamp Codex/Gemini/Antigravity bypass switches for a non-granted owner (no-op single-user/granted).
1105
- const { codexConfig: gatedCodexConfig, geminiConfig: gatedGeminiConfig, antigravityConfig: gatedAntigravityConfig, piConfig: gatedPiConfig, grokConfig: gatedGrokConfig, deepSeekConfig: gatedDeepSeekConfig, } = await _clampExternalCliBypassForOwner(owner, launchBody.codexConfig, launchBody.geminiConfig, launchBody.antigravityConfig, launchBody.piConfig, launchBody.grokConfig, launchBody.deepSeekConfig);
1157
+ const { codexConfig: gatedCodexConfig, geminiConfig: gatedGeminiConfig, antigravityConfig: gatedAntigravityConfig, piConfig: gatedPiConfig, grokConfig: gatedGrokConfig, deepSeekConfig: gatedDeepSeekConfig, copilotConfig: gatedCopilotConfig, } = await _clampExternalCliBypassForOwner(owner, launchBody.codexConfig, launchBody.geminiConfig, launchBody.antigravityConfig, launchBody.piConfig, launchBody.grokConfig, launchBody.deepSeekConfig, launchBody.copilotConfig);
1106
1158
  const terminalHistoryConfig = await ctx.getTerminalHistoryConfig();
1107
1159
  const session = new Session({
1108
1160
  workingDir,
@@ -1120,6 +1172,7 @@ options = {}) {
1120
1172
  antigravityConfig: mode === 'antigravity' ? gatedAntigravityConfig : undefined,
1121
1173
  piConfig: mode === 'pi' ? gatedPiConfig : undefined,
1122
1174
  grokConfig: mode === 'grok' ? gatedGrokConfig : undefined,
1175
+ copilotConfig: mode === 'copilot' ? gatedCopilotConfig : undefined,
1123
1176
  deepSeekConfig: mode === 'deepseek' ? gatedDeepSeekConfig : undefined,
1124
1177
  ompConfig: resolveOmpConfigForCreate(mode, workingDir, launchBody.ompConfig),
1125
1178
  resumeSessionId: validatedResumeId,
@@ -1131,9 +1184,13 @@ options = {}) {
1131
1184
  owner,
1132
1185
  parentSessionId: resolveParentSessionId(ctx, req, body.parentSessionId, owner),
1133
1186
  });
1134
- const tabLayout = body.tabGroupId
1135
- ? await ctx.addSession(session, { tabGroupId: body.tabGroupId })
1136
- : await ctx.addSession(session);
1187
+ // Only a named workingDir: without one the session starts in the server's own cwd, which is no case.
1188
+ const placementCase = remote || !body.workingDir ? undefined : await inferLocalCaseName(req, workingDir);
1189
+ const placement = {
1190
+ ...(body.tabGroupId ? { tabGroupId: body.tabGroupId } : {}),
1191
+ ...(placementCase ? { caseName: placementCase } : {}),
1192
+ };
1193
+ const tabLayout = Object.keys(placement).length > 0 ? await ctx.addSession(session, placement) : await ctx.addSession(session);
1137
1194
  ctx.store.incrementSessionsCreated();
1138
1195
  ctx.persistSessionState(session);
1139
1196
  await ctx.setupSessionListeners(session);
@@ -1148,9 +1205,13 @@ options = {}) {
1148
1205
  // Avoids serializing 2-3MB of terminal+text buffers per session creation.
1149
1206
  const lightState = ctx.getSessionStateWithRespawn(session);
1150
1207
  ctx.broadcast(SseEvent.SessionCreated, lightState);
1151
- // A grouped creation also returns the layout that placed it, so the browser
1152
- // draws the tab inside its group at once instead of after a re-read.
1153
- return { session: lightState, ...(body.tabGroupId ? { tabLayout } : {}) };
1208
+ // A grouped creation (asked for, or its case's default group) also returns the
1209
+ // layout that placed it, so the browser draws the tab inside its group at once
1210
+ // instead of after a re-read.
1211
+ return {
1212
+ session: lightState,
1213
+ ...(body.tabGroupId || landedInGroup(tabLayout, session.id) ? { tabLayout } : {}),
1214
+ };
1154
1215
  });
1155
1216
  // ========== Rename Session ==========
1156
1217
  app.put('/api/sessions/:id/name', async (req) => {
@@ -1228,6 +1289,7 @@ options = {}) {
1228
1289
  return createErrorResponse(ApiErrorCode.NOT_FOUND, 'Model endpoint not found');
1229
1290
  }
1230
1291
  const contextLength = endpoint.modelContextLengths?.[body.modelId];
1292
+ const slotCount = endpoint.modelSlotCounts?.[body.modelId];
1231
1293
  // Some CLIs (today: only claude) carry enough of their own fixed system-prompt/tool-
1232
1294
  // schema overhead that a small enough real context guarantees a first-message failure
1233
1295
  // no matter what CLAUDE_CODE_MAX_CONTEXT_TOKENS says — confirmed live at ~36.4K tokens
@@ -1237,12 +1299,13 @@ options = {}) {
1237
1299
  // `confirmed`, which still means both) — NOT by `confirmedSwap`: this warning is
1238
1300
  // about the caller's own session, and the swap warning below is about someone
1239
1301
  // else's, so an answer to one is not consent to the other.
1240
- if (!(body.confirmed || body.confirmedContext) && exceedsSafeContextFloor(entry, contextLength)) {
1302
+ if (!(body.confirmed || body.confirmedContext) && exceedsSafeContextFloor(entry, contextLength, slotCount)) {
1241
1303
  return {
1242
1304
  requiresContextWarning: true,
1243
1305
  modelId: body.modelId,
1244
1306
  contextLength,
1245
1307
  minSafeContextTokens: CLAUDE_MIN_SAFE_CONTEXT_TOKENS,
1308
+ ...(slotCount && slotCount > 1 ? { slotCount } : {}),
1246
1309
  };
1247
1310
  }
1248
1311
  // llama.cpp runs exactly one model at a time; llama-swap unloads and reloads it on
@@ -2417,7 +2480,13 @@ options = {}) {
2417
2480
  // where a real rollout file is the better source.
2418
2481
  if (isExternalCliTranscriptMode(session.mode)) {
2419
2482
  const externalQuery = req.query;
2420
- const blocks = parseExternalCliTranscript(session.terminalBuffer, session.mode);
2483
+ // A repainting full-screen TUI (copilot) streams absolute cursor moves, not lines, so the
2484
+ // buffer cannot be segmented; read the pane's visible screen instead, and fall back to the
2485
+ // buffer when there is no tmux pane to ask (direct PTY, tests).
2486
+ const screen = transcriptNeedsScreenCapture(session.mode) && session.muxName
2487
+ ? (ctx.mux.capturePaneText?.(session.muxName) ?? null)
2488
+ : null;
2489
+ const blocks = parseExternalCliTranscript(screen ?? session.terminalBuffer, session.mode);
2421
2490
  return {
2422
2491
  text: getLastTranscriptResponse(blocks),
2423
2492
  timestamp: '',
@@ -3065,6 +3134,32 @@ options = {}) {
3065
3134
  },
3066
3135
  };
3067
3136
  });
3137
+ // ========== Prompt-cache keep-warm ==========
3138
+ // Per-session opt-in for the keep-alive nudge (prompt-cache-keepwarm.ts). `enabled: null`
3139
+ // clears the choice so the app-wide default applies again. Refused for a CLI that reports
3140
+ // no cache telemetry on its statusline: without `expires_at` there is nothing to schedule.
3141
+ app.post('/api/sessions/:id/keep-warm', async (req) => {
3142
+ const { id } = req.params;
3143
+ const body = parseBody(KeepWarmSchema, req.body, 'Invalid request body');
3144
+ const session = findSessionOrFail(ctx, id, req);
3145
+ if (!getCli(session.mode)?.capabilities.statusLineTelemetry) {
3146
+ return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'Prompt-cache keep-warm needs the cache telemetry this CLI does not report');
3147
+ }
3148
+ session.setKeepWarmEnabled(body.enabled ?? undefined);
3149
+ // Re-evaluate BEFORE the state broadcast and the response: both carry `keepWarm`, and a
3150
+ // page that received the previous status would overwrite the one the toggle just produced.
3151
+ await ctx.keepWarm.schedule(id);
3152
+ persistAndBroadcastSession(ctx, session);
3153
+ return {
3154
+ success: true,
3155
+ data: {
3156
+ keepWarm: {
3157
+ enabled: session.keepWarmEnabled ?? null,
3158
+ status: session.keepWarm,
3159
+ },
3160
+ },
3161
+ };
3162
+ });
3068
3163
  // ========== Auto-Resume (usage-limit pause) ==========
3069
3164
  app.post('/api/sessions/:id/auto-resume', async (req) => {
3070
3165
  const { id } = req.params;
@@ -3227,7 +3322,7 @@ options = {}) {
3227
3322
  const capMsg = sessionCapacityMessage(ctx.sessions, owner);
3228
3323
  if (capMsg)
3229
3324
  return createErrorResponse(ApiErrorCode.SESSION_BUSY, capMsg);
3230
- const { caseName = 'testcase', sessionName, mode = 'claude', modelOverride, openCodeConfig, codexConfig, geminiConfig, antigravityConfig, piConfig, grokConfig, deepSeekConfig, ompConfig, envOverrides, effort, advisorModel, parentSessionId, agentOrigin, customModel, tabGroupId, worktree, } = parseBody(QuickStartSchema, req.body);
3325
+ const { caseName = 'testcase', sessionName, mode = 'claude', modelOverride, openCodeConfig, codexConfig, geminiConfig, antigravityConfig, piConfig, grokConfig, copilotConfig, deepSeekConfig, ompConfig, envOverrides, effort, advisorModel, parentSessionId, agentOrigin, customModel, tabGroupId, worktree, } = parseBody(QuickStartSchema, req.body);
3231
3326
  // Resolved ONCE here: the same value labels a case directory this request creates
3232
3327
  // (agent-case-marker.ts) and draws the tab lineage line on the session below.
3233
3328
  const qsParentSessionId = resolveParentSessionId(ctx, req, parentSessionId, owner);
@@ -3277,6 +3372,7 @@ options = {}) {
3277
3372
  antigravityConfig ||
3278
3373
  piConfig ||
3279
3374
  grokConfig ||
3375
+ copilotConfig ||
3280
3376
  deepSeekConfig ||
3281
3377
  ompConfig ||
3282
3378
  openCodeConfig ||
@@ -3335,6 +3431,7 @@ options = {}) {
3335
3431
  antigravityConfig ||
3336
3432
  piConfig ||
3337
3433
  grokConfig ||
3434
+ copilotConfig ||
3338
3435
  deepSeekConfig ||
3339
3436
  ompConfig ||
3340
3437
  openCodeConfig ||
@@ -3419,6 +3516,7 @@ options = {}) {
3419
3516
  antigravityConfig,
3420
3517
  piConfig,
3421
3518
  grokConfig,
3519
+ copilotConfig,
3422
3520
  deepSeekConfig,
3423
3521
  }));
3424
3522
  if (qsLaunchError) {
@@ -3605,6 +3703,7 @@ options = {}) {
3605
3703
  antigravityConfig,
3606
3704
  piConfig,
3607
3705
  grokConfig,
3706
+ copilotConfig,
3608
3707
  deepSeekConfig,
3609
3708
  ompConfig,
3610
3709
  };
@@ -3623,7 +3722,7 @@ options = {}) {
3623
3722
  const qsClaudeModeConfig = await ctx.getClaudeModeConfig();
3624
3723
  const qsEffectiveClaudeMode = await resolveClaudeModeForUsername(qsClaudeModeConfig.claudeMode, owner);
3625
3724
  // Section 6.3: clamp Codex/Gemini/Antigravity bypass switches for a non-granted owner (no-op single-user/granted).
3626
- const { codexConfig: qsGatedCodexConfig, geminiConfig: qsGatedGeminiConfig, antigravityConfig: qsGatedAntigravityConfig, piConfig: qsGatedPiConfig, grokConfig: qsGatedGrokConfig, deepSeekConfig: qsGatedDeepSeekConfig, } = await _clampExternalCliBypassForOwner(owner, qsLaunchConfigs.codexConfig, qsLaunchConfigs.geminiConfig, qsLaunchConfigs.antigravityConfig, qsLaunchConfigs.piConfig, qsLaunchConfigs.grokConfig, qsLaunchConfigs.deepSeekConfig);
3725
+ const { codexConfig: qsGatedCodexConfig, geminiConfig: qsGatedGeminiConfig, antigravityConfig: qsGatedAntigravityConfig, piConfig: qsGatedPiConfig, grokConfig: qsGatedGrokConfig, deepSeekConfig: qsGatedDeepSeekConfig, copilotConfig: qsGatedCopilotConfig, } = await _clampExternalCliBypassForOwner(owner, qsLaunchConfigs.codexConfig, qsLaunchConfigs.geminiConfig, qsLaunchConfigs.antigravityConfig, qsLaunchConfigs.piConfig, qsLaunchConfigs.grokConfig, qsLaunchConfigs.deepSeekConfig, qsLaunchConfigs.copilotConfig);
3627
3726
  const qsTerminalHistoryConfig = await ctx.getTerminalHistoryConfig();
3628
3727
  const qsGatedEnvOverrides = await clampEnvOverridesForOwner(owner, envOverrides);
3629
3728
  const qsResolvedOmpConfig = resolveOmpConfigForCreate(mode, workspacePath, qsLaunchOmpConfig);
@@ -3660,6 +3759,7 @@ options = {}) {
3660
3759
  if (!cmEndpoint)
3661
3760
  return createErrorResponse(ApiErrorCode.NOT_FOUND, 'Model endpoint not found');
3662
3761
  const cmContextLength = cmEndpoint.modelContextLengths?.[customModel.modelId];
3762
+ const cmSlotCount = cmEndpoint.modelSlotCounts?.[customModel.modelId];
3663
3763
  // See the dedicated route's own comment for the full reasoning: some CLIs' own fixed
3664
3764
  // overhead can exceed a small enough real context on the very first message,
3665
3765
  // regardless of contextLengthVar. Warn before creating a session that's certain to
@@ -3667,12 +3767,13 @@ options = {}) {
3667
3767
  // See the dedicated route above for why this reads `confirmedContext` and never
3668
3768
  // `confirmedSwap`.
3669
3769
  if (!(customModel.confirmed || customModel.confirmedContext) &&
3670
- exceedsSafeContextFloor(cmEntry, cmContextLength)) {
3770
+ exceedsSafeContextFloor(cmEntry, cmContextLength, cmSlotCount)) {
3671
3771
  return {
3672
3772
  requiresContextWarning: true,
3673
3773
  modelId: customModel.modelId,
3674
3774
  contextLength: cmContextLength,
3675
3775
  minSafeContextTokens: CLAUDE_MIN_SAFE_CONTEXT_TOKENS,
3776
+ ...(cmSlotCount && cmSlotCount > 1 ? { slotCount: cmSlotCount } : {}),
3676
3777
  };
3677
3778
  }
3678
3779
  // See the dedicated route's own comment for the full reasoning: llama.cpp runs one
@@ -3758,6 +3859,7 @@ options = {}) {
3758
3859
  antigravityConfig: mode === 'antigravity' ? qsGatedAntigravityConfig : undefined,
3759
3860
  piConfig: mode === 'pi' ? qsGatedPiConfig : undefined,
3760
3861
  grokConfig: mode === 'grok' ? qsGatedGrokConfig : undefined,
3862
+ copilotConfig: mode === 'copilot' ? qsGatedCopilotConfig : undefined,
3761
3863
  deepSeekConfig: mode === 'deepseek' ? qsGatedDeepSeekConfig : undefined,
3762
3864
  ompConfig: qsResolvedOmpConfig,
3763
3865
  envOverrides: qsCustomModelEnvOverrides,
@@ -3805,7 +3907,7 @@ options = {}) {
3805
3907
  session.ralphTracker.enableAutoEnable(); // Allow re-enabling on restart
3806
3908
  }
3807
3909
  }
3808
- const tabLayout = tabGroupId ? await ctx.addSession(session, { tabGroupId }) : await ctx.addSession(session);
3910
+ const tabLayout = await ctx.addSession(session, { caseName, ...(tabGroupId ? { tabGroupId } : {}) });
3809
3911
  ctx.store.incrementSessionsCreated();
3810
3912
  ctx.persistSessionState(session);
3811
3913
  await ctx.setupSessionListeners(session);
@@ -3891,7 +3993,7 @@ options = {}) {
3891
3993
  caseName,
3892
3994
  ...(worktree ? { worktree, workingDir: workspacePath } : {}),
3893
3995
  ...(customModel ? { modelSwapInProgress: qsCustomModelSwapInProgress } : {}),
3894
- ...(tabGroupId ? { tabLayout } : {}),
3996
+ ...(tabGroupId || landedInGroup(tabLayout, session.id) ? { tabLayout } : {}),
3895
3997
  };
3896
3998
  }
3897
3999
  catch (err) {
@@ -4016,8 +4118,6 @@ options = {}) {
4016
4118
  }
4017
4119
  return sawNonCli;
4018
4120
  }
4019
- /** `<repo>/.claude/worktrees/<name>` — the layout Claude Code's own worktree feature creates. */
4020
- const CLAUDE_WORKTREE_PATH = /^(.*)\/\.claude\/worktrees\/([^/]+)\/?$/;
4021
4121
  /**
4022
4122
  * Recover cwd / branch / worktree from a transcript chunk.
4023
4123
  *
@@ -4782,12 +4882,6 @@ options = {}) {
4782
4882
  // so an uploaded video is a type that previews. 3gp/mkv are deliberately out: they
4783
4883
  // would upload but not preview, and a phone's camera roll produces neither.
4784
4884
  const ALLOWED_VIDEO_EXTS = new Set([...VIDEO_ATTACHMENT_EXTENSIONS].map((e) => `.${e}`));
4785
- // Documents a user hands an agent to read: the office formats plus the text
4786
- // family. Curated on purpose, never SUPPORTED_ATTACHMENT_EXTENSIONS or
4787
- // EDITABLE_EXTENSIONS, which carry sh/py/js/html; a prompt upload is a thing
4788
- // to read, not a thing to run or serve. Each is judged on its bytes by
4789
- // imageMagicMatchesExt (signature, or UTF-8 without NUL for text).
4790
- const ALLOWED_DOCUMENT_EXTS = new Set(['.pdf', '.docx', '.pptx', '.xlsx', '.zip', ...TEXT_UPLOAD_EXTS]);
4791
4885
  const VIDEO_MIME_EXT = {
4792
4886
  mp4: '.mp4',
4793
4887
  quicktime: '.mov',
@@ -5006,12 +5100,19 @@ options = {}) {
5006
5100
  if (!imageMagicMatchesExt(imageBytes, origExt)) {
5007
5101
  // The first bytes of a text file are user content, so for text the log
5008
5102
  // names the failure and never the bytes; a signature is safe to print.
5009
- const detail = TEXT_UPLOAD_EXTS.has(origExt)
5010
- ? `reason=${imageBytes.includes(0) ? 'nul-byte' : 'not-utf8'}`
5103
+ // A text refusal names the encoding, since an Excel or Notepad export
5104
+ // (UTF-16 with a BOM, or Windows-1252) is not a corrupt file.
5105
+ const isText = TEXT_UPLOAD_EXTS.has(origExt);
5106
+ const bom = imageBytes.length >= 2 ? imageBytes.readUInt16BE(0) : 0;
5107
+ const utf16 = bom === 0xfffe || bom === 0xfeff;
5108
+ const detail = isText
5109
+ ? `reason=${utf16 ? 'utf16-bom' : imageBytes.includes(0) ? 'nul-byte' : 'not-utf8'}`
5011
5110
  : `magic=${imageBytes.subarray(0, 12).toString('hex')}`;
5012
5111
  console.warn(`[paste-image] document mismatch: filename=${JSON.stringify(part.filename)} mime=${JSON.stringify(part.mimetype)} declaredExt=${origExt} size=${imageBytes.length} ${detail}`);
5013
5112
  reply.code(415);
5014
- return createErrorResponse(ApiErrorCode.INVALID_INPUT, `File bytes do not match declared type ${origExt}`);
5113
+ return createErrorResponse(ApiErrorCode.INVALID_INPUT, isText
5114
+ ? `This ${origExt} file is ${utf16 ? 'UTF-16 text, not UTF-8' : 'not UTF-8 text'}; save it as UTF-8 and attach it again`
5115
+ : `File bytes do not match declared type ${origExt}`);
5015
5116
  }
5016
5117
  const saved = await writeBufferedUpload(session.workingDir, origExt, imageBytes, sanitizeUploadStem(part.filename));
5017
5118
  if (!saved) {