@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,608 @@
1
+ import { createContext, memo, useContext, useMemo, useState, type ReactNode } from "react";
2
+ import { Link } from "react-router";
3
+ import { useMutation } from "@tanstack/react-query";
4
+ import type { Item, TaskProgress, ToolResult } from "../transcript.ts";
5
+ import { collapse, fromHunks, lineDiff, type DiffLine } from "../diff.ts";
6
+ import { Markdown } from "./Markdown.tsx";
7
+ import { CopyButton as CopyMessageButton } from "./CopyButton.tsx";
8
+ import { copyText } from "../clipboard.ts";
9
+ import { ChevronIcon } from "./icons.tsx";
10
+ import { AlertIcon, MicIcon, VolumeOffIcon } from "../ui/icons.tsx";
11
+ import { splitUserText } from "../user-text.ts";
12
+ import { Button } from "../ui/Button.tsx";
13
+ import { openImagePreview } from "./ImagePreview.tsx";
14
+ import { getReasoningExpanded } from "../reasoning-preference.ts";
15
+ import { ItemViewState } from "../item-view-state.ts";
16
+ import { IMAGE_EXTENSIONS } from "@shared/files.ts";
17
+ import { uuid } from "../uuid.ts";
18
+
19
+ /** Where tool cards live: project root (for relative paths) and the link base for subagent pages. */
20
+ export const TranscriptContext = createContext<{
21
+ root?: string;
22
+ sessionUrl?: string;
23
+ /** Current transcript identity for folds, independent of the root used by agent links. */
24
+ viewKey?: string;
25
+ userActions?: (uuid: string) => ReactNode;
26
+ loadMessage?: (uuid: string) => Promise<void>;
27
+ backgroundTool?: (toolUseId: string) => Promise<void>;
28
+ readImageUrl?: (toolUseId: string) => string | undefined;
29
+ /** Todo id → subject, so TaskUpdate lines can say which item changed. A stable, mutated map (no re-renders). */
30
+ todoNames?: Map<string, string>;
31
+ }>({});
32
+
33
+ export const ItemView = memo(function ItemView({ item }: { item: Item }) {
34
+ const deferredUuid = item.deferred?.uuid ?? (item.kind === "tool" ? item.result?.deferredUuid : undefined);
35
+ return <div data-annotation-item={item.key}><ItemContent item={item} />{deferredUuid && <DeferredMessage uuid={deferredUuid} />}</div>;
36
+ });
37
+
38
+ function DeferredMessage({ uuid }: { uuid: string }) {
39
+ const { loadMessage } = useContext(TranscriptContext);
40
+ const [busy, setBusy] = useState(false);
41
+ const [error, setError] = useState("");
42
+ if (!loadMessage) return null;
43
+ return <div className="mt-2 text-xs text-muted">
44
+ <Button size="sm" disabled={busy} onClick={async () => {
45
+ setBusy(true); setError("");
46
+ try { await loadMessage(uuid); } catch (e) { setError((e as Error).message); } finally { setBusy(false); }
47
+ }}>{busy ? "加载中…" : "加载完整消息"}</Button>
48
+ {error && <p className="mt-1 text-danger">{error}</p>}
49
+ </div>;
50
+ }
51
+
52
+ function ItemContent({ item }: { item: Item }) {
53
+ switch (item.kind) {
54
+ case "user":
55
+ return <UserMessage item={item} />;
56
+ case "text":
57
+ return (
58
+ <div className="group/text relative" data-testid="assistant-text" data-streaming={item.streaming ? "true" : undefined}>
59
+ <Markdown text={item.text} />
60
+ {!item.streaming && <CopyButton text={item.text} />}
61
+ </div>
62
+ );
63
+ case "thinking":
64
+ return (
65
+ <Collapsible label={<span className="italic">思考过程</span>} testId="thinking" stateKey={item.key} stateAliases={item.streamStateKey ? [item.streamStateKey] : undefined} defaultOpen={getReasoningExpanded()} rememberInitial>
66
+ <div className="whitespace-pre-wrap px-3 py-3 text-sm text-muted md:px-4">{item.text}</div>
67
+ </Collapsible>
68
+ );
69
+ case "tool":
70
+ return <ToolView item={item} />;
71
+ case "result":
72
+ return (
73
+ <div className="flex items-center gap-3 border-t border-border pt-2 text-xs text-muted" data-testid="turn-result">
74
+ {!item.ok && <span className="text-danger">{item.subtype === "error_during_execution" ? "已中断" : item.error || (item.subtype === "success" ? "出错" : item.subtype)}</span>}
75
+ {item.durationMs !== undefined && <span>耗时 {(item.durationMs / 1000).toFixed(1)}s</span>}
76
+ {item.turnCostUsd !== undefined && item.turnCostUsd > 0 && (
77
+ <span title="本轮花费" data-testid="turn-cost">
78
+ 费用 ${item.turnCostUsd.toFixed(4)}
79
+ </span>
80
+ )}
81
+ </div>
82
+ );
83
+ case "bash":
84
+ return <BashBlock item={item} />;
85
+ case "local":
86
+ return (
87
+ <Clipped text={item.text} stateKey={item.key}>
88
+ {(text) => (
89
+ <pre className="overflow-x-auto whitespace-pre-wrap break-words rounded-lg border border-border bg-surface-2/60 px-3 py-2 font-mono text-xs leading-relaxed" data-testid="local-output">
90
+ {text}
91
+ </pre>
92
+ )}
93
+ </Clipped>
94
+ );
95
+ case "notice":
96
+ return <div className={`text-center text-xs ${item.tone === "error" ? "text-danger" : "text-muted"}`}>{item.text}</div>;
97
+ }
98
+ }
99
+
100
+ function BashBlock({ item }: { item: Extract<Item, { kind: "bash" }> }) {
101
+ return (
102
+ <div className="overflow-hidden rounded-lg border border-border font-mono text-xs" data-testid="bash-block" data-running={item.running ? "true" : undefined}>
103
+ <div className="flex items-center gap-2 bg-surface-2 px-3 py-2">
104
+ <span className="text-accent">!</span>
105
+ <span className="min-w-0 flex-1 whitespace-pre-wrap break-all text-text">{item.command}</span>
106
+ {item.running && <span className="size-1.5 shrink-0 animate-pulse rounded-full bg-accent" />}
107
+ </div>
108
+ {(item.stdout || item.stderr) && (
109
+ <Clipped text={(item.stdout ?? "") + (item.stderr ? (item.stdout ? "\n" : "") + item.stderr : "")} stateKey={item.key}>
110
+ {(_, shown) => (
111
+ <pre className="max-h-80 overflow-auto whitespace-pre-wrap break-words px-3 py-2" data-testid="bash-output">
112
+ {item.stdout?.slice(0, shown)}
113
+ {item.stderr && shown > (item.stdout?.length ?? 0) && (
114
+ <span className="text-danger">
115
+ {item.stdout ? "\n" : ""}
116
+ {item.stderr.slice(0, shown - (item.stdout ? item.stdout.length + 1 : 0))}
117
+ </span>
118
+ )}
119
+ </pre>
120
+ )}
121
+ </Clipped>
122
+ )}
123
+ </div>
124
+ );
125
+ }
126
+
127
+ /** /copy on the web: copy one reply's markdown. Always visible on touch screens, on hover elsewhere. */
128
+ function CopyButton({ text }: { text: string }) {
129
+ const [done, setDone] = useState(false);
130
+ return (
131
+ <button
132
+ className="mt-1 rounded px-1.5 py-0.5 text-xs text-muted opacity-100 hover:bg-surface-2 hover:text-text md:opacity-0 md:group-hover/text:opacity-100"
133
+ aria-label="复制回复"
134
+ onClick={async () => {
135
+ await copyText(text);
136
+ setDone(true);
137
+ setTimeout(() => setDone(false), 1500);
138
+ }}
139
+ >
140
+ {done ? "已复制" : "复制"}
141
+ </button>
142
+ );
143
+ }
144
+
145
+ function UserMessage({ item }: { item: Extract<Item, { kind: "user" }> }) {
146
+ const { userActions } = useContext(TranscriptContext);
147
+ // Deferred history contains only a preview; load the full message before copying.
148
+ const canCopyText = !!item.text.trim() && !item.deferred;
149
+ return (
150
+ <div className="group" data-testid="user-message" data-uuid={item.key}>
151
+ <div className="flex justify-end">
152
+ <div className="max-w-[85%] whitespace-pre-wrap break-words rounded-2xl bg-surface-2 px-4 py-2.5">
153
+ {item.scheduled && <div className="mb-1 text-[10px] font-medium text-accent">IDECron</div>}
154
+ {item.imageSrcs?.length ? (
155
+ <div className="mb-1.5 flex flex-wrap justify-end gap-1.5">
156
+ {item.imageSrcs.map((src, i) => (
157
+ <button key={i} type="button" onClick={() => openImagePreview(src, `图片 ${i + 1}`)} aria-label={`预览图片 ${i + 1}`} className="shrink-0 cursor-zoom-in rounded-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/50">
158
+ <img src={src} alt={`图片 ${i + 1}`} data-testid="user-image" className="size-20 rounded-lg border border-border object-cover" />
159
+ </button>
160
+ ))}
161
+ </div>
162
+ ) : (
163
+ item.images > 0 && <div className="mb-1 text-xs text-muted">[{item.images} 张图片]</div>
164
+ )}
165
+ <UserMessageText text={item.text} />
166
+ </div>
167
+ </div>
168
+ {(canCopyText || userActions) && <div className="mt-1 flex items-center justify-end gap-1">
169
+ {canCopyText && <CopyMessageButton text={item.text} label="复制消息" />}
170
+ {userActions?.(item.key)}
171
+ </div>}
172
+ </div>
173
+ );
174
+ }
175
+
176
+ function UserMessageText({ text }: { text: string }) {
177
+ const parts = useMemo(() => splitUserText(text), [text]);
178
+ if (parts.every((part) => part.kind === "text")) return text;
179
+ return (
180
+ <div className="space-y-2">
181
+ {parts.map((part, index) => part.kind === "text" ? (
182
+ part.text.trim() ? <div key={index} className="whitespace-pre-wrap">{part.text}</div> : null
183
+ ) : (
184
+ <div key={index} className={`min-w-0 rounded-lg border px-3 py-2 ${part.kind === "asr-warning" ? "border-accent/40 bg-accent/5" : part.kind === "unheard_text" ? "border-dashed border-border bg-bg/60" : "border-accent/20 bg-bg/60"}`} data-testid={part.kind}>
185
+ <div className="mb-1.5 flex flex-wrap items-center gap-x-2 gap-y-1 whitespace-normal text-xs text-muted">
186
+ <span className={`inline-flex items-center gap-1.5 ${part.kind !== "unheard_text" ? "text-accent" : ""}`}>
187
+ {part.kind === "asr-warning" ? <AlertIcon /> : part.kind === "asr-recognized-text" ? <MicIcon /> : <VolumeOffIcon />}
188
+ {part.kind === "asr-warning" ? "语音识别纠偏" : part.kind === "asr-recognized-text" ? "语音输入" : "未听到的内容"}
189
+ </span>
190
+ {part.kind === "unheard_text" && part.reason && <span className="rounded bg-surface-2 px-1.5 py-0.5">{part.reason}</span>}
191
+ </div>
192
+ <div className={`whitespace-pre-wrap ${part.kind === "unheard_text" ? "text-muted" : ""}`}>{part.text}</div>
193
+ </div>
194
+ ))}
195
+ </div>
196
+ );
197
+ }
198
+
199
+ // ---------- view state that survives remounts ----------
200
+ //
201
+ // Long transcripts are virtualized: rows leave the DOM when scrolled away. Whether a
202
+ // card was opened (or "show all" clicked) is kept here by item key so it comes back.
203
+
204
+ const viewState = new ItemViewState();
205
+
206
+ function useViewState(key: string | undefined, initial: boolean, rememberInitial = false, aliases?: readonly string[]): [boolean, (v: boolean) => void] {
207
+ const [local, setLocal] = useState(() => ({ key, choice: viewState.read(key, initial, rememberInitial, aliases) }));
208
+ // Resolve on render as well: replay/history may reveal the stream alias after
209
+ // this completed card has already mounted with its default choice.
210
+ const choice = viewState.read(key, initial, rememberInitial, aliases, local.key === key ? local.choice : undefined);
211
+ return [
212
+ choice.value,
213
+ (next: boolean) => {
214
+ setLocal({ key, choice: viewState.write(key, next, aliases) });
215
+ },
216
+ ];
217
+ }
218
+
219
+ // ---------- long output ----------
220
+
221
+ const CLIP_LINES = 200;
222
+ const CLIP_CHARS = 20_000;
223
+
224
+ /** Where to cut a long text for the first render, or -1 when it is short enough. */
225
+ function clipAt(text: string, lines = CLIP_LINES, chars = CLIP_CHARS): number {
226
+ if (text.length <= chars && text.length < lines * 20) return -1;
227
+ let at = -1;
228
+ for (let n = 0; n < lines; n++) {
229
+ at = text.indexOf("\n", at + 1);
230
+ if (at === -1) break;
231
+ }
232
+ const cut = at === -1 ? Math.min(text.length, chars) : Math.min(at, chars);
233
+ return cut >= text.length ? -1 : cut;
234
+ }
235
+
236
+ function countLines(text: string) {
237
+ let n = 1;
238
+ for (let i = text.indexOf("\n"); i !== -1; i = text.indexOf("\n", i + 1)) n++;
239
+ return n;
240
+ }
241
+
242
+ /** Renders the start of a long text first, with a button to show all of it. */
243
+ export function Clipped({ text, stateKey, children }: { text: string; stateKey?: string; children: (shown: string, length: number) => ReactNode }) {
244
+ const [all, setAll] = useViewState(stateKey && `${stateKey}:all`, false);
245
+ const cut = all ? -1 : clipAt(text);
246
+ if (cut === -1) return <>{children(text, text.length)}</>;
247
+ return (
248
+ <>
249
+ {children(text.slice(0, cut), cut)}
250
+ <ShowAll onClick={() => setAll(true)} label={`显示全部(共 ${countLines(text)} 行,${Math.ceil(text.length / 1024)} KB)`} />
251
+ </>
252
+ );
253
+ }
254
+
255
+ function ShowAll({ onClick, label }: { onClick: () => void; label: string }) {
256
+ return (
257
+ <button className="block w-full border-t border-border px-3 py-2 text-left text-xs text-accent hover:bg-surface-2" onClick={onClick} data-testid="show-all">
258
+ {label}
259
+ </button>
260
+ );
261
+ }
262
+
263
+ function Collapsible({
264
+ label,
265
+ children,
266
+ testId,
267
+ attrs,
268
+ stateKey,
269
+ stateAliases,
270
+ actions,
271
+ defaultOpen = false,
272
+ rememberInitial = false,
273
+ }: {
274
+ label: ReactNode;
275
+ children: ReactNode;
276
+ testId?: string;
277
+ attrs?: Record<string, string>;
278
+ /** Remember open/closed under this key across remounts. */
279
+ stateKey?: string;
280
+ stateAliases?: readonly string[];
281
+ actions?: ReactNode;
282
+ defaultOpen?: boolean;
283
+ rememberInitial?: boolean;
284
+ }) {
285
+ const [open, setOpen] = useViewState(stateKey && `${stateKey}:open`, defaultOpen, rememberInitial, stateAliases?.map((key) => `${key}:open`));
286
+ return (
287
+ <div className="min-w-0 rounded-lg border border-border bg-surface/60" data-testid={testId} {...attrs}>
288
+ <div className="flex min-w-0 items-center">
289
+ <button className="flex min-w-0 flex-1 items-center gap-2 px-3 py-2 text-left text-sm text-muted" onClick={() => setOpen(!open)} aria-expanded={open}>
290
+ <ChevronIcon open={open} />
291
+ {label}
292
+ </button>
293
+ {actions && <div className="shrink-0 pr-2">{actions}</div>}
294
+ </div>
295
+ {open && <div className="min-w-0 border-t border-border">{children}</div>}
296
+ </div>
297
+ );
298
+ }
299
+
300
+ // ---------- tools ----------
301
+
302
+ type ToolItem = Extract<Item, { kind: "tool" }>;
303
+
304
+ const str = (v: unknown) => (typeof v === "string" ? v : "");
305
+
306
+ function useRel() {
307
+ const { root } = useContext(TranscriptContext);
308
+ return (p: string) => (root && p.startsWith(root + "/") ? p.slice(root.length + 1) : p);
309
+ }
310
+
311
+ function statusOf(item: ToolItem): "running" | "done" | "error" {
312
+ if (item.task && item.task.status !== "running" && !item.result) return item.task.status === "completed" ? "done" : "error";
313
+ return !item.result ? "running" : item.result.isError ? "error" : "done";
314
+ }
315
+
316
+ function StatusMark({ status }: { status: "running" | "done" | "error" }) {
317
+ if (status === "running") return <span className="ml-auto size-1.5 shrink-0 animate-pulse rounded-full bg-accent" aria-label="运行中" />;
318
+ if (status === "error") return <span className="ml-auto shrink-0 text-xs text-danger">失败</span>;
319
+ return null;
320
+ }
321
+
322
+ function ToolLabel({ item, children }: { item: ToolItem; children?: ReactNode }) {
323
+ return (
324
+ <span className="flex min-w-0 flex-1 items-center gap-2">
325
+ <span className="shrink-0 font-medium text-text">{item.name}</span>
326
+ <span className="min-w-0 truncate font-mono text-xs">{children}</span>
327
+ <StatusMark status={statusOf(item)} />
328
+ </span>
329
+ );
330
+ }
331
+
332
+ export function resultText(result: ToolResult | undefined): string {
333
+ if (!result) return "";
334
+ const c = result.content;
335
+ if (typeof c === "string") return c;
336
+ if (Array.isArray(c)) return c.map((b) => (b && typeof b === "object" && "text" in b ? String(b.text) : "")).join("\n");
337
+ return JSON.stringify(c, null, 2);
338
+ }
339
+
340
+ function ToolView({ item }: { item: ToolItem }) {
341
+ const attrs = { "data-tool": item.name, "data-status": statusOf(item), "data-tool-id": item.id };
342
+ switch (item.name) {
343
+ case "Bash":
344
+ return <BashCard item={item} attrs={attrs} />;
345
+ case "Edit":
346
+ case "MultiEdit":
347
+ case "Write":
348
+ case "NotebookEdit":
349
+ return <EditCard item={item} attrs={attrs} />;
350
+ case "Agent":
351
+ case "Task":
352
+ return <AgentCard item={item} attrs={attrs} />;
353
+ case "TaskCreate":
354
+ case "TaskUpdate":
355
+ case "TodoWrite":
356
+ case "TaskList":
357
+ case "TaskGet":
358
+ return <TodoLine item={item} attrs={attrs} />;
359
+ default:
360
+ return <GenericCard item={item} attrs={attrs} />;
361
+ }
362
+ }
363
+
364
+ function Output({ text, error, stateKey }: { text: string; error?: boolean; stateKey?: string }) {
365
+ if (!text) return null;
366
+ return (
367
+ <Clipped text={text} stateKey={stateKey}>
368
+ {(shown) => <pre className={`max-h-80 overflow-auto whitespace-pre-wrap break-words px-3 py-2 font-mono text-xs leading-relaxed ${error ? "text-danger" : ""}`}>{shown}</pre>}
369
+ </Clipped>
370
+ );
371
+ }
372
+
373
+ function BashCard({ item, attrs }: { item: ToolItem; attrs: Record<string, string> }) {
374
+ const { backgroundTool } = useContext(TranscriptContext);
375
+ const [backgrounded, setBackgrounded] = useViewState(`${item.key}:backgrounded`, false);
376
+ const background = useMutation({
377
+ mutationFn: async () => {
378
+ if (!backgroundTool) throw new Error("会话当前未运行");
379
+ await backgroundTool(item.id);
380
+ },
381
+ onSuccess: () => setBackgrounded(true),
382
+ });
383
+ const canBackground = !!backgroundTool && !item.result && item.input.run_in_background !== true && statusOf(item) === "running";
384
+ const cmd = str(item.input.command);
385
+ const out = resultText(item.result);
386
+ return (
387
+ <div className="space-y-1">
388
+ <Collapsible testId="tool-call" attrs={attrs} stateKey={item.key} label={<ToolLabel item={item}>{str(item.input.description) || cmd.split("\n")[0]}</ToolLabel>} actions={canBackground && (
389
+ <Button variant="ghost" size="sm" loading={background.isPending} disabled={backgrounded} title="命令继续在后台运行,让 agent 继续执行" data-testid="bash-background" onClick={() => background.mutate()}>
390
+ {backgrounded ? "已转后台" : "转后台"}
391
+ </Button>
392
+ )}>
393
+ <pre className="overflow-x-auto whitespace-pre-wrap break-words bg-surface-2/60 px-3 py-2 font-mono text-xs" data-testid="bash-command">
394
+ <span className="select-none text-muted">$ </span>
395
+ {cmd}
396
+ </pre>
397
+ <div data-testid="bash-output">
398
+ <Output text={out} error={item.result?.isError} stateKey={item.key} />
399
+ </div>
400
+ </Collapsible>
401
+ {background.isError && !item.result && <p role="alert" className="px-3 text-xs text-danger">{background.error.message}</p>}
402
+ </div>
403
+ );
404
+ }
405
+
406
+ function editDiff(item: ToolItem): DiffLine[] {
407
+ const structured = item.result?.structured as { structuredPatch?: { oldStart: number; newStart: number; lines: string[] }[]; type?: string } | undefined;
408
+ if (structured?.structuredPatch?.length) return fromHunks(structured.structuredPatch);
409
+ const input = item.input;
410
+ if (item.name === "Write") return collapse(str(input.content).split("\n").map((t, i) => ({ type: "+" as const, text: t, newNo: i + 1 })), 1e9);
411
+ if (item.name === "MultiEdit" && Array.isArray(input.edits)) {
412
+ return (input.edits as { old_string: string; new_string: string }[]).flatMap((e, i) => [
413
+ ...(i ? [{ type: "gap" as const, count: 0 }] : []),
414
+ ...lineDiff(e.old_string ?? "", e.new_string ?? ""),
415
+ ]);
416
+ }
417
+ return lineDiff(str(input.old_string), str(input.new_string));
418
+ }
419
+
420
+ function EditCard({ item, attrs }: { item: ToolItem; attrs: Record<string, string> }) {
421
+ const rel = useRel();
422
+ const rows = editDiff(item);
423
+ const adds = rows.filter((r) => r.type === "+").length;
424
+ const dels = rows.filter((r) => r.type === "-").length;
425
+ return (
426
+ <Collapsible
427
+ testId="tool-call"
428
+ attrs={attrs}
429
+ stateKey={item.key}
430
+ label={
431
+ <ToolLabel item={item}>
432
+ {rel(str(item.input.file_path) || str(item.input.notebook_path))}
433
+ <span className="ml-2 text-ok">+{adds}</span> <span className="text-danger">-{dels}</span>
434
+ </ToolLabel>
435
+ }
436
+ >
437
+ {item.result?.isError ? <Output text={resultText(item.result)} error /> : <DiffView rows={rows} stateKey={item.key} />}
438
+ </Collapsible>
439
+ );
440
+ }
441
+
442
+ /** Diffs longer than this render their first rows until "show all". */
443
+ const DIFF_ROWS = 300;
444
+
445
+ export function DiffView({ rows: allRows, stateKey }: { rows: DiffLine[]; stateKey?: string }) {
446
+ const [all, setAll] = useViewState(stateKey && `${stateKey}:all`, false);
447
+ const rows = all || allRows.length <= DIFF_ROWS ? allRows : allRows.slice(0, DIFF_ROWS);
448
+ return (
449
+ <div className="max-h-[28rem] overflow-auto font-mono text-xs leading-relaxed" data-testid="diff">
450
+ <table className="w-full border-collapse">
451
+ <tbody>
452
+ {rows.map((r, i) =>
453
+ r.type === "gap" ? (
454
+ <tr key={i} className="bg-surface-2/50 text-muted">
455
+ <td colSpan={3} className="px-3 py-0.5 text-center">
456
+ {r.count ? `… ${r.count} 行未改动` : "⋯"}
457
+ </td>
458
+ </tr>
459
+ ) : (
460
+ <tr key={i} className={r.type === "+" ? "bg-ok/10" : r.type === "-" ? "bg-danger/10" : ""} data-diff={r.type === "+" ? "add" : r.type === "-" ? "del" : "ctx"}>
461
+ <td className="w-10 select-none px-2 text-right align-top text-muted/70">{r.type === "+" ? r.newNo : r.oldNo}</td>
462
+ <td className={`w-4 select-none align-top ${r.type === "+" ? "text-ok" : r.type === "-" ? "text-danger" : "text-muted"}`}>{r.type === " " ? "" : r.type}</td>
463
+ <td className="whitespace-pre-wrap break-all pr-3">{r.text || " "}</td>
464
+ </tr>
465
+ ),
466
+ )}
467
+ </tbody>
468
+ </table>
469
+ {rows.length < allRows.length && <ShowAll onClick={() => setAll(true)} label={`显示全部(共 ${allRows.length} 行)`} />}
470
+ </div>
471
+ );
472
+ }
473
+
474
+ /** The subagent's final report without the hand-back frame and usage trailer. */
475
+ export function agentReport(text: string): string {
476
+ let t = text;
477
+ const marker = t.indexOf("The report follows:\n");
478
+ if (t.startsWith("[Subagent hand-back]") && marker !== -1) {
479
+ const body = t.slice(marker + "The report follows:\n".length).split("\n");
480
+ const lines: string[] = [];
481
+ for (const l of body) {
482
+ if (l.startsWith(" ") || l === "") lines.push(l.slice(2));
483
+ else break;
484
+ }
485
+ t = lines.join("\n");
486
+ }
487
+ return t.replace(/\nagentId: [\s\S]*$/, "").trim();
488
+ }
489
+
490
+ function AgentCard({ item, attrs }: { item: ToolItem; attrs: Record<string, string> }) {
491
+ const { sessionUrl } = useContext(TranscriptContext);
492
+ const status = statusOf(item);
493
+ const task: TaskProgress | undefined = item.task;
494
+ const description = str(item.input.description) || task?.description || "子 agent";
495
+ const type = str(item.input.subagent_type) || task?.subagentType;
496
+ const report = item.result ? agentReport(resultText(item.result)) : "";
497
+ const asyncLaunch = report.startsWith("Async agent launched");
498
+ return (
499
+ <Collapsible testId="tool-call" attrs={{ ...attrs, "data-agent": "true" }} stateKey={item.key} label={
500
+ <span className="flex min-w-0 flex-1 items-center gap-2">
501
+ <span className="shrink-0 rounded bg-accent/15 px-1.5 py-0.5 text-xs font-medium text-accent">子 agent</span>
502
+ <span className="min-w-0 flex-1 truncate font-medium">{description}</span>
503
+ {type && <span className="hidden shrink-0 text-xs text-muted sm:inline">{type}</span>}
504
+ <StatusMark status={asyncLaunch && (!task || task.status === "running") ? "running" : status} />
505
+ </span>
506
+ }>
507
+ {(status === "running" || asyncLaunch) && task?.status !== "completed" && (
508
+ <div className="border-t border-border px-3 py-1.5 text-xs text-muted" data-testid="agent-progress">
509
+ {task?.summary || (task?.lastTool ? `正在使用 ${task.lastTool}` : "启动中…")}
510
+ {task?.toolUses !== undefined && <span className="ml-2">· {task.toolUses} 次工具调用</span>}
511
+ {item.steps ? <span className="ml-2">· {item.steps} 步</span> : null}
512
+ </div>
513
+ )}
514
+ {report && !asyncLaunch && (
515
+ <div className="max-h-96 overflow-y-auto border-t border-border px-3 py-2 text-sm" data-testid="agent-report">
516
+ <Markdown text={report} />
517
+ </div>
518
+ )}
519
+ {sessionUrl && (
520
+ <div className="border-t border-border px-3 py-1.5 text-right">
521
+ <Link to={`${sessionUrl}/agent/${item.id}`} className="text-xs text-accent hover:underline">
522
+ 查看完整过程 →
523
+ </Link>
524
+ </div>
525
+ )}
526
+ </Collapsible>
527
+ );
528
+ }
529
+
530
+ function TodoLine({ item, attrs }: { item: ToolItem; attrs: Record<string, string> }) {
531
+ const { todoNames } = useContext(TranscriptContext);
532
+ const i = item.input;
533
+ let text: string;
534
+ if (item.name === "TaskCreate") text = `新增待办:${str(i.subject)}`;
535
+ else if (item.name === "TaskUpdate") {
536
+ const name = todoNames?.get(str(i.taskId)) ?? `#${str(i.taskId)}`;
537
+ text = i.status ? `${STATUS_ZH[str(i.status)] ?? str(i.status)}:${name}` : `更新待办:${name}`;
538
+ }
539
+ else if (item.name === "TodoWrite") text = "更新待办清单";
540
+ else text = "查看待办";
541
+ return (
542
+ <div className="flex items-center gap-2 px-1 text-xs text-muted" data-testid="tool-call" {...attrs}>
543
+ <span>☑</span>
544
+ <span className="min-w-0 truncate">{text}</span>
545
+ </div>
546
+ );
547
+ }
548
+
549
+ const STATUS_ZH: Record<string, string> = { pending: "待办", in_progress: "进行中", completed: "已完成", deleted: "已删除" };
550
+
551
+ function genericSummary(name: string, input: Record<string, unknown>, rel: (p: string) => string): string {
552
+ switch (name) {
553
+ case "Read":
554
+ return rel(str(input.file_path));
555
+ case "Glob":
556
+ case "Grep":
557
+ return str(input.pattern) + (input.path ? ` ${rel(str(input.path))}` : "");
558
+ case "WebFetch":
559
+ return str(input.url);
560
+ case "WebSearch":
561
+ return str(input.query);
562
+ case "ToolSearch":
563
+ return str(input.query);
564
+ case "Skill":
565
+ return str(input.skill) || str(input.command);
566
+ default:
567
+ return "";
568
+ }
569
+ }
570
+
571
+ function GenericCard({ item, attrs }: { item: ToolItem; attrs: Record<string, string> }) {
572
+ const rel = useRel();
573
+ const out = resultText(item.result);
574
+ return (
575
+ <Collapsible testId="tool-call" attrs={attrs} stateKey={item.key} label={<ToolLabel item={item}>{genericSummary(item.name, item.input, rel)}</ToolLabel>}>
576
+ <pre className="max-h-48 overflow-auto whitespace-pre-wrap break-words px-3 py-2 font-mono text-xs text-muted">{JSON.stringify(item.input, null, 2)}</pre>
577
+ <ReadImage item={item} />
578
+ {item.result && (
579
+ <div className="border-t border-border">
580
+ <Output text={out} error={item.result.isError} stateKey={item.key} />
581
+ </div>
582
+ )}
583
+ </Collapsible>
584
+ );
585
+ }
586
+
587
+ function ReadImage({ item }: { item: ToolItem }) {
588
+ const { readImageUrl } = useContext(TranscriptContext);
589
+ const file = str(item.input.file_path);
590
+ const src = item.name === "Read" && !item.result?.isError && IMAGE_EXTENSIONS.some((extension) => file.toLowerCase().endsWith(extension))
591
+ ? readImageUrl?.(item.id) : undefined;
592
+ return src ? <OptionalReadImage key={src} src={src} file={file} /> : null;
593
+ }
594
+
595
+ function OptionalReadImage({ src, file }: { src: string; file: string }) {
596
+ // A decoded <img> can be reused despite no-store. A fresh expansion must
597
+ // check the current temporary file instead of reviving an old decoded image.
598
+ const [url] = useState(() => `${src}${src.includes("?") ? "&" : "?"}preview=${uuid()}`);
599
+ const [loaded, setLoaded] = useState(false);
600
+ const [failed, setFailed] = useState(false);
601
+ if (failed) return null;
602
+ return <button type="button" hidden={!loaded} aria-label={`预览图片 ${file}`} title="临时预览当前文件"
603
+ onClick={() => openImagePreview(url, file, true)}
604
+ className="mx-3 mb-3 max-w-[calc(100%_-_1.5rem)] cursor-zoom-in rounded-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/50">
605
+ <img src={url} alt={file} onLoad={() => setLoaded(true)} onError={() => setFailed(true)}
606
+ data-testid="read-image" className="max-h-80 max-w-full rounded-lg border border-border object-contain" />
607
+ </button>;
608
+ }