aicodeman 1.47.0 → 1.49.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 (361) hide show
  1. package/README.md +15 -3
  2. package/dist/cli-notify.d.ts +34 -0
  3. package/dist/cli-notify.d.ts.map +1 -0
  4. package/dist/cli-notify.js +100 -0
  5. package/dist/cli-notify.js.map +1 -0
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +16 -0
  8. package/dist/cli.js.map +1 -1
  9. package/dist/config/cli-registry/patterns.d.ts +1 -1
  10. package/dist/config/cli-registry/patterns.d.ts.map +1 -1
  11. package/dist/config/cli-registry/patterns.js +4 -0
  12. package/dist/config/cli-registry/patterns.js.map +1 -1
  13. package/dist/config/cli-registry/schema.d.ts +7 -0
  14. package/dist/config/cli-registry/schema.d.ts.map +1 -1
  15. package/dist/config/cli-registry/schema.js +67 -3
  16. package/dist/config/cli-registry/schema.js.map +1 -1
  17. package/dist/config/cli-registry/stock.d.ts.map +1 -1
  18. package/dist/config/cli-registry/stock.js +114 -31
  19. package/dist/config/cli-registry/stock.js.map +1 -1
  20. package/dist/config/cli-registry/types.d.ts +51 -21
  21. package/dist/config/cli-registry/types.d.ts.map +1 -1
  22. package/dist/config/foreign-tmux.d.ts +48 -0
  23. package/dist/config/foreign-tmux.d.ts.map +1 -0
  24. package/dist/config/foreign-tmux.js +48 -0
  25. package/dist/config/foreign-tmux.js.map +1 -0
  26. package/dist/config/path-probe.d.ts +6 -0
  27. package/dist/config/path-probe.d.ts.map +1 -1
  28. package/dist/config/path-probe.js +6 -0
  29. package/dist/config/path-probe.js.map +1 -1
  30. package/dist/config-backup.d.ts +1 -1
  31. package/dist/config-backup.d.ts.map +1 -1
  32. package/dist/config-backup.js +2 -0
  33. package/dist/config-backup.js.map +1 -1
  34. package/dist/custom-model-hosts.d.ts +32 -8
  35. package/dist/custom-model-hosts.d.ts.map +1 -1
  36. package/dist/custom-model-hosts.js +10 -0
  37. package/dist/custom-model-hosts.js.map +1 -1
  38. package/dist/custom-model-injection-apply.d.ts +21 -5
  39. package/dist/custom-model-injection-apply.d.ts.map +1 -1
  40. package/dist/custom-model-injection-apply.js +121 -27
  41. package/dist/custom-model-injection-apply.js.map +1 -1
  42. package/dist/custom-model-injection.d.ts +6 -0
  43. package/dist/custom-model-injection.d.ts.map +1 -1
  44. package/dist/custom-model-injection.js +92 -10
  45. package/dist/custom-model-injection.js.map +1 -1
  46. package/dist/discord-bridge.d.ts +338 -0
  47. package/dist/discord-bridge.d.ts.map +1 -0
  48. package/dist/discord-bridge.js +1256 -0
  49. package/dist/discord-bridge.js.map +1 -0
  50. package/dist/docker-hosts.d.ts +8 -1
  51. package/dist/docker-hosts.d.ts.map +1 -1
  52. package/dist/docker-hosts.js +5 -5
  53. package/dist/docker-hosts.js.map +1 -1
  54. package/dist/edit-history-local-source.d.ts +83 -0
  55. package/dist/edit-history-local-source.d.ts.map +1 -0
  56. package/dist/edit-history-local-source.js +198 -0
  57. package/dist/edit-history-local-source.js.map +1 -0
  58. package/dist/edit-history-parse.d.ts +114 -0
  59. package/dist/edit-history-parse.d.ts.map +1 -0
  60. package/dist/edit-history-parse.js +312 -0
  61. package/dist/edit-history-parse.js.map +1 -0
  62. package/dist/edit-history-store.d.ts +209 -0
  63. package/dist/edit-history-store.d.ts.map +1 -0
  64. package/dist/edit-history-store.js +599 -0
  65. package/dist/edit-history-store.js.map +1 -0
  66. package/dist/foreign-tmux-discovery.d.ts +140 -0
  67. package/dist/foreign-tmux-discovery.d.ts.map +1 -0
  68. package/dist/foreign-tmux-discovery.js +366 -0
  69. package/dist/foreign-tmux-discovery.js.map +1 -0
  70. package/dist/foreign-tmux.d.ts +381 -0
  71. package/dist/foreign-tmux.d.ts.map +1 -0
  72. package/dist/foreign-tmux.js +775 -0
  73. package/dist/foreign-tmux.js.map +1 -0
  74. package/dist/git-clone.d.ts +6 -1
  75. package/dist/git-clone.d.ts.map +1 -1
  76. package/dist/git-clone.js +79 -14
  77. package/dist/git-clone.js.map +1 -1
  78. package/dist/git-fetch-scheduler.d.ts +2 -0
  79. package/dist/git-fetch-scheduler.d.ts.map +1 -1
  80. package/dist/git-fetch-scheduler.js +1 -1
  81. package/dist/git-fetch-scheduler.js.map +1 -1
  82. package/dist/git-fetch.d.ts +1 -1
  83. package/dist/git-fetch.d.ts.map +1 -1
  84. package/dist/git-workspace-status.d.ts +2 -2
  85. package/dist/git-workspace-status.d.ts.map +1 -1
  86. package/dist/hooks-config.d.ts +2 -1
  87. package/dist/hooks-config.d.ts.map +1 -1
  88. package/dist/hooks-config.js +2 -1
  89. package/dist/hooks-config.js.map +1 -1
  90. package/dist/image-watcher.js +4 -4
  91. package/dist/image-watcher.js.map +1 -1
  92. package/dist/live-worktree.d.ts +53 -0
  93. package/dist/live-worktree.d.ts.map +1 -0
  94. package/dist/live-worktree.js +97 -0
  95. package/dist/live-worktree.js.map +1 -0
  96. package/dist/mux-interface.d.ts +22 -2
  97. package/dist/mux-interface.d.ts.map +1 -1
  98. package/dist/reboot-restore.d.ts +1 -1
  99. package/dist/reboot-restore.d.ts.map +1 -1
  100. package/dist/reboot-restore.js +9 -0
  101. package/dist/reboot-restore.js.map +1 -1
  102. package/dist/remote-files.d.ts +150 -5
  103. package/dist/remote-files.d.ts.map +1 -1
  104. package/dist/remote-files.js +505 -7
  105. package/dist/remote-files.js.map +1 -1
  106. package/dist/remote-hosts.d.ts +15 -9
  107. package/dist/remote-hosts.d.ts.map +1 -1
  108. package/dist/remote-hosts.js +42 -20
  109. package/dist/remote-hosts.js.map +1 -1
  110. package/dist/session-cli-registry-bridge.d.ts.map +1 -1
  111. package/dist/session-cli-registry-bridge.js +23 -8
  112. package/dist/session-cli-registry-bridge.js.map +1 -1
  113. package/dist/session-env-clamp.d.ts +1 -1
  114. package/dist/session-env-clamp.d.ts.map +1 -1
  115. package/dist/session-env-clamp.js +4 -3
  116. package/dist/session-env-clamp.js.map +1 -1
  117. package/dist/session.d.ts +161 -24
  118. package/dist/session.d.ts.map +1 -1
  119. package/dist/session.js +337 -37
  120. package/dist/session.js.map +1 -1
  121. package/dist/tmux-manager.d.ts +114 -12
  122. package/dist/tmux-manager.d.ts.map +1 -1
  123. package/dist/tmux-manager.js +318 -41
  124. package/dist/tmux-manager.js.map +1 -1
  125. package/dist/types/discord.d.ts +55 -0
  126. package/dist/types/discord.d.ts.map +1 -0
  127. package/dist/types/discord.js +18 -0
  128. package/dist/types/discord.js.map +1 -0
  129. package/dist/types/foreign-tmux.d.ts +101 -0
  130. package/dist/types/foreign-tmux.d.ts.map +1 -0
  131. package/dist/types/foreign-tmux.js +27 -0
  132. package/dist/types/foreign-tmux.js.map +1 -0
  133. package/dist/types/index.d.ts +3 -0
  134. package/dist/types/index.d.ts.map +1 -1
  135. package/dist/types/index.js +3 -0
  136. package/dist/types/index.js.map +1 -1
  137. package/dist/types/session.d.ts +119 -2
  138. package/dist/types/session.d.ts.map +1 -1
  139. package/dist/types/session.js.map +1 -1
  140. package/dist/types/update.d.ts +4 -1
  141. package/dist/types/update.d.ts.map +1 -1
  142. package/dist/web/case-path.d.ts +2 -0
  143. package/dist/web/case-path.d.ts.map +1 -1
  144. package/dist/web/case-path.js +3 -3
  145. package/dist/web/case-path.js.map +1 -1
  146. package/dist/web/edit-history-sync.d.ts +178 -0
  147. package/dist/web/edit-history-sync.d.ts.map +1 -0
  148. package/dist/web/edit-history-sync.js +461 -0
  149. package/dist/web/edit-history-sync.js.map +1 -0
  150. package/dist/web/launch-defaults.d.ts +1 -1
  151. package/dist/web/launch-defaults.js +2 -2
  152. package/dist/web/launch-defaults.js.map +1 -1
  153. package/dist/web/linked-cases-writer.d.ts +43 -0
  154. package/dist/web/linked-cases-writer.d.ts.map +1 -0
  155. package/dist/web/linked-cases-writer.js +107 -0
  156. package/dist/web/linked-cases-writer.js.map +1 -0
  157. package/dist/web/paste-image-gc.d.ts +10 -1
  158. package/dist/web/paste-image-gc.d.ts.map +1 -1
  159. package/dist/web/paste-image-gc.js +10 -2
  160. package/dist/web/paste-image-gc.js.map +1 -1
  161. package/dist/web/ports/session-port.d.ts +13 -0
  162. package/dist/web/ports/session-port.d.ts.map +1 -1
  163. package/dist/web/public/admin-ui.js.gz +0 -0
  164. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  165. package/dist/web/public/{app.fe8a6b4c.js → app.9ea37070.js} +6 -6
  166. package/dist/web/public/app.9ea37070.js.br +0 -0
  167. package/dist/web/public/app.9ea37070.js.gz +0 -0
  168. package/dist/web/public/approvals-ui.js.gz +0 -0
  169. package/dist/web/public/{constants.affcdde6.js → constants.782170e1.js} +114 -25
  170. package/dist/web/public/constants.782170e1.js.br +0 -0
  171. package/dist/web/public/constants.782170e1.js.gz +0 -0
  172. package/dist/web/public/cron-ui.js.gz +0 -0
  173. package/dist/web/public/edit-history-ui.js +643 -0
  174. package/dist/web/public/edit-history-ui.js.br +0 -0
  175. package/dist/web/public/edit-history-ui.js.gz +0 -0
  176. package/dist/web/public/entrance-animations.js.gz +0 -0
  177. package/dist/web/public/foreign-sessions.js +476 -0
  178. package/dist/web/public/foreign-sessions.js.br +0 -0
  179. package/dist/web/public/foreign-sessions.js.gz +0 -0
  180. package/dist/web/public/git-status-ui.js +5 -3
  181. package/dist/web/public/git-status-ui.js.br +0 -0
  182. package/dist/web/public/git-status-ui.js.gz +0 -0
  183. package/dist/web/public/header-divider.eb2d2463.js.gz +0 -0
  184. package/dist/web/public/home-sessions.js.gz +0 -0
  185. package/dist/web/public/host-wake-ui.js.gz +0 -0
  186. package/dist/web/public/i18n.a5e82c95.js +2 -0
  187. package/dist/web/public/i18n.a5e82c95.js.br +0 -0
  188. package/dist/web/public/i18n.a5e82c95.js.gz +0 -0
  189. package/dist/web/public/image-input.a5d0f8f3.js.gz +0 -0
  190. package/dist/web/public/index.html +183 -18
  191. package/dist/web/public/index.html.br +0 -0
  192. package/dist/web/public/index.html.gz +0 -0
  193. package/dist/web/public/input-cjk.8bc46081.js.gz +0 -0
  194. package/dist/web/public/{keyboard-accessory.ff64fafc.js → keyboard-accessory.b941676c.js} +75 -2
  195. package/dist/web/public/keyboard-accessory.b941676c.js.br +0 -0
  196. package/dist/web/public/keyboard-accessory.b941676c.js.gz +0 -0
  197. package/dist/web/public/mobile-handlers.f7c3f692.js.gz +0 -0
  198. package/dist/web/public/mobile-ime-preview.2dbaf262.js.gz +0 -0
  199. package/dist/web/public/mobile-overview.js +10 -0
  200. package/dist/web/public/mobile-overview.js.br +0 -0
  201. package/dist/web/public/mobile-overview.js.gz +0 -0
  202. package/dist/web/public/mobile.67c8eb5b.css +1 -0
  203. package/dist/web/public/mobile.67c8eb5b.css.br +0 -0
  204. package/dist/web/public/mobile.67c8eb5b.css.gz +0 -0
  205. package/dist/web/public/notification-manager.feb82c04.js.gz +0 -0
  206. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  207. package/dist/web/public/{panels-ui.4a56ee63.js → panels-ui.8bcd4473.js} +5 -5
  208. package/dist/web/public/panels-ui.8bcd4473.js.br +0 -0
  209. package/dist/web/public/panels-ui.8bcd4473.js.gz +0 -0
  210. package/dist/web/public/ralph-panel.b350e2ef.js.gz +0 -0
  211. package/dist/web/public/ralph-wizard.b6c0dd19.js.gz +0 -0
  212. package/dist/web/public/readmymind-ui.js.gz +0 -0
  213. package/dist/web/public/reboot-restore-ui.js.gz +0 -0
  214. package/dist/web/public/{respawn-ui.d7adcf6a.js → respawn-ui.304ba0d4.js} +1 -1
  215. package/dist/web/public/respawn-ui.304ba0d4.js.br +0 -0
  216. package/dist/web/public/respawn-ui.304ba0d4.js.gz +0 -0
  217. package/dist/web/public/sanitize-html.e7147e76.js.gz +0 -0
  218. package/dist/web/public/security-notices.json +1 -21
  219. package/dist/web/public/session-lineage.js.gz +0 -0
  220. package/dist/web/public/{session-ui.426fb671.js → session-ui.a7c8c095.js} +30 -30
  221. package/dist/web/public/session-ui.a7c8c095.js.br +0 -0
  222. package/dist/web/public/session-ui.a7c8c095.js.gz +0 -0
  223. package/dist/web/public/settings-ui.18f92052.js +106 -0
  224. package/dist/web/public/settings-ui.18f92052.js.br +0 -0
  225. package/dist/web/public/settings-ui.18f92052.js.gz +0 -0
  226. package/dist/web/public/spreadsheet-preview-worker.js.gz +0 -0
  227. package/dist/web/public/spreadsheet-preview.75c751de.js.gz +0 -0
  228. package/dist/web/public/spreadsheet-xlsx-core.js.gz +0 -0
  229. package/dist/web/public/styles.6a718331.css +1 -0
  230. package/dist/web/public/styles.6a718331.css.br +0 -0
  231. package/dist/web/public/styles.6a718331.css.gz +0 -0
  232. package/dist/web/public/subagent-windows.07277a58.js.gz +0 -0
  233. package/dist/web/public/sw.js +2 -2
  234. package/dist/web/public/sw.js.br +0 -0
  235. package/dist/web/public/sw.js.gz +0 -0
  236. package/dist/web/public/tab-layout-browser.2d6189f7.js.gz +0 -0
  237. package/dist/web/public/tab-rail-resize.b5cdceaf.js.gz +0 -0
  238. package/dist/web/public/terminal-keycode229-recovery.98843069.js.gz +0 -0
  239. package/dist/web/public/terminal-split.js.gz +0 -0
  240. package/dist/web/public/terminal-tile.js +2 -2
  241. package/dist/web/public/terminal-tile.js.br +0 -0
  242. package/dist/web/public/terminal-tile.js.gz +0 -0
  243. package/dist/web/public/terminal-ui.a7883640.js +4 -0
  244. package/dist/web/public/terminal-ui.a7883640.js.br +0 -0
  245. package/dist/web/public/terminal-ui.a7883640.js.gz +0 -0
  246. package/dist/web/public/tile-grid.js.gz +0 -0
  247. package/dist/web/public/ultracode-panel.js.gz +0 -0
  248. package/dist/web/public/ultracode-windows.js.gz +0 -0
  249. package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
  250. package/dist/web/public/vendor/exceljs.min.js.gz +0 -0
  251. package/dist/web/public/vendor/fflate.min.js.gz +0 -0
  252. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  253. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  254. package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
  255. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  256. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  257. package/dist/web/public/vendor/xterm-predictive-echo.276b11d9.js.gz +0 -0
  258. package/dist/web/public/vendor/xterm-zerolag-input.1be42307.js.gz +0 -0
  259. package/dist/web/public/vendor/xterm.css.gz +0 -0
  260. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  261. package/dist/web/public/voice-input.2342c37b.js.gz +0 -0
  262. package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
  263. package/dist/web/public/webview-tabs.js.gz +0 -0
  264. package/dist/web/public/whats-new.json +9 -8
  265. package/dist/web/routes/case-routes.d.ts.map +1 -1
  266. package/dist/web/routes/case-routes.js +165 -50
  267. package/dist/web/routes/case-routes.js.map +1 -1
  268. package/dist/web/routes/config-backup-routes.d.ts +5 -1
  269. package/dist/web/routes/config-backup-routes.d.ts.map +1 -1
  270. package/dist/web/routes/config-backup-routes.js +4 -1
  271. package/dist/web/routes/config-backup-routes.js.map +1 -1
  272. package/dist/web/routes/custom-model-routes.d.ts +7 -2
  273. package/dist/web/routes/custom-model-routes.d.ts.map +1 -1
  274. package/dist/web/routes/custom-model-routes.js +51 -6
  275. package/dist/web/routes/custom-model-routes.js.map +1 -1
  276. package/dist/web/routes/discord-routes.d.ts +25 -0
  277. package/dist/web/routes/discord-routes.d.ts.map +1 -0
  278. package/dist/web/routes/discord-routes.js +206 -0
  279. package/dist/web/routes/discord-routes.js.map +1 -0
  280. package/dist/web/routes/edit-history-routes.d.ts +43 -0
  281. package/dist/web/routes/edit-history-routes.d.ts.map +1 -0
  282. package/dist/web/routes/edit-history-routes.js +107 -0
  283. package/dist/web/routes/edit-history-routes.js.map +1 -0
  284. package/dist/web/routes/file-routes.d.ts.map +1 -1
  285. package/dist/web/routes/file-routes.js +273 -102
  286. package/dist/web/routes/file-routes.js.map +1 -1
  287. package/dist/web/routes/git-status-routes.d.ts.map +1 -1
  288. package/dist/web/routes/git-status-routes.js +7 -2
  289. package/dist/web/routes/git-status-routes.js.map +1 -1
  290. package/dist/web/routes/index.d.ts +2 -0
  291. package/dist/web/routes/index.d.ts.map +1 -1
  292. package/dist/web/routes/index.js +2 -0
  293. package/dist/web/routes/index.js.map +1 -1
  294. package/dist/web/routes/mux-routes.d.ts +10 -3
  295. package/dist/web/routes/mux-routes.d.ts.map +1 -1
  296. package/dist/web/routes/mux-routes.js +59 -1
  297. package/dist/web/routes/mux-routes.js.map +1 -1
  298. package/dist/web/routes/ralph-routes.d.ts.map +1 -1
  299. package/dist/web/routes/ralph-routes.js +22 -0
  300. package/dist/web/routes/ralph-routes.js.map +1 -1
  301. package/dist/web/routes/readmymind-routes.d.ts.map +1 -1
  302. package/dist/web/routes/readmymind-routes.js +3 -2
  303. package/dist/web/routes/readmymind-routes.js.map +1 -1
  304. package/dist/web/routes/respawn-routes.d.ts.map +1 -1
  305. package/dist/web/routes/respawn-routes.js +24 -0
  306. package/dist/web/routes/respawn-routes.js.map +1 -1
  307. package/dist/web/routes/session-routes.d.ts +21 -1
  308. package/dist/web/routes/session-routes.d.ts.map +1 -1
  309. package/dist/web/routes/session-routes.js +338 -55
  310. package/dist/web/routes/session-routes.js.map +1 -1
  311. package/dist/web/routes/system-routes.d.ts.map +1 -1
  312. package/dist/web/routes/system-routes.js +4 -2
  313. package/dist/web/routes/system-routes.js.map +1 -1
  314. package/dist/web/schemas.d.ts +92 -2
  315. package/dist/web/schemas.d.ts.map +1 -1
  316. package/dist/web/schemas.js +111 -10
  317. package/dist/web/schemas.js.map +1 -1
  318. package/dist/web/self-update.d.ts +9 -0
  319. package/dist/web/self-update.d.ts.map +1 -1
  320. package/dist/web/self-update.js +19 -3
  321. package/dist/web/self-update.js.map +1 -1
  322. package/dist/web/sensitive-path.d.ts.map +1 -1
  323. package/dist/web/sensitive-path.js +15 -0
  324. package/dist/web/sensitive-path.js.map +1 -1
  325. package/dist/web/server.d.ts +56 -0
  326. package/dist/web/server.d.ts.map +1 -1
  327. package/dist/web/server.js +285 -17
  328. package/dist/web/server.js.map +1 -1
  329. package/dist/web/session-wait-registry.d.ts +13 -0
  330. package/dist/web/session-wait-registry.d.ts.map +1 -1
  331. package/dist/web/session-wait-registry.js +16 -8
  332. package/dist/web/session-wait-registry.js.map +1 -1
  333. package/package.json +2 -2
  334. package/skills/codeman/SKILL.md +20 -4
  335. package/dist/web/public/app.fe8a6b4c.js.br +0 -0
  336. package/dist/web/public/app.fe8a6b4c.js.gz +0 -0
  337. package/dist/web/public/constants.affcdde6.js.br +0 -0
  338. package/dist/web/public/constants.affcdde6.js.gz +0 -0
  339. package/dist/web/public/i18n.a618ef4e.js +0 -2
  340. package/dist/web/public/i18n.a618ef4e.js.br +0 -0
  341. package/dist/web/public/i18n.a618ef4e.js.gz +0 -0
  342. package/dist/web/public/keyboard-accessory.ff64fafc.js.br +0 -0
  343. package/dist/web/public/keyboard-accessory.ff64fafc.js.gz +0 -0
  344. package/dist/web/public/mobile.1bbd2d67.css +0 -1
  345. package/dist/web/public/mobile.1bbd2d67.css.br +0 -0
  346. package/dist/web/public/mobile.1bbd2d67.css.gz +0 -0
  347. package/dist/web/public/panels-ui.4a56ee63.js.br +0 -0
  348. package/dist/web/public/panels-ui.4a56ee63.js.gz +0 -0
  349. package/dist/web/public/respawn-ui.d7adcf6a.js.br +0 -0
  350. package/dist/web/public/respawn-ui.d7adcf6a.js.gz +0 -0
  351. package/dist/web/public/session-ui.426fb671.js.br +0 -0
  352. package/dist/web/public/session-ui.426fb671.js.gz +0 -0
  353. package/dist/web/public/settings-ui.a191154e.js +0 -106
  354. package/dist/web/public/settings-ui.a191154e.js.br +0 -0
  355. package/dist/web/public/settings-ui.a191154e.js.gz +0 -0
  356. package/dist/web/public/styles.4f0c67a0.css +0 -1
  357. package/dist/web/public/styles.4f0c67a0.css.br +0 -0
  358. package/dist/web/public/styles.4f0c67a0.css.gz +0 -0
  359. package/dist/web/public/terminal-ui.7e787829.js +0 -4
  360. package/dist/web/public/terminal-ui.7e787829.js.br +0 -0
  361. package/dist/web/public/terminal-ui.7e787829.js.gz +0 -0
