diff --git a/tools/src/hwlab-cli/trace-renderer.ts b/tools/src/hwlab-cli/trace-renderer.ts index ce92154e..2504944b 100644 --- a/tools/src/hwlab-cli/trace-renderer.ts +++ b/tools/src/hwlab-cli/trace-renderer.ts @@ -121,12 +121,12 @@ function traceEventsForDisplay(events: TraceEvent[] = []): TraceEvent[] { } function traceEventDisplaySeq(event: TraceEvent | null | undefined): number | null { - return numberOrNull(event?.projectedSeq) ?? numberOrNull(event?.seq); + return numberOrNull(event?.projectedSeq); } function traceEventIdentityToken(event: TraceEvent): string | number | null { const seq = traceEventDisplaySeq(event); - return seq ?? nonEmptyString(event.projectedSeq ?? event.seq); + return seq ?? nonEmptyString(event.projectedSeq); } function isToolTraceRow(row: TraceEventRow): boolean { diff --git a/web/hwlab-cloud-web/src/composables/useTraceSubscription.ts b/web/hwlab-cloud-web/src/composables/useTraceSubscription.ts index ab439b05..90af4616 100644 --- a/web/hwlab-cloud-web/src/composables/useTraceSubscription.ts +++ b/web/hwlab-cloud-web/src/composables/useTraceSubscription.ts @@ -218,14 +218,17 @@ function traceEventSortSeq(event: TraceEvent): number { function traceEventIdentity(event: TraceEvent): string | null { const label = typeof event.label === "string" ? event.label : typeof event.type === "string" ? event.type : ""; - const seqValue = event.projectedSeq ?? event.seq; + const seqValue = event.projectedSeq; const seq = typeof seqValue === "number" || typeof seqValue === "string" ? String(seqValue) : ""; if (seq) return `projected:${seq}:${label || "event"}`; + const source = typeof event.source === "string" ? event.source : ""; + const sourceSeq = typeof event.sourceSeq === "number" || typeof event.sourceSeq === "string" ? String(event.sourceSeq) : ""; + if (source && sourceSeq) return `source:${source}:${sourceSeq}:${label || "event"}`; return null; } function traceEventProjectedSeq(event: TraceEvent): number { - const seq = Number(event.projectedSeq ?? event.seq); + const seq = Number(event.projectedSeq); return Number.isFinite(seq) ? Math.trunc(seq) : Number.NaN; } diff --git a/web/hwlab-cloud-web/src/stores/workbench.ts b/web/hwlab-cloud-web/src/stores/workbench.ts index 0fbb983b..112ff0e5 100644 --- a/web/hwlab-cloud-web/src/stores/workbench.ts +++ b/web/hwlab-cloud-web/src/stores/workbench.ts @@ -832,23 +832,19 @@ function nonBlockingProjection(projection: ProjectionDiagnostic | null): Project async function cancelAgentMessage(message: ChatMessage): Promise { const traceId = message.traceId ?? message.runnerTrace?.traceId; if (!traceId) return; + const sessionId = message.sessionId ?? selectedSessionId.value; const response = await api.agent.cancelAgentMessage({ traceId, sessionId: message.sessionId ?? selectedSessionId.value, threadId: message.threadId ?? selectedThreadId.value }); - const payload = response.data as AgentChatResultResponse | null; - const status = normalizedStatusText(payload?.status) ?? "canceled"; - const terminal = payload?.terminal === true || isTerminalMessageStatus(status); - if (response.ok && payload) { - applyTurnStatusSnapshot(traceId, payload); - if (terminal) completeTrace(traceId, payload); - } else { - applyTurnStatusSnapshot(traceId, { traceId, status: "canceled", running: false, terminal: true, sessionId: message.sessionId ?? selectedSessionId.value ?? undefined, threadId: message.threadId ?? selectedThreadId.value ?? undefined } as AgentChatResultResponse); - } - if (status === "canceled" || status === "cancelled") { - markMessage(traceId, { status: "canceled", text: "用户已取消该 turn。" }); - void clearActiveTrace(traceId, "cancel-agent-message"); + if (!response.ok) { + applyProjectionDiagnostic(traceId, projectionDiagnosticFromApiFailure(response, { code: "code_agent_cancel_failed", message: response.error ?? "Code Agent 取消请求失败,当前 turn 状态保持 canonical projection。", health: response.status === 0 ? "unavailable" : "degraded" })); + return; } + recordActivity("cancel:accepted"); if (message.status === "running") chatPending.value = false; currentRequest.value = null; - scheduleSessionListRefresh(message.sessionId ?? selectedSessionId.value, SESSION_LIST_TERMINAL_REFRESH_DELAY_MS); + void clearActiveTrace(traceId, "cancel-agent-message"); + void refreshMessageProjectionForTrace(sessionId, traceId); + if (sessionId) void hydrateTraceEvents(serverState.value.messagesBySessionId[sessionId] ?? messages.value); + scheduleSessionListRefresh(sessionId, SESSION_LIST_TERMINAL_REFRESH_DELAY_MS); } async function cancelRunningTrace(): Promise { @@ -2231,7 +2227,7 @@ function traceNextProjectedSeq(result: AgentChatResultResponse, fallback: number } function traceEventProjectedSeq(event: TraceEvent): number { - const seq = Number(event.projectedSeq ?? event.seq); + const seq = Number(event.projectedSeq); return Number.isFinite(seq) ? Math.trunc(seq) : Number.NaN; }