diff --git a/apps/desktop/src/renderer/routes/_authenticated/components/AgentHooks/hooks/useCommandWatcher/tools/create-worktree.ts b/apps/desktop/src/renderer/routes/_authenticated/components/AgentHooks/hooks/useCommandWatcher/tools/create-worktree.ts index 522e9acbc75..edd5a9ba743 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/components/AgentHooks/hooks/useCommandWatcher/tools/create-worktree.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/components/AgentHooks/hooks/useCommandWatcher/tools/create-worktree.ts @@ -14,6 +14,7 @@ const workspaceInputSchema = z.object({ }); const schema = z.object({ + projectId: z.string(), workspaces: z.array(workspaceInputSchema).min(1).max(5), }); @@ -21,6 +22,7 @@ interface CreatedWorkspace { workspaceId: string; workspaceName: string; branch: string; + worktreePath: string; wasExisting: boolean; } @@ -28,32 +30,7 @@ async function execute( params: z.infer, ctx: ToolContext, ): Promise { - // Derive projectId from current workspace or use the only available project - const workspaces = ctx.getWorkspaces(); - if (!workspaces || workspaces.length === 0) { - return { success: false, error: "No workspaces available" }; - } - - // Try to get from current workspace first - let projectId: string | null = null; - const activeWorkspaceId = ctx.getActiveWorkspaceId(); - if (activeWorkspaceId) { - const activeWorkspace = workspaces.find( - (ws) => ws.id === activeWorkspaceId, - ); - if (activeWorkspace) { - projectId = activeWorkspace.projectId; - } - } - - // Fall back to the most recently used workspace's project - if (!projectId) { - const sorted = [...workspaces].sort( - (a, b) => (b.lastOpenedAt ?? 0) - (a.lastOpenedAt ?? 0), - ); - projectId = sorted[0].projectId; - } - + const { projectId } = params; const created: CreatedWorkspace[] = []; const errors: BulkItemError[] = []; @@ -70,6 +47,7 @@ async function execute( workspaceId: result.workspace.id, workspaceName: result.workspace.name, branch: result.workspace.branch, + worktreePath: result.worktreePath, wasExisting: result.wasExisting, }); } catch (error) { diff --git a/apps/desktop/src/renderer/routes/_authenticated/components/AgentHooks/hooks/useCommandWatcher/tools/start-claude-session.ts b/apps/desktop/src/renderer/routes/_authenticated/components/AgentHooks/hooks/useCommandWatcher/tools/start-claude-session.ts index 6caa51a764b..4cf9c3844e9 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/components/AgentHooks/hooks/useCommandWatcher/tools/start-claude-session.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/components/AgentHooks/hooks/useCommandWatcher/tools/start-claude-session.ts @@ -5,50 +5,33 @@ import type { CommandResult, ToolContext, ToolDefinition } from "./types"; const schema = z.object({ command: z.string(), name: z.string(), + workspaceId: z.string(), }); async function execute( params: z.infer, ctx: ToolContext, ): Promise { - // 1. Derive projectId from current workspace or most recent const workspaces = ctx.getWorkspaces(); if (!workspaces || workspaces.length === 0) { return { success: false, error: "No workspaces available" }; } - let projectId: string | null = null; - const activeWorkspaceId = ctx.getActiveWorkspaceId(); - if (activeWorkspaceId) { - const activeWorkspace = workspaces.find( - (ws) => ws.id === activeWorkspaceId, - ); - if (activeWorkspace) { - projectId = activeWorkspace.projectId; - } - } - - if (!projectId) { - const sorted = [...workspaces].sort( - (a, b) => (b.lastOpenedAt ?? 0) - (a.lastOpenedAt ?? 0), - ); - projectId = sorted[0].projectId; + const workspace = workspaces.find((ws) => ws.id === params.workspaceId); + if (!workspace) { + return { + success: false, + error: `Workspace not found: ${params.workspaceId}`, + }; } try { - // 2. Create workspace - const result = await ctx.createWorktree.mutateAsync({ - projectId, - name: params.name, - branchName: params.name, - }); - - // 3. Append command to pending terminal setup + // Append command to pending terminal setup for the existing workspace const store = useWorkspaceInitStore.getState(); - const pending = store.pendingTerminalSetups[result.workspace.id]; + const pending = store.pendingTerminalSetups[workspace.id]; store.addPendingTerminalSetup({ - workspaceId: result.workspace.id, - projectId: pending?.projectId ?? projectId, + workspaceId: workspace.id, + projectId: pending?.projectId ?? workspace.projectId, initialCommands: [...(pending?.initialCommands ?? []), params.command], defaultPreset: pending?.defaultPreset ?? null, }); @@ -56,8 +39,8 @@ async function execute( return { success: true, data: { - workspaceId: result.workspace.id, - branch: result.workspace.branch, + workspaceId: workspace.id, + branch: workspace.branch, }, }; } catch (error) { diff --git a/packages/mcp/src/tools/devices/create-workspace/create-workspace.ts b/packages/mcp/src/tools/devices/create-workspace/create-workspace.ts index 7b53c5ba355..4df76b9931d 100644 --- a/packages/mcp/src/tools/devices/create-workspace/create-workspace.ts +++ b/packages/mcp/src/tools/devices/create-workspace/create-workspace.ts @@ -25,6 +25,7 @@ export function register(server: McpServer) { "Create one or more workspaces (git worktrees) on a device. Use this when the user asks to create worktrees or workspaces.", inputSchema: { deviceId: z.string().describe("Target device ID"), + projectId: z.string().describe("Project ID to create workspaces in"), workspaces: z .array(workspaceInputSchema) .min(1) @@ -35,13 +36,19 @@ export function register(server: McpServer) { async (args, extra) => { const ctx = getMcpContext(extra); const deviceId = args.deviceId as string; + const projectId = args.projectId as string; const workspaces = args.workspaces as z.infer< typeof workspaceInputSchema >[]; - if (!deviceId) { + if (!deviceId || !projectId) { return { - content: [{ type: "text", text: "Error: deviceId is required" }], + content: [ + { + type: "text", + text: "Error: deviceId and projectId are required", + }, + ], isError: true, }; } @@ -50,7 +57,7 @@ export function register(server: McpServer) { ctx, deviceId, tool: "create_workspace", - params: { workspaces }, + params: { projectId, workspaces }, }); }, ); diff --git a/packages/mcp/src/tools/devices/start-claude-session/start-claude-session.ts b/packages/mcp/src/tools/devices/start-claude-session/start-claude-session.ts index ca47be5d7c1..fc6743c920c 100644 --- a/packages/mcp/src/tools/devices/start-claude-session/start-claude-session.ts +++ b/packages/mcp/src/tools/devices/start-claude-session/start-claude-session.ts @@ -81,7 +81,19 @@ async function fetchTask({ return task ?? null; } -function validateArgs(args: Record): { +function validateSessionArgs(args: Record): { + deviceId: string; + taskId: string; + workspaceId: string; +} | null { + const deviceId = args.deviceId as string; + const taskId = args.taskId as string; + const workspaceId = args.workspaceId as string; + if (!deviceId || !taskId || !workspaceId) return null; + return { deviceId, taskId, workspaceId }; +} + +function validateSubagentArgs(args: Record): { deviceId: string; taskId: string; } | null { @@ -91,9 +103,22 @@ function validateArgs(args: Record): { return { deviceId, taskId }; } -const ERROR_DEVICE_AND_TASK_REQUIRED = { +const ERROR_SESSION_ARGS_REQUIRED = { + content: [ + { + type: "text" as const, + text: "Error: deviceId, taskId, and workspaceId are required", + }, + ], + isError: true, +}; + +const ERROR_SUBAGENT_ARGS_REQUIRED = { content: [ - { type: "text" as const, text: "Error: deviceId and taskId are required" }, + { + type: "text" as const, + text: "Error: deviceId and taskId are required", + }, ], isError: true, }; @@ -108,16 +133,21 @@ export function register(server: McpServer) { "start_claude_session", { description: - "Start an autonomous Claude Code session for a task. Creates a new workspace with its own git branch and launches Claude with the task context.", + "Start an autonomous Claude Code session for a task in an existing workspace. Launches Claude with the task context in the specified workspace.", inputSchema: { deviceId: z.string().describe("Target device ID"), taskId: z.string().describe("Task ID to work on"), + workspaceId: z + .string() + .describe( + "Workspace ID to run the session in (from create_workspace)", + ), }, }, async (args, extra) => { const ctx = getMcpContext(extra); - const validated = validateArgs(args); - if (!validated) return ERROR_DEVICE_AND_TASK_REQUIRED; + const validated = validateSessionArgs(args); + if (!validated) return ERROR_SESSION_ARGS_REQUIRED; const task = await fetchTask({ taskId: validated.taskId, @@ -129,7 +159,11 @@ export function register(server: McpServer) { ctx, deviceId: validated.deviceId, tool: "start_claude_session", - params: { command: buildCommand(task), name: task.slug }, + params: { + command: buildCommand(task), + name: task.slug, + workspaceId: validated.workspaceId, + }, }); }, ); @@ -146,8 +180,8 @@ export function register(server: McpServer) { }, async (args, extra) => { const ctx = getMcpContext(extra); - const validated = validateArgs(args); - if (!validated) return ERROR_DEVICE_AND_TASK_REQUIRED; + const validated = validateSubagentArgs(args); + if (!validated) return ERROR_SUBAGENT_ARGS_REQUIRED; const task = await fetchTask({ taskId: validated.taskId,