fix: harden runtime and worker launch controls
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
"dev": "deno run -A npm:vite@7.2.7 dev",
|
||||
"dev:backend": "cd ../.. && cargo run -p yoi-workspace-server -- serve --workspace . --db .yoi/workspace.db --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 src/lib/workspace-console/model.test.ts src/lib/workspace-console/worker-console.ui.test.ts src/lib/workspace-settings/model.test.ts",
|
||||
"test": "deno test --allow-read=src src/lib/workspace-console/model.test.ts src/lib/workspace-console/worker-console.ui.test.ts src/lib/workspace-settings/model.test.ts src/lib/workspace-sidebar/worker-launch.test.ts",
|
||||
"build": "deno run -A npm:vite@7.2.7 build",
|
||||
"preview": "deno run -A npm:vite@7.2.7 preview"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { workerConsoleHref } from '$lib/workspace-console/model';
|
||||
import { buildBrowserCreateWorkerRequest, defaultWorkerLaunchForm } from './worker-launch';
|
||||
import type {
|
||||
BrowserCreateWorkerResponse,
|
||||
ListResponse,
|
||||
@@ -77,16 +78,15 @@
|
||||
}
|
||||
const payload = (await response.json()) as WorkerLaunchOptionsResponse;
|
||||
options = payload;
|
||||
const preferredRuntime = payload.runtimes.find((runtime) => runtime.can_spawn_worker && runtime.status === 'active')
|
||||
?? payload.runtimes.find((runtime) => runtime.can_spawn_worker)
|
||||
?? payload.runtimes[0];
|
||||
if (preferredRuntime && !runtimeId) {
|
||||
runtimeId = preferredRuntime.runtime_id;
|
||||
}
|
||||
const preferredProfile = payload.profiles.find((candidate) => candidate.id === 'builtin:coder') ?? payload.profiles[0];
|
||||
if (preferredProfile && !payload.profiles.some((candidate) => candidate.id === profile)) {
|
||||
profile = preferredProfile.id;
|
||||
}
|
||||
const form = defaultWorkerLaunchForm(payload, {
|
||||
runtime_id: runtimeId,
|
||||
display_name: displayName,
|
||||
profile,
|
||||
initial_text: initialText,
|
||||
});
|
||||
runtimeId = form.runtime_id;
|
||||
displayName = form.display_name;
|
||||
profile = form.profile;
|
||||
} catch (err) {
|
||||
if (err instanceof DOMException && err.name === 'AbortError') {
|
||||
return;
|
||||
@@ -102,12 +102,12 @@
|
||||
const response = await fetch('/api/workers', {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
body: JSON.stringify(buildBrowserCreateWorkerRequest({
|
||||
runtime_id: runtimeId,
|
||||
display_name: displayName,
|
||||
profile,
|
||||
initial_text: initialText,
|
||||
}),
|
||||
})),
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(await responseErrorMessage(response, 'worker create failed'));
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import {
|
||||
buildBrowserCreateWorkerRequest,
|
||||
defaultWorkerLaunchForm,
|
||||
type WorkerLaunchFormState,
|
||||
} from './worker-launch.ts';
|
||||
|
||||
import type { WorkerLaunchOptionsResponse } from './types.ts';
|
||||
|
||||
declare const Deno: {
|
||||
test(name: string, fn: () => void): void;
|
||||
};
|
||||
|
||||
function assert(condition: unknown, message: string): asserts condition {
|
||||
if (!condition) {
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
|
||||
const options: WorkerLaunchOptionsResponse = {
|
||||
workspace_id: 'workspace',
|
||||
runtimes: [
|
||||
{
|
||||
runtime_id: 'remote-runtime',
|
||||
display_name: 'Remote Runtime',
|
||||
built_in: false,
|
||||
can_spawn_worker: false,
|
||||
status: 'active',
|
||||
diagnostics: [],
|
||||
},
|
||||
{
|
||||
runtime_id: 'embedded-worker-runtime',
|
||||
display_name: 'Embedded Runtime',
|
||||
built_in: true,
|
||||
can_spawn_worker: true,
|
||||
status: 'active',
|
||||
diagnostics: [],
|
||||
},
|
||||
],
|
||||
profiles: [
|
||||
{
|
||||
id: 'runtime_default',
|
||||
label: 'Runtime default',
|
||||
description: 'Runtime default profile.',
|
||||
},
|
||||
{
|
||||
id: 'builtin:coder',
|
||||
label: 'Coding Worker',
|
||||
description: 'Coding role.',
|
||||
},
|
||||
],
|
||||
diagnostics: [],
|
||||
};
|
||||
|
||||
Deno.test('new worker form defaults to backend-published runtime and profile candidates', () => {
|
||||
const current: WorkerLaunchFormState = {
|
||||
runtime_id: '',
|
||||
display_name: '',
|
||||
profile: 'free-text-profile',
|
||||
initial_text: 'start here',
|
||||
};
|
||||
|
||||
const form = defaultWorkerLaunchForm(options, current);
|
||||
assert(form.runtime_id === 'embedded-worker-runtime', 'should choose spawn-capable runtime');
|
||||
assert(form.profile === 'builtin:coder', 'should choose backend-published coder profile');
|
||||
assert(form.display_name === 'Coding Worker', 'should derive default display name');
|
||||
assert(form.initial_text === 'start here', 'should preserve initial text');
|
||||
});
|
||||
|
||||
Deno.test('new worker submit payload exposes only browser contract fields', () => {
|
||||
const request = buildBrowserCreateWorkerRequest({
|
||||
runtime_id: 'embedded-worker-runtime',
|
||||
display_name: 'Coding Worker',
|
||||
profile: 'builtin:coder',
|
||||
initial_text: 'implement ticket',
|
||||
});
|
||||
|
||||
assert(
|
||||
JSON.stringify(Object.keys(request).sort()) ===
|
||||
JSON.stringify(['display_name', 'initial_text', 'profile', 'runtime_id'].sort()),
|
||||
'submit payload should contain only Browser-facing worker create fields',
|
||||
);
|
||||
assert(!('kind' in request), 'kind must not be exposed as a Browser request field');
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import type { WorkerLaunchOptionsResponse } from './types';
|
||||
|
||||
export type WorkerLaunchFormState = {
|
||||
runtime_id: string;
|
||||
display_name: string;
|
||||
profile: string;
|
||||
initial_text: string;
|
||||
};
|
||||
|
||||
export type BrowserCreateWorkerRequest = WorkerLaunchFormState;
|
||||
|
||||
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];
|
||||
|
||||
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 || '',
|
||||
initial_text: current.initial_text,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildBrowserCreateWorkerRequest(
|
||||
form: WorkerLaunchFormState,
|
||||
): BrowserCreateWorkerRequest {
|
||||
return {
|
||||
runtime_id: form.runtime_id,
|
||||
display_name: form.display_name,
|
||||
profile: form.profile,
|
||||
initial_text: form.initial_text,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user