feat: vendor permission system source

This commit is contained in:
云服务部-叶林立
2026-08-19 14:35:19 +08:00
parent 198584daf8
commit 410c50a3e5
809 changed files with 157793 additions and 139 deletions
@@ -0,0 +1,547 @@
import { afterEach, describe, expect, test, vi } from "vitest";
import { SUBAGENT_ENV_HINT_KEYS } from "#src/authority/permission-forwarding";
import {
isRegisteredSubagentChild,
isSubagentExecutionContext,
normalizeFilesystemPath,
type SubagentDetectionContext,
} from "#src/authority/subagent-context";
import { SubagentSessionRegistry } from "#src/authority/subagent-registry";
import { posixPathFlavor, win32PathFlavor } from "#src/path/path-flavor";
afterEach(() => {
vi.unstubAllEnvs();
vi.restoreAllMocks();
});
function makeCtx(
sessionDir: string | null,
sessionId: string = "",
): SubagentDetectionContext {
return {
sessionManager: {
getSessionDir: vi.fn(() => sessionDir ?? ""),
getSessionId: vi.fn(() => sessionId),
},
};
}
describe("isRegisteredSubagentChild", () => {
const childSessionId = "child-session-abc";
test("returns true when the session id is registered", () => {
const registry = new SubagentSessionRegistry();
registry.register(childSessionId, {});
expect(
isRegisteredSubagentChild(makeCtx(null, childSessionId), registry),
).toBe(true);
});
test("returns false when the session id is not registered", () => {
const registry = new SubagentSessionRegistry();
expect(
isRegisteredSubagentChild(makeCtx(null, childSessionId), registry),
).toBe(false);
});
test("returns false when the session id is empty", () => {
const registry = new SubagentSessionRegistry();
registry.register("", {});
expect(isRegisteredSubagentChild(makeCtx(null, ""), registry)).toBe(false);
});
test("returns false when getSessionId throws", () => {
const registry = new SubagentSessionRegistry();
registry.register(childSessionId, {});
const ctx: SubagentDetectionContext = {
sessionManager: {
getSessionDir: vi.fn(() => ""),
getSessionId: vi.fn(() => {
throw new Error("session id unavailable");
}),
},
};
expect(isRegisteredSubagentChild(ctx, registry)).toBe(false);
});
});
describe("normalizeFilesystemPath", () => {
test("normalizes a simple absolute path", () => {
expect(normalizeFilesystemPath("/projects/my-app", posixPathFlavor)).toBe(
"/projects/my-app",
);
});
test("collapses redundant separators", () => {
expect(normalizeFilesystemPath("/projects//my-app", posixPathFlavor)).toBe(
"/projects/my-app",
);
});
test("resolves . and .. segments", () => {
expect(
normalizeFilesystemPath("/projects/my-app/../other", posixPathFlavor),
).toBe("/projects/other");
});
test("win32: lowercases and normalizes with win32 separators", () => {
expect(
normalizeFilesystemPath("C:\\Projects\\My-App", win32PathFlavor),
).toBe("c:\\projects\\my-app");
});
test("posix: leaves case untouched", () => {
expect(normalizeFilesystemPath("/Projects/My-App", posixPathFlavor)).toBe(
"/Projects/My-App",
);
});
});
describe("isSubagentExecutionContext — injected platform (#510)", () => {
test("win32: detects a subagent session dir case-insensitively", () => {
const subagentRoot = "C:\\Sessions\\Subagents";
const sessionDir = "c:\\sessions\\subagents\\child";
expect(
isSubagentExecutionContext(
makeCtx(sessionDir),
subagentRoot,
win32PathFlavor,
),
).toBe(true);
});
test("posix: the same mixed-case dir is not a subagent context", () => {
const subagentRoot = "/Sessions/Subagents";
const sessionDir = "/sessions/subagents/child";
expect(
isSubagentExecutionContext(
makeCtx(sessionDir),
subagentRoot,
posixPathFlavor,
),
).toBe(false);
});
});
describe("isSubagentExecutionContext — env hint detection", () => {
test("returns true when PI_IS_SUBAGENT is set", () => {
vi.stubEnv("PI_IS_SUBAGENT", "true");
expect(
isSubagentExecutionContext(
makeCtx(null),
"/sessions/subagents",
posixPathFlavor,
),
).toBe(true);
});
test("returns true when PI_SUBAGENT_SESSION_ID is set", () => {
vi.stubEnv("PI_SUBAGENT_SESSION_ID", "abc123");
expect(
isSubagentExecutionContext(
makeCtx(null),
"/sessions/subagents",
posixPathFlavor,
),
).toBe(true);
});
test("returns true when PI_AGENT_ROUTER_SUBAGENT is set", () => {
vi.stubEnv("PI_AGENT_ROUTER_SUBAGENT", "1");
expect(
isSubagentExecutionContext(
makeCtx(null),
"/sessions/subagents",
posixPathFlavor,
),
).toBe(true);
});
// nicobailon/pi-subagents keys
test("returns true when PI_SUBAGENT_CHILD is set", () => {
vi.stubEnv("PI_SUBAGENT_CHILD", "1");
expect(
isSubagentExecutionContext(
makeCtx(null),
"/sessions/subagents",
posixPathFlavor,
),
).toBe(true);
});
test("returns true when PI_SUBAGENT_RUN_ID is set", () => {
vi.stubEnv("PI_SUBAGENT_RUN_ID", "run-abc");
expect(
isSubagentExecutionContext(
makeCtx(null),
"/sessions/subagents",
posixPathFlavor,
),
).toBe(true);
});
test("returns true when PI_SUBAGENT_CHILD_AGENT is set", () => {
vi.stubEnv("PI_SUBAGENT_CHILD_AGENT", "worker");
expect(
isSubagentExecutionContext(
makeCtx(null),
"/sessions/subagents",
posixPathFlavor,
),
).toBe(true);
});
test("returns true when PI_SUBAGENT_DEPTH is set", () => {
vi.stubEnv("PI_SUBAGENT_DEPTH", "1");
expect(
isSubagentExecutionContext(
makeCtx(null),
"/sessions/subagents",
posixPathFlavor,
),
).toBe(true);
});
test("returns true when PI_SUBAGENT_DEPTH is zero (depth-0 is still a subagent context)", () => {
vi.stubEnv("PI_SUBAGENT_DEPTH", "0");
expect(
isSubagentExecutionContext(
makeCtx(null),
"/sessions/subagents",
posixPathFlavor,
),
).toBe(true);
});
// HazAT/pi-interactive-subagents keys
test("returns true when PI_SUBAGENT_NAME is set", () => {
vi.stubEnv("PI_SUBAGENT_NAME", "my-agent");
expect(
isSubagentExecutionContext(
makeCtx(null),
"/sessions/subagents",
posixPathFlavor,
),
).toBe(true);
});
test("returns true when PI_SUBAGENT_ID is set", () => {
vi.stubEnv("PI_SUBAGENT_ID", "id-xyz");
expect(
isSubagentExecutionContext(
makeCtx(null),
"/sessions/subagents",
posixPathFlavor,
),
).toBe(true);
});
test("returns true when PI_SUBAGENT_SESSION is set", () => {
vi.stubEnv("PI_SUBAGENT_SESSION", "session-xyz");
expect(
isSubagentExecutionContext(
makeCtx(null),
"/sessions/subagents",
posixPathFlavor,
),
).toBe(true);
});
test("returns true when PI_SUBAGENT_ACTIVITY_FILE is set", () => {
vi.stubEnv("PI_SUBAGENT_ACTIVITY_FILE", "/tmp/activity.json");
expect(
isSubagentExecutionContext(
makeCtx(null),
"/sessions/subagents",
posixPathFlavor,
),
).toBe(true);
});
test("covers all declared SUBAGENT_ENV_HINT_KEYS", () => {
// Verify the keys we test match what the module declares.
expect(SUBAGENT_ENV_HINT_KEYS).toContain("PI_IS_SUBAGENT");
expect(SUBAGENT_ENV_HINT_KEYS).toContain("PI_SUBAGENT_SESSION_ID");
expect(SUBAGENT_ENV_HINT_KEYS).toContain("PI_AGENT_ROUTER_SUBAGENT");
// nicobailon/pi-subagents
expect(SUBAGENT_ENV_HINT_KEYS).toContain("PI_SUBAGENT_CHILD");
expect(SUBAGENT_ENV_HINT_KEYS).toContain("PI_SUBAGENT_RUN_ID");
expect(SUBAGENT_ENV_HINT_KEYS).toContain("PI_SUBAGENT_CHILD_AGENT");
expect(SUBAGENT_ENV_HINT_KEYS).toContain("PI_SUBAGENT_DEPTH");
// HazAT/pi-interactive-subagents
expect(SUBAGENT_ENV_HINT_KEYS).toContain("PI_SUBAGENT_NAME");
expect(SUBAGENT_ENV_HINT_KEYS).toContain("PI_SUBAGENT_ID");
expect(SUBAGENT_ENV_HINT_KEYS).toContain("PI_SUBAGENT_SESSION");
expect(SUBAGENT_ENV_HINT_KEYS).toContain("PI_SUBAGENT_ACTIVITY_FILE");
});
test("returns false when env hint value is empty string", () => {
vi.stubEnv("PI_IS_SUBAGENT", "");
expect(
isSubagentExecutionContext(
makeCtx(null),
"/sessions/subagents",
posixPathFlavor,
),
).toBe(false);
});
test("returns false when env hint value is whitespace only", () => {
vi.stubEnv("PI_IS_SUBAGENT", " ");
expect(
isSubagentExecutionContext(
makeCtx(null),
"/sessions/subagents",
posixPathFlavor,
),
).toBe(false);
});
});
describe("isSubagentExecutionContext — session dir detection", () => {
const subagentRoot = "/home/user/.pi/agent/sessions/subagents";
test("returns true when session dir is within subagent root", () => {
const sessionDir = `${subagentRoot}/session-abc`;
expect(
isSubagentExecutionContext(
makeCtx(sessionDir),
subagentRoot,
posixPathFlavor,
),
).toBe(true);
});
test("returns true when session dir equals subagent root", () => {
expect(
isSubagentExecutionContext(
makeCtx(subagentRoot),
subagentRoot,
posixPathFlavor,
),
).toBe(true);
});
test("returns false when session dir is outside subagent root", () => {
const sessionDir = "/home/user/.pi/agent/sessions/main-session";
expect(
isSubagentExecutionContext(
makeCtx(sessionDir),
subagentRoot,
posixPathFlavor,
),
).toBe(false);
});
test("returns false when session dir is a sibling with shared prefix", () => {
// "/sessions/subagents-extra" should not match root "/sessions/subagents"
const sessionDir = `${subagentRoot}-extra/session-abc`;
expect(
isSubagentExecutionContext(
makeCtx(sessionDir),
subagentRoot,
posixPathFlavor,
),
).toBe(false);
});
test("returns false when a `..` segment escapes the subagent root", () => {
// Normalizes to /home/user/.pi/agent/sessions/evil/session-abc — outside.
const sessionDir = `${subagentRoot}/../evil/session-abc`;
expect(
isSubagentExecutionContext(
makeCtx(sessionDir),
subagentRoot,
posixPathFlavor,
),
).toBe(false);
});
test("returns true when a `..` segment resolves back inside the root", () => {
// Normalizes to /home/user/.pi/agent/sessions/subagents/session-abc — inside.
const sessionDir = `${subagentRoot}/nested/../session-abc`;
expect(
isSubagentExecutionContext(
makeCtx(sessionDir),
subagentRoot,
posixPathFlavor,
),
).toBe(true);
});
test("returns false when session dir is under a different root", () => {
const sessionDir = "/var/other/subagents/session-abc";
expect(
isSubagentExecutionContext(
makeCtx(sessionDir),
subagentRoot,
posixPathFlavor,
),
).toBe(false);
});
test("returns false when getSessionDir returns null", () => {
expect(
isSubagentExecutionContext(makeCtx(null), subagentRoot, posixPathFlavor),
).toBe(false);
});
test("returns false when getSessionDir returns empty string", () => {
expect(
isSubagentExecutionContext(makeCtx(""), subagentRoot, posixPathFlavor),
).toBe(false);
});
});
describe("isSubagentExecutionContext — session dir detection (win32 flavor)", () => {
const subagentRoot = "C:\\Users\\dev\\.pi\\agent\\sessions\\subagents";
test("returns true when session dir is within subagent root", () => {
const sessionDir = `${subagentRoot}\\session-abc`;
expect(
isSubagentExecutionContext(
makeCtx(sessionDir),
subagentRoot,
win32PathFlavor,
),
).toBe(true);
});
test("returns true when session dir equals subagent root (case-insensitive)", () => {
expect(
isSubagentExecutionContext(
makeCtx(subagentRoot.toUpperCase()),
subagentRoot,
win32PathFlavor,
),
).toBe(true);
});
test("returns false when session dir is a sibling with shared prefix", () => {
const sessionDir = `${subagentRoot}-extra\\session-abc`;
expect(
isSubagentExecutionContext(
makeCtx(sessionDir),
subagentRoot,
win32PathFlavor,
),
).toBe(false);
});
test("returns false when a `..` segment escapes the subagent root", () => {
const sessionDir = `${subagentRoot}\\..\\evil\\session-abc`;
expect(
isSubagentExecutionContext(
makeCtx(sessionDir),
subagentRoot,
win32PathFlavor,
),
).toBe(false);
});
test("returns false when session dir is on a different drive", () => {
const sessionDir =
"D:\\Users\\dev\\.pi\\agent\\sessions\\subagents\\session-abc";
expect(
isSubagentExecutionContext(
makeCtx(sessionDir),
subagentRoot,
win32PathFlavor,
),
).toBe(false);
});
});
describe("isSubagentExecutionContext — registry detection", () => {
const subagentRoot = "/home/user/.pi/agent/sessions/subagents";
const outsideDir =
"/home/user/projects/my-app/.pi/agent/sessions/parent/tasks";
const childSessionId = "child-session-abc";
test("returns true when session id is registered (no env vars, dir outside filesystem root)", () => {
const registry = new SubagentSessionRegistry();
registry.register(childSessionId, {});
expect(
isSubagentExecutionContext(
makeCtx(outsideDir, childSessionId),
subagentRoot,
posixPathFlavor,
registry,
),
).toBe(true);
});
test("returns true when registered session has a parentSessionId", () => {
const registry = new SubagentSessionRegistry();
registry.register(childSessionId, { parentSessionId: "parent-123" });
expect(
isSubagentExecutionContext(
makeCtx(outsideDir, childSessionId),
subagentRoot,
posixPathFlavor,
registry,
),
).toBe(true);
});
test("returns false when registry is provided but session id is not registered", () => {
const registry = new SubagentSessionRegistry();
expect(
isSubagentExecutionContext(
makeCtx(outsideDir, childSessionId),
subagentRoot,
posixPathFlavor,
registry,
),
).toBe(false);
});
test("returns false when session id is empty and registry has no matching entry", () => {
const registry = new SubagentSessionRegistry();
expect(
isSubagentExecutionContext(
makeCtx(null, ""),
subagentRoot,
posixPathFlavor,
registry,
),
).toBe(false);
});
test("registry check takes priority over env var detection", () => {
// Registry says registered; env var not set — should still return true.
const registry = new SubagentSessionRegistry();
registry.register(childSessionId, {});
// Confirm no env var is set
expect(process.env.PI_IS_SUBAGENT).toBeUndefined();
expect(
isSubagentExecutionContext(
makeCtx(outsideDir, childSessionId),
subagentRoot,
posixPathFlavor,
registry,
),
).toBe(true);
});
test("unregistered session falls through to env var detection", () => {
vi.stubEnv("PI_IS_SUBAGENT", "true");
const registry = new SubagentSessionRegistry(); // empty — childSessionId not registered
// Env var present → still true even without registry entry
expect(
isSubagentExecutionContext(
makeCtx(outsideDir, childSessionId),
subagentRoot,
posixPathFlavor,
registry,
),
).toBe(true);
});
test("no registry passed — existing behaviour unchanged", () => {
// Ensure the parameter is truly optional (no registry arg)
expect(
isSubagentExecutionContext(makeCtx(null), subagentRoot, posixPathFlavor),
).toBe(false);
});
});