From 374d51d5e2f57eed9fa678785a5a5ea2fc4767a6 Mon Sep 17 00:00:00 2001 From: Adam Bowker Date: Thu, 28 May 2026 09:11:38 -0700 Subject: [PATCH] feat(code): add additional directories UI button --- .../0007_bake_default_directories.sql | 10 + .../db/migrations/meta/0007_snapshot.json | 559 ++++++++++++++++++ .../src/main/db/migrations/meta/_journal.json | 7 + .../src/main/services/agent/service.test.ts | 6 - apps/code/src/main/services/agent/service.ts | 21 +- .../AdditionalDirectoriesButton.tsx | 135 +++++ .../task-detail/components/TaskInput.tsx | 17 +- .../task-detail/hooks/useTaskCreation.ts | 25 +- .../src/renderer/sagas/task/task-creation.ts | 35 ++ 9 files changed, 789 insertions(+), 26 deletions(-) create mode 100644 apps/code/src/main/db/migrations/0007_bake_default_directories.sql create mode 100644 apps/code/src/main/db/migrations/meta/0007_snapshot.json create mode 100644 apps/code/src/renderer/features/folder-picker/components/AdditionalDirectoriesButton.tsx diff --git a/apps/code/src/main/db/migrations/0007_bake_default_directories.sql b/apps/code/src/main/db/migrations/0007_bake_default_directories.sql new file mode 100644 index 0000000000..ba98a0ea24 --- /dev/null +++ b/apps/code/src/main/db/migrations/0007_bake_default_directories.sql @@ -0,0 +1,10 @@ +UPDATE workspaces +SET additional_directories = ( + SELECT json_group_array(path) + FROM ( + SELECT value AS path FROM json_each(workspaces.additional_directories) + UNION + SELECT path FROM default_additional_directories + ) +) +WHERE (SELECT COUNT(*) FROM default_additional_directories) > 0; diff --git a/apps/code/src/main/db/migrations/meta/0007_snapshot.json b/apps/code/src/main/db/migrations/meta/0007_snapshot.json new file mode 100644 index 0000000000..1717f3fa80 --- /dev/null +++ b/apps/code/src/main/db/migrations/meta/0007_snapshot.json @@ -0,0 +1,559 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "6d99dc19-32d0-4955-afe3-a87c3f715375", + "prevId": "805d2ed3-331d-4ba6-8379-30f926268064", + "tables": { + "archives": { + "name": "archives", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "branch_name": { + "name": "branch_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "checkpoint_id": { + "name": "checkpoint_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "archived_at": { + "name": "archived_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "archives_workspaceId_unique": { + "name": "archives_workspaceId_unique", + "columns": ["workspace_id"], + "isUnique": true + } + }, + "foreignKeys": { + "archives_workspace_id_workspaces_id_fk": { + "name": "archives_workspace_id_workspaces_id_fk", + "tableFrom": "archives", + "tableTo": "workspaces", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "auth_preferences": { + "name": "auth_preferences", + "columns": { + "account_key": { + "name": "account_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cloud_region": { + "name": "cloud_region", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_selected_project_id": { + "name": "last_selected_project_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "auth_preferences_account_region_idx": { + "name": "auth_preferences_account_region_idx", + "columns": ["account_key", "cloud_region"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "auth_sessions": { + "name": "auth_sessions", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "refresh_token_encrypted": { + "name": "refresh_token_encrypted", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cloud_region": { + "name": "cloud_region", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "selected_project_id": { + "name": "selected_project_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope_version": { + "name": "scope_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "default_additional_directories": { + "name": "default_additional_directories", + "columns": { + "path": { + "name": "path", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "repositories": { + "name": "repositories", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "remote_url": { + "name": "remote_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_accessed_at": { + "name": "last_accessed_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "repositories_path_unique": { + "name": "repositories_path_unique", + "columns": ["path"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "suspensions": { + "name": "suspensions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "branch_name": { + "name": "branch_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "checkpoint_id": { + "name": "checkpoint_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "suspended_at": { + "name": "suspended_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "suspensions_workspaceId_unique": { + "name": "suspensions_workspaceId_unique", + "columns": ["workspace_id"], + "isUnique": true + } + }, + "foreignKeys": { + "suspensions_workspace_id_workspaces_id_fk": { + "name": "suspensions_workspace_id_workspaces_id_fk", + "tableFrom": "suspensions", + "tableTo": "workspaces", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "workspaces": { + "name": "workspaces", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "repository_id": { + "name": "repository_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "linked_branch": { + "name": "linked_branch", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pinned_at": { + "name": "pinned_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_viewed_at": { + "name": "last_viewed_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_activity_at": { + "name": "last_activity_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "additional_directories": { + "name": "additional_directories", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "workspaces_taskId_unique": { + "name": "workspaces_taskId_unique", + "columns": ["task_id"], + "isUnique": true + }, + "workspaces_repository_id_idx": { + "name": "workspaces_repository_id_idx", + "columns": ["repository_id"], + "isUnique": false + } + }, + "foreignKeys": { + "workspaces_repository_id_repositories_id_fk": { + "name": "workspaces_repository_id_repositories_id_fk", + "tableFrom": "workspaces", + "tableTo": "repositories", + "columnsFrom": ["repository_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "worktrees": { + "name": "worktrees", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "worktrees_workspaceId_unique": { + "name": "worktrees_workspaceId_unique", + "columns": ["workspace_id"], + "isUnique": true + } + }, + "foreignKeys": { + "worktrees_workspace_id_workspaces_id_fk": { + "name": "worktrees_workspace_id_workspaces_id_fk", + "tableFrom": "worktrees", + "tableTo": "workspaces", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} diff --git a/apps/code/src/main/db/migrations/meta/_journal.json b/apps/code/src/main/db/migrations/meta/_journal.json index 98745d4e45..e470e8d139 100644 --- a/apps/code/src/main/db/migrations/meta/_journal.json +++ b/apps/code/src/main/db/migrations/meta/_journal.json @@ -50,6 +50,13 @@ "when": 1777639303535, "tag": "0006_youthful_warstar", "breakpoints": true + }, + { + "idx": 7, + "version": "6", + "when": 1779062400000, + "tag": "0007_bake_default_directories", + "breakpoints": true } ] } diff --git a/apps/code/src/main/services/agent/service.test.ts b/apps/code/src/main/services/agent/service.test.ts index 8507cc6075..880a400c2e 100644 --- a/apps/code/src/main/services/agent/service.test.ts +++ b/apps/code/src/main/services/agent/service.test.ts @@ -190,11 +190,6 @@ function createMockDependencies() { appDataPath: "/mock/userData", logsPath: "/mock/logs", }, - defaultAdditionalDirectoryRepository: { - list: vi.fn(() => [] as string[]), - add: vi.fn(), - remove: vi.fn(), - }, workspaceRepository: { getAdditionalDirectories: vi.fn(() => [] as string[]), addAdditionalDirectory: vi.fn(), @@ -230,7 +225,6 @@ describe("AgentService", () => { deps.bundledResources as never, deps.appMeta as never, deps.storagePaths as never, - deps.defaultAdditionalDirectoryRepository as never, deps.workspaceRepository as never, ); }); diff --git a/apps/code/src/main/services/agent/service.ts b/apps/code/src/main/services/agent/service.ts index 1596f9ff5b..a80a85193b 100644 --- a/apps/code/src/main/services/agent/service.ts +++ b/apps/code/src/main/services/agent/service.ts @@ -45,7 +45,6 @@ import type { IStoragePaths } from "@posthog/platform/storage-paths"; import { isAuthError } from "@shared/errors"; import type { AcpMessage } from "@shared/types/session-events"; import { inject, injectable, preDestroy } from "inversify"; -import type { IDefaultAdditionalDirectoryRepository } from "../../db/repositories/default-additional-directory-repository"; import type { IWorkspaceRepository } from "../../db/repositories/workspace-repository"; import { MAIN_TOKENS } from "../../di/tokens"; import { isDevBuild } from "../../utils/env"; @@ -319,8 +318,6 @@ export class AgentService extends TypedEventEmitter { private readonly appMeta: IAppMeta, @inject(MAIN_TOKENS.StoragePaths) private readonly storagePaths: IStoragePaths, - @inject(MAIN_TOKENS.DefaultAdditionalDirectoryRepository) - private readonly defaultAdditionalDirectoryRepository: IDefaultAdditionalDirectoryRepository, @inject(MAIN_TOKENS.WorkspaceRepository) private readonly workspaceRepository: IWorkspaceRepository, ) { @@ -522,20 +519,6 @@ When creating pull requests, add the following footer at the end of the PR descr return { append: prompt }; } - private resolveAdditionalDirectories(taskId: string): string[] { - const defaults = this.defaultAdditionalDirectoryRepository.list(); - const taskScoped = - this.workspaceRepository.getAdditionalDirectories(taskId); - const seen = new Set(); - const merged: string[] = []; - for (const path of [...defaults, ...taskScoped]) { - if (!path || seen.has(path)) continue; - seen.add(path); - merged.push(path); - } - return merged; - } - async startSession(params: StartSessionInput): Promise { this.validateSessionParams(params); const config = this.toSessionConfig(params); @@ -584,7 +567,9 @@ When creating pull requests, add the following footer at the end of the PR descr const repoPath = taskId === "__preview__" ? tmpdir() : rawRepoPath; const additionalDirectories = - taskId === "__preview__" ? [] : this.resolveAdditionalDirectories(taskId); + taskId === "__preview__" + ? [] + : this.workspaceRepository.getAdditionalDirectories(taskId); if (!isRetry) { const existing = this.sessions.get(taskRunId); diff --git a/apps/code/src/renderer/features/folder-picker/components/AdditionalDirectoriesButton.tsx b/apps/code/src/renderer/features/folder-picker/components/AdditionalDirectoriesButton.tsx new file mode 100644 index 0000000000..d73b49edaa --- /dev/null +++ b/apps/code/src/renderer/features/folder-picker/components/AdditionalDirectoriesButton.tsx @@ -0,0 +1,135 @@ +import { useFolders } from "@features/folders/hooks/useFolders"; +import { Check, FolderOpen, FolderPlus } from "@phosphor-icons/react"; +import { + Button, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, + MenuLabel, +} from "@posthog/quill"; +import { trpcClient } from "@renderer/trpc"; +import { logger } from "@utils/logger"; +import { useMemo } from "react"; + +const log = logger.scope("additional-directories-button"); + +interface AdditionalDirectoriesButtonProps { + values: string[]; + onChange: (values: string[]) => void; + primaryDirectory?: string | null; + disabled?: boolean; +} + +export function AdditionalDirectoriesButton({ + values, + onChange, + primaryDirectory, + disabled, +}: AdditionalDirectoriesButtonProps) { + const { getFolderByPath, getRecentFolders, addFolder, updateLastAccessed } = + useFolders(); + const count = values.length; + const selected = useMemo(() => new Set(values), [values]); + + const folders = useMemo(() => { + const recent = getRecentFolders().filter( + (f) => f.path !== primaryDirectory, + ); + const seen = new Set(recent.map((f) => f.path)); + for (const path of values) { + if (seen.has(path)) continue; + const folder = getFolderByPath(path); + if (folder) { + recent.push(folder); + seen.add(path); + } + } + return recent; + }, [getRecentFolders, getFolderByPath, values, primaryDirectory]); + + const toggle = (path: string) => { + if (selected.has(path)) { + onChange(values.filter((p) => p !== path)); + return; + } + onChange([...values, path]); + const folder = getFolderByPath(path); + if (folder) updateLastAccessed(folder.id); + }; + + const handlePickNative = async () => { + try { + const selectedPath = await trpcClient.os.selectDirectory.query(); + if (!selectedPath) return; + if (selectedPath === primaryDirectory) return; + await addFolder(selectedPath); + if (!selected.has(selectedPath)) { + onChange([...values, selectedPath]); + } + } catch (error) { + log.error("Failed to open directory picker", { error }); + } + }; + + return ( + + + + {count > 0 && ( + +{count} + )} + + } + /> + + {folders.length > 0 && ( + <> + Additional directories + {folders.map((folder) => { + const isSelected = selected.has(folder.path); + return ( + toggle(folder.path)} + > + + + {folder.name} + + + ); + })} + + + )} + + + + Open folder... + + + + ); +} diff --git a/apps/code/src/renderer/features/task-detail/components/TaskInput.tsx b/apps/code/src/renderer/features/task-detail/components/TaskInput.tsx index f49729abd4..311af305a4 100644 --- a/apps/code/src/renderer/features/task-detail/components/TaskInput.tsx +++ b/apps/code/src/renderer/features/task-detail/components/TaskInput.tsx @@ -1,5 +1,6 @@ import { DotPatternBackground } from "@components/DotPatternBackground"; import { EnvironmentSelector } from "@features/environments/components/EnvironmentSelector"; +import { AdditionalDirectoriesButton } from "@features/folder-picker/components/AdditionalDirectoriesButton"; import { FolderPicker } from "@features/folder-picker/components/FolderPicker"; import { GitHubRepoPicker } from "@features/folder-picker/components/GitHubRepoPicker"; import { useFolders } from "@features/folders/hooks/useFolders"; @@ -469,7 +470,13 @@ export function TaskInput({ ? selectedBranch : null; - const { isCreatingTask, canSubmit, handleSubmit } = useTaskCreation({ + const { + isCreatingTask, + canSubmit, + handleSubmit, + additionalDirectories, + setAdditionalDirectories, + } = useTaskCreation({ editorRef, selectedDirectory, selectedRepository: selectedCloudRepository, @@ -731,6 +738,14 @@ export function TaskInput({ anchor={buttonGroupRef} /> + {workspaceMode !== "cloud" && ( + + )} {cloudRegion === "dev" && ( Promise; + additionalDirectories: string[]; + setAdditionalDirectories: (next: string[]) => void; } function prepareTaskInput( @@ -70,6 +73,7 @@ function prepareTaskInput( environmentId?: string | null; sandboxEnvironmentId?: string; signalReportId?: string; + additionalDirectories?: string[]; }, ): TaskCreationInput { const serializedContent = contentToXml(content).trim(); @@ -107,6 +111,10 @@ function prepareTaskInput( ? "signal_report" : undefined, signalReportId: options.signalReportId, + additionalDirectories: + options.workspaceMode === "cloud" + ? undefined + : options.additionalDirectories, }; } @@ -190,6 +198,16 @@ export function useTaskCreation({ onTaskCreated, }: UseTaskCreationOptions): UseTaskCreationReturn { const [isCreatingTask, setIsCreatingTask] = useState(false); + const trpc = useTRPC(); + const defaultAdditionalDirectoriesQuery = useQuery( + trpc.additionalDirectories.listDefaults.queryOptions(), + ); + const defaultAdditionalDirectories = + defaultAdditionalDirectoriesQuery.data ?? []; + const [additionalDirectoriesOverride, setAdditionalDirectoriesOverride] = + useState(null); + const additionalDirectories = + additionalDirectoriesOverride ?? defaultAdditionalDirectories; const { clearTaskInputReportAssociation, navigateToTask, @@ -260,6 +278,7 @@ export function useTaskCreation({ environmentId, sandboxEnvironmentId, signalReportId, + additionalDirectories, }); if (executionMode) { @@ -287,6 +306,7 @@ export function useTaskCreation({ }); if (result.success) { + setAdditionalDirectoriesOverride(null); void trackTaskCreated(input, selectedDirectory); } @@ -334,6 +354,7 @@ export function useTaskCreation({ environmentId, sandboxEnvironmentId, signalReportId, + additionalDirectories, clearTaskInputReportAssociation, invalidateTasks, navigateToTask, @@ -347,5 +368,7 @@ export function useTaskCreation({ isCreatingTask, canSubmit, handleSubmit, + additionalDirectories, + setAdditionalDirectories: setAdditionalDirectoriesOverride, }; } diff --git a/apps/code/src/renderer/sagas/task/task-creation.ts b/apps/code/src/renderer/sagas/task/task-creation.ts index 582c0b94ad..b291fb8422 100644 --- a/apps/code/src/renderer/sagas/task/task-creation.ts +++ b/apps/code/src/renderer/sagas/task/task-creation.ts @@ -60,6 +60,7 @@ export interface TaskCreationInput { cloudPrAuthorshipMode?: PrAuthorshipMode; cloudRunSource?: CloudRunSource; signalReportId?: string; + additionalDirectories?: string[]; } export interface TaskCreationOutput { @@ -204,6 +205,40 @@ export class TaskCreationSaga extends Saga< }; } + const extraDirectories = input.taskId + ? [] + : (input.additionalDirectories ?? []).filter( + (path) => path && path !== repoPath, + ); + if (extraDirectories.length > 0) { + await this.step({ + name: "additional_directories", + execute: async () => { + await Promise.all( + extraDirectories.map((path) => + trpcClient.additionalDirectories.addForTask.mutate({ + taskId: task.id, + path, + }), + ), + ); + return { taskId: task.id, paths: extraDirectories }; + }, + rollback: async ({ taskId, paths }) => { + log.info("Rolling back: removing additional directories", { taskId }); + await Promise.all( + paths.map((path) => + trpcClient.additionalDirectories.removeForTask + .mutate({ taskId, path }) + .catch((error) => { + log.warn("Failed to remove additional directory", { error }); + }), + ), + ); + }, + }); + } + const shouldStartCloudRun = workspaceMode === "cloud" && !task.latest_run; if (!hasProvisioning && !shouldStartCloudRun && this.deps.onTaskReady) {