fix(v02): load compact conversation traces (#856)
Co-authored-by: Codex Agent <codex@hwlab.local>
This commit is contained in:
@@ -166,7 +166,7 @@ export function App(): ReactElement {
|
||||
/>
|
||||
) : null}
|
||||
<section className="center-workspace">
|
||||
{route === "workspace" ? <ConversationPanel messages={store.state.messages} availability={store.state.codeAgentAvailability} live={store.state.live} chatPending={store.state.chatPending} codeAgentTimeoutMs={store.state.codeAgentTimeoutMs} onCopySession={() => copyTextToClipboard(firstNonEmptyString(store.composer.sessionId, store.state.workspace?.selectedAgentSessionId, store.state.workspace?.workspace?.selectedAgentSessionId, store.activeConversationId) ?? "")} onCancelMessage={(id) => void store.cancelAgentMessage(id)} onRetryMessage={(id) => void store.retryAgentMessage(id)} /> : null}
|
||||
{route === "workspace" ? <ConversationPanel messages={store.state.messages} availability={store.state.codeAgentAvailability} live={store.state.live} chatPending={store.state.chatPending} codeAgentTimeoutMs={store.state.codeAgentTimeoutMs} onCopySession={() => copyTextToClipboard(firstNonEmptyString(store.composer.sessionId, store.state.workspace?.selectedAgentSessionId, store.state.workspace?.workspace?.selectedAgentSessionId, store.activeConversationId) ?? "")} onCancelMessage={(id) => void store.cancelAgentMessage(id)} onRetryMessage={(id) => void store.retryAgentMessage(id)} onReplayTrace={(id) => void store.replayAgentTrace(id)} /> : null}
|
||||
{route === "skills" ? <SkillsView /> : null}
|
||||
{route === "gate" ? <GateView /> : null}
|
||||
{route === "help" ? <HelpView help={help} /> : null}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { test } from "bun:test";
|
||||
|
||||
import { shouldAutoReplayTrace } from "./ConversationPanel";
|
||||
import type { ChatMessage } from "../../types/domain";
|
||||
|
||||
const baseAgentMessage: ChatMessage = {
|
||||
id: "msg_agent",
|
||||
role: "agent",
|
||||
title: "Code Agent 回复",
|
||||
text: "done",
|
||||
status: "idle" as ChatMessage["status"],
|
||||
createdAt: "2026-06-04T12:13:54.661Z",
|
||||
traceId: "trc_issue849",
|
||||
runnerTrace: {
|
||||
traceId: "trc_issue849",
|
||||
status: "completed",
|
||||
eventCount: 49,
|
||||
eventsCompacted: false,
|
||||
fullTraceLoaded: false
|
||||
}
|
||||
};
|
||||
|
||||
test("conversation panel auto replays compact historical traces", () => {
|
||||
assert.equal(shouldAutoReplayTrace(baseAgentMessage), true);
|
||||
});
|
||||
|
||||
test("conversation panel does not auto replay loaded, empty, or running traces", () => {
|
||||
assert.equal(shouldAutoReplayTrace({ ...baseAgentMessage, runnerTrace: { ...baseAgentMessage.runnerTrace, events: [{ seq: 1, label: "assistant:message" }] } }), false);
|
||||
assert.equal(shouldAutoReplayTrace({ ...baseAgentMessage, runnerTrace: { ...baseAgentMessage.runnerTrace, eventCount: 0 } }), false);
|
||||
assert.equal(shouldAutoReplayTrace({ ...baseAgentMessage, status: "running" }), false);
|
||||
assert.equal(shouldAutoReplayTrace({ ...baseAgentMessage, traceReplayStatus: "完整 trace 已回放:49 个原始 event" }), false);
|
||||
assert.equal(shouldAutoReplayTrace({ ...baseAgentMessage, runnerTrace: { ...baseAgentMessage.runnerTrace, fullTraceLoaded: true } }), false);
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { ReactElement } from "react";
|
||||
import { useMemo } from "react";
|
||||
import { useEffect, useMemo, useRef } from "react";
|
||||
|
||||
import { useScrollFollow } from "../../hooks/useScrollFollow";
|
||||
import type { ChatMessage, CodeAgentAvailability, LiveSurface } from "../../types/domain";
|
||||
@@ -20,9 +20,10 @@ interface ConversationPanelProps {
|
||||
onCopySession(): void;
|
||||
onCancelMessage(messageId: string): void;
|
||||
onRetryMessage(messageId: string): void;
|
||||
onReplayTrace(messageId: string): void;
|
||||
}
|
||||
|
||||
export function ConversationPanel({ messages, availability, live, chatPending, codeAgentTimeoutMs, onCopySession, onCancelMessage, onRetryMessage }: ConversationPanelProps & { codeAgentTimeoutMs: number }): ReactElement {
|
||||
export function ConversationPanel({ messages, availability, live, chatPending, codeAgentTimeoutMs, onCopySession, onCancelMessage, onRetryMessage, onReplayTrace }: ConversationPanelProps & { codeAgentTimeoutMs: number }): ReactElement {
|
||||
const scrollFollow = useScrollFollow<HTMLElement>([messages]);
|
||||
return (
|
||||
<section className="view workbench-view" id="workspace" data-view="workspace" data-scroll-follow={scrollFollow.following ? "bottom" : "detached"} aria-labelledby="workspace-title" ref={scrollFollow.ref} onScroll={scrollFollow.onScroll}>
|
||||
@@ -50,6 +51,7 @@ export function ConversationPanel({ messages, availability, live, chatPending, c
|
||||
codeAgentTimeoutMs={codeAgentTimeoutMs}
|
||||
onCancel={() => onCancelMessage(message.id)}
|
||||
onRetry={() => onRetryMessage(message.id)}
|
||||
onReplayTrace={() => onReplayTrace(message.id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -98,8 +100,28 @@ function CodeAgentSummary({ availability, live, latestMessage }: { availability:
|
||||
);
|
||||
}
|
||||
|
||||
function MessageCard({ message, codeAgentTimeoutMs, onCancel, onRetry }: { message: ChatMessage; codeAgentTimeoutMs: number; onCancel(): void; onRetry(): void }): ReactElement {
|
||||
export function shouldAutoReplayTrace(message: ChatMessage): boolean {
|
||||
if (message.role !== "agent") return false;
|
||||
const trace = message.runnerTrace;
|
||||
const traceId = message.traceId ?? trace?.traceId;
|
||||
if (!traceId) return false;
|
||||
if (message.status === "running" || trace?.status === "running") return false;
|
||||
if (message.traceReplayStatus) return false;
|
||||
if (trace?.fullTraceLoaded === true) return false;
|
||||
const events = Array.isArray(trace?.events) ? trace.events : [];
|
||||
const declaredCount = Number(trace?.eventCount ?? 0);
|
||||
return events.length === 0 && declaredCount > 0;
|
||||
}
|
||||
|
||||
function MessageCard({ message, codeAgentTimeoutMs, onCancel, onRetry, onReplayTrace }: { message: ChatMessage; codeAgentTimeoutMs: number; onCancel(): void; onRetry(): void; onReplayTrace(): void }): ReactElement {
|
||||
const traceKey = `hwlab.workbench.trace-open.${message.traceId ?? message.id}`;
|
||||
const replayRequestedRef = useRef(false);
|
||||
useEffect(() => {
|
||||
if (replayRequestedRef.current) return;
|
||||
if (!shouldAutoReplayTrace(message)) return;
|
||||
replayRequestedRef.current = true;
|
||||
onReplayTrace();
|
||||
}, [message, onReplayTrace]);
|
||||
if (message.role === "agent") {
|
||||
const hasFinalResponse = message.status !== "running" && message.text.trim().length > 0;
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user