feat: support multiple worker workdir attachments

This commit is contained in:
2026-09-19 23:27:57 +09:00
parent 308ae19f5e
commit 37df3ec545
52 changed files with 3715 additions and 1404 deletions
+8 -7
View File
@@ -236,6 +236,12 @@ export type SubscriptionWorkerAvailability = "observed" | "unavailable";
export type EventSubscriptionSelector = { "topic": "runtime_workers" } | { "topic": "worker_lifecycle", worker_ids: SubscriptionWorkerIds, } | { "topic": "worker_protocol", worker_id: SubscriptionWorkerId, runtime_id?: string | null, } | { "topic": "workspace_workers" } | { "topic": "workspace_workdirs" };
export type SubscriptionWorkerWorkdirAttachment = {
/**
* Stable Worker-local routing alias.
*/
alias: string, repository_key?: string | null, working_directory_id: SubscriptionWorkdirId, };
export type SubscriptionWorker = { worker_id: SubscriptionWorkerId,
/**
* Set by the Workspace Server when projecting a Runtime-owned Worker to clients.
@@ -264,14 +270,9 @@ worker_state?: WorkerStateSnapshot | null,
/**
* Runtime catalog lifecycle compatibility projection; not foreground-state authority.
*/
state: SubscriptionWorkerState, has_running_internal_workers: boolean, workspace_id?: string | null, display_name?: string | null, profile?: string | null,
/**
* Workspace-facing Repository key. Runtime producers leave this unset and
* Workspace Server projections replace `repository_id` with this field.
*/
repository_key?: string | null, working_directory_id?: SubscriptionWorkdirId | null, };
state: SubscriptionWorkerState, has_running_internal_workers: boolean, workspace_id?: string | null, display_name?: string | null, profile?: string | null, workdir_attachments?: Array<SubscriptionWorkerWorkdirAttachment>, };
export type WorkspaceSubscriptionWorkdir = { working_directory_id: SubscriptionWorkdirId, repository_key: string, state: string, primary_worker_id?: SubscriptionWorkerId | null, };
export type WorkspaceSubscriptionWorkdir = { working_directory_id: SubscriptionWorkdirId, repository_key: string, state: string, };
export type SubscriptionSnapshot = { "topic": "workers", "data": { workers: Array<SubscriptionWorker>, } } | { "topic": "worker_protocol", "data": { worker_id: SubscriptionWorkerId, events: Array<Event>, } } | { "topic": "workspace_workdirs", "data": { workdirs: Array<WorkspaceSubscriptionWorkdir>, } };
+10 -2
View File
@@ -13,9 +13,17 @@ export type WorkingDirectoryCleanupTarget = { kind: string, working_directory_id
export type WorkingDirectoryOccupancy = { runtime_id: string, worker_id: string, display_name: string, linked_at: string, };
export type WorkingDirectorySummary = { working_directory_id: string, repository_key: string, creation_selector?: string | null, creation_ref?: string | null, creation_tree?: string | null, current_selector?: string | null, current_ref?: string | null, current_tree?: string | null, observed_at_epoch_seconds?: number | null, materializer_kind: WorkingDirectoryMaterializerKind, cleanup_target?: WorkingDirectoryCleanupTarget | null, status: WorkingDirectoryStatusKind, cleanliness?: string | null, primary_worker_id?: string | null, occupied_by?: WorkingDirectoryOccupancy | null, };
export type WorkingDirectorySummary = { working_directory_id: string,
/**
* Optional human-facing label, never a routing key.
*/
display_name?: string | null, repository_key: string, creation_selector?: string | null, creation_ref?: string | null, creation_tree?: string | null, current_selector?: string | null, current_ref?: string | null, current_tree?: string | null, observed_at_epoch_seconds?: number | null, materializer_kind: WorkingDirectoryMaterializerKind, cleanup_target?: WorkingDirectoryCleanupTarget | null, status: WorkingDirectoryStatusKind, cleanliness?: string | null, occupied_by?: WorkingDirectoryOccupancy | null, };
export type WorkingDirectoryCreateRequest = { runtime_id?: string | null, repository_key: string, selector?: string | null, operation_id?: string | null, };
export type WorkingDirectoryCreateRequest = { runtime_id?: string | null,
/**
* Optional human-facing label, independent of every Worker attachment alias.
*/
display_name?: string | null, repository_key: string, selector?: string | null, operation_id?: string | null, };
export type WorkingDirectoryListResponse = { workspace_id: string, items: Array<WorkingDirectorySummary>, diagnostics: Array<Diagnostic>, };
@@ -15,11 +15,19 @@ export type WorkingDirectoryCleanupTarget = { kind: string, working_directory_id
export type RuntimeWorkingDirectoryCleanupTarget = { kind: string, working_directory_id: string, repository_id: string, };
export type RuntimeWorkingDirectorySummary = { working_directory_id: string, repository_id: string, creation_selector?: string | null, creation_ref?: string | null, creation_tree?: string | null, current_selector?: string | null, current_ref?: string | null, current_tree?: string | null, observed_at_epoch_seconds?: number | null, materializer_kind: WorkingDirectoryMaterializerKind, cleanup_target?: RuntimeWorkingDirectoryCleanupTarget | null, status: WorkingDirectoryStatusKind, cleanliness?: string | null, primary_worker_id?: string | null, occupied_by?: WorkingDirectoryOccupancy | null, };
export type RuntimeWorkingDirectorySummary = { working_directory_id: string,
/**
* Optional human-facing label, never a routing key.
*/
display_name?: string | null, repository_id: string, creation_selector?: string | null, creation_ref?: string | null, creation_tree?: string | null, current_selector?: string | null, current_ref?: string | null, current_tree?: string | null, observed_at_epoch_seconds?: number | null, materializer_kind: WorkingDirectoryMaterializerKind, cleanup_target?: RuntimeWorkingDirectoryCleanupTarget | null, status: WorkingDirectoryStatusKind, cleanliness?: string | null, occupied_by?: WorkingDirectoryOccupancy | null, };
export type WorkingDirectoryOccupancy = { runtime_id: string, worker_id: string, display_name: string, linked_at: string, };
export type WorkingDirectorySummary = { working_directory_id: string, repository_key: string, creation_selector?: string | null, creation_ref?: string | null, creation_tree?: string | null, current_selector?: string | null, current_ref?: string | null, current_tree?: string | null, observed_at_epoch_seconds?: number | null, materializer_kind: WorkingDirectoryMaterializerKind, cleanup_target?: WorkingDirectoryCleanupTarget | null, status: WorkingDirectoryStatusKind, cleanliness?: string | null, primary_worker_id?: string | null, occupied_by?: WorkingDirectoryOccupancy | null, };
export type WorkingDirectorySummary = { working_directory_id: string,
/**
* Optional human-facing label, never a routing key.
*/
display_name?: string | null, repository_key: string, creation_selector?: string | null, creation_ref?: string | null, creation_tree?: string | null, current_selector?: string | null, current_ref?: string | null, current_tree?: string | null, observed_at_epoch_seconds?: number | null, materializer_kind: WorkingDirectoryMaterializerKind, cleanup_target?: WorkingDirectoryCleanupTarget | null, status: WorkingDirectoryStatusKind, cleanliness?: string | null, occupied_by?: WorkingDirectoryOccupancy | null, };
export type WorkerWorkspaceSummary = { visibility: string, identity: string, workspace_id?: string | null, };
@@ -27,7 +35,9 @@ export type WorkerImplementationSummary = { kind: string, display_hint: string,
export type WorkerCapabilitySummary = { can_stop: boolean, can_spawn_followup: boolean, };
export type WorkerLaunchWorkerSummary = { runtime_id: string, worker_id: string, host_id: string, display_name: string, label: string, profile: string | null, singleton_key: string | null, tags: Array<string>, workspace: WorkerWorkspaceSummary, state: string, last_seen_at: string | null, pinned: boolean, retention_state: string, implementation: WorkerImplementationSummary, capabilities: WorkerCapabilitySummary, working_directory?: RuntimeWorkingDirectorySummary | null, diagnostics: Array<Diagnostic>, };
export type RuntimeWorkerWorkdirAttachmentSummary = { alias: string, working_directory: RuntimeWorkingDirectorySummary, };
export type WorkerLaunchWorkerSummary = { runtime_id: string, worker_id: string, host_id: string, display_name: string, label: string, profile: string | null, singleton_key: string | null, tags: Array<string>, workspace: WorkerWorkspaceSummary, state: string, last_seen_at: string | null, pinned: boolean, retention_state: string, implementation: WorkerImplementationSummary, capabilities: WorkerCapabilitySummary, workdir_attachments?: Array<RuntimeWorkerWorkdirAttachmentSummary>, diagnostics: Array<Diagnostic>, };
export type WorkerLaunchRuntimeOption = { runtime_id: string, display_name: string, built_in: boolean, worker_creation_available: boolean, working_directory_required: boolean, status: string, diagnostics: Array<Diagnostic>, };
@@ -37,11 +47,15 @@ export type WorkingDirectoryRepositoryOption = { repository_key: string, default
export type WorkerLaunchOptionsResponse = { workspace_id: string, runtimes: Array<WorkerLaunchRuntimeOption>, default_profile: string | null, profiles: Array<WorkerLaunchProfileCandidate>, repositories: Array<WorkingDirectoryRepositoryOption>, working_directories: Array<WorkingDirectorySummary>, diagnostics: Array<Diagnostic>, };
export type BrowserWorkerWorkingDirectorySelection = { working_directory_id: string, relative_cwd: string | null, };
export type BrowserWorkerWorkingDirectorySelection = {
/**
* Stable Worker-local routing alias.
*/
alias: string, working_directory_id: string, relative_cwd: string | null, };
export type CreateWorkspaceWorkerTicketAssignmentRequest = { ticket_id: string, operation_id: string, };
export type CreateWorkspaceWorkerRequest = { runtime_id: string, display_name: string, profile: string | null, ticket_assignment: CreateWorkspaceWorkerTicketAssignmentRequest | null, initial_submit: Array<Segment>, working_directory: BrowserWorkerWorkingDirectorySelection | null,
export type CreateWorkspaceWorkerRequest = { runtime_id: string, display_name: string, profile: string | null, ticket_assignment: CreateWorkspaceWorkerTicketAssignmentRequest | null, initial_submit: Array<Segment>, workdir_attachments: Array<BrowserWorkerWorkingDirectorySelection>,
/**
* Backend idempotency key used only for authenticated Worker-owned spawn/control.
*/
@@ -11,6 +11,7 @@ import type {
const SUMMARY_KEYS = new Set([
"working_directory_id",
"display_name",
"repository_key",
"creation_selector",
"creation_ref",
@@ -23,11 +24,11 @@ const SUMMARY_KEYS = new Set([
"cleanup_target",
"status",
"cleanliness",
"primary_worker_id",
"occupied_by",
]);
const CREATE_REQUEST_KEYS = new Set([
"runtime_id",
"display_name",
"repository_key",
"selector",
"operation_id",
@@ -84,6 +85,7 @@ export function validateWorkingDirectoryCreateRequest(
repository_key: stringField(record, "repository_key"),
};
assignOptionalString(request, record, "runtime_id");
assignOptionalString(request, record, "display_name");
assignOptionalString(request, record, "selector");
assignOptionalString(request, record, "operation_id");
return request;
@@ -124,6 +126,7 @@ export function parseWorkingDirectorySummary(
"unknown",
]),
};
assignOptionalString(summary, record, "display_name");
assignOptionalString(summary, record, "creation_selector");
assignOptionalString(summary, record, "creation_ref");
assignOptionalString(summary, record, "creation_tree");
@@ -131,7 +134,6 @@ export function parseWorkingDirectorySummary(
assignOptionalString(summary, record, "current_ref");
assignOptionalString(summary, record, "current_tree");
assignOptionalString(summary, record, "cleanliness");
assignOptionalString(summary, record, "primary_worker_id");
if (record.observed_at_epoch_seconds !== undefined) {
const observedAt = record.observed_at_epoch_seconds;
if (observedAt === null) {
@@ -139,10 +139,11 @@ Deno.test("Worker create request parser requires the complete shared request", (
{ kind: "text", content: "Implement T-565." },
{ kind: "flow", selector: "builtin:coder-review" },
],
working_directory: {
workdir_attachments: [{
alias: "source",
working_directory_id: "workdir-a",
relative_cwd: null,
},
}],
control_operation_id: null,
};
+38 -17
View File
@@ -8,6 +8,7 @@ import type {
DiagnosticSeverity,
RuntimeWorkingDirectoryCleanupTarget,
RuntimeWorkingDirectorySummary,
RuntimeWorkerWorkdirAttachmentSummary,
WorkerCapabilitySummary,
WorkerImplementationSummary,
WorkerLaunchOptionsResponse,
@@ -261,6 +262,7 @@ function runtimeWorkingDirectory(
item,
[
"working_directory_id",
"display_name",
"repository_id",
"creation_selector",
"creation_ref",
@@ -273,7 +275,6 @@ function runtimeWorkingDirectory(
"cleanup_target",
"status",
"cleanliness",
"primary_worker_id",
"occupied_by",
],
label,
@@ -318,6 +319,11 @@ function runtimeWorkingDirectory(
item.working_directory_id,
`${label}.working_directory_id`,
),
display_name: optional(
item.display_name,
`${label}.display_name`,
string,
),
repository_id: string(item.repository_id, `${label}.repository_id`),
creation_selector: optional(
item.creation_selector,
@@ -350,15 +356,25 @@ function runtimeWorkingDirectory(
),
status: status as RuntimeWorkingDirectorySummary["status"],
cleanliness: optional(item.cleanliness, `${label}.cleanliness`, string),
primary_worker_id: optional(
item.primary_worker_id,
`${label}.primary_worker_id`,
string,
),
occupied_by: occupied,
};
}
function runtimeWorkdirAttachment(
value: unknown,
label: string,
): RuntimeWorkerWorkdirAttachmentSummary {
const item = record(value, label);
exact(item, ["alias", "working_directory"], label);
return {
alias: string(item.alias, `${label}.alias`),
working_directory: runtimeWorkingDirectory(
item.working_directory,
`${label}.working_directory`,
),
};
}
function workerSummary(
value: unknown,
label: string,
@@ -382,7 +398,7 @@ function workerSummary(
"retention_state",
"implementation",
"capabilities",
"working_directory",
"workdir_attachments",
"diagnostics",
],
label,
@@ -406,11 +422,13 @@ function workerSummary(
`${label}.implementation`,
),
capabilities: capabilitySummary(item.capabilities, `${label}.capabilities`),
working_directory: optional(
item.working_directory,
`${label}.working_directory`,
runtimeWorkingDirectory,
),
workdir_attachments: item.workdir_attachments === undefined
? undefined
: array(
item.workdir_attachments,
`${label}.workdir_attachments`,
runtimeWorkdirAttachment,
),
diagnostics: array(item.diagnostics, `${label}.diagnostics`, diagnostic),
};
}
@@ -464,8 +482,9 @@ function workingDirectorySelection(
label: string,
): BrowserWorkerWorkingDirectorySelection {
const item = record(value, label);
exact(item, ["working_directory_id", "relative_cwd"], label);
exact(item, ["alias", "working_directory_id", "relative_cwd"], label);
return {
alias: string(item.alias, `${label}.alias`),
working_directory_id: string(
item.working_directory_id,
`${label}.working_directory_id`,
@@ -638,7 +657,7 @@ export function parseCreateWorkspaceWorkerRequest(
"profile",
"ticket_assignment",
"initial_submit",
"working_directory",
"workdir_attachments",
"control_operation_id",
],
"Worker create request",
@@ -651,9 +670,11 @@ export function parseCreateWorkspaceWorkerRequest(
? null
: ticketAssignment(item.ticket_assignment, "ticket_assignment"),
initial_submit: array(item.initial_submit, "initial_submit", segment),
working_directory: item.working_directory === null
? null
: workingDirectorySelection(item.working_directory, "working_directory"),
workdir_attachments: array(
item.workdir_attachments,
"workdir_attachments",
workingDirectorySelection,
),
control_operation_id: nullableString(
item.control_operation_id,
"control_operation_id",
@@ -37,6 +37,14 @@
Math.max(0, workers.length - COLLAPSED_WORKER_COUNT),
);
function workdirMeta(worker: SidebarWorker): string {
const attachments = worker.workdir_attachments;
if (attachments.length === 0) return '—';
return attachments
.map((attachment) => `${attachment.alias}: ${attachment.repository_key ?? '—'}・${attachment.working_directory_id}`)
.join(', ');
}
function workerKey(worker: SidebarWorker): string {
return `${worker.runtime_id}:${worker.worker_id}`;
}
@@ -190,7 +198,7 @@
</span>
<span class="worker-nav-label">{label}</span>
<small class="worker-nav-meta">
{worker.repository_key ?? '—'}・{worker.working_directory_id ?? '—'}
{workdirMeta(worker)}
</small>
</a>
<button
@@ -89,6 +89,11 @@ export type WorkerCapabilities = {
can_spawn_followup: boolean;
};
export type WorkerWorkdirAttachment = {
alias: string;
working_directory: WorkingDirectorySummary;
};
export type Worker = {
runtime_id: string;
worker_id: string;
@@ -107,7 +112,7 @@ export type Worker = {
last_seen_at?: string | null;
implementation: { kind: string; display_hint: string };
capabilities: WorkerCapabilities;
working_directory?: WorkingDirectorySummary | null;
workdir_attachments?: WorkerWorkdirAttachment[];
diagnostics: Diagnostic[];
};
@@ -7,13 +7,14 @@ import type {
} from "./types";
type FetchFn = typeof fetch;
type WorkerActionTarget = Pick<Worker, "runtime_id" | "worker_id" | "state">;
type WorkerLifecycleResponse = {
state: string;
diagnostics?: Diagnostic[];
};
function workerPath(workspaceId: string, worker: Worker): string {
function workerPath(workspaceId: string, worker: WorkerActionTarget): string {
return workspaceApiPath(
workspaceId,
`/runtimes/${encodeURIComponent(worker.runtime_id)}/workers/${
@@ -45,13 +46,13 @@ function diagnosticMessage(
fallback;
}
export function canDeleteSidebarWorker(worker: Worker): boolean {
export function canDeleteSidebarWorker(worker: WorkerActionTarget): boolean {
return worker.state === "stopped";
}
export async function stopSidebarWorker(
workspaceId: string,
worker: Worker,
worker: WorkerActionTarget,
fetchFn: FetchFn = fetch,
): Promise<void> {
const response = await fetchFn(`${workerPath(workspaceId, worker)}/stop`, {
@@ -71,7 +72,7 @@ export async function stopSidebarWorker(
export async function deleteSidebarWorker(
workspaceId: string,
worker: Worker,
worker: WorkerActionTarget,
fetchFn: FetchFn = fetch,
): Promise<void> {
const runtimePath = `/runtimes/${encodeURIComponent(worker.runtime_id)}`;
@@ -57,7 +57,6 @@ const options: WorkerLaunchOptionsResponse = {
materializer_kind: "runtime_git_clone",
status: "active",
cleanliness: "clean",
primary_worker_id: null,
cleanup_target: {
kind: "runtime_git_clone",
working_directory_id: "wd-1-repo",
@@ -180,7 +179,7 @@ Deno.test("defaultWorkerLaunchForm preserves a Ticket repository target", () =>
assertEquals(form.working_directory_selector, "work/ticket");
});
Deno.test("buildCreateWorkspaceWorkerRequest sends working_directory id and relative cwd only", () => {
Deno.test("buildCreateWorkspaceWorkerRequest sends one aliased attachment", () => {
const request = buildCreateWorkspaceWorkerRequest({
runtime_id: "embedded",
display_name: "Worker",
@@ -198,10 +197,11 @@ Deno.test("buildCreateWorkspaceWorkerRequest sends working_directory id and rela
profile: "builtin:coder",
ticket_assignment: null,
initial_submit: [{ kind: "text", content: "go" }],
working_directory: {
workdir_attachments: [{
alias: "workdir",
working_directory_id: "wd-1-repo",
relative_cwd: "crates/yoi",
},
}],
control_operation_id: null,
});
});
@@ -221,7 +221,7 @@ Deno.test("buildCreateWorkspaceWorkerRequest sends no initial segments for an em
assertEquals(request.initial_submit, []);
});
Deno.test("buildCreateWorkspaceWorkerRequest emits null for embedded no-workdir launches", () => {
Deno.test("buildCreateWorkspaceWorkerRequest emits an empty attachment list for workdirless launches", () => {
const request = buildCreateWorkspaceWorkerRequest({
runtime_id: "embedded",
display_name: "Worker",
@@ -239,7 +239,7 @@ Deno.test("buildCreateWorkspaceWorkerRequest emits null for embedded no-workdir
profile: "builtin:companion",
ticket_assignment: null,
initial_submit: [{ kind: "text", content: "chat" }],
working_directory: null,
workdir_attachments: [],
control_operation_id: null,
});
});
@@ -31,7 +31,6 @@ export function defaultWorkerLaunchForm(
options?.working_directories.filter((directory) =>
directory.status === "active" &&
directory.cleanliness === "clean" &&
directory.primary_worker_id == null &&
directory.occupied_by == null
) ?? [];
const selectedRuntime = current.runtime_id
@@ -97,12 +96,13 @@ export function buildCreateWorkspaceWorkerRequest(
initial_submit: initialMessage
? [{ kind: "text", content: form.initial_text }]
: [],
working_directory: form.working_directory_id
? {
workdir_attachments: form.working_directory_id
? [{
alias: "workdir",
working_directory_id: form.working_directory_id,
relative_cwd: form.relative_cwd.trim() || null,
}
: null,
}]
: [],
control_operation_id: null,
});
}
@@ -32,7 +32,7 @@ function worker(
workspace_id: 'workspace-test',
display_name: null,
profile: null,
working_directory_id: null,
workdir_attachments: [],
};
}
@@ -1,5 +1,8 @@
import { readable, type Readable } from 'svelte/store';
import type { SubscriptionWorker } from '$lib/generated/protocol';
import type {
SubscriptionWorker,
SubscriptionWorkerWorkdirAttachment,
} from '$lib/generated/protocol';
import { workspaceMultiplexer } from '$lib/workspace/multiplexer';
import {
applyWorkspaceWorkersFrame,
@@ -9,9 +12,8 @@ import { liveWorkerState } from './worker-state';
import { compareWorkersForSidebar } from './workers';
import type { Worker } from './types';
export type SidebarWorker = Worker & {
repository_key: string | null;
working_directory_id: string | null;
export type SidebarWorker = Omit<Worker, 'workdir_attachments'> & {
workdir_attachments: SubscriptionWorkerWorkdirAttachment[];
has_running_internal_workers: boolean;
};
@@ -103,10 +105,8 @@ function projectWorker(worker: SubscriptionWorker): SidebarWorker {
can_stop: worker.availability !== 'unavailable' && worker.state !== 'stopped',
can_spawn_followup: false,
},
repository_key: worker.repository_key ?? null,
working_directory_id: worker.working_directory_id ?? null,
workdir_attachments: worker.workdir_attachments ?? [],
has_running_internal_workers: worker.has_running_internal_workers,
working_directory: null,
diagnostics: [],
};
}
@@ -40,7 +40,7 @@ function worker(overrides: Partial<Worker>): Worker {
can_stop: true,
can_spawn_followup: false,
},
working_directory: null,
workdir_attachments: [],
diagnostics: [],
...overrides,
};
@@ -19,11 +19,15 @@ export function sidebarWorkerActivity(
return 'none';
}
export function canShowWorkerInSidebar(worker: Worker): boolean {
export function canShowWorkerInSidebar(
worker: Pick<Worker, 'implementation'>,
): boolean {
return worker.implementation.kind !== 'backend_worker_registry';
}
export function canOpenWorkerConsole(worker: Worker): boolean {
export function canOpenWorkerConsole(
worker: Pick<Worker, 'implementation'>,
): boolean {
return canShowWorkerInSidebar(worker);
}
@@ -138,7 +138,10 @@
{#each workdirs as workdir}
{@const cleanup = cleanupCandidate(workdir)}
<tr>
<td><code>{workdir.working_directory_id}</code></td>
<td>
<span>{workdir.display_name ?? '—'}</span>
<small><code>{workdir.working_directory_id}</code></small>
</td>
<td>{workdir.repository_key}</td>
<td><code>{currentRevision(workdir)}</code></td>
<td>{workdir.status}</td>
@@ -145,11 +145,14 @@
}
function workerDirectory(worker: Worker): string {
const directory = worker.working_directory;
if (!directory) return '—';
const provider = data.repositories?.items.find((repository) => repository.repository_key === directory.repository_key)
?.provider;
return `${directory.repository_key} · ${formatCurrentWorkdirRevision(directory, provider)}`;
const attachments = worker.workdir_attachments ?? [];
if (attachments.length === 0) return '—';
return attachments.map(({ alias, working_directory: directory }) => {
const provider = data.repositories?.items.find((repository) => repository.repository_key === directory.repository_key)
?.provider;
const label = directory.display_name ?? directory.repository_key;
return `${alias}: ${label} · ${formatCurrentWorkdirRevision(directory, provider)}`;
}).join(', ');
}
</script>
@@ -33,7 +33,8 @@
function workdirOptionLabel(directory: WorkingDirectorySummary): string {
const provider = data.repositories?.items.find((repository) => repository.repository_key === directory.repository_key)
?.provider;
return `${directory.repository_key} · ${formatCurrentWorkdirRevision(directory, provider)}`;
const label = directory.display_name ?? directory.repository_key;
return `${label} · ${formatCurrentWorkdirRevision(directory, provider)}`;
}
let { data }: PageProps = $props();
@@ -62,6 +63,7 @@
);
let initialText = $state(ticketContext?.initialInput ?? '');
let workingDirectoryId = $state('');
let workingDirectoryDisplayName = $state('');
let workingDirectoryRepositoryKey = $state(ticketContext?.repositoryKey ?? '');
let workingDirectorySelector = $state(ticketContext?.refSelector ?? 'HEAD');
let relativeCwd = $state('');
@@ -78,7 +80,6 @@
: (options?.working_directories ?? []).filter((directory) =>
directory.status === 'active' &&
directory.cleanliness === 'clean' &&
directory.primary_worker_id == null &&
directory.occupied_by == null
),
);
@@ -168,6 +169,7 @@
try {
const request = validateWorkingDirectoryCreateRequest({
runtime_id: runtimeId,
...(workingDirectoryDisplayName.trim() ? { display_name: workingDirectoryDisplayName.trim() } : {}),
repository_key: workingDirectoryRepositoryKey,
...(workingDirectorySelector ? { selector: workingDirectorySelector } : {}),
});
@@ -340,6 +342,10 @@
<div class="new-working-directory-panel">
<h3>New workdir</h3>
<div class="new-working-directory-fields">
<label>
<span>Display name</span>
<input bind:value={workingDirectoryDisplayName} autocomplete="off" placeholder="Optional label" maxlength="80" />
</label>
<label>
<span>Repository</span>
<select bind:value={workingDirectoryRepositoryKey}>
+8
View File
@@ -10,6 +10,7 @@ import {
const summary = {
working_directory_id: "workdir-1",
display_name: "Checkout",
repository_key: "main",
materializer_kind: "runtime_git_clone",
status: "active",
@@ -27,6 +28,9 @@ Deno.test("Workdir REST validation accepts the generated list and create contrac
items: [summary],
diagnostics: [],
});
if (list.items[0]?.display_name !== "Checkout") {
throw new Error("display name was not preserved");
}
if (list.items[0]?.occupied_by?.runtime_id !== "arcadia") {
throw new Error("occupancy subject was not preserved");
}
@@ -60,6 +64,7 @@ Deno.test("Workdir REST validation rejects stale response JSON", () => {
Deno.test("Workdir REST validation enforces create operation fields", () => {
const request = validateWorkingDirectoryCreateRequest({
runtime_id: "arcadia",
display_name: "Review checkout",
repository_key: "main",
selector: "develop",
operation_id: "operation-1",
@@ -67,6 +72,9 @@ Deno.test("Workdir REST validation enforces create operation fields", () => {
if (request.operation_id !== "operation-1") {
throw new Error("operation id was not preserved");
}
if (request.display_name !== "Review checkout") {
throw new Error("display name was not preserved");
}
for (
const invalid of [