// SPEC: PJ2026-010403 API契约 draft-2026-06-18-r1; PJ2026-010401 Web工作台 draft-2026-06-18-r1; PJ2026-0104010803 唯一投影 draft-2026-06-18-p0-unique-projection; PJ2026-01060505 Workbench Performance draft-2026-06-17-p0.
// Responsibility: Session-first Workbench state orchestration for selection, turn admission, and trace lifecycle rendering.

import { computed, nextTick, ref } from "vue";
import { defineStore } from "pinia";
import { api } from "@/api";
import { connectWorkbenchEvents, type WorkbenchEventStream, type WorkbenchRealtimeEvent } from "@/api/workbench-events";
import { mergeRunnerTrace, snapshotToRunnerTrace, type TraceSnapshot } from "@/composables/useTraceSubscription";
import type { AgentChatResponse, AgentChatResultResponse, AgentRunProvenance, ApiError, ApiResult, ChatMessage, ErrorDiagnostic, LiveSurface, ProjectionBlocker, ProjectionDiagnostic, ProviderProfile, TraceEvent, WorkbenchSessionRecord, WorkbenchTurnTimingProjection } from "@/types";
import { firstNonEmptyString, nextProtocolId, normalizeWorkbenchSessionId, normalizeWorkbenchSessionRouteId } from "@/utils";
import { failWorkbenchSessionSwitch, failWorkbenchSubmitJourney, finishWorkbenchSessionSwitchFullLoad, markWorkbenchSubmitApiAccepted, markWorkbenchTraceEventsReceived, markWorkbenchTraceProjected, recordWorkbenchLoadingState, startWorkbenchSessionSwitch, startWorkbenchSubmitJourney } from "@/utils/workbench-performance";
import { RECENT_DRAFTS_STORAGE_KEY, appendSessionPage, defaultProviderProfileOptions, isArchivedSession, mergeSessionIntoList, normalizeChatMessageStatus, normalizeRecentDrafts, normalizeWorkbenchMessageTitle, providerProfileOptionsFromPayload, recordRecentDraft, resolveCancelableAgentMessage, resolveComposerState, shouldShowSessionListLoading, sortSessionTabs, stableSessionList, type DraftEntry, type ProviderProfileOption, type TurnStatusAuthority } from "./workbench-session";
import { initialWorkbenchSessionIdFromLocation } from "./workbench-projection";
import { createWorkbenchServerState, reduceWorkbenchServerState, selectActiveMessages, selectActiveSession, selectSessionList, selectSessionStatusAuthority, selectTraceAuthorityById, selectTurnStatusAuthority, type WorkbenchServerAction } from "./workbench-server-state";

const DEFAULT_CODE_AGENT_TIMEOUT_MS = 1_800_000;
const DEFAULT_GATEWAY_TIMEOUT_MS = 120_000;
const TRACE_HYDRATION_PAGE_LIMIT = 100;
const TRACE_HYDRATION_MAX_PAGES = 60;
const TRACE_HYDRATION_MAX_ATTEMPTS = 3;
const TRACE_HYDRATION_RETRY_DELAY_MS = 700;
const TRACE_HYDRATION_AUTO_QUEUE_LIMIT = 12;
const TRACE_HYDRATION_BACKGROUND_CONCURRENCY = 2;
const TRACE_HYDRATION_BACKGROUND_DELAY_MS = 150;
const SESSION_LIST_PAGE_LIMIT = 20;
const WORKBENCH_READ_HYDRATION_CONCURRENCY = 3;
const WORKBENCH_READ_FAILURE_COOLDOWN_MS = 5_000;
const WORKBENCH_TURN_STATUS_MIN_REFRESH_MS = 2_000;
const WORKBENCH_TRACE_EVENTS_MIN_REFRESH_MS = 4_000;
const WORKBENCH_SESSION_MESSAGES_MIN_REFRESH_MS = 5_000;
const WORKBENCH_TRACE_EVENTS_TIMEOUT_MS = 5_000;
const SESSION_LIST_REALTIME_REFRESH_DELAY_MS = 5_000;
const SESSION_LIST_TERMINAL_REFRESH_DELAY_MS = 1_500;
const SESSION_LIST_MIN_REFRESH_INTERVAL_MS = 15_000;

interface HydrateOptions {
  sessionId?: string | null;
  invalidRouteId?: string | null;
}

type SessionSelectionSource = "route" | "user" | "system";

interface SelectSessionOptions {
  source?: SessionSelectionSource;
}

