// @vitest-environment happy-dom

import type {
  EasyFlowAguiResumeOptions,
  EasyFlowAguiRunOptions,
} from '../shared/agent-agui/client';

import { useUserStore } from '@easyflow/stores';

import { EventType } from '@ag-ui/client';
import { createPinia, setActivePinia } from 'pinia';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

import { clearAgentChatBrowserCache } from '#/utils/agent-chat-cache';

import { easyFlowAguiCustomEvent } from '../shared/agent-agui/custom-events';
import { agentChatRuntimeManager } from './agentChatRuntimeManager';

const aguiMocks = vi.hoisted(() => ({
  abort: vi.fn(),
  cancel: vi.fn(async () => undefined),
  detach: vi.fn(),
  resume: vi.fn(),
  run: vi.fn(),
}));

vi.mock('../shared/agent-agui/client', () => ({
  createAguiRunId: () => 'run-test',
  EasyFlowAguiHttpError: class extends Error {
    constructor(
      message: string,
      public readonly status: number,
    ) {
      super(message);
    }
  },
  EasyFlowAguiProjectionError: class extends Error {
    constructor(public readonly projectionCause: unknown) {
      super('Agent 事件投影失败');
    }
  },
  EasyFlowAguiClient: class {
    abort = aguiMocks.abort;
    cancel = aguiMocks.cancel;
    detach = aguiMocks.detach;
    resume = aguiMocks.resume;
    run = aguiMocks.run;
  },
}));

vi.mock('./api', () => ({
  generateAgentSessionId: vi.fn(),
}));

