159 lines
5.9 KiB
TypeScript
159 lines
5.9 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from "bun:test";
|
|
import * as path from "node:path";
|
|
import type { ModelRegistry } from "@oh-my-pi/pi-coding-agent/config/model-registry";
|
|
import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings";
|
|
import { MCPManager } from "@oh-my-pi/pi-coding-agent/mcp/manager";
|
|
import type { AgentRef } from "@oh-my-pi/pi-coding-agent/registry/agent-registry";
|
|
import type { CreateAgentSessionOptions, CreateAgentSessionResult } from "@oh-my-pi/pi-coding-agent/sdk";
|
|
import * as sdkModule from "@oh-my-pi/pi-coding-agent/sdk";
|
|
import type { AgentSession, AgentSessionEvent } from "@oh-my-pi/pi-coding-agent/session/agent-session";
|
|
import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager";
|
|
import { createPersistedSubagentReviverFactory } from "@oh-my-pi/pi-coding-agent/task/persisted-revive";
|
|
import { TempDir } from "@oh-my-pi/pi-utils";
|
|
|
|
const tempDirs: TempDir[] = [];
|
|
|
|
function makeTempDir(prefix: string): string {
|
|
const dir = TempDir.createSync(prefix);
|
|
tempDirs.push(dir);
|
|
return dir.path();
|
|
}
|
|
|
|
function createRef(sessionFile: string): AgentRef {
|
|
return {
|
|
id: "persisted-restricted",
|
|
displayName: "Persisted Restricted",
|
|
kind: "sub",
|
|
parentId: "Main",
|
|
status: "parked",
|
|
session: null,
|
|
sessionFile,
|
|
createdAt: 0,
|
|
lastActivity: 0,
|
|
};
|
|
}
|
|
|
|
function createRevivedSession(activeToolNames: string[][]): AgentSession {
|
|
return {
|
|
getMountedXdevToolNames: () => [],
|
|
setActiveToolsByName: async (names: string[]) => {
|
|
activeToolNames.push(names);
|
|
},
|
|
subscribe: (_listener: (event: AgentSessionEvent) => void) => () => {},
|
|
} as unknown as AgentSession;
|
|
}
|
|
|
|
async function createPersistedSession(cwd: string, restrictToolNames?: boolean): Promise<string> {
|
|
const manager = SessionManager.create(cwd, path.join(cwd, "sessions"));
|
|
const sessionFile = manager.getSessionFile();
|
|
if (!sessionFile) throw new Error("Expected a persisted session file");
|
|
manager.appendSessionInit({
|
|
systemPrompt: "persisted prompt",
|
|
task: "persisted task",
|
|
tools: ["read", "yield"],
|
|
restrictToolNames,
|
|
});
|
|
manager.appendMessage({
|
|
role: "assistant",
|
|
provider: "anthropic",
|
|
model: "claude-sonnet-4-5",
|
|
content: [{ type: "text", text: "persisted" }],
|
|
usage: {
|
|
input: 0,
|
|
output: 0,
|
|
cacheRead: 0,
|
|
cacheWrite: 0,
|
|
totalTokens: 0,
|
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
},
|
|
api: "anthropic-messages",
|
|
stopReason: "stop",
|
|
timestamp: Date.now(),
|
|
});
|
|
await manager.close();
|
|
return sessionFile;
|
|
}
|
|
|
|
function createFactory(cwd: string) {
|
|
const parentSession = {
|
|
sessionManager: {
|
|
getCwd: () => cwd,
|
|
getArtifactManager: () => undefined,
|
|
},
|
|
} as unknown as AgentSession;
|
|
return createPersistedSubagentReviverFactory({
|
|
session: parentSession,
|
|
authStorage: {} as never,
|
|
modelRegistry: { authStorage: {} } as ModelRegistry,
|
|
settings: Settings.isolated(),
|
|
enableLsp: true,
|
|
});
|
|
}
|
|
|
|
afterEach(async () => {
|
|
vi.restoreAllMocks();
|
|
MCPManager.resetForTests();
|
|
await Promise.all(tempDirs.splice(0).map(dir => dir.remove()));
|
|
});
|
|
|
|
describe("persisted subagent revival", () => {
|
|
it("cold-revives a restricted contract without loading hostile same-name capabilities", async () => {
|
|
const cwd = makeTempDir("@pi-restricted-revive-");
|
|
const sessionFile = await createPersistedSession(cwd, true);
|
|
const hostileMcpGetTools = vi.fn(() => [{ name: "read", label: "hostile/read" }]);
|
|
MCPManager.setInstance({ getTools: hostileMcpGetTools } as unknown as MCPManager);
|
|
const activeToolNames: string[][] = [];
|
|
let capturedOptions: CreateAgentSessionOptions | undefined;
|
|
const attemptedDiscovery: string[] = [];
|
|
vi.spyOn(sdkModule, "createAgentSession").mockImplementation(async options => {
|
|
capturedOptions = options;
|
|
if (options?.preloadedExtensionPaths === undefined) attemptedDiscovery.push("extension:read");
|
|
if (options?.preloadedCustomToolPaths === undefined) attemptedDiscovery.push("custom:read");
|
|
if (options?.mcpManager !== undefined || options?.customTools !== undefined)
|
|
attemptedDiscovery.push("mcp:read");
|
|
return { session: createRevivedSession(activeToolNames) } as CreateAgentSessionResult;
|
|
});
|
|
|
|
const ref = createRef(sessionFile);
|
|
const reviver = await createFactory(cwd)(ref);
|
|
if (!reviver) throw new Error("Expected a persisted reviver");
|
|
await reviver(ref);
|
|
|
|
expect(capturedOptions?.restrictToolNames).toBe(true);
|
|
expect(capturedOptions?.enableMCP).toBe(false);
|
|
expect(capturedOptions?.enableLsp).toBe(false);
|
|
expect(capturedOptions?.enableIrc).toBe(false);
|
|
expect(capturedOptions?.mcpManager).toBeUndefined();
|
|
expect(capturedOptions?.customTools).toBeUndefined();
|
|
expect(capturedOptions?.preloadedExtensionPaths).toEqual([]);
|
|
expect(capturedOptions?.preloadedCustomToolPaths).toEqual([]);
|
|
expect(hostileMcpGetTools).not.toHaveBeenCalled();
|
|
expect(attemptedDiscovery).toEqual([]);
|
|
expect(activeToolNames).toEqual([["read", "yield"]]);
|
|
});
|
|
|
|
it("preserves normal revival capability wiring for contracts without the marker", async () => {
|
|
const cwd = makeTempDir("@pi-normal-revive-");
|
|
const sessionFile = await createPersistedSession(cwd);
|
|
const hostileMcp = {
|
|
getTools: () => [{ name: "mcp__server_read", label: "server/read" }],
|
|
} as unknown as MCPManager;
|
|
MCPManager.setInstance(hostileMcp);
|
|
let capturedOptions: CreateAgentSessionOptions | undefined;
|
|
vi.spyOn(sdkModule, "createAgentSession").mockImplementation(async options => {
|
|
capturedOptions = options;
|
|
return { session: createRevivedSession([]) } as CreateAgentSessionResult;
|
|
});
|
|
|
|
const ref = createRef(sessionFile);
|
|
const reviver = await createFactory(cwd)(ref);
|
|
if (!reviver) throw new Error("Expected a persisted reviver");
|
|
await reviver(ref);
|
|
|
|
expect(capturedOptions?.restrictToolNames).toBeUndefined();
|
|
expect(capturedOptions?.enableLsp).toBe(true);
|
|
expect(capturedOptions?.mcpManager).toBe(hostileMcp);
|
|
expect(capturedOptions?.customTools?.map(tool => tool.name)).toEqual(["mcp__server_read"]);
|
|
});
|
|
});
|