|
|
|
@@ -349,6 +349,19 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
|
|
|
|
replaceActiveMessages(project(messages.value));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function updateSessionMessages(sessionId: string | null | undefined, project: (source: ChatMessage[]) => ChatMessage[]): void {
|
|
|
|
|
const id = normalizeWorkbenchSessionId(sessionId);
|
|
|
|
|
if (!id) return;
|
|
|
|
|
const current = serverState.value.messagesBySessionId[id] ?? serverState.value.sessionsById[id]?.messages ?? [];
|
|
|
|
|
rememberSessionMessages(id, project(current));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function updateTraceMessages(traceId: string, authoritySessionId: string | null | undefined, project: (message: ChatMessage) => ChatMessage): void {
|
|
|
|
|
const ownerSessionId = traceOwnerSessionId(traceId, authoritySessionId);
|
|
|
|
|
if (!ownerSessionId) return;
|
|
|
|
|
updateSessionMessages(ownerSessionId, (source) => source.map((message) => messageMatchesTraceAuthority(message, traceId, authoritySessionId, ownerSessionId) ? project(message) : message));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function appendActiveMessages(...items: ChatMessage[]): void {
|
|
|
|
|
replaceActiveMessages([...messages.value, ...items]);
|
|
|
|
|
}
|
|
|
|
@@ -402,6 +415,44 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
|
|
|
|
return runWorkbenchReadHydration(() => api.workbench.traceEvents(traceId, codeAgentTimeoutMs.value, () => activityRef.value, { sinceSeq, limit: TRACE_HYDRATION_PAGE_LIMIT }));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function refreshMessageProjectionForTrace(sessionId: string | null | undefined, traceId: string): Promise<void> {
|
|
|
|
|
const id = normalizeWorkbenchSessionId(sessionId);
|
|
|
|
|
if (!id) return;
|
|
|
|
|
const response = await api.workbench.sessionMessages(id, { limit: 100 });
|
|
|
|
|
if (!response.ok || !response.data) {
|
|
|
|
|
applyProjectionDiagnostic(traceId, projectionDiagnosticFromFailure({ code: "message_projection_refresh_failed", message: response.error ?? "消息投影刷新失败,主消息正文保持上一份 canonical projection。", health: response.status === 0 ? "unavailable" : "degraded" }));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const pageMessages = Array.isArray(response.data.messages) ? response.data.messages.map((message) => normalizeChatMessage(message as ChatMessage)) : [];
|
|
|
|
|
rememberSessionMessages(id, mergeMessageProjectionPage(id, pageMessages));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function mergeMessageProjectionPage(sessionId: string, pageMessages: ChatMessage[]): ChatMessage[] {
|
|
|
|
|
const existing = serverState.value.messagesBySessionId[sessionId] ?? [];
|
|
|
|
|
return pageMessages.map((message) => mergeMessageProjectionMessage(message, existing));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function mergeMessageProjectionMessage(message: ChatMessage, existing: ChatMessage[]): ChatMessage {
|
|
|
|
|
const previous = findExistingProjectionMessage(message, existing);
|
|
|
|
|
const traceId = firstNonEmptyString(message.traceId, message.runnerTrace?.traceId, previous?.traceId, previous?.runnerTrace?.traceId);
|
|
|
|
|
const authority = traceId ? traceAuthorityById.value[traceId] ?? null : null;
|
|
|
|
|
const preservedTrace = authority ?? previous?.runnerTrace ?? null;
|
|
|
|
|
if (!preservedTrace) return message;
|
|
|
|
|
const runnerTrace = message.runnerTrace ? mergeRunnerTrace(message.runnerTrace, preservedTrace) : preservedTrace;
|
|
|
|
|
return { ...message, runnerTrace };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function findExistingProjectionMessage(message: ChatMessage, existing: ChatMessage[]): ChatMessage | null {
|
|
|
|
|
const messageId = firstNonEmptyString(message.messageId, message.id);
|
|
|
|
|
if (messageId) {
|
|
|
|
|
const byId = existing.find((item) => firstNonEmptyString(item.messageId, item.id) === messageId);
|
|
|
|
|
if (byId) return byId;
|
|
|
|
|
}
|
|
|
|
|
const traceId = firstNonEmptyString(message.traceId, message.runnerTrace?.traceId);
|
|
|
|
|
if (!traceId) return null;
|
|
|
|
|
return existing.find((item) => item.role === message.role && firstNonEmptyString(item.traceId, item.runnerTrace?.traceId) === traceId) ?? null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function hydrateTurnStatusAuthority(source: ChatMessage[] = messages.value): Promise<void> {
|
|
|
|
|
await Promise.all(uniqueTraceIds(source).slice(-12).map((traceId) => refreshTurnStatusByTraceId(traceId)));
|
|
|
|
|
}
|
|
|
|
@@ -419,8 +470,9 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function applyTurnStatusSnapshot(traceId: string, result: AgentChatResultResponse | TraceSnapshot): void {
|
|
|
|
|
if (!shouldApplyActiveTraceAuthority(traceId, traceResultSessionId(result))) return;
|
|
|
|
|
recordActivity(`turn:${firstNonEmptyString(result.lastEventLabel, result.status, result.waitingFor, "status") ?? "status"}`);
|
|
|
|
|
const ownerSessionId = traceOwnerSessionId(traceId, traceResultSessionId(result));
|
|
|
|
|
if (!ownerSessionId) return;
|
|
|
|
|
if (ownerSessionId === activeSessionId.value) recordActivity(`turn:${firstNonEmptyString(result.lastEventLabel, result.status, result.waitingFor, "status") ?? "status"}`);
|
|
|
|
|
rememberTurnStatus(traceId, result);
|
|
|
|
|
syncTurnStatusToMessage(traceId, result);
|
|
|
|
|
}
|
|
|
|
@@ -447,27 +499,15 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function syncTurnStatusToMessage(traceId: string, result: AgentChatResultResponse | TraceSnapshot): void {
|
|
|
|
|
const resultStatus = statusFromResult(result.status);
|
|
|
|
|
const authoritySessionId = traceResultSessionId(result);
|
|
|
|
|
updateActiveMessages((source) => source.map((message) => {
|
|
|
|
|
if (!shouldApplyTraceToMessage(message, traceId, authoritySessionId)) return message;
|
|
|
|
|
const status = isTerminalMessageStatus(message.status) ? message.status : resultStatus;
|
|
|
|
|
const terminal = isTerminalMessageStatus(status) || (result as AgentChatResultResponse).terminal === true;
|
|
|
|
|
updateTraceMessages(traceId, authoritySessionId, (message) => {
|
|
|
|
|
const runnerTrace = mergeTerminalResultTrace(message.runnerTrace, result as AgentChatResultResponse);
|
|
|
|
|
rememberTraceAuthority(runnerTrace);
|
|
|
|
|
const error = normalizeAgentError((result as AgentChatResultResponse).error ?? runnerTrace?.error ?? message.error);
|
|
|
|
|
const errorText = agentErrorDisplayText(error);
|
|
|
|
|
const agentRun = agentRunFromResult(result as AgentChatResultResponse, runnerTrace) ?? agentRunFromMessage(message);
|
|
|
|
|
const text = terminal
|
|
|
|
|
? projectedAgentMessageText({
|
|
|
|
|
status,
|
|
|
|
|
finalText: finalResponseText((result as AgentChatResultResponse).finalResponse),
|
|
|
|
|
errorText,
|
|
|
|
|
baseText: message.text
|
|
|
|
|
})
|
|
|
|
|
: message.text;
|
|
|
|
|
return { ...message, status, text, runnerTrace, error: error ?? message.error ?? null, agentRun: agentRun ?? undefined, updatedAt: new Date().toISOString() };
|
|
|
|
|
}));
|
|
|
|
|
const projection = projectionFromResult(result as AgentChatResultResponse) ?? runnerTrace.projection ?? message.projection ?? null;
|
|
|
|
|
return { ...message, runnerTrace, error: error ?? message.error ?? null, projection, projectionStatus: projection?.projectionStatus ?? null, projectionHealth: projection?.projectionHealth ?? null, blocker: projection?.blocker ?? null, agentRun: agentRun ?? undefined, updatedAt: new Date().toISOString() };
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function submitMessage(text: string): Promise<boolean> {
|
|
|
|
@@ -625,18 +665,20 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
|
|
|
|
|
|
|
|
|
function applyTraceHydrationResult(traceId: string, result: AgentChatResultResponse): void {
|
|
|
|
|
const authoritySessionId = traceResultSessionId(result);
|
|
|
|
|
if (!shouldApplyActiveTraceAuthority(traceId, authoritySessionId)) return;
|
|
|
|
|
const ownerSessionId = traceOwnerSessionId(traceId, authoritySessionId);
|
|
|
|
|
if (!ownerSessionId) return;
|
|
|
|
|
const events = Array.isArray(result.events) ? result.events : Array.isArray(result.traceEvents) ? result.traceEvents : [];
|
|
|
|
|
const activityLabel = firstNonEmptyString(result.lastEventLabel, events.at(-1)?.label, events.at(-1)?.type, result.status);
|
|
|
|
|
if (events.length > 0 || result.terminal === true || isTerminalMessageStatus(result.status)) recordActivity(`trace:${activityLabel ?? "hydrated"}`);
|
|
|
|
|
if (ownerSessionId === activeSessionId.value && (events.length > 0 || result.terminal === true || isTerminalMessageStatus(result.status))) recordActivity(`trace:${activityLabel ?? "hydrated"}`);
|
|
|
|
|
markWorkbenchTraceEventsReceived({ traceId, events, transport: "rest_gap" });
|
|
|
|
|
updateActiveMessages((source) => source.map((message) => {
|
|
|
|
|
if (!shouldApplyTraceToMessage(message, traceId, authoritySessionId)) return message;
|
|
|
|
|
updateSessionMessages(ownerSessionId, (source) => source.map((message) => {
|
|
|
|
|
if (!messageMatchesTraceAuthority(message, traceId, authoritySessionId, ownerSessionId)) return message;
|
|
|
|
|
const displayStatus = firstNonEmptyString(message.status, message.runnerTrace?.status, result.status, result.runnerTrace?.status);
|
|
|
|
|
const runnerTrace = mergeRunnerTrace(message.runnerTrace, {
|
|
|
|
|
...(result.runnerTrace ?? {}),
|
|
|
|
|
traceId: result.traceId ?? traceId,
|
|
|
|
|
status: result.status ?? result.traceStatus ?? message.runnerTrace?.status,
|
|
|
|
|
sessionId: result.sessionId ?? message.runnerTrace?.sessionId,
|
|
|
|
|
status: displayStatus ?? undefined,
|
|
|
|
|
sessionId: authoritySessionId ?? message.runnerTrace?.sessionId,
|
|
|
|
|
threadId: result.threadId ?? message.runnerTrace?.threadId,
|
|
|
|
|
events,
|
|
|
|
|
eventSource: "trace-api",
|
|
|
|
@@ -650,7 +692,6 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
|
|
|
|
traceStatus: result.traceStatus,
|
|
|
|
|
retention: result.retention,
|
|
|
|
|
terminalEvidence: result.terminalEvidence,
|
|
|
|
|
finalResponse: result.finalResponse,
|
|
|
|
|
traceSummary: result.traceSummary,
|
|
|
|
|
agentRun: result.agentRun,
|
|
|
|
|
projection: projectionFromResult(result),
|
|
|
|
@@ -661,17 +702,11 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
|
|
|
|
lastEventLabel: result.lastEventLabel ?? events.at(-1)?.label ?? events.at(-1)?.type,
|
|
|
|
|
updatedAt: new Date().toISOString()
|
|
|
|
|
});
|
|
|
|
|
const explicitStatus = normalizedStatusText(result.status);
|
|
|
|
|
const resultStatus = explicitStatus ? statusFromResult(result.status) : message.status;
|
|
|
|
|
const status = isTerminalMessageStatus(message.status) ? message.status : resultStatus;
|
|
|
|
|
const terminal = result.terminal === true || isTerminalMessageStatus(status);
|
|
|
|
|
const error = normalizeAgentError(result.error ?? runnerTrace?.error ?? message.error);
|
|
|
|
|
const projection = projectionFromResult(result) ?? runnerTrace.projection ?? message.projection ?? null;
|
|
|
|
|
const errorText = agentErrorDisplayText(error);
|
|
|
|
|
const text = terminal ? projectedAgentMessageText({ status, finalText: finalResponseText(result.finalResponse), errorText, baseText: message.text }) : message.text;
|
|
|
|
|
const agentRun = agentRunFromResult(result, runnerTrace) ?? agentRunFromMessage(message);
|
|
|
|
|
rememberTraceAuthority(runnerTrace);
|
|
|
|
|
return { ...message, status, text, runnerTrace, error: error ?? message.error ?? null, projection, projectionStatus: projection?.projectionStatus ?? null, projectionHealth: projection?.projectionHealth ?? null, blocker: projection?.blocker ?? null, agentRun: agentRun ?? undefined, updatedAt: new Date().toISOString() };
|
|
|
|
|
return { ...message, runnerTrace, error: error ?? message.error ?? null, projection, projectionStatus: projection?.projectionStatus ?? null, projectionHealth: projection?.projectionHealth ?? null, blocker: projection?.blocker ?? null, agentRun: agentRun ?? undefined, updatedAt: new Date().toISOString() };
|
|
|
|
|
}));
|
|
|
|
|
markWorkbenchTraceProjected(traceId);
|
|
|
|
|
}
|
|
|
|
@@ -776,6 +811,28 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
|
|
|
|
return normalizeWorkbenchSessionId(firstNonEmptyString(message?.sessionId, message?.runnerTrace?.sessionId));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function traceOwnerSessionId(traceId: string | null | undefined, authoritySessionId: string | null | undefined): string | null {
|
|
|
|
|
const sessionId = normalizeWorkbenchSessionId(authoritySessionId);
|
|
|
|
|
if (sessionId) return sessionId;
|
|
|
|
|
const id = firstNonEmptyString(traceId);
|
|
|
|
|
if (!id) return activeSessionId.value;
|
|
|
|
|
for (const [candidateSessionId, candidateMessages] of Object.entries(serverState.value.messagesBySessionId)) {
|
|
|
|
|
if (candidateMessages.some((message) => firstNonEmptyString(message.traceId, message.runnerTrace?.traceId) === id)) return normalizeWorkbenchSessionId(candidateSessionId);
|
|
|
|
|
}
|
|
|
|
|
return activeSessionId.value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function messageMatchesTraceAuthority(message: ChatMessage, traceId: string, authoritySessionId: string | null | undefined, ownerSessionId: string | null | undefined): boolean {
|
|
|
|
|
if (message.role !== "agent" || firstNonEmptyString(message.traceId, message.runnerTrace?.traceId) !== traceId) return false;
|
|
|
|
|
const sessionId = normalizeWorkbenchSessionId(authoritySessionId);
|
|
|
|
|
const ownerId = normalizeWorkbenchSessionId(ownerSessionId);
|
|
|
|
|
const messageSessionId = messageSessionAuthority(message);
|
|
|
|
|
if (sessionId && ownerId && sessionId !== ownerId) return false;
|
|
|
|
|
if (messageSessionId && ownerId && messageSessionId !== ownerId) return false;
|
|
|
|
|
if (sessionId && messageSessionId && sessionId !== messageSessionId) return false;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function shouldApplyActiveTraceAuthority(traceId: string | null | undefined, authoritySessionId: string | null | undefined): boolean {
|
|
|
|
|
const id = firstNonEmptyString(traceId);
|
|
|
|
|
const activeId = activeSessionId.value;
|
|
|
|
@@ -940,58 +997,51 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function applyTraceSnapshot(traceId: string, snapshot: TraceSnapshot): void {
|
|
|
|
|
const turn = turnStatusAuthority.value[traceId];
|
|
|
|
|
const trace = snapshotToRunnerTrace({
|
|
|
|
|
...snapshot,
|
|
|
|
|
status: firstNonEmptyString(turn?.status, snapshot.status) ?? snapshot.status,
|
|
|
|
|
traceStatus: firstNonEmptyString(snapshot.traceStatus, snapshot.status) ?? undefined
|
|
|
|
|
});
|
|
|
|
|
const authoritySessionId = traceResultSessionId(trace) ?? traceResultSessionId(snapshot);
|
|
|
|
|
if (!shouldApplyActiveTraceAuthority(traceId, authoritySessionId)) return;
|
|
|
|
|
updateActiveMessages((source) => source.map((message) => {
|
|
|
|
|
if (!shouldApplyTraceToMessage(message, traceId, authoritySessionId)) return message;
|
|
|
|
|
const ownerSessionId = traceOwnerSessionId(traceId, authoritySessionId);
|
|
|
|
|
if (!ownerSessionId) return;
|
|
|
|
|
updateSessionMessages(ownerSessionId, (source) => source.map((message) => {
|
|
|
|
|
if (!messageMatchesTraceAuthority(message, traceId, authoritySessionId, ownerSessionId)) return message;
|
|
|
|
|
const runnerTrace = mergeRunnerTrace(message.runnerTrace, trace);
|
|
|
|
|
rememberTraceAuthority(runnerTrace);
|
|
|
|
|
const status = message.status;
|
|
|
|
|
const terminal = isTerminalMessageStatus(status);
|
|
|
|
|
const error = message.role === "agent" ? normalizeAgentError(runnerTrace.error ?? message.error) : normalizeAgentError(message.error);
|
|
|
|
|
const errorText = message.role === "agent" ? agentErrorDisplayText(error) : null;
|
|
|
|
|
const nextText = message.role === "agent" && terminal
|
|
|
|
|
? projectedAgentMessageText({ status, finalText: finalResponseText(runnerTrace.finalResponse), errorText, baseText: message.text })
|
|
|
|
|
: message.text;
|
|
|
|
|
const projection = trace.projection ?? runnerTrace.projection ?? message.projection ?? null;
|
|
|
|
|
return { ...message, status, text: nextText, traceAutoLifecycle: terminal ? "terminal" : message.traceAutoLifecycle, runnerTrace, error: error ?? message.error ?? null, projection, projectionStatus: projection?.projectionStatus ?? null, projectionHealth: projection?.projectionHealth ?? null, blocker: projection?.blocker ?? null, updatedAt: new Date().toISOString() };
|
|
|
|
|
return { ...message, runnerTrace, error: error ?? message.error ?? null, projection, projectionStatus: projection?.projectionStatus ?? null, projectionHealth: projection?.projectionHealth ?? null, blocker: projection?.blocker ?? null, updatedAt: new Date().toISOString() };
|
|
|
|
|
}));
|
|
|
|
|
markWorkbenchTraceProjected(traceId);
|
|
|
|
|
void refreshSessions(trace.sessionId ?? selectedSessionId.value);
|
|
|
|
|
void refreshSessions(ownerSessionId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function completeTrace(traceId: string, result: AgentChatResultResponse): void {
|
|
|
|
|
const authoritySessionId = traceResultSessionId(result);
|
|
|
|
|
if (!shouldApplyActiveTraceAuthority(traceId, authoritySessionId)) return;
|
|
|
|
|
recordActivity(`trace:terminal:${firstNonEmptyString(result.lastEventLabel, result.status, "completed") ?? "completed"}`);
|
|
|
|
|
const terminalStatus = result.status === "completed" ? "completed" : statusFromResult(result.status);
|
|
|
|
|
const finalText = finalResponseText(result.finalResponse);
|
|
|
|
|
const errorText = agentErrorDisplayText(result.error);
|
|
|
|
|
updateActiveMessages((source) => source.map((message) => {
|
|
|
|
|
if (!shouldApplyTraceToMessage(message, traceId, authoritySessionId)) return message;
|
|
|
|
|
const ownerSessionId = traceOwnerSessionId(traceId, authoritySessionId);
|
|
|
|
|
if (!ownerSessionId) return;
|
|
|
|
|
if (ownerSessionId === activeSessionId.value) recordActivity(`trace:terminal:${firstNonEmptyString(result.lastEventLabel, result.status, "completed") ?? "completed"}`);
|
|
|
|
|
updateSessionMessages(ownerSessionId, (source) => source.map((message) => {
|
|
|
|
|
if (!messageMatchesTraceAuthority(message, traceId, authoritySessionId, ownerSessionId)) return message;
|
|
|
|
|
const runnerTrace = mergeTerminalResultTrace(message.runnerTrace, result);
|
|
|
|
|
rememberTraceAuthority(runnerTrace);
|
|
|
|
|
const error = normalizeAgentError(result.error ?? runnerTrace?.error ?? message.error);
|
|
|
|
|
const projection = projectionFromResult(result) ?? runnerTrace.projection ?? message.projection ?? null;
|
|
|
|
|
const agentRun = agentRunFromResult(result, runnerTrace) ?? agentRunFromMessage(message);
|
|
|
|
|
const text = projectedAgentMessageText({ status: terminalStatus, finalText, errorText, baseText: message.text });
|
|
|
|
|
return { ...message, status: terminalStatus, text, traceAutoLifecycle: "terminal", runnerTrace, error: error ?? message.error ?? null, projection, projectionStatus: projection?.projectionStatus ?? null, projectionHealth: projection?.projectionHealth ?? null, blocker: projection?.blocker ?? null, agentRun: agentRun ?? undefined, updatedAt: new Date().toISOString() };
|
|
|
|
|
return { ...message, runnerTrace, error: error ?? message.error ?? null, projection, projectionStatus: projection?.projectionStatus ?? null, projectionHealth: projection?.projectionHealth ?? null, blocker: projection?.blocker ?? null, agentRun: agentRun ?? undefined, updatedAt: new Date().toISOString() };
|
|
|
|
|
}));
|
|
|
|
|
rememberTurnStatus(traceId, result);
|
|
|
|
|
markWorkbenchTraceProjected(traceId);
|
|
|
|
|
clearActiveTurnGapRefresh(traceId);
|
|
|
|
|
void hydrateTraceEvents(messages.value);
|
|
|
|
|
chatPending.value = false;
|
|
|
|
|
currentRequest.value = null;
|
|
|
|
|
void clearActiveTrace(traceId, "trace-terminal");
|
|
|
|
|
void refreshSessions(result.sessionId ?? activeSessionId.value);
|
|
|
|
|
restartRealtime("trace-terminal");
|
|
|
|
|
void refreshMessageProjectionForTrace(ownerSessionId, traceId);
|
|
|
|
|
void hydrateTraceEvents(serverState.value.messagesBySessionId[ownerSessionId] ?? []);
|
|
|
|
|
if (ownerSessionId === activeSessionId.value) {
|
|
|
|
|
chatPending.value = false;
|
|
|
|
|
currentRequest.value = null;
|
|
|
|
|
void clearActiveTrace(traceId, "trace-terminal");
|
|
|
|
|
restartRealtime("trace-terminal");
|
|
|
|
|
}
|
|
|
|
|
void refreshSessions(ownerSessionId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function hydrateTerminalMessageDiagnostics(): Promise<void> {
|
|
|
|
@@ -1008,19 +1058,19 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
|
|
|
|
|
|
|
|
|
function applyTerminalResultDiagnostics(traceId: string, result: AgentChatResultResponse): void {
|
|
|
|
|
const authoritySessionId = traceResultSessionId(result);
|
|
|
|
|
if (!shouldApplyActiveTraceAuthority(traceId, authoritySessionId)) return;
|
|
|
|
|
updateActiveMessages((source) => source.map((message) => {
|
|
|
|
|
if (!shouldApplyTraceToMessage(message, traceId, authoritySessionId)) return message;
|
|
|
|
|
const ownerSessionId = traceOwnerSessionId(traceId, authoritySessionId);
|
|
|
|
|
if (!ownerSessionId) return;
|
|
|
|
|
updateSessionMessages(ownerSessionId, (source) => source.map((message) => {
|
|
|
|
|
if (!messageMatchesTraceAuthority(message, traceId, authoritySessionId, ownerSessionId)) return message;
|
|
|
|
|
const runnerTrace = mergeTerminalResultTrace(message.runnerTrace, result);
|
|
|
|
|
rememberTraceAuthority(runnerTrace);
|
|
|
|
|
const error = normalizeAgentError(result.error ?? runnerTrace?.error ?? message.error);
|
|
|
|
|
const agentRun = agentRunFromResult(result, runnerTrace) ?? agentRunFromMessage(message);
|
|
|
|
|
const projection = projectionFromResult(result) ?? runnerTrace.projection ?? message.projection ?? null;
|
|
|
|
|
const status = statusFromResult(result.status);
|
|
|
|
|
return { ...message, status, title: normalizeWorkbenchMessageTitle(message.role, message.title), runnerTrace, error: error ?? message.error ?? null, projection, projectionStatus: projection?.projectionStatus ?? null, projectionHealth: projection?.projectionHealth ?? null, blocker: projection?.blocker ?? null, agentRun: agentRun ?? undefined, updatedAt: new Date().toISOString() };
|
|
|
|
|
return { ...message, title: normalizeWorkbenchMessageTitle(message.role, message.title), runnerTrace, error: error ?? message.error ?? null, projection, projectionStatus: projection?.projectionStatus ?? null, projectionHealth: projection?.projectionHealth ?? null, blocker: projection?.blocker ?? null, agentRun: agentRun ?? undefined, updatedAt: new Date().toISOString() };
|
|
|
|
|
}));
|
|
|
|
|
void hydrateTraceEvents(messages.value);
|
|
|
|
|
void refreshSessions(result.sessionId ?? activeSessionId.value);
|
|
|
|
|
void hydrateTraceEvents(serverState.value.messagesBySessionId[ownerSessionId] ?? []);
|
|
|
|
|
void refreshSessions(ownerSessionId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function applyRealtimeProjectionError(event: WorkbenchRealtimeEvent): void {
|
|
|
|
@@ -1230,7 +1280,7 @@ function normalizeChatMessage(message: ChatMessage): ChatMessage {
|
|
|
|
|
const agentRun = agentRunFromMessage(message) ?? asAgentRun(runnerTrace?.agentRun);
|
|
|
|
|
const status = normalizeChatMessageStatus(message.status);
|
|
|
|
|
const baseText = firstNonEmptyString(message.text, messageText((message as Record<string, unknown>).content), messageText((message as Record<string, unknown>).message));
|
|
|
|
|
const finalText = firstNonEmptyString(finalResponseText((message as Record<string, unknown>).finalResponse), finalResponseText(runnerTrace?.finalResponse));
|
|
|
|
|
const finalText = finalResponseText((message as Record<string, unknown>).finalResponse);
|
|
|
|
|
const errorText = agentErrorDisplayText(error);
|
|
|
|
|
const text = role === "agent"
|
|
|
|
|
? isTerminalMessageStatus(status)
|
|
|
|
@@ -1271,7 +1321,6 @@ function realtimeSnapshotToTraceSnapshot(traceId: string, snapshot: WorkbenchRea
|
|
|
|
|
traceStatus: firstNonEmptyString(source.traceStatus) ?? undefined,
|
|
|
|
|
retention: source.retention,
|
|
|
|
|
terminalEvidence: source.terminalEvidence,
|
|
|
|
|
finalResponse: source.finalResponse,
|
|
|
|
|
traceSummary: source.traceSummary,
|
|
|
|
|
error: normalizeAgentError(source.error) ?? undefined,
|
|
|
|
|
projection: normalizeProjectionDiagnostic(source.projection ?? source) ?? null,
|
|
|
|
@@ -1294,19 +1343,17 @@ function mergeTerminalResultTrace(previous: ChatMessage["runnerTrace"], result:
|
|
|
|
|
const resultTrace = recordValue(result.runnerTrace);
|
|
|
|
|
const events = firstArray(result.events, result.traceEvents, resultTrace?.events, previous?.events);
|
|
|
|
|
const agentRun = asAgentRun(result.agentRun ?? resultTrace?.agentRun ?? previous?.agentRun);
|
|
|
|
|
return mergeRunnerTrace(previous, {
|
|
|
|
|
const traceDetailStatus = firstNonEmptyString(result.traceStatus, resultTrace?.traceStatus, resultTrace?.status);
|
|
|
|
|
const nextTrace = {
|
|
|
|
|
...resultTrace,
|
|
|
|
|
traceId: firstNonEmptyString(result.traceId, resultTrace?.traceId, previous?.traceId) ?? undefined,
|
|
|
|
|
status: firstNonEmptyString(result.traceStatus, resultTrace?.status, result.status, previous?.status) ?? undefined,
|
|
|
|
|
sessionId: firstNonEmptyString(result.sessionId, resultTrace?.sessionId, previous?.sessionId) ?? undefined,
|
|
|
|
|
threadId: firstNonEmptyString(result.threadId, resultTrace?.threadId, previous?.threadId) ?? undefined,
|
|
|
|
|
events,
|
|
|
|
|
eventCount: firstFiniteNumber(result.eventCount, resultTrace?.eventCount, previous?.eventCount, events.length),
|
|
|
|
|
eventsCompacted: firstBoolean(resultTrace?.eventsCompacted, previous?.eventsCompacted),
|
|
|
|
|
traceStatus: firstNonEmptyString(result.traceStatus, resultTrace?.traceStatus, previous?.traceStatus) ?? undefined,
|
|
|
|
|
retention: result.retention ?? resultTrace?.retention ?? previous?.retention,
|
|
|
|
|
terminalEvidence: result.terminalEvidence ?? resultTrace?.terminalEvidence ?? previous?.terminalEvidence,
|
|
|
|
|
finalResponse: result.finalResponse ?? resultTrace?.finalResponse ?? previous?.finalResponse,
|
|
|
|
|
traceSummary: result.traceSummary ?? resultTrace?.traceSummary ?? previous?.traceSummary,
|
|
|
|
|
agentRun: agentRun ?? undefined,
|
|
|
|
|
error: normalizeAgentError(result.error ?? resultTrace?.error ?? previous?.error) ?? undefined,
|
|
|
|
@@ -1319,7 +1366,11 @@ function mergeTerminalResultTrace(previous: ChatMessage["runnerTrace"], result:
|
|
|
|
|
sessionMode: firstNonEmptyString(agentRun?.backendProfile, resultTrace?.sessionMode, previous?.sessionMode) ?? undefined,
|
|
|
|
|
lastEventLabel: firstNonEmptyString(result.lastEventLabel, resultTrace?.lastEventLabel, previous?.lastEventLabel, events.at(-1)?.label, events.at(-1)?.type) ?? undefined,
|
|
|
|
|
updatedAt: new Date().toISOString()
|
|
|
|
|
} as NonNullable<ChatMessage["runnerTrace"]>);
|
|
|
|
|
} as NonNullable<ChatMessage["runnerTrace"]>;
|
|
|
|
|
if (traceDetailStatus) nextTrace.status = traceDetailStatus;
|
|
|
|
|
const traceStatus = firstNonEmptyString(result.traceStatus, resultTrace?.traceStatus);
|
|
|
|
|
if (traceStatus) nextTrace.traceStatus = traceStatus;
|
|
|
|
|
return mergeRunnerTrace(previous, nextTrace);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function normalizeMessageRunnerTrace(message: ChatMessage): ChatMessage["runnerTrace"] {
|
|
|
|
@@ -1442,7 +1493,7 @@ function traceEventHasTerminalEvidence(event: unknown): boolean {
|
|
|
|
|
function traceSnapshotHasTerminalEvidence(snapshot: unknown): boolean {
|
|
|
|
|
const record = recordValue(snapshot);
|
|
|
|
|
if (!record) return false;
|
|
|
|
|
if (record.terminal === true || record.terminalEvidence || record.finalResponse) return true;
|
|
|
|
|
if (record.terminal === true || record.terminalEvidence) return true;
|
|
|
|
|
const events = Array.isArray(record.events) ? record.events : [];
|
|
|
|
|
return events.some((event) => traceEventHasTerminalEvidence(event));
|
|
|
|
|
}
|
|
|
|
@@ -1532,16 +1583,6 @@ function makeMessage(role: ChatMessage["role"], text: string, status: ChatMessag
|
|
|
|
|
return { id: nextProtocolId("msg"), role, title: extra.title ?? (role === "user" ? "用户" : "Code Agent"), text, status, createdAt: new Date().toISOString(), ...extra };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function statusFromResult(status: string | undefined): ChatMessage["status"] {
|
|
|
|
|
if (isTraceActiveStatus(status)) return "running";
|
|
|
|
|
if (status === "blocked") return "blocked";
|
|
|
|
|
if (status === "timeout") return "timeout";
|
|
|
|
|
if (status === "canceled" || status === "cancelled") return "canceled";
|
|
|
|
|
if (status === "completed") return "completed";
|
|
|
|
|
if (!status || status === "unknown") return "pending";
|
|
|
|
|
return "failed";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isTraceActiveStatus(status: unknown): boolean {
|
|
|
|
|
return ["accepted", "pending", "queued", "dispatching", "streaming", "processing", "running", "retrying", "busy", "creating"].includes(String(status ?? "").trim().toLowerCase().replace(/_/gu, "-"));
|
|
|
|
|
}
|
|
|
|
|