Fix package runtime and subagent reliability
This commit is contained in:
56
tests/package-ops.test.ts
Normal file
56
tests/package-ops.test.ts
Normal 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);
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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'));
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user