Files
pikasTech-HWLAB/web/hwlab-cloud-web/scripts/workbench-e2e-server.ts
T
2026-06-19 16:49:13 +08:00

1498 lines
84 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// SPEC: PJ2026-010401 Web工作台 draft-2026-06-18-r1; PJ2026-0104010803 唯一投影 draft-2026-06-19-p0-projector-resume; PJ2026-010403 API契约 draft-2026-06-18-r1.
// Responsibility: Same-origin fake Workbench API and static server for Playwright browser regression tests.
import { createReadStream, existsSync, statSync } from "node:fs";
import { readFile } from "node:fs/promises";
import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
import { extname, join, resolve, sep } from "node:path";
import {
DEFAULT_WORKBENCH_EMPTY_SESSION_TTL_MS,
classifyWorkbenchEmptySessionGcCandidate
} from "../../../internal/cloud/workbench-empty-session-gc.ts";
type JsonRecord = Record<string, unknown>;
interface SessionRecord extends JsonRecord {
sessionId: string;
threadId?: string | null;
status?: string | null;
lastTraceId?: string | null;
messages?: JsonRecord[];
hidden?: boolean;
}
interface ScenarioState {
scenarioId: string;
providerProfile: string;
selectedSessionId: string;
sessions: SessionRecord[];
traces: Record<string, JsonRecord>;
requestLedger: JsonRecord[];
legacyRequestLedger: JsonRecord[];
chatRequests: JsonRecord[];
listOmitSelected: boolean;
sessionDelayMs: number;
sessionDetailDelayMs: number;
chatDelayMs: number;
terminalScript: boolean;
terminalFailureScript: boolean;
staleTraceId: string | null;
liveBackfillTraceId: string | null;
liveBackfillReadyAtMs: number | null;
}
const cwd = process.cwd();
const args = new Map<string, string>();
for (let index = 2; index < process.argv.length; index += 1) {
const key = process.argv[index];
const value = process.argv[index + 1];
if (key?.startsWith("--")) {
args.set(key.slice(2), value && !value.startsWith("--") ? value : "true");
if (value && !value.startsWith("--")) index += 1;
}
}
const port = Number(args.get("port") ?? process.env.HWLAB_WORKBENCH_E2E_PORT ?? 4173);
const distDir = resolve(cwd, args.get("dist") ?? "dist");
const capturePath = resolve(cwd, "tests/workbench-e2e/fixtures/real-captures/d601-v03-redacted.json");
const capture = JSON.parse(await readFile(capturePath, "utf8")) as { scenario: { selectedSessionId: string; providerProfile: string; sessions: SessionRecord[]; traces: Record<string, JsonRecord> } };
const progressOnlyText = "PROGRESS_ONLY_SHOULD_NOT_RENDER_AS_FINAL";
const finalOnlyText = "FINAL_ONLY_SHOULD_RENDER";
const markdownFinalText = [
"已新增并实际运行:",
"",
"- Go`go test ./...` 保留两个空格",
"- Rust`cargo test --release`",
"",
"```sh",
"go test ./...",
"cargo test --release",
"```",
"",
"| benchmark | Go | Rust |",
"|---|---:|---:|",
"| fib | 1.2ms | 1.0ms |",
"",
"| runtime | status |",
"|---|---|",
"| local | pass |"
].join("\n");
let state = createScenarioState("baseline");
const sseClients = new Set<ServerResponse>();
const server = createServer((request, response) => {
void handleRequest(request, response).catch((error) => {
json(response, 500, { ok: false, status: 500, error: { code: "e2e_server_error", message: error instanceof Error ? error.message : String(error) } });
});
});
server.listen(port, "127.0.0.1", () => {
console.log(`workbench-e2e-server listening on http://127.0.0.1:${port}`);
});
async function handleRequest(request: IncomingMessage, response: ServerResponse): Promise<void> {
const method = request.method ?? "GET";
const url = new URL(request.url ?? "/", `http://${request.headers.host ?? `127.0.0.1:${port}`}`);
const path = url.pathname;
recordLedger(method, url);
refreshBackgroundProjection();
if (path === "/__e2e/health") return json(response, 200, { ok: true, scenarioId: state.scenarioId });
if (path === "/__e2e/state") return json(response, 200, stateSummary());
if (path === "/__e2e/reset" && method === "POST") {
const body = await readJson(request);
state = createScenarioState(String(body.scenarioId ?? "baseline"));
return json(response, 200, stateSummary());
}
if (path === "/__e2e/scroll-follow/append" && method === "POST") {
const body = await readJson(request);
return json(response, 200, appendScrollFollowEvents(positiveInteger(body.count, 1)));
}
if (path === "/__e2e/gc-empty-sessions" && method === "POST") {
const body = await readJson(request);
return json(response, 200, gcEmptySessions(body));
}
if (path === "/auth/session" || path === "/auth/bootstrap") {
const payload = authPayload();
return json(response, payload.authenticated === true ? 200 : 401, payload);
}
if (path === "/auth/login" && method === "POST") return authLoginResponse(response);
if (path === "/v1/workbench/events" && method === "GET") return sse(request, response, url);
if (path === "/v1/workbench/sessions" && method === "GET") {
await delay(state.sessionDelayMs);
return json(response, 200, workbenchSessionListPayload(url));
}
const turnMatch = path.match(/^\/v1\/workbench\/turns\/([^/]+)$/u);
if (turnMatch && method === "GET") {
const traceId = decodeURIComponent(turnMatch[1] ?? "");
if (state.scenarioId === "completed-replay-detail-404" && traceId === "trc_completed") return json(response, 404, { ok: false, status: 404, error: { code: "turn_replay_unavailable" } });
if (traceId === state.staleTraceId) return json(response, 502, { ok: false, status: 502, error: { code: "upstream_unavailable", message: "stale trace is unavailable" } });
return json(response, 200, workbenchTurnPayload(traceId));
}
const traceMatch = path.match(/^\/v1\/workbench\/traces\/([^/]+)\/events$/u);
if (traceMatch && method === "GET") {
const traceId = decodeURIComponent(traceMatch[1] ?? "");
if (state.scenarioId === "completed-replay-detail-404" && traceId === "trc_completed") return json(response, 404, { ok: false, status: 404, error: { code: "trace_replay_unavailable" } });
if (state.scenarioId === "trace-hydration-timeout-diagnostic" && traceId === "trc_trace_hydration_timeout") return json(response, 504, { ok: false, status: 504, error: { code: "trace_hydration_timeout", message: "Trace 更新超时,运行记录暂不可见。" } });
if (traceId === state.staleTraceId) return json(response, 502, { ok: false, status: 502, error: { code: "upstream_unavailable", message: "stale trace is unavailable" } });
return json(response, 200, workbenchTracePayload(traceId, url));
}
const sessionMessagesMatch = path.match(/^\/v1\/workbench\/sessions\/([^/]+)\/messages$/u);
if (sessionMessagesMatch && method === "GET") {
const sessionId = canonicalSessionId(decodeURIComponent(sessionMessagesMatch[1] ?? ""));
await delay(sessionDetailDelayMs(sessionId));
if (state.scenarioId === "created-session-read-failure" && sessionId === "ses_created_read_failure") return json(response, 503, { ok: false, status: 503, error: { code: "session_messages_unavailable" } });
const session = visibleSessionById(sessionId);
return session ? json(response, 200, workbenchSessionMessagesPayload(session, url)) : json(response, 404, { ok: false, status: 404, error: { code: "session_not_found" } });
}
const workbenchSessionMatch = path.match(/^\/v1\/workbench\/sessions\/([^/]+)$/u);
if (workbenchSessionMatch && method === "GET") {
const sessionId = canonicalSessionId(decodeURIComponent(workbenchSessionMatch[1] ?? ""));
await delay(sessionDetailDelayMs(sessionId));
if (state.scenarioId === "created-session-read-failure" && sessionId === "ses_created_read_failure") return json(response, 503, { ok: false, status: 503, error: { code: "session_detail_unavailable" } });
if (state.scenarioId === "session-switch-detail-404-isolated" && sessionId === "ses_stale_404") return json(response, 404, { ok: false, status: 404, error: { code: "session_not_found" } });
if (state.scenarioId === "completed-replay-detail-404" && sessionId === "ses_completed") return json(response, 404, { ok: false, status: 404, error: { code: "session_replay_unavailable" } });
const session = visibleSessionById(sessionId, { includeArchived: true });
return session ? json(response, 200, { ok: true, status: "found", session }) : json(response, 404, { ok: false, status: 404, error: { code: "session_not_found" } });
}
if (path === "/v1/agent/chat" && method === "POST") {
const body = await readJson(request);
state.chatRequests.push(redactRequestBody(body));
await delay(state.chatDelayMs);
return json(response, 200, acceptChatTurn(body));
}
if (path === "/v1/agent/sessions" && method === "POST") {
const body = await readJson(request);
const session = createManualSession(body);
state.selectedSessionId = session.sessionId;
state.sessions.unshift(session);
return json(response, state.scenarioId === "create-while-route-loading" ? 201 : 200, { ok: true, status: "ok", session });
}
const agentSessionMatch = path.match(/^\/v1\/agent\/sessions\/([^/]+)$/u);
if (agentSessionMatch && method === "DELETE") {
const session = sessionById(decodeURIComponent(agentSessionMatch[1] ?? ""));
if (!session) return json(response, 404, { ok: false, status: 404, error: { code: "session_not_found" } });
session.status = "archived";
session.updatedAt = new Date().toISOString();
return json(response, 200, { ok: true, status: "archived", session, archivedCount: 1 });
}
if (agentSessionMatch && method === "GET") return json(response, 200, { session: sessionPayload(decodeURIComponent(agentSessionMatch[1] ?? "")) });
if (path === "/v1/provider-profiles") return json(response, 200, { profiles: [{ profile: "codex-api", name: "Codex API", configured: true }, { profile: "deepseek", name: "DeepSeek", configured: true }] });
if (path === "/health/live" || path === "/health" || path === "/v1") return json(response, 200, { status: "ok", serviceId: "workbench-e2e", codeAgent: { ready: true, status: "ready" } });
if (path === "/v1/live-builds") return json(response, 200, { status: "ok", builds: [] });
if (path === "/v1/hwpod/specs") return json(response, 200, hwpodSpecsPayload());
if (path === "/v1/hwpod-node-ops") return json(response, 200, hwpodNodeOpsPayload());
if (path === "/v1/web-performance/summary") return json(response, 200, webPerformanceSummaryPayload());
if (path.startsWith("/v1/") || path.startsWith("/auth/") || path.startsWith("/health")) return json(response, 500, { ok: false, status: 500, error: { code: "unmocked_request", path } });
return staticFile(response, path);
}
function hwpodSpecsPayload(): JsonRecord {
return {
ok: true,
status: "completed",
count: 1,
availableCount: 0,
nodeOpsContractVersion: "hwpod-node-ops-v1",
specs: [
{
name: "d601-f103-v2",
hwpodId: "d601-f103-v2",
nodeId: "node-d601-f103-v2",
workspacePath: "F:\\Work\\D601-HWLAB",
source: { caseId: "d601-f103-v2-compile" },
availability: {
ok: false,
status: "blocked",
checkedAt: "2026-06-18T04:30:00.000Z",
results: [
{ opId: "op_01_workspace_ls", op: "workspace.ls", ok: false, status: "blocked", blocker: { code: "hwpod_node_unavailable", layer: "hwpod-node", retryable: true, summary: "no outbound WebSocket hwpod-node is connected" } }
]
}
}
]
};
}
function hwpodNodeOpsPayload(): JsonRecord {
return {
ok: true,
status: "ready",
contractVersion: "hwpod-node-ops-v1",
route: "/v1/hwpod-node-ops",
supportedOps: ["node.health", "workspace.ls", "debug.build", "debug.flash"],
websocket: { connectedCount: 0, pendingCount: 1, nodes: [] },
events: [
{ ts: "2026-06-18T04:30:00.000Z", status: "blocked", blocker: { code: "hwpod_node_unavailable", summary: "no outbound WebSocket hwpod-node is connected" } }
]
};
}
function webPerformanceSummaryPayload(): JsonRecord {
const rows = [
performanceRow({ kind: "workbench-journey", metric: "session-switch-full-load", route: "/workbench/sessions/:sessionId", count: 21, p50: 0.18, p75: 0.31, p95: 1.12, tone: "warn", backend: "agentrun", transport: "sse", targetState: "completed" }),
performanceRow({ kind: "workbench-event-phase", metric: "trace-event-projected", route: "/workbench", count: 28, p50: 0.08, p75: 0.14, p95: 0.42, tone: "ok", eventType: "tool_result", phase: "render", source: "rum" }),
performanceRow({ kind: "workbench-backend-event", metric: "backend-event-visible", route: "/v1/workbench/events", count: 15, p50: 0.2, p75: 0.34, p95: 1.38, tone: "warn", eventType: "agentrun.stdout", backend: "agentrun", cache: "miss" }),
performanceRow({ kind: "web-vital", metric: "LCP", route: "/performance", count: 9, p50: 1.2, p75: 1.8, p95: 2.9, tone: "ok" }),
performanceRow({ kind: "api", metric: "fetchJson", route: "/v1/web-performance/summary", method: "GET", statusClass: "2xx", outcome: "ok", count: 17, p50: 0.09, p75: 0.15, p95: 0.33, tone: "ok" }),
performanceRow({ kind: "long-task", metric: "longtask", route: "/workbench", count: 3, p50: 0.08, p75: 0.11, p95: 0.2, tone: "warn", problem: "main-thread busy" })
];
return {
ok: true,
status: "ready",
source: "fake-server",
observedAt: "2026-06-18T04:30:00.000Z",
namespace: "hwlab-v03",
gitopsTarget: "D601/v03",
summary: { status: "ready", sampleCount: 93, sampleSeries: 12, problemCount: 2, lowSampleThreshold: 5, workbenchJourneySeries: 1, workbenchEventPhaseSeries: 1, workbenchBackendEventVisibleSeries: 1 },
dashboard: webPerformanceDashboardPayload(rows),
rows,
problems: rows.filter((row) => row.tone === "warn"),
webVitals: rows.filter((row) => row.kind === "web-vital"),
apiRoutes: rows.filter((row) => row.kind === "api"),
longTasks: rows.filter((row) => row.kind === "long-task"),
workbenchJourneys: rows.filter((row) => row.kind === "workbench-journey"),
workbenchEventPhases: rows.filter((row) => row.kind === "workbench-event-phase"),
workbenchBackendEvents: rows.filter((row) => row.kind === "workbench-backend-event")
};
}
function performanceRow(row: JsonRecord): JsonRecord {
return { method: "GET", statusClass: "2xx", outcome: "ok", unit: "seconds", sampleState: "ok", ...row };
}
function webPerformanceDashboardPayload(rows: JsonRecord[]): JsonRecord {
const workbench = rows.filter((row) => String(row.kind).startsWith("workbench"));
const api = rows.filter((row) => row.kind === "api");
const vital = rows.filter((row) => row.kind === "web-vital");
const longTask = rows.filter((row) => row.kind === "long-task");
return {
schemaVersion: "hwlab-web-performance-dashboard-v1",
generatedAt: "2026-06-18T04:30:00.000Z",
sampleWindow: { label: "当前采集窗口", bucketKind: "snapshot", buckets: [{ id: "current", label: "当前窗口", observedAt: "2026-06-18T04:30:00.000Z", sampleCount: 93 }] },
freshness: { observedAt: "2026-06-18T04:30:00.000Z", source: "fake-server", namespace: "hwlab-v03", gitopsTarget: "D601/v03", status: "watch", statusLabel: "预警", lowSampleThreshold: 5 },
cards: [
{ id: "health", label: "采集状态", value: "预警", unit: "状态", tone: "warn", detail: "fake-server" },
{ id: "samples", label: "样本数", value: 93, unit: "样本", tone: "ok", detail: "12 个序列" },
{ id: "workbench", label: "工作台序列", value: 3, unit: "序列", tone: "ok", detail: "hwlab-v03 / D601/v03" },
{ id: "problems", label: "问题行", value: 2, unit: "条", tone: "warn", detail: "低样本阈值 5 个样本" }
],
trends: [
{ id: "workbench-experience", title: "工作台体感趋势", subtitle: "首屏、会话切换和提交首个可见结果的 P95", unit: "seconds", points: workbench.map(dashboardPoint) },
{ id: "api-timing", title: "同源 API 耗时", subtitle: "Cloud Web 同源 API 请求 P95", unit: "seconds", points: api.map(dashboardPoint) },
{ id: "web-vitals", title: "浏览器核心指标", subtitle: "浏览器体感当前窗口", unit: "mixed", points: vital.map(dashboardPoint) }
],
distributions: [
{ id: "status", title: "状态分布", subtitle: "按健康状态聚合当前窗口行数", unit: "count", buckets: [{ label: "正常", count: 4, tone: "ok" }, { label: "预警", count: 2, tone: "warn" }] },
{ id: "samples", title: "样本状态", subtitle: "正常、低样本和暂无数据分布", unit: "count", buckets: [{ label: "正常样本", count: 6, tone: "ok" }] }
],
topN: [
{ id: "top-api", title: "慢 API TopN", subtitle: "同源 API P95 最慢路由", unit: "seconds", rows: api.map(dashboardTopRow) },
{ id: "top-workbench", title: "工作台慢路径 TopN", subtitle: "工作台 journey 和事件可见延迟", unit: "seconds", rows: workbench.map(dashboardTopRow) },
{ id: "top-long-task", title: "长任务 TopN", subtitle: "主线程长任务当前窗口", unit: "seconds", rows: longTask.map(dashboardTopRow) }
]
};
}
function dashboardPoint(row: JsonRecord): JsonRecord {
return { label: dashboardMetricLabel(row), detail: dashboardRouteLabel(row.route), value: row.p95, p50: row.p50, p75: row.p75, p95: row.p95, count: row.count, tone: row.tone, sampleState: row.sampleState };
}
function dashboardTopRow(row: JsonRecord): JsonRecord {
return { label: dashboardMetricLabel(row), detail: dashboardRouteLabel(row.route), value: row.p95, unit: row.unit ?? "seconds", count: row.count, tone: row.tone };
}
function dashboardRouteLabel(route: unknown): string {
const value = String(route ?? "").trim();
if (!value || value === "-") return "当前窗口";
return value
.replace(/:sessionId/gu, ":会话")
.replace(/:traceId/gu, ":轨迹")
.replace(/:runId/gu, ":运行")
.replace(/:threadId/gu, ":线程")
.replace(/:turnId/gu, ":轮次");
}
function dashboardMetricLabel(row: JsonRecord): string {
const metric = String(row.metric ?? "unknown");
const labels: Record<string, string> = {
"session-switch-full-load": "切换会话完整加载",
"trace-event-projected": "轨迹事件投影",
"backend-event-visible": "后端事件到可见",
LCP: "最大内容绘制",
fetchJson: "同源 API 请求",
longtask: "主线程长任务"
};
return labels[metric] ?? metric;
}
function createScenarioState(scenarioId: string): ScenarioState {
const base = structuredClone(capture.scenario);
const id = scenarioId || "baseline";
const sessions = base.sessions;
const traces = base.traces;
if (id === "cross-project-detail-boundary") sessions.push(hiddenBoundarySession());
if (id === "legacy-cnv-deeplink-canonical") {
sessions.unshift(legacyCanonicalSession());
traces.trc_40badc15523146c9 = legacyCanonicalTrace();
}
if (id === "submit-authority-race") {
sessions.unshift(submitSplitSession());
traces.trc_submit_split = submitSplitTrace();
}
if (id === "session-switch-delayed-detail-frame") {
markSessionText(sessions, "ses_running", "SESSION_A_ONLY", "SESSION_A_ONLY running source session");
markSessionText(sessions, "ses_completed", "SESSION_B_ONLY", "SESSION_B_ONLY completed target session");
}
if (id === "session-rail-many") {
for (let index = 0; index < 51; index += 1) sessions.push(manyRailSession(index));
}
if (id === "session-switch-detail-404-isolated") {
sessions.unshift(staleDetailSession());
sessions.push(emptySession());
}
if (id === "terminal-empty-trace") {
sessions.push(terminalEmptyTraceSession());
traces.trc_terminal_empty = terminalEmptyTrace();
}
if (id === "markdown-final-response") {
sessions.unshift(markdownFinalSession());
traces.trc_markdown_final = markdownFinalTrace();
}
if (id === "progress-only-final-response") markRunningProgressOnly(sessions, traces);
if (id === "terminal-completed-no-final-response") markRunningNoFinalResponse(sessions, traces);
if (id === "tool-completed-projection-running") markToolCompletedProjectionRunning(sessions, traces);
if (id === "projection-degraded-diagnostics") {
sessions.unshift(projectionDegradedSession());
traces.trc_projection_degraded = projectionDegradedTrace();
}
if (id === "projection-sse-error") {
sessions.unshift(projectionSseErrorSession());
traces.trc_projection_sse_error = projectionSseErrorTrace();
}
if (id === "trace-hydration-timeout-diagnostic") {
sessions.unshift(traceHydrationTimeoutSession());
traces.trc_trace_hydration_timeout = traceHydrationTimeoutTrace();
}
if (id === "scroll-follow-long-trace") {
const session = scrollFollowSession();
sessions.unshift(session);
traces.trc_scroll_follow = scrollFollowTrace(90);
}
if (id === "projector-resume-from-checkpoint") {
const session = projectorResumeSession();
sessions.unshift(session);
traces.trc_f2456023233a485b = projectorResumeEarlyTrace();
}
if (id === "cross-session-late-events") {
sessions.unshift(crossSessionLateB(), crossSessionLateA());
traces.trc_late_A = crossSessionLateTraceA();
}
if (id === "deleted-session-deeplink") sessions.unshift(archivedDeletedSession());
if (id === "session-switch-empty-reload") sessions.push(emptySession());
if (id === "empty-session-gc") sessions.unshift(staleEmptyTtlSession(), freshEmptyTtlSession(), staleMessagedTtlSession());
const selectedSessionId = id === "scroll-follow-long-trace"
? "ses_scroll_follow"
: id === "deep-link" || id === "stale-nested-trace"
? "ses_failed"
: id === "legacy-cnv-deeplink-canonical"
? "ses_running"
: id === "session-switch-detail-404-isolated" || id === "completed-replay-detail-404" || id === "deleted-session-deeplink" || id === "terminal-turn-stale-session-active"
? "ses_completed"
: id === "terminal-empty-trace"
? "ses_terminal_empty"
: id === "markdown-final-response"
? "ses_markdown_final"
: id === "progress-only-final-response" || id === "terminal-completed-no-final-response" || id === "tool-completed-projection-running"
? "ses_running"
: id === "projector-resume-from-checkpoint"
? "ses_317f78e1-ed91-4b09-a72a-3668d5469f59"
: id === "projection-degraded-diagnostics"
? "ses_projection_degraded"
: id === "projection-sse-error"
? "ses_projection_sse_error"
: id === "trace-hydration-timeout-diagnostic"
? "ses_trace_hydration_timeout"
: id === "create-while-route-loading"
? "ses_running"
: base.selectedSessionId;
const staleTraceId = id === "stale-nested-trace" || id === "stale-submit-restore" ? "trc_stale_502" : null;
return {
scenarioId: id,
providerProfile: base.providerProfile,
selectedSessionId,
sessions,
traces,
requestLedger: [],
legacyRequestLedger: [],
chatRequests: [],
listOmitSelected: id === "selected-missing-from-list",
sessionDelayMs: id === "loading" ? 2_500 : 0,
sessionDetailDelayMs: id === "legacy-cnv-deeplink-canonical" ? 1_500 : id === "session-switch-delayed-detail-frame" ? 900 : id === "submit-authority-race" ? 1_000 : 0,
chatDelayMs: id === "submit-authority-race" ? 1_000 : 0,
terminalScript: id === "event-replay" || id === "running-to-terminal" || id === "stale-submit-restore" || id === "progress-only-final-response" || id === "terminal-completed-no-final-response",
terminalFailureScript: id === "stale-submit-restore",
staleTraceId,
liveBackfillTraceId: id === "projector-resume-from-checkpoint" ? "trc_f2456023233a485b" : null,
liveBackfillReadyAtMs: id === "projector-resume-from-checkpoint" ? Date.now() + 5_000 : null
};
}
function refreshBackgroundProjection(): void {
if (state.scenarioId !== "projector-resume-from-checkpoint") return;
if (!state.liveBackfillReadyAtMs || Date.now() < state.liveBackfillReadyAtMs) return;
const session = sessionById("ses_317f78e1-ed91-4b09-a72a-3668d5469f59");
if (session?.status === "completed") return;
commitProjectorResumeProjection();
}
function projectorResumeSession(): SessionRecord {
const now = new Date().toISOString();
const trace = projectorResumeEarlyTrace();
return {
sessionId: "ses_317f78e1-ed91-4b09-a72a-3668d5469f59",
threadId: "thr_projector_resume_1596",
status: "running",
lastTraceId: "trc_f2456023233a485b",
startedAt: now,
updatedAt: now,
messageCount: 2,
firstUserMessagePreview: "issue 1596 durable projector resume sample",
turnSummary: { traceId: "trc_f2456023233a485b", status: "running", running: true, terminal: false, ...projectorResumeProjection("projecting", 4) },
messages: [
{ id: "msg_projector_resume_user", messageId: "msg_projector_resume_user", role: "user", title: "用户", text: "issue 1596 durable projector resume sample", status: "sent", createdAt: now, sessionId: "ses_317f78e1-ed91-4b09-a72a-3668d5469f59", threadId: "thr_projector_resume_1596", traceId: "trc_f2456023233a485b", turnId: "turn_projector_resume_1596" },
{ id: "msg_projector_resume_agent", messageId: "msg_projector_resume_agent", role: "agent", title: "Code Agent", text: "", status: "running", createdAt: now, sessionId: "ses_317f78e1-ed91-4b09-a72a-3668d5469f59", threadId: "thr_projector_resume_1596", traceId: "trc_f2456023233a485b", turnId: "turn_projector_resume_1596", runnerTrace: trace, ...projectorResumeProjection("projecting", 4) }
]
};
}
function projectorResumeEarlyTrace(): JsonRecord {
const createdAt = new Date().toISOString();
const projection = projectorResumeProjection("projecting", 4);
const events = [
{ seq: 1, sourceSeq: 1, createdAt, label: "agentrun:request:accepted", type: "backend_status", status: "running", payload: { commandId: "cmd_d4d20aa2bb2c4d19a91143ce87d73704" } },
{ seq: 2, sourceSeq: 2, createdAt, label: "agentrun:run:created", type: "backend_status", status: "running", payload: { runId: "run_58ef1811071548539f881515912560d8", runStatus: "claimed" } },
{ seq: 3, sourceSeq: 3, createdAt, label: "agentrun:command:created", type: "backend_status", status: "running", payload: { commandId: "cmd_d4d20aa2bb2c4d19a91143ce87d73704", commandState: "running" } },
{ seq: 4, sourceSeq: 4, createdAt, label: "agentrun:runner-job:created", type: "backend_status", status: "running", payload: { commandId: "cmd_d4d20aa2bb2c4d19a91143ce87d73704", namespace: "agentrun-v01" } }
];
return { traceId: "trc_f2456023233a485b", status: "running", running: true, terminal: false, sessionId: "ses_317f78e1-ed91-4b09-a72a-3668d5469f59", threadId: "thr_projector_resume_1596", turnId: "turn_projector_resume_1596", events, eventCount: events.length, fullTraceLoaded: true, hasMore: false, ...projection };
}
function projectorResumeCompletedTrace(): JsonRecord {
const createdAt = new Date().toISOString();
const finalText = "fake projector resume completed from durable checkpoint.";
const projection = projectorResumeProjection("caught-up", 7);
const events = [
...(projectorResumeEarlyTrace().events as JsonRecord[]),
{ seq: 5, sourceSeq: 2748, createdAt, label: "agentrun:assistant:message", type: "assistant_message", status: "completed", message: finalText, payload: { commandId: "cmd_d4d20aa2bb2c4d19a91143ce87d73704" } },
{ seq: 6, sourceSeq: 2749, createdAt, label: "item/commandExecution:completed", type: "commandExecution", status: "completed", stdout: "OK\n", payload: { commandId: "cmd_d4d20aa2bb2c4d19a91143ce87d73704", exitCode: 0 } },
{ seq: 7, sourceSeq: 2750, createdAt, label: "agentrun:backend:turn/completed", type: "backend_status", status: "completed", terminal: true, payload: { commandId: "cmd_d4d20aa2bb2c4d19a91143ce87d73704", terminalStatus: "completed" } }
];
return { traceId: "trc_f2456023233a485b", status: "completed", running: false, terminal: true, sessionId: "ses_317f78e1-ed91-4b09-a72a-3668d5469f59", threadId: "thr_projector_resume_1596", turnId: "turn_projector_resume_1596", events, eventCount: events.length, fullTraceLoaded: true, hasMore: false, finalResponse: { text: finalText, status: "completed" }, traceSummary: { source: "durable-projector-resume", terminalStatus: "completed", valuesPrinted: false }, ...projection };
}
function projectorResumeProjection(status: "projecting" | "caught-up", lastProjectedSeq: number): JsonRecord {
const caughtUp = status === "caught-up";
const projection = {
projectionStatus: status,
projectionHealth: status,
lastProjectedSeq,
sourceLatestSeq: 2750,
sourceRunId: "run_58ef1811071548539f881515912560d8",
sourceCommandId: "cmd_d4d20aa2bb2c4d19a91143ce87d73704",
staleMs: caughtUp ? 0 : 90_000,
blocker: caughtUp ? null : { code: "projector_resume_pending", layer: "workbench-projector", retryable: true, message: "后台投影恢复中,已从 checkpoint 记录 lastProjectedSeq=4。", valuesPrinted: false },
valuesRedacted: true
};
return { projection, ...projection };
}
function commitProjectorResumeProjection(): void {
const completed = projectorResumeCompletedTrace();
state.traces.trc_f2456023233a485b = completed;
const session = sessionById("ses_317f78e1-ed91-4b09-a72a-3668d5469f59");
if (!session) return;
session.status = "completed";
session.updatedAt = new Date().toISOString();
session.turnSummary = { traceId: "trc_f2456023233a485b", status: "completed", running: false, terminal: true, ...projectorResumeProjection("caught-up", 7) };
session.messages = (session.messages ?? []).map((message) => {
if (message.role !== "agent" || message.traceId !== "trc_f2456023233a485b") return message;
return { ...message, text: "fake projector resume completed from durable checkpoint.", status: "completed", runnerTrace: completed, ...projectorResumeProjection("caught-up", 7) };
});
const terminalEvent = (completed.events as JsonRecord[]).at(-1) ?? null;
if (terminalEvent) broadcastSse("workbench.trace.event", { type: "trace.event", sessionId: session.sessionId, threadId: session.threadId ?? null, traceId: "trc_f2456023233a485b", event: terminalEvent, snapshot: completed });
broadcastSse("workbench.turn.snapshot", { type: "turn.snapshot", sessionId: session.sessionId, threadId: session.threadId ?? null, traceId: "trc_f2456023233a485b", turn: turnPayload("trc_f2456023233a485b") });
}
function markSessionText(sessions: SessionRecord[], sessionId: string, userText: string, agentText: string): void {
const session = sessions.find((item) => item.sessionId === sessionId);
if (!session) return;
session.firstUserMessagePreview = userText;
session.messages = (session.messages ?? []).map((message) => {
if (message.role === "user") return { ...message, text: userText };
if (message.role === "agent") return { ...message, text: agentText };
return message;
});
}
function markRunningProgressOnly(sessions: SessionRecord[], traces: Record<string, JsonRecord>): void {
const createdAt = new Date().toISOString();
const progressEvent = { seq: 1, createdAt, label: "agentrun:assistant:message", type: "assistant_message", status: "running", replyAuthority: false, final: false, message: progressOnlyText };
const trace = { traceId: "trc_running", status: "running", sessionId: "ses_running", threadId: "thr_running", turnId: "turn_running", events: [progressEvent], eventCount: 1, fullTraceLoaded: false, hasMore: false };
traces.trc_running = trace;
const session = sessions.find((item) => item.sessionId === "ses_running");
if (!session) return;
session.status = "running";
session.lastTraceId = "trc_running";
session.firstUserMessagePreview = "progress-only running turn";
session.messages = (session.messages ?? []).map((message) => {
if (message.role === "user") return { ...message, text: "progress-only running turn" };
if (message.role !== "agent" || message.traceId !== "trc_running") return message;
return { ...message, text: "", status: "running", runnerTrace: trace };
});
}
function markRunningNoFinalResponse(sessions: SessionRecord[], traces: Record<string, JsonRecord>): void {
const trace = noFinalRunningTrace();
traces.trc_running = trace;
const session = sessions.find((item) => item.sessionId === "ses_running");
if (!session) return;
session.status = "running";
session.lastTraceId = "trc_running";
session.firstUserMessagePreview = "completed turn without final response";
session.messages = (session.messages ?? []).map((message) => {
if (message.role === "user") return { ...message, text: "completed turn without final response" };
if (message.role !== "agent" || message.traceId !== "trc_running") return message;
return { ...message, text: "", status: "running", runnerTrace: trace };
});
}
function markToolCompletedProjectionRunning(sessions: SessionRecord[], traces: Record<string, JsonRecord>): void {
const trace = toolCompletedProjectionRunningTrace();
traces.trc_running = trace;
const session = sessions.find((item) => item.sessionId === "ses_running");
if (!session) return;
session.status = "running";
session.lastTraceId = "trc_running";
session.firstUserMessagePreview = "tool completed but turn still running";
session.messages = (session.messages ?? []).map((message) => {
if (message.role === "user") return { ...message, text: "tool completed but turn still running" };
if (message.role !== "agent" || message.traceId !== "trc_running") return message;
return { ...message, text: "", status: "running", runnerTrace: trace };
});
}
function toolCompletedProjectionRunningTrace(): JsonRecord {
const createdAt = new Date().toISOString();
const events = [
{ seq: 1, createdAt, label: "agentrun:assistant:message", type: "assistant_message", status: "running", terminal: false, message: "正在安装 Python。" },
{ seq: 2, createdAt, label: "item/commandExecution:completed", type: "commandExecution", status: "completed", terminal: false, command: "ls -ld .", stdout: "OK\n" }
];
return {
traceId: "trc_running",
status: "completed",
turnStatus: "running",
projectionStatus: "projecting",
sessionId: "ses_running",
threadId: "thr_running",
turnId: "turn_running",
events,
eventCount: events.length,
fullTraceLoaded: true,
hasMore: false,
finalResponse: null,
assistantText: null
};
}
function projectionDiagnostic(input: { code: string; message: string; health?: string; category?: string; layer?: string; traceId: string; runId?: string; commandId?: string; staleMs?: number }): JsonRecord {
return {
projectionStatus: "projecting",
projectionHealth: input.health ?? "degraded",
lastProjectedSeq: 137,
sourceRunId: input.runId ?? "run_projection_diag",
sourceCommandId: input.commandId ?? "cmd_projection_diag",
staleMs: input.staleMs ?? 12_345,
blocker: {
code: input.code,
layer: input.layer ?? "agentrun",
category: input.category ?? "upstream-timeout",
message: input.message,
retryable: true,
timeoutMs: 2500,
runId: input.runId ?? "run_projection_diag",
commandId: input.commandId ?? "cmd_projection_diag",
valuesPrinted: false
},
valuesRedacted: true
};
}
function projectionDegradedTrace(): JsonRecord {
const projection = projectionDiagnostic({ traceId: "trc_projection_degraded", code: "agentrun_result_timeout", message: "状态更新超时,最近投影 seq=137,AgentRun 结果暂不可见。" });
return { traceId: "trc_projection_degraded", status: "running", sessionId: "ses_projection_degraded", threadId: "thr_projection_degraded", turnId: "turn_projection_degraded", events: [], eventCount: 0, fullTraceLoaded: true, hasMore: false, projection, ...projection };
}
function projectionDegradedSession(): SessionRecord {
const now = new Date().toISOString();
const projection = projectionDiagnostic({ traceId: "trc_projection_degraded", code: "agentrun_result_timeout", message: "状态更新超时,最近投影 seq=137,AgentRun 结果暂不可见。" });
return {
sessionId: "ses_projection_degraded",
threadId: "thr_projection_degraded",
status: "running",
lastTraceId: "trc_projection_degraded",
updatedAt: now,
messageCount: 2,
firstUserMessagePreview: "projection degraded visibility",
turnSummary: { traceId: "trc_projection_degraded", status: "running", running: true, terminal: false, ...projection },
messages: [
{ id: "msg_projection_degraded_user", messageId: "msg_projection_degraded_user", role: "user", title: "用户", text: "projection degraded visibility", status: "sent", createdAt: now, sessionId: "ses_projection_degraded", threadId: "thr_projection_degraded", turnId: "turn_projection_degraded" },
{ id: "msg_projection_degraded_agent", messageId: "msg_projection_degraded_agent", role: "agent", title: "Code Agent", text: "", status: "running", createdAt: now, sessionId: "ses_projection_degraded", threadId: "thr_projection_degraded", traceId: "trc_projection_degraded", turnId: "turn_projection_degraded", projection, runnerTrace: projectionDegradedTrace() }
]
};
}
function projectionSseErrorTrace(): JsonRecord {
return { traceId: "trc_projection_sse_error", status: "running", sessionId: "ses_projection_sse_error", threadId: "thr_projection_sse_error", turnId: "turn_projection_sse_error", events: [], eventCount: 0, fullTraceLoaded: false, hasMore: false };
}
function projectionSseErrorSession(): SessionRecord {
const now = new Date().toISOString();
return {
sessionId: "ses_projection_sse_error",
threadId: "thr_projection_sse_error",
status: "running",
lastTraceId: "trc_projection_sse_error",
updatedAt: now,
messageCount: 2,
firstUserMessagePreview: "projection SSE error visibility",
messages: [
{ id: "msg_projection_sse_error_user", messageId: "msg_projection_sse_error_user", role: "user", title: "用户", text: "projection SSE error visibility", status: "sent", createdAt: now, sessionId: "ses_projection_sse_error", threadId: "thr_projection_sse_error", turnId: "turn_projection_sse_error" },
{ id: "msg_projection_sse_error_agent", messageId: "msg_projection_sse_error_agent", role: "agent", title: "Code Agent", text: "", status: "running", createdAt: now, sessionId: "ses_projection_sse_error", threadId: "thr_projection_sse_error", traceId: "trc_projection_sse_error", turnId: "turn_projection_sse_error", runnerTrace: projectionSseErrorTrace() }
]
};
}
function traceHydrationTimeoutTrace(): JsonRecord {
return { traceId: "trc_trace_hydration_timeout", status: "running", sessionId: "ses_trace_hydration_timeout", threadId: "thr_trace_hydration_timeout", turnId: "turn_trace_hydration_timeout", events: [], eventCount: 0, fullTraceLoaded: false, hasMore: true };
}
function traceHydrationTimeoutSession(): SessionRecord {
const now = new Date().toISOString();
return {
sessionId: "ses_trace_hydration_timeout",
threadId: "thr_trace_hydration_timeout",
status: "running",
lastTraceId: "trc_trace_hydration_timeout",
updatedAt: now,
messageCount: 2,
firstUserMessagePreview: "trace hydration timeout visibility",
messages: [
{ id: "msg_trace_hydration_timeout_user", messageId: "msg_trace_hydration_timeout_user", role: "user", title: "用户", text: "trace hydration timeout visibility", status: "sent", createdAt: now, sessionId: "ses_trace_hydration_timeout", threadId: "thr_trace_hydration_timeout", turnId: "turn_trace_hydration_timeout" },
{ id: "msg_trace_hydration_timeout_agent", messageId: "msg_trace_hydration_timeout_agent", role: "agent", title: "Code Agent", text: "", status: "running", createdAt: now, sessionId: "ses_trace_hydration_timeout", threadId: "thr_trace_hydration_timeout", traceId: "trc_trace_hydration_timeout", turnId: "turn_trace_hydration_timeout", runnerTrace: traceHydrationTimeoutTrace() }
]
};
}
function noFinalRunningTrace(): JsonRecord {
const createdAt = new Date().toISOString();
return {
traceId: "trc_running",
status: "running",
sessionId: "ses_running",
threadId: "thr_running",
turnId: "turn_running",
events: [{ seq: 1, createdAt, label: "agentrun:backend:turn/running", type: "backend_status", status: "running" }],
eventCount: 1,
fullTraceLoaded: false,
hasMore: false
};
}
function completedNoFinalTrace(): JsonRecord {
const createdAt = new Date().toISOString();
const events = [
{ seq: 1, createdAt, label: "agentrun:backend:turn/running", type: "backend_status", status: "running" },
{ seq: 2, createdAt, label: "agentrun:backend:turn/completed", type: "backend_status", status: "completed", terminal: true }
];
return { traceId: "trc_running", status: "completed", sessionId: "ses_running", threadId: "thr_running", turnId: "turn_running", events, eventCount: events.length, fullTraceLoaded: true, hasMore: false };
}
function scrollFollowSession(): SessionRecord {
const now = new Date().toISOString();
const trace = scrollFollowTrace(90);
const history = Array.from({ length: 18 }, (_, index) => {
const seq = index + 1;
return seq % 2 === 0
? { id: `msg_scroll_history_agent_${seq}`, messageId: `msg_scroll_history_agent_${seq}`, role: "agent", title: "Code Agent", text: `历史回复 ${seq}\n\n用于撑开主工作区滚动高度。`, status: "completed", createdAt: now, sessionId: "ses_scroll_follow", threadId: "thr_scroll_follow", turnId: `turn_scroll_history_${seq}` }
: { id: `msg_scroll_history_user_${seq}`, messageId: `msg_scroll_history_user_${seq}`, role: "user", title: "用户", text: `历史问题 ${seq}:保持主工作区可滚动。`, status: "sent", createdAt: now, sessionId: "ses_scroll_follow", threadId: "thr_scroll_follow", turnId: `turn_scroll_history_${seq}` };
});
const messages = [
...history,
{ id: "msg_scroll_follow_user", messageId: "msg_scroll_follow_user", role: "user", title: "用户", text: "scroll follow long trace probe", status: "sent", createdAt: now, sessionId: "ses_scroll_follow", threadId: "thr_scroll_follow", traceId: "trc_scroll_follow", turnId: "turn_scroll_follow" },
{ id: "msg_scroll_follow_agent", messageId: "msg_scroll_follow_agent", role: "agent", title: "Code Agent", text: "", status: "running", createdAt: now, sessionId: "ses_scroll_follow", threadId: "thr_scroll_follow", traceId: "trc_scroll_follow", turnId: "turn_scroll_follow", runnerTrace: trace }
];
return {
sessionId: "ses_scroll_follow",
threadId: "thr_scroll_follow",
status: "running",
lastTraceId: "trc_scroll_follow",
startedAt: now,
updatedAt: now,
messageCount: messages.length,
firstUserMessagePreview: "scroll follow long trace probe",
messages
};
}
function scrollFollowTrace(count: number): JsonRecord {
const events = Array.from({ length: count }, (_, index) => scrollFollowEvent(index + 1));
return { traceId: "trc_scroll_follow", status: "running", sessionId: "ses_scroll_follow", threadId: "thr_scroll_follow", turnId: "turn_scroll_follow", events, eventCount: events.length, fullTraceLoaded: true, hasMore: false };
}
function scrollFollowEvent(seq: number): JsonRecord {
const createdAt = new Date(Date.parse("2026-06-18T18:00:00.000Z") + seq * 1000).toISOString();
if (seq % 5 === 0) {
return { seq, sourceSeq: seq, createdAt, label: "agentrun:assistant:message", type: "assistant_message", status: "running", message: `滚动跟随采样 assistant row ${seq}\n\n- markdown 行 ${seq}\n- append 后不能闪到顶部` };
}
return {
seq,
sourceSeq: seq,
createdAt,
label: "item/commandExecution:running",
type: "commandExecution",
status: "running",
itemId: `scroll-follow-${seq}`,
command: `printf 'scroll follow row ${seq}\\n'`,
stdout: `scroll-follow stdout ${seq}\nline ${seq} keeps the row tall enough for scroll sampling\n`
};
}
function appendScrollFollowEvents(count: number): JsonRecord {
if (state.scenarioId !== "scroll-follow-long-trace") return { ok: false, status: 409, error: { code: "scenario_mismatch", scenarioId: state.scenarioId } };
const trace = state.traces.trc_scroll_follow ?? scrollFollowTrace(0);
const events = Array.isArray(trace.events) ? [...trace.events as JsonRecord[]] : [];
const appended: JsonRecord[] = [];
for (let index = 0; index < count; index += 1) {
const seq = Number(events.at(-1)?.seq ?? events.length) + 1;
const event = scrollFollowEvent(seq);
events.push(event);
appended.push(event);
}
const nextTrace = { ...trace, status: "running", events, eventCount: events.length, fullTraceLoaded: true, hasMore: false };
state.traces.trc_scroll_follow = nextTrace;
const session = sessionById("ses_scroll_follow");
if (session) {
session.status = "running";
session.updatedAt = new Date().toISOString();
session.messages = (session.messages ?? []).map((message) => message.role === "agent" && message.traceId === "trc_scroll_follow" ? { ...message, status: "running", runnerTrace: nextTrace } : message);
}
for (const event of appended) {
broadcastSse("workbench.trace.event", { type: "trace.event", sessionId: "ses_scroll_follow", threadId: "thr_scroll_follow", traceId: "trc_scroll_follow", event, snapshot: nextTrace });
}
broadcastSse("workbench.turn.snapshot", { type: "turn.snapshot", sessionId: "ses_scroll_follow", threadId: "thr_scroll_follow", traceId: "trc_scroll_follow", turn: turnPayload("trc_scroll_follow") });
return { ok: true, status: "appended", appended: appended.length, eventCount: events.length };
}
function createManualSession(body: JsonRecord): SessionRecord {
const now = new Date().toISOString();
const token = state.scenarioId === "server-authoritative-create"
? "server_created"
: state.scenarioId === "create-while-route-loading"
? "created_while_running"
: state.scenarioId === "created-session-read-failure"
? "created_read_failure"
: Date.now().toString(36);
const sessionId = typeof body.sessionId === "string" && body.sessionId.trim() ? body.sessionId : `ses_${token}`;
const threadId = typeof body.threadId === "string" && body.threadId.trim() ? body.threadId : null;
return { sessionId, threadId, status: "active", startedAt: now, updatedAt: now, messageCount: 0, firstUserMessagePreview: null, messages: [] };
}
function hiddenBoundarySession(): SessionRecord {
const now = new Date().toISOString();
return { sessionId: "ses_cross_project", threadId: "thr_cross_project", hidden: true, status: "completed", lastTraceId: "trc_cross_project", startedAt: now, updatedAt: now, messageCount: 2, firstUserMessagePreview: "边界外 session 不应泄露", messages: [] };
}
function terminalEmptyTraceSession(): SessionRecord {
const now = new Date().toISOString();
return {
sessionId: "ses_terminal_empty",
threadId: "thr_terminal_empty",
status: "completed",
lastTraceId: "trc_terminal_empty",
startedAt: now,
updatedAt: now,
messageCount: 2,
firstUserMessagePreview: "完成态空 Trace 文案验证",
messages: [
{ id: "msg_terminal_empty_user", role: "user", title: "用户", text: "完成态空 Trace 文案验证", status: "sent", createdAt: now, sessionId: "ses_terminal_empty", threadId: "thr_terminal_empty", turnId: "turn_terminal_empty" },
{ id: "msg_terminal_empty_agent", role: "agent", title: "Code Agent", text: "终态空 Trace 已完成。", status: "completed", createdAt: now, sessionId: "ses_terminal_empty", threadId: "thr_terminal_empty", traceId: "trc_terminal_empty", turnId: "turn_terminal_empty", runnerTrace: terminalEmptyTrace() }
]
};
}
function terminalEmptyTrace(): JsonRecord {
return { traceId: "trc_terminal_empty", status: "completed", sessionId: "ses_terminal_empty", threadId: "thr_terminal_empty", events: [], eventCount: 0, fullTraceLoaded: true, hasMore: false, finalResponse: { text: "终态空 Trace 已完成。" } };
}
function legacyCanonicalSession(): SessionRecord {
const now = new Date().toISOString();
return {
sessionId: "ses_c5e1330558c94d8e",
conversationId: "cnv_c5e1330558c94d8e",
threadId: "thr_c5e1330558c94d8e",
status: "completed",
lastTraceId: "trc_40badc15523146c9",
startedAt: now,
updatedAt: now,
messageCount: 2,
firstUserMessagePreview: "legacy cnv deep link canonical smoke",
messages: [
{ id: "msg_c5_user", role: "user", title: "用户", text: "legacy cnv deep link canonical smoke", status: "sent", createdAt: now, sessionId: "ses_c5e1330558c94d8e", threadId: "thr_c5e1330558c94d8e", traceId: "trc_40badc15523146c9" },
{ id: "msg_c5_agent", role: "agent", title: "Code Agent", text: "已完成:新增 Python benchmark 脚本。", status: "completed", createdAt: now, sessionId: "ses_c5e1330558c94d8e", threadId: "thr_c5e1330558c94d8e", traceId: "trc_40badc15523146c9", runnerTrace: legacyCanonicalTrace() }
]
};
}
function legacyCanonicalTrace(): JsonRecord {
return {
traceId: "trc_40badc15523146c9",
status: "completed",
sessionId: "ses_c5e1330558c94d8e",
threadId: "thr_c5e1330558c94d8e",
events: [{ seq: 1, label: "agentrun:assistant:message", type: "assistant_message", status: "completed", terminal: true, final: true, message: "已完成:新增 Python benchmark 脚本。" }],
eventCount: 1,
fullTraceLoaded: true,
hasMore: false,
finalResponse: { text: "已完成:新增 Python benchmark 脚本。", status: "completed" }
};
}
function markdownFinalSession(): SessionRecord {
const now = new Date().toISOString();
return {
sessionId: "ses_markdown_final",
conversationId: "cnv_markdown_final",
threadId: "thr_markdown_final",
status: "completed",
lastTraceId: "trc_markdown_final",
startedAt: now,
updatedAt: now,
messageCount: 2,
firstUserMessagePreview: "render markdown final response",
messages: [
{ id: "msg_markdown_user", messageId: "msg_markdown_user", role: "user", title: "用户", text: "render markdown final response", status: "sent", createdAt: now, sessionId: "ses_markdown_final", threadId: "thr_markdown_final", traceId: "trc_markdown_final", turnId: "trc_markdown_final" },
{ id: "msg_markdown_agent", messageId: "msg_markdown_agent", role: "agent", title: "Code Agent", text: markdownFinalText, parts: [{ type: "text", text: markdownFinalText }], status: "completed", createdAt: now, updatedAt: now, sessionId: "ses_markdown_final", threadId: "thr_markdown_final", traceId: "trc_markdown_final", turnId: "trc_markdown_final", runnerTrace: markdownFinalTrace() }
]
};
}
function markdownFinalTrace(): JsonRecord {
const createdAt = new Date().toISOString();
const event = { seq: 1, sourceSeq: 88, createdAt, label: "agentrun:assistant:message", type: "assistant_message", status: "completed", terminal: true, final: true, replyAuthority: true, message: markdownFinalText };
return {
traceId: "trc_markdown_final",
status: "completed",
sessionId: "ses_markdown_final",
threadId: "thr_markdown_final",
events: [event],
eventCount: 1,
fullTraceLoaded: true,
hasMore: false,
finalResponse: { text: markdownFinalText, status: "completed" }
};
}
function manyRailSession(index: number): SessionRecord {
const id = String(index).padStart(2, "0");
const now = new Date(Date.now() - (index + 1) * 1000).toISOString();
return { sessionId: `ses_many_${id}`, threadId: `thr_many_${id}`, status: "completed", lastTraceId: `trc_many_${id}`, startedAt: now, updatedAt: now, messageCount: 0, firstUserMessagePreview: `历史 session ${id}`, messages: [] };
}
function staleDetailSession(): SessionRecord {
const now = new Date().toISOString();
return { sessionId: "ses_stale_404", threadId: "thr_stale_404", status: "active", startedAt: now, updatedAt: now, messageCount: 0, firstUserMessagePreview: "已失效的列表项", messages: [] };
}
function emptySession(): SessionRecord {
const now = new Date().toISOString();
return { sessionId: "ses_empty", threadId: "thr_empty", status: "active", startedAt: now, updatedAt: now, messageCount: 0, firstUserMessagePreview: "空白会话", messages: [] };
}
function staleEmptyTtlSession(): SessionRecord {
const at = "2026-06-18T16:00:00.000Z";
return { sessionId: "ses_empty_ttl", threadId: "thr_empty_ttl", status: "idle", startedAt: at, updatedAt: at, messageCount: 0, firstUserMessagePreview: null, messages: [] };
}
function freshEmptyTtlSession(): SessionRecord {
const at = "2026-06-18T16:15:30.000Z";
return { sessionId: "ses_empty_fresh", threadId: "thr_empty_fresh", status: "idle", startedAt: at, updatedAt: at, messageCount: 0, firstUserMessagePreview: null, messages: [] };
}
function staleMessagedTtlSession(): SessionRecord {
const at = "2026-06-18T15:50:00.000Z";
return {
sessionId: "ses_empty_ttl_has_message",
threadId: "thr_empty_ttl_has_message",
status: "completed",
startedAt: at,
updatedAt: at,
messageCount: 2,
firstUserMessagePreview: "超过 TTL 但已有消息,不能被 GC",
messages: [
{ id: "msg_empty_ttl_user", role: "user", title: "用户", text: "超过 TTL 但已有消息,不能被 GC", status: "sent", createdAt: at, sessionId: "ses_empty_ttl_has_message", threadId: "thr_empty_ttl_has_message" },
{ id: "msg_empty_ttl_agent", role: "agent", title: "Code Agent", text: "保留已有消息 session。", status: "completed", createdAt: at, sessionId: "ses_empty_ttl_has_message", threadId: "thr_empty_ttl_has_message" }
]
};
}
function archivedDeletedSession(): SessionRecord {
const now = new Date().toISOString();
return { sessionId: "ses_deleted", threadId: "thr_deleted", status: "archived", startedAt: now, updatedAt: now, messageCount: 0, firstUserMessagePreview: "已删除 session", messages: [] };
}
function submitSplitSession(): SessionRecord {
const now = new Date().toISOString();
return {
sessionId: "ses_submit_split",
threadId: "thr_submit_split",
status: "idle",
lastTraceId: "trc_submit_split",
startedAt: now,
updatedAt: now,
messageCount: 2,
firstUserMessagePreview: "workbench submit race probe redacted: 请只回复 OK。",
messages: [
{ id: "msg_submit_split_user", messageId: "msg_submit_split_user", role: "user", title: "用户", text: "", status: "sent", createdAt: now, sessionId: "ses_submit_split", threadId: "thr_submit_split", traceId: "trc_submit_split", turnId: "turn_submit_split" },
{ id: "msg_submit_split_agent", messageId: "msg_submit_split_agent", role: "agent", title: "Code Agent", text: "", status: "running", createdAt: now, sessionId: "ses_submit_split", threadId: "thr_submit_split", traceId: "trc_submit_split", turnId: "turn_submit_split", runnerTrace: { traceId: "trc_submit_split", status: "running", sessionId: "ses_submit_split", threadId: "thr_submit_split", events: [], eventCount: 0, fullTraceLoaded: false, hasMore: false } }
]
};
}
function submitSplitTrace(): JsonRecord {
return { traceId: "trc_submit_split", status: "running", sessionId: "ses_submit_split", threadId: "thr_submit_split", turnId: "turn_submit_split", events: [], eventCount: 0, fullTraceLoaded: false, hasMore: false };
}
function crossSessionLateA(): SessionRecord {
const now = new Date().toISOString();
return {
sessionId: "ses_late_A",
threadId: "thr_late_A",
status: "completed",
lastTraceId: "trc_late_A",
startedAt: now,
updatedAt: now,
messageCount: 2,
firstUserMessagePreview: "写一个python脚本测试性能",
messages: [
{ id: "msg_late_A_user", messageId: "msg_late_A_user", role: "user", title: "用户", text: "写一个python脚本测试性能", status: "sent", createdAt: now, sessionId: "ses_late_A", threadId: "thr_late_A", traceId: "trc_late_A", turnId: "turn_late_A" },
{ id: "msg_late_A_agent", messageId: "msg_late_A_agent", role: "agent", title: "Code Agent", text: "已写好 perf_test.py,脚本可用。", status: "completed", createdAt: now, sessionId: "ses_late_A", threadId: "thr_late_A", traceId: "trc_late_A", turnId: "turn_late_A", runnerTrace: crossSessionLateTraceA() }
]
};
}
function crossSessionLateB(): SessionRecord {
const now = new Date().toISOString();
return {
sessionId: "ses_late_B",
threadId: "thr_late_B",
status: "active",
lastTraceId: "trc_late_A",
startedAt: now,
updatedAt: now,
messageCount: 2,
firstUserMessagePreview: "B session waiting",
messages: [
{ id: "msg_late_B_user", messageId: "msg_late_B_user", role: "user", title: "用户", text: "B session waiting", status: "sent", createdAt: now, sessionId: "ses_late_B", threadId: "thr_late_B", traceId: "trc_late_A", turnId: "turn_late_B" },
{ id: "msg_late_B_agent", messageId: "msg_late_B_agent", role: "agent", title: "Code Agent", text: "", status: "running", createdAt: now, sessionId: "ses_late_B", threadId: "thr_late_B", traceId: "trc_late_A", turnId: "turn_late_B", runnerTrace: { traceId: "trc_late_A", status: "running", sessionId: "ses_late_B", threadId: "thr_late_B", events: [], eventCount: 0, fullTraceLoaded: false, hasMore: false } }
]
};
}
function crossSessionLateTraceA(): JsonRecord {
const now = new Date().toISOString();
const finalText = "已写好 perf_test.py,脚本可用。";
const event = { seq: 1, createdAt: now, label: "agentrun:assistant:message", type: "assistant_message", status: "completed", replyAuthority: true, final: true, message: finalText, terminal: true };
return { traceId: "trc_late_A", status: "completed", sessionId: "ses_late_A", threadId: "thr_late_A", turnId: "turn_late_A", events: [event], eventCount: 1, fullTraceLoaded: true, hasMore: false, finalResponse: { text: finalText, status: "completed" }, assistantText: finalText };
}
function sessionDetailDelayMs(sessionId: string): number {
if (state.scenarioId === "cross-session-late-events" && sessionId === "ses_late_A") return 900;
if (state.scenarioId === "create-while-route-loading" && (sessionId === "ses_running" || sessionId === "ses_created_while_running")) return 4_000;
return state.sessionDetailDelayMs;
}
function acceptChatTurn(body: JsonRecord): JsonRecord {
const sessionId = typeof body.sessionId === "string" && body.sessionId.trim() ? body.sessionId : state.selectedSessionId;
const session = sessionById(sessionId) ?? createManualSession({ sessionId });
if (!state.sessions.includes(session)) state.sessions.unshift(session);
const threadId = typeof body.threadId === "string" && body.threadId.trim() ? body.threadId : null;
const traceId = state.scenarioId === "live-rest-backfill-without-terminal-sse"
? "trc_live_backfill"
: typeof body.traceId === "string" && body.traceId.trim() ? body.traceId : `trc_${Date.now().toString(16)}`;
const message = typeof body.message === "string" ? body.message : "";
const now = new Date().toISOString();
session.status = "running";
session.lastTraceId = traceId;
session.updatedAt = now;
session.firstUserMessagePreview = session.firstUserMessagePreview ?? message;
session.messageCount = (session.messages?.length ?? 0) + 2;
session.messages = [
...(session.messages ?? []),
{ id: `msg_${traceId}_user`, role: "user", title: "用户", text: message, status: "sent", createdAt: now, sessionId, threadId, traceId },
{ id: `msg_${traceId}_agent`, role: "agent", title: "Code Agent", text: "", status: "running", createdAt: now, sessionId, threadId, traceId, runnerTrace: { traceId, status: "running", sessionId, threadId, events: [], eventCount: 0, fullTraceLoaded: false, hasMore: false } }
];
if (state.scenarioId === "live-rest-backfill-without-terminal-sse") {
state.liveBackfillTraceId = traceId;
state.liveBackfillReadyAtMs = Date.now() + 2_000;
state.traces[traceId] = liveBackfillEarlyTrace(sessionId, threadId, traceId);
scheduleLiveBackfillProjection(sessionId, threadId, traceId, state.liveBackfillReadyAtMs);
} else {
state.traces[traceId] = { traceId, status: "running", sessionId, threadId, events: [], eventCount: 0, fullTraceLoaded: false, hasMore: false };
}
return { status: "running", accepted: true, running: true, terminal: false, traceId, sessionId, threadId, turnId: traceId };
}
function liveBackfillEarlyTrace(sessionId: string, threadId: string | null, traceId: string): JsonRecord {
const createdAt = new Date().toISOString();
const agentRun = liveBackfillAgentRun("pending", null, 4);
return {
traceId,
status: "running",
running: true,
terminal: false,
sessionId,
threadId,
agentRun,
events: [
{ seq: 1, sourceSeq: 1, createdAt, label: "agentrun:request:accepted", type: "backend_status", status: "running", payload: { commandId: agentRun.commandId } },
{ seq: 2, sourceSeq: 2, createdAt, label: "agentrun:run:created", type: "backend_status", status: "running", payload: { runId: agentRun.runId, runStatus: agentRun.runStatus } },
{ seq: 3, sourceSeq: 3, createdAt, label: "agentrun:command:created", type: "backend_status", status: "running", payload: { commandId: agentRun.commandId, commandState: agentRun.commandState } },
{ seq: 4, sourceSeq: 4, createdAt, label: "agentrun:runner-job:created", type: "backend_status", status: "running", payload: { commandId: agentRun.commandId, jobName: agentRun.jobName, namespace: agentRun.namespace } }
],
eventCount: 4,
fullTraceLoaded: true,
hasMore: false,
traceSummary: { source: "agentrun-events", agentRun, valuesPrinted: false }
};
}
function liveBackfillCompletedTrace(sessionId: string, threadId: string | null, traceId: string): JsonRecord {
const createdAt = new Date().toISOString();
const finalText = "fake AgentRun completed after REST backfill without terminal SSE.";
const agentRun = liveBackfillAgentRun("completed", "completed", 23);
const events = [
...(liveBackfillEarlyTrace(sessionId, threadId, traceId).events as JsonRecord[]),
{ seq: 5, sourceSeq: 21, createdAt, label: "agentrun:assistant:message", type: "assistant_message", status: "completed", message: finalText, payload: { commandId: agentRun.commandId } },
{ seq: 6, sourceSeq: 22, createdAt, label: "item/commandExecution:completed", type: "commandExecution", status: "completed", stdout: "OK\n", payload: { commandId: agentRun.commandId, exitCode: 0 } },
{ seq: 7, sourceSeq: 23, createdAt, label: "agentrun:backend:turn/completed", type: "backend_status", status: "completed", terminal: true, payload: { commandId: agentRun.commandId, phase: "command-terminal", terminalStatus: "completed" } }
];
return {
traceId,
status: "completed",
running: false,
terminal: true,
sessionId,
threadId,
agentRun,
events,
eventCount: events.length,
fullTraceLoaded: true,
hasMore: false,
finalResponse: { text: finalText, status: "completed" },
traceSummary: { source: "agentrun-command-result", terminalStatus: "completed", agentRun, valuesPrinted: false }
};
}
function liveBackfillAgentRun(commandState: string, terminalStatus: string | null, lastSeq: number): JsonRecord {
return {
adapter: "agentrun-v01",
runId: "run_live_backfill_issue1555",
commandId: "cmd_live_backfill_issue1555",
status: commandState,
runStatus: "claimed",
commandState,
terminalStatus,
runnerStatus: "running",
namespace: "agentrun-v02",
jobName: "agentrun-v01-runner-live-backfill-issue1555",
lastSeq,
valuesPrinted: false
};
}
function scheduleLiveBackfillProjection(sessionId: string, threadId: string | null, traceId: string, readyAtMs: number): void {
const scenarioId = state.scenarioId;
setTimeout(() => {
if (state.scenarioId !== scenarioId || state.liveBackfillTraceId !== traceId) return;
commitLiveBackfillProjection(sessionId, threadId, traceId);
}, Math.max(0, readyAtMs - Date.now()));
}
function commitLiveBackfillProjection(sessionId: string, threadId: string | null, traceId: string): void {
const completed = liveBackfillCompletedTrace(sessionId, threadId, traceId);
const finalText = typeof (completed.finalResponse as JsonRecord | undefined)?.text === "string" ? String((completed.finalResponse as JsonRecord).text) : "";
state.traces[traceId] = completed;
const session = sessionById(sessionId);
if (!session) return;
session.status = "completed";
session.updatedAt = new Date().toISOString();
session.messages = (session.messages ?? []).map((message) => {
if (message.role !== "agent" || message.traceId !== traceId) return message;
return { ...message, text: finalText, status: "completed", runnerTrace: completed };
});
}
function sessionSummary(session: SessionRecord): SessionRecord {
const { messages: _messages, hidden: _hidden, ...rest } = session;
const status = typeof rest.status === "string" ? rest.status : "unknown";
return { ...rest, status, running: isRunningStatus(status), terminal: isTerminalStatus(status) } as SessionRecord;
}
function isRunningStatus(status: string): boolean {
return ["active", "accepted", "pending", "running"].includes(status);
}
function isTerminalStatus(status: string): boolean {
return ["blocked", "canceled", "cancelled", "completed", "failed", "timeout"].includes(status);
}
function workbenchSessionListPayload(url: URL): JsonRecord {
if (url.searchParams.has("projectId") || url.searchParams.has("workspaceId")) return { ok: false, status: 400, error: { code: "workbench_authority_removed" } };
const includeRouteId = url.searchParams.get("includeSessionId") || "";
const includeSessionId = includeRouteId ? canonicalSessionId(includeRouteId) : "";
const limit = boundedPageLimit(url.searchParams.get("limit"));
const offset = cursorOffset(url.searchParams.get("cursor") || url.searchParams.get("after"));
const visible = state.sessions.filter((item) => !item.hidden && (item.status !== "archived" || item.sessionId === includeSessionId));
const authorityVisible = state.scenarioId === "create-while-route-loading" && includeSessionId === "ses_running"
? visible.filter((item) => item.sessionId !== "ses_created_while_running")
: visible;
const listed = state.listOmitSelected && includeSessionId ? authorityVisible.filter((item) => item.sessionId !== includeSessionId) : authorityVisible;
const page = listed.slice(offset, offset + limit);
const included = includeSessionId && !page.some((session) => session.sessionId === includeSessionId) ? listed.find((session) => session.sessionId === includeSessionId) ?? null : null;
const summaries = [...(included ? [included] : []), ...page].map((session) => sessionSummary(session));
const hasMore = offset + limit < listed.length;
return { ok: true, status: "succeeded", contractVersion: "workbench-sessions-v1", sessions: summaries, count: summaries.length, total: listed.length, cursor: offset > 0 ? cursorFromOffset(offset) : null, hasMore, nextCursor: hasMore ? cursorFromOffset(offset + limit) : null };
}
function boundedPageLimit(value: string | null): number {
const parsed = Number.parseInt(value ?? "", 10);
return Math.min(100, Math.max(1, Number.isInteger(parsed) && parsed > 0 ? parsed : 50));
}
function cursorOffset(value: string | null): number {
const text = typeof value === "string" ? value.trim() : "";
if (!text) return 0;
if (text.startsWith("idx:")) return Math.max(0, Number.parseInt(text.slice(4), 10) || 0);
return Math.max(0, Number.parseInt(text, 10) || 0);
}
function cursorFromOffset(offset: number): string {
return `idx:${Math.max(0, Math.trunc(offset))}`;
}
function workbenchSessionMessagesPayload(session: SessionRecord, url: URL): JsonRecord {
const messages = Array.isArray(session.messages) ? session.messages : [];
const requestedLimit = Number(url.searchParams.get("limit") ?? messages.length);
const limit = Math.max(1, Number.isFinite(requestedLimit) ? Math.trunc(requestedLimit) : messages.length || 100);
const page = messages.slice(0, limit);
return { ok: true, status: "succeeded", sessionId: session.sessionId, messages: page, count: page.length, total: messages.length, hasMore: messages.length > page.length };
}
function sessionById(id: string): SessionRecord | null {
return state.sessions.find((session) => session.sessionId === id) ?? null;
}
function canonicalSessionId(id: string): string {
const text = id.trim();
if (!text.startsWith("cnv_")) return text;
const conversation = state.sessions.find((session) => session.conversationId === text);
return conversation?.sessionId ?? `ses_${text.slice("cnv_".length)}`;
}
function visibleSessionById(id: string, options: { includeArchived?: boolean } = {}): SessionRecord | null {
const session = sessionById(id);
if (!session || session.hidden) return null;
if (session.status === "archived" && !options.includeArchived) return null;
return session;
}
function sessionPayload(sessionId: string): JsonRecord {
const session = sessionById(sessionId);
if (state.scenarioId === "terminal-turn-stale-session-active" && sessionId === "ses_completed") return { sessionId, status: "active", lastTraceId: "trc_completed", updatedAt: new Date().toISOString() };
return { sessionId, status: session?.status ?? "unknown", lastTraceId: session?.lastTraceId ?? null, updatedAt: new Date().toISOString() };
}
function turnPayload(traceId: string): JsonRecord {
if (state.scenarioId === "live-rest-backfill-without-terminal-sse" && traceId === state.liveBackfillTraceId) {
const session = state.sessions.find((item) => item.lastTraceId === traceId) ?? state.sessions[0];
const sessionId = session?.sessionId ?? state.selectedSessionId;
const threadId = typeof session?.threadId === "string" ? session.threadId : null;
return state.traces[traceId] ?? liveBackfillEarlyTrace(sessionId, threadId, traceId);
}
const trace = state.traces[traceId] ?? { traceId, status: "unknown", events: [] };
if (state.scenarioId === "tool-completed-projection-running" && traceId === "trc_running") return { ...trace, traceId, status: "running", running: true, terminal: false, projectionStatus: "projecting", trace: { status: trace.status, eventCount: trace.eventCount ?? 0 } };
if (state.scenarioId === "terminal-turn-stale-session-active" && traceId === "trc_completed") return { ...trace, traceId, status: "active", running: true, terminal: false, trace: { status: "completed", eventCount: trace.eventCount ?? 0 } };
const status = String(trace.status ?? "unknown");
return { ...trace, traceId, status, running: ["running", "pending", "accepted"].includes(status), terminal: ["completed", "failed", "blocked", "timeout", "canceled"].includes(status) };
}
function workbenchTurnPayload(traceId: string): JsonRecord {
const turn = turnPayload(traceId);
return { ok: true, status: "ok", contractVersion: "workbench-read-model-v1", turn, ...projectionEnvelope(turn) };
}
function tracePayload(traceId: string, url: URL): JsonRecord {
const turn = turnPayload(traceId);
const events = Array.isArray(turn.events) ? turn.events as JsonRecord[] : [];
const afterSeq = traceAfterSeq(url);
const requestedLimit = Number(url.searchParams.get("limit") ?? events.length);
const limit = requestedLimit || events.length || 100;
const filtered = afterSeq > 0 ? events.filter((event) => Number(event.seq ?? 0) > afterSeq) : events;
const page = filtered.slice(0, Math.max(1, Number.isFinite(limit) ? limit : 100));
const firstSeq = page.length > 0 ? Number(page[0]?.seq ?? 0) : null;
const lastSeq = page.length > 0 ? Number(page.at(-1)?.seq ?? 0) : null;
const hasMore = filtered.length > page.length;
return { ...turn, events: page, eventCount: events.length, hasMore, fullTraceLoaded: !hasMore, nextSinceSeq: lastSeq, nextSeq: page.length > 0 ? lastSeq : afterSeq, range: { afterSeq, fromSeq: firstSeq, toSeq: lastSeq, limit: Math.max(1, Number.isFinite(limit) ? limit : 100), returned: page.length, total: events.length } };
}
function traceAfterSeq(url: URL): number {
const cursor = url.searchParams.get("cursor") ?? "";
if (cursor.startsWith("seq:")) return Math.max(0, Number.parseInt(cursor.slice(4), 10) || 0);
return Math.max(0, Number.parseInt(url.searchParams.get("sinceSeq") ?? "0", 10) || 0);
}
function workbenchTracePayload(traceId: string, url: URL): JsonRecord {
const payload = tracePayload(traceId, url);
if (state.scenarioId === "tool-completed-projection-running" && traceId === "trc_running") {
return { ok: true, status: "ok", contractVersion: "workbench-read-model-v1", traceId, sessionId: payload.sessionId ?? null, threadId: payload.threadId ?? null, traceStatus: "completed", events: payload.events, eventCount: payload.eventCount, hasMore: payload.hasMore, nextSeq: payload.nextSeq, range: payload.range, fullTraceLoaded: payload.fullTraceLoaded, projectionStatus: "projecting", terminalEvidence: null, finalResponse: null, traceSummary: payload.traceSummary, retention: payload.retention };
}
return { ok: true, status: "ok", contractVersion: "workbench-read-model-v1", traceId, sessionId: payload.sessionId ?? null, threadId: payload.threadId ?? null, traceStatus: payload.status, events: payload.events, eventCount: payload.eventCount, hasMore: payload.hasMore, nextSeq: payload.nextSeq, range: payload.range, fullTraceLoaded: payload.fullTraceLoaded, terminalEvidence: payload.terminalEvidence, finalResponse: payload.finalResponse, traceSummary: payload.traceSummary, retention: payload.retention, ...projectionEnvelope(payload) };
}
function projectionEnvelope(payload: JsonRecord): JsonRecord {
const projection = payload.projection && typeof payload.projection === "object" ? payload.projection as JsonRecord : null;
if (!projection) return {};
return {
projection,
projectionStatus: projection.projectionStatus,
projectionHealth: projection.projectionHealth,
lastProjectedSeq: projection.lastProjectedSeq,
sourceRunId: projection.sourceRunId,
sourceCommandId: projection.sourceCommandId,
staleMs: projection.staleMs,
blocker: projection.blocker
};
}
function sse(request: IncomingMessage, response: ServerResponse, url: URL): void {
response.writeHead(200, { "content-type": "text/event-stream; charset=utf-8", "cache-control": "no-cache", connection: "keep-alive" });
sseClients.add(response);
request.on("close", () => sseClients.delete(response));
writeSse(response, "workbench.connected", { type: "connected", sessionId: url.searchParams.get("sessionId") });
if (state.scenarioId === "projection-sse-error") {
const scenarioId = state.scenarioId;
setTimeout(() => {
if (state.scenarioId !== scenarioId) return;
const projection = projectionDiagnostic({ traceId: "trc_projection_sse_error", code: "workbench_sse_projection_error", message: "SSE 投影事件异常,实时状态暂不可见。", category: "realtime-error", layer: "workbench-sse", runId: "run_projection_sse_error", commandId: "cmd_projection_sse_error" });
writeSse(response, "workbench.error", { type: "error", sessionId: "ses_projection_sse_error", threadId: "thr_projection_sse_error", traceId: "trc_projection_sse_error", projection, ...projectionEnvelope({ projection }), error: projection.blocker });
}, 350);
}
if (state.scenarioId === "cross-session-late-events") {
const scenarioId = state.scenarioId;
setTimeout(() => {
if (state.scenarioId !== scenarioId) return;
const trace = crossSessionLateTraceA();
const event = (trace.events as JsonRecord[])[0];
writeSse(response, "workbench.trace.event", { type: "trace.event", sessionId: "ses_late_A", threadId: "thr_late_A", traceId: "trc_late_A", event, snapshot: trace });
writeSse(response, "workbench.turn.snapshot", { type: "turn.snapshot", sessionId: "ses_late_A", threadId: "thr_late_A", traceId: "trc_late_A", turn: trace });
}, 650);
return;
}
if (!state.terminalScript) return;
const scenarioId = state.scenarioId;
setTimeout(() => {
if (state.scenarioId !== scenarioId) return;
if (state.scenarioId === "terminal-completed-no-final-response") {
const trace = completedNoFinalTrace();
const event = (trace.events as JsonRecord[]).at(-1) ?? { status: "completed", terminal: true };
writeSse(response, "workbench.trace.event", { type: "trace.event", sessionId: "ses_running", threadId: "thr_running", traceId: "trc_running", event, snapshot: trace });
finishRunningNoFinalResponse(trace);
writeSse(response, "workbench.turn.snapshot", { type: "turn.snapshot", sessionId: "ses_running", threadId: "thr_running", traceId: "trc_running", turn: turnPayload("trc_running") });
return;
}
const terminalStatus = state.terminalFailureScript ? "failed" : "completed";
const terminalText = state.terminalFailureScript ? "恢复后失败:缺少受控依赖。" : state.scenarioId === "progress-only-final-response" ? finalOnlyText : "事件重放后完成。";
const event = { seq: 3, createdAt: new Date().toISOString(), label: "agentrun:assistant:message", type: "assistant_message", status: terminalStatus, replyAuthority: true, final: true, message: terminalText, terminal: true };
writeSse(response, "workbench.trace.event", { type: "trace.event", sessionId: "ses_running", threadId: "thr_running", traceId: "trc_running", event, snapshot: { traceId: "trc_running", sessionId: "ses_running", threadId: "thr_running", status: terminalStatus, events: [event], eventCount: 3, fullTraceLoaded: true, finalResponse: { text: terminalText, status: terminalStatus } } });
finishRunningSession(terminalStatus, terminalText);
writeSse(response, "workbench.turn.snapshot", { type: "turn.snapshot", sessionId: "ses_running", threadId: "thr_running", traceId: "trc_running", turn: turnPayload("trc_running") });
}, state.scenarioId === "progress-only-final-response" ? 5_000 : 350);
}
function broadcastSse(eventName: string, payload: JsonRecord): void {
for (const client of Array.from(sseClients)) writeSse(client, eventName, payload);
}
function writeSse(response: ServerResponse, eventName: string, payload: JsonRecord): void {
response.write(`event: ${eventName}\n`);
response.write(`data: ${JSON.stringify(payload)}\n\n`);
}
function finishRunningSession(status: "completed" | "failed", text: string): void {
const session = sessionById("ses_running");
if (!session) return;
session.status = status;
session.updatedAt = new Date().toISOString();
const trace = state.traces.trc_running ?? { traceId: "trc_running", status: "running", events: [] };
const finalEvent = { seq: 4, createdAt: new Date().toISOString(), label: `agentrun:terminal:${status}`, status, terminal: true };
const events = [...(Array.isArray(trace.events) ? trace.events as JsonRecord[] : []), finalEvent];
state.traces.trc_running = { ...trace, status, events, eventCount: events.length, fullTraceLoaded: true, hasMore: false, assistantText: text, finalResponse: { text, status } };
session.messages = (session.messages ?? []).map((message) => message.role === "agent" ? { ...message, text, status, runnerTrace: state.traces.trc_running } : message);
}
function finishRunningNoFinalResponse(trace: JsonRecord = completedNoFinalTrace()): void {
state.traces.trc_running = trace;
const session = sessionById("ses_running");
if (!session) return;
session.status = "completed";
session.updatedAt = new Date().toISOString();
session.messages = (session.messages ?? []).map((message) => message.role === "agent" ? { ...message, text: "", status: "completed", runnerTrace: trace } : message);
}
function stateSummary(): JsonRecord {
return { scenarioId: state.scenarioId, selectedSessionId: state.selectedSessionId, requestLedger: state.requestLedger, legacyRequestLedger: state.legacyRequestLedger, chatRequests: state.chatRequests, staleTraceId: state.staleTraceId, sessions: state.sessions.map((item) => ({ sessionId: item.sessionId, threadId: item.threadId, status: item.status, lastTraceId: item.lastTraceId, hidden: item.hidden === true })) };
}
function gcEmptySessions(body: JsonRecord): JsonRecord {
const ttlMs = positiveInteger(body.ttlMs, DEFAULT_WORKBENCH_EMPTY_SESSION_TTL_MS);
const nowMs = Date.parse(typeof body.now === "string" ? body.now : "2026-06-18T16:20:30.000Z");
const archivedSessionIds: string[] = [];
const keptByReason: Record<string, number> = {};
for (const session of state.sessions) {
if (session.status === "archived") continue;
const decision = classifyWorkbenchEmptySessionGcCandidate(toBackendSessionRecord(session), { nowMs, ttlMs });
if (!decision.eligible) {
keptByReason[decision.reason] = (keptByReason[decision.reason] ?? 0) + 1;
continue;
}
session.status = "archived";
session.updatedAt = new Date(nowMs).toISOString();
archivedSessionIds.push(session.sessionId);
}
return { ok: true, status: "completed", ttlMs, archived: archivedSessionIds.length, archivedSessionIds, keptByReason, valuesRedacted: true };
}
function toBackendSessionRecord(session: SessionRecord): JsonRecord {
return {
id: session.sessionId,
status: session.status,
startedAt: session.startedAt,
updatedAt: session.updatedAt,
lastTraceId: session.lastTraceId,
session: {
createdAt: session.startedAt,
updatedAt: session.updatedAt,
messageCount: session.messageCount,
messages: session.messages ?? [],
lastTraceId: session.lastTraceId,
valuesRedacted: true,
secretMaterialStored: false
}
};
}
function recordLedger(method: string, url: URL): void {
if (!url.pathname.startsWith("/v1/")) return;
const entry = { method, path: url.pathname, query: Object.fromEntries(url.searchParams.entries()) };
state.requestLedger.push(entry);
if (isLegacyWorkbenchPath(url.pathname)) state.legacyRequestLedger.push(entry);
}
function isLegacyWorkbenchPath(path: string): boolean {
if (/^\/v1\/workbench\/(sessions(?:\/[^/]+(?:\/messages)?)?|events|turns\/[^/]+|traces\/[^/]+\/events)$/u.test(path)) return false;
if (path === "/v1/agent/chat" || /^\/v1\/agent\/sessions(?:\/[^/]+(?:\/select)?)?$/u.test(path)) return false;
return path.startsWith("/v1/workbench/") || path.startsWith("/v1/agent/");
}
function authPayload(): JsonRecord {
const deniedCapabilities = { web: "forbidden", workbench: "forbidden", codeAgent: "forbidden", billing: "forbidden", apiKeys: "forbidden", admin: "forbidden" };
if (state.scenarioId === "auth-upstream-unavailable" || state.scenarioId === "auth-invalid-credentials") {
return { authenticated: false, mode: "server", authMethod: null, identityAuthority: null, sessionKind: null, actor: null, user: null, capabilities: deniedCapabilities, expiresAt: null, valuesRedacted: true, error: { code: "auth_required", message: "Authentication is required", status: 401 } };
}
const actor = { id: "usr_e2e_admin", username: "e2e-admin", role: "admin", status: "active" };
return { authenticated: true, mode: "server", authMethod: "web-session", identityAuthority: "hwlab-local", sessionKind: "browser", actor, user: actor, capabilities: { web: "available", workbench: "available", codeAgent: "available", billing: "unlinked", apiKeys: "available", admin: "available" }, expiresAt: null, valuesRedacted: true };
}
function authLoginResponse(response: ServerResponse): void {
if (state.scenarioId === "auth-upstream-unavailable") return json(response, 502, { ok: false, status: "failed", error: { code: "upstream_unavailable", message: "auth upstream unavailable", retryable: true, layer: "proxy" } });
return json(response, 401, { ok: false, status: "failed", error: { code: "invalid_credentials", message: "invalid credentials" } });
}
function redactRequestBody(value: JsonRecord): JsonRecord {
const output: JsonRecord = {};
for (const [key, item] of Object.entries(value)) output[key] = /secret|token|key|password|authorization/iu.test(key) ? "<redacted>" : item;
return output;
}
async function readJson(request: IncomingMessage): Promise<JsonRecord> {
const chunks: Buffer[] = [];
for await (const chunk of request) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
if (chunks.length === 0) return {};
try {
const parsed = JSON.parse(Buffer.concat(chunks).toString("utf8"));
return parsed && typeof parsed === "object" ? parsed as JsonRecord : {};
} catch {
return {};
}
}
function json(response: ServerResponse, status: number, body: unknown): void {
response.writeHead(status, { "content-type": "application/json; charset=utf-8" });
response.end(JSON.stringify(body));
}
function staticFile(response: ServerResponse, path: string): void {
const filePath = safeStaticPath(path);
const target = filePath && existsSync(filePath) && statSync(filePath).isFile() ? filePath : join(distDir, "index.html");
response.writeHead(200, { "content-type": contentType(target) });
createReadStream(target).pipe(response);
}
function safeStaticPath(path: string): string | null {
const normalized = resolve(distDir, `.${decodeURIComponent(path === "/" ? "/index.html" : path)}`);
return normalized === distDir || normalized.startsWith(`${distDir}${sep}`) ? normalized : null;
}
function contentType(filePath: string): string {
switch (extname(filePath)) {
case ".html": return "text/html; charset=utf-8";
case ".js": return "text/javascript; charset=utf-8";
case ".css": return "text/css; charset=utf-8";
case ".svg": return "image/svg+xml";
case ".ico": return "image/x-icon";
default: return "application/octet-stream";
}
}
function delay(ms: number): Promise<void> {
return ms > 0 ? new Promise((resolveDelay) => setTimeout(resolveDelay, ms)) : Promise.resolve();
}
function positiveInteger(value: unknown, fallback: number): number {
const parsed = Number.parseInt(String(value ?? ""), 10);
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
}