export const useWorkbenchStore = defineStore("workbench", () => {
  const providerProfile = ref<ProviderProfile>(readString("hwlab.workbench.providerProfile.v1", "codex"));
  const providerOptions = ref<ProviderProfileOption[]>(defaultProviderProfileOptions(providerProfile.value));
  const recentDrafts = ref<DraftEntry[]>(readRecentDrafts());
  const codeAgentTimeoutMs = ref(readNumber("hwlab.workbench.codeAgentTimeoutMs.v1", DEFAULT_CODE_AGENT_TIMEOUT_MS));
  const gatewayShellTimeoutMs = ref(readNumber("hwlab.workbench.gatewayShellTimeoutMs.v1", DEFAULT_GATEWAY_TIMEOUT_MS));
  const live = ref<LiveSurface | null>(null);
  const loading = ref(false);
  const sessionsReady = ref(false);
  const switchingSessionId = ref<string | null>(null);
  const sessionDetailLoadingId = ref<string | null>(null);
  const sessionListHasMore = ref(false);
  const sessionListNextCursor = ref<string | null>(null);
  const sessionListLoadingMore = ref(false);
  const sessionListLoadMoreError = ref<string | null>(null);
  const chatPending = ref(false);
  const error = ref<string | null>(null);
  const activityRef = ref({ lastActivityAt: Date.now(), lastActivityIso: new Date().toISOString(), waitingFor: "idle", lastEventLabel: null as string | null });
  const currentRequest = ref<{ traceId: string; sessionId: string | null; threadId: string | null; status?: string | null } | null>(null);
  const explicitSessionId = ref<string | null>(initialWorkbenchSessionIdFromLocation());
  const activeSelectionSource = ref<SessionSelectionSource>(explicitSessionId.value ? "route" : "system");
  const selectionEpoch = ref(0);
  const serverState = ref(createWorkbenchServerState());
  const sessions = computed(() => selectSessionList(serverState.value));
  const sessionStatusAuthority = computed(() => selectSessionStatusAuthority(serverState.value));
  const turnStatusAuthority = computed(() => selectTurnStatusAuthority(serverState.value));
  const traceAuthorityById = computed(() => selectTraceAuthorityById(serverState.value));
  const traceHydrationInFlight = new Set<string>();
  const traceHydrationQueued = new Set<string>();
  const traceHydrationQueue: ChatMessage[] = [];
  let traceHydrationPumpActive = false;
  const terminalRealtimeRefreshInFlight = new Set<string>();
  const realtimeSessionRefreshInFlight = new Set<string>();
  let realtimeStream: WorkbenchEventStream | null = null;
  let realtimeKey = "";
  const sessionListRefreshInFlight = new Map<string, Promise<void>>();
  const sessionListRefreshTimers = new Map<string, number>();
  const sessionListLastRefreshAtByKey = new Map<string, number>();
  let workbenchReadHydrationActive = 0;
  const workbenchReadHydrationQueue: Array<() => void> = [];
  const workbenchReadCooldownUntilByKey = new Map<string, number>();
  const workbenchReadLastStartedAtByKey = new Map<string, number>();

  const projectedActiveSession = computed(() => selectActiveSession(serverState.value, explicitSessionId.value));
  const routeActiveSession = computed(() => routeSelectedSessionRecord(explicitSessionId.value, sessionDetailLoadingId.value, error.value, projectedActiveSession.value));
  const activeSession = computed(() => projectedActiveSession.value ?? routeActiveSession.value);
  const activeSessionSelectionSource = computed(() => activeSelectionSource.value);
  const selectedSessionId = computed(() => firstNonEmptyString(activeSession.value?.sessionId, explicitSessionId.value));
  const activeSessionId = computed(() => selectedSessionId.value);
  const activeMessages = computed(() => selectActiveMessages(serverState.value, activeSessionId.value));
  const messages = activeMessages;
  const selectedThreadId = computed(() => firstNonEmptyString(activeSession.value?.threadId));
  const sessionTabs = computed(() => sortSessionTabs(routeActiveSession.value ? mergeSessionIntoList(sessions.value, routeActiveSession.value) : sessions.value, activeSessionId.value, sessionStatusAuthority.value));
  const sessionListLoadedCount = computed(() => sessionTabs.value.length);
  const sessionListLoading = computed(() => shouldShowSessionListLoading({ loading: loading.value, sessionsReady: sessionsReady.value }));
  const sessionDetailLoading = computed(() => Boolean(sessionDetailLoadingId.value || switchingSessionId.value || (loading.value && messages.value.length === 0)));
  const composer = computed(() => resolveComposerState({ messages: messages.value, sessions: sessions.value, activeSessionId: activeSessionId.value, chatPending: chatPending.value, currentRequest: currentRequest.value, turnStatusAuthority: turnStatusAuthority.value }));

  function recordActivity(label = "user-activity"): void {
    const now = Date.now();
    activityRef.value = { lastActivityAt: now, lastActivityIso: new Date(now).toISOString(), waitingFor: "code-agent", lastEventLabel: label };
  }

  async function hydrate(options: HydrateOptions = {}): Promise<void> {
    const routeRequestId = normalizeWorkbenchSessionRouteId(options.sessionId);
    const routeSessionId = normalizeWorkbenchSessionId(options.sessionId);
    const requestEpoch = beginSessionSelection();
    sessionsReady.value = sessions.value.length > 0;
    const includeSessionId = routeSessionId ?? activeSessionId.value;
    loading.value = true;
    recordWorkbenchLoadingState({ scope: "workbench", active: true, reason: "hydrate", sessionId: includeSessionId });
    recordWorkbenchLoadingState({ scope: "session_list", active: true, reason: "hydrate", sessionId: includeSessionId });
    error.value = null;
    void refreshProviderOptions();
    if (routeSessionId) {
      sessionDetailLoadingId.value = routeSessionId;
      recordWorkbenchLoadingState({ scope: "session_detail", active: true, reason: "hydrate", sessionId: routeSessionId });
      setActiveSessionSelection(routeSessionId, "route");
    }
    const sessionsResult = await api.workbench.sessions({ includeSessionId, limit: SESSION_LIST_PAGE_LIMIT });
    const listedSessions = sessionsResult.ok ? workbenchSessionsFromPayload(sessionsResult.data) : [];
    if (sessionsResult.ok) {
      applySessionPagination(sessionsResult.data);
    } else {
      error.value = sessionsResult.error ?? "session list unavailable";
    }
    const targetSessionId = routeRequestId ?? includeSessionId ?? activeSessionId.value ?? listedSessions[0]?.sessionId ?? null;
    if (!sessionsResult.ok && !targetSessionId) {
      clearSessionDetailLoading(includeSessionId);
      loading.value = false;
      recordWorkbenchLoadingState({ scope: "session_detail", active: false, reason: "hydrate", sessionId: includeSessionId });
      recordWorkbenchLoadingState({ scope: "session_list", active: false, reason: "hydrate", sessionId: includeSessionId });
      recordWorkbenchLoadingState({ scope: "workbench", active: false, reason: "hydrate", sessionId: includeSessionId });
      return;
    }
    if (targetSessionId && (routeSessionId || !routeRequestId)) {
      setActiveSessionSelection(targetSessionId, routeSessionId ? "route" : "system");
    }
    if (targetSessionId) {
      sessionDetailLoadingId.value = targetSessionId;
      recordWorkbenchLoadingState({ scope: "session_detail", active: true, reason: "hydrate", sessionId: targetSessionId });
    }
    const selected = targetSessionId ? await loadWorkbenchSession(targetSessionId, listedSessions.find((item) => item.sessionId === targetSessionId) ?? null) : null;
    if (selected && !isArchivedSession(selected) && routeRequestId && !routeSessionId && requestEpoch === selectionEpoch.value) setActiveSessionSelection(selected.sessionId, "route");
    const selectedIsCurrent = Boolean(selected && !isArchivedSession(selected) && isCurrentSessionSelection(requestEpoch, selected.sessionId));
    if (sessionsResult.ok || (selectedIsCurrent && selected)) error.value = null;
    if (selectedIsCurrent && selected) applySelectedSessionDetail(selected, "route");
    if (selected && !isArchivedSession(selected) && routeRequestId && !isCurrentSessionSelection(requestEpoch, selected.sessionId) && selected.sessionId !== activeSessionId.value) rememberSessionDetail(selected);
    if (selected && isArchivedSession(selected)) applyCanonicalSessionArchive(selected.sessionId, "session archived");
    if (options.invalidRouteId) markSessionReadUnavailable(options.invalidRouteId, "invalid session URL");
    if (targetSessionId && !selected) markSessionReadUnavailable(targetSessionId, "session detail unavailable");
    const stableSelected = selectedIsCurrent && selected ? selected : activeSessionListSeed();
    const nextSessions = stableSessionList(sessions.value, listedSessions, stableSelected?.sessionId ?? selected?.sessionId ?? routeSessionId ?? includeSessionId, stableSelected);
    rememberSessionList(nextSessions);
    sessionsReady.value = sessionsResult.ok || nextSessions.length > 0;
    void refreshSessionStatusAuthority(nextSessions);
    clearSessionDetailLoading(targetSessionId);
    loading.value = false;
    recordWorkbenchLoadingState({ scope: "session_detail", active: false, reason: "hydrate", sessionId: targetSessionId });
    recordWorkbenchLoadingState({ scope: "session_list", active: false, reason: "hydrate", sessionId: includeSessionId });
    recordWorkbenchLoadingState({ scope: "workbench", active: false, reason: "hydrate", sessionId: targetSessionId ?? includeSessionId });
    await refreshProviderOptions();
    restartRealtime("hydrate");
  }

  async function refreshLive(): Promise<void> {
    const [healthLive, health, restIndex, hwpodSpecs, hwpodNodeOps, liveBuilds] = await Promise.all([
      liveCall("health/live", api.healthLive),
      liveCall("health", api.health),
      liveCall("REST index", api.restIndex),
      liveCall("hwpod specs", api.hwpod.specs),
      liveCall("hwpod node ops", api.hwpod.nodeOpsHealth),
      liveCall("live builds", api.liveBuilds)
    ]);
    live.value = { healthLive, health, restIndex, hwpodSpecs, hwpodNodeOps, liveBuilds, loadedAt: new Date().toISOString() };
  }

  async function createSession(): Promise<void> {
    beginSessionSelection();
    loading.value = true;
    recordWorkbenchLoadingState({ scope: "workbench", active: true, reason: "create_session", sessionId: activeSessionId.value });
    error.value = null;
    void refreshProviderOptions();
    const response = await api.agent.createAgentSession({ providerProfile: providerProfile.value });
    loading.value = false;
    recordWorkbenchLoadingState({ scope: "workbench", active: false, reason: "create_session", sessionId: activeSessionId.value });
    const created = response.ok ? sessionFromWorkbenchSession(response.data?.session) : null;
    if (!response.ok || !created) {
      error.value = response.error ?? "session create failed";
      return;
    }
    rememberSessionList(mergeSessionIntoList(sessions.value, created));
    sessionsReady.value = true;
    setActiveSessionSelection(created.sessionId, "user");
    await selectSession(created);
  }

  async function selectSession(session: WorkbenchSessionRecord, options: SelectSessionOptions = {}): Promise<void> {
    await selectSessionById(session.sessionId, session, options);
  }

  async function selectSessionById(sessionId: string, seed: WorkbenchSessionRecord | null = null, options: SelectSessionOptions = {}): Promise<boolean> {
    const source = options.source ?? "user";
    const requestId = normalizeWorkbenchSessionRouteId(sessionId);
    const normalized = normalizeWorkbenchSessionId(sessionId);
    if (!requestId) {
      error.value = "invalid session URL";
      return false;
    }
    const existing = seed ?? sessions.value.find((session) => session.sessionId === normalized) ?? null;
    const requestEpoch = beginSessionSelection();
    startWorkbenchSessionSwitch({ sessionId: normalized ?? requestId, source: existing ? "rail" : "deeplink", targetState: existing?.status ?? "unknown", cache: (existing?.messages?.length ?? 0) > 0 ? "warm" : "cold" });
    switchingSessionId.value = requestId;
    sessionDetailLoadingId.value = requestId;
    loading.value = true;
    recordWorkbenchLoadingState({ scope: "workbench", active: true, reason: "select_session", sessionId: normalized ?? requestId });
    recordWorkbenchLoadingState({ scope: "session_detail", active: true, reason: "select_session", sessionId: normalized ?? requestId });
    if (normalized) setActiveSessionSelection(normalized, source);
    if (existing) {
      rememberSessionList(mergeSessionIntoList(sessions.value, existing));
      sessionsReady.value = true;
    }
    const selected = await loadWorkbenchSession(requestId, existing);
    if (selected && !isArchivedSession(selected) && !normalized && requestEpoch === selectionEpoch.value) setActiveSessionSelection(selected.sessionId, source);
    if (!isCurrentSessionRequest(requestEpoch, requestId, selected?.sessionId ?? normalized)) {
      clearSessionDetailLoading(requestId);
      clearSwitchingSession(requestId);
      recordWorkbenchLoadingState({ scope: "session_detail", active: false, reason: "select_session", sessionId: normalized ?? requestId });
      recordWorkbenchLoadingState({ scope: "workbench", active: false, reason: "select_session", sessionId: normalized ?? requestId });
      return true;
    }
    if (selected && !isArchivedSession(selected)) {
      applySelectedSessionDetail(selected, source);
      await nextTick();
      loading.value = false;
      clearSessionDetailLoading(requestId);
      clearSwitchingSession(requestId);
      recordWorkbenchLoadingState({ scope: "session_detail", active: false, reason: "select_session", sessionId: selected.sessionId });
      recordWorkbenchLoadingState({ scope: "workbench", active: false, reason: "select_session", sessionId: selected.sessionId });
      error.value = null;
      await refreshSessions(selected.sessionId, { force: true });
      finishWorkbenchSessionSwitchFullLoad(selected.sessionId, "ok");
      return activeSessionId.value === selected.sessionId;
    }
    loading.value = false;
    clearSessionDetailLoading(requestId);
    clearSwitchingSession(requestId);
    recordWorkbenchLoadingState({ scope: "session_detail", active: false, reason: "select_session", sessionId: normalized ?? requestId });
    recordWorkbenchLoadingState({ scope: "workbench", active: false, reason: "select_session", sessionId: normalized ?? requestId });
    if (selected && isArchivedSession(selected)) applyCanonicalSessionArchive(selected.sessionId, "session archived");
    else markSessionReadUnavailable(requestId, "session detail unavailable");
    failWorkbenchSessionSwitch(normalized ?? requestId);
    return false;
  }

  async function deleteCurrentSession(): Promise<void> {
    const sessionId = activeSessionId.value;
    if (!sessionId) return;
    error.value = null;
    const response = await api.agent.deleteAgentSession(sessionId);
    if (!response.ok) {
      error.value = response.error ?? "session delete failed";
      return;
    }
    forgetSession(sessionId);
    await refreshSessions(null, { force: true });
    const next = sessions.value.find((session) => !isArchivedSession(session)) ?? null;
    replaceActiveSessionSelection(next?.sessionId ?? null);
    currentRequest.value = null;
    if (next) {
      await selectSession(next, { source: "system" });
      return;
    }
    restartRealtime("delete-current-session");
  }

  async function refreshSessions(includeSessionId: string | null = activeSessionId.value, options: { force?: boolean } = {}): Promise<void> {
    const requestIncludeSessionId = firstNonEmptyString(includeSessionId);
    const requestLimit = currentSessionListLimit();
    const requestKey = sessionListRefreshKey(requestIncludeSessionId, requestLimit);
    const existing = sessionListRefreshInFlight.get(requestKey);
    if (existing) return existing;
    const now = Date.now();
    const lastRefreshAt = sessionListLastRefreshAtByKey.get(requestKey) ?? 0;
    if (options.force !== true && lastRefreshAt > 0 && now - lastRefreshAt < SESSION_LIST_MIN_REFRESH_INTERVAL_MS) return;
    sessionListLastRefreshAtByKey.set(requestKey, now);
    const refresh = refreshSessionsNow(requestIncludeSessionId, requestLimit).finally(() => {
      if (sessionListRefreshInFlight.get(requestKey) === refresh) sessionListRefreshInFlight.delete(requestKey);
    });
    sessionListRefreshInFlight.set(requestKey, refresh);
    return refresh;
  }

  async function refreshSessionsNow(includeSessionId: string | null, limit: number): Promise<void> {
    const response = await api.workbench.sessions({ includeSessionId, limit });
    if (response.ok) {
      const listed = workbenchSessionsFromPayload(response.data);
      const activeSeed = activeSessionListSeed();
      const refreshed = stableSessionList(sessions.value, listed, activeSeed?.sessionId ?? includeSessionId, activeSeed);
      const next = sessions.value.length > refreshed.length ? appendSessionPage(sessions.value, refreshed) : refreshed;
      rememberSessionList(next);
      applySessionPagination(response.data);
      sessionsReady.value = true;
      await refreshSessionStatusAuthority(next);
      return;
    }
    sessionsReady.value = sessions.value.length > 0;
    if (sessions.value.length === 0) error.value = response.error ?? "session list unavailable";
  }

  function scheduleSessionListRefresh(includeSessionId: string | null | undefined = activeSessionId.value, delayMs = SESSION_LIST_REALTIME_REFRESH_DELAY_MS): void {
    const requestIncludeSessionId = firstNonEmptyString(includeSessionId);
    const requestLimit = currentSessionListLimit();
    const requestKey = sessionListRefreshKey(requestIncludeSessionId, requestLimit);
    if (typeof window === "undefined") {
      void refreshSessions(requestIncludeSessionId);
      return;
    }
    const existing = sessionListRefreshTimers.get(requestKey);
    if (existing) window.clearTimeout(existing);
    const lastRefreshAt = sessionListLastRefreshAtByKey.get(requestKey) ?? 0;
    const cooldownMs = lastRefreshAt > 0 ? Math.max(0, SESSION_LIST_MIN_REFRESH_INTERVAL_MS - (Date.now() - lastRefreshAt)) : 0;
    const boundedDelayMs = Math.max(0, Math.trunc(delayMs), cooldownMs);
    const timer = window.setTimeout(() => {
      sessionListRefreshTimers.delete(requestKey);
      void refreshSessions(requestIncludeSessionId);
    }, boundedDelayMs);
    sessionListRefreshTimers.set(requestKey, timer);
  }

  function sessionListRefreshKey(includeSessionId: string | null | undefined, limit: number): string {
    return `${firstNonEmptyString(includeSessionId) ?? ""}|${limit}`;
  }

  async function loadMoreSessions(): Promise<void> {
    if (sessionListLoadingMore.value || !sessionListHasMore.value) return;
    const cursor = sessionListNextCursor.value;
    if (!cursor) {
      sessionListHasMore.value = false;
      return;
    }
    sessionListLoadingMore.value = true;
    sessionListLoadMoreError.value = null;
    const response = await api.workbench.sessions({ includeSessionId: activeSessionId.value, limit: SESSION_LIST_PAGE_LIMIT, cursor });
    sessionListLoadingMore.value = false;
    if (!response.ok) {
      sessionListLoadMoreError.value = response.error ?? "session list load more failed";
      return;
    }
    const next = appendSessionPage(sessions.value, workbenchSessionsFromPayload(response.data));
    rememberSessionList(next);
    applySessionPagination(response.data);
    sessionsReady.value = true;
    await refreshSessionStatusAuthority(next);
  }

  function currentSessionListLimit(): number {
    return Math.max(SESSION_LIST_PAGE_LIMIT, sessionListLoadedCount.value || 0);
  }

  function applySessionPagination(payload: unknown): void {
    const record = recordValue(payload);
    sessionListHasMore.value = record?.hasMore === true;
    sessionListNextCursor.value = sessionListHasMore.value ? firstNonEmptyString(record?.nextCursor) : null;
    if (!sessionListHasMore.value) sessionListLoadMoreError.value = null;
  }

  function reduceServerState(action: WorkbenchServerAction): void {
    serverState.value = reduceWorkbenchServerState(serverState.value, action);
  }

  function rememberSessionDetail(session: WorkbenchSessionRecord | null | undefined): void {
    reduceServerState({ type: "session.detail", session });
  }

  function rememberSessionList(source: WorkbenchSessionRecord[]): void {
    reduceServerState({ type: "session.list", sessions: source });
  }

  function forgetSession(sessionId: string): void {
    reduceServerState({ type: "session.forget", sessionId });
  }

  function rememberSessionMessages(sessionId: string | null | undefined, source: ChatMessage[]): void {
    reduceServerState({ type: "session.messages", sessionId, messages: source });
  }

  function replaceActiveMessages(source: ChatMessage[]): void {
    rememberSessionMessages(activeSessionId.value, source);
  }

  function updateActiveMessages(project: (source: ChatMessage[]) => ChatMessage[]): void {
    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]);
  }

  function setActiveSessionSelection(sessionId: string | null | undefined, source: SessionSelectionSource = "system"): void {
    const normalized = normalizeWorkbenchSessionId(sessionId);
    if (!normalized) return;
    explicitSessionId.value = normalized;
    activeSelectionSource.value = source;
  }

  function replaceActiveSessionSelection(sessionId: string | null | undefined, source: SessionSelectionSource = "system"): void {
    explicitSessionId.value = normalizeWorkbenchSessionId(sessionId);
    activeSelectionSource.value = source;
  }

  function activeSessionListSeed(): WorkbenchSessionRecord | null {
    return projectedActiveSession.value ?? null;
  }

  async function refreshSessionStatusAuthority(source: WorkbenchSessionRecord[] = sessions.value): Promise<void> {
    void source;
  }

  function runWorkbenchReadHydration<T>(task: () => Promise<T>, cooldownKey?: string, options: { minIntervalMs?: number } = {}): Promise<T> {
    return new Promise<T>((resolve, reject) => {
      const run = () => {
        if (cooldownKey) {
          const cooldownUntilMs = workbenchReadCooldownUntilByKey.get(cooldownKey) ?? 0;
          if (cooldownUntilMs > Date.now()) {
            resolve(createWorkbenchReadCooldownResult(cooldownKey, cooldownUntilMs) as T);
            const next = workbenchReadHydrationQueue.shift();
            if (next) next();
            return;
          }
          if (cooldownUntilMs > 0) workbenchReadCooldownUntilByKey.delete(cooldownKey);
          const minIntervalMs = Math.max(0, Math.trunc(options.minIntervalMs ?? 0));
          const lastStartedAtMs = workbenchReadLastStartedAtByKey.get(cooldownKey) ?? 0;
          if (minIntervalMs > 0 && lastStartedAtMs > 0 && Date.now() - lastStartedAtMs < minIntervalMs) {
            resolve(createWorkbenchReadThrottledResult(cooldownKey, lastStartedAtMs + minIntervalMs) as T);
            const next = workbenchReadHydrationQueue.shift();
            if (next) next();
            return;
          }
          if (minIntervalMs > 0) workbenchReadLastStartedAtByKey.set(cooldownKey, Date.now());
        }
        workbenchReadHydrationActive += 1;
        Promise.resolve()
          .then(task)
          .then(
            (value) => {
              if (cooldownKey && isApiResultLike(value) && shouldCooldownWorkbenchReadFailure(value)) {
                workbenchReadCooldownUntilByKey.set(cooldownKey, Date.now() + WORKBENCH_READ_FAILURE_COOLDOWN_MS);
              }
              resolve(value);
            },
            reject,
          )
          .finally(() => {
            workbenchReadHydrationActive = Math.max(0, workbenchReadHydrationActive - 1);
            const next = workbenchReadHydrationQueue.shift();
            if (next) next();
          });
      };
      if (workbenchReadHydrationActive < WORKBENCH_READ_HYDRATION_CONCURRENCY) {
        run();
        return;
      }
      workbenchReadHydrationQueue.push(run);
    });
  }

  function workbenchReadCooldownKey(kind: string, id: string): string {
    return `${kind}:${id}`;
  }

  function createWorkbenchReadCooldownResult(key: string, cooldownUntilMs: number): ApiResult<unknown> {
    const retryAfterMs = Math.max(0, cooldownUntilMs - Date.now());
    const message = `Workbench read hydration is cooling down after a transient failure (${key}); retry after ${retryAfterMs}ms.`;
    const diagnostic = {
      code: "workbench_read_hydration_cooldown",
      category: "network",
      source: "workbench-web",
      message,
      retryable: true,
    };
    return {
      ok: false,
      status: 503,
      error: message,
      apiError: {
        code: "workbench_read_hydration_cooldown",
        category: "network",
        source: "workbench-web",
        message,
        retryable: true,
        diagnostic,
      },
      diagnostic,
    } as ApiResult<unknown>;
  }

  function createWorkbenchReadThrottledResult(key: string, nextAllowedAtMs: number): ApiResult<unknown> {
    const retryAfterMs = Math.max(0, nextAllowedAtMs - Date.now());
    const message = `Workbench read hydration is throttled (${key}); retry after ${retryAfterMs}ms.`;
    const diagnostic = {
      code: "workbench_read_hydration_throttled",
      category: "throttle",
      source: "workbench-web",
      message,
      retryable: true,
    };
    return {
      ok: false,
      status: 0,
      error: message,
      apiError: {
        code: "workbench_read_hydration_throttled",
        category: "throttle",
        source: "workbench-web",
        message,
        retryable: true,
        diagnostic,
      },
      diagnostic,
    } as ApiResult<unknown>;
  }

  function isApiResultLike(value: unknown): value is ApiResult<unknown> {
    return Boolean(value && typeof value === "object" && "ok" in value && "status" in value);
  }

  function shouldCooldownWorkbenchReadFailure(result: ApiResult<unknown>): boolean {
    const diagnostic = normalizeErrorDiagnostic(result.diagnostic, result.apiError?.diagnostic);
    const code = firstStringOrNumber(result.apiError?.code, diagnostic?.code);
    const category = firstNonEmptyString(result.apiError?.category, diagnostic?.category);
    const source = firstNonEmptyString(result.apiError?.source, diagnostic?.source);
    if (result.status === 0 && source === "browser" && code === "browser_network_error" && category === "network") return true;
    if (result.status === 503) return code === "projection_store_unavailable" || code === "workbench_read_model_store_unavailable";
    return false;
  }

  function fetchWorkbenchTurnStatus(traceId: string, useActivityTimeout = shouldUseActivityTimeoutForTrace(traceId)): Promise<ApiResult<AgentChatResultResponse>> {
    const activitySource = useActivityTimeout ? () => activityRef.value : null;
    return runWorkbenchReadHydration(
      () => api.workbench.turn(traceId, 8000, activitySource),
      workbenchReadCooldownKey("turn", traceId),
      { minIntervalMs: WORKBENCH_TURN_STATUS_MIN_REFRESH_MS },
    );
  }

  function fetchWorkbenchTraceEvents(traceId: string, afterProjectedSeq: number, useActivityTimeout = shouldUseActivityTimeoutForTrace(traceId)): Promise<ApiResult<AgentChatResultResponse>> {
    const activitySource = useActivityTimeout ? () => activityRef.value : null;
    return runWorkbenchReadHydration(
      () => api.workbench.traceEvents(traceId, WORKBENCH_TRACE_EVENTS_TIMEOUT_MS, activitySource, { afterProjectedSeq, limit: TRACE_HYDRATION_PAGE_LIMIT }),
      workbenchReadCooldownKey("trace-events", traceId),
      { minIntervalMs: WORKBENCH_TRACE_EVENTS_MIN_REFRESH_MS },
    );
  }

  function shouldUseActivityTimeoutForTrace(traceId: string | null | undefined): boolean {
    const id = firstNonEmptyString(traceId);
    if (!id) return false;
    if (currentRequest.value?.traceId === id) return true;
    const turn = turnStatusAuthority.value[id];
    if (turn?.terminal === true || isTerminalMessageStatus(turn?.status)) return false;
    const message = [...messages.value].reverse().find((item) => firstNonEmptyString(item.traceId, item.runnerTrace?.traceId) === id) ?? null;
    if (isTerminalMessageStatus(message?.status) || isTerminalMessageStatus(message?.runnerTrace?.status)) return false;
    return isTraceActiveStatus(turn?.status) || isTraceActiveStatus(message?.status) || isTraceActiveStatus(message?.runnerTrace?.status);
  }

  async function refreshSessionMessageProjectionPage(sessionId: string | null | undefined): Promise<void> {
    const id = normalizeWorkbenchSessionId(sessionId);
    if (!id) return;
    const response = await runWorkbenchReadHydration(
      () => api.workbench.sessionMessages(id, { limit: 100 }),
      workbenchReadCooldownKey("session-messages", id),
      { minIntervalMs: WORKBENCH_SESSION_MESSAGES_MIN_REFRESH_MS },
    );
    if (!response.ok || !response.data) return;
    const pageMessages = Array.isArray(response.data.messages) ? response.data.messages.map((message) => normalizeChatMessage(message as ChatMessage)) : [];
    rememberSessionMessages(id, mergeMessageProjectionPage(id, pageMessages));
  }

  async function refreshMessageProjectionForTrace(sessionId: string | null | undefined, traceId: string): Promise<void> {
    const id = normalizeWorkbenchSessionId(sessionId);
    if (!id) return;
    const response = await runWorkbenchReadHydration(
      () => api.workbench.sessionMessages(id, { limit: 100 }),
      workbenchReadCooldownKey("session-messages", id),
      { minIntervalMs: WORKBENCH_SESSION_MESSAGES_MIN_REFRESH_MS },
    );
    if (!response.ok || !response.data) {
      if (shouldSuppressTransientWorkbenchReadFailure(response)) return;
      if (traceHasCompletedFinalResponse(traceId, messages.value)) return;
      applyProjectionDiagnostic(traceId, projectionDiagnosticFromApiFailure(response, { 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, ...messageTimingPatch(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(activeTurnStatusRefreshTraceIds(source).map((traceId) => refreshTurnStatusByTraceId(traceId)));
  }

  function activeTurnStatusRefreshTraceIds(source: ChatMessage[] = messages.value): string[] {
    const traceIds = new Set<string>();
    const requestTraceId = firstNonEmptyString(currentRequest.value?.traceId);
    if (requestTraceId && traceNeedsTurnStatusRefresh(requestTraceId, source)) traceIds.add(requestTraceId);
    for (const message of [...source].reverse()) {
      const traceId = firstNonEmptyString(message.traceId, message.runnerTrace?.traceId);
      if (!traceId || traceIds.has(traceId)) continue;
      if (!messageHasActiveTurnStatusEvidence(message, traceId)) continue;
      if (traceNeedsTurnStatusRefresh(traceId, source)) traceIds.add(traceId);
      if (traceIds.size >= 3) break;
    }
    return [...traceIds];
  }

  function messageHasActiveTurnStatusEvidence(message: ChatMessage, traceId: string): boolean {
    if (currentRequest.value?.traceId === traceId) return true;
    return isTraceActiveStatus(message.status) || isTraceActiveStatus(message.runnerTrace?.status);
  }

  function traceNeedsTurnStatusRefresh(traceId: string | null | undefined, source: ChatMessage[] = messages.value): boolean {
    const id = firstNonEmptyString(traceId);
    if (!id) return false;
    const turn = turnStatusAuthority.value[id];
    if (turn?.terminal === true || isTerminalMessageStatus(turn?.status)) return false;
    const message = [...source].reverse().find((item) => firstNonEmptyString(item.traceId, item.runnerTrace?.traceId) === id) ?? null;
    if (isTerminalMessageStatus(message?.status) || isTerminalMessageStatus(message?.runnerTrace?.status)) return false;
    if (message?.runnerTrace && traceSnapshotHasTerminalEvidence(message.runnerTrace)) return true;
    return true;
  }

  async function refreshTurnStatusByTraceId(traceId: string | null | undefined): Promise<void> {
    const id = firstNonEmptyString(traceId);
    if (!id) return;
    const response = await fetchWorkbenchTurnStatus(id);
    if (response.ok && response.data) {
      applyTurnStatusSnapshot(id, response.data);
      if (response.data.terminal === true || isTerminalMessageStatus(response.data.status)) completeTrace(id, response.data);
      return;
    }
    if (shouldSuppressTransientWorkbenchReadFailure(response)) return;
    if (traceHasCompletedFinalResponse(id, messages.value)) return;
    applyProjectionDiagnostic(id, projectionDiagnosticFromApiFailure(response, { code: "turn_status_poll_failed", message: response.error ?? "状态更新失败，当前 turn 状态暂不可见。", health: response.status === 0 ? "unavailable" : "degraded" }));
  }

  function applyTurnStatusSnapshot(traceId: string, result: AgentChatResultResponse | TraceSnapshot): void {
    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);
  }

  function rememberTurnStatus(traceId: string, result: AgentChatResultResponse | TraceSnapshot): void {
    const id = firstNonEmptyString(result.traceId, traceId);
    if (!id) return;
    const status = normalizedStatusText(result.status) ?? null;
    const running = (result as AgentChatResultResponse).running === true || isTraceActiveStatus(status);
    const terminal = (result as AgentChatResultResponse).terminal === true || isTerminalMessageStatus(status);
    reduceServerState({
      type: "turn.status",
      turn: {
        traceId: id,
        status,
        running,
        terminal,
        sessionId: firstNonEmptyString(result.sessionId) ?? null,
        threadId: firstNonEmptyString(result.threadId) ?? null,
        updatedAt: firstNonEmptyString((result as AgentChatResultResponse).updatedAt) ?? null,
        loadedAt: new Date().toISOString()
      }
    });
  }

  function syncTurnStatusToMessage(traceId: string, result: AgentChatResultResponse | TraceSnapshot): void {
    const authoritySessionId = traceResultSessionId(result);
    updateTraceMessages(traceId, authoritySessionId, (message) => {
      const resultStatus = normalizedStatusText((result as AgentChatResultResponse).status) ?? null;
      const terminal = (result as AgentChatResultResponse).terminal === true || isTerminalMessageStatus(resultStatus);
      const resultError = normalizeAgentError((result as AgentChatResultResponse).error ?? null);
      const resultProjection = projectionFromResult(result as AgentChatResultResponse);
      const mergedRunnerTrace = mergeTerminalResultTrace(message.runnerTrace, result as AgentChatResultResponse);
      const clearCompletedDiagnostics = shouldClearCompletedTurnDiagnostics(resultStatus, resultError);
      const runnerTrace = clearCompletedDiagnostics ? clearRunnerTraceTransientDiagnostics(mergedRunnerTrace) : mergedRunnerTrace;
      rememberTraceAuthority(runnerTrace);
      const error = resultError ?? (terminal || clearCompletedDiagnostics ? null : normalizeAgentError(runnerTrace?.error ?? message.error));
      const agentRun = agentRunFromResult(result as AgentChatResultResponse, runnerTrace) ?? agentRunFromMessage(message);
      const projection = clearCompletedDiagnostics ? nonBlockingProjection(resultProjection) : resultProjection ?? (terminal ? null : runnerTrace.projection ?? message.projection ?? null);
      return { ...message, ...messageTimingPatchForMerge(message, result), runnerTrace, error, projection, projectionStatus: projection?.projectionStatus ?? null, projectionHealth: projection?.projectionHealth ?? null, blocker: projection?.blocker ?? null, agentRun: agentRun ?? undefined, updatedAt: new Date().toISOString() };
    });
  }

  function clearRunnerTraceTransientDiagnostics(trace: NonNullable<ChatMessage["runnerTrace"]>): NonNullable<ChatMessage["runnerTrace"]> {
    return { ...trace, error: undefined, projection: null, projectionStatus: null, projectionHealth: null, staleMs: null, blocker: null };
  }

function shouldClearCompletedTurnDiagnostics(status: unknown, error: ChatMessage["error"] | null): boolean {
  return normalizedStatusText(status) === "completed" && !error;
}

function nonBlockingProjection(projection: ProjectionDiagnostic | null): ProjectionDiagnostic | null {
  if (!projection) return null;
  const status = firstNonEmptyString(projection.projectionStatus);
  const health = firstNonEmptyString(projection.projectionHealth);
  if (status === "caught-up" || health === "healthy") return projection;
  if (!projection.blocker && !projection.apiError && !projection.diagnostic) return projection;
  return null;
}

  async function submitMessage(text: string): Promise<boolean> {
    const value = text.trim();
    if (!value) return false;
    beginSessionSelection();
    if (!composer.value.sessionId) {
      error.value = "session_required：请先显式新建或选择 Code Agent session。";
      return false;
    }
    const steerMode = composer.value.submitMode === "steer";
    const submitEntry = steerMode ? "steer" : "existing";
    const traceId = steerMode && composer.value.targetTraceId ? composer.value.targetTraceId : nextProtocolId("trc");
    const steerTraceId = steerMode ? nextProtocolId("trc_steer") : null;
    const sessionId = composer.value.sessionId;
    const threadId = composer.value.threadId;
    const providerThreadId = providerThreadIdForRequest(threadId);
    const submittedAt = new Date().toISOString();
    const user = makeMessage("user", value, "sent", { traceId: steerTraceId ?? traceId, sessionId, threadId, title: steerMode ? "用户引导" : "用户", createdAt: submittedAt, updatedAt: submittedAt });
    const pending = makeMessage("agent", "", "running", { traceId, sessionId, threadId, title: "Code Agent", retryInput: value, traceAutoLifecycle: "running", createdAt: submittedAt, updatedAt: submittedAt, ...optimisticRunningTimingPatch(submittedAt) });
    appendActiveMessages(user, pending);
    startWorkbenchSubmitJourney({ traceId, sessionId, entry: submitEntry, backend: providerProfile.value, transport: "sse" });
    chatPending.value = true;
    rememberRecentDraft(value);
    recordActivity("submit");
    const payload = { message: value, prompt: value, sessionId, threadId: providerThreadId, traceId, providerProfile: providerProfile.value, gatewayShellTimeoutMs: gatewayShellTimeoutMs.value, targetTraceId: composer.value.targetTraceId, steerTraceId };
    const route = steerMode ? api.agent.steerAgentMessage : api.agent.sendAgentMessage;
    const response = await route(payload, codeAgentTimeoutMs.value, () => activityRef.value);
    if (!response.ok || !response.data) {
      const projection = projectionDiagnosticFromApiFailure(response, { code: "code_agent_admission_failed", message: response.error ?? "Code Agent 请求失败", health: response.status === 0 ? "unavailable" : "degraded" });
      const failureError = agentErrorFromApiFailure(response, projection, "Code Agent 请求失败");
      const failureMessage = agentErrorDisplayText(failureError) ?? "Code Agent 请求失败";
      markMessage(traceId, { status: "failed", text: failureMessage, error: failureError, projection, projectionStatus: projection.projectionStatus ?? null, projectionHealth: projection.projectionHealth ?? null, blocker: projection.blocker ?? null });
      applyProjectionDiagnostic(traceId, projection);
      reduceServerState({ type: "turn.forget", traceId });
      failWorkbenchSubmitJourney(traceId, response.status === 0 ? "network" : "error");
      await clearActiveTrace(traceId, steerMode && response.status === 404 ? "steer-trace-not-found" : "submit-not-admitted");
      chatPending.value = false;
      currentRequest.value = null;
      restartRealtime("submit-not-admitted");
      return false;
    }
    markWorkbenchSubmitApiAccepted(traceId);
    const canonicalTraceId = alignOptimisticTurnMessages(traceId, response.data);
    if (canonicalTraceId !== traceId) {
      reduceServerState({ type: "turn.forget", traceId });
    }
    currentRequest.value = {
      traceId: canonicalTraceId,
      sessionId: firstNonEmptyString(response.data.sessionId, sessionId),
      threadId: firstNonEmptyString(response.data.threadId, threadId),
      status: response.data.status ?? "running"
    };
    applyTurnStatusSnapshot(canonicalTraceId, response.data);
    scheduleSessionListRefresh(sessionId, SESSION_LIST_TERMINAL_REFRESH_DELAY_MS);
    if ((response.data as AgentChatResultResponse).terminal === true || isTerminalMessageStatus(response.data.status)) {
      completeTrace(canonicalTraceId, response.data as AgentChatResultResponse);
      return true;
    }
    restartRealtime("submit");
    return true;
  }

  async function cancelAgentMessage(message: ChatMessage): Promise<void> {
    const traceId = message.traceId ?? message.runnerTrace?.traceId;
    if (!traceId) return;
    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 (message.status === "running") chatPending.value = false;
    currentRequest.value = null;
    scheduleSessionListRefresh(message.sessionId ?? selectedSessionId.value, SESSION_LIST_TERMINAL_REFRESH_DELAY_MS);
  }

  async function cancelRunningTrace(): Promise<void> {
    const target = composer.value;
    const message = resolveCancelableAgentMessage({
      messages: messages.value,
      targetTraceId: target.targetTraceId,
      targetSessionId: target.sessionId,
      targetThreadId: target.threadId,
      turnStatusAuthority: turnStatusAuthority.value
    });
    if (message) await cancelAgentMessage(message);
  }

  async function retryAgentMessage(message: ChatMessage): Promise<void> {
    const retryInput = firstNonEmptyString(message.retryInput, messages.value.find((item) => item.role === "user" && item.traceId === message.traceId)?.text);
    if (!retryInput) return;
    await submitMessage(retryInput);
  }

  async function hydrateTraceEventsForMessage(message: ChatMessage): Promise<void> {
    const traceId = message.traceId ?? message.runnerTrace?.traceId;
    if (!traceId) return;
    if (traceHydrationInFlight.has(traceId)) return;
    traceHydrationInFlight.add(traceId);
    try {
      let afterProjectedSeq = traceHydrationProjectedSeq(message.runnerTrace);
      for (let page = 0; page < TRACE_HYDRATION_MAX_PAGES; page += 1) {
        const result = await fetchTraceHydrationPage(traceId, afterProjectedSeq);
        if (!result.ok || !result.data) {
          if (shouldSuppressTransientWorkbenchReadFailure(result)) return;
          if (messageHasCompletedFinalResponse(message)) return;
          applyProjectionDiagnostic(traceId, projectionDiagnosticFromApiFailure(result, { code: "trace_hydration_failed", message: result.error ?? "Trace 更新超时，运行记录暂不可见。", health: result.status === 0 ? "unavailable" : "degraded" }));
          return;
        }
        applyTraceHydrationResult(traceId, result.data);
        const nextProjectedSeq = traceNextProjectedSeq(result.data, afterProjectedSeq);
        if (result.data.hasMore !== true || nextProjectedSeq <= afterProjectedSeq) return;
        afterProjectedSeq = nextProjectedSeq;
      }
    } finally {
      traceHydrationInFlight.delete(traceId);
    }
  }

  async function fetchTraceHydrationPage(traceId: string, afterProjectedSeq: number): Promise<ApiResult<AgentChatResultResponse>> {
    let lastResult: ApiResult<AgentChatResultResponse> | null = null;
    for (let attempt = 0; attempt < TRACE_HYDRATION_MAX_ATTEMPTS; attempt += 1) {
      const result = await fetchWorkbenchTraceEvents(traceId, afterProjectedSeq);
      if (result.ok && result.data) return result;
      lastResult = result;
      if (attempt < TRACE_HYDRATION_MAX_ATTEMPTS - 1) await delayTraceHydrationRetry(TRACE_HYDRATION_RETRY_DELAY_MS * (attempt + 1));
    }
    return lastResult ?? { ok: false, status: 0, data: null, error: "trace_hydration_failed" };
  }

  function delayTraceHydrationRetry(ms: number): Promise<void> {
    return new Promise((resolve) => window.setTimeout(resolve, ms));
  }

  async function hydrateTraceEvents(source: ChatMessage[] = messages.value): Promise<void> {
    for (const message of traceHydrationCandidates(source)) queueTraceHydration(message);
    void pumpTraceHydrationQueue();
  }

  function traceHydrationCandidates(source: ChatMessage[]): ChatMessage[] {
    return source.filter(messageNeedsTraceHydration).slice(-TRACE_HYDRATION_AUTO_QUEUE_LIMIT).reverse();
  }

  function queueTraceHydration(message: ChatMessage): void {
    const traceId = message.traceId ?? message.runnerTrace?.traceId;
    if (!traceId) return;
    if (traceHydrationInFlight.has(traceId) || traceHydrationQueued.has(traceId)) return;
    traceHydrationQueued.add(traceId);
    traceHydrationQueue.push(message);
  }

  async function pumpTraceHydrationQueue(): Promise<void> {
    if (traceHydrationPumpActive) return;
    traceHydrationPumpActive = true;
    try {
      while (traceHydrationQueue.length > 0) {
        const batch = traceHydrationQueue.splice(0, TRACE_HYDRATION_BACKGROUND_CONCURRENCY);
        await Promise.all(batch.map(async (message) => {
          const traceId = message.traceId ?? message.runnerTrace?.traceId;
          try {
            await hydrateTraceEventsForMessage(message);
          } finally {
            if (traceId) traceHydrationQueued.delete(traceId);
          }
        }));
        if (traceHydrationQueue.length > 0) await delayTraceHydrationRetry(TRACE_HYDRATION_BACKGROUND_DELAY_MS);
      }
    } finally {
      traceHydrationPumpActive = false;
    }
  }

  function messagesWithTraceAuthority(source: ChatMessage[]): ChatMessage[] {
    return source.map((message) => {
      if (message.role !== "agent") return message;
      const traceId = firstNonEmptyString(message.traceId, message.runnerTrace?.traceId);
      const authority = traceId ? traceAuthorityById.value[traceId] ?? null : null;
      if (!authority) return message;
      const runnerTrace = mergeRunnerTrace(message.runnerTrace, authority);
      return { ...message, ...messageTimingPatchForMerge(message, runnerTrace), runnerTrace };
    });
  }

  function rememberTraceAuthority(trace: ChatMessage["runnerTrace"]): void {
    const traceId = firstNonEmptyString(trace?.traceId);
    if (!traceId || !trace) return;
    const existing = traceAuthorityById.value[traceId] ?? null;
    if (trace.eventSource !== "trace-api" && !traceHasEvents(trace) && !existing) return;
    const nextTrace = mergeRunnerTrace(existing, trace as NonNullable<ChatMessage["runnerTrace"]>);
    reduceServerState({ type: "trace.snapshot", traceId, trace: nextTrace });
  }

  function applyTraceHydrationResult(traceId: string, result: AgentChatResultResponse): void {
    const authoritySessionId = traceResultSessionId(result);
    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, 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) => {
      if (!messageMatchesTraceAuthority(message, traceId, authoritySessionId, ownerSessionId)) return message;
      const displayStatus = firstNonEmptyString(message.status, message.runnerTrace?.status, result.status, result.runnerTrace?.status);
      const { traceId: _resultTraceId, sessionId: _resultSessionId, threadId: _resultThreadId, ...resultTraceRest } = result.runnerTrace ?? {};
      const nextTrace = {
        ...resultTraceRest,
        traceId: optionalString(result.traceId, traceId),
        status: displayStatus ?? undefined,
        sessionId: optionalString(authoritySessionId, message.runnerTrace?.sessionId),
        threadId: optionalString(result.threadId, message.runnerTrace?.threadId),
        events,
        eventSource: "trace-api",
        eventCount: result.eventCount ?? events.length,
        eventsCompacted: result.runnerTrace?.eventsCompacted ?? false,
        fullTraceLoaded: result.fullTraceLoaded === true || result.hasMore === false,
        hasMore: result.hasMore,
        truncated: result.truncated,
        nextProjectedSeq: typeof result.nextProjectedSeq === "number" ? result.nextProjectedSeq : null,
        range: result.range,
        traceStatus: result.traceStatus,
        retention: result.retention,
        terminalEvidence: result.terminalEvidence,
        traceSummary: result.traceSummary,
        agentRun: result.agentRun,
        projection: projectionFromResult(result),
        projectionStatus: result.projectionStatus ?? result.projection?.projectionStatus,
        projectionHealth: result.projectionHealth ?? result.projection?.projectionHealth,
        staleMs: result.staleMs ?? result.projection?.staleMs,
        blocker: result.blocker ?? result.projection?.blocker,
        ...messageTimingPatch(result),
        lastEventLabel: result.lastEventLabel ?? undefined,
        updatedAt: new Date().toISOString()
      } as NonNullable<ChatMessage["runnerTrace"]>;
      const resultStatus = normalizedStatusText(result.status) ?? null;
      const terminal = result.terminal === true || isTerminalMessageStatus(resultStatus) || traceResultHasTerminalEvidence(result);
      const resultError = normalizeAgentError(result.error ?? null);
      const resultProjection = projectionFromResult(result);
      const mergedRunnerTrace = mergeRunnerTrace(message.runnerTrace, nextTrace);
      const clearCompletedDiagnostics = shouldClearCompletedTurnDiagnostics(resultStatus, resultError);
      const runnerTrace = clearCompletedDiagnostics ? clearRunnerTraceTransientDiagnostics(mergedRunnerTrace) : mergedRunnerTrace;
      const error = resultError ?? (terminal || clearCompletedDiagnostics ? null : normalizeAgentError(runnerTrace?.error ?? message.error));
      const projection = clearCompletedDiagnostics ? nonBlockingProjection(resultProjection) : resultProjection ?? (terminal ? null : runnerTrace.projection ?? message.projection ?? null);
      const agentRun = agentRunFromResult(result, runnerTrace) ?? agentRunFromMessage(message);
      rememberTraceAuthority(runnerTrace);
      return { ...message, ...messageTimingPatchForMerge(message, result), runnerTrace, error, projection, projectionStatus: projection?.projectionStatus ?? null, projectionHealth: projection?.projectionHealth ?? null, blocker: projection?.blocker ?? null, agentRun: agentRun ?? undefined, updatedAt: new Date().toISOString() };
    }));
    markWorkbenchTraceProjected(traceId);
    if (traceResultHasTerminalEvidence(result) && result.terminal !== true && !isTerminalMessageStatus(result.status)) {
      void refreshTerminalTraceFromRest(traceId, "trace-hydration-terminal-evidence");
    }
    if (result.terminal === true || isTerminalMessageStatus(result.status)) {
      rememberTurnStatus(traceId, result);
      if (ownerSessionId === activeSessionId.value) {
        chatPending.value = false;
        currentRequest.value = null;
        void clearActiveTrace(traceId, "trace-hydration-terminal");
        restartRealtime("trace-hydration-terminal");
      }
    }
  }

  function setProviderProfile(value: ProviderProfile): void {
    providerProfile.value = value;
    localStorage.setItem("hwlab.workbench.providerProfile.v1", value);
  }

  async function refreshProviderOptions(): Promise<void> {
    const response = await api.providerProfiles.catalog();
    providerOptions.value = response.ok ? providerProfileOptionsFromPayload(response.data, providerProfile.value) : defaultProviderProfileOptions(providerProfile.value);
  }

  function rememberRecentDraft(text: string): void {
    recentDrafts.value = recordRecentDraft(recentDrafts.value, text);
    writeRecentDrafts(recentDrafts.value);
  }

  function pickDraft(draft: DraftEntry | string): string {
    return typeof draft === "string" ? draft : draft.text;
  }

  function clearRecentDrafts(): void {
    recentDrafts.value = [];
    try { localStorage.removeItem(RECENT_DRAFTS_STORAGE_KEY); } catch { /* ignore */ }
  }

  function clearSessionMessages(): void {
    const activeTraceId = activeTraceIdFromMessages(messages.value, turnStatusAuthority.value);
    replaceActiveMessages([]);
    currentRequest.value = null;
    if (activeTraceId) void clearActiveTrace(activeTraceId, "clear-session-messages");
    restartRealtime("clear-session-messages");
  }

  function reattachTrace(traceId: string): void {
    const initial: AgentChatResponse = { status: "running", traceId, turnUrl: `/v1/workbench/turns/${encodeURIComponent(traceId)}` };
    if (!messages.value.some((message) => message.traceId === traceId)) appendActiveMessages(makeMessage("agent", "", "running", { traceId, sessionId: selectedSessionId.value ?? undefined, threadId: selectedThreadId.value ?? undefined, title: "Code Agent", traceAutoLifecycle: "running" }));
    currentRequest.value = { traceId, sessionId: selectedSessionId.value ?? null, threadId: selectedThreadId.value ?? null, status: initial.status };
    restartRealtime("reattach");
  }

  async function validateAndReattachTrace(traceId: string): Promise<void> {
    const result = await fetchWorkbenchTurnStatus(traceId);
    if (!result.ok || !result.data) {
      await clearActiveTrace(traceId, result.status === 404 ? "reattach-result-not-found" : "reattach-result-unavailable");
      return;
    }
    applyTurnStatusSnapshot(traceId, result.data);
    if (result.data.running === true || isTraceActiveStatus(result.data.status)) {
      reattachTrace(traceId);
      return;
    }
    await clearActiveTrace(traceId, "reattach-terminal-result");
  }

  function restartRealtime(reason: string): void {
    const traceId = realtimeTraceId();
    const sessionId = selectedSessionId.value ?? null;
    const key = [sessionId ?? "", traceId ?? ""].join("|");
    if (key === realtimeKey && realtimeStream) return;
    stopRealtime();
    realtimeKey = key;
    if (!sessionId && !traceId) return;
    realtimeStream = connectWorkbenchEvents({
      sessionId,
      traceId,
      onOpen: () => undefined,
      onError: () => undefined,
      onEvent: (event, eventName) => applyRealtimeEvent(event, eventName)
    });
  }

  function stopRealtime(): void {
    realtimeStream?.close();
    realtimeStream = null;
    realtimeKey = "";
  }

  function realtimeTraceId(): string | null {
    return firstNonEmptyString(currentRequest.value?.traceId, activeTraceIdFromMessages(messages.value, turnStatusAuthority.value));
  }

  function realtimeEventSessionId(event: WorkbenchRealtimeEvent): string | null {
    const turn = recordValue(event.turn);
    const snapshot = recordValue(event.snapshot);
    const traceEvent = recordValue(event.event);
    return normalizeWorkbenchSessionId(firstNonEmptyString(event.sessionId, turn?.sessionId, snapshot?.sessionId, traceEvent?.sessionId));
  }

  function traceResultSessionId(result: AgentChatResultResponse | TraceSnapshot | Record<string, unknown> | null | undefined): string | null {
    const value = recordValue(result);
    const runnerTrace = recordValue(value?.runnerTrace);
    const session = recordValue(value?.session);
    return normalizeWorkbenchSessionId(firstNonEmptyString(value?.sessionId, runnerTrace?.sessionId, session?.sessionId));
  }

  function messageSessionAuthority(message: ChatMessage | null | undefined): string | null {
    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;
    const sessionId = normalizeWorkbenchSessionId(authoritySessionId);
    if (!activeId) return false;
    if (sessionId && sessionId !== activeId) return false;
    if (!id) return Boolean(sessionId && sessionId === activeId);
    const message = messages.value.find((item) => firstNonEmptyString(item.traceId, item.runnerTrace?.traceId) === id) ?? null;
    const messageSessionId = messageSessionAuthority(message);
    if (messageSessionId && messageSessionId !== activeId) return false;
    if (sessionId && messageSessionId && sessionId !== messageSessionId) return false;
    const knownSessionId = normalizeWorkbenchSessionId(firstNonEmptyString(turnStatusAuthority.value[id]?.sessionId, traceAuthorityById.value[id]?.sessionId));
    if (knownSessionId && knownSessionId !== activeId) return false;
    if (sessionId && knownSessionId && sessionId !== knownSessionId) return false;
    return Boolean(sessionId || messageSessionId || knownSessionId || message);
  }

  function shouldApplyTraceToMessage(message: ChatMessage, traceId: string, authoritySessionId: string | null | undefined): boolean {
    if (message.role !== "agent" || firstNonEmptyString(message.traceId, message.runnerTrace?.traceId) !== traceId) return false;
    const activeId = activeSessionId.value;
    const sessionId = normalizeWorkbenchSessionId(authoritySessionId);
    const messageSessionId = messageSessionAuthority(message);
    if (activeId && messageSessionId && messageSessionId !== activeId) return false;
    if (sessionId && activeId && sessionId !== activeId) return false;
    if (sessionId && messageSessionId && sessionId !== messageSessionId) return false;
    return true;
  }

  function applyRealtimeEvent(event: WorkbenchRealtimeEvent, eventName: string): void {
    recordActivity(event.type ? `realtime:${event.type}` : `realtime:${eventName}`);
    if (event.type === "trace.snapshot") {
      applyRealtimeTraceSnapshot(event.traceId, event.snapshot);
      return;
    }
    if (event.type === "trace.event") {
      applyRealtimeTraceEvent(event.traceId, event.event, event.snapshot, event);
      return;
    }
    if (event.type === "turn.snapshot" && event.turn) {
      applyRealtimeTurnSnapshot(event.turn);
      return;
    }
    if (eventName === "workbench.error" || event.type === "error") {
      applyRealtimeProjectionError(event);
      return;
    }
    if (event.type === "trace.unavailable") {
      const traceId = firstNonEmptyString(event.traceId);
      if (traceId) void clearActiveTrace(traceId, "realtime-trace-unavailable");
    }
  }

  function applyRealtimeTraceSnapshot(traceId: string | null | undefined, snapshot: WorkbenchRealtimeEvent["snapshot"]): void {
    const id = firstNonEmptyString(traceId, snapshot?.traceId);
    if (!id || !snapshot) return;
    if (!shouldApplyActiveTraceAuthority(id, traceResultSessionId(snapshot))) return;
    applyTraceSnapshot(id, realtimeSnapshotToTraceSnapshot(id, snapshot));
    if (traceSnapshotHasTerminalEvidence(snapshot)) void refreshTerminalTraceFromRest(id, "realtime-trace-snapshot");
  }

  function applyRealtimeTraceEvent(traceId: string | null | undefined, event: WorkbenchRealtimeEvent["event"], snapshot: WorkbenchRealtimeEvent["snapshot"], realtimeEvent?: WorkbenchRealtimeEvent | null): void {
    const id = firstNonEmptyString(traceId, event?.traceId, snapshot?.traceId);
    if (!id) return;
    const sessionId = realtimeEvent ? realtimeEventSessionId(realtimeEvent) : traceResultSessionId(snapshot ?? event ?? null);
    if (!shouldApplyActiveTraceAuthority(id, sessionId)) return;
    const events = event ? [event] : Array.isArray(snapshot?.events) ? snapshot.events : [];
    markWorkbenchTraceEventsReceived({ traceId: id, events, transport: "sse", serverSentAt: realtimeEvent?.serverSentAt, eventCreatedAt: realtimeEvent?.eventCreatedAt, traceSeq: realtimeEvent?.traceSeq ?? realtimeEvent?.cursor?.traceSeq });
    applyTraceSnapshot(id, realtimeSnapshotToTraceSnapshot(id, snapshot ?? { traceId: id, status: event?.status, events }, events));
    if (traceEventHasTerminalEvidence(event) || traceSnapshotHasTerminalEvidence(snapshot)) void refreshTerminalTraceFromRest(id, "realtime-trace-event");
  }

  function applyRealtimeTurnSnapshot(turn: Record<string, unknown>): void {
    const traceId = firstNonEmptyString(turn.traceId);
    if (!traceId) return;
    if (!shouldApplyActiveTraceAuthority(traceId, traceResultSessionId(turn))) return;
    const status = firstNonEmptyString(turn.status) ?? undefined;
    applyTurnStatusSnapshot(traceId, { ...turn, traceId, status, running: turn.running === true, terminal: turn.terminal === true, sessionId: firstNonEmptyString(turn.sessionId) ?? undefined, threadId: firstNonEmptyString(turn.threadId) ?? undefined, agentRun: turn.agentRun as AgentRunProvenance | undefined } as AgentChatResultResponse);
    if (turn.terminal === true || isTerminalMessageStatus(status)) void refreshTerminalTraceFromRest(traceId, "realtime-turn-snapshot");
  }

  async function refreshTerminalTraceFromRest(traceId: string, reason: string): Promise<void> {
    if (terminalRealtimeRefreshInFlight.has(traceId)) return;
    terminalRealtimeRefreshInFlight.add(traceId);
    try {
      const result = await fetchWorkbenchTurnStatus(traceId);
      if (!result.ok || !result.data) return;
      applyTurnStatusSnapshot(traceId, result.data);
      if (result.data.terminal === true || isTerminalMessageStatus(result.data.status)) completeTrace(traceId, result.data);
    } finally {
      terminalRealtimeRefreshInFlight.delete(traceId);
      restartRealtime(reason);
    }
  }

  function installRealtimeVisibilityHandler(): void {
    if (typeof document === "undefined") return;
    document.addEventListener("visibilitychange", () => {
      if (document.visibilityState !== "visible") return;
      restartRealtime("visibility");
    });
  }

  function applyTraceSnapshot(traceId: string, snapshot: TraceSnapshot): void {
    const trace = snapshotToRunnerTrace({
      ...snapshot,
      traceStatus: firstNonEmptyString(snapshot.traceStatus, snapshot.status) ?? undefined
    });
    const authoritySessionId = traceResultSessionId(trace) ?? traceResultSessionId(snapshot);
    const ownerSessionId = traceOwnerSessionId(traceId, authoritySessionId);
    if (!ownerSessionId) return;
    let matchedMessage = false;
    updateSessionMessages(ownerSessionId, (source) => source.map((message) => {
      if (!messageMatchesTraceAuthority(message, traceId, authoritySessionId, ownerSessionId)) return message;
      matchedMessage = true;
      const mergedRunnerTrace = mergeRunnerTrace(message.runnerTrace, trace);
      const traceStatus = normalizedStatusText(trace.status ?? snapshot.status) ?? null;
      const clearCompletedDiagnostics = shouldClearCompletedTurnDiagnostics(traceStatus, null);
      const runnerTrace = clearCompletedDiagnostics ? clearRunnerTraceTransientDiagnostics(mergedRunnerTrace) : mergedRunnerTrace;
      rememberTraceAuthority(runnerTrace);
      const error = clearCompletedDiagnostics ? null : message.role === "agent" ? normalizeAgentError(runnerTrace.error ?? message.error) : normalizeAgentError(message.error);
      const projection = clearCompletedDiagnostics ? nonBlockingProjection(trace.projection ?? null) : trace.projection ?? runnerTrace.projection ?? message.projection ?? null;
      return { ...message, ...messageTimingPatchForMerge(message, trace), runnerTrace, error: clearCompletedDiagnostics ? null : error ?? message.error ?? null, projection, projectionStatus: projection?.projectionStatus ?? null, projectionHealth: projection?.projectionHealth ?? null, blocker: projection?.blocker ?? null, updatedAt: new Date().toISOString() };
    }));
    if (!matchedMessage && ownerSessionId === activeSessionId.value) void refreshRealtimeSessionFromRest(ownerSessionId, `realtime-trace-snapshot:${traceId}`);
    markWorkbenchTraceProjected(traceId);
    scheduleSessionListRefresh(ownerSessionId, SESSION_LIST_REALTIME_REFRESH_DELAY_MS);
  }

  async function refreshRealtimeSessionFromRest(sessionId: string, reason: string): Promise<void> {
    const id = normalizeWorkbenchSessionId(sessionId);
    if (!id || id !== activeSessionId.value || realtimeSessionRefreshInFlight.has(id)) return;
    realtimeSessionRefreshInFlight.add(id);
    try {
      recordActivity(reason);
      const existing = sessions.value.find((item) => item.sessionId === id) ?? null;
      const selected = await loadWorkbenchSession(id, existing);
      if (selected && activeSessionId.value === id) applySelectedSessionDetail(selected, "system");
    } finally {
      realtimeSessionRefreshInFlight.delete(id);
    }
  }

  function completeTrace(traceId: string, result: AgentChatResultResponse): void {
    const authoritySessionId = traceResultSessionId(result);
    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 resultStatus = normalizedStatusText(result.status) ?? null;
      const terminal = result.terminal === true || isTerminalMessageStatus(resultStatus);
      const resultError = normalizeAgentError(result.error ?? null);
      const resultProjection = projectionFromResult(result);
      const mergedRunnerTrace = mergeTerminalResultTrace(message.runnerTrace, result);
      const clearCompletedDiagnostics = shouldClearCompletedTurnDiagnostics(resultStatus, resultError);
      const runnerTrace = clearCompletedDiagnostics ? clearRunnerTraceTransientDiagnostics(mergedRunnerTrace) : mergedRunnerTrace;
      rememberTraceAuthority(runnerTrace);
      const error = resultError ?? (terminal || clearCompletedDiagnostics ? null : normalizeAgentError(runnerTrace?.error ?? message.error));
      const projection = clearCompletedDiagnostics ? nonBlockingProjection(resultProjection) : resultProjection ?? (terminal ? null : runnerTrace.projection ?? message.projection ?? null);
      const agentRun = agentRunFromResult(result, runnerTrace) ?? agentRunFromMessage(message);
      return { ...message, ...messageTimingPatchForMerge(message, result), runnerTrace, error, 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);
    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");
    }
    scheduleSessionListRefresh(ownerSessionId, SESSION_LIST_TERMINAL_REFRESH_DELAY_MS);
  }

  async function hydrateTerminalMessageDiagnostics(): Promise<void> {
    const targets = messages.value.filter(messageNeedsTerminalDiagnostics).slice(-6);
    await Promise.all(targets.map(async (message) => {
      const traceId = firstNonEmptyString(message.traceId, message.runnerTrace?.traceId);
      if (!traceId) return;
      const result = await fetchWorkbenchTurnStatus(traceId);
      if (!result.ok || !result.data) return;
      applyTurnStatusSnapshot(traceId, result.data);
    }));
    void hydrateTraceEvents(messages.value);
  }

  function applyTerminalResultDiagnostics(traceId: string, result: AgentChatResultResponse): void {
    const authoritySessionId = traceResultSessionId(result);
    const ownerSessionId = traceOwnerSessionId(traceId, authoritySessionId);
    if (!ownerSessionId) return;
    updateSessionMessages(ownerSessionId, (source) => source.map((message) => {
      if (!messageMatchesTraceAuthority(message, traceId, authoritySessionId, ownerSessionId)) return message;
      const resultStatus = normalizedStatusText(result.status) ?? null;
      const terminal = result.terminal === true || isTerminalMessageStatus(resultStatus);
      const resultError = normalizeAgentError(result.error ?? null);
      const resultProjection = projectionFromResult(result);
      const mergedRunnerTrace = mergeTerminalResultTrace(message.runnerTrace, result);
      const clearCompletedDiagnostics = shouldClearCompletedTurnDiagnostics(resultStatus, resultError);
      const runnerTrace = clearCompletedDiagnostics ? clearRunnerTraceTransientDiagnostics(mergedRunnerTrace) : mergedRunnerTrace;
      rememberTraceAuthority(runnerTrace);
      const error = resultError ?? (terminal || clearCompletedDiagnostics ? null : normalizeAgentError(runnerTrace?.error ?? message.error));
      const agentRun = agentRunFromResult(result, runnerTrace) ?? agentRunFromMessage(message);
      const projection = clearCompletedDiagnostics ? nonBlockingProjection(resultProjection) : resultProjection ?? (terminal ? null : runnerTrace.projection ?? message.projection ?? null);
      return { ...message, ...messageTimingPatchForMerge(message, result), title: normalizeWorkbenchMessageTitle(message.role, message.title), runnerTrace, error, projection, projectionStatus: projection?.projectionStatus ?? null, projectionHealth: projection?.projectionHealth ?? null, blocker: projection?.blocker ?? null, agentRun: agentRun ?? undefined, updatedAt: new Date().toISOString() };
    }));
    void hydrateTraceEvents(serverState.value.messagesBySessionId[ownerSessionId] ?? []);
    scheduleSessionListRefresh(ownerSessionId, SESSION_LIST_TERMINAL_REFRESH_DELAY_MS);
  }

  function applyRealtimeProjectionError(event: WorkbenchRealtimeEvent): void {
    const traceId = firstNonEmptyString(event.traceId, realtimeTraceId());
    if (!traceId) return;
    const error = recordValue(event.error) ?? event;
    const projection = normalizeProjectionDiagnostic(event) ?? projectionDiagnosticFromFailure({ code: firstNonEmptyString(error.code, "workbench_realtime_error") ?? "workbench_realtime_error", message: firstNonEmptyString(error.message, error.summary, "Workbench 实时状态更新异常，最新运行记录暂不可见。") ?? "Workbench 实时状态更新异常，最新运行记录暂不可见。", health: "degraded", diagnostic: normalizeErrorDiagnostic(error.diagnostic, recordValue(event.projection)?.diagnostic, recordValue(event)?.diagnostic), apiError: normalizeApiErrorRecord(error, "Workbench 实时状态更新异常，最新运行记录暂不可见。") });
    applyProjectionDiagnostic(traceId, projection.projectionStatus ? projection : { ...projection, projectionStatus: "blocked", projectionHealth: projection.projectionHealth ?? "degraded" });
  }

  function applyProjectionDiagnostic(traceId: string, projection: ProjectionDiagnostic): void {
    const message = [...messages.value].reverse().find((item) => shouldApplyTraceToMessage(item, traceId, activeSessionId.value)) ?? null;
    const sessionId = normalizeWorkbenchSessionId(firstNonEmptyString(message?.sessionId, message?.runnerTrace?.sessionId, activeSessionId.value));
    const existing = turnStatusAuthority.value[traceId];
    if (existing?.terminal === true || messageHasSealedTerminalResult(message)) return;
    const status = existing?.status ?? normalizedStatusText(message?.status) ?? null;
    const running = existing?.running === true || (!isTerminalMessageStatus(message?.status) && isTraceActiveStatus(message?.status));
    const now = new Date().toISOString();
    reduceServerState({ type: "turn.status", turn: { traceId, status, running, terminal: existing?.terminal === true, sessionId, threadId: firstNonEmptyString(message?.threadId, message?.runnerTrace?.threadId) ?? existing?.threadId ?? null, updatedAt: now, projection, loadedAt: now } });
    if (sessionId) reduceServerState({ type: "session.status", session: { sessionId, status: activeSession.value?.status ?? status, updatedAt: now, lastTraceId: traceId, projection, loadedAt: now } });
    const projectionError = agentErrorFromProjection(projection);
    updateActiveMessages((source) => source.map((item) => {
      if (!shouldApplyTraceToMessage(item, traceId, sessionId)) return item;
      if (messageHasSealedTerminalResult(item)) return item;
      const runnerTrace = mergeRunnerTrace(item.runnerTrace, { ...(item.runnerTrace ?? {}), traceId, sessionId: sessionId ?? item.runnerTrace?.sessionId, projection, projectionStatus: projection.projectionStatus ?? null, projectionHealth: projection.projectionHealth ?? null, staleMs: projection.staleMs ?? null, blocker: projection.blocker ?? null, events: item.runnerTrace?.events ?? [], eventCount: item.runnerTrace?.eventCount ?? item.runnerTrace?.events?.length ?? 0, eventSource: item.runnerTrace?.eventSource ?? "projection-diagnostic", updatedAt: now });
      rememberTraceAuthority(runnerTrace);
      return { ...item, runnerTrace, error: projectionError ? { ...(item.error ?? {}), ...projectionError } : item.error ?? null, projection, projectionStatus: projection.projectionStatus ?? null, projectionHealth: projection.projectionHealth ?? null, blocker: projection.blocker ?? null, updatedAt: now };
    }));
  }

  function messageHasSealedTerminalResult(message: ChatMessage | null): boolean {
    if (!message) return false;
    if (isTerminalMessageStatus(message.status) || isTerminalMessageStatus(message.runnerTrace?.status)) return true;
    return message.runnerTrace ? traceSnapshotHasTerminalEvidence(message.runnerTrace) : false;
  }

  function failTrace(traceId: string, message: string): void {
    markMessage(traceId, { status: "failed", text: message });
    chatPending.value = false;
    currentRequest.value = null;
    void clearActiveTrace(traceId, "trace-infrastructure-error");
    scheduleSessionListRefresh(selectedSessionId.value, SESSION_LIST_TERMINAL_REFRESH_DELAY_MS);
  }

  function projectOptimisticRunningTurn(input: { sessionId: string; threadId: string | null; traceId: string; userText: string }): void {
    const now = new Date().toISOString();
    reduceServerState({ type: "turn.status", turn: { traceId: input.traceId, status: "running", running: true, terminal: false, sessionId: input.sessionId, threadId: input.threadId, updatedAt: now, loadedAt: now } });
    reduceServerState({ type: "session.status", session: { sessionId: input.sessionId, status: "running", updatedAt: now, lastTraceId: input.traceId, loadedAt: now } });
    const existing = sessions.value.find((session) => session.sessionId === input.sessionId) ?? null;
    rememberSessionList(mergeSessionIntoList(sessions.value, {
      ...(existing ?? {}),
      sessionId: input.sessionId,
      threadId: firstNonEmptyString(input.threadId, existing?.threadId),
      status: "running",
      updatedAt: now,
      lastTraceId: input.traceId,
      firstUserMessagePreview: firstNonEmptyString(existing?.firstUserMessagePreview, input.userText),
      messageCount: Math.max(existing?.messageCount ?? 0, messages.value.filter((message) => firstNonEmptyString(message.sessionId, message.runnerTrace?.sessionId) === input.sessionId).length),
      messages: messages.value.filter((message) => firstNonEmptyString(message.sessionId, message.runnerTrace?.sessionId) === input.sessionId)
    } as WorkbenchSessionRecord));
  }

  function markMessage(traceId: string, patch: Partial<ChatMessage>): void {
    updateActiveMessages((source) => source.map((message) => message.traceId === traceId && message.role === "agent" ? { ...message, ...patch, updatedAt: new Date().toISOString() } : message));
  }

  function alignOptimisticTurnMessages(traceId: string, lifecycle: AgentChatResponse): string {
    const canonicalTraceId = firstNonEmptyString(lifecycle.traceId, traceId) ?? traceId;
    const turnId = firstNonEmptyString(lifecycle.turnId, canonicalTraceId);
    const userMessageId = firstNonEmptyString(lifecycle.userMessageId);
    const assistantMessageId = firstNonEmptyString(lifecycle.assistantMessageId);
    const admissionTiming = optimisticAdmissionTimingPatch(lifecycle);
    const hasAdmissionTiming = Boolean(admissionTiming.timing?.startedAt || admissionTiming.timing?.lastEventAt);
    if (!turnId && !userMessageId && !assistantMessageId && !hasAdmissionTiming && canonicalTraceId === traceId) return canonicalTraceId;
    updateActiveMessages((source) => source.map((message) => {
      if (message.traceId !== traceId) return message;
      const runnerTrace = message.runnerTrace ? { ...message.runnerTrace, traceId: canonicalTraceId } : message.runnerTrace;
      const base = { ...message, traceId: canonicalTraceId, runnerTrace, updatedAt: new Date().toISOString() };
      if (message.role === "user" && userMessageId) return { ...base, id: userMessageId, messageId: userMessageId, turnId };
      if (message.role === "agent") {
        const next = { ...base, ...stableAdmissionTimingPatchForRunningMessage(message, admissionTiming) };
        return {
          ...next,
          ...(assistantMessageId ? { id: assistantMessageId, messageId: assistantMessageId } : {}),
          ...(turnId ? { turnId } : {})
        };
      }
      return turnId ? { ...base, turnId } : base;
    }));
    return canonicalTraceId;
  }

  function stableAdmissionTimingPatchForRunningMessage(message: ChatMessage, patch: Partial<ChatMessage>): Partial<ChatMessage> {
    const incoming = patch.timing;
    const existing = message.timing;
    if (!incoming || !existing?.startedAt || !isRunningAgentMessageForTiming(message)) return patch;
    const timing = {
      ...incoming,
      startedAt: existing.startedAt,
      lastEventAt: existing.lastEventAt ?? incoming.lastEventAt ?? existing.startedAt,
      finishedAt: null,
      durationMs: null,
      valuesRedacted: incoming.valuesRedacted !== false,
    } as WorkbenchTurnTimingProjection;
    return { ...patch, timing, startedAt: timing.startedAt ?? null, lastEventAt: timing.lastEventAt ?? null, finishedAt: null, durationMs: null };
  }

  function isRunningAgentMessageForTiming(message: ChatMessage): boolean {
    if (message.role !== "agent") return false;
    const status = normalizedStatusText(firstNonEmptyString(message.status, message.runnerTrace?.status));
    if (status && isTerminalMessageStatus(status)) return false;
    return status === null || isTraceActiveStatus(status) || message.traceAutoLifecycle === "running";
  }

  function optimisticAdmissionTimingPatch(lifecycle: AgentChatResponse): Partial<ChatMessage> {
    const patch = messageTimingPatch(lifecycle);
    if (patch.timing?.startedAt && patch.timing?.lastEventAt) return patch;
    const record = lifecycle as Record<string, unknown>;
    const admittedAt = firstNonEmptyString(record.startedAt, record.createdAt, record.updatedAt);
    if (!admittedAt) return patch;
    const timing = {
      ...(patch.timing ?? {}),
      startedAt: patch.timing?.startedAt ?? admittedAt,
      lastEventAt: patch.timing?.lastEventAt ?? admittedAt,
      finishedAt: null,
      durationMs: null,
      valuesRedacted: true,
    } as WorkbenchTurnTimingProjection;
    return { ...patch, timing, startedAt: timing.startedAt ?? null, lastEventAt: timing.lastEventAt ?? null, finishedAt: null, durationMs: null };
  }

  function optimisticRunningTimingPatch(startedAt: string): Partial<ChatMessage> {
    const timing = {
      startedAt,
      lastEventAt: startedAt,
      finishedAt: null,
      durationMs: null,
      valuesRedacted: true,
    } as WorkbenchTurnTimingProjection;
    return { timing, startedAt, lastEventAt: startedAt, finishedAt: null, durationMs: null };
  }

  async function clearActiveTrace(traceId: string, reason: string): Promise<void> {
    if (currentRequest.value?.traceId === traceId) currentRequest.value = null;
  }

  function beginSessionSelection(): number {
    selectionEpoch.value += 1;
    return selectionEpoch.value;
  }

  function isCurrentSessionSelection(epoch: number, sessionId?: string | null): boolean {
    if (epoch !== selectionEpoch.value) return false;
    if (!sessionId) return true;
    return activeSessionId.value === sessionId || switchingSessionId.value === sessionId;
  }

  function isCurrentSessionRequest(epoch: number, requestId: string, selectedSessionId?: string | null): boolean {
    if (epoch !== selectionEpoch.value) return false;
    return activeSessionId.value === requestId || switchingSessionId.value === requestId || Boolean(selectedSessionId && (activeSessionId.value === selectedSessionId || switchingSessionId.value === selectedSessionId));
  }

  function clearSwitchingSession(sessionId: string): void {
    if (switchingSessionId.value === sessionId) switchingSessionId.value = null;
  }

  function clearSessionDetailLoading(sessionId: string | null | undefined): void {
    if (!sessionId || sessionDetailLoadingId.value === sessionId) sessionDetailLoadingId.value = null;
  }

  function applySelectedSessionDetail(session: WorkbenchSessionRecord, source: SessionSelectionSource = "system"): void {
    setActiveSessionSelection(session.sessionId, source);
    const projectedMessages = messagesWithTraceAuthority(messagesFromSession(session));
    rememberSessionDetail({ ...session, messages: projectedMessages, messageCount: session.messageCount ?? projectedMessages.length });
    sessionsReady.value = true;
    currentRequest.value = null;
    void hydrateTurnStatusAuthority(messages.value);
    void hydrateTerminalMessageDiagnostics();
    void hydrateTraceEvents(messages.value);
    reattachRestoredActiveTrace();
    restartRealtime("apply-selected-session");
  }

  function applyCanonicalSessionArchive(sessionId: string, message: string): void {
    forgetSession(sessionId);
    if (activeSessionId.value === sessionId) replaceActiveSessionSelection(null);
    if (currentRequest.value?.sessionId === sessionId) currentRequest.value = null;
    error.value = message;
    sessionsReady.value = sessions.value.length > 0;
    restartRealtime("session-archived");
  }

  function markSessionReadUnavailable(sessionId: string, message: string): void {
    void sessionId;
    error.value = message;
    sessionsReady.value = sessions.value.length > 0;
    restartRealtime("session-read-unavailable");
  }

  function reattachRestoredActiveTrace(): void {
    void hydrateTraceEvents(messages.value);
    const traceId = activeTraceIdFromMessages(messages.value, turnStatusAuthority.value);
    if (traceId) void validateAndReattachTrace(traceId);
  }

  installRealtimeVisibilityHandler();

  return { sessions, messages, activeMessages, providerProfile, providerOptions, recentDrafts, codeAgentTimeoutMs, gatewayShellTimeoutMs, live, loading, sessionListLoading, sessionDetailLoading, sessionListLoadedCount, sessionListHasMore, sessionListNextCursor, sessionListLoadingMore, sessionListLoadMoreError, chatPending, error, activeSessionId, activeSessionSelectionSource, selectedSessionId, selectedThreadId, sessionTabs, composer, hydrate, refreshLive, createSession, selectSession, selectSessionById, deleteCurrentSession, refreshSessions, loadMoreSessions, submitMessage, cancelAgentMessage, cancelRunningTrace, retryAgentMessage, setProviderProfile, refreshProviderOptions, pickDraft, clearRecentDrafts, clearSessionMessages, recordActivity };
});

