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 {
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 });
}
});
});
@@ -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],
+10 -1
View File
@@ -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: [],