@thaut/claude-ide 0.0.0-stage → 1.0.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 (559) hide show
  1. package/bin/claude-ide.js +23 -0
  2. package/package.json +75 -4
  3. package/server/add-dirs.ts +126 -0
  4. package/server/auth.test.ts +148 -0
  5. package/server/auth.ts +235 -0
  6. package/server/bang.ts +54 -0
  7. package/server/btw-instructions.ts +19 -0
  8. package/server/btw-policy.ts +17 -0
  9. package/server/btw.ts +143 -0
  10. package/server/child-env.ts +29 -0
  11. package/server/cli-control.ts +255 -0
  12. package/server/cli-files.ts +55 -0
  13. package/server/cli-info.ts +73 -0
  14. package/server/config/account.ts +65 -0
  15. package/server/config/agents.ts +186 -0
  16. package/server/config/mcp.ts +86 -0
  17. package/server/config/memory.ts +193 -0
  18. package/server/config/paths.ts +156 -0
  19. package/server/config/plugins.ts +204 -0
  20. package/server/config/routes.ts +95 -0
  21. package/server/config/settings.ts +246 -0
  22. package/server/config.ts +46 -0
  23. package/server/db.ts +121 -0
  24. package/server/directories.test.ts +180 -0
  25. package/server/directories.ts +87 -0
  26. package/server/dist-version.ts +69 -0
  27. package/server/durable-tasks.ts +61 -0
  28. package/server/file-transfers.ts +79 -0
  29. package/server/files.test.ts +127 -0
  30. package/server/files.ts +386 -0
  31. package/server/fs-queue.ts +14 -0
  32. package/server/fsutil.ts +144 -0
  33. package/server/git.ts +86 -0
  34. package/server/goal-state.test.ts +64 -0
  35. package/server/goal-state.ts +78 -0
  36. package/server/history-pages.test.ts +619 -0
  37. package/server/history-pages.ts +513 -0
  38. package/server/http-channel-manager.ts +468 -0
  39. package/server/http-channel-pull.test.ts +314 -0
  40. package/server/http-channel-push.test.ts +364 -0
  41. package/server/http-channel-registration.test.ts +291 -0
  42. package/server/http-channel-routes.ts +73 -0
  43. package/server/http-channel-store.ts +142 -0
  44. package/server/http-channel-test-fixture.ts +103 -0
  45. package/server/ide-messages-mcp.test.ts +82 -0
  46. package/server/ide-messages-mcp.ts +47 -0
  47. package/server/idecron-mcp.ts +65 -0
  48. package/server/idecron.test.ts +79 -0
  49. package/server/idecron.ts +271 -0
  50. package/server/index.ts +336 -0
  51. package/server/live-session.test.ts +1126 -0
  52. package/server/live-session.ts +1165 -0
  53. package/server/project-routes.ts +46 -0
  54. package/server/projects.test.ts +28 -0
  55. package/server/push.ts +315 -0
  56. package/server/report-cache.ts +28 -0
  57. package/server/session-aliases.test.ts +57 -0
  58. package/server/session-aliases.ts +50 -0
  59. package/server/session-branch.ts +71 -0
  60. package/server/session-dirs.ts +189 -0
  61. package/server/session-file.test.ts +134 -0
  62. package/server/session-file.ts +113 -0
  63. package/server/session-messaging.test.ts +297 -0
  64. package/server/session-messaging.ts +165 -0
  65. package/server/sessions.ts +638 -0
  66. package/server/subagents.test.ts +43 -0
  67. package/server/subagents.ts +58 -0
  68. package/server/tool-images.test.ts +76 -0
  69. package/server/tool-images.ts +45 -0
  70. package/server/turn-changes.ts +308 -0
  71. package/server/worktree-git.ts +87 -0
  72. package/server/worktrees.ts +138 -0
  73. package/server/ws.test.ts +195 -0
  74. package/server/ws.ts +197 -0
  75. package/shared/agent-model.ts +15 -0
  76. package/shared/agent-tasks.ts +12 -0
  77. package/shared/config.ts +175 -0
  78. package/shared/directories.ts +12 -0
  79. package/shared/files.ts +51 -0
  80. package/shared/http-channels.ts +63 -0
  81. package/shared/message-window.ts +189 -0
  82. package/shared/project.ts +77 -0
  83. package/shared/protocol.ts +203 -0
  84. package/shared/task-types.ts +4 -0
  85. package/shared/todos.ts +40 -0
  86. package/shared/write-policy.ts +9 -0
  87. package/web/dist/assets/AgentView-wrTKgcJ3.js +1 -0
  88. package/web/dist/assets/AnnotationSelection-BZmNmwVW.js +7 -0
  89. package/web/dist/assets/ChangesView-G1WTzh4q.js +2 -0
  90. package/web/dist/assets/FilesView-DPgAbYt_.js +6 -0
  91. package/web/dist/assets/Form-BKTwiYyw.js +1 -0
  92. package/web/dist/assets/HomePage-Dq2apjBe.js +1 -0
  93. package/web/dist/assets/Markdown-IwP-xF29.js +67 -0
  94. package/web/dist/assets/MessageList-DD9GypSy.js +15 -0
  95. package/web/dist/assets/Modal-g2rv8I_n.js +1 -0
  96. package/web/dist/assets/NotificationSettings-DpuLO_Bv.js +1 -0
  97. package/web/dist/assets/OverviewPage-Dx6miabE.js +1 -0
  98. package/web/dist/assets/ProjectHome-CousyTLL.js +1 -0
  99. package/web/dist/assets/SessionView-CPVL7_HO.js +2 -0
  100. package/web/dist/assets/SettingsPage-D_oYSnW2.js +7 -0
  101. package/web/dist/assets/WorktreesView-C7e0HV__.js +1 -0
  102. package/web/dist/assets/abap-CLvhMVsD.js +1 -0
  103. package/web/dist/assets/actionscript-3-Cs6JJXT5.js +1 -0
  104. package/web/dist/assets/ada-X3Y2mlq2.js +1 -0
  105. package/web/dist/assets/ahk-DwADkxyd.js +1 -0
  106. package/web/dist/assets/ahk2-_6rzW9BS.js +1 -0
  107. package/web/dist/assets/andromeeda-vGVdxbeo.js +1 -0
  108. package/web/dist/assets/angular-html-Cw29lun8.js +1 -0
  109. package/web/dist/assets/angular-ts-BXz-O3SJ.js +1 -0
  110. package/web/dist/assets/apache-U0d_L8uA.js +1 -0
  111. package/web/dist/assets/apex-KFGFyKwG.js +1 -0
  112. package/web/dist/assets/apl-xBchdRhy.js +1 -0
  113. package/web/dist/assets/applescript-CCn79oCD.js +1 -0
  114. package/web/dist/assets/ara-4CJ0cIlV.js +1 -0
  115. package/web/dist/assets/asciidoc-SCjQUq34.js +1 -0
  116. package/web/dist/assets/asm-Cmm7eHzH.js +1 -0
  117. package/web/dist/assets/astro-1q6DGuN3.js +1 -0
  118. package/web/dist/assets/aurora-x-CDeNXAV0.js +1 -0
  119. package/web/dist/assets/awk-BWXHIvNe.js +1 -0
  120. package/web/dist/assets/ayu-dark-DluEY0Gj.js +1 -0
  121. package/web/dist/assets/ayu-light-C3h-C4tm.js +1 -0
  122. package/web/dist/assets/ayu-mirage-Bqwy1Gya.js +1 -0
  123. package/web/dist/assets/ballerina-B7ZEbQpA.js +1 -0
  124. package/web/dist/assets/bat-qdxAdBWQ.js +1 -0
  125. package/web/dist/assets/beancount-C2IN4Vk8.js +1 -0
  126. package/web/dist/assets/berry-DKpUyyne.js +1 -0
  127. package/web/dist/assets/bibtex-Ci_nEsc7.js +1 -0
  128. package/web/dist/assets/bicep-CUHmPFLl.js +1 -0
  129. package/web/dist/assets/bird2-CYIUgYqB.js +1 -0
  130. package/web/dist/assets/blade-Cqdv4oZN.js +1 -0
  131. package/web/dist/assets/bsl-CVED6lJ1.js +1 -0
  132. package/web/dist/assets/c-D9gIxSfx.js +1 -0
  133. package/web/dist/assets/c3-D0apG41V.js +1 -0
  134. package/web/dist/assets/cadence-CQ2zXKGN.js +1 -0
  135. package/web/dist/assets/cairo-DRAZz75c.js +1 -0
  136. package/web/dist/assets/catppuccin-frappe-C9Q5zVZY.js +1 -0
  137. package/web/dist/assets/catppuccin-latte-CtsyrtPq.js +1 -0
  138. package/web/dist/assets/catppuccin-macchiato-R8nAf1vR.js +1 -0
  139. package/web/dist/assets/catppuccin-mocha-DjuaAJKi.js +1 -0
  140. package/web/dist/assets/chapel-CuseGurG.js +1 -0
  141. package/web/dist/assets/clarity-CtfBBspg.js +1 -0
  142. package/web/dist/assets/clojure-DqKBuwfJ.js +1 -0
  143. package/web/dist/assets/cmake-Bj61d0ZC.js +1 -0
  144. package/web/dist/assets/cobol-Bdpj3Qoh.js +1 -0
  145. package/web/dist/assets/codeowners-C8r90Shi.js +1 -0
  146. package/web/dist/assets/codeql-oeQT6MSM.js +1 -0
  147. package/web/dist/assets/coffee-Sk8pHFjs.js +1 -0
  148. package/web/dist/assets/common-lisp-Cv5bFMCO.js +1 -0
  149. package/web/dist/assets/coq-CGaSfhwR.js +1 -0
  150. package/web/dist/assets/core-BxI4V4-T.js +12 -0
  151. package/web/dist/assets/cpp-BQfBYKx3.js +1 -0
  152. package/web/dist/assets/crystal-B3j7DoOZ.js +1 -0
  153. package/web/dist/assets/csharp-dmMxLVLW.js +1 -0
  154. package/web/dist/assets/css-DpFbYmRh.js +1 -0
  155. package/web/dist/assets/csv-Dx-8-gkx.js +1 -0
  156. package/web/dist/assets/cue-CE9AQfxI.js +1 -0
  157. package/web/dist/assets/cypher-ClKdZ_lG.js +1 -0
  158. package/web/dist/assets/d-qD-0Kul2.js +1 -0
  159. package/web/dist/assets/dark-plus-Cs2F2srj.js +1 -0
  160. package/web/dist/assets/dart-CHZY9Xhz.js +1 -0
  161. package/web/dist/assets/dax-BkyTk9wS.js +1 -0
  162. package/web/dist/assets/desktop-Dlh5hvp9.js +1 -0
  163. package/web/dist/assets/diff-woXpYk--.js +1 -0
  164. package/web/dist/assets/docker-IyjqRm3v.js +1 -0
  165. package/web/dist/assets/dotenv-CuH5my32.js +1 -0
  166. package/web/dist/assets/dracula-BHWKrbxM.js +1 -0
  167. package/web/dist/assets/dracula-soft-5eyTD99u.js +1 -0
  168. package/web/dist/assets/dream-maker-DW3nJb8Q.js +1 -0
  169. package/web/dist/assets/edge-DXQWmXTB.js +1 -0
  170. package/web/dist/assets/elixir-CrDIY2Vo.js +1 -0
  171. package/web/dist/assets/elm-BslFEg8K.js +1 -0
  172. package/web/dist/assets/emacs-lisp-DR2vtWaj.js +1 -0
  173. package/web/dist/assets/engine-javascript-DJg3-Vu2.js +141 -0
  174. package/web/dist/assets/erb-CUYhg0Ws.js +1 -0
  175. package/web/dist/assets/erlang-BXdgHVfi.js +1 -0
  176. package/web/dist/assets/everforest-dark-sB-x3p7T.js +1 -0
  177. package/web/dist/assets/everforest-light-Df2xbC6M.js +1 -0
  178. package/web/dist/assets/fennel-DQxkIbk2.js +1 -0
  179. package/web/dist/assets/fish-BJitypiv.js +1 -0
  180. package/web/dist/assets/fluent-C03EYrpw.js +1 -0
  181. package/web/dist/assets/format-DrBC7w0A.js +1 -0
  182. package/web/dist/assets/fortran-fixed-form-D6mN0kjy.js +1 -0
  183. package/web/dist/assets/fortran-free-form-CYNrtFtB.js +1 -0
  184. package/web/dist/assets/fsharp-CwoEF39o.js +1 -0
  185. package/web/dist/assets/gdresource-CZkFGo3B.js +1 -0
  186. package/web/dist/assets/gdscript-DYvRD3hg.js +1 -0
  187. package/web/dist/assets/gdshader-CBce3t8t.js +1 -0
  188. package/web/dist/assets/genie-CV2tkWYe.js +1 -0
  189. package/web/dist/assets/gherkin-DExj1W_8.js +1 -0
  190. package/web/dist/assets/git-commit-dUGrvm6Q.js +1 -0
  191. package/web/dist/assets/git-rebase-4zCRqF28.js +1 -0
  192. package/web/dist/assets/github-dark-C-LZuMrd.js +1 -0
  193. package/web/dist/assets/github-dark-default-DXG-b-1a.js +1 -0
  194. package/web/dist/assets/github-dark-dimmed-Bx1FflLF.js +1 -0
  195. package/web/dist/assets/github-dark-high-contrast-B_tTalzw.js +1 -0
  196. package/web/dist/assets/github-light-EUqPIrTm.js +1 -0
  197. package/web/dist/assets/github-light-default-BXViO-2h.js +1 -0
  198. package/web/dist/assets/github-light-high-contrast-B68TUdTA.js +1 -0
  199. package/web/dist/assets/gleam-CSRkHgEL.js +1 -0
  200. package/web/dist/assets/glimmer-js-7adTRvH4.js +1 -0
  201. package/web/dist/assets/glimmer-ts-IdSskDYw.js +1 -0
  202. package/web/dist/assets/glsl-Qzy53KDt.js +1 -0
  203. package/web/dist/assets/gn-CAXYAgV-.js +1 -0
  204. package/web/dist/assets/gnuplot-7GGW24-e.js +1 -0
  205. package/web/dist/assets/go-rLFTqkRN.js +1 -0
  206. package/web/dist/assets/graphql-CV_puxbB.js +1 -0
  207. package/web/dist/assets/groovy-CacY0gHj.js +1 -0
  208. package/web/dist/assets/gruvbox-dark-hard-C820rvS2.js +1 -0
  209. package/web/dist/assets/gruvbox-dark-medium-BPjhmG05.js +1 -0
  210. package/web/dist/assets/gruvbox-dark-soft-MrdJrrXF.js +1 -0
  211. package/web/dist/assets/gruvbox-light-hard-BC_s9l72.js +1 -0
  212. package/web/dist/assets/gruvbox-light-medium-BAWPOn9u.js +1 -0
  213. package/web/dist/assets/gruvbox-light-soft-BSMLrYjP.js +1 -0
  214. package/web/dist/assets/hack-C5GPjiLV.js +1 -0
  215. package/web/dist/assets/haml-BQ5YR1DQ.js +1 -0
  216. package/web/dist/assets/handlebars-DnhRK9A1.js +1 -0
  217. package/web/dist/assets/haskell-D8IpX4py.js +1 -0
  218. package/web/dist/assets/haxe-C8oCnwJJ.js +1 -0
  219. package/web/dist/assets/hcl-Dh228itO.js +1 -0
  220. package/web/dist/assets/highlight.worker-CY-ZeDAl.js +1 -0
  221. package/web/dist/assets/hjson-CxZEssPk.js +1 -0
  222. package/web/dist/assets/hlsl-Cvrh5tZx.js +1 -0
  223. package/web/dist/assets/horizon-CE9ld1lL.js +1 -0
  224. package/web/dist/assets/horizon-bright-DSNQnXHK.js +1 -0
  225. package/web/dist/assets/houston-CsvMBhTu.js +1 -0
  226. package/web/dist/assets/html-CmHiEKV9.js +1 -0
  227. package/web/dist/assets/html-derivative-BXE-rYIK.js +1 -0
  228. package/web/dist/assets/http-DPjjqtcL.js +1 -0
  229. package/web/dist/assets/hurl-AMBpSloO.js +1 -0
  230. package/web/dist/assets/hxml-D7Y24wKF.js +1 -0
  231. package/web/dist/assets/hy-CZbG8q4J.js +1 -0
  232. package/web/dist/assets/imba-DsUTQ-LC.js +1 -0
  233. package/web/dist/assets/index-DNPnl4sh.css +2 -0
  234. package/web/dist/assets/index-DQh72lEx.js +14 -0
  235. package/web/dist/assets/ini-B5eOa1yu.js +1 -0
  236. package/web/dist/assets/java-CEM7Tdw_.js +1 -0
  237. package/web/dist/assets/javascript-CU3PLowd.js +1 -0
  238. package/web/dist/assets/jinja-fR0x_9Xy.js +1 -0
  239. package/web/dist/assets/jison-BoKsxs5M.js +1 -0
  240. package/web/dist/assets/json-BM2FWsRB.js +1 -0
  241. package/web/dist/assets/json5-BR5RXkoi.js +1 -0
  242. package/web/dist/assets/jsonc-CYpm1nAK.js +1 -0
  243. package/web/dist/assets/jsonl-CmCQp5Yx.js +1 -0
  244. package/web/dist/assets/jsonnet-CJTPZ8u_.js +1 -0
  245. package/web/dist/assets/jssm-DXw9l8Rf.js +1 -0
  246. package/web/dist/assets/jsx-B5fTecQP.js +1 -0
  247. package/web/dist/assets/julia-DGyDG39_.js +1 -0
  248. package/web/dist/assets/just-B-RyLcSR.js +1 -0
  249. package/web/dist/assets/kanagawa-dragon-Bozd7klA.js +1 -0
  250. package/web/dist/assets/kanagawa-lotus-BmuOG9ZW.js +1 -0
  251. package/web/dist/assets/kanagawa-wave-BML5mF2J.js +1 -0
  252. package/web/dist/assets/kdl-CsD5j6eV.js +1 -0
  253. package/web/dist/assets/kotlin-DhhofPvG.js +1 -0
  254. package/web/dist/assets/kusto-C7mF5XQf.js +1 -0
  255. package/web/dist/assets/langs-CciPKqaF.js +1 -0
  256. package/web/dist/assets/laserwave-C_8bwKvT.js +1 -0
  257. package/web/dist/assets/latex-CV1pQelm.js +1 -0
  258. package/web/dist/assets/lean-CewbzKMR.js +1 -0
  259. package/web/dist/assets/less-DVTAwKKz.js +1 -0
  260. package/web/dist/assets/lib-DlcVMUqT.js +1 -0
  261. package/web/dist/assets/light-plus-DVQuIRkW.js +1 -0
  262. package/web/dist/assets/liquid-9p05RSzQ.js +1 -0
  263. package/web/dist/assets/llvm-DqHOMS-s.js +1 -0
  264. package/web/dist/assets/log-BNLmms1o.js +1 -0
  265. package/web/dist/assets/logo-Cluzi2Zq.js +1 -0
  266. package/web/dist/assets/lua-CIPVh0S-.js +1 -0
  267. package/web/dist/assets/luau-VGWVLoqX.js +1 -0
  268. package/web/dist/assets/make-Dixweg8N.js +1 -0
  269. package/web/dist/assets/markdown-BYOwaDjH.js +1 -0
  270. package/web/dist/assets/marko-h2h6YUMS.js +1 -0
  271. package/web/dist/assets/material-theme-Bm3Qr25_.js +1 -0
  272. package/web/dist/assets/material-theme-darker-2IIEA8gg.js +1 -0
  273. package/web/dist/assets/material-theme-lighter-uhdI0v04.js +1 -0
  274. package/web/dist/assets/material-theme-ocean-CHQ94UKr.js +1 -0
  275. package/web/dist/assets/material-theme-palenight-B5W6OYN7.js +1 -0
  276. package/web/dist/assets/matlab-D7qyCx1q.js +1 -0
  277. package/web/dist/assets/mdc-yAyZMdoC.js +1 -0
  278. package/web/dist/assets/mdx-DQZ5AkYe.js +1 -0
  279. package/web/dist/assets/mermaid-BxpfTIzt.js +1 -0
  280. package/web/dist/assets/message-font-size-DoaZfwkT.js +1 -0
  281. package/web/dist/assets/min-dark-BSWPekZh.js +1 -0
  282. package/web/dist/assets/min-light-DDpmG2fV.js +1 -0
  283. package/web/dist/assets/mipsasm-BMqwQI7S.js +1 -0
  284. package/web/dist/assets/mojo-fg40_ntQ.js +1 -0
  285. package/web/dist/assets/monokai-BscAjiQZ.js +1 -0
  286. package/web/dist/assets/moonbit-D9W784E_.js +1 -0
  287. package/web/dist/assets/move-BYnGhZoM.js +1 -0
  288. package/web/dist/assets/narrat-_X_XdTYD.js +1 -0
  289. package/web/dist/assets/nextflow-BoTnSBQ_.js +1 -0
  290. package/web/dist/assets/nextflow-groovy-D7IRt6Pp.js +1 -0
  291. package/web/dist/assets/nginx-jWINucNT.js +1 -0
  292. package/web/dist/assets/night-owl-DhmEMT88.js +1 -0
  293. package/web/dist/assets/night-owl-light-eJ-hLW7d.js +1 -0
  294. package/web/dist/assets/nim-Dtt9DZ0t.js +1 -0
  295. package/web/dist/assets/nix-Cl_7024t.js +1 -0
  296. package/web/dist/assets/nord-Cb4Vim4T.js +1 -0
  297. package/web/dist/assets/nsis-CjNLYCLg.js +1 -0
  298. package/web/dist/assets/nushell--0fT3PI1.js +1 -0
  299. package/web/dist/assets/objective-c-D1A_Heim.js +1 -0
  300. package/web/dist/assets/objective-cpp-BsSzOQcm.js +1 -0
  301. package/web/dist/assets/ocaml-O90oeIOV.js +1 -0
  302. package/web/dist/assets/odin-B1RWQWA5.js +1 -0
  303. package/web/dist/assets/one-dark-pro-C6YyR8OD.js +1 -0
  304. package/web/dist/assets/one-light-D7Lr4KcI.js +1 -0
  305. package/web/dist/assets/openscad-BUDT5pXO.js +1 -0
  306. package/web/dist/assets/org-ClD5huOa.js +1 -0
  307. package/web/dist/assets/pascal-4ZHwLPI5.js +1 -0
  308. package/web/dist/assets/perl-BFegxyeJ.js +1 -0
  309. package/web/dist/assets/php-B-qOA3Y4.js +1 -0
  310. package/web/dist/assets/pkl-ot-7Btpt.js +1 -0
  311. package/web/dist/assets/plastic-DQwYfKfQ.js +1 -0
  312. package/web/dist/assets/plsql-DGHpHOYJ.js +1 -0
  313. package/web/dist/assets/po-BiJDBrnU.js +1 -0
  314. package/web/dist/assets/poimandres-DRFjx7u4.js +1 -0
  315. package/web/dist/assets/polar-C7UOKdEL.js +1 -0
  316. package/web/dist/assets/postcss-BXeXVLqQ.js +1 -0
  317. package/web/dist/assets/powerquery-DNMTfnFr.js +1 -0
  318. package/web/dist/assets/powershell-QtSnmXcu.js +1 -0
  319. package/web/dist/assets/prisma-DMgNhnYS.js +1 -0
  320. package/web/dist/assets/prolog-iXnhIJG7.js +1 -0
  321. package/web/dist/assets/proto-DB4EqR-F.js +1 -0
  322. package/web/dist/assets/pug-Cq0NBPu-.js +1 -0
  323. package/web/dist/assets/puppet-CDv2pdJW.js +1 -0
  324. package/web/dist/assets/purescript-9MfHhQsQ.js +1 -0
  325. package/web/dist/assets/python-gzcpVVnB.js +1 -0
  326. package/web/dist/assets/qml-CYUsa87k.js +1 -0
  327. package/web/dist/assets/qmldir-DCQb3MpD.js +1 -0
  328. package/web/dist/assets/qss-Fe1Jh2GI.js +1 -0
  329. package/web/dist/assets/r-4xJhgbOf.js +1 -0
  330. package/web/dist/assets/racket-DcIDlBhZ.js +1 -0
  331. package/web/dist/assets/raku-B3gFvitq.js +1 -0
  332. package/web/dist/assets/razor-BlHaOHx8.js +1 -0
  333. package/web/dist/assets/rbs-C0MpOrj4.js +1 -0
  334. package/web/dist/assets/react-B4UGpcWC.js +1 -0
  335. package/web/dist/assets/red-CYlAq2b0.js +1 -0
  336. package/web/dist/assets/reg-CRGYupPL.js +1 -0
  337. package/web/dist/assets/regexp-BUSxjv2o.js +1 -0
  338. package/web/dist/assets/rel-BtDbiS_P.js +1 -0
  339. package/web/dist/assets/riscv-Ckw8ddFX.js +1 -0
  340. package/web/dist/assets/ron-VUp2lXgN.js +1 -0
  341. package/web/dist/assets/rose-pine-aMsaMD1D.js +1 -0
  342. package/web/dist/assets/rose-pine-dawn-8PwUyIlP.js +1 -0
  343. package/web/dist/assets/rose-pine-moon-DO-qXg-I.js +1 -0
  344. package/web/dist/assets/rosmsg-CAekHB0j.js +1 -0
  345. package/web/dist/assets/rst-zjfguEh7.js +1 -0
  346. package/web/dist/assets/ruby-4oR9waBK.js +1 -0
  347. package/web/dist/assets/rust-DpiIf5JO.js +1 -0
  348. package/web/dist/assets/sas-Do4V_2i9.js +1 -0
  349. package/web/dist/assets/sass-DXrisJhu.js +1 -0
  350. package/web/dist/assets/scala-BiFt_7mu.js +1 -0
  351. package/web/dist/assets/scheme-DQCgrYNe.js +1 -0
  352. package/web/dist/assets/scss-CW6JPtag.js +1 -0
  353. package/web/dist/assets/sdbl-bTVj8UrX.js +1 -0
  354. package/web/dist/assets/shaderlab-C4rnI7x0.js +1 -0
  355. package/web/dist/assets/shellscript-zBmOMFJh.js +1 -0
  356. package/web/dist/assets/shellsession-DxGUTWaf.js +1 -0
  357. package/web/dist/assets/slack-dark-DnToyrRv.js +1 -0
  358. package/web/dist/assets/slack-ochin-B2OO5cIa.js +1 -0
  359. package/web/dist/assets/smalltalk-BHpQb5nM.js +1 -0
  360. package/web/dist/assets/smithy-Dgaz7aur.js +1 -0
  361. package/web/dist/assets/snazzy-light-4G7pJPwS.js +1 -0
  362. package/web/dist/assets/solarized-dark-b97czJOk.js +1 -0
  363. package/web/dist/assets/solarized-light-DFPxpIYQ.js +1 -0
  364. package/web/dist/assets/solidity-Bj7E1mRn.js +1 -0
  365. package/web/dist/assets/soy-DVtw20Yn.js +1 -0
  366. package/web/dist/assets/sparql-C0ia5K60.js +1 -0
  367. package/web/dist/assets/splunk-BC2Px7Mm.js +1 -0
  368. package/web/dist/assets/sql-DGCM-jag.js +1 -0
  369. package/web/dist/assets/ssh-config-BgfXC-Er.js +1 -0
  370. package/web/dist/assets/stata-DfuMOXna.js +1 -0
  371. package/web/dist/assets/stylus-B6D30XZt.js +1 -0
  372. package/web/dist/assets/surrealql-CBoRWxfR.js +1 -0
  373. package/web/dist/assets/svelte-g_uCUem5.js +1 -0
  374. package/web/dist/assets/swift-CyEgAFGc.js +1 -0
  375. package/web/dist/assets/synthwave-84-nFMaYfgc.js +1 -0
  376. package/web/dist/assets/system-verilog-Dh5ihWbf.js +1 -0
  377. package/web/dist/assets/systemd-BxMlprV5.js +1 -0
  378. package/web/dist/assets/talonscript-CohzipZa.js +1 -0
  379. package/web/dist/assets/tasl-DMoTqEGO.js +1 -0
  380. package/web/dist/assets/tcl-CZd0xW_V.js +1 -0
  381. package/web/dist/assets/templ-N5C1hJ4Y.js +1 -0
  382. package/web/dist/assets/terraform-DswuEJGm.js +1 -0
  383. package/web/dist/assets/tex-mx6xDwuK.js +1 -0
  384. package/web/dist/assets/themes-CNElGaXw.js +1 -0
  385. package/web/dist/assets/tokyo-night-oM2G3aXe.js +1 -0
  386. package/web/dist/assets/toml-CcmNWLt0.js +1 -0
  387. package/web/dist/assets/ts-tags-C-j71FRb.js +1 -0
  388. package/web/dist/assets/tsv-sltzmVWM.js +1 -0
  389. package/web/dist/assets/tsx-z04y7qR9.js +1 -0
  390. package/web/dist/assets/turtle-ByJddavk.js +1 -0
  391. package/web/dist/assets/twig-Bz57G_nl.js +1 -0
  392. package/web/dist/assets/typescript-RMqVTmzR.js +1 -0
  393. package/web/dist/assets/typespec-BGhR2SqG.js +1 -0
  394. package/web/dist/assets/typst-BgkqA2Pg.js +1 -0
  395. package/web/dist/assets/v-DBj4K9d_.js +1 -0
  396. package/web/dist/assets/vala-zf12oZj6.js +1 -0
  397. package/web/dist/assets/vb-DOwzaHZ0.js +1 -0
  398. package/web/dist/assets/verilog-B2gwz-sJ.js +1 -0
  399. package/web/dist/assets/vesper-DdrHHSXu.js +1 -0
  400. package/web/dist/assets/vhdl-BroJfC0k.js +1 -0
  401. package/web/dist/assets/viml-DvXPmvsu.js +1 -0
  402. package/web/dist/assets/vitesse-black-fwtXNY1n.js +1 -0
  403. package/web/dist/assets/vitesse-dark-BZCL-v6S.js +1 -0
  404. package/web/dist/assets/vitesse-light-VbXTXTou.js +1 -0
  405. package/web/dist/assets/vue-CQgvQytb.js +1 -0
  406. package/web/dist/assets/vue-html-Dhl6fh9l.js +1 -0
  407. package/web/dist/assets/vue-vine-Dw_uU59K.js +1 -0
  408. package/web/dist/assets/vyper-CgoNMtux.js +1 -0
  409. package/web/dist/assets/wasm-ByWQv1Qj.js +1 -0
  410. package/web/dist/assets/wenyan-C8pVoKbM.js +1 -0
  411. package/web/dist/assets/wgsl-BsKzXJz4.js +1 -0
  412. package/web/dist/assets/wikitext-ClFFjSW2.js +1 -0
  413. package/web/dist/assets/wit-DdvCle-K.js +1 -0
  414. package/web/dist/assets/wolfram-DLL8P-h_.js +1 -0
  415. package/web/dist/assets/xml-B49klYQ3.js +1 -0
  416. package/web/dist/assets/xsl-D7tnD3qq.js +1 -0
  417. package/web/dist/assets/yaml-DNQ5_xkL.js +1 -0
  418. package/web/dist/assets/zenscript-BnlCZFoB.js +1 -0
  419. package/web/dist/assets/zig-CMLA9XwU.js +1 -0
  420. package/web/dist/icons/apple-touch-icon.png +0 -0
  421. package/web/dist/icons/badge-96.png +0 -0
  422. package/web/dist/icons/icon-192.png +0 -0
  423. package/web/dist/icons/icon-512.png +0 -0
  424. package/web/dist/icons/icon.svg +4 -0
  425. package/web/dist/icons/maskable-192.png +0 -0
  426. package/web/dist/icons/maskable-512.png +0 -0
  427. package/web/dist/index.html +30 -0
  428. package/web/dist/manifest.webmanifest +20 -0
  429. package/web/dist/sw.js +176 -0
  430. package/web/dist/version.json +1 -0
  431. package/web/src/App.tsx +97 -0
  432. package/web/src/agent-history-flow.test.ts +191 -0
  433. package/web/src/agent-history-flow.ts +58 -0
  434. package/web/src/agent-owners.ts +30 -0
  435. package/web/src/annotation-selection.test.ts +36 -0
  436. package/web/src/annotation-selection.ts +123 -0
  437. package/web/src/annotations.test.ts +279 -0
  438. package/web/src/annotations.ts +191 -0
  439. package/web/src/api.ts +27 -0
  440. package/web/src/auth-events.ts +11 -0
  441. package/web/src/auth-lifecycle.test.ts +105 -0
  442. package/web/src/clipboard.ts +32 -0
  443. package/web/src/components/ActivityNotifier.tsx +22 -0
  444. package/web/src/components/AnnotationEditor.tsx +135 -0
  445. package/web/src/components/AnnotationSelection.tsx +122 -0
  446. package/web/src/components/Automation.tsx +390 -0
  447. package/web/src/components/CodeView.tsx +121 -0
  448. package/web/src/components/Composer.tsx +552 -0
  449. package/web/src/components/ComposerAnnotations.tsx +45 -0
  450. package/web/src/components/Confirmation.tsx +91 -0
  451. package/web/src/components/CopyButton.tsx +23 -0
  452. package/web/src/components/DiffView.tsx +94 -0
  453. package/web/src/components/DirectoryPicker.tsx +230 -0
  454. package/web/src/components/FileCopyMenu.tsx +58 -0
  455. package/web/src/components/FileEntryActions.tsx +95 -0
  456. package/web/src/components/ImagePreview.tsx +68 -0
  457. package/web/src/components/Items.tsx +608 -0
  458. package/web/src/components/Markdown.tsx +73 -0
  459. package/web/src/components/MessageList.tsx +458 -0
  460. package/web/src/components/Modal.tsx +72 -0
  461. package/web/src/components/NotificationEntry.tsx +31 -0
  462. package/web/src/components/NotificationSettings.tsx +226 -0
  463. package/web/src/components/NotificationToasts.tsx +59 -0
  464. package/web/src/components/OpenTabs.tsx +112 -0
  465. package/web/src/components/Pending.tsx +175 -0
  466. package/web/src/components/ProjectExtras.tsx +299 -0
  467. package/web/src/components/ProjectNav.tsx +47 -0
  468. package/web/src/components/SessionActions.tsx +271 -0
  469. package/web/src/components/SessionChannels.tsx +288 -0
  470. package/web/src/components/SessionExtras.tsx +263 -0
  471. package/web/src/components/SettingsLink.tsx +21 -0
  472. package/web/src/components/Shell.tsx +320 -0
  473. package/web/src/components/SvgPreview.tsx +18 -0
  474. package/web/src/components/TodoPanel.tsx +37 -0
  475. package/web/src/components/VersionNotice.tsx +40 -0
  476. package/web/src/components/icons.tsx +43 -0
  477. package/web/src/diff.ts +62 -0
  478. package/web/src/drafts.test.ts +80 -0
  479. package/web/src/drafts.ts +64 -0
  480. package/web/src/format.ts +5 -0
  481. package/web/src/highlight.worker.ts +49 -0
  482. package/web/src/hooks.ts +59 -0
  483. package/web/src/item-view-state.test.ts +51 -0
  484. package/web/src/item-view-state.ts +43 -0
  485. package/web/src/live-history-flow.test.ts +77 -0
  486. package/web/src/live-merge.test.ts +64 -0
  487. package/web/src/live-merge.ts +50 -0
  488. package/web/src/live-message-hydration.test.ts +253 -0
  489. package/web/src/live.ts +404 -0
  490. package/web/src/main.tsx +27 -0
  491. package/web/src/markdown-assets.test.ts +70 -0
  492. package/web/src/markdown-assets.ts +78 -0
  493. package/web/src/markdown-html.test.ts +84 -0
  494. package/web/src/markdown-html.ts +60 -0
  495. package/web/src/markdown-svg.test.ts +37 -0
  496. package/web/src/markdown-svg.ts +42 -0
  497. package/web/src/markdown-tables.test.ts +34 -0
  498. package/web/src/markdown-tables.ts +17 -0
  499. package/web/src/message-bodies.ts +117 -0
  500. package/web/src/message-cache.test.ts +208 -0
  501. package/web/src/message-cache.ts +158 -0
  502. package/web/src/message-font-size.test.ts +41 -0
  503. package/web/src/message-font-size.ts +57 -0
  504. package/web/src/message-time.test.ts +32 -0
  505. package/web/src/message-time.ts +26 -0
  506. package/web/src/notify.ts +132 -0
  507. package/web/src/open-tabs.test.ts +69 -0
  508. package/web/src/open-tabs.ts +110 -0
  509. package/web/src/pages/AgentView.tsx +138 -0
  510. package/web/src/pages/ChangesView.tsx +193 -0
  511. package/web/src/pages/FilesView.tsx +406 -0
  512. package/web/src/pages/HomePage.tsx +96 -0
  513. package/web/src/pages/LoginPage.tsx +56 -0
  514. package/web/src/pages/OverviewPage.tsx +124 -0
  515. package/web/src/pages/ProjectHome.tsx +136 -0
  516. package/web/src/pages/SessionView.tsx +238 -0
  517. package/web/src/pages/WorktreesView.tsx +197 -0
  518. package/web/src/pages/settings/AccountPage.tsx +149 -0
  519. package/web/src/pages/settings/AgentsPage.tsx +182 -0
  520. package/web/src/pages/settings/ConfigPage.tsx +181 -0
  521. package/web/src/pages/settings/GeneralPage.tsx +27 -0
  522. package/web/src/pages/settings/HooksPage.tsx +244 -0
  523. package/web/src/pages/settings/McpPage.tsx +310 -0
  524. package/web/src/pages/settings/MemoryPage.tsx +309 -0
  525. package/web/src/pages/settings/PermissionsPage.tsx +160 -0
  526. package/web/src/pages/settings/PluginsPage.tsx +251 -0
  527. package/web/src/pages/settings/SettingsPage.tsx +141 -0
  528. package/web/src/pages/settings/SkillsPage.tsx +142 -0
  529. package/web/src/pages/settings/ui.tsx +165 -0
  530. package/web/src/preference-document.ts +133 -0
  531. package/web/src/project-actions.ts +18 -0
  532. package/web/src/project-files-state.test.ts +49 -0
  533. package/web/src/project-files-state.ts +64 -0
  534. package/web/src/project-images.ts +13 -0
  535. package/web/src/pwa.ts +33 -0
  536. package/web/src/queryClient.ts +5 -0
  537. package/web/src/reasoning-preference.test.ts +39 -0
  538. package/web/src/reasoning-preference.ts +42 -0
  539. package/web/src/session-confirmations.ts +36 -0
  540. package/web/src/stream-replay.test.ts +163 -0
  541. package/web/src/styles.css +231 -0
  542. package/web/src/tool-result-body.test.ts +115 -0
  543. package/web/src/transcript.test.ts +186 -0
  544. package/web/src/transcript.ts +583 -0
  545. package/web/src/turns.test.ts +338 -0
  546. package/web/src/turns.ts +178 -0
  547. package/web/src/ui/Button.tsx +60 -0
  548. package/web/src/ui/Controls.tsx +227 -0
  549. package/web/src/ui/Form.tsx +203 -0
  550. package/web/src/ui/MenuPopover.tsx +144 -0
  551. package/web/src/ui/Popover.tsx +147 -0
  552. package/web/src/ui/ResizablePane.tsx +110 -0
  553. package/web/src/ui/Select.tsx +150 -0
  554. package/web/src/ui/icons.tsx +270 -0
  555. package/web/src/unsaved.tsx +99 -0
  556. package/web/src/user-text.test.ts +60 -0
  557. package/web/src/user-text.ts +35 -0
  558. package/web/src/uuid.ts +12 -0
  559. package/README.md +0 -3
