Merge remote-tracking branch 'origin/work/T-674-backend-job-runner' into develop

This commit is contained in:
2026-10-01 12:33:46 +09:00
33 changed files with 4158 additions and 160 deletions
File diff suppressed because one or more lines are too long
+8 -1
View File
@@ -279,6 +279,8 @@ export type SubscriptionWorkerWorkdirAttachment = {
*/
alias: string, repository_key?: string | null, working_directory_id: SubscriptionWorkdirId, };
export type SubscriptionWorkerJob = { job_id: string, attempt_id: string, purpose: string, };
export type SubscriptionWorker = { worker_id: SubscriptionWorkerId,
/**
* Set by the Workspace Server when projecting a Runtime-owned Worker to clients.
@@ -307,7 +309,12 @@ 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, workdir_attachments?: Array<SubscriptionWorkerWorkdirAttachment>, };
state: SubscriptionWorkerState, has_running_internal_workers: boolean, workspace_id?: string | null, display_name?: string | null, profile?: string | null,
/**
* Backend-owned Job binding. Runtime producers leave this unset; the
* Workspace projection enriches both snapshots and live updates.
*/
job?: SubscriptionWorkerJob | null, workdir_attachments?: Array<SubscriptionWorkerWorkdirAttachment>, };
export type WorkspaceSubscriptionWorkdir = { working_directory_id: SubscriptionWorkdirId, repository_key: string, state: string, };
@@ -30,6 +30,7 @@ import type {
Event as PodProtocolEvent,
Method as PodProtocolMethod,
Segment as PodProtocolSegment,
SubscriptionWorkerJob,
WorkerStateSnapshot,
} from "$lib/generated/protocol";
import type {
@@ -88,6 +89,7 @@ export type Worker =
display_name: string;
tags: string[];
worker_state?: WorkerStateSnapshot | null;
job?: SubscriptionWorkerJob | null;
diagnostics: Diagnostic[];
};
@@ -88,6 +88,47 @@ Deno.test('workspace Worker snapshot keeps equal local ids from different Runtim
assertEquals([...projection.workers.keys()].sort(), ['runtime-a:1', 'runtime-b:1']);
});
Deno.test('workspace Worker snapshot and live update preserve canonical Job metadata', () => {
const projection = createWorkspaceWorkersProjection();
const snapshotWorker = worker('runtime-a', 'job-worker', 1);
snapshotWorker.job = {
job_id: 'check:T-1:r1',
attempt_id: 'check:T-1:r1:attempt:1',
purpose: 'ticket_item_check',
};
applyWorkspaceWorkersFrame(projection, {
protocol_version: 1,
frame: 'response',
message: {
result: 'subscribed',
payload: {
request_id: 'request-1',
subscription_id: 'subscription-1',
selector: { topic: 'workspace_workers' },
snapshot_revision: 1,
snapshot: { topic: 'workers', data: { workers: [snapshotWorker] } },
},
},
});
assertEquals(projection.workers.get('runtime-a:job-worker')?.job, snapshotWorker.job);
const updated = { ...snapshotWorker, subject_revision: 2 };
updated.job = { ...snapshotWorker.job, attempt_id: 'check:T-1:r1:attempt:2' };
applyWorkspaceWorkersFrame(projection, {
protocol_version: 1,
frame: 'event',
message: {
event: 'event',
data: {
subscription_id: 'subscription-1',
subject_revision: 2,
payload: { event: 'worker_upserted', data: { worker: updated } },
},
},
});
assertEquals(projection.workers.get('runtime-a:job-worker')?.job, updated.job);
});
Deno.test('workspace Worker reducer ignores stale events and removes composite subject', () => {
const projection = createWorkspaceWorkersProjection();
projection.workers.set('runtime-a:1', worker('runtime-a', '1', 3));
@@ -143,6 +143,7 @@ function projectWorker(
display_name: displayName,
label: displayName,
profile: worker.profile ?? null,
job: worker.job ?? null,
tags: [],
workspace: {
visibility: "workspace",
@@ -63,6 +63,19 @@ Deno.test("live runtime workers are sidebar targets and console targets", () =>
assertEquals(canOpenWorkerConsole(liveWorker), true);
});
Deno.test("Backend Job Workers are hidden only from the sidebar View", () => {
const jobWorker = worker({
state: "running",
job: {
job_id: "check:T-1:r1",
attempt_id: "check:T-1:r1:attempt:1",
purpose: "ticket_item_check",
},
});
assertEquals(canShowWorkerInSidebar(jobWorker), false);
assertEquals(canOpenWorkerConsole(jobWorker), true);
});
Deno.test("sidebar workers sort running then idle then stopped", () => {
const workers = [
worker({ worker_id: "3", display_name: "Stopped", state: "stopped" }),
@@ -22,15 +22,15 @@ export function sidebarWorkerActivity(
}
export function canShowWorkerInSidebar(
worker: Pick<Worker, 'implementation'>,
worker: Pick<Worker, 'implementation' | 'job'>,
): boolean {
return worker.implementation.kind !== 'backend_worker_registry';
return !worker.job && worker.implementation.kind !== 'backend_worker_registry';
}
export function canOpenWorkerConsole(
worker: Pick<Worker, 'implementation'>,
): boolean {
return canShowWorkerInSidebar(worker);
return worker.implementation.kind !== 'backend_worker_registry';
}
type SidebarWorkerLink = Pick<Worker, 'resource_key' | 'display_name'>;