Fix package runtime and subagent reliability

This commit is contained in:
Advait Paliwal
2026-04-15 13:51:06 -07:00
parent dd3c07633b
commit 01c2808606
8 changed files with 718 additions and 5 deletions

56
tests/package-ops.test.ts Normal file
View File

@@ -0,0 +1,56 @@
import test from "node:test";
import assert from "node:assert/strict";
import { existsSync, lstatSync, mkdtempSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { seedBundledWorkspacePackages } from "../src/pi/package-ops.js";
function createBundledWorkspace(appRoot: string, packageNames: string[]): void {
for (const packageName of packageNames) {
const packageDir = resolve(appRoot, ".feynman", "npm", "node_modules", packageName);
mkdirSync(packageDir, { recursive: true });
writeFileSync(
join(packageDir, "package.json"),
JSON.stringify({ name: packageName, version: "1.0.0" }, null, 2) + "\n",
"utf8",
);
}
}
test("seedBundledWorkspacePackages links bundled packages into the Feynman npm prefix", () => {
const appRoot = mkdtempSync(join(tmpdir(), "feynman-bundle-"));
const homeRoot = mkdtempSync(join(tmpdir(), "feynman-home-"));
const agentDir = resolve(homeRoot, "agent");
mkdirSync(agentDir, { recursive: true });
createBundledWorkspace(appRoot, ["pi-subagents", "@samfp/pi-memory"]);
const seeded = seedBundledWorkspacePackages(agentDir, appRoot, [
"npm:pi-subagents",
"npm:@samfp/pi-memory",
]);
assert.deepEqual(seeded.sort(), ["npm:@samfp/pi-memory", "npm:pi-subagents"]);
const globalRoot = resolve(homeRoot, "npm-global", "lib", "node_modules");
assert.equal(existsSync(resolve(globalRoot, "pi-subagents", "package.json")), true);
assert.equal(existsSync(resolve(globalRoot, "@samfp", "pi-memory", "package.json")), true);
});
test("seedBundledWorkspacePackages preserves existing installed packages", () => {
const appRoot = mkdtempSync(join(tmpdir(), "feynman-bundle-"));
const homeRoot = mkdtempSync(join(tmpdir(), "feynman-home-"));
const agentDir = resolve(homeRoot, "agent");
const existingPackageDir = resolve(homeRoot, "npm-global", "lib", "node_modules", "pi-subagents");
mkdirSync(agentDir, { recursive: true });
createBundledWorkspace(appRoot, ["pi-subagents"]);
mkdirSync(existingPackageDir, { recursive: true });
writeFileSync(resolve(existingPackageDir, "package.json"), '{"name":"pi-subagents","version":"user"}\n', "utf8");
const seeded = seedBundledWorkspacePackages(agentDir, appRoot, ["npm:pi-subagents"]);
assert.deepEqual(seeded, []);
assert.equal(readFileSync(resolve(existingPackageDir, "package.json"), "utf8"), '{"name":"pi-subagents","version":"user"}\n');
assert.equal(lstatSync(existingPackageDir).isSymbolicLink(), false);
});

View File

@@ -4,7 +4,13 @@ import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";
import { CORE_PACKAGE_SOURCES, getOptionalPackagePresetSources, shouldPruneLegacyDefaultPackages } from "../src/pi/package-presets.js";
import {
CORE_PACKAGE_SOURCES,
getOptionalPackagePresetSources,
NATIVE_PACKAGE_SOURCES,
shouldPruneLegacyDefaultPackages,
supportsNativePackageSources,
} from "../src/pi/package-presets.js";
import { normalizeFeynmanSettings, normalizeThinkingLevel } from "../src/pi/settings.js";
test("normalizeThinkingLevel accepts the latest Pi thinking levels", () => {
@@ -71,3 +77,42 @@ test("optional package presets map friendly aliases", () => {
assert.deepEqual(getOptionalPackagePresetSources("search"), undefined);
assert.equal(shouldPruneLegacyDefaultPackages(["npm:custom"]), false);
});
test("supportsNativePackageSources disables sqlite-backed packages on Node 25+", () => {
assert.equal(supportsNativePackageSources("24.8.0"), true);
assert.equal(supportsNativePackageSources("25.0.0"), false);
});
test("normalizeFeynmanSettings prunes native core packages on unsupported Node majors", () => {
const root = mkdtempSync(join(tmpdir(), "feynman-settings-"));
const settingsPath = join(root, "settings.json");
const bundledSettingsPath = join(root, "bundled-settings.json");
const authPath = join(root, "auth.json");
writeFileSync(
settingsPath,
JSON.stringify(
{
packages: [...CORE_PACKAGE_SOURCES],
},
null,
2,
) + "\n",
"utf8",
);
writeFileSync(bundledSettingsPath, "{}\n", "utf8");
writeFileSync(authPath, "{}\n", "utf8");
const originalVersion = process.versions.node;
Object.defineProperty(process.versions, "node", { value: "25.0.0", configurable: true });
try {
normalizeFeynmanSettings(settingsPath, bundledSettingsPath, "medium", authPath);
} finally {
Object.defineProperty(process.versions, "node", { value: originalVersion, configurable: true });
}
const settings = JSON.parse(readFileSync(settingsPath, "utf8")) as { packages?: string[] };
for (const source of NATIVE_PACKAGE_SOURCES) {
assert.equal(settings.packages?.includes(source), false);
}
});

View File

@@ -102,3 +102,41 @@ test("patchPiSubagentsSource is idempotent", () => {
assert.equal(twice, once);
});
test("patchPiSubagentsSource rewrites modern agents.ts discovery paths", () => {
const input = [
'import * as fs from "node:fs";',
'import * as os from "node:os";',
'import * as path from "node:path";',
'export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryResult {',
'\tconst userDirOld = path.join(os.homedir(), ".pi", "agent", "agents");',
'\tconst userDirNew = path.join(os.homedir(), ".agents");',
'\tconst userAgentsOld = scope === "project" ? [] : loadAgentsFromDir(userDirOld, "user");',
'\tconst userAgentsNew = scope === "project" ? [] : loadAgentsFromDir(userDirNew, "user");',
'\tconst userAgents = [...userAgentsOld, ...userAgentsNew];',
'}',
'export function discoverAgentsAll(cwd: string) {',
'\tconst userDirOld = path.join(os.homedir(), ".pi", "agent", "agents");',
'\tconst userDirNew = path.join(os.homedir(), ".agents");',
'\tconst user = [',
'\t\t...loadAgentsFromDir(userDirOld, "user"),',
'\t\t...loadAgentsFromDir(userDirNew, "user"),',
'\t];',
'\tconst chains = [',
'\t\t...loadChainsFromDir(userDirOld, "user"),',
'\t\t...loadChainsFromDir(userDirNew, "user"),',
'\t\t...(projectDir ? loadChainsFromDir(projectDir, "project") : []),',
'\t];',
'\tconst userDir = fs.existsSync(userDirNew) ? userDirNew : userDirOld;',
'}',
].join("\n");
const patched = patchPiSubagentsSource("agents.ts", input);
assert.match(patched, /function resolvePiAgentDir\(\): string \{/);
assert.match(patched, /const userDir = path\.join\(resolvePiAgentDir\(\), "agents"\);/);
assert.match(patched, /const userAgents = scope === "project" \? \[\] : loadAgentsFromDir\(userDir, "user"\);/);
assert.ok(!patched.includes('loadAgentsFromDir(userDirOld, "user")'));
assert.ok(!patched.includes('loadChainsFromDir(userDirNew, "user")'));
assert.ok(!patched.includes('fs.existsSync(userDirNew) ? userDirNew : userDirOld'));
});