@@ -0,0 +1,64 @@
1
+ import { create } from "zustand";
2
+ import type { ImageInput } from "@shared/protocol.ts";
3
+
4
+ export type Attached = ImageInput & { id: string; url: string };
5
+ export type Draft = { text: string; images: Attached[]; revision: number; generation: number; processing: number; rewindId?: string };
6
+ export const useDrafts = create<{ drafts: Record<string, Draft> }>(() => ({ drafts: {} }));
7
+ const key = (sid: string) => `cide.draft.${sid}`;
8
+ const rewindKey = (sid: string) => `cide.draft-rewind.${sid}`;
9
+ function read(key: string): string | undefined {
10
+ try { return localStorage.getItem(key) ?? undefined; } catch { return undefined; }
11
+ }
12
+
13
+ export function getDraft(sid: string): Draft {
14
+ const current = useDrafts.getState().drafts[sid];
15
+ if (current) return current;
16
+ const draft: Draft = { text: read(key(sid)) ?? "", images: [], revision: 0, generation: 0, processing: 0, rewindId: read(rewindKey(sid)) };
17
+ useDrafts.setState((s) => ({ drafts: { ...s.drafts, [sid]: draft } }));
18
+ return draft;
19
+ }
20
+ function write(sid: string, draft: Draft) {
21
+ useDrafts.setState((s) => ({ drafts: { ...s.drafts, [sid]: draft } }));
22
+ try {
23
+ if (draft.text) localStorage.setItem(key(sid), draft.text);
24
+ else localStorage.removeItem(key(sid));
25
+ if (draft.rewindId) localStorage.setItem(rewindKey(sid), draft.rewindId);
26
+ else localStorage.removeItem(rewindKey(sid));
27
+ } catch { /* In-memory drafts still work when storage is unavailable. */ }
28
+ }
29
+ export function editDraft(sid: string, patch: Partial<Pick<Draft, "text" | "images">>) {
30
+ const draft = getDraft(sid);
31
+ write(sid, { ...draft, ...patch, revision: draft.revision + 1 });
32
+ }
33
+ export function clearDraft(sid: string, revision?: number) {
34
+ const draft = getDraft(sid);
35
+ if (revision !== undefined && draft.revision !== revision) return;
36
+ write(sid, { ...draft, text: "", images: [], processing: 0, revision: draft.revision + 1, generation: draft.generation + 1 });
37
+ }
38
+ /** An explicit rewind replaces the draft; a server replay only initializes an untouched draft once. */
39
+ export function setDraft(sid: string, text: string, rewindId?: string) {
40
+ const draft = getDraft(sid);
41
+ write(sid, { ...draft, text, images: [], processing: 0, rewindId, revision: draft.revision + 1, generation: draft.generation + 1 });
42
+ }
43
+ export function restoreRewindDraft(sid: string, text: string, rewindId: string, generation?: number) {
44
+ const draft = getDraft(sid);
45
+ if (generation !== undefined && draft.generation !== generation) return;
46
+ if (draft.rewindId === rewindId) return;
47
+ // Remember this rewind even when the user already has a draft (including an intentionally emptied one).
48
+ if (draft.text || draft.images.length || draft.processing || draft.revision) write(sid, { ...draft, rewindId });
49
+ else setDraft(sid, text, rewindId);
50
+ }
51
+ /** Reading belongs to the source session even after its Composer has unmounted. */
52
+ export async function addDraftImages(sid: string, count: number, prepare: () => Promise<Attached[]>) {
53
+ const draft = getDraft(sid);
54
+ const generation = draft.generation;
55
+ write(sid, { ...draft, processing: draft.processing + count });
56
+ try {
57
+ const images = await prepare();
58
+ const current = getDraft(sid);
59
+ if (current.generation === generation) editDraft(sid, { images: [...current.images, ...images] });
60
+ } finally {
61
+ const current = getDraft(sid);
62
+ if (current.generation === generation) write(sid, { ...current, processing: Math.max(0, current.processing - count) });
63
+ }
64
+ }
@@ -0,0 +1,5 @@
1
+ export function formatSize(n: number) {
2
+ if (n < 1024) return `${n} B`;
3
+ if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
4
+ return `${(n / 1024 / 1024).toFixed(1)} MB`;
5
+ }
@@ -0,0 +1,49 @@
1
+ /// <reference lib="webworker" />
2
+ /**
3
+ * Syntax highlighting off the main thread: highlighting a few thousand lines takes
4
+ * seconds on a phone, and the page must keep scrolling and typing meanwhile.
5
+ * Shiki is loaded lazily (core + JS regex engine, no wasm); each grammar is its own chunk.
6
+ */
7
+
8
+ type Highlighter = {
9
+ codeToHtml: (code: string, opts: { lang: string; themes: Record<string, string>; defaultColor: false }) => string;
10
+ loadLanguage: (...langs: unknown[]) => Promise<void>;
11
+ getLoadedLanguages: () => string[];
12
+ };
13
+
14
+ let highlighter: Promise<{ hl: Highlighter; langs: Record<string, () => Promise<unknown>> }> | undefined;
15
+
16
+ function getHighlighter() {
17
+ highlighter ??= (async () => {
18
+ const [{ createHighlighterCore }, { createJavaScriptRegexEngine }, { bundledLanguages }, { bundledThemes }] = await Promise.all([
19
+ import("shiki/core"),
20
+ import("shiki/engine/javascript"),
21
+ import("shiki/langs"),
22
+ import("shiki/themes"),
23
+ ]);
24
+ const hl = await createHighlighterCore({
25
+ themes: [bundledThemes["github-light"](), bundledThemes["github-dark"]()],
26
+ langs: [],
27
+ engine: createJavaScriptRegexEngine({ forgiving: true }),
28
+ });
29
+ return { hl: hl as unknown as Highlighter, langs: bundledLanguages as unknown as Record<string, () => Promise<unknown>> };
30
+ })();
31
+ return highlighter;
32
+ }
33
+
34
+ async function highlight(code: string, lang: string): Promise<string | null> {
35
+ const { hl, langs } = await getHighlighter();
36
+ const loader = langs[lang];
37
+ if (!loader) return null;
38
+ if (!hl.getLoadedLanguages().includes(lang)) await hl.loadLanguage(loader());
39
+ const html = hl.codeToHtml(code, { lang, themes: { light: "github-light", dark: "github-dark" }, defaultColor: false });
40
+ // Keep only the <code> body; lines become blocks, so drop the newlines between them.
41
+ const m = /<code>([\s\S]*)<\/code>/.exec(html);
42
+ return m ? m[1]!.replace(/<\/span>\n<span class="line">/g, '</span><span class="line">') : null;
43
+ }
44
+
45
+ self.onmessage = async (e: MessageEvent<{ id: number; code: string; lang: string }>) => {
46
+ const { id, code, lang } = e.data;
47
+ const html = await highlight(code, lang).catch(() => null);
48
+ self.postMessage({ id, html });
49
+ };
@@ -0,0 +1,59 @@
1
+ import { useQuery } from "@tanstack/react-query";
2
+ import type { CommandInfo, ModelOption, ProjectInfo, SessionListItem } from "@shared/protocol.ts";
3
+ import { api } from "./api.ts";
4
+
5
+ export function useProjects() {
6
+ return useQuery({ queryKey: ["projects"], queryFn: () => api<ProjectInfo[]>("/api/projects") });
7
+ }
8
+
9
+ export function useSessions(projectId: string | undefined) {
10
+ return useQuery({
11
+ queryKey: ["sessions", projectId],
12
+ queryFn: () => api<SessionListItem[]>(`/api/projects/${projectId}/sessions`),
13
+ enabled: !!projectId,
14
+ });
15
+ }
16
+
17
+ export function useModels(projectId: string | undefined) {
18
+ return useQuery({
19
+ queryKey: ["models", projectId],
20
+ queryFn: () => api<ModelOption[]>(`/api/projects/${projectId}/models`),
21
+ enabled: !!projectId,
22
+ staleTime: 10 * 60_000,
23
+ });
24
+ }
25
+
26
+ export function useCommands(projectId: string | undefined) {
27
+ return useQuery({
28
+ queryKey: ["commands", projectId],
29
+ queryFn: () => api<CommandInfo[]>(`/api/projects/${projectId}/commands`),
30
+ enabled: !!projectId,
31
+ staleTime: 10 * 60_000,
32
+ });
33
+ }
34
+
35
+ export type OverviewItem = {
36
+ projectId: string;
37
+ projectName: string;
38
+ sessionId: string;
39
+ title: string;
40
+ state: "idle" | "running" | "requires_action";
41
+ waitingFor: ("ask" | "plan")[];
42
+ crons: number;
43
+ /** Optional for older servers; used to dedupe project-wide durable tasks. */
44
+ cronIds?: string[];
45
+ tasks: number;
46
+ };
47
+
48
+ export function useOverview() {
49
+ return useQuery({ queryKey: ["overview"], queryFn: () => api<OverviewItem[]>("/api/overview"), refetchInterval: 30_000 });
50
+ }
51
+
52
+ export function relativeTime(ms: number): string {
53
+ const s = Math.round((Date.now() - ms) / 1000);
54
+ if (s < 60) return "刚刚";
55
+ if (s < 3600) return `${Math.floor(s / 60)} 分钟前`;
56
+ if (s < 86400) return `${Math.floor(s / 3600)} 小时前`;
57
+ if (s < 86400 * 7) return `${Math.floor(s / 86400)} 天前`;
58
+ return new Date(ms).toLocaleDateString();
59
+ }
@@ -0,0 +1,51 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { ItemViewState } from "./item-view-state.ts";
4
+ import { Transcript } from "./transcript.ts";
5
+
6
+ test("manual stream collapse survives completion, remount and later preference changes", () => {
7
+ const state = new ItemViewState();
8
+ const transcript = new Transcript();
9
+ const events = [{ k: "delta" as const, msgId: "api", index: 0, kind: "thinking" as const, text: "thought", parent: null }];
10
+ const streaming = transcript.update(undefined, events)[0]!;
11
+ assert.equal(state.read(streaming.key, true, true).value, true);
12
+ const collapsed = state.write(streaming.key, false);
13
+ const completed = transcript.update(undefined, [...events, { k: "sdk", m: { type: "assistant", uuid: "frame", message: { id: "api", content: [{ type: "thinking", thinking: "thought" }] } } }])[0]!;
14
+ assert.ok(completed.kind === "thinking" && completed.streamStateKey);
15
+ assert.equal(state.read(completed.key, true, true, [completed.streamStateKey]), collapsed);
16
+ assert.equal(state.read(completed.key, true, true).value, false, "remount can use the final key alone");
17
+ const reopened = state.write(completed.key, true);
18
+ assert.equal(state.read(streaming.key, false, true), reopened, "replaying the stream alias sees the latest explicit action");
19
+ assert.equal(state.read("another-block", false, true).value, false);
20
+ });
21
+
22
+ test("late alias discovery overrides a mounted default but never a newer explicit choice", () => {
23
+ const state = new ItemViewState();
24
+ const mounted = state.read("final", true, true);
25
+ const streamChoice = state.write("stream", false);
26
+ assert.equal(state.read("final", true, true, ["stream"], mounted), streamChoice);
27
+ const finalChoice = state.write("final", true);
28
+ assert.equal(state.read("final", false, true, ["stream"], streamChoice), finalChoice);
29
+ assert.equal(state.read("stream", false, true), finalChoice);
30
+ });
31
+
32
+ test("default-only aliases preserve the original default and independent cards stay isolated", () => {
33
+ const state = new ItemViewState();
34
+ const original = state.read("stream", false, true);
35
+ state.read("final", true, true);
36
+ assert.equal(state.read("final", true, true, ["stream"]), original);
37
+ state.write("other:open", true);
38
+ assert.equal(state.read("other:all", false).value, false, "show-all and fold state are separate");
39
+ assert.equal(state.read(undefined, false).value, false);
40
+ const local = state.write(undefined, true);
41
+ assert.equal(state.read(undefined, false, false, [], local), local);
42
+ });
43
+
44
+ test("cache eviction is bounded while a mounted explicit choice can still be restored", () => {
45
+ const state = new ItemViewState(3);
46
+ const mounted = state.write("old", false);
47
+ for (const id of ["a", "b", "c"]) state.read(id, true, true);
48
+ assert.equal(state.read("old", true).value, true, "old unmounted state was evicted");
49
+ assert.equal(state.read("old", true, true, [], mounted), mounted);
50
+ assert.equal(state.read("a", false).value, false, "restoring mounted state still honors the capacity");
51
+ });
@@ -0,0 +1,43 @@
1
+ export type ViewChoice = { value: boolean; revision: number };
2
+
3
+ /** Scalar-only, bounded UI state for virtualized items. A completed reasoning
4
+ * block aliases its former stream key; explicit actions outrank either default. */
5
+ export class ItemViewState {
6
+ private choices = new Map<string, ViewChoice>();
7
+ private revision = 0;
8
+
9
+ constructor(private readonly limit = 7500) {}
10
+
11
+ read(key: string | undefined, initial: boolean, rememberInitial = false, aliases: readonly string[] = [], mounted?: ViewChoice): ViewChoice {
12
+ if (key === undefined) return mounted ?? { value: initial, revision: 0 };
13
+ const keys = [...aliases, key];
14
+ const known = keys.flatMap((id) => this.choices.has(id) ? [this.choices.get(id)!] : []);
15
+ let choice = known[0] ?? mounted ?? { value: initial, revision: 0 };
16
+ for (const candidate of [...known, ...(mounted ? [mounted] : [])]) {
17
+ if (candidate.revision > choice.revision) choice = candidate;
18
+ }
19
+ if (known.length || rememberInitial || choice.revision > 0) this.link(keys, choice);
20
+ return choice;
21
+ }
22
+
23
+ write(key: string | undefined, value: boolean, aliases: readonly string[] = []): ViewChoice {
24
+ const choice = { value, revision: ++this.revision };
25
+ if (key !== undefined) this.link([...aliases, key], choice);
26
+ return choice;
27
+ }
28
+
29
+ private link(keys: readonly string[], choice: ViewChoice) {
30
+ const previous = new Set(keys.flatMap((key) => {
31
+ const value = this.choices.get(key);
32
+ return value && value !== choice ? [value] : [];
33
+ }));
34
+ if (previous.size) for (const [key, value] of this.choices) {
35
+ if (previous.has(value)) this.choices.set(key, choice);
36
+ }
37
+ for (const key of keys) {
38
+ if (this.choices.get(key) === choice) continue;
39
+ this.choices.delete(key); this.choices.set(key, choice);
40
+ }
41
+ while (this.choices.size > this.limit) this.choices.delete(this.choices.keys().next().value!);
42
+ }
43
+ }
@@ -0,0 +1,77 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mergeSnapshot } from "./live-merge.ts";
4
+
5
+ // Store/history flows need only the document's build metadata, not a browser.
6
+ (globalThis as any).document = { querySelector: () => null };
7
+ const { useLive, subscribeSession, loadSessionHistory } = await import("./live.ts");
8
+ const snapshot = { state: "idle" as const, pending: [], planMode: false, crons: [], tasks: [] };
9
+ const page = (revision = "branch") => ({ items: [{ type: "user", uuid: "persisted", message: { content: "history" } }], generation: "g", historyRevision: revision, older: "older" });
10
+ const attach = (revision: string, reset = true) => {
11
+ const current = useLive.getState().sessions.s;
12
+ useLive.setState({ sessions: { s: mergeSnapshot(current, { epoch: "e", seq: 2, reset, historyRevision: revision, live: snapshot, events: [] }) } });
13
+ };
14
+
15
+ test("HTTP-first history works without WS, and first/checkpoint WS preserve historical browsing", async () => {
16
+ const original = globalThis.fetch;
17
+ let requests = 0;
18
+ globalThis.fetch = async () => { requests++; return Response.json(page()); };
19
+ useLive.setState({ sessions: {}, connected: false });
20
+ const unsubscribe = subscribeSession("s", "p");
21
+ try {
22
+ await loadSessionHistory("s", "p");
23
+ await loadSessionHistory("s", "p", "older");
24
+ const before = useLive.getState().sessions.s!;
25
+ assert.equal(before.history?.mode, "history");
26
+ assert.equal(requests, 2);
27
+ assert.equal(useLive.getState().connected, false);
28
+ attach("branch");
29
+ assert.deepEqual(useLive.getState().sessions.s?.history, before.history);
30
+ attach("branch");
31
+ assert.deepEqual(useLive.getState().sessions.s?.history, before.history);
32
+ assert.equal(useLive.getState().sessions.s?.resetCount, before.resetCount);
33
+ attach("rewound");
34
+ assert.equal(useLive.getState().sessions.s?.history, undefined);
35
+ assert.equal(useLive.getState().sessions.s?.resetCount, before.resetCount + 1);
36
+ } finally { unsubscribe(); globalThis.fetch = original; }
37
+ });
38
+
39
+ test("in-flight HTTP accepts matching first WS and rejects a stale branch without stranded loading", async () => {
40
+ const original = globalThis.fetch;
41
+ let resolve!: (response: Response) => void;
42
+ globalThis.fetch = () => new Promise((r) => { resolve = r; });
43
+ const unsubscribe = subscribeSession("s", "p");
44
+ try {
45
+ useLive.setState({ sessions: { s: { seq: 0, snapshot, events: [], resetCount: 0 } } });
46
+ const matching = loadSessionHistory("s", "p");
47
+ attach("branch");
48
+ resolve(Response.json(page()));
49
+ await matching;
50
+ assert.equal(useLive.getState().sessions.s?.history?.items[0]?.uuid, "persisted");
51
+ useLive.setState({ sessions: { s: { seq: 0, snapshot, events: [], resetCount: 0 } } });
52
+ const stale = loadSessionHistory("s", "p");
53
+ attach("rewound");
54
+ resolve(Response.json(page()));
55
+ await stale;
56
+ assert.equal(useLive.getState().sessions.s?.history, undefined);
57
+ assert.equal(useLive.getState().sessions.s?.resetCount, 1);
58
+ assert.equal(useLive.getState().sessions.s?.historyRevision, "rewound");
59
+ } finally { unsubscribe(); globalThis.fetch = original; }
60
+ });
61
+
62
+
63
+ test("HTTP adopts a recreated server view while WS is unavailable instead of retrying forever", async () => {
64
+ const original = globalThis.fetch;
65
+ globalThis.fetch = async () => Response.json(page("recreated"));
66
+ const unsubscribe = subscribeSession("s", "p");
67
+ try {
68
+ useLive.setState({ sessions: { s: { seq: 8, snapshot, events: [{ k: "user", uuid: "abandoned", text: "old", ts: 1 }], resetCount: 0, historyRevision: "previous" } } });
69
+ await loadSessionHistory("s", "p");
70
+ assert.equal(useLive.getState().sessions.s?.historyRevision, "recreated");
71
+ assert.equal(useLive.getState().sessions.s?.events.length, 0);
72
+ assert.equal(useLive.getState().sessions.s?.resetCount, 1);
73
+ await loadSessionHistory("s", "p");
74
+ assert.equal(useLive.getState().sessions.s?.resetCount, 1);
75
+ assert.equal(useLive.getState().sessions.s?.history?.loading, undefined);
76
+ } finally { unsubscribe(); globalThis.fetch = original; }
77
+ });
@@ -0,0 +1,64 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import type { LiveEvent, LiveSnapshot } from "@shared/protocol.ts";
4
+ import { mergeSnapshot, updateTodosFromEvents } from "./live-merge.ts";
5
+
6
+ const live: LiveSnapshot = { state: "idle", pending: [], planMode: false, crons: [], tasks: [] };
7
+ const ev = (n: number): LiveEvent => ({ k: "user", uuid: `u${n}`, text: `m${n}`, ts: n });
8
+
9
+ test("the same incremental snapshot delivered twice appends its events once", () => {
10
+ const base = mergeSnapshot(undefined, { epoch: "e", seq: 2, reset: true, live, events: [{ seq: 1, ev: ev(1) }, { seq: 2, ev: ev(2) }] });
11
+ const inc = { epoch: "e", seq: 4, reset: false, live, events: [{ seq: 3, ev: ev(3) }, { seq: 4, ev: ev(4) }] };
12
+ const once = mergeSnapshot(base, inc);
13
+ const twice = mergeSnapshot(once, inc);
14
+ assert.deepEqual(twice.events.map((e) => (e as { uuid: string }).uuid), ["u1", "u2", "u3", "u4"]);
15
+ assert.equal(twice.seq, 4);
16
+ });
17
+
18
+ test("a reset snapshot replaces events and bumps resetCount", () => {
19
+ const base = mergeSnapshot(undefined, { epoch: "e", seq: 2, reset: true, live, events: [{ seq: 2, ev: ev(2) }] });
20
+ const next = mergeSnapshot(base, { epoch: "f", seq: 1, reset: true, live, events: [{ seq: 1, ev: ev(9) }] });
21
+ assert.equal(next.events.length, 1);
22
+ assert.equal(next.resetCount, base.resetCount + 1);
23
+ });
24
+
25
+ test("reconnect replay updates cached todos exactly like newly received messages", () => {
26
+ const base = mergeSnapshot(undefined, { epoch: "e", seq: 1, reset: true, live, events: [{ seq: 1, ev: ev(1) }] });
27
+ base.todos = [{ id: "1", subject: "first", status: "pending" }, { id: "2", subject: "second", status: "pending" }];
28
+ const fresh: LiveEvent[] = [{ k: "sdk", m: { type: "assistant", uuid: "updates", message: { content: [
29
+ { type: "tool_use", id: "update", name: "TaskUpdate", input: { taskId: "1", status: "completed" } },
30
+ { type: "tool_use", id: "delete", name: "TaskUpdate", input: { taskId: "2", status: "deleted" } },
31
+ ] } } }];
32
+ const msg = { epoch: "e", seq: 2, reset: false, live, events: [{ seq: 2, ev: fresh[0]! }] };
33
+ const next = mergeSnapshot(base, msg);
34
+ assert.deepEqual(next.todos, [{ id: "1", subject: "first", status: "completed" }]);
35
+ assert.deepEqual(next.todos, updateTodosFromEvents([...base.events, ...fresh], fresh, base.todos));
36
+ assert.deepEqual(mergeSnapshot(next, msg).todos, next.todos);
37
+ assert.equal(next.resetCount, base.resetCount);
38
+ });
39
+
40
+ test("reset replay does not inherit todos from the previous conversation branch", () => {
41
+ const base = mergeSnapshot(undefined, { epoch: "e", seq: 1, reset: true, live, events: [] });
42
+ base.todos = [{ id: "old", subject: "old branch", status: "pending" }];
43
+ const next = mergeSnapshot(base, { epoch: "e", seq: 2, reset: true, live, events: [{ seq: 2, ev: ev(2) }] });
44
+ assert.equal(next.todos, undefined);
45
+ });
46
+
47
+
48
+ test("checkpoint replay replaces cumulative draft but retains history and todos on the same view", () => {
49
+ const base = mergeSnapshot(undefined, { epoch: "e", seq: 1, reset: true, historyRevision: "view", live,
50
+ events: [{ seq: 1, ev: { k: "delta", msgId: "m", index: 0, kind: "text", text: "old", parent: null } }] });
51
+ base.history = { items: [], generation: "g", mode: "history", source: "main", request: 1 };
52
+ base.todos = [{ id: "t", subject: "keep", status: "pending" }];
53
+ const next = mergeSnapshot(base, { epoch: "e", seq: 2, reset: true, historyRevision: "view", live,
54
+ events: [{ seq: 2, ev: { k: "delta", msgId: "m", index: 0, kind: "text", text: "complete", parent: null, checkpoint: true } }] });
55
+ assert.equal(next.history, base.history);
56
+ assert.deepEqual(next.todos, base.todos);
57
+ assert.equal(next.resetCount, base.resetCount);
58
+ assert.equal(next.events.length, 1);
59
+ assert.equal((next.events[0] as any).text, "complete");
60
+ const rewound = mergeSnapshot(next, { epoch: "e", seq: 3, reset: false, historyRevision: "new-view", live, events: [] });
61
+ assert.equal(rewound.history, undefined);
62
+ assert.equal(rewound.todos, undefined);
63
+ assert.equal(rewound.resetCount, next.resetCount + 1);
64
+ });
@@ -0,0 +1,50 @@
1
+ import { boundSession, compactEvents, type HistoryWindow } from "./message-cache.ts";
2
+ import type { LiveEvent, LiveSnapshot, SeqEvent } from "@shared/protocol.ts";
3
+ import { buildItems, deriveTodos, type Todo } from "./transcript.ts";
4
+ import { mergeAgentOwners } from "./agent-owners.ts";
5
+
6
+ export type SessionLive = {
7
+ epoch?: string;
8
+ historyRevision?: string;
9
+ seq: number;
10
+ snapshot: LiveSnapshot;
11
+ events: LiveEvent[];
12
+ agentOwners?: Record<string, string>;
13
+ /** Bumped only when the transcript branch/view is invalidated. */
14
+ resetCount: number;
15
+ history?: HistoryWindow;
16
+ evicted?: boolean;
17
+ accessed?: number;
18
+ todos?: import("@shared/todos.ts").Todo[];
19
+ };
20
+
21
+ /** Replayed and newly received SDK messages must update the same cached todo state. */
22
+ export function updateTodosFromEvents(events: LiveEvent[], added: LiveEvent[], initial?: Todo[]): Todo[] | undefined {
23
+ return added.some((e) => e.k === "sdk" && (e.m.type === "user" || e.m.type === "assistant"))
24
+ ? deriveTodos(buildItems(undefined, events), initial)
25
+ : initial;
26
+ }
27
+
28
+ /**
29
+ * Apply a subscription snapshot. `reset` replaces live replay, while the view revision
30
+ * determines whether persisted history is still valid. Incremental replay is idempotent.
31
+ */
32
+ export function mergeSnapshot(
33
+ cur: SessionLive | undefined,
34
+ msg: { epoch: string; seq: number; reset: boolean; historyRevision?: string; live: LiveSnapshot; events: SeqEvent[] },
35
+ ): SessionLive {
36
+ // Legacy snapshots cannot prove branch continuity. New servers always attach a view
37
+ // revision, including the first WS response after an HTTP-only bootstrap.
38
+ const invalidated = !!cur && (msg.historyRevision !== undefined
39
+ ? cur.historyRevision !== undefined && cur.historyRevision !== msg.historyRevision
40
+ : msg.reset);
41
+ const replace = msg.reset || !cur || invalidated;
42
+ const fresh = replace ? msg.events.map((e) => e.ev) : msg.events.filter((e) => e.seq > cur!.seq).map((e) => e.ev);
43
+ const events = replace ? compactEvents(fresh) : fresh.length ? compactEvents([...cur!.events, ...fresh]) : cur!.events;
44
+ return boundSession({ ...cur, epoch: msg.epoch, historyRevision: msg.historyRevision ?? cur?.historyRevision,
45
+ agentOwners: mergeAgentOwners(invalidated ? undefined : cur?.agentOwners, fresh, msg.live.agentTasks),
46
+ seq: replace ? msg.seq : Math.max(msg.seq, cur!.seq), snapshot: msg.live, events,
47
+ history: invalidated ? undefined : cur?.history,
48
+ todos: updateTodosFromEvents(events, fresh, invalidated ? undefined : cur?.todos),
49
+ resetCount: (cur?.resetCount ?? 0) + (invalidated ? 1 : 0), accessed: Date.now() });
50
+ }