import { describe, expect, it, vi } from 'vitest';

import { OpenCodeTaskLogStreamSource } from '../../../../src/main/services/team/taskLogs/stream/OpenCodeTaskLogStreamSource';

import type { TeamTask } from '../../../../src/shared/types';
import type { OpenCodeRuntimeTranscriptLogMessage } from '../../../../src/main/services/runtime/ClaudeMultimodelBridgeService';
import type { OpenCodeTaskLogAttributionRecord } from '../../../../src/main/services/team/taskLogs/stream/OpenCodeTaskLogAttributionStore';

function createTask(overrides: Partial<TeamTask> = {}): TeamTask {
  return {
    id: 'task-a',
    displayId: 'task-a',
    subject: 'Investigate failing command',
    owner: 'alice',
    status: 'in_progress',
    createdAt: '2026-04-21T09:59:00.000Z',
    updatedAt: '2026-04-21T10:10:00.000Z',
    workIntervals: [
      {
        startedAt: '2026-04-21T10:00:00.000Z',
        completedAt: '2026-04-21T10:10:00.000Z',
      },
    ],
    ...overrides,
  };
}

function textLogMessage(
  overrides: Pick<OpenCodeRuntimeTranscriptLogMessage, 'uuid' | 'timestamp'> &
    Partial<OpenCodeRuntimeTranscriptLogMessage>
): OpenCodeRuntimeTranscriptLogMessage {
  const type = overrides.type ?? 'assistant';
  return {
    uuid: overrides.uuid,
    parentUuid: overrides.parentUuid ?? null,
    type,
    timestamp: overrides.timestamp,
    role: overrides.role ?? type,
    content: overrides.content ?? [{ type: 'text', text: overrides.uuid }],
    isMeta: overrides.isMeta ?? false,
    sessionId: overrides.sessionId ?? 'session-opencode',
    toolCalls: overrides.toolCalls ?? [],
    toolResults: overrides.toolResults ?? [],
    ...(overrides.sourceToolUseID ? { sourceToolUseID: overrides.sourceToolUseID } : {}),
    ...(overrides.sourceToolAssistantUUID
      ? { sourceToolAssistantUUID: overrides.sourceToolAssistantUUID }
      : {}),
  };
}

function taskMarkerLogMessage({
  uuid,
  parentUuid = null,
  timestamp,
  toolName,
  input,
}: {
  uuid: string;
  parentUuid?: string | null;
  timestamp: string;
  toolName: string;
  input: Record<string, unknown>;
}): OpenCodeRuntimeTranscriptLogMessage {
  const toolId = `${uuid}-tool`;
  return textLogMessage({
    uuid,
    parentUuid,
    timestamp,
    content: [
      {
        type: 'tool_use',
        id: toolId,
        name: toolName,
        input,
      },
    ],
    toolCalls: [
      {
        id: toolId,
        name: toolName,
        input,
        isTask: false,
      },
    ],
  });
}

function toolResultLogMessage({
  uuid,
  parentUuid,
  timestamp,
  sourceToolAssistantUUID,
}: {
  uuid: string;
  parentUuid: string;
  timestamp: string;
  sourceToolAssistantUUID: string;
}): OpenCodeRuntimeTranscriptLogMessage {
  const toolUseId = `${sourceToolAssistantUUID}-tool`;
  return textLogMessage({
    uuid,
    parentUuid,
    type: 'user',
    role: 'user',
    timestamp,
    content: [
      {
        type: 'tool_result',
        tool_use_id: toolUseId,
        content: 'ok',
      },
    ],
    isMeta: true,
    toolResults: [
      {
        toolUseId,
        content: 'ok',
        isError: false,
      },
    ],
    sourceToolUseID: toolUseId,
    sourceToolAssistantUUID,
  });
}

