aicodeman 1.41.0 → 1.41.1

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 (287) hide show
  1. package/README.md +58 -8
  2. package/dist/attachment-registry.d.ts +2 -0
  3. package/dist/attachment-registry.d.ts.map +1 -1
  4. package/dist/attachment-registry.js +14 -3
  5. package/dist/attachment-registry.js.map +1 -1
  6. package/dist/cli-agent.d.ts +2 -0
  7. package/dist/cli-agent.d.ts.map +1 -1
  8. package/dist/cli-agent.js +12 -8
  9. package/dist/cli-agent.js.map +1 -1
  10. package/dist/cli.d.ts +25 -1
  11. package/dist/cli.d.ts.map +1 -1
  12. package/dist/cli.js +97 -11
  13. package/dist/cli.js.map +1 -1
  14. package/dist/config/buffer-limits.d.ts +9 -0
  15. package/dist/config/buffer-limits.d.ts.map +1 -1
  16. package/dist/config/buffer-limits.js +9 -0
  17. package/dist/config/buffer-limits.js.map +1 -1
  18. package/dist/config/cli-registry/schema.d.ts +8 -0
  19. package/dist/config/cli-registry/schema.d.ts.map +1 -1
  20. package/dist/config/cli-registry/schema.js +12 -0
  21. package/dist/config/cli-registry/schema.js.map +1 -1
  22. package/dist/config/cli-registry/stock.d.ts.map +1 -1
  23. package/dist/config/cli-registry/stock.js +42 -0
  24. package/dist/config/cli-registry/stock.js.map +1 -1
  25. package/dist/config/cli-registry/types.d.ts +35 -0
  26. package/dist/config/cli-registry/types.d.ts.map +1 -1
  27. package/dist/folder-listing.d.ts +94 -0
  28. package/dist/folder-listing.d.ts.map +1 -0
  29. package/dist/folder-listing.js +241 -0
  30. package/dist/folder-listing.js.map +1 -0
  31. package/dist/hooks-config.d.ts +36 -16
  32. package/dist/hooks-config.d.ts.map +1 -1
  33. package/dist/hooks-config.js +45 -20
  34. package/dist/hooks-config.js.map +1 -1
  35. package/dist/html-view-capabilities.d.ts +54 -0
  36. package/dist/html-view-capabilities.d.ts.map +1 -0
  37. package/dist/html-view-capabilities.js +103 -0
  38. package/dist/html-view-capabilities.js.map +1 -0
  39. package/dist/mcp-server.d.ts +114 -0
  40. package/dist/mcp-server.d.ts.map +1 -0
  41. package/dist/mcp-server.js +917 -0
  42. package/dist/mcp-server.js.map +1 -0
  43. package/dist/mcp-sync.d.ts +5 -0
  44. package/dist/mcp-sync.d.ts.map +1 -1
  45. package/dist/mcp-sync.js +51 -5
  46. package/dist/mcp-sync.js.map +1 -1
  47. package/dist/mux-interface.d.ts +36 -1
  48. package/dist/mux-interface.d.ts.map +1 -1
  49. package/dist/remote-files.d.ts +37 -0
  50. package/dist/remote-files.d.ts.map +1 -1
  51. package/dist/remote-files.js +103 -0
  52. package/dist/remote-files.js.map +1 -1
  53. package/dist/remote-pane-history.d.ts +103 -0
  54. package/dist/remote-pane-history.d.ts.map +1 -0
  55. package/dist/remote-pane-history.js +258 -0
  56. package/dist/remote-pane-history.js.map +1 -0
  57. package/dist/remote-ssh-limiter.d.ts +16 -3
  58. package/dist/remote-ssh-limiter.d.ts.map +1 -1
  59. package/dist/remote-ssh-limiter.js +37 -6
  60. package/dist/remote-ssh-limiter.js.map +1 -1
  61. package/dist/session.d.ts +29 -1
  62. package/dist/session.d.ts.map +1 -1
  63. package/dist/session.js +53 -0
  64. package/dist/session.js.map +1 -1
  65. package/dist/tab-layout-legacy-order.d.ts.map +1 -1
  66. package/dist/tab-layout-legacy-order.js +72 -13
  67. package/dist/tab-layout-legacy-order.js.map +1 -1
  68. package/dist/tab-layout-service.d.ts +33 -2
  69. package/dist/tab-layout-service.d.ts.map +1 -1
  70. package/dist/tab-layout-service.js +102 -7
  71. package/dist/tab-layout-service.js.map +1 -1
  72. package/dist/tab-layout.d.ts +9 -0
  73. package/dist/tab-layout.d.ts.map +1 -1
  74. package/dist/tab-layout.js +15 -2
  75. package/dist/tab-layout.js.map +1 -1
  76. package/dist/tmux-manager.d.ts +71 -2
  77. package/dist/tmux-manager.d.ts.map +1 -1
  78. package/dist/tmux-manager.js +169 -18
  79. package/dist/tmux-manager.js.map +1 -1
  80. package/dist/types/session.d.ts +53 -0
  81. package/dist/types/session.d.ts.map +1 -1
  82. package/dist/types/session.js.map +1 -1
  83. package/dist/usage-telemetry.d.ts +44 -7
  84. package/dist/usage-telemetry.d.ts.map +1 -1
  85. package/dist/usage-telemetry.js +72 -7
  86. package/dist/usage-telemetry.js.map +1 -1
  87. package/dist/web/approval-inbox.d.ts +12 -0
  88. package/dist/web/approval-inbox.d.ts.map +1 -1
  89. package/dist/web/approval-inbox.js +1 -0
  90. package/dist/web/approval-inbox.js.map +1 -1
  91. package/dist/web/middleware/auth.d.ts.map +1 -1
  92. package/dist/web/middleware/auth.js +33 -0
  93. package/dist/web/middleware/auth.js.map +1 -1
  94. package/dist/web/ports/session-port.d.ts +8 -1
  95. package/dist/web/ports/session-port.d.ts.map +1 -1
  96. package/dist/web/public/admin-ui.js.gz +0 -0
  97. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  98. package/dist/web/public/app.24689381.js +43 -0
  99. package/dist/web/public/app.24689381.js.br +0 -0
  100. package/dist/web/public/app.24689381.js.gz +0 -0
  101. package/dist/web/public/approvals-ui.js +4 -0
  102. package/dist/web/public/approvals-ui.js.br +0 -0
  103. package/dist/web/public/approvals-ui.js.gz +0 -0
  104. package/dist/web/public/{constants.bc9c1f63.js → constants.17d77b65.js} +395 -7
  105. package/dist/web/public/constants.17d77b65.js.br +0 -0
  106. package/dist/web/public/constants.17d77b65.js.gz +0 -0
  107. package/dist/web/public/cron-ui.js.gz +0 -0
  108. package/dist/web/public/entrance-animations.js.gz +0 -0
  109. package/dist/web/public/gesture/gesture-codeman.js +112 -6
  110. package/dist/web/public/git-status-ui.js.gz +0 -0
  111. package/dist/web/public/home-sessions.js.gz +0 -0
  112. package/dist/web/public/host-wake-ui.js.gz +0 -0
  113. package/dist/web/public/i18n.9afd8e4d.js +2 -0
  114. package/dist/web/public/i18n.9afd8e4d.js.br +0 -0
  115. package/dist/web/public/i18n.9afd8e4d.js.gz +0 -0
  116. package/dist/web/public/{image-input.e00471c6.js → image-input.9ed1d0d2.js} +186 -16
  117. package/dist/web/public/image-input.9ed1d0d2.js.br +0 -0
  118. package/dist/web/public/image-input.9ed1d0d2.js.gz +0 -0
  119. package/dist/web/public/index.html +105 -23
  120. package/dist/web/public/index.html.br +0 -0
  121. package/dist/web/public/index.html.gz +0 -0
  122. package/dist/web/public/input-cjk.8bc46081.js.gz +0 -0
  123. package/dist/web/public/{keyboard-accessory.c1e7c7b7.js → keyboard-accessory.c458eea8.js} +115 -35
  124. package/dist/web/public/keyboard-accessory.c458eea8.js.br +0 -0
  125. package/dist/web/public/keyboard-accessory.c458eea8.js.gz +0 -0
  126. package/dist/web/public/mobile-handlers.0ef9c311.js.gz +0 -0
  127. package/dist/web/public/mobile-ime-preview.2dbaf262.js.gz +0 -0
  128. package/dist/web/public/mobile-overview.js +11 -2
  129. package/dist/web/public/mobile-overview.js.br +0 -0
  130. package/dist/web/public/mobile-overview.js.gz +0 -0
  131. package/dist/web/public/mobile.2dff1580.css +1 -0
  132. package/dist/web/public/mobile.2dff1580.css.br +0 -0
  133. package/dist/web/public/mobile.2dff1580.css.gz +0 -0
  134. package/dist/web/public/notification-manager.feb82c04.js.gz +0 -0
  135. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  136. package/dist/web/public/{panels-ui.71846fcb.js → panels-ui.73ec0137.js} +87 -87
  137. package/dist/web/public/panels-ui.73ec0137.js.br +0 -0
  138. package/dist/web/public/panels-ui.73ec0137.js.gz +0 -0
  139. package/dist/web/public/ralph-panel.b350e2ef.js.gz +0 -0
  140. package/dist/web/public/ralph-wizard.b6c0dd19.js.gz +0 -0
  141. package/dist/web/public/readmymind-ui.js.gz +0 -0
  142. package/dist/web/public/reboot-restore-ui.js.gz +0 -0
  143. package/dist/web/public/respawn-ui.ff0dae4c.js.gz +0 -0
  144. package/dist/web/public/sanitize-html.e7147e76.js.gz +0 -0
  145. package/dist/web/public/session-lineage.js.gz +0 -0
  146. package/dist/web/public/{session-ui.f731602c.js → session-ui.dbc6819b.js} +33 -33
  147. package/dist/web/public/session-ui.dbc6819b.js.br +0 -0
  148. package/dist/web/public/session-ui.dbc6819b.js.gz +0 -0
  149. package/dist/web/public/{settings-ui.4e33390f.js → settings-ui.81ac264e.js} +9 -9
  150. package/dist/web/public/settings-ui.81ac264e.js.br +0 -0
  151. package/dist/web/public/settings-ui.81ac264e.js.gz +0 -0
  152. package/dist/web/public/spreadsheet-preview-worker.js +11 -0
  153. package/dist/web/public/spreadsheet-preview-worker.js.br +0 -0
  154. package/dist/web/public/spreadsheet-preview-worker.js.gz +0 -0
  155. package/dist/web/public/{spreadsheet-preview.af82fa2f.js → spreadsheet-preview.75c751de.js} +1 -1
  156. package/dist/web/public/spreadsheet-preview.75c751de.js.br +0 -0
  157. package/dist/web/public/spreadsheet-preview.75c751de.js.gz +0 -0
  158. package/dist/web/public/spreadsheet-xlsx-core.js +110 -0
  159. package/dist/web/public/spreadsheet-xlsx-core.js.br +0 -0
  160. package/dist/web/public/spreadsheet-xlsx-core.js.gz +0 -0
  161. package/dist/web/public/styles.900a28e4.css +1 -0
  162. package/dist/web/public/styles.900a28e4.css.br +0 -0
  163. package/dist/web/public/styles.900a28e4.css.gz +0 -0
  164. package/dist/web/public/subagent-windows.07277a58.js.gz +0 -0
  165. package/dist/web/public/sw.js +6 -2
  166. package/dist/web/public/sw.js.br +0 -0
  167. package/dist/web/public/sw.js.gz +0 -0
  168. package/dist/web/public/tab-layout-browser.2d6189f7.js +1 -0
  169. package/dist/web/public/tab-layout-browser.2d6189f7.js.br +0 -0
  170. package/dist/web/public/tab-layout-browser.2d6189f7.js.gz +0 -0
  171. package/dist/web/public/{tab-rail-resize.a5867793.js → tab-rail-resize.7157e0bd.js} +1 -1
  172. package/dist/web/public/tab-rail-resize.7157e0bd.js.br +0 -0
  173. package/dist/web/public/tab-rail-resize.7157e0bd.js.gz +0 -0
  174. package/dist/web/public/terminal-keycode229-recovery.af09f99a.js.gz +0 -0
  175. package/dist/web/public/terminal-split.js.gz +0 -0
  176. package/dist/web/public/terminal-tile.js +44 -24
  177. package/dist/web/public/terminal-tile.js.br +0 -0
  178. package/dist/web/public/terminal-tile.js.gz +0 -0
  179. package/dist/web/public/terminal-ui.858f5da5.js +2 -0
  180. package/dist/web/public/terminal-ui.858f5da5.js.br +0 -0
  181. package/dist/web/public/terminal-ui.858f5da5.js.gz +0 -0
  182. package/dist/web/public/tile-grid.js +439 -18
  183. package/dist/web/public/tile-grid.js.br +0 -0
  184. package/dist/web/public/tile-grid.js.gz +0 -0
  185. package/dist/web/public/ultracode-panel.js.gz +0 -0
  186. package/dist/web/public/ultracode-windows.js.gz +0 -0
  187. package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
  188. package/dist/web/public/vendor/exceljs.min.js.gz +0 -0
  189. package/dist/web/public/vendor/fflate.min.js.gz +0 -0
  190. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  191. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  192. package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
  193. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  194. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  195. package/dist/web/public/vendor/xterm-predictive-echo.ae5e3d5d.js.gz +0 -0
  196. package/dist/web/public/vendor/xterm-zerolag-input.0b69623c.js.gz +0 -0
  197. package/dist/web/public/vendor/xterm.css.gz +0 -0
  198. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  199. package/dist/web/public/voice-input.2342c37b.js.gz +0 -0
  200. package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
  201. package/dist/web/public/webview-tabs.js +12 -0
  202. package/dist/web/public/webview-tabs.js.br +0 -0
  203. package/dist/web/public/webview-tabs.js.gz +0 -0
  204. package/dist/web/route-helpers.d.ts +8 -0
  205. package/dist/web/route-helpers.d.ts.map +1 -1
  206. package/dist/web/route-helpers.js +8 -0
  207. package/dist/web/route-helpers.js.map +1 -1
  208. package/dist/web/routes/admin-routes.d.ts.map +1 -1
  209. package/dist/web/routes/admin-routes.js +4 -1
  210. package/dist/web/routes/admin-routes.js.map +1 -1
  211. package/dist/web/routes/approval-routes.d.ts.map +1 -1
  212. package/dist/web/routes/approval-routes.js +6 -8
  213. package/dist/web/routes/approval-routes.js.map +1 -1
  214. package/dist/web/routes/case-routes.js +1 -1
  215. package/dist/web/routes/case-routes.js.map +1 -1
  216. package/dist/web/routes/file-routes.d.ts +46 -1
  217. package/dist/web/routes/file-routes.d.ts.map +1 -1
  218. package/dist/web/routes/file-routes.js +214 -27
  219. package/dist/web/routes/file-routes.js.map +1 -1
  220. package/dist/web/routes/hook-event-routes.d.ts.map +1 -1
  221. package/dist/web/routes/hook-event-routes.js +8 -1
  222. package/dist/web/routes/hook-event-routes.js.map +1 -1
  223. package/dist/web/routes/html-view-routes.d.ts +60 -0
  224. package/dist/web/routes/html-view-routes.d.ts.map +1 -0
  225. package/dist/web/routes/html-view-routes.js +288 -0
  226. package/dist/web/routes/html-view-routes.js.map +1 -0
  227. package/dist/web/routes/index.d.ts +1 -0
  228. package/dist/web/routes/index.d.ts.map +1 -1
  229. package/dist/web/routes/index.js +1 -0
  230. package/dist/web/routes/index.js.map +1 -1
  231. package/dist/web/routes/reboot-restore-routes.js +2 -2
  232. package/dist/web/routes/reboot-restore-routes.js.map +1 -1
  233. package/dist/web/routes/session-routes.d.ts +1 -0
  234. package/dist/web/routes/session-routes.d.ts.map +1 -1
  235. package/dist/web/routes/session-routes.js +278 -56
  236. package/dist/web/routes/session-routes.js.map +1 -1
  237. package/dist/web/routes/status-telemetry-routes.d.ts.map +1 -1
  238. package/dist/web/routes/status-telemetry-routes.js +16 -2
  239. package/dist/web/routes/status-telemetry-routes.js.map +1 -1
  240. package/dist/web/schemas.d.ts +29 -0
  241. package/dist/web/schemas.d.ts.map +1 -1
  242. package/dist/web/schemas.js +65 -0
  243. package/dist/web/schemas.js.map +1 -1
  244. package/dist/web/server.d.ts +6 -1
  245. package/dist/web/server.d.ts.map +1 -1
  246. package/dist/web/server.js +62 -14
  247. package/dist/web/server.js.map +1 -1
  248. package/dist/web/sse-events.d.ts +5 -2
  249. package/dist/web/sse-events.d.ts.map +1 -1
  250. package/dist/web/sse-events.js +5 -2
  251. package/dist/web/sse-events.js.map +1 -1
  252. package/package.json +2 -1
  253. package/skills/codeman/SKILL.md +29 -12
  254. package/dist/web/public/app.6f8552e1.js +0 -43
  255. package/dist/web/public/app.6f8552e1.js.br +0 -0
  256. package/dist/web/public/app.6f8552e1.js.gz +0 -0
  257. package/dist/web/public/constants.bc9c1f63.js.br +0 -0
  258. package/dist/web/public/constants.bc9c1f63.js.gz +0 -0
  259. package/dist/web/public/i18n.a9840ea2.js +0 -2
  260. package/dist/web/public/i18n.a9840ea2.js.br +0 -0
  261. package/dist/web/public/i18n.a9840ea2.js.gz +0 -0
  262. package/dist/web/public/image-input.e00471c6.js.br +0 -0
  263. package/dist/web/public/image-input.e00471c6.js.gz +0 -0
  264. package/dist/web/public/keyboard-accessory.c1e7c7b7.js.br +0 -0
  265. package/dist/web/public/keyboard-accessory.c1e7c7b7.js.gz +0 -0
  266. package/dist/web/public/mobile.111b187d.css +0 -1
  267. package/dist/web/public/mobile.111b187d.css.br +0 -0
  268. package/dist/web/public/mobile.111b187d.css.gz +0 -0
  269. package/dist/web/public/panels-ui.71846fcb.js.br +0 -0
  270. package/dist/web/public/panels-ui.71846fcb.js.gz +0 -0
  271. package/dist/web/public/session-ui.f731602c.js.br +0 -0
  272. package/dist/web/public/session-ui.f731602c.js.gz +0 -0
  273. package/dist/web/public/settings-ui.4e33390f.js.br +0 -0
  274. package/dist/web/public/settings-ui.4e33390f.js.gz +0 -0
  275. package/dist/web/public/spreadsheet-preview.af82fa2f.js.br +0 -0
  276. package/dist/web/public/spreadsheet-preview.af82fa2f.js.gz +0 -0
  277. package/dist/web/public/styles.81a72906.css +0 -1
  278. package/dist/web/public/styles.81a72906.css.br +0 -0
  279. package/dist/web/public/styles.81a72906.css.gz +0 -0
  280. package/dist/web/public/tab-layout-browser.065fcba4.js +0 -1
  281. package/dist/web/public/tab-layout-browser.065fcba4.js.br +0 -0
  282. package/dist/web/public/tab-layout-browser.065fcba4.js.gz +0 -0
  283. package/dist/web/public/tab-rail-resize.a5867793.js.br +0 -0
  284. package/dist/web/public/tab-rail-resize.a5867793.js.gz +0 -0
  285. package/dist/web/public/terminal-ui.b7267005.js +0 -2
  286. package/dist/web/public/terminal-ui.b7267005.js.br +0 -0
  287. package/dist/web/public/terminal-ui.b7267005.js.gz +0 -0
