ui: filter worker console protocol noise

This commit is contained in:
Keisuke Hirata 2026-07-01 23:32:39 +09:00
parent 11d94d3866
commit 9ae9b0a043
No known key found for this signature in database
3 changed files with 46 additions and 168 deletions

View File

@ -11,6 +11,14 @@ function assert(condition: unknown, message: string): asserts condition {
}
}
function assertEquals<T>(actual: T, expected: T): void {
const actualJson = JSON.stringify(actual);
const expectedJson = JSON.stringify(expected);
if (actualJson !== expectedJson) {
throw new Error(`Expected ${expectedJson}, got ${actualJson}`);
}
}
Deno.test("workerConsoleHref encodes runtime and worker target authority", () => {
assert(
workerConsoleHref({
@ -45,7 +53,7 @@ Deno.test("segmentsToText preserves protocol segment semantics", () => {
);
});
Deno.test("projectConsole projects initial console output and live protocol rows", () => {
Deno.test("projectConsole projects initial console output and live visible protocol rows", () => {
const projection = projectConsole(
[
{
@ -120,8 +128,8 @@ Deno.test("projectConsole projects initial console output and live protocol rows
"tool event row expected",
);
assert(
projection.lines.some((line) => line.kind === "usage"),
"usage event row expected",
!projection.lines.some((line) => line.kind === "usage"),
"usage should update the summary without rendering a console row",
);
assert(
projection.lines.some((line) => line.kind === "error" && line.error),
@ -133,6 +141,37 @@ Deno.test("projectConsole projects initial console output and live protocol rows
);
});
Deno.test("projectConsole keeps protocol lifecycle events out of the console surface", () => {
const projection = projectConsole([], [
{
cursor: "30",
event: { event: "status", data: { status: "running" } } satisfies Event,
},
{
cursor: "31",
event: { event: "llm_call_end", data: { llm_call: 0 } } satisfies Event,
},
{
cursor: "32",
event: { event: "turn_end", data: { turn: 0, result: "finished" } } satisfies Event,
},
{
cursor: "33",
event: { event: "run_end", data: { result: "finished" } } satisfies Event,
},
{
cursor: "34",
event: {
event: "system_item",
data: { item: { kind: "note", content: "internal" } },
} satisfies Event,
},
]);
assertEquals(projection.lines, []);
assertEquals(projection.status, "running");
});
Deno.test("projectConsole uses snapshot for state without rendering it as console output", () => {
const projection = projectConsole([], [
{

View File

@ -98,14 +98,7 @@ export function applyProtocolEvent(
);
break;
case "system_item":
next.lines.push(
line(
envelope.cursor,
"system",
"system item",
jsonPreview(event.data.item),
),
);
// System items are protocol/internal context, not console output.
break;
case "text_delta":
appendStreaming(
@ -189,7 +182,6 @@ export function applyProtocolEvent(
break;
case "usage":
next.usage = usageText(event.data);
next.lines.push(line(envelope.cursor, "usage", "usage", next.usage));
break;
case "error":
next.lines.push(
@ -212,179 +204,28 @@ export function applyProtocolEvent(
break;
case "status":
next.status = event.data.status;
next.lines.push(
line(envelope.cursor, "status", "status", event.data.status),
);
break;
case "invoke_start":
next.lines.push(
line(envelope.cursor, "status", "invoke start", event.data.kind),
);
break;
case "turn_start":
next.lines.push(
line(
envelope.cursor,
"status",
"turn start",
`turn ${event.data.turn}`,
),
);
break;
case "turn_end":
next.lines.push(
line(
envelope.cursor,
"status",
"turn end",
`turn ${event.data.turn} · ${event.data.result}`,
),
);
break;
case "llm_call_start":
next.lines.push(
line(
envelope.cursor,
"status",
"llm call start",
`call ${event.data.llm_call}`,
),
);
break;
case "llm_call_end":
next.lines.push(
line(
envelope.cursor,
"status",
"llm call end",
`call ${event.data.llm_call}`,
),
);
break;
case "llm_retry":
next.lines.push(
line(
envelope.cursor,
"status",
"llm retry",
`${event.data.error} · attempt ${event.data.failed_attempt}/${event.data.max_attempts}`,
),
);
break;
case "llm_continuation":
next.lines.push(
line(
envelope.cursor,
"status",
"llm continuation",
`${event.data.reason} · attempt ${event.data.attempt}/${event.data.max_attempts}`,
),
);
break;
case "run_end":
next.lines.push(
line(envelope.cursor, "status", "run end", event.data.result),
);
break;
case "alert":
next.lines.push(
line(
envelope.cursor,
"status",
`alert · ${event.data.level}`,
event.data.message,
),
);
break;
case "memory_worker":
next.lines.push(
line(envelope.cursor, "status", "memory worker", event.data.message),
);
break;
case "segment_rotated":
next.lines.push(
line(
envelope.cursor,
"status",
"segment rotated",
jsonPreview(event.data.entry),
),
);
break;
case "completions":
next.lines.push(
line(
envelope.cursor,
"status",
"completions",
`${event.data.kind} · ${event.data.entries.length} entries`,
),
);
break;
case "rewind_targets":
next.lines.push(
line(
envelope.cursor,
"status",
"rewind targets",
`${event.data.targets.length} targets · head ${event.data.head_entries}`,
),
);
break;
case "rewind_applied":
next.lines.push(
line(
envelope.cursor,
"status",
"rewind applied",
`${event.data.summary.discarded_entries} discarded · ${event.data.summary.truncated_to_entries} retained`,
),
);
break;
case "workers_listed":
next.lines.push(
line(
envelope.cursor,
"status",
"workers listed",
jsonPreview(event.data.workers),
),
);
break;
case "worker_restored":
next.lines.push(
line(
envelope.cursor,
"status",
"worker restored",
jsonPreview(event.data.result),
),
);
break;
case "peer_registered":
next.lines.push(
line(
envelope.cursor,
"status",
"peer registered",
jsonPreview(event.data.result),
),
);
break;
case "compact_start":
next.lines.push(
line(envelope.cursor, "status", "compact start", "compaction started"),
);
break;
case "compact_done":
next.lines.push(
line(
envelope.cursor,
"status",
"compact done",
event.data.new_segment_id,
),
);
// These are protocol/status/control events. TUI Console does not append
// them to the conversation surface; browser Console should not either.
break;
case "compact_failed":
next.lines.push(
@ -401,9 +242,6 @@ export function applyProtocolEvent(
break;
case "shutdown":
next.status = "shutdown";
next.lines.push(
line(envelope.cursor, "status", "shutdown", "worker shut down"),
);
break;
}

View File

@ -4,6 +4,7 @@ import { defineConfig } from 'vite';
export default defineConfig({
plugins: [sveltekit()],
server: {
allowedHosts: ['develop.hareworks.net'],
proxy: {
'/api': {
target: 'http://127.0.0.1:8787',