diff --git a/packages/server/src/server/agent/mcp-parity.e2e.test.ts b/packages/server/src/server/agent/mcp-parity.e2e.test.ts index cf2b7dae0..1df7abb34 100644 --- a/packages/server/src/server/agent/mcp-parity.e2e.test.ts +++ b/packages/server/src/server/agent/mcp-parity.e2e.test.ts @@ -683,8 +683,11 @@ describe("Suite E: Worktree Tools", () => { try { const created = await callToolStructured(topLevelClient, "create_worktree", { cwd: worktreeRepoCwd, - branchName, - baseBranch: "main", + target: { + mode: "branch-off", + newBranch: branchName, + base: "main", + }, }); worktreePath = str(created.worktreePath); @@ -711,8 +714,11 @@ describe("Suite E: Worktree Tools", () => { try { const created = await callToolStructured(topLevelClient, "create_worktree", { cwd: worktreeRepoCwd, - branchName, - baseBranch: "main", + target: { + mode: "branch-off", + newBranch: branchName, + base: "main", + }, }); worktreePath = str(created.worktreePath); @@ -731,4 +737,49 @@ describe("Suite E: Worktree Tools", () => { await archiveWorktreeIfPresent({ cwd: worktreeRepoCwd, worktreePath }); } }); + + test("archive_worktree succeeds when caller cwd is inside the archived worktree", async () => { + let worktreePath: string | null = null; + let worktreeAgentId: string | null = null; + let worktreeScopedClient: McpClient | null = null; + const branchName = `parity-archive-self-cwd-${Date.now()}`; + + try { + const created = await callToolStructured(topLevelClient, "create_worktree", { + cwd: worktreeRepoCwd, + target: { + mode: "branch-off", + newBranch: branchName, + base: "main", + }, + }); + worktreePath = str(created.worktreePath); + worktreeAgentId = await createTopLevelAgent({ + cwd: worktreePath, + title: "Worktree scoped parity agent", + }); + worktreeScopedClient = await createMcpClient( + `http://127.0.0.1:${daemonHandle.port}/mcp/agents?callerAgentId=${encodeURIComponent( + worktreeAgentId, + )}`, + ); + + const archived = await callToolStructured(worktreeScopedClient, "archive_worktree", { + worktreePath, + }); + expect(archived).toEqual({ success: true }); + worktreePath = null; + worktreeAgentId = null; + + const listed = await callToolStructured(topLevelClient, "list_worktrees", { + cwd: worktreeRepoCwd, + }); + const worktrees = recordArr(listed.worktrees); + expect(worktrees.map((worktree) => worktree.path)).not.toContain(created.worktreePath); + } finally { + await worktreeScopedClient?.close(); + await archiveAgentIfPresent(worktreeAgentId); + await archiveWorktreeIfPresent({ cwd: worktreeRepoCwd, worktreePath }); + } + }); }); diff --git a/packages/server/src/server/agent/mcp-server.test.ts b/packages/server/src/server/agent/mcp-server.test.ts index 1a28b80ef..bfb339f14 100644 --- a/packages/server/src/server/agent/mcp-server.test.ts +++ b/packages/server/src/server/agent/mcp-server.test.ts @@ -1033,6 +1033,8 @@ describe("create_agent MCP tool", () => { execFileSync("git", ["branch", "-M", "main"], { cwd: repoDir, stdio: "pipe" }); const workspaceGitService = { getSnapshot: vi.fn(async () => null), + listWorktrees: vi.fn(async () => []), + resolveRepoRoot: vi.fn(async () => repoDir), }; const server = await createAgentMcpServer({ @@ -1046,7 +1048,7 @@ describe("create_agent MCP tool", () => { }), workspaceGitService: workspaceGitService as unknown as Pick< WorkspaceGitService, - "getSnapshot" | "listWorktrees" + "getSnapshot" | "listWorktrees" | "resolveRepoRoot" >, logger, }); @@ -1059,6 +1061,10 @@ describe("create_agent MCP tool", () => { expect(response.structuredContent.branchName).toBe("tool-worktree"); expect(response.structuredContent.worktreePath).toContain("tool-worktree"); expect(workspaceGitService.getSnapshot).not.toHaveBeenCalled(); + expect(workspaceGitService.listWorktrees).toHaveBeenCalledWith(repoDir, { + force: true, + reason: "mcp:create-worktree", + }); expect(setupContinuations).toEqual([undefined]); expect(broadcasts).toHaveLength(1); expect(broadcasts[0]).toContain("tool-worktree"); @@ -1093,6 +1099,8 @@ describe("create_agent MCP tool", () => { const workspaceGitService = { getSnapshot: vi.fn(async () => null), + listWorktrees: vi.fn(async () => []), + resolveRepoRoot: vi.fn(async () => repoDir), }; const archiveWorkspaceRecord = vi.fn(async () => undefined); const emitWorkspaceUpdatesForWorkspaceIds = vi.fn(async () => undefined); @@ -1106,7 +1114,7 @@ describe("create_agent MCP tool", () => { createPaseoWorktree: createPaseoWorktreeForMcpTest({ paseoHome, broadcasts: [] }), workspaceGitService: workspaceGitService as unknown as Pick< WorkspaceGitService, - "getSnapshot" | "listWorktrees" + "getSnapshot" | "listWorktrees" | "resolveRepoRoot" >, archiveWorkspaceRecord, emitWorkspaceUpdatesForWorkspaceIds, @@ -1133,6 +1141,11 @@ describe("create_agent MCP tool", () => { force: true, reason: "archive-worktree", }); + expect(workspaceGitService.resolveRepoRoot).toHaveBeenCalledWith(repoDir); + expect(workspaceGitService.listWorktrees).toHaveBeenCalledWith(repoDir, { + force: true, + reason: "mcp:archive-worktree", + }); expect(archiveWorkspaceRecord).toHaveBeenCalledWith(created.structuredContent.worktreePath); expect(markWorkspaceArchiving).toHaveBeenCalledWith( [created.structuredContent.worktreePath], diff --git a/packages/server/src/server/agent/mcp-server.ts b/packages/server/src/server/agent/mcp-server.ts index 0f8f91bb7..283ee0501 100644 --- a/packages/server/src/server/agent/mcp-server.ts +++ b/packages/server/src/server/agent/mcp-server.ts @@ -1858,6 +1858,10 @@ export async function createAgentMcpServer(options: AgentMcpServerOptions): Prom resolveDefaultBranch: mcpInput.resolveDefaultBranch, }); const { worktree } = createdWorktree; + await options.workspaceGitService?.listWorktrees?.(repoRoot, { + force: true, + reason: "mcp:create-worktree", + }); return { content: [], @@ -1887,7 +1891,7 @@ export async function createAgentMcpServer(options: AgentMcpServerOptions): Prom }, }, async ({ cwd, worktreePath, worktreeSlug }) => { - const repoRoot = resolveScopedCwd(cwd, { required: true }); + const resolvedCwd = resolveScopedCwd(cwd, { required: true }); if (!worktreePath && !worktreeSlug) { throw new Error("worktreePath or worktreeSlug is required"); } @@ -1912,6 +1916,7 @@ export async function createAgentMcpServer(options: AgentMcpServerOptions): Prom if (!options.emitSessionMessage) { throw new Error("Session message emitter is required to archive worktrees"); } + const repoRoot = await options.workspaceGitService.resolveRepoRoot(resolvedCwd); const targetPath = worktreePath ?? @@ -1948,6 +1953,10 @@ export async function createAgentMcpServer(options: AgentMcpServerOptions): Prom requestId: "mcp:archive_worktree", }, ); + await options.workspaceGitService.listWorktrees(repoRoot, { + force: true, + reason: "mcp:archive-worktree", + }); return { content: [],