Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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"
Expand Down
41 changes: 23 additions & 18 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
104 changes: 104 additions & 0 deletions src/local-agent-pi-authstorage-pin.test.ts
Original file line number Diff line number Diff line change
@@ -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<string, string>;
overrides?: Record<string, string>;
};

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<string, string> };
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`,
);
}
});
Comment on lines +55 to +73

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Consumer Resolution Is Untested

These assertions only compare override text in repository manifests; they never install DevSpace from a separate consumer project or inspect the resolved Pi dependency tree. A fresh consumer installation can resolve the Pi siblings to 0.80.10 while this predicate still passes, so the test provides false confidence that published dependency resolution remains pinned. This is non-blocking, but it leaves consumer dependency-resolution regressions undetected.

Artifacts

Evidence from the check

  • The executable script checks the exact focused manifest predicate and performs a fresh override-free pnpm consumer installation; it is the executed source for the result.

Command output from the check

  • Captured output from executing the validation script in `/home/user/repo`; it shows the text-only predicate passes while the real consumer resolves incompatible 0.80.10 siblings, confirming the gap.

View artifacts

T-Rex Ran code and verified through T-Rex


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\(/);
});
Loading