@@ -21,13 +21,13 @@ import { getOfficePreviewPdfPath, getPreviewPdfDownloadName } from '../../docume
21
21
  import { sanitizeAttachmentHistoryItem } from '../../session-attachment-history.js';
22
22
  import { isBlockedAttachmentPath, isUnderTree, loadAttachmentGuardConfig } from '../../config/attachment-guard.js';
23
23
  import { isMultiUserMode, userSpacePath } from '../../config/multiuser.js';
24
- import { CASES_DIR, canAccessOwned, findSessionOrFail, getAuthUser, parseBody, validateSessionFilePath, validateSessionFilePathLexical, } from '../route-helpers.js';
24
+ import { CASES_DIR, canAccessOwned, findSessionOrFail, getAuthUser, parseBody, resolveCasesDir, validateSessionFilePath, validateSessionFilePathLexical, } from '../route-helpers.js';
25
25
  import { RemoteFileAccessError, remoteCreateReadStream, remoteProbePaths, remoteReadFile, } from '../../remote-files.js';
26
26
  import { downloadTooLargeMessage, exceedsDownloadLimit } from '../../config/buffer-limits.js';
27
27
  import { parseByteRange } from '../http-range.js';
28
28
  import { isSensitivePath } from '../sensitive-path.js';
29
29
  import { SseEvent } from '../sse-events.js';