function workbenchSessionsFromPayload(payload: unknown): WorkbenchSessionRecord[] {
  const record = recordValue(payload);
  const sessions = Array.isArray(record?.sessions) ? record.sessions : [];
  return sessions.map((item) => sessionFromWorkbenchSession(item)).filter((item): item is WorkbenchSessionRecord => Boolean(item));
}

function sessionFromWorkbenchSession(value: unknown): WorkbenchSessionRecord | null {
  const record = recordValue(value);
  const sessionId = normalizeWorkbenchSessionId(record?.sessionId ?? record?.id);
  if (!sessionId) return null;
  return {
    ...record,
    sessionId,
    threadId: firstNonEmptyString(record?.threadId),
    status: firstNonEmptyString(record?.status),
    updatedAt: firstNonEmptyString(record?.updatedAt),
    startedAt: firstNonEmptyString(record?.startedAt),
    lastTraceId: firstNonEmptyString(record?.lastTraceId),
    messageCount: firstFiniteNumber(record?.messageCount) ?? undefined,
    firstUserMessagePreview: firstNonEmptyString(record?.firstUserMessagePreview),
    messages: Array.isArray(record?.messages) ? record.messages.map((message) => normalizeChatMessage(message as ChatMessage)) : undefined
  };
}

function routeSelectedSessionRecord(sessionId: string | null | undefined, loadingSessionId: string | null | undefined, errorText: string | null | undefined, projected: WorkbenchSessionRecord | null): WorkbenchSessionRecord | null {
  if (projected) return null;
  const id = normalizeWorkbenchSessionId(sessionId);
  if (!id) return null;
  const loadingId = normalizeWorkbenchSessionId(loadingSessionId);
  const loading = loadingId === id;
  const message = loading ? "session 投影加载中" : firstNonEmptyString(errorText, "session 投影暂不可见");
  const projection: ProjectionDiagnostic = {
    projectionStatus: loading ? "pending" : "blocked",
    projectionHealth: loading ? "degraded" : "unavailable",
    blocker: {
      code: loading ? "session_detail_loading" : "session_detail_unavailable",
      message,
      userMessage: message,
      retryable: true
    }
  };
  return {
    sessionId: id,
    status: "unknown",
    messageCount: 0,
    messages: [],
    firstUserMessagePreview: loading ? "正在加载 session 投影" : "session 投影暂不可见",
    projection,
    projectionStatus: projection.projectionStatus ?? undefined,
    projectionHealth: projection.projectionHealth ?? undefined
  } as WorkbenchSessionRecord;
}

