@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.
- package/bin/claude-ide.js +23 -0
- package/package.json +75 -4
- package/server/add-dirs.ts +126 -0
- package/server/auth.test.ts +148 -0
- package/server/auth.ts +235 -0
- package/server/bang.ts +54 -0
- package/server/btw-instructions.ts +19 -0
- package/server/btw-policy.ts +17 -0
- package/server/btw.ts +143 -0
- package/server/child-env.ts +29 -0
- package/server/cli-control.ts +255 -0
- package/server/cli-files.ts +55 -0
- package/server/cli-info.ts +73 -0
- package/server/config/account.ts +65 -0
- package/server/config/agents.ts +186 -0
- package/server/config/mcp.ts +86 -0
- package/server/config/memory.ts +193 -0
- package/server/config/paths.ts +156 -0
- package/server/config/plugins.ts +204 -0
- package/server/config/routes.ts +95 -0
- package/server/config/settings.ts +246 -0
- package/server/config.ts +46 -0
- package/server/db.ts +121 -0
- package/server/directories.test.ts +180 -0
- package/server/directories.ts +87 -0
- package/server/dist-version.ts +69 -0
- package/server/durable-tasks.ts +61 -0
- package/server/file-transfers.ts +79 -0
- package/server/files.test.ts +127 -0
- package/server/files.ts +386 -0
- package/server/fs-queue.ts +14 -0
- package/server/fsutil.ts +144 -0
- package/server/git.ts +86 -0
- package/server/goal-state.test.ts +64 -0
- package/server/goal-state.ts +78 -0
- package/server/history-pages.test.ts +619 -0
- package/server/history-pages.ts +513 -0
- package/server/http-channel-manager.ts +468 -0
- package/server/http-channel-pull.test.ts +314 -0
- package/server/http-channel-push.test.ts +364 -0
- package/server/http-channel-registration.test.ts +291 -0
- package/server/http-channel-routes.ts +73 -0
- package/server/http-channel-store.ts +142 -0
- package/server/http-channel-test-fixture.ts +103 -0
- package/server/ide-messages-mcp.test.ts +82 -0
- package/server/ide-messages-mcp.ts +47 -0
- package/server/idecron-mcp.ts +65 -0
- package/server/idecron.test.ts +79 -0
- package/server/idecron.ts +271 -0
- package/server/index.ts +336 -0
- package/server/live-session.test.ts +1126 -0
- package/server/live-session.ts +1165 -0
- package/server/project-routes.ts +46 -0
- package/server/projects.test.ts +28 -0
- package/server/push.ts +315 -0
- package/server/report-cache.ts +28 -0
- package/server/session-aliases.test.ts +57 -0
- package/server/session-aliases.ts +50 -0
- package/server/session-branch.ts +71 -0
- package/server/session-dirs.ts +189 -0
- package/server/session-file.test.ts +134 -0
- package/server/session-file.ts +113 -0
- package/server/session-messaging.test.ts +297 -0
- package/server/session-messaging.ts +165 -0
- package/server/sessions.ts +638 -0
- package/server/subagents.test.ts +43 -0
- package/server/subagents.ts +58 -0
- package/server/tool-images.test.ts +76 -0
- package/server/tool-images.ts +45 -0
- package/server/turn-changes.ts +308 -0
- package/server/worktree-git.ts +87 -0
- package/server/worktrees.ts +138 -0
- package/server/ws.test.ts +195 -0
- package/server/ws.ts +197 -0
- package/shared/agent-model.ts +15 -0
- package/shared/agent-tasks.ts +12 -0
- package/shared/config.ts +175 -0
- package/shared/directories.ts +12 -0
- package/shared/files.ts +51 -0
- package/shared/http-channels.ts +63 -0
- package/shared/message-window.ts +189 -0
- package/shared/project.ts +77 -0
- package/shared/protocol.ts +203 -0
- package/shared/task-types.ts +4 -0
- package/shared/todos.ts +40 -0
- package/shared/write-policy.ts +9 -0
- package/web/dist/assets/AgentView-wrTKgcJ3.js +1 -0
- package/web/dist/assets/AnnotationSelection-BZmNmwVW.js +7 -0
- package/web/dist/assets/ChangesView-G1WTzh4q.js +2 -0
- package/web/dist/assets/FilesView-DPgAbYt_.js +6 -0
- package/web/dist/assets/Form-BKTwiYyw.js +1 -0
- package/web/dist/assets/HomePage-Dq2apjBe.js +1 -0
- package/web/dist/assets/Markdown-IwP-xF29.js +67 -0
- package/web/dist/assets/MessageList-DD9GypSy.js +15 -0
- package/web/dist/assets/Modal-g2rv8I_n.js +1 -0
- package/web/dist/assets/NotificationSettings-DpuLO_Bv.js +1 -0
- package/web/dist/assets/OverviewPage-Dx6miabE.js +1 -0
- package/web/dist/assets/ProjectHome-CousyTLL.js +1 -0
- package/web/dist/assets/SessionView-CPVL7_HO.js +2 -0
- package/web/dist/assets/SettingsPage-D_oYSnW2.js +7 -0
- package/web/dist/assets/WorktreesView-C7e0HV__.js +1 -0
- package/web/dist/assets/abap-CLvhMVsD.js +1 -0
- package/web/dist/assets/actionscript-3-Cs6JJXT5.js +1 -0
- package/web/dist/assets/ada-X3Y2mlq2.js +1 -0
- package/web/dist/assets/ahk-DwADkxyd.js +1 -0
- package/web/dist/assets/ahk2-_6rzW9BS.js +1 -0
- package/web/dist/assets/andromeeda-vGVdxbeo.js +1 -0
- package/web/dist/assets/angular-html-Cw29lun8.js +1 -0
- package/web/dist/assets/angular-ts-BXz-O3SJ.js +1 -0
- package/web/dist/assets/apache-U0d_L8uA.js +1 -0
- package/web/dist/assets/apex-KFGFyKwG.js +1 -0
- package/web/dist/assets/apl-xBchdRhy.js +1 -0
- package/web/dist/assets/applescript-CCn79oCD.js +1 -0
- package/web/dist/assets/ara-4CJ0cIlV.js +1 -0
- package/web/dist/assets/asciidoc-SCjQUq34.js +1 -0
- package/web/dist/assets/asm-Cmm7eHzH.js +1 -0
- package/web/dist/assets/astro-1q6DGuN3.js +1 -0
- package/web/dist/assets/aurora-x-CDeNXAV0.js +1 -0
- package/web/dist/assets/awk-BWXHIvNe.js +1 -0
- package/web/dist/assets/ayu-dark-DluEY0Gj.js +1 -0
- package/web/dist/assets/ayu-light-C3h-C4tm.js +1 -0
- package/web/dist/assets/ayu-mirage-Bqwy1Gya.js +1 -0
- package/web/dist/assets/ballerina-B7ZEbQpA.js +1 -0
- package/web/dist/assets/bat-qdxAdBWQ.js +1 -0
- package/web/dist/assets/beancount-C2IN4Vk8.js +1 -0
- package/web/dist/assets/berry-DKpUyyne.js +1 -0
- package/web/dist/assets/bibtex-Ci_nEsc7.js +1 -0
- package/web/dist/assets/bicep-CUHmPFLl.js +1 -0
- package/web/dist/assets/bird2-CYIUgYqB.js +1 -0
- package/web/dist/assets/blade-Cqdv4oZN.js +1 -0
- package/web/dist/assets/bsl-CVED6lJ1.js +1 -0
- package/web/dist/assets/c-D9gIxSfx.js +1 -0
- package/web/dist/assets/c3-D0apG41V.js +1 -0
- package/web/dist/assets/cadence-CQ2zXKGN.js +1 -0
- package/web/dist/assets/cairo-DRAZz75c.js +1 -0
- package/web/dist/assets/catppuccin-frappe-C9Q5zVZY.js +1 -0
- package/web/dist/assets/catppuccin-latte-CtsyrtPq.js +1 -0
- package/web/dist/assets/catppuccin-macchiato-R8nAf1vR.js +1 -0
- package/web/dist/assets/catppuccin-mocha-DjuaAJKi.js +1 -0
- package/web/dist/assets/chapel-CuseGurG.js +1 -0
- package/web/dist/assets/clarity-CtfBBspg.js +1 -0
- package/web/dist/assets/clojure-DqKBuwfJ.js +1 -0
- package/web/dist/assets/cmake-Bj61d0ZC.js +1 -0
- package/web/dist/assets/cobol-Bdpj3Qoh.js +1 -0
- package/web/dist/assets/codeowners-C8r90Shi.js +1 -0
- package/web/dist/assets/codeql-oeQT6MSM.js +1 -0
- package/web/dist/assets/coffee-Sk8pHFjs.js +1 -0
- package/web/dist/assets/common-lisp-Cv5bFMCO.js +1 -0
- package/web/dist/assets/coq-CGaSfhwR.js +1 -0
- package/web/dist/assets/core-BxI4V4-T.js +12 -0
- package/web/dist/assets/cpp-BQfBYKx3.js +1 -0
- package/web/dist/assets/crystal-B3j7DoOZ.js +1 -0
- package/web/dist/assets/csharp-dmMxLVLW.js +1 -0
- package/web/dist/assets/css-DpFbYmRh.js +1 -0
- package/web/dist/assets/csv-Dx-8-gkx.js +1 -0
- package/web/dist/assets/cue-CE9AQfxI.js +1 -0
- package/web/dist/assets/cypher-ClKdZ_lG.js +1 -0
- package/web/dist/assets/d-qD-0Kul2.js +1 -0
- package/web/dist/assets/dark-plus-Cs2F2srj.js +1 -0
- package/web/dist/assets/dart-CHZY9Xhz.js +1 -0
- package/web/dist/assets/dax-BkyTk9wS.js +1 -0
- package/web/dist/assets/desktop-Dlh5hvp9.js +1 -0
- package/web/dist/assets/diff-woXpYk--.js +1 -0
- package/web/dist/assets/docker-IyjqRm3v.js +1 -0
- package/web/dist/assets/dotenv-CuH5my32.js +1 -0
- package/web/dist/assets/dracula-BHWKrbxM.js +1 -0
- package/web/dist/assets/dracula-soft-5eyTD99u.js +1 -0
- package/web/dist/assets/dream-maker-DW3nJb8Q.js +1 -0
- package/web/dist/assets/edge-DXQWmXTB.js +1 -0
- package/web/dist/assets/elixir-CrDIY2Vo.js +1 -0
- package/web/dist/assets/elm-BslFEg8K.js +1 -0
- package/web/dist/assets/emacs-lisp-DR2vtWaj.js +1 -0
- package/web/dist/assets/engine-javascript-DJg3-Vu2.js +141 -0
- package/web/dist/assets/erb-CUYhg0Ws.js +1 -0
- package/web/dist/assets/erlang-BXdgHVfi.js +1 -0
- package/web/dist/assets/everforest-dark-sB-x3p7T.js +1 -0
- package/web/dist/assets/everforest-light-Df2xbC6M.js +1 -0
- package/web/dist/assets/fennel-DQxkIbk2.js +1 -0
- package/web/dist/assets/fish-BJitypiv.js +1 -0
- package/web/dist/assets/fluent-C03EYrpw.js +1 -0
- package/web/dist/assets/format-DrBC7w0A.js +1 -0
- package/web/dist/assets/fortran-fixed-form-D6mN0kjy.js +1 -0
- package/web/dist/assets/fortran-free-form-CYNrtFtB.js +1 -0
- package/web/dist/assets/fsharp-CwoEF39o.js +1 -0
- package/web/dist/assets/gdresource-CZkFGo3B.js +1 -0
- package/web/dist/assets/gdscript-DYvRD3hg.js +1 -0
- package/web/dist/assets/gdshader-CBce3t8t.js +1 -0
- package/web/dist/assets/genie-CV2tkWYe.js +1 -0
- package/web/dist/assets/gherkin-DExj1W_8.js +1 -0
- package/web/dist/assets/git-commit-dUGrvm6Q.js +1 -0
- package/web/dist/assets/git-rebase-4zCRqF28.js +1 -0
- package/web/dist/assets/github-dark-C-LZuMrd.js +1 -0
- package/web/dist/assets/github-dark-default-DXG-b-1a.js +1 -0
- package/web/dist/assets/github-dark-dimmed-Bx1FflLF.js +1 -0
- package/web/dist/assets/github-dark-high-contrast-B_tTalzw.js +1 -0
- package/web/dist/assets/github-light-EUqPIrTm.js +1 -0
- package/web/dist/assets/github-light-default-BXViO-2h.js +1 -0
- package/web/dist/assets/github-light-high-contrast-B68TUdTA.js +1 -0
- package/web/dist/assets/gleam-CSRkHgEL.js +1 -0
- package/web/dist/assets/glimmer-js-7adTRvH4.js +1 -0
- package/web/dist/assets/glimmer-ts-IdSskDYw.js +1 -0
- package/web/dist/assets/glsl-Qzy53KDt.js +1 -0
- package/web/dist/assets/gn-CAXYAgV-.js +1 -0
- package/web/dist/assets/gnuplot-7GGW24-e.js +1 -0
- package/web/dist/assets/go-rLFTqkRN.js +1 -0
- package/web/dist/assets/graphql-CV_puxbB.js +1 -0
- package/web/dist/assets/groovy-CacY0gHj.js +1 -0
- package/web/dist/assets/gruvbox-dark-hard-C820rvS2.js +1 -0
- package/web/dist/assets/gruvbox-dark-medium-BPjhmG05.js +1 -0
- package/web/dist/assets/gruvbox-dark-soft-MrdJrrXF.js +1 -0
- package/web/dist/assets/gruvbox-light-hard-BC_s9l72.js +1 -0
- package/web/dist/assets/gruvbox-light-medium-BAWPOn9u.js +1 -0
- package/web/dist/assets/gruvbox-light-soft-BSMLrYjP.js +1 -0
- package/web/dist/assets/hack-C5GPjiLV.js +1 -0
- package/web/dist/assets/haml-BQ5YR1DQ.js +1 -0
- package/web/dist/assets/handlebars-DnhRK9A1.js +1 -0
- package/web/dist/assets/haskell-D8IpX4py.js +1 -0
- package/web/dist/assets/haxe-C8oCnwJJ.js +1 -0
- package/web/dist/assets/hcl-Dh228itO.js +1 -0
- package/web/dist/assets/highlight.worker-CY-ZeDAl.js +1 -0
- package/web/dist/assets/hjson-CxZEssPk.js +1 -0
- package/web/dist/assets/hlsl-Cvrh5tZx.js +1 -0
- package/web/dist/assets/horizon-CE9ld1lL.js +1 -0
- package/web/dist/assets/horizon-bright-DSNQnXHK.js +1 -0
- package/web/dist/assets/houston-CsvMBhTu.js +1 -0
- package/web/dist/assets/html-CmHiEKV9.js +1 -0
- package/web/dist/assets/html-derivative-BXE-rYIK.js +1 -0
- package/web/dist/assets/http-DPjjqtcL.js +1 -0
- package/web/dist/assets/hurl-AMBpSloO.js +1 -0
- package/web/dist/assets/hxml-D7Y24wKF.js +1 -0
- package/web/dist/assets/hy-CZbG8q4J.js +1 -0
- package/web/dist/assets/imba-DsUTQ-LC.js +1 -0
- package/web/dist/assets/index-DNPnl4sh.css +2 -0
- package/web/dist/assets/index-DQh72lEx.js +14 -0
- package/web/dist/assets/ini-B5eOa1yu.js +1 -0
- package/web/dist/assets/java-CEM7Tdw_.js +1 -0
- package/web/dist/assets/javascript-CU3PLowd.js +1 -0
- package/web/dist/assets/jinja-fR0x_9Xy.js +1 -0
- package/web/dist/assets/jison-BoKsxs5M.js +1 -0
- package/web/dist/assets/json-BM2FWsRB.js +1 -0
- package/web/dist/assets/json5-BR5RXkoi.js +1 -0
- package/web/dist/assets/jsonc-CYpm1nAK.js +1 -0
- package/web/dist/assets/jsonl-CmCQp5Yx.js +1 -0
- package/web/dist/assets/jsonnet-CJTPZ8u_.js +1 -0
- package/web/dist/assets/jssm-DXw9l8Rf.js +1 -0
- package/web/dist/assets/jsx-B5fTecQP.js +1 -0
- package/web/dist/assets/julia-DGyDG39_.js +1 -0
- package/web/dist/assets/just-B-RyLcSR.js +1 -0
- package/web/dist/assets/kanagawa-dragon-Bozd7klA.js +1 -0
- package/web/dist/assets/kanagawa-lotus-BmuOG9ZW.js +1 -0
- package/web/dist/assets/kanagawa-wave-BML5mF2J.js +1 -0
- package/web/dist/assets/kdl-CsD5j6eV.js +1 -0
- package/web/dist/assets/kotlin-DhhofPvG.js +1 -0
- package/web/dist/assets/kusto-C7mF5XQf.js +1 -0
- package/web/dist/assets/langs-CciPKqaF.js +1 -0
- package/web/dist/assets/laserwave-C_8bwKvT.js +1 -0
- package/web/dist/assets/latex-CV1pQelm.js +1 -0
- package/web/dist/assets/lean-CewbzKMR.js +1 -0
- package/web/dist/assets/less-DVTAwKKz.js +1 -0
- package/web/dist/assets/lib-DlcVMUqT.js +1 -0
- package/web/dist/assets/light-plus-DVQuIRkW.js +1 -0
- package/web/dist/assets/liquid-9p05RSzQ.js +1 -0
- package/web/dist/assets/llvm-DqHOMS-s.js +1 -0
- package/web/dist/assets/log-BNLmms1o.js +1 -0
- package/web/dist/assets/logo-Cluzi2Zq.js +1 -0
- package/web/dist/assets/lua-CIPVh0S-.js +1 -0
- package/web/dist/assets/luau-VGWVLoqX.js +1 -0
- package/web/dist/assets/make-Dixweg8N.js +1 -0
- package/web/dist/assets/markdown-BYOwaDjH.js +1 -0
- package/web/dist/assets/marko-h2h6YUMS.js +1 -0
- package/web/dist/assets/material-theme-Bm3Qr25_.js +1 -0
- package/web/dist/assets/material-theme-darker-2IIEA8gg.js +1 -0
- package/web/dist/assets/material-theme-lighter-uhdI0v04.js +1 -0
- package/web/dist/assets/material-theme-ocean-CHQ94UKr.js +1 -0
- package/web/dist/assets/material-theme-palenight-B5W6OYN7.js +1 -0
- package/web/dist/assets/matlab-D7qyCx1q.js +1 -0
- package/web/dist/assets/mdc-yAyZMdoC.js +1 -0
- package/web/dist/assets/mdx-DQZ5AkYe.js +1 -0
- package/web/dist/assets/mermaid-BxpfTIzt.js +1 -0
- package/web/dist/assets/message-font-size-DoaZfwkT.js +1 -0
- package/web/dist/assets/min-dark-BSWPekZh.js +1 -0
- package/web/dist/assets/min-light-DDpmG2fV.js +1 -0
- package/web/dist/assets/mipsasm-BMqwQI7S.js +1 -0
- package/web/dist/assets/mojo-fg40_ntQ.js +1 -0
- package/web/dist/assets/monokai-BscAjiQZ.js +1 -0
- package/web/dist/assets/moonbit-D9W784E_.js +1 -0
- package/web/dist/assets/move-BYnGhZoM.js +1 -0
- package/web/dist/assets/narrat-_X_XdTYD.js +1 -0
- package/web/dist/assets/nextflow-BoTnSBQ_.js +1 -0
- package/web/dist/assets/nextflow-groovy-D7IRt6Pp.js +1 -0
- package/web/dist/assets/nginx-jWINucNT.js +1 -0
- package/web/dist/assets/night-owl-DhmEMT88.js +1 -0
- package/web/dist/assets/night-owl-light-eJ-hLW7d.js +1 -0
- package/web/dist/assets/nim-Dtt9DZ0t.js +1 -0
- package/web/dist/assets/nix-Cl_7024t.js +1 -0
- package/web/dist/assets/nord-Cb4Vim4T.js +1 -0
- package/web/dist/assets/nsis-CjNLYCLg.js +1 -0
- package/web/dist/assets/nushell--0fT3PI1.js +1 -0
- package/web/dist/assets/objective-c-D1A_Heim.js +1 -0
- package/web/dist/assets/objective-cpp-BsSzOQcm.js +1 -0
- package/web/dist/assets/ocaml-O90oeIOV.js +1 -0
- package/web/dist/assets/odin-B1RWQWA5.js +1 -0
- package/web/dist/assets/one-dark-pro-C6YyR8OD.js +1 -0
- package/web/dist/assets/one-light-D7Lr4KcI.js +1 -0
- package/web/dist/assets/openscad-BUDT5pXO.js +1 -0
- package/web/dist/assets/org-ClD5huOa.js +1 -0
- package/web/dist/assets/pascal-4ZHwLPI5.js +1 -0
- package/web/dist/assets/perl-BFegxyeJ.js +1 -0
- package/web/dist/assets/php-B-qOA3Y4.js +1 -0
- package/web/dist/assets/pkl-ot-7Btpt.js +1 -0
- package/web/dist/assets/plastic-DQwYfKfQ.js +1 -0
- package/web/dist/assets/plsql-DGHpHOYJ.js +1 -0
- package/web/dist/assets/po-BiJDBrnU.js +1 -0
- package/web/dist/assets/poimandres-DRFjx7u4.js +1 -0
- package/web/dist/assets/polar-C7UOKdEL.js +1 -0
- package/web/dist/assets/postcss-BXeXVLqQ.js +1 -0
- package/web/dist/assets/powerquery-DNMTfnFr.js +1 -0
- package/web/dist/assets/powershell-QtSnmXcu.js +1 -0
- package/web/dist/assets/prisma-DMgNhnYS.js +1 -0
- package/web/dist/assets/prolog-iXnhIJG7.js +1 -0
- package/web/dist/assets/proto-DB4EqR-F.js +1 -0
- package/web/dist/assets/pug-Cq0NBPu-.js +1 -0
- package/web/dist/assets/puppet-CDv2pdJW.js +1 -0
- package/web/dist/assets/purescript-9MfHhQsQ.js +1 -0
- package/web/dist/assets/python-gzcpVVnB.js +1 -0
- package/web/dist/assets/qml-CYUsa87k.js +1 -0
- package/web/dist/assets/qmldir-DCQb3MpD.js +1 -0
- package/web/dist/assets/qss-Fe1Jh2GI.js +1 -0
- package/web/dist/assets/r-4xJhgbOf.js +1 -0
- package/web/dist/assets/racket-DcIDlBhZ.js +1 -0
- package/web/dist/assets/raku-B3gFvitq.js +1 -0
- package/web/dist/assets/razor-BlHaOHx8.js +1 -0
- package/web/dist/assets/rbs-C0MpOrj4.js +1 -0
- package/web/dist/assets/react-B4UGpcWC.js +1 -0
- package/web/dist/assets/red-CYlAq2b0.js +1 -0
- package/web/dist/assets/reg-CRGYupPL.js +1 -0
- package/web/dist/assets/regexp-BUSxjv2o.js +1 -0
- package/web/dist/assets/rel-BtDbiS_P.js +1 -0
- package/web/dist/assets/riscv-Ckw8ddFX.js +1 -0
- package/web/dist/assets/ron-VUp2lXgN.js +1 -0
- package/web/dist/assets/rose-pine-aMsaMD1D.js +1 -0
- package/web/dist/assets/rose-pine-dawn-8PwUyIlP.js +1 -0
- package/web/dist/assets/rose-pine-moon-DO-qXg-I.js +1 -0
- package/web/dist/assets/rosmsg-CAekHB0j.js +1 -0
- package/web/dist/assets/rst-zjfguEh7.js +1 -0
- package/web/dist/assets/ruby-4oR9waBK.js +1 -0
- package/web/dist/assets/rust-DpiIf5JO.js +1 -0
- package/web/dist/assets/sas-Do4V_2i9.js +1 -0
- package/web/dist/assets/sass-DXrisJhu.js +1 -0
- package/web/dist/assets/scala-BiFt_7mu.js +1 -0
- package/web/dist/assets/scheme-DQCgrYNe.js +1 -0
- package/web/dist/assets/scss-CW6JPtag.js +1 -0
- package/web/dist/assets/sdbl-bTVj8UrX.js +1 -0
- package/web/dist/assets/shaderlab-C4rnI7x0.js +1 -0
- package/web/dist/assets/shellscript-zBmOMFJh.js +1 -0
- package/web/dist/assets/shellsession-DxGUTWaf.js +1 -0
- package/web/dist/assets/slack-dark-DnToyrRv.js +1 -0
- package/web/dist/assets/slack-ochin-B2OO5cIa.js +1 -0
- package/web/dist/assets/smalltalk-BHpQb5nM.js +1 -0
- package/web/dist/assets/smithy-Dgaz7aur.js +1 -0
- package/web/dist/assets/snazzy-light-4G7pJPwS.js +1 -0
- package/web/dist/assets/solarized-dark-b97czJOk.js +1 -0
- package/web/dist/assets/solarized-light-DFPxpIYQ.js +1 -0
- package/web/dist/assets/solidity-Bj7E1mRn.js +1 -0
- package/web/dist/assets/soy-DVtw20Yn.js +1 -0
- package/web/dist/assets/sparql-C0ia5K60.js +1 -0
- package/web/dist/assets/splunk-BC2Px7Mm.js +1 -0
- package/web/dist/assets/sql-DGCM-jag.js +1 -0
- package/web/dist/assets/ssh-config-BgfXC-Er.js +1 -0
- package/web/dist/assets/stata-DfuMOXna.js +1 -0
- package/web/dist/assets/stylus-B6D30XZt.js +1 -0
- package/web/dist/assets/surrealql-CBoRWxfR.js +1 -0
- package/web/dist/assets/svelte-g_uCUem5.js +1 -0
- package/web/dist/assets/swift-CyEgAFGc.js +1 -0
- package/web/dist/assets/synthwave-84-nFMaYfgc.js +1 -0
- package/web/dist/assets/system-verilog-Dh5ihWbf.js +1 -0
- package/web/dist/assets/systemd-BxMlprV5.js +1 -0
- package/web/dist/assets/talonscript-CohzipZa.js +1 -0
- package/web/dist/assets/tasl-DMoTqEGO.js +1 -0
- package/web/dist/assets/tcl-CZd0xW_V.js +1 -0
- package/web/dist/assets/templ-N5C1hJ4Y.js +1 -0
- package/web/dist/assets/terraform-DswuEJGm.js +1 -0
- package/web/dist/assets/tex-mx6xDwuK.js +1 -0
- package/web/dist/assets/themes-CNElGaXw.js +1 -0
- package/web/dist/assets/tokyo-night-oM2G3aXe.js +1 -0
- package/web/dist/assets/toml-CcmNWLt0.js +1 -0
- package/web/dist/assets/ts-tags-C-j71FRb.js +1 -0
- package/web/dist/assets/tsv-sltzmVWM.js +1 -0
- package/web/dist/assets/tsx-z04y7qR9.js +1 -0
- package/web/dist/assets/turtle-ByJddavk.js +1 -0
- package/web/dist/assets/twig-Bz57G_nl.js +1 -0
- package/web/dist/assets/typescript-RMqVTmzR.js +1 -0
- package/web/dist/assets/typespec-BGhR2SqG.js +1 -0
- package/web/dist/assets/typst-BgkqA2Pg.js +1 -0
- package/web/dist/assets/v-DBj4K9d_.js +1 -0
- package/web/dist/assets/vala-zf12oZj6.js +1 -0
- package/web/dist/assets/vb-DOwzaHZ0.js +1 -0
- package/web/dist/assets/verilog-B2gwz-sJ.js +1 -0
- package/web/dist/assets/vesper-DdrHHSXu.js +1 -0
- package/web/dist/assets/vhdl-BroJfC0k.js +1 -0
- package/web/dist/assets/viml-DvXPmvsu.js +1 -0
- package/web/dist/assets/vitesse-black-fwtXNY1n.js +1 -0
- package/web/dist/assets/vitesse-dark-BZCL-v6S.js +1 -0
- package/web/dist/assets/vitesse-light-VbXTXTou.js +1 -0
- package/web/dist/assets/vue-CQgvQytb.js +1 -0
- package/web/dist/assets/vue-html-Dhl6fh9l.js +1 -0
- package/web/dist/assets/vue-vine-Dw_uU59K.js +1 -0
- package/web/dist/assets/vyper-CgoNMtux.js +1 -0
- package/web/dist/assets/wasm-ByWQv1Qj.js +1 -0
- package/web/dist/assets/wenyan-C8pVoKbM.js +1 -0
- package/web/dist/assets/wgsl-BsKzXJz4.js +1 -0
- package/web/dist/assets/wikitext-ClFFjSW2.js +1 -0
- package/web/dist/assets/wit-DdvCle-K.js +1 -0
- package/web/dist/assets/wolfram-DLL8P-h_.js +1 -0
- package/web/dist/assets/xml-B49klYQ3.js +1 -0
- package/web/dist/assets/xsl-D7tnD3qq.js +1 -0
- package/web/dist/assets/yaml-DNQ5_xkL.js +1 -0
- package/web/dist/assets/zenscript-BnlCZFoB.js +1 -0
- package/web/dist/assets/zig-CMLA9XwU.js +1 -0
- package/web/dist/icons/apple-touch-icon.png +0 -0
- package/web/dist/icons/badge-96.png +0 -0
- package/web/dist/icons/icon-192.png +0 -0
- package/web/dist/icons/icon-512.png +0 -0
- package/web/dist/icons/icon.svg +4 -0
- package/web/dist/icons/maskable-192.png +0 -0
- package/web/dist/icons/maskable-512.png +0 -0
- package/web/dist/index.html +30 -0
- package/web/dist/manifest.webmanifest +20 -0
- package/web/dist/sw.js +176 -0
- package/web/dist/version.json +1 -0
- package/web/src/App.tsx +97 -0
- package/web/src/agent-history-flow.test.ts +191 -0
- package/web/src/agent-history-flow.ts +58 -0
- package/web/src/agent-owners.ts +30 -0
- package/web/src/annotation-selection.test.ts +36 -0
- package/web/src/annotation-selection.ts +123 -0
- package/web/src/annotations.test.ts +279 -0
- package/web/src/annotations.ts +191 -0
- package/web/src/api.ts +27 -0
- package/web/src/auth-events.ts +11 -0
- package/web/src/auth-lifecycle.test.ts +105 -0
- package/web/src/clipboard.ts +32 -0
- package/web/src/components/ActivityNotifier.tsx +22 -0
- package/web/src/components/AnnotationEditor.tsx +135 -0
- package/web/src/components/AnnotationSelection.tsx +122 -0
- package/web/src/components/Automation.tsx +390 -0
- package/web/src/components/CodeView.tsx +121 -0
- package/web/src/components/Composer.tsx +552 -0
- package/web/src/components/ComposerAnnotations.tsx +45 -0
- package/web/src/components/Confirmation.tsx +91 -0
- package/web/src/components/CopyButton.tsx +23 -0
- package/web/src/components/DiffView.tsx +94 -0
- package/web/src/components/DirectoryPicker.tsx +230 -0
- package/web/src/components/FileCopyMenu.tsx +58 -0
- package/web/src/components/FileEntryActions.tsx +95 -0
- package/web/src/components/ImagePreview.tsx +68 -0
- package/web/src/components/Items.tsx +608 -0
- package/web/src/components/Markdown.tsx +73 -0
- package/web/src/components/MessageList.tsx +458 -0
- package/web/src/components/Modal.tsx +72 -0
- package/web/src/components/NotificationEntry.tsx +31 -0
- package/web/src/components/NotificationSettings.tsx +226 -0
- package/web/src/components/NotificationToasts.tsx +59 -0
- package/web/src/components/OpenTabs.tsx +112 -0
- package/web/src/components/Pending.tsx +175 -0
- package/web/src/components/ProjectExtras.tsx +299 -0
- package/web/src/components/ProjectNav.tsx +47 -0
- package/web/src/components/SessionActions.tsx +271 -0
- package/web/src/components/SessionChannels.tsx +288 -0
- package/web/src/components/SessionExtras.tsx +263 -0
- package/web/src/components/SettingsLink.tsx +21 -0
- package/web/src/components/Shell.tsx +320 -0
- package/web/src/components/SvgPreview.tsx +18 -0
- package/web/src/components/TodoPanel.tsx +37 -0
- package/web/src/components/VersionNotice.tsx +40 -0
- package/web/src/components/icons.tsx +43 -0
- package/web/src/diff.ts +62 -0
- package/web/src/drafts.test.ts +80 -0
- package/web/src/drafts.ts +64 -0
- package/web/src/format.ts +5 -0
- package/web/src/highlight.worker.ts +49 -0
- package/web/src/hooks.ts +59 -0
- package/web/src/item-view-state.test.ts +51 -0
- package/web/src/item-view-state.ts +43 -0
- package/web/src/live-history-flow.test.ts +77 -0
- package/web/src/live-merge.test.ts +64 -0
- package/web/src/live-merge.ts +50 -0
- package/web/src/live-message-hydration.test.ts +253 -0
- package/web/src/live.ts +404 -0
- package/web/src/main.tsx +27 -0
- package/web/src/markdown-assets.test.ts +70 -0
- package/web/src/markdown-assets.ts +78 -0
- package/web/src/markdown-html.test.ts +84 -0
- package/web/src/markdown-html.ts +60 -0
- package/web/src/markdown-svg.test.ts +37 -0
- package/web/src/markdown-svg.ts +42 -0
- package/web/src/markdown-tables.test.ts +34 -0
- package/web/src/markdown-tables.ts +17 -0
- package/web/src/message-bodies.ts +117 -0
- package/web/src/message-cache.test.ts +208 -0
- package/web/src/message-cache.ts +158 -0
- package/web/src/message-font-size.test.ts +41 -0
- package/web/src/message-font-size.ts +57 -0
- package/web/src/message-time.test.ts +32 -0
- package/web/src/message-time.ts +26 -0
- package/web/src/notify.ts +132 -0
- package/web/src/open-tabs.test.ts +69 -0
- package/web/src/open-tabs.ts +110 -0
- package/web/src/pages/AgentView.tsx +138 -0
- package/web/src/pages/ChangesView.tsx +193 -0
- package/web/src/pages/FilesView.tsx +406 -0
- package/web/src/pages/HomePage.tsx +96 -0
- package/web/src/pages/LoginPage.tsx +56 -0
- package/web/src/pages/OverviewPage.tsx +124 -0
- package/web/src/pages/ProjectHome.tsx +136 -0
- package/web/src/pages/SessionView.tsx +238 -0
- package/web/src/pages/WorktreesView.tsx +197 -0
- package/web/src/pages/settings/AccountPage.tsx +149 -0
- package/web/src/pages/settings/AgentsPage.tsx +182 -0
- package/web/src/pages/settings/ConfigPage.tsx +181 -0
- package/web/src/pages/settings/GeneralPage.tsx +27 -0
- package/web/src/pages/settings/HooksPage.tsx +244 -0
- package/web/src/pages/settings/McpPage.tsx +310 -0
- package/web/src/pages/settings/MemoryPage.tsx +309 -0
- package/web/src/pages/settings/PermissionsPage.tsx +160 -0
- package/web/src/pages/settings/PluginsPage.tsx +251 -0
- package/web/src/pages/settings/SettingsPage.tsx +141 -0
- package/web/src/pages/settings/SkillsPage.tsx +142 -0
- package/web/src/pages/settings/ui.tsx +165 -0
- package/web/src/preference-document.ts +133 -0
- package/web/src/project-actions.ts +18 -0
- package/web/src/project-files-state.test.ts +49 -0
- package/web/src/project-files-state.ts +64 -0
- package/web/src/project-images.ts +13 -0
- package/web/src/pwa.ts +33 -0
- package/web/src/queryClient.ts +5 -0
- package/web/src/reasoning-preference.test.ts +39 -0
- package/web/src/reasoning-preference.ts +42 -0
- package/web/src/session-confirmations.ts +36 -0
- package/web/src/stream-replay.test.ts +163 -0
- package/web/src/styles.css +231 -0
- package/web/src/tool-result-body.test.ts +115 -0
- package/web/src/transcript.test.ts +186 -0
- package/web/src/transcript.ts +583 -0
- package/web/src/turns.test.ts +338 -0
- package/web/src/turns.ts +178 -0
- package/web/src/ui/Button.tsx +60 -0
- package/web/src/ui/Controls.tsx +227 -0
- package/web/src/ui/Form.tsx +203 -0
- package/web/src/ui/MenuPopover.tsx +144 -0
- package/web/src/ui/Popover.tsx +147 -0
- package/web/src/ui/ResizablePane.tsx +110 -0
- package/web/src/ui/Select.tsx +150 -0
- package/web/src/ui/icons.tsx +270 -0
- package/web/src/unsaved.tsx +99 -0
- package/web/src/user-text.test.ts +60 -0
- package/web/src/user-text.ts +35 -0
- package/web/src/uuid.ts +12 -0
- package/README.md +0 -3
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { create } from "zustand";
|
|
2
|
+
import type { ImageInput } from "@shared/protocol.ts";
|
|
3
|
+
|
|
4
|
+
export type Attached = ImageInput & { id: string; url: string };
|
|
5
|
+
export type Draft = { text: string; images: Attached[]; revision: number; generation: number; processing: number; rewindId?: string };
|
|
6
|
+
export const useDrafts = create<{ drafts: Record<string, Draft> }>(() => ({ drafts: {} }));
|
|
7
|
+
const key = (sid: string) => `cide.draft.${sid}`;
|
|
8
|
+
const rewindKey = (sid: string) => `cide.draft-rewind.${sid}`;
|
|
9
|
+
function read(key: string): string | undefined {
|
|
10
|
+
try { return localStorage.getItem(key) ?? undefined; } catch { return undefined; }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function getDraft(sid: string): Draft {
|
|
14
|
+
const current = useDrafts.getState().drafts[sid];
|
|
15
|
+
if (current) return current;
|
|
16
|
+
const draft: Draft = { text: read(key(sid)) ?? "", images: [], revision: 0, generation: 0, processing: 0, rewindId: read(rewindKey(sid)) };
|
|
17
|
+
useDrafts.setState((s) => ({ drafts: { ...s.drafts, [sid]: draft } }));
|
|
18
|
+
return draft;
|
|
19
|
+
}
|
|
20
|
+
function write(sid: string, draft: Draft) {
|
|
21
|
+
useDrafts.setState((s) => ({ drafts: { ...s.drafts, [sid]: draft } }));
|
|
22
|
+
try {
|
|
23
|
+
if (draft.text) localStorage.setItem(key(sid), draft.text);
|
|
24
|
+
else localStorage.removeItem(key(sid));
|
|
25
|
+
if (draft.rewindId) localStorage.setItem(rewindKey(sid), draft.rewindId);
|
|
26
|
+
else localStorage.removeItem(rewindKey(sid));
|
|
27
|
+
} catch { /* In-memory drafts still work when storage is unavailable. */ }
|
|
28
|
+
}
|
|
29
|
+
export function editDraft(sid: string, patch: Partial<Pick<Draft, "text" | "images">>) {
|
|
30
|
+
const draft = getDraft(sid);
|
|
31
|
+
write(sid, { ...draft, ...patch, revision: draft.revision + 1 });
|
|
32
|
+
}
|
|
33
|
+
export function clearDraft(sid: string, revision?: number) {
|
|
34
|
+
const draft = getDraft(sid);
|
|
35
|
+
if (revision !== undefined && draft.revision !== revision) return;
|
|
36
|
+
write(sid, { ...draft, text: "", images: [], processing: 0, revision: draft.revision + 1, generation: draft.generation + 1 });
|
|
37
|
+
}
|
|
38
|
+
/** An explicit rewind replaces the draft; a server replay only initializes an untouched draft once. */
|
|
39
|
+
export function setDraft(sid: string, text: string, rewindId?: string) {
|
|
40
|
+
const draft = getDraft(sid);
|
|
41
|
+
write(sid, { ...draft, text, images: [], processing: 0, rewindId, revision: draft.revision + 1, generation: draft.generation + 1 });
|
|
42
|
+
}
|
|
43
|
+
export function restoreRewindDraft(sid: string, text: string, rewindId: string, generation?: number) {
|
|
44
|
+
const draft = getDraft(sid);
|
|
45
|
+
if (generation !== undefined && draft.generation !== generation) return;
|
|
46
|
+
if (draft.rewindId === rewindId) return;
|
|
47
|
+
// Remember this rewind even when the user already has a draft (including an intentionally emptied one).
|
|
48
|
+
if (draft.text || draft.images.length || draft.processing || draft.revision) write(sid, { ...draft, rewindId });
|
|
49
|
+
else setDraft(sid, text, rewindId);
|
|
50
|
+
}
|
|
51
|
+
/** Reading belongs to the source session even after its Composer has unmounted. */
|
|
52
|
+
export async function addDraftImages(sid: string, count: number, prepare: () => Promise<Attached[]>) {
|
|
53
|
+
const draft = getDraft(sid);
|
|
54
|
+
const generation = draft.generation;
|
|
55
|
+
write(sid, { ...draft, processing: draft.processing + count });
|
|
56
|
+
try {
|
|
57
|
+
const images = await prepare();
|
|
58
|
+
const current = getDraft(sid);
|
|
59
|
+
if (current.generation === generation) editDraft(sid, { images: [...current.images, ...images] });
|
|
60
|
+
} finally {
|
|
61
|
+
const current = getDraft(sid);
|
|
62
|
+
if (current.generation === generation) write(sid, { ...current, processing: Math.max(0, current.processing - count) });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/// <reference lib="webworker" />
|
|
2
|
+
/**
|
|
3
|
+
* Syntax highlighting off the main thread: highlighting a few thousand lines takes
|
|
4
|
+
* seconds on a phone, and the page must keep scrolling and typing meanwhile.
|
|
5
|
+
* Shiki is loaded lazily (core + JS regex engine, no wasm); each grammar is its own chunk.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
type Highlighter = {
|
|
9
|
+
codeToHtml: (code: string, opts: { lang: string; themes: Record<string, string>; defaultColor: false }) => string;
|
|
10
|
+
loadLanguage: (...langs: unknown[]) => Promise<void>;
|
|
11
|
+
getLoadedLanguages: () => string[];
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
let highlighter: Promise<{ hl: Highlighter; langs: Record<string, () => Promise<unknown>> }> | undefined;
|
|
15
|
+
|
|
16
|
+
function getHighlighter() {
|
|
17
|
+
highlighter ??= (async () => {
|
|
18
|
+
const [{ createHighlighterCore }, { createJavaScriptRegexEngine }, { bundledLanguages }, { bundledThemes }] = await Promise.all([
|
|
19
|
+
import("shiki/core"),
|
|
20
|
+
import("shiki/engine/javascript"),
|
|
21
|
+
import("shiki/langs"),
|
|
22
|
+
import("shiki/themes"),
|
|
23
|
+
]);
|
|
24
|
+
const hl = await createHighlighterCore({
|
|
25
|
+
themes: [bundledThemes["github-light"](), bundledThemes["github-dark"]()],
|
|
26
|
+
langs: [],
|
|
27
|
+
engine: createJavaScriptRegexEngine({ forgiving: true }),
|
|
28
|
+
});
|
|
29
|
+
return { hl: hl as unknown as Highlighter, langs: bundledLanguages as unknown as Record<string, () => Promise<unknown>> };
|
|
30
|
+
})();
|
|
31
|
+
return highlighter;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function highlight(code: string, lang: string): Promise<string | null> {
|
|
35
|
+
const { hl, langs } = await getHighlighter();
|
|
36
|
+
const loader = langs[lang];
|
|
37
|
+
if (!loader) return null;
|
|
38
|
+
if (!hl.getLoadedLanguages().includes(lang)) await hl.loadLanguage(loader());
|
|
39
|
+
const html = hl.codeToHtml(code, { lang, themes: { light: "github-light", dark: "github-dark" }, defaultColor: false });
|
|
40
|
+
// Keep only the <code> body; lines become blocks, so drop the newlines between them.
|
|
41
|
+
const m = /<code>([\s\S]*)<\/code>/.exec(html);
|
|
42
|
+
return m ? m[1]!.replace(/<\/span>\n<span class="line">/g, '</span><span class="line">') : null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
self.onmessage = async (e: MessageEvent<{ id: number; code: string; lang: string }>) => {
|
|
46
|
+
const { id, code, lang } = e.data;
|
|
47
|
+
const html = await highlight(code, lang).catch(() => null);
|
|
48
|
+
self.postMessage({ id, html });
|
|
49
|
+
};
|
package/web/src/hooks.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { useQuery } from "@tanstack/react-query";
|
|
2
|
+
import type { CommandInfo, ModelOption, ProjectInfo, SessionListItem } from "@shared/protocol.ts";
|
|
3
|
+
import { api } from "./api.ts";
|
|
4
|
+
|
|
5
|
+
export function useProjects() {
|
|
6
|
+
return useQuery({ queryKey: ["projects"], queryFn: () => api<ProjectInfo[]>("/api/projects") });
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function useSessions(projectId: string | undefined) {
|
|
10
|
+
return useQuery({
|
|
11
|
+
queryKey: ["sessions", projectId],
|
|
12
|
+
queryFn: () => api<SessionListItem[]>(`/api/projects/${projectId}/sessions`),
|
|
13
|
+
enabled: !!projectId,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function useModels(projectId: string | undefined) {
|
|
18
|
+
return useQuery({
|
|
19
|
+
queryKey: ["models", projectId],
|
|
20
|
+
queryFn: () => api<ModelOption[]>(`/api/projects/${projectId}/models`),
|
|
21
|
+
enabled: !!projectId,
|
|
22
|
+
staleTime: 10 * 60_000,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function useCommands(projectId: string | undefined) {
|
|
27
|
+
return useQuery({
|
|
28
|
+
queryKey: ["commands", projectId],
|
|
29
|
+
queryFn: () => api<CommandInfo[]>(`/api/projects/${projectId}/commands`),
|
|
30
|
+
enabled: !!projectId,
|
|
31
|
+
staleTime: 10 * 60_000,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type OverviewItem = {
|
|
36
|
+
projectId: string;
|
|
37
|
+
projectName: string;
|
|
38
|
+
sessionId: string;
|
|
39
|
+
title: string;
|
|
40
|
+
state: "idle" | "running" | "requires_action";
|
|
41
|
+
waitingFor: ("ask" | "plan")[];
|
|
42
|
+
crons: number;
|
|
43
|
+
/** Optional for older servers; used to dedupe project-wide durable tasks. */
|
|
44
|
+
cronIds?: string[];
|
|
45
|
+
tasks: number;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export function useOverview() {
|
|
49
|
+
return useQuery({ queryKey: ["overview"], queryFn: () => api<OverviewItem[]>("/api/overview"), refetchInterval: 30_000 });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function relativeTime(ms: number): string {
|
|
53
|
+
const s = Math.round((Date.now() - ms) / 1000);
|
|
54
|
+
if (s < 60) return "刚刚";
|
|
55
|
+
if (s < 3600) return `${Math.floor(s / 60)} 分钟前`;
|
|
56
|
+
if (s < 86400) return `${Math.floor(s / 3600)} 小时前`;
|
|
57
|
+
if (s < 86400 * 7) return `${Math.floor(s / 86400)} 天前`;
|
|
58
|
+
return new Date(ms).toLocaleDateString();
|
|
59
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { ItemViewState } from "./item-view-state.ts";
|
|
4
|
+
import { Transcript } from "./transcript.ts";
|
|
5
|
+
|
|
6
|
+
test("manual stream collapse survives completion, remount and later preference changes", () => {
|
|
7
|
+
const state = new ItemViewState();
|
|
8
|
+
const transcript = new Transcript();
|
|
9
|
+
const events = [{ k: "delta" as const, msgId: "api", index: 0, kind: "thinking" as const, text: "thought", parent: null }];
|
|
10
|
+
const streaming = transcript.update(undefined, events)[0]!;
|
|
11
|
+
assert.equal(state.read(streaming.key, true, true).value, true);
|
|
12
|
+
const collapsed = state.write(streaming.key, false);
|
|
13
|
+
const completed = transcript.update(undefined, [...events, { k: "sdk", m: { type: "assistant", uuid: "frame", message: { id: "api", content: [{ type: "thinking", thinking: "thought" }] } } }])[0]!;
|
|
14
|
+
assert.ok(completed.kind === "thinking" && completed.streamStateKey);
|
|
15
|
+
assert.equal(state.read(completed.key, true, true, [completed.streamStateKey]), collapsed);
|
|
16
|
+
assert.equal(state.read(completed.key, true, true).value, false, "remount can use the final key alone");
|
|
17
|
+
const reopened = state.write(completed.key, true);
|
|
18
|
+
assert.equal(state.read(streaming.key, false, true), reopened, "replaying the stream alias sees the latest explicit action");
|
|
19
|
+
assert.equal(state.read("another-block", false, true).value, false);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("late alias discovery overrides a mounted default but never a newer explicit choice", () => {
|
|
23
|
+
const state = new ItemViewState();
|
|
24
|
+
const mounted = state.read("final", true, true);
|
|
25
|
+
const streamChoice = state.write("stream", false);
|
|
26
|
+
assert.equal(state.read("final", true, true, ["stream"], mounted), streamChoice);
|
|
27
|
+
const finalChoice = state.write("final", true);
|
|
28
|
+
assert.equal(state.read("final", false, true, ["stream"], streamChoice), finalChoice);
|
|
29
|
+
assert.equal(state.read("stream", false, true), finalChoice);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("default-only aliases preserve the original default and independent cards stay isolated", () => {
|
|
33
|
+
const state = new ItemViewState();
|
|
34
|
+
const original = state.read("stream", false, true);
|
|
35
|
+
state.read("final", true, true);
|
|
36
|
+
assert.equal(state.read("final", true, true, ["stream"]), original);
|
|
37
|
+
state.write("other:open", true);
|
|
38
|
+
assert.equal(state.read("other:all", false).value, false, "show-all and fold state are separate");
|
|
39
|
+
assert.equal(state.read(undefined, false).value, false);
|
|
40
|
+
const local = state.write(undefined, true);
|
|
41
|
+
assert.equal(state.read(undefined, false, false, [], local), local);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("cache eviction is bounded while a mounted explicit choice can still be restored", () => {
|
|
45
|
+
const state = new ItemViewState(3);
|
|
46
|
+
const mounted = state.write("old", false);
|
|
47
|
+
for (const id of ["a", "b", "c"]) state.read(id, true, true);
|
|
48
|
+
assert.equal(state.read("old", true).value, true, "old unmounted state was evicted");
|
|
49
|
+
assert.equal(state.read("old", true, true, [], mounted), mounted);
|
|
50
|
+
assert.equal(state.read("a", false).value, false, "restoring mounted state still honors the capacity");
|
|
51
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export type ViewChoice = { value: boolean; revision: number };
|
|
2
|
+
|
|
3
|
+
/** Scalar-only, bounded UI state for virtualized items. A completed reasoning
|
|
4
|
+
* block aliases its former stream key; explicit actions outrank either default. */
|
|
5
|
+
export class ItemViewState {
|
|
6
|
+
private choices = new Map<string, ViewChoice>();
|
|
7
|
+
private revision = 0;
|
|
8
|
+
|
|
9
|
+
constructor(private readonly limit = 7500) {}
|
|
10
|
+
|
|
11
|
+
read(key: string | undefined, initial: boolean, rememberInitial = false, aliases: readonly string[] = [], mounted?: ViewChoice): ViewChoice {
|
|
12
|
+
if (key === undefined) return mounted ?? { value: initial, revision: 0 };
|
|
13
|
+
const keys = [...aliases, key];
|
|
14
|
+
const known = keys.flatMap((id) => this.choices.has(id) ? [this.choices.get(id)!] : []);
|
|
15
|
+
let choice = known[0] ?? mounted ?? { value: initial, revision: 0 };
|
|
16
|
+
for (const candidate of [...known, ...(mounted ? [mounted] : [])]) {
|
|
17
|
+
if (candidate.revision > choice.revision) choice = candidate;
|
|
18
|
+
}
|
|
19
|
+
if (known.length || rememberInitial || choice.revision > 0) this.link(keys, choice);
|
|
20
|
+
return choice;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
write(key: string | undefined, value: boolean, aliases: readonly string[] = []): ViewChoice {
|
|
24
|
+
const choice = { value, revision: ++this.revision };
|
|
25
|
+
if (key !== undefined) this.link([...aliases, key], choice);
|
|
26
|
+
return choice;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private link(keys: readonly string[], choice: ViewChoice) {
|
|
30
|
+
const previous = new Set(keys.flatMap((key) => {
|
|
31
|
+
const value = this.choices.get(key);
|
|
32
|
+
return value && value !== choice ? [value] : [];
|
|
33
|
+
}));
|
|
34
|
+
if (previous.size) for (const [key, value] of this.choices) {
|
|
35
|
+
if (previous.has(value)) this.choices.set(key, choice);
|
|
36
|
+
}
|
|
37
|
+
for (const key of keys) {
|
|
38
|
+
if (this.choices.get(key) === choice) continue;
|
|
39
|
+
this.choices.delete(key); this.choices.set(key, choice);
|
|
40
|
+
}
|
|
41
|
+
while (this.choices.size > this.limit) this.choices.delete(this.choices.keys().next().value!);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mergeSnapshot } from "./live-merge.ts";
|
|
4
|
+
|
|
5
|
+
// Store/history flows need only the document's build metadata, not a browser.
|
|
6
|
+
(globalThis as any).document = { querySelector: () => null };
|
|
7
|
+
const { useLive, subscribeSession, loadSessionHistory } = await import("./live.ts");
|
|
8
|
+
const snapshot = { state: "idle" as const, pending: [], planMode: false, crons: [], tasks: [] };
|
|
9
|
+
const page = (revision = "branch") => ({ items: [{ type: "user", uuid: "persisted", message: { content: "history" } }], generation: "g", historyRevision: revision, older: "older" });
|
|
10
|
+
const attach = (revision: string, reset = true) => {
|
|
11
|
+
const current = useLive.getState().sessions.s;
|
|
12
|
+
useLive.setState({ sessions: { s: mergeSnapshot(current, { epoch: "e", seq: 2, reset, historyRevision: revision, live: snapshot, events: [] }) } });
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
test("HTTP-first history works without WS, and first/checkpoint WS preserve historical browsing", async () => {
|
|
16
|
+
const original = globalThis.fetch;
|
|
17
|
+
let requests = 0;
|
|
18
|
+
globalThis.fetch = async () => { requests++; return Response.json(page()); };
|
|
19
|
+
useLive.setState({ sessions: {}, connected: false });
|
|
20
|
+
const unsubscribe = subscribeSession("s", "p");
|
|
21
|
+
try {
|
|
22
|
+
await loadSessionHistory("s", "p");
|
|
23
|
+
await loadSessionHistory("s", "p", "older");
|
|
24
|
+
const before = useLive.getState().sessions.s!;
|
|
25
|
+
assert.equal(before.history?.mode, "history");
|
|
26
|
+
assert.equal(requests, 2);
|
|
27
|
+
assert.equal(useLive.getState().connected, false);
|
|
28
|
+
attach("branch");
|
|
29
|
+
assert.deepEqual(useLive.getState().sessions.s?.history, before.history);
|
|
30
|
+
attach("branch");
|
|
31
|
+
assert.deepEqual(useLive.getState().sessions.s?.history, before.history);
|
|
32
|
+
assert.equal(useLive.getState().sessions.s?.resetCount, before.resetCount);
|
|
33
|
+
attach("rewound");
|
|
34
|
+
assert.equal(useLive.getState().sessions.s?.history, undefined);
|
|
35
|
+
assert.equal(useLive.getState().sessions.s?.resetCount, before.resetCount + 1);
|
|
36
|
+
} finally { unsubscribe(); globalThis.fetch = original; }
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("in-flight HTTP accepts matching first WS and rejects a stale branch without stranded loading", async () => {
|
|
40
|
+
const original = globalThis.fetch;
|
|
41
|
+
let resolve!: (response: Response) => void;
|
|
42
|
+
globalThis.fetch = () => new Promise((r) => { resolve = r; });
|
|
43
|
+
const unsubscribe = subscribeSession("s", "p");
|
|
44
|
+
try {
|
|
45
|
+
useLive.setState({ sessions: { s: { seq: 0, snapshot, events: [], resetCount: 0 } } });
|
|
46
|
+
const matching = loadSessionHistory("s", "p");
|
|
47
|
+
attach("branch");
|
|
48
|
+
resolve(Response.json(page()));
|
|
49
|
+
await matching;
|
|
50
|
+
assert.equal(useLive.getState().sessions.s?.history?.items[0]?.uuid, "persisted");
|
|
51
|
+
useLive.setState({ sessions: { s: { seq: 0, snapshot, events: [], resetCount: 0 } } });
|
|
52
|
+
const stale = loadSessionHistory("s", "p");
|
|
53
|
+
attach("rewound");
|
|
54
|
+
resolve(Response.json(page()));
|
|
55
|
+
await stale;
|
|
56
|
+
assert.equal(useLive.getState().sessions.s?.history, undefined);
|
|
57
|
+
assert.equal(useLive.getState().sessions.s?.resetCount, 1);
|
|
58
|
+
assert.equal(useLive.getState().sessions.s?.historyRevision, "rewound");
|
|
59
|
+
} finally { unsubscribe(); globalThis.fetch = original; }
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
test("HTTP adopts a recreated server view while WS is unavailable instead of retrying forever", async () => {
|
|
64
|
+
const original = globalThis.fetch;
|
|
65
|
+
globalThis.fetch = async () => Response.json(page("recreated"));
|
|
66
|
+
const unsubscribe = subscribeSession("s", "p");
|
|
67
|
+
try {
|
|
68
|
+
useLive.setState({ sessions: { s: { seq: 8, snapshot, events: [{ k: "user", uuid: "abandoned", text: "old", ts: 1 }], resetCount: 0, historyRevision: "previous" } } });
|
|
69
|
+
await loadSessionHistory("s", "p");
|
|
70
|
+
assert.equal(useLive.getState().sessions.s?.historyRevision, "recreated");
|
|
71
|
+
assert.equal(useLive.getState().sessions.s?.events.length, 0);
|
|
72
|
+
assert.equal(useLive.getState().sessions.s?.resetCount, 1);
|
|
73
|
+
await loadSessionHistory("s", "p");
|
|
74
|
+
assert.equal(useLive.getState().sessions.s?.resetCount, 1);
|
|
75
|
+
assert.equal(useLive.getState().sessions.s?.history?.loading, undefined);
|
|
76
|
+
} finally { unsubscribe(); globalThis.fetch = original; }
|
|
77
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import type { LiveEvent, LiveSnapshot } from "@shared/protocol.ts";
|
|
4
|
+
import { mergeSnapshot, updateTodosFromEvents } from "./live-merge.ts";
|
|
5
|
+
|
|
6
|
+
const live: LiveSnapshot = { state: "idle", pending: [], planMode: false, crons: [], tasks: [] };
|
|
7
|
+
const ev = (n: number): LiveEvent => ({ k: "user", uuid: `u${n}`, text: `m${n}`, ts: n });
|
|
8
|
+
|
|
9
|
+
test("the same incremental snapshot delivered twice appends its events once", () => {
|
|
10
|
+
const base = mergeSnapshot(undefined, { epoch: "e", seq: 2, reset: true, live, events: [{ seq: 1, ev: ev(1) }, { seq: 2, ev: ev(2) }] });
|
|
11
|
+
const inc = { epoch: "e", seq: 4, reset: false, live, events: [{ seq: 3, ev: ev(3) }, { seq: 4, ev: ev(4) }] };
|
|
12
|
+
const once = mergeSnapshot(base, inc);
|
|
13
|
+
const twice = mergeSnapshot(once, inc);
|
|
14
|
+
assert.deepEqual(twice.events.map((e) => (e as { uuid: string }).uuid), ["u1", "u2", "u3", "u4"]);
|
|
15
|
+
assert.equal(twice.seq, 4);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("a reset snapshot replaces events and bumps resetCount", () => {
|
|
19
|
+
const base = mergeSnapshot(undefined, { epoch: "e", seq: 2, reset: true, live, events: [{ seq: 2, ev: ev(2) }] });
|
|
20
|
+
const next = mergeSnapshot(base, { epoch: "f", seq: 1, reset: true, live, events: [{ seq: 1, ev: ev(9) }] });
|
|
21
|
+
assert.equal(next.events.length, 1);
|
|
22
|
+
assert.equal(next.resetCount, base.resetCount + 1);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("reconnect replay updates cached todos exactly like newly received messages", () => {
|
|
26
|
+
const base = mergeSnapshot(undefined, { epoch: "e", seq: 1, reset: true, live, events: [{ seq: 1, ev: ev(1) }] });
|
|
27
|
+
base.todos = [{ id: "1", subject: "first", status: "pending" }, { id: "2", subject: "second", status: "pending" }];
|
|
28
|
+
const fresh: LiveEvent[] = [{ k: "sdk", m: { type: "assistant", uuid: "updates", message: { content: [
|
|
29
|
+
{ type: "tool_use", id: "update", name: "TaskUpdate", input: { taskId: "1", status: "completed" } },
|
|
30
|
+
{ type: "tool_use", id: "delete", name: "TaskUpdate", input: { taskId: "2", status: "deleted" } },
|
|
31
|
+
] } } }];
|
|
32
|
+
const msg = { epoch: "e", seq: 2, reset: false, live, events: [{ seq: 2, ev: fresh[0]! }] };
|
|
33
|
+
const next = mergeSnapshot(base, msg);
|
|
34
|
+
assert.deepEqual(next.todos, [{ id: "1", subject: "first", status: "completed" }]);
|
|
35
|
+
assert.deepEqual(next.todos, updateTodosFromEvents([...base.events, ...fresh], fresh, base.todos));
|
|
36
|
+
assert.deepEqual(mergeSnapshot(next, msg).todos, next.todos);
|
|
37
|
+
assert.equal(next.resetCount, base.resetCount);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("reset replay does not inherit todos from the previous conversation branch", () => {
|
|
41
|
+
const base = mergeSnapshot(undefined, { epoch: "e", seq: 1, reset: true, live, events: [] });
|
|
42
|
+
base.todos = [{ id: "old", subject: "old branch", status: "pending" }];
|
|
43
|
+
const next = mergeSnapshot(base, { epoch: "e", seq: 2, reset: true, live, events: [{ seq: 2, ev: ev(2) }] });
|
|
44
|
+
assert.equal(next.todos, undefined);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
test("checkpoint replay replaces cumulative draft but retains history and todos on the same view", () => {
|
|
49
|
+
const base = mergeSnapshot(undefined, { epoch: "e", seq: 1, reset: true, historyRevision: "view", live,
|
|
50
|
+
events: [{ seq: 1, ev: { k: "delta", msgId: "m", index: 0, kind: "text", text: "old", parent: null } }] });
|
|
51
|
+
base.history = { items: [], generation: "g", mode: "history", source: "main", request: 1 };
|
|
52
|
+
base.todos = [{ id: "t", subject: "keep", status: "pending" }];
|
|
53
|
+
const next = mergeSnapshot(base, { epoch: "e", seq: 2, reset: true, historyRevision: "view", live,
|
|
54
|
+
events: [{ seq: 2, ev: { k: "delta", msgId: "m", index: 0, kind: "text", text: "complete", parent: null, checkpoint: true } }] });
|
|
55
|
+
assert.equal(next.history, base.history);
|
|
56
|
+
assert.deepEqual(next.todos, base.todos);
|
|
57
|
+
assert.equal(next.resetCount, base.resetCount);
|
|
58
|
+
assert.equal(next.events.length, 1);
|
|
59
|
+
assert.equal((next.events[0] as any).text, "complete");
|
|
60
|
+
const rewound = mergeSnapshot(next, { epoch: "e", seq: 3, reset: false, historyRevision: "new-view", live, events: [] });
|
|
61
|
+
assert.equal(rewound.history, undefined);
|
|
62
|
+
assert.equal(rewound.todos, undefined);
|
|
63
|
+
assert.equal(rewound.resetCount, next.resetCount + 1);
|
|
64
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { boundSession, compactEvents, type HistoryWindow } from "./message-cache.ts";
|
|
2
|
+
import type { LiveEvent, LiveSnapshot, SeqEvent } from "@shared/protocol.ts";
|
|
3
|
+
import { buildItems, deriveTodos, type Todo } from "./transcript.ts";
|
|
4
|
+
import { mergeAgentOwners } from "./agent-owners.ts";
|
|
5
|
+
|
|
6
|
+
export type SessionLive = {
|
|
7
|
+
epoch?: string;
|
|
8
|
+
historyRevision?: string;
|
|
9
|
+
seq: number;
|
|
10
|
+
snapshot: LiveSnapshot;
|
|
11
|
+
events: LiveEvent[];
|
|
12
|
+
agentOwners?: Record<string, string>;
|
|
13
|
+
/** Bumped only when the transcript branch/view is invalidated. */
|
|
14
|
+
resetCount: number;
|
|
15
|
+
history?: HistoryWindow;
|
|
16
|
+
evicted?: boolean;
|
|
17
|
+
accessed?: number;
|
|
18
|
+
todos?: import("@shared/todos.ts").Todo[];
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/** Replayed and newly received SDK messages must update the same cached todo state. */
|
|
22
|
+
export function updateTodosFromEvents(events: LiveEvent[], added: LiveEvent[], initial?: Todo[]): Todo[] | undefined {
|
|
23
|
+
return added.some((e) => e.k === "sdk" && (e.m.type === "user" || e.m.type === "assistant"))
|
|
24
|
+
? deriveTodos(buildItems(undefined, events), initial)
|
|
25
|
+
: initial;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Apply a subscription snapshot. `reset` replaces live replay, while the view revision
|
|
30
|
+
* determines whether persisted history is still valid. Incremental replay is idempotent.
|
|
31
|
+
*/
|
|
32
|
+
export function mergeSnapshot(
|
|
33
|
+
cur: SessionLive | undefined,
|
|
34
|
+
msg: { epoch: string; seq: number; reset: boolean; historyRevision?: string; live: LiveSnapshot; events: SeqEvent[] },
|
|
35
|
+
): SessionLive {
|
|
36
|
+
// Legacy snapshots cannot prove branch continuity. New servers always attach a view
|
|
37
|
+
// revision, including the first WS response after an HTTP-only bootstrap.
|
|
38
|
+
const invalidated = !!cur && (msg.historyRevision !== undefined
|
|
39
|
+
? cur.historyRevision !== undefined && cur.historyRevision !== msg.historyRevision
|
|
40
|
+
: msg.reset);
|
|
41
|
+
const replace = msg.reset || !cur || invalidated;
|
|
42
|
+
const fresh = replace ? msg.events.map((e) => e.ev) : msg.events.filter((e) => e.seq > cur!.seq).map((e) => e.ev);
|
|
43
|
+
const events = replace ? compactEvents(fresh) : fresh.length ? compactEvents([...cur!.events, ...fresh]) : cur!.events;
|
|
44
|
+
return boundSession({ ...cur, epoch: msg.epoch, historyRevision: msg.historyRevision ?? cur?.historyRevision,
|
|
45
|
+
agentOwners: mergeAgentOwners(invalidated ? undefined : cur?.agentOwners, fresh, msg.live.agentTasks),
|
|
46
|
+
seq: replace ? msg.seq : Math.max(msg.seq, cur!.seq), snapshot: msg.live, events,
|
|
47
|
+
history: invalidated ? undefined : cur?.history,
|
|
48
|
+
todos: updateTodosFromEvents(events, fresh, invalidated ? undefined : cur?.todos),
|
|
49
|
+
resetCount: (cur?.resetCount ?? 0) + (invalidated ? 1 : 0), accessed: Date.now() });
|
|
50
|
+
}
|