describe('OpenCodeTaskLogStreamSource', () => {
  it('projects OpenCode runtime logs into a task stream segment and caches repeated reads', async () => {
    const bridge = {
      getOpenCodeTranscript: vi.fn(async () => ({
        sessionId: 'session-opencode',
        logProjection: {
          messages: [
            {
              uuid: 'assistant-1',
              parentUuid: 'user-1',
              type: 'assistant',
              timestamp: '2026-04-21T10:05:00.000Z',
              role: 'assistant',
              content: [
                { type: 'thinking', thinking: 'Inspecting repository', signature: 'opencode' },
                { type: 'text', text: 'Running the check now.' },
                {
                  type: 'tool_use',
                  id: 'tool-1',
                  name: 'bash',
                  input: { command: 'npm test' },
                },
              ],
              isMeta: false,
              sessionId: 'session-opencode',
              toolCalls: [
                {
                  id: 'tool-1',
                  name: 'bash',
                  input: { command: 'npm test' },
                  isTask: false,
                },
              ],
              toolResults: [],
            },
            {
              uuid: 'assistant-1::tool_results',
              parentUuid: 'assistant-1',
              type: 'user',
              timestamp: '2026-04-21T10:05:03.000Z',
              role: 'user',
              content: [
                {
                  type: 'tool_result',
                  tool_use_id: 'tool-1',
                  content: 'ok',
                },
              ],
              isMeta: true,
              sessionId: 'session-opencode',
              toolCalls: [],
              toolResults: [
                {
                  toolUseId: 'tool-1',
                  content: 'ok',
                  isError: false,
                },
              ],
              sourceToolUseID: 'tool-1',
              sourceToolAssistantUUID: 'assistant-1',
            },
            {
              uuid: 'assistant-outside-window',
              parentUuid: 'user-2',
              type: 'assistant',
              timestamp: '2026-04-21T08:00:00.000Z',
              role: 'assistant',
              content: [{ type: 'text', text: 'Old task output' }],
              isMeta: false,
              sessionId: 'session-opencode',
              toolCalls: [],
              toolResults: [],
            },
          ],
        },
      })),
    };

    const chunkBuilder = {
      buildBundleChunks: vi.fn((messages) => [
        {
          id: 'chunk-1',
          kind: 'assistant',
          messages,
        },
      ]),
    };

    const taskReader = {
      getTasks: vi.fn(async () => [createTask()]),
      getDeletedTasks: vi.fn(async () => []),
    };

    const source = new OpenCodeTaskLogStreamSource(
      bridge as never,
      { resolve: async () => '/tmp/claude' },
      taskReader as never,
      chunkBuilder as never,
      { readTaskRecords: vi.fn(async () => []) }
    );

    const first = await source.getTaskLogStream('team-a', 'task-a');
    const second = await source.getTaskLogStream('team-a', 'task-a');

    expect(first?.source).toBe('opencode_runtime_fallback');
    expect(first?.runtimeProjection).toEqual({
      provider: 'opencode',
      mode: 'heuristic',
      attributionRecordCount: 0,
      projectedMessageCount: 2,
      boardMcpToolCount: 0,
      nativeToolCount: 1,
      fallbackReason: 'no_attribution_records',
    });
    expect(first?.participants).toEqual([
      {
        key: 'member:alice',
        label: 'alice',
        role: 'member',
        isLead: false,
        isSidechain: true,
      },
    ]);
    expect(first?.segments).toHaveLength(1);
    expect(first?.segments[0]?.actor).toEqual({
      memberName: 'alice',
      role: 'member',
      sessionId: 'session-opencode',
      isSidechain: true,
    });
    expect(chunkBuilder.buildBundleChunks).toHaveBeenCalledTimes(1);
    expect(chunkBuilder.buildBundleChunks.mock.calls[0]?.[0]).toHaveLength(2);
    expect(
      chunkBuilder.buildBundleChunks.mock.calls[0]?.[0].map(
        (message: { uuid: string }) => message.uuid
      )
    ).toEqual(['assistant-1', 'assistant-1::tool_results']);
    expect(bridge.getOpenCodeTranscript).toHaveBeenCalledTimes(1);
    expect(second).toEqual(first);
  });

  it('uses exact OpenCode session evidence from delivery ledgers before current-lane fallback', async () => {
    const bridge = {
      getOpenCodeTranscript: vi.fn(async (_binaryPath, request: { sessionId?: string }) => ({
        sessionId: request.sessionId ?? 'current-session',
        logProjection: {
          messages: [
            textLogMessage({
              uuid: 'runtime-user-evidence',
              type: 'user',
              role: 'user',
              timestamp: '2026-04-21T10:01:00.000Z',
              sessionId: 'session-from-ledger',
              content: [{ type: 'text', text: 'Start task-a now' }],
            }),
            taskMarkerLogMessage({
              uuid: 'assistant-start-evidence',
              parentUuid: 'runtime-user-evidence',
              timestamp: '2026-04-21T10:02:00.000Z',
              toolName: 'mcp__agent-teams__task_start',
              input: { teamName: 'team-a', taskId: 'task-a' },
            }),
            taskMarkerLogMessage({
              uuid: 'assistant-native-evidence',
              parentUuid: 'assistant-start-evidence',
              timestamp: '2026-04-21T10:03:00.000Z',
              toolName: 'bash',
              input: { command: 'pnpm test' },
            }),
          ].map((message) => ({
            ...message,
            sessionId: request.sessionId ?? message.sessionId,
          })),
        },
      })),
    };
    const chunkBuilder = {
      buildBundleChunks: vi.fn((messages) => [
        {
          id: 'chunk-ledger-session',
          kind: 'assistant',
          messages,
        },
      ]),
    };
    const sessionEvidenceSource = {
      readTaskRecords: vi.fn(async () => [
        {
          taskId: 'task-a',
          memberName: 'bob',
          scope: 'member_session_window',
          laneId: 'lane-from-ledger',
          sessionId: 'session-from-ledger',
          since: '2026-04-21T10:00:00.000Z',
          source: 'delivery_ledger',
          startMessageUuid: 'runtime-user-evidence',
        } satisfies OpenCodeTaskLogAttributionRecord,
      ]),
    };
    const source = new OpenCodeTaskLogStreamSource(
      bridge as never,
      { resolve: async () => '/tmp/claude' },
      {
        getTasks: async () => [createTask({ owner: undefined })],
        getDeletedTasks: async () => [],
      } as never,
      chunkBuilder as never,
      { readTaskRecords: vi.fn(async () => []) },
      sessionEvidenceSource
    );

    const response = await source.getTaskLogStream('team-a', 'task-a');

    expect(response?.source).toBe('opencode_runtime_attribution');
    expect(response?.segments[0]?.actor.sessionId).toBe('session-from-ledger');
    expect(response?.segments[0]?.id).toContain('session-from-ledger');
    expect(bridge.getOpenCodeTranscript).toHaveBeenCalledWith('/tmp/claude', {
      teamId: 'team-a',
      memberName: 'bob',
      limit: 500,
      laneId: 'lane-from-ledger',
      sessionId: 'session-from-ledger',
    });
    expect(
      chunkBuilder.buildBundleChunks.mock.calls[0]?.[0].map(
        (message: { uuid: string }) => message.uuid
      )
    ).toEqual(['runtime-user-evidence', 'assistant-start-evidence', 'assistant-native-evidence']);
  });

  it('sanitizes OpenCode delivery retry envelopes from projected task log text', async () => {
    const bridge = {
      getOpenCodeTranscript: vi.fn(async () => ({
        sessionId: 'session-opencode',
        logProjection: {
          messages: [
            textLogMessage({
              uuid: 'task-delivery',
              type: 'user',
              role: 'user',
              timestamp: '2026-04-21T10:05:00.000Z',
              content: [
                {
                  type: 'text',
                  text: [
                    '<opencode_inbound_app_message>',
                    '<opencode_delivery_retry>',
                    'This is retry attempt 3/3 for inbound app messageId "message-1".',
                    '</opencode_delivery_retry>',
                    '',
                    'New task assigned to you: #task-a Investigate failing command',
                    '</opencode_inbound_app_message>',
                  ].join('\n'),
                },
              ],
            }),
          ],
        },
      })),
    };
    const chunkBuilder = {
      buildBundleChunks: vi.fn((messages) => [
        {
          id: 'chunk-sanitized',
          kind: 'assistant',
          messages,
        },
      ]),
    };
    const source = new OpenCodeTaskLogStreamSource(
      bridge as never,
      { resolve: async () => '/tmp/claude' },
      {
        getTasks: async () => [createTask()],
        getDeletedTasks: async () => [],
      } as never,
      chunkBuilder as never,
      { readTaskRecords: vi.fn(async () => []) }
    );

    const response = await source.getTaskLogStream('team-a', 'task-a');

    expect(response?.source).toBe('opencode_runtime_fallback');
    const projectedMessage = chunkBuilder.buildBundleChunks.mock.calls[0]?.[0]?.[0] as
      | { content: Array<{ type: string; text?: string }> }
      | undefined;
    expect(projectedMessage?.content).toEqual([
      {
        type: 'text',
        text: 'New task assigned to you: #task-a Investigate failing command',
      },
    ]);
  });

  it('returns null when the task has no owner', async () => {
    const source = new OpenCodeTaskLogStreamSource(
      { getOpenCodeTranscript: vi.fn() } as never,
      { resolve: async () => '/tmp/claude' },
      {
        getTasks: async () => [createTask({ owner: undefined })],
        getDeletedTasks: async () => [],
      } as never,
      { buildBundleChunks: vi.fn() } as never,
      { readTaskRecords: vi.fn(async () => []) }
    );

    await expect(source.getTaskLogStream('team-a', 'task-a')).resolves.toBeNull();
  });

  it('narrows owner fallback to OpenCode task tool marker ranges when available', async () => {
    const bridge = {
      getOpenCodeTranscript: vi.fn(async () => ({
        sessionId: 'session-opencode',
        logProjection: {
          messages: [
            {
              uuid: 'unrelated-before',
              parentUuid: undefined,
              type: 'assistant',
              timestamp: '2026-04-21T10:01:00.000Z',
              role: 'assistant',
              content: [{ type: 'text', text: 'Other work before the task marker' }],
              isMeta: false,
              sessionId: 'session-opencode',
              toolCalls: [],
              toolResults: [],
            },
            {
              uuid: 'user-task-prompt',
              parentUuid: undefined,
              type: 'user',
              timestamp: '2026-04-21T10:02:00.000Z',
              role: 'user',
              content: [{ type: 'text', text: 'Start task-a now' }],
              isMeta: false,
              sessionId: 'session-opencode',
              toolCalls: [],
              toolResults: [],
            },
            {
              uuid: 'assistant-start',
              parentUuid: 'user-task-prompt',
              type: 'assistant',
              timestamp: '2026-04-21T10:03:00.000Z',
              role: 'assistant',
              content: [
                {
                  type: 'tool_use',
                  id: 'tool-start',
                  name: 'mcp__agent-teams__task_start',
                  input: { teamName: 'team-a', taskId: 'task-a' },
                },
              ],
              isMeta: false,
              sessionId: 'session-opencode',
              toolCalls: [
                {
                  id: 'tool-start',
                  name: 'mcp__agent-teams__task_start',
                  input: { teamName: 'team-a', taskId: 'task-a' },
                  isTask: false,
                },
              ],
              toolResults: [],
            },
            {
              uuid: 'assistant-start::tool_results',
              parentUuid: 'assistant-start',
              type: 'user',
              timestamp: '2026-04-21T10:03:01.000Z',
              role: 'user',
              content: [
                {
                  type: 'tool_result',
                  tool_use_id: 'tool-start',
                  content: 'started',
                },
              ],
              isMeta: true,
              sessionId: 'session-opencode',
              toolCalls: [],
              toolResults: [
                {
                  toolUseId: 'tool-start',
                  content: 'started',
                  isError: false,
                },
              ],
              sourceToolUseID: 'tool-start',
              sourceToolAssistantUUID: 'assistant-start',
            },
            {
              uuid: 'assistant-work',
              parentUuid: 'assistant-start::tool_results',
              type: 'assistant',
              timestamp: '2026-04-21T10:04:00.000Z',
              role: 'assistant',
              content: [{ type: 'text', text: 'Doing the actual work' }],
              isMeta: false,
              sessionId: 'session-opencode',
              toolCalls: [],
              toolResults: [],
            },
            {
              uuid: 'assistant-complete',
              parentUuid: 'assistant-work',
              type: 'assistant',
              timestamp: '2026-04-21T10:06:00.000Z',
              role: 'assistant',
              content: [
                {
                  type: 'tool_use',
                  id: 'tool-complete',
                  name: 'mcp__agent-teams__task_complete',
                  input: { teamName: 'team-a', taskId: 'task-a' },
                },
              ],
              isMeta: false,
              sessionId: 'session-opencode',
              toolCalls: [
                {
                  id: 'tool-complete',
                  name: 'mcp__agent-teams__task_complete',
                  input: { teamName: 'team-a', taskId: 'task-a' },
                  isTask: false,
                },
              ],
              toolResults: [],
            },
            {
              uuid: 'assistant-complete::tool_results',
              parentUuid: 'assistant-complete',
              type: 'user',
              timestamp: '2026-04-21T10:06:01.000Z',
              role: 'user',
              content: [
                {
                  type: 'tool_result',
                  tool_use_id: 'tool-complete',
                  content: 'completed',
                },
              ],
              isMeta: true,
              sessionId: 'session-opencode',
              toolCalls: [],
              toolResults: [
                {
                  toolUseId: 'tool-complete',
                  content: 'completed',
                  isError: false,
                },
              ],
              sourceToolUseID: 'tool-complete',
              sourceToolAssistantUUID: 'assistant-complete',
            },
            {
              uuid: 'unrelated-after',
              parentUuid: 'assistant-complete::tool_results',
              type: 'assistant',
              timestamp: '2026-04-21T10:07:00.000Z',
              role: 'assistant',
              content: [{ type: 'text', text: 'Other work after task completion' }],
              isMeta: false,
              sessionId: 'session-opencode',
              toolCalls: [],
              toolResults: [],
            },
          ],
        },
      })),
    };
    const chunkBuilder = {
      buildBundleChunks: vi.fn((messages) => [
        {
          id: 'chunk-marker',
          kind: 'assistant',
          messages,
        },
      ]),
    };
    const source = new OpenCodeTaskLogStreamSource(
      bridge as never,
      { resolve: async () => '/tmp/claude' },
      {
        getTasks: async () => [createTask()],
        getDeletedTasks: async () => [],
      } as never,
      chunkBuilder as never,
      { readTaskRecords: vi.fn(async () => []) }
    );

    const response = await source.getTaskLogStream('team-a', 'task-a');

    expect(response?.source).toBe('opencode_runtime_fallback');
    expect(response?.runtimeProjection).toEqual({
      provider: 'opencode',
      mode: 'heuristic',
      attributionRecordCount: 0,
      projectedMessageCount: 6,
      boardMcpToolCount: 2,
      nativeToolCount: 0,
      fallbackReason: 'task_tool_markers',
      markerMatchCount: 2,
      markerSpanCount: 1,
    });
    expect(
      chunkBuilder.buildBundleChunks.mock.calls[0]?.[0].map(
        (message: { uuid: string }) => message.uuid
      )
    ).toEqual([
      'user-task-prompt',
      'assistant-start',
      'assistant-start::tool_results',
      'assistant-work',
      'assistant-complete',
      'assistant-complete::tool_results',
    ]);
  });

  it('keeps native OpenCode tools near task markers in the task stream', async () => {
    const bridge = {
      getOpenCodeTranscript: vi.fn(async () => ({
        sessionId: 'session-opencode',
        logProjection: {
          messages: [
            taskMarkerLogMessage({
              uuid: 'native-before-task',
              timestamp: '2026-04-21T09:50:00.000Z',
              toolName: 'read',
              input: { filePath: '/tmp/unrelated.ts' },
            }),
            textLogMessage({
              uuid: 'user-task-prompt',
              type: 'user',
              role: 'user',
              timestamp: '2026-04-21T10:01:00.000Z',
              content: [{ type: 'text', text: 'Start task-a now' }],
            }),
            taskMarkerLogMessage({
              uuid: 'assistant-start',
              parentUuid: 'user-task-prompt',
              timestamp: '2026-04-21T10:02:00.000Z',
              toolName: 'mcp__agent-teams__task_start',
              input: { teamName: 'team-a', taskId: 'task-a' },
            }),
            toolResultLogMessage({
              uuid: 'assistant-start::tool_results',
              parentUuid: 'assistant-start',
              timestamp: '2026-04-21T10:02:01.000Z',
              sourceToolAssistantUUID: 'assistant-start',
            }),
            taskMarkerLogMessage({
              uuid: 'native-read',
              parentUuid: 'assistant-start::tool_results',
              timestamp: '2026-04-21T10:03:00.000Z',
              toolName: 'read',
              input: { filePath: '/tmp/app.ts' },
            }),
            toolResultLogMessage({
              uuid: 'native-read::tool_results',
              parentUuid: 'native-read',
              timestamp: '2026-04-21T10:03:01.000Z',
              sourceToolAssistantUUID: 'native-read',
            }),
            taskMarkerLogMessage({
              uuid: 'native-bash',
              parentUuid: 'native-read::tool_results',
              timestamp: '2026-04-21T10:04:00.000Z',
              toolName: 'bash',
              input: { command: 'pnpm test' },
            }),
            toolResultLogMessage({
              uuid: 'native-bash::tool_results',
              parentUuid: 'native-bash',
              timestamp: '2026-04-21T10:04:01.000Z',
              sourceToolAssistantUUID: 'native-bash',
            }),
            taskMarkerLogMessage({
              uuid: 'assistant-comment',
              parentUuid: 'native-bash::tool_results',
              timestamp: '2026-04-21T10:05:00.000Z',
              toolName: 'mcp__agent-teams__task_add_comment',
              input: { teamName: 'team-a', taskId: 'task-a', text: 'Tests passed' },
            }),
            toolResultLogMessage({
              uuid: 'assistant-comment::tool_results',
              parentUuid: 'assistant-comment',
              timestamp: '2026-04-21T10:05:01.000Z',
              sourceToolAssistantUUID: 'assistant-comment',
            }),
            taskMarkerLogMessage({
              uuid: 'native-after-task',
              timestamp: '2026-04-21T10:20:00.000Z',
              toolName: 'bash',
              input: { command: 'echo unrelated' },
            }),
          ],
        },
      })),
    };
    const chunkBuilder = {
      buildBundleChunks: vi.fn((messages) => [
        {
          id: 'chunk-native-tools',
          kind: 'assistant',
          messages,
        },
      ]),
    };
    const source = new OpenCodeTaskLogStreamSource(
      bridge as never,
      { resolve: async () => '/tmp/claude' },
      {
        getTasks: async () => [
          createTask({
            workIntervals: [
              {
                startedAt: '2026-04-21T10:00:00.000Z',
                completedAt: '2026-04-21T10:06:00.000Z',
              },
            ],
          }),
        ],
        getDeletedTasks: async () => [],
      } as never,
      chunkBuilder as never,
      { readTaskRecords: vi.fn(async () => []) }
    );

    const response = await source.getTaskLogStream('team-a', 'task-a');

    expect(response?.runtimeProjection).toEqual({
      provider: 'opencode',
      mode: 'heuristic',
      attributionRecordCount: 0,
      projectedMessageCount: 9,
      boardMcpToolCount: 2,
      nativeToolCount: 2,
      fallbackReason: 'task_tool_markers',
      markerMatchCount: 2,
      markerSpanCount: 1,
    });
    expect(
      chunkBuilder.buildBundleChunks.mock.calls[0]?.[0].map(
        (message: { uuid: string }) => message.uuid
      )
    ).toEqual([
      'user-task-prompt',
      'assistant-start',
      'assistant-start::tool_results',
      'native-read',
      'native-read::tool_results',
      'native-bash',
      'native-bash::tool_results',
      'assistant-comment',
      'assistant-comment::tool_results',
    ]);
  });

  it('can include native OpenCode work shortly before a comment-only task marker', async () => {
    const bridge = {
      getOpenCodeTranscript: vi.fn(async () => ({
        sessionId: 'session-opencode',
        logProjection: {
          messages: [
            taskMarkerLogMessage({
              uuid: 'native-read',
              timestamp: '2026-04-21T10:02:00.000Z',
              toolName: 'read',
              input: { filePath: '/tmp/app.ts' },
            }),
            toolResultLogMessage({
              uuid: 'native-read::tool_results',
              parentUuid: 'native-read',
              timestamp: '2026-04-21T10:02:01.000Z',
              sourceToolAssistantUUID: 'native-read',
            }),
            taskMarkerLogMessage({
              uuid: 'assistant-comment',
              parentUuid: 'native-read::tool_results',
              timestamp: '2026-04-21T10:04:00.000Z',
              toolName: 'mcp__agent-teams__task_add_comment',
              input: { teamName: 'team-a', taskId: 'task-a', text: 'Found the issue' },
            }),
            toolResultLogMessage({
              uuid: 'assistant-comment::tool_results',
              parentUuid: 'assistant-comment',
              timestamp: '2026-04-21T10:04:01.000Z',
              sourceToolAssistantUUID: 'assistant-comment',
            }),
          ],
        },
      })),
    };
    const chunkBuilder = {
      buildBundleChunks: vi.fn((messages) => [
        {
          id: 'chunk-comment-only-native',
          kind: 'assistant',
          messages,
        },
      ]),
    };
    const source = new OpenCodeTaskLogStreamSource(
      bridge as never,
      { resolve: async () => '/tmp/claude' },
      {
        getTasks: async () => [createTask()],
        getDeletedTasks: async () => [],
      } as never,
      chunkBuilder as never,
      { readTaskRecords: vi.fn(async () => []) }
    );

    const response = await source.getTaskLogStream('team-a', 'task-a');

    expect(response?.runtimeProjection).toEqual({
      provider: 'opencode',
      mode: 'heuristic',
      attributionRecordCount: 0,
      projectedMessageCount: 4,
      boardMcpToolCount: 1,
      nativeToolCount: 1,
      fallbackReason: 'task_tool_markers',
      markerMatchCount: 1,
      markerSpanCount: 1,
    });
    expect(
      chunkBuilder.buildBundleChunks.mock.calls[0]?.[0].map(
        (message: { uuid: string }) => message.uuid
      )
    ).toEqual([
      'native-read',
      'native-read::tool_results',
      'assistant-comment',
      'assistant-comment::tool_results',
    ]);
  });

  it('ignores OpenCode task markers that explicitly belong to another team', async () => {
    const bridge = {
      getOpenCodeTranscript: vi.fn(async () => ({
        sessionId: 'session-opencode',
        logProjection: {
          messages: [
            textLogMessage({
              uuid: 'other-team-prompt',
              type: 'user',
              role: 'user',
              timestamp: '2026-04-21T10:01:00.000Z',
            }),
            taskMarkerLogMessage({
              uuid: 'other-team-start',
              parentUuid: 'other-team-prompt',
              timestamp: '2026-04-21T10:02:00.000Z',
              toolName: 'mcp__agent-teams__task_start',
              input: { teamName: 'other-team', taskId: 'task-a' },
            }),
            toolResultLogMessage({
              uuid: 'other-team-start::tool_results',
              parentUuid: 'other-team-start',
              timestamp: '2026-04-21T10:02:01.000Z',
              sourceToolAssistantUUID: 'other-team-start',
            }),
            textLogMessage({
              uuid: 'team-a-prompt',
              type: 'user',
              role: 'user',
              timestamp: '2026-04-21T10:03:00.000Z',
            }),
            taskMarkerLogMessage({
              uuid: 'team-a-start',
              parentUuid: 'team-a-prompt',
              timestamp: '2026-04-21T10:04:00.000Z',
              toolName: 'mcp__agent-teams__task_start',
              input: { teamName: 'team-a', taskId: 'task-a' },
            }),
            toolResultLogMessage({
              uuid: 'team-a-start::tool_results',
              parentUuid: 'team-a-start',
              timestamp: '2026-04-21T10:04:01.000Z',
              sourceToolAssistantUUID: 'team-a-start',
            }),
          ],
        },
      })),
    };
    const chunkBuilder = {
      buildBundleChunks: vi.fn((messages) => [
        {
          id: 'chunk-team-marker',
          kind: 'assistant',
          messages,
        },
      ]),
    };
    const source = new OpenCodeTaskLogStreamSource(
      bridge as never,
      { resolve: async () => '/tmp/claude' },
      {
        getTasks: async () => [createTask()],
        getDeletedTasks: async () => [],
      } as never,
      chunkBuilder as never,
      { readTaskRecords: vi.fn(async () => []) }
    );

    const response = await source.getTaskLogStream('team-a', 'task-a');

    expect(response?.runtimeProjection).toEqual({
      provider: 'opencode',
      mode: 'heuristic',
      attributionRecordCount: 0,
      projectedMessageCount: 3,
      boardMcpToolCount: 1,
      nativeToolCount: 0,
      fallbackReason: 'task_tool_markers',
      markerMatchCount: 1,
      markerSpanCount: 1,
    });
    expect(
      chunkBuilder.buildBundleChunks.mock.calls[0]?.[0].map(
        (message: { uuid: string }) => message.uuid
      )
    ).toEqual(['team-a-prompt', 'team-a-start', 'team-a-start::tool_results']);
  });

  it('keeps separate marker spans for repeated task work cycles without including unrelated gaps', async () => {
    const bridge = {
      getOpenCodeTranscript: vi.fn(async () => ({
        sessionId: 'session-opencode',
        logProjection: {
          messages: [
            textLogMessage({
              uuid: 'cycle-1-prompt',
              type: 'user',
              role: 'user',
              timestamp: '2026-04-21T10:01:00.000Z',
            }),
            taskMarkerLogMessage({
              uuid: 'cycle-1-start',
              parentUuid: 'cycle-1-prompt',
              timestamp: '2026-04-21T10:02:00.000Z',
              toolName: 'mcp__agent-teams__task_start',
              input: { teamName: 'team-a', taskId: 'task-a' },
            }),
            toolResultLogMessage({
              uuid: 'cycle-1-start::tool_results',
              parentUuid: 'cycle-1-start',
              timestamp: '2026-04-21T10:02:01.000Z',
              sourceToolAssistantUUID: 'cycle-1-start',
            }),
            textLogMessage({
              uuid: 'cycle-1-work',
              timestamp: '2026-04-21T10:04:00.000Z',
            }),
            taskMarkerLogMessage({
              uuid: 'cycle-1-complete',
              parentUuid: 'cycle-1-work',
              timestamp: '2026-04-21T10:08:00.000Z',
              toolName: 'mcp__agent-teams__task_complete',
              input: { teamName: 'team-a', taskId: 'task-a' },
            }),
            toolResultLogMessage({
              uuid: 'cycle-1-complete::tool_results',
              parentUuid: 'cycle-1-complete',
              timestamp: '2026-04-21T10:08:01.000Z',
              sourceToolAssistantUUID: 'cycle-1-complete',
            }),
            textLogMessage({
              uuid: 'unrelated-between-cycles',
              timestamp: '2026-04-21T12:00:00.000Z',
            }),
            textLogMessage({
              uuid: 'cycle-2-prompt',
              type: 'user',
              role: 'user',
              timestamp: '2026-04-21T14:01:00.000Z',
            }),
            taskMarkerLogMessage({
              uuid: 'cycle-2-start',
              parentUuid: 'cycle-2-prompt',
              timestamp: '2026-04-21T14:02:00.000Z',
              toolName: 'mcp__agent-teams__task_start',
              input: { teamName: 'team-a', taskId: 'task-a' },
            }),
            toolResultLogMessage({
              uuid: 'cycle-2-start::tool_results',
              parentUuid: 'cycle-2-start',
              timestamp: '2026-04-21T14:02:01.000Z',
              sourceToolAssistantUUID: 'cycle-2-start',
            }),
            textLogMessage({
              uuid: 'cycle-2-work-after-open-marker',
              timestamp: '2026-04-21T14:04:00.000Z',
            }),
          ],
        },
      })),
    };
    const chunkBuilder = {
      buildBundleChunks: vi.fn((messages) => [
        {
          id: 'chunk-marker-cycles',
          kind: 'assistant',
          messages,
        },
      ]),
    };
    const source = new OpenCodeTaskLogStreamSource(
      bridge as never,
      { resolve: async () => '/tmp/claude' },
      {
        getTasks: async () => [
          createTask({
            updatedAt: '2026-04-21T14:10:00.000Z',
            workIntervals: [
              {
                startedAt: '2026-04-21T10:00:00.000Z',
                completedAt: '2026-04-21T10:10:00.000Z',
              },
              {
                startedAt: '2026-04-21T14:00:00.000Z',
                completedAt: '2026-04-21T14:10:00.000Z',
              },
            ],
          }),
        ],
        getDeletedTasks: async () => [],
      } as never,
      chunkBuilder as never,
      { readTaskRecords: vi.fn(async () => []) }
    );

    const response = await source.getTaskLogStream('team-a', 'task-a');

    expect(response?.runtimeProjection).toEqual({
      provider: 'opencode',
      mode: 'heuristic',
      attributionRecordCount: 0,
      projectedMessageCount: 10,
      boardMcpToolCount: 3,
      nativeToolCount: 0,
      fallbackReason: 'task_tool_markers',
      markerMatchCount: 3,
      markerSpanCount: 2,
    });
    expect(
      chunkBuilder.buildBundleChunks.mock.calls[0]?.[0].map(
        (message: { uuid: string }) => message.uuid
      )
    ).toEqual([
      'cycle-1-prompt',
      'cycle-1-start',
      'cycle-1-start::tool_results',
      'cycle-1-work',
      'cycle-1-complete',
      'cycle-1-complete::tool_results',
      'cycle-2-prompt',
      'cycle-2-start',
      'cycle-2-start::tool_results',
      'cycle-2-work-after-open-marker',
    ]);
  });

  it('ignores stale task markers outside current task windows before falling back to time-window logs', async () => {
    const bridge = {
      getOpenCodeTranscript: vi.fn(async () => ({
        sessionId: 'session-opencode',
        logProjection: {
          messages: [
            taskMarkerLogMessage({
              uuid: 'stale-start-marker',
              timestamp: '2026-04-21T08:00:00.000Z',
              toolName: 'mcp__agent-teams__task_start',
              input: { teamName: 'team-a', taskId: 'task-a' },
            }),
            textLogMessage({
              uuid: 'current-window-work',
              timestamp: '2026-04-21T10:05:00.000Z',
            }),
          ],
        },
      })),
    };
    const chunkBuilder = {
      buildBundleChunks: vi.fn((messages) => [
        {
          id: 'chunk-stale-marker',
          kind: 'assistant',
          messages,
        },
      ]),
    };
    const source = new OpenCodeTaskLogStreamSource(
      bridge as never,
      { resolve: async () => '/tmp/claude' },
      {
        getTasks: async () => [createTask()],
        getDeletedTasks: async () => [],
      } as never,
      chunkBuilder as never,
      { readTaskRecords: vi.fn(async () => []) }
    );

    const response = await source.getTaskLogStream('team-a', 'task-a');

    expect(response?.runtimeProjection).toEqual({
      provider: 'opencode',
      mode: 'heuristic',
      attributionRecordCount: 0,
      projectedMessageCount: 1,
      boardMcpToolCount: 0,
      nativeToolCount: 0,
      fallbackReason: 'no_attribution_records',
    });
    expect(
      chunkBuilder.buildBundleChunks.mock.calls[0]?.[0].map(
        (message: { uuid: string }) => message.uuid
      )
    ).toEqual(['current-window-work']);
  });

  it('matches hash-prefixed display task refs in OpenCode task tool markers', async () => {
    const bridge = {
      getOpenCodeTranscript: vi.fn(async () => ({
        sessionId: 'session-opencode',
        logProjection: {
          messages: [
            taskMarkerLogMessage({
              uuid: 'display-ref-start',
              timestamp: '2026-04-21T10:03:00.000Z',
              toolName: 'mcp__agent-teams__task_start',
              input: { teamName: 'team-a', taskId: '#short123' },
            }),
            toolResultLogMessage({
              uuid: 'display-ref-start::tool_results',
              parentUuid: 'display-ref-start',
              timestamp: '2026-04-21T10:03:01.000Z',
              sourceToolAssistantUUID: 'display-ref-start',
            }),
          ],
        },
      })),
    };
    const chunkBuilder = {
      buildBundleChunks: vi.fn((messages) => [
        {
          id: 'chunk-display-ref-marker',
          kind: 'assistant',
          messages,
        },
      ]),
    };
    const source = new OpenCodeTaskLogStreamSource(
      bridge as never,
      { resolve: async () => '/tmp/claude' },
      {
        getTasks: async () => [createTask({ id: 'task-canonical', displayId: 'short123' })],
        getDeletedTasks: async () => [],
      } as never,
      chunkBuilder as never,
      { readTaskRecords: vi.fn(async () => []) }
    );

    const response = await source.getTaskLogStream('team-a', 'task-canonical');

    expect(response?.runtimeProjection).toEqual({
      provider: 'opencode',
      mode: 'heuristic',
      attributionRecordCount: 0,
      projectedMessageCount: 2,
      boardMcpToolCount: 1,
      nativeToolCount: 0,
      fallbackReason: 'task_tool_markers',
      markerMatchCount: 1,
      markerSpanCount: 1,
    });
    expect(
      chunkBuilder.buildBundleChunks.mock.calls[0]?.[0].map(
        (message: { uuid: string }) => message.uuid
      )
    ).toEqual(['display-ref-start', 'display-ref-start::tool_results']);
  });

  it('prefers explicit OpenCode attribution over owner/time-window heuristic', async () => {
    const bridge = {
      getOpenCodeTranscript: vi.fn(async (_binaryPath, params: { memberName: string }) => {
        if (params.memberName !== 'bob') {
          throw new Error(`unexpected member ${params.memberName}`);
        }
        return {
          sessionId: 'session-bob',
          logProjection: {
            messages: [
              {
                uuid: 'bob-outside',
                parentUuid: undefined,
                type: 'assistant',
                timestamp: '2026-04-21T11:50:00.000Z',
                role: 'assistant',
                content: [{ type: 'text', text: 'Before attributed window' }],
                isMeta: false,
                sessionId: 'session-bob',
                toolCalls: [],
                toolResults: [],
              },
              {
                uuid: 'bob-inside',
                parentUuid: undefined,
                type: 'assistant',
                timestamp: '2026-04-21T12:05:00.000Z',
                role: 'assistant',
                content: [{ type: 'text', text: 'Explicitly attributed OpenCode work' }],
                isMeta: false,
                sessionId: 'session-bob',
                toolCalls: [],
                toolResults: [],
              },
            ],
          },
        };
      }),
    };
    const chunkBuilder = {
      buildBundleChunks: vi.fn((messages) => [
        {
          id: 'chunk-attributed',
          kind: 'assistant',
          messages,
        },
      ]),
    };
    const taskReader = {
      getTasks: vi.fn(async () => [createTask()]),
      getDeletedTasks: vi.fn(async () => []),
    };
    const attributionRecords: OpenCodeTaskLogAttributionRecord[] = [
      {
        taskId: 'task-a',
        memberName: 'bob',
        scope: 'member_session_window',
        sessionId: 'session-bob',
        since: '2026-04-21T12:00:00.000Z',
        until: '2026-04-21T12:10:00.000Z',
        source: 'launch_runtime',
      },
    ];

    const source = new OpenCodeTaskLogStreamSource(
      bridge as never,
      { resolve: async () => '/tmp/claude' },
      taskReader as never,
      chunkBuilder as never,
      { readTaskRecords: vi.fn(async () => attributionRecords) }
    );

    const response = await source.getTaskLogStream('team-a', 'task-a');

    expect(response?.source).toBe('opencode_runtime_attribution');
    expect(response?.runtimeProjection).toEqual({
      provider: 'opencode',
      mode: 'attribution',
      attributionRecordCount: 1,
      projectedMessageCount: 1,
      boardMcpToolCount: 0,
      nativeToolCount: 0,
    });
    expect(response?.participants).toEqual([
      {
        key: 'member:bob',
        label: 'bob',
        role: 'member',
        isLead: false,
        isSidechain: true,
      },
    ]);
    expect(response?.defaultFilter).toBe('member:bob');
    expect(response?.segments[0]?.actor).toEqual({
      memberName: 'bob',
      role: 'member',
      sessionId: 'session-bob',
      isSidechain: true,
    });
    expect(
      chunkBuilder.buildBundleChunks.mock.calls[0]?.[0].map(
        (message: { uuid: string }) => message.uuid
      )
    ).toEqual(['bob-inside']);
    expect(bridge.getOpenCodeTranscript).toHaveBeenCalledWith('/tmp/claude', {
      teamId: 'team-a',
      memberName: 'bob',
      limit: 500,
      sessionId: 'session-bob',
    });
  });

  it('falls back to owner heuristic when explicit attribution is stale', async () => {
    const bridge = {
      getOpenCodeTranscript: vi.fn(async (_binaryPath, params: { memberName: string }) => {
        if (params.memberName === 'bob') {
          return {
            sessionId: 'stale-session',
            logProjection: {
              messages: [
                {
                  uuid: 'stale-bob',
                  parentUuid: undefined,
                  type: 'assistant',
                  timestamp: '2026-04-21T10:05:00.000Z',
                  role: 'assistant',
                  content: [{ type: 'text', text: 'Wrong session' }],
                  isMeta: false,
                  sessionId: 'stale-session',
                  toolCalls: [],
                  toolResults: [],
                },
              ],
            },
          };
        }
        return {
          sessionId: 'session-alice',
          logProjection: {
            messages: [
              {
                uuid: 'alice-inside',
                parentUuid: undefined,
                type: 'assistant',
                timestamp: '2026-04-21T10:05:00.000Z',
                role: 'assistant',
                content: [{ type: 'text', text: 'Heuristic owner work' }],
                isMeta: false,
                sessionId: 'session-alice',
                toolCalls: [],
                toolResults: [],
              },
            ],
          },
        };
      }),
    };
    const chunkBuilder = {
      buildBundleChunks: vi.fn((messages) => [
        {
          id: 'chunk-heuristic',
          kind: 'assistant',
          messages,
        },
      ]),
    };
    const attributionRecords: OpenCodeTaskLogAttributionRecord[] = [
      {
        taskId: 'task-a',
        memberName: 'bob',
        scope: 'member_session_window',
        sessionId: 'session-bob',
        since: '2026-04-21T10:00:00.000Z',
        until: '2026-04-21T10:10:00.000Z',
      },
    ];
    const source = new OpenCodeTaskLogStreamSource(
      bridge as never,
      { resolve: async () => '/tmp/claude' },
      {
        getTasks: async () => [createTask()],
        getDeletedTasks: async () => [],
      } as never,
      chunkBuilder as never,
      { readTaskRecords: vi.fn(async () => attributionRecords) }
    );

    const response = await source.getTaskLogStream('team-a', 'task-a');

    expect(response?.source).toBe('opencode_runtime_fallback');
    expect(response?.runtimeProjection).toEqual({
      provider: 'opencode',
      mode: 'heuristic',
      attributionRecordCount: 1,
      projectedMessageCount: 1,
      boardMcpToolCount: 0,
      nativeToolCount: 0,
      fallbackReason: 'attribution_no_projected_messages',
    });
    expect(response?.participants[0]?.label).toBe('alice');
    expect(
      chunkBuilder.buildBundleChunks.mock.calls
        .at(-1)?.[0]
        .map((message: { uuid: string }) => message.uuid)
    ).toEqual(['alice-inside']);
    expect(bridge.getOpenCodeTranscript).toHaveBeenNthCalledWith(1, '/tmp/claude', {
      teamId: 'team-a',
      memberName: 'bob',
      limit: 500,
      sessionId: 'session-bob',
    });
    expect(bridge.getOpenCodeTranscript).toHaveBeenNthCalledWith(2, '/tmp/claude', {
      teamId: 'team-a',
      memberName: 'alice',
      limit: 200,
    });
  });

  it('keys the short cache by attribution records so new explicit attribution is visible immediately', async () => {
    const attributionRecords: OpenCodeTaskLogAttributionRecord[] = [
      {
        taskId: 'task-a',
        memberName: 'bob',
        scope: 'member_session_window',
        sessionId: 'session-bob',
        since: '2026-04-21T12:00:00.000Z',
        until: '2026-04-21T12:10:00.000Z',
      },
    ];
    const attributionStore = {
      readTaskRecords: vi
        .fn<() => Promise<OpenCodeTaskLogAttributionRecord[]>>()
        .mockResolvedValueOnce([])
        .mockResolvedValueOnce(attributionRecords),
    };
    const bridge = {
      getOpenCodeTranscript: vi.fn(async (_binaryPath, params: { memberName: string }) => {
        const isBob = params.memberName === 'bob';
        return {
          sessionId: isBob ? 'session-bob' : 'session-alice',
          logProjection: {
            messages: [
              {
                uuid: isBob ? 'bob-new-attribution' : 'alice-old-heuristic',
                parentUuid: undefined,
                type: 'assistant',
                timestamp: isBob ? '2026-04-21T12:05:00.000Z' : '2026-04-21T10:05:00.000Z',
                role: 'assistant',
                content: [{ type: 'text', text: isBob ? 'new attribution' : 'old heuristic' }],
                isMeta: false,
                sessionId: isBob ? 'session-bob' : 'session-alice',
                toolCalls: [],
                toolResults: [],
              },
            ],
          },
        };
      }),
    };
    const chunkBuilder = {
      buildBundleChunks: vi.fn((messages) => [
        {
          id: 'chunk',
          kind: 'assistant',
          messages,
        },
      ]),
    };
    const source = new OpenCodeTaskLogStreamSource(
      bridge as never,
      { resolve: async () => '/tmp/claude' },
      {
        getTasks: async () => [createTask()],
        getDeletedTasks: async () => [],
      } as never,
      chunkBuilder as never,
      attributionStore
    );

    const first = await source.getTaskLogStream('team-a', 'task-a');
    const second = await source.getTaskLogStream('team-a', 'task-a');

    expect(first?.source).toBe('opencode_runtime_fallback');
    expect(second?.source).toBe('opencode_runtime_attribution');
    expect(second?.runtimeProjection).toMatchObject({
      provider: 'opencode',
      mode: 'attribution',
      attributionRecordCount: 1,
      projectedMessageCount: 1,
    });
    expect(bridge.getOpenCodeTranscript).toHaveBeenNthCalledWith(1, '/tmp/claude', {
      teamId: 'team-a',
      memberName: 'alice',
      limit: 200,
    });
    expect(bridge.getOpenCodeTranscript).toHaveBeenNthCalledWith(2, '/tmp/claude', {
      teamId: 'team-a',
      memberName: 'bob',
      limit: 500,
      sessionId: 'session-bob',
    });
    expect(
      chunkBuilder.buildBundleChunks.mock.calls
        .at(-1)?.[0]
        .map((message: { uuid: string }) => message.uuid)
    ).toEqual(['bob-new-attribution']);
  });

  it('keeps same-member exact OpenCode session attributions in distinct segments', async () => {
    const attributionRecords: OpenCodeTaskLogAttributionRecord[] = [
      {
        taskId: 'task-a',
        memberName: 'bob',
        scope: 'member_session_window',
        sessionId: 'session-bob-old',
        since: '2026-04-21T10:00:00.000Z',
        until: '2026-04-21T10:10:00.000Z',
      },
      {
        taskId: 'task-a',
        memberName: 'bob',
        scope: 'member_session_window',
        sessionId: 'session-bob-new',
        since: '2026-04-21T11:00:00.000Z',
        until: '2026-04-21T11:10:00.000Z',
      },
    ];
    const bridge = {
      getOpenCodeTranscript: vi.fn(
        async (_binaryPath, params: { memberName: string; sessionId?: string }) => {
          if (params.memberName !== 'bob' || !params.sessionId) {
            throw new Error(`unexpected transcript request ${JSON.stringify(params)}`);
          }
          const timestamp =
            params.sessionId === 'session-bob-old'
              ? '2026-04-21T10:05:00.000Z'
              : '2026-04-21T11:05:00.000Z';
          return {
            sessionId: params.sessionId,
            logProjection: {
              messages: [
                {
                  uuid: 'same-runtime-uuid',
                  parentUuid: undefined,
                  type: 'assistant',
                  timestamp,
                  role: 'assistant',
                  content: [{ type: 'text', text: params.sessionId }],
                  isMeta: false,
                  sessionId: params.sessionId,
                  toolCalls: [],
                  toolResults: [],
                },
              ],
            },
          };
        }
      ),
    };
    const chunkBuilder = {
      buildBundleChunks: vi.fn((messages) => [
        {
          id: `chunk-${messages[0]?.sessionId ?? 'unknown'}`,
          kind: 'assistant',
          messages,
        },
      ]),
    };
    const source = new OpenCodeTaskLogStreamSource(
      bridge as never,
      { resolve: async () => '/tmp/claude' },
      {
        getTasks: async () => [createTask()],
        getDeletedTasks: async () => [],
      } as never,
      chunkBuilder as never,
      { readTaskRecords: vi.fn(async () => attributionRecords) }
    );

    const response = await source.getTaskLogStream('team-a', 'task-a');

    expect(response?.source).toBe('opencode_runtime_attribution');
    expect(response?.segments.map((segment) => segment.id)).toEqual([
      'opencode-attributed:team-a:task-a:bob:session-bob-old',
      'opencode-attributed:team-a:task-a:bob:session-bob-new',
    ]);
    expect(response?.segments.map((segment) => segment.actor.sessionId)).toEqual([
      'session-bob-old',
      'session-bob-new',
    ]);
    expect(bridge.getOpenCodeTranscript).toHaveBeenNthCalledWith(1, '/tmp/claude', {
      teamId: 'team-a',
      memberName: 'bob',
      limit: 500,
      sessionId: 'session-bob-old',
    });
    expect(bridge.getOpenCodeTranscript).toHaveBeenNthCalledWith(2, '/tmp/claude', {
      teamId: 'team-a',
      memberName: 'bob',
      limit: 500,
      sessionId: 'session-bob-new',
    });
  });
});