async function loadWorkbenchSession(sessionId: string, seed: WorkbenchSessionRecord | null = null): Promise<WorkbenchSessionRecord | null> {
  const requestId = normalizeWorkbenchSessionRouteId(sessionId);
  if (!requestId) return null;
  const normalizedRequestId = normalizeWorkbenchSessionId(requestId);
  const eagerMessages = normalizedRequestId ? api.workbench.sessionMessages(normalizedRequestId, { limit: 100 }) : null;
  const detail = await api.workbench.session(requestId);
  if (!detail.ok) return null;
  const detailSession = sessionFromWorkbenchSession(detail.data?.session);
  const id = detailSession?.sessionId ?? normalizedRequestId ?? seed?.sessionId;
  if (!id) return null;
  const messages = eagerMessages && id === normalizedRequestId ? await eagerMessages : await api.workbench.sessionMessages(id, { limit: 100 });
  const base = detailSession ?? seed;
  if (!base) return null;
  const page = messages.ok ? messages.data : null;
  const pageMessages = Array.isArray(page?.messages) ? page.messages.map((message) => normalizeChatMessage(message as ChatMessage)) : base.messages;
  return { ...base, sessionId: id, messages: pageMessages, messageCount: page?.total ?? pageMessages?.length ?? base.messageCount };
}

