@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,583 @@
1
+ import type { LiveEvent, SdkMessageLike } from "@shared/protocol.ts";
2
+ import { toolResultSourceUuid } from "./message-bodies.ts";
3
+
4
+ /**
5
+ * Turns jsonl history + live events into a flat list of render items.
6
+ * Messages are deduped by uuid (history and live overlap after a turn ends).
7
+ */
8
+
9
+ export type ContentBlock =
10
+ | { type: "text"; text: string }
11
+ | { type: "thinking"; thinking: string }
12
+ | { type: "image"; source?: { media_type?: string; data?: string } }
13
+ | { type: "tool_use"; id: string; name: string; input: Record<string, unknown> }
14
+ | { type: "tool_result"; tool_use_id: string; content: unknown; is_error?: boolean }
15
+ | { type: string; [k: string]: unknown };
16
+
17
+ export type ToolResult = { content: unknown; isError: boolean; structured?: unknown; deferredUuid?: string };
18
+
19
+ /** Live state of a subagent / background task, from task_* system events. */
20
+ export type TaskProgress = {
21
+ taskId: string;
22
+ status: "running" | "completed" | "failed" | "stopped" | "killed";
23
+ description?: string;
24
+ subagentType?: string;
25
+ lastTool?: string;
26
+ toolUses?: number;
27
+ durationMs?: number;
28
+ summary?: string;
29
+ };
30
+
31
+ export type BuildOptions = {
32
+ /** Build the transcript of one subagent (the Agent tool_use id) instead of the main thread. */
33
+ parent?: string;
34
+ /** Other invocations of the same stable subagent, never its descendants. */
35
+ parents?: ReadonlySet<string>;
36
+ };
37
+
38
+ export type Item = (
39
+ | { kind: "user"; key: string; text: string; images: number; imageSrcs?: string[]; scheduled?: boolean }
40
+ | { kind: "text"; key: string; text: string; streaming?: boolean }
41
+ | { kind: "thinking"; key: string; text: string; streaming?: boolean; streamStateKey?: string }
42
+ | { kind: "tool"; key: string; id: string; name: string; input: Record<string, unknown>; result?: ToolResult; task?: TaskProgress; steps?: number }
43
+ | { kind: "result"; key: string; ok: boolean; subtype: string; costUsd?: number; durationMs?: number; error?: string; turnCostUsd?: number; userMessageIds?: string[] }
44
+ /** `!command` and its output (running until the output arrives). */
45
+ | { kind: "bash"; key: string; command: string; stdout?: string; stderr?: string; running: boolean }
46
+ /** Output of a local slash command (/cost, /context…), shown verbatim. */
47
+ | { kind: "local"; key: string; text: string }
48
+ | { kind: "notice"; key: string; text: string; tone: "info" | "error" }
49
+ ) & { deferred?: { uuid: string; bytes: number }; ts?: number };
50
+
51
+ type Msg = { uuid: string; type: string; parent: string | null; fromHistory: boolean; message: { role?: string; content?: unknown; id?: string }; raw: SdkMessageLike; ts?: number };
52
+
53
+ /** Strip CLI-internal wrappers out of user text; returns null for messages that should not be shown. */
54
+ function visibleUserText(text: string): string | null {
55
+ if (/^\s*<(local-command-caveat|command-name|local-command-stdout|task-notification|system-reminder)/.test(text)) return null;
56
+ if (text.startsWith("Caveat: The messages below were generated")) return null;
57
+ // /goal bookkeeping the CLI injects; the goal bar shows its state instead.
58
+ if (text.startsWith("A session-scoped Stop hook is now active") || text.startsWith("Stop hook feedback:")) return null;
59
+ return text;
60
+ }
61
+
62
+ const ANSI = /\x1b\[[0-9;]*m/g;
63
+
64
+ /** Parse an SDK ISO timestamp; live emitters without one fall back to receive time. */
65
+ function timestampOf(raw: SdkMessageLike, fallback: boolean): number | undefined {
66
+ const value = raw.timestamp;
67
+ if (typeof value === "string") {
68
+ const parsed = Date.parse(value);
69
+ if (Number.isFinite(parsed)) return parsed;
70
+ }
71
+ if (typeof value === "number" && Number.isFinite(value)) return value;
72
+ if (!fallback) return undefined;
73
+ const ts = Date.now();
74
+ // Keep the fallback on the raw record so freezing live history into the
75
+ // transcript preserves the same displayed time instead of losing it.
76
+ try { raw.timestamp = new Date(ts).toISOString(); } catch { /* frozen SDK object */ }
77
+ return ts;
78
+ }
79
+
80
+ function localText(s: string): string {
81
+ return s.replace(/<\/?local-command-std(out|err)>/g, "").replace(ANSI, "").trim();
82
+ }
83
+
84
+ /** `<command-name>/x</command-name>…<command-args>a</command-args>` → "/x a" */
85
+ function slashCommandText(text: string): string | null {
86
+ const name = /<command-name>([^<]*)<\/command-name>/.exec(text)?.[1];
87
+ if (!name) return null;
88
+ const args = /<command-args>([\s\S]*?)<\/command-args>/.exec(text)?.[1]?.trim();
89
+ return `${name.startsWith("/") ? name : `/${name}`}${args ? ` ${args}` : ""}`;
90
+ }
91
+
92
+ function parseBash(text: string): { command?: string; stdout?: string; stderr?: string } | null {
93
+ const input = /^\s*<bash-input>([\s\S]*?)<\/bash-input>/.exec(text);
94
+ const stdout = /<bash-stdout>([\s\S]*?)<\/bash-stdout>/.exec(text);
95
+ if (!input && !stdout) return null;
96
+ const stderr = /<bash-stderr>([\s\S]*?)<\/bash-stderr>/.exec(text);
97
+ return { command: input?.[1], stdout: stdout?.[1]?.replace(ANSI, ""), stderr: stderr?.[1]?.replace(ANSI, "") };
98
+ }
99
+
100
+ function blocksOf(content: unknown): ContentBlock[] {
101
+ if (typeof content === "string") return [{ type: "text", text: content }];
102
+ if (Array.isArray(content)) return content as ContentBlock[];
103
+ return [];
104
+ }
105
+
106
+ type Draft = { parent: string | null; msgId: string; index: number; kind: "text" | "thinking"; text: string; checkpoint?: boolean; truncated?: boolean; ts: number };
107
+
108
+ const draftKey = (parent: string | null, msgId: string, index: number) => JSON.stringify([parent, msgId, index]);
109
+ const draftItemKey = (parent: string | null, msgId: string, index: number) =>
110
+ parent === null ? `draft-${msgId}:${index}` : `draft-${draftKey(parent, msgId, index)}`;
111
+
112
+ /** Everything learned from history + events so far; items are derived from it. */
113
+ type State = {
114
+ msgs: Msg[];
115
+ tasks: Map<string, TaskProgress>; // by tool_use_id
116
+ taskTool: Map<string, string>; // task_id → tool_use_id
117
+ steps: Map<string, number>; // subagent messages seen live, by parent tool_use_id
118
+ seen: Set<string>;
119
+ positions: Map<string, number>;
120
+ liveSeen: Set<string>;
121
+ liveAfter?: number;
122
+ replayAnchored: boolean;
123
+ results: Extract<Item, { kind: "result" }>[];
124
+ notices: { after: number; item: Item }[];
125
+ drafts: Map<string, Draft>;
126
+ settledDrafts: Set<string>;
127
+ thinkingStreams: Map<string, string>;
128
+ bashPending: Set<string>;
129
+ };
130
+
131
+ const freshState = (): State => ({
132
+ msgs: [],
133
+ tasks: new Map(),
134
+ taskTool: new Map(),
135
+ steps: new Map(),
136
+ seen: new Set(),
137
+ positions: new Map(),
138
+ liveSeen: new Set(),
139
+ replayAnchored: false,
140
+ results: [],
141
+ notices: [],
142
+ drafts: new Map(),
143
+ settledDrafts: new Set(),
144
+ thinkingStreams: new Map(),
145
+ bashPending: new Set(),
146
+ });
147
+
148
+ /**
149
+ * Incremental transcript: history is read once, and live events are applied as they
150
+ * are appended (live.ts only ever appends to a session's event array, or replaces it).
151
+ * Items whose content did not change keep their object identity, so memoized rows
152
+ * skip re-rendering.
153
+ */
154
+ export class Transcript {
155
+ private s = freshState();
156
+ private history?: SdkMessageLike[];
157
+ private events?: LiveEvent[];
158
+ private applied = 0;
159
+ private last: Item[] = [];
160
+ /** `last` without the streaming drafts at its end. */
161
+ private base?: Item[];
162
+ private byKey = new Map<string, Item>();
163
+
164
+ constructor(private readonly opts: BuildOptions = {}) {}
165
+
166
+ update(history: SdkMessageLike[] | undefined, events: LiveEvent[]): Item[] {
167
+ const n = this.applied;
168
+ let extended =
169
+ this.events !== undefined &&
170
+ history === this.history &&
171
+ events.length >= n &&
172
+ (n === 0 || (events[0] === this.events[0] && events[n - 1] === this.events[n - 1]));
173
+ // History and WebSocket replay can arrive in separate batches. Once the first
174
+ // shared history message arrives, rebuild so earlier live-only frames take their real position.
175
+ if (extended && !this.s.replayAnchored && history?.length) {
176
+ extended = !events.slice(n).some((ev) => {
177
+ const uuid = ev.k === "sdk" ? ev.m.uuid : ev.k === "user" || ev.k === "bash" ? ev.uuid : undefined;
178
+ const position = uuid ? this.s.positions.get(uuid) : undefined;
179
+ return position !== undefined && this.s.msgs[position]?.fromHistory;
180
+ });
181
+ }
182
+ if (extended && events.length === n) {
183
+ this.events = events;
184
+ return this.last;
185
+ }
186
+ if (!extended) {
187
+ this.s = freshState();
188
+ for (const m of history ?? []) {
189
+ if (m.type === "system" && m.subtype === "compact_boundary") this.apply({ k: "sdk", m });
190
+ else this.push(m, true);
191
+ }
192
+ // Replayed events belong among the history, rather than after all of it.
193
+ // A leading result with no preceding buffered message goes before the next known message.
194
+ for (const ev of events) {
195
+ const uuid = ev.k === "sdk" ? ev.m.uuid : ev.k === "user" || ev.k === "bash" ? ev.uuid : undefined;
196
+ const position = uuid ? this.s.positions.get(uuid) : undefined;
197
+ if (position !== undefined) { this.s.liveAfter = position; this.s.replayAnchored = true; break; }
198
+ }
199
+ this.applied = 0;
200
+ this.base = undefined;
201
+ }
202
+ // Streaming text only grows the drafts at the end: everything before them stays as it was.
203
+ let onlyText = extended && this.base !== undefined;
204
+ for (let i = this.applied; i < events.length; i++) {
205
+ const ev = events[i]!;
206
+ if (ev.k !== "delta") onlyText = false;
207
+ this.apply(ev);
208
+ }
209
+ this.history = history;
210
+ this.events = events;
211
+ this.applied = events.length;
212
+ const prevDrafts = new Map(this.last.slice(this.base?.length ?? this.last.length).map((it) => [it.key, it]));
213
+ if (!onlyText) this.base = this.stabilize(this.derive());
214
+ const drafts = [...this.s.drafts.values()]
215
+ .filter((d) => d.text.trim() || d.truncated)
216
+ .sort((a, b) => a.index - b.index)
217
+ .map((d): Item => {
218
+ const key = draftItemKey(d.parent, d.msgId, d.index);
219
+ const prev = prevDrafts.get(key);
220
+ const text = d.truncated ? `${d.text}\n\n(流式预览已截断,完成后可加载完整消息。)` : d.text;
221
+ return prev && prev.kind === d.kind && (prev as { text: string }).text === text ? prev : { kind: d.kind, key, text, streaming: true, ts: d.ts };
222
+ });
223
+ this.last = drafts.length ? [...this.base!, ...drafts] : this.base!;
224
+ return this.last;
225
+ }
226
+
227
+ /** Reuse the previous object for items whose content is unchanged. */
228
+ private stabilize(items: Item[]): Item[] {
229
+ const byKey = new Map<string, Item>();
230
+ const out = items.map((it) => {
231
+ const prev = this.byKey.get(it.key);
232
+ const keep = prev && sameItem(prev, it) ? prev : it;
233
+ byKey.set(it.key, keep);
234
+ return keep;
235
+ });
236
+ this.byKey = byKey;
237
+ return out;
238
+ }
239
+
240
+ private push(raw: SdkMessageLike, fromHistory = false) {
241
+ const s = this.s;
242
+ const isLocal = raw.type === "system" && raw.subtype === "local_command";
243
+ if (raw.type !== "user" && raw.type !== "assistant" && !isLocal) return;
244
+ const parent = raw.parent_tool_use_id ?? null;
245
+ const uuid = raw.uuid ?? `anon-${s.msgs.length}`;
246
+ const firstLive = !fromHistory && !s.liveSeen.has(uuid);
247
+ if (firstLive) {
248
+ // Even a frame already loaded from history must settle its replayed streaming draft.
249
+ if (raw.type === "assistant") this.settleDrafts(raw, parent);
250
+ s.liveSeen.add(uuid);
251
+ }
252
+ if (raw._bash && s.bashPending.delete(uuid)) {
253
+ const pending = s.msgs.find((m) => m.uuid === uuid);
254
+ if (pending) {
255
+ pending.raw = raw;
256
+ pending.message = (raw.message ?? {}) as Msg["message"];
257
+ pending.ts ??= timestampOf(raw, true);
258
+ }
259
+ return;
260
+ }
261
+ if (s.seen.has(uuid)) {
262
+ const position = s.positions.get(uuid);
263
+ if (position !== undefined) {
264
+ const existing = s.msgs[position];
265
+ if (existing && existing.ts === undefined) existing.ts = timestampOf(raw, false);
266
+ if (firstLive) s.liveAfter = Math.max(s.liveAfter ?? 0, position + 1);
267
+ }
268
+ return;
269
+ }
270
+ s.seen.add(uuid);
271
+ if (parent && raw.type === "assistant") s.steps.set(parent, (s.steps.get(parent) ?? 0) + 1);
272
+ // A live-only frame belongs at the replay cursor, before later history, not at the array's end.
273
+ const at = fromHistory ? s.msgs.length : s.liveAfter ?? s.msgs.length;
274
+ s.msgs.splice(at, 0, { uuid, type: raw.type, parent, fromHistory, message: (raw.message ?? {}) as Msg["message"], raw, ts: timestampOf(raw, !fromHistory) });
275
+ for (let i = at; i < s.msgs.length; i++) s.positions.set(s.msgs[i]!.uuid, i);
276
+ if (!fromHistory) {
277
+ s.liveAfter = at + 1;
278
+ for (const notice of s.notices) if (notice.after > at) notice.after++;
279
+ }
280
+ }
281
+
282
+ private settleDrafts(raw: SdkMessageLike, parent: string | null) {
283
+ if (!this.ownsParent(parent)) return;
284
+ const message = raw.message as Msg["message"] | undefined;
285
+ if (!message?.id) return;
286
+ const blocks = blocksOf(message.content);
287
+ const rememberThinking = (blockIndex: number, streamIndex: number) => {
288
+ // Keep the final SDK UUID as the item/annotation identity. Only its UI
289
+ // fold state inherits the identity of the streaming block it replaces.
290
+ if (raw.uuid) this.s.thinkingStreams.set(`${raw.uuid}:${blockIndex}`, draftItemKey(parent, message.id!, streamIndex));
291
+ };
292
+ if (Array.isArray(raw._streamBlocks) && raw._streamBlocks.length) {
293
+ const indexes = raw._streamBlocks.filter((index): index is number => Number.isInteger(index) && index >= 0);
294
+ const thinking = raw._streamThinking as Record<number, number> | undefined;
295
+ const streamed = blocks.map((block, index) => ({ block, index })).filter(({ block }) => block.type === "thinking" || block.type === "text");
296
+ const remaining = new Set(indexes);
297
+ for (const [position, { block, index: blockIndex }] of streamed.entries()) {
298
+ // Compacted replay retains the settled indices even after deltas are
299
+ // discarded. Prefer the exact mapping; older servers only send indices,
300
+ // which are unambiguous if every text/thinking block is accounted for.
301
+ const recorded = thinking?.[blockIndex];
302
+ const index = block.type === "thinking" && recorded !== undefined && remaining.has(recorded) ? recorded
303
+ : streamed.length === indexes.length ? indexes[position] : indexes.find((candidate) => {
304
+ const draft = this.s.drafts.get(draftKey(parent, message.id!, candidate));
305
+ const text = block.type === "thinking" ? block.thinking : block.type === "text" ? block.text : undefined;
306
+ return remaining.has(candidate) && draft?.kind === block.type && typeof text === "string"
307
+ && (raw._deferred || draft.checkpoint || text.startsWith(draft.text));
308
+ });
309
+ if (index === undefined) continue;
310
+ remaining.delete(index);
311
+ const draft = this.s.drafts.get(draftKey(parent, message.id, index));
312
+ if (block.type === "thinking" && (!draft || draft.kind === "thinking")) rememberThinking(blockIndex, index);
313
+ }
314
+ for (const index of indexes) {
315
+ const key = draftKey(parent, message.id, index);
316
+ this.s.drafts.delete(key);
317
+ this.s.settledDrafts.add(key);
318
+ }
319
+ return;
320
+ }
321
+ for (const [blockIndex, block] of blocks.entries()) {
322
+ const text = block.type === "thinking" ? block.thinking : block.type === "text" ? block.text : undefined;
323
+ if (typeof text !== "string") continue;
324
+ // SDK frames contain completed blocks, often one at a time with the same message.id.
325
+ // A tool block must not remove a thinking/text draft, nor may a replay remove a later block.
326
+ const match = [...this.s.drafts.entries()]
327
+ .filter(([, draft]) => draft.parent === parent && draft.msgId === message.id && draft.kind === block.type && (raw._deferred || draft.checkpoint || text.startsWith(draft.text)))
328
+ .sort(([, a], [, b]) => a.index - b.index)[0];
329
+ if (match) {
330
+ if (block.type === "thinking") rememberThinking(blockIndex, match[1].index);
331
+ this.s.drafts.delete(match[0]);
332
+ this.s.settledDrafts.add(match[0]);
333
+ }
334
+ }
335
+ }
336
+
337
+ private ownsParent(parent: string | null): boolean {
338
+ return parent === (this.opts.parent ?? null)
339
+ || !!this.opts.parent && parent !== null && this.opts.parents?.has(parent) === true;
340
+ }
341
+
342
+ private apply(ev: LiveEvent) {
343
+ const s = this.s;
344
+ const want = this.opts.parent ?? null;
345
+ if (ev.k === "sdk") {
346
+ if (ev.m.type === "result" && !want) {
347
+ const r = ev.m;
348
+ if (r.uuid && s.seen.has(r.uuid)) return;
349
+ if (r.uuid) s.seen.add(r.uuid);
350
+ s.results.push({
351
+ kind: "result",
352
+ key: `result-${r.uuid ?? s.results.length}`,
353
+ ok: r.subtype === "success" && !r.is_error,
354
+ subtype: String(r.subtype),
355
+ costUsd: typeof r.total_cost_usd === "number" ? r.total_cost_usd : undefined,
356
+ turnCostUsd: typeof r.turn_cost_usd === "number" ? r.turn_cost_usd : undefined,
357
+ userMessageIds: Array.isArray(r.user_message_uuids) ? r.user_message_uuids.filter((id): id is string => typeof id === "string") : undefined,
358
+ durationMs: typeof r.duration_ms === "number" ? r.duration_ms : undefined,
359
+ error: Array.isArray(r.errors) ? (r.errors as string[]).join("\n") : undefined,
360
+ ts: timestampOf(r, true),
361
+ });
362
+ s.notices.push({ after: s.liveAfter ?? s.msgs.length, item: s.results[s.results.length - 1]! });
363
+ s.drafts.clear();
364
+ } else if (ev.m.type === "system" && typeof ev.m.subtype === "string" && ev.m.subtype.startsWith("task_")) {
365
+ this.trackTask(ev.m);
366
+ } else if (ev.m.type === "system" && ev.m.subtype === "compact_boundary") {
367
+ const key = `compact-${ev.m.uuid}`;
368
+ if (s.seen.has(key)) return;
369
+ s.seen.add(key);
370
+ s.notices.push({ after: s.liveAfter ?? s.msgs.length, item: { kind: "notice", key: `compact-${ev.m.uuid}`, text: "上下文已压缩", tone: "info", ts: timestampOf(ev.m, true) } });
371
+ } else {
372
+ this.push(ev.m);
373
+ }
374
+ } else if (ev.k === "user") {
375
+ // The CLI does not echo prompts; the server's own echo keeps them in stream order.
376
+ const content: ContentBlock[] = ev.imageData?.length
377
+ ? ev.imageData.map((img) => ({ type: "image", source: { media_type: img.mediaType, data: img.data } }) as ContentBlock)
378
+ : [...Array.from({ length: ev.images ?? 0 }, () => ({ type: "image" }) as ContentBlock)];
379
+ if (ev.text) content.push({ type: "text", text: ev.text });
380
+ if (s.bashPending.delete(ev.uuid)) {
381
+ // `!command` finished: fill in the output of the block shown while it ran.
382
+ const pending = s.msgs.find((m) => m.uuid === ev.uuid);
383
+ if (pending) pending.message = { role: "user", content };
384
+ return;
385
+ }
386
+ this.push({ type: "user", uuid: ev.uuid, parent_tool_use_id: null, timestamp: new Date(ev.ts).toISOString(), scheduled: ev.scheduled, message: { role: "user", content } });
387
+ } else if (ev.k === "bash") {
388
+ if (s.seen.has(ev.uuid)) return;
389
+ s.bashPending.add(ev.uuid);
390
+ this.push({ type: "user", uuid: ev.uuid, parent_tool_use_id: null, timestamp: new Date(ev.ts).toISOString(), message: { role: "user", content: `<bash-input>${ev.command}</bash-input>` } });
391
+ } else if (ev.k === "delta") {
392
+ const parent = ev.parent ?? null;
393
+ if (!this.ownsParent(parent)) return;
394
+ const key = draftKey(parent, ev.msgId, ev.index);
395
+ if ((!ev.text && !ev.truncated) || s.settledDrafts.has(key)) return;
396
+ const d = s.drafts.get(key);
397
+ if (d && !ev.checkpoint) { d.text += ev.text; d.truncated ||= ev.truncated; }
398
+ else s.drafts.set(key, { parent, msgId: ev.msgId, index: ev.index, kind: ev.kind, text: ev.text, checkpoint: ev.checkpoint, truncated: ev.truncated, ts: d?.ts ?? Date.now() });
399
+ } else if (ev.k === "error" && !want) {
400
+ s.notices.push({ after: s.liveAfter ?? s.msgs.length, item: { kind: "notice", key: `err-${s.notices.length}`, text: ev.message, tone: "error", ts: Date.now() } });
401
+ }
402
+ }
403
+
404
+ private trackTask(m: SdkMessageLike) {
405
+ const s = this.s;
406
+ const toolUseId = (m.tool_use_id as string | undefined) ?? s.taskTool.get(m.task_id as string);
407
+ if (!toolUseId) return;
408
+ s.taskTool.set(m.task_id as string, toolUseId);
409
+ const cur: TaskProgress = s.tasks.get(toolUseId) ?? { taskId: m.task_id as string, status: "running" };
410
+ const usage = m.usage as { tool_uses?: number; duration_ms?: number } | undefined;
411
+ const next: TaskProgress = { ...cur };
412
+ if (m.description) next.description = m.description as string;
413
+ if (m.subagent_type) next.subagentType = m.subagent_type as string;
414
+ if (m.last_tool_name) next.lastTool = m.last_tool_name as string;
415
+ if (usage?.tool_uses !== undefined) next.toolUses = usage.tool_uses;
416
+ if (usage?.duration_ms !== undefined) next.durationMs = usage.duration_ms;
417
+ if (m.summary) next.summary = m.summary as string;
418
+ const status = (m.status as string | undefined) ?? (m.patch as { status?: string } | undefined)?.status;
419
+ if (status === "completed" || status === "failed" || status === "stopped" || status === "killed") next.status = status;
420
+ s.tasks.set(toolUseId, next);
421
+ }
422
+
423
+ /** The flat item list from the current state: a cheap linear pass. */
424
+ private derive(): Item[] {
425
+ const { msgs, notices, tasks, steps, bashPending } = this.s;
426
+ const want = this.opts.parent ?? null;
427
+
428
+ // Tool results are attached to their tool_use.
429
+ const toolResults = new Map<string, ToolResult>();
430
+ const resultSources = new Map<string, string | undefined>();
431
+ const rememberResult = (id: string, result: ToolResult, uuid?: string) => {
432
+ // Paging may retain both forms of one result. A preview cannot replace its
433
+ // already loaded body, but a genuinely later result remains authoritative.
434
+ if (uuid && resultSources.get(id) === uuid && result.deferredUuid && !toolResults.get(id)?.deferredUuid) return;
435
+ toolResults.set(id, result);
436
+ resultSources.set(id, uuid);
437
+ };
438
+ for (const m of msgs) {
439
+ for (const [id, result] of Object.entries((m.raw.linkedToolResults ?? {}) as Record<string, ToolResult>)) rememberResult(id, result, toolResultSourceUuid(m.raw, id, result));
440
+ if (m.type !== "user") continue;
441
+ for (const b of blocksOf(m.message.content)) {
442
+ if (b.type === "tool_result") {
443
+ const tr = b as Extract<ContentBlock, { type: "tool_result" }>;
444
+ rememberResult(tr.tool_use_id, { content: tr.content, isError: !!tr.is_error, structured: m.raw.tool_use_result, ...(m.raw._deferred ? { deferredUuid: m.uuid } : {}) }, m.uuid);
445
+ }
446
+ }
447
+ }
448
+
449
+ const items: Item[] = [];
450
+ let ni = 0;
451
+ const flushNotices = (upTo: number) => {
452
+ while (ni < notices.length && notices[ni]!.after <= upTo) items.push(notices[ni++]!.item);
453
+ };
454
+
455
+ const localTexts = new Set<string>();
456
+ msgs.forEach((m, i) => {
457
+ flushNotices(i);
458
+ // Main view: subagent traffic is shown inside its tool card. Subagent view: its own file + live frames.
459
+ if (want ? !(m.fromHistory || this.ownsParent(m.parent)) : m.parent) return;
460
+ if (m.type === "system") {
461
+ const text = localText(String(m.raw.content ?? ""));
462
+ localTexts.add(text);
463
+ if (text) items.push({ kind: "local", key: m.uuid, text, ts: m.ts });
464
+ return;
465
+ }
466
+ const blocks = blocksOf(m.message.content);
467
+ if (m.type === "assistant" && (m.message as { model?: string }).model === "<synthetic>") {
468
+ // Local slash command output (live); the same text from history is already shown.
469
+ const text = localText(blocks.map((b) => (b.type === "text" ? (b as { text: string }).text : "")).join("\n"));
470
+ if (text && !localTexts.has(text)) items.push({ kind: "local", key: m.uuid, text, ts: m.ts });
471
+ return;
472
+ }
473
+ if (m.type === "user") {
474
+ const firstText = blocks.find((b) => b.type === "text") as { text: string } | undefined;
475
+ // CLI output written as a user record (e.g. a skill's failed `!command` preamble).
476
+ if (firstText && /^\s*<local-command-std(out|err)>/.test(firstText.text)) {
477
+ const text = localText(firstText.text.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&"));
478
+ if (text && !localTexts.has(text)) {
479
+ localTexts.add(text);
480
+ items.push({ kind: "local", key: m.uuid, text, ts: m.ts });
481
+ }
482
+ return;
483
+ }
484
+ const bash = (!m.raw._hydrated ? m.raw._bash as { command?: string; stdout?: string; stderr?: string } | undefined : undefined) ?? (firstText ? parseBash(firstText.text) : null);
485
+ if (bash) {
486
+ const prev = items[items.length - 1];
487
+ if (bash.command === undefined && prev?.kind === "bash" && prev.stdout === undefined) {
488
+ // The CLI's own bash mode writes the output as a separate message.
489
+ prev.stdout = bash.stdout;
490
+ prev.stderr = bash.stderr;
491
+ prev.running = false;
492
+ } else {
493
+ items.push({ kind: "bash", key: m.uuid, command: bash.command ?? "", stdout: bash.stdout, stderr: bash.stderr, running: bash.stdout === undefined && bashPending.has(m.uuid), ts: m.ts });
494
+ }
495
+ return;
496
+ }
497
+ const slash = firstText ? slashCommandText(firstText.text) : null;
498
+ if (slash) {
499
+ items.push({ kind: "user", key: m.uuid, text: slash, images: 0, ts: m.ts, ...(m.raw.scheduled === true ? { scheduled: true } : {}) });
500
+ return;
501
+ }
502
+ const texts: string[] = [];
503
+ const imageSrcs: string[] = [];
504
+ let images = 0;
505
+ for (const b of blocks) {
506
+ if (b.type === "text") {
507
+ const t = visibleUserText((b as { text: string }).text);
508
+ if (t !== null) texts.push(t);
509
+ } else if (b.type === "image") {
510
+ images++;
511
+ const src = (b as { source?: { media_type?: string; data?: string } }).source;
512
+ if (src?.data) imageSrcs.push(`data:${src.media_type ?? "image/png"};base64,${src.data}`);
513
+ }
514
+ }
515
+ if (texts.length || images) items.push({ kind: "user", key: m.uuid, text: texts.join("\n\n"), images, ...(imageSrcs.length ? { imageSrcs } : {}), ...(m.raw.scheduled === true ? { scheduled: true } : {}), ts: m.ts });
516
+ return;
517
+ }
518
+ blocks.forEach((b, j) => {
519
+ const key = `${m.uuid}:${j}`;
520
+ if (b.type === "text") {
521
+ const text = (b as { text: string }).text;
522
+ // API failures (rate limit, auth, overload…) arrive as assistant text with an `error` tag.
523
+ if (m.raw.error && text.trim()) items.push({ kind: "notice", key, text, tone: "error", ts: m.ts });
524
+ else if (text.trim()) items.push({ kind: "text", key, text, ts: m.ts });
525
+ } else if (b.type === "thinking") {
526
+ const text = (b as { thinking: string }).thinking;
527
+ const streamStateKey = this.s.thinkingStreams.get(key);
528
+ if (text.trim()) items.push({ kind: "thinking", key, text, ...(streamStateKey ? { streamStateKey } : {}), ts: m.ts });
529
+ } else if (b.type === "tool_use") {
530
+ const tu = b as Extract<ContentBlock, { type: "tool_use" }>;
531
+ items.push({
532
+ kind: "tool",
533
+ key: tu.id,
534
+ id: tu.id,
535
+ name: tu.name,
536
+ input: tu.input ?? {},
537
+ result: toolResults.get(tu.id),
538
+ task: tasks.get(tu.id),
539
+ steps: steps.get(tu.id),
540
+ ts: m.ts,
541
+ });
542
+ }
543
+ });
544
+ });
545
+ flushNotices(Infinity);
546
+ for (const m of msgs) {
547
+ const deferred = m.raw._deferred as { bytes: number } | undefined;
548
+ if (!deferred || !this.ownsParent(m.parent) && !m.fromHistory) continue;
549
+ const ids = blocksOf(m.message.content).filter((b) => b.type === "tool_use").map((b) => (b as { id: string }).id);
550
+ const owner = items.find((it) => it.key === m.uuid || it.key.startsWith(`${m.uuid}:`) || ids.includes(it.key));
551
+ if (owner) owner.deferred = { uuid: m.uuid, bytes: deferred.bytes };
552
+ else if (!blocksOf(m.message.content).some((b) => b.type === "tool_result")) items.push({ kind: "notice", key: m.uuid, text: "较大的历史消息", tone: "info", deferred: { uuid: m.uuid, bytes: deferred.bytes }, ts: m.ts });
553
+ }
554
+ return items;
555
+ }
556
+ }
557
+
558
+ /** One-shot build (subagent pages and other places that do not stream long transcripts). */
559
+ export function buildItems(history: SdkMessageLike[] | undefined, events: LiveEvent[], opts: BuildOptions = {}): Item[] {
560
+ return new Transcript(opts).update(history, events);
561
+ }
562
+
563
+ /** Same content: equal fields, comparing nested objects and arrays one level deep. */
564
+ function sameItem(a: Item, b: Item): boolean {
565
+ if (a.kind !== b.kind) return false;
566
+ const ra = a as Record<string, unknown>;
567
+ const rb = b as Record<string, unknown>;
568
+ const ka = Object.keys(ra);
569
+ if (ka.length !== Object.keys(rb).length) return false;
570
+ return ka.every((k) => shallowEqual(ra[k], rb[k]));
571
+ }
572
+
573
+ function shallowEqual(x: unknown, y: unknown): boolean {
574
+ if (x === y) return true;
575
+ if (!x || !y || typeof x !== "object" || typeof y !== "object") return false;
576
+ if (Array.isArray(x)) return Array.isArray(y) && x.length === y.length && x.every((v, i) => v === y[i]);
577
+ const ox = x as Record<string, unknown>;
578
+ const oy = y as Record<string, unknown>;
579
+ const kx = Object.keys(ox);
580
+ return kx.length === Object.keys(oy).length && kx.every((k) => ox[k] === oy[k]);
581
+ }
582
+
583
+ export { deriveTodos, type Todo } from "@shared/todos.ts";