diff --git a/package.json b/package.json index fa39fb46c..3f7121fc7 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "@anthropic-ai/claude-agent-sdk": "0.3.200", "@anthropic-ai/sandbox-runtime": "0.0.71", "@clack/prompts": "^1.5.1", - "@earendil-works/pi-coding-agent": "^0.80.3", + "@earendil-works/pi-coding-agent": "0.80.7", "@modelcontextprotocol/ext-apps": "^1.7.2", "@modelcontextprotocol/node": "^2.0.0", "@modelcontextprotocol/sdk": "^1.29.0", @@ -87,6 +87,9 @@ "vite": "^8.0.14" }, "overrides": { + "@earendil-works/pi-agent-core": "0.80.7", + "@earendil-works/pi-ai": "0.80.7", + "@earendil-works/pi-tui": "0.80.7", "protobufjs": "7.6.4", "ws": "8.21.0", "undici": "8.5.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 92ca7f368..96717e1aa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,11 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + '@earendil-works/pi-agent-core': 0.80.7 + '@earendil-works/pi-ai': 0.80.7 + '@earendil-works/pi-tui': 0.80.7 + importers: .: @@ -21,8 +26,8 @@ importers: specifier: ^1.5.1 version: 1.5.1 '@earendil-works/pi-coding-agent': - specifier: ^0.80.3 - version: 0.80.3(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3) + specifier: 0.80.7 + version: 0.80.7(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3) '@modelcontextprotocol/ext-apps': specifier: ^1.7.2 version: 1.7.2(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(zod@4.4.3) @@ -311,22 +316,22 @@ packages: resolution: {integrity: sha512-zccHj2z2oCCO4yrDiRSlFOxWerGqRiysP7a5jPK6uoI9URKAquwY42Dd/iUP8JWHxEzdRe4TlbvZCo8z1/mhrw==} engines: {node: '>= 20.12.0'} - '@earendil-works/pi-agent-core@0.80.3': - resolution: {integrity: sha512-3qw0/GeRQBU/nlGjDe5Yb7ePKTmoxefx2YxyKMFAviFUMXpFexBG/hS7mBtwFahFvzrrTPPoRT6sFIDjwoDWPQ==} + '@earendil-works/pi-agent-core@0.80.7': + resolution: {integrity: sha512-EFjyAuoz2kn24sR9Q5A86sZCG6mD+nz58DCsA2I2wxgmS50cF1tSLCBOZaHKI5U9Y3pJs4BefeK3LRkB5TdJag==} engines: {node: '>=22.19.0'} - '@earendil-works/pi-ai@0.80.3': - resolution: {integrity: sha512-jPZLMeGL5kkMSEAwAklfXTMHqZvfhsJtCCpKGIr5Duk7mc0n4skjB1dugk7y0z3z8ZHIUCmPAWHdyDqgUz5vdA==} + '@earendil-works/pi-ai@0.80.7': + resolution: {integrity: sha512-8RLKLwe5TFM9kKFMNu/lTzveduq4GxZbnlG6ba8FAhLeb5wJP4zbj1eBumKBRvggpFQnW5R/Vo2a8zTlHsV9SQ==} engines: {node: '>=22.19.0'} hasBin: true - '@earendil-works/pi-coding-agent@0.80.3': - resolution: {integrity: sha512-TIggw9gCXpA+Ph7OjdTA7ka2NPwTVuPmy39KDSyUzaKq8VvHfMGR7vtRz4JB7Um/RMRblmzhu4p9tUCk6MTgGA==} + '@earendil-works/pi-coding-agent@0.80.7': + resolution: {integrity: sha512-mxq3IClhdgmCrYiKuzKehs4QKCVJgKmlA70nUEzsgeNsGdxriT7o5sKQTCcxzVJkzDRMcHD/8tAP4/eGrV4gKQ==} engines: {node: '>=22.19.0'} hasBin: true - '@earendil-works/pi-tui@0.80.3': - resolution: {integrity: sha512-2BJI6qwRQfnM0Q7seL1+SbacU/jRRjBnN7Hu3n9BjAn7/s5FaBNnvdD1qBQYRsFTHfjqMaDsjYqanPyqwXj99w==} + '@earendil-works/pi-tui@0.80.7': + resolution: {integrity: sha512-1B2++fLZfgI3XMzW2BTpuDuam2uyHnUUEmsOvi5R0Ne9RAt59WjFV0G8ozX6l1Xafa9P5Y3eT4aDtRr/v/CUTA==} engines: {node: '>=22.19.0'} '@emnapi/core@1.10.0': @@ -2492,9 +2497,9 @@ snapshots: fast-wrap-ansi: 0.2.2 sisteransi: 1.0.5 - '@earendil-works/pi-agent-core@0.80.3(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3)': + '@earendil-works/pi-agent-core@0.80.7(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3)': dependencies: - '@earendil-works/pi-ai': 0.80.3(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3) + '@earendil-works/pi-ai': 0.80.7(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3) ignore: 7.0.5 typebox: 1.1.38 yaml: 2.9.0 @@ -2506,7 +2511,7 @@ snapshots: - ws - zod - '@earendil-works/pi-ai@0.80.3(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3)': + '@earendil-works/pi-ai@0.80.7(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3)': dependencies: '@anthropic-ai/sdk': 0.91.1(zod@4.4.3) '@aws-sdk/client-bedrock-runtime': 3.1048.0 @@ -2527,11 +2532,11 @@ snapshots: - ws - zod - '@earendil-works/pi-coding-agent@0.80.3(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3)': + '@earendil-works/pi-coding-agent@0.80.7(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3)': dependencies: - '@earendil-works/pi-agent-core': 0.80.3(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3) - '@earendil-works/pi-ai': 0.80.3(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3) - '@earendil-works/pi-tui': 0.80.3 + '@earendil-works/pi-agent-core': 0.80.7(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3) + '@earendil-works/pi-ai': 0.80.7(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3) + '@earendil-works/pi-tui': 0.80.7 '@silvia-odwyer/photon-node': 0.3.4 chalk: 5.6.2 cross-spawn: 7.0.6 @@ -2557,7 +2562,7 @@ snapshots: - ws - zod - '@earendil-works/pi-tui@0.80.3': + '@earendil-works/pi-tui@0.80.7': dependencies: get-east-asian-width: 1.6.0 marked: 18.0.5 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index b10c94d08..2140ab29b 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -5,3 +5,11 @@ allowBuilds: koffi: true node-pty: true protobufjs: false + +# pnpm 11 reads overrides here, not from package.json. 0.80.7 depends on +# these siblings with ^0.80.7; without an override they resolve to 0.80.10, +# which no longer exports getOAuthApiKey and breaks AuthStorage. +overrides: + '@earendil-works/pi-agent-core': '0.80.7' + '@earendil-works/pi-ai': '0.80.7' + '@earendil-works/pi-tui': '0.80.7' diff --git a/src/local-agent-pi-authstorage-pin.test.ts b/src/local-agent-pi-authstorage-pin.test.ts new file mode 100644 index 000000000..30eed79da --- /dev/null +++ b/src/local-agent-pi-authstorage-pin.test.ts @@ -0,0 +1,104 @@ +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import test from "node:test"; +import { satisfies } from "semver"; +import { parse as parseYaml } from "yaml"; + +// 0.80.8 dropped AuthStorage. pi-coding-agent@0.80.7 still exports it, but +// depends on its sibling packages with ^0.80.7. pnpm 11 ignores that +// package's npm-shrinkwrap and package.json overrides, so those ranges +// install 0.80.10 and AuthStorage fails to load (getOAuthApiKey was +// removed). The direct pin is for npm; pnpm-workspace.yaml overrides keep +// the frozen lockfile on 0.80.7. See Waishnav/devspace#360. +const PI_PACKAGE = "@earendil-works/pi-coding-agent"; +const PI_SIBLINGS = [ + "@earendil-works/pi-agent-core", + "@earendil-works/pi-ai", + "@earendil-works/pi-tui", +] as const; +const AUTOSTORAGE_PIN = "0.80.7"; +const AUTOSTORAGE_REMOVED_VERSIONS = ["0.80.8", "0.80.9", "0.80.10"] as const; + +type PackageManifest = { + dependencies?: Record; + overrides?: Record; +}; + +const packageJson = JSON.parse( + readFileSync(new URL("../package.json", import.meta.url), "utf8"), +) as PackageManifest; + +function declaredSpecifier(): string { + const specifier = packageJson.dependencies?.[PI_PACKAGE]; + if (typeof specifier !== "string") { + assert.fail(`${PI_PACKAGE} must be a direct dependency`); + } + return specifier; +} + +test("pi-coding-agent cannot resolve to an AuthStorage-less release", () => { + const specifier = declaredSpecifier(); + for (const version of AUTOSTORAGE_REMOVED_VERSIONS) { + assert.equal( + satisfies(version, specifier), + false, + `declared ${PI_PACKAGE}@${specifier} satisfies ${version}, which dropped AuthStorage (issue #360). Pin exact ${AUTOSTORAGE_PIN}.`, + ); + } + assert.equal( + specifier, + AUTOSTORAGE_PIN, + `${PI_PACKAGE} must be the exact pin ${AUTOSTORAGE_PIN}, the last release that still exports AuthStorage`, + ); +}); + +test("pi sibling packages stay on the AuthStorage pin", () => { + const npmOverrides = packageJson.overrides ?? {}; + const workspace = parseYaml( + readFileSync(new URL("../pnpm-workspace.yaml", import.meta.url), "utf8"), + ) as { overrides?: Record }; + const pnpmOverrides = workspace.overrides ?? {}; + for (const name of PI_SIBLINGS) { + assert.equal( + npmOverrides[name], + AUTOSTORAGE_PIN, + `${name} must be overridden in package.json to exact ${AUTOSTORAGE_PIN} so a fresh npm install cannot float onto an AuthStorage-less release`, + ); + assert.equal( + pnpmOverrides[name], + AUTOSTORAGE_PIN, + `${name} must be overridden in pnpm-workspace.yaml to exact ${AUTOSTORAGE_PIN}; pnpm 11 does not apply package.json overrides`, + ); + } +}); + +test("lockfile freezes the AuthStorage pin for the whole pi family", () => { + const lockText = readFileSync(new URL("../pnpm-lock.yaml", import.meta.url), "utf8"); + const match = lockText.match( + /'@earendil-works\/pi-coding-agent':\r?\n\s+specifier: ([^\r\n]+)\r?\n\s+version: ([^\s(]+)/, + ); + assert.ok(match, "pnpm-lock.yaml importer must record @earendil-works/pi-coding-agent"); + assert.equal(match[1], declaredSpecifier()); + assert.equal(match[1], AUTOSTORAGE_PIN); + assert.equal(match[2], AUTOSTORAGE_PIN); + + for (const name of [PI_PACKAGE, ...PI_SIBLINGS]) { + const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const versions = [...lockText.matchAll(new RegExp(`${escaped}@(\\d+\\.\\d+\\.\\d+)`, "g"))].map((found) => found[1]); + assert.ok(versions.length > 0, `pnpm-lock.yaml must record ${name}`); + for (const version of versions) { + assert.equal( + version, + AUTOSTORAGE_PIN, + `${name}@${version} is locked; only ${AUTOSTORAGE_PIN} still matches the AuthStorage session factory`, + ); + } + } +}); + +test("pi session factory still constructs AuthStorage", () => { + const source = readFileSync(new URL("./local-agent-pi.ts", import.meta.url), "utf8"); + assert.match(source, /AuthStorage,\s*\n\s*ModelRegistry,/); + assert.match(source, /AuthStorage\.create\(/); + assert.match(source, /ModelRegistry\.create\(/); +});