function providerThreadIdForRequest(threadId: string | null | undefined): string | null {
  const value = firstNonEmptyString(threadId) ?? null;
  if (!value) return null;
  return /^thr_[A-Za-z0-9]{12,32}$/u.test(value) ? null : value;
}

function uniqueSessionIds(sessions: WorkbenchSessionRecord[]): string[] {
  return [...new Set(sessions.map((session) => firstNonEmptyString(session.sessionId)).filter((sessionId): sessionId is string => Boolean(sessionId)))];
}

function uniqueTraceIds(messages: ChatMessage[]): string[] {
  return [...new Set(messages.map((message) => firstNonEmptyString(message.traceId, message.runnerTrace?.traceId)).filter((traceId): traceId is string => Boolean(traceId)))];
}

function messagesFromSession(session: WorkbenchSessionRecord): ChatMessage[] {
  return (session.messages ?? []).map((message) => normalizeChatMessage(message));
}

function normalizeChatMessage(message: ChatMessage): ChatMessage {
  const role = (message.role as string) === "assistant" ? "agent" : message.role;
  const runnerTrace = normalizeMessageRunnerTrace(message);
  const error = normalizeAgentError(message.error ?? runnerTrace?.error);
  const projection = projectionFromMessage(message, runnerTrace);
  const agentRun = agentRunFromMessage(message) ?? asAgentRun(runnerTrace?.agentRun);
  const rawStatus = normalizeChatMessageStatus(message.status);
  const baseText = firstNonEmptyString(message.text, messageText((message as Record<string, unknown>).content), messageText((message as Record<string, unknown>).message));
  const finalText = finalResponseText((message as Record<string, unknown>).finalResponse);
  const errorText = agentErrorDisplayText(error);
  const status = normalizeAgentMessageStatusFromEvidence(role, rawStatus, message, runnerTrace, finalText);
  const text = role === "agent"
    ? isTerminalMessageStatus(status)
      ? projectedAgentMessageText({ status, finalText, errorText, baseText })
      : ""
    : firstNonEmptyString(baseText, finalText, errorText) ?? "";
  const messageId = firstNonEmptyString((message as Record<string, unknown>).messageId, message.id) ?? nextProtocolId("msg");
  return { ...message, ...messageTimingPatch(message), role, text, id: messageId, messageId, title: normalizeWorkbenchMessageTitle(role, message.title), createdAt: message.createdAt ?? new Date().toISOString(), status, runnerTrace, error: error ?? message.error ?? null, projection, projectionStatus: projection?.projectionStatus ?? null, projectionHealth: projection?.projectionHealth ?? null, blocker: projection?.blocker ?? null, agentRun: agentRun ?? undefined };
}