describe('agentChatRuntimeManager', () => {
  beforeEach(() => {
    setActivePinia(createPinia());
    sessionStorage.clear();
    vi.clearAllMocks();
    aguiMocks.cancel.mockResolvedValue(undefined);
    aguiMocks.resume.mockImplementation(() => new Promise(() => {}));
    aguiMocks.run.mockImplementation(() => new Promise(() => {}));
  });

  afterEach(() => {
    vi.useRealTimers();
  });

  it('发起请求后立即创建带起始时间的助手轮次', async () => {
    vi.useFakeTimers();
    vi.setSystemTime(12_000);
    aguiMocks.run.mockImplementation(() => new Promise(() => {}));
    useUserStore().setUserInfo({
      avatar: '',
      id: 'timer-user',
      loginName: 'timer-user',
      nickname: '计时用户',
      tenantId: 'tenant-1',
    });

    await agentChatRuntimeManager.start({
      agentId: 'agent-1',
      prompt: '计时测试',
      sessionId: 'timer-session',
    });

    const assistant = agentChatRuntimeManager
      .getSnapshot('timer-session')
      ?.items.find(
        (item) => item.type === 'message' && item.role === 'assistant',
      );
    expect(assistant).toEqual(
      expect.objectContaining({
        roundId: expect.any(String),
        status: 'streaming',
        turnStartedAt: 12_000,
      }),
    );
  });

  it('投影 AG-UI 输入确认并按账号隔离快照', async () => {
    let runOptions: EasyFlowAguiRunOptions | undefined;
    aguiMocks.run.mockImplementation((options: EasyFlowAguiRunOptions) => {
      runOptions = options;
      return new Promise(() => {});
    });
    const userStore = useUserStore();
    const firstAccount = {
      avatar: '',
      id: 'user-1',
      loginName: 'admin',
      nickname: '管理员',
      tenantId: 'tenant-1',
    };
    userStore.setUserInfo(firstAccount);

    await agentChatRuntimeManager.start({
      agentId: 'agent-1',
      documentUploadIds: ['document-upload-1'],
      documents: [{ name: 'draft.docx', status: 'ready' }],
      images: [
        {
          name: 'draft.png',
          previewUrl: '/draft.png',
          status: 'ready',
        },
      ],
      prompt: '识别图片',
      sessionId: '101',
    });
    runOptions?.onEvent({
      name: easyFlowAguiCustomEvent.inputAccepted,
      type: EventType.CUSTOM,
      value: {
        attachments: [
          {
            attachmentRef: 'formal:101:201:document:0',
            name: 'draft.docx',
            readSnapshotId: 'snapshot-1',
          },
        ],
        images: [
          {
            imageRef: 'formal:101:201:0:png',
            name: 'draft.png',
            previewUrl: '/formal.png',
          },
        ],
      },
    });

    const accepted = agentChatRuntimeManager.getSnapshot('101');
    const userMessage = accepted?.items.find(
      (item) => item.type === 'message' && item.role === 'user',
    );
    expect(userMessage?.type === 'message' && userMessage.images?.[0]).toEqual(
      expect.objectContaining({ imageRef: 'formal:101:201:0:png' }),
    );
    expect(
      userMessage?.type === 'message' && userMessage.documents?.[0],
    ).toEqual(expect.objectContaining({ readSnapshotId: 'snapshot-1' }));
    expect(runOptions?.forwardedProps).toEqual(
      expect.objectContaining({
        easyflow: expect.objectContaining({
          input: expect.objectContaining({
            documentUploadIds: ['document-upload-1'],
          }),
        }),
      }),
    );

    userStore.setUserInfo({
      ...firstAccount,
      id: 'user-2',
      loginName: 'other',
    });
    expect(agentChatRuntimeManager.getSnapshot('101')).toBeUndefined();

    clearAgentChatBrowserCache(firstAccount);
    userStore.setUserInfo(firstAccount);
    expect(agentChatRuntimeManager.getSnapshot('101')).toBeUndefined();
  });

  it('输入已确认后将模型错误转换为可重试的用户提示', async () => {
    let resolveRun: (() => void) | undefined;
    let runOptions: EasyFlowAguiRunOptions | undefined;
    aguiMocks.run.mockImplementation((options: EasyFlowAguiRunOptions) => {
      runOptions = options;
      return new Promise<void>((resolve) => {
        resolveRun = resolve;
      });
    });
    useUserStore().setUserInfo({
      avatar: '',
      id: 'retry-user',
      loginName: 'retry-user',
      nickname: '重试用户',
      tenantId: 'tenant-1',
    });

    await agentChatRuntimeManager.start({
      agentId: 'agent-1',
      prompt: '介绍文档',
      sessionId: 'retry-session',
    });
    runOptions?.onEvent({
      name: easyFlowAguiCustomEvent.inputAccepted,
      type: EventType.CUSTOM,
      value: {},
    });
    runOptions?.onEvent({
      code: 'MODEL_ERROR',
      message: 'Retries exhausted: 2/2',
      runId: 'run-test',
      threadId: 'retry-session',
      type: EventType.RUN_ERROR,
    });
    resolveRun?.();
    await Promise.resolve();
    await Promise.resolve();

    const snapshot = agentChatRuntimeManager.getSnapshot('retry-session');
    expect(snapshot).toEqual(
      expect.objectContaining({
        error: '模型连接异常',
        retryContextReady: true,
        terminalOutcome: 'failed',
      }),
    );
    expect(snapshot?.items.find((item) => item.type === 'error')).toEqual(
      expect.objectContaining({ message: '模型连接异常' }),
    );
  });

  it('刷新恢复已确认输入时重新触发草稿清理回调', async () => {
    const account = {
      avatar: '',
      id: 'accepted-restore-user',
      loginName: 'accepted-restore-user',
      nickname: '输入恢复用户',
      tenantId: 'tenant-1',
    };
    useUserStore().setUserInfo(account);
    const sessionId = 'accepted-restore-session';
    sessionStorage.setItem(
      `easyflow:agent-chat-runtime:tenant-1:accepted-restore-user:${sessionId}`,
      JSON.stringify({
        agentId: 'agent-1',
        completed: false,
        inputAccepted: true,
        items: [],
        prompt: '刷新前输入',
        runId: 'run-accepted-restore',
        roundId: 'round-accepted-restore',
        sending: true,
        sessionId,
        startedAt: Date.now(),
        updatedAt: Date.now(),
        version: 4,
      }),
    );
    aguiMocks.resume.mockImplementation(() => new Promise(() => {}));
    const onInputAccepted = vi.fn();

    agentChatRuntimeManager.resume(sessionId, onInputAccepted);

    expect(onInputAccepted).toHaveBeenCalledTimes(1);
    expect(aguiMocks.resume).toHaveBeenCalledWith(
      expect.objectContaining({ runId: 'run-accepted-restore' }),
    );
    clearAgentChatBrowserCache(account);
  });

  it('硬刷新后从持久化游标续传且保留已投影正文', async () => {
    const account = {
      avatar: '',
      id: 'cursor-restore-user',
      loginName: 'cursor-restore-user',
      nickname: '游标恢复用户',
      tenantId: 'tenant-1',
    };
    useUserStore().setUserInfo(account);
    const sessionId = 'cursor-restore-session';
    sessionStorage.setItem(
      `easyflow:agent-chat-runtime:tenant-1:cursor-restore-user:${sessionId}`,
      JSON.stringify({
        agentId: 'agent-1',
        completed: false,
        inputAccepted: false,
        items: [
          {
            id: 'assistant-cursor',
            parts: [
              {
                content: '刷新前正文',
                id: 'text-cursor',
                type: 'text',
              },
            ],
            role: 'assistant',
            roundId: 'round-cursor',
            status: 'streaming',
            type: 'message',
          },
        ],
        lastCursor: 9,
        prompt: '游标恢复',
        projectionToolArgs: {},
        projectionToolNames: {},
        runId: 'run-cursor-restore',
        roundId: 'round-cursor',
        sending: true,
        sessionId,
        startedAt: Date.now(),
        updatedAt: Date.now(),
        version: 5,
      }),
    );
    aguiMocks.resume.mockImplementationOnce(
      async (options: EasyFlowAguiResumeOptions) => {
        expect(options.afterCursor).toBe(9);
        options.onEvent({
          delta: '续传正文',
          messageId: 'assistant-cursor',
          type: EventType.TEXT_MESSAGE_CONTENT,
        });
        options.onCursor?.(10);
        options.onEvent({
          runId: 'run-cursor-restore',
          threadId: sessionId,
          type: EventType.RUN_FINISHED,
        });
        options.onCursor?.(11);
      },
    );

    agentChatRuntimeManager.resume(sessionId);

    expect(aguiMocks.resume).toHaveBeenCalledTimes(1);
    expect(aguiMocks.resume).toHaveBeenCalledWith(
      expect.objectContaining({ afterCursor: 9 }),
    );

    await vi.waitFor(() => {
      expect(agentChatRuntimeManager.getSnapshot(sessionId)).toEqual(
        expect.objectContaining({ completed: true, lastCursor: 11 }),
      );
    });
    const timeline = JSON.stringify(
      agentChatRuntimeManager.getSnapshot(sessionId)?.items,
    );
    expect(timeline).toContain('刷新前正文续传正文');
    clearAgentChatBrowserCache(account);
  });

  it('短暂断网超过旧重试窗口后仍按游标恢复到服务端终态', async () => {
    vi.useFakeTimers();
    const account = {
      avatar: '',
      id: 'reconnect-user',
      loginName: 'reconnect-user',
      nickname: '重连用户',
      tenantId: 'tenant-1',
    };
    useUserStore().setUserInfo(account);
    aguiMocks.run.mockRejectedValueOnce(new TypeError('Failed to fetch'));
    aguiMocks.resume
      .mockRejectedValueOnce(new TypeError('Network unavailable'))
      .mockRejectedValueOnce(new TypeError('Network unavailable'))
      .mockImplementationOnce(async (options: EasyFlowAguiResumeOptions) => {
        await options.onEvent({
          runId: 'run-test',
          threadId: 'reconnect-session',
          type: EventType.RUN_FINISHED,
        });
      });

    await agentChatRuntimeManager.start({
      agentId: 'agent-1',
      prompt: '断网恢复',
      sessionId: 'reconnect-session',
    });
    await vi.advanceTimersByTimeAsync(1000);

    expect(aguiMocks.resume).toHaveBeenCalledTimes(3);
    expect(agentChatRuntimeManager.getSnapshot('reconnect-session')).toEqual(
      expect.objectContaining({ completed: true, sending: false }),
    );
    clearAgentChatBrowserCache(account);
  });

  it('合并流式通知并在 AG-UI 终态立即持久化', async () => {
    vi.useFakeTimers();
    let runOptions: EasyFlowAguiRunOptions | undefined;
    let resolveRun: (() => void) | undefined;
    aguiMocks.run.mockImplementation((options: EasyFlowAguiRunOptions) => {
      runOptions = options;
      return new Promise<void>((resolve) => {
        resolveRun = resolve;
      });
    });
    const account = {
      avatar: '',
      id: 'stream-user',
      loginName: 'stream-user',
      nickname: '流式用户',
      tenantId: 'tenant-1',
    };
    useUserStore().setUserInfo(account);

    await agentChatRuntimeManager.start({
      agentId: 'agent-1',
      prompt: '流式测试',
      sessionId: 'stream-session',
    });
    const listener = vi.fn();
    const unsubscribe = agentChatRuntimeManager.subscribe(listener);
    const storageSpy = vi.spyOn(sessionStorage, 'setItem');

    for (const delta of ['A', 'B', 'C']) {
      runOptions?.onEvent({
        delta,
        messageId: 'assistant-1',
        type: EventType.TEXT_MESSAGE_CONTENT,
      });
    }
    expect(listener).not.toHaveBeenCalled();
    expect(
      JSON.stringify(
        agentChatRuntimeManager.getSnapshot('stream-session')?.items,
      ),
    ).toContain('ABC');

    await vi.advanceTimersByTimeAsync(50);
    expect(listener).toHaveBeenCalledTimes(1);
    await vi.advanceTimersByTimeAsync(250);
    expect(storageSpy).toHaveBeenCalledTimes(2);

    listener.mockClear();
    storageSpy.mockClear();
    runOptions?.onEvent({
      runId: 'run-1',
      threadId: 'stream-session',
      type: EventType.RUN_FINISHED,
    });
    resolveRun?.();
    await Promise.resolve();
    await Promise.resolve();

    expect(listener).toHaveBeenCalledTimes(1);
    expect(storageSpy).toHaveBeenCalledTimes(2);
    expect(agentChatRuntimeManager.getSnapshot('stream-session')).toEqual(
      expect.objectContaining({ completed: true, sending: false }),
    );

    unsubscribe();
    storageSpy.mockRestore();
    clearAgentChatBrowserCache(account);
  });

  it('工具开始事件立即通知页面并保留调用中快照', async () => {
    vi.useFakeTimers();
    let runOptions: EasyFlowAguiRunOptions | undefined;
    aguiMocks.run.mockImplementation((options: EasyFlowAguiRunOptions) => {
      runOptions = options;
      return new Promise(() => {});
    });
    const account = {
      avatar: '',
      id: 'tool-status-user',
      loginName: 'tool-status-user',
      nickname: '工具状态用户',
      tenantId: 'tenant-1',
    };
    useUserStore().setUserInfo(account);

    await agentChatRuntimeManager.start({
      agentId: 'agent-1',
      prompt: '生成文件',
      sessionId: 'tool-status-session',
    });
    const listener = vi.fn();
    const unsubscribe = agentChatRuntimeManager.subscribe(listener);

    runOptions?.onEvent({
      toolCallId: 'write-call-1',
      toolCallName: 'write_text_file',
      type: EventType.TOOL_CALL_START,
    });

    expect(listener).toHaveBeenCalledTimes(1);
    expect(
      agentChatRuntimeManager
        .getSnapshot('tool-status-session')
        ?.items.find(
          (item) => item.type === 'tool' && item.toolCallId === 'write-call-1',
        ),
    ).toEqual(expect.objectContaining({ status: 'running' }));

    unsubscribe();
    clearAgentChatBrowserCache(account);
  });

  it('停止后同会话重发不会接收旧运行的迟到事件', async () => {
    const runs: EasyFlowAguiRunOptions[] = [];
    aguiMocks.run.mockImplementation((options: EasyFlowAguiRunOptions) => {
      runs.push(options);
      return new Promise(() => {});
    });
    useUserStore().setUserInfo({
      avatar: '',
      id: 'race-user',
      loginName: 'race-user',
      nickname: '竞态用户',
      tenantId: 'tenant-1',
    });
    aguiMocks.resume.mockImplementationOnce(
      async (options: EasyFlowAguiResumeOptions) => {
        options.onEvent({
          code: 'RUN_CANCELLED',
          message: '用户已停止生成',
          runId: 'run-test',
          threadId: 'race-session',
          type: EventType.RUN_ERROR,
        });
      },
    );

    await agentChatRuntimeManager.start({
      agentId: 'agent-1',
      prompt: '旧问题',
      sessionId: 'race-session',
    });
    await agentChatRuntimeManager.stop('race-session');
    await Promise.resolve();
    await Promise.resolve();
    await agentChatRuntimeManager.start({
      agentId: 'agent-1',
      prompt: '新问题',
      sessionId: 'race-session',
    });

    runs[0]?.onEvent({
      delta: '旧流迟到正文',
      messageId: 'old-assistant',
      type: EventType.TEXT_MESSAGE_CONTENT,
    });
    runs[1]?.onEvent({
      delta: '新流正文',
      messageId: 'new-assistant',
      type: EventType.TEXT_MESSAGE_CONTENT,
    });

    const snapshot = JSON.stringify(
      agentChatRuntimeManager.getSnapshot('race-session')?.items,
    );
    expect(snapshot).toContain('新流正文');
    expect(snapshot).not.toContain('旧流迟到正文');
  });

  it('用户取消的 AG-UI 终态不会被异步完成回调改写为成功', async () => {
    let runOptions: EasyFlowAguiRunOptions | undefined;
    let resolveRun: (() => void) | undefined;
    aguiMocks.run.mockImplementation((options: EasyFlowAguiRunOptions) => {
      runOptions = options;
      return new Promise<void>((resolve) => {
        resolveRun = resolve;
      });
    });
    useUserStore().setUserInfo({
      avatar: '',
      id: 'cancel-user',
      loginName: 'cancel-user',
      nickname: '取消用户',
      tenantId: 'tenant-1',
    });

    await agentChatRuntimeManager.start({
      agentId: 'agent-1',
      prompt: '需要审批的任务',
      sessionId: 'cancel-session',
    });
    runOptions?.onEvent({
      delta: '准备执行',
      messageId: 'assistant-1',
      type: EventType.TEXT_MESSAGE_CONTENT,
    });
    runOptions?.onEvent({
      code: 'RUN_CANCELLED',
      message: '用户拒绝执行',
      runId: 'run-1',
      threadId: 'cancel-session',
      type: EventType.RUN_ERROR,
    });
    resolveRun?.();
    await Promise.resolve();
    await Promise.resolve();

    const assistant = agentChatRuntimeManager
      .getSnapshot('cancel-session')
      ?.items.find(
        (item) => item.type === 'message' && item.role === 'assistant',
      );
    expect(assistant).toEqual(
      expect.objectContaining({
        turnSucceeded: false,
      }),
    );
  });

  it('取消请求失败且原流无终态时保留等待期间事件并立即恢复订阅', async () => {
    let rejectCancel: ((error: Error) => void) | undefined;
    let resolveRun: (() => void) | undefined;
    let runOptions: EasyFlowAguiRunOptions | undefined;
    aguiMocks.run.mockImplementation((options: EasyFlowAguiRunOptions) => {
      runOptions = options;
      return new Promise<void>((resolve) => {
        resolveRun = resolve;
      });
    });
    aguiMocks.detach.mockImplementationOnce(() => resolveRun?.());
    aguiMocks.cancel.mockImplementation(
      () =>
        new Promise<undefined>((_resolve, reject) => {
          rejectCancel = reject;
        }),
    );
    aguiMocks.resume.mockImplementation(() => new Promise(() => {}));
    useUserStore().setUserInfo({
      avatar: '',
      id: 'cancel-failed-user',
      loginName: 'cancel-failed-user',
      nickname: '取消失败用户',
      tenantId: 'tenant-1',
    });
    await agentChatRuntimeManager.start({
      agentId: 'agent-1',
      prompt: '取消失败恢复',
      sessionId: 'cancel-failed-session',
    });

    const stopPromise = agentChatRuntimeManager.stop('cancel-failed-session');
    runOptions?.onEvent({
      delta: '取消等待期间正文',
      messageId: 'assistant-cancel-failed',
      type: EventType.TEXT_MESSAGE_CONTENT,
    });
    runOptions?.onCursor?.(1);
    rejectCancel?.(new Error('取消接口暂时不可用'));

    await expect(stopPromise).rejects.toThrow('取消接口暂时不可用');
    await Promise.resolve();
    await Promise.resolve();
    expect(
      JSON.stringify(
        agentChatRuntimeManager.getSnapshot('cancel-failed-session')?.items,
      ),
    ).toContain('取消等待期间正文');
    expect(
      agentChatRuntimeManager.getSnapshot('cancel-failed-session'),
    ).toEqual(expect.objectContaining({ sending: true }));
    expect(aguiMocks.resume).toHaveBeenCalledTimes(1);
  });

  it('取消成功后重放服务端终态且不把尚未投影的自然完成改成取消', async () => {
    aguiMocks.resume.mockImplementationOnce(
      async (options: EasyFlowAguiResumeOptions) => {
        options.onEvent({
          runId: 'run-test',
          threadId: 'cancel-authoritative-session',
          type: EventType.RUN_FINISHED,
        });
        options.onCursor?.(8);
      },
    );
    useUserStore().setUserInfo({
      avatar: '',
      id: 'cancel-authoritative-user',
      loginName: 'cancel-authoritative-user',
      nickname: '取消终态仲裁用户',
      tenantId: 'tenant-1',
    });
    await agentChatRuntimeManager.start({
      agentId: 'agent-1',
      prompt: '取消时服务端已经自然完成',
      sessionId: 'cancel-authoritative-session',
    });

    await agentChatRuntimeManager.stop('cancel-authoritative-session');
    await Promise.resolve();
    await Promise.resolve();

    expect(
      agentChatRuntimeManager.getSnapshot('cancel-authoritative-session'),
    ).toEqual(
      expect.objectContaining({
        completed: true,
        lastCursor: 8,
        sending: false,
        terminalOutcome: 'success',
      }),
    );
    expect(
      JSON.stringify(
        agentChatRuntimeManager.getSnapshot('cancel-authoritative-session')
          ?.items,
      ),
    ).not.toContain('"turnSucceeded":false');
  });

  it('取消请求失败但原流已到终态时直接展示权威完成态', async () => {
    let rejectCancel: ((error: Error) => void) | undefined;
    let resolveRun: (() => void) | undefined;
    let runOptions: EasyFlowAguiRunOptions | undefined;
    aguiMocks.run.mockImplementation((options: EasyFlowAguiRunOptions) => {
      runOptions = options;
      return new Promise<void>((resolve) => {
        resolveRun = resolve;
      });
    });
    aguiMocks.cancel.mockImplementation(
      () =>
        new Promise<undefined>((_resolve, reject) => {
          rejectCancel = reject;
        }),
    );
    useUserStore().setUserInfo({
      avatar: '',
      id: 'cancel-terminal-user',
      loginName: 'cancel-terminal-user',
      nickname: '取消终态用户',
      tenantId: 'tenant-1',
    });
    await agentChatRuntimeManager.start({
      agentId: 'agent-1',
      prompt: '取消与完成竞态',
      sessionId: 'cancel-terminal-session',
    });

    const stopPromise = agentChatRuntimeManager.stop('cancel-terminal-session');
    runOptions?.onEvent({
      runId: 'run-test',
      threadId: 'cancel-terminal-session',
      type: EventType.RUN_FINISHED,
    });
    runOptions?.onCursor?.(1);
    resolveRun?.();
    await Promise.resolve();
    rejectCancel?.(new Error('取消接口晚到失败'));

    await expect(stopPromise).rejects.toThrow('取消接口晚到失败');
    expect(
      agentChatRuntimeManager.getSnapshot('cancel-terminal-session'),
    ).toEqual(
      expect.objectContaining({
        completed: true,
        sending: false,
        terminalOutcome: 'success',
      }),
    );
    expect(aguiMocks.resume).not.toHaveBeenCalled();
  });

  it('首条传输闪断后按最后游标恢复且不会重复投影', async () => {
    aguiMocks.run.mockImplementation(
      async (options: EasyFlowAguiRunOptions) => {
        options.onEvent({
          delta: '前半段',
          messageId: 'assistant-1',
          type: EventType.TEXT_MESSAGE_CONTENT,
        });
        options.onCursor?.(7);
        throw new Error('network reset');
      },
    );
    aguiMocks.resume.mockImplementation(
      async (options: EasyFlowAguiResumeOptions) => {
        expect(options.afterCursor).toBe(7);
        options.onEvent({
          delta: '后半段',
          messageId: 'assistant-1',
          type: EventType.TEXT_MESSAGE_CONTENT,
        });
        options.onEvent({
          runId: 'run-test',
          threadId: 'recover-session',
          type: EventType.RUN_FINISHED,
        });
      },
    );
    useUserStore().setUserInfo({
      avatar: '',
      id: 'recover-user',
      loginName: 'recover-user',
      nickname: '恢复用户',
      tenantId: 'tenant-1',
    });

    await agentChatRuntimeManager.start({
      agentId: 'agent-1',
      prompt: '恢复测试',
      sessionId: 'recover-session',
    });
    await vi.waitFor(() => {
      expect(aguiMocks.resume).toHaveBeenCalledTimes(1);
      expect(agentChatRuntimeManager.getSnapshot('recover-session')).toEqual(
        expect.objectContaining({ completed: true, sending: false }),
      );
    });

    const timeline = JSON.stringify(
      agentChatRuntimeManager.getSnapshot('recover-session')?.items,
    );
    expect(timeline).toContain('前半段后半段');
    expect(timeline.match(/前半段/g)).toHaveLength(1);
  });

  it('pagehide 只断开页面传输并保留可恢复运行态', async () => {
    aguiMocks.run.mockImplementation(() => new Promise(() => {}));
    aguiMocks.resume.mockImplementation(() => new Promise(() => {}));
    useUserStore().setUserInfo({
      avatar: '',
      id: 'reload-user',
      loginName: 'reload-user',
      nickname: '刷新用户',
      tenantId: 'tenant-1',
    });
    await agentChatRuntimeManager.start({
      agentId: 'agent-1',
      prompt: '刷新测试',
      sessionId: 'reload-session',
    });

    globalThis.dispatchEvent(new Event('pagehide'));

    expect(aguiMocks.detach).toHaveBeenCalled();
    expect(agentChatRuntimeManager.getSnapshot('reload-session')).toEqual(
      expect.objectContaining({ completed: false, sending: true }),
    );

    globalThis.dispatchEvent(new Event('pageshow'));
    expect(aguiMocks.resume).toHaveBeenCalled();
  });
});