@@ -14,6 +14,69 @@
14
14
  Object.assign(CodemanApp.prototype, {
15
15
 
16
16
  initImageInput() {
17
+ // Phone toolbar Attach (index.html): the tap lands on the file INPUT, which
18
+ // sits invisibly over the button, and the browser opens the picker itself.
19
+ //
20
+ // Nothing here may open the picker from script. While the keyboard is up,
21
+ // app.js's keyboard tap shim cancels every touch on a toolbar BUTTON and
22
+ // re-dispatches it as a scripted click, and iOS will not open a file picker
23
+ // from an untrusted click: measured on iOS 17, `isTrusted: false` two
24
+ // milliseconds after pointerdown, no picker, no error, and no real click after
25
+ // it either because the gesture was cancelled. A native tap on the input is not
26
+ // a button tap, so the shim leaves it alone and the click stays trusted. It also
27
+ // anchors the picker popover to the input's own frame (a `hidden` input has
28
+ // none, which is why the composer's picker floated loose).
29
+ //
30
+ // Cancelling mousedown's default keeps focus on the terminal's hidden textarea,
31
+ // so the fixed toolbar never reflows out from under the finger mid-tap.
32
+ //
33
+ // It does NOT keep the keyboard up, and nothing here can: iOS resigns first
34
+ // responder to present the picker, and on DEVICE it does not hand the keyboard
35
+ // back when the picker closes, even though focus never left the textarea.
36
+ // (An iOS 27 simulator does hand it back, which is a simulator artifact, so do
37
+ // not take a keyboard measurement from one.) A blur-then-focus on the close
38
+ // of the picker, cancel and change alike, restores DOM focus and still leaves
39
+ // the keyboard down, because iOS only raises it inside a user gesture. So the
40
+ // user taps the prompt when ready to type, which is the keyboard policy
41
+ // everywhere else in the app.
42
+ //
43
+ // Do not try to hide the keyboard's exit by freezing the page's keyboard
44
+ // state across the picker. That was tried (holdKeyboardState, reverted) to
45
+ // avoid the reflow and the two PTY resizes the hide/show pair costs: with the
46
+ // keyboard gone and the layout still keyboard-sized, the toolbar and accessory
47
+ // bar strand in the MIDDLE of the screen over a dead black band, which is far
48
+ // worse than the reflow. The page must follow the keyboard out.
49
+ const attachBtn = document.getElementById('attachBtnMobile');
50
+ const attachInput = document.getElementById('attachFileInput');
51
+ if (attachBtn && attachInput) {
52
+ attachInput.addEventListener('mousedown', (event) => event.preventDefault());
53
+ // The button is the ACCESSIBLE control (the input is aria-hidden and not
54
+ // tabbable), so a keyboard or VoiceOver activation arrives here instead of on
55
+ // the input. That click is trusted, so opening the picker from it is allowed.
56
+ attachBtn.addEventListener('click', () => attachInput.click());
57
+ attachInput.addEventListener('click', (event) => {
58
+ if (!this.activeSessionId) {
59
+ event.preventDefault(); // refuse before the picker opens, not after a pick
60
+ this.showToast('Open a session to attach a file', 'error');
61
+ return;
62
+ }
63
+ // Captured before the picker opens: the session can be deleted while it is
64
+ // up, and the pick must not land on whatever replaced it.
65
+ this._attachSessionId = this.activeSessionId;
66
+ });
67
+ attachInput.addEventListener('change', () => {
68
+ const files = Array.from(attachInput.files || []);
69
+ // Reset so picking the same file again still fires change.
70
+ attachInput.value = '';
71
+ // Captured when the picker opened (see the click handler above).
72
+ const sessionId = this._attachSessionId;
73
+ this._attachSessionId = null;
74
+ if (files.length > 0) {
75
+ void this._attachFromToolbar(files, sessionId).catch((err) => console.warn('Attach failed:', err));
76
+ }
77
+ });
78
+ }
79
+
17
80
  // Drag-and-drop handlers on terminal container
18
81
  const container = document.getElementById('terminalContainer');
19
82
  if (!container) return;
@@ -38,15 +101,100 @@ Object.assign(CodemanApp.prototype, {
38
101
  if (!this.activeSessionId) return;
39
102
  if (!e.dataTransfer || !e.dataTransfer.files.length) return;
40
103
 
41
- const imageFiles = Array.from(e.dataTransfer.files).filter((f) => f.type.startsWith('image/'));
42
- if (imageFiles.length === 0) {
43
- this.showToast('Only image files are supported', 'error');
104
+ const mediaFiles = Array.from(e.dataTransfer.files).filter((f) => this._promptAttachKind(f));
105
+ if (mediaFiles.length === 0) {
106
+ this.showToast('Only image or video files are supported', 'error');
44
107
  return;
45
108
  }
46
- this._uploadAndInsertImages(imageFiles);
109
+ this._uploadAndInsertImages(mediaFiles);
47
110
  });
48
111
  },
49
112
 
113
+ /** Upload the toolbar Attach selection and put the paths on the prompt. The
114
+ * Compose dialog is deliberately not involved (see initImageInput).
115
+ *
116
+ * With local echo on (every phone by default) the paths go into the OVERLAY as
117
+ * pending text rather than to the PTY. That is what makes them part of "what the
118
+ * user typed": they paint at the prompt, anything typed after them appends in
119
+ * order, Enter flushes the whole line, and opening Compose adopts path and prose
120
+ * together (_takePendingLocalEcho reads exactly this buffer). Writing to the PTY
121
+ * instead would strand the path on the terminal while Compose took only the prose.
122
+ * Without the overlay (local echo off) there is nothing to buffer into, so the
123
+ * text goes straight out. The trailing space keeps the next word off the path. */
124
+ async _attachFromToolbar(files, sessionId) {
125
+ const media = files.filter((f) => this._promptAttachKind(f));
126
+ if (media.length === 0) {
127
+ this.showToast('Only image or video files are supported', 'error');
128
+ return;
129
+ }
130
+ if (!sessionId) return; // no captured session: refuse rather than guess at one
131
+ const paths = await this._uploadAndInsertImages(media, { insert: false, sessionId });
132
+ if (paths.length === 0) return;
133
+ // Deleted while the upload ran: a write would go to a dead session, and the
134
+ // flushed maps below would gain an entry whose cleanup has already run.
135
+ if (this.sessions && !this.sessions.has(sessionId)) return;
136
+ const text = paths.join(' ');
137
+
138
+ // The prompt composer, if it opened for this session while the upload ran, is
139
+ // where the prompt now lives: it took the terminal's pending text with it, so
140
+ // appending behind it would strand the path outside the text being composed.
141
+ // _insertComposerText adds its own separator and fires the draft save.
142
+ const openComposer = KeyboardAccessoryBar._composerOverlay;
143
+ const composerTextarea =
144
+ openComposer?.isConnected && openComposer.dataset.sessionId === sessionId
145
+ ? openComposer.querySelector('.prompt-composer-textarea')
146
+ : null;
147
+ if (composerTextarea) {
148
+ KeyboardAccessoryBar._insertComposerText(composerTextarea, text);
149
+ return;
150
+ }
151
+
152
+ const stillActive = this.activeSessionId === sessionId;
153
+ // The overlay holds the prompt only while local echo actually owns it. A
154
+ // session handed back to plain PTY echo by a composer nav key (echo
155
+ // passthrough) skips the whole overlay branch in onData, Enter included, so
156
+ // text buffered there would sit on screen and never be submitted. Nor while
157
+ // the tile grid owns the terminal area: the main terminal is parked and
158
+ // hidden then, and a tile's Enter never reaches its overlay, so the path
159
+ // goes to the PTY like any other background write.
160
+ const overlayOwnsPrompt =
161
+ stillActive &&
162
+ !this._tilesOwnTerminal?.() &&
163
+ this._localEchoEnabled &&
164
+ this._localEchoOverlay &&
165
+ !this._echoPassthroughSessions?.has(sessionId);
166
+ if (overlayOwnsPrompt) {
167
+ // Prompt text the overlay has not adopted yet (a tab completion, a restored
168
+ // tab) only lands in its flushed half once detection runs, and appendText
169
+ // runs it AFTER we would have measured. Detect first, or the separator is
170
+ // computed against an empty string and the path fuses onto a real word.
171
+ if (!this._localEchoOverlay.pendingText && !this._localEchoOverlay.getFlushed().count) {
172
+ this._localEchoOverlay.detectBufferText();
173
+ }
174
+ const flushed = this._localEchoOverlay.getFlushed().text || '';
175
+ const prompt = flushed + (this._localEchoOverlay.pendingText || '');
176
+ const gap = prompt && !/\s$/.test(prompt) ? ' ' : '';
177
+ this._localEchoOverlay.appendText(gap + text + ' ');
178
+ } else {
179
+ // No buffer to measure, so lead with a separator unconditionally rather than
180
+ // risk fusing onto whatever the CLI's own composer already holds.
181
+ const out = ` ${text} `;
182
+ this._sendInputAsync(sessionId, out);
183
+ // A background session's prompt is tracked in the flushed maps, which is what
184
+ // a later composer open adopts and erases. An untracked write leaves that
185
+ // count short, so the erase would eat the tail of this path instead. Not for
186
+ // a passthrough session: its cursor can sit mid-text, nothing tracks its
187
+ // composer, and Enter there never clears the maps, so a record would offset
188
+ // the overlay for the next prompt instead.
189
+ if (!stillActive && !this._echoPassthroughSessions?.has(sessionId)) {
190
+ if (!this._flushedOffsets) this._flushedOffsets = new Map();
191
+ if (!this._flushedTexts) this._flushedTexts = new Map();
192
+ this._flushedTexts.set(sessionId, (this._flushedTexts.get(sessionId) || '') + out);
193
+ this._flushedOffsets.set(sessionId, (this._flushedOffsets.get(sessionId) || 0) + out.length);
194
+ }
195
+ }
196
+ },
197
+
50
198
  // Called from customKeyEventHandler in terminal-ui.js on Ctrl+V keydown.
51
199
  // Creates a hidden paste trap, lets the browser paste into it, then inspects
52
200
  // the result for images. Works on plain HTTP (no Clipboard API needed).
@@ -82,15 +230,13 @@ Object.assign(CodemanApp.prototype, {
82
230
  if (pasteConsumed) return;
83
231
  pasteConsumed = true;
84
232
 
85
- // Check for images in clipboard items
233
+ // Check for images or videos in clipboard items
86
234
  var imageFiles = [];
87
235
  var items = e.clipboardData && e.clipboardData.items;
88
236
  if (items) {
89
237
  for (var i = 0; i < items.length; i++) {
90
- if (items[i].type.startsWith('image/')) {
91
- var blob = items[i].getAsFile();
92
- if (blob) imageFiles.push(blob);
93
- }
238
+ var blob = items[i].getAsFile();
239
+ if (blob && self._promptAttachKind(blob)) imageFiles.push(blob);
94
240
  }
95
241
  }
96
242
 
@@ -120,6 +266,21 @@ Object.assign(CodemanApp.prototype, {
120
266
  document.execCommand('paste');
121
267
  },
122
268
 
269
+ /** What a pick or a paste may upload: 'image', or 'video' for the camera
270
+ * roll's clips (the server streams those to disk under their own cap), else
271
+ * null. The one filter behind Attach, drop, the Compose and paste-dialog
272
+ * pickers and the clipboard handlers. The MIME type decides; some Android
273
+ * file managers hand over a File with none, so the name's extension is the
274
+ * fallback, mirroring the server's own allowlists. */
275
+ _promptAttachKind(file) {
276
+ if (file.type.startsWith('image/')) return 'image';
277
+ if (file.type.startsWith('video/')) return 'video';
278
+ const ext = (file.name || '').toLowerCase().match(/\.([a-z0-9]+)$/)?.[1] || '';
279
+ if (['png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp', 'heic', 'heif'].includes(ext)) return 'image';
280
+ if (['mp4', 'webm', 'mov', 'm4v', 'ogv'].includes(ext)) return 'video';
281
+ return null;
282
+ },
283
+
123
284
  // Max images accepted in one batch (paste / drop / mobile picker). Each is
124
285
  // uploaded as its own request, so 20 stays under the server's 30 uploads/min
125
286
  // rate limit while covering "select a bunch of photos at once".
@@ -136,6 +297,7 @@ Object.assign(CodemanApp.prototype, {
136
297
  async _uploadAndInsertImages(fileList, options = {}) {
137
298
  const sessionId = options.sessionId || this.activeSessionId;
138
299
  if (!sessionId) return [];
300
+ if (options.sessionId && this.sessions && !this.sessions.has(sessionId)) return [];
139
301
 
140
302
  let files = Array.from(fileList || []);
141
303
  if (files.length === 0) return [];
@@ -153,7 +315,7 @@ Object.assign(CodemanApp.prototype, {
153
315
  let failReason = ''; // first server reason, shown in the toast so a failure is not just a count
154
316
  const results = new Array(total); // preserve selection order for insertion
155
317
  const progress = () =>
156
- this.showToast(`Uploading ${Math.min(done + 1, total)}/${total} image${total > 1 ? 's' : ''}…`, 'info');
318
+ this.showToast(`Uploading ${Math.min(done + 1, total)}/${total} file${total > 1 ? 's' : ''}…`, 'info');
157
319
  progress();
158
320
 
159
321
  // Bounded-concurrency worker pool over the file list.
@@ -197,11 +359,11 @@ Object.assign(CodemanApp.prototype, {
197
359
 
198
360
  // Final status: successes, plus any failures / cap so nothing is silent.
199
361
  const parts = [];
200
- if (paths.length > 0) parts.push(`${paths.length} image${paths.length > 1 ? 's' : ''} ready`);
362
+ if (paths.length > 0) parts.push(`${paths.length} file${paths.length > 1 ? 's' : ''} ready`);
201
363
  if (failed > 0) parts.push(failReason ? `${failed} failed: ${failReason}` : `${failed} failed`);
202
364
  if (capped) parts.push(`max ${this._maxBatchImages} per batch`);
203
365
  const tone = paths.length > 0 ? (failed > 0 || capped ? 'info' : 'success') : 'error';
204
- this.showToast(parts.join(' · ') || 'No images uploaded', tone);
366
+ this.showToast(parts.join(' · ') || 'No files uploaded', tone);
205
367
  return paths;
206
368
  },
207
369
 
@@ -209,7 +371,10 @@ Object.assign(CodemanApp.prototype, {
209
371
  const form = new FormData();
210
372
  form.append('image', file);
211
373
 
212
- const resp = await fetch('/api/sessions/' + sessionId + '/paste-image', {
374
+ // A video takes the server's disk-streamed path and its larger cap; the flag
375
+ // has to ride the URL because the size budget is fixed before the part is read.
376
+ const kind = this._promptAttachKind(file) === 'video' ? '?kind=video' : '';
377
+ const resp = await fetch('/api/sessions/' + sessionId + '/paste-image' + kind, {
213
378
  method: 'POST',
214
379
  body: form,
215
380
  });
@@ -231,9 +396,14 @@ Object.assign(CodemanApp.prototype, {
231
396
  // decode/encode failure the original file is returned unchanged so the server
232
397
  // still gets a chance (and logs a precise diagnostic).
233
398
  async _normalizeImageForUpload(file) {
234
- if (file.type === 'image/gif') return file;
235
-
236
- const toPng = file.type === 'image/png';
399
+ // Only an image goes through the canvas; a video is uploaded as picked. With no
400
+ // MIME (some Android file managers) the name's extension stands in, as it does in
401
+ // _promptAttachKind, so a .gif keeps its frames and a .png its transparency.
402
+ const name = (file.name || '').toLowerCase();
403
+ const isGif = file.type === 'image/gif' || (!file.type && name.endsWith('.gif'));
404
+ if (this._promptAttachKind(file) !== 'image' || isGif) return file;
405
+
406
+ const toPng = file.type === 'image/png' || (!file.type && name.endsWith('.png'));
237
407
  const url = URL.createObjectURL(file);
238
408
  try {
239
409
  const img = new Image();
@@ -16,8 +16,8 @@
16
16
  <link rel="manifest" href="manifest.json">
17
17
  <title>Codeman</title>
18
18
  <link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' stop-color='%2360a5fa'/%3E%3Cstop offset='100%25' stop-color='%233b82f6'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='32' height='32' rx='6' fill='%230a0a0a'/%3E%3Cpath d='M18 4L8 18h6l-2 10 10-14h-6z' fill='url(%23g)'/%3E%3C/svg%3E">
19
- <link rel="stylesheet" href="styles.81a72906.css">
20
- <link rel="stylesheet" href="mobile.111b187d.css" media="(max-width: 1023px)">
19
+ <link rel="stylesheet" href="styles.900a28e4.css">
20
+ <link rel="stylesheet" href="mobile.2dff1580.css" media="(max-width: 1023px)">
21
21
  <!-- xterm.css loaded async — terminal won't display until xterm.js runs anyway -->
22
22
  <link rel="preload" href="vendor/xterm.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
23
23
  <noscript><link rel="stylesheet" href="vendor/xterm.css"></noscript>
@@ -27,8 +27,8 @@
27
27
  <!-- Symbols font before the terminal first paints — a late-loading icon font
28
28
  leaves tofu in xterm's glyph atlas until something forces a re-render. -->
29
29
  <link rel="preload" href="fonts/symbols-nerd-font-mono.woff2" as="font" type="font/woff2" crossorigin>
30
- <link rel="preload" href="constants.bc9c1f63.js" as="script">
31
- <link rel="preload" href="app.6f8552e1.js" as="script">
30
+ <link rel="preload" href="constants.17d77b65.js" as="script">
31
+ <link rel="preload" href="app.24689381.js" as="script">
32
32
  <!-- Self-hosted xterm.js — eliminates CDN DNS/TLS latency (~100ms).
33
33
  'defer' preserves execution order (xterm loads before fit addon). -->
34
34
  <script defer src="vendor/xterm.min.js"></script>
@@ -614,7 +614,11 @@
614
614
  <div class="file-preview-header">
615
615
  <span class="file-preview-title" id="filePreviewTitle">file.ts</span>
616
616
  <div class="file-preview-actions">
617
+ <button class="btn-icon-sm" id="filePreviewPrevBtn" onclick="app.stepFolderPreview(-1)" title="Previous file in folder (←)" aria-label="Previous file in folder" hidden>&lsaquo;</button>
618
+ <button class="btn-icon-sm" id="filePreviewNextBtn" onclick="app.stepFolderPreview(1)" title="Next file in folder (→)" aria-label="Next file in folder" hidden>&rsaquo;</button>
619
+ <button class="btn-icon-sm" id="filePreviewFolderBtn" onclick="app.openFolderOfPreview()" title="Show folder" aria-label="Show folder" hidden><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg></button>
617
620
  <button class="btn-icon-sm file-preview-pill" id="filePreviewMdBtn" onclick="app.toggleFilePreviewMd()" title="Rendered markdown" aria-label="Rendered markdown" aria-pressed="true" hidden>MD</button>
621
+ <button class="btn-icon-sm file-preview-pill" id="filePreviewPageBtn" onclick="app.toggleFilePreviewPage()" title="Rendered page" aria-label="Rendered page" aria-pressed="true" hidden>Page</button>
618
622
  <button class="btn-icon-sm" id="filePreviewLinesBtn" onclick="app.toggleFilePreviewLines()" title="Line numbers" aria-label="Line numbers" aria-pressed="false" hidden><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="10" y1="6" x2="21" y2="6"/><line x1="10" y1="12" x2="21" y2="12"/><line x1="10" y1="18" x2="21" y2="18"/><path d="M4 6h1v4"/><path d="M4 10h2"/><path d="M6 18H4c0-1 2-2 2-3s-1-1.5-2-1"/></svg></button>
619
623
  <button class="btn-icon-sm" id="filePreviewWrapBtn" onclick="app.toggleFilePreviewWrap()" title="Wrap lines" aria-label="Wrap lines" aria-pressed="true" hidden><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="3" y1="6" x2="21" y2="6"/><path d="M3 12h15a3 3 0 1 1 0 6h-4"/><polyline points="16 16 14 18 16 20"/><line x1="3" y1="18" x2="10" y2="18"/></svg></button>
620
624
  <button class="btn-icon-sm file-preview-edit-btn" id="filePreviewEditBtn" onclick="app.enterFilePreviewEdit()" title="Edit file" aria-label="Edit file" hidden><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5z"/></svg></button>
@@ -634,6 +638,22 @@
634
638
  </div>
635
639
  </div>
636
640
 
641
+ <!-- Folder Grid: every previewable file of one folder (panels-ui.js openFolderGrid) -->
642
+ <div class="folder-grid-overlay" id="folderGridOverlay">
643
+ <div class="folder-grid-window">
644
+ <div class="file-preview-header">
645
+ <span class="file-preview-title" id="folderGridTitle" data-i18n-skip></span>
646
+ <div class="file-preview-actions">
647
+ <button class="btn-icon-sm" id="folderGridUpBtn" onclick="app.folderGridUp()" title="Parent folder" aria-label="Parent folder">&uarr;</button>
648
+ <button class="btn-icon-sm" onclick="app.closeFolderGrid()" title="Close" aria-label="Close">&times;</button>
649
+ </div>
650
+ </div>
651
+ <div class="folder-grid-folders" id="folderGridFolders" data-i18n-skip></div>
652
+ <div class="folder-grid-body" id="folderGridBody" data-i18n-skip></div>
653
+ <div class="file-preview-footer" id="folderGridFooter" data-i18n-skip></div>
654
+ </div>
655
+ </div>
656
+
637
657
  <!-- Response Viewer (mobile: native-scroll overlay for reading full Claude responses) -->
638
658
  <div class="response-viewer" id="responseViewer">
639
659
  <div class="response-viewer-header">
@@ -757,6 +777,21 @@
757
777
  </div>
758
778
  </div>
759
779
  <!-- Mobile-only case button + gear -->
780
+ <!-- Phone-only Attach (wired in image-input.js): the button opens its own file input,
781
+ which sits invisibly over the button so iOS anchors the picker popover to it. The
782
+ Compose dialog is deliberately not involved: it focuses its textarea, which raises
783
+ the keyboard under the picker sheet. Sits BEFORE the mic so the two share order: 5
784
+ and source order keeps Attach on the left. -->
785
+ <span class="btn-attach-mobile-wrap">
786
+ <button type="button" class="btn-attach-mobile" id="attachBtnMobile" title="Attach" aria-label="Attach">
787
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
788
+ <rect x="3" y="3" width="18" height="18" rx="2"/>
789
+ <circle cx="8.5" cy="8.5" r="1.5"/>
790
+ <path d="m21 15-5-5L5 21"/>
791
+ </svg>
792
+ </button>
793
+ <input type="file" class="btn-attach-mobile-input" id="attachFileInput" accept="image/*,video/*" multiple tabindex="-1" aria-hidden="true">
794
+ </span>
760
795
  <button class="btn-voice-mobile" id="voiceInputBtnMobile" onclick="VoiceInput.toggle()"
761
796
  title="Voice input" aria-label="Start voice input" aria-pressed="false">
762
797
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
@@ -860,6 +895,7 @@
860
895
  <div><kbd>Alt/Option</kbd>+<kbd>Shift</kbd>+<kbd>Arrows</kbd></div><div>Focus Tile Left / Right / Up / Down</div>
861
896
  <div><kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>Arrows</kbd></div><div>Move Tile Left / Right / Up / Down</div>
862
897
  <div><kbd>Drag</kbd> a tile's header</div><div>Move the Tile (onto Another: Swap)</div>
898
+ <div><kbd>Drag</kbd> a tile's header out of the window</div><div>Open the Tile in Its Own Window (Detach Tiles)</div>
863
899
  <div><kbd>Alt/Option</kbd>+<kbd>Shift</kbd>+<kbd>Enter</kbd></div><div>Zoom Focused Tile</div>
864
900
  <div><kbd>Ctrl/Cmd</kbd>+<kbd>Click</kbd> a tab</div><div>Add the Session to the Tile Grid</div>
865
901
  <div><kbd>Right-click</kbd> the Tiles button</div><div>Choose How Many Tiles (2, 4 or 6)</div>
@@ -1286,6 +1322,22 @@
1286
1322
  <!-- Hidden wholesale for non-claude sessions and for a session with
1287
1323
  no live process (session-ui.js: openSessionOptions). -->
1288
1324
  <div id="sessionRespawnSection">
1325
+ <!-- Claude Code (v2.1.251+) reports the main conversation's prompt-cache
1326
+ state on the statusline; the exporter keeps it per session and this
1327
+ row shows it live (session-ui.js: updatePromptCacheStatus). -->
1328
+ <div class="set-group">
1329
+ <div class="set-group-head"><h4>Prompt cache</h4></div>
1330
+ <p class="set-group-hint">While the cached conversation is warm, a turn re-reads it at the cheap cached rate. Once it sits idle past the cache lifetime, the next turn re-processes the whole context at the cache-write rate.</p>
1331
+ <div class="set-group-body">
1332
+ <div class="set-row">
1333
+ <div class="set-row-text">
1334
+ <span class="set-row-label">Status</span>
1335
+ <span class="set-row-desc" id="promptCacheStatus" data-i18n-skip>Not reported yet.</span>
1336
+ </div>
1337
+ </div>
1338
+ </div>
1339
+ </div>
1340
+
1289
1341
  <div class="set-group">
1290
1342
  <div class="set-group-head"><h4>Loop control</h4></div>
1291
1343
  <div class="set-group-body">
@@ -1407,6 +1459,16 @@
1407
1459
  <input type="text" id="modalSessionName" class="set-input" maxlength="128" placeholder="Auto (directory name)" onblur="app.saveSessionName()">
1408
1460
  </div>
1409
1461
  </div>
1462
+ <div class="set-row set-row-block">
1463
+ <div class="set-row-text">
1464
+ <span class="set-row-label">Working folder</span>
1465
+ <span class="set-row-desc" id="modalSessionPathDesc">Where this session's CLI runs, on the machine hosting Codeman.</span>
1466
+ </div>
1467
+ <div class="set-row-actions">
1468
+ <code id="modalSessionPath" class="set-static mono" style="flex: 1; min-width: 0; word-break: break-all; user-select: all;"></code>
1469
+ <button type="button" class="btn-toolbar btn-sm" id="modalSessionPathCopy" onclick="app.copySessionPath()">Copy</button>
1470
+ </div>
1471
+ </div>
1410
1472
  <div class="set-row set-row-block">
1411
1473
  <div class="set-row-text">
1412
1474
  <span class="set-row-label">Session color</span>
@@ -2233,6 +2295,13 @@
2233
2295
  </div>
2234
2296
  <label class="switch switch-sm"><input type="checkbox" id="appSettingsShowTabDetachButton"><span class="slider"></span></label>
2235
2297
  </div>
2298
+ <div class="set-row" id="appSettingsTileDetachItem" data-search="detach tiles tile pop out window drag move another window">
2299
+ <div class="set-row-text">
2300
+ <span class="set-row-label">Detach Tiles <span class="set-tag">desktop</span> <span class="set-tag set-tag-beta">beta</span></span>
2301
+ <span class="set-row-desc">Drag a tile's header out of the browser to open it in its own window, or onto another Codeman window's tiles to move it there. A popped-out window's title drags back onto tiles.</span>
2302
+ </div>
2303
+ <label class="switch switch-sm"><input type="checkbox" id="appSettingsTileDetach"><span class="slider"></span></label>
2304
+ </div>
2236
2305
  <div class="set-row" id="appSettingsLineageLinesItem" data-search="lineage lines spawned worker parent connection">
2237
2306
  <div class="set-row-text">
2238
2307
  <span class="set-row-label">Spawn Lineage Lines <span class="set-tag">desktop</span></span>
@@ -2247,6 +2316,13 @@
2247
2316
  </div>
2248
2317
  <label class="switch switch-sm"><input type="checkbox" id="appSettingsAutoNameSessions"><span class="slider"></span></label>
2249
2318
  </div>
2319
+ <div class="set-row" id="appSettingsSpawnedTabsFollowParentItem" data-search="spawned child session follow parent tab group lineage move">
2320
+ <div class="set-row-text">
2321
+ <span class="set-row-label">Spawned Tabs Follow Their Parent <span class="set-tag">synced</span></span>
2322
+ <span class="set-row-desc">A session started by another one keeps following its parent's tab into its group and moves with it, until you move it by hand. Off: it starts next to its parent and stays where it is.</span>
2323
+ </div>
2324
+ <label class="switch switch-sm"><input type="checkbox" id="appSettingsSpawnedTabsFollowParent"><span class="slider"></span></label>
2325
+ </div>
2250
2326
  <div class="set-row" id="appSettingsMobileOverviewItem" data-search="overview home screen phone logo">
2251
2327
  <div class="set-row-text">
2252
2328
  <span class="set-row-label">Overview Home Screen <span class="set-tag">phone</span></span>
@@ -2597,6 +2673,19 @@
2597
2673
  </div>
2598
2674
  </div>
2599
2675
 
2676
+ <div class="set-group" id="agentSkillGroup">
2677
+ <div class="set-group-head"><h4>All agent CLIs</h4><span class="set-scope">synced</span></div>
2678
+ <div class="set-group-body">
2679
+ <div class="set-row" data-search="agent skill codeman api workers codex gemini opencode pi grok deepseek omp antigravity">
2680
+ <div class="set-row-text">
2681
+ <span class="set-row-label">Codeman Agent Skill</span>
2682
+ <span class="set-row-desc">Write Codeman's agent skill into each new session's folder, where that CLI looks for skills, so the agent in it can start workers in any installed CLI, send them prompts and wait for results. Every CLI except Pi and a plain shell.</span>
2683
+ </div>
2684
+ <label class="switch switch-sm"><input type="checkbox" id="appSettingsAgentSkill"><span class="slider"></span></label>
2685
+ </div>
2686
+ </div>
2687
+ </div>
2688
+
2600
2689
  <div class="set-group">
2601
2690
  <div class="set-group-head"><h4>Claude</h4><span class="set-scope">synced</span></div>
2602
2691
  <div class="set-group-body">
@@ -2633,13 +2722,6 @@
2633
2722
  </div>
2634
2723
  <label class="switch switch-sm"><input type="checkbox" id="appSettingsAgentTeams"><span class="slider"></span></label>
2635
2724
  </div>
2636
- <div class="set-row" data-search="agent skill codeman api workers">
2637
- <div class="set-row-text">
2638
- <span class="set-row-label">Codeman Agent Skill</span>
2639
- <span class="set-row-desc">Let new sessions start workers, send prompts and wait for results over the API.</span>
2640
- </div>
2641
- <label class="switch switch-sm"><input type="checkbox" id="appSettingsAgentSkill"><span class="slider"></span></label>
2642
- </div>
2643
2725
  <div class="set-row" data-search="workspace hooks alerts approvals notifications settings.local.json">
2644
2726
  <div class="set-row-text">
2645
2727
  <span class="set-row-label">Workspace Hooks</span>
@@ -4089,12 +4171,12 @@
4089
4171
  <div class="approvals-list"></div>
4090
4172
  </div>
4091
4173
 
4092
- <script defer src="constants.bc9c1f63.js"></script>
4093
- <script defer src="i18n.a9840ea2.js"></script>
4174
+ <script defer src="constants.17d77b65.js"></script>
4175
+ <script defer src="i18n.9afd8e4d.js"></script>
4094
4176
  <script defer src="mobile-handlers.0ef9c311.js"></script>
4095
4177
  <script defer src="voice-input.2342c37b.js"></script>
4096
4178
  <script defer src="notification-manager.feb82c04.js"></script>
4097
- <script defer src="keyboard-accessory.c1e7c7b7.js"></script>
4179
+ <script defer src="keyboard-accessory.c458eea8.js"></script>
4098
4180
  <script defer src="input-cjk.8bc46081.js"></script>
4099
4181
  <!-- Shows iOS Safari IME composition text inside the terminal. Must precede terminal-ui.js. -->
4100
4182
  <script defer src="mobile-ime-preview.2dbaf262.js"></script>
@@ -4103,10 +4185,10 @@
4103
4185
  <!-- Hardened markdown HTML sanitizer (wires DOMPurify). Must precede app.js. -->
4104
4186
  <script defer src="sanitize-html.e7147e76.js"></script>
4105
4187
  <!-- Owner tab layout projection (grouped vertical rail); pure, read by app.js. -->
4106
- <script defer src="tab-layout-browser.065fcba4.js"></script>
4107
- <script defer src="app.6f8552e1.js"></script>
4108
- <script defer src="tab-rail-resize.a5867793.js"></script>
4109
- <script defer src="terminal-ui.b7267005.js"></script>
4188
+ <script defer src="tab-layout-browser.2d6189f7.js"></script>
4189
+ <script defer src="app.24689381.js"></script>
4190
+ <script defer src="tab-rail-resize.7157e0bd.js"></script>
4191
+ <script defer src="terminal-ui.858f5da5.js"></script>
4110
4192
  <script defer src="terminal-tile.js"></script>
4111
4193
  <script defer src="terminal-split.js"></script>
4112
4194
  <script defer src="tile-grid.js"></script>
@@ -4114,14 +4196,14 @@
4114
4196
  <script defer src="ralph-panel.b350e2ef.js"></script>
4115
4197
  <script defer src="orchestrator-panel.js"></script>
4116
4198
  <script defer src="cron-ui.js"></script>
4117
- <script defer src="settings-ui.4e33390f.js"></script>
4118
- <script defer src="panels-ui.71846fcb.js"></script>
4199
+ <script defer src="settings-ui.81ac264e.js"></script>
4200
+ <script defer src="panels-ui.73ec0137.js"></script>
4119
4201
  <script defer src="readmymind-ui.js"></script>
4120
4202
  <script defer src="ultracode-panel.js"></script>
4121
4203
  <script defer src="approvals-ui.js"></script>
4122
4204
  <script defer src="reboot-restore-ui.js"></script>
4123
4205
  <script defer src="admin-ui.js"></script>
4124
- <script defer src="session-ui.f731602c.js"></script>
4206
+ <script defer src="session-ui.dbc6819b.js"></script>
4125
4207
  <script defer src="host-wake-ui.js"></script>
4126
4208
  <script defer src="webview-tabs.js"></script>
4127
4209
  <script defer src="mobile-overview.js"></script>
@@ -4133,7 +4215,7 @@
4133
4215
  <script defer src="subagent-windows.07277a58.js"></script>
4134
4216
  <script defer src="ultracode-windows.js"></script>
4135
4217
  <script defer src="session-lineage.js"></script>
4136
- <script defer src="image-input.e00471c6.js"></script>
4137
- <script defer src="spreadsheet-preview.af82fa2f.js"></script>
4218
+ <script defer src="image-input.9ed1d0d2.js"></script>
4219
+ <script defer src="spreadsheet-preview.75c751de.js"></script>
4138
4220
  </body>
4139
4221
  </html>
Binary file
Binary file