265 lines
11 KiB
TypeScript
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);
|
|
});
|