30
- import { FilesystemBrowseQuerySchema, FilesystemPreviewQuerySchema, FileWriteSchema } from '../schemas.js';
30
+ import { FilesystemBrowseQuerySchema, FilesystemMkdirSchema, FilesystemPreviewQuerySchema, FileWriteSchema, } from '../schemas.js';
31
31
  import { MAX_EDITABLE_BYTES, applyEol, detectEol, isDeniedEditRelativePath, isEditableFileName, } from '../../config/file-editing.js';
32
32
  /**
33
33
  * Upper bound on an XLSX the browser preview will fetch (`?preview=true`).
@@ -523,6 +523,85 @@ export function isPathWithinRoot(root, candidate) {
523
523
  const rel = relative(root, candidate);
524
524
  return rel === '' || (!isAbsolute(rel) && rel !== '..' && !rel.startsWith(`..${sep}`));
525
525
  }
526
+ /**
527
+ * Folder entries one Files panel listing may read, summed over the folders it opens. The budget of
528
+ * the walk caps what a listing RETURNS, but taking turns means opening every folder found so far,
529
+ * so this is what caps the reading: past it, folders not opened yet stay unread and are reported as
530
+ * unfinished. It also bounds what the walk holds, since a folder keeps only entries it has read. A
531
+ * folder is read whole, so the one that crosses this line overshoots by its own size.
532
+ */
533
+ const MAX_WALK_ENTRIES_READ = 250_000;
534
+ /**
535
+ * First turn of a folder: reads it and sets `cursor.entries` to the part the listing could use,
536
+ * which is the entries it lists, sorted, up to what is left of the budget. Leaves `entries` unset
537
+ * for a folder it could not read, and sets `unanswered` when that was a probe that established
538
+ * nothing. Answers the number of entries read, for the walk's read ceiling.
539
+ */
540
+ async function readWalkFolder(cursor, budgetLeft, isListed) {
541
+ // The walk's root is the session's own folder. Below it any folder can be a mount that stopped
542
+ // answering, and a bulk scan keeps the stall cap (#516): an unbounded readdir there would hold a
543
+ // threadpool worker for good. `unknown` is never `absent`, so only a folder the filesystem says
544
+ // is gone drops out quietly: one that did not answer goes on the walk's unfinished list.
545
+ if (cursor.depth > 1) {
546
+ const kind = await probePathKind(cursor.dirPath);
547
+ cursor.unanswered = kind === 'unknown';
548
+ if (kind !== 'directory')
549
+ return 0;
550
+ }
551
+ let read;
552
+ try {
553
+ read = await fs.readdir(cursor.dirPath, { withFileTypes: true });
554
+ }
555
+ catch {
556
+ // Can't read directory (permission denied, etc.).
557
+ return 0;
558
+ }
559
+ const listed = read.filter(isListed).sort((a, b) => {
560
+ if (a.isDirectory() && !b.isDirectory())
561
+ return -1;
562
+ if (!a.isDirectory() && b.isDirectory())
563
+ return 1;
564
+ return a.name.localeCompare(b.name);
565
+ });
566
+ cursor.more = listed.length > budgetLeft;
567
+ cursor.entries = cursor.more ? listed.slice(0, budgetLeft) : listed;
568
+ return read.length;
569
+ }
570
+ /**
571
+ * Where a folder's turn ends, given a `share` of the budget. Folders sort first, and each one found
572
+ * here waits for a later round, by which time a folder that spent the rest of its share on its own
573
+ * files has left nothing to read it with. So those files count as one more folder: they take an
574
+ * equal part of what the subfolders left and the remainder stays in the budget. A turn that found
575
+ * no subfolder has nothing to hold back for, and a turn always covers at least one entry.
576
+ */
577
+ function walkTurnEnd(entries, start, share) {
578
+ const end = Math.min(start + share, entries.length);
579
+ let firstFile = start;
580
+ while (firstFile < end && entries[firstFile].isDirectory())
581
+ firstFile++;
582
+ return firstFile + Math.floor((end - firstFile) / (firstFile - start + 1));
583
+ }
584
+ /**
585
+ * Whether a `root=live` listing may start at a session's `liveWorktree` (#765). `findLiveWorktree()`
586
+ * judged that path lexically when the pane moved, and a symlink can take its place, or a parent's,
587
+ * without moving the pane. The probe and `readdir` both follow one, and the walk's `path`s only look
588
+ * relative to `workingDir`, so this is the request-time half of the rule, the same one
589
+ * `validateSessionFilePath()` applies to a file: resolved, the worktree must lie strictly inside the
590
+ * resolved `workingDir`. Only the CHECK uses the resolved paths: the walk keeps the session's spelling,
591
+ * or a workspace reached through a symlink would stop producing paths relative to `workingDir`.
592
+ * Probed first (#516); both realpaths then walk the components that probe has just answered for.
593
+ */
594
+ async function canWalkLiveWorktree(workingDir, worktreePath) {
595
+ if ((await probePathKind(worktreePath, { pastCap: true })) !== 'directory')
596
+ return false;
597
+ try {
598
+ const [realRoot, realWorktree] = await Promise.all([fs.realpath(workingDir), fs.realpath(worktreePath)]);
599
+ return realWorktree !== realRoot && isPathWithinRoot(realRoot, realWorktree);
600
+ }
601
+ catch {
602
+ return false;
603
+ }
604
+ }
526
605
  function findMatchingPickerRoot(roots, candidate) {
527
606
  return roots
528
607
  .filter((root) => isPathWithinRoot(root.path, candidate))
@@ -1113,6 +1192,69 @@ export function registerFileRoutes(app, ctx) {
1113
1192
  },
1114
1193
  };
