From 65942bde13f5e9e5c88e30c3642fb45962a107e3 Mon Sep 17 00:00:00 2001 From: Jeffrey Boadi Date: Fri, 10 Jul 2026 01:47:55 +0200 Subject: [PATCH 1/2] fix: pause on manual thinking level changes Register the current thinking_level_select event and share manual model-state pause handling so external thinking changes cannot leave stale handoff state or target tracking. Guard both model and thinking-level updates initiated by Downshift, update the adapter and core tests, and align Pi development dependencies so agent_settled can use the typed ExtensionAPI directly. Tests: npm run check Packaging: npm pack --dry-run --- package-lock.json | 176 +++++++++++++++++++++++++------------ package.json | 4 +- src/downshift-core.test.ts | 38 +++++++- src/downshift-core.ts | 23 ++++- src/downshift.test.ts | 6 ++ src/downshift.ts | 30 +++---- 6 files changed, 201 insertions(+), 76 deletions(-) diff --git a/package-lock.json b/package-lock.json index 311e170..f2119c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,8 @@ "version": "0.5.0", "license": "Apache-2.0", "devDependencies": { - "@earendil-works/pi-ai": "^0.78.0", - "@earendil-works/pi-coding-agent": "^0.78.0", + "@earendil-works/pi-ai": "^0.80.6", + "@earendil-works/pi-coding-agent": "^0.80.6", "@types/node": "^25.9.1", "typescript": "^6.0.3", "vitest": "^4.1.8" @@ -529,16 +529,17 @@ } }, "node_modules/@earendil-works/pi-ai": { - "version": "0.78.1", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.78.1.tgz", - "integrity": "sha512-CM2pkTs1iupG/maw381lC9Q/Y/aQaMGK7GILc28ttImD0ci3LDwKroDsGkWbly5JIy3iqxdRxB9JlG7vvzCzTg==", + "version": "0.80.6", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.80.6.tgz", + "integrity": "sha512-7xfLk8sANBp+bpPEbjoOZTbPxsa+++b1JXAoSJsNa3vbs9AHHEclmvg54XLQcxH+fuwaeti/g2jeIfJ+mVYLpA==", "dev": true, "license": "MIT", "dependencies": { "@anthropic-ai/sdk": "0.91.1", "@aws-sdk/client-bedrock-runtime": "3.1048.0", "@google/genai": "1.52.0", - "@mistralai/mistralai": "2.2.1", + "@mistralai/mistralai": "2.2.6", + "@opentelemetry/api": "1.9.0", "@smithy/node-http-handler": "4.7.3", "http-proxy-agent": "7.0.2", "https-proxy-agent": "7.0.6", @@ -554,16 +555,16 @@ } }, "node_modules/@earendil-works/pi-coding-agent": { - "version": "0.78.1", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-coding-agent/-/pi-coding-agent-0.78.1.tgz", - "integrity": "sha512-Syjf6Ib8UoY5t9ZdKjp0BRrQZuFkFBc8j2KEU9zG/ZnmYPcAxYeioofdv2Q3MEXnHEX2U8sKQptkSnJIdMsd0g==", + "version": "0.80.6", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-coding-agent/-/pi-coding-agent-0.80.6.tgz", + "integrity": "sha512-vcfD6tOk402isLl3Cm/qbn2O10TvgroMp1+/fEGM24ZdvETFCdOYv5VZ7m59EI5fPsjfSJh+CpQ5bhBrhfOg7g==", "dev": true, "hasShrinkwrap": true, "license": "MIT", "dependencies": { - "@earendil-works/pi-agent-core": "^0.78.1", - "@earendil-works/pi-ai": "^0.78.1", - "@earendil-works/pi-tui": "^0.78.1", + "@earendil-works/pi-agent-core": "^0.80.6", + "@earendil-works/pi-ai": "^0.80.6", + "@earendil-works/pi-tui": "^0.80.6", "@silvia-odwyer/photon-node": "0.3.4", "chalk": "5.6.2", "cross-spawn": "7.0.6", @@ -575,8 +576,9 @@ "jiti": "2.7.0", "minimatch": "10.2.5", "proper-lockfile": "4.1.2", + "semver": "7.8.0", "typebox": "1.1.38", - "undici": "8.3.0", + "undici": "8.5.0", "yaml": "2.9.0" }, "bin": { @@ -1052,12 +1054,12 @@ } }, "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-agent-core": { - "version": "0.78.1", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.78.1.tgz", + "version": "0.80.6", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.80.6.tgz", "dev": true, "license": "MIT", "dependencies": { - "@earendil-works/pi-ai": "^0.78.1", + "@earendil-works/pi-ai": "^0.80.6", "ignore": "7.0.5", "typebox": "1.1.38", "yaml": "2.9.0" @@ -1067,15 +1069,16 @@ } }, "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-ai": { - "version": "0.78.1", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.78.1.tgz", + "version": "0.80.6", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.80.6.tgz", "dev": true, "license": "MIT", "dependencies": { "@anthropic-ai/sdk": "0.91.1", "@aws-sdk/client-bedrock-runtime": "3.1048.0", "@google/genai": "1.52.0", - "@mistralai/mistralai": "2.2.1", + "@mistralai/mistralai": "2.2.6", + "@opentelemetry/api": "1.9.0", "@smithy/node-http-handler": "4.7.3", "http-proxy-agent": "7.0.2", "https-proxy-agent": "7.0.6", @@ -1084,20 +1087,20 @@ "typebox": "1.1.38" }, "bin": { - "pi-ai": "./dist/cli.js" + "pi-ai": "dist/cli.js" }, "engines": { "node": ">=22.19.0" } }, "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-tui": { - "version": "0.78.1", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.78.1.tgz", + "version": "0.80.6", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.80.6.tgz", "dev": true, "license": "MIT", "dependencies": { "get-east-asian-width": "1.6.0", - "marked": "15.0.12" + "marked": "18.0.5" }, "engines": { "node": ">=22.19.0" @@ -1334,15 +1337,24 @@ } }, "node_modules/@earendil-works/pi-coding-agent/node_modules/@mistralai/mistralai": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-2.2.1.tgz", - "integrity": "sha512-uKU8CZmL2RzYKmplsU01hii4p3pe4HqJefpWNRWXm1Tcm0Sm4xXfwSLIy4k7ZCPlbETCGcp69E7hZs+WOJ5itQ==", + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-2.2.6.tgz", + "integrity": "sha512-W8pX7zHxjJvMIpw8JMxeJEleapXX0Q9NPszdNzqkM3MIEoIGPObdodujj+WHteXEvGfaP/AMwlNyRfEzSY6dQQ==", "dev": true, "license": "Apache-2.0", "dependencies": { + "@opentelemetry/semantic-conventions": "^1.40.0", "ws": "^8.18.0", "zod": "^3.25.0 || ^4.0.0", "zod-to-json-schema": "^3.25.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + } } }, "node_modules/@earendil-works/pi-coding-agent/node_modules/@nodable/entities": { @@ -1358,6 +1370,26 @@ ], "license": "MIT" }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@opentelemetry/semantic-conventions": { + "version": "1.41.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.41.1.tgz", + "integrity": "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -1380,9 +1412,9 @@ "license": "BSD-3-Clause" }, "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", + "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", "dev": true, "license": "BSD-3-Clause" }, @@ -1403,13 +1435,6 @@ "dev": true, "license": "BSD-3-Clause" }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/inquire": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.2.tgz", - "integrity": "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==", - "dev": true, - "license": "BSD-3-Clause" - }, "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/path": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", @@ -2053,16 +2078,16 @@ } }, "node_modules/@earendil-works/pi-coding-agent/node_modules/marked": { - "version": "15.0.12", - "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", - "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", + "version": "18.0.5", + "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.5.tgz", + "integrity": "sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w==", "dev": true, "license": "MIT", "bin": { "marked": "bin/marked.js" }, "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@earendil-works/pi-coding-agent/node_modules/minimatch": { @@ -2254,9 +2279,9 @@ } }, "node_modules/@earendil-works/pi-coding-agent/node_modules/protobufjs": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.9.tgz", - "integrity": "sha512-Od4muIm3HW1AouyHF5lONOf1FWo3hY1NbFDoy191X9GzhpgW1clCoaFjfVs2rKJNFYpTNJbje4cbAIDBZJ63ZA==", + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.4.tgz", + "integrity": "sha512-RJJPTTpvFfHcWLkIa2JFWK4XvtSzS0yEWDmunqHXli1h3JlkbcQZXDZdcWxv+JK3Xsl5/UFDPZ0iGm7DAengYw==", "dev": true, "hasInstallScript": true, "license": "BSD-3-Clause", @@ -2264,15 +2289,14 @@ "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.5", - "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/eventemitter": "^1.1.1", "@protobufjs/fetch": "^1.1.1", "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.2", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.1", "@types/node": ">=13.7.0", - "long": "^5.0.0" + "long": "^5.3.2" }, "engines": { "node": ">=12.0.0" @@ -2309,6 +2333,19 @@ ], "license": "MIT" }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@earendil-works/pi-coding-agent/node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -2374,9 +2411,9 @@ "license": "MIT" }, "node_modules/@earendil-works/pi-coding-agent/node_modules/undici": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-8.3.0.tgz", - "integrity": "sha512-TkUDgb6tl7KOGZ+7e8E3d2FYgUQgF6z5YypqjWmixVQSQERFcVrVg0ySADm2LVLRh5ljAaHTCR5Fmz3Q34rB7Q==", + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.5.0.tgz", + "integrity": "sha512-xamtWoB1EshgjpmlXd7GGm2VfdDtw1+rD8uhry8pSNW3If6S8E0m2T2+orSKeZXEn/aPJMviCpDBA65WJt8zhg==", "dev": true, "license": "MIT", "engines": { @@ -2417,9 +2454,9 @@ } }, "node_modules/@earendil-works/pi-coding-agent/node_modules/ws": { - "version": "8.20.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", - "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", "dev": true, "license": "MIT", "engines": { @@ -2557,15 +2594,24 @@ "license": "MIT" }, "node_modules/@mistralai/mistralai": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-2.2.1.tgz", - "integrity": "sha512-uKU8CZmL2RzYKmplsU01hii4p3pe4HqJefpWNRWXm1Tcm0Sm4xXfwSLIy4k7ZCPlbETCGcp69E7hZs+WOJ5itQ==", + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-2.2.6.tgz", + "integrity": "sha512-W8pX7zHxjJvMIpw8JMxeJEleapXX0Q9NPszdNzqkM3MIEoIGPObdodujj+WHteXEvGfaP/AMwlNyRfEzSY6dQQ==", "dev": true, "license": "Apache-2.0", "dependencies": { + "@opentelemetry/semantic-conventions": "^1.40.0", "ws": "^8.18.0", "zod": "^3.25.0 || ^4.0.0", "zod-to-json-schema": "^3.25.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + } } }, "node_modules/@napi-rs/wasm-runtime": { @@ -2600,6 +2646,26 @@ ], "license": "MIT" }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.43.0.tgz", + "integrity": "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, "node_modules/@oxc-project/types": { "version": "0.133.0", "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", diff --git a/package.json b/package.json index b792103..3c1486c 100644 --- a/package.json +++ b/package.json @@ -45,8 +45,8 @@ "@earendil-works/pi-coding-agent": "*" }, "devDependencies": { - "@earendil-works/pi-ai": "^0.78.0", - "@earendil-works/pi-coding-agent": "^0.78.0", + "@earendil-works/pi-ai": "^0.80.6", + "@earendil-works/pi-coding-agent": "^0.80.6", "@types/node": "^25.9.1", "typescript": "^6.0.3", "vitest": "^4.1.8" diff --git a/src/downshift-core.test.ts b/src/downshift-core.test.ts index 8fe0f5c..c73dced 100644 --- a/src/downshift-core.test.ts +++ b/src/downshift-core.test.ts @@ -6,6 +6,7 @@ import { handleAgentEnd, handleBeforeAgentStart, handleManualModelSelect, + handleManualThinkingLevelSelect, maybeDownshift, maybeUpshiftAfterCompaction, restoreStateFromEntries, @@ -518,7 +519,7 @@ describe("downshift core", () => { await handleManualModelSelect(deps, runtime, { source: "restore" }, ctx); expect(runtime.state).toEqual(createState({ handoff: "requested" })); - await handleManualModelSelect(deps, runtime, { source: "user" }, ctx); + await handleManualModelSelect(deps, runtime, { source: "cycle" }, ctx); expect(runtime.state.paused).toBe(true); expect(runtime.state.handoff).toBe("idle"); @@ -526,6 +527,41 @@ describe("downshift core", () => { expect(runtime.state.lastError).toBe("manual model change"); }); + it("manual thinking level change pauses downshift and clears handoff", async () => { + const deps = createDeps(); + const runtime = { state: createState({ handoff: "active" }) }; + + await handleManualThinkingLevelSelect( + deps, + runtime, + { level: "high", previousLevel: "medium" }, + ctx, + ); + + expect(runtime.state.paused).toBe(true); + expect(runtime.state.handoff).toBe("idle"); + expect(runtime.state.continueAfterHandoff).toBe(false); + expect(runtime.state.lastError).toBe("manual thinking level change"); + }); + + it("does not pause for a manual thinking level change when disabled", async () => { + const deps = createDeps({ ...baseConfig, enabled: false }); + const runtime = { state: createState({ handoff: "requested" }) }; + + await handleManualThinkingLevelSelect( + deps, + runtime, + { level: "high", previousLevel: "medium" }, + ctx, + ); + + expect(runtime.state).toEqual(createState({ handoff: "requested" })); + expect(deps.updateStatus).toHaveBeenCalledWith({ + ...baseConfig, + enabled: false, + }); + }); + it("restores interrupted handoff as paused instead of stranded", () => { for (const handoff of ["requested", "active"] as const) { const restored = restoreStateFromEntries( diff --git a/src/downshift-core.ts b/src/downshift-core.ts index 0d3ed45..754cfdf 100644 --- a/src/downshift-core.ts +++ b/src/downshift-core.ts @@ -531,6 +531,27 @@ export async function handleManualModelSelect( _ctx: UsageContext, ): Promise { if (event.source === "restore") return runtime.state; + return pauseForManualChange(deps, runtime, "manual model change"); +} + +export async function handleManualThinkingLevelSelect( + deps: CoreDeps, + runtime: Runtime, + _event: unknown, + _ctx: UsageContext, +): Promise { + return pauseForManualChange( + deps, + runtime, + "manual thinking level change", + ); +} + +async function pauseForManualChange( + deps: CoreDeps, + runtime: Runtime, + reason: string, +): Promise { const config = await deps.readConfig(); if (!isDownshiftEnabled(config, runtime.state)) { deps.updateStatus(config); @@ -541,7 +562,7 @@ export async function handleManualModelSelect( position: "premium", handoff: "idle", continueAfterHandoff: false, - lastError: "manual model change", + lastError: reason, }); deps.updateStatus(config); return runtime.state; diff --git a/src/downshift.test.ts b/src/downshift.test.ts index 2a110c4..a0657b7 100644 --- a/src/downshift.test.ts +++ b/src/downshift.test.ts @@ -117,6 +117,12 @@ describe("downshift lifecycle adapter", () => { expect(pi.setModel).not.toHaveBeenCalled(); }); + it("registers a thinking level select handler", () => { + const { handlers } = createExtension(); + + expect(handlers.has("thinking_level_select")).toBe(true); + }); + it("refreshes status immediately after saving configuration", async () => { const usage = { current: { tokens: 100, percent: 10 } }; const { commands } = createExtension(); diff --git a/src/downshift.ts b/src/downshift.ts index 1197a56..8fd6a1e 100644 --- a/src/downshift.ts +++ b/src/downshift.ts @@ -17,6 +17,7 @@ import { handleAgentEnd, handleBeforeAgentStart, handleManualModelSelect, + handleManualThinkingLevelSelect, maybeDownshift, maybeUpshiftAfterCompaction, parseTarget, @@ -94,7 +95,7 @@ type ConfigFieldEditor = ( ) => Promise; let runtime: Runtime = { state: createInitialState() }; -let internalModelChange = false; +let internalTargetChange = false; function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); @@ -228,7 +229,7 @@ async function switchToTarget( return pause(pi, ctx, `thinking level unsupported: ${targetLabel(target)}`); } try { - internalModelChange = true; + internalTargetChange = true; const ok = await pi.setModel(model); if (!ok) return pause( @@ -244,7 +245,7 @@ async function switchToTarget( const message = error instanceof Error ? error.message : String(error); return pause(pi, ctx, message); } finally { - internalModelChange = false; + internalTargetChange = false; } } @@ -912,13 +913,6 @@ function hasExplicitStartPremium( return !!config?.enabled && config.startOnPremium && !!config.premium; } -type ExtensionAPIWithAgentSettled = ExtensionAPI & { - on( - event: "agent_settled", - handler: (event: unknown, ctx: ExtensionContext) => void | Promise, - ): void; -}; - export default function downshift(pi: ExtensionAPI): void { pi.on("session_start", async (event, ctx) => { await handleSessionStart(pi, event, ctx); @@ -937,12 +931,9 @@ export default function downshift(pi: ExtensionAPI): void { await refreshStatus(ctx); }); - (pi as ExtensionAPIWithAgentSettled).on( - "agent_settled", - async (_event, ctx) => { - await refreshStatus(ctx); - }, - ); + pi.on("agent_settled", async (_event, ctx) => { + await refreshStatus(ctx); + }); pi.on("before_agent_start", async (event, ctx) => { await handleBeforeAgentStart(coreDeps(pi, ctx), runtime, event, ctx); @@ -957,10 +948,15 @@ export default function downshift(pi: ExtensionAPI): void { }); pi.on("model_select", async (event, ctx) => { - if (internalModelChange) return; + if (internalTargetChange) return; await handleManualModelSelect(coreDeps(pi, ctx), runtime, event, ctx); }); + pi.on("thinking_level_select", async (event, ctx) => { + if (internalTargetChange) return; + await handleManualThinkingLevelSelect(coreDeps(pi, ctx), runtime, event, ctx); + }); + pi.registerCommand("downshift", { description: "Configure automatic model downshifting by context threshold", handler: async (args, ctx) => { From 737cd082caa323d16240ac03aa2a9d77465f7994 Mon Sep 17 00:00:00 2001 From: Jeffrey Boadi Date: Fri, 10 Jul 2026 01:49:06 +0200 Subject: [PATCH 2/2] style: format downshift sources Apply the repository Prettier formatting to the Downshift core and adapter files. --- src/downshift-core.ts | 6 +----- src/downshift.ts | 7 ++++++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/downshift-core.ts b/src/downshift-core.ts index 754cfdf..da86a5e 100644 --- a/src/downshift-core.ts +++ b/src/downshift-core.ts @@ -540,11 +540,7 @@ export async function handleManualThinkingLevelSelect( _event: unknown, _ctx: UsageContext, ): Promise { - return pauseForManualChange( - deps, - runtime, - "manual thinking level change", - ); + return pauseForManualChange(deps, runtime, "manual thinking level change"); } async function pauseForManualChange( diff --git a/src/downshift.ts b/src/downshift.ts index 8fd6a1e..ae6fcab 100644 --- a/src/downshift.ts +++ b/src/downshift.ts @@ -954,7 +954,12 @@ export default function downshift(pi: ExtensionAPI): void { pi.on("thinking_level_select", async (event, ctx) => { if (internalTargetChange) return; - await handleManualThinkingLevelSelect(coreDeps(pi, ctx), runtime, event, ctx); + await handleManualThinkingLevelSelect( + coreDeps(pi, ctx), + runtime, + event, + ctx, + ); }); pi.registerCommand("downshift", {