Merge commit '80221289935e227820aec902988a4f987273e05e' into work/T-549-provider-published-ref

# Conflicts:
#	crates/worker-runtime/src/runtime.rs
This commit is contained in:
2026-09-03 15:25:34 +09:00
39 changed files with 2322 additions and 716 deletions
+1 -1
View File
@@ -6,7 +6,7 @@
"dev": "deno run -A npm:vite@7.2.7 dev",
"dev:backend": "cd ../.. && cargo run -p yoi-workspace-server --bin yoi-server -- serve --listen 127.0.0.1:8787",
"check": "deno run -A npm:@sveltejs/kit@2.49.4 sync && deno run -A npm:svelte-check@4.3.4 --tsconfig ./tsconfig.json",
"test": "deno test --allow-read=src,test,tests --allow-env=LOG,VSCODE_TEXTMATE_DEBUG,NODE_ENV tests/workspace-model.test.ts tests/workspace-catalog.test.ts tests/profile-api.test.ts src/lib/workspace/auth/model.test.ts src/lib/workspace/api/http.test.ts src/lib/workspace/header/breadcrumb-model.test.ts src/lib/workspace/console/chat-submit.test.ts test/composer-history.test.ts tests/composer-paste.test.ts src/lib/workspace/console/composer-command.test.ts src/lib/workspace/console/composer-draft.test.ts src/lib/workspace/console/composer-completion.test.ts src/lib/workspace/console/markdown.test.ts test/console/ansi.test.ts src/lib/workspace/console/model.test.ts src/lib/workspace/companion/api.test.ts tests/workdir-api.test.ts src/lib/workspace/console/tasks.test.ts test/ticket-detail-route-reuse.test.ts test/repositories/ui.test.ts src/lib/workspace/console/worker-console.ui.test.ts src/lib/workspace/settings/model.test.ts src/lib/workspace/sidebar/override-stack.test.ts src/lib/workspace/sidebar/workers.test.ts src/lib/workspace/sidebar/workspace-switcher.test.ts src/lib/workspace/sidebar/worker-subscription.test.ts src/lib/workspace/sidebar/worker-launch.test.ts test/sidebar/worker-actions.test.ts src/lib/workspace/tickets/merge-request-resources.test.ts src/lib/workspace/tickets/ticket-panel.test.ts test/merge-request-status.test.ts test/config-source/decodal-grammar.test.ts test/config-source/editor-state.test.ts test/config-source/fixed-schema-wrapper.test.ts test/config-source/toolchain.test.ts test/config-source/wasm-parity.test.ts test/repository-access/api.test.ts test/repository-access/loader.test.ts test/repository-access/ui.test.ts",
"test": "deno test --allow-read=src,test,tests --allow-env=LOG,VSCODE_TEXTMATE_DEBUG,NODE_ENV tests/workspace-model.test.ts tests/workspace-catalog.test.ts tests/profile-api.test.ts src/lib/workspace/auth/model.test.ts src/lib/workspace/api/http.test.ts src/lib/workspace/api/workers.test.ts src/lib/workspace/header/breadcrumb-model.test.ts src/lib/workspace/console/chat-submit.test.ts test/composer-history.test.ts tests/composer-paste.test.ts src/lib/workspace/console/composer-command.test.ts src/lib/workspace/console/composer-draft.test.ts src/lib/workspace/console/composer-completion.test.ts src/lib/workspace/console/markdown.test.ts test/console/ansi.test.ts src/lib/workspace/console/model.test.ts src/lib/workspace/companion/api.test.ts tests/workdir-api.test.ts src/lib/workspace/console/tasks.test.ts test/ticket-detail-route-reuse.test.ts test/repositories/ui.test.ts src/lib/workspace/console/worker-console.ui.test.ts src/lib/workspace/settings/model.test.ts src/lib/workspace/sidebar/override-stack.test.ts src/lib/workspace/sidebar/workers.test.ts src/lib/workspace/sidebar/workspace-switcher.test.ts src/lib/workspace/sidebar/worker-subscription.test.ts src/lib/workspace/sidebar/worker-launch.test.ts test/sidebar/worker-actions.test.ts src/lib/workspace/tickets/merge-request-resources.test.ts src/lib/workspace/tickets/ticket-panel.test.ts test/merge-request-status.test.ts test/config-source/decodal-grammar.test.ts test/config-source/editor-state.test.ts test/config-source/fixed-schema-wrapper.test.ts test/config-source/toolchain.test.ts test/config-source/wasm-parity.test.ts test/repository-access/api.test.ts test/repository-access/loader.test.ts test/repository-access/ui.test.ts",
"build": "deno run -A npm:vite@7.2.7 build",
"preview": "deno run -A npm:vite@7.2.7 preview"
},
+1 -1
View File
@@ -178,7 +178,7 @@ export type SubscriptionWorkdirId = string;
export type SubscriptionWorkerIds = Array<SubscriptionWorkerId>;
export type SubscriptionWorkerState = "idle" | "running" | "paused" | "stopped" | "cancelled";
export type SubscriptionWorkerState = "idle" | "running" | "paused" | "stopped";
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" };
@@ -0,0 +1,185 @@
// Generated from workspace-api. Do not edit by hand.
// Regenerate: cargo run -q -p workspace-api --features typescript --example generate_worker_launch_api_types > web/workspace/src/lib/generated/worker-launch-api.ts
import type { Segment } from "./protocol";
export type DiagnosticSeverity = "info" | "warning" | "error";
export type Diagnostic = {
code: string;
severity: DiagnosticSeverity;
message: string;
};
export type WorkingDirectoryMaterializerKind =
| "runtime_git_cache"
| "local_git_worktree";
export type WorkingDirectoryStatusKind =
| "active"
| "cleanup_pending"
| "corrupted"
| "not_found"
| "unknown";
export type WorkingDirectoryCleanupTarget = {
kind: string;
working_directory_id: string;
repository_key: string;
};
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 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 WorkerWorkspaceSummary = {
visibility: string;
identity: string;
workspace_id?: string | null;
};
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 WorkerLaunchRuntimeOption = {
runtime_id: string;
display_name: string;
built_in: boolean;
worker_creation_available: boolean;
working_directory_required: boolean;
status: string;
diagnostics: Array<Diagnostic>;
};
export type WorkerLaunchProfileCandidate = {
id: string;
label: string;
description: string;
};
export type WorkingDirectoryRepositoryOption = {
repository_key: string;
default_selector?: string | null;
};
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 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;
/**
* Backend idempotency key used only for authenticated Worker-owned spawn/control.
*/
control_operation_id: string | null;
};
export type BrowserCreateWorkerResponse = {
workspace_id: string;
runtime_id: string;
worker_id: string;
console_href: string;
worker: WorkerLaunchWorkerSummary;
diagnostics: Array<Diagnostic>;
};
export type BrowserWorkspaceOrchestratorResponse = {
workspace_id: string;
online: boolean;
disposition: string;
worker?: WorkerLaunchWorkerSummary | null;
diagnostics: Array<Diagnostic>;
};
@@ -55,7 +55,7 @@ export function parseWorkingDirectoryListResponse(
);
return {
workspace_id: stringField(record, "workspace_id"),
items: arrayField(record, "items").map(parseSummary),
items: arrayField(record, "items").map(parseWorkingDirectorySummary),
diagnostics: arrayField(record, "diagnostics").map(parseDiagnostic),
};
}
@@ -101,12 +101,14 @@ function parseDetailLike(
return {
workspace_id: stringField(record, "workspace_id"),
runtime_id: stringField(record, "runtime_id"),
item: parseSummary(record.item),
item: parseWorkingDirectorySummary(record.item),
diagnostics: arrayField(record, "diagnostics").map(parseDiagnostic),
};
}
function parseSummary(value: unknown): WorkingDirectorySummary {
export function parseWorkingDirectorySummary(
value: unknown,
): WorkingDirectorySummary {
const record = exactRecord(value, SUMMARY_KEYS, "Workdir summary");
const summary: WorkingDirectorySummary = {
working_directory_id: stringField(record, "working_directory_id"),
@@ -0,0 +1,183 @@
declare const Deno: {
test(name: string, fn: () => Promise<void> | void): void;
};
function assertEquals(actual: unknown, expected: unknown): void {
const actualJson = JSON.stringify(actual);
const expectedJson = JSON.stringify(expected);
if (actualJson !== expectedJson) {
throw new Error(`Expected ${expectedJson}, received ${actualJson}`);
}
}
function assertThrows(
operation: () => unknown,
errorClass: typeof Error,
message: string,
): void {
try {
operation();
} catch (error) {
if (!(error instanceof errorClass) || !error.message.includes(message)) {
throw error;
}
return;
}
throw new Error(`Expected operation to throw ${errorClass.name}: ${message}`);
}
import {
parseBrowserCreateWorkerResponse,
parseBrowserWorkspaceOrchestratorResponse,
parseCreateWorkspaceWorkerRequest,
parseWorkerLaunchOptionsResponse,
} from "./workers.ts";
const worker = {
runtime_id: "runtime-a",
worker_id: "worker-a",
host_id: "host-a",
display_name: "Worker A",
label: "worker-a",
profile: "builtin:coder",
singleton_key: null,
tags: [],
workspace: {
visibility: "workspace",
identity: "workspace-a",
workspace_id: "workspace-a",
},
state: "idle",
last_seen_at: null,
pinned: false,
retention_state: "active",
implementation: {
kind: "runtime",
display_hint: "Runtime Worker",
},
capabilities: {
can_stop: true,
can_spawn_followup: false,
},
diagnostics: [],
};
Deno.test("Worker launch options parser accepts the generated wire shape", () => {
const parsed = parseWorkerLaunchOptionsResponse({
workspace_id: "workspace-a",
runtimes: [{
runtime_id: "runtime-a",
display_name: "Runtime A",
built_in: false,
worker_creation_available: true,
working_directory_required: true,
status: "connected",
diagnostics: [],
}],
default_profile: null,
profiles: [{ id: "builtin:coder", label: "Coder", description: "Code" }],
repositories: [{ repository_key: "main" }],
working_directories: [],
diagnostics: [],
});
assertEquals(parsed.runtimes[0].runtime_id, "runtime-a");
assertEquals(parsed.repositories[0].default_selector, undefined);
});
Deno.test("Worker launch response parsers reject missing and unknown fields", () => {
assertThrows(
() =>
parseWorkerLaunchOptionsResponse({
workspace_id: "workspace-a",
runtimes: [],
profiles: [],
repositories: [],
working_directories: [],
diagnostics: [],
}),
Error,
"default_profile",
);
assertThrows(
() =>
parseBrowserCreateWorkerResponse({
workspace_id: "workspace-a",
runtime_id: "runtime-a",
worker_id: "worker-a",
console_href: "/workers/worker-a",
worker,
diagnostics: [],
unexpected: true,
}),
Error,
"unknown field unexpected",
);
assertThrows(
() =>
parseBrowserWorkspaceOrchestratorResponse({
workspace_id: "workspace-a",
online: false,
disposition: "missing",
diagnostics: [],
extra: false,
}),
Error,
"unknown field extra",
);
});
Deno.test("Worker create request parser requires the complete shared request", () => {
const request = {
runtime_id: "runtime-a",
display_name: "Worker A",
profile: "builtin:coder",
ticket_assignment: null,
initial_submit: [
{ kind: "text", content: "Implement T-565." },
{ kind: "flow", selector: "builtin:coder-review" },
],
working_directory: {
working_directory_id: "workdir-a",
relative_cwd: null,
},
control_operation_id: null,
};
assertEquals(parseCreateWorkspaceWorkerRequest(request), request);
assertThrows(
() =>
parseCreateWorkspaceWorkerRequest({
...request,
operation_id: "legacy-literal",
}),
Error,
"unknown field operation_id",
);
const { initial_submit: _initialSubmit, ...missingInitialSubmit } = request;
assertThrows(
() => parseCreateWorkspaceWorkerRequest(missingInitialSubmit),
Error,
"initial_submit",
);
assertThrows(
() =>
parseCreateWorkspaceWorkerRequest({
...request,
initial_submit: [{ kind: "flow" }],
}),
Error,
"selector",
);
assertThrows(
() =>
parseCreateWorkspaceWorkerRequest({
...request,
initial_submit: [{ kind: "newer_client_segment" }],
}),
Error,
"kind is invalid",
);
});
@@ -0,0 +1,665 @@
import type {
BrowserCreateWorkerResponse,
BrowserWorkerWorkingDirectorySelection,
BrowserWorkspaceOrchestratorResponse,
CreateWorkspaceWorkerRequest,
CreateWorkspaceWorkerTicketAssignmentRequest,
Diagnostic,
DiagnosticSeverity,
RuntimeWorkingDirectoryCleanupTarget,
RuntimeWorkingDirectorySummary,
WorkerCapabilitySummary,
WorkerImplementationSummary,
WorkerLaunchOptionsResponse,
WorkerLaunchProfileCandidate,
WorkerLaunchRuntimeOption,
WorkerLaunchWorkerSummary,
WorkerWorkspaceSummary,
WorkingDirectoryRepositoryOption,
} from "$lib/generated/worker-launch-api";
import type { Segment } from "$lib/generated/protocol";
import { parseWorkingDirectorySummary } from "$lib/workspace/api/workdirs";
const DIAGNOSTIC_SEVERITIES = new Set<DiagnosticSeverity>([
"info",
"warning",
"error",
]);
function record(value: unknown, label: string): Record<string, unknown> {
if (typeof value !== "object" || value === null || Array.isArray(value)) {
throw new Error(`${label} must be an object`);
}
return value as Record<string, unknown>;
}
function exact(
value: Record<string, unknown>,
allowed: readonly string[],
label: string,
): void {
const unexpected = Object.keys(value).filter((key) => !allowed.includes(key));
if (unexpected.length > 0) {
throw new Error(`${label} contains unknown field ${unexpected[0]}`);
}
}
function string(value: unknown, label: string): string {
if (typeof value !== "string") throw new Error(`${label} must be a string`);
return value;
}
function boolean(value: unknown, label: string): boolean {
if (typeof value !== "boolean") throw new Error(`${label} must be a boolean`);
return value;
}
function number(value: unknown, label: string): number {
if (typeof value !== "number" || !Number.isFinite(value)) {
throw new Error(`${label} must be a finite number`);
}
return value;
}
function nullableString(value: unknown, label: string): string | null {
return value === null ? null : string(value, label);
}
function array<T>(
value: unknown,
label: string,
parse: (item: unknown, label: string) => T,
): T[] {
if (!Array.isArray(value)) throw new Error(`${label} must be an array`);
return value.map((item, index) => parse(item, `${label}[${index}]`));
}
function optional<T>(
value: unknown,
label: string,
parse: (item: unknown, label: string) => T,
): T | null | undefined {
return value === undefined
? undefined
: value === null
? null
: parse(value, label);
}
function diagnostic(value: unknown, label: string): Diagnostic {
const item = record(value, label);
exact(item, ["code", "severity", "message"], label);
const severity = string(item.severity, `${label}.severity`);
if (!DIAGNOSTIC_SEVERITIES.has(severity as DiagnosticSeverity)) {
throw new Error(`${label}.severity is invalid`);
}
return {
code: string(item.code, `${label}.code`),
severity: severity as DiagnosticSeverity,
message: string(item.message, `${label}.message`),
};
}
function runtimeOption(
value: unknown,
label: string,
): WorkerLaunchRuntimeOption {
const item = record(value, label);
exact(
item,
[
"runtime_id",
"display_name",
"built_in",
"worker_creation_available",
"working_directory_required",
"status",
"diagnostics",
],
label,
);
return {
runtime_id: string(item.runtime_id, `${label}.runtime_id`),
display_name: string(item.display_name, `${label}.display_name`),
built_in: boolean(item.built_in, `${label}.built_in`),
worker_creation_available: boolean(
item.worker_creation_available,
`${label}.worker_creation_available`,
),
working_directory_required: boolean(
item.working_directory_required,
`${label}.working_directory_required`,
),
status: string(item.status, `${label}.status`),
diagnostics: array(item.diagnostics, `${label}.diagnostics`, diagnostic),
};
}
function profileCandidate(
value: unknown,
label: string,
): WorkerLaunchProfileCandidate {
const item = record(value, label);
exact(item, ["id", "label", "description"], label);
return {
id: string(item.id, `${label}.id`),
label: string(item.label, `${label}.label`),
description: string(item.description, `${label}.description`),
};
}
function repositoryOption(
value: unknown,
label: string,
): WorkingDirectoryRepositoryOption {
const item = record(value, label);
exact(item, ["repository_key", "default_selector"], label);
return {
repository_key: string(item.repository_key, `${label}.repository_key`),
default_selector: optional(
item.default_selector,
`${label}.default_selector`,
string,
),
};
}
export function parseWorkerLaunchOptionsResponse(
value: unknown,
): WorkerLaunchOptionsResponse {
const item = record(value, "Worker launch options response");
exact(
item,
[
"workspace_id",
"runtimes",
"default_profile",
"profiles",
"repositories",
"working_directories",
"diagnostics",
],
"Worker launch options response",
);
return {
workspace_id: string(item.workspace_id, "workspace_id"),
runtimes: array(item.runtimes, "runtimes", runtimeOption),
default_profile: nullableString(item.default_profile, "default_profile"),
profiles: array(item.profiles, "profiles", profileCandidate),
repositories: array(item.repositories, "repositories", repositoryOption),
working_directories: array(
item.working_directories,
"working_directories",
parseWorkingDirectorySummary,
),
diagnostics: array(item.diagnostics, "diagnostics", diagnostic),
};
}
function workspaceSummary(
value: unknown,
label: string,
): WorkerWorkspaceSummary {
const item = record(value, label);
exact(item, ["visibility", "identity", "workspace_id"], label);
return {
visibility: string(item.visibility, `${label}.visibility`),
identity: string(item.identity, `${label}.identity`),
workspace_id: optional(item.workspace_id, `${label}.workspace_id`, string),
};
}
function implementationSummary(
value: unknown,
label: string,
): WorkerImplementationSummary {
const item = record(value, label);
exact(item, ["kind", "display_hint"], label);
return {
kind: string(item.kind, `${label}.kind`),
display_hint: string(item.display_hint, `${label}.display_hint`),
};
}
function capabilitySummary(
value: unknown,
label: string,
): WorkerCapabilitySummary {
const item = record(value, label);
exact(item, ["can_stop", "can_spawn_followup"], label);
return {
can_stop: boolean(item.can_stop, `${label}.can_stop`),
can_spawn_followup: boolean(
item.can_spawn_followup,
`${label}.can_spawn_followup`,
),
};
}
function runtimeCleanupTarget(
value: unknown,
label: string,
): RuntimeWorkingDirectoryCleanupTarget {
const item = record(value, label);
exact(item, ["kind", "working_directory_id", "repository_id"], label);
return {
kind: string(item.kind, `${label}.kind`),
working_directory_id: string(
item.working_directory_id,
`${label}.working_directory_id`,
),
repository_id: string(item.repository_id, `${label}.repository_id`),
};
}
function runtimeWorkingDirectory(
value: unknown,
label: string,
): RuntimeWorkingDirectorySummary {
const item = record(value, label);
exact(
item,
[
"working_directory_id",
"repository_id",
"creation_selector",
"creation_ref",
"creation_tree",
"current_selector",
"current_ref",
"current_tree",
"observed_at_epoch_seconds",
"materializer_kind",
"cleanup_target",
"status",
"cleanliness",
"primary_worker_id",
"occupied_by",
],
label,
);
const materializerKind = string(
item.materializer_kind,
`${label}.materializer_kind`,
);
if (
materializerKind !== "runtime_git_cache" &&
materializerKind !== "local_git_worktree"
) {
throw new Error(`${label}.materializer_kind is invalid`);
}
const status = string(item.status, `${label}.status`);
if (
!["active", "cleanup_pending", "corrupted", "not_found", "unknown"]
.includes(status)
) {
throw new Error(`${label}.status is invalid`);
}
const occupied = optional(
item.occupied_by,
`${label}.occupied_by`,
(value, occupiedLabel) => {
const occupancy = record(value, occupiedLabel);
exact(
occupancy,
["runtime_id", "worker_id", "display_name", "linked_at"],
occupiedLabel,
);
return {
runtime_id: string(occupancy.runtime_id, `${occupiedLabel}.runtime_id`),
worker_id: string(occupancy.worker_id, `${occupiedLabel}.worker_id`),
display_name: string(
occupancy.display_name,
`${occupiedLabel}.display_name`,
),
linked_at: string(occupancy.linked_at, `${occupiedLabel}.linked_at`),
};
},
);
return {
working_directory_id: string(
item.working_directory_id,
`${label}.working_directory_id`,
),
repository_id: string(item.repository_id, `${label}.repository_id`),
creation_selector: optional(
item.creation_selector,
`${label}.creation_selector`,
string,
),
creation_ref: optional(item.creation_ref, `${label}.creation_ref`, string),
creation_tree: optional(
item.creation_tree,
`${label}.creation_tree`,
string,
),
current_selector: optional(
item.current_selector,
`${label}.current_selector`,
string,
),
current_ref: optional(item.current_ref, `${label}.current_ref`, string),
current_tree: optional(item.current_tree, `${label}.current_tree`, string),
observed_at_epoch_seconds: optional(
item.observed_at_epoch_seconds,
`${label}.observed_at_epoch_seconds`,
number,
),
materializer_kind: materializerKind,
cleanup_target: optional(
item.cleanup_target,
`${label}.cleanup_target`,
runtimeCleanupTarget,
),
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 workerSummary(
value: unknown,
label: string,
): WorkerLaunchWorkerSummary {
const item = record(value, label);
exact(
item,
[
"runtime_id",
"worker_id",
"host_id",
"display_name",
"label",
"profile",
"singleton_key",
"tags",
"workspace",
"state",
"last_seen_at",
"pinned",
"retention_state",
"implementation",
"capabilities",
"working_directory",
"diagnostics",
],
label,
);
return {
runtime_id: string(item.runtime_id, `${label}.runtime_id`),
worker_id: string(item.worker_id, `${label}.worker_id`),
host_id: string(item.host_id, `${label}.host_id`),
display_name: string(item.display_name, `${label}.display_name`),
label: string(item.label, `${label}.label`),
profile: nullableString(item.profile, `${label}.profile`),
singleton_key: nullableString(item.singleton_key, `${label}.singleton_key`),
tags: array(item.tags, `${label}.tags`, string),
workspace: workspaceSummary(item.workspace, `${label}.workspace`),
state: string(item.state, `${label}.state`),
last_seen_at: nullableString(item.last_seen_at, `${label}.last_seen_at`),
pinned: boolean(item.pinned, `${label}.pinned`),
retention_state: string(item.retention_state, `${label}.retention_state`),
implementation: implementationSummary(
item.implementation,
`${label}.implementation`,
),
capabilities: capabilitySummary(item.capabilities, `${label}.capabilities`),
working_directory: optional(
item.working_directory,
`${label}.working_directory`,
runtimeWorkingDirectory,
),
diagnostics: array(item.diagnostics, `${label}.diagnostics`, diagnostic),
};
}
export function parseBrowserCreateWorkerResponse(
value: unknown,
): BrowserCreateWorkerResponse {
const item = record(value, "Worker create response");
exact(
item,
[
"workspace_id",
"runtime_id",
"worker_id",
"console_href",
"worker",
"diagnostics",
],
"Worker create response",
);
return {
workspace_id: string(item.workspace_id, "workspace_id"),
runtime_id: string(item.runtime_id, "runtime_id"),
worker_id: string(item.worker_id, "worker_id"),
console_href: string(item.console_href, "console_href"),
worker: workerSummary(item.worker, "worker"),
diagnostics: array(item.diagnostics, "diagnostics", diagnostic),
};
}
export function parseBrowserWorkspaceOrchestratorResponse(
value: unknown,
): BrowserWorkspaceOrchestratorResponse {
const item = record(value, "Workspace Orchestrator response");
exact(
item,
["workspace_id", "online", "disposition", "worker", "diagnostics"],
"Workspace Orchestrator response",
);
return {
workspace_id: string(item.workspace_id, "workspace_id"),
online: boolean(item.online, "online"),
disposition: string(item.disposition, "disposition"),
worker: optional(item.worker, "worker", workerSummary),
diagnostics: array(item.diagnostics, "diagnostics", diagnostic),
};
}
function workingDirectorySelection(
value: unknown,
label: string,
): BrowserWorkerWorkingDirectorySelection {
const item = record(value, label);
exact(item, ["working_directory_id", "relative_cwd"], label);
return {
working_directory_id: string(
item.working_directory_id,
`${label}.working_directory_id`,
),
relative_cwd: nullableString(item.relative_cwd, `${label}.relative_cwd`),
};
}
function ticketAssignment(
value: unknown,
label: string,
): CreateWorkspaceWorkerTicketAssignmentRequest {
const item = record(value, label);
exact(item, ["ticket_id", "operation_id"], label);
return {
ticket_id: string(item.ticket_id, `${label}.ticket_id`),
operation_id: string(item.operation_id, `${label}.operation_id`),
};
}
function unsignedInteger(value: unknown, label: string): number {
const parsed = number(value, label);
if (!Number.isSafeInteger(parsed) || parsed < 0) {
throw new Error(`${label} must be a non-negative safe integer`);
}
return parsed;
}
function pasteArtifact(
value: unknown,
label: string,
): Extract<Segment, { kind: "paste_artifact" }>["artifact"] {
const item = record(value, label);
exact(
item,
[
"artifact_id",
"created_at_ms",
"media_type",
"availability",
"byte_len",
"char_count",
"line_count",
"sha256",
"source_entry_id",
],
label,
);
const mediaType = string(item.media_type, `${label}.media_type`);
if (mediaType !== "text_plain_utf8") {
throw new Error(`${label}.media_type is invalid`);
}
const availability = string(item.availability, `${label}.availability`);
if (
!["available", "unavailable", "integrity_failed"].includes(availability)
) {
throw new Error(`${label}.availability is invalid`);
}
return {
artifact_id: string(item.artifact_id, `${label}.artifact_id`),
created_at_ms: unsignedInteger(
item.created_at_ms,
`${label}.created_at_ms`,
),
media_type: mediaType,
availability: availability as Extract<Segment, { kind: "paste_artifact" }>[
"artifact"
]["availability"],
byte_len: unsignedInteger(item.byte_len, `${label}.byte_len`),
char_count: unsignedInteger(item.char_count, `${label}.char_count`),
line_count: unsignedInteger(item.line_count, `${label}.line_count`),
sha256: string(item.sha256, `${label}.sha256`),
source_entry_id: string(item.source_entry_id, `${label}.source_entry_id`),
};
}
function uploadedFile(
value: unknown,
label: string,
): Extract<Segment, { kind: "uploaded_file" }>["file"] {
const item = record(value, label);
exact(
item,
[
"artifact_id",
"file_name",
"media_type",
"created_at_ms",
"availability",
"byte_len",
"sha256",
"source_entry_id",
],
label,
);
const availability = string(item.availability, `${label}.availability`);
if (
!["available", "unavailable", "integrity_failed"].includes(availability)
) {
throw new Error(`${label}.availability is invalid`);
}
return {
artifact_id: string(item.artifact_id, `${label}.artifact_id`),
file_name: string(item.file_name, `${label}.file_name`),
media_type: string(item.media_type, `${label}.media_type`),
created_at_ms: unsignedInteger(
item.created_at_ms,
`${label}.created_at_ms`,
),
availability: availability as Extract<Segment, { kind: "uploaded_file" }>[
"file"
]["availability"],
byte_len: unsignedInteger(item.byte_len, `${label}.byte_len`),
sha256: string(item.sha256, `${label}.sha256`),
source_entry_id: optional(
item.source_entry_id,
`${label}.source_entry_id`,
string,
),
};
}
function segment(value: unknown, label: string): Segment {
const item = record(value, label);
const kind = string(item.kind, `${label}.kind`) as Segment["kind"];
switch (kind) {
case "text":
exact(item, ["kind", "content"], label);
return { kind, content: string(item.content, `${label}.content`) };
case "paste":
exact(item, ["kind", "id", "chars", "lines", "content"], label);
return {
kind,
id: unsignedInteger(item.id, `${label}.id`),
chars: unsignedInteger(item.chars, `${label}.chars`),
lines: unsignedInteger(item.lines, `${label}.lines`),
content: string(item.content, `${label}.content`),
};
case "paste_artifact":
exact(item, ["kind", "artifact"], label);
return {
kind,
artifact: pasteArtifact(item.artifact, `${label}.artifact`),
};
case "uploaded_file":
exact(item, ["kind", "file"], label);
return { kind, file: uploadedFile(item.file, `${label}.file`) };
case "file_ref":
exact(item, ["kind", "path"], label);
return { kind, path: string(item.path, `${label}.path`) };
case "flow":
exact(item, ["kind", "selector"], label);
return { kind, selector: string(item.selector, `${label}.selector`) };
case "unknown":
throw new Error(`${label}.kind is not supported by Worker creation`);
}
const exhaustive: never = kind;
throw new Error(`${label}.kind is invalid: ${exhaustive}`);
}
export function parseCreateWorkspaceWorkerRequest(
value: unknown,
): CreateWorkspaceWorkerRequest {
const item = record(value, "Worker create request");
exact(
item,
[
"runtime_id",
"display_name",
"profile",
"ticket_assignment",
"initial_submit",
"working_directory",
"control_operation_id",
],
"Worker create request",
);
return {
runtime_id: string(item.runtime_id, "runtime_id"),
display_name: string(item.display_name, "display_name"),
profile: nullableString(item.profile, "profile"),
ticket_assignment: item.ticket_assignment === null
? 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"),
control_operation_id: nullableString(
item.control_operation_id,
"control_operation_id",
),
};
}
@@ -1,3 +1,12 @@
import type {
BrowserCreateWorkerResponse as SharedBrowserCreateWorkerResponse,
BrowserWorkerWorkingDirectorySelection
as SharedBrowserWorkerWorkingDirectorySelection,
WorkerLaunchOptionsResponse as SharedWorkerLaunchOptionsResponse,
WorkerLaunchProfileCandidate as SharedWorkerLaunchProfileCandidate,
WorkerLaunchRuntimeOption as SharedWorkerLaunchRuntimeOption,
WorkingDirectoryRepositoryOption as SharedWorkingDirectoryRepositoryOption,
} from "$lib/generated/worker-launch-api";
import type {
WorkingDirectoryCreateRequest,
WorkingDirectoryCreateResponse,
@@ -101,26 +110,10 @@ export type Worker = {
export type WorkerOperationState = "accepted" | "unsupported" | "rejected";
export type WorkerLaunchRuntimeOption = {
runtime_id: string;
display_name: string;
built_in: boolean;
worker_creation_available: boolean;
working_directory_required: boolean;
status: string;
diagnostics: Diagnostic[];
};
export type WorkerLaunchProfileCandidate = {
id: string;
label: string;
description: string;
};
export type WorkingDirectoryRepositoryOption = {
repository_key: string;
default_selector?: string | null;
};
export type WorkerLaunchRuntimeOption = SharedWorkerLaunchRuntimeOption;
export type WorkerLaunchProfileCandidate = SharedWorkerLaunchProfileCandidate;
export type WorkingDirectoryRepositoryOption =
SharedWorkingDirectoryRepositoryOption;
export type CleanupTargetKind =
| "worker_delete"
@@ -185,29 +178,10 @@ export type RuntimeCleanupExecutionResponse = {
diagnostics: Diagnostic[];
};
export type BrowserWorkerWorkingDirectorySelection = {
working_directory_id: string;
relative_cwd?: string | null;
};
export type WorkerLaunchOptionsResponse = {
workspace_id: string;
runtimes: WorkerLaunchRuntimeOption[];
default_profile?: string | null;
profiles: WorkerLaunchProfileCandidate[];
repositories: WorkingDirectoryRepositoryOption[];
working_directories: WorkingDirectorySummary[];
diagnostics: Diagnostic[];
};
export type BrowserCreateWorkerResponse = {
workspace_id: string;
runtime_id: string;
worker_id: string;
console_href: string;
worker: Worker;
diagnostics: Diagnostic[];
};
export type BrowserWorkerWorkingDirectorySelection =
SharedBrowserWorkerWorkingDirectorySelection;
export type WorkerLaunchOptionsResponse = SharedWorkerLaunchOptionsResponse;
export type BrowserCreateWorkerResponse = SharedBrowserCreateWorkerResponse;
export type WorkerInputResult = {
state: WorkerOperationState;
@@ -46,7 +46,7 @@ function diagnosticMessage(
}
export function canDeleteSidebarWorker(worker: Worker): boolean {
return worker.state === "stopped" || worker.state === "cancelled";
return worker.state === "stopped";
}
export async function stopSidebarWorker(
@@ -196,11 +196,13 @@ Deno.test("buildCreateWorkspaceWorkerRequest sends working_directory id and rela
runtime_id: "embedded",
display_name: "Worker",
profile: "builtin:coder",
ticket_assignment: null,
initial_submit: [{ kind: "text", content: "go" }],
working_directory: {
working_directory_id: "wd-1-repo",
relative_cwd: "crates/yoi",
},
control_operation_id: null,
});
});
@@ -219,7 +221,7 @@ Deno.test("buildCreateWorkspaceWorkerRequest sends no initial segments for an em
assertEquals(request.initial_submit, []);
});
Deno.test("buildCreateWorkspaceWorkerRequest omits working_directory for embedded no-workdir launches", () => {
Deno.test("buildCreateWorkspaceWorkerRequest emits null for embedded no-workdir launches", () => {
const request = buildCreateWorkspaceWorkerRequest({
runtime_id: "embedded",
display_name: "Worker",
@@ -235,6 +237,9 @@ Deno.test("buildCreateWorkspaceWorkerRequest omits working_directory for embedde
runtime_id: "embedded",
display_name: "Worker",
profile: "builtin:companion",
ticket_assignment: null,
initial_submit: [{ kind: "text", content: "chat" }],
working_directory: null,
control_operation_id: null,
});
});
@@ -1,9 +1,7 @@
import type { Segment } from "$lib/generated/protocol";
import type { CreateWorkspaceWorkerRequest } from "$lib/generated/worker-launch-api";
import { parseCreateWorkspaceWorkerRequest } from "$lib/workspace/api/workers";
import type {
BrowserWorkerWorkingDirectorySelection,
WorkerLaunchOptionsResponse,
} from "./types";
import type { WorkerLaunchOptionsResponse } from "./types";
export type WorkerLaunchFormState = {
runtime_id: string;
@@ -16,14 +14,6 @@ export type WorkerLaunchFormState = {
relative_cwd: string;
};
export type CreateWorkspaceWorkerRequest = {
runtime_id: string;
display_name: string;
profile: string;
initial_submit: Segment[];
working_directory?: BrowserWorkerWorkingDirectorySelection;
};
export function defaultWorkerLaunchForm(
options: WorkerLaunchOptionsResponse | null,
current: WorkerLaunchFormState,
@@ -86,7 +76,8 @@ export function defaultWorkerLaunchForm(
)
? current.working_directory_id
: preferredWorkingDirectory?.working_directory_id || "",
working_directory_repository_key: current.working_directory_repository_key ||
working_directory_repository_key:
current.working_directory_repository_key ||
preferredRepository?.repository_key || "",
working_directory_selector: current.working_directory_selector ||
preferredRepository?.default_selector || "HEAD",
@@ -97,22 +88,21 @@ export function defaultWorkerLaunchForm(
export function buildCreateWorkspaceWorkerRequest(
form: WorkerLaunchFormState,
): CreateWorkspaceWorkerRequest {
const request: CreateWorkspaceWorkerRequest = {
runtime_id: form.runtime_id,
display_name: form.display_name,
profile: form.profile,
initial_submit: form.initial_text.trim()
const initialMessage = form.initial_text.trim();
return parseCreateWorkspaceWorkerRequest({
runtime_id: form.runtime_id.trim(),
display_name: form.display_name.trim(),
profile: form.profile.trim() || null,
ticket_assignment: null,
initial_submit: initialMessage
? [{ kind: "text", content: form.initial_text }]
: [],
};
if (form.working_directory_id) {
request.working_directory = {
working_directory_id: form.working_directory_id,
};
const relativeCwd = form.relative_cwd.trim();
if (relativeCwd) {
request.working_directory.relative_cwd = relativeCwd;
}
}
return request;
working_directory: form.working_directory_id
? {
working_directory_id: form.working_directory_id,
relative_cwd: form.relative_cwd.trim() || null,
}
: null,
control_operation_id: null,
});
}
@@ -94,7 +94,7 @@ function projectWorker(worker: SubscriptionWorker): SidebarWorker {
display_hint: 'Workspace-authorized Runtime Worker',
},
capabilities: {
can_stop: worker.state !== 'stopped' && worker.state !== 'cancelled',
can_stop: worker.state !== 'stopped',
can_spawn_followup: false,
},
repository_key: worker.repository_key ?? null,
@@ -1,3 +1,4 @@
import type { BrowserWorkspaceOrchestratorResponse } from "$lib/generated/worker-launch-api";
import type { TicketDetail, TicketSummary } from "$lib/generated/ticket-api";
export const TICKET_STATES = [
@@ -12,22 +13,7 @@ export const TICKET_STATES = [
export type TicketState = (typeof TICKET_STATES)[number];
export type TicketWorkerRole = "coder" | "reviewer";
export type WorkspaceOrchestratorStatus = {
workspace_id: string;
online: boolean;
disposition: string;
worker?: {
runtime_id: string;
worker_id: string;
state: string;
display_name: string;
} | null;
diagnostics: Array<{
code: string;
severity: string;
message: string;
}>;
};
export type WorkspaceOrchestratorStatus = BrowserWorkspaceOrchestratorResponse;
const LANE_DEFINITIONS = [
{
@@ -2,6 +2,7 @@
import { untrack } from "svelte";
import type { ApiResult } from "$lib/workspace/api/http";
import { loadJson, workspaceApiPath } from "$lib/workspace/api/http";
import { parseBrowserWorkspaceOrchestratorResponse } from "$lib/workspace/api/workers";
import type {
QueryPage,
TicketListResponse,
@@ -99,6 +100,7 @@
fetch,
workspaceApiPath(data.workspaceId, "/orchestrator"),
{ method: "POST" },
parseBrowserWorkspaceOrchestratorResponse,
);
orchestratorStarting = false;
}
@@ -1,3 +1,4 @@
import { parseBrowserWorkspaceOrchestratorResponse } from "$lib/workspace/api/workers";
import { loadJson, workspaceApiPath } from "$lib/workspace/api/http";
import type { TicketListResponse } from "$lib/generated/ticket-api";
import type { WorkspaceOrchestratorStatus } from "$lib/workspace/tickets/ticket-panel";
@@ -42,6 +43,8 @@ export const load: PageLoad = async ({ fetch, params }) => {
loadJson<WorkspaceOrchestratorStatus>(
fetch,
workspaceApiPath(workspaceId, "/orchestrator"),
undefined,
parseBrowserWorkspaceOrchestratorResponse,
),
]);
@@ -6,10 +6,13 @@
parseWorkingDirectoryCreateResponse,
validateWorkingDirectoryCreateRequest,
} from '$lib/workspace/api/workdirs';
import {
parseBrowserCreateWorkerResponse,
parseWorkerLaunchOptionsResponse,
} from '$lib/workspace/api/workers';
import { formatCurrentWorkdirRevision } from '$lib/workspace/settings/workdir-revision';
import { buildCreateWorkspaceWorkerRequest, defaultWorkerLaunchForm } from '$lib/workspace/sidebar/worker-launch';
import type {
BrowserCreateWorkerResponse,
Diagnostic,
WorkerLaunchOptionsResponse,
WorkingDirectorySummary,
@@ -115,7 +118,7 @@
if (!response.ok) {
throw new Error(`worker launch options failed (${response.status})`);
}
const payload = (await response.json()) as WorkerLaunchOptionsResponse;
const payload = parseWorkerLaunchOptionsResponse(await response.json());
options = payload;
const form = defaultWorkerLaunchForm(payload, {
runtime_id: runtimeId,
@@ -229,7 +232,7 @@
submitError = await responseDisplayError(response, 'worker create failed');
return;
}
const payload = (await response.json()) as BrowserCreateWorkerResponse;
const payload = parseBrowserCreateWorkerResponse(await response.json());
await goto(payload.console_href);
} catch (err) {
submitError = exceptionDisplayError(err, 'worker create failed');
@@ -155,10 +155,9 @@ Deno.test("sidebar Delete reports cleanup-plan blocking reasons", async () => {
);
});
Deno.test("sidebar Delete is enabled only for terminal Worker states", () => {
Deno.test("sidebar Delete is enabled only for stopped Workers", () => {
assert(!canDeleteSidebarWorker(worker));
assert(canDeleteSidebarWorker({ ...worker, state: "stopped" }));
assert(canDeleteSidebarWorker({ ...worker, state: "cancelled" }));
});
Deno.test("Worker navigation exposes an accessible hover action menu", async () => {