fix(workbench): expose turn lifecycle ids

This commit is contained in:
lyon
2026-06-17 12:34:22 +08:00
parent 9e460346b9
commit 8a4a3f1248
4 changed files with 94 additions and 4 deletions
+41 -3
View File
@@ -1,3 +1,5 @@
// SPEC: PJ2026-010403 API契约 draft-2026-06-17-r0; PJ2026-0102 Agent编排 draft-2026-06-17-r0.
// Responsibility: Code Agent HTTP turn admission, lifecycle compatibility wrappers, and session evidence projection.
import { createHash, randomUUID } from "node:crypto";
@@ -85,9 +87,11 @@ export async function handleCodeAgentChatHttp(request, response, options) {
}
const traceId = safeTraceId(getHeader(request, "x-trace-id") || params.traceId) || `trc_${randomUUID()}`;
const lifecycle = codeAgentTurnLifecycleFields(traceId, params);
const chatParams = {
...params,
traceId,
...lifecycle,
ownerUserId: options.actor?.id ?? params.ownerUserId,
ownerRole: options.actor?.role ?? params.ownerRole
};
@@ -113,6 +117,7 @@ export async function handleCodeAgentChatHttp(request, response, options) {
shortConnection: true,
controlSemantics: "submit-and-poll",
traceId,
...lifecycle,
workspaceId: workspaceClaim?.workspace?.id ?? null,
workspaceRevision: workspaceClaim?.workspace?.revision ?? null,
conversationId: safeConversationId(nativeSessionChatParams.conversationId) || null,
@@ -702,6 +707,7 @@ function submitCodeAgentChatTurn({ params, options, traceId }) {
accepted: true,
status: "running",
traceId,
...codeAgentTurnLifecycleFields(traceId, params),
conversationId: safeConversationId(params.conversationId) || null,
sessionId: safeSessionId(params.sessionId) || null,
threadId: safeOpaqueId(params.threadId) || null,
@@ -1276,6 +1282,7 @@ function forbiddenTurnSnapshot(traceId) {
function codeAgentTurnStatusPayload({ traceId, result, snapshot, resultPollError, refreshError, options }) {
const resultObject = result && typeof result === "object" ? result : null;
const snapshotObject = snapshot && typeof snapshot === "object" ? snapshot : null;
const lifecycle = codeAgentTurnLifecycleFields(traceId, resultObject ?? snapshotObject ?? {});
const events = Array.isArray(snapshotObject?.events) ? snapshotObject.events : Array.isArray(resultObject?.runnerTrace?.events) ? resultObject.runnerTrace.events : [];
const lastEvent = events.at(-1) ?? null;
const status = normalizeTurnStatus(
@@ -1300,6 +1307,7 @@ function codeAgentTurnStatusPayload({ traceId, result, snapshot, resultPollError
running: found ? running : false,
terminal: found ? terminal : false,
traceId,
...lifecycle,
conversationId: safeConversationId(resultObject?.conversationId ?? snapshotObject?.conversationId) || null,
sessionId: safeSessionId(resultObject?.sessionId ?? resultObject?.session?.sessionId ?? snapshotObject?.sessionId) || null,
threadId: safeOpaqueId(resultObject?.threadId ?? resultObject?.session?.threadId ?? snapshotObject?.threadId) || null,
@@ -2219,6 +2227,7 @@ function codeAgentConversationMessagesEvidence(payload = {}, params = {}, traceI
const userText = boundedConversationMessageText(params.message ?? params.prompt ?? payload.userMessage ?? payload.prompt);
if (!userText) return [];
const resolvedTraceId = safeTraceId(traceId) || null;
const lifecycle = codeAgentTurnLifecycleFields(resolvedTraceId, payload);
const session = payload.session && typeof payload.session === "object" ? payload.session : null;
const sessionReuse = payload.sessionReuse && typeof payload.sessionReuse === "object" ? payload.sessionReuse : null;
const conversationId = safeConversationId(payload.conversationId ?? session?.conversationId ?? sessionReuse?.conversationId ?? params.conversationId) || null;
@@ -2226,14 +2235,15 @@ function codeAgentConversationMessagesEvidence(payload = {}, params = {}, traceI
const threadId = safeOpaqueId(session?.threadId ?? sessionReuse?.threadId ?? payload.threadId ?? params.threadId) || null;
const createdAt = payload.createdAt ?? payload.reply?.createdAt ?? new Date().toISOString();
const updatedAt = payload.updatedAt ?? createdAt;
const traceSuffix = codeAgentMessageTraceSuffix(resolvedTraceId);
const userMessage = {
id: `msg_${traceSuffix}_user`,
id: lifecycle.userMessageId,
messageId: lifecycle.userMessageId,
role: "user",
title: "用户",
text: userText,
status: "sent",
traceId: resolvedTraceId,
turnId: lifecycle.turnId,
conversationId,
sessionId,
threadId,
@@ -2256,12 +2266,14 @@ function codeAgentConversationMessagesEvidence(payload = {}, params = {}, traceI
);
const agentStatus = codeAgentConversationAgentMessageStatus(payload);
const agentMessage = {
id: `msg_${traceSuffix}_agent`,
id: lifecycle.assistantMessageId,
messageId: lifecycle.assistantMessageId,
role: "agent",
title: agentStatus === "failed" ? "Code Agent 返回阻塞" : agentStatus === "running" ? "Code Agent 处理中" : "Code Agent 回复",
text: agentText || (agentStatus === "running" ? "" : "Code Agent 请求已结束,请查看 Trace 详情。"),
status: agentStatus,
traceId: resolvedTraceId,
turnId: lifecycle.turnId,
conversationId,
sessionId,
threadId,
@@ -2306,6 +2318,31 @@ function codeAgentMessageTraceSuffix(traceId) {
return text.replace(/^trc_/u, "").replace(/[^A-Za-z0-9_.:-]/gu, "_").slice(0, 48) || "trace";
}
function codeAgentTurnLifecycleFields(traceId, source = {}) {
const resolvedTraceId = safeTraceId(source?.traceId ?? traceId) || safeTraceId(traceId) || null;
const turnId = safeTurnId(source?.turnId) || resolvedTraceId;
const traceSuffix = codeAgentMessageTraceSuffix(resolvedTraceId);
return {
turnId,
userMessageId: safeMessageId(source?.userMessageId ?? source?.userMessage?.messageId) || codeAgentLifecycleMessageId(traceSuffix, "user"),
assistantMessageId: safeMessageId(source?.assistantMessageId ?? source?.assistantMessage?.messageId) || codeAgentLifecycleMessageId(traceSuffix, "agent")
};
}
function codeAgentLifecycleMessageId(traceSuffix, role) {
return `msg_${traceSuffix}_${role}`;
}
function safeTurnId(value) {
const text = textValue(value);
return /^turn_[A-Za-z0-9_.:-]+$/u.test(text) ? text : null;
}
function safeMessageId(value) {
const text = textValue(value);
return /^msg_[A-Za-z0-9_.:-]+$/u.test(text) ? text : null;
}
function codeAgentConversationAgentMessageStatus(payload = {}) {
const status = textValue(payload.status ?? payload.agentRun?.terminalStatus ?? payload.commandState ?? payload.runStatus).toLowerCase();
if (["completed", "done", "success", "active"].includes(status)) return "completed";
@@ -2831,6 +2868,7 @@ function compactCodeAgentChatResultPayload(payload, options = {}) {
const { userBillingReservation, ...publicPayload } = payload;
return {
...publicPayload,
...codeAgentTurnLifecycleFields(publicPayload.traceId, publicPayload),
...(terminalEvidence ? { terminalEvidence: terminalEvidencePayload(terminalEvidence) } : {}),
...(publicPayload.runnerTrace && typeof publicPayload.runnerTrace === "object"
? { runnerTrace: compactRunnerTraceForResult(publicPayload.runnerTrace, limit) }