feat: enable permission-aware subagents

This commit is contained in:
叶林立
2026-08-26 10:49:26 +08:00
parent d3bf562189
commit 7571ba6dd9
185 changed files with 48365 additions and 26 deletions
@@ -0,0 +1,81 @@
import { mkdirSync, mkdtempSync, rmSync, symlinkSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
// Mock homedir so the legacy ~/.pi/agent-memory fallback can be exercised
// against a temp directory instead of the real home. The default return must
// be a valid string: pi-coding-agent evaluates getAgentDir() at module load
// (before beforeEach runs), so an undefined homedir would throw at import.
const mockHomedir = vi.hoisted(() => vi.fn(() => "/tmp"));
vi.mock("node:os", async (importOriginal) => {
const actual = await importOriginal<typeof import("node:os")>();
return { ...actual, homedir: mockHomedir };
});
import { resolveMemoryDir } from "../src/memory.js";
describe("resolveMemoryDir user-scope legacy fallback", () => {
let tmpDir: string;
let fakeHome: string;
let agentDir: string;
let originalEnv: string | undefined;
beforeEach(() => {
tmpDir = mkdtempSync(join(tmpdir(), "pi-mem-legacy-test-"));
fakeHome = join(tmpDir, "home");
agentDir = join(tmpDir, "agent-dir");
mkdirSync(fakeHome, { recursive: true });
mockHomedir.mockReturnValue(fakeHome);
originalEnv = process.env.PI_CODING_AGENT_DIR;
process.env.PI_CODING_AGENT_DIR = agentDir;
});
afterEach(() => {
if (originalEnv == null) delete process.env.PI_CODING_AGENT_DIR;
else process.env.PI_CODING_AGENT_DIR = originalEnv;
rmSync(tmpDir, { recursive: true, force: true });
});
it("uses the agent dir location when no legacy memory exists", () => {
const dir = resolveMemoryDir("chronicler", "user", "/workspace");
expect(dir).toBe(join(agentDir, "agent-memory", "chronicler"));
});
it("falls back to legacy ~/.pi/agent-memory when it exists and the new location doesn't", () => {
const legacy = join(fakeHome, ".pi", "agent-memory", "chronicler");
mkdirSync(legacy, { recursive: true });
const dir = resolveMemoryDir("chronicler", "user", "/workspace");
expect(dir).toBe(legacy);
});
it("prefers the new location once it exists, even if legacy also exists", () => {
const legacy = join(fakeHome, ".pi", "agent-memory", "chronicler");
const current = join(agentDir, "agent-memory", "chronicler");
mkdirSync(legacy, { recursive: true });
mkdirSync(current, { recursive: true });
const dir = resolveMemoryDir("chronicler", "user", "/workspace");
expect(dir).toBe(current);
});
it("ignores a symlinked legacy directory", () => {
const target = join(tmpDir, "elsewhere");
mkdirSync(target, { recursive: true });
mkdirSync(join(fakeHome, ".pi", "agent-memory"), { recursive: true });
symlinkSync(target, join(fakeHome, ".pi", "agent-memory", "chronicler"));
const dir = resolveMemoryDir("chronicler", "user", "/workspace");
expect(dir).toBe(join(agentDir, "agent-memory", "chronicler"));
});
it("scopes the fallback per agent name", () => {
const legacyOther = join(fakeHome, ".pi", "agent-memory", "other-agent");
mkdirSync(legacyOther, { recursive: true });
// "other-agent" has legacy memory; "chronicler" doesn't — only the former falls back.
expect(resolveMemoryDir("other-agent", "user", "/workspace")).toBe(legacyOther);
expect(resolveMemoryDir("chronicler", "user", "/workspace")).toBe(join(agentDir, "agent-memory", "chronicler"));
});
});