1115
1194
  });
1195
+ // "New folder" in the path picker: creates ONE folder directly inside a folder the picker may browse.
1196
+ app.post('/api/filesystem/mkdir', async (req, reply) => {
1197
+ const { path: parentPath, name, sessionId, showHidden } = parseBody(FilesystemMkdirSchema, req.body);
1198
+ // Probed first (#516): the picker's resolver runs realpathSync on this path, which would freeze the
1199
+ // whole server on a mount that stopped answering.
1200
+ if ((await probePathKind(parentPath, { pastCap: true })) === 'unknown') {
1201
+ reply.code(422);
1202
+ return createErrorResponse(ApiErrorCode.OPERATION_FAILED, describeUnknownPath('The folder', parentPath, { pastCap: true }));
1203
+ }
1204
+ const { candidatePath, resolvedPath, blockedTrees } = await resolveFilesystemPickerPath(ctx, req, parentPath, sessionId, wantsHiddenPickerEntries(showHidden));
1205
+ const target = join(resolvedPath, name);
1206
+ if (isBlockedPickerPath(resolvedPath, blockedTrees, true) || isBlockedPickerPath(target, blockedTrees, true)) {
1207
+ reply.code(403);
1208
+ return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'Folders cannot be created here');
1209
+ }
1210
+ // The cases directories (shared, and the caller's own in multi-user mode) are what GET /api/cases lists as
1211
+ // local cases: a folder made there is a case with no scaffold, which Link then refuses by name and which
1212
+ // deleting would take the linked folder with. A case there is made through Add Case > Create New with no
1213
+ // custom folder. The refusal has to read right from every picker that offers New folder: a Create New or
1214
+ // Clone Repo user is already on that tab, with the custom folder ticked, and this is where Browse opens
1215
+ // while the field is empty.
1216
+ const casesDirs = await Promise.all([...new Set([CASES_DIR, resolveCasesDir(getAuthUser(req))])].map((dir) => fs.realpath(dir).catch(() => resolve(dir))));
1217
+ if (casesDirs.includes(resolvedPath)) {
1218
+ reply.code(400);
1219
+ return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'This is a cases folder: every folder in it is a case. Pick a folder outside it, or make the case with Add Case > Create New or Clone Repo (no custom folder).');
1220
+ }
1221
+ // Like browse, a file is not a place to create in.
1222
+ try {
1223
+ if (!(await fs.stat(resolvedPath)).isDirectory())
1224
+ throw new Error('not a directory');
1225
+ }
1226
+ catch {
1227
+ reply.code(400);
1228
+ return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'The folder to create in must be a directory');
1229
+ }
1230
+ try {
1231
+ // Not recursive: the parent was just resolved inside an allowed root, and an existing folder must be a 409.
1232
+ await fs.mkdir(target);
1233
+ }
1234
+ catch (err) {
1235
+ const code = err.code;
1236
+ if (code === 'EEXIST') {
1237
+ reply.code(409);
1238
+ return createErrorResponse(ApiErrorCode.ALREADY_EXISTS, `"${name}" already exists in this folder`);
1239
+ }
1240
+ if (code === 'EACCES' || code === 'EPERM' || code === 'EROFS') {
1241
+ reply.code(403);
1242
+ return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'This folder is not writable');
1243
+ }
1244
+ if (code === 'ENOENT') {
1245
+ reply.code(404);
1246
+ return createErrorResponse(ApiErrorCode.NOT_FOUND, `Folder not found: ${candidatePath}`);
1247
+ }
1248
+ if (code === 'ENAMETOOLONG') {
1249
+ reply.code(400);
1250
+ return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'Folder name is too long');
1251
+ }
1252
+ // Never the errno text: it names the resolved, symlink-followed path, not the one the picker shows.
1253
+ reply.code(500);
1254
+ return createErrorResponse(ApiErrorCode.OPERATION_FAILED, `Could not create the folder${code ? ` (${code})` : ''}`);
1255
+ }
1256
+ return { success: true, data: { path: join(candidatePath, name) } };
1257
+ });
1116
1258
  // Inline preview for files selected through the root-confined filesystem picker.
