fix: route worker consoles through logical resources
This commit is contained in:
@@ -12,8 +12,8 @@ import {
|
||||
resolveConsoleWorkerView,
|
||||
segmentsToText,
|
||||
selectConsoleTimelineLines,
|
||||
workerConsoleHref,
|
||||
} from "./model.ts";
|
||||
import { workerConsoleHref } from "../resource-links.ts";
|
||||
|
||||
declare const Deno: {
|
||||
test(name: string, fn: () => void): void;
|
||||
@@ -461,14 +461,13 @@ Deno.test("reload snapshot projects provenance-annotated history entries", () =>
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("workerConsoleHref encodes runtime and worker target authority", () => {
|
||||
Deno.test("workerConsoleHref uses logical Worker resource authority", () => {
|
||||
assert(
|
||||
workerConsoleHref({
|
||||
runtime_id: "local runtime",
|
||||
worker_id: "worker/one",
|
||||
}, "workspace-1") ===
|
||||
"/w/workspace-1/runtimes/local%20runtime/workers/worker%2Fone/console",
|
||||
"href should contain encoded runtime_id and worker_id segments",
|
||||
workerConsoleHref("workspace-1", {
|
||||
resource_key: "W-123",
|
||||
display_name: "Worker One",
|
||||
}) === "/w/workspace-1/workers/W-123-worker-one/console",
|
||||
"href should contain the canonical logical Worker reference",
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ import type {
|
||||
WorkerStatus,
|
||||
} from "$lib/generated/protocol";
|
||||
import { stringify as stringifyYaml } from "yaml";
|
||||
import { workspaceRoute } from "$lib/workspace/api/http";
|
||||
import {
|
||||
applyRunActivityEvent,
|
||||
emptyRunActivityStats,
|
||||
@@ -220,31 +219,6 @@ export type WorkerTarget = {
|
||||
worker_id: string;
|
||||
};
|
||||
|
||||
export function workerConsoleHref(
|
||||
target: WorkerTarget,
|
||||
workspaceId: string,
|
||||
): string {
|
||||
return workspaceRoute(
|
||||
workspaceId,
|
||||
`/runtimes/${encodeURIComponent(target.runtime_id)}/workers/${
|
||||
encodeURIComponent(
|
||||
target.worker_id,
|
||||
)
|
||||
}/console`,
|
||||
);
|
||||
}
|
||||
|
||||
export function workerConsolePath(
|
||||
workspaceId: string,
|
||||
runtimeId: string,
|
||||
workerId: string,
|
||||
): string {
|
||||
return workerConsoleHref(
|
||||
{ runtime_id: runtimeId, worker_id: workerId },
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
export type ConsoleEventInput = {
|
||||
eventId: string;
|
||||
event: ProtocolEvent;
|
||||
|
||||
@@ -371,7 +371,7 @@ Deno.test("root layout keeps Workspace selection explicit", async () => {
|
||||
Deno.test("Worker Console uses protocol observation events without transcript fetch", async () => {
|
||||
const consolePage = await Deno.readTextFile(
|
||||
new URL(
|
||||
"./../../../routes/w/[workspaceId]/runtimes/[runtimeId]/workers/[workerId]/console/+page.svelte",
|
||||
"./../../../routes/w/[workspaceId]/workers/[workerRef]/console/+page.svelte",
|
||||
import.meta.url,
|
||||
),
|
||||
);
|
||||
@@ -395,7 +395,7 @@ Deno.test("Worker Console uses protocol observation events without transcript fe
|
||||
Deno.test("Worker Console owns its narrower centered shell width", async () => {
|
||||
const page = await Deno.readTextFile(
|
||||
new URL(
|
||||
"./../../../routes/w/[workspaceId]/runtimes/[runtimeId]/workers/[workerId]/console/+page.svelte",
|
||||
"./../../../routes/w/[workspaceId]/workers/[workerRef]/console/+page.svelte",
|
||||
import.meta.url,
|
||||
),
|
||||
);
|
||||
@@ -494,7 +494,7 @@ Deno.test("Worker Console renders Edit diffs without preformatted template gaps"
|
||||
Deno.test("Worker Console exposes a foldable timeline beside the scroll body", async () => {
|
||||
const consolePage = await Deno.readTextFile(
|
||||
new URL(
|
||||
"./../../../routes/w/[workspaceId]/runtimes/[runtimeId]/workers/[workerId]/console/+page.svelte",
|
||||
"./../../../routes/w/[workspaceId]/workers/[workerRef]/console/+page.svelte",
|
||||
import.meta.url,
|
||||
),
|
||||
);
|
||||
@@ -529,7 +529,7 @@ Deno.test("Worker Console exposes a foldable timeline beside the scroll body", a
|
||||
Deno.test("Worker Console removes redundant chrome and uses shared alerts", async () => {
|
||||
const page = await Deno.readTextFile(
|
||||
new URL(
|
||||
"./../../../routes/w/[workspaceId]/runtimes/[runtimeId]/workers/[workerId]/console/+page.svelte",
|
||||
"./../../../routes/w/[workspaceId]/workers/[workerRef]/console/+page.svelte",
|
||||
import.meta.url,
|
||||
),
|
||||
);
|
||||
@@ -558,7 +558,7 @@ Deno.test("Worker Console removes redundant chrome and uses shared alerts", asyn
|
||||
Deno.test("Worker Console composer keeps a compact bounded chip editor", async () => {
|
||||
const consolePage = await Deno.readTextFile(
|
||||
new URL(
|
||||
"./../../../routes/w/[workspaceId]/runtimes/[runtimeId]/workers/[workerId]/console/+page.svelte",
|
||||
"./../../../routes/w/[workspaceId]/workers/[workerRef]/console/+page.svelte",
|
||||
import.meta.url,
|
||||
),
|
||||
);
|
||||
@@ -592,7 +592,7 @@ Deno.test("Worker Console composer keeps a compact bounded chip editor", async (
|
||||
Deno.test("Worker Console paste chips preserve typed draft and target authority", async () => {
|
||||
const consolePage = await Deno.readTextFile(
|
||||
new URL(
|
||||
"./../../../routes/w/[workspaceId]/runtimes/[runtimeId]/workers/[workerId]/console/+page.svelte",
|
||||
"./../../../routes/w/[workspaceId]/workers/[workerRef]/console/+page.svelte",
|
||||
import.meta.url,
|
||||
),
|
||||
);
|
||||
@@ -724,24 +724,28 @@ Deno.test("workspace Worker sidebar links New to the dedicated create page", asy
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("Worker Console page is routed by runtime_id and worker_id through backend APIs", async () => {
|
||||
Deno.test("Worker Console route resolves logical Worker authority before Runtime APIs", async () => {
|
||||
const consolePage = await Deno.readTextFile(
|
||||
new URL(
|
||||
"./../../../routes/w/[workspaceId]/runtimes/[runtimeId]/workers/[workerId]/console/+page.svelte",
|
||||
"./../../../routes/w/[workspaceId]/workers/[workerRef]/console/+page.svelte",
|
||||
import.meta.url,
|
||||
),
|
||||
);
|
||||
const routeLoad = await Deno.readTextFile(
|
||||
new URL(
|
||||
"./../../../routes/w/[workspaceId]/runtimes/[runtimeId]/workers/[workerId]/console/+page.ts",
|
||||
"./../../../routes/w/[workspaceId]/workers/[workerRef]/console/+page.ts",
|
||||
import.meta.url,
|
||||
),
|
||||
);
|
||||
|
||||
assert(
|
||||
routeLoad.includes("workspaceId") &&
|
||||
routeLoad.includes("runtimeId") && routeLoad.includes("workerId"),
|
||||
"route load should expose workspace and target ids",
|
||||
routeLoad.includes("params.workerRef") &&
|
||||
routeLoad.includes("resourceKey(params.workerRef)") &&
|
||||
routeLoad.includes("/workers/${encodeURIComponent(reference)}") &&
|
||||
routeLoad.includes("canonicalResourceReference") &&
|
||||
routeLoad.includes("runtimeId: result.data?.runtime_id") &&
|
||||
routeLoad.includes("workerId: result.data?.worker_id"),
|
||||
"route load should resolve the logical Worker reference before exposing its execution target",
|
||||
);
|
||||
assert(
|
||||
consolePage.includes("workspaceApiPath(workspaceId, path)") &&
|
||||
@@ -951,7 +955,7 @@ Deno.test("Account UI owns browser passkey session state without workspace autho
|
||||
Deno.test("Workspace Worker list and Console share the multiplexed connection", async () => {
|
||||
const consolePage = await Deno.readTextFile(
|
||||
new URL(
|
||||
"./../../../routes/w/[workspaceId]/runtimes/[runtimeId]/workers/[workerId]/console/+page.svelte",
|
||||
"./../../../routes/w/[workspaceId]/workers/[workerRef]/console/+page.svelte",
|
||||
import.meta.url,
|
||||
),
|
||||
);
|
||||
@@ -988,7 +992,7 @@ Deno.test("Workspace Worker list and Console share the multiplexed connection",
|
||||
Deno.test("Web Console renders the client-projected Worker task store", async () => {
|
||||
const consolePage = await Deno.readTextFile(
|
||||
new URL(
|
||||
"./../../../routes/w/[workspaceId]/runtimes/[runtimeId]/workers/[workerId]/console/+page.svelte",
|
||||
"./../../../routes/w/[workspaceId]/workers/[workerRef]/console/+page.svelte",
|
||||
import.meta.url,
|
||||
),
|
||||
);
|
||||
@@ -1027,7 +1031,7 @@ Deno.test("Web Console renders the client-projected Worker task store", async ()
|
||||
Deno.test("Web Console switches main and direct SubWorker views from the Tasks row", async () => {
|
||||
const consolePage = await Deno.readTextFile(
|
||||
new URL(
|
||||
"./../../../routes/w/[workspaceId]/runtimes/[runtimeId]/workers/[workerId]/console/+page.svelte",
|
||||
"./../../../routes/w/[workspaceId]/workers/[workerRef]/console/+page.svelte",
|
||||
import.meta.url,
|
||||
),
|
||||
);
|
||||
@@ -1071,7 +1075,7 @@ Deno.test("Web Console switches main and direct SubWorker views from the Tasks r
|
||||
Deno.test("Web Console uses Notify while running and exposes durable pending controls", async () => {
|
||||
const consolePage = await Deno.readTextFile(
|
||||
new URL(
|
||||
"./../../../routes/w/[workspaceId]/runtimes/[runtimeId]/workers/[workerId]/console/+page.svelte",
|
||||
"./../../../routes/w/[workspaceId]/workers/[workerRef]/console/+page.svelte",
|
||||
import.meta.url,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -25,7 +25,7 @@ Deno.test("Ticket detail breadcrumbs expose the Ticket list and current id", ()
|
||||
Deno.test("Worker console breadcrumbs use the logical Workers route and display name", () => {
|
||||
assertEquals(
|
||||
buildWorkspaceBreadcrumbs(
|
||||
"/w/workspace/runtimes/runtime-a/workers/worker-7/console",
|
||||
"/w/workspace/workers/W-7-review-worker/console",
|
||||
"workspace",
|
||||
{ workerName: "Review Worker" },
|
||||
),
|
||||
@@ -39,12 +39,12 @@ Deno.test("Worker console breadcrumbs use the logical Workers route and display
|
||||
Deno.test("Worker console breadcrumbs fall back to Worker id", () => {
|
||||
assertEquals(
|
||||
buildWorkspaceBreadcrumbs(
|
||||
"/w/workspace/runtimes/runtime-a/workers/worker-7/console",
|
||||
"/w/workspace/workers/W-7-review-worker/console",
|
||||
"workspace",
|
||||
),
|
||||
[
|
||||
{ label: "workers", href: "/w/workspace/workers" },
|
||||
{ label: "worker-7" },
|
||||
{ label: "W-7-review-worker" },
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
@@ -24,13 +24,13 @@ export function buildWorkspaceBreadcrumbs(
|
||||
.map(decodeURIComponent);
|
||||
|
||||
if (
|
||||
segments[0] === "runtimes" &&
|
||||
segments[2] === "workers" &&
|
||||
segments[3]
|
||||
segments[0] === "workers" &&
|
||||
segments[1] &&
|
||||
segments[2] === "console"
|
||||
) {
|
||||
return [
|
||||
{ label: "workers", href: `${workspaceRoot}/workers` },
|
||||
{ label: context.workerName?.trim() || segments[3] },
|
||||
{ label: context.workerName?.trim() || segments[1] },
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -45,3 +45,10 @@ export function workerHref(
|
||||
const reference = canonicalResourceReference(worker.resource_key, worker.display_name);
|
||||
return `/w/${encodeURIComponent(workspaceId)}/workers/${encodeURIComponent(reference)}`;
|
||||
}
|
||||
|
||||
export function workerConsoleHref(
|
||||
workspaceId: string,
|
||||
worker: { resource_key: string; display_name: string },
|
||||
): string {
|
||||
return `${workerHref(workspaceId, worker)}/console`;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import Spinner from '$lib/workspace/console/Spinner.svelte';
|
||||
import { workerConsoleHref } from '$lib/workspace/console/model';
|
||||
import { workerConsoleHref } from '$lib/workspace/resource-links';
|
||||
import { pushWorkspaceAlert } from '$lib/workspace/alerts/store';
|
||||
import {
|
||||
canDeleteSidebarWorker,
|
||||
@@ -168,7 +168,7 @@
|
||||
{#if error}<p class="section-state error">{error}</p>{/if}
|
||||
<ul class="nav-list" aria-label="Workers">
|
||||
{#each visibleWorkers as worker (`${worker.runtime_id}:${worker.worker_id}`)}
|
||||
{@const href = workerConsoleHref(worker, workspaceId)}
|
||||
{@const href = workerConsoleHref(workspaceId, worker)}
|
||||
{@const activity = sidebarWorkerActivity(worker)}
|
||||
{@const key = workerKey(worker)}
|
||||
{@const label = worker.display_name || worker.label}
|
||||
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
import { loadJson, workspaceApiPath } from "$lib/workspace/api/http";
|
||||
import type { Worker } from "$lib/workspace/sidebar/types";
|
||||
import type { PageLoad } from "./$types";
|
||||
|
||||
export const load: PageLoad = async ({ fetch, params }) => {
|
||||
const worker = await loadJson<Worker>(
|
||||
fetch,
|
||||
workspaceApiPath(
|
||||
params.workspaceId,
|
||||
`/runtimes/${encodeURIComponent(params.runtimeId)}/workers/${
|
||||
encodeURIComponent(params.workerId)
|
||||
}`,
|
||||
),
|
||||
);
|
||||
|
||||
return {
|
||||
workspaceId: params.workspaceId,
|
||||
runtimeId: params.runtimeId,
|
||||
workerId: params.workerId,
|
||||
worker: worker.data,
|
||||
workerError: worker.error,
|
||||
};
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { workspaceRoute } from '$lib/workspace/api/http';
|
||||
import { workerConsoleHref } from '$lib/workspace/resource-links';
|
||||
import { liveWorkerState } from '$lib/workspace/sidebar/worker-state';
|
||||
import type { PageData } from './$types';
|
||||
let { data }: { data: PageData } = $props();
|
||||
@@ -18,10 +18,7 @@
|
||||
</div>
|
||||
<a
|
||||
class="button-primary"
|
||||
href={workspaceRoute(
|
||||
data.workspaceId,
|
||||
`/runtimes/${data.worker.runtime_id}/workers/${data.worker.worker_id}/console`,
|
||||
)}
|
||||
href={workerConsoleHref(data.workspaceId, data.worker)}
|
||||
>Open console</a>
|
||||
</header>
|
||||
<dl class="resource-meta">
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { redirect } from "@sveltejs/kit";
|
||||
import { loadJson, workspaceApiPath } from "$lib/workspace/api/http";
|
||||
import {
|
||||
canonicalResourceReference,
|
||||
resourceKey,
|
||||
} from "$lib/workspace/resource-links";
|
||||
import type { Worker } from "$lib/workspace/sidebar/types";
|
||||
import type { PageLoad } from "./$types";
|
||||
|
||||
export const load = (async ({ fetch, params }) => {
|
||||
const reference = resourceKey(params.workerRef);
|
||||
const result = await loadJson<Worker>(
|
||||
fetch,
|
||||
workspaceApiPath(
|
||||
params.workspaceId,
|
||||
`/workers/${encodeURIComponent(reference)}`,
|
||||
),
|
||||
);
|
||||
if (result.data?.resource_key) {
|
||||
const canonical = canonicalResourceReference(
|
||||
result.data.resource_key,
|
||||
result.data.display_name,
|
||||
);
|
||||
if (params.workerRef !== canonical) {
|
||||
redirect(
|
||||
308,
|
||||
`/w/${encodeURIComponent(params.workspaceId)}/workers/${encodeURIComponent(canonical)}/console`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
workspaceId: params.workspaceId,
|
||||
runtimeId: result.data?.runtime_id ?? "",
|
||||
workerId: result.data?.worker_id ?? "",
|
||||
worker: result.data,
|
||||
workerError: result.error,
|
||||
};
|
||||
}) satisfies PageLoad;
|
||||
@@ -186,7 +186,7 @@ Deno.test("Composer input uses boundary-aware Up and Down history navigation", a
|
||||
);
|
||||
const consoleSource = await Deno.readTextFile(
|
||||
new URL(
|
||||
"../src/routes/w/[workspaceId]/runtimes/[runtimeId]/workers/[workerId]/console/+page.svelte",
|
||||
"../src/routes/w/[workspaceId]/workers/[workerRef]/console/+page.svelte",
|
||||
import.meta.url,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -87,7 +87,7 @@ Deno.test("sidebar running status reuses the green symbol spinner", async () =>
|
||||
Deno.test("running status is Composer-side above mini Tasks", async () => {
|
||||
const page = await Deno.readTextFile(
|
||||
new URL(
|
||||
"../src/routes/w/[workspaceId]/runtimes/[runtimeId]/workers/[workerId]/console/+page.svelte",
|
||||
"../src/routes/w/[workspaceId]/workers/[workerRef]/console/+page.svelte",
|
||||
import.meta.url,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -6,7 +6,7 @@ function assert(condition: unknown, message: string): asserts condition {
|
||||
Deno.test("Worker Console exposes Overview and Normal display modes", async () => {
|
||||
const page = await Deno.readTextFile(
|
||||
new URL(
|
||||
"../src/routes/w/[workspaceId]/runtimes/[runtimeId]/workers/[workerId]/console/+page.svelte",
|
||||
"../src/routes/w/[workspaceId]/workers/[workerRef]/console/+page.svelte",
|
||||
import.meta.url,
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user