@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,189 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { db } from "./db.ts";
|
|
5
|
+
import { claudeDir, encodedDir, sessionFileDir } from "./cli-files.ts";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Per-session view state for M4 (kept out of db.ts's migration list so parallel work can add
|
|
9
|
+
* migrations without renumbering): the working directory of sessions that live in a git
|
|
10
|
+
* worktree, and the extra directories (/add-dir) a session may access.
|
|
11
|
+
*/
|
|
12
|
+
db.exec(`
|
|
13
|
+
CREATE TABLE IF NOT EXISTS session_cwd (
|
|
14
|
+
session_id TEXT PRIMARY KEY,
|
|
15
|
+
cwd TEXT NOT NULL
|
|
16
|
+
);
|
|
17
|
+
CREATE TABLE IF NOT EXISTS session_dirs (
|
|
18
|
+
session_id TEXT NOT NULL,
|
|
19
|
+
path TEXT NOT NULL,
|
|
20
|
+
added_at INTEGER NOT NULL,
|
|
21
|
+
PRIMARY KEY (session_id, path)
|
|
22
|
+
);
|
|
23
|
+
`);
|
|
24
|
+
|
|
25
|
+
// ---------- working directory ----------
|
|
26
|
+
|
|
27
|
+
export function setSessionCwd(sessionId: string, cwd: string) {
|
|
28
|
+
db.prepare("INSERT INTO session_cwd (session_id, cwd) VALUES (?, ?) ON CONFLICT(session_id) DO UPDATE SET cwd = excluded.cwd").run(sessionId, cwd);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The `cwd` the CLI recorded in a session's transcript (first records carry it). */
|
|
32
|
+
async function transcriptCwd(projectPath: string, sessionId: string): Promise<string | undefined> {
|
|
33
|
+
const dir = await sessionFileDir(projectPath, sessionId);
|
|
34
|
+
return dir ? fileCwd(path.join(dir, `${sessionId}.jsonl`)) : undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function fileCwd(file: string): Promise<string | undefined> {
|
|
38
|
+
let fh: fs.FileHandle | undefined;
|
|
39
|
+
try {
|
|
40
|
+
fh = await fs.open(file, "r");
|
|
41
|
+
const buf = Buffer.alloc(256 * 1024);
|
|
42
|
+
const { bytesRead } = await fh.read(buf, 0, buf.length, 0);
|
|
43
|
+
for (const line of buf.subarray(0, bytesRead).toString("utf8").split("\n")) {
|
|
44
|
+
try {
|
|
45
|
+
const cwd = (JSON.parse(line) as { cwd?: unknown }).cwd;
|
|
46
|
+
if (typeof cwd === "string") return cwd;
|
|
47
|
+
} catch {
|
|
48
|
+
// partial line
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
} catch {
|
|
52
|
+
// unreadable
|
|
53
|
+
} finally {
|
|
54
|
+
await fh?.close();
|
|
55
|
+
}
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function exists(p: string) {
|
|
60
|
+
try {
|
|
61
|
+
await fs.access(p);
|
|
62
|
+
return true;
|
|
63
|
+
} catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const WT_SEGMENT = `${path.sep}.claude${path.sep}worktrees${path.sep}`;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Is `p` inside one of the project's worktree checkouts? They live under the repository's
|
|
72
|
+
* `.claude/worktrees/<name>` (the CLI's `--worktree` layout); the repository top is the
|
|
73
|
+
* project itself or one of its ancestors.
|
|
74
|
+
*/
|
|
75
|
+
export function isWorktreeOf(projectPath: string, p: string) {
|
|
76
|
+
const idx = p.lastIndexOf(WT_SEGMENT);
|
|
77
|
+
if (idx <= 0) return false;
|
|
78
|
+
const top = p.slice(0, idx);
|
|
79
|
+
return projectPath === top || projectPath.startsWith(top + path.sep);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** `<top>/.claude/worktrees/<name>/…` → name */
|
|
83
|
+
export function worktreeName(p: string): string | undefined {
|
|
84
|
+
const idx = p.lastIndexOf(WT_SEGMENT);
|
|
85
|
+
if (idx <= 0) return undefined;
|
|
86
|
+
return p.slice(idx + WT_SEGMENT.length).split(path.sep)[0] || undefined;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Where a session runs: the project directory, or a worktree of it. Sessions created in a
|
|
91
|
+
* worktree from the web are recorded in sqlite; others (CLI `--worktree`, forks) are
|
|
92
|
+
* recognised by the cwd in their transcript.
|
|
93
|
+
*/
|
|
94
|
+
export async function sessionCwd(projectPath: string, sessionId: string): Promise<string> {
|
|
95
|
+
const row = db.prepare("SELECT cwd FROM session_cwd WHERE session_id = ?").get(sessionId) as { cwd: string } | undefined;
|
|
96
|
+
if (row) return row.cwd;
|
|
97
|
+
const recorded = await transcriptCwd(projectPath, sessionId);
|
|
98
|
+
if (recorded && recorded !== projectPath && isWorktreeOf(projectPath, recorded) && (await exists(recorded))) {
|
|
99
|
+
setSessionCwd(sessionId, recorded);
|
|
100
|
+
return recorded;
|
|
101
|
+
}
|
|
102
|
+
return projectPath;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Where a session's transcript lives, for reading history. Unlike sessionCwd this does not
|
|
107
|
+
* require the directory to still exist: a cleaned-up worktree keeps its transcript.
|
|
108
|
+
*/
|
|
109
|
+
export async function historyCwd(projectPath: string, sessionId: string): Promise<string> {
|
|
110
|
+
const row = db.prepare("SELECT cwd FROM session_cwd WHERE session_id = ?").get(sessionId) as { cwd: string } | undefined;
|
|
111
|
+
return row?.cwd ?? (await transcriptCwd(projectPath, sessionId)) ?? projectPath;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Worktree checkouts of this project that have transcripts, including ones already removed
|
|
116
|
+
* from disk (git no longer lists them, but their sessions are still history).
|
|
117
|
+
*/
|
|
118
|
+
export async function worktreeTranscriptCwds(projectPath: string): Promise<string[]> {
|
|
119
|
+
const root = path.join(claudeDir(), "projects");
|
|
120
|
+
const out = new Set<string>();
|
|
121
|
+
const top = await repoTop(projectPath);
|
|
122
|
+
if (!top) return [];
|
|
123
|
+
const wtRoot = path.join(top, ".claude", "worktrees");
|
|
124
|
+
// Transcript dirs are named after the cwd, so this repo's worktrees share a name prefix.
|
|
125
|
+
const prefix = encodedDir(wtRoot + path.sep);
|
|
126
|
+
let names: string[] = [];
|
|
127
|
+
try {
|
|
128
|
+
names = (await fs.readdir(root)).filter((n) => n.startsWith(prefix));
|
|
129
|
+
} catch {
|
|
130
|
+
return [];
|
|
131
|
+
}
|
|
132
|
+
for (const name of names) {
|
|
133
|
+
let cwd = dirCwdCache.get(name);
|
|
134
|
+
if (cwd === undefined) {
|
|
135
|
+
const dir = path.join(root, name);
|
|
136
|
+
const file = (await fs.readdir(dir)).find((f) => f.endsWith(".jsonl"));
|
|
137
|
+
if (!file) continue;
|
|
138
|
+
cwd = (await fileCwd(path.join(dir, file))) ?? "";
|
|
139
|
+
dirCwdCache.set(name, cwd); // a transcript dir's cwd never changes
|
|
140
|
+
}
|
|
141
|
+
// The encoding is lossy: confirm it is exactly <repo top>/.claude/worktrees/<name>.
|
|
142
|
+
if (cwd && path.dirname(cwd) === wtRoot) out.add(cwd);
|
|
143
|
+
}
|
|
144
|
+
return [...out];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const dirCwdCache = new Map<string, string>();
|
|
148
|
+
const topCache = new Map<string, string>();
|
|
149
|
+
|
|
150
|
+
/** The git top-level of a project (undefined when it is not inside a repository). */
|
|
151
|
+
async function repoTop(projectPath: string): Promise<string | undefined> {
|
|
152
|
+
const cached = topCache.get(projectPath);
|
|
153
|
+
if (cached) return cached;
|
|
154
|
+
let top: string | undefined;
|
|
155
|
+
try {
|
|
156
|
+
top = await new Promise<string>((resolve, reject) => {
|
|
157
|
+
execFile("git", ["rev-parse", "--show-toplevel"], { cwd: projectPath, encoding: "utf8" }, (err, stdout) => (err ? reject(err) : resolve(stdout)));
|
|
158
|
+
});
|
|
159
|
+
top = top.trim() || undefined;
|
|
160
|
+
} catch {
|
|
161
|
+
top = undefined;
|
|
162
|
+
}
|
|
163
|
+
// Only positive answers are cached: a project may be `git init`ed later.
|
|
164
|
+
if (top) topCache.set(projectPath, top);
|
|
165
|
+
return top;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function draftCwds(sessionIds: string[]): Map<string, string> {
|
|
169
|
+
const out = new Map<string, string>();
|
|
170
|
+
for (const id of sessionIds) {
|
|
171
|
+
const row = db.prepare("SELECT cwd FROM session_cwd WHERE session_id = ?").get(id) as { cwd: string } | undefined;
|
|
172
|
+
if (row) out.set(id, row.cwd);
|
|
173
|
+
}
|
|
174
|
+
return out;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ---------- extra directories (/add-dir) ----------
|
|
178
|
+
|
|
179
|
+
export function sessionDirs(sessionId: string): string[] {
|
|
180
|
+
return (db.prepare("SELECT path FROM session_dirs WHERE session_id = ? ORDER BY added_at").all(sessionId) as { path: string }[]).map((r) => r.path);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function insertSessionDir(sessionId: string, dir: string) {
|
|
184
|
+
db.prepare("INSERT OR IGNORE INTO session_dirs (session_id, path, added_at) VALUES (?, ?, ?)").run(sessionId, dir, Date.now());
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function deleteSessionDir(sessionId: string, dir: string) {
|
|
188
|
+
db.prepare("DELETE FROM session_dirs WHERE session_id = ? AND path = ?").run(sessionId, dir);
|
|
189
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import crypto from "node:crypto";
|
|
7
|
+
|
|
8
|
+
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "ide-goal-"));
|
|
9
|
+
process.env.CLAUDE_IDE_DATA_DIR = path.join(temporary, "data");
|
|
10
|
+
process.env.CLAUDE_CONFIG_DIR = path.join(temporary, "claude");
|
|
11
|
+
const { db } = await import("./db.ts");
|
|
12
|
+
const { goalState } = await import("./session-file.ts");
|
|
13
|
+
const cwd = path.join(temporary, "project");
|
|
14
|
+
const files = path.join(process.env.CLAUDE_CONFIG_DIR, "projects", cwd.replace(/[^a-zA-Z0-9]/g, "-"));
|
|
15
|
+
fs.mkdirSync(files, { recursive: true });
|
|
16
|
+
const goal = (condition: string) => ({ type: "attachment", attachment: { type: "goal_status", condition, sentinel: true } });
|
|
17
|
+
const command = (args: string) => ({ type: "user", message: { content: [{ type: "text", text: `<command-name>/goal</command-name><command-args>${args}</command-args>` }] } });
|
|
18
|
+
|
|
19
|
+
function save(file: string, records: unknown[]) {
|
|
20
|
+
let parent: string | null = null;
|
|
21
|
+
const lines = records.map((record) => {
|
|
22
|
+
const r = record as Record<string, unknown>;
|
|
23
|
+
const uuid = typeof r.uuid === "string" ? r.uuid : crypto.randomUUID();
|
|
24
|
+
const result = { ...r, uuid, parentUuid: r.parentUuid === undefined ? parent : r.parentUuid };
|
|
25
|
+
parent = uuid;
|
|
26
|
+
return JSON.stringify(result);
|
|
27
|
+
});
|
|
28
|
+
fs.writeFileSync(file, lines.join("\n") + "\n");
|
|
29
|
+
}
|
|
30
|
+
const cleared = (condition: string) => ({ type: "attachment", attachment: { type: "goal_status", sentinel: true, met: true, condition } });
|
|
31
|
+
|
|
32
|
+
test("btw ignores inherited goals and their continuations but can set its own goal", async () => {
|
|
33
|
+
const parent = crypto.randomUUID(), child = crypto.randomUUID(), prompt = crypto.randomUUID();
|
|
34
|
+
const inherited = [command("parent goal"), goal("parent goal")];
|
|
35
|
+
const parentFile = path.join(files, `${parent}.jsonl`), childFile = path.join(files, `${child}.jsonl`);
|
|
36
|
+
const write = (records: unknown[]) => save(childFile, records);
|
|
37
|
+
save(parentFile, inherited);
|
|
38
|
+
const original = fs.readFileSync(parentFile, "utf8");
|
|
39
|
+
db.prepare("INSERT INTO session_view (session_id, project_path, btw_parent, btw_prompt_uuid, write_enabled) VALUES (?, ?, ?, ?, 0)").run(child, cwd, parent, prompt);
|
|
40
|
+
write(inherited);
|
|
41
|
+
assert.equal((await goalState(cwd, child)), null);
|
|
42
|
+
const own = [...inherited, { type: "user", uuid: prompt, message: { content: "side question" } }, goal("parent goal")];
|
|
43
|
+
write(own);
|
|
44
|
+
assert.equal((await goalState(cwd, child)), null, "an old goal's later status cannot assign it to the child");
|
|
45
|
+
write([...own, { type: "user", message: { content: "请检查这个示例:\n<command-name>/goal</command-name><command-args>parent goal</command-args>" } }, goal("parent goal")]);
|
|
46
|
+
assert.equal((await goalState(cwd, child)), null, "quoted command examples cannot adopt the inherited goal");
|
|
47
|
+
write([...own, command("child goal"), goal("child goal")]);
|
|
48
|
+
assert.equal((await goalState(cwd, child))?.condition, "child goal");
|
|
49
|
+
write([...own, goal("native child goal"), command("native child goal")]);
|
|
50
|
+
assert.deepEqual((await goalState(cwd, child)), { condition: "native child goal", status: "active" }, "native SDK persists the initialization before its command");
|
|
51
|
+
write([...own, goal("with caveat"), { type: "user", message: { content: "<local-command-caveat>Local command, not model input.</local-command-caveat>" } }, command("with caveat")]);
|
|
52
|
+
assert.equal((await goalState(cwd, child))?.condition, "with caveat", "the small native caveat classification survives projection without loading ordinary bodies");
|
|
53
|
+
write([...own, goal("parent goal"), command(""), { type: "attachment", attachment: { type: "goal_status", condition: "parent goal", met: true } }]);
|
|
54
|
+
assert.equal((await goalState(cwd, child)), null, "inspecting /goal does not adopt an inherited goal");
|
|
55
|
+
write([...own, command("child goal"), goal("child goal"), cleared("child goal"), command("clear")]);
|
|
56
|
+
assert.equal((await goalState(cwd, child))?.status, "cleared");
|
|
57
|
+
write([...inherited, { type: "attachment", uuid: "queue", attachment: { type: "queued_command", source_uuid: prompt, prompt: "side question" } }, command("queued child goal"), goal("queued child goal")]);
|
|
58
|
+
assert.equal((await goalState(cwd, child))?.condition, "queued child goal", "queued delivery preserves the supplied prompt boundary");
|
|
59
|
+
db.prepare("UPDATE session_view SET btw_parent=NULL WHERE session_id=?").run(child);
|
|
60
|
+
write(own);
|
|
61
|
+
assert.equal((await goalState(cwd, child)), null, "promotion cannot revive the inherited goal");
|
|
62
|
+
assert.equal((await goalState(cwd, parent))?.condition, "parent goal");
|
|
63
|
+
assert.equal(fs.readFileSync(parentFile, "utf8"), original);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("goal commands at the own prompt boundary and after clear survive either native ordering", async () => {
|
|
67
|
+
const child = crypto.randomUUID(), prompt = crypto.randomUUID();
|
|
68
|
+
db.prepare("INSERT INTO session_view (session_id, project_path, btw_parent, btw_prompt_uuid) VALUES (?, ?, ?, ?)").run(child, cwd, crypto.randomUUID(), prompt);
|
|
69
|
+
const file = path.join(files, `${child}.jsonl`);
|
|
70
|
+
const write = (records: unknown[]) => save(file, records);
|
|
71
|
+
const own = { ...command("own goal"), uuid: prompt };
|
|
72
|
+
write([goal("parent"), goal("own goal"), own]);
|
|
73
|
+
assert.equal((await goalState(cwd, child))?.condition, "own goal");
|
|
74
|
+
for (const init of [[goal("new"), command("new")], [command("new"), goal("new")]]) {
|
|
75
|
+
write([goal("parent"), goal("own goal"), own, cleared("own goal"), command("clear"), ...init]);
|
|
76
|
+
assert.equal((await goalState(cwd, child))?.condition, "new");
|
|
77
|
+
assert.equal((await goalState(cwd, child))?.status, "active");
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("a legacy promoted fork keeps its exact question boundary without a prompt UUID", async () => {
|
|
82
|
+
const child = crypto.randomUUID();
|
|
83
|
+
db.prepare("INSERT INTO session_view (session_id, project_path, btw_question) VALUES (?, ?, ?)").run(child, cwd, "legacy question");
|
|
84
|
+
const file = path.join(files, `${child}.jsonl`);
|
|
85
|
+
const records = [goal("parent"), command("parent"), { type: "user", message: { content: "legacy question" } }, goal("own"), command("own")];
|
|
86
|
+
save(file, records);
|
|
87
|
+
assert.equal((await goalState(cwd, child))?.condition, "own");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("a promoted btw retains a literal goal delimiter through indexing and startup policy", async () => {
|
|
91
|
+
const child = crypto.randomUUID(), prompt = crypto.randomUUID();
|
|
92
|
+
const condition = "完整保留 </command-args>\n以及 <command-args>嵌套</command-args>";
|
|
93
|
+
db.prepare("INSERT INTO session_view (session_id, project_path, btw_prompt_uuid) VALUES (?, ?, ?)").run(child, cwd, prompt);
|
|
94
|
+
save(path.join(files, `${child}.jsonl`), [
|
|
95
|
+
{ ...goal("parent"), forkedFrom: true }, { ...command("parent"), forkedFrom: true },
|
|
96
|
+
{ type: "user", uuid: prompt, message: { content: "side question" } }, goal(condition), command(condition),
|
|
97
|
+
]);
|
|
98
|
+
assert.deepEqual(await goalState(cwd, child), { condition, status: "active" });
|
|
99
|
+
const { needsInheritedGoalCleanup } = await import("./btw-policy.ts");
|
|
100
|
+
assert.equal(await needsInheritedGoalCleanup(cwd, child), false, "a successful own goal needs no inherited cleanup even without an earlier cleanup receipt");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("goal projection uses the selected native branch including trailing status effects", async () => {
|
|
104
|
+
const id = crypto.randomUUID(), file = path.join(files, `${id}.jsonl`);
|
|
105
|
+
const initial = { ...goal("A"), uuid: "goal-A", parentUuid: null };
|
|
106
|
+
const accepted = { ...command("A"), uuid: "accepted", parentUuid: "goal-A" };
|
|
107
|
+
const failed = { ...command("B".repeat(4001)), uuid: "rejected", parentUuid: "accepted" };
|
|
108
|
+
const progress = { type: "attachment", uuid: "progress", parentUuid: "rejected", attachment: { type: "goal_status", condition: "A", met: false, reason: "working", iterations: 2 } };
|
|
109
|
+
const met = { type: "attachment", uuid: "met", parentUuid: "progress", attachment: { type: "goal_status", condition: "A", met: true, reason: "done" } };
|
|
110
|
+
save(file, [initial, accepted, failed, progress, met]);
|
|
111
|
+
assert.equal((await goalState(cwd, id))?.status, "met", "the terminal metadata tail is not a page's last user/assistant message");
|
|
112
|
+
db.prepare("INSERT INTO session_view (session_id, project_path, rewind_anchor) VALUES (?, ?, ?)").run(id, cwd, "progress");
|
|
113
|
+
assert.deepEqual(await goalState(cwd, id), { condition: "A", status: "active", reason: "working", iterations: 2 });
|
|
114
|
+
// A later physical branch cannot replace the state selected by its parent chain.
|
|
115
|
+
save(file, [initial, accepted, failed, progress, met, { ...goal("C"), uuid: "goal-C", parentUuid: "accepted" }, { ...command("C"), uuid: "cmd-C", parentUuid: "goal-C" }]);
|
|
116
|
+
assert.equal((await goalState(cwd, id))?.condition, "A");
|
|
117
|
+
db.prepare("UPDATE session_view SET rewind_anchor=NULL WHERE session_id=?").run(id);
|
|
118
|
+
assert.equal((await goalState(cwd, id))?.condition, "C");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("a compacted inherited goal is still inherited, even when its sentinel was re-emitted locally", async () => {
|
|
122
|
+
const id = crypto.randomUUID(), prompt = crypto.randomUUID(), file = path.join(files, `${id}.jsonl`);
|
|
123
|
+
const { sessionGoalProjection } = await import("./session-file.ts");
|
|
124
|
+
db.prepare("INSERT INTO session_view (session_id, project_path, btw_prompt_uuid, btw_goal_isolated) VALUES (?, ?, ?, 1)").run(id, cwd, prompt);
|
|
125
|
+
save(file, [
|
|
126
|
+
{ ...goal("parent"), uuid: "parent-init", parentUuid: null, forkedFrom: true },
|
|
127
|
+
{ ...command("parent"), uuid: "parent-cmd", parentUuid: "parent-init", forkedFrom: true },
|
|
128
|
+
{ type: "user", uuid: prompt, parentUuid: "parent-cmd", message: { content: "side question" } },
|
|
129
|
+
{ type: "system", subtype: "compact_boundary", uuid: "compact", parentUuid: null, logicalParentUuid: prompt },
|
|
130
|
+
{ type: "user", uuid: "summary", parentUuid: "compact", isCompactSummary: true, message: { content: "Summary" } },
|
|
131
|
+
{ ...goal("parent"), uuid: "restored-goal", parentUuid: "summary" },
|
|
132
|
+
]);
|
|
133
|
+
assert.deepEqual(await sessionGoalProjection(cwd, id), { goal: null, isolated: false });
|
|
134
|
+
});
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { nativeGoalBranch } from "./history-pages.ts";
|
|
2
|
+
import { projectGoal } from "./goal-state.ts";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { sessionFileDir } from "./cli-files.ts";
|
|
7
|
+
import { db } from "./db.ts";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Direct reads of a session's jsonl for records the SDK's readers do not return
|
|
11
|
+
* (cost-state, local command output). See NOTES.md "SDK:会话文件结构".
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
function projectsRoot() {
|
|
15
|
+
return path.join(process.env.CLAUDE_CONFIG_DIR ?? path.join(os.homedir(), ".claude"), "projects");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const SESSION_ID = /^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$/;
|
|
19
|
+
|
|
20
|
+
/** Session ids are UUIDs; anything else must not reach a filesystem path. */
|
|
21
|
+
export function isSessionId(id: string): boolean {
|
|
22
|
+
return SESSION_ID.test(id);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function exists(p: string, strict = false) {
|
|
26
|
+
try {
|
|
27
|
+
await fs.access(p);
|
|
28
|
+
return true;
|
|
29
|
+
} catch (err) {
|
|
30
|
+
if (strict && (err as NodeJS.ErrnoException).code !== "ENOENT") throw err;
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function sessionFile(cwd: string, sessionId: string, strict = false): Promise<string | undefined> {
|
|
36
|
+
if (!isSessionId(sessionId)) return undefined;
|
|
37
|
+
const encoded = cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
38
|
+
const root = projectsRoot();
|
|
39
|
+
if (encoded.length <= 200) {
|
|
40
|
+
const f = path.join(root, encoded, `${sessionId}.jsonl`);
|
|
41
|
+
if (await exists(f, strict)) return f;
|
|
42
|
+
// Sessions of a worktree of the project live under the worktree's own directory.
|
|
43
|
+
const dir = await sessionFileDir(cwd, sessionId, strict);
|
|
44
|
+
return dir ? path.join(dir, `${sessionId}.jsonl`) : undefined;
|
|
45
|
+
}
|
|
46
|
+
// Long paths are truncated and get a hash suffix: look the directory up by prefix.
|
|
47
|
+
try {
|
|
48
|
+
for (const d of await fs.readdir(root)) {
|
|
49
|
+
if (!d.startsWith(encoded.slice(0, 200))) continue;
|
|
50
|
+
const f = path.join(root, d, `${sessionId}.jsonl`);
|
|
51
|
+
if (await exists(f, strict)) return f;
|
|
52
|
+
}
|
|
53
|
+
} catch (err) {
|
|
54
|
+
if (strict && (err as NodeJS.ErrnoException).code !== "ENOENT") throw err;
|
|
55
|
+
// no projects dir yet
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
type Rec = { type?: string; subtype?: string; uuid?: string; parentUuid?: string | null; content?: unknown; totalCostUSD?: number };
|
|
61
|
+
|
|
62
|
+
async function readRecords(cwd: string, sessionId: string): Promise<Rec[]> {
|
|
63
|
+
const f = await sessionFile(cwd, sessionId);
|
|
64
|
+
if (!f) return [];
|
|
65
|
+
const out: Rec[] = [];
|
|
66
|
+
for (const line of (await fs.readFile(f, "utf8")).split("\n")) {
|
|
67
|
+
if (!line) continue;
|
|
68
|
+
try {
|
|
69
|
+
out.push(JSON.parse(line));
|
|
70
|
+
} catch {
|
|
71
|
+
// partial last line while the CLI is writing
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** The cumulative cost the CLI restores on resume. */
|
|
78
|
+
export async function lastCostState(cwd: string, sessionId: string): Promise<number | undefined> {
|
|
79
|
+
const recs = await readRecords(cwd, sessionId);
|
|
80
|
+
for (let i = recs.length - 1; i >= 0; i--) {
|
|
81
|
+
const r = recs[i]!;
|
|
82
|
+
if (r.type === "cost-state" && typeof r.totalCostUSD === "number") return r.totalCostUSD;
|
|
83
|
+
}
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** `system/local_command` records (the output of /cost, /context…), keyed by their parent uuid. */
|
|
88
|
+
export async function localCommandOutputs(cwd: string, sessionId: string): Promise<Map<string, { uuid: string; content: string }[]>> {
|
|
89
|
+
const byParent = new Map<string, { uuid: string; content: string }[]>();
|
|
90
|
+
for (const r of await readRecords(cwd, sessionId)) {
|
|
91
|
+
if (r.type !== "system" || r.subtype !== "local_command" || !r.parentUuid || typeof r.content !== "string") continue;
|
|
92
|
+
const list = byParent.get(r.parentUuid) ?? [];
|
|
93
|
+
list.push({ uuid: r.uuid ?? `${r.parentUuid}-out`, content: r.content });
|
|
94
|
+
byParent.set(r.parentUuid, list);
|
|
95
|
+
}
|
|
96
|
+
return byParent;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export type { GoalState } from "./goal-state.ts";
|
|
100
|
+
|
|
101
|
+
/** Both the UI and startup isolation inspect the branch the SDK will resume. */
|
|
102
|
+
export async function sessionGoalProjection(cwd: string, sessionId: string) {
|
|
103
|
+
const view = db.prepare("SELECT btw_parent, btw_prompt_uuid, btw_question, btw_goal_isolation_uuid, rewind_anchor FROM session_view WHERE session_id=?").get(sessionId) as {
|
|
104
|
+
btw_parent?: string | null; btw_prompt_uuid?: string | null; btw_question?: string | null; btw_goal_isolation_uuid?: string | null; rewind_anchor?: string | null;
|
|
105
|
+
} | undefined;
|
|
106
|
+
const records = await nativeGoalBranch(await sessionFile(cwd, sessionId), view?.rewind_anchor, !view?.btw_prompt_uuid ? view?.btw_question : undefined);
|
|
107
|
+
return projectGoal(records, { fork: !!(view?.btw_parent || view?.btw_prompt_uuid || view?.btw_question),
|
|
108
|
+
promptUuid: view?.btw_prompt_uuid, question: view?.btw_question, isolationUuid: view?.btw_goal_isolation_uuid });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export async function goalState(cwd: string, sessionId: string) {
|
|
112
|
+
return (await sessionGoalProjection(cwd, sessionId)).goal;
|
|
113
|
+
}
|