@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,513 @@
|
|
|
1
|
+
import { goalCommand, recordText, type GoalRecord } from "./goal-state.ts";
|
|
2
|
+
import { selectSessionBranch, eligibleBranchEntry, HistoryError } from "./session-branch.ts";
|
|
3
|
+
export { HistoryError } from "./session-branch.ts";
|
|
4
|
+
import { deriveTodos, type TodoItem } from "../shared/todos.ts";
|
|
5
|
+
import fs from "node:fs/promises";
|
|
6
|
+
import crypto from "node:crypto";
|
|
7
|
+
import { PAGE_BYTES, PAGE_LIMIT, SESSION_BYTES, displayMessage } from "../shared/message-window.ts";
|
|
8
|
+
import type { HistoryPage, SdkMessageLike } from "../shared/protocol.ts";
|
|
9
|
+
|
|
10
|
+
type RecordLike = SdkMessageLike & { parentUuid?: string | null; logicalParentUuid?: string; isSidechain?: boolean; agentId?: string; sourceToolAssistantUUID?: string; teamName?: string; isMeta?: boolean; operation?: string; reason?: string; deliveryId?: string; commandUuid?: string; origin?: { kind?: string }; attachment?: { type?: string; prompt?: unknown; source_uuid?: string; delivery_id?: string; isMeta?: boolean; origin?: { kind?: string }; forwardedIntent?: unknown }; compactMetadata?: { preservedMessages?: { uuids: string[]; anchorUuid: string }; preservedSegment?: { headUuid: string; tailUuid: string; anchorUuid: string } } };
|
|
11
|
+
type Entry = Pick<RecordLike, "type" | "subtype" | "uuid" | "parentUuid" | "logicalParentUuid" | "isSidechain" | "teamName" | "isMeta" | "isCompactSummary" | "compactMetadata"> & {
|
|
12
|
+
inherited?: boolean; goalCommand?: string;
|
|
13
|
+
uuid: string; offset: number; length: number; order: number; messageId?: string; timestamp?: string; agentId?: string; sourceToolAssistantUUID?: string; queued?: boolean; sourceUuid?: string; queuedKey?: string; queuedHash?: string; queuedFirstOrder?: number; attachmentType?: string; originKind?: string; queuedMeta?: boolean; queuedOriginKind?: string; queuedPrompt?: boolean; queuedForwarded?: boolean; interrupted?: boolean; localPrompt?: "caveat" | "record" | "output"; completedLocalCommand?: boolean; tools?: string[]; results?: string[]; todoItems?: (TodoItem & { id: string })[]; todoIds?: { id: string; taskId: string; isError: boolean }[];
|
|
14
|
+
};
|
|
15
|
+
type Index = { generation: string; inode: number; size: number; fileSize: number; mtime: number; entries: Map<string, Entry>; deliveries: { key: string; order: number }[]; legacyCopies: Map<string, { hash: string; mixedAt?: number }>; used: number; bytes: number };
|
|
16
|
+
const indexes = new Map<string, Index>();
|
|
17
|
+
// Keep only revision scalars when an offset index is evicted. Rebuilding an
|
|
18
|
+
// unchanged file must retain its generation, including after an in-place rewrite.
|
|
19
|
+
type Revision = { identity: string; generation: string; size: number; mtime: number; prefixLength: number; prefixHash: string };
|
|
20
|
+
const revisions = new Map<string, Revision>();
|
|
21
|
+
const REVISION_LIMIT = 1024;
|
|
22
|
+
const locks = new Map<string, Promise<unknown>>();
|
|
23
|
+
const INDEX_BUDGET = 32 * 1024 * 1024;
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
/** Serialize indexing + reads per file. The cache contains offsets/links, never message bodies. */
|
|
27
|
+
async function withIndex<T>(file: string | undefined, fn: (index: Index, handle: fs.FileHandle) => Promise<T>): Promise<T | undefined> {
|
|
28
|
+
if (!file) return undefined;
|
|
29
|
+
const previous = locks.get(file) ?? Promise.resolve();
|
|
30
|
+
const work = previous.catch(() => {}).then(async () => {
|
|
31
|
+
let handle: fs.FileHandle;
|
|
32
|
+
try { handle = await fs.open(file, "r"); } catch (e) { if ((e as NodeJS.ErrnoException).code === "ENOENT") return undefined; throw e; }
|
|
33
|
+
try {
|
|
34
|
+
const stat = await handle.stat();
|
|
35
|
+
const identity = `${stat.dev}:${stat.ino}:${stat.birthtimeMs}`;
|
|
36
|
+
let revision = revisions.get(file);
|
|
37
|
+
const prefixLength = revision?.identity === identity && revision.prefixLength ? Math.min(revision.prefixLength, stat.size) : Math.min(4096, stat.size);
|
|
38
|
+
const prefix = Buffer.alloc(prefixLength);
|
|
39
|
+
let prefixRead = 0;
|
|
40
|
+
while (prefixRead < prefix.length) {
|
|
41
|
+
const { bytesRead } = await handle.read(prefix, prefixRead, prefix.length - prefixRead, prefixRead);
|
|
42
|
+
if (!bytesRead) throw new HistoryError(409, "会话文件已变化,请重新加载");
|
|
43
|
+
prefixRead += bytesRead;
|
|
44
|
+
}
|
|
45
|
+
const prefixHash = crypto.createHash("sha256").update(prefix).digest("hex");
|
|
46
|
+
if (!revision || revision.identity !== identity || stat.size < revision.size || stat.size === revision.size && stat.mtimeMs !== revision.mtime || revision.prefixLength > 0 && prefixHash !== revision.prefixHash) {
|
|
47
|
+
const generation = crypto.createHash("sha256").update(`${file}:${identity}:${stat.size}:${stat.mtimeMs}:${prefixHash}`).digest("hex");
|
|
48
|
+
revision = { identity, generation, size: stat.size, mtime: stat.mtimeMs, prefixLength, prefixHash };
|
|
49
|
+
}
|
|
50
|
+
revisions.delete(file); revisions.set(file, revision);
|
|
51
|
+
while (revisions.size > REVISION_LIMIT) revisions.delete(revisions.keys().next().value!);
|
|
52
|
+
let index = indexes.get(file);
|
|
53
|
+
if (!index || index.generation !== revision.generation) {
|
|
54
|
+
// Cache eviction must not invalidate a cursor for the same file. Revision
|
|
55
|
+
// checks live in the cursor; this identity remains stable across appends.
|
|
56
|
+
index = { generation: revision.generation, inode: stat.ino, size: 0, fileSize: 0, mtime: 0, entries: new Map(), deliveries: [], legacyCopies: new Map(), used: Date.now(), bytes: 0 };
|
|
57
|
+
indexes.set(file, index);
|
|
58
|
+
}
|
|
59
|
+
// Only complete lines are committed. A half-written final line is retried next time.
|
|
60
|
+
let pending: Buffer[] = [];
|
|
61
|
+
let pendingLength = 0;
|
|
62
|
+
let offset = index.size;
|
|
63
|
+
const end = stat.size;
|
|
64
|
+
for (let position = offset; position < end;) {
|
|
65
|
+
const chunk = Buffer.allocUnsafe(Math.min(256 * 1024, end - position));
|
|
66
|
+
const { bytesRead } = await handle.read(chunk, 0, chunk.length, position);
|
|
67
|
+
if (!bytesRead) break;
|
|
68
|
+
const chunkStart = position;
|
|
69
|
+
position += bytesRead;
|
|
70
|
+
const data = chunk.subarray(0, bytesRead);
|
|
71
|
+
let start = 0;
|
|
72
|
+
for (;;) {
|
|
73
|
+
const nl = data.indexOf(10, start);
|
|
74
|
+
if (nl < 0) break;
|
|
75
|
+
const length = pendingLength + nl - start;
|
|
76
|
+
const line = pendingLength ? Buffer.concat([...pending, data.subarray(start, nl)], length) : data.subarray(start, nl);
|
|
77
|
+
try {
|
|
78
|
+
const r = JSON.parse(line.toString("utf8")) as RecordLike;
|
|
79
|
+
if (r.type === "queue-operation" && r.operation === "remove" && r.reason === "absorbed_mid_turn") {
|
|
80
|
+
const key = deliveryKey(r.deliveryId, r.commandUuid);
|
|
81
|
+
if (key) { index.deliveries.push({ key, order: offset }); index.bytes += 48 + key.length * 2; }
|
|
82
|
+
}
|
|
83
|
+
if (r.uuid && ["user", "assistant", "system", "attachment", "progress"].includes(r.type)) {
|
|
84
|
+
const message = r.message as { id?: string; content?: unknown } | undefined;
|
|
85
|
+
const content = message?.content;
|
|
86
|
+
const blocks = Array.isArray(content) ? content : [];
|
|
87
|
+
const queued = r.attachment?.type === "queued_command";
|
|
88
|
+
const queuedKey = queued ? deliveryKey(r.attachment!.delivery_id, r.attachment!.source_uuid) : undefined;
|
|
89
|
+
const queuedHash = queued ? crypto.createHash("sha256").update(JSON.stringify(r.attachment)).digest("hex") : undefined;
|
|
90
|
+
const previous = index.entries.get(r.uuid);
|
|
91
|
+
// Rewrites of the same attachment retain its membership position;
|
|
92
|
+
// a changed payload or key is a new copy at the current offset.
|
|
93
|
+
let queuedFirstOrder: number | undefined;
|
|
94
|
+
if (queued) queuedFirstOrder = previous?.queued && previous.queuedHash === queuedHash ? previous.queuedFirstOrder ?? offset : offset;
|
|
95
|
+
if (queuedKey?.startsWith("uuid:")) {
|
|
96
|
+
const hash = queuedHash!;
|
|
97
|
+
const copy = index.legacyCopies.get(queuedKey);
|
|
98
|
+
if (!copy) { index.legacyCopies.set(queuedKey, { hash }); index.bytes += 80 + (queuedKey.length + hash.length) * 2; }
|
|
99
|
+
else if (copy.hash !== hash && copy.mixedAt === undefined) copy.mixedAt = offset;
|
|
100
|
+
}
|
|
101
|
+
const command = goalCommand(r as GoalRecord);
|
|
102
|
+
const entry: Entry = { inherited: !!r.forkedFrom, goalCommand: command, uuid: r.uuid, type: r.type, subtype: r.subtype, parentUuid: r.parentUuid, logicalParentUuid: r.logicalParentUuid,
|
|
103
|
+
messageId: r.type === "assistant" && typeof message?.id === "string" && message.id ? message.id : undefined,
|
|
104
|
+
timestamp: typeof r.timestamp === "string" ? r.timestamp : undefined, agentId: r.agentId, sourceToolAssistantUUID: r.sourceToolAssistantUUID,
|
|
105
|
+
isSidechain: r.isSidechain, teamName: r.teamName, isMeta: r.isMeta, isCompactSummary: r.isCompactSummary, compactMetadata: r.compactMetadata ? {
|
|
106
|
+
preservedMessages: r.compactMetadata.preservedMessages ? { uuids: r.compactMetadata.preservedMessages.uuids, anchorUuid: r.compactMetadata.preservedMessages.anchorUuid } : undefined,
|
|
107
|
+
preservedSegment: r.compactMetadata.preservedSegment,
|
|
108
|
+
} : undefined,
|
|
109
|
+
queued, sourceUuid: nonemptyString(r.attachment?.source_uuid),
|
|
110
|
+
queuedKey, queuedHash, queuedFirstOrder,
|
|
111
|
+
attachmentType: r.attachment?.type, originKind: r.origin?.kind,
|
|
112
|
+
queuedMeta: r.attachment?.isMeta, queuedOriginKind: r.attachment?.origin?.kind, queuedPrompt: typeof r.attachment?.prompt === "string" || Array.isArray(r.attachment?.prompt),
|
|
113
|
+
queuedForwarded: forwardedIntent(r.attachment?.forwardedIntent), interrupted: r.type === "user" && interruptedContent(content),
|
|
114
|
+
localPrompt: r.promptSource === undefined ? localPrompt(content) : undefined,
|
|
115
|
+
tools: blocks.filter((b) => b.type === "tool_use").map((b) => b.id),
|
|
116
|
+
results: blocks.filter((b) => b.type === "tool_result").map((b) => b.tool_use_id),
|
|
117
|
+
todoItems: blocks.filter((b) => b.type === "tool_use" && ["TodoWrite", "TaskCreate", "TaskUpdate"].includes(b.name)).map((b) => {
|
|
118
|
+
const input = b.input ?? {};
|
|
119
|
+
return { kind: "tool", id: b.id, name: b.name, input: {
|
|
120
|
+
taskId: input.taskId, subject: String(input.subject ?? "").slice(0, 1024), status: input.status, activeForm: input.activeForm,
|
|
121
|
+
todos: Array.isArray(input.todos) ? input.todos.slice(0, 1024).map((t: Record<string, unknown>) => ({ ...t, content: String(t.content ?? "").slice(0, 1024) })) : undefined,
|
|
122
|
+
} };
|
|
123
|
+
}),
|
|
124
|
+
todoIds: blocks.filter((b) => b.type === "tool_result").flatMap((b) => {
|
|
125
|
+
const structured = (r.toolUseResult ?? r.tool_use_result) as { task?: { id?: string } } | undefined;
|
|
126
|
+
const taskId = structured?.task?.id ?? (typeof b.content === "string" ? b.content.slice(0, 1000).match(/Task #(\d+)/)?.[1] : undefined);
|
|
127
|
+
return taskId ? [{ id: b.tool_use_id, taskId, isError: !!b.is_error }] : [];
|
|
128
|
+
}),
|
|
129
|
+
offset, length, order: offset };
|
|
130
|
+
index.entries.set(r.uuid, entry);
|
|
131
|
+
index.bytes += 240 + (entry.goalCommand?.length ?? 0) * 2 + ((entry.queuedKey?.length ?? 0) + (entry.queuedHash?.length ?? 0)) * 2 + (entry.tools?.length ?? 0) * 100 + (entry.results?.length ?? 0) * 100 + JSON.stringify(entry.todoItems ?? []).length * 2 + JSON.stringify(entry.compactMetadata ?? {}).length * 2;
|
|
132
|
+
}
|
|
133
|
+
} catch { /* Ignore malformed records without retaining their payload. */ }
|
|
134
|
+
start = nl + 1;
|
|
135
|
+
offset = chunkStart + start;
|
|
136
|
+
pending = []; pendingLength = 0;
|
|
137
|
+
}
|
|
138
|
+
if (start < bytesRead) { pending.push(data.subarray(start)); pendingLength += bytesRead - start; }
|
|
139
|
+
}
|
|
140
|
+
index.size = offset;
|
|
141
|
+
index.fileSize = stat.size;
|
|
142
|
+
index.mtime = stat.mtimeMs;
|
|
143
|
+
index.used = Date.now();
|
|
144
|
+
revision.size = stat.size; revision.mtime = stat.mtimeMs;
|
|
145
|
+
if (!revision.prefixLength) { revision.prefixLength = prefixLength; revision.prefixHash = prefixHash; }
|
|
146
|
+
return await fn(index, handle);
|
|
147
|
+
} finally {
|
|
148
|
+
await handle.close();
|
|
149
|
+
let bytes = [...indexes.values()].reduce((n, v) => n + v.bytes, 0);
|
|
150
|
+
for (const [key, value] of [...indexes].sort((a, b) => a[1].used - b[1].used)) {
|
|
151
|
+
if (indexes.size <= 8 && bytes <= INDEX_BUDGET) break;
|
|
152
|
+
indexes.delete(key); bytes -= value.bytes;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
locks.set(file, work);
|
|
157
|
+
try { return await work; } finally { if (locks.get(file) === work) locks.delete(file); }
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Message display builds on the same native branch used for control state. */
|
|
161
|
+
function branch(index: Index, anchor?: string | null, sidechain = false): Entry[] {
|
|
162
|
+
const entries = index.entries;
|
|
163
|
+
const eligible = (e: Entry) => eligibleBranchEntry(e, sidechain);
|
|
164
|
+
const { chain, tail, cutoff } = selectSessionBranch(entries, anchor, sidechain);
|
|
165
|
+
const completed = completeAssistantMessages(entries, chain, cutoff);
|
|
166
|
+
// The SDK subagent reader normalizes delivered chain entries only.
|
|
167
|
+
const trailing = sidechain ? [] : trailingQueued(entries, completed, tail, cutoff);
|
|
168
|
+
completed.push(...trailing);
|
|
169
|
+
const chainIds = new Set(completed.map((e) => e.uuid));
|
|
170
|
+
const commandOutputs = new Map<string, Entry[]>();
|
|
171
|
+
for (const e of entries.values()) {
|
|
172
|
+
if (e.order <= cutoff && e.type === "system" && e.subtype === "local_command" && e.parentUuid && chainIds.has(e.parentUuid) && !chainIds.has(e.uuid)) {
|
|
173
|
+
const list = commandOutputs.get(e.parentUuid) ?? []; list.push(e); commandOutputs.set(e.parentUuid, list);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const out = completed.flatMap((e) => [e, ...(commandOutputs.get(e.uuid) ?? [])]);
|
|
177
|
+
const trailingIds = new Set(trailing.map((e) => e.uuid));
|
|
178
|
+
const normalizedIds = new Set(out.map((e) => e.uuid));
|
|
179
|
+
const visibleQueued = new Set<string>();
|
|
180
|
+
const localCommands = completedLocalCommands(out);
|
|
181
|
+
// getSubagentMessages does not use queue-operation receipts.
|
|
182
|
+
const delivered = sidechain ? new Map<string, boolean>() : deliveredQueued(index, cutoff);
|
|
183
|
+
let next: "reply" | "prompt" | undefined;
|
|
184
|
+
for (let i = out.length - 1; i >= 0; i--) {
|
|
185
|
+
const e = out[i]!;
|
|
186
|
+
if (e.queued && (e.queuedFirstOrder ?? e.order) <= cutoff && e.queuedPrompt && !e.queuedForwarded && (delivered.get(e.uuid) ?? (next === "reply" || trailingIds.has(e.uuid))) &&
|
|
187
|
+
(sidechain || !e.queuedMeta || externalOrigin(e.queuedOriginKind))) visibleQueued.add(e.uuid);
|
|
188
|
+
if (e.type === "assistant" || e.results?.length || e.interrupted) next = "reply";
|
|
189
|
+
else if (e.type === "user" && !e.isMeta && !e.isCompactSummary && !localCommands.has(e.uuid)) next = "prompt";
|
|
190
|
+
}
|
|
191
|
+
return out.filter((e) => {
|
|
192
|
+
if (!eligible(e)) return false;
|
|
193
|
+
if (e.type === "user" || e.type === "assistant") return sidechain || !e.isMeta || externalOrigin(e.originKind);
|
|
194
|
+
if (e.type === "system") return !sidechain && ["compact_boundary", "local_command"].includes(e.subtype ?? "");
|
|
195
|
+
if (!visibleQueued.has(e.uuid) || e.sourceUuid && e.sourceUuid !== e.uuid && normalizedIds.has(e.sourceUuid)) return false;
|
|
196
|
+
if (e.sourceUuid) normalizedIds.add(e.sourceUuid);
|
|
197
|
+
return true;
|
|
198
|
+
}).map((e) => localCommands.has(e.uuid) ? { ...e, completedLocalCommand: true } : e);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const externalOrigin = (kind?: string) => ["channel", "observer", "observer-activity", "slack-ping", "peer"].includes(kind ?? "");
|
|
202
|
+
const nonemptyString = (value: unknown) => typeof value === "string" && value ? value : undefined;
|
|
203
|
+
const deliveryKey = (deliveryId: unknown, sourceUuid: unknown) => nonemptyString(deliveryId) ? `delivery:${deliveryId}` : nonemptyString(sourceUuid) ? `uuid:${sourceUuid}` : undefined;
|
|
204
|
+
|
|
205
|
+
/** Confirm specific queued copies before intersecting with the current branch.
|
|
206
|
+
* A legacy source UUID with conflicting payloads is ambiguous, so retain the
|
|
207
|
+
* SDK fallback rather than assigning another command's delivery to it. */
|
|
208
|
+
function deliveredQueued(index: Index, cutoff: number): Map<string, boolean> {
|
|
209
|
+
const counts = new Map<string, number>();
|
|
210
|
+
for (const op of index.deliveries) if (op.order <= cutoff) counts.set(op.key, (counts.get(op.key) ?? 0) + 1);
|
|
211
|
+
const groups = new Map<string, Entry[]>();
|
|
212
|
+
for (const e of index.entries.values()) if ((e.queuedFirstOrder ?? e.order) <= cutoff && e.queuedKey && counts.has(e.queuedKey)) {
|
|
213
|
+
const group = groups.get(e.queuedKey) ?? []; group.push(e); groups.set(e.queuedKey, group);
|
|
214
|
+
}
|
|
215
|
+
const out = new Map<string, boolean>();
|
|
216
|
+
for (const [key, group] of groups) {
|
|
217
|
+
const mixedAt = index.legacyCopies.get(key)?.mixedAt;
|
|
218
|
+
if (mixedAt !== undefined && mixedAt <= cutoff) continue;
|
|
219
|
+
group.sort((a, b) => (a.queuedFirstOrder ?? a.order) - (b.queuedFirstOrder ?? b.order));
|
|
220
|
+
const firstDelivered = group.length - counts.get(key)!;
|
|
221
|
+
group.forEach((e, i) => out.set(e.uuid, i >= firstDelivered));
|
|
222
|
+
}
|
|
223
|
+
return out;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function localPrompt(content: unknown): Entry["localPrompt"] {
|
|
227
|
+
const text = typeof content === "string" ? content : Array.isArray(content) ? content.findLast((b) => b?.type === "text")?.text : undefined;
|
|
228
|
+
if (typeof text !== "string") return undefined;
|
|
229
|
+
if (text.startsWith("<local-command-caveat>")) return "caveat";
|
|
230
|
+
if (text.startsWith("<command-name>")) return "record";
|
|
231
|
+
if (text.startsWith("<local-command-stdout>") || text.startsWith("<local-command-stderr>")) return "output";
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function completedLocalCommands(chain: Entry[]): Set<string> {
|
|
235
|
+
const out = new Set<string>();
|
|
236
|
+
for (let i = 0; i < chain.length; i++) {
|
|
237
|
+
const e = chain[i]!;
|
|
238
|
+
if (e.type !== "user" || !e.isMeta || e.localPrompt !== "caveat") continue;
|
|
239
|
+
let record = false;
|
|
240
|
+
for (let j = i + 1; j < chain.length; j++) {
|
|
241
|
+
const next = chain[j]!;
|
|
242
|
+
if (next.type === "assistant") break;
|
|
243
|
+
if (next.type !== "user" || next.isMeta) continue;
|
|
244
|
+
if (next.localPrompt === "record" && !record) record = true;
|
|
245
|
+
else if (next.localPrompt !== "output" || !record) break;
|
|
246
|
+
out.add(next.uuid);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return out;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Match the SDK's forwarded-intent schema: only a nonempty lineage is required;
|
|
253
|
+
// malformed metadata must not suppress an otherwise visible queued prompt.
|
|
254
|
+
function forwardedIntent(value: unknown): boolean {
|
|
255
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
256
|
+
const lineage = (value as { lineage?: unknown }).lineage;
|
|
257
|
+
return typeof lineage === "string" && lineage.length > 0;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// These SDK transcript sentinels terminate delivery, rather than beginning a new
|
|
261
|
+
// user prompt. Keep only the classification in the offset index, never the text.
|
|
262
|
+
const INTERRUPTION_PREFIXES = [
|
|
263
|
+
"[Request interrupted by user]",
|
|
264
|
+
"[Request interrupted by user for tool use]",
|
|
265
|
+
"[Tool call did not complete: the turn was ended to deliver the message that follows. Nothing refused it; re-run it if still needed.]",
|
|
266
|
+
"[Tool call interrupted: the session ended before this call's result was recorded, so its outcome is unknown. Check whether it took effect before relying on it or running it again.]",
|
|
267
|
+
"[Tool call result not in this copy: this session was copied from another session before that session recorded this call's result. The call may have finished there, may still be running there, or may never have run. Check whether it took effect before relying on it or running it again.]",
|
|
268
|
+
"The user doesn't want to take this action right now. STOP what you are doing and wait for the user to tell you how to proceed.",
|
|
269
|
+
"[Tool call skipped: the turn was stopped before this call ran, by the check whose denial is on another call in this batch. Nothing refused this call and it had no effects; re-run it if still needed.]",
|
|
270
|
+
"[Tool call skipped: the turn ended to deliver the message that follows before this call ran. Nothing refused it; re-run it if still needed.]",
|
|
271
|
+
];
|
|
272
|
+
function interruptedContent(content: unknown): boolean {
|
|
273
|
+
const sentinel = (value: unknown) => typeof value === "string" && INTERRUPTION_PREFIXES.some((prefix) => value.startsWith(prefix));
|
|
274
|
+
if (typeof content === "string") return sentinel(content);
|
|
275
|
+
return Array.isArray(content) && content.length > 0 && content.every((block) =>
|
|
276
|
+
sentinel(block?.type === "text" ? block.text : block?.type === "tool_result" && block.is_error === true ? block.content : undefined));
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** Only non-conversation descendants of the selected leaf may be pending prompts.
|
|
280
|
+
* Ancestors of abandoned conversation branches are excluded even if they also
|
|
281
|
+
* descend from the selected leaf. Keep traversal iterative for long tails. */
|
|
282
|
+
function trailingQueued(entries: Map<string, Entry>, chain: Entry[], tail: Entry | undefined, cutoff: number): Entry[] {
|
|
283
|
+
if (!tail) return [];
|
|
284
|
+
const seen = new Set(chain.map((e) => e.uuid));
|
|
285
|
+
const blocked = new Set<string>();
|
|
286
|
+
const children = new Map<string, Entry[]>();
|
|
287
|
+
for (const e of entries.values()) {
|
|
288
|
+
if (e.order > cutoff) continue;
|
|
289
|
+
if (e.parentUuid && e.type !== "user" && e.type !== "assistant") {
|
|
290
|
+
const list = children.get(e.parentUuid) ?? []; list.push(e); children.set(e.parentUuid, list);
|
|
291
|
+
}
|
|
292
|
+
if ((e.type === "user" || e.type === "assistant") && !e.isSidechain && !e.teamName && !seen.has(e.uuid)) {
|
|
293
|
+
let parent = entries.get(e.parentUuid ?? "");
|
|
294
|
+
while (parent && !seen.has(parent.uuid) && !blocked.has(parent.uuid)) {
|
|
295
|
+
blocked.add(parent.uuid); parent = entries.get(parent.parentUuid ?? "");
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
const out: Entry[] = [];
|
|
300
|
+
const stack = [tail];
|
|
301
|
+
while (stack.length) {
|
|
302
|
+
const e = stack.pop()!;
|
|
303
|
+
if (e !== tail) {
|
|
304
|
+
if (seen.has(e.uuid)) continue;
|
|
305
|
+
seen.add(e.uuid);
|
|
306
|
+
if (e.queued && !blocked.has(e.uuid)) out.push(e);
|
|
307
|
+
}
|
|
308
|
+
const list = children.get(e.uuid) ?? [];
|
|
309
|
+
list.sort((a, b) => (a.timestamp ?? "").localeCompare(b.timestamp ?? ""));
|
|
310
|
+
for (let i = list.length - 1; i >= 0; i--) if (!seen.has(list[i]!.uuid)) stack.push(list[i]!);
|
|
311
|
+
}
|
|
312
|
+
return out;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** Match the SDK reader: assistant blocks can be siblings rather than ancestors,
|
|
316
|
+
* and their results can point at another block of the same API message. */
|
|
317
|
+
function completeAssistantMessages(entries: Map<string, Entry>, chain: Entry[], cutoff: number): Entry[] {
|
|
318
|
+
const assistants = chain.filter((e) => e.messageId);
|
|
319
|
+
if (!assistants.length) return chain;
|
|
320
|
+
const lastByMessage = new Map(assistants.map((e) => [e.messageId!, e]));
|
|
321
|
+
const groups = new Map<string, Entry[]>();
|
|
322
|
+
const toolOwners = new Map<string, Entry | null>();
|
|
323
|
+
const results: Entry[] = [];
|
|
324
|
+
for (const entry of entries.values()) {
|
|
325
|
+
if (entry.order > cutoff) continue;
|
|
326
|
+
if (entry.messageId) {
|
|
327
|
+
const group = groups.get(entry.messageId) ?? [];
|
|
328
|
+
group.push(entry); groups.set(entry.messageId, group);
|
|
329
|
+
for (const id of entry.tools ?? []) {
|
|
330
|
+
const owner = toolOwners.get(id);
|
|
331
|
+
toolOwners.set(id, owner === undefined || owner !== null && owner.messageId === entry.messageId ? entry : null);
|
|
332
|
+
}
|
|
333
|
+
} else if (entry.type === "user" && entry.parentUuid && entry.results?.length) results.push(entry);
|
|
334
|
+
}
|
|
335
|
+
const byAssistant = new Map<string, Map<string, Entry>>();
|
|
336
|
+
const associate = (id: string, result: Entry) => {
|
|
337
|
+
const list = byAssistant.get(id) ?? new Map<string, Entry>();
|
|
338
|
+
list.set(result.uuid, result); byAssistant.set(id, list);
|
|
339
|
+
};
|
|
340
|
+
const sameAgent = (a: Entry, b: Entry) => !!a.isSidechain === !!b.isSidechain && a.agentId === b.agentId;
|
|
341
|
+
for (const result of results) {
|
|
342
|
+
associate(result.parentUuid!, result);
|
|
343
|
+
const source = entries.get(result.sourceToolAssistantUUID ?? "");
|
|
344
|
+
if (source && sameAgent(source, result)) associate(source.uuid, result);
|
|
345
|
+
for (const id of result.results ?? []) {
|
|
346
|
+
const owner = toolOwners.get(id);
|
|
347
|
+
if (owner && sameAgent(owner, result)) associate(owner.uuid, result);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
const seen = new Set(chain.map((e) => e.uuid));
|
|
351
|
+
const existingResults = new Set(chain.flatMap((e) => e.results ?? []));
|
|
352
|
+
const additions = new Map<string, Entry[]>();
|
|
353
|
+
const byTimestamp = (a: Entry, b: Entry) => (a.timestamp ?? "").localeCompare(b.timestamp ?? "");
|
|
354
|
+
for (const [messageId, last] of lastByMessage) {
|
|
355
|
+
const group = groups.get(messageId) ?? [last];
|
|
356
|
+
const groupIds = new Set(group.map((e) => e.uuid));
|
|
357
|
+
const siblings = group.filter((e) => !seen.has(e.uuid));
|
|
358
|
+
const direct: Entry[] = [];
|
|
359
|
+
const indirect: Entry[] = [];
|
|
360
|
+
const resultIds = new Set<string>();
|
|
361
|
+
for (const block of group) for (const result of byAssistant.get(block.uuid)?.values() ?? []) {
|
|
362
|
+
if (seen.has(result.uuid) || resultIds.has(result.uuid)) continue;
|
|
363
|
+
resultIds.add(result.uuid);
|
|
364
|
+
(groupIds.has(result.parentUuid!) ? direct : indirect).push(result);
|
|
365
|
+
}
|
|
366
|
+
// Global results are read-only membership checks. Each group stores only
|
|
367
|
+
// its own additions, avoiding a whole-branch Set copy per API message.
|
|
368
|
+
const included = new Set(direct.flatMap((e) => e.results ?? []));
|
|
369
|
+
indirect.sort((a, b) => a.order - b.order);
|
|
370
|
+
for (const result of indirect) {
|
|
371
|
+
if (!result.results?.some((id) => !existingResults.has(id) && !included.has(id) && groupIds.has(toolOwners.get(id)?.uuid ?? ""))) continue;
|
|
372
|
+
for (const id of result.results) included.add(id);
|
|
373
|
+
direct.push(result);
|
|
374
|
+
}
|
|
375
|
+
siblings.sort(byTimestamp); direct.sort(byTimestamp);
|
|
376
|
+
const extra = [...siblings, ...direct];
|
|
377
|
+
for (const entry of extra) seen.add(entry.uuid);
|
|
378
|
+
additions.set(last.uuid, extra);
|
|
379
|
+
}
|
|
380
|
+
return chain.flatMap((entry) => [entry, ...(additions.get(entry.uuid) ?? [])]);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
async function read(handle: fs.FileHandle, entry: Entry): Promise<RecordLike> {
|
|
384
|
+
const buf = Buffer.allocUnsafe(entry.length);
|
|
385
|
+
let at = 0;
|
|
386
|
+
while (at < buf.length) {
|
|
387
|
+
const r = await handle.read(buf, at, buf.length - at, entry.offset + at);
|
|
388
|
+
if (!r.bytesRead) throw new HistoryError(409, "会话文件已变化,请重新加载");
|
|
389
|
+
at += r.bytesRead;
|
|
390
|
+
}
|
|
391
|
+
return JSON.parse(buf.toString("utf8"));
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/** Control-state reader: same index, lock, UUID dedup and native branch as
|
|
395
|
+
* history, but retain effect attachments and never hydrate ordinary text/images. */
|
|
396
|
+
export async function nativeGoalBranch(file: string | undefined, anchor?: string | null, legacyQuestion?: string | null): Promise<GoalRecord[]> {
|
|
397
|
+
return await withIndex(file, async (index, handle) => {
|
|
398
|
+
const { chain } = selectSessionBranch(index.entries, anchor, false, true);
|
|
399
|
+
const records: GoalRecord[] = [];
|
|
400
|
+
let foundQuestion = false;
|
|
401
|
+
for (const entry of chain) {
|
|
402
|
+
const record: GoalRecord = { type: entry.type, subtype: entry.subtype, uuid: entry.uuid, isMeta: entry.isMeta, localCommandCaveat: entry.localPrompt === "caveat", forkedFrom: entry.inherited || undefined, command: entry.goalCommand };
|
|
403
|
+
if (entry.attachmentType === "goal_status") record.attachment = (await read(handle, entry)).attachment;
|
|
404
|
+
else if (entry.queued) record.attachment = { type: "queued_command", source_uuid: entry.sourceUuid };
|
|
405
|
+
if (legacyQuestion && !foundQuestion && (entry.type === "user" || entry.queued)) {
|
|
406
|
+
const raw = await read(handle, entry);
|
|
407
|
+
if (recordText(raw as GoalRecord) === legacyQuestion) { record.message = { content: legacyQuestion }; foundQuestion = true; }
|
|
408
|
+
}
|
|
409
|
+
records.push(record);
|
|
410
|
+
}
|
|
411
|
+
return records;
|
|
412
|
+
}) ?? [];
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function normalized(r: RecordLike, entry: Entry): SdkMessageLike {
|
|
416
|
+
if (r.attachment?.type === "queued_command") return { type: "user", uuid: entry.sourceUuid ?? r.uuid, isQueuedCommand: true, is_meta: r.attachment.isMeta === true || undefined, origin: r.attachment.origin, timestamp: r.timestamp, message: { role: "user", content: r.attachment.prompt }, parent_tool_use_id: null };
|
|
417
|
+
return { type: r.type, subtype: r.subtype, uuid: r.uuid, message: r.message, content: r.content, isCompactSummary: r.isCompactSummary,
|
|
418
|
+
isCompletedLocalCommand: entry.completedLocalCommand || undefined,
|
|
419
|
+
is_meta: r.isMeta === true || r.isCompactSummary === true || undefined, origin: r.origin, parent_tool_use_id: null, tool_use_result: r.toolUseResult ?? r.tool_use_result, timestamp: r.timestamp };
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
type Cursor = { g: string; size: number; mtime: number; id: string; direction: "older" | "newer" };
|
|
423
|
+
const cursor = (index: Index, id: string, direction: Cursor["direction"]) => Buffer.from(JSON.stringify({ g: index.generation, size: index.fileSize, mtime: index.mtime, id, direction })).toString("base64url");
|
|
424
|
+
export type PageOptions = { cursor?: string; before?: string; after?: string; anchor?: string | null; sidechain?: boolean };
|
|
425
|
+
|
|
426
|
+
export async function historyPage(file: string | undefined, options: PageOptions = {}): Promise<HistoryPage> {
|
|
427
|
+
return await withIndex(file, async (index, handle) => {
|
|
428
|
+
const chain = branch(index, options.anchor, options.sidechain);
|
|
429
|
+
let direction: Cursor["direction"] = options.after ? "newer" : "older";
|
|
430
|
+
let id = options.before ?? options.after;
|
|
431
|
+
if (options.cursor) {
|
|
432
|
+
let c: Cursor;
|
|
433
|
+
try { c = JSON.parse(Buffer.from(options.cursor, "base64url").toString()); } catch { throw new HistoryError(400, "无效的历史游标"); }
|
|
434
|
+
if (c.g !== index.generation || index.fileSize < c.size || index.fileSize === c.size && index.mtime !== c.mtime) throw new HistoryError(409, "会话文件已变化,请重新加载");
|
|
435
|
+
if (!c.id || !["older", "newer"].includes(c.direction)) throw new HistoryError(400, "无效的历史游标");
|
|
436
|
+
direction = c.direction; id = c.id;
|
|
437
|
+
}
|
|
438
|
+
let boundary = id ? chain.findIndex((e) => e.uuid === id || e.sourceUuid === id) : chain.length;
|
|
439
|
+
if (boundary < 0) throw new HistoryError(409, "消息已不在当前分支,请重新加载");
|
|
440
|
+
const selected: { entry: Entry; message: SdkMessageLike }[] = [];
|
|
441
|
+
let bytes = 0;
|
|
442
|
+
for (let i = direction === "older" ? boundary - 1 : boundary + 1; i >= 0 && i < chain.length && selected.length < PAGE_LIMIT; i += direction === "older" ? -1 : 1) {
|
|
443
|
+
const entry = chain[i]!;
|
|
444
|
+
const message = displayMessage(normalized(await read(handle, entry), entry), entry.length * 2);
|
|
445
|
+
const size = Buffer.byteLength(JSON.stringify(message));
|
|
446
|
+
if (selected.length && bytes + size > PAGE_BYTES) break;
|
|
447
|
+
selected.push({ entry, message }); bytes += size;
|
|
448
|
+
}
|
|
449
|
+
if (direction === "older") selected.reverse();
|
|
450
|
+
const first = selected[0]?.entry;
|
|
451
|
+
const last = selected.at(-1)?.entry;
|
|
452
|
+
// Fetch only results belonging to visible tool calls, even when they lie on another page.
|
|
453
|
+
const wanted = new Set(selected.flatMap((e) => e.entry.tools ?? []));
|
|
454
|
+
if (wanted.size) {
|
|
455
|
+
for (const entry of chain) {
|
|
456
|
+
if (!entry.results?.some((id) => wanted.has(id))) continue;
|
|
457
|
+
const raw = await read(handle, entry);
|
|
458
|
+
const blocks = (raw.message as { content?: unknown[] })?.content;
|
|
459
|
+
for (const block of blocks ?? []) {
|
|
460
|
+
const b = block as { type: string; tool_use_id: string; content: unknown; is_error?: boolean };
|
|
461
|
+
if (b.type !== "tool_result" || !wanted.has(b.tool_use_id)) continue;
|
|
462
|
+
const owner = selected.find((e) => e.entry.tools?.includes(b.tool_use_id))!;
|
|
463
|
+
const results = (owner.message.linkedToolResults ??= {}) as Record<string, unknown>;
|
|
464
|
+
const resultUuids = (owner.message._toolResultUuids ??= {}) as Record<string, string>;
|
|
465
|
+
resultUuids[b.tool_use_id] = entry.uuid;
|
|
466
|
+
const result = { content: b.content, isError: !!b.is_error, structured: raw.toolUseResult ?? raw.tool_use_result };
|
|
467
|
+
const bodySize = Buffer.byteLength(JSON.stringify(result));
|
|
468
|
+
results[b.tool_use_id] = bodySize < 16 * 1024 && bytes + bodySize < PAGE_BYTES
|
|
469
|
+
? result
|
|
470
|
+
: { content: "结果较大,请加载完整消息查看。", isError: !!b.is_error, deferredUuid: entry.uuid };
|
|
471
|
+
bytes += Math.min(bodySize, 16 * 1024);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
const todoIds = new Map(chain.flatMap((e) => e.todoIds ?? []).map((r) => [r.id, r]));
|
|
476
|
+
const todos = deriveTodos(chain.flatMap((e) => e.todoItems ?? []).map((item) => {
|
|
477
|
+
const result = todoIds.get(item.id);
|
|
478
|
+
return result ? { ...item, result: { content: `Task #${result.taskId}`, isError: result.isError, structured: { task: { id: result.taskId } } } } : item;
|
|
479
|
+
}));
|
|
480
|
+
return { items: selected.map((s) => s.message), generation: index.generation, todos,
|
|
481
|
+
older: first && chain.indexOf(first) > 0 ? cursor(index, first.uuid, "older") : undefined,
|
|
482
|
+
newer: last && chain.indexOf(last) < chain.length - 1 ? cursor(index, last.uuid, "newer") : undefined };
|
|
483
|
+
}) ?? { items: [], generation: "empty" };
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
export async function historyMessage(file: string | undefined, uuid: string, anchor?: string | null, sidechain = false): Promise<SdkMessageLike> {
|
|
487
|
+
const found = await withIndex(file, async (index, handle) => {
|
|
488
|
+
const entry = branch(index, anchor, sidechain).find((e) => e.uuid === uuid || e.sourceUuid === uuid);
|
|
489
|
+
if (!entry) throw new HistoryError(404, "消息不存在");
|
|
490
|
+
if (entry.length * 2 > SESSION_BYTES / 2) throw new HistoryError(400, "这条消息超过缓存上限,请导出会话查看");
|
|
491
|
+
return normalized(await read(handle, entry), entry);
|
|
492
|
+
});
|
|
493
|
+
if (!found) throw new HistoryError(404, "消息不存在");
|
|
494
|
+
return found;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/** Resolve a Read from this visible transcript, never from a client-supplied path. */
|
|
498
|
+
export async function historyReadPath(file: string | undefined, toolId: string, anchor?: string | null, sidechain = false): Promise<{ path: string; cwd?: string }> {
|
|
499
|
+
const found = await withIndex(file, async (index, handle) => {
|
|
500
|
+
const entry = branch(index, anchor, sidechain).find((e) => e.type === "assistant" && e.tools?.includes(toolId));
|
|
501
|
+
if (!entry || entry.length * 2 > SESSION_BYTES / 2) return;
|
|
502
|
+
const record = await read(handle, entry);
|
|
503
|
+
const content = (record.message as { content?: unknown[] } | undefined)?.content;
|
|
504
|
+
const call = content?.find((block) => {
|
|
505
|
+
const b = block as { type?: string; id?: string; name?: string } | null;
|
|
506
|
+
return b?.type === "tool_use" && b.id === toolId && b.name === "Read";
|
|
507
|
+
}) as { input?: { file_path?: unknown } } | undefined;
|
|
508
|
+
if (typeof call?.input?.file_path !== "string" || !call.input.file_path) return;
|
|
509
|
+
return { path: call.input.file_path, cwd: typeof record.cwd === "string" ? record.cwd : undefined };
|
|
510
|
+
});
|
|
511
|
+
if (!found) throw new HistoryError(404, "图片调用不存在");
|
|
512
|
+
return found;
|
|
513
|
+
}
|