Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
import { z } from "zod";
import type { CommandResult, ToolContext, ToolDefinition } from "./types";
import type {
BulkItemError,
CommandResult,
ToolContext,
ToolDefinition,
} from "./types";
import { buildBulkResult } from "./types";

const schema = z.object({
const workspaceInputSchema = z.object({
name: z.string().optional(),
branchName: z.string().optional(),
baseBranch: z.string().optional(),
});

const schema = z.object({
workspaces: z.array(workspaceInputSchema).min(1).max(5),
});

interface CreatedWorkspace {
workspaceId: string;
workspaceName: string;
branch: string;
}

async function execute(
params: z.infer<typeof schema>,
ctx: ToolContext,
Expand Down Expand Up @@ -37,29 +53,41 @@ async function execute(
projectId = sorted[0].projectId;
}

try {
const result = await ctx.createWorktree.mutateAsync({
projectId,
name: params.name,
branchName: params.branchName,
baseBranch: params.baseBranch,
});
const created: CreatedWorkspace[] = [];
const errors: BulkItemError[] = [];

for (const [i, input] of params.workspaces.entries()) {
try {
const result = await ctx.createWorktree.mutateAsync({
projectId,
name: input.name,
branchName: input.branchName,
baseBranch: input.baseBranch,
});

return {
success: true,
data: {
created.push({
workspaceId: result.workspace.id,
workspaceName: result.workspace.name,
branch: result.workspace.branch,
},
};
} catch (error) {
return {
success: false,
error:
error instanceof Error ? error.message : "Failed to create workspace",
};
});
} catch (error) {
errors.push({
index: i,
name: input.name,
branchName: input.branchName,
error:
error instanceof Error ? error.message : "Failed to create workspace",
});
}
}

return buildBulkResult({
items: created,
errors,
itemKey: "created",
allFailedMessage: "All workspace creations failed",
total: params.workspaces.length,
});
}

export const createWorkspace: ToolDefinition<typeof schema> = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,59 @@
import { z } from "zod";
import type { CommandResult, ToolContext, ToolDefinition } from "./types";
import type {
BulkItemError,
CommandResult,
ToolContext,
ToolDefinition,
} from "./types";
import { buildBulkResult } from "./types";

const schema = z.object({
workspaceId: z.string(),
workspaceIds: z.array(z.string().uuid()).min(1).max(5),
});

interface DeletedWorkspace {
workspaceId: string;
}

async function execute(
params: z.infer<typeof schema>,
ctx: ToolContext,
): Promise<CommandResult> {
try {
const result = await ctx.deleteWorkspace.mutateAsync({
id: params.workspaceId,
});
const deleted: DeletedWorkspace[] = [];
const errors: BulkItemError[] = [];

if (!result.success) {
return { success: false, error: result.error ?? "Delete failed" };
}
for (const [i, workspaceId] of params.workspaceIds.entries()) {
try {
const result = await ctx.deleteWorkspace.mutateAsync({
id: workspaceId,
});

return { success: true, data: { workspaceId: params.workspaceId } };
} catch (error) {
return {
success: false,
error:
error instanceof Error ? error.message : "Failed to delete workspace",
};
if (!result.success) {
errors.push({
index: i,
workspaceId,
error: result.error ?? "Delete failed",
});
} else {
deleted.push({ workspaceId });
}
} catch (error) {
errors.push({
index: i,
workspaceId,
error:
error instanceof Error ? error.message : "Failed to delete workspace",
});
}
}

return buildBulkResult({
items: deleted,
errors,
itemKey: "deleted",
allFailedMessage: "All workspace deletions failed",
total: params.workspaceIds.length,
});
}

export const deleteWorkspace: ToolDefinition<typeof schema> = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { startClaudeSession } from "./start-claude-session";
import { startClaudeSubagent } from "./start-claude-subagent";
import { switchWorkspace } from "./switch-workspace";
import type { CommandResult, ToolContext, ToolDefinition } from "./types";
import { updateWorkspace } from "./update-workspace";

// Registry of all available tools
// biome-ignore lint/suspicious/noExplicitAny: Tool schemas vary
Expand All @@ -21,6 +22,7 @@ const tools: ToolDefinition<any>[] = [
startClaudeSession,
startClaudeSubagent,
switchWorkspace,
updateWorkspace,
];

// Map for O(1) lookup by name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,37 @@ export interface CommandResult {
error?: string;
}

export interface BulkItemError {
index: number;
error: string;
[key: string]: unknown;
}

export function buildBulkResult<T>({
items,
errors,
itemKey,
allFailedMessage,
total,
}: {
items: T[];
errors: BulkItemError[];
itemKey: string;
allFailedMessage: string;
total: number;
}): CommandResult {
const data: Record<string, unknown> = {
[itemKey]: items,
summary: { total, succeeded: items.length, failed: errors.length },
};
if (errors.length > 0) data.errors = errors;
return {
success: items.length > 0,
data,
error: items.length === 0 ? allFailedMessage : undefined,
};
}

// Available mutations and queries passed to tool handlers
export interface ToolContext {
// Mutations
Expand All @@ -16,6 +47,9 @@ export interface ToolContext {
deleteWorkspace: ReturnType<
typeof electronTrpc.workspaces.delete.useMutation
>;
updateWorkspace: ReturnType<
typeof electronTrpc.workspaces.update.useMutation
>;
// Query helpers
refetchWorkspaces: () => Promise<unknown>;
getWorkspaces: () => SelectWorkspace[] | undefined;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { z } from "zod";
import type {
BulkItemError,
CommandResult,
ToolContext,
ToolDefinition,
} from "./types";
import { buildBulkResult } from "./types";

const workspaceUpdateSchema = z.object({
workspaceId: z.string().uuid(),
name: z.string().min(1),
});

const schema = z.object({
updates: z.array(workspaceUpdateSchema).min(1).max(5),
});

interface UpdatedWorkspace {
workspaceId: string;
name: string;
}

async function execute(
params: z.infer<typeof schema>,
ctx: ToolContext,
): Promise<CommandResult> {
const updated: UpdatedWorkspace[] = [];
const errors: BulkItemError[] = [];

for (const [i, update] of params.updates.entries()) {
try {
await ctx.updateWorkspace.mutateAsync({
id: update.workspaceId,
patch: { name: update.name },
});

updated.push({
workspaceId: update.workspaceId,
name: update.name,
});
} catch (error) {
errors.push({
index: i,
workspaceId: update.workspaceId,
error:
error instanceof Error ? error.message : "Failed to update workspace",
});
}
}

return buildBulkResult({
items: updated,
errors,
itemKey: "updated",
allFailedMessage: "All workspace updates failed",
total: params.updates.length,
});
}

export const updateWorkspace: ToolDefinition<typeof schema> = {
name: "update_workspace",
schema,
execute,
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { authClient } from "renderer/lib/auth-client";
import { electronTrpc } from "renderer/lib/electron-trpc";
import { useCreateWorkspace } from "renderer/react-query/workspaces/useCreateWorkspace";
import { useDeleteWorkspace } from "renderer/react-query/workspaces/useDeleteWorkspace";
import { useUpdateWorkspace } from "renderer/react-query/workspaces/useUpdateWorkspace";
import { navigateToWorkspace } from "renderer/routes/_authenticated/_dashboard/utils/workspace-navigation";
import { useCollections } from "renderer/routes/_authenticated/providers/CollectionsProvider/CollectionsProvider";
import { executeTool, type ToolContext } from "./tools";
Expand All @@ -24,6 +25,7 @@ export function useCommandWatcher() {
const createWorktree = useCreateWorkspace({ skipNavigation: true });
const setActive = electronTrpc.workspaces.setActive.useMutation();
const deleteWorkspace = useDeleteWorkspace();
const updateWorkspace = useUpdateWorkspace();

const { data: workspaces, refetch: refetchWorkspaces } =
electronTrpc.workspaces.getAll.useQuery();
Expand All @@ -41,6 +43,7 @@ export function useCommandWatcher() {
createWorktree,
setActive,
deleteWorkspace,
updateWorkspace,
refetchWorkspaces: async () => refetchWorkspaces(),
getWorkspaces: () => workspaces,
getProjects: () => projects,
Expand All @@ -52,6 +55,7 @@ export function useCommandWatcher() {
createWorktree,
setActive,
deleteWorkspace,
updateWorkspace,
refetchWorkspaces,
workspaces,
projects,
Expand Down
Loading