diff --git a/web/hwlab-cloud-web/scripts/workbench-performance.test.ts b/web/hwlab-cloud-web/scripts/workbench-performance.test.ts new file mode 100644 index 00000000..9a51b4e7 --- /dev/null +++ b/web/hwlab-cloud-web/scripts/workbench-performance.test.ts @@ -0,0 +1,65 @@ +// SPEC: PJ2026-01060505 Workbench Performance draft-2026-06-17-p0 +// Exercises the browser-side Workbench performance probe without sending network traffic. + +import assert from "node:assert/strict"; +import test from "node:test"; + +import type { ChatMessage, TraceEvent } from "../src/types/index.ts"; +import { + acknowledgeWorkbenchVisible, + drainWorkbenchPerformanceEventsForTest, + finishWorkbenchOpenFullLoad, + finishWorkbenchSessionSwitchFullLoad, + markWorkbenchSubmitApiAccepted, + markWorkbenchTraceEventsReceived, + markWorkbenchTraceProjected, + resetWorkbenchPerformanceForTest, + startWorkbenchOpenJourney, + startWorkbenchSessionSwitch, + startWorkbenchSubmitJourney +} from "../src/utils/workbench-performance.ts"; + +test("Workbench performance probe records journeys and strips high-cardinality identifiers", () => { + resetWorkbenchPerformanceForTest(); + startWorkbenchOpenJourney({ route: "/workbench/sessions/cnv_secret", cache: "cold", authState: "warm" }); + acknowledgeWorkbenchVisible({ messages: [], activeConversationId: "cnv_secret", detailLoading: false }); + finishWorkbenchOpenFullLoad("ok"); + + startWorkbenchSessionSwitch({ conversationId: "cnv_secret", source: "rail", targetState: "running", cache: "warm" }); + acknowledgeWorkbenchVisible({ messages: [userMessage("cnv_secret")], activeConversationId: "cnv_secret", detailLoading: false }); + finishWorkbenchSessionSwitchFullLoad("cnv_secret", "ok"); + + const createdAt = new Date(Date.now() - 250).toISOString(); + startWorkbenchSubmitJourney({ traceId: "trc_secret", conversationId: "cnv_secret", entry: "existing", backend: "codex", transport: "sse" }); + markWorkbenchSubmitApiAccepted("trc_secret"); + markWorkbenchTraceEventsReceived({ traceId: "trc_secret", transport: "sse", events: [{ type: "assistant_message", createdAt, ts: createdAt } as TraceEvent] }); + markWorkbenchTraceProjected("trc_secret"); + acknowledgeWorkbenchVisible({ messages: [agentMessage("cnv_secret", "trc_secret")], activeConversationId: "cnv_secret", detailLoading: false }); + + const events = drainWorkbenchPerformanceEventsForTest(); + const payload = JSON.stringify(events); + assert.ok(events.some((event) => event.kind === "workbench_journey" && event.journey === "workbench_open_first_visible")); + assert.ok(events.some((event) => event.kind === "workbench_journey" && event.journey === "session_switch_first_visible")); + assert.ok(events.some((event) => event.kind === "workbench_journey" && event.journey === "submit_to_first_visible" && event.backend === "agentrun-v01/codex")); + assert.ok(events.some((event) => event.kind === "workbench_event_phase" && event.phase === "project_to_paint")); + assert.ok(events.some((event) => event.kind === "workbench_backend_event_visible" && event.eventType === "assistant")); + assert.doesNotMatch(payload, /trc_secret|cnv_secret|sessionId|conversationId|runId|commandId/u); +}); + +function userMessage(conversationId: string): ChatMessage { + return { id: "msg_user", role: "user", title: "User", text: "hello", status: "sent", createdAt: new Date().toISOString(), conversationId }; +} + +function agentMessage(conversationId: string, traceId: string): ChatMessage { + return { + id: "msg_agent", + role: "agent", + title: "Code Agent", + text: "visible", + status: "running", + createdAt: new Date().toISOString(), + conversationId, + traceId, + runnerTrace: { traceId, eventCount: 1, events: [{ type: "assistant_message" }] } + }; +} diff --git a/web/hwlab-cloud-web/src/components/workbench/ConversationPanel.vue b/web/hwlab-cloud-web/src/components/workbench/ConversationPanel.vue index f4a351ef..a0df1189 100644 --- a/web/hwlab-cloud-web/src/components/workbench/ConversationPanel.vue +++ b/web/hwlab-cloud-web/src/components/workbench/ConversationPanel.vue @@ -1,4 +1,7 @@