feat: PJ2026-0104010803 P4 前端/CLI 单读源收敛 - 删除 events.at(-1) 读侧推理 (#1758)
workbench.ts (stores): - applyTraceHydrationResult: activityLabel 只读 result.lastEventLabel,不读 events.at(-1) - lastEventLabel: 只读 result.lastEventLabel,不 fallback 到 events.at(-1) - normalizeTraceSnapshot: lastEventLabel 只读 source.lastEventLabel - mergeRunnerTrace: lastEventLabel 不从 events.at(-1) 推断 useTraceSubscription.ts: - lastEventLabel 只读 snapshot.lastEventLabel,不读 events.at(-1) trace-renderer.ts (CLI): - traceNoiseSummaryRow: status 只读 trace.status/traceStatus,不 fallback 到 lastEvent.status - 保持 lastLabel 用于展示,但不参与 lifecycle 推断 api/workbench.ts: - 添加注释标记 observedAt/lastEventAgeMs 为 diagnostic-only,不作用户可见相对时间权威 Closes #1750 Refs #1742
This commit is contained in:
@@ -191,6 +191,9 @@ function normalizeTimingProjection(value: unknown): WorkbenchTurnTimingProjectio
|
||||
const lastEventAt = textValue(source.lastEventAt);
|
||||
const finishedAt = textValue(source.finishedAt);
|
||||
const durationMs = numberValue(source.durationMs);
|
||||
// observedAt and lastEventAgeMs are server-side diagnostic snapshots.
|
||||
// They must NOT be used as the user-visible relative time authority.
|
||||
// Running relative time display must use startedAt/lastEventAt + browser local now().
|
||||
const observedAt = textValue(source.observedAt);
|
||||
const lastEventAgeMs = numberValue(source.lastEventAgeMs);
|
||||
if (!startedAt && !lastEventAt && !finishedAt && durationMs == null && lastEventAgeMs == null) return undefined;
|
||||
|
||||
@@ -89,7 +89,7 @@ export function snapshotToRunnerTrace(snapshot: TraceSnapshot): NonNullable<Chat
|
||||
eventSource: snapshot.eventSource ?? "trace-api",
|
||||
runnerKind: snapshot.agentRun?.adapter,
|
||||
sessionMode: snapshot.agentRun?.backendProfile,
|
||||
lastEventLabel: snapshot.lastEventLabel ?? events.at(-1)?.label ?? events.at(-1)?.type,
|
||||
lastEventLabel: snapshot.lastEventLabel ?? undefined,
|
||||
waitingFor: snapshot.waitingFor,
|
||||
timing: snapshot.timing ?? null,
|
||||
startedAt: snapshot.startedAt ?? snapshot.timing?.startedAt ?? null,
|
||||
|
||||
@@ -673,7 +673,7 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
||||
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);
|
||||
const activityLabel = firstNonEmptyString(result.lastEventLabel, result.status);
|
||||
if (ownerSessionId === activeSessionId.value && (events.length > 0 || result.terminal === true || isTerminalMessageStatus(result.status))) recordActivity(`trace:${activityLabel ?? "hydrated"}`);
|
||||
markWorkbenchTraceEventsReceived({ traceId, events, transport: "rest_gap" });
|
||||
updateSessionMessages(ownerSessionId, (source) => source.map((message) => {
|
||||
@@ -706,7 +706,7 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
||||
staleMs: result.staleMs ?? result.projection?.staleMs,
|
||||
blocker: result.blocker ?? result.projection?.blocker,
|
||||
...messageTimingPatch(result),
|
||||
lastEventLabel: result.lastEventLabel ?? events.at(-1)?.label ?? events.at(-1)?.type,
|
||||
lastEventLabel: result.lastEventLabel ?? undefined,
|
||||
updatedAt: new Date().toISOString()
|
||||
} as NonNullable<ChatMessage["runnerTrace"]>;
|
||||
const runnerTrace = mergeRunnerTrace(message.runnerTrace, nextTrace);
|
||||
@@ -1340,7 +1340,7 @@ function realtimeSnapshotToTraceSnapshot(traceId: string, snapshot: WorkbenchRea
|
||||
staleMs: firstFiniteNumber(source.staleMs, recordValue(source.projection)?.staleMs),
|
||||
blocker: recordValue(source.blocker) ?? recordValue(recordValue(source.projection)?.blocker) ?? undefined,
|
||||
...messageTimingPatch(source),
|
||||
lastEventLabel: firstNonEmptyString(source.lastEventLabel, source.lastEvent?.label, source.lastEvent?.type, events.at(-1)?.label, events.at(-1)?.type) ?? undefined,
|
||||
lastEventLabel: firstNonEmptyString(source.lastEventLabel) ?? undefined,
|
||||
eventSource: "trace-api",
|
||||
updatedAt: firstNonEmptyString(source.updatedAt) ?? new Date().toISOString()
|
||||
} as TraceSnapshot;
|
||||
@@ -1396,7 +1396,7 @@ function mergeTerminalResultTrace(previous: ChatMessage["runnerTrace"], result:
|
||||
durationMs: timing?.durationMs ?? null,
|
||||
runnerKind: firstNonEmptyString(agentRun?.adapter, resultTrace?.runnerKind, previous?.runnerKind) ?? undefined,
|
||||
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,
|
||||
lastEventLabel: firstNonEmptyString(result.lastEventLabel, resultTrace?.lastEventLabel, previous?.lastEventLabel) ?? undefined,
|
||||
updatedAt: new Date().toISOString()
|
||||
} as NonNullable<ChatMessage["runnerTrace"]>;
|
||||
if (traceDetailStatus) nextTrace.status = traceDetailStatus;
|
||||
|
||||
Reference in New Issue
Block a user