Merge pull request #1631 from pikasTech/fix-1629-workbench-final-response
修复 Workbench terminal assistant finalResponse 封存
This commit is contained in:
@@ -1670,8 +1670,8 @@ function agentRunLongLivedSessionGate(base = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
function agentRunToolCalls(result = {}, status = "completed") {
|
||||
const replyText = agentRunReplyText(result);
|
||||
function agentRunToolCalls(result = {}, status = "completed", replyTextOverride = null) {
|
||||
const replyText = messageAuthorityTextValue(replyTextOverride) || agentRunReplyText(result);
|
||||
return [{
|
||||
name: "agentrun.v01.command.turn",
|
||||
status,
|
||||
@@ -1701,8 +1701,10 @@ function agentRunResultToCodeAgentPayload({ base, result, traceStore, traceId, a
|
||||
const terminalEventCreatedAt = agentRunResultTraceCreatedAt(runnerTrace, now);
|
||||
const providerTrace = agentRunProviderTrace({ base, result, terminalStatus });
|
||||
const replyText = agentRunReplyText(result);
|
||||
if (terminalStatus === "completed" && replyText) {
|
||||
const finalResponse = agentRunCompletedFinalResponse({ base, result, traceId, now, replyText });
|
||||
if (terminalStatus === "completed") {
|
||||
const traceReplyText = agentRunTraceTerminalAssistantText(runnerTrace);
|
||||
const completedReplyText = replyText || traceReplyText;
|
||||
const finalResponse = completedReplyText ? agentRunCompletedFinalResponse({ base, result, traceId, now, replyText: completedReplyText }) : null;
|
||||
const traceSummary = agentRunCompletedTraceSummary({ base, runnerTrace, finalResponse, traceId });
|
||||
if (appendResultEvent) {
|
||||
traceStore.append(traceId, agentRunTraceEvent({
|
||||
@@ -1727,18 +1729,18 @@ function agentRunResultToCodeAgentPayload({ base, result, traceStore, traceId, a
|
||||
sessionReuse: agentRunSessionReuseSummary(base, base.agentRun.reused === true),
|
||||
runner: agentRunRunnerSummary(base.agentRun),
|
||||
runnerTrace: traceStore.snapshot(traceId, agentRunTraceMeta({}, {})),
|
||||
toolCalls: agentRunToolCalls(result, "completed"),
|
||||
toolCalls: agentRunToolCalls(result, "completed", completedReplyText),
|
||||
skills: { status: "delegated", provider: ADAPTER_ID, count: 0, items: [], valuesPrinted: false },
|
||||
longLivedSessionGate: agentRunLongLivedSessionGate(base),
|
||||
providerTrace,
|
||||
finalResponse,
|
||||
finalResponse: finalResponse ?? null,
|
||||
traceSummary,
|
||||
reply: {
|
||||
reply: finalResponse ? {
|
||||
messageId: finalResponse.messageId,
|
||||
role: "assistant",
|
||||
content: finalResponse.text,
|
||||
createdAt: finalResponse.createdAt
|
||||
},
|
||||
} : null,
|
||||
usage: null,
|
||||
agentRun: { ...base.agentRun, terminalStatus, completed: true, reuseEligible: true, providerTrace, valuesPrinted: false },
|
||||
valuesPrinted: false
|
||||
@@ -1831,6 +1833,25 @@ function agentRunCompletedFinalResponse({ base, result, traceId, now, replyText
|
||||
};
|
||||
}
|
||||
|
||||
function agentRunTraceTerminalAssistantText(runnerTrace = {}) {
|
||||
const events = Array.isArray(runnerTrace?.events) ? runnerTrace.events : [];
|
||||
for (let index = events.length - 1; index >= 0; index -= 1) {
|
||||
const event = events[index];
|
||||
if (!event || typeof event !== "object") continue;
|
||||
if (!isAgentRunAssistantTraceEvent(event)) continue;
|
||||
if (!(event.terminal === true || event.final === true || event.replyAuthority === true)) continue;
|
||||
const textValue = messageAuthorityTextValue(event.finalResponse ?? event.text ?? event.content ?? event.message ?? event.summary ?? event.payload?.text ?? event.payload?.content ?? event.payload?.message);
|
||||
if (textValue) return textValue;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function isAgentRunAssistantTraceEvent(event = {}) {
|
||||
const type = String(event.type ?? event.eventType ?? "").trim().toLowerCase();
|
||||
if (type === "assistant" || type === "assistant_message") return true;
|
||||
return /assistant:message|assistant_message/u.test(String(event.label ?? "").toLowerCase());
|
||||
}
|
||||
|
||||
function agentRunTerminalFailureFinalResponse({ base, traceId, now, status, text }) {
|
||||
const textValue = String(text ?? "").trim();
|
||||
return {
|
||||
@@ -1880,14 +1901,14 @@ function agentRunCompletedTraceSummary({ base, runnerTrace, finalResponse, trace
|
||||
source: "agentrun-command-result",
|
||||
sourceEventCount: Number(runnerTrace?.eventCount ?? events.length ?? 0),
|
||||
terminalStatus: "completed",
|
||||
finalAssistantRow: {
|
||||
finalAssistantRow: finalResponse ? {
|
||||
role: finalResponse.role,
|
||||
status: finalResponse.status,
|
||||
textChars: finalResponse.textChars,
|
||||
textPreview: finalResponse.text.slice(0, 240),
|
||||
messageId: finalResponse.messageId,
|
||||
valuesPrinted: false
|
||||
},
|
||||
} : null,
|
||||
agentRun: {
|
||||
runId: base.agentRun?.runId ?? null,
|
||||
commandId: base.agentRun?.commandId ?? null,
|
||||
|
||||
@@ -1054,6 +1054,116 @@ test("AgentRun sync converts terminal command result even when run remains claim
|
||||
}
|
||||
});
|
||||
|
||||
test("AgentRun sync seals completed final response from authoritative terminal assistant trace event (#1629)", async () => {
|
||||
const calls = [];
|
||||
const traceId = "trc_issue1629_terminal_assistant_final";
|
||||
const runId = "run_issue1629_trace_final";
|
||||
const commandId = "cmd_issue1629_trace_final";
|
||||
const finalText = [
|
||||
"全部六份数据到手!下面是完整的六语言终极性能对比:",
|
||||
"",
|
||||
"| language | runtime | status |",
|
||||
"|---|---|---|",
|
||||
"| Lua | LuaJIT | pass |"
|
||||
].join("\n");
|
||||
const agentRunServer = createHttpServer(async (request, response) => {
|
||||
const url = new URL(request.url || "/", "http://127.0.0.1");
|
||||
calls.push({ method: request.method, path: url.pathname, search: url.search });
|
||||
const send = (data) => {
|
||||
response.writeHead(200, { "content-type": "application/json" });
|
||||
response.end(`${JSON.stringify({ ok: true, data, traceId: "trc_fake_issue1629" })}\n`);
|
||||
};
|
||||
if (request.method === "GET" && url.pathname === `/api/v1/runs/${runId}/commands`) {
|
||||
return send({ items: [
|
||||
{ id: commandId, runId, state: "completed", type: "turn", seq: 1, idempotencyKey: traceId, payload: { traceId, conversationId: "cnv_issue1629", hwlabSessionId: "ses_issue1629", threadId: "thread_issue1629" } }
|
||||
] });
|
||||
}
|
||||
if (request.method === "GET" && url.pathname === `/api/v1/runs/${runId}/events`) {
|
||||
return send({ items: [
|
||||
{ id: "evt_issue1629_progress", runId, seq: 21, type: "assistant_message", payload: { commandId, text: "正在补 Lua 基准测试。" }, createdAt: "2026-06-19T15:47:13.000Z" },
|
||||
{ id: "evt_issue1629_final", runId, seq: 28, type: "assistant_message", payload: { commandId, text: finalText, final: true, replyAuthority: true }, createdAt: "2026-06-19T15:47:28.000Z" },
|
||||
{ id: "evt_issue1629_terminal", runId, seq: 29, type: "terminal_status", payload: { commandId, terminalStatus: "completed" }, createdAt: "2026-06-19T15:47:29.000Z" }
|
||||
] });
|
||||
}
|
||||
if (request.method === "GET" && url.pathname === `/api/v1/runs/${runId}/commands/${commandId}/result`) {
|
||||
return send({
|
||||
runId,
|
||||
commandId,
|
||||
attemptId: "attempt_issue1629",
|
||||
runnerId: "runner_issue1629",
|
||||
jobName: "agentrun-v01-runner-issue1629",
|
||||
namespace: "agentrun-v01",
|
||||
status: "completed",
|
||||
runStatus: "completed",
|
||||
commandState: "completed",
|
||||
terminalStatus: "completed",
|
||||
completed: true,
|
||||
reply: null,
|
||||
finalResponse: null,
|
||||
lastSeq: 29,
|
||||
eventCount: 29,
|
||||
sessionRef: { sessionId: "ses_issue1629", conversationId: "cnv_issue1629", threadId: "thread_issue1629" }
|
||||
});
|
||||
}
|
||||
response.writeHead(404, { "content-type": "application/json" });
|
||||
response.end(`${JSON.stringify({ ok: false, message: `unexpected ${request.method} ${url.pathname}` })}\n`);
|
||||
});
|
||||
await new Promise((resolve) => agentRunServer.listen(0, "127.0.0.1", resolve));
|
||||
const agentRunPort = agentRunServer.address().port;
|
||||
const traceStore = createCodeAgentTraceStore();
|
||||
try {
|
||||
const currentResult = {
|
||||
ok: true,
|
||||
accepted: true,
|
||||
shortConnection: true,
|
||||
status: "running",
|
||||
traceId,
|
||||
conversationId: "cnv_issue1629",
|
||||
sessionId: "ses_issue1629",
|
||||
threadId: "thread_issue1629",
|
||||
agentRun: {
|
||||
adapter: "agentrun-v01",
|
||||
managerUrl: `http://127.0.0.1:${agentRunPort}`,
|
||||
runId,
|
||||
commandId,
|
||||
status: "running",
|
||||
runStatus: "running",
|
||||
commandState: "running",
|
||||
terminalStatus: null,
|
||||
lastSeq: 0,
|
||||
valuesPrinted: false
|
||||
},
|
||||
valuesPrinted: false
|
||||
};
|
||||
const synced = await syncAgentRunChatResult({
|
||||
traceId,
|
||||
currentResult,
|
||||
traceStore,
|
||||
options: {
|
||||
env: {
|
||||
AGENTRUN_MGR_URL: `http://127.0.0.1:${agentRunPort}`,
|
||||
HWLAB_CODE_AGENT_AGENTRUN_ALLOW_NON_K3S_URL: "1"
|
||||
}
|
||||
}
|
||||
});
|
||||
assert.equal(synced.resultSynced, true);
|
||||
assert.equal(synced.result.status, "completed");
|
||||
assert.equal(synced.result.finalResponse.text, finalText);
|
||||
assert.equal(synced.result.assistantText ?? synced.result.finalResponse.text, finalText);
|
||||
assert.equal(synced.result.reply.content, finalText);
|
||||
assert.equal(synced.result.traceSummary.finalAssistantRow.textChars, finalText.length);
|
||||
const assistantEvent = synced.result.runnerTrace.events.find((event) => event.label === "agentrun:assistant:message" && event.message === finalText);
|
||||
assert.equal(assistantEvent?.message, finalText);
|
||||
assert.deepEqual(calls.map((call) => call.path), [
|
||||
`/api/v1/runs/${runId}/commands`,
|
||||
`/api/v1/runs/${runId}/events`,
|
||||
`/api/v1/runs/${runId}/commands/${commandId}/result`
|
||||
]);
|
||||
} finally {
|
||||
await new Promise((resolve, reject) => agentRunServer.close((error) => (error ? reject(error) : resolve())));
|
||||
}
|
||||
});
|
||||
|
||||
test("cloud api AgentRun adapter reports persistent thread resume when a completed run needs a new runner", async () => {
|
||||
const calls = [];
|
||||
const hwlabSessionId = "ses_server-test-thread-resume";
|
||||
|
||||
@@ -58,6 +58,51 @@ test("workbench projection diagnostics keeps projecting health distinct from cau
|
||||
assert.equal(diagnostics.projectionHealth, "projecting");
|
||||
});
|
||||
|
||||
test("workbench turn projection seals final response from authoritative terminal assistant trace event (#1629)", () => {
|
||||
const traceId = "trc_workbench_terminal_assistant_final";
|
||||
const finalText = [
|
||||
"全部六份数据到手!下面是完整的六语言终极性能对比:",
|
||||
"",
|
||||
"| language | runtime | status |",
|
||||
"|---|---|---|",
|
||||
"| Lua | LuaJIT | pass |"
|
||||
].join("\n");
|
||||
const trace = {
|
||||
traceId,
|
||||
status: "completed",
|
||||
events: [
|
||||
{ seq: 1, type: "assistant_message", status: "running", message: "正在补 Lua 基准测试。" },
|
||||
{ seq: 2, type: "assistant_message", status: "completed", replyAuthority: true, final: true, message: finalText },
|
||||
{ seq: 3, type: "result", status: "completed", terminal: true, label: "agentrun:terminal:completed" }
|
||||
],
|
||||
eventCount: 3
|
||||
};
|
||||
const projection = createWorkbenchTurnProjection({ traceId, result: { traceId, status: "completed" }, trace });
|
||||
assert.equal(projection.status, "completed");
|
||||
assert.equal(projection.terminal, true);
|
||||
assert.equal(projection.finalResponse.text, finalText);
|
||||
assert.equal(projection.assistantText, finalText);
|
||||
assert.equal(projection.terminalEvidence.finalResponse.text, finalText);
|
||||
});
|
||||
|
||||
test("workbench turn projection keeps progress-only assistant trace text out of final response (#1629)", () => {
|
||||
const traceId = "trc_workbench_terminal_without_authoritative_final";
|
||||
const trace = {
|
||||
traceId,
|
||||
status: "completed",
|
||||
events: [
|
||||
{ seq: 1, type: "assistant_message", status: "running", message: "这只是运行中的进度。" },
|
||||
{ seq: 2, type: "result", status: "completed", terminal: true, label: "agentrun:terminal:completed" }
|
||||
],
|
||||
eventCount: 2
|
||||
};
|
||||
const projection = createWorkbenchTurnProjection({ traceId, result: { traceId, status: "completed" }, trace });
|
||||
assert.equal(projection.status, "completed");
|
||||
assert.equal(projection.terminal, true);
|
||||
assert.equal(projection.finalResponse, null);
|
||||
assert.equal(projection.assistantText, null);
|
||||
});
|
||||
|
||||
test("workbench trace event page exposes monotonic cursor range for restored mixed events", async () => {
|
||||
const traceId = "trc_workbench_trace_mixed_cursor";
|
||||
const session = {
|
||||
|
||||
@@ -9,12 +9,13 @@ export const RUNNING_STATUSES = new Set(["running", "pending", "queued", "accept
|
||||
export function createWorkbenchTurnProjection({ turnId = null, traceId = null, result = null, session = null, trace = null } = {}) {
|
||||
const projectionTraceId = textValue(traceId ?? trace?.traceId ?? result?.traceId ?? session?.lastTraceId) || null;
|
||||
const projectionTurnId = textValue(turnId) || projectionTraceId;
|
||||
const terminalEvidence = terminalTurnEvidence({ result, trace });
|
||||
const traceTerminal = traceTerminalEvidence(trace);
|
||||
const terminalEvidence = terminalTurnEvidence({ result, traceTerminal });
|
||||
const activeEvidence = activeTurnEvidence({ result, session, trace });
|
||||
const status = terminalEvidence?.status ?? activeEvidence?.status ?? "unknown";
|
||||
const running = RUNNING_STATUSES.has(status);
|
||||
const terminal = Boolean(terminalEvidence && TERMINAL_STATUSES.has(status) && !running);
|
||||
const finalText = terminal ? projectionText(result?.finalResponse, result?.assistantText, result?.reply, result?.text, result?.summary, trace?.finalResponse, trace?.terminalEvidence?.finalResponse) : null;
|
||||
const finalText = terminal ? projectionText(result?.finalResponse, result?.assistantText, result?.reply, result?.text, result?.summary, trace?.finalResponse, trace?.terminalEvidence?.finalResponse, terminalEvidence?.finalResponse) : null;
|
||||
const agentRun = objectValue(result?.agentRun ?? trace?.agentRun);
|
||||
const lastEvent = traceLastEvent(trace);
|
||||
return {
|
||||
@@ -88,10 +89,10 @@ export function normalizeWorkbenchStatus(value) {
|
||||
return text || "unknown";
|
||||
}
|
||||
|
||||
function terminalTurnEvidence({ result = null, trace = null } = {}) {
|
||||
function terminalTurnEvidence({ result = null, traceTerminal = null } = {}) {
|
||||
const resultStatus = terminalStatusFromValue(result?.status ?? result?.terminalStatus ?? result?.agentRun?.terminalStatus);
|
||||
if (resultStatus) return { source: "result", status: resultStatus, valuesRedacted: true };
|
||||
return traceTerminalEvidence(trace);
|
||||
if (resultStatus) return { source: "result", status: resultStatus, finalResponse: traceTerminal?.finalResponse ?? null, valuesRedacted: true };
|
||||
return traceTerminal;
|
||||
}
|
||||
|
||||
function activeTurnEvidence({ result = null, session = null, trace = null } = {}) {
|
||||
@@ -128,7 +129,9 @@ function normalizeActiveStatus(status) {
|
||||
}
|
||||
|
||||
function terminalTraceEventEvidence(events = []) {
|
||||
for (const event of [...events].reverse()) {
|
||||
const finalResponse = terminalAssistantEventFinalResponse(events);
|
||||
for (let index = events.length - 1; index >= 0; index -= 1) {
|
||||
const event = events[index];
|
||||
if (!event || typeof event !== "object") continue;
|
||||
const terminal = event.terminal === true || event.final === true || event.replyAuthority === true;
|
||||
if (!terminal) continue;
|
||||
@@ -136,14 +139,43 @@ function terminalTraceEventEvidence(events = []) {
|
||||
return {
|
||||
source: "trace-terminal-event",
|
||||
status,
|
||||
seq: eventSeq(event, events.indexOf(event)),
|
||||
seq: eventSeq(event, index),
|
||||
eventType: textValue(event.type ?? event.label) || null,
|
||||
finalResponse,
|
||||
evidence: finalResponse ? { textPresent: true, source: "trace-terminal-assistant-event", valuesRedacted: true } : null,
|
||||
valuesRedacted: true
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function terminalAssistantEventFinalResponse(events = []) {
|
||||
for (let index = events.length - 1; index >= 0; index -= 1) {
|
||||
const event = events[index];
|
||||
if (!event || typeof event !== "object") continue;
|
||||
if (!isAssistantTraceEvent(event)) continue;
|
||||
if (!(event.terminal === true || event.final === true || event.replyAuthority === true)) continue;
|
||||
const text = projectionText(event.finalResponse, event.text, event.content, event.message, event.summary, event.payload?.text, event.payload?.content, event.payload?.message);
|
||||
if (!text) continue;
|
||||
const status = terminalStatusFromValue(event.status ?? event.terminalStatus ?? event.payload?.terminalStatus) ?? "completed";
|
||||
return {
|
||||
text,
|
||||
status,
|
||||
traceId: textValue(event.traceId) || null,
|
||||
seq: eventSeq(event, index),
|
||||
eventType: textValue(event.type ?? event.label) || null,
|
||||
valuesPrinted: false
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function isAssistantTraceEvent(event = {}) {
|
||||
const type = String(event.type ?? event.eventType ?? "").trim().toLowerCase();
|
||||
if (type === "assistant" || type === "assistant_message") return true;
|
||||
return /assistant:message|assistant_message/u.test(String(event.label ?? "").toLowerCase());
|
||||
}
|
||||
|
||||
function activeTraceEventStatus(events = []) {
|
||||
for (const event of [...events].reverse()) {
|
||||
const status = normalizeWorkbenchStatus(event?.status ?? event?.type);
|
||||
|
||||
@@ -78,6 +78,15 @@ const markdownFinalText = [
|
||||
"|---|---|",
|
||||
"| local | pass |"
|
||||
].join("\n");
|
||||
const terminalAssistantFinalText = [
|
||||
"全部六份数据到手!下面是完整的六语言终极性能对比:",
|
||||
"",
|
||||
"| language | runtime | status |",
|
||||
"|---|---|---|",
|
||||
"| Lua | LuaJIT | pass |",
|
||||
"| Python | CPython | pass |",
|
||||
"| Rust | native | pass |"
|
||||
].join("\n");
|
||||
|
||||
let state = createScenarioState("baseline");
|
||||
const sseClients = new Set<ServerResponse>();
|
||||
@@ -383,6 +392,10 @@ function createScenarioState(scenarioId: string): ScenarioState {
|
||||
sessions.unshift(markdownFinalSession());
|
||||
traces.trc_markdown_final = markdownFinalTrace();
|
||||
}
|
||||
if (id === "terminal-assistant-event-final-response") {
|
||||
sessions.unshift(terminalAssistantFinalSession());
|
||||
traces.trc_terminal_assistant_final = terminalAssistantFinalTrace();
|
||||
}
|
||||
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);
|
||||
@@ -431,6 +444,8 @@ function createScenarioState(scenarioId: string): ScenarioState {
|
||||
? "ses_terminal_empty"
|
||||
: id === "markdown-final-response"
|
||||
? "ses_markdown_final"
|
||||
: id === "terminal-assistant-event-final-response"
|
||||
? "ses_terminal_assistant_final"
|
||||
: id === "progress-only-final-response" || id === "terminal-completed-no-final-response" || id === "tool-completed-projection-running"
|
||||
? "ses_running"
|
||||
: id === "projector-resume-from-checkpoint"
|
||||
@@ -972,6 +987,47 @@ function markdownFinalTrace(): JsonRecord {
|
||||
};
|
||||
}
|
||||
|
||||
function terminalAssistantFinalSession(): SessionRecord {
|
||||
const now = new Date().toISOString();
|
||||
const trace = terminalAssistantFinalTrace();
|
||||
return {
|
||||
sessionId: "ses_terminal_assistant_final",
|
||||
conversationId: "cnv_terminal_assistant_final",
|
||||
threadId: "thr_terminal_assistant_final",
|
||||
status: "completed",
|
||||
lastTraceId: "trc_terminal_assistant_final",
|
||||
startedAt: now,
|
||||
updatedAt: now,
|
||||
messageCount: 2,
|
||||
firstUserMessagePreview: "再把lua加入对比测试",
|
||||
messages: [
|
||||
{ id: "msg_terminal_assistant_user", messageId: "msg_terminal_assistant_user", role: "user", title: "用户", text: "再把lua加入对比测试", status: "sent", createdAt: now, sessionId: "ses_terminal_assistant_final", threadId: "thr_terminal_assistant_final", traceId: "trc_terminal_assistant_final", turnId: "trc_terminal_assistant_final" },
|
||||
{ id: "msg_terminal_assistant_agent", messageId: "msg_terminal_assistant_agent", role: "agent", title: "Code Agent", text: terminalAssistantFinalText, parts: [{ type: "text", text: terminalAssistantFinalText, status: "completed" }], status: "completed", createdAt: now, updatedAt: now, sessionId: "ses_terminal_assistant_final", threadId: "thr_terminal_assistant_final", traceId: "trc_terminal_assistant_final", turnId: "trc_terminal_assistant_final", runnerTrace: trace, finalResponse: { text: terminalAssistantFinalText, status: "completed" } }
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
function terminalAssistantFinalTrace(): JsonRecord {
|
||||
const createdAt = new Date().toISOString();
|
||||
const events = [
|
||||
{ seq: 1, sourceSeq: 21, createdAt, label: "agentrun:assistant:message", type: "assistant_message", status: "running", replyAuthority: false, final: false, terminal: false, message: "现在写 Lua 基准测试脚本。" },
|
||||
{ seq: 2, sourceSeq: 28, createdAt, label: "agentrun:assistant:message", type: "assistant_message", status: "completed", replyAuthority: true, final: true, terminal: true, message: terminalAssistantFinalText },
|
||||
{ seq: 3, sourceSeq: 29, createdAt, label: "agentrun:terminal:completed", type: "result", status: "completed", terminal: true, message: "AgentRun command completed." }
|
||||
];
|
||||
return {
|
||||
traceId: "trc_terminal_assistant_final",
|
||||
status: "completed",
|
||||
sessionId: "ses_terminal_assistant_final",
|
||||
threadId: "thr_terminal_assistant_final",
|
||||
events,
|
||||
eventCount: events.length,
|
||||
fullTraceLoaded: true,
|
||||
hasMore: false,
|
||||
assistantText: terminalAssistantFinalText,
|
||||
finalResponse: { text: terminalAssistantFinalText, status: "completed" }
|
||||
};
|
||||
}
|
||||
|
||||
function manyRailSession(index: number): SessionRecord {
|
||||
const id = String(index).padStart(2, "0");
|
||||
const now = new Date(Date.now() - (index + 1) * 1000).toISOString();
|
||||
|
||||
@@ -43,6 +43,41 @@ test.describe("terminal completed without final response", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("terminal assistant event final response", () => {
|
||||
test.use({ scenarioId: "terminal-assistant-event-final-response" });
|
||||
|
||||
test("renders the sealed Workbench final response from the authoritative terminal assistant event", async ({ page }, testInfo) => {
|
||||
const sessionId = "ses_terminal_assistant_final";
|
||||
const traceId = "trc_terminal_assistant_final";
|
||||
const messagesResponse = await page.request.get(`/v1/workbench/sessions/${sessionId}/messages?limit=100`);
|
||||
expect(messagesResponse.status()).toBe(200);
|
||||
const messagesPayload = await messagesResponse.json();
|
||||
const agentMessage = messagesPayload.messages.find((message: { role?: string }) => message.role === "agent");
|
||||
expect(agentMessage?.text).toContain("全部六份数据到手");
|
||||
expect(agentMessage?.finalResponse?.text).toBe(agentMessage?.text);
|
||||
|
||||
const turnResponse = await page.request.get(`/v1/workbench/turns/${traceId}`);
|
||||
expect(turnResponse.status()).toBe(200);
|
||||
const turnPayload = await turnResponse.json();
|
||||
expect(turnPayload.turn.finalResponse.text).toBe(agentMessage?.text);
|
||||
|
||||
const traceResponse = await page.request.get(`/v1/workbench/traces/${traceId}/events?limit=20`);
|
||||
expect(traceResponse.status()).toBe(200);
|
||||
const tracePayload = await traceResponse.json();
|
||||
const finalEvent = tracePayload.events.find((event: { label?: string; final?: boolean }) => event.label === "agentrun:assistant:message" && event.final === true);
|
||||
expect(finalEvent?.replyAuthority).toBe(true);
|
||||
expect(finalEvent?.message).toBe(agentMessage?.text);
|
||||
|
||||
await gotoWorkbench(page, `/workbench/sessions/${sessionId}`);
|
||||
const card = page.locator(`${selectors.messageCard}[data-role="agent"][data-status="completed"]`).last();
|
||||
await expect(card.locator(".message-markdown.message-text")).toContainText("全部六份数据到手");
|
||||
await expect(card.locator(".message-markdown.message-text table")).toHaveCount(1);
|
||||
await card.locator("summary.trace-disclosure-summary").click();
|
||||
await expect(card.locator(".trace-row-markdown table")).toHaveCount(1);
|
||||
await saveScreenshot(page, testInfo, "terminal-assistant-event-final-response");
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("non-terminal tool completed event", () => {
|
||||
test.use({ scenarioId: "tool-completed-projection-running" });
|
||||
|
||||
|
||||
Reference in New Issue
Block a user