fix: show Web compacting status for tagged Worker states
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 --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 tests/skill-api.test.ts src/lib/workspace/auth/model.test.ts tests/auth-api.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 src/lib/workspace/console/composer-delivery.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/objective-pages.test.ts test/repositories/ui.test.ts src/lib/workspace/console/worker-console.ui.test.ts src/lib/workspace/settings/model.test.ts tests/runtime-connection.test.ts tests/runtime-management.test.ts tests/runtime-management-source.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/repository-ssh-connection-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 tests/skill-api.test.ts src/lib/workspace/auth/model.test.ts tests/auth-api.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 src/lib/workspace/console/composer-delivery.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/console/run-status.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/objective-pages.test.ts test/repositories/ui.test.ts src/lib/workspace/console/worker-console.ui.test.ts src/lib/workspace/settings/model.test.ts tests/runtime-connection.test.ts tests/runtime-management.test.ts tests/runtime-management-source.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/repository-ssh-connection-ui.test.ts",
|
||||
"build": "deno run -A npm:vite@7.2.7 build",
|
||||
"preview": "deno run -A npm:vite@7.2.7 preview"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
<script lang="ts">
|
||||
import type { InFlightCompaction } from "$lib/generated/protocol.ts";
|
||||
import type {
|
||||
InFlightCompaction,
|
||||
WorkerStateSnapshot,
|
||||
} from "$lib/generated/protocol.ts";
|
||||
import Spinner from "./Spinner.svelte";
|
||||
import { formatRunElapsed, formatRunTokens } from "./run-status";
|
||||
import {
|
||||
formatRunElapsed,
|
||||
formatRunTokens,
|
||||
resolveCompactionStatusPresentation,
|
||||
} from "./run-status";
|
||||
|
||||
type Props = {
|
||||
startedAtMs: number | null;
|
||||
@@ -9,12 +16,24 @@
|
||||
uploadTokens: number;
|
||||
outputTokens: number;
|
||||
compaction?: InFlightCompaction | null;
|
||||
workerState?: WorkerStateSnapshot | null;
|
||||
};
|
||||
|
||||
let { startedAtMs, requests, uploadTokens, outputTokens, compaction = null }: Props =
|
||||
$props();
|
||||
let {
|
||||
startedAtMs,
|
||||
requests,
|
||||
uploadTokens,
|
||||
outputTokens,
|
||||
compaction = null,
|
||||
workerState = null,
|
||||
}: Props = $props();
|
||||
let nowMs = $state(Date.now());
|
||||
const clockStartedAtMs = $derived(compaction?.started_at_ms ?? startedAtMs);
|
||||
const compactionStatus = $derived(
|
||||
resolveCompactionStatusPresentation(compaction, workerState),
|
||||
);
|
||||
const clockStartedAtMs = $derived(
|
||||
compactionStatus?.progress.started_at_ms ?? startedAtMs,
|
||||
);
|
||||
|
||||
$effect(() => {
|
||||
clockStartedAtMs;
|
||||
@@ -33,8 +52,8 @@
|
||||
|
||||
<div class="worker-run-status" role="status" aria-live="off">
|
||||
<Spinner />
|
||||
{#if compaction}
|
||||
<span>Compacting · {compaction.phase}</span>
|
||||
{#if compactionStatus}
|
||||
<span>{compactionStatus.label}</span>
|
||||
<span aria-hidden="true">|</span>
|
||||
{/if}
|
||||
<span>{elapsed}</span>
|
||||
|
||||
@@ -5,6 +5,8 @@ import {
|
||||
formatRunElapsed,
|
||||
formatRunElapsedCompact,
|
||||
formatRunTokens,
|
||||
resolveCompactionStatusPresentation,
|
||||
visibleCompactionProgress,
|
||||
} from "./run-status.ts";
|
||||
|
||||
function assertEquals(actual: unknown, expected: unknown): void {
|
||||
@@ -98,3 +100,57 @@ Deno.test("run status formatting matches the compact TUI shape", () => {
|
||||
assertEquals(formatRunTokens(3_000), "3.0k");
|
||||
assertEquals(formatRunTokens(999), "999");
|
||||
});
|
||||
|
||||
Deno.test("WorkerRunStatus presentation follows compaction and busy state", () => {
|
||||
const manual = {
|
||||
phase: "preparing",
|
||||
started_at_ms: 1_700_000_000_000,
|
||||
trigger: "manual",
|
||||
} as const;
|
||||
const automatic = {
|
||||
phase: "summarizing",
|
||||
started_at_ms: 1_700_000_000_000,
|
||||
trigger: "request_threshold",
|
||||
} as const;
|
||||
const maintenance = {
|
||||
last_command_id: 7,
|
||||
state: {
|
||||
kind: "busy",
|
||||
state: { kind: "maintenance", state: "compacting" },
|
||||
},
|
||||
} as const;
|
||||
const run = {
|
||||
last_command_id: 7,
|
||||
state: {
|
||||
kind: "busy",
|
||||
state: { kind: "run", state: "running" },
|
||||
},
|
||||
} as const;
|
||||
const idle = {
|
||||
last_command_id: 7,
|
||||
state: { kind: "idle" },
|
||||
} as const;
|
||||
|
||||
assertEquals(visibleCompactionProgress(manual, maintenance), manual);
|
||||
assertEquals(visibleCompactionProgress(automatic, run), automatic);
|
||||
assertEquals(visibleCompactionProgress(manual, run), null);
|
||||
assertEquals(visibleCompactionProgress(automatic, maintenance), null);
|
||||
assertEquals(visibleCompactionProgress(manual, idle), null);
|
||||
assertEquals(visibleCompactionProgress(manual, null), null);
|
||||
assertEquals(visibleCompactionProgress(null, maintenance), null);
|
||||
|
||||
assertEquals(resolveCompactionStatusPresentation(manual, maintenance), {
|
||||
progress: manual,
|
||||
label: "Compacting · preparing",
|
||||
});
|
||||
assertEquals(resolveCompactionStatusPresentation(automatic, run), {
|
||||
progress: automatic,
|
||||
label: "Compacting · summarizing",
|
||||
});
|
||||
assertEquals(resolveCompactionStatusPresentation(manual, run), null);
|
||||
assertEquals(
|
||||
resolveCompactionStatusPresentation(automatic, maintenance),
|
||||
null,
|
||||
);
|
||||
assertEquals(resolveCompactionStatusPresentation(manual, idle), null);
|
||||
});
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import type { Event as ProtocolEvent } from "$lib/generated/protocol";
|
||||
import type {
|
||||
Event as ProtocolEvent,
|
||||
InFlightCompaction,
|
||||
WorkerStateSnapshot,
|
||||
} from "$lib/generated/protocol";
|
||||
|
||||
export type RunActivityStats = {
|
||||
startedAtMs: number | null;
|
||||
@@ -71,3 +75,35 @@ export function formatRunTokens(tokens: number): string {
|
||||
if (tokens >= 1_000) return `${(tokens / 1_000).toFixed(1)}k`;
|
||||
return String(tokens);
|
||||
}
|
||||
|
||||
export type CompactionStatusPresentation = {
|
||||
progress: InFlightCompaction;
|
||||
label: string;
|
||||
};
|
||||
|
||||
export function resolveCompactionStatusPresentation(
|
||||
compaction: InFlightCompaction | null,
|
||||
workerState: WorkerStateSnapshot | null,
|
||||
): CompactionStatusPresentation | null {
|
||||
const progress = visibleCompactionProgress(compaction, workerState);
|
||||
return progress
|
||||
? { progress, label: `Compacting · ${progress.phase}` }
|
||||
: null;
|
||||
}
|
||||
|
||||
export function visibleCompactionProgress(
|
||||
compaction: InFlightCompaction | null,
|
||||
workerState: WorkerStateSnapshot | null,
|
||||
): InFlightCompaction | null {
|
||||
if (!compaction || workerState?.state.kind !== "busy") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const busyState = workerState.state.state;
|
||||
const triggerMatches = compaction.trigger === "manual"
|
||||
? busyState.kind === "maintenance" &&
|
||||
busyState.state === "compacting"
|
||||
: busyState.kind === "run";
|
||||
|
||||
return triggerMatches ? compaction : null;
|
||||
}
|
||||
|
||||
@@ -1116,3 +1116,29 @@ Deno.test("Web Console uses Notify while running and exposes durable pending con
|
||||
"ordinary text must remain Submit instead of being implicitly converted to Notify",
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("Web Console gates compaction progress before rendering run status", async () => {
|
||||
const consolePage = await Deno.readTextFile(
|
||||
new URL(
|
||||
"./../../../routes/w/[workspaceId]/workers/[workerRef]/console/+page.svelte",
|
||||
import.meta.url,
|
||||
),
|
||||
);
|
||||
const runStatus = await Deno.readTextFile(
|
||||
new URL("./WorkerRunStatus.svelte", import.meta.url),
|
||||
);
|
||||
|
||||
assert(
|
||||
consolePage.includes("compaction={consoleProjection.compaction}") &&
|
||||
consolePage.includes("workerState={consoleProjection.workerState}"),
|
||||
"Console page should pass compaction metadata with authoritative worker state to the status component",
|
||||
);
|
||||
assert(
|
||||
runStatus.includes("resolveCompactionStatusPresentation(") &&
|
||||
runStatus.includes("{compactionStatus.label}") &&
|
||||
runStatus.includes(
|
||||
"compactionStatus?.progress.started_at_ms ?? startedAtMs",
|
||||
),
|
||||
"run status should render the guarded presentation and derive elapsed time from its authoritative start timestamp",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -248,18 +248,6 @@
|
||||
liveWorkerState ?? (worker?.state === "stopped" ? "stopped" : "loading"),
|
||||
);
|
||||
const workerRunning = $derived(workerState === "running");
|
||||
const compactionProgress = $derived.by(() => {
|
||||
const state = consoleProjection.workerState?.state;
|
||||
if (!state || typeof state !== "object" || !("busy" in state)) return null;
|
||||
const progress = consoleProjection.compaction;
|
||||
if (!progress) return null;
|
||||
const busy = state.busy;
|
||||
if (!busy || typeof busy !== "object") return null;
|
||||
const valid = progress.trigger === "manual"
|
||||
? "maintenance" in busy && busy.maintenance === "compacting"
|
||||
: "run" in busy;
|
||||
return valid ? progress : null;
|
||||
});
|
||||
const workerPaused = $derived(workerState === "paused");
|
||||
const composerEditable = $derived(protocolState === "open" && !sending);
|
||||
const draftHasText = $derived(draft.content.trim().length > 0);
|
||||
@@ -1917,7 +1905,8 @@
|
||||
requests={consoleProjection.runActivity.requests}
|
||||
uploadTokens={consoleProjection.runActivity.uploadTokens}
|
||||
outputTokens={consoleProjection.runActivity.outputTokens}
|
||||
compaction={compactionProgress}
|
||||
compaction={consoleProjection.compaction}
|
||||
workerState={consoleProjection.workerState}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user