Files
pikasTech-HWLAB/web/hwlab-cloud-web/scripts/workbench-message-projection-runtime.test.ts

265 lines
11 KiB
TypeScript

// SPEC: PJ2026-0106050514 Workbench实时运行面 draft-2026-06-30-p0-1297-spec-first; PJ2026-0104010803 Workbench唯一投影 draft-2026-06-18-p0-unique-projection.
// Responsibility: Regression tests for pure Workbench trace/message/projection merge helpers before Pinia integration.
import assert from "node:assert/strict";
import test from "node:test";
import type { AgentChatResultResponse, ChatMessage } from "../src/types/index.ts";
import { clearRunnerTraceTransientDiagnostics, mergeTerminalResultTrace, messageHasTerminalResponse, messageStatusPatchForTerminalMerge, messageTimingPatchForMerge, messageTimingSealPatchForProjectionMerge, nonBlockingProjection, normalizeAgentError, projectionFromResult, shouldClearCompletedTurnDiagnostics, terminalAuthorityMessageFromTurnResult, terminalMessagePatchFromTurnResult, terminalMessageTimingPatchForNormalize } from "../src/stores/workbench-message-projection-runtime.ts";
test("terminal result merge preserves runner trace evidence and timing", () => {
const previous: ChatMessage["runnerTrace"] = {
traceId: "trc_1",
sessionId: "ses_1",
events: [{ id: "evt_old", type: "tool" } as never],
eventCount: 1,
projection: { projectionStatus: "blocked", projectionHealth: "degraded", blocker: { code: "old" } },
updatedAt: "2026-01-01T00:00:00.000Z"
};
const result = {
traceId: "trc_1",
sessionId: "ses_1",
status: "completed",
terminal: true,
events: [{ id: "evt_new", type: "finish" }],
eventCount: 1,
startedAt: "2026-01-01T00:00:00.000Z",
finishedAt: "2026-01-01T00:00:05.000Z",
durationMs: 5000,
projectionStatus: "caught-up",
projectionHealth: "healthy",
agentRun: { runId: "run_1", backendProfile: "codex-api" }
} as unknown as AgentChatResultResponse;
const merged = mergeTerminalResultTrace(previous, result);
assert.equal(merged.traceId, "trc_1");
assert.equal(merged.sessionId, "ses_1");
assert.equal(merged.eventCount, 1);
assert.equal(merged.events?.[0]?.id, "evt_old");
assert.equal(merged.terminalEvidence, undefined);
assert.equal(merged.timing?.durationMs, 5000);
assert.equal((merged.agentRun as { runId?: string } | undefined)?.runId, "run_1");
assert.equal(merged.projectionStatus, "caught-up");
});
test("message projection runtime clears only non-blocking completed diagnostics", () => {
const error = normalizeAgentError({ message: "provider failed", diagnostic: { code: "provider_error", source: "provider" } });
assert.equal(error?.message, "provider failed");
assert.equal(error?.diagnostic?.code, "provider_error");
assert.equal(shouldClearCompletedTurnDiagnostics("completed", error), false);
assert.equal(shouldClearCompletedTurnDiagnostics("completed", null), true);
const projection = projectionFromResult({ projectionStatus: "caught-up", projectionHealth: "healthy" } as AgentChatResultResponse);
assert.equal(nonBlockingProjection(projection)?.projectionStatus, "caught-up");
assert.equal(nonBlockingProjection({ projectionStatus: "blocked", projectionHealth: "degraded", blocker: { code: "read_model_gap" } }), null);
const cleared = clearRunnerTraceTransientDiagnostics({ traceId: "trc_1", error, projection, projectionStatus: "caught-up", projectionHealth: "healthy", staleMs: 10, blocker: { code: "old" } });
assert.equal(cleared.error, undefined);
assert.equal(cleared.projection, null);
assert.equal(cleared.blocker, null);
});
test("message timing and terminal status patches stay independent from store state", () => {
const message = {
role: "agent",
status: "running",
startedAt: "2026-01-01T00:00:00.000Z",
timing: { startedAt: "2026-01-01T00:00:00.000Z", valuesRedacted: true }
} as ChatMessage;
const result = {
status: "completed",
terminal: true,
startedAt: "2026-01-01T00:00:00.000Z",
finishedAt: "2026-01-01T00:00:03.500Z"
};
const timingPatch = messageTimingPatchForMerge(message, result);
assert.equal(timingPatch.durationMs, 3500);
assert.equal(timingPatch.finishedAt, "2026-01-01T00:00:03.500Z");
assert.deepEqual(messageStatusPatchForTerminalMerge(message, "completed", true), { status: "completed" });
const terminalPatch = terminalMessageTimingPatchForNormalize(result);
assert.equal(terminalPatch.durationMs, 3500);
});
test("terminal turn result seals completed final response body", () => {
const restored = {
role: "agent",
status: "running",
text: "",
traceAutoLifecycle: "running",
traceId: "trc_unit_final",
runnerTrace: { traceId: "trc_unit_final", status: "running" }
} as ChatMessage;
const terminal = terminalMessagePatchFromTurnResult(restored, {
traceId: "trc_unit_final",
status: "completed",
terminal: true,
finalResponse: { text: "final answer" },
startedAt: "2026-01-01T00:00:00.000Z",
finishedAt: "2026-01-01T00:00:04.000Z",
durationMs: 4000
} as AgentChatResultResponse);
assert.equal(terminal?.status, "completed");
assert.equal(terminal?.traceAutoLifecycle, "terminal");
assert.equal(terminal?.text, "final answer");
assert.equal((terminal?.finalResponse as { text?: string } | undefined)?.text, "final answer");
});
test("terminal turn authority can project final response without a session message page row", () => {
const terminal = terminalAuthorityMessageFromTurnResult({
traceId: "trc_direct_terminal",
sessionId: "ses_direct_terminal",
threadId: "thr_direct_terminal",
status: "completed",
terminal: true,
finalResponse: { text: "direct final body" },
startedAt: "2026-07-02T13:18:40.000Z",
finishedAt: "2026-07-02T13:18:47.000Z"
} as AgentChatResultResponse);
assert.equal(terminal?.id, "msg_trc_direct_terminal_terminal_authority");
assert.equal(terminal?.status, "completed");
assert.equal(terminal?.traceAutoLifecycle, "terminal");
assert.equal(terminal?.text, "direct final body");
assert.equal(messageHasTerminalResponse(terminal), true);
});
test("terminal turn authority does not synthesize an empty completed message", () => {
const terminal = terminalAuthorityMessageFromTurnResult({
traceId: "trc_direct_terminal_empty",
status: "completed",
terminal: true,
terminalSealBlocked: true,
waitingFor: "final_response"
} as AgentChatResultResponse);
assert.equal(terminal, null);
});
test("terminal turn result accepts authoritative assistant trace final", () => {
const restored = {
role: "agent",
status: "running",
text: "",
traceId: "trc_trace_final",
runnerTrace: { traceId: "trc_trace_final", status: "running" }
} as ChatMessage;
const terminal = terminalMessagePatchFromTurnResult(restored, {
traceId: "trc_trace_final",
status: "completed",
terminal: true,
runnerTrace: {
traceId: "trc_trace_final",
status: "completed",
events: [
{ seq: 1, type: "assistant_message", status: "running", message: "progress only" },
{ seq: 2, type: "assistant_message", status: "completed", message: "final from trace", replyAuthority: true, final: true }
]
}
} as unknown as AgentChatResultResponse);
assert.equal(terminal?.status, "completed");
assert.equal(terminal?.text, "final from trace");
assert.equal((terminal?.finalResponse as { text?: string } | undefined)?.text, "final from trace");
});
test("terminal failure does not synthesize completed final response from progress text", () => {
const restored = {
role: "agent",
status: "running",
text: "",
traceId: "trc_failed_progress",
runnerTrace: { traceId: "trc_failed_progress", status: "running" }
} as ChatMessage;
const terminal = terminalMessagePatchFromTurnResult(restored, {
traceId: "trc_failed_progress",
status: "failed",
terminal: true,
runnerTrace: {
traceId: "trc_failed_progress",
status: "failed",
events: [{ seq: 1, type: "assistant_message", status: "running", message: "progress only" }]
},
error: { message: "provider failed" }
} as unknown as AgentChatResultResponse);
assert.equal(terminal?.status, "failed");
assert.equal(terminal?.text, undefined);
assert.equal(terminal?.finalResponse, undefined);
assert.equal(terminal?.error?.message, "provider failed");
});
test("terminal seal blocks stale running snapshots after session restore", () => {
const sealed = {
role: "agent",
status: "failed",
text: "provider stream disconnected",
traceAutoLifecycle: "terminal",
runnerTrace: { traceId: "trc_1", status: "failed" },
timing: { startedAt: "2026-01-01T00:00:00.000Z", finishedAt: "2026-01-01T00:00:05.000Z", durationMs: 5000, valuesRedacted: true }
} as ChatMessage;
assert.deepEqual(messageStatusPatchForTerminalMerge(sealed, "running", false), { status: "failed", traceAutoLifecycle: "terminal" });
const restored = {
role: "agent",
status: "running",
traceAutoLifecycle: "running",
traceId: "trc_1",
runnerTrace: { traceId: "trc_1", status: "running" },
timing: { startedAt: "2026-01-01T00:00:00.000Z", valuesRedacted: true }
} as ChatMessage;
const terminal = terminalMessagePatchFromTurnResult(restored, {
traceId: "trc_1",
status: "failed",
terminal: true,
startedAt: "2026-01-01T00:00:00.000Z",
finishedAt: "2026-01-01T00:00:04.000Z",
durationMs: 4000,
error: { message: "provider stream disconnected" }
} as AgentChatResultResponse);
assert.equal(terminal?.status, "failed");
assert.equal(terminal?.traceAutoLifecycle, "terminal");
assert.equal(terminal?.durationMs, 4000);
assert.equal(terminal?.error?.message, "provider stream disconnected");
});
test("terminal projection page seal preserves final response object", () => {
const previous = {
id: "msg_terminal_projection_previous",
role: "agent",
title: "Code Agent",
status: "completed",
createdAt: "2026-01-01T00:00:00.000Z",
text: "final answer",
finalResponse: { text: "final answer", status: "completed", sealed: true },
traceAutoLifecycle: "terminal",
timing: { startedAt: "2026-01-01T00:00:00.000Z", finishedAt: "2026-01-01T00:00:05.000Z", durationMs: 5000, valuesRedacted: true }
} as ChatMessage;
const incoming = {
id: "msg_terminal_projection_incoming",
role: "agent",
title: "Code Agent",
status: "running",
createdAt: "2026-01-01T00:00:00.000Z",
text: "",
traceAutoLifecycle: "running",
timing: { startedAt: "2026-01-01T00:00:00.000Z", valuesRedacted: true }
} as ChatMessage;
const patch = messageTimingSealPatchForProjectionMerge(incoming, previous);
assert.equal(patch.status, "completed");
assert.equal(patch.traceAutoLifecycle, "terminal");
assert.equal(patch.text, "final answer");
assert.deepEqual(patch.finalResponse, { text: "final answer", status: "completed", sealed: true });
assert.equal(patch.durationMs, 5000);
});