fix: converge workbench session state projection
This commit is contained in:
@@ -101,14 +101,14 @@ async function handleRequest(request: IncomingMessage, response: ServerResponse)
|
||||
|
||||
const sessionMessagesMatch = path.match(/^\/v1\/workbench\/sessions\/([^/]+)\/messages$/u);
|
||||
if (sessionMessagesMatch && method === "GET") {
|
||||
const sessionId = decodeURIComponent(sessionMessagesMatch[1] ?? "");
|
||||
const sessionId = canonicalSessionId(decodeURIComponent(sessionMessagesMatch[1] ?? ""));
|
||||
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 = decodeURIComponent(workbenchSessionMatch[1] ?? "");
|
||||
const sessionId = canonicalSessionId(decodeURIComponent(workbenchSessionMatch[1] ?? ""));
|
||||
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 });
|
||||
@@ -130,6 +130,13 @@ async function handleRequest(request: IncomingMessage, response: ServerResponse)
|
||||
}
|
||||
|
||||
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 }] });
|
||||
@@ -148,6 +155,13 @@ function createScenarioState(scenarioId: string): ScenarioState {
|
||||
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 === "session-rail-many") {
|
||||
for (let index = 0; index < 46; index += 1) sessions.push(manyRailSession(index));
|
||||
}
|
||||
if (id === "session-switch-detail-404-isolated") {
|
||||
sessions.unshift(staleDetailSession());
|
||||
sessions.push(emptySession());
|
||||
@@ -160,6 +174,8 @@ function createScenarioState(scenarioId: string): ScenarioState {
|
||||
if (id === "session-switch-empty-reload") sessions.push(emptySession());
|
||||
const selectedSessionId = 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"
|
||||
@@ -218,6 +234,45 @@ 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 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: [] };
|
||||
@@ -281,6 +336,13 @@ 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;
|
||||
@@ -296,6 +358,7 @@ function sessionPayload(sessionId: string): JsonRecord {
|
||||
|
||||
function turnPayload(traceId: string): JsonRecord {
|
||||
const trace = state.traces[traceId] ?? { traceId, status: "unknown", events: [] };
|
||||
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) };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user