diff --git a/THIRD-PARTY-NOTICES.md b/THIRD-PARTY-NOTICES.md index 6dc723c7..696f2529 100644 --- a/THIRD-PARTY-NOTICES.md +++ b/THIRD-PARTY-NOTICES.md @@ -27,26 +27,25 @@ standard text of the named license. | Package | Version | License | Copyright holder | | --- | --- | --- | --- | -| `@bufbuild/protobuf` | 2.14.1 | (Apache-2.0 AND BSD-3-Clause) | (not stated) | -| `@gajae-code/agent-core` | 0.16.4 | MIT | Yeachan-Heo and Gajae Code Contributors | -| `@gajae-code/ai` | 0.16.4 | MIT | Yeachan-Heo and Gajae Code Contributors | -| `@gajae-code/coding-agent` | 0.16.4 | MIT | Yeachan-Heo and Gajae Code Contributors | -| `@gajae-code/natives` | 0.16.4 | MIT | Yeachan-Heo | -| `@gajae-code/natives-darwin-arm64` | 0.16.4 | MIT | Yeachan-Heo | -| `@gajae-code/natives-darwin-x64` | 0.16.4 | MIT | Yeachan-Heo | -| `@gajae-code/natives-linux-arm64` | 0.16.4 | MIT | Yeachan-Heo | -| `@gajae-code/natives-linux-x64` | 0.16.4 | MIT | Yeachan-Heo | -| `@gajae-code/natives-win32-x64` | 0.16.4 | MIT | Yeachan-Heo | -| `@gajae-code/stats` | 0.16.4 | MIT | Yeachan-Heo | -| `@gajae-code/tui` | 0.16.4 | MIT | Yeachan-Heo and Gajae Code Contributors | -| `@gajae-code/utils` | 0.16.4 | MIT | Yeachan-Heo | +| `@bufbuild/protobuf` | 2.15.0 | (Apache-2.0 AND BSD-3-Clause) | (not stated) | +| `@gajae-code/agent-core` | 0.17.6 | MIT | Yeachan-Heo and Gajae Code Contributors | +| `@gajae-code/ai` | 0.17.6 | MIT | Yeachan-Heo and Gajae Code Contributors | +| `@gajae-code/coding-agent` | 0.17.6 | MIT | Yeachan-Heo and Gajae Code Contributors | +| `@gajae-code/natives` | 0.17.6 | MIT | Yeachan-Heo | +| `@gajae-code/natives-darwin-arm64` | 0.17.6 | MIT | Yeachan-Heo | +| `@gajae-code/natives-darwin-x64` | 0.17.6 | MIT | Yeachan-Heo | +| `@gajae-code/natives-linux-arm64` | 0.17.6 | MIT | Yeachan-Heo | +| `@gajae-code/natives-linux-x64` | 0.17.6 | MIT | Yeachan-Heo | +| `@gajae-code/natives-win32-x64` | 0.17.6 | MIT | Yeachan-Heo | +| `@gajae-code/stats` | 0.17.6 | MIT | Yeachan-Heo | +| `@gajae-code/tui` | 0.17.6 | MIT | Yeachan-Heo and Gajae Code Contributors | +| `@gajae-code/utils` | 0.17.6 | MIT | Yeachan-Heo | | `@nodable/entities` | 3.0.0 | MIT | Amit Gupta (https://solothought.com) | | `@puppeteer/browsers` | 2.13.2 | Apache-2.0 | The Chromium Authors | | `@tokenizer/token` | 0.3.0 | MIT | Borewit | | `@xterm/headless` | 6.0.0 | MIT | (not stated) | | `cookie-signature` | 1.0.6 | MIT | TJ Holowaychuk | | `degenerator` | 5.0.1 | MIT | Nathan Rajlich (http://n8.io/) | -| `dingbat-to-unicode` | 1.0.1 | BSD-2-Clause | Michael Williamson | | `format` | 0.2.2 | UNKNOWN | Sami Samhuri | | `highlightjs-vue` | 1.0.0 | CC0-1.0 | Sara Lissette (https://github.com/LissetteIbnz) | | `html-parse-stringify` | 3.0.1 | MIT | Henrik Joreteg | @@ -440,7 +439,7 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` -### @bufbuild/protobuf 2.14.1 +### @bufbuild/protobuf 2.15.0 License: (Apache-2.0 AND BSD-3-Clause) Homepage: https://protobufes.com/ @@ -448,7 +447,7 @@ Homepage: https://protobufes.com/ _This package ships no license file. It declares (Apache-2.0 AND BSD-3-Clause); the terms are the standard text of that license._ -### @colors/colors 1.6.0 +### @colors/colors 1.6.1 License: MIT Copyright holder: DABH @@ -483,7 +482,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` -### @dabh/diagnostics 2.0.8 +### @dabh/diagnostics 2.0.9 License: MIT Copyright holder: Arnout Kazemier @@ -512,7 +511,7 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` -### @gajae-code/agent-core 0.16.4 +### @gajae-code/agent-core 0.17.6 License: MIT Copyright holder: Yeachan-Heo and Gajae Code Contributors @@ -521,7 +520,7 @@ Homepage: https://gajae-code.com _This package ships no license file. It declares MIT; the terms are the standard text of that license._ -### @gajae-code/ai 0.16.4 +### @gajae-code/ai 0.17.6 License: MIT Copyright holder: Yeachan-Heo and Gajae Code Contributors @@ -530,7 +529,7 @@ Homepage: https://gajae-code.com _This package ships no license file. It declares MIT; the terms are the standard text of that license._ -### @gajae-code/coding-agent 0.16.4 +### @gajae-code/coding-agent 0.17.6 License: MIT Copyright holder: Yeachan-Heo and Gajae Code Contributors @@ -539,7 +538,7 @@ Homepage: https://gajae-code.com _This package ships no license file. It declares MIT; the terms are the standard text of that license._ -### @gajae-code/natives 0.16.4 +### @gajae-code/natives 0.17.6 License: MIT Copyright holder: Yeachan-Heo @@ -548,7 +547,7 @@ Homepage: https://gajae-code.com _This package ships no license file. It declares MIT; the terms are the standard text of that license._ -### @gajae-code/natives-darwin-arm64 0.16.4 +### @gajae-code/natives-darwin-arm64 0.17.6 License: MIT Copyright holder: Yeachan-Heo @@ -557,7 +556,7 @@ Homepage: https://gajae-code.com _This package ships no license file. It declares MIT; the terms are the standard text of that license._ -### @gajae-code/natives-darwin-x64 0.16.4 +### @gajae-code/natives-darwin-x64 0.17.6 License: MIT Copyright holder: Yeachan-Heo @@ -566,7 +565,7 @@ Homepage: https://gajae-code.com _This package ships no license file. It declares MIT; the terms are the standard text of that license._ -### @gajae-code/natives-linux-arm64 0.16.4 +### @gajae-code/natives-linux-arm64 0.17.6 License: MIT Copyright holder: Yeachan-Heo @@ -575,7 +574,7 @@ Homepage: https://gajae-code.com _This package ships no license file. It declares MIT; the terms are the standard text of that license._ -### @gajae-code/natives-linux-x64 0.16.4 +### @gajae-code/natives-linux-x64 0.17.6 License: MIT Copyright holder: Yeachan-Heo @@ -584,7 +583,7 @@ Homepage: https://gajae-code.com _This package ships no license file. It declares MIT; the terms are the standard text of that license._ -### @gajae-code/natives-win32-x64 0.16.4 +### @gajae-code/natives-win32-x64 0.17.6 License: MIT Copyright holder: Yeachan-Heo @@ -593,7 +592,7 @@ Homepage: https://gajae-code.com _This package ships no license file. It declares MIT; the terms are the standard text of that license._ -### @gajae-code/stats 0.16.4 +### @gajae-code/stats 0.17.6 License: MIT Copyright holder: Yeachan-Heo @@ -602,7 +601,7 @@ Homepage: https://gajae-code.com _This package ships no license file. It declares MIT; the terms are the standard text of that license._ -### @gajae-code/tui 0.16.4 +### @gajae-code/tui 0.17.6 License: MIT Copyright holder: Yeachan-Heo and Gajae Code Contributors @@ -611,7 +610,7 @@ Homepage: https://gajae-code.com _This package ships no license file. It declares MIT; the terms are the standard text of that license._ -### @gajae-code/utils 0.16.4 +### @gajae-code/utils 0.17.6 License: MIT Copyright holder: Yeachan-Heo @@ -5559,7 +5558,7 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` -### commander 8.3.0 +### commander 14.0.3 License: MIT Copyright holder: TJ Holowaychuk @@ -6527,14 +6526,36 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ``` -### dingbat-to-unicode 1.0.1 +### dingbat-to-unicode 1.0.2 License: BSD-2-Clause Copyright holder: Michael Williamson Homepage: https://github.com/mwilliamson/dingbat-to-unicode#readme -_This package ships no license file. It declares BSD-2-Clause; the terms are -the standard text of that license._ +```text +Copyright (c) 2021, Michael Williamson +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` ### dom-serializer 3.1.1 @@ -7614,7 +7635,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` -### fast-xml-builder 1.3.0 +### fast-xml-builder 1.3.1 License: MIT Copyright holder: Amit Gupta (https://solothought.com) @@ -9403,10 +9424,10 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` -### is-unsafe 2.0.0 +### is-unsafe 2.0.2 License: MIT -Copyright holder: Amit Gupta (https://solothought.work/) +Copyright holder: Amit Gupta (https://solothought.com/) ```text MIT License @@ -9611,6 +9632,35 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` +### jsonc-parser 3.3.1 + +License: MIT +Copyright holder: Microsoft Corporation + +```text +The MIT License (MIT) + +Copyright (c) Microsoft + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + ### jszip 3.10.1 License: (MIT OR GPL-3.0-or-later) @@ -15300,7 +15350,7 @@ will be liable to anyone for any damages related to this software or this license, under any kind of legal claim.*** ``` -### lucide-react 1.41.0 +### lucide-react 1.48.0 License: ISC Copyright holder: Eric Fennis @@ -15480,35 +15530,6 @@ Redistribution and use in source and binary forms, with or without modification, This software is provided by the copyright holders and contributors “as is” and any express or implied warranties, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose are disclaimed. In no event shall the copyright owner or contributors be liable for any direct, indirect, incidental, special, exemplary, or consequential damages (including, but not limited to, procurement of substitute goods or services; loss of use, data, or profits; or business interruption) however caused and on any theory of liability, whether in contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this software, even if advised of the possibility of such damage. ``` -### markit-ai 0.5.3 - -License: MIT -Homepage: https://github.com/Michaelliv/markit#readme - -```text -MIT License - -Copyright (c) 2026 Michael Liv - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -``` - ### math-intrinsics 1.1.0 License: MIT @@ -17176,14 +17197,13 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` -### moment 2.30.1 +### moment 2.31.0 License: MIT -Copyright holder: Iskren Ivov Chernev (https://github.com/ichernev) Homepage: https://momentjs.com ```text -Copyright (c) JS Foundation and other contributors +Copyright (c) OpenJS Foundation and other contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -19607,7 +19627,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` -### sax 1.6.0 +### sax 1.6.1 License: BlueOak-1.0.0 Copyright holder: Isaac Z. Schlueter (http://blog.izs.me/) @@ -20623,7 +20643,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` -### strnum 2.4.1 +### strnum 2.4.2 License: MIT Copyright holder: Amit Gupta (https://solothought.work/) @@ -22782,33 +22802,33 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` -### xmlbuilder 10.1.1 +### xmlbuilder 11.0.1 License: MIT Copyright holder: Ozgur Ozcitak Homepage: http://github.com/oozcitak/xmlbuilder-js ```text -The MIT License (MIT) - -Copyright (c) 2013 Ozgur Ozcitak - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +The MIT License (MIT) + +Copyright (c) 2013 Ozgur Ozcitak + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` diff --git a/docs/V2-SESSION-HANDOFF.md b/docs/V2-SESSION-HANDOFF.md index dbc14ace..8641f1a8 100644 --- a/docs/V2-SESSION-HANDOFF.md +++ b/docs/V2-SESSION-HANDOFF.md @@ -38,6 +38,22 @@ Still open in this repo: #160 (serviceTier Settings exposure vs documented fixed — owner product decision) and #158/#162 (upstream). PR #44 (Windows) was already closed on 2026-09-16; Windows desktop stays out of scope. +2026-09-25: beta.20 is being batched rather than cut for `0c5e6aa` alone. +First item: SDK 0.16.4 → 0.17.6 on `chore/gjc-sdk-0.17.6`. The lifecycle +patch is ported (32 files, 209 edits, none dropped; per-edit decisions in +`patches/gjc-sdk-lifecycle/README.md`), agent/project/settings extension +modules stay out of app sessions, and a delegated `task` with an unknown +`model` key now runs on the parent model instead of being rejected (0.17.6 +strips unknown keys before the app sees them). The built-in preset copy is +regenerated from 0.17.6 (17 missing presets added) and pinned by +`server/builtin-model-profiles-contract.bun.test.ts`. Upstream fixes for +#158 (`Yeachan-Heo/gajae-code#5743`) and #162 (`#5745`) are contained in the +v0.17.4 and v0.17.6 tags; close both after the bump is observed live. Full +`npm run verify`, `server:payload:macos` and `src-tauri` cargo test +(382 + 11) pass on the branch. #175 (Windows timeline) answered and closed +as not planned. The `/Applications` copy on the primary Mac is beta.18 +(0.2.12), not beta.14 as stated below. + **Published and installed: v2.0.0-beta.14 / desktop 0.2.8**, source `60c98e3`, release `386182917`, `updateMode: production`. It supersedes beta.13, whose binary shipped with the updater compiled out (`RELEASE-BETA13-ACCEPTANCE.md`); diff --git a/package-lock.json b/package-lock.json index 250f0c8e..7236015b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "hasInstallScript": true, "license": "MIT", "dependencies": { - "@gajae-code/coding-agent": "0.16.4", + "@gajae-code/coding-agent": "0.17.6", "@octokit/rest": "^22.0.0", "@tailwindcss/typography": "^0.5.16", "@tanstack/react-query": "^5.102.6", @@ -524,15 +524,15 @@ } }, "node_modules/@bufbuild/protobuf": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.14.1.tgz", - "integrity": "sha512-agRJn3+EJDUe8AvxTx/LnHA/GErvLE62pSaSk7+MwFOtOv8eWBu/qCq2qoZjBjVZ3C2aiFJCveuSs17KMkYGOw==", + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.15.0.tgz", + "integrity": "sha512-DAheWUkVr/SJTWCc+lg9dhY0eN4SaWlf4+bG1KzHeXbnqt0AfB/NX0Z+VunGlM1ki1B4zVvye27MpKh/svySUA==", "license": "(Apache-2.0 AND BSD-3-Clause)" }, "node_modules/@colors/colors": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", - "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.1.tgz", + "integrity": "sha512-dTmUJzXSuayBK+hZydEaXd2mhx61qWQwkwaBBY6LyEOVx/L9aQU5ac8eFNEsd9nrD1+zb9zvDCphLSe8g1F4Qw==", "license": "MIT", "engines": { "node": ">=0.1.90" @@ -846,9 +846,9 @@ } }, "node_modules/@dabh/diagnostics": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.8.tgz", - "integrity": "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==", + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.9.tgz", + "integrity": "sha512-R6siwR65Hm+3yfgP7o8DKhNvputQAwfoz9zTc3kyDudnomj2/BcLmD+uGQQPICjuFUp8ounPBU+jmKsocwVVAg==", "license": "MIT", "dependencies": { "@so-ric/colorspace": "^1.1.6", @@ -1581,14 +1581,14 @@ } }, "node_modules/@gajae-code/agent-core": { - "version": "0.16.4", - "resolved": "https://registry.npmjs.org/@gajae-code/agent-core/-/agent-core-0.16.4.tgz", - "integrity": "sha512-Ck0TIybhjf7qfWNQMbFEHDXCUiT0xDOq27DCeO2UbRglorI49iBmbw7Lq2J8yWVf8FgtEVD2RXEENX13GwH1Tg==", + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@gajae-code/agent-core/-/agent-core-0.17.6.tgz", + "integrity": "sha512-BX97ZGbBk90JhXZ4AUrFR1GLMth92gW+9YDEhW9nu4jbAgD6wtMVHoWUoXMgQHc/ulMxEczCmilMXcGozpmHqQ==", "license": "MIT", "dependencies": { - "@gajae-code/ai": "0.16.4", - "@gajae-code/natives": "0.16.4", - "@gajae-code/utils": "0.16.4", + "@gajae-code/ai": "0.17.6", + "@gajae-code/natives": "0.17.6", + "@gajae-code/utils": "0.17.6", "@opentelemetry/api": "^1.9.0" }, "engines": { @@ -1596,15 +1596,16 @@ } }, "node_modules/@gajae-code/ai": { - "version": "0.16.4", - "resolved": "https://registry.npmjs.org/@gajae-code/ai/-/ai-0.16.4.tgz", - "integrity": "sha512-q89+ggA3vWaWxx3u9cEelpdiLtHX2uHFsqHkJtYZDVj40RNgU8PPpYwPvHi3jwJ/THQE49GOwSddAPvT57N5KA==", + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@gajae-code/ai/-/ai-0.17.6.tgz", + "integrity": "sha512-g0ArBNjk3fgyhTYW8M3+HzhHuB6N53EL7/a3DiLVx/ctqflTbeP5JDRadA+Wpbbk+Gsv73w9P8ygYrd3FvrUjA==", "license": "MIT", "dependencies": { + "@agentclientprotocol/sdk": "1.3.0", "@anthropic-ai/sdk": "^0.94.0", "@bufbuild/protobuf": "^2.12.0", - "@gajae-code/natives": "0.16.4", - "@gajae-code/utils": "0.16.4", + "@gajae-code/natives": "0.17.6", + "@gajae-code/utils": "0.17.6", "openai": "^6.36.0", "partial-json": "^0.1.7", "zod": "4.4.3" @@ -1617,32 +1618,40 @@ } }, "node_modules/@gajae-code/coding-agent": { - "version": "0.16.4", - "resolved": "https://registry.npmjs.org/@gajae-code/coding-agent/-/coding-agent-0.16.4.tgz", - "integrity": "sha512-cnqyYOEGygPp87gCEkqahNiRYoBhL4gxvQnWY16lDADThfNjhrl7VP+5f9cLakevI+pjRbTSdDBw/iZ3Nc6PCw==", + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@gajae-code/coding-agent/-/coding-agent-0.17.6.tgz", + "integrity": "sha512-ENjs77fG9R5iv8EZFQfLSQKtoPTyi4qCwkJj0c2B8NxFWH9u2VN4V4OAAO0cibBLE5q/Zm4wEWsbHe5oh0xiNA==", "license": "MIT", "dependencies": { "@agentclientprotocol/sdk": "1.3.0", "@babel/parser": "^7.29.3", - "@gajae-code/agent-core": "0.16.4", - "@gajae-code/ai": "0.16.4", - "@gajae-code/natives": "0.16.4", - "@gajae-code/stats": "0.16.4", - "@gajae-code/tui": "0.16.4", - "@gajae-code/utils": "0.16.4", + "@gajae-code/agent-core": "0.17.6", + "@gajae-code/ai": "0.17.6", + "@gajae-code/natives": "0.17.6", + "@gajae-code/stats": "0.17.6", + "@gajae-code/tui": "0.17.6", + "@gajae-code/utils": "0.17.6", "@mozilla/readability": "^0.6.0", "@puppeteer/browsers": "^2.13.0", "@types/turndown": "5.0.6", "@xterm/headless": "^6.0.0", "chalk": "^5.6.2", + "commander": "14.0.3", "diff": "^9.0.0", + "exifr": "7.1.3", + "fast-xml-parser": "5.10.1", "fflate": "0.8.2", "handlebars": "^4.7.9", + "jsonc-parser": "3.3.1", + "jszip": "3.10.1", "linkedom": "^0.18.12", "lru-cache": "11.3.6", + "mammoth": "1.12.0", "marked": "18.0.6", - "markit-ai": "0.5.3", + "mupdf": "1.28.0", + "music-metadata": "11.14.0", "puppeteer-core": "^24.42.0", + "rss-parser": "3.13.0", "turndown": "7.2.4", "turndown-plugin-gfm": "1.0.2", "zod": "4.4.3" @@ -1677,25 +1686,25 @@ } }, "node_modules/@gajae-code/natives": { - "version": "0.16.4", - "resolved": "https://registry.npmjs.org/@gajae-code/natives/-/natives-0.16.4.tgz", - "integrity": "sha512-l+9f6Ql+FcOw0IcbbTLqoYUcwnRiPDQ7gB9AH/R4I98vwy5FiTcgRHHk5u1mKpiyoPGMICdLgJn18rhU6ivyHA==", + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@gajae-code/natives/-/natives-0.17.6.tgz", + "integrity": "sha512-yXrWm64YjwhY5rNW4DZ/TSHSnhTzJyW/k+m8ARQg4EKrTHAlZqf22U5GvxVQCs3IIKuSMrrEJnRJotMgtjkjkQ==", "license": "MIT", "engines": { "bun": ">=1.4.0" }, "optionalDependencies": { - "@gajae-code/natives-darwin-arm64": "0.16.4", - "@gajae-code/natives-darwin-x64": "0.16.4", - "@gajae-code/natives-linux-arm64": "0.16.4", - "@gajae-code/natives-linux-x64": "0.16.4", - "@gajae-code/natives-win32-x64": "0.16.4" + "@gajae-code/natives-darwin-arm64": "0.17.6", + "@gajae-code/natives-darwin-x64": "0.17.6", + "@gajae-code/natives-linux-arm64": "0.17.6", + "@gajae-code/natives-linux-x64": "0.17.6", + "@gajae-code/natives-win32-x64": "0.17.6" } }, "node_modules/@gajae-code/natives-darwin-arm64": { - "version": "0.16.4", - "resolved": "https://registry.npmjs.org/@gajae-code/natives-darwin-arm64/-/natives-darwin-arm64-0.16.4.tgz", - "integrity": "sha512-xDilnrxyyBrhBHhgjfer3ZOM4HnJ2KPixREP+8X4N9MPs8FPylRsHXoXjY60B2BT6LmSiBsCGNivY84pn7VwpA==", + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@gajae-code/natives-darwin-arm64/-/natives-darwin-arm64-0.17.6.tgz", + "integrity": "sha512-LxdcEVEQ/7I4wRJv8M6Ua5FKVwV0I7plHA9w05HgKHnCjzCbcI67T5PW69j675HkM6EOSjBI4rvYRnlDmfSJog==", "cpu": [ "arm64" ], @@ -1706,9 +1715,9 @@ ] }, "node_modules/@gajae-code/natives-darwin-x64": { - "version": "0.16.4", - "resolved": "https://registry.npmjs.org/@gajae-code/natives-darwin-x64/-/natives-darwin-x64-0.16.4.tgz", - "integrity": "sha512-DLjM/6bO13oNCpzWMRAfhMRRhmXLD0spzN8OYAUQ4A7U51uxFVesZy0r+p1ujIK7EJtmhl4DJSJgcgs7cZWywA==", + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@gajae-code/natives-darwin-x64/-/natives-darwin-x64-0.17.6.tgz", + "integrity": "sha512-SKyqft6uMb8qleyaHKoHesoJHYa3W8smFwEncxGpnUYxD3/8E+I728+J80lQhKKp4bkKKQj1S+x7/QwNKNZh5g==", "cpu": [ "x64" ], @@ -1719,9 +1728,9 @@ ] }, "node_modules/@gajae-code/natives-linux-arm64": { - "version": "0.16.4", - "resolved": "https://registry.npmjs.org/@gajae-code/natives-linux-arm64/-/natives-linux-arm64-0.16.4.tgz", - "integrity": "sha512-Fwxe810dQ9jQsqT6/GhlAeXxxO+/gdR1C7nqhFC0HmkzVjO7rs5Sc9zfvcGMJEFkktD3qjVPcf6/t/mDpcHkBg==", + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@gajae-code/natives-linux-arm64/-/natives-linux-arm64-0.17.6.tgz", + "integrity": "sha512-BaqISRp4F2v9tRcfyNs2+RIuh1bUStnPFxY5wO+JlVhg5SmWe8XcYjYw7g+x3Hg4w1P5UC0NWB0Tdnj9WseB+A==", "cpu": [ "arm64" ], @@ -1732,9 +1741,9 @@ ] }, "node_modules/@gajae-code/natives-linux-x64": { - "version": "0.16.4", - "resolved": "https://registry.npmjs.org/@gajae-code/natives-linux-x64/-/natives-linux-x64-0.16.4.tgz", - "integrity": "sha512-D4qfYuWaUU1WpCDPAQVRPKACMA1PX0gozweGwM8+E/CA/8IzLV05qQLus128EL1BLUNI1sPesV4hBpTFhm4+pA==", + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@gajae-code/natives-linux-x64/-/natives-linux-x64-0.17.6.tgz", + "integrity": "sha512-YpLnJsRata+XowQe7jShRTqeYnoiscPdfOx7ipgi3oCRTeXgFHHg3+1tPTJC1GkQR6K3PRqXJCw5MxRJ7Lh0HA==", "cpu": [ "x64" ], @@ -1745,9 +1754,9 @@ ] }, "node_modules/@gajae-code/natives-win32-x64": { - "version": "0.16.4", - "resolved": "https://registry.npmjs.org/@gajae-code/natives-win32-x64/-/natives-win32-x64-0.16.4.tgz", - "integrity": "sha512-xRllpdWBMu1CGuW7/nS2BuFbePI60rc/zAVn7xlszKGlle3jqWdQ+4DL10cNuYzekShzadPASTblPFVvfNs5/w==", + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@gajae-code/natives-win32-x64/-/natives-win32-x64-0.17.6.tgz", + "integrity": "sha512-apzrzbzYNJqNfHD+9N33Z3XNNzMqWUGlgN2zbnW6P69SizM6UeEhc0zmCB2zVUkpmV3yMexpSwM0sUqiXjmy3A==", "cpu": [ "x64" ], @@ -1758,13 +1767,13 @@ ] }, "node_modules/@gajae-code/stats": { - "version": "0.16.4", - "resolved": "https://registry.npmjs.org/@gajae-code/stats/-/stats-0.16.4.tgz", - "integrity": "sha512-DAcqmoF6x1bdYvgaBBQPf7QMvXFY6BUcNE5Gof8V8OLlitwhMyCwwdabuCjsZ+q/gNDea1uxw7lTIVBAjEOSxA==", + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@gajae-code/stats/-/stats-0.17.6.tgz", + "integrity": "sha512-TPMysv3Vgr/eVQ886O43vfjdCsfmF/hksUTtwWsb4b7duNM0SHRPmhrRDdpiIK/zDaKZhkuYCuQBz4hKr8IxAA==", "license": "MIT", "dependencies": { - "@gajae-code/ai": "0.16.4", - "@gajae-code/utils": "0.16.4", + "@gajae-code/ai": "0.17.6", + "@gajae-code/utils": "0.17.6", "@tailwindcss/node": "^4.2.4", "chart.js": "^4.5.1", "date-fns": "^4.1.0", @@ -1792,9 +1801,9 @@ } }, "node_modules/@gajae-code/stats/node_modules/lucide-react": { - "version": "1.41.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.41.0.tgz", - "integrity": "sha512-6lksP35l6KszDKUeRTi4LV7i6DEe0Yzl2ALJm9j4c5xEYN91GdW1xGsawGMOg2mgjF5GHBVX8pKX9kP+cWsP3Q==", + "version": "1.48.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.48.0.tgz", + "integrity": "sha512-R0CIKY/fXiC6y9xRBADgsK+VW2p/pcTJOMhLZf1T+uG+vJUvyUR02nGOgmIIC7MPkiNK4Ox8QDnbqF8rJYWPZQ==", "license": "ISC", "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" @@ -1822,13 +1831,13 @@ } }, "node_modules/@gajae-code/tui": { - "version": "0.16.4", - "resolved": "https://registry.npmjs.org/@gajae-code/tui/-/tui-0.16.4.tgz", - "integrity": "sha512-XLwNT+zC3tMtPtIvwd2AHDpKXzl6wVbh4nXaGdmHJyPjEtrracBFVCyn9VwIyLNkcbOcRl02vhO4ltXnkR3eBQ==", + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@gajae-code/tui/-/tui-0.17.6.tgz", + "integrity": "sha512-znVIJMLzXUoUw/muDITU9bL4sh2Jsll+mAU+YtUrfuP5qt7Ba1qIL3IRPMPY3vRZ3PlWN5E9+9QCrtWS0OM4XQ==", "license": "MIT", "dependencies": { - "@gajae-code/natives": "0.16.4", - "@gajae-code/utils": "0.16.4", + "@gajae-code/natives": "0.17.6", + "@gajae-code/utils": "0.17.6", "lru-cache": "11.3.6", "marked": "18.0.6" }, @@ -1846,12 +1855,12 @@ } }, "node_modules/@gajae-code/utils": { - "version": "0.16.4", - "resolved": "https://registry.npmjs.org/@gajae-code/utils/-/utils-0.16.4.tgz", - "integrity": "sha512-Yli+ugw+7QWQejvzRWZiRmCu4IIFcDkKoKM+gRMCqNnl8VlPCZyqb4R/osXBY4pmjqnwoXts4+kUSGQhH52DLg==", + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@gajae-code/utils/-/utils-0.17.6.tgz", + "integrity": "sha512-r7qOWdA5A2hL5qLY13VnZ5Syy2Xfgb4DQsxppLEypFNXYlyMAoz6UZxg1AogxthM/QeXVLR17ZtJVKx76mLrWg==", "license": "MIT", "dependencies": { - "@gajae-code/natives": "0.16.4", + "@gajae-code/natives": "0.17.6", "beautiful-mermaid": "^1.1.3", "handlebars": "^4.7.9", "winston": "^3.19.0", @@ -4319,16 +4328,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@react-grab/cli/node_modules/commander": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", - "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20" - } - }, "node_modules/@react-grab/cli/node_modules/ignore": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", @@ -6699,16 +6698,6 @@ "agent-install": "bin/agent-install.mjs" } }, - "node_modules/agent-install/node_modules/commander": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", - "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20" - } - }, "node_modules/ajv": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", @@ -7378,6 +7367,12 @@ "readable-stream": "^3.4.0" } }, + "node_modules/bluebird": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", + "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==", + "license": "MIT" + }, "node_modules/body-parser": { "version": "1.20.6", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.6.tgz", @@ -8158,6 +8153,15 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, "node_modules/comment-parser": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.5.tgz", @@ -9079,9 +9083,9 @@ } }, "node_modules/dingbat-to-unicode": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dingbat-to-unicode/-/dingbat-to-unicode-1.0.1.tgz", - "integrity": "sha512-98l0sW87ZT58pU4i61wa2OHwxbiYSbuxsCBozaVnYX2iCnr3bLM3fIes1/ej7h1YdOKuKt/MLs706TVnALA65w==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/dingbat-to-unicode/-/dingbat-to-unicode-1.0.2.tgz", + "integrity": "sha512-R0bRerYzy/EZP6QzD3Hkl6JjUDA1Mnn+pd928w0nWjPzihnbAR6dcjGJGiRyznj5E4S1duEKzWrTqw02DPcdzg==", "license": "BSD-2-Clause" }, "node_modules/dir-glob": { @@ -10435,9 +10439,9 @@ } }, "node_modules/fast-xml-builder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.3.0.tgz", - "integrity": "sha512-F74cZEdCvuw9P41GAC3rod4X04jjWGM1JPEv/GWSqFTWLsdyMSBMBMlm9Hk3GLBgLBbdBNY8yee0pQh2RBVESQ==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.3.1.tgz", + "integrity": "sha512-pIM/1n3ntFXKYrUZwW7QCK0gAW7XY+wzj1YMIV3tLDvPj/V+zTGJK5e3/4WJfwj0qWw2ElNXiTixda/R+3YSug==", "funding": [ { "type": "github", @@ -12423,9 +12427,9 @@ } }, "node_modules/is-unsafe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-unsafe/-/is-unsafe-2.0.0.tgz", - "integrity": "sha512-2LdV822R+wmI86unXA93WCFpL6g+av8ynWk0nrHyJqGop5VoocYsSLFgN8jrfalT6iGeLNM4KXuVSsULP53kEA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-unsafe/-/is-unsafe-2.0.2.tgz", + "integrity": "sha512-HgbIHPBH0KHHCcjLfGsCvhtPTVxjaAZlXjwdz7/GQC40SjSe4sfQsar8J5VFo8JOSbarkpV0OLG95bbaNd9aAQ==", "funding": [ { "type": "github", @@ -12642,7 +12646,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", - "dev": true, "license": "MIT" }, "node_modules/jsx-ast-utils": { @@ -13090,16 +13093,6 @@ "url": "https://opencollective.com/lint-staged" } }, - "node_modules/lint-staged/node_modules/commander": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", - "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20" - } - }, "node_modules/listr2": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", @@ -13446,6 +13439,15 @@ "node": ">= 12.0.0" } }, + "node_modules/logform/node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/longest-streak": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", @@ -13580,21 +13582,6 @@ "node": ">=12.0.0" } }, - "node_modules/mammoth/node_modules/bluebird": { - "version": "3.4.7", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", - "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==", - "license": "MIT" - }, - "node_modules/mammoth/node_modules/xmlbuilder": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-10.1.1.tgz", - "integrity": "sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg==", - "license": "MIT", - "engines": { - "node": ">=4.0" - } - }, "node_modules/markdown-table": { "version": "3.0.4", "resolved": "https://registry.npmmirror.com/markdown-table/-/markdown-table-3.0.4.tgz", @@ -13617,49 +13604,6 @@ "node": ">= 20" } }, - "node_modules/markit-ai": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/markit-ai/-/markit-ai-0.5.3.tgz", - "integrity": "sha512-h4nhn6a/SNXEdc3kLVtL37TspxjUNCNL0OM7LRWxd389ZByI/B7bjNNgxFdVAT0O+H7ZekSwLdVe/lws1l2AZQ==", - "license": "MIT", - "dependencies": { - "chalk": "^5.6.2", - "commander": "^14.0.3", - "exifr": "^7.1.3", - "fast-xml-parser": "^5.5.9", - "jszip": "^3.10.1", - "mammoth": "^1.9.0", - "mupdf": "^1.27.0", - "music-metadata": "^11.12.3", - "rss-parser": "^3.13.0", - "turndown": "^7.2.0", - "turndown-plugin-gfm": "^1.0.2" - }, - "bin": { - "markit": "dist/main.js" - } - }, - "node_modules/markit-ai/node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/markit-ai/node_modules/commander": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", - "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", - "license": "MIT", - "engines": { - "node": ">=20" - } - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -14776,9 +14720,9 @@ "license": "MIT" }, "node_modules/moment": { - "version": "2.30.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", - "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.31.0.tgz", + "integrity": "sha512-0acOTfMiWOheYS4eoWb80yYMb/JLvVv9SHbs2PehaDzfUG0Bw855SKyk0IKTnPGa5+U2bmi3W68l1+sGLX/pvw==", "license": "MIT", "engines": { "node": "*" @@ -14847,9 +14791,9 @@ } }, "node_modules/music-metadata/node_modules/content-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", "license": "MIT", "engines": { "node": ">=18" @@ -16611,16 +16555,6 @@ "agent-install": "bin/agent-install.mjs" } }, - "node_modules/react-doctor/node_modules/commander": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", - "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20" - } - }, "node_modules/react-dom": { "version": "19.2.8", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", @@ -16888,16 +16822,6 @@ } } }, - "node_modules/react-scan/node_modules/commander": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", - "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20" - } - }, "node_modules/react-style-singleton": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", @@ -17527,9 +17451,9 @@ "license": "MIT" }, "node_modules/sax": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", - "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz", + "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", "license": "BlueOak-1.0.0", "engines": { "node": ">=11.0.0" @@ -18295,9 +18219,9 @@ } }, "node_modules/strnum": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.4.1.tgz", - "integrity": "sha512-M9eUSMT2dCB2cTNPG7UYj6KuK7RJR2SN2+yCV/fTW3xzTCS6EaGZ5pSMgDIjB7r8zSfTGk+dvvn9rTjpVS9Mwg==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.4.2.tgz", + "integrity": "sha512-rDG3Ah4TV0k1hWvLSzkZtMmLN9+eS+h3knq4MP6A42Y3Yh5qGNnOUs1jJkoSr8FG5dsL28c7KgkIBzSEykqtuw==", "funding": [ { "type": "github", @@ -20071,6 +19995,15 @@ "node": ">=4.0" } }, + "node_modules/xmlbuilder": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-10.1.1.tgz", + "integrity": "sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg==", + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index a0c3fbea..24e25997 100644 --- a/package.json +++ b/package.json @@ -138,7 +138,7 @@ } }, "dependencies": { - "@gajae-code/coding-agent": "0.16.4", + "@gajae-code/coding-agent": "0.17.6", "@octokit/rest": "^22.0.0", "@tailwindcss/typography": "^0.5.16", "@tanstack/react-query": "^5.102.6", diff --git a/patches/gjc-sdk-lifecycle/README.md b/patches/gjc-sdk-lifecycle/README.md index 2099f454..1914acc6 100644 --- a/patches/gjc-sdk-lifecycle/README.md +++ b/patches/gjc-sdk-lifecycle/README.md @@ -1,10 +1,12 @@ # GJC SDK lifecycle remediation — app-owned patch -Status: producer-completion and enabled-default-host remediation verified in isolation -and applied through a clean checkout `npm ci` on September 8, 2026 for exactly -**0.16.4**. All 32 files passed pristine before-hash validation and installation; -check-only subsequently verified all 32. The app, worker and native validators -share `shared/sdkLifecyclePolicy.json` (maximum 32 files). This is not +Status: ported to exactly **0.17.6** on September 25, 2026 and applied through a +clean `rm -rf node_modules && npm ci`. All 32 files passed pristine 0.17.6 +before-hash validation and installation; check-only subsequently verified all 32. +The previous verified pin was 0.16.4 (September 8, 2026); see +[0.17.6 port](#0176-port) for every re-anchored edit and every 0.17.6 addition. +The app, worker and native validators share `shared/sdkLifecyclePolicy.json` +(maximum 32 files). This is not an updater/plugin fork, an upstream publication, or permission to clear `sdk_background_ownership_unproven`. Root and packaging postinstall apply this same manifest; runtime manifest v2 checks all post-hashes before SDK startup. @@ -18,26 +20,21 @@ full before/after SHA-256 digests and ordered replace-once edits. Each `before` snippet occurs exactly once in the preceding source. The complete transformed file must match `afterSha256`; snippet matches alone are not sufficient. -Pristine packages were fetched on September 8, 2026 into a new temporary -directory with `npm pack --ignore-scripts --registry=https://registry.npmjs.org`. -The artifact review did not mutate installed dependencies. Parent integration -subsequently applied the original eight verified edits using the guarded applier. -The producer candidate started from independently fetched pristine packages and -that same guarded eight-file application. New edits were made only to the isolated -candidate and encoded as additional replace-once operations. All old replacements -remain in order, including the four otherwise unchanged leaf/registry files; -versions remain pinned to the published 0.16.4 packages. +Pristine 0.17.6 packages were fetched on September 25, 2026 into a new temporary +directory with `npm pack --ignore-scripts`; their SHA-1 digests match the registry +`dist` values below. Every `beforeSha256` is the pristine 0.17.6 file, and every +`afterSha256` is the complete transformed file. The port replayed the 0.16.4 +manifest's ordered edits against these files; edits whose `before` snippet no +longer occurred exactly once were re-anchored with the same semantics, and new +0.17.6 detached work inside already-patched files was given its own replace-once +edits (209 edits in total, previously 191). No installed dependency file was edited +by hand: the manifest is applied only by `npm run apply:sdk-patch`/postinstall. | Published package | npm tarball SHA-1 | npm integrity | | --- | --- | --- | -| `@gajae-code/coding-agent@0.16.4` | `4613aba27825509b0be68de5b9eea05fd8c6e841` | `sha512-cnqyYOEGygPp87gCEkqahNiRYoBhL4gxvQnWY16lDADThfNjhrl7VP+5f9cLakevI+pjRbTSdDBw/iZ3Nc6PCw==` | -| `@gajae-code/agent-core@0.16.4` | `64f8c249bad9c0f1ff8574ec476417294ef3c6c9` | `sha512-Ck0TIybhjf7qfWNQMbFEHDXCUiT0xDOq27DCeO2UbRglorI49iBmbw7Lq2J8yWVf8FgtEVD2RXEENX13GwH1Tg==` | -| `@gajae-code/ai@0.16.4` | `e69483ff743d6e9b23e63e1781d3ed099e749f3a` | `sha512-q89+ggA3vWaWxx3u9cEelpdiLtHX2uHFsqHkJtYZDVj40RNgU8PPpYwPvHi3jwJ/THQE49GOwSddAPvT57N5KA==` | - -The separately inspected official `coding-agent@0.16.6` tarball has SHA-1 -`b72393e4107c69d6cfaf5549e64971c5b8017232`. Its relevant prewarm, disposal and -forced-recovery blocks, plus its async-job manager, are unchanged from 0.16.4. -That comparison is not runtime compatibility qualification for a pin upgrade. +| `@gajae-code/coding-agent@0.17.6` | `d5b540e05e435dbd209be1fe3db5861bb9f1c9cb` | `sha512-ENjs77fG9R5iv8EZFQfLSQKtoPTyi4qCwkJj0c2B8NxFWH9u2VN4V4OAAO0cibBLE5q/Zm4wEWsbHe5oh0xiNA==` | +| `@gajae-code/agent-core@0.17.6` | `bd915aea9624073a2041e5feacae900d5a45be4c` | `sha512-BX97ZGbBk90JhXZ4AUrFR1GLMth92gW+9YDEhW9nu4jbAgD6wtMVHoWUoXMgQHc/ulMxEczCmilMXcGozpmHqQ==` | +| `@gajae-code/ai@0.17.6` | `62916c5f0d83638cae49ec1a99b0b2157179d049` | `sha512-g0ArBNjk3fgyhTYW8M3+HzhHuB6N53EL7/a3DiLVx/ctqflTbeP5JDRadA+Wpbbk+Gsv73w9P8ygYrd3FvrUjA==` | ## What changes @@ -169,6 +166,81 @@ These APIs are additions to the Bun source runtime, not changes to npm declarati files. The async-local iterator retention is qualified for the pinned Bun runtime; it does not constitute browser or other JavaScript-runtime qualification. +## 0.17.6 port + +Replay of the 0.16.4 manifest against pristine 0.17.6: 21 of 32 files applied +unchanged (only their hashes changed); 11 files had edits whose anchor moved. +**No edit was dropped.** Every failing edit was re-anchored to the 0.17.6 code +with the same semantics; none of the guarantees is implemented upstream yet. + +| File | Edit | Decision | Reason / new anchor | +| --- | --- | --- | --- | +| `coding-agent/src/sdk/session.ts` | #3 | re-anchored | `session.registerToolSessionTransitionCleanup(joinStartupTasks)` stays directly after `hasSession = true`, now before 0.17.6's owned-MCP cleanup registration. | +| | #7 | re-anchored | failed-creation async-job disposal still joins `awaitRetainedDisposalCompletion()`; only the surrounding comment changed. | +| | #10 | re-anchored | context-file discovery now passes `{ cwd, agentDir, profileAuthority }`; still wrapped in `ownFactoryTask`. | +| | #11 | re-anchored (split) | hook-discovery comment reworded; the two `appFactoryUnknown` insertions are separate anchors. | +| | #12 | re-anchored | constrained plugin hooks gained `activationGeneration`/structured findings; same insertion point. | +| | #17 | re-anchored (split) | 0.17.6 replaced conventional MCP publication with serialized owned/inherited publishers. Upstream now joins those tails in tool-session cleanup, but does not count them as activity; every reactive `void sync…`/`refreshMCPTools` publication is still owned by `startOwnedStartupTask`, including the new inherited publisher. | +| | #19 | re-anchored | failed-creation branch comment reworded; `await joinAppHosts()` stays first. | +| `coding-agent/src/session/agent-session.ts` | #7, #8 | re-anchored | 0.17.6 added a logical session work lease per post-prompt task. Forced recovery releases those leases, so the separate physical owner set is still required. | +| | #12 | re-anchored | `#sessionShutdownReason` was inserted after `#disposeTerminalError`. | +| `coding-agent/src/config/model-registry.ts` | #8, #9 (+#1, #4 context) | re-anchored | `refreshInBackground()` gained `credentialSessionId`; a fenced deferral now stores and replays it with the strategy. | +| `ai/src/auth-storage.ts` | #4, #8, #9 | re-anchored | usage requests gained provider-generation checks and a cross-process usage lease inside the same in-flight promises; the `#ownAppTask` wrap is unchanged. | +| `ai/src/utils/idle-iterator.ts` | #1, #2 | re-anchored | the loop was re-indented and `iterator.return()` became idempotent; raced `next()`/`return()` promises are still retained. | +| `ai/src/stream.ts` | #3, #8 | re-anchored | `complete`/`completeSimple` now drain the stream before `result()`; the producer join wraps both. | +| `ai/src/providers/cursor.ts` | #0–#2 | re-anchored | new `node:tls` import, first-event watchdog setup and write-drain finally; same producer wrap and unknown marker. | +| `coding-agent/src/sdk/host/host.ts` | #0 | re-anchored | a `file-lock` import was inserted between anchored imports. | +| `coding-agent/src/sdk/host/session-runtime.ts` | #0 | re-anchored | multi-line `./host` import with capability constants. | +| | #1 | re-anchored (split) | runtime now keeps its own connection-capability fields. | +| | #2 | re-anchored | `sendFrameTo()` takes one connection; capability fan-out moved to callers. | +| | #11 | re-anchored | terminal-boundary helpers were inserted before `trackLifecycle`. | +| | #13 | re-anchored | deadline settings resolve through helpers; `onDeadlineExceeded` added. | +| | #19 | re-anchored | a workflow-gate turn provider follows the runtime owner literal. | +| | #20 | re-anchored (split) | `stop()` takes lock-contention/unregister-reason options; `session_shutdown` forwards `event.reason`. | +| `coding-agent/src/sdk/host/websocket-transport.ts` | #0 | re-anchored | a value import from `./host` was inserted. | +| `coding-agent/src/sdk/prompt-deadline-manager.ts` | #2 | re-anchored (split) | constructor gained flush options between the anchored blocks. | + +New 0.17.6 detached work inside already-patched files, owned by additional edits: + +- `sdk/host/host.ts`: `reportActivity()` durable heartbeats are fire-and-forget from + `start()`, activation and session-runtime turn boundaries. The serialized write + is owned at its source by the host's `AppSdkHostOwner`. +- `sdk/host/session-runtime.ts`: periodic broker re-registration. Stopping clears + the interval, but an already-dispatched `runBrokerRecovery()` is now owned. +- `sdk/prompt-deadline-manager.ts`: the bounded `onDeadlineExceeded` worktree flush + keeps its public 10s budget, but the abandoned hook promise is physically owned. +- `sdk/session.ts`: 0.17.6 re-enabled filesystem/plugin/settings extension-module + discovery. Any discovered module reports `sdk_extension_effects_unrepresented`, + like preloaded ones (a second layer; the app itself does not let them load, see + below). + +Audited and deliberately not patched: + +- `ai/src/providers/devin-acp.ts` is a new 0.17.6 transport that spawns a cached + `devin acp` child with detached stdio drains. It is not a registered producer, so + its streams report `sdk_provider_producer_unrepresented` (core join or lazy + adoption), i.e. fail closed. Covering it would exceed the 32-file policy and a + persistent child cannot be certified by promise joins anyway. +- `agent-session.ts` gained in-memory follow-up reservation waits + (`#queueFollowUpAfterReservation`) and a workflow-gate continuation `abort()`. + They perform no I/O beyond session queues and are bounded by existing session + logic; they are not separately owned. + +App consequences of 0.17.6 recorded outside this manifest: + +- **Extension-module discovery.** `createAgentSession` now discovers + `/extensions`, project `.gjc/extensions`, plugin entry points and the + `extensions` setting. The adapter passes an empty `preloadedExtensions` result + (0.16.4's default, fresh `ExtensionRuntime` per session) so none of them load. + `disableExtensionDiscovery` is avoided because on 0.17.6 it also disables + hook-convention discovery, which top-level sessions kept on 0.16.4. +- **Delegation arguments.** 0.17.6 `ExtensionToolWrapper` runs + `validateToolArguments` on direct execution too, and that validation strips + unknown strict-object keys (0.16.4's model-facing agent-loop path already did). + A `task` call carrying `model` is therefore ignored, not rejected, and the child + is forced onto the parent's exact model. The app cannot intercept, because + `customToolToDefinition` drops `rawArgumentValidation`/`lenientArgValidation`. + ## Public registry seam ```ts @@ -251,7 +323,7 @@ the public `awaitDisposeCompletion(): Promise` signature are unchanged. `lifecycle.bun.test.ts` imports an explicitly selected isolated candidate SDK and core/AI. Only their 32 manifest-listed source files differ from pristine published packages. -Other dependencies are read-only links to the existing 0.16.4 dependency closure; +Other dependencies are read-only links to the existing 0.17.6 dependency closure; this is not a clean-install, cross-platform or packaged-binary qualification. All session data goes into independent temporary fixtures. No live credentials, provider requests or shell commands are needed. diff --git a/patches/gjc-sdk-lifecycle/manifest.json b/patches/gjc-sdk-lifecycle/manifest.json index 95fa5a9e..257c4dc7 100644 --- a/patches/gjc-sdk-lifecycle/manifest.json +++ b/patches/gjc-sdk-lifecycle/manifest.json @@ -2,16 +2,16 @@ "schemaVersion": 1, "id": "gjc-sdk-lifecycle-v1", "packages": { - "@gajae-code/coding-agent": "0.16.4", - "@gajae-code/agent-core": "0.16.4", - "@gajae-code/ai": "0.16.4" + "@gajae-code/coding-agent": "0.17.6", + "@gajae-code/agent-core": "0.17.6", + "@gajae-code/ai": "0.17.6" }, "files": [ { "package": "@gajae-code/coding-agent", "path": "src/sdk/session.ts", - "beforeSha256": "cd7856280e70f466a4d88113d5b96c5175f0b37c55a3c157bb12d40c2ef09b5e", - "afterSha256": "6bc35899fa1386ab4887537eb0bdd068289291d588b0ce4cd1f1660d4075841c", + "beforeSha256": "0172390b5288193d3f2abbf5682092a79ca1cafa3dc2d536d65cf03688ac6611", + "afterSha256": "b4ff66a6c5a38ad3a998883b6efaebe9bb3893c781b5d856820898dd6caec864", "replacements": [ { "before": "\tlet session!: AgentSession;\n\tlet sessionManager!: SessionManager;\n\tlet hasSession = false;\n\tlet processCwdClaimed = false;\n\tlet hasRegistered = false;\n\tlet asyncJobManager: AsyncJobManager | undefined;\n", @@ -26,8 +26,8 @@ "after": "\t\t\tthinkingLevel,\n\t\t\tsessionManager,\n\t\t\tsettings,\n\t\t\tflushBorrowedSettingsOnDispose: !ownsScopedSettings,\n\t\t\t// The session's REQUESTED agent directory when the caller explicitly\n\t\t\t// supplied it, independent of the reused global Settings singleton\n\t\t\t// (which may belong to an earlier session). When the option is\n" }, { - "before": "\t\t// carried by the host replay ring through the internal runtime seam above.\n\t\tif (autoroutingInactive) session.configWarnings.push(AUTOROUTING_INACTIVE_WARNING);\n\t\thasSession = true;\n\t\tif (masterModeContext) {\n\t\t\t// One scoped, no-probe peer snapshot immediately before the FIRST accepted\n\t\t\t// provider request; see createMasterPeerSnapshotContributor for the\n", - "after": "\t\t// carried by the host replay ring through the internal runtime seam above.\n\t\tif (autoroutingInactive) session.configWarnings.push(AUTOROUTING_INACTIVE_WARNING);\n\t\thasSession = true;\n\t\t// Drain startup before credential/provider resources are released. Do not\n\t\t// race this with finishCleanup's independent resource-close callbacks.\n\t\tsession.registerToolSessionTransitionCleanup(joinStartupTasks);\n\t\tif (masterModeContext) {\n\t\t\t// One scoped, no-probe peer snapshot immediately before the FIRST accepted\n\t\t\t// provider request; see createMasterPeerSnapshotContributor for the\n" + "before": "\t\thasSession = true;\n\t\tconst cleanupOwnedManager = cleanupOwnedMcpManager;\n", + "after": "\t\thasSession = true;\n\t\t// Drain startup before credential/provider resources are released. Do not\n\t\t// race this with finishCleanup's independent resource-close callbacks.\n\t\tsession.registerToolSessionTransitionCleanup(joinStartupTasks);\n\t\tconst cleanupOwnedManager = cleanupOwnedMcpManager;\n" }, { "before": "\t\t\t\tproviderSessionState: session.providerSessionState,\n\t\t\t});\n\t\t\tif (codexTransport.websocketPreferred) {\n\t\t\t\tvoid (async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst codexPrewarmApiKey = await modelRegistry.getApiKey(codexModel, credentialSessionId);\n\t\t\t\t\t\tif (!codexPrewarmApiKey) return;\n", @@ -42,8 +42,8 @@ "after": "\t\treleaseCredentialDisabledSubscription();\n\t\tlet cleanupDiagnostic: unknown;\n\t\ttry {\n\t\t\tawait joinStartupTasks();\n\t\t\tif (hasSession) {\n\t\t\t\ttry {\n\t\t\t\t\tawait session.dispose();\n" }, { - "before": "\t\t\t\t\tif (AsyncJobManager.instance() === asyncJobManager) {\n\t\t\t\t\t\tAsyncJobManager.setInstance(priorAsyncJobManager);\n\t\t\t\t\t}\n\t\t\t\t\tawait asyncJobManager.dispose({ timeoutMs: 100 });\n\t\t\t\t}\n\t\t\t\tawait cleanupOwnedMcpManager?.();\n\t\t\t\tconst [{ disposeKernelSessionsByOwner }, { disposeVmContextsByOwner }] = await Promise.all([\n", - "after": "\t\t\t\t\tif (AsyncJobManager.instance() === asyncJobManager) {\n\t\t\t\t\t\tAsyncJobManager.setInstance(priorAsyncJobManager);\n\t\t\t\t\t}\n\t\t\t\t\tif (!(await asyncJobManager.dispose({ timeoutMs: 100 })))\n\t\t\t\t\t\tawait asyncJobManager.awaitRetainedDisposalCompletion();\n\t\t\t\t}\n\t\t\t\tawait cleanupOwnedMcpManager?.();\n\t\t\t\tconst [{ disposeKernelSessionsByOwner }, { disposeVmContextsByOwner }] = await Promise.all([\n" + "before": "\t\t\t\t\tawait asyncJobManager.dispose({ timeoutMs: 100 });\n\t\t\t\t}\n\t\t\t\tawait cleanupOwnedMcpManager?.();\n", + "after": "\t\t\t\t\tif (!(await asyncJobManager.dispose({ timeoutMs: 100 })))\n\t\t\t\t\t\tawait asyncJobManager.awaitRetainedDisposalCompletion();\n\t\t\t\t}\n\t\t\t\tawait cleanupOwnedMcpManager?.();\n" }, { "before": "\tlet agent: Agent;\n\tlet sessionAgent: Agent | undefined;\n\tlet session!: AgentSession;\n\tlet sessionManager!: SessionManager;\n\tlet hasSession = false;\n\tconst startupTasks = new Set>();\n\tconst startOwnedStartupTask = (operation: () => Promise): void => {\n\t\tconst owner = Promise.withResolvers();\n\t\tstartupTasks.add(owner.promise);\n\t\tvoid Promise.resolve().then(operation).finally(() => {\n\t\t\tstartupTasks.delete(owner.promise);\n\t\t\towner.resolve();\n\t\t}).catch(() => {}); // Existing startup probes remain best-effort.\n\t};\n\tconst joinStartupTasks = async (): Promise => {\n\t\twhile (startupTasks.size > 0) await Promise.allSettled([...startupTasks]);\n\t};\n\tlet processCwdClaimed = false;\n\tlet hasRegistered = false;\n\tlet asyncJobManager: AsyncJobManager | undefined;\n", @@ -54,16 +54,20 @@ "after": "\t\t// credential_disabled event past us. An embedder's constructor handler makes the\n\t\t// listener set non-empty from construction, which defeats AuthStorage's no-listener\n\t\t// buffer — so we can't rely on it to catch startup events for the extension runner.\n\t\tconst startupCredentialDisabledEvents: CredentialDisabledEvent[] = [];\n\t\tlet credentialDisabledTarget: ExtensionRunner | undefined;\n\t\tunsubscribeCredentialDisabled = authStorage.onCredentialDisabled(event => {\n\t\t\t// Runner.initialize() may defer buffered delivery to its own microtask.\n\t\t\t// Its returned promise does not yet represent that buffered dispatch.\n\t\t\tappFactoryUnknown.add(\"sdk_extension_credential_dispatch_unrepresented\"); appFactoryRevision++;\n\t\t\tif (credentialDisabledTarget) {\n\t\t\t\treturn ownFactoryTask(() => credentialDisabledTarget!.emitCredentialDisabled(event));\n\t\t\t} else {\n\t\t\t\tstartupCredentialDisabledEvents.push(event);\n\t\t\t}\n\t\t});\n\t\tconst applyCredentialSelector = (scopeId: string, provider: string, selector: AuthCredentialSelector): void => {\n\t\t\tauthStorage.setSessionCredentialSelector(scopeId, provider, selector, authStorageOwner);\n" }, { - "before": "\t\t\tagentsMdFiles: [],\n\t\t};\n\t\tlet workspaceTreePromise: Promise = options.workspaceTree\n\t\t\t? Promise.resolve(options.workspaceTree)\n\t\t\t: workspaceTreeMode === \"lazy\"\n\t\t\t\t? Promise.resolve(emptyWorkspaceTree)\n\t\t\t\t: logger.time(\"buildWorkspaceTree\", () =>\n\t\t\t\t\t\truntimeServices.workspaceTree.get(\"legacy-startup\").then(runtime => runtime.snapshot),\n\t\t\t\t\t);\n\t\tworkspaceTreePromise.catch(() => {});\n\n\t\t// Independent discoveries that depend only on cwd/agentDir — kicked off in parallel and awaited\n\t\t// at their respective consumer sites. Their work can overlap with model resolution, secret loading,\n\t\t// session-context build, tool creation, MCP discovery, and extension discovery.\n\t\tconst contextFilesResultPromise = options.contextFiles\n\t\t\t? Promise.resolve({ contextFiles: options.contextFiles, warnings: [] })\n\t\t\t: logger.time(\"discoverContextFiles\", loadContextFilesResultInternal, { cwd });\n\t\tcontextFilesResultPromise.catch(() => {});\n\t\tconst promptTemplatesPromise = options.promptTemplates\n\t\t\t? Promise.resolve(options.promptTemplates)\n\t\t\t: logger.time(\"discoverPromptTemplates\", discoverPromptTemplates, cwd, agentDir);\n\t\tpromptTemplatesPromise.catch(() => {});\n\t\tconst slashCommandsPromise = options.slashCommands ? Promise.resolve(options.slashCommands) : Promise.resolve([]);\n\t\tslashCommandsPromise.catch(() => {});\n\n\t\t// Initialize provider preferences from settings\n\t\tconst { getConfiguredSearchProviderPreference, setPreferredSearchProvider, setSearchFallbackProviders } =\n", - "after": "\t\t\tagentsMdFiles: [],\n\t\t};\n\t\tlet workspaceTreePromise: Promise = options.workspaceTree\n\t\t\t? Promise.resolve(options.workspaceTree)\n\t\t\t: workspaceTreeMode === \"lazy\"\n\t\t\t\t? Promise.resolve(emptyWorkspaceTree)\n\t\t\t\t: ownFactoryTask(() => logger.time(\"buildWorkspaceTree\", () =>\n\t\t\t\t\t\truntimeServices.workspaceTree.get(\"legacy-startup\").then(runtime => runtime.snapshot),\n\t\t\t\t\t));\n\t\tworkspaceTreePromise.catch(() => {});\n\n\t\t// Independent discoveries that depend only on cwd/agentDir — kicked off in parallel and awaited\n\t\t// at their respective consumer sites. Their work can overlap with model resolution, secret loading,\n\t\t// session-context build, tool creation, MCP discovery, and extension discovery.\n\t\tconst contextFilesResultPromise = options.contextFiles\n\t\t\t? Promise.resolve({ contextFiles: options.contextFiles, warnings: [] })\n\t\t\t: ownFactoryTask(() => logger.time(\"discoverContextFiles\", loadContextFilesResultInternal, { cwd }));\n\t\tcontextFilesResultPromise.catch(() => {});\n\t\tconst promptTemplatesPromise = options.promptTemplates\n\t\t\t? Promise.resolve(options.promptTemplates)\n\t\t\t: ownFactoryTask(() => logger.time(\"discoverPromptTemplates\", discoverPromptTemplates, cwd, agentDir));\n\t\tpromptTemplatesPromise.catch(() => {});\n\t\tconst slashCommandsPromise = options.slashCommands ? Promise.resolve(options.slashCommands) : Promise.resolve([]);\n\t\tslashCommandsPromise.catch(() => {});\n\n\t\t// Initialize provider preferences from settings\n\t\tconst { getConfiguredSearchProviderPreference, setPreferredSearchProvider, setSearchFallbackProviders } =\n" + "before": "\t\t\tagentsMdFiles: [],\n\t\t};\n\t\tlet workspaceTreePromise: Promise = options.workspaceTree\n\t\t\t? Promise.resolve(options.workspaceTree)\n\t\t\t: workspaceTreeMode === \"lazy\"\n\t\t\t\t? Promise.resolve(emptyWorkspaceTree)\n\t\t\t\t: logger.time(\"buildWorkspaceTree\", () =>\n\t\t\t\t\t\truntimeServices.workspaceTree.get(\"legacy-startup\").then(runtime => runtime.snapshot),\n\t\t\t\t\t);\n\t\tworkspaceTreePromise.catch(() => {});\n\n\t\t// Independent discoveries that depend only on cwd/agentDir — kicked off in parallel and awaited\n\t\t// at their respective consumer sites. Their work can overlap with model resolution, secret loading,\n\t\t// session-context build, tool creation, MCP discovery, and extension discovery.\n\t\tconst contextFilesResultPromise = options.contextFiles\n\t\t\t? Promise.resolve({ contextFiles: options.contextFiles, warnings: [] })\n\t\t\t: logger.time(\"discoverContextFiles\", loadContextFilesResultInternal, { cwd, agentDir, profileAuthority });\n\t\tcontextFilesResultPromise.catch(() => {});\n\t\tconst promptTemplatesPromise = options.promptTemplates\n\t\t\t? Promise.resolve(options.promptTemplates)\n\t\t\t: logger.time(\"discoverPromptTemplates\", discoverPromptTemplates, cwd, agentDir);\n\t\tpromptTemplatesPromise.catch(() => {});\n\t\tconst slashCommandsPromise = options.slashCommands ? Promise.resolve(options.slashCommands) : Promise.resolve([]);\n\t\tslashCommandsPromise.catch(() => {});\n\n\t\t// Initialize provider preferences from settings\n\t\tconst { getConfiguredSearchProviderPreference, setPreferredSearchProvider, setSearchFallbackProviders } =\n", + "after": "\t\t\tagentsMdFiles: [],\n\t\t};\n\t\tlet workspaceTreePromise: Promise = options.workspaceTree\n\t\t\t? Promise.resolve(options.workspaceTree)\n\t\t\t: workspaceTreeMode === \"lazy\"\n\t\t\t\t? Promise.resolve(emptyWorkspaceTree)\n\t\t\t\t: ownFactoryTask(() => logger.time(\"buildWorkspaceTree\", () =>\n\t\t\t\t\t\truntimeServices.workspaceTree.get(\"legacy-startup\").then(runtime => runtime.snapshot),\n\t\t\t\t\t));\n\t\tworkspaceTreePromise.catch(() => {});\n\n\t\t// Independent discoveries that depend only on cwd/agentDir — kicked off in parallel and awaited\n\t\t// at their respective consumer sites. Their work can overlap with model resolution, secret loading,\n\t\t// session-context build, tool creation, MCP discovery, and extension discovery.\n\t\tconst contextFilesResultPromise = options.contextFiles\n\t\t\t? Promise.resolve({ contextFiles: options.contextFiles, warnings: [] })\n\t\t\t: ownFactoryTask(() => logger.time(\"discoverContextFiles\", loadContextFilesResultInternal, { cwd, agentDir, profileAuthority }));\n\t\tcontextFilesResultPromise.catch(() => {});\n\t\tconst promptTemplatesPromise = options.promptTemplates\n\t\t\t? Promise.resolve(options.promptTemplates)\n\t\t\t: ownFactoryTask(() => logger.time(\"discoverPromptTemplates\", discoverPromptTemplates, cwd, agentDir));\n\t\tpromptTemplatesPromise.catch(() => {});\n\t\tconst slashCommandsPromise = options.slashCommands ? Promise.resolve(options.slashCommands) : Promise.resolve([]);\n\t\tslashCommandsPromise.catch(() => {});\n\n\t\t// Initialize provider preferences from settings\n\t\tconst { getConfiguredSearchProviderPreference, setPreferredSearchProvider, setSearchFallbackProviders } =\n" }, { - "before": "\t\t// MCP routing is scope-held; no process-global manager registration.\n\n\t\t// General extension discovery is quarantined from the public SDK surface.\n\t\t// Recognized hook conventions are the bounded exception: their descriptors\n\t\t// normalize before import and then adapt into the authoritative ExtensionRunner.\n\t\tconst inlineExtensions: ExtensionFactory[] = [...(options.extensions ?? [])];\n\t\tconst discoveredHookExtensions: Array<{ factory: ExtensionFactory; name: string }> = [];\n\t\tif (customTools.length > 0) {\n\t\t\tinlineExtensions.push(createCustomToolsExtension(customTools));\n\t\t}\n\t\tif (!options.disableExtensionDiscovery) {\n\t\t\ttry {\n\t\t\t\tconst hookExtensions = await discoverAndLoadHookExtensions(options.hookPaths ?? [], cwd);\n\t\t\t\tdiscoveredHookExtensions.push(...hookExtensions.factories);\n\t\t\t\tfor (const error of hookExtensions.errors) {\n\t\t\t\t\tlogger.warn(\"Rejected discovered hook\", { path: error.path, error: error.error });\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tlogger.warn(\"Failed to discover hook extensions\", { error: safeErrorForLog(error) });\n\t\t\t}\n", - "after": "\t\t// MCP routing is scope-held; no process-global manager registration.\n\n\t\t// General extension discovery is quarantined from the public SDK surface.\n\t\t// Recognized hook conventions are the bounded exception: their descriptors\n\t\t// normalize before import and then adapt into the authoritative ExtensionRunner.\n\t\tconst inlineExtensions: ExtensionFactory[] = [...(options.extensions ?? [])];\n\t\tif (options.extensions?.length || options.preloadedExtensions?.extensions.length)\n\t\t\tappFactoryUnknown.add(\"sdk_extension_effects_unrepresented\");\n\t\tif (options.runtimeServices || options.eventBus || options.mcpManager || options.inheritedMcpManager)\n\t\t\tappFactoryUnknown.add(\"sdk_injected_services_unrepresented\");\n\t\tconst discoveredHookExtensions: Array<{ factory: ExtensionFactory; name: string }> = [];\n\t\tif (customTools.length > 0) {\n\t\t\tinlineExtensions.push(createCustomToolsExtension(customTools));\n\t\t}\n\t\tif (!options.disableExtensionDiscovery) {\n\t\t\ttry {\n\t\t\t\tconst hookExtensions = await discoverAndLoadHookExtensions(options.hookPaths ?? [], cwd);\n\t\t\t\tdiscoveredHookExtensions.push(...hookExtensions.factories);\n\t\t\t\tif (hookExtensions.factories.length) appFactoryUnknown.add(\"sdk_extension_effects_unrepresented\");\n\t\t\t\tfor (const error of hookExtensions.errors) {\n\t\t\t\t\tlogger.warn(\"Rejected discovered hook\", { path: error.path, error: error.error });\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tlogger.warn(\"Failed to discover hook extensions\", { error: safeErrorForLog(error) });\n\t\t\t}\n" + "before": "\t\tconst inlineExtensions: ExtensionFactory[] = [...(options.extensions ?? [])];\n", + "after": "\t\tconst inlineExtensions: ExtensionFactory[] = [...(options.extensions ?? [])];\n\t\tif (options.extensions?.length || options.preloadedExtensions?.extensions.length)\n\t\t\tappFactoryUnknown.add(\"sdk_extension_effects_unrepresented\");\n\t\tif (options.runtimeServices || options.eventBus || options.mcpManager || options.inheritedMcpManager)\n\t\t\tappFactoryUnknown.add(\"sdk_injected_services_unrepresented\");\n" }, { - "before": "\t\t// Always-on constrained plugin hooks (validated registry surfaces). Additive\n\t\t// and a no-op without installed plugins; the loader denies all dangerous APIs.\n\t\ttry {\n\t\t\tconst pluginHookResult = await loadConstrainedPluginHooks({ cwd });\n\t\t\tif (pluginHookResult.hooks.length > 0) {\n\t\t\t\tinlineExtensions.push(createPluginHooksExtension(pluginHookResult.hooks));\n\t\t\t}\n\t\t\tfor (const q of pluginHookResult.quarantine) {\n\t\t\t\tgjcFindings.add({ identity: q.identity, surfaceId: q.surfaceId, code: q.code, message: q.message });\n\t\t\t\tlogger.warn(\"Quarantined GJC plugin hook\", { plugin: q.plugin, surface: q.surfaceId, code: q.code });\n\t\t\t}\n\t\t} catch (error) {\n", - "after": "\t\t// Always-on constrained plugin hooks (validated registry surfaces). Additive\n\t\t// and a no-op without installed plugins; the loader denies all dangerous APIs.\n\t\ttry {\n\t\t\tconst pluginHookResult = await loadConstrainedPluginHooks({ cwd });\n\t\t\tif (pluginHookResult.hooks.length > 0) {\n\t\t\t\tinlineExtensions.push(createPluginHooksExtension(pluginHookResult.hooks));\n\t\t\t\tappFactoryUnknown.add(\"sdk_extension_effects_unrepresented\");\n\t\t\t}\n\t\t\tfor (const q of pluginHookResult.quarantine) {\n\t\t\t\tgjcFindings.add({ identity: q.identity, surfaceId: q.surfaceId, code: q.code, message: q.message });\n\t\t\t\tlogger.warn(\"Quarantined GJC plugin hook\", { plugin: q.plugin, surface: q.surfaceId, code: q.code });\n\t\t\t}\n\t\t} catch (error) {\n" + "before": "\t\t\t\tdiscoveredHookExtensions.push(...hookExtensions.factories);\n", + "after": "\t\t\t\tdiscoveredHookExtensions.push(...hookExtensions.factories);\n\t\t\t\tif (hookExtensions.factories.length) appFactoryUnknown.add(\"sdk_extension_effects_unrepresented\");\n" + }, + { + "before": "\t\t\t\tinlineExtensions.push(createPluginHooksExtension(pluginHookResult.hooks));\n", + "after": "\t\t\t\tinlineExtensions.push(createPluginHooksExtension(pluginHookResult.hooks));\n\t\t\t\tappFactoryUnknown.add(\"sdk_extension_effects_unrepresented\");\n" }, { "before": "\t\t\t\t\tcurrentAgentType: options.currentAgentType,\n\t\t\t\t\tspawnedByGjc,\n\t\t\t\t}),\n\t\t);\n\t\tconst sdkHostEligible =\n\t\t\tshouldHostSdk(notificationCfg, isTopLevelSdkSession) && (options.sdkHostModeSupported ?? true);\n\t\tconst notificationAdapterService = createLazyService({\n\t\t\tid: \"sdk.notifications.adapters\",\n\t\t\tenabled: () => notificationsExtensionEligible || sdkHostEligible,\n\t\t\tinitialize: async () => ({\n\t\t\t\tvalue: (await import(\"../sdk/bus\")).createNotificationsExtension,\n\t\t\t}),\n", @@ -82,24 +86,48 @@ "after": "\t\t\t\tif (cleanupPromise) return cleanupPromise;\n\t\t\t\tconst cleanup = (async () => {\n\t\t\t\t\tconst failures: unknown[] = [];\n\t\t\t\t\ttry {\n\t\t\t\t\t\tagentRegistry.unregister(resolvedAgentId);\n\t\t\t\t\t\treleaseCredentialDisabledSubscription();\n\t\t\t\t\t\tawait joinStartupTasks();\n\t\t\t\t\t\tawait joinAppHosts();\n\t\t\t\t\t\treleaseLocalProtocolOverride();\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tfailures.push(error);\n\t\t\t\t\t} finally {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tif (!options.parentTaskPrefix) {\n" }, { - "before": "\t\t\t\t\t\t.catch(error => {\n\t\t\t\t\t\t\tlogger.warn(\"Failed to publish conventional MCP tools\", { error: safeErrorForLog(error) });\n\t\t\t\t\t\t});\n\t\t\t\t\treturn conventionalToolsSync;\n\t\t\t\t};\n\t\t\t\tmcpManager.setOnToolsChanged(tools => {\n\t\t\t\t\tvoid syncConventionalTools(tools as CustomTool[]);\n\t\t\t\t});\n\t\t\t\tvoid syncConventionalTools(mcpManager.getTools() as CustomTool[]);\n\t\t\t} else if (!ownsMcpManager) {\n\t\t\t\tmcpManager.setOnToolsChanged(tools => {\n\t\t\t\t\tvoid session.refreshMCPTools(tools);\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst clearDebounceTimers = () => {\n\t\t\t\tfor (const timer of notificationDebounceTimers.values()) clearTimeout(timer);\n\t\t\t\tnotificationDebounceTimers.clear();\n\t\t\t};\n", - "after": "\t\t\t\t\t\t.catch(error => {\n\t\t\t\t\t\t\tlogger.warn(\"Failed to publish conventional MCP tools\", { error: safeErrorForLog(error) });\n\t\t\t\t\t\t});\n\t\t\t\t\treturn conventionalToolsSync;\n\t\t\t\t};\n\t\t\t\tmcpManager.setOnToolsChanged(tools => {\n\t\t\t\t\tif (!session.isDisposed) startOwnedStartupTask(() => syncConventionalTools(tools as CustomTool[]));\n\t\t\t\t});\n\t\t\t\tstartOwnedStartupTask(() => syncConventionalTools(mcpManager.getTools() as CustomTool[]));\n\t\t\t} else if (!ownsMcpManager) {\n\t\t\t\tmcpManager.setOnToolsChanged(tools => {\n\t\t\t\t\tif (!session.isDisposed) startOwnedStartupTask(() => session.refreshMCPTools(tools));\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst clearDebounceTimers = () => {\n\t\t\t\tfor (const timer of notificationDebounceTimers.values()) clearTimeout(timer);\n\t\t\t\tnotificationDebounceTimers.clear();\n\t\t\t};\n" + "before": "\t\t\tmanager.setOnToolsChanged(tools => {\n\t\t\t\tvoid syncOwnedMcpTools(tools);\n\t\t\t});\n", + "after": "\t\t\tmanager.setOnToolsChanged(tools => {\n\t\t\t\tif (!session.isDisposed) startOwnedStartupTask(() => syncOwnedMcpTools(tools));\n\t\t\t});\n" + }, + { + "before": "\t\t\t\tvoid syncOwnedMcpTools(currentTools);\n", + "after": "\t\t\t\tstartOwnedStartupTask(() => syncOwnedMcpTools(currentTools));\n" + }, + { + "before": "\t\t\t\t\tvoid syncOwnedMcpTools(catalog.tools.map(tool => tool as CustomTool));\n", + "after": "\t\t\t\t\tstartOwnedStartupTask(() => syncOwnedMcpTools(catalog.tools.map(tool => tool as CustomTool)));\n" + }, + { + "before": "\t\t\t\tvoid syncOwnedMcpTools(ownedMcpManagerTools);\n", + "after": "\t\t\t\tstartOwnedStartupTask(() => syncOwnedMcpTools(ownedMcpManagerTools));\n" + }, + { + "before": "\t\t\tinheritedMcpToolsPublisher = tools => {\n\t\t\t\tvoid syncInheritedTools(tools);\n\t\t\t};\n", + "after": "\t\t\tinheritedMcpToolsPublisher = tools => {\n\t\t\t\tif (!session.isDisposed) startOwnedStartupTask(() => syncInheritedTools(tools));\n\t\t\t};\n" + }, + { + "before": "\t\t\t\t\tvoid session.refreshMCPTools(tools.map(tool => tool as CustomTool));\n", + "after": "\t\t\t\t\tif (!session.isDisposed) startOwnedStartupTask(() => session.refreshMCPTools(tools.map(tool => tool as CustomTool)));\n" }, { "before": "\t\t}\n\t\t// Expose the published evidence on the session itself so UI surfaces that\n\t\t// only hold a session (Settings) can consume it without threading the\n\t\t// creation result through every controller.\n\t\tsession.gjcRuntimeSnapshot = gjcRuntimeStore;\n\t\tsession.gjcActivationGeneration = gjcActivationGeneration;\n\t\treturn {\n\t\t\tsession,\n\t\t\textensionsResult,\n\t\t\tsetToolUIContext,\n\t\t\tmcpManager: ownsMcpManager && mcpManager?.isToolsOnly() ? undefined : mcpManager,\n\t\t\tstartDeferredMcpConfig,\n", "after": "\t\t}\n\t\t// Expose the published evidence on the session itself so UI surfaces that\n\t\t// only hold a session (Settings) can consume it without threading the\n\t\t// creation result through every controller.\n\t\tsession.gjcRuntimeSnapshot = gjcRuntimeStore;\n\t\tsession.gjcActivationGeneration = gjcActivationGeneration;\n\t\tsession.registerAppLifecycleFactoryOwner(() => {\n\t\t\tconst hosts = [...appHostOwners].map(owner => owner.getAppLifecycleActivity());\n\t\t\treturn { generation: `${appFactoryRevision}:${hosts.map(host => host.generation).join(\":\")}`,\n\t\t\t\tpending: startupTasks.size + hosts.reduce((n, host) => n + host.starting + host.queued + host.running + host.settling, 0),\n\t\t\t\tunknown: [...new Set([...appFactoryUnknown, ...hosts.flatMap(host => host.unknown)])] };\n\t\t});\n\t\treturn {\n\t\t\tsession,\n\t\t\textensionsResult,\n\t\t\tsetToolUIContext,\n\t\t\tmcpManager: ownsMcpManager && mcpManager?.isToolsOnly() ? undefined : mcpManager,\n\t\t\tstartDeferredMcpConfig,\n" }, { - "before": "\t\t\t\t\t}\n\t\t\t\t\t// The first call is intentionally bounded for startup callers. Join the\n\t\t\t\t\t// retained teardown before releasing the resources it still owns.\n\t\t\t\t\tawait session.awaitDisposeCompletion();\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif (hasRegistered) agentRegistry.unregister(resolvedAgentId);\n\t\t\t\t// Admission happens before session construction. Any later startup\n\t\t\t\t// failure must remove THIS manager's endpoint mapping and restore\n\t\t\t\t// the prior global only when this manager is still global: otherwise\n\t\t\t\t// a retry under the same endpoint is falsely rejected and an orphan\n\t\t\t\t// redirects global-manager consumers away from the live session\n", - "after": "\t\t\t\t\t}\n\t\t\t\t\t// The first call is intentionally bounded for startup callers. Join the\n\t\t\t\t\t// retained teardown before releasing the resources it still owns.\n\t\t\t\t\tawait session.awaitDisposeCompletion();\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tawait joinAppHosts();\n\t\t\t\tif (hasRegistered) agentRegistry.unregister(resolvedAgentId);\n\t\t\t\t// Admission happens before session construction. Any later startup\n\t\t\t\t// failure must remove THIS manager's endpoint mapping and restore\n\t\t\t\t// the prior global only when this manager is still global: otherwise\n\t\t\t\t// a retry under the same endpoint is falsely rejected and an orphan\n\t\t\t\t// redirects global-manager consumers away from the live session\n" + "before": "\t\t\t} else {\n\t\t\t\tif (hasRegistered) agentRegistry.unregister(resolvedAgentId);\n\t\t\t\t// Admission happens before session construction. Any later startup\n", + "after": "\t\t\t} else {\n\t\t\t\tawait joinAppHosts();\n\t\t\t\tif (hasRegistered) agentRegistry.unregister(resolvedAgentId);\n\t\t\t\t// Admission happens before session construction. Any later startup\n" + }, + { + "before": "\t\tconst extensionsResult: LoadExtensionsResult = options.preloadedExtensions ?? (await loadStartupExtensions());\n", + "after": "\t\tconst extensionsResult: LoadExtensionsResult = options.preloadedExtensions ?? (await loadStartupExtensions());\n\t\tif (options.preloadedExtensions === undefined && extensionsResult.extensions.length)\n\t\t\tappFactoryUnknown.add(\"sdk_extension_effects_unrepresented\");\n" } ] }, { "package": "@gajae-code/coding-agent", "path": "src/session/agent-session.ts", - "beforeSha256": "f5ad1c0f021ff54183f78c01640d9214eae29444f48c9388b51b01e6bb2193ed", - "afterSha256": "838658da725233a0628bf813fc4ca9ff7f060e056636cebcfe6eb03b9a1ecc29", + "beforeSha256": "d4969a5fd58a687effb97e586118b01c741369bd2419902af4b4c7b9de912003", + "afterSha256": "7cea2f60e01a9fcbc4c7631026bfcbf351023f30c650b9352a1a1e91c20295b5", "replacements": [ { "before": "\tThinkingLevel,\n} from \"@gajae-code/agent-core\";\nimport { ESCAPED_NONASCII_RECOVERY_PROMPT, normalizeMessagesForProvider } from \"@gajae-code/agent-core/agent-loop\";\nimport type { AttemptRunHandle, AttemptScope, AttemptScopeAuthority } from \"@gajae-code/agent-core/attempt-scope\";\nimport {\n\tAUTO_HANDOFF_THRESHOLD_FOCUS,\n", @@ -130,12 +158,12 @@ "after": "\t\tthis.agent.bindRunCancellationDomainBridge(this.#runCancellationDomains, this.#agentSessionClaimKey);\n\t\tthis.sessionManager = config.sessionManager;\n\t\tthis.settings = config.settings;\n\t\tthis.#flushBorrowedSettingsOnDispose = config.flushBorrowedSettingsOnDispose === true;\n\t\tthis.#requestedAgentDir = config.agentDir ? path.resolve(config.agentDir) : undefined;\n\t\tthis.sessionManager.setSessionMemoryMode(this.settings.get(\"sessionMemory.mode\"));\n\t\tthis.#unregisterSessionMemorySettings = this.settings.onChanged(settingPath => {\n" }, { - "before": "\t\texcludeFromRecovery = false,\n\t): void {\n\t\tthis.#postPromptTasks.add(task);\n\t\tthis.#postPromptTaskSelectionFenceGenerations.set(task, selectionFenceGeneration);\n\t\tif (excludeFromRecovery) this.#postPromptTaskRecoveryExcluded.add(task);\n\t\tthis.#ensurePostPromptTasksPromise();\n", - "after": "\t\texcludeFromRecovery = false,\n\t): void {\n\t\tthis.#postPromptTasks.add(task);\n\t\tthis.#physicalPostPromptTasks.add(task);\n\t\tthis.#postPromptTaskSelectionFenceGenerations.set(task, selectionFenceGeneration);\n\t\tif (excludeFromRecovery) this.#postPromptTaskRecoveryExcluded.add(task);\n\t\tthis.#ensurePostPromptTasksPromise();\n" + "before": "\t\tthis.#postPromptTasks.add(task);\n\t\tthis.#postPromptWorkLeases.set(task, this.#sessionWorkLease.acquire());\n", + "after": "\t\tthis.#postPromptTasks.add(task);\n\t\tthis.#physicalPostPromptTasks.add(task);\n\t\tthis.#postPromptWorkLeases.set(task, this.#sessionWorkLease.acquire());\n" }, { - "before": "\t\t\t.catch(() => {})\n\t\t\t.finally(() => {\n\t\t\t\tthis.#postPromptTasks.delete(task);\n\t\t\t\tthis.#postPromptTaskSelectionFenceGenerations.delete(task);\n\t\t\t\tthis.#postPromptTaskRecoveryExcluded.delete(task);\n\t\t\t\tif (this.#postPromptTasks.size === 0) this.#resolvePostPromptTasks();\n", - "after": "\t\t\t.catch(() => {})\n\t\t\t.finally(() => {\n\t\t\t\tthis.#postPromptTasks.delete(task);\n\t\t\t\tthis.#physicalPostPromptTasks.delete(task);\n\t\t\t\tthis.#postPromptTaskSelectionFenceGenerations.delete(task);\n\t\t\t\tthis.#postPromptTaskRecoveryExcluded.delete(task);\n\t\t\t\tif (this.#postPromptTasks.size === 0) this.#resolvePostPromptTasks();\n" + "before": "\t\t\t\tthis.#postPromptWorkLeases.delete(task);\n\t\t\t\tthis.#postPromptTasks.delete(task);\n", + "after": "\t\t\t\tthis.#postPromptWorkLeases.delete(task);\n\t\t\t\tthis.#postPromptTasks.delete(task);\n\t\t\t\tthis.#physicalPostPromptTasks.delete(task);\n" }, { "before": "\n\t/** Join the retained teardown owner without allocating another public deadline. */\n\tawaitDisposeCompletion(): Promise {\n\t\treturn this.#disposeRunPromise ?? this.dispose();\n\t}\n\n\tsetDisposeTimeoutForTests(timeoutMs: number): void {\n", @@ -150,8 +178,8 @@ "after": "\t\t}\n\t\tthis.#eventListeners = [];\n\t\tthis.#rebuildEventListenerSnapshot();\n\t\tif (this.#flushBorrowedSettingsOnDispose)\n\t\t\tawait awaitDisposeStep(\"borrowed settings persistence\", this.settings.flushOrThrow(), true);\n\t\tif (criticalDisposeError !== undefined) {\n\t\t\tdisposeFailures.push({ label: \"session manager close\", error: criticalDisposeError, critical: true });\n\t\t}\n" }, { - "before": "\treadonly #asyncJobProviderSessionId: string | undefined;\n\t#isDisposed = false;\n\t#disposeRunPromise: Promise | undefined;\n\t#disposeCallerPromise: Promise | undefined;\n\t#disposeCompleted = false;\n\t#disposeTerminalError: unknown;\n\treadonly #disposeAbortController = new AbortController();\n\t#disposeAdmissionClosed: Promise | undefined;\n\t#disposePostPromptDrain: Promise | undefined;\n\t#disposeDeadline = 0;\n\t#disposeDeadlineTimer: NodeJS.Timeout | undefined;\n\t#disposeDeadlineExpired: PromiseWithResolvers | undefined;\n", - "after": "\treadonly #asyncJobProviderSessionId: string | undefined;\n\t#isDisposed = false;\n\t#disposeRunPromise: Promise | undefined;\n\t#disposeCallerPromise: Promise | undefined;\n\t#disposeCompleted = false;\n\t#disposeTerminalError: unknown;\n\t#appFactoryOwner?: () => { generation: string; pending: number; unknown: string[] };\n\t/** The SDK factory supplies its real asynchronous owner, once, before returning. */\n\tregisterAppLifecycleFactoryOwner(owner: () => { generation: string; pending: number; unknown: string[] }): void {\n\t\tif (this.#appFactoryOwner) throw new Error(\"Factory lifecycle owner already registered\");\n\t\tthis.#appFactoryOwner = owner;\n\t}\n\tgetAppLifecycleActivity(): {\n\t\tgeneration: string; complete: boolean; starting: number; queued: number;\n\t\trunning: number; settling: number; unknown: string[];\n\t} {\n\t\tconst read = (appLifecycle as typeof appLifecycle & {\n\t\t\t\tgetPhysicalRunResourceActivity?: (ledger: ReturnType) => {\n\t\t\t\tgeneration: string; complete: boolean; running: number; unknown: string[];\n\t\t\t};\n\t\t}).getPhysicalRunResourceActivity;\n\t\tconst physical = read?.(this.agent.resourceLedger);\n\t\tconst factory = this.#appFactoryOwner?.();\n\t\tconst unknown = [...(physical?.unknown ?? [\"sdk_physical_ledger_unrepresented\"]),\n\t\t\t...(factory?.unknown ?? [\"sdk_factory_ownership_unrepresented\"]),\n\t\t\t...(this.#disposeTerminalError === undefined ? [] : [\"sdk_session_cleanup_unconfirmed\"])];\n\t\treturn { generation: crypto.createHash(\"sha256\").update(`${physical?.generation ?? \"missing\"}:${factory?.generation ?? 0}:${Number(this.#isDisposed)}:${Number(this.#disposeCompleted)}`).digest(\"hex\"),\n\t\t\tcomplete: unknown.length === 0, starting: factory?.pending ?? 0, queued: 0,\n\t\t\trunning: (physical?.running ?? 0) + this.#physicalPromptCompletions.size + this.#physicalPostPromptTasks.size + Number(!this.#isDisposed),\n\t\t\tsettling: this.#isDisposed && !this.#disposeCompleted ? 1 : 0, unknown: [...new Set(unknown)] };\n\t}\n\treadonly #disposeAbortController = new AbortController();\n\t#disposeAdmissionClosed: Promise | undefined;\n\t#disposePostPromptDrain: Promise | undefined;\n\t#disposeDeadline = 0;\n\t#disposeDeadlineTimer: NodeJS.Timeout | undefined;\n\t#disposeDeadlineExpired: PromiseWithResolvers | undefined;\n" + "before": "\treadonly #asyncJobProviderSessionId: string | undefined;\n\t#isDisposed = false;\n\t#disposeRunPromise: Promise | undefined;\n\t#disposeCallerPromise: Promise | undefined;\n\t#disposeCompleted = false;\n\t#disposeTerminalError: unknown;\n\t#sessionShutdownReason: \"detached_idle\" | undefined;\n\treadonly #disposeAbortController = new AbortController();\n\t#disposeAdmissionClosed: Promise | undefined;\n\t#disposePostPromptDrain: Promise | undefined;\n\t#disposeDeadline = 0;\n\t#disposeDeadlineTimer: NodeJS.Timeout | undefined;\n\t#disposeDeadlineExpired: PromiseWithResolvers | undefined;\n", + "after": "\treadonly #asyncJobProviderSessionId: string | undefined;\n\t#isDisposed = false;\n\t#disposeRunPromise: Promise | undefined;\n\t#disposeCallerPromise: Promise | undefined;\n\t#disposeCompleted = false;\n\t#disposeTerminalError: unknown;\n\t#sessionShutdownReason: \"detached_idle\" | undefined;\n\t#appFactoryOwner?: () => { generation: string; pending: number; unknown: string[] };\n\t/** The SDK factory supplies its real asynchronous owner, once, before returning. */\n\tregisterAppLifecycleFactoryOwner(owner: () => { generation: string; pending: number; unknown: string[] }): void {\n\t\tif (this.#appFactoryOwner) throw new Error(\"Factory lifecycle owner already registered\");\n\t\tthis.#appFactoryOwner = owner;\n\t}\n\tgetAppLifecycleActivity(): {\n\t\tgeneration: string; complete: boolean; starting: number; queued: number;\n\t\trunning: number; settling: number; unknown: string[];\n\t} {\n\t\tconst read = (appLifecycle as typeof appLifecycle & {\n\t\t\t\tgetPhysicalRunResourceActivity?: (ledger: ReturnType) => {\n\t\t\t\tgeneration: string; complete: boolean; running: number; unknown: string[];\n\t\t\t};\n\t\t}).getPhysicalRunResourceActivity;\n\t\tconst physical = read?.(this.agent.resourceLedger);\n\t\tconst factory = this.#appFactoryOwner?.();\n\t\tconst unknown = [...(physical?.unknown ?? [\"sdk_physical_ledger_unrepresented\"]),\n\t\t\t...(factory?.unknown ?? [\"sdk_factory_ownership_unrepresented\"]),\n\t\t\t...(this.#disposeTerminalError === undefined ? [] : [\"sdk_session_cleanup_unconfirmed\"])];\n\t\treturn { generation: crypto.createHash(\"sha256\").update(`${physical?.generation ?? \"missing\"}:${factory?.generation ?? 0}:${Number(this.#isDisposed)}:${Number(this.#disposeCompleted)}`).digest(\"hex\"),\n\t\t\tcomplete: unknown.length === 0, starting: factory?.pending ?? 0, queued: 0,\n\t\t\trunning: (physical?.running ?? 0) + this.#physicalPromptCompletions.size + this.#physicalPostPromptTasks.size + Number(!this.#isDisposed),\n\t\t\tsettling: this.#isDisposed && !this.#disposeCompleted ? 1 : 0, unknown: [...new Set(unknown)] };\n\t}\n\treadonly #disposeAbortController = new AbortController();\n\t#disposeAdmissionClosed: Promise | undefined;\n\t#disposePostPromptDrain: Promise | undefined;\n\t#disposeDeadline = 0;\n\t#disposeDeadlineTimer: NodeJS.Timeout | undefined;\n\t#disposeDeadlineExpired: PromiseWithResolvers | undefined;\n" } ] }, @@ -206,8 +234,8 @@ { "package": "@gajae-code/agent-core", "path": "src/agent-loop.ts", - "beforeSha256": "3bf15d130e1613898fae3f77417673f54c4cef00f71e2602319fe9afb5290cee", - "afterSha256": "efccd78060495147b0475aabb69fe10c5cefdabe7bf88b618b16c0ad49e6eab6", + "beforeSha256": "1d4c8bc8a393f9f003558ebeb879d57dd92ca8471b96846aede8a2693c2d6eeb", + "afterSha256": "73a34c415e6903f70ac23d922a4ee195a6aa335562d28e9f799a5a866d2fd388", "replacements": [ { "before": "\tverifyUnicodeEscapeEvidence,\n} from \"@gajae-code/ai/utils/json-parse\";\nimport { $credentialEnv, sanitizeText } from \"@gajae-code/utils\";\nimport * as logger from \"@gajae-code/utils/logger\";\nimport { revokeProviderSafetyStop } from \"../../ai/src/adapter-internals/provider-safety-stop\";\nimport type { AttemptScope } from \"./attempt-scope\";\nimport {\n\tcreateHarmonyAuditEvent,\n\tdetectHarmonyLeakInAssistantMessage,\n\textractHarmonyRemoved,\n\ttype HarmonyDetection,\n\ttype HarmonyRecoveredToolCall,\n", @@ -242,8 +270,8 @@ { "package": "@gajae-code/coding-agent", "path": "src/config/model-registry.ts", - "beforeSha256": "e5afb12f7447014ec6772dabb33b68731aac601d13d85e6585342771673f5301", - "afterSha256": "60529fec5306da95d08127b014ee7b13e83adf6a5e6ebdfff3df53010fff4f67", + "beforeSha256": "5e62bd57a8236990e4c69131fff2d386174759f7d830dd5b8b21a761945bea92", + "afterSha256": "c9735abb54e2493d4c741922a4771cb0f6ee00f6380ae05fff0c357848052b99", "replacements": [ { "before": "\ttype ProviderSelectionPolicy,\n\tprojectCatalogProviderOrder,\n} from \"./provider-selection-policy\";\nimport { type Settings, settings } from \"./settings\";\n\nexport type { BillingPath, BillingPathKind } from \"./billing-path\";\nexport type { EffectiveProviderAuth, ProviderSelectionPolicy } from \"./provider-selection-policy\";\n", @@ -251,7 +279,7 @@ }, { "before": " */\nexport class ModelRegistry {\n\t#models: Model[] = [];\n\t#catalogChangeListeners: Set<() => void> = new Set();\n\t#canonicalIndex: CanonicalModelIndex = {\n\t\trecords: [],\n\t\tbyId: new Map(),\n", - "after": " */\nexport class ModelRegistry {\n\t#models: Model[] = [];\n\t#catalogChangeListeners: Set<() => void | Promise> = new Set();\n\treadonly #appCatalogCallbacks = new Set>();\n\treadonly #appLifecycleEpoch = crypto.randomUUID();\n\t#appLifecycleRevision = 0;\n\t#appLifecycleFenced = false;\n\t#appDisposing = false;\n\t#appSettingsUnaccounted = false;\n\t#appSettingsFailure = false;\n\t#appDeferredRefresh: ModelRefreshStrategy | undefined;\n\t#canonicalIndex: CanonicalModelIndex = {\n\t\trecords: [],\n\t\tbyId: new Map(),\n" + "after": " */\nexport class ModelRegistry {\n\t#models: Model[] = [];\n\t#catalogChangeListeners: Set<() => void | Promise> = new Set();\n\treadonly #appCatalogCallbacks = new Set>();\n\treadonly #appLifecycleEpoch = crypto.randomUUID();\n\t#appLifecycleRevision = 0;\n\t#appLifecycleFenced = false;\n\t#appDisposing = false;\n\t#appSettingsUnaccounted = false;\n\t#appSettingsFailure = false;\n\t#appDeferredRefresh: { strategy: ModelRefreshStrategy; credentialSessionId?: string } | undefined;\n\t#canonicalIndex: CanonicalModelIndex = {\n\t\trecords: [],\n\t\tbyId: new Map(),\n" }, { "before": "\t#loadedModelPresetRegistryManifestSha256: string | undefined;\n\t#modelPresetRegistryAgentDir: string;\n\t#modelPresetRegistryDependencies: Omit;\n\t#cancelModelPresetRegistryRefresh: (() => Promise) | undefined;\n\t#disposePromise: Promise | undefined;\n\t#unsubscribeAuthGeneration: (() => void) | undefined;\n\t#staticModelsLoaded = false;\n", @@ -263,7 +291,7 @@ }, { "before": "\t\t\t\tagentDir: this.#modelPresetRegistryAgentDir,\n\t\t\t\tknownManifestSha256: this.#loadedModelPresetRegistryManifestSha256,\n\t\t\t},\n\t\t\t() => {\n\t\t\t\tvoid this.#enqueueCatalogMutation(() => {\n\t\t\t\t\tif (this.#disposed) return;\n\t\t\t\t\tthis.#reloadStaticModels();\n\t\t\t\t\tthis.#modelBindingsApplier.apply();\n\t\t\t\t\tthis.#notifyCatalogChanged();\n\t\t\t\t}).catch(() => undefined);\n\t\t\t},\n\t\t);\n\t}\n\n\tdispose(): Promise {\n\t\tif (this.#disposePromise) return this.#disposePromise;\n\t\tthis.#disposed = true;\n\t\tthis.#catalogRefreshGeneration++;\n\t\tconst awaitRefreshDisposal = this.#cancelModelPresetRegistryRefresh?.() ?? Promise.resolve();\n\t\tthis.#cancelModelPresetRegistryRefresh = undefined;\n", - "after": "\t\t\t\tagentDir: this.#modelPresetRegistryAgentDir,\n\t\t\t\tknownManifestSha256: this.#loadedModelPresetRegistryManifestSha256,\n\t\t\t},\n\t\t\tasync () => {\n\t\t\t\tawait this.#enqueueCatalogMutation(() => {\n\t\t\t\t\tif (this.#disposed) return;\n\t\t\t\t\tthis.#reloadStaticModels();\n\t\t\t\t\tthis.#modelBindingsApplier.apply();\n\t\t\t\t\tthis.#notifyCatalogChanged();\n\t\t\t\t});\n\t\t\t\tawait Promise.allSettled([...this.#appCatalogCallbacks]);\n\t\t\t\tawait this.#flushAppSettings();\n\t\t\t},\n\t\t);\n\t}\n\n\t/** Registry maintenance only; borrowed AuthStorage/Settings require separate owners. */\n\tgetAppLifecycleActivity(): {\n\t\tgeneration: string; complete: boolean; starting: number; queued: number;\n\t\trunning: number; settling: number; unknown: string[];\n\t} {\n\t\tconst helper = this.#cancelModelPresetRegistryRefresh?.getActivity();\n\t\tconst unknown = this.#appLifecycleFenced ? [] : [\"model_registry_admission_open\"];\n\t\tif (!helper && !this.#disposed) unknown.push(\"model_registry_maintenance_unaccounted\");\n\t\tif (this.#appSettingsUnaccounted) unknown.push(\"model_registry_settings_unaccounted\");\n\t\tif (this.#appSettingsFailure) unknown.push(\"model_registry_settings_unconfirmed\");\n\t\treturn {\n\t\t\tgeneration: `${this.#appLifecycleEpoch}:${this.#appLifecycleRevision}:${helper?.revision ?? 0}`,\n\t\t\tcomplete: unknown.length === 0,\n\t\t\tstarting: Number(helper?.scheduled === true), queued: this.#pendingCatalogMutations,\n\t\t\trunning: (helper?.pending ?? 0) + Number(this.#backgroundRefresh !== undefined),\n\t\t\tsettling: this.#appCatalogCallbacks.size + Number(this.#appDisposing), unknown,\n\t\t};\n\t}\n\n\tsetAppLifecycleAdmission(closed: boolean): void {\n\t\tif (this.#disposed || this.#appLifecycleFenced === closed) return;\n\t\tthis.#appLifecycleFenced = closed; this.#appLifecycleRevision++;\n\t\tthis.#cancelModelPresetRegistryRefresh?.setAdmissionFence(closed);\n\t\tif (!closed && this.#appDeferredRefresh !== undefined) {\n\t\t\tconst strategy = this.#appDeferredRefresh; this.#appDeferredRefresh = undefined;\n\t\t\tthis.refreshInBackground(strategy);\n\t\t}\n\t}\n\n\tasync awaitAppLifecycleSettlement(): Promise {\n\t\tif (!this.#appLifecycleFenced && !this.#disposed) throw new Error(\"Model registry admission is not fenced\");\n\t\tdo {\n\t\t\tawait Promise.allSettled([\n\t\t\t\tthis.#cancelModelPresetRegistryRefresh?.awaitSettlement(), this.#backgroundRefresh,\n\t\t\t\tthis.#catalogMutationTail, ...this.#appCatalogCallbacks,\n\t\t\t]);\n\t\t\tif (!this.#appLifecycleFenced && !this.#disposed) throw new Error(\"Model registry admission fence changed\");\n\t\t} while ((this.#cancelModelPresetRegistryRefresh?.getActivity().pending ?? 0) > 0\n\t\t\t|| this.#backgroundRefresh || this.#pendingCatalogMutations > 0 || this.#appCatalogCallbacks.size > 0);\n\t\tif (this.#appSettingsFailure) throw new Error(\"Model registry settings persistence is unconfirmed\");\n\t}\n\n\tasync #flushAppSettings(): Promise {\n\t\t// The default proxy has no object or pending writes before initialization.\n\t\tif (this.#settings === settings && !isSettingsInitialized()) return;\n\t\tconst owner = this.#settings as Partial>;\n\t\tif (typeof owner.flushOrThrow !== \"function\") return;\n\t\ttry { await owner.flushOrThrow(); }\n\t\tcatch (error) { this.#appSettingsFailure = true; this.#appLifecycleRevision++; throw error; }\n\t}\n\n\tdispose(): Promise {\n\t\tif (this.#disposePromise) return this.#disposePromise;\n\t\tthis.#disposed = true;\n\t\tthis.#appDisposing = true;\n\t\tthis.#appLifecycleFenced = true; this.#appLifecycleRevision++;\n\t\tthis.#catalogRefreshGeneration++;\n\t\tconst awaitRefreshDisposal = this.#cancelModelPresetRegistryRefresh?.() ?? Promise.resolve();\n\t\tthis.#cancelModelPresetRegistryRefresh = undefined;\n" + "after": "\t\t\t\tagentDir: this.#modelPresetRegistryAgentDir,\n\t\t\t\tknownManifestSha256: this.#loadedModelPresetRegistryManifestSha256,\n\t\t\t},\n\t\t\tasync () => {\n\t\t\t\tawait this.#enqueueCatalogMutation(() => {\n\t\t\t\t\tif (this.#disposed) return;\n\t\t\t\t\tthis.#reloadStaticModels();\n\t\t\t\t\tthis.#modelBindingsApplier.apply();\n\t\t\t\t\tthis.#notifyCatalogChanged();\n\t\t\t\t});\n\t\t\t\tawait Promise.allSettled([...this.#appCatalogCallbacks]);\n\t\t\t\tawait this.#flushAppSettings();\n\t\t\t},\n\t\t);\n\t}\n\n\t/** Registry maintenance only; borrowed AuthStorage/Settings require separate owners. */\n\tgetAppLifecycleActivity(): {\n\t\tgeneration: string; complete: boolean; starting: number; queued: number;\n\t\trunning: number; settling: number; unknown: string[];\n\t} {\n\t\tconst helper = this.#cancelModelPresetRegistryRefresh?.getActivity();\n\t\tconst unknown = this.#appLifecycleFenced ? [] : [\"model_registry_admission_open\"];\n\t\tif (!helper && !this.#disposed) unknown.push(\"model_registry_maintenance_unaccounted\");\n\t\tif (this.#appSettingsUnaccounted) unknown.push(\"model_registry_settings_unaccounted\");\n\t\tif (this.#appSettingsFailure) unknown.push(\"model_registry_settings_unconfirmed\");\n\t\treturn {\n\t\t\tgeneration: `${this.#appLifecycleEpoch}:${this.#appLifecycleRevision}:${helper?.revision ?? 0}`,\n\t\t\tcomplete: unknown.length === 0,\n\t\t\tstarting: Number(helper?.scheduled === true), queued: this.#pendingCatalogMutations,\n\t\t\trunning: (helper?.pending ?? 0) + Number(this.#backgroundRefresh !== undefined),\n\t\t\tsettling: this.#appCatalogCallbacks.size + Number(this.#appDisposing), unknown,\n\t\t};\n\t}\n\n\tsetAppLifecycleAdmission(closed: boolean): void {\n\t\tif (this.#disposed || this.#appLifecycleFenced === closed) return;\n\t\tthis.#appLifecycleFenced = closed; this.#appLifecycleRevision++;\n\t\tthis.#cancelModelPresetRegistryRefresh?.setAdmissionFence(closed);\n\t\tif (!closed && this.#appDeferredRefresh !== undefined) {\n\t\t\tconst { strategy, credentialSessionId } = this.#appDeferredRefresh; this.#appDeferredRefresh = undefined;\n\t\t\tthis.refreshInBackground(strategy, credentialSessionId);\n\t\t}\n\t}\n\n\tasync awaitAppLifecycleSettlement(): Promise {\n\t\tif (!this.#appLifecycleFenced && !this.#disposed) throw new Error(\"Model registry admission is not fenced\");\n\t\tdo {\n\t\t\tawait Promise.allSettled([\n\t\t\t\tthis.#cancelModelPresetRegistryRefresh?.awaitSettlement(), this.#backgroundRefresh,\n\t\t\t\tthis.#catalogMutationTail, ...this.#appCatalogCallbacks,\n\t\t\t]);\n\t\t\tif (!this.#appLifecycleFenced && !this.#disposed) throw new Error(\"Model registry admission fence changed\");\n\t\t} while ((this.#cancelModelPresetRegistryRefresh?.getActivity().pending ?? 0) > 0\n\t\t\t|| this.#backgroundRefresh || this.#pendingCatalogMutations > 0 || this.#appCatalogCallbacks.size > 0);\n\t\tif (this.#appSettingsFailure) throw new Error(\"Model registry settings persistence is unconfirmed\");\n\t}\n\n\tasync #flushAppSettings(): Promise {\n\t\t// The default proxy has no object or pending writes before initialization.\n\t\tif (this.#settings === settings && !isSettingsInitialized()) return;\n\t\tconst owner = this.#settings as Partial>;\n\t\tif (typeof owner.flushOrThrow !== \"function\") return;\n\t\ttry { await owner.flushOrThrow(); }\n\t\tcatch (error) { this.#appSettingsFailure = true; this.#appLifecycleRevision++; throw error; }\n\t}\n\n\tdispose(): Promise {\n\t\tif (this.#disposePromise) return this.#disposePromise;\n\t\tthis.#disposed = true;\n\t\tthis.#appDisposing = true;\n\t\tthis.#appLifecycleFenced = true; this.#appLifecycleRevision++;\n\t\tthis.#catalogRefreshGeneration++;\n\t\tconst awaitRefreshDisposal = this.#cancelModelPresetRegistryRefresh?.() ?? Promise.resolve();\n\t\tthis.#cancelModelPresetRegistryRefresh = undefined;\n" }, { "before": "\t\tthis.#disposeAuthStorageFallbackResolver?.();\n\t\tthis.#disposeAuthStorageFallbackResolver = undefined;\n\t\tthis.#catalogChangeListeners.clear();\n\t\tthis.#disposePromise = awaitRefreshDisposal;\n\t\treturn this.#disposePromise;\n\t}\n\n\t#enqueueCatalogMutation(operation: () => void | Promise): Promise {\n\t\tthis.#pendingCatalogMutations++;\n\t\tlet run: Promise;\n\t\tif (this.#pendingCatalogMutations === 1) {\n\t\t\ttry {\n", @@ -278,12 +306,12 @@ "after": "\tsetScopedSettings(settingsReader: Pick): void {\n\t\tthis.#catalogRefreshGeneration++;\n\t\tthis.#settings = settingsReader;\n\t\tthis.#appSettingsUnaccounted = typeof (settingsReader as Partial>).flushOrThrow !== \"function\";\n\t\tthis.#appLifecycleRevision++;\n\t\tthis.#staticModelsLoaded = false;\n\t\tthis.#reloadStaticModels();\n\t\tthis.#rebuildCanonicalIndex();\n" }, { - "before": "\t}\n\n\trefreshInBackground(strategy: ModelRefreshStrategy = \"online-if-uncached\"): void {\n\t\tif (this.#backgroundRefresh) {\n\t\t\treturn;\n\t\t}\n", - "after": "\t}\n\n\trefreshInBackground(strategy: ModelRefreshStrategy = \"online-if-uncached\"): void {\n\t\tif (this.#disposed) return;\n\t\tif (this.#appLifecycleFenced) {\n\t\t\tif (this.#appDeferredRefresh === undefined) { this.#appDeferredRefresh = strategy; this.#appLifecycleRevision++; }\n\t\t\treturn;\n\t\t}\n\t\tif (this.#backgroundRefresh) {\n\t\t\treturn;\n\t\t}\n" + "before": "\trefreshInBackground(strategy: ModelRefreshStrategy = \"online-if-uncached\", credentialSessionId?: string): void {\n\t\tif (this.#backgroundRefresh) {\n\t\t\treturn;\n\t\t}\n", + "after": "\trefreshInBackground(strategy: ModelRefreshStrategy = \"online-if-uncached\", credentialSessionId?: string): void {\n\t\tif (this.#disposed) return;\n\t\tif (this.#appLifecycleFenced) {\n\t\t\tif (this.#appDeferredRefresh === undefined) { this.#appDeferredRefresh = { strategy, credentialSessionId }; this.#appLifecycleRevision++; }\n\t\t\treturn;\n\t\t}\n\t\tif (this.#backgroundRefresh) {\n\t\t\treturn;\n\t\t}\n" }, { - "before": "\t\t\t.finally(() => {\n\t\t\t\tif (this.#backgroundRefresh === refreshPromise) {\n\t\t\t\t\tthis.#backgroundRefresh = undefined;\n\t\t\t\t}\n\t\t\t});\n\t\tthis.#backgroundRefresh = refreshPromise;\n\t}\n\n\tasync refreshProvider(providerId: string, strategy: ModelRefreshStrategy = \"online\"): Promise {\n", - "after": "\t\t\t.finally(() => {\n\t\t\t\tif (this.#backgroundRefresh === refreshPromise) {\n\t\t\t\t\tthis.#backgroundRefresh = undefined;\n\t\t\t\t\tthis.#appLifecycleRevision++;\n\t\t\t\t}\n\t\t\t});\n\t\tthis.#backgroundRefresh = refreshPromise;\n\t\tthis.#appLifecycleRevision++;\n\t}\n\n\tasync refreshProvider(providerId: string, strategy: ModelRefreshStrategy = \"online\"): Promise {\n" + "before": "\t\t\t.finally(() => {\n\t\t\t\tif (this.#backgroundRefresh === refreshPromise) {\n\t\t\t\t\tthis.#backgroundRefresh = undefined;\n\t\t\t\t}\n\t\t\t});\n\t\tthis.#backgroundRefresh = refreshPromise;\n\t}\n", + "after": "\t\t\t.finally(() => {\n\t\t\t\tif (this.#backgroundRefresh === refreshPromise) {\n\t\t\t\t\tthis.#backgroundRefresh = undefined;\n\t\t\t\t\tthis.#appLifecycleRevision++;\n\t\t\t\t}\n\t\t\t});\n\t\tthis.#backgroundRefresh = refreshPromise;\n\t\tthis.#appLifecycleRevision++;\n\t}\n" }, { "before": "\t}\n\n\t#notifyCatalogChanged(): void {\n\t\tfor (const listener of [...this.#catalogChangeListeners]) {\n\t\t\ttry {\n\t\t\t\tlistener();\n\t\t\t} catch (error) {\n\t\t\t\tlogger.debug(\"ModelRegistry catalog listener failed\", { error: String(error) });\n\t\t\t}\n\t\t}\n\t}\n\n\t#suspendRebuild(): void {\n", @@ -294,8 +322,8 @@ { "package": "@gajae-code/coding-agent", "path": "src/config/model-preset-registry.ts", - "beforeSha256": "2bfda0d780c68ca8cc8110940cc28486495bc8016e69d3ebd2326ae561d727df", - "afterSha256": "0a7f77c3d3a1cdf09db189d22bfefd4129de6adf3dc5cf72a18b6ef0a7b77835", + "beforeSha256": "7eac6212729002b166a24f1ad196e0f0961c6421fbaf524dda603224e65306ce", + "afterSha256": "c5b58f5acdd48436d6fa63e34b704d8d177bc520a70b5de4bbfda8c0fee11df0", "replacements": [ { "before": "\t\tpresetCount: valid ? valid.presets.presets.length + valid.retainedPresets.length : 0,\n\t};\n}\n\nexport function refreshModelPresetRegistryInBackground(\n\tdependencies: ModelPresetRegistryDependencies = {},\n\tonAccepted?: () => void,\n): () => Promise {\n\tif (dependencies.automaticRefresh === false) return async () => {};\n\tconst agentDir = effectiveAgentDir(dependencies);\n\tlet status: ModelPresetRegistryStatus;\n\ttry {\n", @@ -326,8 +354,8 @@ { "package": "@gajae-code/ai", "path": "src/auth-storage.ts", - "beforeSha256": "0d4cbf3422a1312bb2a3e8eca8522024b7b18980da19f30a565855a3297b5ac8", - "afterSha256": "a005e9f1d9d3dc515ae04668ab364aef497f7fbce093d12fac97182981240581", + "beforeSha256": "1ccf1eb4f7ef9ac7542b38e9239f78a3c24698ecadf72b244e8585190cbf1a2e", + "afterSha256": "dec11edcbad05b640034502ef032666ad2436643e0cb7e0a20358815f91f5328", "replacements": [ { "before": "\t#oauthRefreshLeaseOwner = crypto.randomUUID();\n\n\t#closed = false;\n\n\tconstructor(store: AuthCredentialStore, options: AuthStorageOptions = {}) {\n\t\tthis.#store = store;\n\t\tstore.onSnapshotChanged?.(() => {\n\t\t\tthis.#reloadCredentialRowsFromStore();\n\t\t\tvoid this.reload();\n\t\t});\n\t\tthis.#configValueResolver = options.configValueResolver ?? defaultConfigValueResolver;\n\t\tthis.#usageProviderResolver = options.usageProviderResolver ?? resolveDefaultUsageProvider;\n\t\tthis.#rankingStrategyResolver = options.rankingStrategyResolver ?? resolveDefaultRankingStrategy;\n\t\tthis.#usageCache = new AuthStorageUsageCache(this.#store);\n", @@ -346,8 +374,8 @@ "after": "\t\tif (providerImpl.supports && !providerImpl.supports(params)) return null;\n\n\t\ttry {\n\t\t\treturn await this.#ownAppTask(() => providerImpl.fetchUsage(params, {\n\t\t\t\tfetch: this.#usageFetch,\n\t\t\t\tlogger: logDetails ? this.#usageLogger : undefined,\n\t\t\t}));\n\t\t} catch (error) {\n\t\t\tif (logDetails) {\n\t\t\t\tlogger.debug(\"AuthStorage usage fetch failed\", {\n" }, { - "before": "\t\tconst inFlight = this.#usageRequestInFlight.get(cacheKey);\n\t\tif (inFlight) return inFlight;\n\n\t\tconst promise = (async () => {\n\t\t\tconst report = await this.#fetchUsageUncached(request, timeoutMs, logDetails);\n\t\t\tconst ttlJitter = USAGE_REPORT_TTL_MS * (Math.random() * 0.5 - 0.25);\n\t\t\tif (report !== null) {\n", - "after": "\t\tconst inFlight = this.#usageRequestInFlight.get(cacheKey);\n\t\tif (inFlight) return inFlight;\n\n\t\tconst promise = this.#ownAppTask(async () => {\n\t\t\tconst report = await this.#fetchUsageUncached(request, timeoutMs, logDetails);\n\t\t\tconst ttlJitter = USAGE_REPORT_TTL_MS * (Math.random() * 0.5 - 0.25);\n\t\t\tif (report !== null) {\n" + "before": "\t\tif (inFlight) return inFlight;\n\n\t\tconst promise = (async () => {\n\t\t\tconst report = await this.#fetchUsageUncached(request, timeoutMs, logDetails);\n", + "after": "\t\tif (inFlight) return inFlight;\n\n\t\tconst promise = this.#ownAppTask(async () => {\n\t\t\tconst report = await this.#fetchUsageUncached(request, timeoutMs, logDetails);\n" }, { "before": "\t\t\t\tthis.#usageCache.set(cacheKey, { value: lastGood, expiresAt: coolDown });\n\t\t\t}\n\t\t\treturn lastGood;\n\t\t})().finally(() => {\n\t\t\tif (this.#usageRequestInFlight.get(cacheKey) === promise) {\n\t\t\t\tthis.#usageRequestInFlight.delete(cacheKey);\n\t\t\t}\n", @@ -362,12 +390,12 @@ "after": "\t\t\tif (!shared) {\n\t\t\t\t// Don't forward the caller signal into the shared fetch — first caller's\n\t\t\t\t// abort would otherwise cancel the upstream for every peer.\n\t\t\t\tshared = this.#ownAppTask(() => override()).finally(() => {\n\t\t\t\t\tif (this.#usageReportsInFlight.get(OVERRIDE_KEY) === shared) {\n\t\t\t\t\t\tthis.#usageReportsInFlight.delete(OVERRIDE_KEY);\n\t\t\t\t\t}\n" }, { - "before": "\t\tconst inFlight = this.#usageReportsInFlight.get(cacheKey);\n\t\tif (inFlight) return raceUsageWithSignal(inFlight, options?.signal);\n\n\t\tconst promise = (async () => {\n\t\t\tif (options?.logDetails !== false) {\n\t\t\t\tfor (const request of requests) {\n\t\t\t\t\tthis.#usageLogger?.debug(\"Usage fetch queued\", {\n", - "after": "\t\tconst inFlight = this.#usageReportsInFlight.get(cacheKey);\n\t\tif (inFlight) return raceUsageWithSignal(inFlight, options?.signal);\n\n\t\tconst promise = this.#ownAppTask(async () => {\n\t\t\tif (options?.logDetails !== false) {\n\t\t\t\tfor (const request of requests) {\n\t\t\t\t\tthis.#usageLogger?.debug(\"Usage fetch queued\", {\n" + "before": "\t\tif (inFlight) return raceUsageWithSignal(inFlight, options?.signal);\n\n\t\tconst promise = (async () => {\n", + "after": "\t\tif (inFlight) return raceUsageWithSignal(inFlight, options?.signal);\n\n\t\tconst promise = this.#ownAppTask(async () => {\n" }, { - "before": "\t\t\t\t});\n\t\t\t}\n\t\t\treturn resolved;\n\t\t})().finally(() => {\n\t\t\tif (this.#usageReportsInFlight.get(cacheKey) === promise) {\n\t\t\t\tthis.#usageReportsInFlight.delete(cacheKey);\n\t\t\t}\n", - "after": "\t\t\t\t});\n\t\t\t}\n\t\t\treturn resolved;\n\t\t}).finally(() => {\n\t\t\tif (this.#usageReportsInFlight.get(cacheKey) === promise) {\n\t\t\t\tthis.#usageReportsInFlight.delete(cacheKey);\n\t\t\t}\n" + "before": "\t\t\t\tif (leaseOwned) this.#releaseUsageFetchLease(cacheKey);\n\t\t\t}\n\t\t})().finally(() => {\n", + "after": "\t\t\t\tif (leaseOwned) this.#releaseUsageFetchLease(cacheKey);\n\t\t\t}\n\t\t}).finally(() => {\n" }, { "before": "\t\tif (credentialId === undefined) {\n\t\t\treturn this.#refreshOAuthCredentialUnshared(provider, credential, undefined, signal, force, mcpClient);\n\t\t}\n\t\tconst promise = this.#refreshOAuthCredentialUnshared(\n\t\t\tprovider,\n\t\t\tcredential,\n\t\t\tcredentialId,\n\t\t\tundefined,\n\t\t\tforce,\n\t\t\tmcpClient,\n\t\t).finally(() => {\n\t\t\tthis.#oauthCredentialRefreshInFlight.delete(credentialId);\n\t\t});\n\t\tthis.#oauthCredentialRefreshInFlight.set(credentialId, promise);\n", @@ -422,38 +450,40 @@ { "package": "@gajae-code/ai", "path": "src/utils/event-stream.ts", + "beforeSha256": "0bed38466df6d56c6b63a0295bfa7aa9f4f189937299246f2f8705a5936952ae", + "afterSha256": "eb81b0961f3ef800905efb80245049c88b5a0c498e0b79a23caafedc8e519559", "replacements": [ { "before": "import type { AssistantMessage, AssistantMessageEvent } from \"../types\";\n\ninterface EventQueueNode {\n\ttype: \"event\";\n\tevent: T;\n}\n\ntype QueueNode = EventQueueNode | { type: \"consumer-drain\"; drain: ConsumerDrain };\n", "after": "import { AsyncLocalStorage } from \"node:async_hooks\";\nimport type { AssistantMessage, AssistantMessageEvent } from \"../types\";\n\n// Physical producer identity is held here, not inferred from a terminal event,\n// a source marker, or a structurally similar property on an arbitrary stream.\ninterface AppStreamProducer {\n\tcompletion: Promise;\n\tchildren: Set>;\n\tunknown: Set;\n\tclosed: boolean;\n}\nconst appStreamProducers = new WeakMap();\nconst appStreamProducerContext = new AsyncLocalStorage();\n\n/** Retain raced iterator work in the actual initiating producer, not its caller. */\nexport function retainAppStreamTask(task: PromiseLike): Promise {\n\tconst promise = Promise.resolve(task);\n\tconst owner = appStreamProducerContext.getStore();\n\tif (owner) {\n\t\tif (owner.closed) throw new Error(\"Stream producer started work after completion\");\n\t\tconst joined = promise.then(() => {}, () => {}).finally(() => owner.children.delete(joined));\n\t\towner.children.add(joined);\n\t}\n\treturn promise;\n}\n\nexport function getAppStreamProducer(source: object): {\n\tcompletion: Promise; getUnknown(): string[];\n} | undefined {\n\tconst owner = appStreamProducers.get(source);\n\treturn owner && Object.freeze({ completion: owner.completion, getUnknown: () => [...owner.unknown] });\n}\n\n/** Reserve BEFORE invoking the body; settle only after its actual finally and children. */\nexport function runAppStreamProducer(stream: AssistantMessageEventStream, operation: () => Promise): void {\n\tif (appStreamProducers.has(stream)) throw new Error(\"Stream producer already registered\");\n\tconst completion = Promise.withResolvers();\n\tconst owner: AppStreamProducer = { completion: completion.promise, children: new Set(), unknown: new Set(), closed: false };\n\tappStreamProducers.set(stream, owner);\n\tvoid (async () => {\n\t\ttry { await appStreamProducerContext.run(owner, operation); }\n\t\tcatch (error) { stream.fail(error); }\n\t\tfinally {\n\t\t\twhile (owner.children.size) await Promise.allSettled([...owner.children]);\n\t\t\towner.closed = true;\n\t\t\tcompletion.resolve();\n\t\t}\n\t})();\n}\n\n/** Wrapper consumption is not the child transport's producer completion. */\nexport function adoptAppStreamProducer(target: object, source: object): void {\n\tconst owner = appStreamProducers.get(target);\n\tif (!owner || owner.closed) throw new Error(\"Stream producer ownership is unavailable\");\n\tconst child = getAppStreamProducer(source);\n\tif (!child) { owner.unknown.add(\"sdk_provider_producer_unrepresented\"); return; }\n\tconst joined = child.completion.then(() => {\n\t\tfor (const reason of child.getUnknown()) owner.unknown.add(reason);\n\t}).finally(() => owner.children.delete(joined));\n\towner.children.add(joined);\n}\n\n/** Used only for concrete unaudited subfeatures, never as a substitute for retention. */\nexport function markAppStreamProducerUnknown(stream: object, reason: string): void {\n\tconst owner = appStreamProducers.get(stream);\n\tif (!owner || owner.closed) throw new Error(\"Stream producer ownership is unavailable\");\n\towner.unknown.add(reason);\n}\n\ninterface EventQueueNode {\n\ttype: \"event\";\n\tevent: T;\n}\n\ntype QueueNode = EventQueueNode | { type: \"consumer-drain\"; drain: ConsumerDrain };\n" } - ], - "beforeSha256": "0bed38466df6d56c6b63a0295bfa7aa9f4f189937299246f2f8705a5936952ae", - "afterSha256": "eb81b0961f3ef800905efb80245049c88b5a0c498e0b79a23caafedc8e519559" + ] }, { "package": "@gajae-code/ai", "path": "src/utils/idle-iterator.ts", + "beforeSha256": "7fd95258ecbb2c28c45be45add2a4873bb3081fec4cdb7f016169a9e815d0b2b", + "afterSha256": "78dc9774e9ce1d9ccfe78eb9f97fdcf88d375092c9839ee07ab3d9eaae830cb4", "replacements": [ { "before": "import { $env } from \"@gajae-code/utils\";\nimport { STREAM_FIRST_EVENT_TIMEOUT_PROVIDER_CODE } from \"./fallback-transport\";\n\nconst DEFAULT_STREAM_IDLE_TIMEOUT_MS = 120_000;\nconst DEFAULT_STREAM_FIRST_EVENT_TIMEOUT_MS = 100_000;\nconst ALIBABA_TOKEN_PLAN_FIRST_EVENT_TIMEOUT_MS = 600_000;\nconst KIMI_CODE_FIRST_EVENT_TIMEOUT_MS = 300_000;\n", "after": "import { $env } from \"@gajae-code/utils\";\nimport { retainAppStreamTask } from \"./event-stream\";\nimport { STREAM_FIRST_EVENT_TIMEOUT_PROVIDER_CODE } from \"./fallback-transport\";\n\nconst DEFAULT_STREAM_IDLE_TIMEOUT_MS = 120_000;\nconst DEFAULT_STREAM_FIRST_EVENT_TIMEOUT_MS = 100_000;\nconst ALIBABA_TOKEN_PLAN_FIRST_EVENT_TIMEOUT_MS = 600_000;\nconst KIMI_CODE_FIRST_EVENT_TIMEOUT_MS = 300_000;\n" }, { - "before": "\tconst abortSignal = options.abortSignal;\n\tconst iterator = iterable[Symbol.asyncIterator]();\n\n\tconst closeIterator = (): void => {\n\t\tconst returnPromise = iterator.return?.();\n\t\tif (returnPromise) {\n\t\t\tvoid returnPromise.catch(() => {});\n\t\t}\n\t};\n\n\tif (abortSignal?.aborted) {\n\t\tcloseIterator();\n\t\tthrow abortReason(abortSignal);\n", - "after": "\tconst abortSignal = options.abortSignal;\n\tconst iterator = iterable[Symbol.asyncIterator]();\n\n\tconst closeIterator = (): void => {\n\t\tconst returnPromise = iterator.return?.();\n\t\tif (returnPromise) {\n\t\t\tvoid retainAppStreamTask(returnPromise).catch(() => {});\n\t\t}\n\t};\n\n\tif (abortSignal?.aborted) {\n\t\tcloseIterator();\n\t\tthrow abortReason(abortSignal);\n" + "before": "\t\t\tconst returnPromise = iterator.return?.();\n\t\t\tif (returnPromise) {\n\t\t\t\tvoid returnPromise.catch(() => {});\n", + "after": "\t\t\tconst returnPromise = iterator.return?.();\n\t\t\tif (returnPromise) {\n\t\t\t\tvoid retainAppStreamTask(returnPromise).catch(() => {});\n" }, { - "before": "\n\t\t// Arm timeout/abort races before asking the source for its next item. A\n\t\t// periodic keepalive iterator commonly registers its own timer inside\n\t\t// `next()`; registering that first lets equal-deadline keepalives win every\n\t\t// race and extend the idle window forever. Already-buffered items still\n\t\t// settle as microtasks before a 0ms watchdog.\n\t\tracers.unshift(withRacy(iterator.next()));\n\n\t\ttry {\n\t\t\tconst outcome = await Promise.race(racers);\n\t\t\tif (outcome.kind === \"abort\") {\n\t\t\t\tcloseIterator();\n\t\t\t\tthrow abortReason(abortSignal!);\n", - "after": "\n\t\t// Arm timeout/abort races before asking the source for its next item. A\n\t\t// periodic keepalive iterator commonly registers its own timer inside\n\t\t// `next()`; registering that first lets equal-deadline keepalives win every\n\t\t// race and extend the idle window forever. Already-buffered items still\n\t\t// settle as microtasks before a 0ms watchdog.\n\t\tracers.unshift(withRacy(retainAppStreamTask(iterator.next())));\n\n\t\ttry {\n\t\t\tconst outcome = await Promise.race(racers);\n\t\t\tif (outcome.kind === \"abort\") {\n\t\t\t\tcloseIterator();\n\t\t\t\tthrow abortReason(abortSignal!);\n" + "before": "\t\t\tracers.unshift(withRacy(iterator.next()));\n", + "after": "\t\t\tracers.unshift(withRacy(retainAppStreamTask(iterator.next())));\n" } - ], - "beforeSha256": "2bd8de687b56695bc9faa6ac4772c2693ce573a614144eba2b47a1cbb5e2d875", - "afterSha256": "fbc6742abee2bcf20d22fa174ba7d21e688fde5cec13b32401fe36225ea90fd3" + ] }, { "package": "@gajae-code/ai", "path": "src/stream.ts", + "beforeSha256": "334ed7da5400d43f427008d50276dafa2770592a4522308cb31b91fd9580992c", + "afterSha256": "f488c3f848386daeaf3b701b6ed13dbf6feebd6efc0c6c633b601689c23dca15", "replacements": [ { "before": "\tOptionsForApi,\n\tSimpleStreamOptions,\n\tStreamOptions,\n\tThinkingBudgets,\n\tToolChoice,\n} from \"./types\";\nimport { AssistantMessageEventStream } from \"./utils/event-stream\";\nimport { isFoundryEnabled } from \"./utils/foundry\";\n\nlet cachedVertexAdcCredentialsExists: boolean | null = null;\n\nfunction hasVertexAdcCredentials(): boolean {\n\tif (cachedVertexAdcCredentialsExists === null) {\n", @@ -468,8 +498,8 @@ "after": "export function streamFromLazyImport(\n\tcreateInner: () => Promise,\n\tsignal?: AbortSignal,\n\tonStreamCreated?: () => void,\n): AssistantMessageEventStream {\n\tconst outer = new AssistantMessageEventStream();\n\trunAppStreamProducer(outer, async () => {\n\t\ttry {\n\t\t\tconst inner = await createInner();\n\t\t\tawait pipeAssistantStream(outer, inner, signal, onStreamCreated);\n\t\t} catch (error) {\n\t\t\touter.fail(error);\n\t\t}\n\t});\n\treturn outer;\n}\n\n/**\n * Build an actionable \"missing API key\" error for a provider, used by the\n * low-level `stream`/`complete` entry points (#755).\n" }, { - "before": "export async function complete(\n\tmodel: Model,\n\tcontext: Context,\n\toptions?: OptionsForApi,\n): Promise {\n\tconst s = stream(model, context, options);\n\treturn s.result();\n}\n\ntype AuthRetryFailure = {\n\terror: unknown;\n\tbufferedEvents: AssistantMessageEvent[];\n\tterminalEvent?: Extract;\n", - "after": "export async function complete(\n\tmodel: Model,\n\tcontext: Context,\n\toptions?: OptionsForApi,\n): Promise {\n\tconst s = stream(model, context, options);\n\ttry { return await s.result(); }\n\tfinally { await getAppStreamProducer(s)?.completion; }\n}\n\ntype AuthRetryFailure = {\n\terror: unknown;\n\tbufferedEvents: AssistantMessageEvent[];\n\tterminalEvent?: Extract;\n" + "before": "export async function complete(\n\tmodel: Model,\n\tcontext: Context,\n\toptions?: OptionsForApi,\n): Promise {\n\tconst s = stream(model, context, options);\n\tfor await (const _event of s) {\n\t\t// Completion callers only need the terminal message, not buffered events.\n\t}\n\treturn s.result();\n}\n\ntype AuthRetryFailure = {\n\terror: unknown;\n\tbufferedEvents: AssistantMessageEvent[];\n\tterminalEvent?: Extract;\n", + "after": "export async function complete(\n\tmodel: Model,\n\tcontext: Context,\n\toptions?: OptionsForApi,\n): Promise {\n\tconst s = stream(model, context, options);\n\ttry {\n\t\tfor await (const _event of s) {\n\t\t\t// Completion callers only need the terminal message, not buffered events.\n\t\t}\n\t\treturn await s.result();\n\t}\n\tfinally { await getAppStreamProducer(s)?.completion; }\n}\n\ntype AuthRetryFailure = {\n\terror: unknown;\n\tbufferedEvents: AssistantMessageEvent[];\n\tterminalEvent?: Extract;\n" }, { "before": "\t\t\t\t\t...options,\n\t\t\t\t\tapiKey,\n\t\t\t\t\tonAuthError: undefined,\n\t\t\t\t\tonStreamCreated: markAdmission,\n\t\t\t\t\tsignal: requestSignal,\n\t\t\t\t});\n\t\t\t\tfor await (const event of inner) {\n\t\t\t\t\tif (!emittedReplayUnsafeEvent && event.type === \"start\") {\n\t\t\t\t\t\tbufferedEvents.push(event);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tif (\n", @@ -488,16 +518,16 @@ "after": "\t\tconst events = customApiProvider.streamSimple(model, context, {\n\t\t\t...options,\n\t\t\tmaxTokens: resolvedRequestMaxTokens,\n\t\t});\n\t\tif (!options?.onStreamCreated) return events;\n\t\tconst forwarded = new AssistantMessageEventStream();\n\t\trunAppStreamProducer(forwarded, () => pipeAssistantStream(forwarded, events, options.signal, options.onStreamCreated));\n\t\treturn forwarded;\n\t}\n\n\t// Vertex AI uses Application Default Credentials, not API keys\n\tif (model.api === \"google-vertex\") {\n\t\tconst providerOptions = mapOptionsForApi(model, options, undefined);\n" }, { - "before": "export async function completeSimple(\n\tmodel: Model,\n\tcontext: Context,\n\toptions?: SimpleStreamOptions,\n): Promise {\n\tconst s = streamSimple(model, context, options);\n\treturn s.result();\n}\n\nconst MIN_OUTPUT_TOKENS = 1024;\nconst DEFAULT_REQUEST_MAX_TOKENS = 32000;\nexport const OUTPUT_FALLBACK_BUFFER = 4000;\nconst ANTHROPIC_USE_INTERLEAVED_THINKING = Bun.env.PI_NO_INTERLEAVED_THINKING !== \"1\";\n", - "after": "export async function completeSimple(\n\tmodel: Model,\n\tcontext: Context,\n\toptions?: SimpleStreamOptions,\n): Promise {\n\tconst s = streamSimple(model, context, options);\n\ttry { return await s.result(); }\n\tfinally { await getAppStreamProducer(s)?.completion; }\n}\n\nconst MIN_OUTPUT_TOKENS = 1024;\nconst DEFAULT_REQUEST_MAX_TOKENS = 32000;\nexport const OUTPUT_FALLBACK_BUFFER = 4000;\nconst ANTHROPIC_USE_INTERLEAVED_THINKING = Bun.env.PI_NO_INTERLEAVED_THINKING !== \"1\";\n" + "before": "export async function completeSimple(\n\tmodel: Model,\n\tcontext: Context,\n\toptions?: SimpleStreamOptions,\n): Promise {\n\tconst s = streamSimple(model, context, options);\n\tfor await (const _event of s) {\n\t\t// Completion callers only need the terminal message, not buffered events.\n\t}\n\treturn s.result();\n}\n\nconst MIN_OUTPUT_TOKENS = 1024;\nconst DEFAULT_REQUEST_MAX_TOKENS = 32000;\nexport const OUTPUT_FALLBACK_BUFFER = 4000;\nconst ANTHROPIC_USE_INTERLEAVED_THINKING = Bun.env.PI_NO_INTERLEAVED_THINKING !== \"1\";\n", + "after": "export async function completeSimple(\n\tmodel: Model,\n\tcontext: Context,\n\toptions?: SimpleStreamOptions,\n): Promise {\n\tconst s = streamSimple(model, context, options);\n\ttry {\n\t\tfor await (const _event of s) {\n\t\t\t// Completion callers only need the terminal message, not buffered events.\n\t\t}\n\t\treturn await s.result();\n\t}\n\tfinally { await getAppStreamProducer(s)?.completion; }\n}\n\nconst MIN_OUTPUT_TOKENS = 1024;\nconst DEFAULT_REQUEST_MAX_TOKENS = 32000;\nexport const OUTPUT_FALLBACK_BUFFER = 4000;\nconst ANTHROPIC_USE_INTERLEAVED_THINKING = Bun.env.PI_NO_INTERLEAVED_THINKING !== \"1\";\n" } - ], - "beforeSha256": "47180a9a63d6abe2faaa23da6a68b592f369eae46c657a677f502266dd134cda", - "afterSha256": "404db53024a885ef16e6749911964b228d6670d3796e00acfe245956dad28a22" + ] }, { "package": "@gajae-code/ai", "path": "src/providers/register-builtins.ts", + "beforeSha256": "7afcc2c2627fd1eb0eb11392921b1c5afb4a4368c654731960c251f1b8e4a434", + "afterSha256": "1b0df1d1df9180e637cf38a6198f29759e8b8ecb8ed0207e7d0d15cd98ead0c4", "replacements": [ { "before": "\tAssistantMessageEventStream,\n\tContext,\n\tModel,\n\tOptionsForApi,\n} from \"../types\";\nimport { type AbortSourceTracker, createAbortSourceTracker } from \"../utils/abort\";\nimport { AssistantMessageEventStream as EventStreamImpl } from \"../utils/event-stream\";\nimport { transportFailureFacts } from \"../utils/fallback-transport\";\nimport {\n\tFirstEventTimeoutError,\n\tgetProviderFirstEventTimeoutFallbackMs,\n\tgetStreamFirstEventTimeoutMs,\n\tgetStreamIdleTimeoutMs,\n", @@ -511,13 +541,13 @@ "before": "\t\tlet abortTracker: AbortSourceTracker | undefined;\n\t\tconst outer = new EventStreamImpl(() =>\n\t\t\tabortTracker?.abortLocally(new Error(\"Provider stream consumer stopped before completion\")),\n\t\t);\n\t\tconst streamOptions = (options ?? {}) as OptionsForApi;\n\n\t\tloadModule()\n\t\t\t.then(module => {\n\t\t\t\tabortTracker = createAbortSourceTracker(streamOptions.signal);\n\t\t\t\tconst providerOptions = { ...streamOptions, signal: abortTracker.requestSignal } as OptionsForApi;\n\t\t\t\tconst inner = module.stream(model, context, providerOptions);\n\t\t\t\tforwardStream(outer, inner, model, streamOptions, abortTracker, onStreamCreated, limits);\n\t\t\t})\n\t\t\t.catch(error => {\n\t\t\t\tconst message = createLazyLoadErrorMessage(model, error);\n\t\t\t\touter.push({ type: \"error\", reason: \"error\", error: message });\n\t\t\t\touter.end(message);\n\t\t\t});\n\n\t\treturn outer;\n\t};\n}\n\n// ---------------------------------------------------------------------------\n", "after": "\t\tlet abortTracker: AbortSourceTracker | undefined;\n\t\tconst outer = new EventStreamImpl(() =>\n\t\t\tabortTracker?.abortLocally(new Error(\"Provider stream consumer stopped before completion\")),\n\t\t);\n\t\tconst streamOptions = (options ?? {}) as OptionsForApi;\n\n\t\trunAppStreamProducer(outer, () => loadModule()\n\t\t\t.then(async module => {\n\t\t\t\tabortTracker = createAbortSourceTracker(streamOptions.signal);\n\t\t\t\tconst providerOptions = { ...streamOptions, signal: abortTracker.requestSignal } as OptionsForApi;\n\t\t\t\tconst inner = module.stream(model, context, providerOptions);\n\t\t\t\tawait forwardStream(outer, inner, model, streamOptions, abortTracker, onStreamCreated, limits);\n\t\t\t})\n\t\t\t.catch(error => {\n\t\t\t\tconst message = createLazyLoadErrorMessage(model, error);\n\t\t\t\touter.push({ type: \"error\", reason: \"error\", error: message });\n\t\t\t\touter.end(message);\n\t\t\t}));\n\n\t\treturn outer;\n\t};\n}\n\n// ---------------------------------------------------------------------------\n" } - ], - "beforeSha256": "6ef5d14c49188b889dcf8fce48a7281366bbd719091d679a748fa329d53d4ba5", - "afterSha256": "abceac1f4e6e107da1f0bef71000ba4085508c1f56812127ecf6d556ee751a9d" + ] }, { "package": "@gajae-code/ai", "path": "src/providers/amazon-bedrock.ts", + "beforeSha256": "9f2c631e09f154f41bf5363fe1f982325c2bc08b03594717711f6791b9dc75ff", + "afterSha256": "0ee6014454a819e6fcb1c7d212dfa53c9acb84dda56088d1883bd436a0c0da64", "replacements": [ { "before": "/**\n * Amazon Bedrock Converse Stream provider.\n *\n * Talks directly to `bedrock-runtime.{region}.amazonaws.com` over HTTPS with\n * SigV4 signing and decodes the `application/vnd.amazon.eventstream` response.\n * No `@aws-sdk/*`, no `@smithy/*`, no `proxy-agent`. Proxies are honored via\n", @@ -531,13 +561,13 @@ "before": "\t\t\toutput.errorMessage = await appendRawHttpRequestDumpFor400(baseMessage + diagnostics, error, rawRequestDump);\n\t\t\toutput.duration = Date.now() - startTime;\n\t\t\tif (firstTokenTime) output.ttft = firstTokenTime - startTime;\n\t\t\tstream.push({ type: \"error\", reason: output.stopReason, error: output });\n\t\t\tstream.end();\n\t\t}\n\t})();\n\n\treturn stream;\n};\n\nfunction safeParsePayload(payload: Uint8Array): unknown {\n\tif (payload.length === 0) return {};\n", "after": "\t\t\toutput.errorMessage = await appendRawHttpRequestDumpFor400(baseMessage + diagnostics, error, rawRequestDump);\n\t\t\toutput.duration = Date.now() - startTime;\n\t\t\tif (firstTokenTime) output.ttft = firstTokenTime - startTime;\n\t\t\tstream.push({ type: \"error\", reason: output.stopReason, error: output });\n\t\t\tstream.end();\n\t\t}\n\t});\n\n\treturn stream;\n};\n\nfunction safeParsePayload(payload: Uint8Array): unknown {\n\tif (payload.length === 0) return {};\n" } - ], - "beforeSha256": "9b78c2af7e97d018f100fb6e6e8dc88694821ec6bcd802d6165b50ab11f43c37", - "afterSha256": "972549a554509bc2c2e1f0723c197aadd339c10c8345c0b78420b8499821fd65" + ] }, { "package": "@gajae-code/ai", "path": "src/providers/anthropic.ts", + "beforeSha256": "eaaa3017a5bf047e44698034e01df7e585a18a58ef4874eaf06968b45dddd833", + "afterSha256": "ca73a63fe6c875d656deb6922702701afefc273ffd28475f9cffcef23851c325", "replacements": [ { "before": "import * as nodeCrypto from \"node:crypto\";\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport { scheduler } from \"node:timers/promises\";\nimport * as tls from \"node:tls\";\nimport Anthropic, { type ClientOptions as AnthropicSdkClientOptions } from \"@anthropic-ai/sdk\";\n", @@ -551,13 +581,13 @@ "before": "\t\t\toutput.errorMessage = rewriteCopilotError(output.errorMessage, error, model.provider);\n\t\t\toutput.duration = Date.now() - startTime;\n\t\t\tif (firstTokenTime) output.ttft = firstTokenTime - startTime;\n\t\t\tstream.push({ type: \"error\", reason: output.stopReason, error: output });\n\t\t\tstream.end();\n\t\t}\n\t})();\n\n\treturn stream;\n};\n\nexport type AnthropicSystemBlock = {\n\ttype: \"text\";\n", "after": "\t\t\toutput.errorMessage = rewriteCopilotError(output.errorMessage, error, model.provider);\n\t\t\toutput.duration = Date.now() - startTime;\n\t\t\tif (firstTokenTime) output.ttft = firstTokenTime - startTime;\n\t\t\tstream.push({ type: \"error\", reason: output.stopReason, error: output });\n\t\t\tstream.end();\n\t\t}\n\t});\n\n\treturn stream;\n};\n\nexport type AnthropicSystemBlock = {\n\ttype: \"text\";\n" } - ], - "beforeSha256": "33e7c0cfef5c849bbfe958c4a62bcf217e19366d2ca3133e71b4399ad1fb10b5", - "afterSha256": "eee1c52fdd77f53dad676297560ca5722553088cf9888e7300e3d77507e4eefc" + ] }, { "package": "@gajae-code/ai", "path": "src/providers/azure-openai-responses.ts", + "beforeSha256": "89918ddbf5df35cc44fed0a300fe4ffb595ad4ac36f2c56e033331f0b1ace7fc", + "afterSha256": "9ecb3efeb5c0c52d99128ba366f763d595d0ee2cf41ffa6a59fa991d10c2f330", "replacements": [ { "before": "import { $credentialEnv, $env, extractHttpStatusFromError, logger } from \"@gajae-code/utils\";\nimport { APIConnectionTimeoutError, AzureOpenAI } from \"openai\";\nimport type {\n\tTool as OpenAITool,\n\tResponseCreateParamsStreaming,\n\tResponseInput,\n", @@ -571,33 +601,33 @@ "before": "\t\t\t\tfirstEventTimeoutError?.message ?? (await finalizeErrorMessage(normalizedError, rawRequestDump));\n\t\t\toutput.duration = Date.now() - startTime;\n\t\t\tif (firstTokenTime) output.ttft = firstTokenTime - startTime;\n\t\t\tstream.push({ type: \"error\", reason: output.stopReason, error: output });\n\t\t\tstream.end();\n\t\t}\n\t})();\n\n\treturn stream;\n};\n\nfunction normalizeAzureBaseUrl(baseUrl: string): string {\n\treturn baseUrl.replace(/\\/+$/, \"\");\n", "after": "\t\t\t\tfirstEventTimeoutError?.message ?? (await finalizeErrorMessage(normalizedError, rawRequestDump));\n\t\t\toutput.duration = Date.now() - startTime;\n\t\t\tif (firstTokenTime) output.ttft = firstTokenTime - startTime;\n\t\t\tstream.push({ type: \"error\", reason: output.stopReason, error: output });\n\t\t\tstream.end();\n\t\t}\n\t});\n\n\treturn stream;\n};\n\nfunction normalizeAzureBaseUrl(baseUrl: string): string {\n\treturn baseUrl.replace(/\\/+$/, \"\");\n" } - ], - "beforeSha256": "757e869c4cf59a0b7cd214e943f6a46b9457f93266a0c9f98ab1c91aa17c9014", - "afterSha256": "403196951817447052fbe2f0b059679923f64ce2f1322bcdc44cc1b0a3ff0075" + ] }, { "package": "@gajae-code/ai", "path": "src/providers/cursor.ts", + "beforeSha256": "a5bf34a23979f4b7e8a8343567c091807879680779fc6e1cd03cfae3adc75c08", + "afterSha256": "a3ba24be86d584df06207ea534f894c436ca46e07184beb300211a4dad7c6192", "replacements": [ { - "before": "import { createHash } from \"node:crypto\";\nimport * as fs from \"node:fs/promises\";\nimport http2 from \"node:http2\";\nimport { create, fromBinary, fromJson, type JsonValue, toBinary, toJson } from \"@bufbuild/protobuf\";\nimport { ValueSchema } from \"@bufbuild/protobuf/wkt\";\nimport { $env, extractHttpStatusFromError, sanitizeText } from \"@gajae-code/utils\";\n", - "after": "import { runAppStreamProducer, markAppStreamProducerUnknown } from \"../utils/event-stream\";\nimport { createHash } from \"node:crypto\";\nimport * as fs from \"node:fs/promises\";\nimport http2 from \"node:http2\";\nimport { create, fromBinary, fromJson, type JsonValue, toBinary, toJson } from \"@bufbuild/protobuf\";\nimport { ValueSchema } from \"@bufbuild/protobuf/wkt\";\nimport { $env, extractHttpStatusFromError, sanitizeText } from \"@gajae-code/utils\";\n" + "before": "import { createHash } from \"node:crypto\";\nimport * as fs from \"node:fs/promises\";\nimport http2 from \"node:http2\";\nimport type * as tls from \"node:tls\";\n", + "after": "import { runAppStreamProducer, markAppStreamProducerUnknown } from \"../utils/event-stream\";\nimport { createHash } from \"node:crypto\";\nimport * as fs from \"node:fs/promises\";\nimport http2 from \"node:http2\";\nimport type * as tls from \"node:tls\";\n" }, { - "before": "\tcontext: Context,\n\toptions?: CursorOptions,\n): AssistantMessageEventStream => {\n\tconst stream = new AssistantMessageEventStream();\n\tconst requestContextRules = buildCursorRequestContextRules(context.systemPrompt);\n\n\t(async () => {\n\t\tconst startTime = Date.now();\n\t\tlet firstTokenTime: number | undefined;\n\n\t\tconst output: AssistantMessage = {\n\t\t\trole: \"assistant\",\n\t\t\tcontent: [],\n", - "after": "\tcontext: Context,\n\toptions?: CursorOptions,\n): AssistantMessageEventStream => {\n\tconst stream = new AssistantMessageEventStream();\n\tconst requestContextRules = buildCursorRequestContextRules(context.systemPrompt);\n\n\trunAppStreamProducer(stream, async () => {\n\t\tmarkAppStreamProducerUnknown(stream, \"sdk_cursor_subtasks_unrepresented\");\n\t\tconst startTime = Date.now();\n\t\tlet firstTokenTime: number | undefined;\n\n\t\tconst output: AssistantMessage = {\n\t\t\trole: \"assistant\",\n\t\t\tcontent: [],\n" + "before": "\tconst requestContextRules = buildCursorRequestContextRules(context.systemPrompt);\n\n\t(async () => {\n\t\tconst startTime = Date.now();\n", + "after": "\tconst requestContextRules = buildCursorRequestContextRules(context.systemPrompt);\n\n\trunAppStreamProducer(stream, async () => {\n\t\tmarkAppStreamProducerUnknown(stream, \"sdk_cursor_subtasks_unrepresented\");\n\t\tconst startTime = Date.now();\n" }, { - "before": "\t\t\tif (h2Request && !h2Request.closed && !h2Request.destroyed) {\n\t\t\t\th2Request.end();\n\t\t\t}\n\t\t\th2Client?.close();\n\t\t\tproxiedSocket?.destroy();\n\t\t}\n\t})();\n\n\treturn stream;\n};\n\ntype ToolCallState = ToolCall & {\n\tindex: number;\n", - "after": "\t\t\tif (h2Request && !h2Request.closed && !h2Request.destroyed) {\n\t\t\t\th2Request.end();\n\t\t\t}\n\t\t\th2Client?.close();\n\t\t\tproxiedSocket?.destroy();\n\t\t}\n\t});\n\n\treturn stream;\n};\n\ntype ToolCallState = ToolCall & {\n\tindex: number;\n" + "before": "\t\t\tproxiedSocket?.destroy();\n\t\t}\n\t})();\n\n\treturn stream;\n};\n\ntype ToolCallState = ToolCall & {\n", + "after": "\t\t\tproxiedSocket?.destroy();\n\t\t}\n\t});\n\n\treturn stream;\n};\n\ntype ToolCallState = ToolCall & {\n" } - ], - "beforeSha256": "600ec9d4ae4cbd13e02e394938fb4c234cb5fbab38d0526c77df1fd86665de74", - "afterSha256": "3c8cbb82d7e210b126a627292e492ba010ffabe37f408b4dcfe017f2db0593ba" + ] }, { "package": "@gajae-code/ai", "path": "src/providers/google-gemini-cli.ts", + "beforeSha256": "bfc8ede6c1c39180145d668e8a1f7e5b3632df7589bed88a53b2fac8b9370281", + "afterSha256": "7a3a4f96bfe1b8f445b230a9eb098cf07c33b9cd832a6918eab3651b9c709312", "replacements": [ { "before": "/**\n * Google Gemini CLI / Antigravity provider.\n * Shared implementation for both google-gemini-cli and google-antigravity providers.\n * Uses the Cloud Code Assist API endpoint to access Gemini and Anthropic model models.\n */\nimport { createHash, randomBytes, randomUUID } from \"node:crypto\";\n", @@ -611,13 +641,13 @@ "before": "\t\t\t);\n\t\t\toutput.duration = Date.now() - startTime;\n\t\t\tif (firstTokenTime) output.ttft = firstTokenTime - startTime;\n\t\t\tstream.push({ type: \"error\", reason: output.stopReason, error: output });\n\t\t\tstream.end();\n\t\t}\n\t})();\n\n\treturn stream;\n};\n\nconst INT63_MASK = (1n << 63n) - 1n;\nconst ANTIGRAVITY_RANDOM_BOUND = 9_000_000_000_000_000_000n;\n", "after": "\t\t\t);\n\t\t\toutput.duration = Date.now() - startTime;\n\t\t\tif (firstTokenTime) output.ttft = firstTokenTime - startTime;\n\t\t\tstream.push({ type: \"error\", reason: output.stopReason, error: output });\n\t\t\tstream.end();\n\t\t}\n\t});\n\n\treturn stream;\n};\n\nconst INT63_MASK = (1n << 63n) - 1n;\nconst ANTIGRAVITY_RANDOM_BOUND = 9_000_000_000_000_000_000n;\n" } - ], - "beforeSha256": "a9341059e06c18341b448325452c9e7b7d0b1a5b837b73bcbb2f41d72a4bfd88", - "afterSha256": "0a6d88064e796dfb9890af1e88a5ea49b5844128c8a4900b4bc43f765693677b" + ] }, { "package": "@gajae-code/ai", "path": "src/providers/google-shared.ts", + "beforeSha256": "127b447c690b7964692a07844f1f8acfe02ebffebed8d5ad9fcdc25a822cecac", + "afterSha256": "a48c96e577ee46ac98d79d13717bf340fe087018a16ffeb35bce9671040da436", "replacements": [ { "before": "/**\n * Shared utilities for Google Generative AI and Google Cloud Code Assist providers.\n */\n\nimport { extractHttpStatusFromError, readJsonl, readSseJson } from \"@gajae-code/utils\";\nimport type { ProviderSafetyStopAdapterInvocation } from \"../adapter-internals/provider-safety-stop\";\n", @@ -631,13 +661,13 @@ "before": "\t\t\toutput.errorMessage = await finalizeErrorMessage(error, rawRequestDump);\n\t\t\toutput.duration = Date.now() - startTime;\n\t\t\tif (firstTokenTime) output.ttft = firstTokenTime - startTime;\n\t\t\tstream.push({ type: \"error\", reason: output.stopReason, error: output });\n\t\t\tstream.end();\n\t\t}\n\t})();\n\n\treturn stream;\n}\n\n/**\n * Lift the SDK's `params.config` fields out of `config` and place them where the\n", "after": "\t\t\toutput.errorMessage = await finalizeErrorMessage(error, rawRequestDump);\n\t\t\toutput.duration = Date.now() - startTime;\n\t\t\tif (firstTokenTime) output.ttft = firstTokenTime - startTime;\n\t\t\tstream.push({ type: \"error\", reason: output.stopReason, error: output });\n\t\t\tstream.end();\n\t\t}\n\t});\n\n\treturn stream;\n}\n\n/**\n * Lift the SDK's `params.config` fields out of `config` and place them where the\n" } - ], - "beforeSha256": "047262d15dc484dcc29cb97e4572a070e74f65ec42bd21ca81522d328081f164", - "afterSha256": "8348e5dfdc0771292695295738024f339219ffa12d121644ce5b756b49a7871d" + ] }, { "package": "@gajae-code/ai", "path": "src/providers/kiro-api-key.ts", + "beforeSha256": "81f1c81501512fbf072669e19b21e3bf2b31e924cd2e737084d25d4934dd83b3", + "afterSha256": "f4b7bf701fa82eafebbbcb43de2ff646276e3d598f3ed3806944e3b5b78490d6", "replacements": [ { "before": "/**\n * Kiro API-key (ksk_) transport.\n *\n * Headless Kiro Pro keys authenticate against the Kiro service root with\n * `tokentype: API_KEY` and `origin: AI_EDITOR`. This is distinct from the\n * AWS SSO OIDC / CodeWhisperer streaming path used by `gjc auth-broker login kiro`.\n", @@ -651,13 +681,13 @@ "before": "\t})().catch(() => {\n\t\ttry {\n\t\t\tstream.end();\n\t\t} catch {\n\t\t\t// ignore\n\t\t}\n\t});\n\treturn stream;\n};\n", "after": "\t})().catch(() => {\n\t\ttry {\n\t\t\tstream.end();\n\t\t} catch {\n\t\t\t// ignore\n\t\t}\n\t}));\n\treturn stream;\n};\n" } - ], - "beforeSha256": "991efd42b6b445a95786cc1632efe0d868489103685b31f8e7a27f58d225ac5d", - "afterSha256": "b687ff7b23bcd08640652dc910303d1534069569a162082be5e6604baba17d2e" + ] }, { "package": "@gajae-code/ai", "path": "src/providers/kiro-codewhisperer.ts", + "beforeSha256": "d6da5fa7ae81814350d76d1f4dffce9022da2500498636c27be37c910fc98a95", + "afterSha256": "0e17bd85373a43055b4870d9d4cf4aa1e4fb889a14f8f07c70b5026a52fab0e6", "replacements": [ { "before": "/**\n * Kiro / Amazon Q Developer / CodeWhisperer streaming transport.\n *\n * Talks directly to the CodeWhisperer streaming service over HTTPS using\n * a bearer token from AWS SSO OIDC. The response is an\n * `application/vnd.amazon.eventstream`, decoded by the shared\n", @@ -671,13 +701,13 @@ "before": "\t\t\toutput.errorMessage = baseMessage;\n\t\t\toutput.duration = Date.now() - startTime;\n\t\t\tif (firstTokenTime) output.ttft = firstTokenTime - startTime;\n\t\t\tstream.push({ type: \"error\", reason: output.stopReason, error: output });\n\t\t\tstream.end();\n\t\t}\n\t})();\n\n\treturn stream;\n};\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Request building\n", "after": "\t\t\toutput.errorMessage = baseMessage;\n\t\t\toutput.duration = Date.now() - startTime;\n\t\t\tif (firstTokenTime) output.ttft = firstTokenTime - startTime;\n\t\t\tstream.push({ type: \"error\", reason: output.stopReason, error: output });\n\t\t\tstream.end();\n\t\t}\n\t});\n\n\treturn stream;\n};\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Request building\n" } - ], - "beforeSha256": "c07fd910ef704d1542f561e1453fd99c0047ad3eac573022595f1ac3e97b1e25", - "afterSha256": "1769145787803a0da584b18ada0393364a576d764f7853e3cd05cf3f63be02fb" + ] }, { "package": "@gajae-code/ai", "path": "src/providers/ollama.ts", + "beforeSha256": "fdb7ab8c38fa623d425532d6c15159de6318c1fb872de4538f3c71fea8f623b5", + "afterSha256": "0085d9ed21a97b816065dde12ab7822eb5a4ee5b9ac11abcfdb971e11074acc4", "replacements": [ { "before": "import { extractHttpStatusFromError, fetchWithRetry } from \"@gajae-code/utils\";\nimport { getEnvApiKey } from \"../stream\";\nimport type {\n\tApi,\n\tAssistantMessage,\n\tContext,\n", @@ -691,13 +721,13 @@ "before": "\t\t\tif (firstTokenTime) {\n\t\t\t\toutput.ttft = firstTokenTime - startTime;\n\t\t\t}\n\t\t\tstream.push({ type: \"error\", reason: output.stopReason, error: output });\n\t\t\tstream.end();\n\t\t}\n\t})();\n\treturn stream;\n};\n", "after": "\t\t\tif (firstTokenTime) {\n\t\t\t\toutput.ttft = firstTokenTime - startTime;\n\t\t\t}\n\t\t\tstream.push({ type: \"error\", reason: output.stopReason, error: output });\n\t\t\tstream.end();\n\t\t}\n\t});\n\treturn stream;\n};\n" } - ], - "beforeSha256": "1c159fa9917eb12af4c23b9398ed09412271a96d0f4e3243479f278bd00e8d86", - "afterSha256": "d2b1272d91d46b92fd494605b78d0619c8f072b45e68ba8f2ef25cfc5aab7df0" + ] }, { "package": "@gajae-code/ai", "path": "src/providers/openai-completions.ts", + "beforeSha256": "267a7ce7fa2c5f355c386895b4cc018cc6552b263499246ecb29205d9ea38316", + "afterSha256": "f0908b97461a6ce53496a969b5a7f1198a8b21c521e135cb9c2b4ede2da12b1d", "replacements": [ { "before": "import { scheduler } from \"node:timers/promises\";\nimport { $credentialEnv, $env, extractHttpStatusFromError, logger } from \"@gajae-code/utils\";\nimport OpenAI, { APIConnectionTimeoutError } from \"openai\";\nimport type {\n\tChatCompletionAssistantMessageParam,\n\tChatCompletionChunk,\n", @@ -711,13 +741,13 @@ "before": "\t\t\t}\n\t\t\toutput.duration = Date.now() - startTime;\n\t\t\tif (firstTokenTime) output.ttft = firstTokenTime - startTime;\n\t\t\tstream.push({ type: \"error\", reason: output.stopReason, error: output });\n\t\t\tstream.end();\n\t\t}\n\t})();\n\n\treturn stream;\n};\n\nasync function createClient(\n\tmodel: Model<\"openai-completions\">,\n", "after": "\t\t\t}\n\t\t\toutput.duration = Date.now() - startTime;\n\t\t\tif (firstTokenTime) output.ttft = firstTokenTime - startTime;\n\t\t\tstream.push({ type: \"error\", reason: output.stopReason, error: output });\n\t\t\tstream.end();\n\t\t}\n\t});\n\n\treturn stream;\n};\n\nasync function createClient(\n\tmodel: Model<\"openai-completions\">,\n" } - ], - "beforeSha256": "f33c6065494a3143f70fee94cd91427bb8475f0f4fe55128b6e6d5b05d9f9604", - "afterSha256": "b0ba241c2645acf199dfcab65091d755286b06e5aea949d3eb9512fe25a74323" + ] }, { "package": "@gajae-code/ai", "path": "src/providers/openai-responses.ts", + "beforeSha256": "de60cfdc459d280588ab9c0c5e327ef069da70a4afa067fe234d19ecb28b263b", + "afterSha256": "9425ee939fcf612fe15e504d9a40e39569ed1092224c0317a56134c53b137df3", "replacements": [ { "before": "import { $credentialEnv, extractHttpStatusFromError, logger, structuredCloneJSON } from \"@gajae-code/utils\";\nimport OpenAI, { APIConnectionTimeoutError } from \"openai\";\nimport type {\n\tTool as OpenAITool,\n\tResponseCreateParamsStreaming,\n\tResponseInput,\n", @@ -731,13 +761,13 @@ "before": "\t\t\t}\n\t\t\toutput.duration = Date.now() - startTime;\n\t\t\tif (firstTokenTime) output.ttft = firstTokenTime - startTime;\n\t\t\tstream.push({ type: \"error\", reason: output.stopReason, error: output });\n\t\t\tstream.end();\n\t\t}\n\t})();\n\n\treturn stream;\n};\n\nfunction createClient(\n\tmodel: Model<\"openai-responses\">,\n", "after": "\t\t\t}\n\t\t\toutput.duration = Date.now() - startTime;\n\t\t\tif (firstTokenTime) output.ttft = firstTokenTime - startTime;\n\t\t\tstream.push({ type: \"error\", reason: output.stopReason, error: output });\n\t\t\tstream.end();\n\t\t}\n\t});\n\n\treturn stream;\n};\n\nfunction createClient(\n\tmodel: Model<\"openai-responses\">,\n" } - ], - "beforeSha256": "fc45debb2e3f89fe505407d0d90b5d33a9dc5ad9370c18be03275522a1a1a9c4", - "afterSha256": "bb6991c117a20f2e38ca7d018db3c781c74bbe2c0843b698ef38929929099d26" + ] }, { "package": "@gajae-code/ai", "path": "src/providers/openai-codex-responses.ts", + "beforeSha256": "67fc9c2370d73227ac49f3f0f3344ae77ce86007d42cd507dbf4b7366013a419", + "afterSha256": "1454476b45ccb12f99c33b069b3b675ed438f600c763e83b0324853fd6ee7244", "replacements": [ { "before": "import * as os from \"node:os\";\nimport { scheduler } from \"node:timers/promises\";\nimport {\n\t$env,\n\t$pickflag,\n\tasRecord,\n", @@ -751,13 +781,13 @@ "before": "\t\t\t\t\tstartTime,\n\t\t\t\t} satisfies CodexStreamProcessingContext);\n\t\t\tconst failure = await handleCodexStreamFailure(failureContext, error);\n\t\t\tstream.push({ type: \"error\", reason: failure.stopReason as \"error\" | \"aborted\", error: failure });\n\t\t\tstream.end();\n\t\t}\n\t})();\n\n\treturn stream;\n};\n\nexport async function prewarmOpenAICodexResponses(\n\tmodel: Model<\"openai-codex-responses\">,\n", "after": "\t\t\t\t\tstartTime,\n\t\t\t\t} satisfies CodexStreamProcessingContext);\n\t\t\tconst failure = await handleCodexStreamFailure(failureContext, error);\n\t\t\tstream.push({ type: \"error\", reason: failure.stopReason as \"error\" | \"aborted\", error: failure });\n\t\t\tstream.end();\n\t\t}\n\t});\n\n\treturn stream;\n};\n\nexport async function prewarmOpenAICodexResponses(\n\tmodel: Model<\"openai-codex-responses\">,\n" } - ], - "beforeSha256": "565d287edcd17dbbe067ffd8c094afb12c2794258960d29fb2e941866500b73a", - "afterSha256": "961ea9317a7852759c43555ef8a72901025cb17f6de2bfb86e72446197cc87eb" + ] }, { "package": "@gajae-code/ai", "path": "src/providers/openai-anthropic-shim.ts", + "beforeSha256": "185f653297144b10de41a27c96ae85b2fd73f62eb680cc12373c3bf60ebee4a8", + "afterSha256": "ac5bef1d48b06dbf714fb0afa908d3c57e437050e628d6d37ebf33ee06ea6adb", "replacements": [ { "before": "/**\n * Shared implementation for providers that expose BOTH an OpenAI-compatible\n * and an Anthropic-compatible API surface against the same model catalog\n * (currently Kimi Code and Synthetic).\n *\n * Each call site supplies the provider-specific bits (base URLs, default\n", @@ -775,13 +805,13 @@ "before": "\t\t\t\t\t\tstreamIdleTimeoutMs: options?.streamIdleTimeoutMs,\n\t\t\t\t\t\tstreamFirstEventTimeoutMs: options?.streamFirstEventTimeoutMs,\n\t\t\t\t\t\treasoning: reasoningEffort,\n\t\t\t\t\t}),\n\t\t\t\t);\n\n\t\t\t\tfor await (const event of innerStream) {\n\t\t\t\t\tstream.push(event);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tstream.push({\n\t\t\t\ttype: \"error\",\n\t\t\t\treason: \"error\",\n\t\t\t\terror: createProviderErrorMessage(model, err),\n\t\t\t});\n\t\t}\n\t})();\n\n\treturn stream;\n}\n", "after": "\t\t\t\t\t\tstreamIdleTimeoutMs: options?.streamIdleTimeoutMs,\n\t\t\t\t\t\tstreamFirstEventTimeoutMs: options?.streamFirstEventTimeoutMs,\n\t\t\t\t\t\treasoning: reasoningEffort,\n\t\t\t\t\t}),\n\t\t\t\t);\n\n\t\t\t\tadoptAppStreamProducer(stream, innerStream);\n\t\t\t\tfor await (const event of innerStream) {\n\t\t\t\t\tstream.push(event);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tstream.push({\n\t\t\t\ttype: \"error\",\n\t\t\t\treason: \"error\",\n\t\t\t\terror: createProviderErrorMessage(model, err),\n\t\t\t});\n\t\t}\n\t});\n\n\treturn stream;\n}\n" } - ], - "beforeSha256": "185f653297144b10de41a27c96ae85b2fd73f62eb680cc12373c3bf60ebee4a8", - "afterSha256": "ac5bef1d48b06dbf714fb0afa908d3c57e437050e628d6d37ebf33ee06ea6adb" + ] }, { "package": "@gajae-code/ai", "path": "src/providers/gitlab-duo.ts", + "beforeSha256": "a8982837c0f8f4b6c6e433db4b103b02215d9bc9423bf240094081b78a943a45", + "afterSha256": "9a742a461ebe03e0a13f55a756a32ac146518312ce21f5e64f022919b5d15f71", "replacements": [ { "before": "import { copyProviderSafetyStopAdapterInvocation } from \"../adapter-internals/provider-safety-stop\";\nimport { ANTHROPIC_THINKING, mapAnthropicToolChoice } from \"../stream\";\nimport type { Api, Context, FetchImpl, Model, SimpleStreamOptions } from \"../types\";\nimport { AssistantMessageEventStream } from \"../utils/event-stream\";\nimport { createProviderErrorMessage } from \"./error-message\";\nimport type { OpenAICompletionsOptions } from \"./openai-completions\";\n", @@ -795,13 +825,13 @@ "before": "\t\t\t\t\t\t\t\t\tfetch: options.fetch,\n\t\t\t\t\t\t\t\t\treasoning: reasoningEffort,\n\t\t\t\t\t\t\t\t\ttoolChoice: options.toolChoice,\n\t\t\t\t\t\t\t\t}) satisfies OpenAICompletionsOptions,\n\t\t\t\t\t\t\t);\n\n\t\t\tfor await (const event of inner) {\n\t\t\t\tstream.push(event);\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tstream.push({\n\t\t\t\ttype: \"error\",\n\t\t\t\treason: \"error\",\n\t\t\t\terror: createProviderErrorMessage(model, err),\n\t\t\t});\n\t\t}\n\t})();\n\n\treturn stream;\n}\n", "after": "\t\t\t\t\t\t\t\t\tfetch: options.fetch,\n\t\t\t\t\t\t\t\t\treasoning: reasoningEffort,\n\t\t\t\t\t\t\t\t\ttoolChoice: options.toolChoice,\n\t\t\t\t\t\t\t\t}) satisfies OpenAICompletionsOptions,\n\t\t\t\t\t\t\t);\n\n\t\t\tadoptAppStreamProducer(stream, inner);\n\t\t\tfor await (const event of inner) {\n\t\t\t\tstream.push(event);\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tstream.push({\n\t\t\t\ttype: \"error\",\n\t\t\t\treason: \"error\",\n\t\t\t\terror: createProviderErrorMessage(model, err),\n\t\t\t});\n\t\t}\n\t});\n\n\treturn stream;\n}\n" } - ], - "beforeSha256": "a8982837c0f8f4b6c6e433db4b103b02215d9bc9423bf240094081b78a943a45", - "afterSha256": "9a742a461ebe03e0a13f55a756a32ac146518312ce21f5e64f022919b5d15f71" + ] }, { "package": "@gajae-code/ai", "path": "src/providers/pi-native-client.ts", + "beforeSha256": "9ca2db0f86aa261a819d4c7cb49cb1aa5f21d5087e6e31eed5c5bf0f9499746d", + "afterSha256": "64be63baba7dfeac15bfa1a4779d1cf8d3be6c8f9ef14f92ca8ea0b28c829d49", "replacements": [ { "before": "/**\n * Client half of the pi-native auth-gateway protocol.\n *\n * Dispatches a {@link streamSimple}-shaped request to an `gjc auth-gateway`\n * via `POST /v1/pi/stream`, reads the SSE event stream back, and pushes the\n * parsed events into a local {@link AssistantMessageEventStream} — the same\n", @@ -815,17 +845,17 @@ "before": "\t\t\tstream.end();\n\t\t} catch (err) {\n\t\t\tstream.fail(err);\n\t\t} finally {\n\t\t\tif (signal) signal.removeEventListener(\"abort\", onAbort);\n\t\t}\n\t})();\n\n\treturn stream;\n}\n\nfunction makeSyntheticAssistant(model: Model): AssistantMessage {\n\treturn {\n", "after": "\t\t\tstream.end();\n\t\t} catch (err) {\n\t\t\tstream.fail(err);\n\t\t} finally {\n\t\t\tif (signal) signal.removeEventListener(\"abort\", onAbort);\n\t\t}\n\t});\n\n\treturn stream;\n}\n\nfunction makeSyntheticAssistant(model: Model): AssistantMessage {\n\treturn {\n" } - ], - "beforeSha256": "9ca2db0f86aa261a819d4c7cb49cb1aa5f21d5087e6e31eed5c5bf0f9499746d", - "afterSha256": "64be63baba7dfeac15bfa1a4779d1cf8d3be6c8f9ef14f92ca8ea0b28c829d49" + ] }, { "package": "@gajae-code/coding-agent", "path": "src/sdk/host/host.ts", + "beforeSha256": "19770ccbffe70489549d4a19954f0aab146d76b37cd525d7beea6cd61ffc42cb", + "afterSha256": "5e3602e9f1e204a589af69afe36d4368d7171babb34cb93596b5d0ea5ea616ef", "replacements": [ { - "before": "import { logger } from \"@gajae-code/utils\";\nimport { AUTOROUTING_INACTIVE_WARNING } from \"../../config/autorouting-contract\";\nimport { redactBrokerRuntimeCapabilities, redactObservedRequestContent } from \"./control/runtime-gate\";\nimport { type EventFrame, SessionEventStream } from \"./events\";\nimport { isAutoroutingInactive } from \"./internal-autorouting-state\";\nimport { type ProviderLease, ReverseLeaseError, ReverseLeaseRuntime } from \"./reverse-leases\";\nimport type { BrokerIndexWriter, HostEndpointAdapters, SdkFrame } from \"./types\";\n\nexport type SdkRequestObserver = (kind: \"control\" | \"query\", connectionId: string, frame: SdkFrame) => void;\n\n/**\n * When a session publishes its replayable readiness signal.\n *\n * `immediate` is the stock contract: `start()` publishes `session_ready` at\n * once, so a chat daemon that attaches (or replays late) surfaces the session\n * and creates its stock root. `deferred` prepares the session instead: the\n", - "after": "import { AsyncLocalStorage } from \"node:async_hooks\";\nimport { logger } from \"@gajae-code/utils\";\nimport { AUTOROUTING_INACTIVE_WARNING } from \"../../config/autorouting-contract\";\nimport { redactBrokerRuntimeCapabilities, redactObservedRequestContent } from \"./control/runtime-gate\";\nimport { type EventFrame, SessionEventStream } from \"./events\";\nimport { isAutoroutingInactive } from \"./internal-autorouting-state\";\nimport { type ProviderLease, ReverseLeaseError, ReverseLeaseRuntime } from \"./reverse-leases\";\nimport type { BrokerIndexWriter, HostEndpointAdapters, SdkFrame } from \"./types\";\n\nexport type SdkRequestObserver = (kind: \"control\" | \"query\", connectionId: string, frame: SdkFrame) => void;\n\nconst appHostContext = new AsyncLocalStorage<{ owner: AppSdkHostOwner; token: object }>();\n/** Session-local physical ownership, independent of bounded/logical SDK shutdown. */\nexport class AppSdkHostOwner {\n\treadonly #epoch = crypto.randomUUID();\n\t#revision = 0;\n\treadonly #tasks = new Set>();\n\treadonly #active = new Set();\n\treadonly #timers = new Map, () => void>();\n\treadonly #unknown = new Set();\n\tmarkUnknown(reason: string): void { if (!this.#unknown.has(reason)) { this.#unknown.add(reason); this.#revision++; } }\n\tgetAppLifecycleActivity() {\n\t\treturn { generation: `${this.#epoch}:${this.#revision}`, complete: this.#unknown.size === 0,\n\t\t\tstarting: 0, queued: this.#timers.size, running: this.#tasks.size, settling: 0, unknown: [...this.#unknown] };\n\t}\n\town(operation: () => T | PromiseLike): Promise {\n\t\tconst reservation = Promise.withResolvers(); const token = {};\n\t\tthis.#tasks.add(reservation.promise); this.#active.add(token); this.#revision++;\n\t\tconst finish = () => { this.#tasks.delete(reservation.promise); this.#active.delete(token); this.#revision++; reservation.resolve(); };\n\t\ttry { return Promise.resolve(appHostContext.run({ owner: this, token }, operation)).finally(finish); }\n\t\tcatch (error) { finish(); return Promise.reject(error); }\n\t}\n\tretain(task: PromiseLike): Promise { return this.own(() => task); }\n\tschedule(operation: () => void | Promise, delay: number): ReturnType {\n\t\tconst due = Promise.withResolvers();\n\t\tvoid this.own(() => due.promise);\n\t\tconst timer = setTimeout(() => {\n\t\t\tthis.#timers.delete(timer); this.#revision++;\n\t\t\tvoid this.own(async () => {\n\t\t\t\ttry { await operation(); }\n\t\t\t\tcatch { this.markUnknown(\"sdk_host_callback_failed\"); }\n\t\t\t\tfinally { due.resolve(); }\n\t\t\t});\n\t\t}, delay);\n\t\tthis.#timers.set(timer, due.resolve); this.#revision++;\n\t\treturn timer;\n\t}\n\tcancelTimer(timer: ReturnType): void {\n\t\tclearTimeout(timer); const release = this.#timers.get(timer);\n\t\tif (release) { this.#timers.delete(timer); this.#revision++; release(); }\n\t}\n\tasync awaitAppLifecycleSettlement(): Promise {\n\t\tconst context = appHostContext.getStore();\n\t\tif (context?.owner === this && this.#active.has(context.token)) {\n\t\t\tthis.markUnknown(\"sdk_host_reentrant_settlement\");\n\t\t\tthrow new Error(\"Cannot join SDK host ownership from its own callback\");\n\t\t}\n\t\twhile (this.#tasks.size) await Promise.allSettled([...this.#tasks]);\n\t}\n}\n\n/** Capture the initiating host across detached continuations, without process-global ownership. */\nexport function ownAppSdkHostTask(operation: () => T | PromiseLike): Promise {\n\tconst owner = appHostContext.getStore()?.owner;\n\tif (owner) return owner.own(operation);\n\ttry { return Promise.resolve(operation()); } catch (error) { return Promise.reject(error); }\n}\nexport function currentAppSdkHostOwner(): AppSdkHostOwner | undefined { return appHostContext.getStore()?.owner; }\n\n/**\n * When a session publishes its replayable readiness signal.\n *\n * `immediate` is the stock contract: `start()` publishes `session_ready` at\n * once, so a chat daemon that attaches (or replays late) surfaces the session\n * and creates its stock root. `deferred` prepares the session instead: the\n" + "before": "import { logger } from \"@gajae-code/utils\";\nimport { AUTOROUTING_INACTIVE_WARNING } from \"../../config/autorouting-contract\";\nimport { isFileLockAcquireTimeout } from \"../../config/file-lock\";\nimport { redactBrokerRuntimeCapabilities, redactObservedRequestContent } from \"./control/runtime-gate\";\nimport { type EventFrame, SessionEventStream } from \"./events\";\nimport { isAutoroutingInactive } from \"./internal-autorouting-state\";\nimport { type ProviderLease, ReverseLeaseError, ReverseLeaseRuntime } from \"./reverse-leases\";\nimport type { BrokerIndexWriter, HostEndpointAdapters, SdkFrame } from \"./types\";\n\nexport type SdkRequestObserver = (kind: \"control\" | \"query\", connectionId: string, frame: SdkFrame) => void;\n\n/**\n * When a session publishes its replayable readiness signal.\n *\n * `immediate` is the stock contract: `start()` publishes `session_ready` at\n * once, so a chat daemon that attaches (or replays late) surfaces the session\n * and creates its stock root. `deferred` prepares the session instead: the\n", + "after": "import { AsyncLocalStorage } from \"node:async_hooks\";\nimport { logger } from \"@gajae-code/utils\";\nimport { AUTOROUTING_INACTIVE_WARNING } from \"../../config/autorouting-contract\";\nimport { isFileLockAcquireTimeout } from \"../../config/file-lock\";\nimport { redactBrokerRuntimeCapabilities, redactObservedRequestContent } from \"./control/runtime-gate\";\nimport { type EventFrame, SessionEventStream } from \"./events\";\nimport { isAutoroutingInactive } from \"./internal-autorouting-state\";\nimport { type ProviderLease, ReverseLeaseError, ReverseLeaseRuntime } from \"./reverse-leases\";\nimport type { BrokerIndexWriter, HostEndpointAdapters, SdkFrame } from \"./types\";\n\nexport type SdkRequestObserver = (kind: \"control\" | \"query\", connectionId: string, frame: SdkFrame) => void;\n\nconst appHostContext = new AsyncLocalStorage<{ owner: AppSdkHostOwner; token: object }>();\n/** Session-local physical ownership, independent of bounded/logical SDK shutdown. */\nexport class AppSdkHostOwner {\n\treadonly #epoch = crypto.randomUUID();\n\t#revision = 0;\n\treadonly #tasks = new Set>();\n\treadonly #active = new Set();\n\treadonly #timers = new Map, () => void>();\n\treadonly #unknown = new Set();\n\tmarkUnknown(reason: string): void { if (!this.#unknown.has(reason)) { this.#unknown.add(reason); this.#revision++; } }\n\tgetAppLifecycleActivity() {\n\t\treturn { generation: `${this.#epoch}:${this.#revision}`, complete: this.#unknown.size === 0,\n\t\t\tstarting: 0, queued: this.#timers.size, running: this.#tasks.size, settling: 0, unknown: [...this.#unknown] };\n\t}\n\town(operation: () => T | PromiseLike): Promise {\n\t\tconst reservation = Promise.withResolvers(); const token = {};\n\t\tthis.#tasks.add(reservation.promise); this.#active.add(token); this.#revision++;\n\t\tconst finish = () => { this.#tasks.delete(reservation.promise); this.#active.delete(token); this.#revision++; reservation.resolve(); };\n\t\ttry { return Promise.resolve(appHostContext.run({ owner: this, token }, operation)).finally(finish); }\n\t\tcatch (error) { finish(); return Promise.reject(error); }\n\t}\n\tretain(task: PromiseLike): Promise { return this.own(() => task); }\n\tschedule(operation: () => void | Promise, delay: number): ReturnType {\n\t\tconst due = Promise.withResolvers();\n\t\tvoid this.own(() => due.promise);\n\t\tconst timer = setTimeout(() => {\n\t\t\tthis.#timers.delete(timer); this.#revision++;\n\t\t\tvoid this.own(async () => {\n\t\t\t\ttry { await operation(); }\n\t\t\t\tcatch { this.markUnknown(\"sdk_host_callback_failed\"); }\n\t\t\t\tfinally { due.resolve(); }\n\t\t\t});\n\t\t}, delay);\n\t\tthis.#timers.set(timer, due.resolve); this.#revision++;\n\t\treturn timer;\n\t}\n\tcancelTimer(timer: ReturnType): void {\n\t\tclearTimeout(timer); const release = this.#timers.get(timer);\n\t\tif (release) { this.#timers.delete(timer); this.#revision++; release(); }\n\t}\n\tasync awaitAppLifecycleSettlement(): Promise {\n\t\tconst context = appHostContext.getStore();\n\t\tif (context?.owner === this && this.#active.has(context.token)) {\n\t\t\tthis.markUnknown(\"sdk_host_reentrant_settlement\");\n\t\t\tthrow new Error(\"Cannot join SDK host ownership from its own callback\");\n\t\t}\n\t\twhile (this.#tasks.size) await Promise.allSettled([...this.#tasks]);\n\t}\n}\n\n/** Capture the initiating host across detached continuations, without process-global ownership. */\nexport function ownAppSdkHostTask(operation: () => T | PromiseLike): Promise {\n\tconst owner = appHostContext.getStore()?.owner;\n\tif (owner) return owner.own(operation);\n\ttry { return Promise.resolve(operation()); } catch (error) { return Promise.reject(error); }\n}\nexport function currentAppSdkHostOwner(): AppSdkHostOwner | undefined { return appHostContext.getStore()?.owner; }\n\n/**\n * When a session publishes its replayable readiness signal.\n *\n * `immediate` is the stock contract: `start()` publishes `session_ready` at\n * once, so a chat daemon that attaches (or replays late) surfaces the session\n * and creates its stock root. `deferred` prepares the session instead: the\n" }, { "before": "\t| \"authority_unavailable\";\n\n/** Proves that a prepared session may publish readiness at this exact generation. */\nexport type SessionActivationGate = (input: { sessionId: string; generation: number }) => boolean | Promise;\n\nexport interface SessionSdkHostOptions extends HostEndpointAdapters {\n\tcontrol?: (connectionId: string, frame: SdkFrame) => unknown | Promise;\n\tquery?: (connectionId: string, frame: SdkFrame) => unknown | Promise;\n\t/** Supplies the durable transcript revision stamped on ordinary emitted events. */\n\teventRevision?: () => number | undefined;\n\t/** Test/lifecycle seam invoked synchronously before fire-and-forget dispatch. */\n\tonFrameAdmitted?: (connectionId: string, frame: SdkFrame) => void;\n", @@ -850,26 +880,38 @@ { "before": "\t * at that same generation. Authority is re-proved after the gate resolves,\n\t * because a stop or an endpoint roll can land while it is in flight, and an\n\t * exact retry after a successful activation is answered `already` instead of\n\t * publishing a second readiness signal.\n\t */\n\tactivate(expectedGeneration?: number): Promise {\n\t\tconst attempt = this.#activation.then(\n\t\t\t() => this.#activateOnce(expectedGeneration),\n\t\t\t() => this.#activateOnce(expectedGeneration),\n\t\t);\n\t\tthis.#activation = attempt.then(\n\t\t\toutcome => outcome,\n\t\t\t() => \"authority_unavailable\" as const,\n\t\t);\n\t\treturn attempt;\n\t}\n", "after": "\t * at that same generation. Authority is re-proved after the gate resolves,\n\t * because a stop or an endpoint roll can land while it is in flight, and an\n\t * exact retry after a successful activation is answered `already` instead of\n\t * publishing a second readiness signal.\n\t */\n\tactivate(expectedGeneration?: number): Promise {\n\t\tconst attempt = this.appLifecycleOwner.own(() => this.#activation.then(\n\t\t\t() => this.#activateOnce(expectedGeneration),\n\t\t\t() => this.#activateOnce(expectedGeneration),\n\t\t));\n\t\tthis.#activation = attempt.then(\n\t\t\toutcome => outcome,\n\t\t\t() => \"authority_unavailable\" as const,\n\t\t);\n\t\treturn attempt;\n\t}\n" + }, + { + "before": "\t\tconst report = this.#activityReportTail.then(write, write);\n", + "after": "\t\tconst report = this.appLifecycleOwner.own(() => this.#activityReportTail.then(write, write));\n" } - ], - "beforeSha256": "038feef9348b0f56a2b92f2947e4d6e2b8cc80a918092e601b5103ed56be24fe", - "afterSha256": "12e497ea081ca73337288cd5ea84f85d8be141372547665bd355beb6628e19ae" + ] }, { "package": "@gajae-code/coding-agent", "path": "src/sdk/host/session-runtime.ts", + "beforeSha256": "c8a338c613fded28246b2b848ebbf73ad4622409e0d92f2247ed006ca175f21e", + "afterSha256": "e31529c513cee59932b9950be3edefd6705d9a3c640dd7d6042a8390b440b3be", "replacements": [ { - "before": "import { type ControlSurface, controlRequestFromFrame, dispatchControl, terminalAbortIdentity } from \"./control\";\nimport {\n\tBROKER_RUNTIME_ABORT_CAPABILITY_FIELD,\n\tBROKER_RUNTIME_CLOSE_CAPABILITY_FIELD,\n\thasBrokerRuntimeAbortCapability,\n} from \"./control/runtime-gate\";\nimport { SessionSdkHost, type SessionSdkHostOptions } from \"./host\";\nimport { clearAutoroutingInactive, isAutoroutingInactive, markAutoroutingInactive } from \"./internal-autorouting-state\";\nimport { CursorRegistry, QueryHandlers, RevisionStore, type SessionSurface } from \"./query\";\nimport { createSdkRunCapability } from \"./sdk-run-capability\";\nimport {\n\tcreateSdkCapabilities,\n\tcreateSdkSurfacePolicyForContext,\n", - "after": "import { type ControlSurface, controlRequestFromFrame, dispatchControl, terminalAbortIdentity } from \"./control\";\nimport {\n\tBROKER_RUNTIME_ABORT_CAPABILITY_FIELD,\n\tBROKER_RUNTIME_CLOSE_CAPABILITY_FIELD,\n\thasBrokerRuntimeAbortCapability,\n} from \"./control/runtime-gate\";\nimport { AppSdkHostOwner, ownAppSdkHostTask, SessionSdkHost, type SessionSdkHostOptions } from \"./host\";\nimport { clearAutoroutingInactive, isAutoroutingInactive, markAutoroutingInactive } from \"./internal-autorouting-state\";\nimport { CursorRegistry, QueryHandlers, RevisionStore, type SessionSurface } from \"./query\";\nimport { createSdkRunCapability } from \"./sdk-run-capability\";\nimport {\n\tcreateSdkCapabilities,\n\tcreateSdkSurfacePolicyForContext,\n" + "before": "import {\n\tSESSION_HOST_OBSERVER_CAPABILITY,\n\tSessionSdkHost,\n\ttype SessionSdkHostOptions,\n\tTURN_STREAM_CAPABILITY,\n} from \"./host\";\n", + "after": "import {\n\tAppSdkHostOwner,\n\townAppSdkHostTask,\n\tSESSION_HOST_OBSERVER_CAPABILITY,\n\tSessionSdkHost,\n\ttype SessionSdkHostOptions,\n\tTURN_STREAM_CAPABILITY,\n} from \"./host\";\n" + }, + { + "before": "export class SessionSdkSessionRuntime {\n\treadonly host: SessionSdkHost;\n", + "after": "export class SessionSdkSessionRuntime {\n\treadonly appLifecycleOwner: AppSdkHostOwner;\n\treadonly host: SessionSdkHost;\n" + }, + { + "before": "\tconstructor(options: SessionSdkRuntimeOptions) {\n\t\tthis.transport = options.transport;\n", + "after": "\tconstructor(options: SessionSdkRuntimeOptions) {\n\t\tthis.appLifecycleOwner = options.appLifecycleOwner ?? new AppSdkHostOwner();\n\t\tthis.transport = options.transport;\n" }, { - "before": " * Concrete transports (including the optional notification/native transport) are\n * injected by the caller. This module owns host construction, control/query\n * dispatch, replay/event publication, and reverse-provider lifecycle without\n * importing any notification adapter or native notification class.\n */\nexport class SessionSdkSessionRuntime {\n\treadonly host: SessionSdkHost;\n\treadonly transport: SessionSdkTransport;\n\treadonly #connectionDisposer?: () => void;\n\treadonly #malformedDisposer?: () => void;\n\treadonly #capabilitiesDisposer?: () => void;\n\t#transportStarted = false;\n\t#transportStartPromise?: Promise<{ url: string }>;\n\n\tconstructor(options: SessionSdkRuntimeOptions) {\n\t\tthis.transport = options.transport;\n\t\tconst capabilities = new Map>();\n\t\tthis.host = new SessionSdkHost({\n\t\t\t...options,\n\t\t\tconnectionCapabilities: options.connectionCapabilities ?? (connectionId => capabilities.get(connectionId)),\n\t\t\tsessionId: options.transport.sessionId,\n\t\t\tstateRoot: options.transport.stateRoot,\n\t\t\ttoken: options.transport.token,\n\t\t\tsendFrame: (connectionId, frame) => {\n\t\t\t\tconst result = options.transport.sendFrame(connectionId, frame);\n", - "after": " * Concrete transports (including the optional notification/native transport) are\n * injected by the caller. This module owns host construction, control/query\n * dispatch, replay/event publication, and reverse-provider lifecycle without\n * importing any notification adapter or native notification class.\n */\nexport class SessionSdkSessionRuntime {\n\treadonly appLifecycleOwner: AppSdkHostOwner;\n\treadonly host: SessionSdkHost;\n\treadonly transport: SessionSdkTransport;\n\treadonly #connectionDisposer?: () => void;\n\treadonly #malformedDisposer?: () => void;\n\treadonly #capabilitiesDisposer?: () => void;\n\t#transportStarted = false;\n\t#transportStartPromise?: Promise<{ url: string }>;\n\n\tconstructor(options: SessionSdkRuntimeOptions) {\n\t\tthis.appLifecycleOwner = options.appLifecycleOwner ?? new AppSdkHostOwner();\n\t\tthis.transport = options.transport;\n\t\tconst capabilities = new Map>();\n\t\tthis.host = new SessionSdkHost({\n\t\t\t...options,\n\t\t\tappLifecycleOwner: this.appLifecycleOwner,\n\t\t\tconnectionCapabilities: options.connectionCapabilities ?? (connectionId => capabilities.get(connectionId)),\n\t\t\tsessionId: options.transport.sessionId,\n\t\t\tstateRoot: options.transport.stateRoot,\n\t\t\ttoken: options.transport.token,\n\t\t\tsendFrame: (connectionId, frame) => {\n\t\t\t\tconst result = options.transport.sendFrame(connectionId, frame);\n" + "before": "\t\t\tconnectionCapabilities: this.#connectionCapabilitiesProvider,\n", + "after": "\t\t\tappLifecycleOwner: this.appLifecycleOwner,\n\t\t\tconnectionCapabilities: this.#connectionCapabilitiesProvider,\n" }, { - "before": "\t * asked for. Delivery failure is ignored: a disconnected consumer must never\n\t * disturb the turn producing the content.\n\t */\n\tsendFrameTo(connectionIds: Iterable, frame: SdkFrame): void {\n\t\tfor (const connectionId of connectionIds) {\n\t\t\ttry {\n\t\t\t\tconst result = this.transport.sendFrame(connectionId, frame);\n\t\t\t\tif (result instanceof Promise) result.catch(() => undefined);\n\t\t\t} catch {\n\t\t\t\t// A dead connection is reaped by the transport's own close handling.\n\t\t\t}\n\t\t}\n\t}\n\n", - "after": "\t * asked for. Delivery failure is ignored: a disconnected consumer must never\n\t * disturb the turn producing the content.\n\t */\n\tsendFrameTo(connectionIds: Iterable, frame: SdkFrame): void {\n\t\tfor (const connectionId of connectionIds) {\n\t\t\ttry {\n\t\t\t\tvoid this.appLifecycleOwner.own(() => this.transport.sendFrame(connectionId, frame)).catch(() => undefined);\n\t\t\t} catch {\n\t\t\t\t// A dead connection is reaped by the transport's own close handling.\n\t\t\t}\n\t\t}\n\t}\n\n" + "before": "\tsendFrameTo(connectionId: string, frame: SdkFrame): void {\n\t\ttry {\n\t\t\tconst result = this.transport.sendFrame(connectionId, frame);\n\t\t\tif (result instanceof Promise) result.catch(() => undefined);\n\t\t} catch {\n", + "after": "\tsendFrameTo(connectionId: string, frame: SdkFrame): void {\n\t\ttry {\n\t\t\tvoid this.appLifecycleOwner.own(() => this.transport.sendFrame(connectionId, frame)).catch(() => undefined);\n\t\t} catch {\n" }, { "before": "\t\t}\n\t\tif (hostError !== undefined) throw hostError;\n\t}\n\n\tasync registerWithBroker(writer: BrokerIndexWriter): Promise {\n\t\tawait this.host.registerWithBroker(writer);\n\t}\n}\n\n/** Narrow extension-facing factory for the SDK-only session path. */\nexport interface CreateSdkSessionRuntimeOptions {\n\t/** Authoritative broker state root for this session's endpoint lifecycle. */\n\tagentDir: string;\n\t/** Lifecycle-owned sessions require broker publication before they become usable. */\n\tbrokerRegistrationRequired?: boolean;\n\t/** Trusted broker-issued lifecycle marker bound to lifecycle host index events. */\n\tlifecycleRequestId?: string;\n", @@ -904,16 +946,16 @@ "after": "\t\ttypeof sdkRunToken === \"string\" && sdkRunToken.length > 0\n\t\t\t? lifecycleRunOwners.get(sdkRunToken)?.state\n\t\t\t: lifecycleStateForContext(ctx, type);\n\tconst removeRetiredLifecycleOwner = (owner: RuntimeState): void => {\n\t\tconst timer = retiredLifecycleOwnerTimers.get(owner);\n\t\tif (timer !== undefined) {\n\t\t\tappHost.cancelTimer(timer);\n\t\t\tretiredLifecycleOwnerTimers.delete(owner);\n\t\t}\n\t\tconst owners = retiredLifecycleOwners.get(owner.sessionId);\n\t\tif (!owners) {\n\t\t\tambiguousLifecycleIdentities.delete(owner.sessionIdentity);\n\t\t\treturn;\n" }, { - "before": "\tconst activePromptOwnerHolder: { connectionIds?: Set; lifecycleEpoch?: number } = {};\n\tlet nextLifecycleEpoch = 0;\n\tconst skillTerminalRecoveryKeys = new Set();\n\tconst trackLifecycle = (handler: () => Promise, owner: RuntimeState | undefined): Promise => {\n\t\tif (!owner) return Promise.resolve();\n\t\tlet task: Promise;\n\t\ttask = handler().finally(() => {\n\t\t\towner.lifecycleTasks.delete(task);\n\t\t\tmaybeRetireLifecycleOwner(owner);\n\t\t});\n\t\towner.lifecycleTasks.add(task);\n\t\treturn task;\n\t};\n\tconst emitLifecycle = async (\n\t\ttype: \"agent_start\" | \"agent_end\" | \"agent_failed\",\n", - "after": "\tconst activePromptOwnerHolder: { connectionIds?: Set; lifecycleEpoch?: number } = {};\n\tlet nextLifecycleEpoch = 0;\n\tconst skillTerminalRecoveryKeys = new Set();\n\tconst trackLifecycle = (handler: () => Promise, owner: RuntimeState | undefined): Promise => {\n\t\tif (!owner) return Promise.resolve();\n\t\tlet task: Promise;\n\t\ttask = appHost.own(async () => {\n\t\t\ttry { await handler(); }\n\t\t\tfinally { owner.lifecycleTasks.delete(task); maybeRetireLifecycleOwner(owner); }\n\t\t});\n\t\towner.lifecycleTasks.add(task);\n\t\treturn task;\n\t};\n\tconst emitLifecycle = async (\n\t\ttype: \"agent_start\" | \"agent_end\" | \"agent_failed\",\n" + "before": "\tconst trackLifecycle = (handler: () => Promise, owner: RuntimeState | undefined): Promise => {\n\t\tif (!owner) return Promise.resolve();\n\t\tlet task: Promise;\n\t\ttask = handler().finally(() => {\n\t\t\towner.lifecycleTasks.delete(task);\n\t\t\tmaybeRetireLifecycleOwner(owner);\n\t\t});\n", + "after": "\tconst trackLifecycle = (handler: () => Promise, owner: RuntimeState | undefined): Promise => {\n\t\tif (!owner) return Promise.resolve();\n\t\tlet task: Promise;\n\t\ttask = appHost.own(async () => {\n\t\t\ttry { await handler(); }\n\t\t\tfinally { owner.lifecycleTasks.delete(task); maybeRetireLifecycleOwner(owner); }\n\t\t});\n" }, { "before": "\t\t\t\t\t\tskillTerminalRecoveryKeys.delete(recoveryKey);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\treturn attempt();\n\t\t\t\t}\n\t\t\t};\n\t\t\tvoid attempt().finally(() => skillTerminalRecoveryControllers.delete(recoveryKey));\n\t\t};\n\t\tif (type === \"agent_end\" && isContinuingMidRunMaintenanceOutcome(maintenanceOutcome)) {\n\t\t\ttry {\n\t\t\t\tcurrent.runtime.emitEvent({ type, sessionId: ctx.sessionManager.getSessionId() });\n\t\t\t} catch {\n\t\t\t\t// Maintenance checkpoints are non-terminal lifecycle observations.\n", "after": "\t\t\t\t\t\tskillTerminalRecoveryKeys.delete(recoveryKey);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\treturn attempt();\n\t\t\t\t}\n\t\t\t};\n\t\t\tvoid appHost.own(async () => {\n\t\t\t\ttry { await attempt(); } finally { skillTerminalRecoveryControllers.delete(recoveryKey); }\n\t\t\t});\n\t\t};\n\t\tif (type === \"agent_end\" && isContinuingMidRunMaintenanceOutcome(maintenanceOutcome)) {\n\t\t\ttry {\n\t\t\t\tcurrent.runtime.emitEvent({ type, sessionId: ctx.sessionManager.getSessionId() });\n\t\t\t} catch {\n\t\t\t\t// Maintenance checkpoints are non-terminal lifecycle observations.\n" }, { - "before": "\t\tconst steerReconciliation = createKindAwareReconciliation({\n\t\t\tstore: reconciliationStore as never,\n\t\t\townedKinds: [\"steer\"],\n\t\t});\n\t\tawait steerReconciliation.hydrateFromStore();\n\t\tconst deadlineManager = new PromptDeadlineManager({\n\t\t\treconciliation,\n\t\t\tgetLeaseMs: () => {\n\t\t\t\tconst v = options.settings?.get(\"sdk.promptDeadlineMs\" as never) as number | undefined;\n\t\t\t\treturn typeof v === \"number\" && Number.isFinite(v) ? v : 1_800_000;\n\t\t\t},\n\t\t\tgetMaxMs: () => {\n", - "after": "\t\tconst steerReconciliation = createKindAwareReconciliation({\n\t\t\tstore: reconciliationStore as never,\n\t\t\townedKinds: [\"steer\"],\n\t\t});\n\t\tawait steerReconciliation.hydrateFromStore();\n\t\tconst deadlineManager = new PromptDeadlineManager({\n\t\t\tappLifecycleOwner: appHost,\n\t\t\treconciliation,\n\t\t\tgetLeaseMs: () => {\n\t\t\t\tconst v = options.settings?.get(\"sdk.promptDeadlineMs\" as never) as number | undefined;\n\t\t\t\treturn typeof v === \"number\" && Number.isFinite(v) ? v : 1_800_000;\n\t\t\t},\n\t\t\tgetMaxMs: () => {\n" + "before": "\t\tconst deadlineManager = new PromptDeadlineManager({\n\t\t\treconciliation,\n", + "after": "\t\tconst deadlineManager = new PromptDeadlineManager({\n\t\t\tappLifecycleOwner: appHost,\n\t\t\treconciliation,\n" }, { "before": "\t\t\t}>;\n\t\t}> = [];\n\t\tconst configRevision = { current: 0 };\n\t\tlet acceptingGateResolutions = true;\n\t\tconst inFlightGateResolutions = new Set>();\n\t\tconst trackGateResolution = (resolution: Promise): Promise => {\n\t\t\tconst tracked = resolution.finally(() => inFlightGateResolutions.delete(tracked));\n\t\t\tinFlightGateResolutions.add(tracked);\n\t\t\treturn tracked;\n\t\t};\n\t\tconst waitForGateResolutionQuiescence = async (): Promise => {\n\t\t\tconst settled = Promise.allSettled(inFlightGateResolutions);\n\t\t\tconst timeout = Bun.sleep(GATE_RESOLUTION_QUIESCENCE_MS).then(() => {\n\t\t\t\tlogger.warn(\"SDK workflow gate resolution drain timed out; proceeding with uncertain outcomes.\");\n\t\t\t});\n\t\t\tawait Promise.race([settled, timeout]);\n\t\t};\n\t\tlet runtime: SessionSdkSessionRuntime;\n\t\tconst surfaceFactory = createSdkSurfaceFactory({\n\t\t\tctx,\n\t\t\tid: sessionId,\n\t\t\tapi,\n", @@ -936,24 +978,40 @@ "after": "\t\t};\n\t\tconst removeProviderDefinitions = (capability: string): void => {\n\t\t\tif (capability === \"permission\") ctx.setSdkPermissionProvider?.(undefined);\n\t\t\tif (capability === \"fs\") ctx.setSdkClientBridge?.(undefined);\n\t\t};\n\t\truntime = new SessionSdkSessionRuntime({\n\t\t\tappLifecycleOwner: appHost,\n\t\t\ttransport,\n\t\t\teventRevision: () =>\n\t\t\t\ttypeof (ctx as Partial).getTranscript === \"function\"\n\t\t\t\t\t? ctx.getTranscript().length\n\t\t\t\t\t: undefined,\n\t\t\tmasterCapabilityVerify: frame =>\n" }, { - "before": "\t\t\tlifecycleActive: false,\n\t\t\tlifecycleEpoch: 0,\n\t\t\tfailureDiagnosticKeys: new Set(),\n\t\t\tfailureDiagnosticCodes: new Map(),\n\t\t\tlifecycleTasks: new Set(),\n\t\t};\n\t\tlifecycleOwnerHolder.state = runtimeOwner;\n\t\tactive = runtimeOwner;\n\t\ttry {\n\t\t\tpublishedEndpointUrl = (await runtime.start()).url;\n\t\t\tawait registerBroker();\n\t\t} catch (error) {\n", - "after": "\t\t\tlifecycleActive: false,\n\t\t\tlifecycleEpoch: 0,\n\t\t\tfailureDiagnosticKeys: new Set(),\n\t\t\tfailureDiagnosticCodes: new Map(),\n\t\t\tlifecycleTasks: new Set(),\n\t\t};\n\t\tappRuntimes.add(runtime);\n\t\tlifecycleOwnerHolder.state = runtimeOwner;\n\t\tactive = runtimeOwner;\n\t\ttry {\n\t\t\tpublishedEndpointUrl = (await runtime.start()).url;\n\t\t\tawait registerBroker();\n\t\t} catch (error) {\n" + "before": "\t\t\tlifecycleTasks: new Set(),\n\t\t};\n\t\t// Capture the SDK turn that owns a headless workflow gate before it is\n", + "after": "\t\t\tlifecycleTasks: new Set(),\n\t\t};\n\t\tappRuntimes.add(runtime);\n\t\t// Capture the SDK turn that owns a headless workflow gate before it is\n" + }, + { + "before": "\t\t\t\t\t\tconst retry = setTimeout(retryCleanup, LIFECYCLE_QUIESCENCE_MS);\n", + "after": "\t\t\t\t\t\tconst retry = appHost.schedule(retryCleanup, LIFECYCLE_QUIESCENCE_MS);\n" + }, + { + "before": "\t\t\t\tconst timer = setTimeout(retryCleanup, LIFECYCLE_QUIESCENCE_MS);\n", + "after": "\t\t\t\tconst timer = appHost.schedule(retryCleanup, LIFECYCLE_QUIESCENCE_MS);\n" + }, + { + "before": "\t\t\tif (active === undefined) active = current;\n\t\t\tlogger.error(\"sdk runtime stop failed\", { code: errorCode(error), error: String(error) });\n", + "after": "\t\t\tif (active === undefined) active = current;\n\t\t\tappHost.markUnknown(\"sdk_host_cleanup_unconfirmed\");\n\t\t\tlogger.error(\"sdk runtime stop failed\", { code: errorCode(error), error: String(error) });\n" + }, + { + "before": "\tapi.on(\"session_start\", async (_event, ctx) => {\n\t\tawait startRuntime(ctx);\n\t});\n\tapi.on(\"session_switch\", async (_event, ctx) => {\n\t\tawait stopActive();\n\t\tawait startRuntime(ctx);\n\t});\n\tapi.on(\"session_branch\", async (_event, ctx) => {\n\t\tawait stopActive();\n\t\tawait startRuntime(ctx);\n\t});\n\tapi.on(\"session_shutdown\", async event => {\n\t\tawait stopActive(true, event.reason);\n\t});\n}\n", + "after": "\tapi.on(\"session_start\", (_event, ctx) => appHost.own(async () => {\n\t\tawait startRuntime(ctx);\n\t}));\n\tapi.on(\"session_switch\", (_event, ctx) => appHost.own(async () => {\n\t\tawait stopActive();\n\t\tawait startRuntime(ctx);\n\t}));\n\tapi.on(\"session_branch\", (_event, ctx) => appHost.own(async () => {\n\t\tawait stopActive();\n\t\tawait startRuntime(ctx);\n\t}));\n\tapi.on(\"session_shutdown\", event => appHost.own(async () => {\n\t\tawait stopActive(true, event.reason);\n\t}));\n}\n" }, { - "before": "\t\t\t\t\t\tcurrent.pending.length > 0 ||\n\t\t\t\t\t\tcurrent.openLifecycleBatches.length > 0 ||\n\t\t\t\t\t\t(current.attachedInvocations?.length ?? 0) > 0 ||\n\t\t\t\t\t\t(current.drainedInvocations?.length ?? 0) > 0 ||\n\t\t\t\t\t\tcurrent.lifecycleTasks.size > 0\n\t\t\t\t\t) {\n\t\t\t\t\t\tconst retry = setTimeout(retryCleanup, LIFECYCLE_QUIESCENCE_MS);\n\t\t\t\t\t\tretry.unref();\n\t\t\t\t\t\tretiredLifecycleOwnerTimers.set(current, retry);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tremoveRetiredLifecycleOwner(current);\n\t\t\t\t};\n\t\t\t\tconst timer = setTimeout(retryCleanup, LIFECYCLE_QUIESCENCE_MS);\n\t\t\t\ttimer.unref();\n\t\t\t\tretiredLifecycleOwnerTimers.set(current, timer);\n\t\t\t} else current.deadlineManager.clearAll();\n\t\t\tcurrent.disposeGate?.();\n\t\t\tawait current.runtime.stop();\n\t\t} catch (error) {\n\t\t\t// Keep the immutable owner available for a retry when transport teardown\n\t\t\t// fails after quiescing. Clearing `active` before stop prevents a second\n\t\t\t// shutdown from retrying the failed endpoint removal.\n\t\t\tif (active === undefined) active = current;\n\t\t\tlogger.error(\"sdk runtime stop failed\", { code: errorCode(error), error: String(error) });\n\t\t\tthrow error;\n\t\t}\n\t\tcurrent.cursors.close();\n\t\tawait current.revisions.close();\n\t};\n\tapi.on(\"session_start\", async (_event, ctx) => {\n\t\tawait startRuntime(ctx);\n\t});\n\tapi.on(\"session_switch\", async (_event, ctx) => {\n\t\tawait stopActive();\n\t\tawait startRuntime(ctx);\n\t});\n\tapi.on(\"session_branch\", async (_event, ctx) => {\n\t\tawait stopActive();\n\t\tawait startRuntime(ctx);\n\t});\n\tapi.on(\"session_shutdown\", async () => {\n\t\tawait stopActive(true);\n\t});\n}\n", - "after": "\t\t\t\t\t\tcurrent.pending.length > 0 ||\n\t\t\t\t\t\tcurrent.openLifecycleBatches.length > 0 ||\n\t\t\t\t\t\t(current.attachedInvocations?.length ?? 0) > 0 ||\n\t\t\t\t\t\t(current.drainedInvocations?.length ?? 0) > 0 ||\n\t\t\t\t\t\tcurrent.lifecycleTasks.size > 0\n\t\t\t\t\t) {\n\t\t\t\t\t\tconst retry = appHost.schedule(retryCleanup, LIFECYCLE_QUIESCENCE_MS);\n\t\t\t\t\t\tretry.unref();\n\t\t\t\t\t\tretiredLifecycleOwnerTimers.set(current, retry);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tremoveRetiredLifecycleOwner(current);\n\t\t\t\t};\n\t\t\t\tconst timer = appHost.schedule(retryCleanup, LIFECYCLE_QUIESCENCE_MS);\n\t\t\t\ttimer.unref();\n\t\t\t\tretiredLifecycleOwnerTimers.set(current, timer);\n\t\t\t} else current.deadlineManager.clearAll();\n\t\t\tcurrent.disposeGate?.();\n\t\t\tawait current.runtime.stop();\n\t\t} catch (error) {\n\t\t\t// Keep the immutable owner available for a retry when transport teardown\n\t\t\t// fails after quiescing. Clearing `active` before stop prevents a second\n\t\t\t// shutdown from retrying the failed endpoint removal.\n\t\t\tif (active === undefined) active = current;\n\t\t\tappHost.markUnknown(\"sdk_host_cleanup_unconfirmed\");\n\t\t\tlogger.error(\"sdk runtime stop failed\", { code: errorCode(error), error: String(error) });\n\t\t\tthrow error;\n\t\t}\n\t\tcurrent.cursors.close();\n\t\tawait current.revisions.close();\n\t};\n\tapi.on(\"session_start\", (_event, ctx) => appHost.own(async () => {\n\t\tawait startRuntime(ctx);\n\t}));\n\tapi.on(\"session_switch\", (_event, ctx) => appHost.own(async () => {\n\t\tawait stopActive();\n\t\tawait startRuntime(ctx);\n\t}));\n\tapi.on(\"session_branch\", (_event, ctx) => appHost.own(async () => {\n\t\tawait stopActive();\n\t\tawait startRuntime(ctx);\n\t}));\n\tapi.on(\"session_shutdown\", () => appHost.own(async () => {\n\t\tawait stopActive(true);\n\t}));\n}\n" + "before": "\t\t\t\t() => void runBrokerRecovery(),\n", + "after": "\t\t\t\t() => void appHost.own(() => runBrokerRecovery()),\n" } - ], - "beforeSha256": "e396424d612ccf65e7057742e69b412b3ab974df9148e03d9892d54e6afe4859", - "afterSha256": "62ba771a376315285efb28ab50676a0ed2d0e1c64e637738bd04eed4e6430cff" + ] }, { "package": "@gajae-code/coding-agent", "path": "src/sdk/host/websocket-transport.ts", + "beforeSha256": "39de924a0a094c8ae67b33c2b6f9556b4f855a3fb9420ca024d4eeb4844e8255", + "afterSha256": "1ea4561acafca103d4db930b94dff4d9b3c6bf0b0a9e423374353e3b2809937e", "replacements": [ { - "before": "import { createHash, randomUUID } from \"node:crypto\";\nimport * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport { exactUnlink, type NativeExactFileIdentity } from \"@gajae-code/natives\";\nimport type { SessionSdkTransport } from \"./session-runtime\";\nimport type { SdkFrame } from \"./types\";\n\ntype SocketData = { connectionId: string };\ntype Socket = { readonly data: SocketData; send(message: string): void; close(): void; terminate?(): void };\n\nexport interface SdkWebSocketTransportDependencies {\n", - "after": "import { createHash, randomUUID } from \"node:crypto\";\nimport * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport { exactUnlink, type NativeExactFileIdentity } from \"@gajae-code/natives\";\nimport type { SessionSdkTransport } from \"./session-runtime\";\nimport { AppSdkHostOwner } from \"./host\";\nimport type { SdkFrame } from \"./types\";\n\ntype SocketData = { connectionId: string };\ntype Socket = { readonly data: SocketData; send(message: string): void; close(): void; terminate?(): void };\n\nexport interface SdkWebSocketTransportDependencies {\n" + "before": "import { createHash, randomUUID } from \"node:crypto\";\nimport * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport { exactUnlink, type NativeExactFileIdentity } from \"@gajae-code/natives\";\nimport {\n\tcanDeliverSdkEvent,\n\tSESSION_HOST_OBSERVER_CAPABILITY,\n\tTOOL_ACTIVITY_CAPABILITY,\n\tTURN_STREAM_CAPABILITY,\n} from \"./host\";\nimport type { SessionSdkTransport } from \"./session-runtime\";\nimport type { SdkFrame } from \"./types\";\n\ntype SocketData = { connectionId: string };\ntype Socket = { readonly data: SocketData; send(message: string): void; close(): void; terminate?(): void };\n\nexport interface SdkWebSocketTransportDependencies {\n", + "after": "import { createHash, randomUUID } from \"node:crypto\";\nimport * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport { exactUnlink, type NativeExactFileIdentity } from \"@gajae-code/natives\";\nimport {\n\tcanDeliverSdkEvent,\n\tSESSION_HOST_OBSERVER_CAPABILITY,\n\tTOOL_ACTIVITY_CAPABILITY,\n\tTURN_STREAM_CAPABILITY,\n} from \"./host\";\nimport type { SessionSdkTransport } from \"./session-runtime\";\nimport { AppSdkHostOwner } from \"./host\";\nimport type { SdkFrame } from \"./types\";\n\ntype SocketData = { connectionId: string };\ntype Socket = { readonly data: SocketData; send(message: string): void; close(): void; terminate?(): void };\n\nexport interface SdkWebSocketTransportDependencies {\n" }, { "before": " * remains an optional notification adapter; this transport keeps SDK hosting\n * available without loading that adapter or its native dependency.\n */\nexport async function createSdkWebSocketTransport(\n\tinput: { sessionId: string; stateRoot: string; token: string } & SdkWebSocketTransportDependencies,\n): Promise {\n\tconst filesystem = input.filesystem ?? fs;\n\tconst serve = input.serve ?? Bun.serve;\n\tlet frameHandler: ((connectionId: string, frame: SdkFrame) => void) | undefined;\n\tlet malformedHandler: ((connectionId: string, message: string) => void) | undefined;\n\tlet connectionCloseHandler: ((connectionId: string) => void) | undefined;\n\tlet capabilitiesHandler: ((connectionId: string, capabilities: readonly string[]) => void) | undefined;\n", @@ -967,13 +1025,13 @@ "before": "\tconst endpointUrl = (current: SdkServer): string => {\n\t\tconst url = new URL(current.url);\n\t\turl.protocol = url.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n\t\treturn url.toString();\n\t};\n\n\tconst transport: SessionSdkTransport = {\n\t\tsessionId: input.sessionId,\n\t\tstateRoot: input.stateRoot,\n\t\ttoken: input.token,\n\t\tonFrame(handler) {\n\t\t\tframeHandler = handler;\n\t\t\treturn () => {\n", "after": "\tconst endpointUrl = (current: SdkServer): string => {\n\t\tconst url = new URL(current.url);\n\t\turl.protocol = url.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n\t\treturn url.toString();\n\t};\n\n\tconst transport: SessionSdkTransport & { awaitAppLifecycleSettlement(): Promise } = {\n\t\tawaitAppLifecycleSettlement: async () => {\n\t\t\tawait appOwner.awaitAppLifecycleSettlement();\n\t\t\tif (appStopFailure !== undefined) throw appStopFailure;\n\t\t},\n\t\tsessionId: input.sessionId,\n\t\tstateRoot: input.stateRoot,\n\t\ttoken: input.token,\n\t\tonFrame(handler) {\n\t\t\tframeHandler = handler;\n\t\t\treturn () => {\n" } - ], - "beforeSha256": "de22d60c2697e74eeabbe1ce9a9900358f70c75a2c7ec7994a11e1e6662caeec", - "afterSha256": "33ec5a5eb9b7bcee8828205990be0843095f490a8ccecc3166ad94ef844d65a2" + ] }, { "package": "@gajae-code/coding-agent", "path": "src/sdk/prompt-deadline-manager.ts", + "beforeSha256": "c8cc2040ef26b45ef37066633c9bd42f98e68016ca10d4d8af7b63d5bfbbb8dd", + "afterSha256": "7a2c24a4e31423037ad060f3c5443f960de5e6ab4b6b07f3dd45330a5524317f", "replacements": [ { "before": "import type { KindAwareReconciliation } from \"./bus/kind-aware-reconciliation\";\nimport type { InvocationCorrelation, InvocationReconciliation } from \"./host/session-runtime\";\nimport {\n\tcreatePromptDeadlineLease,\n\tisAttributableProgressEventType,\n\ttype PromptDeadlineLease,\n\tpromptDeadlineAt,\n", @@ -984,8 +1042,20 @@ "after": "\nfunction leaseKey(correlation: InvocationCorrelation): string {\n\treturn `${correlation.commandId}:${correlation.turnId}`;\n}\n\nexport class PromptDeadlineManager {\n\treadonly #appOwner: AppSdkHostOwner;\n\treadonly #leases = new Map();\n\treadonly #correlations = new Map();\n\treadonly #timers = new Map>();\n\treadonly #reconciliation: DeadlineReconciliation;\n\treadonly #expiryRetries = new Map();\n\treadonly #uncertaintyRetries = new Map();\n" }, { - "before": "\treadonly #getLeaseMs: () => number;\n\treadonly #getMaxMs: () => number;\n\treadonly #now: () => number;\n\treadonly #onExpired?: (correlation: InvocationCorrelation) => void;\n\n\tconstructor(options: {\n\t\treconciliation: DeadlineReconciliation;\n\t\tgetLeaseMs: () => number;\n\t\tgetMaxMs: () => number;\n\t\tnow?: () => number;\n\t\tonExpired?: (correlation: InvocationCorrelation) => void;\n\t}) {\n\t\tthis.#reconciliation = options.reconciliation;\n\t\tthis.#getLeaseMs = options.getLeaseMs;\n\t\tthis.#getMaxMs = options.getMaxMs;\n\t\tthis.#now = options.now ?? Date.now;\n\t\tthis.#onExpired = options.onExpired;\n\t}\n\n\t#clearTimer(key: string): void {\n\t\tconst timer = this.#timers.get(key);\n\t\tif (timer !== undefined) {\n\t\t\tclearTimeout(timer);\n\t\t\tthis.#timers.delete(key);\n\t\t}\n\t}\n\n\t#schedule(key: string): void {\n\t\tconst lease = this.#leases.get(key);\n\t\tif (!lease) return;\n\t\tthis.#clearTimer(key);\n\t\tconst deadlineAt = promptDeadlineAt(lease);\n\t\tconst delayMs = Math.max(0, deadlineAt - this.#now());\n\t\tconst timer = setTimeout(() => {\n\t\t\tvoid this.#onDeadline(key);\n\t\t}, delayMs);\n\t\t// Allow process to exit without waiting for deadline timer.\n\t\t(timer as unknown as { unref?: () => void }).unref?.();\n\t\tthis.#timers.set(key, timer);\n\t}\n\n\tasync #onDeadline(key: string): Promise {\n", - "after": "\treadonly #getLeaseMs: () => number;\n\treadonly #getMaxMs: () => number;\n\treadonly #now: () => number;\n\treadonly #onExpired?: (correlation: InvocationCorrelation) => void;\n\n\tconstructor(options: {\n\t\tappLifecycleOwner?: AppSdkHostOwner;\n\t\treconciliation: DeadlineReconciliation;\n\t\tgetLeaseMs: () => number;\n\t\tgetMaxMs: () => number;\n\t\tnow?: () => number;\n\t\tonExpired?: (correlation: InvocationCorrelation) => void;\n\t}) {\n\t\tthis.#appOwner = options.appLifecycleOwner ?? currentAppSdkHostOwner() ?? new AppSdkHostOwner();\n\t\tthis.#reconciliation = options.reconciliation;\n\t\tthis.#getLeaseMs = options.getLeaseMs;\n\t\tthis.#getMaxMs = options.getMaxMs;\n\t\tthis.#now = options.now ?? Date.now;\n\t\tthis.#onExpired = options.onExpired;\n\t}\n\n\t#clearTimer(key: string): void {\n\t\tconst timer = this.#timers.get(key);\n\t\tif (timer !== undefined) {\n\t\t\tthis.#appOwner.cancelTimer(timer);\n\t\t\tthis.#timers.delete(key);\n\t\t}\n\t}\n\n\t#schedule(key: string): void {\n\t\tconst lease = this.#leases.get(key);\n\t\tif (!lease) return;\n\t\tthis.#clearTimer(key);\n\t\tconst deadlineAt = promptDeadlineAt(lease);\n\t\tconst delayMs = Math.max(0, deadlineAt - this.#now());\n\t\tconst timer = this.#appOwner.schedule(() => this.#onDeadline(key), delayMs);\n\t\t// Allow process to exit without waiting for deadline timer.\n\t\t(timer as unknown as { unref?: () => void }).unref?.();\n\t\tthis.#timers.set(key, timer);\n\t}\n\n\tasync #onDeadline(key: string): Promise {\n" + "before": "\tconstructor(options: {\n\t\treconciliation: DeadlineReconciliation;\n", + "after": "\tconstructor(options: {\n\t\tappLifecycleOwner?: AppSdkHostOwner;\n\t\treconciliation: DeadlineReconciliation;\n" + }, + { + "before": "\t}) {\n\t\tthis.#reconciliation = options.reconciliation;\n", + "after": "\t}) {\n\t\tthis.#appOwner = options.appLifecycleOwner ?? currentAppSdkHostOwner() ?? new AppSdkHostOwner();\n\t\tthis.#reconciliation = options.reconciliation;\n" + }, + { + "before": "\t\tif (timer !== undefined) {\n\t\t\tclearTimeout(timer);\n\t\t\tthis.#timers.delete(key);\n", + "after": "\t\tif (timer !== undefined) {\n\t\t\tthis.#appOwner.cancelTimer(timer);\n\t\t\tthis.#timers.delete(key);\n" + }, + { + "before": "\t\tconst timer = setTimeout(() => {\n\t\t\tvoid this.#onDeadline(key);\n\t\t}, delayMs);\n", + "after": "\t\tconst timer = this.#appOwner.schedule(() => this.#onDeadline(key), delayMs);\n" }, { "before": "\t\tif (attempts > MAX_EXPIRY_RETRIES) {\n\t\t\tconst correlation = this.#correlations.get(key);\n\t\t\tconst lease = this.#leases.get(key);\n\t\t\tif (correlation && lease) this.#recoverUncertainty(key, correlation, lease, lease.generation);\n\t\t\treturn;\n\t\t}\n\t\tconst timer = setTimeout(() => void this.#onDeadline(key), EXPIRY_RETRY_DELAY_MS);\n\t\t(timer as unknown as { unref?: () => void }).unref?.();\n\t\tthis.#timers.set(key, timer);\n\t}\n\n\t#recoverUncertainty(\n\t\tkey: string,\n", @@ -998,14 +1068,18 @@ { "before": "\t\t\t\t\t// write so accepted work is never left indefinitely unbounded (exact-\n\t\t\t\t\t// head review P1: parking without a timer strands the accepted row).\n\t\t\t\t\tthis.#uncertaintyRecoveryPending.add(key);\n\t\t\t\t\tconst live = this.#leases.get(key);\n\t\t\t\t\tif (live === lease) {\n\t\t\t\t\t\tthis.#clearTimer(key);\n\t\t\t\t\t\tconst recoveryTimer = setTimeout(\n\t\t\t\t\t\t\t() => this.#recoverUncertainty(key, correlation, live, live.generation),\n\t\t\t\t\t\t\tUNCERTAINTY_RETRY_DELAY_MS,\n\t\t\t\t\t\t);\n\t\t\t\t\t\t(recoveryTimer as unknown as { unref?: () => void }).unref?.();\n\t\t\t\t\t\tthis.#timers.set(key, recoveryTimer);\n\t\t\t\t\t}\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.#clearTimer(key);\n\t\t\t\tconst timer = setTimeout(\n\t\t\t\t\t() => this.#recoverUncertainty(key, correlation, lease, generation),\n\t\t\t\t\tUNCERTAINTY_RETRY_DELAY_MS,\n\t\t\t\t);\n\t\t\t\t(timer as unknown as { unref?: () => void }).unref?.();\n\t\t\t\tthis.#timers.set(key, timer);\n\t\t\t});\n\t}\n\n\tonAccepted(correlation: InvocationCorrelation): void {\n\t\tconst key = leaseKey(correlation);\n\t\tif (this.#leases.has(key)) return;\n\t\tconst now = this.#now();\n", "after": "\t\t\t\t\t// write so accepted work is never left indefinitely unbounded (exact-\n\t\t\t\t\t// head review P1: parking without a timer strands the accepted row).\n\t\t\t\t\tthis.#uncertaintyRecoveryPending.add(key);\n\t\t\t\t\tconst live = this.#leases.get(key);\n\t\t\t\t\tif (live === lease) {\n\t\t\t\t\t\tthis.#clearTimer(key);\n\t\t\t\t\t\tconst recoveryTimer = this.#appOwner.schedule(\n\t\t\t\t\t\t\t() => this.#recoverUncertainty(key, correlation, live, live.generation),\n\t\t\t\t\t\t\tUNCERTAINTY_RETRY_DELAY_MS,\n\t\t\t\t\t\t);\n\t\t\t\t\t\t(recoveryTimer as unknown as { unref?: () => void }).unref?.();\n\t\t\t\t\t\tthis.#timers.set(key, recoveryTimer);\n\t\t\t\t\t}\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.#clearTimer(key);\n\t\t\t\tconst timer = this.#appOwner.schedule(\n\t\t\t\t\t() => this.#recoverUncertainty(key, correlation, lease, generation),\n\t\t\t\t\tUNCERTAINTY_RETRY_DELAY_MS,\n\t\t\t\t);\n\t\t\t\t(timer as unknown as { unref?: () => void }).unref?.();\n\t\t\t\tthis.#timers.set(key, timer);\n\t\t\t}));\n\t}\n\n\tonAccepted(correlation: InvocationCorrelation): void {\n\t\tconst key = leaseKey(correlation);\n\t\tif (this.#leases.has(key)) return;\n\t\tconst now = this.#now();\n" + }, + { + "before": "\t\tawait runBoundedDeadlineFlush(\n\t\t\tsignal =>\n\t\t\t\thook(correlation, signal, () => {\n\t\t\t\t\tconst current = this.#leases.get(key);\n\t\t\t\t\treturn current === lease && current.generation === generation;\n\t\t\t\t}),\n\t\t\tthis.#deadlineFlushTimeoutMs,\n\t\t);\n", + "after": "\t\tawait runBoundedDeadlineFlush(\n\t\t\tsignal =>\n\t\t\t\tthis.#appOwner.own(() => hook(correlation, signal, () => {\n\t\t\t\t\tconst current = this.#leases.get(key);\n\t\t\t\t\treturn current === lease && current.generation === generation;\n\t\t\t\t})),\n\t\t\tthis.#deadlineFlushTimeoutMs,\n\t\t);\n" } - ], - "beforeSha256": "3acde77a41a96531cc73b92ece9a08419059012072a25ee92b505869900c8449", - "afterSha256": "6c5867ff16c5dfc7a550a0c3dfa67309a9ae234cf23253ab7739c55dd790e295" + ] }, { "package": "@gajae-code/coding-agent", "path": "src/sdk/host/query/revision-store.ts", + "beforeSha256": "038b977862ff7c977b7591155941f64484adb831021465b88a83121a5336eb45", + "afterSha256": "c3947cc6cd894bc93d9cdf29b486150b693be96b7bff5163c1c29c4f6c5d56d3", "replacements": [ { "before": "import { createHash } from \"node:crypto\";\nimport { chmod, mkdir, mkdtemp, open, readFile, rename, rm, unlink } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { postmortem } from \"@gajae-code/utils\";\n\n/**\n", @@ -1023,9 +1097,7 @@ "before": "\n\t#releaseChunk(chunk: string): void {\n\t\tconst refs = (this.#chunkRefs.get(chunk) ?? 1) - 1;\n\t\tif (refs > 0) this.#chunkRefs.set(chunk, refs);\n\t\telse {\n\t\t\tthis.#chunkRefs.delete(chunk);\n\t\t\tif (this.#directory) void unlink(join(this.#directory, \"objects\", chunk)).catch(() => undefined);\n\t\t}\n\t}\n\n\t#releaseManifest(manifest: string): void {\n\t\tconst refs = (this.#manifestRefs.get(manifest) ?? 1) - 1;\n\t\tif (refs > 0) this.#manifestRefs.set(manifest, refs);\n\t\telse {\n\t\t\tthis.#manifestRefs.delete(manifest);\n\t\t\tvoid unlink(manifest).catch(() => undefined);\n\t\t}\n\t}\n\n\tasync #discardUnreferenced(chunks: string[], manifest: string): Promise {\n\t\tawait Promise.all(\n\t\t\tchunks\n", "after": "\n\t#releaseChunk(chunk: string): void {\n\t\tconst refs = (this.#chunkRefs.get(chunk) ?? 1) - 1;\n\t\tif (refs > 0) this.#chunkRefs.set(chunk, refs);\n\t\telse {\n\t\t\tthis.#chunkRefs.delete(chunk);\n\t\t\tif (this.#directory) void this.#appUnlinks.own(() => unlink(join(this.#directory!, \"objects\", chunk))).catch(() => undefined);\n\t\t}\n\t}\n\n\t#releaseManifest(manifest: string): void {\n\t\tconst refs = (this.#manifestRefs.get(manifest) ?? 1) - 1;\n\t\tif (refs > 0) this.#manifestRefs.set(manifest, refs);\n\t\telse {\n\t\t\tthis.#manifestRefs.delete(manifest);\n\t\t\tvoid this.#appUnlinks.own(() => unlink(manifest)).catch(() => undefined);\n\t\t}\n\t}\n\n\tasync #discardUnreferenced(chunks: string[], manifest: string): Promise {\n\t\tawait Promise.all(\n\t\t\tchunks\n" } - ], - "beforeSha256": "00a5fe011ce57db76fda461274fcd1a6ee544251d1192b0835373cd4b328dbd9", - "afterSha256": "db47586d7c821a7c7d5aaf9b2ea6c1dacf2a5c83919babda8a22b4017d6c6e21" + ] } ] } diff --git a/patches/gjc-sdk-lifecycle/manifest.test.mjs b/patches/gjc-sdk-lifecycle/manifest.test.mjs index cbbd14eb..4c1645c6 100644 --- a/patches/gjc-sdk-lifecycle/manifest.test.mjs +++ b/patches/gjc-sdk-lifecycle/manifest.test.mjs @@ -44,7 +44,7 @@ async function fixture(t) { test('exact replace-once edits reproduce every full post-hash from published source', async () => { assert.deepEqual(Object.keys(manifest).sort(), ['files', 'id', 'packages', 'schemaVersion']); assert.equal(manifest.schemaVersion, 1); assert.equal(manifest.id, 'gjc-sdk-lifecycle-v1'); - assert.deepEqual(manifest.packages, { '@gajae-code/coding-agent': '0.16.4', '@gajae-code/agent-core': '0.16.4', '@gajae-code/ai': '0.16.4' }); + assert.deepEqual(manifest.packages, { '@gajae-code/coding-agent': '0.17.6', '@gajae-code/agent-core': '0.17.6', '@gajae-code/ai': '0.17.6' }); assert.equal(manifest.files.length, 32, 'exact reviewed producer/leaf/host source closure'); for (const file of manifest.files) { let source = await original(file); @@ -74,7 +74,7 @@ test('last-file tampering fails all-file prevalidation without partially patchin test('wrong package versions are rejected before any replacement', async (t) => { const root = await fixture(t); const name = '@gajae-code/agent-core'; - await fs.writeFile(path.join(root, 'node_modules', name, 'package.json'), JSON.stringify({ name, version: '0.16.6' })); + await fs.writeFile(path.join(root, 'node_modules', name, 'package.json'), JSON.stringify({ name, version: '0.17.5' })); await assert.rejects(applySdkLifecyclePatch(root, manifest), /version mismatch/); const first = manifest.files[0]; assert.equal(sha(await fs.readFile(path.join(root, 'node_modules', first.package, first.path))), first.beforeSha256); diff --git a/scripts/apply-sdk-lifecycle-patch.test.mjs b/scripts/apply-sdk-lifecycle-patch.test.mjs index 8257b171..8df94f90 100644 --- a/scripts/apply-sdk-lifecycle-patch.test.mjs +++ b/scripts/apply-sdk-lifecycle-patch.test.mjs @@ -17,8 +17,8 @@ async function fixture(t, files = ['one.ts']) { const pkg = '@gajae-code/coding-agent'; const packageRoot = path.join(root, 'node_modules', pkg); await fs.mkdir(path.join(packageRoot, 'src'), { recursive: true }); - await fs.writeFile(path.join(packageRoot, 'package.json'), JSON.stringify({ name: pkg, version: '0.16.4' })); - const manifest = { schemaVersion: 1, id: 'gjc-sdk-lifecycle-v1', packages: { [pkg]: '0.16.4' }, files: [] }; + await fs.writeFile(path.join(packageRoot, 'package.json'), JSON.stringify({ name: pkg, version: '0.17.6' })); + const manifest = { schemaVersion: 1, id: 'gjc-sdk-lifecycle-v1', packages: { [pkg]: '0.17.6' }, files: [] }; for (const name of files) { const before = `export const ${name.split('.')[0]} = 'unjoined';\n`; const after = before.replace('unjoined', 'joined'); @@ -63,7 +63,7 @@ test('all sources are validated before replacing any one file', async (t) => { test('version drift never patches a newly installed SDK even if source bytes happen to match', async (t) => { const f = await fixture(t); - await fs.writeFile(path.join(f.packageRoot, 'package.json'), JSON.stringify({ name: '@gajae-code/coding-agent', version: '0.16.6' })); + await fs.writeFile(path.join(f.packageRoot, 'package.json'), JSON.stringify({ name: '@gajae-code/coding-agent', version: '0.17.5' })); await assert.rejects(applySdkLifecyclePatch(f.root, f.manifest), /version mismatch/u); assert.equal(hash(await fs.readFile(f.source())), f.manifest.files[0].beforeSha256); }); @@ -79,7 +79,7 @@ test('malformed, traversing, duplicate and incorrect-result manifests are reject (m) => { m.files[0].replacements[0].before = 'not present'; }, (m) => { m.files[0].replacements[0].before = ''; }, (m) => { m.files[0].replacements[0].shell = 'no'; }, - (m) => { m.packages = { 'foreign-package': '0.16.4' }; }, + (m) => { m.packages = { 'foreign-package': '0.17.6' }; }, ]) { const manifest = structuredClone(f.manifest); mutate(manifest); await assert.rejects(applySdkLifecyclePatch(f.root, manifest)); diff --git a/scripts/release/LOCAL-RELEASE.md b/scripts/release/LOCAL-RELEASE.md index ad523a7b..c275c2a7 100644 --- a/scripts/release/LOCAL-RELEASE.md +++ b/scripts/release/LOCAL-RELEASE.md @@ -99,7 +99,7 @@ contents/passwords on argv, in release assets or in logs. The verifier accepts only the public-key file. No key generation, credential export, or production signing is implied by these instructions. -For the final SDK 0.16.4 candidate, `scripts/release/MACOS-ACCEPTANCE.md` +For the final SDK 0.17.6 candidate, `scripts/release/MACOS-ACCEPTANCE.md` provides the pinned source snapshot, isolated build paths, bounded local notarization, quarantined copy verification, and separate packaged smokes. diff --git a/scripts/release/MACOS-ACCEPTANCE.md b/scripts/release/MACOS-ACCEPTANCE.md index 5a9a2d01..b3f09ab1 100644 --- a/scripts/release/MACOS-ACCEPTANCE.md +++ b/scripts/release/MACOS-ACCEPTANCE.md @@ -7,7 +7,7 @@ draft, public release, workflow run, or export signing credentials. Run the blocks in order in the same **Bash** shell on the existing arm64 Mac. Keep the output directory until the acceptance record and artifacts are handed off. -The candidate below requires SDK **0.16.4** in +The candidate below requires SDK **0.17.6** in the selected source, installed dependencies, and packaged copy. A future SDK upgrade requires reviewing that expectation before using these commands. @@ -50,8 +50,8 @@ import { assertOutOfTree } from './scripts/release/out-of-tree.mjs'; await assertOutOfTree(process.argv[2], 'Candidate source'); const pkg = JSON.parse(await readFile('package.json', 'utf8')); const lock = JSON.parse(await readFile('package-lock.json', 'utf8')); -assert.equal(pkg.dependencies['@gajae-code/coding-agent'], '0.16.4'); -assert.equal(lock.packages['node_modules/@gajae-code/coding-agent'].version, '0.16.4'); +assert.equal(pkg.dependencies['@gajae-code/coding-agent'], '0.17.6'); +assert.equal(lock.packages['node_modules/@gajae-code/coding-agent'].version, '0.17.6'); NODE printf '%s\n' "$RELEASE_COMMIT" > "$RELEASE_ROOT/source-commit.txt" printf '%s\n' "$VERSION" > "$RELEASE_ROOT/package-version.txt" @@ -63,7 +63,7 @@ node scripts/release/check-signing-readiness.mjs --mode local \ > "$RELEASE_ROOT/signing-readiness.json" node scripts/release/prime-ripgrep-cache.mjs HUSKY=0 npm ci -test "$(node -p "require('./node_modules/@gajae-code/coding-agent/package.json').version")" = 0.16.4 +test "$(node -p "require('./node_modules/@gajae-code/coding-agent/package.json').version")" = 0.17.6 node scripts/fetch-bun.mjs test "$(dist-native/bun --version)" = 1.4.0 shasum -a 256 --check "$RELEASE_ROOT/source-lock.sha256" @@ -173,7 +173,7 @@ console.log(`Local DMG, quarantined-copy and updater verification passed (update NODE COPIED_APP="$RELEASE_ROOT/acceptance/copy/Gajae Code App.app" PAYLOAD="$COPIED_APP/Contents/Resources/resources/server-payload" -node -e 'const fs=require("node:fs"); const p=JSON.parse(fs.readFileSync(process.argv[1],"utf8")); if(p.version!=="0.16.4") throw new Error("Copied payload SDK is not 0.16.4");' \ +node -e 'const fs=require("node:fs"); const p=JSON.parse(fs.readFileSync(process.argv[1],"utf8")); if(p.version!=="0.17.6") throw new Error("Copied payload SDK is not 0.17.6");' \ "$PAYLOAD/node_modules/@gajae-code/coding-agent/package.json" test "$("$COPIED_APP/Contents/MacOS/gajae-app-server" --version)" = v22.22.2 test "$("$PAYLOAD/dist-native/bun" --version)" = 1.4.0 diff --git a/server/GJC-LIVE-SPEC.md b/server/GJC-LIVE-SPEC.md index 5183b7f1..dc71ad42 100644 --- a/server/GJC-LIVE-SPEC.md +++ b/server/GJC-LIVE-SPEC.md @@ -194,6 +194,18 @@ carries only the public snapshot (`delegationId`, `status`, `agent`, projection is what restores the same rows after a reload, per `docs/plans/delegation-lifecycle-contract.md` §10-11. +Delegated children never take a model override. Since SDK 0.17.6 the SDK's +`ExtensionToolWrapper` runs `validateToolArguments` on direct tool execution +as well, and that validation strips unknown keys of strict objects before +`execute` (the model-facing agent-loop path already did this on 0.16.4). A +`task` call that carries e.g. `model` is therefore not rejected: the key is +dropped, and the child is created and checked on exactly the parent's +provider/model and effort (`Child model or effort mismatch.` fails the child +closed before it is prompted). The app cannot intercept the raw arguments, +because the SDK's `customToolToDefinition` drops `rawArgumentValidation` and +`lenientArgValidation`. `server/gjc-delegation-executor.bun.test.ts` pins both +the stripped-override and the mismatch paths. + The codec rejects unknown fields, methods, unsafe identifiers, incompatible versions, invalid JSON values, mismatched responses, oversized or unterminated frames, and unknown response IDs. Pending requests fail when the worker exits. @@ -299,6 +311,23 @@ Which MCP servers reach a session is decided by scope, not by discovery `mcp.discoveryMode` is `false`; with user-scope tools already always-on, `search_tool_bm25` would only add a way to activate built-ins the app withheld. +- **Extension modules do not load.** SDK 0.17.6 turned extension-module + discovery back on in `createAgentSession`: `/extensions`, a + repository's `.gjc/extensions`, installed plugin extension entry points and + the `extensions` setting. The app blocks this deliberately. The trust + boundary is the same as the refused project MCP config: opening a repository + must not run its code in the worker. The adapter passes an empty + `preloadedExtensions` result (0.16.4's own default), with a fresh + `ExtensionRuntime` for each session. Only bundled extensions (Grok) and + inline app extensions load. `disableExtensionDiscovery` is not used, because + on 0.17.6 it also turns off hook-convention discovery. Native `.gjc` + pre/post hooks therefore keep loading exactly as on 0.16.4, including the + project scope, and a discovered hook still makes the session's lifecycle + receipt report `sdk_extension_effects_unrepresented`. + `server/gjc-sdk-contract.bun.test.ts` pins all of this against real files, + with a control run showing the same inputs load every module when the empty + result is absent. User-installed extension support would come later, as a + Settings opt-in. Settings > Automation > Withheld runtime features reports the project-scope refusal in these words; it offers no control because a session cannot change diff --git a/server/builtin-model-profiles-contract.bun.test.ts b/server/builtin-model-profiles-contract.bun.test.ts new file mode 100644 index 00000000..70086300 --- /dev/null +++ b/server/builtin-model-profiles-contract.bun.test.ts @@ -0,0 +1,49 @@ +import assert from 'node:assert/strict'; +import { test } from 'node:test'; + +import { BUILTIN_MODEL_PROFILES } from '@gajae-code/coding-agent/config/model-profiles'; + +import { GJC_BUILTIN_MODEL_PROFILES } from './modules/providers/list/gjc/gjc-builtin-model-profiles.js'; + +/* + * The Node sidecar cannot import the runtime's profile module (it pulls in + * Bun-only utilities), so the app keeps a copy for the preset picker. The copy + * drifted silently across SDK bumps: presets the runtime shipped were missing + * from the picker and existing presets advertised models the runtime no longer + * ran. A bun test because the runtime cannot be imported from node. + * + * Outside the `server/gjc-*` namespace for the same reason as + * tool-configs-contract: it asserts an application claim about the picker's + * copy, so it stays with the application if the engine moves out. + */ + +const ROLES = ['default', 'planner', 'executor', 'architect', 'critic'] as const; + +const sdkProfiles = BUILTIN_MODEL_PROFILES; + +test('the app carries every runtime built-in preset, in runtime order', () => { + assert.deepEqual( + GJC_BUILTIN_MODEL_PROFILES.map((profile) => profile.name), + sdkProfiles.map((profile) => profile.name), + ); +}); + +test('each preset role names the first model of the runtime chain', () => { + const copies = new Map(GJC_BUILTIN_MODEL_PROFILES.map((profile) => [profile.name, profile])); + for (const sdk of sdkProfiles) { + const expected: Record = {}; + for (const role of ROLES) { + const selector = sdk.modelMapping[role]; + if (selector === undefined) continue; + expected[role] = typeof selector === 'string' ? selector : selector[0]; + } + assert.deepEqual({ ...copies.get(sdk.name)?.roles }, expected, `roles of ${sdk.name}`); + } +}); + +test('every preset has an app-authored label and group', () => { + for (const profile of GJC_BUILTIN_MODEL_PROFILES) { + assert.ok(profile.label.trim(), `label of ${profile.name}`); + assert.ok(profile.group.trim(), `group of ${profile.name}`); + } +}); diff --git a/server/gjc-bun-sdk-adapter.ts b/server/gjc-bun-sdk-adapter.ts index c2c7ed9a..d0b7d3e3 100644 --- a/server/gjc-bun-sdk-adapter.ts +++ b/server/gjc-bun-sdk-adapter.ts @@ -19,6 +19,7 @@ import { buildAccountInventorySnapshot } from '@gajae-code/coding-agent/session/ import { SessionDisposalIncompleteError } from '@gajae-code/coding-agent/session/agent-session'; import { parseSessionEntries, SessionManager, type SessionEntry } from '@gajae-code/coding-agent/session/session-manager'; import { MemorySessionStorage } from '@gajae-code/coding-agent/session/session-storage'; +import { ExtensionRuntime } from '@gajae-code/coding-agent/extensibility/extensions/loader'; import { executeAcpBuiltinSlashCommand } from '@gajae-code/coding-agent/slash-commands/acp-builtins'; import { probeAsideCli, type AsideCliProbe } from '@gajae-code/coding-agent/slash-commands/helpers/aside'; import { initTheme, theme } from '@gajae-code/coding-agent/modes/theme/theme'; @@ -225,7 +226,7 @@ const FAILURE = 'GJC SDK configuration is invalid.'; async function disposeSdkSession(session: ActiveRun['session']): Promise { try { await session.dispose(); } catch (error) { - // SDK 0.16.4's public caller deadline does not end its teardown owner. + // SDK 0.17.6's public caller deadline does not end its teardown owner. // Join that exact session's retained promise while the adapter root stays // in settling. Real cleanup failures still poison the worker; an arbitrary // provider error with the same name is not permission to ignore failure. @@ -1294,6 +1295,14 @@ export class GjcBunSdkAdapter implements GjcWorkerRuntime { // factory otherwise defaults to ~/.gjc/agent - so skills, prompts and // credentials would be discovered somewhere the worker never wrote. ...(this.options.agentDir ? { agentDir: this.options.agentDir } : {}), + // SDK 0.17.6 re-enabled extension-module discovery (agent, project + // `.gjc/extensions`, plugin and settings modules). Opening a repository + // must not run its code in the worker - the same trust boundary as the + // refused project MCP config. This empty result is 0.16.4's own default, + // so only bundled extensions load and hook-convention discovery stays + // unchanged. `disableExtensionDiscovery` is deliberately not used: on + // 0.17.6 it also turns off hook discovery. One fresh runtime per session. + preloadedExtensions: { extensions: [], errors: [], runtime: new ExtensionRuntime() }, sessionManager, // The SDK defaults provider/cache identity to this manager's logical // ID, including on exact-ID resume. Supplying the same ID explicitly diff --git a/server/gjc-command-surface.generated.ts b/server/gjc-command-surface.generated.ts index 75e0145a..68dc95da 100644 --- a/server/gjc-command-surface.generated.ts +++ b/server/gjc-command-surface.generated.ts @@ -40,7 +40,7 @@ export const GJC_APP_BUILTIN_COMMANDS: readonly GjcAppCommand[] = [ { name: "star", description: "Star the current session for easier discovery" }, { name: "unstar", description: "Remove the star from the current session" }, { name: "memory", description: "Manage memory", inputHint: "" }, - { name: "rename", description: "Rename the current session", inputHint: "" }, + { name: "rename", description: "Rename the current session, or regenerate the title from the conversation", inputHint: "[title]" }, { name: "login", description: "Login with OAuth provider", inputHint: "[provider|redirect URL]" }, { name: "logout", description: "Logout from OAuth provider", inputHint: "[provider]" }, ]; diff --git a/server/gjc-delegation-executor.bun.test.ts b/server/gjc-delegation-executor.bun.test.ts index 300e0cae..62f427cd 100644 --- a/server/gjc-delegation-executor.bun.test.ts +++ b/server/gjc-delegation-executor.bun.test.ts @@ -71,6 +71,7 @@ async function fixture( configureChild?: (options: CreateAgentSessionOptions) => CreateAgentSessionOptions | Promise<CreateAgentSessionOptions>, storedCredentials = false, onDelegationSettled?: (update: GjcDelegationUpdate) => void, + adjustChild?: (result: Awaited<ReturnType<typeof createAgentSession>>) => Awaited<ReturnType<typeof createAgentSession>>, ) { const scratch = join(await realpath(process.cwd()), '.tmp'); await mkdir(scratch, { recursive: true }); @@ -155,7 +156,7 @@ async function fixture( childInputs.push(options!); const result = await createAgentSession(configureChild ? await configureChild(options!) : options); children.push(result.session); - return result; + return adjustChild ? adjustChild(result) : result; }, }); executors.push(executor); @@ -275,13 +276,58 @@ test('denied delegation, role widening and unsupported SDK controls fail before f.base.spawns = 'planner'; await assert.rejects(tool(f.parent, 'task', task()), /role is not allowed/); f.base.spawns = '*'; - await assert.rejects(tool(f.parent, 'task', { ...task(), model: 'other/model' })); await assert.rejects(tool(f.parent, 'subagent', { action: 'pause' })); assert.equal(f.childInputs.length, 0); assert.equal(f.calls.length, 0); } finally { await f.close(); } }); +// SDK 0.17.6 validates direct tool execution like the model-facing agent loop: +// unknown keys on strict objects are stripped before `execute`. A model override +// therefore cannot be refused by the app; it is ignored and the parent model forced. +test('an unknown model key on task is stripped and the child runs on the exact parent model', { timeout: 30_000 }, async () => { + const f = await fixture(); + try { + const parentModel = f.parent.model!; + const [started] = await tool(f.parent, 'task', { ...task(), model: 'other/model' }); + assert.ok(started && !Object.hasOwn(started, 'model'), 'the spawn receipt carries no model override'); + const [settled] = await tool(f.parent, 'subagent', { action: 'await', id: started.id }); + assert.equal(settled!.status, 'completed', JSON.stringify(settled)); + assert.equal(f.childInputs.length, 1); + const input = f.childInputs[0]!; + assert.equal(input.model?.provider, parentModel.provider); + assert.equal(input.model?.id, parentModel.id); + assert.equal(input.modelPattern, undefined); + assert.ok(!Object.values(input).some((value) => value === 'other/model'), 'the stripped key reaches no child option'); + assert.equal(f.children[0]!.model?.provider, parentModel.provider); + assert.equal(f.children[0]!.model?.id, parentModel.id); + assert.equal(f.calls.length, 1); + assert.equal(f.calls[0]!.model.provider, parentModel.provider); + assert.equal(f.calls[0]!.model.id, parentModel.id); + } finally { await f.close(); } +}); + +test('a child that does not run on the exact parent model fails closed before prompting', { timeout: 30_000 }, async () => { + const f = await fixture(undefined, undefined, undefined, false, undefined, (result) => ({ + ...result, + session: new Proxy(result.session, { + get(target, property) { + if (property === 'model') return { ...target.model!, id: 'other-model' }; + const value = Reflect.get(target, property, target); + return typeof value === 'function' ? value.bind(target) : value; + }, + }), + })); + try { + const [started] = await tool(f.parent, 'task', task()); + const [settled] = await tool(f.parent, 'subagent', { action: 'await', id: started!.id }); + assert.equal(settled!.status, 'failed', JSON.stringify(settled)); + assert.equal(f.childInputs.length, 1); + assert.equal(f.calls.length, 0, 'a mismatched child is never prompted'); + assert.equal(f.children[0]!.isDisposed, true); + } finally { await f.close(); } +}); + test('concurrency is bounded across the tree and parent disposal aborts every real SDK child', { timeout: 30_000 }, async () => { const began = deferred(); let count = 0; diff --git a/server/gjc-runtime-manifest.json b/server/gjc-runtime-manifest.json index 53c45311..99e46080 100644 --- a/server/gjc-runtime-manifest.json +++ b/server/gjc-runtime-manifest.json @@ -1,20 +1,20 @@ { "schemaVersion": 2, - "gjcSdk": "0.16.4", + "gjcSdk": "0.17.6", "bun": "1.4.0", - "natives": "0.16.4", + "natives": "0.17.6", "platforms": { "linux-x64": { "files": [ { "package": "@gajae-code/natives-linux-x64", "path": "native/pi_natives.linux-x64-baseline.node", - "sha256": "dcc7639b0edb4617c32bc2d322105e30126f5c5e7ae75d7f3d93d43776e2c267" + "sha256": "3c61c349832c597574ca5b82b9503d77202b2137fa2ee9fb9597a5c36b20fb54" }, { "package": "@gajae-code/natives-linux-x64", "path": "native/pi_natives.linux-x64-modern.node", - "sha256": "c75ddb2f46d63c3ad13f8a7a6daaa0ff49bcb3ab28c087e3917c7fe3f4eec2f2" + "sha256": "3d090b833bc90758b90dec85c39b5870b68c77433b4d3748f1ac2e6957f62eaf" }, { "package": "@gajae-code/natives", @@ -24,12 +24,12 @@ { "package": "@gajae-code/natives", "path": "native/index.js", - "sha256": "516e4316618c77843058bc7f1c1c723ce8d24aa0e1c90fbea64ed20ace71a863" + "sha256": "3b8bab4b08f871ed6f7fac4f8e115b44ae4e64fba53a65dad12b78296a99aa40" }, { "package": "@gajae-code/natives", "path": "native/loader-state.js", - "sha256": "7332a76de7195891429bf759c00737aef4f0b158b727c3b81cb920defe867e1f" + "sha256": "6330412b29b72ebbf8efc84ab5e3ffee84c3ecdd313ef3c61aa3b15be4402663" } ] }, @@ -38,7 +38,7 @@ { "package": "@gajae-code/natives-darwin-arm64", "path": "native/pi_natives.darwin-arm64.node", - "sha256": "6dc61753c3bbdb8d22900c41aa621f3bd1ada142fa5e3715c3199a92b65a79cf" + "sha256": "cf1a9d54e51487f5b8809c4269c6bc01976a2f208ca4edf766b26f7184a47fc8" }, { "package": "@gajae-code/natives", @@ -48,12 +48,12 @@ { "package": "@gajae-code/natives", "path": "native/index.js", - "sha256": "516e4316618c77843058bc7f1c1c723ce8d24aa0e1c90fbea64ed20ace71a863" + "sha256": "3b8bab4b08f871ed6f7fac4f8e115b44ae4e64fba53a65dad12b78296a99aa40" }, { "package": "@gajae-code/natives", "path": "native/loader-state.js", - "sha256": "7332a76de7195891429bf759c00737aef4f0b158b727c3b81cb920defe867e1f" + "sha256": "6330412b29b72ebbf8efc84ab5e3ffee84c3ecdd313ef3c61aa3b15be4402663" } ] }, @@ -62,7 +62,7 @@ { "package": "@gajae-code/natives-linux-arm64", "path": "native/pi_natives.linux-arm64.node", - "sha256": "c7c3ddb48afeccb9395dc97c50c3557ec879404f8b933df2f459c9aa8f36a3fd" + "sha256": "d5132dfaeb525ee9aa7c6c8ee5c1f86eb55baf5fef4231dc9be1f6aa05b26a2c" }, { "package": "@gajae-code/natives", @@ -72,12 +72,12 @@ { "package": "@gajae-code/natives", "path": "native/index.js", - "sha256": "516e4316618c77843058bc7f1c1c723ce8d24aa0e1c90fbea64ed20ace71a863" + "sha256": "3b8bab4b08f871ed6f7fac4f8e115b44ae4e64fba53a65dad12b78296a99aa40" }, { "package": "@gajae-code/natives", "path": "native/loader-state.js", - "sha256": "7332a76de7195891429bf759c00737aef4f0b158b727c3b81cb920defe867e1f" + "sha256": "6330412b29b72ebbf8efc84ab5e3ffee84c3ecdd313ef3c61aa3b15be4402663" } ] } @@ -85,20 +85,20 @@ "sdkLifecycle": { "id": "gjc-sdk-lifecycle-v1", "packages": { - "@gajae-code/coding-agent": "0.16.4", - "@gajae-code/agent-core": "0.16.4", - "@gajae-code/ai": "0.16.4" + "@gajae-code/coding-agent": "0.17.6", + "@gajae-code/agent-core": "0.17.6", + "@gajae-code/ai": "0.17.6" }, "files": [ { "package": "@gajae-code/coding-agent", "path": "src/sdk/session.ts", - "sha256": "6bc35899fa1386ab4887537eb0bdd068289291d588b0ce4cd1f1660d4075841c" + "sha256": "b4ff66a6c5a38ad3a998883b6efaebe9bb3893c781b5d856820898dd6caec864" }, { "package": "@gajae-code/coding-agent", "path": "src/session/agent-session.ts", - "sha256": "838658da725233a0628bf813fc4ca9ff7f060e056636cebcfe6eb03b9a1ecc29" + "sha256": "7cea2f60e01a9fcbc4c7631026bfcbf351023f30c650b9352a1a1e91c20295b5" }, { "package": "@gajae-code/agent-core", @@ -108,22 +108,22 @@ { "package": "@gajae-code/agent-core", "path": "src/agent-loop.ts", - "sha256": "efccd78060495147b0475aabb69fe10c5cefdabe7bf88b618b16c0ad49e6eab6" + "sha256": "73a34c415e6903f70ac23d922a4ee195a6aa335562d28e9f799a5a866d2fd388" }, { "package": "@gajae-code/coding-agent", "path": "src/config/model-registry.ts", - "sha256": "60529fec5306da95d08127b014ee7b13e83adf6a5e6ebdfff3df53010fff4f67" + "sha256": "c9735abb54e2493d4c741922a4771cb0f6ee00f6380ae05fff0c357848052b99" }, { "package": "@gajae-code/coding-agent", "path": "src/config/model-preset-registry.ts", - "sha256": "0a7f77c3d3a1cdf09db189d22bfefd4129de6adf3dc5cf72a18b6ef0a7b77835" + "sha256": "c5b58f5acdd48436d6fa63e34b704d8d177bc520a70b5de4bbfda8c0fee11df0" }, { "package": "@gajae-code/ai", "path": "src/auth-storage.ts", - "sha256": "a005e9f1d9d3dc515ae04668ab364aef497f7fbce093d12fac97182981240581" + "sha256": "dec11edcbad05b640034502ef032666ad2436643e0cb7e0a20358815f91f5328" }, { "package": "@gajae-code/coding-agent", @@ -138,77 +138,77 @@ { "package": "@gajae-code/ai", "path": "src/utils/idle-iterator.ts", - "sha256": "fbc6742abee2bcf20d22fa174ba7d21e688fde5cec13b32401fe36225ea90fd3" + "sha256": "78dc9774e9ce1d9ccfe78eb9f97fdcf88d375092c9839ee07ab3d9eaae830cb4" }, { "package": "@gajae-code/ai", "path": "src/stream.ts", - "sha256": "404db53024a885ef16e6749911964b228d6670d3796e00acfe245956dad28a22" + "sha256": "f488c3f848386daeaf3b701b6ed13dbf6feebd6efc0c6c633b601689c23dca15" }, { "package": "@gajae-code/ai", "path": "src/providers/register-builtins.ts", - "sha256": "abceac1f4e6e107da1f0bef71000ba4085508c1f56812127ecf6d556ee751a9d" + "sha256": "1b0df1d1df9180e637cf38a6198f29759e8b8ecb8ed0207e7d0d15cd98ead0c4" }, { "package": "@gajae-code/ai", "path": "src/providers/amazon-bedrock.ts", - "sha256": "972549a554509bc2c2e1f0723c197aadd339c10c8345c0b78420b8499821fd65" + "sha256": "0ee6014454a819e6fcb1c7d212dfa53c9acb84dda56088d1883bd436a0c0da64" }, { "package": "@gajae-code/ai", "path": "src/providers/anthropic.ts", - "sha256": "eee1c52fdd77f53dad676297560ca5722553088cf9888e7300e3d77507e4eefc" + "sha256": "ca73a63fe6c875d656deb6922702701afefc273ffd28475f9cffcef23851c325" }, { "package": "@gajae-code/ai", "path": "src/providers/azure-openai-responses.ts", - "sha256": "403196951817447052fbe2f0b059679923f64ce2f1322bcdc44cc1b0a3ff0075" + "sha256": "9ecb3efeb5c0c52d99128ba366f763d595d0ee2cf41ffa6a59fa991d10c2f330" }, { "package": "@gajae-code/ai", "path": "src/providers/cursor.ts", - "sha256": "3c8cbb82d7e210b126a627292e492ba010ffabe37f408b4dcfe017f2db0593ba" + "sha256": "a3ba24be86d584df06207ea534f894c436ca46e07184beb300211a4dad7c6192" }, { "package": "@gajae-code/ai", "path": "src/providers/google-gemini-cli.ts", - "sha256": "0a6d88064e796dfb9890af1e88a5ea49b5844128c8a4900b4bc43f765693677b" + "sha256": "7a3a4f96bfe1b8f445b230a9eb098cf07c33b9cd832a6918eab3651b9c709312" }, { "package": "@gajae-code/ai", "path": "src/providers/google-shared.ts", - "sha256": "8348e5dfdc0771292695295738024f339219ffa12d121644ce5b756b49a7871d" + "sha256": "a48c96e577ee46ac98d79d13717bf340fe087018a16ffeb35bce9671040da436" }, { "package": "@gajae-code/ai", "path": "src/providers/kiro-api-key.ts", - "sha256": "b687ff7b23bcd08640652dc910303d1534069569a162082be5e6604baba17d2e" + "sha256": "f4b7bf701fa82eafebbbcb43de2ff646276e3d598f3ed3806944e3b5b78490d6" }, { "package": "@gajae-code/ai", "path": "src/providers/kiro-codewhisperer.ts", - "sha256": "1769145787803a0da584b18ada0393364a576d764f7853e3cd05cf3f63be02fb" + "sha256": "0e17bd85373a43055b4870d9d4cf4aa1e4fb889a14f8f07c70b5026a52fab0e6" }, { "package": "@gajae-code/ai", "path": "src/providers/ollama.ts", - "sha256": "d2b1272d91d46b92fd494605b78d0619c8f072b45e68ba8f2ef25cfc5aab7df0" + "sha256": "0085d9ed21a97b816065dde12ab7822eb5a4ee5b9ac11abcfdb971e11074acc4" }, { "package": "@gajae-code/ai", "path": "src/providers/openai-completions.ts", - "sha256": "b0ba241c2645acf199dfcab65091d755286b06e5aea949d3eb9512fe25a74323" + "sha256": "f0908b97461a6ce53496a969b5a7f1198a8b21c521e135cb9c2b4ede2da12b1d" }, { "package": "@gajae-code/ai", "path": "src/providers/openai-responses.ts", - "sha256": "bb6991c117a20f2e38ca7d018db3c781c74bbe2c0843b698ef38929929099d26" + "sha256": "9425ee939fcf612fe15e504d9a40e39569ed1092224c0317a56134c53b137df3" }, { "package": "@gajae-code/ai", "path": "src/providers/openai-codex-responses.ts", - "sha256": "961ea9317a7852759c43555ef8a72901025cb17f6de2bfb86e72446197cc87eb" + "sha256": "1454476b45ccb12f99c33b069b3b675ed438f600c763e83b0324853fd6ee7244" }, { "package": "@gajae-code/ai", @@ -228,27 +228,27 @@ { "package": "@gajae-code/coding-agent", "path": "src/sdk/host/host.ts", - "sha256": "12e497ea081ca73337288cd5ea84f85d8be141372547665bd355beb6628e19ae" + "sha256": "5e3602e9f1e204a589af69afe36d4368d7171babb34cb93596b5d0ea5ea616ef" }, { "package": "@gajae-code/coding-agent", "path": "src/sdk/host/session-runtime.ts", - "sha256": "62ba771a376315285efb28ab50676a0ed2d0e1c64e637738bd04eed4e6430cff" + "sha256": "e31529c513cee59932b9950be3edefd6705d9a3c640dd7d6042a8390b440b3be" }, { "package": "@gajae-code/coding-agent", "path": "src/sdk/host/websocket-transport.ts", - "sha256": "33ec5a5eb9b7bcee8828205990be0843095f490a8ccecc3166ad94ef844d65a2" + "sha256": "1ea4561acafca103d4db930b94dff4d9b3c6bf0b0a9e423374353e3b2809937e" }, { "package": "@gajae-code/coding-agent", "path": "src/sdk/prompt-deadline-manager.ts", - "sha256": "6c5867ff16c5dfc7a550a0c3dfa67309a9ae234cf23253ab7739c55dd790e295" + "sha256": "7a2c24a4e31423037ad060f3c5443f960de5e6ab4b6b07f3dd45330a5524317f" }, { "package": "@gajae-code/coding-agent", "path": "src/sdk/host/query/revision-store.ts", - "sha256": "db47586d7c821a7c7d5aaf9b2ea6c1dacf2a5c83919babda8a22b4017d6c6e21" + "sha256": "c3947cc6cd894bc93d9cdf29b486150b693be96b7bff5163c1c29c4f6c5d56d3" } ] } diff --git a/server/gjc-sdk-contract.bun.test.ts b/server/gjc-sdk-contract.bun.test.ts index 9df5af60..ce925a21 100644 --- a/server/gjc-sdk-contract.bun.test.ts +++ b/server/gjc-sdk-contract.bun.test.ts @@ -14,6 +14,7 @@ import { Settings } from '@gajae-code/coding-agent/config/settings'; import { CURRENT_SESSION_VERSION, SessionManager } from '@gajae-code/coding-agent/session/session-manager'; import { SessionDisposalIncompleteError } from '@gajae-code/coding-agent/session/agent-session'; import { AsyncJobManager } from '@gajae-code/coding-agent/async/job-manager'; +import { reset as resetCapabilityCache } from '@gajae-code/coding-agent/capability'; import { registerCustomApi, unregisterCustomApis } from '@gajae-code/ai/api-registry'; import { AssistantMessageEventStream } from '@gajae-code/ai/utils/event-stream'; import type { AssistantMessage, Context } from '@gajae-code/ai/types'; @@ -1541,6 +1542,8 @@ test('goal inspection rejects malformed and ambiguous transcripts without mutati async function identityFixture(behavior: { realPrompts?: boolean; onCreated?: (session: Awaited<ReturnType<typeof createAgentSession>>['session']) => void; + /** Keep the adapter's own extension-discovery request instead of forcing it off. */ + adapterExtensionDiscovery?: boolean; } = {}) { const root = await mkdtemp(join(tmpdir(), 'gjc-sdk-identity-')); const cwd = join(root, 'project'); @@ -1587,7 +1590,7 @@ async function identityFixture(behavior: { enableMcpAutoload: false, enableLsp: false, skipPythonPreflight: true, - disableExtensionDiscovery: true, + ...(behavior.adapterExtensionDiscovery ? {} : { disableExtensionDiscovery: true }), skills: [], rules: [], contextFiles: [], promptTemplates: [], slashCommands: [], systemPrompt: ['Offline session identity contract.'], }; @@ -1639,6 +1642,83 @@ async function identityFixture(behavior: { }; } +// SDK 0.17.6 re-enabled extension-module discovery. App sessions keep 0.16.4's +// behavior: an empty preloaded result blocks agent, project, plugin and settings +// modules while hook-convention discovery (including project scope) still runs. +test('top-level sessions load no discovered extension modules but keep hook discovery and bundled Grok', async () => { + const loaded: string[] = []; + const marker = '__gjcExtensionDiscoveryContract'; + (globalThis as Record<string, unknown>)[marker] = loaded; + const f = await identityFixture({ adapterExtensionDiscovery: true }); + try { + const cwd = f.options.cwd; + const agentDir = join(f.root, 'agent'); + const module = (name: string) => + `export default function () { globalThis.${marker}.push(${JSON.stringify(name)}); }\n`; + const agentExtension = join(agentDir, 'extensions', 'agent-scope.ts'); + const projectExtension = join(cwd, '.gjc', 'extensions', 'project-scope.ts'); + const settingsExtension = join(f.root, 'settings-scope.ts'); + const projectHook = join(cwd, '.gjc', 'hooks', 'pre', 'bash.ts'); + for (const [file, name] of [[agentExtension, 'agent-extension'], [projectExtension, 'project-extension'], + [settingsExtension, 'settings-extension']] as const) { + await mkdir(join(file, '..'), { recursive: true }); + await writeFile(file, module(name)); + } + f.settings.override('extensions', [settingsExtension]); + // Discovery caches directory listings; the fixture created these trees after startup. + resetCapabilityCache(); + const seeded = [agentExtension, projectExtension, settingsExtension]; + type Activity = { getAppLifecycleActivity(): { unknown: string[] } }; + await f.run('extension-discovery', async (session) => { + const input = f.factoryOptions[0]!; + assert.equal(input.disableExtensionDiscovery, undefined, 'hook discovery must not be switched off'); + // The SDK appends bundled and inline extensions to the supplied (empty) result. + assert.ok(input.preloadedExtensions, 'the adapter supplies its own extension result'); + assert.ok(!input.preloadedExtensions.extensions.some((extension: { path: string }) => seeded.includes(extension.path))); + const paths = session.extensionRunner!.getExtensionPaths(); + for (const file of seeded) assert.ok(!paths.includes(file), `${file} must not load`); + assert.ok(paths.includes('bundled:grok-build'), `bundled Grok must load: ${JSON.stringify(paths)}`); + const { unknown } = (session as unknown as Activity).getAppLifecycleActivity(); + assert.ok(!unknown.includes('sdk_extension_effects_unrepresented'), JSON.stringify(unknown)); + }); + assert.deepEqual(loaded, []); + + // Hook-convention discovery still runs for the project scope, exactly as on + // 0.16.4, and a discovered hook keeps the lifecycle receipt fail-closed. + await mkdir(join(projectHook, '..'), { recursive: true }); + await writeFile(projectHook, module('project-hook')); + resetCapabilityCache(); + await f.run('extension-discovery-hook', async (session) => { + const paths = session.extensionRunner!.getExtensionPaths(); + assert.ok(paths.includes(`hook:${await realpath(projectHook)}`) || paths.includes(`hook:${projectHook}`), + `project hook must load: ${JSON.stringify(paths)}`); + for (const file of seeded) assert.ok(!paths.includes(file), `${file} must not load`); + assert.ok(paths.includes('bundled:grok-build')); + const { unknown } = (session as unknown as Activity).getAppLifecycleActivity(); + assert.ok(unknown.includes('sdk_extension_effects_unrepresented'), JSON.stringify(unknown)); + }); + assert.deepEqual(loaded, ['project-hook']); + const first = f.factoryOptions[0]!; + assert.equal(f.factoryOptions.length, 2); + assert.notEqual(f.factoryOptions[1]!.preloadedExtensions!.runtime, first.preloadedExtensions!.runtime, + 'each session receives its own extension runtime'); + + // Control: the same inputs without the app's empty result do load every seeded module, + // so the assertions above are not vacuous. + loaded.length = 0; + const { preloadedExtensions: _omitted, ...raw } = first; + const control = await createAgentSession({ ...raw, sessionManager: SessionManager.create(cwd, join(f.root, 'control')) }); + try { + const paths = control.session.extensionRunner!.getExtensionPaths(); + for (const file of seeded) assert.ok(paths.includes(file), `control must load ${file}: ${JSON.stringify(paths)}`); + assert.deepEqual([...loaded].sort(), ['agent-extension', 'project-extension', 'project-hook', 'settings-extension']); + } finally { await control.session.dispose(); } + } finally { + delete (globalThis as Record<string, unknown>)[marker]; + await f.close(); + } +}); + test('goal-capable production sessions delegate safely and defer worktree abort to their owner', async () => { const f = await identityFixture(); Object.assign(f.options, { toolNames: ['read', 'task', 'subagent'], spawns: '*', goalUiVersion: 1, goalOwner: 'number:1' }); diff --git a/server/modules/providers/list/gjc/gjc-builtin-model-profiles.ts b/server/modules/providers/list/gjc/gjc-builtin-model-profiles.ts index 0ec49f6a..68e604b5 100644 --- a/server/modules/providers/list/gjc/gjc-builtin-model-profiles.ts +++ b/server/modules/providers/list/gjc/gjc-builtin-model-profiles.ts @@ -8,13 +8,15 @@ export type GjcBuiltinModelProfile = { }; /** - * GJC 0.13.1 built-in model profiles. + * Built-in model profiles of the pinned @gajae-code/coding-agent (0.17.6). * - * The upstream runtime module currently imports Bun-only utilities, so the - * Node sidecar cannot load it directly. This catalog is generated from the - * exact @gajae-code/coding-agent dependency used by the app (regenerate by - * dumping BUILTIN_MODEL_PROFILES with dist-native/bun after an SDK bump); + * The upstream runtime module imports Bun-only utilities, so the Node sidecar + * cannot load it directly and keeps this copy. Names, order and roles mirror + * the SDK's BUILTIN_MODEL_PROFILES; each role holds the first selector of the + * SDK's fallback chain, which is the model the preset runs when available. * label and group are app-authored display strings the SDK does not carry. + * server/builtin-model-profiles-contract.bun.test.ts fails when an SDK bump moves + * the SDK away from this copy. */ export const GJC_BUILTIN_MODEL_PROFILES: readonly GjcBuiltinModelProfile[] = [ { @@ -65,6 +67,102 @@ export const GJC_BUILTIN_MODEL_PROFILES: readonly GjcBuiltinModelProfile[] = [ "critic": "openai-codex/gpt-5.6-luna:max" } }, + { + "name": "astra-lite", + "label": "Astra Lite", + "group": "CODEX", + "roles": { + "default": "openai-codex/gpt-6-astra:medium", + "planner": "openai-codex/gpt-6-astra:xhigh", + "executor": "openai-codex/gpt-5.6-luna:xhigh", + "architect": "openai-codex/gpt-5.6-terra:xhigh", + "critic": "openai-codex/gpt-5.6-sol:high" + } + }, + { + "name": "astra-default", + "label": "Astra Default", + "group": "CODEX", + "roles": { + "default": "openai-codex/gpt-6-astra:medium", + "planner": "openai-codex/gpt-6-astra:xhigh", + "executor": "openai-codex/gpt-5.6-luna:max", + "architect": "openai-codex/gpt-5.6-terra:xhigh", + "critic": "openai-codex/gpt-5.6-sol:xhigh" + } + }, + { + "name": "astra-heavy", + "label": "Astra Heavy", + "group": "CODEX", + "roles": { + "default": "openai-codex/gpt-6-astra:medium", + "planner": "openai-codex/gpt-5.6-sol:max", + "executor": "openai-codex/gpt-5.6-sol:max", + "architect": "openai-codex/gpt-6-astra:max", + "critic": "openai-codex/gpt-6-astra:max" + } + }, + { + "name": "macos-omlx-fast", + "label": "macOS oMLX Fast", + "group": "OMLX", + "roles": { + "default": "omlx/Qwen3.6-35B-A3B-4bit:low", + "planner": "omlx/Qwen3.6-35B-A3B-4bit:medium", + "executor": "omlx/Qwen3.6-35B-A3B-4bit:low", + "architect": "omlx/Qwen3.6-35B-A3B-4bit:high", + "critic": "omlx/Qwen3.6-35B-A3B-4bit:high" + } + }, + { + "name": "macos-omlx-balanced", + "label": "macOS oMLX Balanced", + "group": "OMLX", + "roles": { + "default": "omlx/Qwen3.6-35B-A3B-8bit:low", + "planner": "omlx/Qwen3.6-35B-A3B-8bit:medium", + "executor": "omlx/Qwen3.6-35B-A3B-8bit:low", + "architect": "omlx/Qwen3.6-35B-A3B-8bit:high", + "critic": "omlx/Qwen3.6-35B-A3B-8bit:high" + } + }, + { + "name": "macos-omlx-quality", + "label": "macOS oMLX Quality", + "group": "OMLX", + "roles": { + "default": "omlx/Qwen3.6-35B-A3B-8bit:low", + "planner": "omlx/Qwen3.6-35B-A3B-8bit:medium", + "executor": "omlx/Qwen3.6-35B-A3B-8bit:low", + "architect": "omlx/Qwen3.6-35B-A3B-8bit:high", + "critic": "omlx/Qwen3.8-27B-8bit:high" + } + }, + { + "name": "macos-omlx-abliterated-fast", + "label": "macOS oMLX Abliterated Fast", + "group": "OMLX", + "roles": { + "default": "omlx/Qwen3.8-27B-Uncensored-MLX-4bit:low", + "planner": "omlx/Qwen3.8-27B-Uncensored-MLX-4bit:medium", + "executor": "omlx/Qwen3.8-27B-Uncensored-MLX-4bit:low", + "architect": "omlx/Qwen3.8-27B-Uncensored-MLX-4bit:high", + "critic": "omlx/Qwen3.8-27B-Uncensored-MLX-4bit:high" + } + }, + { + "name": "macos-omlx-abliterated-balanced", + "label": "macOS oMLX Abliterated Balanced", + "group": "OMLX", + "roles": { + "default": "omlx/Qwen3.8-27B-Uncensored-MLX-4bit:low", + "planner": "omlx/Qwen3.8-27B-Uncensored-MLX-4bit:medium", + "executor": "omlx/Qwen3.8-27B-Uncensored-MLX-4bit:low", + "architect": "omlx/Qwen3.8-27B-Uncensored-MLX-4bit:high", + "critic": "omlx/Qwen3.8-27B-Uncensored-MLX-4bit:high" + } + }, { "name": "opencodego", "label": "OpenCodeGo", @@ -77,6 +175,18 @@ export const GJC_BUILTIN_MODEL_PROFILES: readonly GjcBuiltinModelProfile[] = [ "critic": "opencode-go/mimo-v2.5-pro" } }, + { + "name": "commandcode-goat", + "label": "Command Code GOAT", + "group": "COMMAND CODE", + "roles": { + "default": "commandcode-goat/zai-org/GLM-5.3", + "planner": "commandcode-goat/moonshotai/Kimi-K3", + "executor": "commandcode-goat/deepseek/deepseek-v4-flash", + "architect": "commandcode-goat/deepseek/deepseek-v4-pro", + "critic": "commandcode-goat/zai-org/GLM-5.2" + } + }, { "name": "open-weights-glm", "label": "Open Weights GLM", @@ -125,6 +235,42 @@ export const GJC_BUILTIN_MODEL_PROFILES: readonly GjcBuiltinModelProfile[] = [ "critic": "gpt-5.6-luna:xhigh" } }, + { + "name": "open-weights-spark", + "label": "Open Weights Spark", + "group": "OPEN WEIGHTS", + "roles": { + "default": "muse-spark-1.2:medium", + "planner": "muse-spark-1.2:high", + "executor": "muse-spark-1.2:low", + "architect": "muse-spark-1.2:xhigh", + "critic": "muse-spark-1.2:high" + } + }, + { + "name": "open-weights-spark-deepseek", + "label": "Open Weights Spark + DeepSeek", + "group": "OPEN WEIGHTS", + "roles": { + "default": "muse-spark-1.2:medium", + "planner": "muse-spark-1.2:high", + "executor": "deepseek-v4-flash:high", + "architect": "muse-spark-1.2:xhigh", + "critic": "muse-spark-1.2:high" + } + }, + { + "name": "open-weights-spark-luna", + "label": "Open Weights Spark + Luna", + "group": "OPEN WEIGHTS", + "roles": { + "default": "muse-spark-1.2:medium", + "planner": "muse-spark-1.2:high", + "executor": "gpt-5.6-luna:high", + "architect": "muse-spark-1.2:xhigh", + "critic": "muse-spark-1.2:high" + } + }, { "name": "open-weights-glm-deepseek", "label": "Open Weights GLM + DeepSeek", @@ -202,11 +348,11 @@ export const GJC_BUILTIN_MODEL_PROFILES: readonly GjcBuiltinModelProfile[] = [ "label": "Claude Fable", "group": "CLAUDE", "roles": { - "default": "anthropic/claude-fable-5:xhigh", - "planner": "anthropic/claude-fable-5:low", + "default": "anthropic/claude-fable-5-1:xhigh", + "planner": "anthropic/claude-fable-5-1:low", "executor": "anthropic/claude-sonnet-5", - "architect": "anthropic/claude-fable-5:xhigh", - "critic": "anthropic/claude-fable-5:high" + "architect": "anthropic/claude-fable-5-1:xhigh", + "critic": "anthropic/claude-fable-5-1:high" } }, { @@ -214,11 +360,11 @@ export const GJC_BUILTIN_MODEL_PROFILES: readonly GjcBuiltinModelProfile[] = [ "label": "GLM Eco", "group": "GLM", "roles": { - "default": "zai/glm-5.2:low", - "planner": "zai/glm-5.2:low", - "executor": "zai/glm-5.2:minimal", - "architect": "zai/glm-5.2:high", - "critic": "zai/glm-5.2:medium" + "default": "zai/glm-5.3-flash:low", + "planner": "zai/glm-5.3-flash:low", + "executor": "zai/glm-5.3-flash:low", + "architect": "zai/glm-5.3:high", + "critic": "zai/glm-5.3:high" } }, { @@ -226,11 +372,11 @@ export const GJC_BUILTIN_MODEL_PROFILES: readonly GjcBuiltinModelProfile[] = [ "label": "GLM Medium", "group": "GLM", "roles": { - "default": "zai/glm-5.2:medium", - "planner": "zai/glm-5.2:medium", - "executor": "zai/glm-5.2:low", - "architect": "zai/glm-5.2:xhigh", - "critic": "zai/glm-5.2:high" + "default": "zai/glm-5.3:high", + "planner": "zai/glm-5.3:high", + "executor": "zai/glm-5.3-flash:low", + "architect": "zai/glm-5.3:max", + "critic": "zai/glm-5.3:high" } }, { @@ -238,11 +384,11 @@ export const GJC_BUILTIN_MODEL_PROFILES: readonly GjcBuiltinModelProfile[] = [ "label": "GLM Pro", "group": "GLM", "roles": { - "default": "zai/glm-5.2:xhigh", - "planner": "zai/glm-5.2:high", - "executor": "zai/glm-5.2:medium", - "architect": "zai/glm-5.2:xhigh", - "critic": "zai/glm-5.2:xhigh" + "default": "zai/glm-5.3:max", + "planner": "zai/glm-5.3:high", + "executor": "zai/glm-5.3-flash:high", + "architect": "zai/glm-5.3:max", + "critic": "zai/glm-5.3:max" } }, { @@ -360,7 +506,7 @@ export const GJC_BUILTIN_MODEL_PROFILES: readonly GjcBuiltinModelProfile[] = [ "roles": { "default": "xai/grok-4.5:low", "planner": "xai/grok-4.5:low", - "executor": "xai/grok-4.5:minimal", + "executor": "xai/grok-4.5:low", "architect": "xai/grok-4.5:high", "critic": "xai/grok-4.5:medium" } @@ -389,6 +535,42 @@ export const GJC_BUILTIN_MODEL_PROFILES: readonly GjcBuiltinModelProfile[] = [ "critic": "xai/grok-4.5:high" } }, + { + "name": "grok-46-eco", + "label": "Grok 4.6 Eco", + "group": "GROK", + "roles": { + "default": "xai/grok-4.6:low", + "planner": "xai/grok-4.6:low", + "executor": "xai/grok-4.6:low", + "architect": "xai/grok-4.6:high", + "critic": "xai/grok-4.6:medium" + } + }, + { + "name": "grok-46-medium", + "label": "Grok 4.6 Medium", + "group": "GROK", + "roles": { + "default": "xai/grok-4.6:medium", + "planner": "xai/grok-4.6:medium", + "executor": "xai/grok-4.6:low", + "architect": "xai/grok-4.6:high", + "critic": "xai/grok-4.6:high" + } + }, + { + "name": "grok-46-pro", + "label": "Grok 4.6 Pro", + "group": "GROK", + "roles": { + "default": "xai/grok-4.6:xhigh", + "planner": "xai/grok-4.6:high", + "executor": "xai/grok-4.6:medium", + "architect": "xai/grok-4.6:xhigh", + "critic": "xai/grok-4.6:xhigh" + } + }, { "name": "grok-build-pro", "label": "Grok Build Pro", @@ -562,11 +744,35 @@ export const GJC_BUILTIN_MODEL_PROFILES: readonly GjcBuiltinModelProfile[] = [ "label": "Fable + Opus + Codex", "group": "COMBOS", "roles": { - "default": "anthropic/claude-fable-5:high", + "default": "anthropic/claude-fable-5-1:high", "planner": "anthropic/claude-opus-5:medium", "executor": "openai-codex/gpt-5.6-terra:medium", "architect": "openai-codex/gpt-5.6-sol:xhigh", "critic": "anthropic/claude-opus-5:high" } + }, + { + "name": "astra-fable", + "label": "Astra + Fable", + "group": "COMBOS", + "roles": { + "default": "openai-codex/gpt-6-astra:medium", + "planner": "openai-codex/gpt-6-astra:xhigh", + "executor": "openai-codex/gpt-5.6-luna:max", + "architect": "anthropic/claude-fable-5-1:xhigh", + "critic": "anthropic/claude-fable-5-1:xhigh" + } + }, + { + "name": "astra-fable-opus", + "label": "Astra + Fable + Opus", + "group": "COMBOS", + "roles": { + "default": "openai-codex/gpt-6-astra:medium", + "planner": "anthropic/claude-opus-5:medium", + "executor": "openai-codex/gpt-5.6-luna:max", + "architect": "anthropic/claude-fable-5-1:xhigh", + "critic": "anthropic/claude-fable-5-1:xhigh" + } } ];