1117
1259
  app.get('/api/filesystem/preview', { compress: false }, async (req, reply) => {
1118
1260
  const { path: requestedPath, sessionId, showHidden } = parseBody(FilesystemPreviewQuerySchema, req.query);
@@ -1156,11 +1298,20 @@ export function registerFileRoutes(app, ctx) {
1156
1298
  // File tree listing
1157
1299
  app.get('/api/sessions/:id/files', async (req) => {
1158
1300
  const { id } = req.params;
1159
- const { depth, showHidden, q } = req.query;
1301
+ const { depth, showHidden, q, root } = req.query;
1160
1302
  const session = findSessionOrFail(ctx, id, req);
1161
1303
  const maxDepth = Math.min(parseInt(depth || '5', 10), 10);
1162
1304
  const includeHidden = showHidden === 'true';
1163
1305
  const workingDir = session.workingDir;
1306
+ // `root=live` starts the walk at the linked worktree the pane is in (#765), once that folder
1307
+ // proves to be inside `workingDir` right now, and every `path` below stays relative to
1308
+ // `workingDir`, so file-content, file-raw and the edit route need nothing new and judge these
1309
+ // paths against the same root as any other. Only the search predicate reads a path relative
1310
+ // to the listing, since that is the tree the person is looking at.
1311
+ const liveWorktree = session.liveWorktree;
1312
+ const walkRoot = root === 'live' && liveWorktree && (await canWalkLiveWorktree(workingDir, liveWorktree.path))
1313
+ ? liveWorktree.path
1314
+ : workingDir;
1164
1315
  // null for an empty/whitespace query, which is what keeps the default
1165
1316
  // tree response byte-identical when no search is requested.
1166
1317
  const matcher = compileFileQuery(q ?? '');
@@ -1185,6 +1336,8 @@ export function registerFileRoutes(app, ctx) {
1185
1336
  let totalDirectories = 0;
1186
1337
  let truncated = false;
1187
1338
  const maxFiles = 5000;
1339
+ let entriesRead = 0;
1340
+ const isListed = (entry) => (includeHidden || !entry.name.startsWith('.')) && !(entry.isDirectory() && excludeDirs.has(entry.name));
1188
1341
  // ===== Search mode =====
1189
1342
  // A query turns this endpoint into a FLAT match list rather than a nested
1190
1343
  // tree. It recurses past non-matching directories on purpose — the whole
@@ -1194,71 +1347,84 @@ export function registerFileRoutes(app, ctx) {
1194
1347
  if (matcher) {
1195
1348
  const matches = [];
1196
1349
  const maxMatches = 1000;
1197
- const searchDirectory = async (dirPath, currentDepth) => {
1198
- if (currentDepth > maxDepth || totalFiles + totalDirectories > maxFiles || matches.length >= maxMatches) {
1199
- truncated = true;
1200
- return;
1201
- }
1202
- let entries;
1203
- try {
1204
- entries = await fs.readdir(dirPath, { withFileTypes: true });
1205
- }
1206
- catch {
1207
- // Can't read directory (permission denied, etc.)
1208
- return;
1209
- }
1210
- entries.sort((a, b) => {
1211
- if (a.isDirectory() && !b.isDirectory())
1212
- return -1;
1213
- if (!a.isDirectory() && b.isDirectory())
1214
- return 1;
1215
- return a.name.localeCompare(b.name);
1216
- });
1217
- for (const entry of entries) {
1218
- if (totalFiles + totalDirectories > maxFiles || matches.length >= maxMatches) {
1219
- truncated = true;
1350
+ const queue = [{ dirPath: walkRoot, depth: 1, index: 0 }];
1351
+ // Folders the read ceiling kept shut or that did not answer. What was already read still gets its turns.
1352
+ const unread = [];
1353
+ while (queue.length > 0 && totalFiles + totalDirectories < maxFiles && matches.length < maxMatches) {
1354
+ // Per-directory round-robin (breadth-first): each queued directory gets an equal share of what
1355
+ // is left before any of them gets more, so results arrive shallow-first. Fair per directory,
1356
+ // not per subtree: a folder of thousands of small subfolders queues thousands of directories
1357
+ // and can still take most of the budget one level down. Reading a folder costs the same
1358
+ // whatever its share, so this never bounds the reading: MAX_WALK_ENTRIES_READ does.
1359
+ const directoriesInRound = queue.length;
1360
+ const nodesPerDirectory = Math.max(1, Math.floor((maxFiles - totalFiles - totalDirectories) / directoriesInRound));
1361
+ for (let directoryIndex = 0; directoryIndex < directoriesInRound; directoryIndex++) {
1362
+ // Spent part way through a round: the rest stay queued unread rather than sorted for nothing.
1363
+ if (totalFiles + totalDirectories >= maxFiles || matches.length >= maxMatches)
1220
1364
  break;
1221
- }
1222
- if (!includeHidden && entry.name.startsWith('.'))
1223
- continue;
1224
- if (entry.isDirectory() && excludeDirs.has(entry.name))
1365
+ const cursor = queue.shift();
1366
+ if (!cursor)
1367
+ break;
1368
+ if (cursor.depth > maxDepth) {
1369
+ truncated = true;
1225
1370
  continue;
1226
- const fullPath = join(dirPath, entry.name);
1227
- const relativePath = relative(workingDir, fullPath);
1228
- if (entry.isDirectory()) {
1229
- totalDirectories++;
1230
- if (matcher(entry.name, relativePath)) {
1231
- matches.push({ name: entry.name, path: relativePath, type: 'directory' });
1371
+ }
1372
+ if (!cursor.entries) {
1373
+ if (entriesRead >= MAX_WALK_ENTRIES_READ) {
1374
+ unread.push(cursor);
1375
+ continue;
1232
1376
  }
1233
- // Always recurse, even when this directory does not match.
1234
- await searchDirectory(fullPath, currentDepth + 1);
1377
+ entriesRead += await readWalkFolder(cursor, maxFiles - totalFiles - totalDirectories, isListed);
1378
+ if (cursor.unanswered)
1379
+ unread.push(cursor);
1380
+ if (!cursor.entries)
1381
+ continue;
1235
1382
  }
1236
- else {
1237
- totalFiles++;
1238
- if (matcher(entry.name, relativePath)) {
1239
- let size;
1240
- try {
1241
- size = (await fs.stat(fullPath)).size;
1383
+ const endIndex = walkTurnEnd(cursor.entries, cursor.index, nodesPerDirectory);
1384
+ while (cursor.index < endIndex && totalFiles + totalDirectories < maxFiles && matches.length < maxMatches) {
1385
+ const entry = cursor.entries[cursor.index++];
1386
+ const fullPath = join(cursor.dirPath, entry.name);
1387
+ const relativePath = relative(workingDir, fullPath);
1388
+ const listedPath = relative(walkRoot, fullPath);
1389
+ if (entry.isDirectory()) {
1390
+ totalDirectories++;
1391
+ if (matcher(entry.name, listedPath)) {
1392
+ matches.push({ name: entry.name, path: relativePath, type: 'directory' });
1242
1393
  }
1243
- catch {
1244
- // Skip size if we can't stat the match.
1394
+ // Queued, not entered: it waits for a later round with every other directory found.
1395
+ queue.push({ dirPath: fullPath, depth: cursor.depth + 1, index: 0 });
1396
+ }
1397
+ else {
1398
+ totalFiles++;
1399
+ if (matcher(entry.name, listedPath)) {
1400
+ let size;
1401
+ try {
1402
+ size = (await fs.stat(fullPath)).size;
1403
+ }
1404
+ catch {
1405
+ // Skip size if we can't stat the match.
1406
+ }
1407
+ matches.push({
1408
+ name: entry.name,
1409
+ path: relativePath,
1410
+ type: 'file',
1411
+ size,
1412
+ extension: entry.name.includes('.') ? entry.name.split('.').pop()?.toLowerCase() : undefined,
1413
+ });
1245
1414
  }
1246
- matches.push({
1247
- name: entry.name,
1248
- path: relativePath,
1249
- type: 'file',
1250
- size,
1251
- extension: entry.name.includes('.') ? entry.name.split('.').pop()?.toLowerCase() : undefined,
1252
- });
1253
1415
  }
1254
1416
  }
1417
+ if (cursor.index < cursor.entries.length || cursor.more)
1418
+ queue.push(cursor);
1255
1419
  }
1256
- };
1257
- await searchDirectory(workingDir, 1);
1420
+ }
1421
+ if (queue.length > 0 || unread.length > 0)
1422
+ truncated = true;
1258
1423
  return {
1259
1424
  success: true,
1260
1425
  data: {
1261
- root: workingDir,
1426
+ root: walkRoot,
1427
+ ...(liveWorktree && { worktree: liveWorktree }),
1262
1428
  tree: [],
1263
1429
  matches,
1264
1430
  totalFiles,
@@ -1270,44 +1436,50 @@ export function registerFileRoutes(app, ctx) {
1270
1436
  },
1271
1437
  };
1272
1438
  }
1273
- const scanDirectory = async (dirPath, currentDepth) => {
1274
- if (currentDepth > maxDepth || totalFiles + totalDirectories > maxFiles) {
1275
- truncated = true;
1276
- return [];
1277
- }
1278
- try {
1279
- const entries = await fs.readdir(dirPath, { withFileTypes: true });
1280
- const nodes = [];
1281
- // Sort: directories first, then alphabetically
1282
- entries.sort((a, b) => {
1283
- if (a.isDirectory() && !b.isDirectory())
1284
- return -1;
1285
- if (!a.isDirectory() && b.isDirectory())
1286
- return 1;
1287
- return a.name.localeCompare(b.name);
1288
- });
1289
- for (const entry of entries) {
1290
- if (totalFiles + totalDirectories > maxFiles) {
1291
- truncated = true;
1292
- break;
1293
- }
1294
- // Skip hidden files unless requested
1295
- if (!includeHidden && entry.name.startsWith('.'))
1439
+ const tree = [];
1440
+ const queue = [{ dirPath: walkRoot, depth: 1, nodes: tree, index: 0 }];
1441
+ const unread = [];
1442
+ while (queue.length > 0 && totalFiles + totalDirectories < maxFiles) {
1443
+ // The same per-directory round-robin as the search walk above, with the same limits.
1444
+ const directoriesInRound = queue.length;
1445
+ const nodesPerDirectory = Math.max(1, Math.floor((maxFiles - totalFiles - totalDirectories) / directoriesInRound));
1446
+ for (let directoryIndex = 0; directoryIndex < directoriesInRound; directoryIndex++) {
1447
+ // Before the shift, so a cursor this skips stays queued and is flagged below.
1448
+ if (totalFiles + totalDirectories >= maxFiles)
1449
+ break;
1450
+ const cursor = queue.shift();
1451
+ if (!cursor)
1452
+ break;
1453
+ if (cursor.depth > maxDepth) {
1454
+ truncated = true;
1455
+ continue;
1456
+ }
1457
+ if (!cursor.entries) {
1458
+ if (entriesRead >= MAX_WALK_ENTRIES_READ) {
1459
+ unread.push(cursor);
1296
1460
  continue;
1297
- // Skip excluded directories
1298
- if (entry.isDirectory() && excludeDirs.has(entry.name))
1461
+ }
1462
+ entriesRead += await readWalkFolder(cursor, maxFiles - totalFiles - totalDirectories, isListed);
1463
+ if (cursor.unanswered)
1464
+ unread.push(cursor);
1465
+ if (!cursor.entries)
1299
1466
  continue;
1300
- const fullPath = join(dirPath, entry.name);
1467
+ }
1468
+ const endIndex = walkTurnEnd(cursor.entries, cursor.index, nodesPerDirectory);
1469
+ while (cursor.index < endIndex && totalFiles + totalDirectories < maxFiles) {
1470
+ const entry = cursor.entries[cursor.index++];
1471
+ const fullPath = join(cursor.dirPath, entry.name);
1301
1472
  const relativePath = fullPath.slice(workingDir.length + 1);
1302
1473
  if (entry.isDirectory()) {
1303
1474
  totalDirectories++;
1304
- const children = await scanDirectory(fullPath, currentDepth + 1);
1305
- nodes.push({
1475
+ const node = {
1306
1476
  name: entry.name,
1307
1477
  path: relativePath,
1308
1478
  type: 'directory',
1309
- children,
1310
- });
1479
+ children: [],
1480
+ };
1481
+ cursor.nodes.push(node);
1482
+ queue.push({ dirPath: fullPath, depth: cursor.depth + 1, node, nodes: node.children, index: 0 });
1311
1483
  }
1312
1484
  else {
1313
1485
  totalFiles++;
@@ -1318,29 +1490,28 @@ export function registerFileRoutes(app, ctx) {
1318
1490
  size = stat.size;
1319
1491
  }
1320
1492
  catch {
1321
- // Skip if can't stat
1493
+ // Skip if can't stat.
1322
1494
  }
1323
- nodes.push({
1324
- name: entry.name,
1325
- path: relativePath,
1326
- type: 'file',
1327
- size,
1328
- extension: ext,
1329
- });
1495
+ cursor.nodes.push({ name: entry.name, path: relativePath, type: 'file', size, extension: ext });
1330
1496
  }
1331
1497
  }
1332
- return nodes;
1498
+ if (cursor.index < cursor.entries.length || cursor.more)
1499
+ queue.push(cursor);
1333
1500
  }
1334
- catch {
1335
- // Can't read directory (permission denied, etc.)
1336
- return [];
1337
- }
1338
- };
1339
- const tree = await scanDirectory(workingDir, 1);
1501
+ }
1502
+ // Every directory still queued is unread or half read, and an empty `children` would read as an
1503
+ // empty folder. The root has no node to flag: `truncated` says the same for it.
1504
+ for (const cursor of [...queue, ...unread]) {
1505
+ if (cursor.node)
1506
+ cursor.node.incomplete = true;
1507
+ }
1508
+ if (queue.length > 0 || unread.length > 0)
1509
+ truncated = true;
1340
1510
  return {
1341
1511
  success: true,
1342
1512
  data: {
1343
- root: workingDir,
1513
+ root: walkRoot,
1514
+ ...(liveWorktree && { worktree: liveWorktree }),
1344
1515
  tree,
1345
1516
  totalFiles,
1346
1517
  totalDirectories,