Improve project-local pi config (#6309)
* feat(coding-agent): improve config resource overrides * fix(coding-agent): simplify config resource overrides
This commit is contained in:
@@ -3,8 +3,11 @@ import { tmpdir } from "node:os";
|
||||
import { delimiter, join } from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { ENV_AGENT_DIR, PACKAGE_NAME, VERSION } from "../src/config.ts";
|
||||
import type { ResolvedPaths } from "../src/core/package-manager.ts";
|
||||
import { InMemorySettingsStorage, SettingsManager } from "../src/core/settings-manager.ts";
|
||||
import { ProjectTrustStore } from "../src/core/trust-manager.ts";
|
||||
import { main } from "../src/main.ts";
|
||||
import { ConfigSelectorComponent } from "../src/modes/interactive/components/config-selector.ts";
|
||||
import { handlePackageCommand } from "../src/package-manager-cli.ts";
|
||||
|
||||
describe("package commands", () => {
|
||||
@@ -28,6 +31,24 @@ describe("package commands", () => {
|
||||
expect(await handlePackageCommand(args)).toBe(true);
|
||||
}
|
||||
|
||||
function extensionPaths(
|
||||
packageRoot: string,
|
||||
source: string,
|
||||
scope: "user" | "project",
|
||||
names: string[],
|
||||
): ResolvedPaths {
|
||||
return {
|
||||
extensions: names.map((name) => ({
|
||||
path: join(packageRoot, "extensions", name),
|
||||
enabled: true,
|
||||
metadata: { source, scope, origin: "package", baseDir: packageRoot },
|
||||
})),
|
||||
skills: [],
|
||||
prompts: [],
|
||||
themes: [],
|
||||
};
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
tempDir = join(tmpdir(), `pi-package-commands-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
||||
agentDir = join(tempDir, "agent");
|
||||
@@ -350,6 +371,37 @@ describe("package commands", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("cycles project package overrides in config local mode", async () => {
|
||||
const storage = new InMemorySettingsStorage();
|
||||
storage.withLock("global", () => JSON.stringify({ packages: ["npm:pi-tools"] }));
|
||||
const settingsManager = SettingsManager.fromStorage(storage, { projectTrusted: true });
|
||||
const resolvedPaths = extensionPaths(join(tempDir, "pkg"), "npm:pi-tools", "user", ["bar.ts"]);
|
||||
const selector = new ConfigSelectorComponent(
|
||||
{ global: resolvedPaths, project: resolvedPaths },
|
||||
settingsManager,
|
||||
projectDir,
|
||||
agentDir,
|
||||
() => {},
|
||||
() => {},
|
||||
() => {},
|
||||
24,
|
||||
"project",
|
||||
);
|
||||
|
||||
selector.getResourceList().handleInput(" ");
|
||||
expect(settingsManager.getProjectSettings().packages).toEqual([
|
||||
{ source: "npm:pi-tools", autoload: false, extensions: ["-extensions/bar.ts"] },
|
||||
]);
|
||||
|
||||
selector.getResourceList().handleInput(" ");
|
||||
expect(settingsManager.getProjectSettings().packages).toEqual([
|
||||
{ source: "npm:pi-tools", autoload: false, extensions: ["+extensions/bar.ts"] },
|
||||
]);
|
||||
|
||||
selector.getResourceList().handleInput(" ");
|
||||
expect(settingsManager.getProjectSettings().packages).toEqual([]);
|
||||
});
|
||||
|
||||
it("shows a friendly error for unknown install options", async () => {
|
||||
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
||||
|
||||
|
||||
@@ -1674,6 +1674,49 @@ Content`,
|
||||
expect(result.extensions.some((r) => isEnabled(r, "one.ts"))).toBe(true);
|
||||
expect(result.extensions.some((r) => isDisabled(r, "two.ts"))).toBe(true);
|
||||
});
|
||||
|
||||
it("should resolve autoload-disabled project package entries as deltas over global packages", async () => {
|
||||
const pkgDir = join(agentDir, "npm", "node_modules", "pi-tools");
|
||||
mkdirSync(join(pkgDir, "extensions"), { recursive: true });
|
||||
writeFileSync(join(pkgDir, "package.json"), JSON.stringify({ name: "pi-tools", version: "1.0.0" }));
|
||||
writeFileSync(join(pkgDir, "extensions", "foo.ts"), "export default function() {}");
|
||||
writeFileSync(join(pkgDir, "extensions", "bar.ts"), "export default function() {}");
|
||||
settingsManager.setPackages(["npm:pi-tools"]);
|
||||
settingsManager.setProjectPackages([
|
||||
{ source: "npm:pi-tools", autoload: false, extensions: ["-extensions/foo.ts"] },
|
||||
]);
|
||||
const runCommandSpy = vi
|
||||
.spyOn(packageManager as unknown as PackageManagerInternals, "runCommand")
|
||||
.mockRejectedValue(new Error("unexpected install"));
|
||||
|
||||
const result = await packageManager.resolve();
|
||||
const states = Object.fromEntries(
|
||||
result.extensions.map((resource) => [
|
||||
resource.path,
|
||||
{ enabled: resource.enabled, scope: resource.metadata.scope },
|
||||
]),
|
||||
);
|
||||
expect(runCommandSpy).not.toHaveBeenCalled();
|
||||
expect(states[join(pkgDir, "extensions", "foo.ts")]).toEqual({ enabled: false, scope: "project" });
|
||||
expect(states[join(pkgDir, "extensions", "bar.ts")]).toEqual({ enabled: true, scope: "user" });
|
||||
});
|
||||
|
||||
it("should resolve autoload-disabled package entries as positive-only without a global package", async () => {
|
||||
const pkgDir = join(tempDir, "positive-only-pkg");
|
||||
mkdirSync(join(pkgDir, "extensions"), { recursive: true });
|
||||
mkdirSync(join(pkgDir, "skills", "foo"), { recursive: true });
|
||||
writeFileSync(join(pkgDir, "extensions", "foo.ts"), "export default function() {}");
|
||||
writeFileSync(join(pkgDir, "extensions", "bar.ts"), "export default function() {}");
|
||||
writeFileSync(join(pkgDir, "skills", "foo", "SKILL.md"), "# Foo\n");
|
||||
settingsManager.setProjectPackages([
|
||||
{ source: relative(join(tempDir, ".pi"), pkgDir), autoload: false, extensions: ["+extensions/foo.ts"] },
|
||||
]);
|
||||
|
||||
const result = await packageManager.resolve();
|
||||
|
||||
expect(result.extensions.map((resource) => resource.path)).toEqual([join(pkgDir, "extensions", "foo.ts")]);
|
||||
expect(result.skills).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("force-include patterns", () => {
|
||||
|
||||
Reference in New Issue
Block a user