function normalizeAgentMessageStatusFromEvidence(role: ChatMessage["role"], status: ChatMessage["status"], message: ChatMessage, runnerTrace: ChatMessage["runnerTrace"], finalText: string | null): ChatMessage["status"] {
  if (role !== "agent") return status;
  const traceStatus = normalizedStatusText(runnerTrace?.status);
  if (isTerminalMessageStatus(traceStatus)) return traceStatus as ChatMessage["status"];
  if (isTerminalMessageStatus(status)) return status;
  if (finalText || traceSnapshotHasTerminalEvidence(message) || traceSnapshotHasTerminalEvidence(runnerTrace)) return "completed" as ChatMessage["status"];
  return status;
}

function activeTraceIdFromMessages(messages: ChatMessage[], turnStatusAuthority: Record<string, TurnStatusAuthority>): string | null {
  for (const message of [...messages].reverse()) {
    if (message.role !== "agent") continue;
    if (messageHasCompletedFinalResponse(message)) continue;
    if (isTerminalMessageStatus(message.status) || isTerminalMessageStatus(message.runnerTrace?.status)) continue;
    const traceId = firstNonEmptyString(message.traceId, message.runnerTrace?.traceId);
    if (!traceId) continue;
    const turn = turnStatusAuthority[traceId];
    if (turn?.running !== true && !isTraceActiveStatus(turn?.status)) continue;
    return traceId;
  }
  return null;
}

