Refresh MCP worktree cache after create/archive (#911)

* Fix MCP worktree list cache after create/archive

* Fix MCP worktree archive refresh from scoped cwd

---------

Co-authored-by: zbl <zbl@zbl-M4Pro.local>
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
This commit is contained in:
Bolun Zhang
2026-05-12 22:17:17 +08:00
committed by GitHub
co-authored by zbl Mohamed Boudra
parent 47d236a299
commit 15cab6014c
3 changed files with 80 additions and 7 deletions
@@ -683,8 +683,11 @@ describe("Suite E: Worktree Tools", () => {
try { try {
const created = await callToolStructured(topLevelClient, "create_worktree", { const created = await callToolStructured(topLevelClient, "create_worktree", {
cwd: worktreeRepoCwd, cwd: worktreeRepoCwd,
branchName, target: {
baseBranch: "main", mode: "branch-off",
newBranch: branchName,
base: "main",
},
}); });
worktreePath = str(created.worktreePath); worktreePath = str(created.worktreePath);
@@ -711,8 +714,11 @@ describe("Suite E: Worktree Tools", () => {
try { try {
const created = await callToolStructured(topLevelClient, "create_worktree", { const created = await callToolStructured(topLevelClient, "create_worktree", {
cwd: worktreeRepoCwd, cwd: worktreeRepoCwd,
branchName, target: {
baseBranch: "main", mode: "branch-off",
newBranch: branchName,
base: "main",
},
}); });
worktreePath = str(created.worktreePath); worktreePath = str(created.worktreePath);
@@ -731,4 +737,49 @@ describe("Suite E: Worktree Tools", () => {
await archiveWorktreeIfPresent({ cwd: worktreeRepoCwd, worktreePath }); 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 });
}
});
}); });
@@ -1033,6 +1033,8 @@ describe("create_agent MCP tool", () => {
execFileSync("git", ["branch", "-M", "main"], { cwd: repoDir, stdio: "pipe" }); execFileSync("git", ["branch", "-M", "main"], { cwd: repoDir, stdio: "pipe" });
const workspaceGitService = { const workspaceGitService = {
getSnapshot: vi.fn(async () => null), getSnapshot: vi.fn(async () => null),
listWorktrees: vi.fn(async () => []),
resolveRepoRoot: vi.fn(async () => repoDir),
}; };
const server = await createAgentMcpServer({ const server = await createAgentMcpServer({
@@ -1046,7 +1048,7 @@ describe("create_agent MCP tool", () => {
}), }),
workspaceGitService: workspaceGitService as unknown as Pick< workspaceGitService: workspaceGitService as unknown as Pick<
WorkspaceGitService, WorkspaceGitService,
"getSnapshot" | "listWorktrees" "getSnapshot" | "listWorktrees" | "resolveRepoRoot"
>, >,
logger, logger,
}); });
@@ -1059,6 +1061,10 @@ describe("create_agent MCP tool", () => {
expect(response.structuredContent.branchName).toBe("tool-worktree"); expect(response.structuredContent.branchName).toBe("tool-worktree");
expect(response.structuredContent.worktreePath).toContain("tool-worktree"); expect(response.structuredContent.worktreePath).toContain("tool-worktree");
expect(workspaceGitService.getSnapshot).not.toHaveBeenCalled(); expect(workspaceGitService.getSnapshot).not.toHaveBeenCalled();
expect(workspaceGitService.listWorktrees).toHaveBeenCalledWith(repoDir, {
force: true,
reason: "mcp:create-worktree",
});
expect(setupContinuations).toEqual([undefined]); expect(setupContinuations).toEqual([undefined]);
expect(broadcasts).toHaveLength(1); expect(broadcasts).toHaveLength(1);
expect(broadcasts[0]).toContain("tool-worktree"); expect(broadcasts[0]).toContain("tool-worktree");
@@ -1093,6 +1099,8 @@ describe("create_agent MCP tool", () => {
const workspaceGitService = { const workspaceGitService = {
getSnapshot: vi.fn(async () => null), getSnapshot: vi.fn(async () => null),
listWorktrees: vi.fn(async () => []),
resolveRepoRoot: vi.fn(async () => repoDir),
}; };
const archiveWorkspaceRecord = vi.fn(async () => undefined); const archiveWorkspaceRecord = vi.fn(async () => undefined);
const emitWorkspaceUpdatesForWorkspaceIds = vi.fn(async () => undefined); const emitWorkspaceUpdatesForWorkspaceIds = vi.fn(async () => undefined);
@@ -1106,7 +1114,7 @@ describe("create_agent MCP tool", () => {
createPaseoWorktree: createPaseoWorktreeForMcpTest({ paseoHome, broadcasts: [] }), createPaseoWorktree: createPaseoWorktreeForMcpTest({ paseoHome, broadcasts: [] }),
workspaceGitService: workspaceGitService as unknown as Pick< workspaceGitService: workspaceGitService as unknown as Pick<
WorkspaceGitService, WorkspaceGitService,
"getSnapshot" | "listWorktrees" "getSnapshot" | "listWorktrees" | "resolveRepoRoot"
>, >,
archiveWorkspaceRecord, archiveWorkspaceRecord,
emitWorkspaceUpdatesForWorkspaceIds, emitWorkspaceUpdatesForWorkspaceIds,
@@ -1133,6 +1141,11 @@ describe("create_agent MCP tool", () => {
force: true, force: true,
reason: "archive-worktree", 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(archiveWorkspaceRecord).toHaveBeenCalledWith(created.structuredContent.worktreePath);
expect(markWorkspaceArchiving).toHaveBeenCalledWith( expect(markWorkspaceArchiving).toHaveBeenCalledWith(
[created.structuredContent.worktreePath], [created.structuredContent.worktreePath],
+10 -1
View File
@@ -1858,6 +1858,10 @@ export async function createAgentMcpServer(options: AgentMcpServerOptions): Prom
resolveDefaultBranch: mcpInput.resolveDefaultBranch, resolveDefaultBranch: mcpInput.resolveDefaultBranch,
}); });
const { worktree } = createdWorktree; const { worktree } = createdWorktree;
await options.workspaceGitService?.listWorktrees?.(repoRoot, {
force: true,
reason: "mcp:create-worktree",
});
return { return {
content: [], content: [],
@@ -1887,7 +1891,7 @@ export async function createAgentMcpServer(options: AgentMcpServerOptions): Prom
}, },
}, },
async ({ cwd, worktreePath, worktreeSlug }) => { async ({ cwd, worktreePath, worktreeSlug }) => {
const repoRoot = resolveScopedCwd(cwd, { required: true }); const resolvedCwd = resolveScopedCwd(cwd, { required: true });
if (!worktreePath && !worktreeSlug) { if (!worktreePath && !worktreeSlug) {
throw new Error("worktreePath or worktreeSlug is required"); throw new Error("worktreePath or worktreeSlug is required");
} }
@@ -1912,6 +1916,7 @@ export async function createAgentMcpServer(options: AgentMcpServerOptions): Prom
if (!options.emitSessionMessage) { if (!options.emitSessionMessage) {
throw new Error("Session message emitter is required to archive worktrees"); throw new Error("Session message emitter is required to archive worktrees");
} }
const repoRoot = await options.workspaceGitService.resolveRepoRoot(resolvedCwd);
const targetPath = const targetPath =
worktreePath ?? worktreePath ??
@@ -1948,6 +1953,10 @@ export async function createAgentMcpServer(options: AgentMcpServerOptions): Prom
requestId: "mcp:archive_worktree", requestId: "mcp:archive_worktree",
}, },
); );
await options.workspaceGitService.listWorktrees(repoRoot, {
force: true,
reason: "mcp:archive-worktree",
});
return { return {
content: [], content: [],