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
@@ -0,0 +1,178 @@
1
+ /**
2
+ * @fileoverview Reads a claude session's edit history on request: the new bytes of its
3
+ * transcripts (main + subagents) go into its `EditHistoryStore`, and the Bash image
4
+ * candidates that came with them are confirmed against the files on disk.
5
+ *
6
+ * WHEN. Only when the panel asks (`GET …/edits?sync=1` on open and while it follows the
7
+ * session, `POST …/edits/older`): no timer, no hook, no background read for sessions nobody
8
+ * looks at. One sync per session runs at a time; requests during a run are folded into one
9
+ * more forward read and/or one more older read afterwards.
10
+ *
11
+ * HOW MUCH. A forward read shares one budget over all files, main first. A file whose chunk
12
+ * the shared budget cut short is not applied (the store would take a chunk with no line end
13
+ * for a line longer than the budget) but read in the next pass; up to
14
+ * {@link MAX_FORWARD_PASSES} passes per sync, the rest with the next one.
15
+ *
16
+ * NEW CONVERSATION. When the transcript path names another conversation (`/clear`, resume),
17
+ * the previous transcript (same directory) is first read to its end, so edits it got after
18
+ * the last read are not lost with its cursors; then the new one is read from its end.
19
+ * "Load older" reaches back within the current conversation only.
20
+ *
21
+ * MEMORY. At most {@link MAX_LOADED_STORES} stores stay loaded; past that the least recently
22
+ * used one without a run is written (if it changed) and dropped, and loaded again on demand.
23
+ *
24
+ * GONE. `remove()` (the session's record is deleted) marks the session gone, deletes the file
25
+ * and then drops the session's slot: a run in flight stops after its current read and its
26
+ * store no longer writes; a request during the removal reads nothing (`unavailable`, reason
27
+ * `removed`). A session that comes back with the same id (reboot restore) starts afresh.
28
+ * `sweep()` deletes, at startup, the stored history of sessions that no longer exist. A
29
+ * subagent transcript that vanished loses its cursor (`dropSource`), not its entries.
30
+ *
31
+ * OLDER. "Load older" reads a full budget block of up to {@link MAX_OLDER_SOURCES} sources
32
+ * with older lines per click; with more sources they take turns.
33
+ *
34
+ * WHICH SESSIONS. Claude sessions, local and remote. Docker and non-claude sessions answer
35
+ * `unsupported` and are never read. The deps get the session and read a remote one over ssh
36
+ * (`remote-files.ts`): one round trip to find its transcript, one per read (more for a
37
+ * conversation with hundreds of subagents). A remote read never wakes its host; a sleeping one
38
+ * is a connect timeout (`unavailable`, stored entries kept).
39
+ *
40
+ * IMAGES. A Bash candidate counts only when the file's mtime lies in the call's window
41
+ * (start − 2 s … end + 2 s), so `ls *.png` lists nothing. Transcript times and mtimes come
42
+ * from the same host, so a remote host's clock skew to this server does not matter.
43
+ *
44
+ * Tests: `test/edit-history-sync.test.ts`.
45
+ *
46
+ * @module web/edit-history-sync
47
+ */
48
+ import type { SessionRemote } from '../types/session.js';
49
+ import type { EditSourceReadOptions, EditSourceRequest, EditSources, PathProbe } from '../edit-history-local-source.js';
50
+ import type { ImageCandidate } from '../edit-history-parse.js';
51
+ import { EditHistoryStore, type ConfirmedImage } from '../edit-history-store.js';
52
+ /** Slack around a Bash call's window for the image mtime check. */
53
+ export declare const IMAGE_MTIME_SLACK_MS = 2000;
54
+ /** Forward passes per sync when files grew by more than one budget. */
55
+ export declare const MAX_FORWARD_PASSES = 3;
56
+ /** Sources read back per "Load older" click, a full budget each. */
57
+ export declare const MAX_OLDER_SOURCES = 3;
58
+ /** Stores kept in memory; the least recently used one past this is dropped. */
59
+ export declare const MAX_LOADED_STORES = 3;
60
+ /** `reason` when the session has no transcript yet (no prompt yet, or not found). */
61
+ export declare const NO_TRANSCRIPT = "no-transcript";
62
+ /** `reason` once the session's history was deleted. */
63
+ export declare const REMOVED = "removed";
64
+ export type EditHistoryState = 'ok' | 'unavailable' | 'unsupported';
65
+ export interface EditHistoryStatus {
66
+ state: EditHistoryState;
67
+ reason?: string;
68
+ }
69
+ /** The slice of a session this needs. */
70
+ export interface EditHistorySession {
71
+ id: string;
72
+ mode: string;
73
+ remote: SessionRemote | undefined;
74
+ docker?: unknown;
75
+ /** Set on a wrapper around a foreign tmux session: Codeman holds no transcript id for what runs there. */
76
+ adopt?: unknown;
77
+ claudeSessionId: string | null;
78
+ /** The session's own `CLAUDE_CONFIG_DIR` (Codeman env override), if it has one. */
79
+ claudeConfigDir?: string;
80
+ }
81
+ export interface EditHistorySyncDeps {
82
+ /**
83
+ * The session's main transcript path, or null when it has none yet. `storedTranscriptId` is
84
+ * the conversation the stored history holds (null before the first read); see
85
+ * {@link remoteTranscriptIds}.
86
+ */
87
+ transcriptPath(session: EditHistorySession, storedTranscriptId: string | null): Promise<string | null>;
88
+ /** The session's transcripts (a remote session's on its host). */
89
+ readSources(session: EditHistorySession, mainPath: string, requests: readonly EditSourceRequest[], budget: number, opts?: EditSourceReadOptions): Promise<EditSources | null>;
90
+ /** Bash image candidates of the session, on the host it runs on. */
91
+ probe(session: EditHistorySession, paths: readonly string[]): Promise<Array<PathProbe | null>>;
92
+ loadStore(sessionId: string): Promise<EditHistoryStore>;
93
+ /** Delete a session's stored history without loading it. */
94
+ deleteStore(sessionId: string): Promise<void>;
95
+ /** Delete the stored history of every session `keep` rejects; returns how many. */
96
+ sweepStores(keep: (sessionId: string) => boolean): Promise<number>;
97
+ budget?: number;
98
+ }
99
+ export interface SyncOptions {
100
+ older?: boolean;
101
+ }
102
+ /**
103
+ * The conversation ids a REMOTE session's transcript lookup considers when its claude writes
104
+ * none now; the most recently written of them wins (`buildRemoteClaudeTranscriptLocateCommand`).
105
+ * The stored history's conversation must be among them: no hook tells Codeman of a remote
106
+ * `/clear`, so `claudeSessionId` stays the launch id, and without the stored one the panel
107
+ * would flip back to the cleared conversation as soon as the remote claude exits.
108
+ */
109
+ export declare function remoteTranscriptIds(session: EditHistorySession, storedTranscriptId: string | null): string[];
110
+ /** Why a session has no edit history, or undefined when it can have one. */
111
+ export declare function unsupportedReason(session: EditHistorySession): string | undefined;
112
+ /**
113
+ * Bash candidates whose probed file is a regular file with an mtime inside the call's window
114
+ * (files claude sent to the user skip the window: they exist and were shown on purpose).
115
+ * A candidate whose file version is already recorded is skipped: `known(path)` is the newest
116
+ * entry time for that path, and an entry at or after `mtime − slack` saw this version: the
117
+ * `ls *.png` right after the render, or a Write followed by a listing. Candidates are taken
118
+ * in call order so a batch dedupes itself the same way.
119
+ */
120
+ export declare function confirmImages(candidates: readonly ImageCandidate[], probe: (path: string) => PathProbe | null | undefined, known?: (path: string) => number | undefined): ConfirmedImage[];
121
+ export declare class EditHistorySync {
122
+ private readonly deps;
123
+ /** By session, least recently used first. */
124
+ private readonly slots;
125
+ /** Writes of dropped stores; loading that session again waits for its write. */
126
+ private readonly unloading;
127
+ private readonly budget;
128
+ constructor(deps: EditHistorySyncDeps);
129
+ /** The session's store, loaded from disk on first use (one load for concurrent callers). */
130
+ getStore(sessionId: string): Promise<EditHistoryStore>;
131
+ getStatus(sessionId: string): EditHistoryStatus;
132
+ /**
133
+ * Read what is new (or, with `older`, the next older block). Resolves when this
134
+ * request's run is done; never rejects. A call during a run queues one more run.
135
+ */
136
+ sync(session: EditHistorySession, opts?: SyncOptions): Promise<EditHistoryStatus>;
137
+ /** Write every loaded store's pending changes (server shutdown). */
138
+ flushAll(): Promise<void>;
139
+ /**
140
+ * Delete a session's stored history (its record is gone). While it runs the session is
141
+ * marked, so a run in flight or a request meanwhile cannot load and write a new store for
142
+ * it; then its slot goes, and a session that comes back with this id starts afresh.
143
+ */
144
+ remove(sessionId: string): Promise<void>;
145
+ /**
146
+ * Delete the stored history of sessions that no longer exist (startup: left behind by a
147
+ * crash during deletion, or by an older build). A session in use here is kept.
148
+ */
149
+ sweep(exists: (sessionId: string) => boolean): Promise<number>;
150
+ /** The session's slot, now the most recently used. */
151
+ private slot;
152
+ private storeOf;
153
+ /**
154
+ * Past {@link MAX_LOADED_STORES}, drop the least recently used stores no run uses: each is
155
+ * written if it changed, and a later request loads it again (after that write).
156
+ */
157
+ private unloadIdle;
158
+ private runLoop;
159
+ private runOnce;
160
+ private forwardRequests;
161
+ /**
162
+ * One forward read of all sources. `more` = some file has unread bytes after it, or was cut
163
+ * short by the shared budget and left for the next pass. A transcript other than the stored
164
+ * one (`/clear`, resume) is read like a first read, once the stored one is read to its end.
165
+ */
166
+ private readForward;
167
+ /**
168
+ * The path of the conversation the store holds cursors for: `path` itself, or the previous
169
+ * conversation's transcript next to it (`/clear` and resume keep the project directory).
170
+ */
171
+ private storedTranscript;
172
+ private readFrom;
173
+ private readOlder;
174
+ /** Requested files the reader no longer lists are gone: their cursors go too. */
175
+ private dropMissing;
176
+ private addImages;
177
+ }
178
+ //# sourceMappingURL=edit-history-sync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edit-history-sync.d.ts","sourceRoot":"","sources":["../../src/web/edit-history-sync.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,461 @@
1
+ /**
2
+ * @fileoverview Reads a claude session's edit history on request: the new bytes of its
3
+ * transcripts (main + subagents) go into its `EditHistoryStore`, and the Bash image
4
+ * candidates that came with them are confirmed against the files on disk.
5
+ *
6
+ * WHEN. Only when the panel asks (`GET …/edits?sync=1` on open and while it follows the
7
+ * session, `POST …/edits/older`): no timer, no hook, no background read for sessions nobody
8
+ * looks at. One sync per session runs at a time; requests during a run are folded into one
9
+ * more forward read and/or one more older read afterwards.
10
+ *
11
+ * HOW MUCH. A forward read shares one budget over all files, main first. A file whose chunk
12
+ * the shared budget cut short is not applied (the store would take a chunk with no line end
13
+ * for a line longer than the budget) but read in the next pass; up to
14
+ * {@link MAX_FORWARD_PASSES} passes per sync, the rest with the next one.
15
+ *
16
+ * NEW CONVERSATION. When the transcript path names another conversation (`/clear`, resume),
17
+ * the previous transcript (same directory) is first read to its end, so edits it got after
18
+ * the last read are not lost with its cursors; then the new one is read from its end.
19
+ * "Load older" reaches back within the current conversation only.
20
+ *
21
+ * MEMORY. At most {@link MAX_LOADED_STORES} stores stay loaded; past that the least recently
22
+ * used one without a run is written (if it changed) and dropped, and loaded again on demand.
23
+ *
24
+ * GONE. `remove()` (the session's record is deleted) marks the session gone, deletes the file
25
+ * and then drops the session's slot: a run in flight stops after its current read and its
26
+ * store no longer writes; a request during the removal reads nothing (`unavailable`, reason
27
+ * `removed`). A session that comes back with the same id (reboot restore) starts afresh.
28
+ * `sweep()` deletes, at startup, the stored history of sessions that no longer exist. A
29
+ * subagent transcript that vanished loses its cursor (`dropSource`), not its entries.
30
+ *
31
+ * OLDER. "Load older" reads a full budget block of up to {@link MAX_OLDER_SOURCES} sources
32
+ * with older lines per click; with more sources they take turns.
33
+ *
34
+ * WHICH SESSIONS. Claude sessions, local and remote. Docker and non-claude sessions answer
35
+ * `unsupported` and are never read. The deps get the session and read a remote one over ssh
36
+ * (`remote-files.ts`): one round trip to find its transcript, one per read (more for a
37
+ * conversation with hundreds of subagents). A remote read never wakes its host; a sleeping one
38
+ * is a connect timeout (`unavailable`, stored entries kept).
39
+ *
40
+ * IMAGES. A Bash candidate counts only when the file's mtime lies in the call's window
41
+ * (start − 2 s … end + 2 s), so `ls *.png` lists nothing. Transcript times and mtimes come
42
+ * from the same host, so a remote host's clock skew to this server does not matter.
43
+ *
44
+ * Tests: `test/edit-history-sync.test.ts`.
45
+ *
46
+ * @module web/edit-history-sync
47
+ */
48
+ import { basename, dirname, join } from 'node:path';
49
+ import { READ_BUDGET_BYTES } from '../edit-history-store.js';
50
+ import { getCli } from '../config/cli-registry/registry.js';
51
+ /** Slack around a Bash call's window for the image mtime check. */
52
+ export const IMAGE_MTIME_SLACK_MS = 2000;
53
+ /** Forward passes per sync when files grew by more than one budget. */
54
+ export const MAX_FORWARD_PASSES = 3;
55
+ /** Sources read back per "Load older" click, a full budget each. */
56
+ export const MAX_OLDER_SOURCES = 3;
57
+ /** Stores kept in memory; the least recently used one past this is dropped. */
58
+ export const MAX_LOADED_STORES = 3;
59
+ /** `reason` when the session has no transcript yet (no prompt yet, or not found). */
60
+ export const NO_TRANSCRIPT = 'no-transcript';
61
+ /** `reason` once the session's history was deleted. */
62
+ export const REMOVED = 'removed';
63
+ /**
64
+ * The conversation ids a REMOTE session's transcript lookup considers when its claude writes
65
+ * none now; the most recently written of them wins (`buildRemoteClaudeTranscriptLocateCommand`).
66
+ * The stored history's conversation must be among them: no hook tells Codeman of a remote
67
+ * `/clear`, so `claudeSessionId` stays the launch id, and without the stored one the panel
68
+ * would flip back to the cleared conversation as soon as the remote claude exits.
69
+ */
70
+ export function remoteTranscriptIds(session, storedTranscriptId) {
71
+ return [...new Set([storedTranscriptId, session.claudeSessionId || session.id, session.id])].filter((id) => !!id);
72
+ }
73
+ /** Why a session has no edit history, or undefined when it can have one. */
74
+ export function unsupportedReason(session) {
75
+ if (session.docker)
76
+ return 'docker';
77
+ if (session.adopt)
78
+ return 'adopted';
79
+ if (getCli(session.mode)?.capabilities.transcript !== 'claude-jsonl')
80
+ return 'mode';
81
+ return undefined;
82
+ }
83
+ /** One turn of the event loop, between synchronous parses of up to one budget each. */
84
+ function breathe() {
85
+ return new Promise((resolve) => setImmediate(resolve));
86
+ }
87
+ /**
88
+ * Bash candidates whose probed file is a regular file with an mtime inside the call's window
89
+ * (files claude sent to the user skip the window: they exist and were shown on purpose).
90
+ * A candidate whose file version is already recorded is skipped: `known(path)` is the newest
91
+ * entry time for that path, and an entry at or after `mtime − slack` saw this version: the
92
+ * `ls *.png` right after the render, or a Write followed by a listing. Candidates are taken
93
+ * in call order so a batch dedupes itself the same way.
94
+ */
95
+ export function confirmImages(candidates, probe, known = () => undefined) {
96
+ const out = [];
97
+ const seen = new Map();
98
+ for (const c of [...candidates].sort((a, b) => a.end - b.end)) {
99
+ for (const path of c.paths) {
100
+ const p = probe(path);
101
+ if (!p || p.kind !== 'file' || !p.mtimeMs)
102
+ continue;
103
+ const inWindow = p.mtimeMs >= c.start - IMAGE_MTIME_SLACK_MS && p.mtimeMs <= c.end + IMAGE_MTIME_SLACK_MS;
104
+ if (!c.sent && !inWindow)
105
+ continue;
106
+ const entryPath = path.startsWith('~/') ? p.realPath : path;
107
+ const last = Math.max(seen.get(entryPath) ?? -Infinity, known(entryPath) ?? -Infinity);
108
+ if (last >= p.mtimeMs - IMAGE_MTIME_SLACK_MS)
109
+ continue;
110
+ seen.set(entryPath, c.end);
111
+ const img = {
112
+ toolUseId: c.toolUseId,
113
+ path: entryPath,
114
+ ts: c.end,
115
+ tool: c.sent ? 'sent' : 'bash',
116
+ };
117
+ if (c.agentId)
118
+ img.agentId = c.agentId;
119
+ out.push(img);
120
+ }
121
+ }
122
+ return out;
123
+ }
124
+ export class EditHistorySync {
125
+ deps;
126
+ /** By session, least recently used first. */
127
+ slots = new Map();
128
+ /** Writes of dropped stores; loading that session again waits for its write. */
129
+ unloading = new Map();
130
+ budget;
131
+ constructor(deps) {
132
+ this.deps = deps;
133
+ this.budget = deps.budget ?? READ_BUDGET_BYTES;
134
+ }
135
+ /** The session's store, loaded from disk on first use (one load for concurrent callers). */
136
+ getStore(sessionId) {
137
+ return this.storeOf(sessionId, this.slot(sessionId));
138
+ }
139
+ getStatus(sessionId) {
140
+ return { ...this.slot(sessionId).status };
141
+ }
142
+ /**
143
+ * Read what is new (or, with `older`, the next older block). Resolves when this
144
+ * request's run is done; never rejects. A call during a run queues one more run.
145
+ */
146
+ async sync(session, opts = {}) {
147
+ const why = unsupportedReason(session);
148
+ const slot = this.slot(session.id);
149
+ if (slot.gone)
150
+ return { ...slot.status };
151
+ if (why) {
152
+ slot.status = { state: 'unsupported', reason: why };
153
+ return { ...slot.status };
154
+ }
155
+ const want = { forward: !opts.older, older: !!opts.older };
156
+ if (slot.running) {
157
+ slot.again = {
158
+ forward: (slot.again?.forward ?? false) || want.forward,
159
+ older: (slot.again?.older ?? false) || want.older,
160
+ };
161
+ await slot.running;
162
+ // The queued run starts when the current one ends; wait for it too.
163
+ if (slot.running)
164
+ await slot.running;
165
+ return { ...slot.status };
166
+ }
167
+ slot.running = this.runLoop(session, slot, want);
168
+ try {
169
+ await slot.running;
170
+ }
171
+ finally {
172
+ slot.running = undefined;
173
+ this.unloadIdle(slot);
174
+ }
175
+ return { ...slot.status };
176
+ }
177
+ /** Write every loaded store's pending changes (server shutdown). */
178
+ async flushAll() {
179
+ await Promise.all([
180
+ ...[...this.slots.values()].map(async (slot) => (await slot.store?.catch(() => undefined))?.settle()),
181
+ ...this.unloading.values(),
182
+ ]);
183
+ }
184
+ /**
185
+ * Delete a session's stored history (its record is gone). While it runs the session is
186
+ * marked, so a run in flight or a request meanwhile cannot load and write a new store for
187
+ * it; then its slot goes, and a session that comes back with this id starts afresh.
188
+ */
189
+ async remove(sessionId) {
190
+ const slot = this.slot(sessionId);
191
+ slot.gone = true;
192
+ slot.again = undefined;
193
+ slot.status = { state: 'unavailable', reason: REMOVED };
194
+ try {
195
+ const store = await slot.store?.catch(() => undefined);
196
+ if (store) {
197
+ await store.remove();
198
+ }
199
+ else {
200
+ await this.unloading.get(sessionId);
201
+ await this.deps.deleteStore(sessionId);
202
+ }
203
+ await slot.running;
204
+ }
205
+ finally {
206
+ if (this.slots.get(sessionId) === slot)
207
+ this.slots.delete(sessionId);
208
+ }
209
+ }
210
+ /**
211
+ * Delete the stored history of sessions that no longer exist (startup: left behind by a
212
+ * crash during deletion, or by an older build). A session in use here is kept.
213
+ */
214
+ sweep(exists) {
215
+ return this.deps.sweepStores((id) => this.slots.has(id) || exists(id));
216
+ }
217
+ /** The session's slot, now the most recently used. */
218
+ slot(sessionId) {
219
+ const slot = this.slots.get(sessionId) ?? { status: { state: 'ok' } };
220
+ this.slots.delete(sessionId);
221
+ this.slots.set(sessionId, slot);
222
+ return slot;
223
+ }
224
+ storeOf(sessionId, slot) {
225
+ if (!slot.store) {
226
+ const loading = (async () => {
227
+ await this.unloading.get(sessionId);
228
+ return this.deps.loadStore(sessionId);
229
+ })();
230
+ slot.store = loading;
231
+ loading.catch(() => {
232
+ if (slot.store === loading)
233
+ slot.store = undefined;
234
+ });
235
+ this.unloadIdle(slot);
236
+ }
237
+ return slot.store;
238
+ }
239
+ /**
240
+ * Past {@link MAX_LOADED_STORES}, drop the least recently used stores no run uses: each is
241
+ * written if it changed, and a later request loads it again (after that write).
242
+ */
243
+ unloadIdle(keep) {
244
+ let loaded = 0;
245
+ for (const slot of this.slots.values())
246
+ if (slot.store)
247
+ loaded++;
248
+ for (const [sessionId, slot] of this.slots) {
249
+ if (loaded <= MAX_LOADED_STORES)
250
+ break;
251
+ if (!slot.store || slot.running || slot.gone || slot === keep)
252
+ continue;
253
+ this.slots.delete(sessionId);
254
+ loaded--;
255
+ const writing = slot.store
256
+ .then((store) => store.settle())
257
+ .catch((err) => {
258
+ console.warn(`[EditHistory] Saving the history of ${sessionId} failed:`, err);
259
+ })
260
+ .finally(() => {
261
+ if (this.unloading.get(sessionId) === writing)
262
+ this.unloading.delete(sessionId);
263
+ });
264
+ this.unloading.set(sessionId, writing);
265
+ }
266
+ }
267
+ async runLoop(session, slot, want) {
268
+ let next = want;
269
+ while (next && !slot.gone) {
270
+ if (next.forward)
271
+ await this.runOnce(session, slot, false);
272
+ if (next.older && !slot.gone)
273
+ await this.runOnce(session, slot, true);
274
+ next = slot.again;
275
+ slot.again = undefined;
276
+ }
277
+ }
278
+ async runOnce(session, slot, older) {
279
+ let status;
280
+ try {
281
+ // This slot's store: a run still in flight after `remove()` cannot reach a later one.
282
+ const store = await this.storeOf(session.id, slot);
283
+ const path = slot.gone ? null : await this.deps.transcriptPath(session, store.getTranscriptId());
284
+ let found;
285
+ if (!path) {
286
+ found = false;
287
+ }
288
+ else if (older) {
289
+ found = await this.readOlder(session, path, store, slot);
290
+ }
291
+ else {
292
+ found = true;
293
+ for (let pass = 0; pass < MAX_FORWARD_PASSES && found && !slot.gone; pass++) {
294
+ const r = await this.readForward(session, path, store);
295
+ found = r.found;
296
+ if (!r.more)
297
+ break;
298
+ }
299
+ }
300
+ status = found ? { state: 'ok' } : { state: 'unavailable', reason: NO_TRANSCRIPT };
301
+ }
302
+ catch (err) {
303
+ status = { state: 'unavailable', reason: err instanceof Error ? err.message : String(err) };
304
+ }
305
+ if (!slot.gone)
306
+ slot.status = status;
307
+ }
308
+ forwardRequests(store) {
309
+ const out = [];
310
+ for (const relPath of store.knownSources()) {
311
+ const c = store.getCursor(relPath);
312
+ if (c)
313
+ out.push({ relPath, offset: c.offset });
314
+ }
315
+ return out;
316
+ }
317
+ /**
318
+ * One forward read of all sources. `more` = some file has unread bytes after it, or was cut
319
+ * short by the shared budget and left for the next pass. A transcript other than the stored
320
+ * one (`/clear`, resume) is read like a first read, once the stored one is read to its end.
321
+ */
322
+ async readForward(session, path, store) {
323
+ const own = this.storedTranscript(path, store);
324
+ if (own !== path && store.knownSources().length > 0) {
325
+ const previous = await this.readFrom(session, own, store, this.forwardRequests(store));
326
+ if (previous.more)
327
+ return { found: true, more: true };
328
+ }
329
+ return this.readFrom(session, path, store, own === path ? this.forwardRequests(store) : []);
330
+ }
331
+ /**
332
+ * The path of the conversation the store holds cursors for: `path` itself, or the previous
333
+ * conversation's transcript next to it (`/clear` and resume keep the project directory).
334
+ */
335
+ storedTranscript(path, store) {
336
+ const known = store.getTranscriptId();
337
+ return known === null || basename(path) === `${known}.jsonl` ? path : join(dirname(path), `${known}.jsonl`);
338
+ }
339
+ async readFrom(session, path, store, requests) {
340
+ const res = await this.deps.readSources(session, path, requests, this.budget);
341
+ if (!res)
342
+ return { found: false, more: false };
343
+ this.dropMissing(store, requests, res);
344
+ for (const [agentId, text] of Object.entries(res.metas)) {
345
+ if (!store.hasAgentMeta(agentId))
346
+ store.setAgentMeta(agentId, text);
347
+ }
348
+ const requested = new Set(requests.map((r) => r.relPath));
349
+ let more = false;
350
+ const candidates = [];
351
+ let parsed = false;
352
+ for (const f of res.files) {
353
+ // Each file is up to one budget (8 MiB) of synchronous parsing: yield between them.
354
+ if (parsed)
355
+ await breathe();
356
+ if (store.isRemoved())
357
+ break;
358
+ // A requested file reads on from its cursor, a new one its tail.
359
+ const available = requested.has(f.relPath) ? f.size - f.startOffset : f.size;
360
+ if (f.chunk.length < Math.min(this.budget, available)) {
361
+ more = true;
362
+ continue;
363
+ }
364
+ parsed = true;
365
+ const r = store.apply({
366
+ transcriptId: res.transcriptId,
367
+ source: f.relPath,
368
+ startOffset: f.startOffset,
369
+ chunk: f.chunk,
370
+ size: f.size,
371
+ });
372
+ candidates.push(...r.candidates);
373
+ if (f.startOffset + f.chunk.length < f.size)
374
+ more = true;
375
+ }
376
+ await this.addImages(session, store, candidates);
377
+ return { found: true, more };
378
+ }
379
+ async readOlder(session, path, store, slot) {
380
+ if (!store.backfillTruncated)
381
+ return true;
382
+ // Older lines belong to the stored conversation, even before a forward read moved on.
383
+ path = this.storedTranscript(path, store);
384
+ const sources = store.sourcesWithOlder().sort();
385
+ if (sources.length === 0)
386
+ return true;
387
+ // Take turns: start after the last source the previous click read.
388
+ const after = slot.olderAfter;
389
+ const first = after === undefined
390
+ ? 0
391
+ : Math.max(0, sources.findIndex((s) => s > after));
392
+ const picked = [...sources.slice(first), ...sources.slice(0, first)].slice(0, MAX_OLDER_SOURCES);
393
+ const requests = [];
394
+ for (const relPath of picked) {
395
+ const range = store.olderRange(relPath, this.budget);
396
+ if (range)
397
+ requests.push({ relPath, offset: range.offset, len: range.len });
398
+ }
399
+ if (requests.length === 0)
400
+ return true;
401
+ slot.olderAfter = picked[picked.length - 1];
402
+ const res = await this.deps.readSources(session, path, requests, this.budget * requests.length, {
403
+ onlyRequested: true,
404
+ });
405
+ if (!res)
406
+ return false;
407
+ if (res.transcriptId !== store.getTranscriptId())
408
+ return true;
409
+ this.dropMissing(store, requests, res);
410
+ const wanted = new Set(requests.map((r) => r.relPath));
411
+ const candidates = [];
412
+ let parsed = false;
413
+ for (const f of res.files) {
414
+ if (!wanted.has(f.relPath))
415
+ continue;
416
+ // Each file is up to one budget (8 MiB) of synchronous parsing: yield between them.
417
+ if (parsed)
418
+ await breathe();
419
+ parsed = true;
420
+ if (slot.gone)
421
+ return true;
422
+ const r = store.applyOlder({
423
+ transcriptId: res.transcriptId,
424
+ source: f.relPath,
425
+ startOffset: f.startOffset,
426
+ chunk: f.chunk,
427
+ size: f.size,
428
+ });
429
+ candidates.push(...r.candidates);
430
+ }
431
+ await this.addImages(session, store, candidates);
432
+ return true;
433
+ }
434
+ /** Requested files the reader no longer lists are gone: their cursors go too. */
435
+ dropMissing(store, requests, res) {
436
+ const listed = new Set(res.files.map((f) => f.relPath));
437
+ for (const r of requests)
438
+ if (!listed.has(r.relPath))
439
+ store.dropSource(r.relPath);
440
+ }
441
+ async addImages(session, store, candidates) {
442
+ const paths = [...new Set(candidates.flatMap((c) => c.paths))];
443
+ // A removed store keeps nothing: do not probe agent-printed paths for it.
444
+ if (paths.length === 0 || store.isRemoved())
445
+ return;
446
+ let probes;
447
+ try {
448
+ probes = await this.deps.probe(session, paths);
449
+ }
450
+ catch {
451
+ // The edits of this read are in; only its images are lost. The read itself succeeded.
452
+ return;
453
+ }
454
+ const byPath = new Map(paths.map((p, i) => [p, probes[i]]));
455
+ const newest = new Map();
456
+ for (const e of store.list())
457
+ newest.set(e.path, Math.max(newest.get(e.path) ?? -Infinity, e.ts));
458
+ store.addImageEntries(confirmImages(candidates, (p) => byPath.get(p), (p) => newest.get(p)));
459
+ }
460
+ }
461
+ //# sourceMappingURL=edit-history-sync.js.map