function realtimeSnapshotToTraceSnapshot(traceId: string, snapshot: WorkbenchRealtimeEvent["snapshot"], eventPageEvents?: TraceEvent[]): TraceSnapshot {
  const source = snapshot ?? { traceId };
  const events = Array.isArray(eventPageEvents) ? eventPageEvents : Array.isArray(source.events) ? source.events : [];
  const { traceId: _sourceTraceId, error: _sourceError, ...sourceRest } = source;
  return {
    ...sourceRest,
    traceId: optionalString(source.traceId, traceId),
    status: firstNonEmptyString(source.status) ?? undefined,
    events,
    eventCount: firstFiniteNumber(source.eventCount, events.length),
    fullTraceLoaded: source.fullTraceLoaded === true,
    hasMore: source.hasMore === true,
    truncated: source.truncated === true,
    nextProjectedSeq: firstFiniteNumber(source.nextProjectedSeq) ?? null,
    range: recordValue(source.range) as TraceSnapshot["range"],
    agentRun: asAgentRun(source.agentRun) ?? undefined,
    traceStatus: firstNonEmptyString(source.traceStatus) ?? undefined,
    retention: source.retention,
    terminalEvidence: source.terminalEvidence,
    traceSummary: source.traceSummary,
    error: traceSnapshotError(source.error),
    projection: normalizeProjectionDiagnostic(source.projection ?? source) ?? null,
    projectionStatus: firstNonEmptyString(source.projectionStatus, recordValue(source.projection)?.projectionStatus) ?? undefined,
    projectionHealth: firstNonEmptyString(source.projectionHealth, recordValue(source.projection)?.projectionHealth) ?? undefined,
    staleMs: firstFiniteNumber(source.staleMs, recordValue(source.projection)?.staleMs),
    blocker: recordValue(source.blocker) ?? recordValue(recordValue(source.projection)?.blocker) ?? undefined,
    ...messageTimingPatch(source),
    lastEventLabel: firstNonEmptyString(source.lastEventLabel) ?? undefined,
    eventSource: "trace-api",
    updatedAt: firstNonEmptyString(source.updatedAt) ?? new Date().toISOString()
  } as TraceSnapshot;
}

function normalizedStatusText(value: unknown): string | null {
  const text = firstNonEmptyString(value);
  return text ? text.trim().toLowerCase().replace(/_/gu, "-") : null;
}

function optionalString(...values: unknown[]): string | undefined {
  for (const value of values) {
    if (typeof value !== "string") continue;
    const text = value.trim();
    if (text) return text;
  }
  return undefined;
}

function traceSnapshotError(value: unknown): Exclude<TraceSnapshot["error"], null> | undefined {
  const error = normalizeAgentError(value);
  return error ? error as Exclude<TraceSnapshot["error"], null> : undefined;
}

function mergeTerminalResultTrace(previous: ChatMessage["runnerTrace"], result: AgentChatResultResponse): NonNullable<ChatMessage["runnerTrace"]> {
  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);
  const traceDetailStatus = firstNonEmptyString(result.traceStatus, resultTrace?.traceStatus, resultTrace?.status);
  const timing = normalizeTimingProjection(result);
  const nextTrace = {
    ...resultTrace,
    traceId: firstNonEmptyString(result.traceId, resultTrace?.traceId, previous?.traceId) ?? 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),
    retention: result.retention ?? resultTrace?.retention ?? previous?.retention,
    terminalEvidence: result.terminalEvidence ?? resultTrace?.terminalEvidence ?? previous?.terminalEvidence,
    traceSummary: result.traceSummary ?? resultTrace?.traceSummary ?? previous?.traceSummary,
    agentRun: agentRun ?? undefined,
    error: normalizeAgentError(result.error ?? resultTrace?.error ?? previous?.error) ?? undefined,
    projection: projectionFromResult(result) ?? normalizeProjectionDiagnostic(resultTrace?.projection ?? resultTrace) ?? previous?.projection ?? null,
    projectionStatus: result.projectionStatus ?? resultTrace?.projectionStatus ?? previous?.projectionStatus ?? null,
    projectionHealth: result.projectionHealth ?? resultTrace?.projectionHealth ?? previous?.projectionHealth ?? null,
    staleMs: result.staleMs ?? resultTrace?.staleMs ?? previous?.staleMs ?? null,
    blocker: result.blocker ?? resultTrace?.blocker ?? previous?.blocker ?? null,
    timing,
    startedAt: timing?.startedAt ?? null,
    lastEventAt: timing?.lastEventAt ?? null,
    finishedAt: timing?.finishedAt ?? null,
    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) ?? undefined,
    updatedAt: new Date().toISOString()
  } 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"] {
  const trace = recordValue(message.runnerTrace);
  const agentRun = agentRunFromMessage(message);
  const error = normalizeAgentError(message.error ?? trace?.error);
  const projection = projectionFromMessage(message, trace as ChatMessage["runnerTrace"] | null);
  const timing = normalizeTimingProjection(message);
  if (!trace && !agentRun && !error && !projection) return message.runnerTrace ?? null;
  return {
    ...(trace ?? {}),
    traceId: message.traceId ?? trace?.traceId,
    sessionId: message.sessionId ?? trace?.sessionId,
    threadId: message.threadId ?? trace?.threadId,
    agentRun: agentRun ?? trace?.agentRun,
    error: error ?? trace?.error,
    projection,
    projectionStatus: projection?.projectionStatus ?? trace?.projectionStatus,
    projectionHealth: projection?.projectionHealth ?? trace?.projectionHealth,
    staleMs: projection?.staleMs ?? trace?.staleMs,
    blocker: projection?.blocker ?? trace?.blocker,
    timing,
    startedAt: timing?.startedAt ?? null,
    lastEventAt: timing?.lastEventAt ?? null,
    finishedAt: timing?.finishedAt ?? null,
    durationMs: timing?.durationMs ?? null,
    runnerKind: agentRun?.adapter ?? trace?.runnerKind,
    sessionMode: agentRun?.backendProfile ?? trace?.sessionMode
  } as ChatMessage["runnerTrace"];
}

function normalizeTimingProjection(value: unknown): WorkbenchTurnTimingProjection | null {
  const record = recordValue(value);
  if (!record) return null;
  const source = recordValue(record.timing) ?? {};
  const startedAt = firstNonEmptyString(source.startedAt, record.startedAt);
  const lastEventAt = firstNonEmptyString(source.lastEventAt, record.lastEventAt);
  const finishedAt = firstNonEmptyString(source.finishedAt, record.finishedAt);
  const durationMs = firstFiniteNumber(source.durationMs, record.durationMs);
  const observedAt = firstNonEmptyString(source.observedAt, record.observedAt);
  const lastEventAgeMs = firstFiniteNumber(source.lastEventAgeMs, record.lastEventAgeMs);
  if (!startedAt && !lastEventAt && !finishedAt && durationMs == null && lastEventAgeMs == null) return null;
  return { ...source, startedAt: startedAt ?? null, lastEventAt: lastEventAt ?? null, finishedAt: finishedAt ?? null, durationMs: durationMs ?? null, observedAt: observedAt ?? null, lastEventAgeMs: lastEventAgeMs ?? null, valuesRedacted: source.valuesRedacted !== false } as WorkbenchTurnTimingProjection;
}

function messageTimingPatch(value: unknown): Partial<ChatMessage> {
  const timing = normalizeTimingProjection(value);
  if (!timing) return {};
  return { timing, startedAt: timing.startedAt ?? null, lastEventAt: timing.lastEventAt ?? null, finishedAt: timing.finishedAt ?? null, durationMs: timing.durationMs ?? null };
}

function messageTimingPatchForMerge(message: ChatMessage, value: unknown): Partial<ChatMessage> {
  void value;
  // Trace/result hydration may update runnerTrace and TraceTimeline, but the
  // main Code Agent card timing is a durable message projection field. Do not
  // let trace snapshots or terminal result polling rewrite message.timing;
  // otherwise visible card elapsed can jump or diverge from the sealed message
  // projection when trace rows arrive out of order.
  return messageTimingPatch(message);
}

function firstPositiveFiniteNumber(...values: unknown[]): number | null {
  for (const value of values) {
    const number = firstFiniteNumber(value);
    if (number !== null && number > 0) return number;
  }
  return null;
}

function maxFiniteNumber(...values: unknown[]): number | null {
  let max: number | null = null;
  for (const value of values) {
    const number = Number(value);
    if (!Number.isFinite(number) || number < 0) continue;
    const normalized = Math.trunc(number);
    max = max === null ? normalized : Math.max(max, normalized);
  }
  return max;
}

function projectionFromResult(result: AgentChatResultResponse | TraceSnapshot | Record<string, unknown> | null | undefined): ProjectionDiagnostic | null {
  return normalizeProjectionDiagnostic(result) ?? normalizeProjectionDiagnostic(recordValue(result)?.runnerTrace) ?? null;
}

function projectionFromMessage(message: ChatMessage, trace: ChatMessage["runnerTrace"] | null | undefined): ProjectionDiagnostic | null {
  return normalizeProjectionDiagnostic(message.projection ?? message) ?? normalizeProjectionDiagnostic(trace?.projection ?? trace) ?? null;
}

function normalizeProjectionDiagnostic(value: unknown): ProjectionDiagnostic | null {
  const record = recordValue(value);
  if (!record) return null;
  const nested = recordValue(record.projection);
  const source = nested ?? record;
  const rawBlocker = recordValue(source.blocker ?? record.blocker);
  const apiError = normalizeApiErrorRecord(source.apiError ?? record.apiError, null);
  const diagnostic = normalizeErrorDiagnostic(source.diagnostic, record.diagnostic, rawBlocker?.diagnostic, apiError?.diagnostic);
  const blocker = normalizeProjectionBlocker(rawBlocker, { diagnostic, apiError });
  const projectionStatus = firstNonEmptyString(source.projectionStatus, record.projectionStatus);
  const projectionHealth = firstNonEmptyString(source.projectionHealth, record.projectionHealth);
  const lastProjectedSeq = firstFiniteNumber(source.lastProjectedSeq, record.lastProjectedSeq);
  const staleMs = firstFiniteNumber(source.staleMs, record.staleMs);
  const sourceRunId = firstNonEmptyString(source.sourceRunId, record.sourceRunId);
  const sourceCommandId = firstNonEmptyString(source.sourceCommandId, record.sourceCommandId);
  if (!projectionStatus && !projectionHealth && !blocker && lastProjectedSeq == null && staleMs == null && !sourceRunId && !sourceCommandId) return null;
  return { ...source, projectionStatus, projectionHealth, lastProjectedSeq: lastProjectedSeq ?? null, staleMs: staleMs ?? null, sourceRunId: sourceRunId ?? null, sourceCommandId: sourceCommandId ?? null, blocker, diagnostic, apiError, valuesRedacted: source.valuesRedacted !== false } as ProjectionDiagnostic;
}

function projectionDiagnosticFromApiFailure(result: ApiResult<unknown>, input: { code: string; message: string; health: string }): ProjectionDiagnostic {
  return projectionDiagnosticFromFailure({ ...input, status: result.status, apiError: result.apiError, diagnostic: result.diagnostic });
}

function shouldSuppressTransientWorkbenchReadFailure(result: ApiResult<unknown>): boolean {
  const diagnostic = normalizeErrorDiagnostic(result.diagnostic, result.apiError?.diagnostic);
  const code = firstStringOrNumber(result.apiError?.code, diagnostic?.code);
  const category = firstNonEmptyString(result.apiError?.category, diagnostic?.category);
  const source = firstNonEmptyString(result.apiError?.source, diagnostic?.source);
  if (code === "workbench_read_hydration_cooldown" || code === "workbench_read_hydration_throttled") return true;
  return result.status === 0 && source === "browser" && code === "browser_network_error" && category === "network";
}

function projectionDiagnosticFromFailure(input: { code: string; message: string; health: string; status?: number | null; apiError?: ApiError | null; diagnostic?: ErrorDiagnostic | null; route?: string | null; source?: string | null }): ProjectionDiagnostic {
  const apiError = input.apiError ?? null;
  const diagnostic = normalizeErrorDiagnostic(input.diagnostic, apiError?.diagnostic, {
    contractVersion: "hwlab-error-diagnostic-v1",
    traceId: apiError?.traceId ?? null,
    requestId: apiError?.requestId ?? null,
    route: firstNonEmptyString(apiError?.route, input.route),
    layer: apiError?.layer ?? "workbench",
    category: apiError?.category ?? "workbench-projection",
    code: firstStringOrNumber(apiError?.code, input.code),
    httpStatus: input.status ?? null,
    source: firstNonEmptyString(apiError?.source, input.source, "browser"),
    retryable: apiError?.retryable ?? true,
    valuesPrinted: false
  });
  const message = firstNonEmptyString(apiError?.userMessage, apiError?.message, input.message) ?? input.message;
  const blocker = normalizeProjectionBlocker({
    code: firstStringOrNumber(apiError?.code, diagnostic?.code, input.code),
    message,
    userMessage: firstNonEmptyString(apiError?.userMessage, message),
    layer: firstNonEmptyString(apiError?.layer, diagnostic?.layer, "workbench"),
    category: firstNonEmptyString(apiError?.category, diagnostic?.category, "workbench-projection"),
    retryable: firstBoolean(apiError?.retryable, diagnostic?.retryable, true),
    route: firstNonEmptyString(apiError?.route, diagnostic?.route, input.route),
    traceId: firstNonEmptyString(apiError?.traceId, diagnostic?.traceId),
    requestId: firstNonEmptyString(apiError?.requestId, diagnostic?.requestId),
    source: firstNonEmptyString(apiError?.source, diagnostic?.source, input.source),
    httpStatus: firstFiniteNumber(diagnostic?.httpStatus, input.status),
    diagnostic,
    valuesPrinted: false
  }, { diagnostic, apiError, fallbackCode: input.code, fallbackMessage: message });
  return { projectionStatus: "blocked", projectionHealth: input.health, blocker, diagnostic, apiError, valuesRedacted: true };
}

function normalizeProjectionBlocker(value: unknown, context: { diagnostic?: ErrorDiagnostic | null; apiError?: ApiError | null; fallbackCode?: string | null; fallbackMessage?: string | null } = {}): ProjectionBlocker | null {
  const record = recordValue(value);
  if (!record && !context.diagnostic && !context.apiError && !context.fallbackMessage && !context.fallbackCode) return null;
  const source = record ?? {};
  const diagnostic = context.diagnostic ?? normalizeErrorDiagnostic(source.diagnostic, context.apiError?.diagnostic);
  const code = firstStringOrNumber(source.code, context.apiError?.code, diagnostic?.code, context.fallbackCode);
  const message = firstNonEmptyString(source.message, source.userMessage, source.summary, context.apiError?.userMessage, context.apiError?.message, context.fallbackMessage);
  const retryable = firstBoolean(source.retryable, context.apiError?.retryable, diagnostic?.retryable);
  return {
    ...source,
    code: code === null ? undefined : String(code),
    message: message ?? null,
    userMessage: firstNonEmptyString(source.userMessage, context.apiError?.userMessage, message),
    summary: firstNonEmptyString(source.summary),
    layer: firstNonEmptyString(source.layer, context.apiError?.layer, diagnostic?.layer),
    category: firstNonEmptyString(source.category, context.apiError?.category, diagnostic?.category, "workbench-projection"),
    retryable: retryable ?? true,
    route: firstNonEmptyString(source.route, context.apiError?.route, diagnostic?.route),
    traceId: firstNonEmptyString(source.traceId, context.apiError?.traceId, diagnostic?.traceId),
    requestId: firstNonEmptyString(source.requestId, context.apiError?.requestId, diagnostic?.requestId),
    source: firstNonEmptyString(source.source, context.apiError?.source, diagnostic?.source),
    httpStatus: firstFiniteNumber(source.httpStatus, source.http_status, diagnostic?.httpStatus) ?? null,
    diagnostic,
    valuesPrinted: source.valuesPrinted === true
  } as ProjectionBlocker;
}

