@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,191 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { QueryClient, QueryObserver } from "@tanstack/react-query";
4
+ import { agentMetadataQuery, agentResponseModel, agentTranscriptParents, backgroundAgentToolUseId, resolveSubagent } from "./agent-history-flow.ts";
5
+ import type { AgentTask, BgTask, LiveEvent, SdkMessageLike } from "@shared/protocol.ts";
6
+ import { currentAgentTask } from "@shared/agent-tasks.ts";
7
+ import { buildItems } from "./transcript.ts";
8
+ import { boundSession } from "./message-cache.ts";
9
+ import { mergeSnapshot, type SessionLive } from "./live-merge.ts";
10
+
11
+ test("current agent run is independent of late update order and its terminal status", () => {
12
+ const old: AgentTask = { taskId: "stable", toolUseId: "old", runId: "run-1", status: "completed", model: "old-model" };
13
+ const current: AgentTask = { taskId: "stable", toolUseId: "new", runId: "run-2", status: "running", model: "new-model" };
14
+ const partial: AgentTask = { taskId: "stable", toolUseId: "no-run", status: "running" };
15
+ const other: AgentTask = { taskId: "sibling", toolUseId: "sibling", runId: "run-9", status: "running" };
16
+ const task: BgTask = { id: "stable", type: "local_agent", description: "agent", status: "running" };
17
+ for (const tasks of [[old, current, partial, other], [current, partial, other, old], [partial, current, old]]) {
18
+ assert.equal(currentAgentTask(tasks, "stable"), current);
19
+ assert.equal(backgroundAgentToolUseId(task, tasks), "new");
20
+ const completed = { ...current, status: "completed" as const };
21
+ assert.equal(currentAgentTask([completed, old, partial], "stable"), completed, "a finished new run cannot fall back to an older one");
22
+ }
23
+ assert.equal(currentAgentTask([old], "missing"), undefined);
24
+ assert.equal(currentAgentTask([partial, { ...partial, toolUseId: "last" }], "stable")?.toolUseId, "last", "legacy events without run IDs retain the arrival-order fallback");
25
+ });
26
+
27
+ test("resumed traces retain preceding live messages against an unchanged partial history window", () => {
28
+ const agent = { agentId: "stable", toolUseId: "original" };
29
+ const tasks: AgentTask[] = [
30
+ { taskId: "stable", toolUseId: "previous", runId: "run-1", status: "completed" },
31
+ { taskId: "stable", toolUseId: "current", runId: "run-2", status: "running" },
32
+ { taskId: "nested", parentToolUseId: "current", toolUseId: "nested", status: "running" },
33
+ { taskId: "sibling", toolUseId: "sibling", status: "running" },
34
+ ];
35
+ const parents = agentTranscriptParents("current", tasks, agent);
36
+ assert.deepEqual([...parents], ["current", "original", "previous"]);
37
+ assert.deepEqual([...agentTranscriptParents("current", tasks)], ["current"], "unresolved routes never guess a transcript owner");
38
+ const message = (uuid: string, parent: string | null): SdkMessageLike => ({ type: "assistant", uuid, parent_tool_use_id: parent, message: { id: uuid, content: [{ type: "text", text: uuid }] } });
39
+ const saved = message("saved", null), old = message("old-live", "previous"), resumed = message("new-live", "current");
40
+ const history = [saved];
41
+ const events: LiveEvent[] = [saved, old, resumed, message("main", null), message("nested", "nested"), message("sibling", "sibling")].map((m) => ({ k: "sdk", m }));
42
+ const options = { parent: "current", parents };
43
+ assert.deepEqual(buildItems(history, events, options).map((item) => item.key), ["saved:0", "old-live:0", "new-live:0"]);
44
+ assert.deepEqual(buildItems([saved, old], events, options).map((item) => item.key), ["saved:0", "old-live:0", "new-live:0"], "history/live overlap renders once");
45
+ assert.deepEqual(buildItems(history, [], options).map((item) => item.key), ["saved:0"], "historical browsing admits no live frames");
46
+ assert.deepEqual(history, [saved], "resuming neither replaces nor extends the selected history window");
47
+ });
48
+
49
+ test("retained invocation owners outlive status eviction and view unmount, then leave with their messages", () => {
50
+ const agent = { agentId: "stable", toolUseId: "spawn" };
51
+ const old: LiveEvent = { k: "sdk", m: { type: "assistant", uuid: "old", parent_tool_use_id: "resume-1", message: { content: [{ type: "text", text: "older live" }] } } };
52
+ const next: LiveEvent = { k: "delta", msgId: "new", index: 0, kind: "text", text: "current live", parent: "resume-2" };
53
+ const tasks: AgentTask[] = [
54
+ { taskId: "stable", toolUseId: "resume-1", status: "completed" },
55
+ { taskId: "stable", toolUseId: "resume-2", status: "running" },
56
+ { taskId: "nested", toolUseId: "nested", parentToolUseId: "resume-2", status: "running" },
57
+ ];
58
+ let session: SessionLive = boundSession({ seq: 1, resetCount: 0, events: [old, next], snapshot: { state: "running", pending: [], crons: [], tasks: [], agentTasks: tasks, planMode: false } });
59
+ session = boundSession({ ...session, snapshot: { ...session.snapshot, agentTasks: tasks.slice(1) } });
60
+ const reopened = agentTranscriptParents("resume-2", session.snapshot.agentTasks!, agent, session.agentOwners);
61
+ assert.equal(reopened.has("resume-1"), true);
62
+ assert.equal(reopened.has("nested"), false);
63
+ assert.deepEqual(buildItems(undefined, session.events, { parent: "resume-2", parents: reopened }).map((item) => item.kind === "text" ? item.text : item.kind), ["older live", "current live"]);
64
+ session = boundSession({ ...session, events: [next] });
65
+ assert.equal(session.agentOwners?.["resume-1"], undefined, "released messages release their owner map entries");
66
+ const reset = mergeSnapshot(session, { epoch: "new", seq: 0, reset: true, historyRevision: "new-branch", live: { ...session.snapshot, agentTasks: [] }, events: [] });
67
+ assert.deepEqual(reset.agentOwners, {}, "branch/replay reset does not retain a second ownership history");
68
+ });
69
+
70
+ test("a fresh browser recovers attribution directly from replay after task metadata eviction", () => {
71
+ const replay: LiveEvent = { k: "sdk", agentId: "stable", m: { type: "assistant", uuid: "old", parent_tool_use_id: "old-owner", message: { content: [{ type: "text", text: "old live" }] } } };
72
+ const session = mergeSnapshot(undefined, { epoch: "e", seq: 9, reset: true, live: { state: "idle", pending: [], crons: [], tasks: [], agentTasks: [], planMode: false }, events: [{ seq: 9, ev: replay }] });
73
+ const parents = agentTranscriptParents("current", [], { agentId: "stable", toolUseId: "spawn" }, session.agentOwners);
74
+ assert.equal(parents.has("old-owner"), true);
75
+ });
76
+
77
+ test("resumed SendMessage calls resolve the original transcript through the stable agent task ID", () => {
78
+ const original = { agentId: "stable-agent", toolUseId: "original-Agent-call", description: "Plan multilingual project site" };
79
+ const other = { agentId: "other-agent", toolUseId: "other-Agent-call", description: original.description };
80
+ const agents = [other, original];
81
+ assert.equal(resolveSubagent(agents, original.toolUseId), original);
82
+ assert.equal(resolveSubagent(agents, "resume-SendMessage-call", original.agentId), original);
83
+ assert.equal(resolveSubagent(agents, "resume-SendMessage-call", undefined, original.agentId), original, "saved URL works after live task eviction or restart");
84
+ assert.equal(resolveSubagent(agents, "resume-SendMessage-call", original.agentId, other.agentId), original);
85
+ assert.equal(resolveSubagent(agents, "resume-SendMessage-call", "missing-agent", other.agentId), undefined, "a conflicting URL cannot replace a known live identity");
86
+ assert.equal(resolveSubagent(agents, "resume-SendMessage-call"), undefined, "descriptions do not establish identity");
87
+ assert.equal(resolveSubagent(agents, "resume-SendMessage-call", undefined, "missing-agent"), undefined, "URL hints must name an available transcript");
88
+ });
89
+
90
+ test("agent model comes from its latest real response, isolated from parent and nested agents", () => {
91
+ const response = (parent: string | null, model: unknown, extra = {}): LiveEvent => ({ k: "sdk", m: { type: "assistant", parent_tool_use_id: parent, message: { model }, ...extra } });
92
+ const events = [response("selected", "first-model"), response(null, "main-model"), response("other", "other-model"),
93
+ response("selected", " latest-model "), response("nested", "nested-model"), response("selected", "<synthetic>"),
94
+ response("selected", "error-model", { error: "failed" }), response("selected", "aborted-model", { aborted: true }),
95
+ response("selected", " "), response("selected", 123)];
96
+ assert.equal(agentResponseModel("selected", events), "latest-model");
97
+ assert.equal(agentResponseModel("missing", events), undefined);
98
+ assert.equal(agentResponseModel("selected", [{ k: "sdk", m: { type: "user", parent_tool_use_id: "selected", message: { model: "not-an-assistant" } } }]), undefined);
99
+ });
100
+
101
+ test("agent model supports stream starts and saved history without guessing from settings", () => {
102
+ const saved: SdkMessageLike[] = [
103
+ { type: "assistant", message: { model: "saved-model" } },
104
+ { type: "assistant", parent_tool_use_id: "nested", message: { model: "nested-model" } },
105
+ { type: "assistant", message: { model: "<synthetic>" } },
106
+ ];
107
+ assert.equal(agentResponseModel("selected", [], saved), "saved-model");
108
+ const start: LiveEvent = { k: "sdk", m: { type: "stream_event", parent_tool_use_id: "selected", event: { type: "message_start", message: { model: "live-model" } } } };
109
+ assert.equal(agentResponseModel("selected", [start], saved), "live-model");
110
+ assert.equal(agentResponseModel("selected", [{ k: "settings", model: "main-setting", planMode: false }], saved), "saved-model");
111
+ assert.equal(agentResponseModel("selected", [{ k: "sdk", m: { type: "stream_event", parent_tool_use_id: "selected", event: { type: "message_delta", message: { model: "invalid" } } } }], saved), "saved-model");
112
+ });
113
+
114
+ test("background agent trace resolves exact task/tool identities for active and completed tasks", () => {
115
+ const task: BgTask = { id: "background-task-id", type: "local_agent", description: "same description", status: "running" };
116
+ const agents: AgentTask[] = [
117
+ { toolUseId: "other-tool", taskId: "other-task", description: task.description, status: "running" },
118
+ { toolUseId: "correct-tool", taskId: task.id, status: "running" },
119
+ ];
120
+ assert.equal(backgroundAgentToolUseId(task, agents), "correct-tool");
121
+ assert.equal(backgroundAgentToolUseId({ ...task, status: "completed" }, agents.map((agent) => ({ ...agent, status: "completed" }))), "correct-tool");
122
+ assert.equal(backgroundAgentToolUseId(task, agents, [{ agentId: task.id, toolUseId: "stale-tool" }]), "correct-tool", "explicit live relation takes precedence");
123
+ assert.equal(backgroundAgentToolUseId(task, [...agents, { toolUseId: "resumed-tool", taskId: task.id, status: "running" }]), "resumed-tool", "resumed task uses its most recently updated relation");
124
+ });
125
+
126
+ test("saved subagent metadata resolves missing live relations without guessing from descriptions", () => {
127
+ const task: BgTask = { id: "agent-file-id", type: "local_agent", description: "same description", status: "completed" };
128
+ const metadata = [{ agentId: "other-agent", toolUseId: "wrong-tool", description: task.description }, { agentId: task.id, toolUseId: "saved-tool" }];
129
+ assert.equal(backgroundAgentToolUseId(task, [], metadata), "saved-tool");
130
+ assert.equal(backgroundAgentToolUseId(task, [], metadata.slice(0, 1)), undefined);
131
+ assert.equal(backgroundAgentToolUseId(task, [], [{ agentId: task.id }]), undefined, "a task ID cannot be used as a tool-call route");
132
+ assert.equal(backgroundAgentToolUseId({ ...task, type: "local_bash" }, [], metadata), undefined);
133
+ });
134
+
135
+ test("Stop-hook subagent summaries resolve the same trace as stream local_agent tasks", () => {
136
+ const task: BgTask = { id: "agent-id", type: "subagent", description: "Plan multilingual project site", status: "running" };
137
+ const live: AgentTask[] = [{ taskId: task.id, toolUseId: "spawn-tool", status: "running" }];
138
+ const saved = [{ agentId: task.id, toolUseId: "saved-spawn-tool" }];
139
+ for (const type of ["subagent", "local_agent"]) {
140
+ assert.equal(backgroundAgentToolUseId({ ...task, type }, live), "spawn-tool");
141
+ assert.equal(backgroundAgentToolUseId({ ...task, type, status: "completed" }, [], saved), "saved-spawn-tool");
142
+ }
143
+ for (const type of ["shell", "local_bash", "monitor", "workflow"]) {
144
+ assert.equal(backgroundAgentToolUseId({ ...task, type }, live, saved), undefined);
145
+ }
146
+ });
147
+
148
+ test("CACHE-013 failed status refresh retains the selected source through retry", async () => {
149
+ const client = new QueryClient({ defaultOptions: { queries: { retry: false, gcTime: Infinity, staleTime: Infinity } } });
150
+ const metadata = [{ agentId: "a", toolUseId: "t" }];
151
+ const options = agentMetadataQuery("p", "s", async () => metadata);
152
+ client.setQueryData(options.queryKey, metadata);
153
+ const observer = new QueryObserver(client, options);
154
+ const states: { status: string; source?: string }[] = [];
155
+ const unsub = observer.subscribe((result) => states.push({ status: result.status, source: result.data?.[0]?.agentId }));
156
+ try {
157
+ observer.setOptions(agentMetadataQuery("p", "s", async () => { throw new Error("offline"); }));
158
+ await observer.refetch();
159
+ assert.equal(observer.getCurrentResult().status, "error");
160
+ assert.equal(observer.getCurrentResult().data?.[0]?.agentId, "a");
161
+ observer.setOptions(options);
162
+ await observer.refetch();
163
+ assert.equal(observer.getCurrentResult().status, "success");
164
+ assert.ok(states.some((state) => state.status === "error"));
165
+ assert.ok(states.every((state) => state.source === "a"));
166
+ // A successful, authoritative removal is distinct from a refresh failure.
167
+ observer.setOptions(agentMetadataQuery("p", "s", async () => []));
168
+ await observer.refetch();
169
+ assert.deepEqual(observer.getCurrentResult().data, []);
170
+ } finally { unsub(); observer.destroy(); client.clear(); }
171
+ });
172
+
173
+ test("metadata never carries a prior source across a project or session boundary", async () => {
174
+ for (const [pid, sid] of [["other-project", "s"], ["p", "other-session"]]) {
175
+ const client = new QueryClient({ defaultOptions: { queries: { retry: false, gcTime: Infinity, staleTime: Infinity } } });
176
+ const metadata = [{ agentId: "a", toolUseId: "t" }];
177
+ const initial = agentMetadataQuery("p", "s", async () => metadata);
178
+ client.setQueryData(initial.queryKey, metadata);
179
+ const observer = new QueryObserver(client, initial);
180
+ const unsub = observer.subscribe(() => {});
181
+ try {
182
+ let finish!: (value: typeof metadata) => void;
183
+ observer.setOptions(agentMetadataQuery(pid!, sid!, () => new Promise<typeof metadata>((resolve) => { finish = resolve; })));
184
+ assert.equal(observer.getCurrentResult().isPending, true);
185
+ assert.equal(observer.getCurrentResult().data, undefined);
186
+ finish([]);
187
+ await observer.refetch();
188
+ assert.deepEqual(observer.getCurrentResult().data, []);
189
+ } finally { unsub(); observer.destroy(); client.clear(); }
190
+ }
191
+ });
@@ -0,0 +1,58 @@
1
+ import type { AgentTask, BgTask, LiveEvent, SdkMessageLike } from "@shared/protocol.ts";
2
+ import { responseModel } from "@shared/agent-model.ts";
3
+ import { isSubagentTaskType } from "@shared/task-types.ts";
4
+ import { currentAgentTask } from "@shared/agent-tasks.ts";
5
+
6
+ export type SubagentInfo = { agentId: string; toolUseId?: string; agentType?: string; description?: string };
7
+
8
+ /** Status changes refresh the same metadata cache, so a failed refresh cannot
9
+ * temporarily erase the selected history source. Never share it across routes. */
10
+ export function agentMetadataQuery<T>(projectId: string, sessionId: string, queryFn: () => Promise<T>) {
11
+ return { queryKey: ["subagents", projectId, sessionId] as const, queryFn };
12
+ }
13
+
14
+ /** A resumed agent uses a new SendMessage tool ID, but keeps its task/file ID. */
15
+ export function resolveSubagent(agents: readonly SubagentInfo[], toolUseId: string, taskId?: string, savedAgentId?: string): SubagentInfo | undefined {
16
+ const original = agents.find((agent) => agent.toolUseId === toolUseId);
17
+ if (original) return original;
18
+ // A known live identity takes precedence over any saved URL hint.
19
+ const agentId = taskId ?? savedAgentId;
20
+ return agentId ? agents.find((agent) => agent.agentId === agentId) : undefined;
21
+ }
22
+
23
+ /** Background task IDs are not tool-call IDs. Prefer the explicit live relation;
24
+ * saved agent metadata also resolves tasks whose live relation was not retained. */
25
+ export function backgroundAgentToolUseId(task: BgTask, tasks: readonly AgentTask[], agents: readonly SubagentInfo[] = []): string | undefined {
26
+ if (!isSubagentTaskType(task.type)) return undefined;
27
+ return currentAgentTask(tasks, task.id)?.toolUseId
28
+ || agents.find((agent) => agent.agentId === task.id)?.toolUseId || undefined;
29
+ }
30
+
31
+ /** A single transcript file survives multiple Agent/SendMessage invocations.
32
+ * Keep their exact owners, without admitting sibling or nested agent traffic. */
33
+ export function agentTranscriptParents(toolUseId: string, tasks: readonly AgentTask[], agent?: SubagentInfo, owners: Readonly<Record<string, string>> = {}): ReadonlySet<string> {
34
+ const parents = new Set([toolUseId]);
35
+ if (agent) {
36
+ if (agent.toolUseId) parents.add(agent.toolUseId);
37
+ for (const task of tasks) if (task.taskId === agent.agentId) parents.add(task.toolUseId);
38
+ for (const [owner, agentId] of Object.entries(owners)) if (agentId === agent.agentId) parents.add(owner);
39
+ }
40
+ return parents;
41
+ }
42
+
43
+ /** Only inspect this agent's live frames or its own selected history file. */
44
+ export function agentResponseModel(toolUseId: string, events: readonly LiveEvent[], history: readonly SdkMessageLike[] = []): string | undefined {
45
+ for (let i = events.length - 1; i >= 0; i--) {
46
+ const event = events[i]!;
47
+ if (event.k !== "sdk" || event.m.parent_tool_use_id !== toolUseId) continue;
48
+ const model = responseModel(event.m);
49
+ if (model) return model;
50
+ }
51
+ for (let i = history.length - 1; i >= 0; i--) {
52
+ const message = history[i]!;
53
+ if (message.parent_tool_use_id && message.parent_tool_use_id !== toolUseId) continue;
54
+ const model = responseModel(message);
55
+ if (model) return model;
56
+ }
57
+ return undefined;
58
+ }
@@ -0,0 +1,30 @@
1
+ import type { AgentTask, LiveEvent } from "@shared/protocol.ts";
2
+
3
+ /** Task status is capped separately from transcript replay. Keep exact owner
4
+ * identities with the cached transcript, including across component unmounts. */
5
+ export function mergeAgentOwners(previous: Readonly<Record<string, string>> = {}, events: readonly LiveEvent[], tasks: readonly AgentTask[] = []): Record<string, string> {
6
+ const owners = { ...previous };
7
+ const remember = (tasks: readonly AgentTask[]) => {
8
+ for (const task of tasks) if (task.taskId) owners[task.toolUseId] = task.taskId;
9
+ };
10
+ for (const event of events) {
11
+ const parent = event.k === "sdk" ? event.m.parent_tool_use_id : event.k === "delta" ? event.parent : undefined;
12
+ if ((event.k === "sdk" || event.k === "delta") && event.agentId && parent) owners[parent] = event.agentId;
13
+ if (event.k === "agent_tasks") remember(event.tasks);
14
+ else if (event.k === "sdk" && event.m.type === "system" && event.m.subtype?.startsWith("task_")
15
+ && typeof event.m.tool_use_id === "string" && typeof event.m.task_id === "string") owners[event.m.tool_use_id] = event.m.task_id;
16
+ }
17
+ remember(tasks);
18
+ return owners;
19
+ }
20
+
21
+ /** At most one scalar relation per retained owner or current task. Eviction and
22
+ * branch reset release it with its messages, rather than growing a second log. */
23
+ export function retainAgentOwners(owners: Readonly<Record<string, string>>, events: readonly LiveEvent[], tasks: readonly AgentTask[] = []): Record<string, string> {
24
+ const retained = new Set(tasks.map((task) => task.toolUseId));
25
+ for (const event of events) {
26
+ const parent = event.k === "sdk" ? event.m.parent_tool_use_id : event.k === "delta" ? event.parent : undefined;
27
+ if (parent) retained.add(parent);
28
+ }
29
+ return Object.fromEntries(Object.entries(owners).filter(([owner]) => retained.has(owner)));
30
+ }
@@ -0,0 +1,36 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { annotationMenuPosition, fileSelectionText } from "./annotation-selection.ts";
4
+
5
+ test("source selections preserve multiline text, blank lines and tabs without DOM newline nodes", () => {
6
+ assert.deepEqual(fileSelectionText("const first = 1;\n\n\tconst second = 2;\n", { line: 0, column: 6 }, { line: 2, column: 13 }), {
7
+ quote: "first = 1;\n\n\tconst second",
8
+ startLine: 1,
9
+ endLine: 3,
10
+ });
11
+ });
12
+
13
+ test("source selections retain original CRLF delimiters and exclude CSS line numbers", () => {
14
+ assert.deepEqual(fileSelectionText("alpha\r\nbeta\r\ngamma", { line: 0, column: 2 }, { line: 1, column: 2 }), {
15
+ quote: "pha\r\nbe",
16
+ startLine: 1,
17
+ endLine: 2,
18
+ });
19
+ });
20
+
21
+ test("a selection ending at the next line boundary reports only included lines", () => {
22
+ assert.deepEqual(fileSelectionText("one\ntwo\nthree", { line: 0, column: 0 }, { line: 2, column: 0 }), {
23
+ quote: "one\ntwo\n",
24
+ startLine: 1,
25
+ endLine: 2,
26
+ });
27
+ assert.equal(fileSelectionText("one", { line: 0, column: 1 }, { line: 0, column: 1 }), null);
28
+ });
29
+
30
+ test("annotation menu stays inside a shifted mobile visual viewport", () => {
31
+ const viewport = { left: 20, top: 100, width: 320, height: 300 };
32
+ assert.deepEqual(annotationMenuPosition({ left: -10, right: 10, top: 370, bottom: 390 }, viewport), { width: 112, left: 28, top: 322 });
33
+ const position = annotationMenuPosition({ left: 330, right: 350, top: 105, bottom: 120 }, viewport);
34
+ assert.equal(position.left, 220);
35
+ assert.equal(position.top, 128);
36
+ });
@@ -0,0 +1,123 @@
1
+ import type { AnnotationSource } from "./annotations.ts";
2
+
3
+ export type AnnotationSelection = {
4
+ source: AnnotationSource;
5
+ quote: string;
6
+ rect: { left: number; right: number; top: number; bottom: number };
7
+ };
8
+
9
+ type FilePoint = { line: number; column: number };
10
+
11
+ /** DOM source lines have no newline nodes. Slice the original file so CRLF, tabs and blank lines survive. */
12
+ export function fileSelectionText(content: string, start: FilePoint, end: FilePoint) {
13
+ const lines = content.split("\n");
14
+ const starts = [0];
15
+ for (let i = 0; i < lines.length - 1; i++) starts.push(starts[i]! + lines[i]!.length + 1);
16
+ const offset = (point: FilePoint) => {
17
+ const line = Math.max(0, Math.min(lines.length - 1, point.line));
18
+ const visibleLength = lines[line]!.replace(/\r$/, "").length;
19
+ return starts[line]! + Math.max(0, Math.min(visibleLength, point.column));
20
+ };
21
+ const from = offset(start);
22
+ const to = offset(end);
23
+ if (from >= to) return null;
24
+ // An end boundary at the start of the next line does not include that line's contents.
25
+ const lastLine = end.column === 0 && end.line > start.line ? end.line - 1 : end.line;
26
+ return { quote: content.slice(from, to), startLine: start.line + 1, endLine: lastLine + 1 };
27
+ }
28
+
29
+ const EXCLUDED = 'button, input, textarea, select, option, [role="button"], [role="menu"], [role="dialog"], [contenteditable]:not([contenteditable="false"]), [data-annotation-exclude], [data-line-number], .line-number, [data-testid="show-all"], [aria-hidden="true"], script, style';
30
+
31
+ function elementFor(node: Node): Element | null {
32
+ return node.nodeType === 1 ? node as Element : node.parentElement;
33
+ }
34
+
35
+ function allowedRange(root: HTMLElement, range: Range) {
36
+ if (!root.contains(range.startContainer) || !root.contains(range.endContainer)) return false;
37
+ if (elementFor(range.startContainer)?.closest(EXCLUDED) || elementFor(range.endContainer)?.closest(EXCLUDED)) return false;
38
+ // Endpoints alone are insufficient: a selection can pass through a toolbar between paragraphs.
39
+ for (const control of root.querySelectorAll(EXCLUDED)) {
40
+ if (range.intersectsNode(control)) return false;
41
+ }
42
+ return true;
43
+ }
44
+
45
+ function filePoint(code: Element, lines: Element[], node: Node, offset: number): FilePoint | null {
46
+ const line = elementFor(node)?.closest(".line");
47
+ if (line && line.parentElement === code) {
48
+ const index = lines.indexOf(line);
49
+ if (index < 0) return null;
50
+ const prefix = code.ownerDocument.createRange();
51
+ prefix.selectNodeContents(line);
52
+ prefix.setEnd(node, offset);
53
+ return { line: index, column: prefix.toString().length };
54
+ }
55
+ // Browsers also express a selection boundary between two line elements on <code> itself.
56
+ if (node === code) {
57
+ const next = code.childNodes[offset];
58
+ if (next) {
59
+ const index = lines.findIndex((candidate) => candidate === next || candidate.contains(next));
60
+ if (index >= 0) return { line: index, column: 0 };
61
+ }
62
+ if (offset === code.childNodes.length && lines.length) {
63
+ return { line: lines.length - 1, column: lines.at(-1)!.textContent?.length ?? 0 };
64
+ }
65
+ }
66
+ return null;
67
+ }
68
+
69
+ /** Only plain data leaves this function; callers never retain a live Range or DOM selection. */
70
+ export function captureAnnotationSelection(
71
+ root: HTMLElement,
72
+ selection: Selection | null,
73
+ context: { sessionId?: string; agentToolUseId?: string; filePath?: string; fileContent?: string },
74
+ ): AnnotationSelection | null {
75
+ if (!selection || selection.isCollapsed || selection.rangeCount !== 1) return null;
76
+ const range = selection.getRangeAt(0);
77
+ if (!allowedRange(root, range)) return null;
78
+ let source: AnnotationSource;
79
+ let quote: string;
80
+ if (context.filePath !== undefined) {
81
+ const code = elementFor(range.startContainer)?.closest(".code-view code");
82
+ if (!code || !root.contains(code) || !code.contains(range.endContainer)) return null;
83
+ const lines = [...code.children].filter((element) => element.classList.contains("line"));
84
+ const start = filePoint(code, lines, range.startContainer, range.startOffset);
85
+ const end = filePoint(code, lines, range.endContainer, range.endOffset);
86
+ if (!start || !end) return null;
87
+ const content = context.fileContent ?? lines.map((line) => line.textContent ?? "").join("\n");
88
+ const captured = fileSelectionText(content, start, end);
89
+ if (!captured) return null;
90
+ quote = captured.quote;
91
+ source = { kind: "file", path: context.filePath, startLine: captured.startLine, endLine: captured.endLine };
92
+ } else {
93
+ if (!context.sessionId) return null;
94
+ const first = elementFor(range.startContainer)?.closest<HTMLElement>("[data-annotation-item]");
95
+ const last = elementFor(range.endContainer)?.closest<HTMLElement>("[data-annotation-item]");
96
+ const itemKey = first?.dataset.annotationItem;
97
+ if (!first || !itemKey || first !== last || !root.contains(first)) return null;
98
+ quote = selection.toString();
99
+ source = { kind: "message", sessionId: context.sessionId, itemKey, ...(context.agentToolUseId ? { agentToolUseId: context.agentToolUseId } : {}) };
100
+ }
101
+ if (!quote.trim()) return null;
102
+ const rect = range.getBoundingClientRect();
103
+ if (!rect.width && !rect.height) return null;
104
+ return { source, quote, rect: { left: rect.left, right: rect.right, top: rect.top, bottom: rect.bottom } };
105
+ }
106
+
107
+ export function annotationMenuPosition(
108
+ rect: AnnotationSelection["rect"],
109
+ viewport: { left: number; top: number; width: number; height: number },
110
+ ) {
111
+ const width = Math.min(112, Math.max(0, viewport.width - 16));
112
+ const height = 40;
113
+ const minLeft = viewport.left + 8;
114
+ const minTop = viewport.top + 8;
115
+ const maxLeft = Math.max(minLeft, viewport.left + viewport.width - width - 8);
116
+ const maxTop = Math.max(minTop, viewport.top + viewport.height - height - 8);
117
+ const below = rect.bottom + 8;
118
+ return {
119
+ width,
120
+ left: Math.max(minLeft, Math.min(maxLeft, (rect.left + rect.right - width) / 2)),
121
+ top: Math.max(minTop, Math.min(maxTop, below + height > viewport.top + viewport.height - 8 ? rect.top - height - 8 : below)),
122
+ };
123
+ }