refactor: rename execution workspace to working directory

This commit is contained in:
2026-07-08 01:14:14 +09:00
parent 0cad2308cc
commit 40463b6133
28 changed files with 759 additions and 750 deletions
@@ -4,7 +4,7 @@
import { buildBrowserCreateWorkerRequest, defaultWorkerLaunchForm } from './worker-launch';
import type {
BrowserCreateWorkerResponse,
BrowserExecutionWorkspaceCreateResponse,
BrowserWorkingDirectoryCreateResponse,
ListResponse,
Worker,
WorkerLaunchOptionsResponse,
@@ -36,11 +36,11 @@
let runtimeId = $state('');
let profile = $state('builtin:coder');
let initialText = $state('');
let executionWorkspaceAllocationId = $state('');
let executionWorkspaceRepositoryId = $state('');
let executionWorkspaceSelector = $state('HEAD');
let workingDirectoryAllocationId = $state('');
let workingDirectoryRepositoryId = $state('');
let workingDirectorySelector = $state('HEAD');
let relativeCwd = $state('');
let creatingWorkspace = $state(false);
let creatingWorkingDirectory = $state(false);
$effect(() => {
if (!workspaceId) {
@@ -102,17 +102,17 @@
display_name: displayName,
profile,
initial_text: initialText,
execution_workspace_allocation_id: executionWorkspaceAllocationId,
execution_workspace_repository_id: executionWorkspaceRepositoryId,
execution_workspace_selector: executionWorkspaceSelector,
working_directory_allocation_id: workingDirectoryAllocationId,
working_directory_repository_id: workingDirectoryRepositoryId,
working_directory_selector: workingDirectorySelector,
relative_cwd: relativeCwd,
});
runtimeId = form.runtime_id;
displayName = form.display_name;
profile = form.profile;
executionWorkspaceAllocationId = form.execution_workspace_allocation_id;
executionWorkspaceRepositoryId = form.execution_workspace_repository_id;
executionWorkspaceSelector = form.execution_workspace_selector;
workingDirectoryAllocationId = form.working_directory_allocation_id;
workingDirectoryRepositoryId = form.working_directory_repository_id;
workingDirectorySelector = form.working_directory_selector;
relativeCwd = form.relative_cwd;
} catch (err) {
if (err instanceof DOMException && err.name === 'AbortError') {
@@ -122,36 +122,36 @@
}
}
async function createExecutionWorkspace() {
if (!executionWorkspaceRepositoryId) {
submitError = 'select a repository before creating an execution workspace';
async function createWorkingDirectory() {
if (!workingDirectoryRepositoryId) {
submitError = 'select a repository before creating a working directory';
return;
}
creatingWorkspace = true;
creatingWorkingDirectory = true;
submitError = null;
try {
const response = await fetch(workerApiPath('/execution-workspaces'), {
const response = await fetch(workerApiPath('/working-directories'), {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
repository_id: executionWorkspaceRepositoryId,
selector: executionWorkspaceSelector || null,
repository_id: workingDirectoryRepositoryId,
selector: workingDirectorySelector || null,
policy: { dirty_state: 'clean_point_only', cleanup: 'manual_or_worker_stop' },
}),
});
if (!response.ok) {
throw new Error(await responseErrorMessage(response, 'execution workspace create failed'));
throw new Error(await responseErrorMessage(response, 'working directory create failed'));
}
const payload = (await response.json()) as BrowserExecutionWorkspaceCreateResponse;
const items = options?.execution_workspaces ?? [];
const payload = (await response.json()) as BrowserWorkingDirectoryCreateResponse;
const items = options?.working_directories ?? [];
options = options
? { ...options, execution_workspaces: [...items.filter((item) => item.allocation_id !== payload.item.allocation_id), payload.item] }
? { ...options, working_directories: [...items.filter((item) => item.allocation_id !== payload.item.allocation_id), payload.item] }
: options;
executionWorkspaceAllocationId = payload.item.allocation_id;
workingDirectoryAllocationId = payload.item.allocation_id;
} catch (err) {
submitError = err instanceof Error ? err.message : 'execution workspace create failed';
submitError = err instanceof Error ? err.message : 'working directory create failed';
} finally {
creatingWorkspace = false;
creatingWorkingDirectory = false;
}
}
@@ -172,9 +172,9 @@
display_name: displayName,
profile,
initial_text: initialText,
execution_workspace_allocation_id: executionWorkspaceAllocationId,
execution_workspace_repository_id: executionWorkspaceRepositoryId,
execution_workspace_selector: executionWorkspaceSelector,
working_directory_allocation_id: workingDirectoryAllocationId,
working_directory_repository_id: workingDirectoryRepositoryId,
working_directory_selector: workingDirectorySelector,
relative_cwd: relativeCwd,
})),
});
@@ -251,22 +251,22 @@
{/if}
</select>
</label>
<fieldset class="worker-execution-workspace">
<legend>Execution workspace</legend>
<fieldset class="worker-working-directory">
<legend>Working directory</legend>
<label>
<span>Allocation</span>
<select bind:value={executionWorkspaceAllocationId}>
<select bind:value={workingDirectoryAllocationId}>
<option value="">No allocation selected</option>
{#each options?.execution_workspaces ?? [] as workspace}
<option value={workspace.allocation_id} disabled={workspace.status !== 'active'}>
{workspace.repository_id} · {workspace.requested_selector ?? 'HEAD'} · {workspace.resolved_commit.slice(0, 12)} · {workspace.status}
{#each options?.working_directories ?? [] as directory}
<option value={directory.allocation_id} disabled={directory.status !== 'active'}>
{directory.repository_id} · {directory.requested_selector ?? 'HEAD'} · {directory.resolved_commit.slice(0, 12)} · {directory.status}
</option>
{/each}
</select>
</label>
<label>
<span>Repository for new allocation</span>
<select bind:value={executionWorkspaceRepositoryId}>
<select bind:value={workingDirectoryRepositoryId}>
{#if options?.repositories.length}
{#each options.repositories as repository}
<option value={repository.id}>{repository.display_name}</option>
@@ -278,10 +278,10 @@
</label>
<label>
<span>Selector</span>
<input bind:value={executionWorkspaceSelector} autocomplete="off" placeholder="HEAD" />
<input bind:value={workingDirectorySelector} autocomplete="off" placeholder="HEAD" />
</label>
<button type="button" disabled={creatingWorkspace || !executionWorkspaceRepositoryId} onclick={() => void createExecutionWorkspace()}>
{creatingWorkspace ? 'Allocating…' : 'Create execution workspace'}
<button type="button" disabled={creatingWorkingDirectory || !workingDirectoryRepositoryId} onclick={() => void createWorkingDirectory()}>
{creatingWorkingDirectory ? 'Allocating…' : 'Create working directory'}
</button>
<label>
<span>Relative cwd</span>
@@ -298,7 +298,7 @@
{#if submitError}
<p class="section-state error">{submitError}</p>
{/if}
<button type="submit" disabled={submitting || !runtimeId || !profile || !executionWorkspaceAllocationId}>
<button type="submit" disabled={submitting || !runtimeId || !profile || !workingDirectoryAllocationId}>
{submitting ? 'Starting…' : 'Start Coding Worker'}
</button>
</form>
@@ -322,7 +322,7 @@
</span>
<span class="item-meta">
{worker.role ? `${worker.role} · ` : ''}{worker.state} · {worker.status} · 🖥 {worker.host_id}
{worker.execution_workspace ? ` · ws:${worker.execution_workspace.repository_id}@${worker.execution_workspace.resolved_commit.slice(0, 8)}` : ''}
{worker.working_directory ? ` · wd:${worker.working_directory.repository_id}@${worker.working_directory.resolved_commit.slice(0, 8)}` : ''}
</span>
</a>
</li>
@@ -1,8 +1,8 @@
import type {
Event as PodProtocolEvent,
Method as PodProtocolMethod,
Segment as PodProtocolSegment
} from '$lib/generated/protocol';
Segment as PodProtocolSegment,
} from "$lib/generated/protocol";
export type { PodProtocolEvent, PodProtocolMethod, PodProtocolSegment };
@@ -88,11 +88,11 @@ export type Worker = {
last_seen_at?: string | null;
implementation: { kind: string; display_hint: string };
capabilities: WorkerCapabilities;
execution_workspace?: ExecutionWorkspaceSummary | null;
working_directory?: WorkingDirectorySummary | null;
diagnostics: Diagnostic[];
};
export type WorkerOperationState = 'accepted' | 'unsupported' | 'rejected';
export type WorkerOperationState = "accepted" | "unsupported" | "rejected";
export type WorkerLaunchRuntimeOption = {
runtime_id: string;
@@ -109,13 +109,13 @@ export type WorkerLaunchProfileCandidate = {
description: string;
};
export type ExecutionWorkspaceRepositoryOption = {
export type WorkingDirectoryRepositoryOption = {
id: string;
display_name: string;
default_selector?: string | null;
};
export type ExecutionWorkspaceSummary = {
export type WorkingDirectorySummary = {
allocation_id: string;
repository_id: string;
requested_selector?: string | null;
@@ -132,29 +132,29 @@ export type ExecutionWorkspaceSummary = {
};
};
export type BrowserExecutionWorkspaceCreateResponse = {
export type BrowserWorkingDirectoryCreateResponse = {
workspace_id: string;
item: ExecutionWorkspaceSummary;
item: WorkingDirectorySummary;
diagnostics: Diagnostic[];
};
export type BrowserExecutionWorkspaceListResponse = {
export type BrowserWorkingDirectoryListResponse = {
workspace_id: string;
items: ExecutionWorkspaceSummary[];
items: WorkingDirectorySummary[];
diagnostics: Diagnostic[];
};
export type BrowserWorkerExecutionWorkspaceSelection = {
export type BrowserWorkerWorkingDirectorySelection = {
allocation_id: string;
relative_cwd?: string | null;
};
export type BrowserExecutionWorkspaceCreateRequest = {
export type BrowserWorkingDirectoryCreateRequest = {
repository_id: string;
selector?: string | null;
policy?: {
dirty_state?: 'clean_point_only';
cleanup?: 'manual_or_worker_stop';
dirty_state?: "clean_point_only";
cleanup?: "manual_or_worker_stop";
};
};
@@ -162,8 +162,8 @@ export type WorkerLaunchOptionsResponse = {
workspace_id: string;
runtimes: WorkerLaunchRuntimeOption[];
profiles: WorkerLaunchProfileCandidate[];
repositories: ExecutionWorkspaceRepositoryOption[];
execution_workspaces: ExecutionWorkspaceSummary[];
repositories: WorkingDirectoryRepositoryOption[];
working_directories: WorkingDirectorySummary[];
diagnostics: Diagnostic[];
};
@@ -187,7 +187,7 @@ export type WorkerInputResult = {
export type WorkerTranscriptItem = {
sequence: number;
role: 'user' | 'assistant' | 'system' | string;
role: "user" | "assistant" | "system" | string;
content: string;
event_id: number;
};
@@ -218,8 +218,8 @@ export type ClientWorkerEventWsDiagnostic = {
};
export type ClientWorkerEventWsFrame =
| { kind: 'event'; envelope: ClientWorkerEventWsEnvelope }
| { kind: 'diagnostic'; diagnostic: ClientWorkerEventWsDiagnostic };
| { kind: "event"; envelope: ClientWorkerEventWsEnvelope }
| { kind: "diagnostic"; diagnostic: ClientWorkerEventWsDiagnostic };
export type ListResponse<T> = {
workspace_id: string;
@@ -349,13 +349,13 @@ export type ObjectiveListResponse = {
};
export type CompanionState =
| 'ready'
| 'busy'
| 'error'
| 'timeout'
| 'cancelled'
| 'accepted'
| 'rejected';
| "ready"
| "busy"
| "error"
| "timeout"
| "cancelled"
| "accepted"
| "rejected";
export type CompanionTransportSummary = {
kind: string;
@@ -372,7 +372,7 @@ export type CompanionStatusResponse = {
export type CompanionTranscriptItem = {
sequence: number;
role: 'user' | 'assistant' | 'system' | string;
role: "user" | "assistant" | "system" | string;
content: string;
created_at: string;
source: string;
@@ -1,5 +1,8 @@
import { buildBrowserCreateWorkerRequest, defaultWorkerLaunchForm } from './worker-launch.ts';
import type { WorkerLaunchOptionsResponse } from './types.ts';
import {
buildBrowserCreateWorkerRequest,
defaultWorkerLaunchForm,
} from "./worker-launch.ts";
import type { WorkerLaunchOptionsResponse } from "./types.ts";
declare const Deno: {
test(name: string, fn: () => Promise<void> | void): void;
@@ -14,89 +17,93 @@ function assertEquals<T>(actual: T, expected: T): void {
}
const options: WorkerLaunchOptionsResponse = {
workspace_id: 'workspace',
workspace_id: "workspace",
runtimes: [
{
runtime_id: 'remote',
display_name: 'Remote',
status: 'active',
runtime_id: "remote",
display_name: "Remote",
status: "active",
can_spawn_worker: true,
built_in: false,
diagnostics: [],
},
{
runtime_id: 'embedded',
display_name: 'Embedded',
status: 'active',
runtime_id: "embedded",
display_name: "Embedded",
status: "active",
can_spawn_worker: true,
built_in: false,
diagnostics: [],
},
],
profiles: [
{ id: 'builtin:companion', label: 'Companion', description: 'chat' },
{ id: 'builtin:coder', label: 'Coder', description: 'code' },
{ id: "builtin:companion", label: "Companion", description: "chat" },
{ id: "builtin:coder", label: "Coder", description: "code" },
],
repositories: [
{ id: 'repo', display_name: 'Repo', default_selector: 'HEAD' },
{ id: "repo", display_name: "Repo", default_selector: "HEAD" },
],
execution_workspaces: [
working_directories: [
{
allocation_id: 'alloc-1-repo',
repository_id: 'repo',
requested_selector: 'HEAD',
materializer_kind: 'local_git_worktree',
dirty_state_policy: 'clean_point_only',
resolved_commit: '0123456789abcdef',
status: 'active',
cleanup_policy: 'manual_or_worker_stop',
cleanup_target: { kind: 'git_worktree', allocation_id: 'alloc-1-repo', repository_id: 'repo' },
allocation_id: "alloc-1-repo",
repository_id: "repo",
requested_selector: "HEAD",
materializer_kind: "local_git_worktree",
dirty_state_policy: "clean_point_only",
resolved_commit: "0123456789abcdef",
status: "active",
cleanup_policy: "manual_or_worker_stop",
cleanup_target: {
kind: "git_worktree",
allocation_id: "alloc-1-repo",
repository_id: "repo",
},
},
],
diagnostics: [],
};
Deno.test('defaultWorkerLaunchForm chooses active runtime, coder profile, repository, and workspace', () => {
Deno.test("defaultWorkerLaunchForm chooses active runtime, coder profile, repository, and working directory", () => {
const form = defaultWorkerLaunchForm(options, {
runtime_id: '',
display_name: '',
profile: '',
initial_text: 'hello',
execution_workspace_allocation_id: '',
execution_workspace_repository_id: '',
execution_workspace_selector: '',
relative_cwd: '',
runtime_id: "",
display_name: "",
profile: "",
initial_text: "hello",
working_directory_allocation_id: "",
working_directory_repository_id: "",
working_directory_selector: "",
relative_cwd: "",
});
assertEquals(form.runtime_id, 'remote');
assertEquals(form.display_name, 'Coding Worker');
assertEquals(form.profile, 'builtin:coder');
assertEquals(form.initial_text, 'hello');
assertEquals(form.execution_workspace_allocation_id, 'alloc-1-repo');
assertEquals(form.execution_workspace_repository_id, 'repo');
assertEquals(form.execution_workspace_selector, 'HEAD');
assertEquals(form.runtime_id, "remote");
assertEquals(form.display_name, "Coding Worker");
assertEquals(form.profile, "builtin:coder");
assertEquals(form.initial_text, "hello");
assertEquals(form.working_directory_allocation_id, "alloc-1-repo");
assertEquals(form.working_directory_repository_id, "repo");
assertEquals(form.working_directory_selector, "HEAD");
});
Deno.test('buildBrowserCreateWorkerRequest sends allocation id and relative cwd only', () => {
Deno.test("buildBrowserCreateWorkerRequest sends allocation id and relative cwd only", () => {
const request = buildBrowserCreateWorkerRequest({
runtime_id: 'embedded',
display_name: 'Worker',
profile: 'builtin:coder',
initial_text: 'go',
execution_workspace_allocation_id: 'alloc-1-repo',
execution_workspace_repository_id: 'repo',
execution_workspace_selector: 'main',
relative_cwd: 'crates/yoi',
runtime_id: "embedded",
display_name: "Worker",
profile: "builtin:coder",
initial_text: "go",
working_directory_allocation_id: "alloc-1-repo",
working_directory_repository_id: "repo",
working_directory_selector: "main",
relative_cwd: "crates/yoi",
});
assertEquals(request, {
runtime_id: 'embedded',
display_name: 'Worker',
profile: 'builtin:coder',
initial_text: 'go',
execution_workspace: {
allocation_id: 'alloc-1-repo',
relative_cwd: 'crates/yoi',
runtime_id: "embedded",
display_name: "Worker",
profile: "builtin:coder",
initial_text: "go",
working_directory: {
allocation_id: "alloc-1-repo",
relative_cwd: "crates/yoi",
},
});
});
@@ -1,13 +1,16 @@
import type { BrowserWorkerExecutionWorkspaceSelection, WorkerLaunchOptionsResponse } from './types';
import type {
BrowserWorkerWorkingDirectorySelection,
WorkerLaunchOptionsResponse,
} from "./types";
export type WorkerLaunchFormState = {
runtime_id: string;
display_name: string;
profile: string;
initial_text: string;
execution_workspace_allocation_id: string;
execution_workspace_repository_id: string;
execution_workspace_selector: string;
working_directory_allocation_id: string;
working_directory_repository_id: string;
working_directory_selector: string;
relative_cwd: string;
};
@@ -16,37 +19,51 @@ export type BrowserCreateWorkerRequest = {
display_name: string;
profile: string;
initial_text: string;
execution_workspace?: BrowserWorkerExecutionWorkspaceSelection;
working_directory?: BrowserWorkerWorkingDirectorySelection;
};
export function defaultWorkerLaunchForm(
options: WorkerLaunchOptionsResponse | null,
current: WorkerLaunchFormState,
): WorkerLaunchFormState {
const preferredRuntime = options?.runtimes.find((runtime) => runtime.can_spawn_worker && runtime.status === 'active')
?? options?.runtimes.find((runtime) => runtime.can_spawn_worker)
?? options?.runtimes[0];
const preferredProfile = options?.profiles.find((candidate) => candidate.id === 'builtin:coder')
?? options?.profiles[0];
const preferredExecutionWorkspace = options?.execution_workspaces.find((workspace) => workspace.status === 'active')
?? options?.execution_workspaces[0];
const preferredRepository = options?.repositories.find((repository) => repository.id === current.execution_workspace_repository_id)
?? options?.repositories[0];
const preferredRuntime =
options?.runtimes.find((runtime) =>
runtime.can_spawn_worker && runtime.status === "active"
) ??
options?.runtimes.find((runtime) => runtime.can_spawn_worker) ??
options?.runtimes[0];
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 preferredRepository =
options?.repositories.find((repository) =>
repository.id === current.working_directory_repository_id
) ??
options?.repositories[0];
return {
runtime_id: current.runtime_id || preferredRuntime?.runtime_id || '',
display_name: current.display_name || 'Coding Worker',
profile: options?.profiles.some((candidate) => candidate.id === current.profile)
? current.profile
: preferredProfile?.id || '',
runtime_id: current.runtime_id || preferredRuntime?.runtime_id || "",
display_name: current.display_name || "Coding Worker",
profile:
options?.profiles.some((candidate) => candidate.id === current.profile)
? current.profile
: preferredProfile?.id || "",
initial_text: current.initial_text,
execution_workspace_allocation_id: options?.execution_workspaces.some(
(workspace) => workspace.allocation_id === current.execution_workspace_allocation_id,
)
? current.execution_workspace_allocation_id
: preferredExecutionWorkspace?.allocation_id || '',
execution_workspace_repository_id: current.execution_workspace_repository_id || preferredRepository?.id || '',
execution_workspace_selector: current.execution_workspace_selector || preferredRepository?.default_selector || 'HEAD',
working_directory_allocation_id: options?.working_directories.some(
(directory) =>
directory.allocation_id === current.working_directory_allocation_id,
)
? current.working_directory_allocation_id
: preferredWorkingDirectory?.allocation_id || "",
working_directory_repository_id: current.working_directory_repository_id ||
preferredRepository?.id || "",
working_directory_selector: current.working_directory_selector ||
preferredRepository?.default_selector || "HEAD",
relative_cwd: current.relative_cwd,
};
}
@@ -60,13 +77,13 @@ export function buildBrowserCreateWorkerRequest(
profile: form.profile,
initial_text: form.initial_text,
};
if (form.execution_workspace_allocation_id) {
request.execution_workspace = {
allocation_id: form.execution_workspace_allocation_id,
if (form.working_directory_allocation_id) {
request.working_directory = {
allocation_id: form.working_directory_allocation_id,
};
const relativeCwd = form.relative_cwd.trim();
if (relativeCwd) {
request.execution_workspace.relative_cwd = relativeCwd;
request.working_directory.relative_cwd = relativeCwd;
}
}
return request;