function normalizeErrorDiagnostic(...values: unknown[]): ErrorDiagnostic | null {
  for (const value of values) {
    const record = recordValue(value);
    if (!record) continue;
    const httpStatus = firstFiniteNumber(record.httpStatus, record.http_status);
    return {
      ...record,
      contractVersion: firstNonEmptyString(record.contractVersion, record.contract_version, "hwlab-error-diagnostic-v1"),
      traceId: firstNonEmptyString(record.traceId, record.trace_id, record.otelTraceId, record.otel_trace_id),
      requestId: firstNonEmptyString(record.requestId, record.request_id),
      serviceId: firstNonEmptyString(record.serviceId, record.service_id),
      route: firstNonEmptyString(record.route),
      layer: firstNonEmptyString(record.layer),
      category: firstNonEmptyString(record.category),
      code: firstStringOrNumber(record.code),
      httpStatus: httpStatus ?? null,
      source: firstNonEmptyString(record.source),
      observedAt: firstNonEmptyString(record.observedAt, record.observed_at),
      retryable: firstBoolean(record.retryable),
      valuesPrinted: record.valuesPrinted === true
    };
  }
  return null;
}

function normalizeApiErrorRecord(value: unknown, fallback: string | null = null, diagnostic: ErrorDiagnostic | null = null): ApiError | null {
  const record = recordValue(value);
  if (!record) return null;
  const normalizedDiagnostic = normalizeErrorDiagnostic(record.diagnostic, diagnostic);
  const message = firstNonEmptyString(record.message, record.userMessage, record.reason, fallback);
  return {
    ...record,
    message: message ?? fallback ?? "Workbench 请求失败",
    userMessage: firstNonEmptyString(record.userMessage, record.message, fallback),
    code: firstStringOrNumber(record.code, normalizedDiagnostic?.code),
    reason: firstNonEmptyString(record.reason),
    retryable: firstBoolean(record.retryable, normalizedDiagnostic?.retryable),
    layer: firstNonEmptyString(record.layer, normalizedDiagnostic?.layer),
    category: firstNonEmptyString(record.category, normalizedDiagnostic?.category),
    route: firstNonEmptyString(record.route, normalizedDiagnostic?.route),
    traceId: firstNonEmptyString(record.traceId, normalizedDiagnostic?.traceId),
    requestId: firstNonEmptyString(record.requestId, normalizedDiagnostic?.requestId),
    source: firstNonEmptyString(record.source, normalizedDiagnostic?.source),
    diagnostic: normalizedDiagnostic,
    valuesPrinted: record.valuesPrinted === true
  } as ApiError;
}

function agentErrorFromApiFailure(result: ApiResult<unknown>, projection: ProjectionDiagnostic, fallback: string): ChatMessage["error"] {
  return normalizeAgentError(result.apiError ?? projection.apiError ?? projection.blocker ?? { message: result.error ?? fallback, diagnostic: result.diagnostic ?? projection.diagnostic }) ?? { message: result.error ?? fallback, diagnostic: result.diagnostic ?? projection.diagnostic };
}

function agentErrorFromProjection(projection: ProjectionDiagnostic): ChatMessage["error"] | null {
  const blocker = projection.blocker ?? null;
  const diagnostic = projection.diagnostic ?? blocker?.diagnostic ?? projection.apiError?.diagnostic ?? null;
  const message = firstNonEmptyString(blocker?.userMessage, blocker?.message, blocker?.summary, projection.apiError?.userMessage, projection.apiError?.message, diagnostic?.code ? String(diagnostic.code) : null);
  if (!blocker && !projection.apiError && !diagnostic && !message) return null;
  return normalizeAgentError({ ...(projection.apiError ?? {}), ...(blocker ?? {}), message, code: firstStringOrNumber(blocker?.code, projection.apiError?.code, diagnostic?.code), diagnostic, traceId: firstNonEmptyString(blocker?.traceId, projection.apiError?.traceId, diagnostic?.traceId), requestId: firstNonEmptyString(blocker?.requestId, projection.apiError?.requestId, diagnostic?.requestId) });
}

function messageNeedsTerminalDiagnostics(message: ChatMessage): boolean {
  if (message.role !== "agent") return false;
  if (!isTerminalMessageStatus(message.status)) return false;
  if (messageHasCompletedFinalResponse(message)) return false;
  const traceId = firstNonEmptyString(message.traceId, message.runnerTrace?.traceId);
  if (!traceId) return false;
  const agentRun = agentRunFromMessage(message);
  const error = normalizeAgentError(message.error ?? message.runnerTrace?.error);
  return !agentRun || (message.status !== "completed" && !error);
}

function messageNeedsTraceHydration(message: ChatMessage): boolean {
  if (message.role !== "agent") return false;
  const traceId = firstNonEmptyString(message.traceId, message.runnerTrace?.traceId);
  if (!traceId) return false;
  const trace = message.runnerTrace;
  const events = Array.isArray(trace?.events) ? trace.events : [];
  const eventCount = firstFiniteNumber(trace?.eventCount) ?? events.length;
  if (messageHasCompletedFinalResponse(message) && events.length === 0) return false;
  if (trace?.fullTraceLoaded === true && trace.eventsCompacted !== true) return events.length === 0 && (eventCount > 0 || isTraceActiveStatus(trace?.status) || isTraceActiveStatus(message.status));
  return events.length === 0 || trace?.eventsCompacted === true || trace?.fullTraceLoaded !== true;
}

function traceHasCompletedFinalResponse(traceId: string | null | undefined, source: ChatMessage[]): boolean {
  const id = firstNonEmptyString(traceId);
  if (!id) return false;
  return source.some((message) => firstNonEmptyString(message.traceId, message.runnerTrace?.traceId) === id && messageHasCompletedFinalResponse(message));
}

function messageHasCompletedFinalResponse(message: ChatMessage | null | undefined): boolean {
  if (!message || message.role !== "agent") return false;
  if (normalizedStatusText(message.status) !== "completed" && normalizedStatusText(message.runnerTrace?.status) !== "completed") return false;
  return Boolean(firstNonEmptyString(message.text, finalResponseText((message as Record<string, unknown>).finalResponse)));
}

function traceHasEvents(trace: ChatMessage["runnerTrace"]): boolean {
  return Array.isArray(trace?.events) && trace.events.length > 0;
}

function agentRunFromResult(result: AgentChatResultResponse, runnerTrace: ChatMessage["runnerTrace"]): AgentRunProvenance | null {
  return asAgentRun(result.agentRun ?? runnerTrace?.agentRun);
}

function agentRunFromMessage(message: ChatMessage): AgentRunProvenance | null {
  return asAgentRun((message as Record<string, unknown>).agentRun ?? message.runnerTrace?.agentRun);
}

function asAgentRun(value: unknown): AgentRunProvenance | null {
  return value && typeof value === "object" ? value as AgentRunProvenance : null;
}

function normalizeAgentError(value: unknown): ChatMessage["error"] | null {
  if (!value) return null;
  if (typeof value === "string") return value.trim() ? { message: value.trim() } : null;
  if (value && typeof value === "object") {
    const record = value as Record<string, unknown>;
    const diagnostic = normalizeErrorDiagnostic(record.diagnostic, recordValue(record.apiError)?.diagnostic, recordValue(record.error)?.diagnostic);
    const message = firstNonEmptyString(record.message, record.userMessage, record.reason, record.detail, record.error);
    const code = firstStringOrNumber(record.code, record.failureKind, record.name, diagnostic?.code);
    const category = firstNonEmptyString(record.category, diagnostic?.category, record.layer, record.type);
    const providerStatus = typeof record.providerStatus === "number" ? record.providerStatus : Number.isFinite(Number(record.providerStatus)) ? Number(record.providerStatus) : undefined;
    return { ...record, code: code ?? undefined, message: message ?? undefined, userMessage: firstNonEmptyString(record.userMessage, message), category: category ?? undefined, layer: firstNonEmptyString(record.layer, diagnostic?.layer), route: firstNonEmptyString(record.route, diagnostic?.route), traceId: firstNonEmptyString(record.traceId, diagnostic?.traceId), requestId: firstNonEmptyString(record.requestId, diagnostic?.requestId), source: firstNonEmptyString(record.source, diagnostic?.source), retryable: firstBoolean(record.retryable, diagnostic?.retryable), diagnostic, providerStatus };
  }
  return { message: String(value) };
}

function agentErrorDisplayText(value: unknown): string | null {
  const error = normalizeAgentError(value);
  if (!error) return null;
  return firstNonEmptyString(error.message, typeof error.userMessage === "string" ? error.userMessage : null, error.code ? `Code Agent 请求失败：${error.code}` : null);
}

function isTerminalMessageStatus(value: unknown): boolean {
  return ["completed", "failed", "blocked", "timeout", "canceled", "cancelled", "stale", "thread-resume-failed"].includes(String(value ?? "").trim().toLowerCase().replace(/_/gu, "-"));
}

function traceEventHasTerminalEvidence(event: unknown): boolean {
  const record = recordValue(event);
  return record?.terminal === true || record?.final === true || record?.replyAuthority === true;
}

function traceSnapshotHasTerminalEvidence(snapshot: unknown): boolean {
  const record = recordValue(snapshot);
  if (!record) return false;
  if (record.terminal === true || record.terminalEvidence) return true;
  const events = Array.isArray(record.events) ? record.events : [];
  return events.some((event) => traceEventHasTerminalEvidence(event));
}

function traceResultHasTerminalEvidence(result: unknown): boolean {
  const record = recordValue(result);
  return traceSnapshotHasTerminalEvidence(result) || traceSnapshotHasTerminalEvidence(record?.runnerTrace);
}

function recordValue(value: unknown): Record<string, unknown> | null {
  return value && typeof value === "object" ? value as Record<string, unknown> : null;
}

function firstArray(...values: unknown[]): TraceEvent[] {
  for (const value of values) {
    if (Array.isArray(value)) return value as TraceEvent[];
  }
  return [];
}

function firstFiniteNumber(...values: unknown[]): number | undefined {
  for (const value of values) {
    const number = typeof value === "number" ? value : Number(value);
    if (Number.isFinite(number)) return number;
  }
  return undefined;
}

function firstStringOrNumber(...values: unknown[]): string | number | null {
  for (const value of values) {
    if (typeof value === "number" && Number.isFinite(value)) return value;
    if (typeof value !== "string") continue;
    const text = value.trim();
    if (text) return text;
  }
  return null;
}

function firstBoolean(...values: unknown[]): boolean | undefined {
  for (const value of values) {
    if (typeof value === "boolean") return value;
  }
  return undefined;
}

function finalResponseText(value: unknown): string | null {
  if (!value || typeof value !== "object") return null;
  const record = value as Record<string, unknown>;
  return firstNonEmptyString(messageText(record.text), messageText(record.content), messageText(record.message));
}

function projectedAgentMessageText(input: { status: unknown; finalText?: string | null; errorText?: string | null; baseText?: string | null }): string {
  const text = firstNonEmptyString(input.finalText, input.baseText);
  if (!isFailureTerminalStatus(input.status)) return text ?? "";
  return firstNonEmptyString(input.errorText, text) ?? "";
}

function isFailureTerminalStatus(value: unknown): boolean {
  return ["failed", "blocked", "timeout", "canceled", "cancelled", "stale", "thread-resume-failed"].includes(String(value ?? "").trim().toLowerCase().replace(/_/gu, "-"));
}

function messageText(value: unknown): string | null {
  if (typeof value === "string") return value.trim() || null;
  if (typeof value === "number" || typeof value === "boolean") return String(value);
  if (Array.isArray(value)) return value.map(messageText).filter((item): item is string => Boolean(item)).join("\n") || null;
  if (value && typeof value === "object") {
    const record = value as Record<string, unknown>;
    return firstNonEmptyString(messageText(record.text), messageText(record.content), messageText(record.message), messageText(record.summary), messageText(record.preview), messageText(record.title));
  }
  return null;
}

export async function liveCall<T>(label: string, call: () => Promise<ApiResult<T>>): Promise<ApiResult<T>> {
  try {
    return await call();
  } catch (error) {
    return { ok: false, status: 0, data: null, error: `${label}: ${error instanceof Error ? error.message : String(error)}` };
  }
}

function traceHydrationProjectedSeq(trace: ChatMessage["runnerTrace"]): number {
  const rangeNext = Number(trace?.nextProjectedSeq ?? trace?.range?.toProjectedSeq);
  if (Number.isFinite(rangeNext) && rangeNext > 0 && trace?.hasMore === true) return Math.trunc(rangeNext);
  const events = Array.isArray(trace?.events) ? trace.events : [];
  return events.reduce((max, event) => {
    const seq = traceEventProjectedSeq(event);
    return Number.isFinite(seq) && seq > max ? Math.trunc(seq) : max;
  }, 0);
}

function traceNextProjectedSeq(result: AgentChatResultResponse, fallback: number): number {
  const direct = Number(result.nextProjectedSeq ?? result.range?.toProjectedSeq);
  if (Number.isFinite(direct) && direct >= 0) return Math.trunc(direct);
  const events = Array.isArray(result.events) ? result.events : Array.isArray(result.traceEvents) ? result.traceEvents : [];
  return events.reduce((max, event) => {
    const seq = traceEventProjectedSeq(event);
    return Number.isFinite(seq) && seq > max ? Math.trunc(seq) : max;
  }, fallback);
}

function traceEventProjectedSeq(event: TraceEvent): number {
  const seq = Number(event.projectedSeq ?? event.seq);
  return Number.isFinite(seq) ? Math.trunc(seq) : Number.NaN;
}

function makeMessage(role: ChatMessage["role"], text: string, status: ChatMessage["status"], extra: Partial<ChatMessage> = {}): ChatMessage {
  return { id: nextProtocolId("msg"), role, title: extra.title ?? (role === "user" ? "用户" : "Code Agent"), text, status, createdAt: new Date().toISOString(), ...extra };
}

function isTraceActiveStatus(status: unknown): boolean {
  return ["accepted", "pending", "queued", "dispatching", "streaming", "processing", "running", "retrying", "busy", "creating"].includes(String(status ?? "").trim().toLowerCase().replace(/_/gu, "-"));
}

function readString(key: string, fallback: string): string {
  try {
    return localStorage.getItem(key) ?? fallback;
  } catch {
    return fallback;
  }
}

function readRecentDrafts(): DraftEntry[] {
  try {
    const raw = localStorage.getItem(RECENT_DRAFTS_STORAGE_KEY);
    return raw ? normalizeRecentDrafts(JSON.parse(raw)) : [];
  } catch {
    return [];
  }
}

function writeRecentDrafts(drafts: DraftEntry[]): void {
  try { localStorage.setItem(RECENT_DRAFTS_STORAGE_KEY, JSON.stringify(drafts)); } catch { /* ignore */ }
}

function readNumber(key: string, fallback: number): number {
  try {
    const value = Number(localStorage.getItem(key));
    return Number.isFinite(value) && value > 0 ? value : fallback;
  } catch {
    return fallback;
  }
}
