workspace: gate workdir assignment

This commit is contained in:
2026-07-12 20:07:32 +09:00
parent 21802d68f8
commit 59b1b3df79
9 changed files with 161 additions and 33 deletions
@@ -126,6 +126,7 @@ export type WorkingDirectorySummary = {
resolved_tree?: string | null;
status: string;
cleanliness?: string | null;
primary_worker_id?: number | null;
management_kind?: "backend_managed" | "runtime_unmanaged" | string | null;
cleanup_target: {
kind: string;
@@ -53,6 +53,8 @@ const options: WorkerLaunchOptionsResponse = {
materializer_kind: "local_git_worktree",
resolved_commit: "0123456789abcdef",
status: "active",
cleanliness: "clean",
primary_worker_id: null,
cleanup_target: {
kind: "git_worktree",
working_directory_id: "wd-1-repo",
@@ -35,11 +35,16 @@ export function defaultWorkerLaunchForm(
const preferredProfile =
options?.profiles.find((candidate) => candidate.id === "builtin:coder") ??
options?.profiles[0];
const preferredWorkingDirectory =
options?.working_directories.find((directory) =>
directory.status === "active"
) ??
options?.working_directories[0];
const availableWorkingDirectories = options?.working_directories.filter((directory) =>
directory.status === "active" &&
directory.cleanliness === "clean" &&
directory.primary_worker_id == null
) ?? [];
const selectedRuntime = current.runtime_id
? options?.runtimes.find((runtime) => runtime.runtime_id === current.runtime_id)
: preferredRuntime;
const workdirlessRuntime = selectedRuntime?.working_directory_required === false;
const preferredWorkingDirectory = workdirlessRuntime ? undefined : availableWorkingDirectories[0];
const preferredRepository =
options?.repositories.find((repository) =>
repository.id === current.working_directory_repository_id
@@ -54,7 +59,7 @@ export function defaultWorkerLaunchForm(
? current.profile
: preferredProfile?.id || "",
initial_text: current.initial_text,
working_directory_id: options?.working_directories.some(
working_directory_id: !workdirlessRuntime && availableWorkingDirectories.some(
(directory) =>
directory.working_directory_id === current.working_directory_id,
)
@@ -43,7 +43,16 @@
let isNewWorkingDirectorySelected = $derived(workingDirectoryId === NEW_WORKING_DIRECTORY_VALUE);
let selectedRuntime = $derived(options?.runtimes.find((runtime) => runtime.runtime_id === runtimeId));
let selectedRuntimeAllowsNoWorkdir = $derived(selectedRuntime?.working_directory_required === false);
let hasSelectedExistingWorkdir = $derived(Boolean(workingDirectoryId && !isNewWorkingDirectorySelected));
let hasSelectedExistingWorkdir = $derived(Boolean(
workingDirectoryId && !isNewWorkingDirectorySelected && !selectedRuntimeAllowsNoWorkdir,
));
let availableWorkingDirectories = $derived(
selectedRuntimeAllowsNoWorkdir
? []
: (options?.working_directories ?? []).filter((directory) =>
directory.status === 'active' && directory.cleanliness === 'clean' && directory.primary_worker_id == null
),
);
let canStartWorker = $derived(Boolean(
runtimeId &&
profile &&
@@ -65,6 +74,13 @@
return () => controller.abort();
});
$effect(() => {
if (selectedRuntimeAllowsNoWorkdir && workingDirectoryId) {
workingDirectoryId = '';
relativeCwd = '';
}
});
async function loadLaunchOptions(signal?: AbortSignal) {
loading = true;
optionsError = null;
@@ -109,6 +125,10 @@
submitError = { message: 'select a runtime before creating a workdir', diagnostics: [] };
return;
}
if (selectedRuntimeAllowsNoWorkdir) {
submitError = { message: 'embedded Runtime does not create workdirs', diagnostics: [] };
return;
}
if (!workingDirectoryRepositoryId) {
submitError = { message: 'select a repository before creating a workdir', diagnostics: [] };
return;
@@ -124,7 +144,6 @@
runtime_id: runtimeId,
repository_id: workingDirectoryRepositoryId,
selector: workingDirectorySelector || null,
policy: { dirty_state: 'clean_point_only', cleanup: 'manual_or_worker_stop' },
}),
},
);
@@ -245,16 +264,16 @@
<span>Run at</span>
<select class="worker-inline-select wd-select" bind:value={workingDirectoryId} aria-label="Workdir">
{#if selectedRuntimeAllowsNoWorkdir}
<option value="">No workdir · embedded conversation only</option>
<option value="">No workdir</option>
{:else}
<option value="" disabled>Select workdir</option>
{#each availableWorkingDirectories as directory}
<option value={directory.working_directory_id}>
{directory.repository_id} · {directory.requested_selector ?? 'HEAD'}
</option>
{/each}
<option value={NEW_WORKING_DIRECTORY_VALUE}>New workdir…</option>
{/if}
{#each options?.working_directories ?? [] as directory}
<option value={directory.working_directory_id} disabled={directory.status !== 'active'}>
{directory.repository_id} · {directory.requested_selector ?? 'HEAD'}
</option>
{/each}
<option value={NEW_WORKING_DIRECTORY_VALUE}>New workdir…</option>
</select>
<span>in</span>
<select class="worker-inline-select runtime-select" bind:value={runtimeId} required aria-label="Runtime">