add compaction (#183)
This commit is contained in:
1 parent
7fc229e457
commit
c722104d6f
11 files changed
+1197
-167
No files matched your search
@@ -139,9 +139,9 @@ Treat the bot token like a password.
|
||||
|
||||
---
|
||||
|
||||
## MCP & project settings
|
||||
## Project settings
|
||||
|
||||
Project file: **`.grok/settings.json`** — e.g. current model, **`mcpServers`** for stdio/http/sse MCP servers. Manage interactively with **`/mcps`** in the TUI.
|
||||
Project file: **`.grok/settings.json`** — e.g. the current model for this project.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"husky": "^9.1.7",
|
||||
"lint-staged": "^16.4.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.1.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -62,6 +63,12 @@
|
||||
|
||||
"@dimforge/rapier2d-simd-compat": ["@dimforge/rapier2d-simd-compat@0.17.3", "", {}, "sha512-bijvwWz6NHsNj5e5i1vtd3dU2pDhthSaTUZSh14DUGGKJfw8eMnlWZsxwHBxB/a3AXVNDjL9abuHw1k9FGR+jg=="],
|
||||
|
||||
"@emnapi/core": ["@emnapi/core@1.9.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" } }, "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA=="],
|
||||
|
||||
"@emnapi/runtime": ["@emnapi/runtime@1.9.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA=="],
|
||||
|
||||
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg=="],
|
||||
|
||||
"@grammyjs/types": ["@grammyjs/types@3.25.0", "", {}, "sha512-iN9i5p+8ZOu9OMxWNcguojQfz4K/PDyMPOnL7PPCON+SoA/F8OKMH3uR7CVUkYfdNe0GCz8QOzAWrnqusQYFOg=="],
|
||||
|
||||
"@hono/node-server": ["@hono/node-server@1.19.11", "", { "peerDependencies": { "hono": "^4" } }, "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g=="],
|
||||
@@ -122,8 +129,12 @@
|
||||
|
||||
"@jimp/utils": ["@jimp/utils@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "tinycolor2": "^1.6.0" } }, "sha512-gqFTGEosKbOkYF/WFj26jMHOI5OH2jeP1MmC/zbK6BF6VJBf8rIC5898dPfSzZEbSA0wbbV5slbntWVc5PKLFA=="],
|
||||
|
||||
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
|
||||
|
||||
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.27.1", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA=="],
|
||||
|
||||
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" } }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="],
|
||||
|
||||
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
|
||||
|
||||
"@opentui/core": ["@opentui/core@0.1.88", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "marked": "17.0.1", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.88", "@opentui/core-darwin-x64": "0.1.88", "@opentui/core-linux-arm64": "0.1.88", "@opentui/core-linux-x64": "0.1.88", "@opentui/core-win32-arm64": "0.1.88", "@opentui/core-win32-x64": "0.1.88", "bun-webgpu": "0.1.5", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-eaDVZfAzZraddOIkgWSHMVkyaY0O20foYnPWKPQx1TY4t7G1oatIoan2zkytx67epW+4BZQ9vGib+61/uNM1MA=="],
|
||||
@@ -142,18 +153,74 @@
|
||||
|
||||
"@opentui/react": ["@opentui/react@0.1.88", "", { "dependencies": { "@opentui/core": "0.1.88", "react-reconciler": "^0.32.0" }, "peerDependencies": { "react": ">=19.0.0", "react-devtools-core": "^7.0.1", "ws": "^8.18.0" } }, "sha512-EfEUBlGgCx9LqpbB852L+m7F19NcR8yeUww+C8NytIQeVKJbFIhSLu3r3wjBEGNoYiRk1DtAwWBpG5YaVvkXXw=="],
|
||||
|
||||
"@oxc-project/types": ["@oxc-project/types@0.120.0", "", {}, "sha512-k1YNu55DuvAip/MGE1FTsIuU3FUCn6v/ujG9V7Nq5Df/kX2CWb13hhwD0lmJGMGqE+bE1MXvv9SZVnMzEXlWcg=="],
|
||||
|
||||
"@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.10", "", { "os": "android", "cpu": "arm64" }, "sha512-jOHxwXhxmFKuXztiu1ORieJeTbx5vrTkcOkkkn2d35726+iwhrY1w/+nYY/AGgF12thg33qC3R1LMBF5tHTZHg=="],
|
||||
|
||||
"@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-rc.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gED05Teg/vtTZbIJBc4VNMAxAFDUPkuO/rAIyyxZjTj1a1/s6z5TII/5yMGZ0uLRCifEtwUQn8OlYzuYc0m70w=="],
|
||||
|
||||
"@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-rc.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-rI15NcM1mA48lqrIxVkHfAqcyFLcQwyXWThy+BQ5+mkKKPvSO26ir+ZDp36AgYoYVkqvMcdS8zOE6SeBsR9e8A=="],
|
||||
|
||||
"@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-rc.10", "", { "os": "freebsd", "cpu": "x64" }, "sha512-XZRXHdTa+4ME1MuDVp021+doQ+z6Ei4CCFmNc5/sKbqb8YmkiJdj8QKlV3rCI0AJtAeSB5n0WGPuJWNL9p/L2w=="],
|
||||
|
||||
"@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.10", "", { "os": "linux", "cpu": "arm" }, "sha512-R0SQMRluISSLzFE20sPWYHVmJdDQnRyc/FzSCN72BqQmh2SOZUFG+N3/vBZpR4C6WpEUVYJLrYUXaj43sJsNLA=="],
|
||||
|
||||
"@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-rc.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-Y1reMrV/o+cwpduYhJuOE3OMKx32RMYCidf14y+HssARRmhDuWXJ4yVguDg2R/8SyyGNo+auzz64LnPK9Hq6jg=="],
|
||||
|
||||
"@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-rc.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-vELN+HNb2IzuzSBUOD4NHmP9yrGwl1DVM29wlQvx1OLSclL0NgVWnVDKl/8tEks79EFek/kebQKnNJkIAA4W2g=="],
|
||||
|
||||
"@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.10", "", { "os": "linux", "cpu": "ppc64" }, "sha512-ZqrufYTgzxbHwpqOjzSsb0UV/aV2TFIY5rP8HdsiPTv/CuAgCRjM6s9cYFwQ4CNH+hf9Y4erHW1GjZuZ7WoI7w=="],
|
||||
|
||||
"@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.0-rc.10", "", { "os": "linux", "cpu": "s390x" }, "sha512-gSlmVS1FZJSRicA6IyjoRoKAFK7IIHBs7xJuHRSmjImqk3mPPWbR7RhbnfH2G6bcmMEllCt2vQ/7u9e6bBnByg=="],
|
||||
|
||||
"@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-rc.10", "", { "os": "linux", "cpu": "x64" }, "sha512-eOCKUpluKgfObT2pHjztnaWEIbUabWzk3qPZ5PuacuPmr4+JtQG4k2vGTY0H15edaTnicgU428XW/IH6AimcQw=="],
|
||||
|
||||
"@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-rc.10", "", { "os": "linux", "cpu": "x64" }, "sha512-Xdf2jQbfQowJnLcgYfD/m0Uu0Qj5OdxKallD78/IPPfzaiaI4KRAwZzHcKQ4ig1gtg1SuzC7jovNiM2TzQsBXA=="],
|
||||
|
||||
"@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-rc.10", "", { "os": "none", "cpu": "arm64" }, "sha512-o1hYe8hLi1EY6jgPFyxQgQ1wcycX+qz8eEbVmot2hFkgUzPxy9+kF0u0NIQBeDq+Mko47AkaFFaChcvZa9UX9Q=="],
|
||||
|
||||
"@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-rc.10", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.1" }, "cpu": "none" }, "sha512-Ugv9o7qYJudqQO5Y5y2N2SOo6S4WiqiNOpuQyoPInnhVzCY+wi/GHltcLHypG9DEUYMB0iTB/huJrpadiAcNcA=="],
|
||||
|
||||
"@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-rc.10", "", { "os": "win32", "cpu": "arm64" }, "sha512-7UODQb4fQUNT/vmgDZBl3XOBAIOutP5R3O/rkxg0aLfEGQ4opbCgU5vOw/scPe4xOqBwL9fw7/RP1vAMZ6QlAQ=="],
|
||||
|
||||
"@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-rc.10", "", { "os": "win32", "cpu": "x64" }, "sha512-PYxKHMVHOb5NJuDL53vBUl1VwUjymDcYI6rzpIni0C9+9mTiJedvUxSk7/RPp7OOAm3v+EjgMu9bIy3N6b408w=="],
|
||||
|
||||
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.10", "", {}, "sha512-UkVDEFk1w3mveXeKgaTuYfKWtPbvgck1dT8TUG3bnccrH0XtLTuAyfCoks4Q/M5ZGToSVJTIQYCzy2g/atAOeg=="],
|
||||
|
||||
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
"@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],
|
||||
|
||||
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
||||
|
||||
"@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],
|
||||
|
||||
"@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
|
||||
|
||||
"@types/diff": ["@types/diff@8.0.0", "", { "dependencies": { "diff": "*" } }, "sha512-o7jqJM04gfaYrdCecCVMbZhNdG6T1MHg/oQoRFdERLV+4d+V7FijhiEAbFu0Usww84Yijk9yH58U4Jk4HbtzZw=="],
|
||||
|
||||
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||
|
||||
"@types/node": ["@types/node@22.19.15", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg=="],
|
||||
|
||||
"@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="],
|
||||
|
||||
"@vercel/oidc": ["@vercel/oidc@3.1.0", "", {}, "sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w=="],
|
||||
|
||||
"@vitest/expect": ["@vitest/expect@4.1.0", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.0", "@vitest/utils": "4.1.0", "chai": "^6.2.2", "tinyrainbow": "^3.0.3" } }, "sha512-EIxG7k4wlWweuCLG9Y5InKFwpMEOyrMb6ZJ1ihYu02LVj/bzUwn2VMU+13PinsjRW75XnITeFrQBMH5+dLvCDA=="],
|
||||
|
||||
"@vitest/mocker": ["@vitest/mocker@4.1.0", "", { "dependencies": { "@vitest/spy": "4.1.0", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-evxREh+Hork43+Y4IOhTo+h5lGmVRyjqI739Rz4RlUPqwrkFFDF6EMvOOYjTx4E8Tl6gyCLRL8Mu7Ry12a13Tw=="],
|
||||
|
||||
"@vitest/pretty-format": ["@vitest/pretty-format@4.1.0", "", { "dependencies": { "tinyrainbow": "^3.0.3" } }, "sha512-3RZLZlh88Ib0J7NQTRATfc/3ZPOnSUn2uDBUoGNn5T36+bALixmzphN26OUD3LRXWkJu4H0s5vvUeqBiw+kS0A=="],
|
||||
|
||||
"@vitest/runner": ["@vitest/runner@4.1.0", "", { "dependencies": { "@vitest/utils": "4.1.0", "pathe": "^2.0.3" } }, "sha512-Duvx2OzQ7d6OjchL+trw+aSrb9idh7pnNfxrklo14p3zmNL4qPCDeIJAK+eBKYjkIwG96Bc6vYuxhqDXQOWpoQ=="],
|
||||
|
||||
"@vitest/snapshot": ["@vitest/snapshot@4.1.0", "", { "dependencies": { "@vitest/pretty-format": "4.1.0", "@vitest/utils": "4.1.0", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-0Vy9euT1kgsnj1CHttwi9i9o+4rRLEaPRSOJ5gyv579GJkNpgJK+B4HSv/rAWixx2wdAFci1X4CEPjiu2bXIMg=="],
|
||||
|
||||
"@vitest/spy": ["@vitest/spy@4.1.0", "", {}, "sha512-pz77k+PgNpyMDv2FV6qmk5ZVau6c3R8HC8v342T2xlFxQKTrSeYw9waIJG8KgV9fFwAtTu4ceRzMivPTH6wSxw=="],
|
||||
|
||||
"@vitest/utils": ["@vitest/utils@4.1.0", "", { "dependencies": { "@vitest/pretty-format": "4.1.0", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.0.3" } }, "sha512-XfPXT6a8TZY3dcGY8EdwsBulFCIw+BeeX0RZn2x/BtiY/75YGh8FeWGG8QISN/WhaqSrE2OrlDgtF8q5uhOTmw=="],
|
||||
|
||||
"@webgpu/types": ["@webgpu/types@0.1.69", "", {}, "sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ=="],
|
||||
|
||||
"abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
|
||||
@@ -174,6 +241,8 @@
|
||||
|
||||
"any-base": ["any-base@1.1.0", "", {}, "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg=="],
|
||||
|
||||
"assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
|
||||
|
||||
"await-to-js": ["await-to-js@3.0.0", "", {}, "sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g=="],
|
||||
|
||||
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
|
||||
@@ -202,6 +271,8 @@
|
||||
|
||||
"call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
|
||||
|
||||
"chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="],
|
||||
|
||||
"cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="],
|
||||
|
||||
"cli-truncate": ["cli-truncate@5.2.0", "", { "dependencies": { "slice-ansi": "^8.0.0", "string-width": "^8.2.0" } }, "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw=="],
|
||||
@@ -214,6 +285,8 @@
|
||||
|
||||
"content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
|
||||
|
||||
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
|
||||
|
||||
"cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
|
||||
|
||||
"cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
|
||||
@@ -228,6 +301,8 @@
|
||||
|
||||
"depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
|
||||
|
||||
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||
|
||||
"diff": ["diff@8.0.3", "", {}, "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ=="],
|
||||
|
||||
"dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="],
|
||||
@@ -246,10 +321,14 @@
|
||||
|
||||
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
|
||||
|
||||
"es-module-lexer": ["es-module-lexer@2.0.0", "", {}, "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw=="],
|
||||
|
||||
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
|
||||
|
||||
"escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
|
||||
|
||||
"estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
|
||||
|
||||
"etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
|
||||
|
||||
"event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="],
|
||||
@@ -264,6 +343,8 @@
|
||||
|
||||
"exif-parser": ["exif-parser@0.1.12", "", {}, "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw=="],
|
||||
|
||||
"expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="],
|
||||
|
||||
"express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="],
|
||||
|
||||
"express-rate-limit": ["express-rate-limit@8.3.1", "", { "dependencies": { "ip-address": "10.1.0" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw=="],
|
||||
@@ -272,6 +353,8 @@
|
||||
|
||||
"fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="],
|
||||
|
||||
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||
|
||||
"file-type": ["file-type@16.5.4", "", { "dependencies": { "readable-web-to-node-stream": "^3.0.0", "strtok3": "^6.2.4", "token-types": "^4.1.1" } }, "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw=="],
|
||||
|
||||
"finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="],
|
||||
@@ -280,6 +363,8 @@
|
||||
|
||||
"fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
|
||||
|
||||
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||
|
||||
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
||||
|
||||
"get-east-asian-width": ["get-east-asian-width@1.5.0", "", {}, "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA=="],
|
||||
@@ -334,12 +419,38 @@
|
||||
|
||||
"json-schema-typed": ["json-schema-typed@8.0.2", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="],
|
||||
|
||||
"lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="],
|
||||
|
||||
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="],
|
||||
|
||||
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="],
|
||||
|
||||
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="],
|
||||
|
||||
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="],
|
||||
|
||||
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="],
|
||||
|
||||
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="],
|
||||
|
||||
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="],
|
||||
|
||||
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="],
|
||||
|
||||
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="],
|
||||
|
||||
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="],
|
||||
|
||||
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
|
||||
|
||||
"lint-staged": ["lint-staged@16.4.0", "", { "dependencies": { "commander": "^14.0.3", "listr2": "^9.0.5", "picomatch": "^4.0.3", "string-argv": "^0.3.2", "tinyexec": "^1.0.4", "yaml": "^2.8.2" }, "bin": { "lint-staged": "bin/lint-staged.js" } }, "sha512-lBWt8hujh/Cjysw5GYVmZpFHXDCgZzhrOm8vbcUdobADZNOK/bRshr2kM3DfgrrtR1DQhfupW9gnIXOfiFi+bw=="],
|
||||
|
||||
"listr2": ["listr2@9.0.5", "", { "dependencies": { "cli-truncate": "^5.0.0", "colorette": "^2.0.20", "eventemitter3": "^5.0.1", "log-update": "^6.1.0", "rfdc": "^1.4.1", "wrap-ansi": "^9.0.0" } }, "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g=="],
|
||||
|
||||
"log-update": ["log-update@6.1.0", "", { "dependencies": { "ansi-escapes": "^7.0.0", "cli-cursor": "^5.0.0", "slice-ansi": "^7.1.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w=="],
|
||||
|
||||
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
||||
|
||||
"marked": ["marked@17.0.1", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg=="],
|
||||
|
||||
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
|
||||
@@ -358,6 +469,8 @@
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
||||
|
||||
"negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
|
||||
|
||||
"node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
|
||||
@@ -366,6 +479,8 @@
|
||||
|
||||
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
|
||||
|
||||
"obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="],
|
||||
|
||||
"omggif": ["omggif@1.0.10", "", {}, "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw=="],
|
||||
|
||||
"on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
|
||||
@@ -388,8 +503,12 @@
|
||||
|
||||
"path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="],
|
||||
|
||||
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||
|
||||
"peek-readable": ["peek-readable@4.1.0", "", {}, "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg=="],
|
||||
|
||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||
|
||||
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
||||
|
||||
"pixelmatch": ["pixelmatch@5.3.0", "", { "dependencies": { "pngjs": "^6.0.0" }, "bin": { "pixelmatch": "bin/pixelmatch" } }, "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q=="],
|
||||
@@ -400,6 +519,8 @@
|
||||
|
||||
"pngjs": ["pngjs@7.0.0", "", {}, "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow=="],
|
||||
|
||||
"postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="],
|
||||
|
||||
"process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="],
|
||||
|
||||
"proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
|
||||
@@ -426,6 +547,8 @@
|
||||
|
||||
"rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="],
|
||||
|
||||
"rolldown": ["rolldown@1.0.0-rc.10", "", { "dependencies": { "@oxc-project/types": "=0.120.0", "@rolldown/pluginutils": "1.0.0-rc.10" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.10", "@rolldown/binding-darwin-arm64": "1.0.0-rc.10", "@rolldown/binding-darwin-x64": "1.0.0-rc.10", "@rolldown/binding-freebsd-x64": "1.0.0-rc.10", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.10", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.10", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.10", "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.10", "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.10", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.10", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.10", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.10", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.10", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.10", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.10" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-q7j6vvarRFmKpgJUT8HCAUljkgzEp4LAhPlJUvQhA5LA1SUL36s5QCysMutErzL3EbNOZOkoziSx9iZC4FddKA=="],
|
||||
|
||||
"router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="],
|
||||
|
||||
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
|
||||
@@ -456,16 +579,24 @@
|
||||
|
||||
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
|
||||
|
||||
"siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="],
|
||||
|
||||
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
|
||||
|
||||
"simple-xml-to-json": ["simple-xml-to-json@1.2.4", "", {}, "sha512-3MY16e0ocMHL7N1ufpdObURGyX+lCo0T/A+y6VCwosLdH1HSda4QZl1Sdt/O+2qWp48WFi26XEp5rF0LoaL0Dg=="],
|
||||
|
||||
"slice-ansi": ["slice-ansi@8.0.0", "", { "dependencies": { "ansi-styles": "^6.2.3", "is-fullwidth-code-point": "^5.1.0" } }, "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg=="],
|
||||
|
||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||
|
||||
"stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
|
||||
|
||||
"stage-js": ["stage-js@1.0.1", "", {}, "sha512-cz14aPp/wY0s3bkb/B93BPP5ZAEhgBbRmAT3CCDqert8eCAqIpQ0RB2zpK8Ksxf+Pisl5oTzvPHtL4CVzzeHcw=="],
|
||||
|
||||
"statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
|
||||
|
||||
"std-env": ["std-env@4.0.0", "", {}, "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ=="],
|
||||
|
||||
"string-argv": ["string-argv@0.3.2", "", {}, "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q=="],
|
||||
|
||||
"string-width": ["string-width@8.2.0", "", { "dependencies": { "get-east-asian-width": "^1.5.0", "strip-ansi": "^7.1.2" } }, "sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw=="],
|
||||
@@ -478,16 +609,24 @@
|
||||
|
||||
"three": ["three@0.177.0", "", {}, "sha512-EiXv5/qWAaGI+Vz2A+JfavwYCMdGjxVsrn3oBwllUoqYeaBO75J63ZfyaQKoiLrqNHoTlUc6PFgMXnS0kI45zg=="],
|
||||
|
||||
"tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="],
|
||||
|
||||
"tinycolor2": ["tinycolor2@1.6.0", "", {}, "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw=="],
|
||||
|
||||
"tinyexec": ["tinyexec@1.0.4", "", {}, "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw=="],
|
||||
|
||||
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
||||
|
||||
"tinyrainbow": ["tinyrainbow@3.1.0", "", {}, "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw=="],
|
||||
|
||||
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
|
||||
|
||||
"token-types": ["token-types@4.2.1", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ=="],
|
||||
|
||||
"tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
|
||||
|
||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],
|
||||
|
||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
@@ -500,6 +639,10 @@
|
||||
|
||||
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
|
||||
|
||||
"vite": ["vite@8.0.1", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.3", "postcss": "^8.5.8", "rolldown": "1.0.0-rc.10", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-wt+Z2qIhfFt85uiyRt5LPU4oVEJBXj8hZNWKeqFG4gRG/0RaRGJ7njQCwzFVjO+v4+Ipmf5CY7VdmZRAYYBPHw=="],
|
||||
|
||||
"vitest": ["vitest@4.1.0", "", { "dependencies": { "@vitest/expect": "4.1.0", "@vitest/mocker": "4.1.0", "@vitest/pretty-format": "4.1.0", "@vitest/runner": "4.1.0", "@vitest/snapshot": "4.1.0", "@vitest/spy": "4.1.0", "@vitest/utils": "4.1.0", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.0.3", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.0", "@vitest/browser-preview": "4.1.0", "@vitest/browser-webdriverio": "4.1.0", "@vitest/ui": "4.1.0", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-YbDrMF9jM2Lqc++2530UourxZHmkKLxrs4+mYhEwqWS97WJ7wOYEkcr+QfRgJ3PW9wz3odRijLZjHEaRLTNbqw=="],
|
||||
|
||||
"web-tree-sitter": ["web-tree-sitter@0.25.10", "", { "peerDependencies": { "@types/emscripten": "^1.40.0" }, "optionalPeers": ["@types/emscripten"] }, "sha512-Y09sF44/13XvgVKgO2cNDw5rGk6s26MgoZPXLESvMXeefBf7i6/73eFurre0IsTW6E14Y0ArIzhUMmjoc7xyzA=="],
|
||||
|
||||
"webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
|
||||
@@ -508,6 +651,8 @@
|
||||
|
||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
|
||||
"why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="],
|
||||
|
||||
"wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="],
|
||||
|
||||
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
|
||||
|
||||
+4
-1
@@ -18,6 +18,8 @@
|
||||
"build": "tsc",
|
||||
"start": "bun run dist/index.js",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test": "bunx vitest run",
|
||||
"test:watch": "bunx vitest",
|
||||
"lint": "biome check src/",
|
||||
"format": "biome format src/",
|
||||
"format:fix": "biome format --write src/",
|
||||
@@ -60,7 +62,8 @@
|
||||
"@types/react": "^19.2.14",
|
||||
"husky": "^9.1.7",
|
||||
"lint-staged": "^16.4.0",
|
||||
"typescript": "^5.9.3"
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
|
||||
+236
-141
@@ -1,13 +1,17 @@
|
||||
import { type ModelMessage, stepCountIs, streamText, type ToolSet } from "ai";
|
||||
import { createProvider, generateTitle as genTitle, type XaiProvider } from "../grok/client";
|
||||
import { getModelInfo } from "../grok/models";
|
||||
import { createTools } from "../grok/tools";
|
||||
import { buildMcpToolSet } from "../mcp/runtime";
|
||||
import {
|
||||
appendCompaction,
|
||||
appendMessages,
|
||||
appendSystemMessage,
|
||||
buildChatEntries,
|
||||
getNextMessageSequence,
|
||||
getSessionTotalTokens,
|
||||
loadTranscript,
|
||||
loadTranscriptState,
|
||||
recordUsageEvent,
|
||||
SessionStore,
|
||||
} from "../storage/index";
|
||||
@@ -29,6 +33,17 @@ import type {
|
||||
import { loadCustomInstructions } from "../utils/instructions";
|
||||
import { loadMcpServers } from "../utils/settings";
|
||||
import { discoverSkills, formatSkillsForPrompt } from "../utils/skills";
|
||||
import {
|
||||
type CompactionSettings,
|
||||
createCompactionSummaryMessage,
|
||||
DEFAULT_KEEP_RECENT_TOKENS,
|
||||
DEFAULT_RESERVE_TOKENS,
|
||||
estimateConversationTokens,
|
||||
generateCompactionSummary,
|
||||
prepareCompaction,
|
||||
relaxCompactionSettings,
|
||||
shouldCompactContext,
|
||||
} from "./compaction";
|
||||
import { DelegationManager } from "./delegations";
|
||||
|
||||
const MAX_TOOL_ROUNDS = 400;
|
||||
@@ -198,7 +213,7 @@ export class Agent {
|
||||
private workspace: WorkspaceInfo | null = null;
|
||||
private session: SessionInfo | null = null;
|
||||
private messages: ModelMessage[] = [];
|
||||
private recordedTokens = 0;
|
||||
private messageSeqs: Array<number | null> = [];
|
||||
private abortController: AbortController | null = null;
|
||||
private maxToolRounds: number;
|
||||
private mode: AgentMode = "agent";
|
||||
@@ -230,8 +245,9 @@ export class Agent {
|
||||
this.workspace = this.sessionStore.getWorkspace();
|
||||
this.session = this.sessionStore.openSession(options.session, this.modelId, this.mode, this.bash.getCwd());
|
||||
this.mode = this.session.mode;
|
||||
this.messages = loadTranscript(this.session.id);
|
||||
this.recordedTokens = getSessionTotalTokens(this.session.id);
|
||||
const transcript = loadTranscriptState(this.session.id);
|
||||
this.messages = transcript.messages;
|
||||
this.messageSeqs = transcript.seqs;
|
||||
this.sessionStore.setModel(this.session.id, this.modelId);
|
||||
}
|
||||
}
|
||||
@@ -291,8 +307,7 @@ export class Agent {
|
||||
ratioRemaining: number;
|
||||
} {
|
||||
const system = buildSystemPrompt(this.bash.getCwd(), this.mode, this.planContext);
|
||||
const estimatedTokens = estimateTokens(`${system}\n${JSON.stringify(this.messages)}\n${inFlightText}`);
|
||||
const usedTokens = Math.min(contextWindow, Math.max(estimatedTokens, this.recordedTokens));
|
||||
const usedTokens = Math.min(contextWindow, estimateConversationTokens(system, this.messages, inFlightText));
|
||||
const remainingTokens = Math.max(0, contextWindow - usedTokens);
|
||||
|
||||
return {
|
||||
@@ -331,7 +346,7 @@ export class Agent {
|
||||
startNewSession(): SessionSnapshot | null {
|
||||
if (!this.sessionStore) {
|
||||
this.messages = [];
|
||||
this.recordedTokens = 0;
|
||||
this.messageSeqs = [];
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -339,7 +354,7 @@ export class Agent {
|
||||
this.workspace = this.sessionStore.getWorkspace();
|
||||
this.session = this.sessionStore.createSession(this.modelId, this.mode, this.bash.getCwd());
|
||||
this.messages = [];
|
||||
this.recordedTokens = 0;
|
||||
this.messageSeqs = [];
|
||||
return this.getSessionSnapshot();
|
||||
}
|
||||
|
||||
@@ -388,6 +403,7 @@ export class Agent {
|
||||
const idx = this.messages.lastIndexOf(userMessage);
|
||||
if (idx >= 0) {
|
||||
this.messages.splice(idx, 1);
|
||||
this.messageSeqs.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,10 +413,6 @@ export class Agent {
|
||||
model = this.modelId,
|
||||
): void {
|
||||
if (!usage) return;
|
||||
const total = usage.totalTokens ?? (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0);
|
||||
if (Number.isFinite(total) && total > 0) {
|
||||
this.recordedTokens += total;
|
||||
}
|
||||
if (this.session) {
|
||||
recordUsageEvent(this.session.id, source, model, usage);
|
||||
}
|
||||
@@ -411,9 +423,11 @@ export class Agent {
|
||||
const notifications = await this.delegations.consumeNotifications();
|
||||
for (const notification of notifications) {
|
||||
this.messages.push({ role: "system", content: notification.message });
|
||||
let seq: number | null = null;
|
||||
if (this.session) {
|
||||
appendSystemMessage(this.session.id, notification.message);
|
||||
seq = appendSystemMessage(this.session.id, notification.message);
|
||||
}
|
||||
this.messageSeqs.push(seq);
|
||||
}
|
||||
return notifications.map((notification) => notification.message);
|
||||
} catch {
|
||||
@@ -600,6 +614,62 @@ export class Agent {
|
||||
}
|
||||
}
|
||||
|
||||
private getCompactionSettings(): CompactionSettings {
|
||||
return {
|
||||
reserveTokens: Math.max(this.maxTokens, DEFAULT_RESERVE_TOKENS),
|
||||
keepRecentTokens: DEFAULT_KEEP_RECENT_TOKENS,
|
||||
};
|
||||
}
|
||||
|
||||
private async compactForContext(
|
||||
provider: XaiProvider,
|
||||
system: string,
|
||||
contextWindow: number,
|
||||
signal: AbortSignal,
|
||||
settings = this.getCompactionSettings(),
|
||||
force = false,
|
||||
): Promise<boolean> {
|
||||
if (!this.session) return false;
|
||||
|
||||
const preparation = prepareCompaction(this.messages, system, settings);
|
||||
if (!preparation) return false;
|
||||
if (!force && !shouldCompactContext(preparation.tokensBefore, contextWindow, settings)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const keptSeqs = this.messageSeqs.slice(preparation.firstKeptIndex);
|
||||
const firstKeptSeq = keptSeqs.find((seq): seq is number => seq !== null) ?? getNextMessageSequence(this.session.id);
|
||||
const summary = await generateCompactionSummary(provider, this.modelId, preparation, undefined, signal);
|
||||
|
||||
appendCompaction(this.session.id, firstKeptSeq, summary, preparation.tokensBefore);
|
||||
this.messages = [createCompactionSummaryMessage(summary), ...preparation.keptMessages];
|
||||
this.messageSeqs = [null, ...keptSeqs];
|
||||
return true;
|
||||
}
|
||||
|
||||
private appendCompletedTurn(userMessage: ModelMessage, newMessages: ModelMessage[]): void {
|
||||
if (newMessages.length === 0) return;
|
||||
|
||||
const userIndex = this.messages.lastIndexOf(userMessage);
|
||||
if (!this.sessionStore || !this.session) {
|
||||
if (userIndex >= 0 && this.messageSeqs[userIndex] == null) {
|
||||
this.messageSeqs[userIndex] = null;
|
||||
}
|
||||
this.messages.push(...newMessages);
|
||||
this.messageSeqs.push(...newMessages.map(() => null));
|
||||
return;
|
||||
}
|
||||
|
||||
const insertedSeqs = appendMessages(this.session.id, [userMessage, ...newMessages]);
|
||||
if (userIndex >= 0) {
|
||||
this.messageSeqs[userIndex] = insertedSeqs[0] ?? this.messageSeqs[userIndex];
|
||||
}
|
||||
this.messages.push(...newMessages);
|
||||
this.messageSeqs.push(...insertedSeqs.slice(1));
|
||||
this.sessionStore.touchSession(this.session.id, this.bash.getCwd());
|
||||
this.session = this.sessionStore.getRequiredSession(this.session.id);
|
||||
}
|
||||
|
||||
async *processMessage(userMessage: string): AsyncGenerator<StreamChunk, void, unknown> {
|
||||
this.abortController = new AbortController();
|
||||
const signal = this.abortController.signal;
|
||||
@@ -608,147 +678,171 @@ export class Agent {
|
||||
await this.consumeBackgroundNotifications();
|
||||
const userModelMessage: ModelMessage = { role: "user", content: userMessage };
|
||||
this.messages.push(userModelMessage);
|
||||
this.messageSeqs.push(null);
|
||||
|
||||
let assistantText = "";
|
||||
let streamOk = false;
|
||||
let closeMcp: (() => Promise<void>) | undefined;
|
||||
const provider = this.requireProvider();
|
||||
const system = buildSystemPrompt(this.bash.getCwd(), this.mode, this.planContext);
|
||||
const modelInfo = getModelInfo(this.modelId);
|
||||
this.planContext = null;
|
||||
let attemptedOverflowRecovery = false;
|
||||
|
||||
try {
|
||||
const provider = this.requireProvider();
|
||||
const baseTools = createTools(this.bash, provider, this.mode, {
|
||||
runTask: (request, abortSignal) => this.runTask(request, combineAbortSignals(signal, abortSignal)),
|
||||
runDelegation: (request, abortSignal) => this.runDelegation(request, combineAbortSignals(signal, abortSignal)),
|
||||
readDelegation: (id) => this.readDelegation(id),
|
||||
listDelegations: () => this.listDelegations(),
|
||||
});
|
||||
let tools: ToolSet = baseTools;
|
||||
if (this.mode === "agent") {
|
||||
const mcpBundle = await buildMcpToolSet(loadMcpServers());
|
||||
closeMcp = mcpBundle.close;
|
||||
tools = { ...baseTools, ...mcpBundle.tools };
|
||||
if (mcpBundle.errors.length > 0) {
|
||||
yield { type: "content", content: `MCP unavailable: ${mcpBundle.errors.join(" | ")}\n\n` };
|
||||
}
|
||||
}
|
||||
const system = buildSystemPrompt(this.bash.getCwd(), this.mode, this.planContext);
|
||||
this.planContext = null;
|
||||
while (true) {
|
||||
let assistantText = "";
|
||||
let streamOk = false;
|
||||
let closeMcp: (() => Promise<void>) | undefined;
|
||||
|
||||
const result = streamText({
|
||||
model: provider(this.modelId),
|
||||
system,
|
||||
messages: this.messages,
|
||||
tools,
|
||||
stopWhen: stepCountIs(this.maxToolRounds),
|
||||
maxRetries: 0,
|
||||
abortSignal: signal,
|
||||
temperature: 0.7,
|
||||
maxOutputTokens: this.maxTokens,
|
||||
onFinish: ({ totalUsage }) => {
|
||||
this.recordUsage(totalUsage, "message");
|
||||
},
|
||||
});
|
||||
|
||||
for await (const part of result.fullStream) {
|
||||
if (signal.aborted) {
|
||||
yield { type: "content", content: "\n\n[Cancelled]" };
|
||||
break;
|
||||
}
|
||||
|
||||
switch (part.type) {
|
||||
case "text-delta":
|
||||
assistantText += part.text;
|
||||
yield { type: "content", content: part.text };
|
||||
break;
|
||||
|
||||
case "reasoning-delta":
|
||||
yield { type: "reasoning", content: part.text };
|
||||
break;
|
||||
|
||||
case "tool-call": {
|
||||
const tc = toToolCall(part);
|
||||
yield { type: "tool_calls", toolCalls: [tc] };
|
||||
break;
|
||||
try {
|
||||
const settings = attemptedOverflowRecovery
|
||||
? relaxCompactionSettings(this.getCompactionSettings())
|
||||
: this.getCompactionSettings();
|
||||
if (modelInfo) {
|
||||
await this.compactForContext(
|
||||
provider,
|
||||
system,
|
||||
modelInfo.contextWindow,
|
||||
signal,
|
||||
settings,
|
||||
attemptedOverflowRecovery,
|
||||
);
|
||||
}
|
||||
|
||||
case "tool-result": {
|
||||
const tc: ToolCall = {
|
||||
id: part.toolCallId,
|
||||
type: "function",
|
||||
function: { name: part.toolName, arguments: JSON.stringify(part.input ?? {}) },
|
||||
};
|
||||
const tr = toToolResult(part.output);
|
||||
yield { type: "tool_result", toolCall: tc, toolResult: tr };
|
||||
break;
|
||||
const baseTools = createTools(this.bash, provider, this.mode, {
|
||||
runTask: (request, abortSignal) => this.runTask(request, combineAbortSignals(signal, abortSignal)),
|
||||
runDelegation: (request, abortSignal) =>
|
||||
this.runDelegation(request, combineAbortSignals(signal, abortSignal)),
|
||||
readDelegation: (id) => this.readDelegation(id),
|
||||
listDelegations: () => this.listDelegations(),
|
||||
});
|
||||
let tools: ToolSet = baseTools;
|
||||
if (this.mode === "agent") {
|
||||
const mcpBundle = await buildMcpToolSet(loadMcpServers());
|
||||
closeMcp = mcpBundle.close;
|
||||
tools = { ...baseTools, ...mcpBundle.tools };
|
||||
if (mcpBundle.errors.length > 0) {
|
||||
yield { type: "content", content: `MCP unavailable: ${mcpBundle.errors.join(" | ")}\n\n` };
|
||||
}
|
||||
}
|
||||
|
||||
case "error":
|
||||
yield { type: "error", content: String(part.error) };
|
||||
break;
|
||||
const result = streamText({
|
||||
model: provider(this.modelId),
|
||||
system,
|
||||
messages: this.messages,
|
||||
tools,
|
||||
stopWhen: stepCountIs(this.maxToolRounds),
|
||||
maxRetries: 0,
|
||||
abortSignal: signal,
|
||||
temperature: 0.7,
|
||||
maxOutputTokens: this.maxTokens,
|
||||
onFinish: ({ totalUsage }) => {
|
||||
this.recordUsage(totalUsage, "message");
|
||||
},
|
||||
});
|
||||
|
||||
case "abort":
|
||||
for await (const part of result.fullStream) {
|
||||
if (signal.aborted) {
|
||||
yield { type: "content", content: "\n\n[Cancelled]" };
|
||||
break;
|
||||
}
|
||||
|
||||
switch (part.type) {
|
||||
case "text-delta":
|
||||
assistantText += part.text;
|
||||
yield { type: "content", content: part.text };
|
||||
break;
|
||||
|
||||
case "reasoning-delta":
|
||||
yield { type: "reasoning", content: part.text };
|
||||
break;
|
||||
|
||||
case "tool-call": {
|
||||
const tc = toToolCall(part);
|
||||
yield { type: "tool_calls", toolCalls: [tc] };
|
||||
break;
|
||||
}
|
||||
|
||||
case "tool-result": {
|
||||
const tc: ToolCall = {
|
||||
id: part.toolCallId,
|
||||
type: "function",
|
||||
function: { name: part.toolName, arguments: JSON.stringify(part.input ?? {}) },
|
||||
};
|
||||
const tr = toToolResult(part.output);
|
||||
yield { type: "tool_result", toolCall: tc, toolResult: tr };
|
||||
break;
|
||||
}
|
||||
|
||||
case "error":
|
||||
yield { type: "error", content: String(part.error) };
|
||||
break;
|
||||
|
||||
case "abort":
|
||||
yield { type: "content", content: "\n\n[Cancelled]" };
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (signal.aborted) {
|
||||
this.discardAbortedTurn(userModelMessage);
|
||||
yield { type: "done" };
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await result.response;
|
||||
if (!signal.aborted) {
|
||||
this.appendCompletedTurn(userModelMessage, response.messages);
|
||||
streamOk = true;
|
||||
}
|
||||
} catch (responseError: unknown) {
|
||||
if (
|
||||
!attemptedOverflowRecovery &&
|
||||
!assistantText.trim() &&
|
||||
modelInfo &&
|
||||
isContextLimitError(responseError)
|
||||
) {
|
||||
attemptedOverflowRecovery = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (signal.aborted) {
|
||||
this.discardAbortedTurn(userModelMessage);
|
||||
yield { type: "done" };
|
||||
return;
|
||||
}
|
||||
|
||||
if (!streamOk && assistantText.trim()) {
|
||||
this.appendCompletedTurn(userModelMessage, [{ role: "assistant", content: assistantText }]);
|
||||
}
|
||||
|
||||
yield { type: "done" };
|
||||
return;
|
||||
} catch (err: unknown) {
|
||||
if (signal.aborted) {
|
||||
this.discardAbortedTurn(userModelMessage);
|
||||
yield { type: "content", content: "\n\n[Cancelled]" };
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (signal.aborted) {
|
||||
this.discardAbortedTurn(userModelMessage);
|
||||
yield { type: "done" };
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await result.response;
|
||||
if (!signal.aborted) {
|
||||
this.messages.push(...response.messages);
|
||||
if (this.sessionStore && this.session) {
|
||||
appendMessages(this.session.id, [userModelMessage, ...response.messages]);
|
||||
this.sessionStore.touchSession(this.session.id, this.bash.getCwd());
|
||||
this.session = this.sessionStore.getRequiredSession(this.session.id);
|
||||
yield { type: "done" };
|
||||
return;
|
||||
}
|
||||
streamOk = true;
|
||||
}
|
||||
} catch {
|
||||
// response promise can fail after stream errors — fall back to manual message
|
||||
}
|
||||
|
||||
if (signal.aborted) {
|
||||
this.discardAbortedTurn(userModelMessage);
|
||||
yield { type: "done" };
|
||||
return;
|
||||
}
|
||||
if (!attemptedOverflowRecovery && !assistantText.trim() && modelInfo && isContextLimitError(err)) {
|
||||
attemptedOverflowRecovery = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!streamOk && assistantText.trim()) {
|
||||
const fallbackMessage: ModelMessage = { role: "assistant", content: assistantText };
|
||||
this.messages.push(fallbackMessage);
|
||||
if (this.sessionStore && this.session) {
|
||||
appendMessages(this.session.id, [userModelMessage, fallbackMessage]);
|
||||
this.sessionStore.touchSession(this.session.id, this.bash.getCwd());
|
||||
this.session = this.sessionStore.getRequiredSession(this.session.id);
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
yield { type: "error", content: `Error: ${msg}` };
|
||||
if (assistantText.trim()) {
|
||||
this.appendCompletedTurn(userModelMessage, [{ role: "assistant", content: assistantText }]);
|
||||
}
|
||||
yield { type: "done" };
|
||||
return;
|
||||
} finally {
|
||||
await closeMcp?.().catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
yield { type: "done" };
|
||||
} catch (err: unknown) {
|
||||
if (signal.aborted) {
|
||||
this.discardAbortedTurn(userModelMessage);
|
||||
yield { type: "content", content: "\n\n[Cancelled]" };
|
||||
} else {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
yield { type: "error", content: `Error: ${msg}` };
|
||||
}
|
||||
if (!signal.aborted && assistantText.trim()) {
|
||||
const fallbackMessage: ModelMessage = { role: "assistant", content: assistantText };
|
||||
this.messages.push(fallbackMessage);
|
||||
if (this.sessionStore && this.session) {
|
||||
appendMessages(this.session.id, [userModelMessage, fallbackMessage]);
|
||||
this.sessionStore.touchSession(this.session.id, this.bash.getCwd());
|
||||
this.session = this.sessionStore.getRequiredSession(this.session.id);
|
||||
}
|
||||
}
|
||||
yield { type: "done" };
|
||||
} finally {
|
||||
await closeMcp?.().catch(() => {});
|
||||
if (this.abortController?.signal === signal) {
|
||||
this.abortController = null;
|
||||
}
|
||||
@@ -828,10 +922,6 @@ function truncate(text: string, max: number): string {
|
||||
return text.length <= max ? text : `${text.slice(0, max - 1)}…`;
|
||||
}
|
||||
|
||||
function estimateTokens(text: string): number {
|
||||
return Math.ceil(text.length / 4);
|
||||
}
|
||||
|
||||
function combineAbortSignals(...signals: Array<AbortSignal | undefined>): AbortSignal | undefined {
|
||||
const activeSignals = signals.filter((signal): signal is AbortSignal => Boolean(signal));
|
||||
if (activeSignals.length === 0) return undefined;
|
||||
@@ -853,3 +943,8 @@ function combineAbortSignals(...signals: Array<AbortSignal | undefined>): AbortS
|
||||
|
||||
return controller.signal;
|
||||
}
|
||||
|
||||
function isContextLimitError(error: unknown): boolean {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
return /(context|token|prompt).*(limit|length|large|window|overflow)|too many tokens|maximum context/i.test(message);
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
import type { ModelMessage } from "ai";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildEffectiveTranscript, type PersistedCompaction } from "../storage/transcript-view";
|
||||
import {
|
||||
COMPACTION_SUMMARY_HEADER,
|
||||
createCompactionSummaryMessage,
|
||||
findCutPoint,
|
||||
prepareCompaction,
|
||||
serializeConversation,
|
||||
shouldCompactContext,
|
||||
} from "./compaction";
|
||||
|
||||
function user(text: string): ModelMessage {
|
||||
return { role: "user", content: text } as ModelMessage;
|
||||
}
|
||||
|
||||
function assistantText(text: string): ModelMessage {
|
||||
return {
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text }],
|
||||
} as ModelMessage;
|
||||
}
|
||||
|
||||
function assistantToolCall(toolCallId: string, toolName: string, input: Record<string, unknown> = {}): ModelMessage {
|
||||
return {
|
||||
role: "assistant",
|
||||
content: [{ type: "tool-call", toolCallId, toolName, input }],
|
||||
} as ModelMessage;
|
||||
}
|
||||
|
||||
function toolResult(toolCallId: string, toolName: string, output: unknown): ModelMessage {
|
||||
return {
|
||||
role: "tool",
|
||||
content: [{ type: "tool-result", toolCallId, toolName, output }],
|
||||
} as ModelMessage;
|
||||
}
|
||||
|
||||
describe("compaction helpers", () => {
|
||||
it("triggers when context exceeds reserved headroom", () => {
|
||||
const settings = { reserveTokens: 100, keepRecentTokens: 40 };
|
||||
|
||||
expect(shouldCompactContext(901, 1000, settings)).toBe(true);
|
||||
expect(shouldCompactContext(900, 1000, settings)).toBe(false);
|
||||
});
|
||||
|
||||
it("never selects a tool-result message as the cut point", () => {
|
||||
const messages = [
|
||||
user("inspect this file"),
|
||||
assistantToolCall("call-1", "read_file", { path: "src/index.ts" }),
|
||||
toolResult("call-1", "read_file", "x".repeat(400)),
|
||||
assistantText("I found the relevant section."),
|
||||
user("continue"),
|
||||
];
|
||||
|
||||
const cutPoint = findCutPoint(messages, 0, 130);
|
||||
|
||||
expect(cutPoint.firstKeptIndex).not.toBe(2);
|
||||
expect(messages[cutPoint.firstKeptIndex]?.role).not.toBe("tool");
|
||||
});
|
||||
|
||||
it("detects split turns and captures the turn prefix for summarization", () => {
|
||||
const messages = [
|
||||
user("Refactor the session loader."),
|
||||
assistantText("a".repeat(320)),
|
||||
assistantText("recent status update"),
|
||||
];
|
||||
|
||||
const preparation = prepareCompaction(messages, "system prompt", {
|
||||
reserveTokens: 100,
|
||||
keepRecentTokens: 5,
|
||||
});
|
||||
|
||||
expect(preparation).not.toBeNull();
|
||||
expect(preparation?.isSplitTurn).toBe(true);
|
||||
expect(preparation?.messagesToSummarize).toHaveLength(0);
|
||||
expect(preparation?.turnPrefixMessages).toHaveLength(2);
|
||||
expect(preparation?.keptMessages).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("excludes the previous summary message from new compaction input", () => {
|
||||
const messages = [
|
||||
createCompactionSummaryMessage("Earlier work"),
|
||||
user("Handle migration edge cases"),
|
||||
assistantText("I added the table and loader changes."),
|
||||
user("Now wire the retry path"),
|
||||
];
|
||||
|
||||
const preparation = prepareCompaction(messages, "system prompt", {
|
||||
reserveTokens: 100,
|
||||
keepRecentTokens: 4,
|
||||
});
|
||||
|
||||
expect(preparation).not.toBeNull();
|
||||
expect(preparation?.previousSummary).toBe("Earlier work");
|
||||
expect(preparation?.messagesToSummarize[0]).toEqual(user("Handle migration edge cases"));
|
||||
expect(preparation?.messagesToSummarize.some((message) => message.role === "system")).toBe(false);
|
||||
});
|
||||
|
||||
it("serializes tool results with truncation markers", () => {
|
||||
const transcript = serializeConversation([user("Read the output"), toolResult("call-1", "bash", "x".repeat(2105))]);
|
||||
|
||||
expect(transcript).toContain("[Tool result]:");
|
||||
expect(transcript).toContain("more characters truncated");
|
||||
});
|
||||
|
||||
it("builds the effective transcript from the latest persisted checkpoint", () => {
|
||||
const rawMessages = [
|
||||
user("old request"),
|
||||
assistantText("old answer"),
|
||||
user("new request"),
|
||||
assistantText("new answer"),
|
||||
user("latest request"),
|
||||
];
|
||||
const seqs = [1, 2, 3, 4, 5];
|
||||
const timestamps = seqs.map((seq) => new Date(`2026-03-20T00:00:0${seq}.000Z`));
|
||||
const checkpoint: PersistedCompaction = {
|
||||
firstKeptSeq: 4,
|
||||
summary: "Summarized old work",
|
||||
tokensBefore: 1234,
|
||||
createdAt: new Date("2026-03-20T00:00:10.000Z"),
|
||||
};
|
||||
|
||||
const transcript = buildEffectiveTranscript(rawMessages, seqs, timestamps, checkpoint);
|
||||
|
||||
expect(transcript.messages).toHaveLength(3);
|
||||
expect(transcript.seqs).toEqual([null, 4, 5]);
|
||||
expect(transcript.messages[0]).toEqual(createCompactionSummaryMessage("Summarized old work"));
|
||||
expect(
|
||||
transcript.messages[0]?.role === "system" && typeof transcript.messages[0].content === "string"
|
||||
? transcript.messages[0].content
|
||||
: "",
|
||||
).toContain(COMPACTION_SUMMARY_HEADER);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,476 @@
|
||||
import { generateText, type ModelMessage } from "ai";
|
||||
import type { XaiProvider } from "../grok/client";
|
||||
|
||||
export interface CompactionSettings {
|
||||
reserveTokens: number;
|
||||
keepRecentTokens: number;
|
||||
}
|
||||
|
||||
export interface CutPointResult {
|
||||
firstKeptIndex: number;
|
||||
turnStartIndex: number;
|
||||
isSplitTurn: boolean;
|
||||
}
|
||||
|
||||
export interface PreparedCompaction {
|
||||
previousSummary?: string;
|
||||
messagesToSummarize: ModelMessage[];
|
||||
turnPrefixMessages: ModelMessage[];
|
||||
keptMessages: ModelMessage[];
|
||||
firstKeptIndex: number;
|
||||
isSplitTurn: boolean;
|
||||
tokensBefore: number;
|
||||
settings: CompactionSettings;
|
||||
}
|
||||
|
||||
const TOOL_RESULT_MAX_CHARS = 2000;
|
||||
const MIN_KEPT_TOKENS_ON_RETRY = 4000;
|
||||
|
||||
export const DEFAULT_RESERVE_TOKENS = 16_384;
|
||||
export const DEFAULT_KEEP_RECENT_TOKENS = 20_000;
|
||||
export const COMPACTION_SUMMARY_HEADER = "[Context checkpoint summary]";
|
||||
|
||||
const SUMMARIZATION_SYSTEM_PROMPT = `You are a context summarization assistant.
|
||||
|
||||
Do not continue the conversation. Do not answer any questions from the conversation.
|
||||
Only output a structured checkpoint summary that another coding agent can use to continue the work.`;
|
||||
|
||||
const SUMMARIZATION_PROMPT = `The messages above are a conversation to summarize. Create a structured context checkpoint summary that another LLM will use to continue the work.
|
||||
|
||||
Use this exact format:
|
||||
|
||||
## Goal
|
||||
[What the user is trying to accomplish]
|
||||
|
||||
## Constraints & Preferences
|
||||
- [Requirements, preferences, or constraints]
|
||||
- [(none) if none were mentioned]
|
||||
|
||||
## Progress
|
||||
### Done
|
||||
- [x] [Completed work]
|
||||
|
||||
### In Progress
|
||||
- [ ] [Current work]
|
||||
|
||||
### Blocked
|
||||
- [Any active blockers]
|
||||
|
||||
## Key Decisions
|
||||
- **[Decision]**: [Rationale]
|
||||
|
||||
## Next Steps
|
||||
1. [The next action to take]
|
||||
|
||||
## Critical Context
|
||||
- [Important details needed to continue]
|
||||
- [(none) if not applicable]
|
||||
|
||||
Keep it concise, but preserve exact file paths, function names, and error messages.`;
|
||||
|
||||
const UPDATE_SUMMARIZATION_PROMPT = `The messages above are new conversation messages to incorporate into the existing summary provided below.
|
||||
|
||||
Update the existing structured summary with new information. Rules:
|
||||
- Preserve still-relevant information from the previous summary
|
||||
- Add new progress, decisions, and critical context
|
||||
- Move completed items from "In Progress" to "Done" when appropriate
|
||||
- Update "Next Steps" based on the current state
|
||||
- Preserve exact file paths, function names, and error messages
|
||||
|
||||
Use the exact same section structure as the existing summary format.`;
|
||||
|
||||
const TURN_PREFIX_SUMMARIZATION_PROMPT = `This is the early prefix of a single turn that was too large to keep in full. The recent suffix is still available.
|
||||
|
||||
Summarize only what is needed so another coding agent can understand the retained suffix.
|
||||
|
||||
Use this exact format:
|
||||
|
||||
## Original Request
|
||||
[What the user asked for in this turn]
|
||||
|
||||
## Early Progress
|
||||
- [Key work done before the kept suffix]
|
||||
|
||||
## Context For Suffix
|
||||
- [Information needed to understand the kept recent messages]`;
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null;
|
||||
}
|
||||
|
||||
function getTextParts(content: unknown): string[] {
|
||||
if (typeof content === "string") return [content];
|
||||
if (!Array.isArray(content)) return [];
|
||||
|
||||
const parts: string[] = [];
|
||||
for (const part of content) {
|
||||
if (!isRecord(part)) continue;
|
||||
if (part.type === "text" && typeof part.text === "string") {
|
||||
parts.push(part.text);
|
||||
continue;
|
||||
}
|
||||
if (part.type === "reasoning" && typeof part.text === "string") {
|
||||
parts.push(part.text);
|
||||
continue;
|
||||
}
|
||||
if (part.type === "reasoning" && typeof part.reasoning === "string") {
|
||||
parts.push(part.reasoning);
|
||||
}
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
function stringifyForSummary(value: unknown): string {
|
||||
if (typeof value === "string") return value;
|
||||
try {
|
||||
return JSON.stringify(value, null, 2);
|
||||
} catch {
|
||||
return String(value);
|
||||
}
|
||||
}
|
||||
|
||||
function truncateForSummary(text: string, maxChars = TOOL_RESULT_MAX_CHARS): string {
|
||||
if (text.length <= maxChars) return text;
|
||||
return `${text.slice(0, maxChars)}\n\n[... ${text.length - maxChars} more characters truncated]`;
|
||||
}
|
||||
|
||||
function extractUserContent(content: unknown): string {
|
||||
if (typeof content === "string") return content;
|
||||
if (!Array.isArray(content)) return "";
|
||||
|
||||
const parts: string[] = [];
|
||||
for (const part of content) {
|
||||
if (!isRecord(part)) continue;
|
||||
if (part.type === "text" && typeof part.text === "string") {
|
||||
parts.push(part.text);
|
||||
continue;
|
||||
}
|
||||
if (part.type === "image") {
|
||||
parts.push("[Image]");
|
||||
continue;
|
||||
}
|
||||
if (part.type === "file") {
|
||||
const filename = typeof part.filename === "string" ? part.filename : null;
|
||||
parts.push(filename ? `[File: ${filename}]` : "[File]");
|
||||
}
|
||||
}
|
||||
return parts.join("\n");
|
||||
}
|
||||
|
||||
function extractAssistantText(content: unknown): string {
|
||||
return getTextParts(content).join("\n");
|
||||
}
|
||||
|
||||
function extractToolCallText(content: unknown): string[] {
|
||||
if (!Array.isArray(content)) return [];
|
||||
|
||||
const toolCalls: string[] = [];
|
||||
for (const part of content) {
|
||||
if (!isRecord(part) || part.type !== "tool-call") continue;
|
||||
const toolName = typeof part.toolName === "string" ? part.toolName : "tool";
|
||||
const input = isRecord(part.input) ? part.input : {};
|
||||
const args = Object.entries(input)
|
||||
.map(([key, value]) => `${key}=${JSON.stringify(value)}`)
|
||||
.join(", ");
|
||||
toolCalls.push(`${toolName}(${args})`);
|
||||
}
|
||||
return toolCalls;
|
||||
}
|
||||
|
||||
function extractToolResultText(content: unknown): string[] {
|
||||
if (!Array.isArray(content)) return [];
|
||||
|
||||
const toolResults: string[] = [];
|
||||
for (const part of content) {
|
||||
if (!isRecord(part) || part.type !== "tool-result") continue;
|
||||
toolResults.push(truncateForSummary(stringifyForSummary(part.output)));
|
||||
}
|
||||
return toolResults;
|
||||
}
|
||||
|
||||
export function createCompactionSummaryMessage(summary: string): ModelMessage {
|
||||
return {
|
||||
role: "system",
|
||||
content: `${COMPACTION_SUMMARY_HEADER}\n${summary.trim()}`,
|
||||
};
|
||||
}
|
||||
|
||||
export function isCompactionSummaryMessage(message: ModelMessage | undefined): boolean {
|
||||
return message?.role === "system" && typeof message.content === "string"
|
||||
? message.content.startsWith(COMPACTION_SUMMARY_HEADER)
|
||||
: false;
|
||||
}
|
||||
|
||||
export function getCompactionSummaryText(message: ModelMessage | undefined): string | null {
|
||||
if (!isCompactionSummaryMessage(message) || typeof message?.content !== "string") {
|
||||
return null;
|
||||
}
|
||||
return message.content.slice(COMPACTION_SUMMARY_HEADER.length).trim();
|
||||
}
|
||||
|
||||
export function estimateMessageTokens(message: ModelMessage): number {
|
||||
let chars = 0;
|
||||
|
||||
switch (message.role) {
|
||||
case "user":
|
||||
chars += extractUserContent(message.content).length;
|
||||
break;
|
||||
case "assistant":
|
||||
chars += extractAssistantText(message.content).length;
|
||||
chars += extractToolCallText(message.content).join("; ").length;
|
||||
break;
|
||||
case "tool":
|
||||
chars += extractToolResultText(message.content).join("\n").length;
|
||||
break;
|
||||
case "system":
|
||||
chars +=
|
||||
typeof message.content === "string" ? message.content.length : getTextParts(message.content).join("\n").length;
|
||||
break;
|
||||
default:
|
||||
chars += stringifyForSummary((message as { content?: unknown }).content).length;
|
||||
break;
|
||||
}
|
||||
|
||||
return Math.ceil(chars / 4);
|
||||
}
|
||||
|
||||
export function estimateConversationTokens(systemPrompt: string, messages: ModelMessage[], inFlightText = ""): number {
|
||||
const systemTokens = Math.ceil((systemPrompt.length + inFlightText.length) / 4);
|
||||
return systemTokens + messages.reduce((sum, message) => sum + estimateMessageTokens(message), 0);
|
||||
}
|
||||
|
||||
export function shouldCompactContext(
|
||||
contextTokens: number,
|
||||
contextWindow: number,
|
||||
settings: CompactionSettings,
|
||||
): boolean {
|
||||
return contextTokens > contextWindow - settings.reserveTokens;
|
||||
}
|
||||
|
||||
function isValidCutPoint(message: ModelMessage): boolean {
|
||||
return message.role !== "tool";
|
||||
}
|
||||
|
||||
function findTurnStartIndex(messages: ModelMessage[], entryIndex: number, startIndex: number): number {
|
||||
for (let i = entryIndex; i >= startIndex; i--) {
|
||||
if (messages[i]?.role === "user") {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
export function findCutPoint(messages: ModelMessage[], startIndex: number, keepRecentTokens: number): CutPointResult {
|
||||
const cutPoints: number[] = [];
|
||||
for (let i = startIndex; i < messages.length; i++) {
|
||||
if (isValidCutPoint(messages[i])) {
|
||||
cutPoints.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (cutPoints.length === 0) {
|
||||
return { firstKeptIndex: startIndex, turnStartIndex: -1, isSplitTurn: false };
|
||||
}
|
||||
|
||||
let accumulatedTokens = 0;
|
||||
let cutIndex = cutPoints[0];
|
||||
|
||||
for (let i = messages.length - 1; i >= startIndex; i--) {
|
||||
accumulatedTokens += estimateMessageTokens(messages[i]);
|
||||
if (accumulatedTokens >= keepRecentTokens) {
|
||||
cutIndex = cutPoints.find((index) => index >= i) ?? cutPoints[cutPoints.length - 1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const cutMessage = messages[cutIndex];
|
||||
const isUserMessage = cutMessage?.role === "user";
|
||||
const turnStartIndex = isUserMessage ? -1 : findTurnStartIndex(messages, cutIndex, startIndex);
|
||||
|
||||
return {
|
||||
firstKeptIndex: cutIndex,
|
||||
turnStartIndex,
|
||||
isSplitTurn: !isUserMessage && turnStartIndex !== -1,
|
||||
};
|
||||
}
|
||||
|
||||
export function prepareCompaction(
|
||||
messages: ModelMessage[],
|
||||
systemPrompt: string,
|
||||
settings: CompactionSettings,
|
||||
): PreparedCompaction | null {
|
||||
const previousSummary = getCompactionSummaryText(messages[0]) ?? undefined;
|
||||
const boundaryStart = previousSummary ? 1 : 0;
|
||||
if (boundaryStart >= messages.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const cutPoint = findCutPoint(messages, boundaryStart, settings.keepRecentTokens);
|
||||
const historyEnd = cutPoint.isSplitTurn ? cutPoint.turnStartIndex : cutPoint.firstKeptIndex;
|
||||
const messagesToSummarize = messages.slice(boundaryStart, Math.max(boundaryStart, historyEnd));
|
||||
const turnPrefixMessages = cutPoint.isSplitTurn
|
||||
? messages.slice(cutPoint.turnStartIndex, cutPoint.firstKeptIndex)
|
||||
: [];
|
||||
const keptMessages = messages.slice(cutPoint.firstKeptIndex);
|
||||
const tokensBefore = estimateConversationTokens(systemPrompt, messages);
|
||||
|
||||
if (keptMessages.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (messagesToSummarize.length === 0 && turnPrefixMessages.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
previousSummary,
|
||||
messagesToSummarize,
|
||||
turnPrefixMessages,
|
||||
keptMessages,
|
||||
firstKeptIndex: cutPoint.firstKeptIndex,
|
||||
isSplitTurn: cutPoint.isSplitTurn,
|
||||
tokensBefore,
|
||||
settings,
|
||||
};
|
||||
}
|
||||
|
||||
export function relaxCompactionSettings(settings: CompactionSettings): CompactionSettings {
|
||||
return {
|
||||
...settings,
|
||||
keepRecentTokens: Math.max(MIN_KEPT_TOKENS_ON_RETRY, Math.floor(settings.keepRecentTokens / 2)),
|
||||
};
|
||||
}
|
||||
|
||||
export function serializeConversation(messages: ModelMessage[]): string {
|
||||
const parts: string[] = [];
|
||||
|
||||
for (const message of messages) {
|
||||
if (isCompactionSummaryMessage(message)) {
|
||||
const summary = getCompactionSummaryText(message);
|
||||
if (summary) {
|
||||
parts.push(`[Previous summary]: ${summary}`);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (message.role === "user") {
|
||||
const content = extractUserContent(message.content).trim();
|
||||
if (content) parts.push(`[User]: ${content}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (message.role === "assistant") {
|
||||
const text = extractAssistantText(message.content).trim();
|
||||
const toolCalls = extractToolCallText(message.content);
|
||||
if (text) parts.push(`[Assistant]: ${text}`);
|
||||
if (toolCalls.length > 0) parts.push(`[Assistant tool calls]: ${toolCalls.join("; ")}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (message.role === "tool") {
|
||||
const results = extractToolResultText(message.content);
|
||||
for (const result of results) {
|
||||
if (result.trim()) parts.push(`[Tool result]: ${result}`);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (message.role === "system") {
|
||||
const content =
|
||||
typeof message.content === "string" ? message.content.trim() : getTextParts(message.content).join("\n").trim();
|
||||
if (content) parts.push(`[System]: ${content}`);
|
||||
}
|
||||
}
|
||||
|
||||
return parts.join("\n\n");
|
||||
}
|
||||
|
||||
async function summarizeConversation(
|
||||
provider: XaiProvider,
|
||||
modelId: string,
|
||||
messages: ModelMessage[],
|
||||
reserveTokens: number,
|
||||
customInstructions?: string,
|
||||
previousSummary?: string,
|
||||
promptOverride?: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<string> {
|
||||
const serialized = serializeConversation(messages);
|
||||
const promptParts = [serialized];
|
||||
|
||||
if (previousSummary) {
|
||||
promptParts.push(`Existing summary:\n${previousSummary}`);
|
||||
}
|
||||
|
||||
const basePrompt = promptOverride ?? (previousSummary ? UPDATE_SUMMARIZATION_PROMPT : SUMMARIZATION_PROMPT);
|
||||
promptParts.push(basePrompt);
|
||||
|
||||
if (customInstructions?.trim()) {
|
||||
promptParts.push(`Additional focus: ${customInstructions.trim()}`);
|
||||
}
|
||||
|
||||
const { text } = await generateText({
|
||||
model: provider(modelId),
|
||||
system: SUMMARIZATION_SYSTEM_PROMPT,
|
||||
prompt: promptParts.filter(Boolean).join("\n\n"),
|
||||
abortSignal: signal,
|
||||
maxRetries: 0,
|
||||
temperature: 0.2,
|
||||
maxOutputTokens: Math.max(512, Math.floor(reserveTokens * 0.8)),
|
||||
});
|
||||
|
||||
return text.trim();
|
||||
}
|
||||
|
||||
export async function generateCompactionSummary(
|
||||
provider: XaiProvider,
|
||||
modelId: string,
|
||||
preparation: PreparedCompaction,
|
||||
customInstructions?: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<string> {
|
||||
const { messagesToSummarize, turnPrefixMessages, isSplitTurn, previousSummary, settings } = preparation;
|
||||
|
||||
if (isSplitTurn && turnPrefixMessages.length > 0) {
|
||||
const [historySummary, prefixSummary] = await Promise.all([
|
||||
messagesToSummarize.length > 0
|
||||
? summarizeConversation(
|
||||
provider,
|
||||
modelId,
|
||||
messagesToSummarize,
|
||||
settings.reserveTokens,
|
||||
customInstructions,
|
||||
previousSummary,
|
||||
undefined,
|
||||
signal,
|
||||
)
|
||||
: Promise.resolve(previousSummary?.trim() || ""),
|
||||
summarizeConversation(
|
||||
provider,
|
||||
modelId,
|
||||
turnPrefixMessages,
|
||||
settings.reserveTokens,
|
||||
undefined,
|
||||
undefined,
|
||||
TURN_PREFIX_SUMMARIZATION_PROMPT,
|
||||
signal,
|
||||
),
|
||||
]);
|
||||
|
||||
if (historySummary && prefixSummary) {
|
||||
return `${historySummary}\n\n---\n\n${prefixSummary}`;
|
||||
}
|
||||
return (historySummary || prefixSummary).trim();
|
||||
}
|
||||
|
||||
return summarizeConversation(
|
||||
provider,
|
||||
modelId,
|
||||
messagesToSummarize,
|
||||
settings.reserveTokens,
|
||||
customInstructions,
|
||||
previousSummary,
|
||||
undefined,
|
||||
signal,
|
||||
);
|
||||
}
|
||||
+12
-1
@@ -1,4 +1,15 @@
|
||||
export { getDatabasePath } from "./db";
|
||||
export { SessionStore } from "./sessions";
|
||||
export { appendMessages, appendSystemMessage, buildChatEntries, loadTranscript } from "./transcript";
|
||||
export {
|
||||
appendCompaction,
|
||||
appendMessages,
|
||||
appendSystemMessage,
|
||||
buildChatEntries,
|
||||
getNextMessageSequence,
|
||||
loadLatestCompaction,
|
||||
loadRawTranscript,
|
||||
loadTranscript,
|
||||
loadTranscriptState,
|
||||
} from "./transcript";
|
||||
export { buildEffectiveTranscript, type LoadedTranscriptState, type PersistedCompaction } from "./transcript-view";
|
||||
export { getSessionTotalTokens, listSessionUsage, recordUsageEvent, type TokenUsageLike } from "./usage";
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { SQLiteDatabase } from "./db";
|
||||
|
||||
const LATEST_DB_VERSION = 1;
|
||||
const LATEST_DB_VERSION = 2;
|
||||
|
||||
export function applyMigrations(db: SQLiteDatabase): void {
|
||||
const version = Number(db.pragma("user_version", { simple: true })) || 0;
|
||||
@@ -11,6 +11,10 @@ export function applyMigrations(db: SQLiteDatabase): void {
|
||||
createInitialSchema(db);
|
||||
db.pragma("user_version = 1");
|
||||
}
|
||||
if (version < 2) {
|
||||
createCompactionSchema(db);
|
||||
db.pragma("user_version = 2");
|
||||
}
|
||||
});
|
||||
|
||||
migrate();
|
||||
@@ -84,6 +88,15 @@ function createInitialSchema(db: SQLiteDatabase): void {
|
||||
created_at TEXT NOT NULL
|
||||
) STRICT;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS compactions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
||||
first_kept_seq INTEGER NOT NULL,
|
||||
summary TEXT NOT NULL,
|
||||
tokens_before INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TEXT NOT NULL
|
||||
) STRICT;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_workspace_updated
|
||||
ON sessions(workspace_id, updated_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_messages_session_seq
|
||||
@@ -92,5 +105,23 @@ function createInitialSchema(db: SQLiteDatabase): void {
|
||||
ON tool_calls(session_id, message_seq);
|
||||
CREATE INDEX IF NOT EXISTS idx_usage_events_session_created
|
||||
ON usage_events(session_id, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_compactions_session_created
|
||||
ON compactions(session_id, created_at DESC);
|
||||
`);
|
||||
}
|
||||
|
||||
function createCompactionSchema(db: SQLiteDatabase): void {
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS compactions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
||||
first_kept_seq INTEGER NOT NULL,
|
||||
summary TEXT NOT NULL,
|
||||
tokens_before INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TEXT NOT NULL
|
||||
) STRICT;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_compactions_session_created
|
||||
ON compactions(session_id, created_at DESC);
|
||||
`);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import type { ModelMessage } from "ai";
|
||||
import { createCompactionSummaryMessage } from "../agent/compaction";
|
||||
|
||||
export interface PersistedCompaction {
|
||||
firstKeptSeq: number;
|
||||
summary: string;
|
||||
tokensBefore: number;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
export interface LoadedTranscriptState {
|
||||
messages: ModelMessage[];
|
||||
seqs: Array<number | null>;
|
||||
timestamps: Date[];
|
||||
compaction: PersistedCompaction | null;
|
||||
}
|
||||
|
||||
export function buildEffectiveTranscript(
|
||||
messages: ModelMessage[],
|
||||
seqs: number[],
|
||||
timestamps: Date[],
|
||||
compaction: PersistedCompaction | null,
|
||||
): LoadedTranscriptState {
|
||||
if (!compaction) {
|
||||
return {
|
||||
messages: [...messages],
|
||||
seqs: [...seqs],
|
||||
timestamps: [...timestamps],
|
||||
compaction: null,
|
||||
};
|
||||
}
|
||||
|
||||
const firstKeptIndex = seqs.findIndex((seq) => seq >= compaction.firstKeptSeq);
|
||||
const keptIndex = firstKeptIndex >= 0 ? firstKeptIndex : messages.length;
|
||||
const keptMessages = messages.slice(keptIndex);
|
||||
const keptSeqs = seqs.slice(keptIndex);
|
||||
const keptTimestamps = timestamps.slice(keptIndex);
|
||||
|
||||
return {
|
||||
messages: [createCompactionSummaryMessage(compaction.summary), ...keptMessages],
|
||||
seqs: [null, ...keptSeqs],
|
||||
timestamps: [compaction.createdAt, ...keptTimestamps],
|
||||
compaction,
|
||||
};
|
||||
}
|
||||
+105
-21
@@ -1,6 +1,8 @@
|
||||
import type { ModelMessage } from "ai";
|
||||
import { getCompactionSummaryText } from "../agent/compaction";
|
||||
import type { ChatEntry, ToolCall, ToolResult } from "../types/index";
|
||||
import { getDatabase, withTransaction } from "./db";
|
||||
import { buildEffectiveTranscript, type LoadedTranscriptState, type PersistedCompaction } from "./transcript-view";
|
||||
|
||||
interface MessageRow {
|
||||
session_id: string;
|
||||
@@ -22,8 +24,22 @@ interface StoredToolResultRow {
|
||||
output_json: string;
|
||||
}
|
||||
|
||||
export function loadTranscript(sessionId: string): ModelMessage[] {
|
||||
const rows = getDatabase()
|
||||
interface CompactionRow {
|
||||
session_id: string;
|
||||
first_kept_seq: number;
|
||||
summary: string;
|
||||
tokens_before: number;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
interface EffectiveMessageRecord {
|
||||
message: ModelMessage;
|
||||
seq: number | null;
|
||||
timestamp: Date;
|
||||
}
|
||||
|
||||
function loadMessageRows(sessionId: string): MessageRow[] {
|
||||
return getDatabase()
|
||||
.prepare(`
|
||||
SELECT session_id, seq, role, message_json, created_at
|
||||
FROM messages
|
||||
@@ -31,13 +47,72 @@ export function loadTranscript(sessionId: string): ModelMessage[] {
|
||||
ORDER BY seq ASC
|
||||
`)
|
||||
.all(sessionId) as MessageRow[];
|
||||
|
||||
return rows.map((row) => JSON.parse(row.message_json) as ModelMessage);
|
||||
}
|
||||
|
||||
export function appendMessages(sessionId: string, messages: ModelMessage[]): void {
|
||||
if (messages.length === 0) return;
|
||||
function toPersistedCompaction(row: CompactionRow | undefined): PersistedCompaction | null {
|
||||
if (!row) return null;
|
||||
return {
|
||||
firstKeptSeq: row.first_kept_seq,
|
||||
summary: row.summary,
|
||||
tokensBefore: row.tokens_before,
|
||||
createdAt: new Date(row.created_at),
|
||||
};
|
||||
}
|
||||
|
||||
export function loadLatestCompaction(sessionId: string): PersistedCompaction | null {
|
||||
const row = getDatabase()
|
||||
.prepare(`
|
||||
SELECT session_id, first_kept_seq, summary, tokens_before, created_at
|
||||
FROM compactions
|
||||
WHERE session_id = ?
|
||||
ORDER BY id DESC
|
||||
LIMIT 1
|
||||
`)
|
||||
.get(sessionId) as CompactionRow | undefined;
|
||||
|
||||
return toPersistedCompaction(row);
|
||||
}
|
||||
|
||||
function buildEffectiveMessageRecords(sessionId: string): EffectiveMessageRecord[] {
|
||||
const rows = loadMessageRows(sessionId);
|
||||
const messages = rows.map((row) => JSON.parse(row.message_json) as ModelMessage);
|
||||
const seqs = rows.map((row) => row.seq);
|
||||
const timestamps = rows.map((row) => new Date(row.created_at));
|
||||
const transcript = buildEffectiveTranscript(messages, seqs, timestamps, loadLatestCompaction(sessionId));
|
||||
|
||||
return transcript.messages.map((message, index) => ({
|
||||
message,
|
||||
seq: transcript.seqs[index],
|
||||
timestamp: transcript.timestamps[index],
|
||||
}));
|
||||
}
|
||||
|
||||
export function loadRawTranscript(sessionId: string): ModelMessage[] {
|
||||
return loadMessageRows(sessionId).map((row) => JSON.parse(row.message_json) as ModelMessage);
|
||||
}
|
||||
|
||||
export function loadTranscriptState(sessionId: string): LoadedTranscriptState {
|
||||
const rows = loadMessageRows(sessionId);
|
||||
return buildEffectiveTranscript(
|
||||
rows.map((row) => JSON.parse(row.message_json) as ModelMessage),
|
||||
rows.map((row) => row.seq),
|
||||
rows.map((row) => new Date(row.created_at)),
|
||||
loadLatestCompaction(sessionId),
|
||||
);
|
||||
}
|
||||
|
||||
export function loadTranscript(sessionId: string): ModelMessage[] {
|
||||
return loadTranscriptState(sessionId).messages;
|
||||
}
|
||||
|
||||
export function getNextMessageSequence(sessionId: string): number {
|
||||
return getNextSequence(getDatabase(), sessionId);
|
||||
}
|
||||
|
||||
export function appendMessages(sessionId: string, messages: ModelMessage[]): number[] {
|
||||
if (messages.length === 0) return [];
|
||||
|
||||
const insertedSeqs: number[] = [];
|
||||
withTransaction((db) => {
|
||||
const nextSeq = getNextSequence(db, sessionId);
|
||||
const insertMessage = db.prepare(`
|
||||
@@ -72,6 +147,7 @@ export function appendMessages(sessionId: string, messages: ModelMessage[]): voi
|
||||
messages.forEach((message, index) => {
|
||||
const seq = nextSeq + index;
|
||||
const createdAt = new Date().toISOString();
|
||||
insertedSeqs.push(seq);
|
||||
insertMessage.run(sessionId, seq, message.role, JSON.stringify(message), createdAt);
|
||||
|
||||
if (message.role === "assistant" && Array.isArray(message.content)) {
|
||||
@@ -122,29 +198,36 @@ export function appendMessages(sessionId: string, messages: ModelMessage[]): voi
|
||||
|
||||
updateSession.run(new Date().toISOString(), sessionId);
|
||||
});
|
||||
|
||||
return insertedSeqs;
|
||||
}
|
||||
|
||||
export function appendSystemMessage(sessionId: string, content: string): void {
|
||||
appendMessages(sessionId, [{ role: "system", content }]);
|
||||
export function appendSystemMessage(sessionId: string, content: string): number | null {
|
||||
return appendMessages(sessionId, [{ role: "system", content }])[0] ?? null;
|
||||
}
|
||||
|
||||
export function appendCompaction(sessionId: string, firstKeptSeq: number, summary: string, tokensBefore: number): void {
|
||||
withTransaction((db) => {
|
||||
db.prepare(`
|
||||
INSERT INTO compactions (session_id, first_kept_seq, summary, tokens_before, created_at)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
`).run(sessionId, firstKeptSeq, summary, tokensBefore, new Date().toISOString());
|
||||
|
||||
db.prepare(`
|
||||
UPDATE sessions
|
||||
SET updated_at = ?
|
||||
WHERE id = ?
|
||||
`).run(new Date().toISOString(), sessionId);
|
||||
});
|
||||
}
|
||||
|
||||
export function buildChatEntries(sessionId: string): ChatEntry[] {
|
||||
const db = getDatabase();
|
||||
const messageRows = db
|
||||
.prepare(`
|
||||
SELECT session_id, seq, role, message_json, created_at
|
||||
FROM messages
|
||||
WHERE session_id = ?
|
||||
ORDER BY seq ASC
|
||||
`)
|
||||
.all(sessionId) as MessageRow[];
|
||||
const toolResults = loadStoredToolResults(sessionId);
|
||||
const callMap = new Map<string, ToolCall>();
|
||||
const entries: ChatEntry[] = [];
|
||||
|
||||
for (const row of messageRows) {
|
||||
const message = JSON.parse(row.message_json) as ModelMessage;
|
||||
const timestamp = new Date(row.created_at);
|
||||
for (const row of buildEffectiveMessageRecords(sessionId)) {
|
||||
const { message, timestamp } = row;
|
||||
|
||||
if (message.role === "user") {
|
||||
const content = renderUserContent(message.content);
|
||||
@@ -155,7 +238,8 @@ export function buildChatEntries(sessionId: string): ChatEntry[] {
|
||||
}
|
||||
|
||||
if (message.role === "system") {
|
||||
const content = typeof message.content === "string" ? message.content.trim() : "";
|
||||
const content =
|
||||
getCompactionSummaryText(message) ?? (typeof message.content === "string" ? message.content.trim() : "");
|
||||
if (content) {
|
||||
entries.push({ type: "assistant", content, timestamp });
|
||||
}
|
||||
|
||||
@@ -1177,6 +1177,12 @@ export function App({ agent, startupConfig, initialMessage, onExit }: AppProps)
|
||||
]);
|
||||
}
|
||||
|
||||
if (!isStale() && agent.getSessionId()) {
|
||||
setMessages(agent.getChatEntries());
|
||||
setSessionTitle(agent.getSessionTitle());
|
||||
setSessionId(agent.getSessionId());
|
||||
}
|
||||
|
||||
contentAccRef.current = "";
|
||||
if (!isStale()) {
|
||||
setStreamContent("");
|
||||
|
||||
Reference in new issue
Block a user