ui: show worker spawn diagnostics

This commit is contained in:
Keisuke Hirata 2026-07-08 02:27:19 +09:00
parent a6fe68301c
commit 9745bf041c
No known key found for this signature in database
2 changed files with 85 additions and 13 deletions

View File

@ -1411,3 +1411,38 @@
background: rgba(255, 255, 255, 0.04);
padding: 0.75rem;
}
@layer components {
.worker-submit-error {
display: grid;
gap: var(--space-2);
}
.worker-submit-error p {
margin: 0;
}
.worker-error-diagnostics {
display: grid;
gap: var(--space-1);
margin: 0;
padding-left: var(--space-4);
}
.worker-error-diagnostics li {
color: var(--text-muted);
}
.worker-error-diagnostics li.error {
color: var(--danger);
}
.worker-error-diagnostics strong {
color: inherit;
font-weight: 750;
}
.worker-error-diagnostics span {
display: block;
}
}

View File

@ -5,6 +5,7 @@
import type {
BrowserCreateWorkerResponse,
BrowserWorkingDirectoryCreateResponse,
Diagnostic,
ListResponse,
Worker,
WorkerLaunchOptionsResponse,
@ -17,6 +18,17 @@
workspaceId: string;
};
type DisplayError = {
message: string;
diagnostics: Diagnostic[];
};
type ErrorPayload = {
error?: { message?: string; code?: string } | string;
message?: string;
diagnostics?: Diagnostic[];
};
let { currentPath = '/', workspaceId }: Props = $props();
function workerApiPath(path: string): string {
@ -31,7 +43,7 @@
let optionsError = $state<string | null>(null);
let showNewWorker = $state(false);
let submitting = $state(false);
let submitError = $state<string | null>(null);
let submitError = $state<DisplayError | null>(null);
let displayName = $state('Coding Worker');
let runtimeId = $state('');
let profile = $state('builtin:coder');
@ -124,7 +136,7 @@
async function createWorkingDirectory() {
if (!workingDirectoryRepositoryId) {
submitError = 'select a repository before creating a working directory';
submitError = { message: 'select a repository before creating a working directory', diagnostics: [] };
return;
}
creatingWorkingDirectory = true;
@ -140,7 +152,8 @@
}),
});
if (!response.ok) {
throw new Error(await responseErrorMessage(response, 'working directory create failed'));
submitError = await responseDisplayError(response, 'working directory create failed');
return;
}
const payload = (await response.json()) as BrowserWorkingDirectoryCreateResponse;
const items = options?.working_directories ?? [];
@ -149,7 +162,7 @@
: options;
workingDirectoryAllocationId = payload.item.allocation_id;
} catch (err) {
submitError = err instanceof Error ? err.message : 'working directory create failed';
submitError = exceptionDisplayError(err, 'working directory create failed');
} finally {
creatingWorkingDirectory = false;
}
@ -157,7 +170,7 @@
async function createWorker() {
if (!workspaceId) {
submitError = 'workspace id is unavailable';
submitError = { message: 'workspace id is unavailable', diagnostics: [] };
return;
}
@ -179,32 +192,44 @@
})),
});
if (!response.ok) {
throw new Error(await responseErrorMessage(response, 'worker create failed'));
submitError = await responseDisplayError(response, 'worker create failed');
return;
}
const payload = (await response.json()) as BrowserCreateWorkerResponse;
await loadWorkers();
window.location.href = payload.console_href;
} catch (err) {
submitError = err instanceof Error ? err.message : 'worker create failed';
submitError = exceptionDisplayError(err, 'worker create failed');
} finally {
submitting = false;
}
}
async function responseErrorMessage(response: Response, fallback: string): Promise<string> {
async function responseDisplayError(response: Response, fallback: string): Promise<DisplayError> {
try {
const payload = (await response.json()) as { error?: { message?: string; code?: string } | string; message?: string };
const payload = (await response.json()) as ErrorPayload;
const diagnostics = Array.isArray(payload.diagnostics) ? payload.diagnostics : [];
if (typeof payload.error === 'object' && payload.error?.message) {
return `${payload.error.code ?? 'request_failed'}: ${payload.error.message}`;
return {
message: `${payload.error.code ?? 'request_failed'}: ${payload.error.message}`,
diagnostics,
};
}
if (payload.message) {
const code = typeof payload.error === 'string' ? payload.error : 'request_failed';
return `${code}: ${payload.message}`;
return { message: `${code}: ${payload.message}`, diagnostics };
}
} catch {
// fall through
}
return `${fallback} (${response.status})`;
return { message: `${fallback} (${response.status})`, diagnostics: [] };
}
function exceptionDisplayError(err: unknown, fallback: string): DisplayError {
return {
message: err instanceof Error ? err.message : fallback,
diagnostics: [],
};
}
</script>
@ -296,7 +321,19 @@
<p class="section-state error">{optionsError}</p>
{/if}
{#if submitError}
<p class="section-state error">{submitError}</p>
<div class="section-state error worker-submit-error">
<p>{submitError.message}</p>
{#if submitError.diagnostics.length > 0}
<ul class="worker-error-diagnostics">
{#each submitError.diagnostics as diagnostic}
<li class={diagnostic.severity}>
<strong>{diagnostic.code}</strong>
<span>{diagnostic.message}</span>
</li>
{/each}
</ul>
{/if}
</div>
{/if}
<button type="submit" disabled={submitting || !runtimeId || !profile || !workingDirectoryAllocationId}>
{submitting ? 'Starting…' : 'Start Coding Worker'}