const fs = require('fs');
const http = require('http');
const os = require('os');
const path = require('path');

const { createController } = require('../src/index.js');

describe('agent-teams-controller API', () => {
  function makeClaudeDir() {
    const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-teams-controller-'));
    fs.mkdirSync(path.join(dir, 'teams', 'my-team'), { recursive: true });
    fs.mkdirSync(path.join(dir, 'tasks', 'my-team'), { recursive: true });
    fs.writeFileSync(
      path.join(dir, 'teams', 'my-team', 'config.json'),
      JSON.stringify(
        {
          name: 'my-team',
          leadSessionId: 'lead-session-1',
          members: [
            { name: 'alice', role: 'team-lead' },
            { name: 'bob', role: 'developer' },
          ],
        },
        null,
        2
      )
    );
    return dir;
  }

  async function startControlServer(handler) {
    const server = http.createServer(async (req, res) => {
      const chunks = [];
      req.on('data', (chunk) => chunks.push(chunk));
      req.on('end', async () => {
        try {
          const bodyText = Buffer.concat(chunks).toString('utf8');
          const body = bodyText ? JSON.parse(bodyText) : undefined;
          const result = await handler({
            method: req.method,
            url: req.url,
            body,
          });
          res.writeHead(result.statusCode || 200, { 'content-type': 'application/json' });
          res.end(JSON.stringify(result.body));
        } catch (error) {
          res.writeHead(500, { 'content-type': 'application/json' });
          res.end(JSON.stringify({ error: error.message }));
        }
      });
    });

    await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
    const address = server.address();
    return {
      baseUrl: `http://127.0.0.1:${address.port}`,
      close: async () =>
        await new Promise((resolve, reject) =>
          server.close((error) => (error ? reject(error) : resolve()))
        ),
    };
  }

  function writeControlApiState(claudeDir, baseUrl) {
    fs.writeFileSync(
      path.join(claudeDir, 'team-control-api.json'),
      JSON.stringify({ baseUrl, updatedAt: new Date().toISOString() }, null, 2)
    );
  }

  it('creates tasks and exposes grouped controller modules', () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });

    const base = controller.tasks.createTask({ subject: 'Base task' });
    const dependency = controller.tasks.createTask({ subject: 'Dependency task' });
    const created = controller.tasks.createTask({
      subject: 'Blocked task',
      owner: 'bob',
      'blocked-by': `${base.displayId},${dependency.displayId}`,
      related: base.displayId,
    });

    expect(created.id).toMatch(
      /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
    );
    expect(created.displayId).toHaveLength(8);
    expect(created.status).toBe('pending');
    expect(created.reviewState).toBe('none');
    expect(controller.tasks.getTask(base.id).blocks).toEqual([created.id]);
    expect(controller.tasks.getTask(created.displayId).blockedBy).toEqual([base.id, dependency.id]);

    controller.kanban.addReviewer('alice');
    controller.tasks.completeTask(created.id, 'bob');
    controller.review.requestReview(created.id, { from: 'alice' });
    controller.review.approveReview(created.id, { 'notify-owner': true, from: 'alice' });

    const kanbanState = controller.kanban.getKanbanState();
    expect(kanbanState.reviewers).toEqual(['alice']);
    expect(kanbanState.tasks[created.id].column).toBe('approved');
    expect(controller.tasks.getTask(created.id).reviewState).toBe('approved');

    const sent = controller.messages.appendSentMessage({
      from: 'team-lead',
      to: 'user',
      text: 'All good',
      leadSessionId: 'session-1',
      source: 'lead_process',
      attachments: [{ id: 'a1', filename: 'diff.txt', mimeType: 'text/plain', size: 12 }],
    });
    expect(sent.leadSessionId).toBe('session-1');

    const ownerInboxPath = path.join(claudeDir, 'teams', 'my-team', 'inboxes', 'bob.json');
    const ownerInbox = JSON.parse(fs.readFileSync(ownerInboxPath, 'utf8'));
    expect(ownerInbox.at(-1).summary).toContain('Approved');
    expect(ownerInbox.at(-1).leadSessionId).toBe('lead-session-1');

    const proc = controller.processes.registerProcess({
      pid: process.pid,
      label: 'dev-server',
      port: '3000',
    });
    expect(proc.port).toBe(3000);
    expect(controller.processes.listProcesses()).toHaveLength(1);
    const stopped = controller.processes.stopProcess({ pid: process.pid });
    expect(typeof stopped.stoppedAt).toBe('string');
  });

  it('builds member briefing from team config language and known member metadata', async () => {
    const claudeDir = makeClaudeDir();
    const configPath = path.join(claudeDir, 'teams', 'my-team', 'config.json');
    const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
    config.language = 'en';
    config.projectPath = '/tmp/project-x';
    config.members = [
      { name: 'alice', role: 'team-lead' },
      { name: 'bob', role: 'developer', workflow: 'Implement carefully', cwd: '/tmp/project-x' },
    ];
    fs.writeFileSync(configPath, JSON.stringify(config, null, 2));

    const controller = createController({ teamName: 'my-team', claudeDir });
    controller.tasks.createTask({ subject: 'Queued task', owner: 'bob' });
    const briefing = await controller.tasks.memberBriefing('bob');

    expect(briefing).toContain('Member briefing for bob on team "my-team" (my-team).');
    expect(briefing).toContain('IMPORTANT: Communicate in English.');
    expect(briefing).toContain('TURN ACTION MODE PROTOCOL (HIGHEST PRIORITY FOR EACH USER TURN):');
    expect(briefing).toContain('Workflow:');
    expect(briefing).toContain('Implement carefully');
    expect(briefing).toContain('Working directory: /tmp/project-x');
    expect(briefing).toContain('Task briefing for bob:');
    expect(briefing).toContain(
      'Use task_briefing as your primary working queue whenever you need to see assigned work.'
    );
    expect(briefing).toContain(
      'Use task_list only to search/browse inventory rows, not as your working queue.'
    );
    expect(briefing).toContain('member_work_sync_status and member_work_sync_report');
    expect(briefing).toContain(
      'Awareness items are watch-only context and do not authorize you to start work unless the lead reroutes the task or you become the actionOwner.'
    );
    expect(briefing).toContain('After task_complete, notify your team lead via SendMessage.');
    expect(briefing).toContain('Full details in task comment e5f6a7b8');
    expect(briefing).not.toContain('task_get_comment {');
  });

  it('uses OpenCode-native visible-message wording for OpenCode member briefing', async () => {
    const claudeDir = makeClaudeDir();
    const configPath = path.join(claudeDir, 'teams', 'my-team', 'config.json');
    const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
    config.members = [
      { name: 'alice', role: 'team-lead' },
      { name: 'bob', role: 'developer', providerId: 'opencode', model: 'openrouter/test-model' },
    ];
    fs.writeFileSync(configPath, JSON.stringify(config, null, 2));

    const controller = createController({ teamName: 'my-team', claudeDir });
    const briefing = await controller.tasks.memberBriefing('bob');

    expect(briefing).toContain(
      'After task_complete, notify your team lead via MCP tool agent-teams_message_send.'
    );
    expect(briefing).toContain('OpenCode visible messaging rule: call agent-teams_message_send');
    expect(briefing).toContain('OpenCode bootstrap silence rule');
    expect(briefing).toContain('If it shows no actionable tasks, stop and wait silently.');
    expect(briefing).toContain(
      'agent-teams_message_send { teamName: "my-team", to: "alice", from: "bob"'
    );
    expect(briefing).toContain('Full details in task comment e5f6a7b8');
    expect(briefing).toContain('Never invent placeholder task refs such as #00000000');
    expect(briefing).not.toContain('task_get_comment {');
    expect(briefing).not.toContain('notify your team lead via SendMessage');
  });

  it('rejects OpenCode idle acknowledgements without explicit delivery context', () => {
    const claudeDir = makeClaudeDir();
    const configPath = path.join(claudeDir, 'teams', 'my-team', 'config.json');
    const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
    config.members = [
      { name: 'alice', role: 'team-lead' },
      { name: 'bob', role: 'developer', providerId: 'opencode', model: 'opencode/test-model' },
    ];
    fs.writeFileSync(configPath, JSON.stringify(config, null, 2));

    const controller = createController({ teamName: 'my-team', claudeDir });

    expect(() =>
      controller.messages.sendMessage({
        to: 'user',
        from: 'bob',
        text: 'Понял.',
      })
    ).toThrow('OpenCode idle/ack-only message_send was not delivered');

    expect(() =>
      controller.messages.sendMessage({
        to: 'team-lead',
        from: 'bob',
        text: 'Нет назначенных задач.',
      })
    ).toThrow('OpenCode idle/ack-only message_send was not delivered');

    expect(() =>
      controller.messages.sendMessage({
        to: 'user',
        from: 'bob',
        text: 'Понял.',
        source: 'runtime_delivery',
      })
    ).toThrow('OpenCode idle/ack-only message_send was not delivered');

    const delivered = controller.messages.sendMessage({
      to: 'user',
      from: 'bob',
      text: 'Понял.',
      source: 'runtime_delivery',
      relayOfMessageId: 'msg-inbound-1',
    });

    expect(delivered.deliveredToInbox).toBe(true);
  });

  it('deduplicates repeated runtime_delivery replies to the same inbound message', () => {
    const claudeDir = makeClaudeDir();
    const configPath = path.join(claudeDir, 'teams', 'my-team', 'config.json');
    const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
    config.members = [
      { name: 'alice', role: 'team-lead' },
      { name: 'bob', role: 'developer', providerId: 'opencode', model: 'opencode/test-model' },
    ];
    fs.writeFileSync(configPath, JSON.stringify(config, null, 2));

    const controller = createController({ teamName: 'my-team', claudeDir });
    const first = controller.messages.sendMessage({
      to: 'user',
      from: 'bob',
      text: 'Да, я здесь!',
      source: 'runtime_delivery',
      relayOfMessageId: 'msg-inbound-1',
    });
    const second = controller.messages.sendMessage({
      to: 'user',
      from: 'bob',
      text: ' Да,   я здесь! ',
      source: 'runtime_delivery',
      relayOfMessageId: 'msg-inbound-1',
    });

    const userInboxPath = path.join(claudeDir, 'teams', 'my-team', 'inboxes', 'user.json');
    const rows = JSON.parse(fs.readFileSync(userInboxPath, 'utf8'));
    expect(rows).toHaveLength(1);
    expect(second).toMatchObject({
      deliveredToInbox: true,
      deduplicated: true,
      messageId: first.messageId,
      duplicateOfMessageId: first.messageId,
      deduplicationNotice: expect.stringContaining('do not call agent-teams_message_send again'),
    });
  });

  it('strips hallucinated zero task placeholder prefixes from visible messages', () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });

    controller.messages.sendMessage({
      to: 'user',
      from: 'bob',
      text: '#00000000 bootstrap check-in and briefing retrieved. No actionable tasks.',
      summary: '#00000000 ready',
    });

    const userInboxPath = path.join(claudeDir, 'teams', 'my-team', 'inboxes', 'user.json');
    const rows = JSON.parse(fs.readFileSync(userInboxPath, 'utf8'));
    expect(rows[0].text).toBe('bootstrap check-in and briefing retrieved. No actionable tasks.');
    expect(rows[0].summary).toBe('ready');
  });

  it('does not infer OpenCode briefing from a generic provider-scoped model alone', async () => {
    const claudeDir = makeClaudeDir();
    const configPath = path.join(claudeDir, 'teams', 'my-team', 'config.json');
    const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
    config.members = [
      { name: 'alice', role: 'team-lead' },
      { name: 'bob', role: 'developer', model: 'openai/gpt-5.4-mini' },
    ];
    fs.writeFileSync(configPath, JSON.stringify(config, null, 2));

    const controller = createController({ teamName: 'my-team', claudeDir });
    const briefing = await controller.tasks.memberBriefing('bob');

    expect(briefing).toContain('After task_complete, notify your team lead via SendMessage.');
    expect(briefing).not.toContain('agent-teams_message_send');
  });

  it('keeps explicit native provider metadata stronger than OpenCode-looking model labels', async () => {
    const claudeDir = makeClaudeDir();
    const configPath = path.join(claudeDir, 'teams', 'my-team', 'config.json');
    const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
    config.members = [
      { name: 'alice', role: 'team-lead' },
      { name: 'bob', role: 'developer', providerId: 'codex', model: 'opencode/minimax-m2.5-free' },
    ];
    fs.writeFileSync(configPath, JSON.stringify(config, null, 2));

    const controller = createController({ teamName: 'my-team', claudeDir });
    const briefing = await controller.tasks.memberBriefing('bob');

    expect(briefing).toContain('After task_complete, notify your team lead via SendMessage.');
    expect(briefing).not.toContain('agent-teams_message_send');
  });

  it('resolves member briefing from members.meta.json when config members are missing', async () => {
    const claudeDir = makeClaudeDir();
    const configPath = path.join(claudeDir, 'teams', 'my-team', 'config.json');
    const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
    config.language = 'en';
    delete config.members;
    fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
    fs.writeFileSync(
      path.join(claudeDir, 'teams', 'my-team', 'members.meta.json'),
      JSON.stringify(
        {
          version: 1,
          members: [{ name: 'bob', role: 'developer', workflow: 'Meta workflow' }],
        },
        null,
        2
      )
    );

    const controller = createController({ teamName: 'my-team', claudeDir });
    const briefing = await controller.tasks.memberBriefing('bob');

    expect(briefing).toContain('Role: developer.');
    expect(briefing).toContain('Meta workflow');
  });

  it('resolves member briefing from inbox presence when member metadata is not persisted yet', async () => {
    const claudeDir = makeClaudeDir();
    const configPath = path.join(claudeDir, 'teams', 'my-team', 'config.json');
    const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
    delete config.members;
    fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
    fs.mkdirSync(path.join(claudeDir, 'teams', 'my-team', 'inboxes'), { recursive: true });
    fs.writeFileSync(path.join(claudeDir, 'teams', 'my-team', 'inboxes', 'carol.json'), '[]');

    const controller = createController({ teamName: 'my-team', claudeDir });
    const fromInboxBriefing = await controller.tasks.memberBriefing('carol');

    expect(fromInboxBriefing).toContain('Member briefing for carol on team "my-team" (my-team).');
    expect(fromInboxBriefing).toContain('Role: team member.');
  });

  it('rejects member briefing when member is unknown to config, members.meta, and inboxes', async () => {
    const claudeDir = makeClaudeDir();
    const configPath = path.join(claudeDir, 'teams', 'my-team', 'config.json');
    const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
    delete config.members;
    fs.writeFileSync(configPath, JSON.stringify(config, null, 2));

    const controller = createController({ teamName: 'my-team', claudeDir });
    await expect(controller.tasks.memberBriefing('dave')).rejects.toThrow(
      'Member not found in team metadata or inboxes: dave'
    );
  });

  it('ignores pseudo-recipient inbox files when resolving members', async () => {
    const claudeDir = makeClaudeDir();
    const configPath = path.join(claudeDir, 'teams', 'my-team', 'config.json');
    const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
    delete config.members;
    fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
    const inboxDir = path.join(claudeDir, 'teams', 'my-team', 'inboxes');
    fs.mkdirSync(inboxDir, { recursive: true });
    fs.writeFileSync(path.join(inboxDir, 'cross-team:other-team.json'), '[]');
    fs.writeFileSync(path.join(inboxDir, 'other-team.alice.json'), '[]');
    fs.writeFileSync(path.join(inboxDir, 'cross_team_send.json'), '[]');

    const controller = createController({ teamName: 'my-team', claudeDir });
    await expect(controller.tasks.memberBriefing('cross-team:other-team')).rejects.toThrow(
      'Member not found in team metadata or inboxes: cross-team:other-team'
    );
    await expect(controller.tasks.memberBriefing('other-team.alice')).rejects.toThrow(
      'Member not found in team metadata or inboxes: other-team.alice'
    );
    await expect(controller.tasks.memberBriefing('cross_team_send')).rejects.toThrow(
      'Member not found in team metadata or inboxes: cross_team_send'
    );
  });

  it('rejects member briefing for explicitly removed members', async () => {
    const claudeDir = makeClaudeDir();
    fs.writeFileSync(
      path.join(claudeDir, 'teams', 'my-team', 'members.meta.json'),
      JSON.stringify(
        {
          version: 1,
          members: [{ name: 'carol', role: 'developer', removedAt: Date.now() }],
        },
        null,
        2
      )
    );

    const controller = createController({ teamName: 'my-team', claudeDir });
    await expect(controller.tasks.memberBriefing('carol')).rejects.toThrow(
      'Member is removed from the team: carol'
    );
  });

  it('creates a fresh registry entry when an old pid was recycled without stoppedAt', () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const processesPath = path.join(claudeDir, 'teams', 'my-team', 'processes.json');

    fs.writeFileSync(
      processesPath,
      JSON.stringify(
        [
          {
            id: 'old-entry',
            pid: 999999,
            label: 'stale',
            registeredAt: '2024-01-01T00:00:00.000Z',
          },
        ],
        null,
        2
      )
    );

    const registered = controller.processes.registerProcess({
      pid: 999999,
      label: 'fresh',
    });

    expect(registered.id).not.toBe('old-entry');
    const rows = JSON.parse(fs.readFileSync(processesPath, 'utf8'));
    expect(rows).toHaveLength(2);
    expect(rows[0].stoppedAt).toBeTruthy();
    expect(rows[1].id).toBe(registered.id);
  });

  it('keeps assigned tasks pending by default, supports explicit immediate start, notifies owners, and groups briefing into actionable and awareness queues', async () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });

    const pendingTask = controller.tasks.createTask({
      subject: 'Queued task',
      description: 'Do this later',
      owner: 'bob',
      prompt: 'Check the migration plan first.',
    });
    const activeTask = controller.tasks.createTask({
      subject: 'Active task',
      description: 'Resume immediately',
      owner: 'bob',
      startImmediately: true,
    });
    const completedTask = controller.tasks.createTask({
      subject: 'Already done',
      description: 'Completed task description should stay out of compact rows',
      owner: 'bob',
    });
    controller.tasks.completeTask(completedTask.id, 'bob');
    controller.tasks.addTaskComment(activeTask.id, {
      from: 'bob',
      text: 'Resumed work with latest context.',
    });
    const needsFixTask = controller.tasks.createTask({
      subject: 'Fix after review',
      owner: 'bob',
      status: 'pending',
      reviewState: 'needsFix',
      createdAt: '2026-01-02T00:00:00.000Z',
      notifyOwner: false,
    });
    const reviewTask = controller.tasks.createTask({
      subject: 'Waiting for review',
      owner: 'bob',
      status: 'completed',
      reviewState: 'review',
      createdAt: '2026-01-03T00:00:00.000Z',
      notifyOwner: false,
    });
    const approvedTask = controller.tasks.createTask({
      subject: 'Approved work',
      owner: 'bob',
      status: 'completed',
      reviewState: 'approved',
      createdAt: '2026-01-04T00:00:00.000Z',
      notifyOwner: false,
    });

    const reassignedTask = controller.tasks.createTask({ subject: 'Reassigned later' });
    controller.tasks.setTaskOwner(reassignedTask.id, 'bob');

    expect(pendingTask.status).toBe('pending');
    expect(activeTask.status).toBe('in_progress');

    const ownerInboxPath = path.join(claudeDir, 'teams', 'my-team', 'inboxes', 'bob.json');
    const ownerInbox = JSON.parse(fs.readFileSync(ownerInboxPath, 'utf8'));
    expect(ownerInbox).toHaveLength(4);
    expect(ownerInbox[0].summary).toContain(`#${pendingTask.displayId}`);
    expect(ownerInbox[0].text).toContain('task_get');
    expect(ownerInbox[0].text).toContain('task_start');
    expect(ownerInbox[0].text).toContain('task_add_comment');
    expect(ownerInbox[0].text).toContain(
      'If you are idle and this task is ready to start, start it now.'
    );
    expect(ownerInbox[0].text).toContain(
      'If you are busy, blocked, or still need more context, immediately add a short task comment'
    );
    expect(ownerInbox[0].text).toContain('Description:');
    expect(ownerInbox[0].text).toContain('Do this later');
    expect(ownerInbox[0].text).toContain('Instructions:');
    expect(ownerInbox[0].text).toContain('Check the migration plan first.');
    expect(ownerInbox[0].leadSessionId).toBe('lead-session-1');
    expect(ownerInbox[3].summary).toContain(`#${reassignedTask.displayId}`);
    expect(ownerInbox[3].text).toContain(
      'If you are idle and this task is ready to start, start it now.'
    );
    expect(ownerInbox[3].text).toContain('task_add_comment');

    const briefing = await controller.tasks.taskBriefing('bob');
    expect(briefing).toContain(
      'Primary queue for bob. Act only on Actionable items. Awareness items are watch-only context unless the lead reroutes the task or you become the actionOwner.'
    );
    expect(briefing).toContain(
      'Use task_list only to search/browse inventory rows, not as your working queue.'
    );
    expect(briefing).toContain('Actionable:');
    expect(briefing).toContain(`#${activeTask.displayId}`);
    expect(briefing).toContain('Description: Resume immediately');
    expect(briefing).toContain('Resumed work with latest context.');
    expect(briefing).toContain(`#${needsFixTask.displayId}`);
    expect(briefing).toContain('reason=needs_fix');
    expect(briefing).toContain(`#${pendingTask.displayId}`);
    expect(briefing).not.toContain('Description: Do this later');
    expect(briefing).toContain('Awareness:');
    expect(briefing).toContain(`#${reviewTask.displayId}`);
    expect(briefing).toContain('reason=review_reviewer_missing');
    expect(briefing).toContain(`#${completedTask.displayId}`);
    expect(briefing).not.toContain('Completed task description should stay out of compact rows');
    expect(briefing).toContain(`#${approvedTask.displayId}`);
    expect(briefing).toContain('Counters: actionable=4, awareness=3');
  });

  it('treats stale legacy terminal reviewState on pending tasks as owner-ready work', async () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });

    const staleTask = controller.tasks.createTask({
      subject: 'Legacy stale approved task',
      owner: 'bob',
      status: 'pending',
      reviewState: 'approved',
      notifyOwner: false,
    });

    const briefing = await controller.tasks.taskBriefing('bob');
    const staleLine = briefing.split('\n').find((line) => line.includes(`#${staleTask.displayId}`));
    expect(staleLine).toContain('[status=pending]');
    expect(staleLine).not.toContain('review=');
    expect(staleLine).toContain('reason=owner_ready');

    const rows = controller.tasks.listTaskInventory({ owner: 'bob' });
    expect(rows.find((row) => row.id === staleTask.id)).toMatchObject({
      status: 'pending',
      reviewState: 'none',
    });
  });

  it('reconciles stale kanban rows and linked inbox comments idempotently', () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const task = controller.tasks.createTask({
      subject: 'Ship migration',
      owner: 'bob',
    });

    const kanbanPath = path.join(claudeDir, 'teams', 'my-team', 'kanban-state.json');
    fs.writeFileSync(
      kanbanPath,
      JSON.stringify(
        {
          teamName: 'my-team',
          reviewers: [],
          tasks: {
            [task.id]: { column: 'review', movedAt: '2026-01-01T00:00:00.000Z', reviewer: null },
            staleTask: { column: 'approved', movedAt: '2026-01-01T00:00:00.000Z' },
          },
          columnOrder: {
            review: [task.id, 'staleTask'],
            approved: ['staleTask'],
          },
        },
        null,
        2
      )
    );

    const inboxDir = path.join(claudeDir, 'teams', 'my-team', 'inboxes');
    fs.mkdirSync(inboxDir, { recursive: true });
    fs.writeFileSync(
      path.join(inboxDir, 'bob.json'),
      JSON.stringify(
        [
          {
            from: 'alice',
            to: 'bob',
            summary: `Please revisit #${task.displayId}`,
            messageId: 'm-1',
            timestamp: '2026-02-23T10:00:00.000Z',
            read: false,
            text: 'Need one more verification pass.',
          },
          {
            from: 'team-lead',
            to: 'bob',
            summary: `Comment on #${task.displayId}`,
            messageId: 'm-2',
            timestamp: '2026-02-23T11:00:00.000Z',
            read: false,
            text:
              `**Comment on task #${task.displayId}**\n> Ship migration\n\n> Heads up\n\n` +
              '<agent-block>\nReply to this comment using:\nnode "tool.js" --team my-team task comment 1 --text "..." --from "bob"\n</agent-block>',
          },
        ],
        null,
        2
      )
    );

    const first = controller.maintenance.reconcileArtifacts({ reason: 'manual' });
    expect(first.staleKanbanEntriesRemoved).toBe(1);
    expect(first.staleColumnOrderRefsRemoved).toBe(2);
    expect(first.linkedCommentsCreated).toBe(1);

    const reloaded = controller.tasks.getTask(task.id);
    expect(reloaded.comments).toHaveLength(1);
    expect(reloaded.comments[0].id).toBe('msg-m-1');
    expect(reloaded.comments[0].text).toBe('Need one more verification pass.');

    const cleanedKanban = JSON.parse(fs.readFileSync(kanbanPath, 'utf8'));
    expect(cleanedKanban.tasks.staleTask).toBeUndefined();
    expect(cleanedKanban.columnOrder.review).toEqual([task.id]);
    expect(cleanedKanban.columnOrder.approved).toBeUndefined();

    const second = controller.maintenance.reconcileArtifacts({ reason: 'manual' });
    expect(second.staleKanbanEntriesRemoved).toBe(0);
    expect(second.staleColumnOrderRefsRemoved).toBe(0);
    expect(second.linkedCommentsCreated).toBe(0);
  });

  it('tracks lifecycle history and intervals without duplicate same-status transitions', () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const task = controller.tasks.createTask({ subject: 'Lifecycle task' });

    expect(task.status).toBe('pending');
    expect(task.historyEvents).toHaveLength(1);
    expect(task.workIntervals).toBeUndefined();

    const started = controller.tasks.startTask(task.id, 'bob');
    const startedAgain = controller.tasks.startTask(task.id, 'bob');
    const completed = controller.tasks.completeTask(task.id, 'bob');
    const completedAgain = controller.tasks.completeTask(task.id, 'bob');
    const deleted = controller.tasks.softDeleteTask(task.id, 'bob');
    const restored = controller.tasks.restoreTask(task.id, 'bob');

    expect(started.status).toBe('in_progress');
    expect(startedAgain.historyEvents).toHaveLength(2);
    expect(startedAgain.workIntervals).toHaveLength(1);
    expect(startedAgain.workIntervals[0].startedAt).toBeTruthy();

    expect(completed.status).toBe('completed');
    expect(completedAgain.historyEvents).toHaveLength(3);
    expect(completedAgain.workIntervals).toHaveLength(1);
    expect(completedAgain.workIntervals[0].completedAt).toBeTruthy();

    expect(deleted.status).toBe('deleted');
    expect(deleted.deletedAt).toBeTruthy();
    expect(restored.status).toBe('pending');
    expect(restored.deletedAt).toBeUndefined();
    expect(restored.historyEvents).toHaveLength(5);

    // Verify the event sequence: task_created, then 4 status_changed events
    const types = restored.historyEvents.map((e) => e.type);
    expect(types).toEqual([
      'task_created',
      'status_changed',
      'status_changed',
      'status_changed',
      'status_changed',
    ]);

    // Verify the status flow: pending -> in_progress -> completed -> deleted -> pending
    const firstEvent = restored.historyEvents[0];
    expect(firstEvent.status).toBe('pending');
    const statusChanges = restored.historyEvents.slice(1).map((e) => e.to);
    expect(statusChanges).toEqual(['in_progress', 'completed', 'deleted', 'pending']);
  });

  it('does not treat malformed empty completedAt work intervals as already open', () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const task = controller.tasks.createTask({ subject: 'Malformed work interval' });
    const taskPath = path.join(claudeDir, 'tasks', 'my-team', `${task.id}.json`);
    const rawTask = JSON.parse(fs.readFileSync(taskPath, 'utf8'));
    rawTask.workIntervals = [{ startedAt: '2026-01-01T00:00:00.000Z', completedAt: '' }];
    fs.writeFileSync(taskPath, JSON.stringify(rawTask, null, 2));

    controller.tasks.startTask(task.id, 'bob');
    const reloaded = controller.tasks.getTask(task.id);

    expect(reloaded.workIntervals).toHaveLength(2);
    expect(reloaded.workIntervals[0].completedAt).toBe('');
    expect(reloaded.workIntervals[1].completedAt).toBeUndefined();
  });

  it('tracks owner assignment history without duplicate same-owner events', () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const task = controller.tasks.createTask({ subject: 'Owner history' });

    controller.tasks.setTaskOwner(task.id, 'bob', 'team-lead');
    controller.tasks.setTaskOwner(task.id, 'bob', 'team-lead');
    controller.tasks.setTaskOwner(task.id, 'alice', 'team-lead');
    controller.tasks.setTaskOwner(task.id, null, 'team-lead');

    const ownerEvents = controller.tasks
      .getTask(task.id)
      .historyEvents.filter((event) => event.type === 'owner_changed');

    expect(ownerEvents).toHaveLength(3);
    expect(ownerEvents[0]).toMatchObject({ to: 'bob', actor: 'team-lead' });
    expect(ownerEvents[1]).toMatchObject({ from: 'bob', to: 'alice', actor: 'team-lead' });
    expect(ownerEvents[2]).toMatchObject({ from: 'alice', actor: 'team-lead' });
    expect(ownerEvents[2].to).toBeUndefined();
  });

  it('wraps review instructions in the canonical agent block format used by the UI', () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const task = controller.tasks.createTask({ subject: 'Review me', owner: 'bob' });

    controller.kanban.addReviewer('alice');
    controller.tasks.completeTask(task.id, 'bob');
    controller.review.requestReview(task.id, { from: 'team-lead' });

    const reviewerInboxPath = path.join(claudeDir, 'teams', 'my-team', 'inboxes', 'alice.json');
    const inbox = JSON.parse(fs.readFileSync(reviewerInboxPath, 'utf8'));

    expect(inbox).toHaveLength(1);
    expect(inbox[0].text).toContain('<info_for_agent>');
    expect(inbox[0].text).toContain('CURRENT review cycle');
    expect(inbox[0].text).toContain('Before declaring it duplicate, call task_get');
    expect(inbox[0].text).toContain('reviewState/status');
    expect(inbox[0].text).toContain('review_approve');
    expect(inbox[0].text).not.toContain('<agent-block>');
    expect(inbox[0].leadSessionId).toBe('lead-session-1');
  });

  it('ignores mismatched leadSessionId placeholders on review_request and uses canonical config session', () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const task = controller.tasks.createTask({ subject: 'Review me', owner: 'bob' });

    controller.kanban.addReviewer('alice');
    controller.tasks.completeTask(task.id, 'bob');
    controller.review.requestReview(task.id, {
      from: 'team-lead',
      leadSessionId: 'team-lead',
    });

    const reviewerInboxPath = path.join(claudeDir, 'teams', 'my-team', 'inboxes', 'alice.json');
    const inbox = JSON.parse(fs.readFileSync(reviewerInboxPath, 'utf8'));

    expect(inbox).toHaveLength(1);
    expect(inbox[0].leadSessionId).toBe('lead-session-1');
  });

  it('starts review idempotently after review_request', () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const task = controller.tasks.createTask({ subject: 'Review me', owner: 'bob' });

    controller.tasks.completeTask(task.id, 'bob');
    controller.review.requestReview(task.id, { from: 'team-lead', reviewer: 'alice' });

    const result = controller.review.startReview(task.id, { from: 'alice' });
    expect(result.ok).toBe(true);
    expect(result.taskId).toBe(task.id);
    expect(result.displayId).toBe(task.displayId);
    expect(result.column).toBe('review');

    // Verify kanban state
    const kanbanState = controller.kanban.getKanbanState();
    expect(kanbanState.tasks[task.id].column).toBe('review');

    // Verify task reviewState
    const updatedTask = controller.tasks.getTask(task.id);
    expect(updatedTask.reviewState).toBe('review');

    // Verify history event
    const reviewEvent = updatedTask.historyEvents.find((e) => e.type === 'review_started');
    expect(reviewEvent).toBeDefined();
    expect(reviewEvent.from).toBe('review');
    expect(reviewEvent.to).toBe('review');
    expect(reviewEvent.actor).toBe('alice');
    expect(updatedTask.reviewIntervals).toHaveLength(1);
    expect(updatedTask.reviewIntervals[0].reviewer).toBe('alice');
    expect(updatedTask.reviewIntervals[0].startedAt).toBeTruthy();
    expect(updatedTask.reviewIntervals[0].completedAt).toBeUndefined();

    // Idempotent: calling again should also succeed without duplicate events
    const again = controller.review.startReview(task.id, { from: 'alice' });
    expect(again.ok).toBe(true);
    const reloaded = controller.tasks.getTask(task.id);
    const startedEvents = reloaded.historyEvents.filter((e) => e.type === 'review_started');
    expect(startedEvents).toHaveLength(1);
    expect(reloaded.reviewIntervals).toHaveLength(1);
  });

  it('closes review intervals when review is approved or changes are requested', () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const approvedTask = controller.tasks.createTask({ subject: 'Approve review', owner: 'bob' });

    controller.tasks.completeTask(approvedTask.id, 'bob');
    controller.review.requestReview(approvedTask.id, { from: 'team-lead', reviewer: 'alice' });
    controller.review.startReview(approvedTask.id, { from: 'alice' });
    const approved = controller.review.approveReview(approvedTask.id, { from: 'alice' });

    expect(approved.reviewIntervals).toHaveLength(1);
    expect(approved.reviewIntervals[0].reviewer).toBe('alice');
    expect(approved.reviewIntervals[0].completedAt).toBeTruthy();

    const changesTask = controller.tasks.createTask({ subject: 'Request changes', owner: 'bob' });
    controller.tasks.completeTask(changesTask.id, 'bob');
    controller.review.requestReview(changesTask.id, { from: 'team-lead', reviewer: 'alice' });
    controller.review.startReview(changesTask.id, { from: 'alice' });
    const changed = controller.review.requestChanges(changesTask.id, {
      from: 'alice',
      comment: 'Needs a fix.',
    });

    expect(changed.reviewIntervals).toHaveLength(1);
    expect(changed.reviewIntervals[0].reviewer).toBe('alice');
    expect(changed.reviewIntervals[0].completedAt).toBeTruthy();
  });

  it('does not treat malformed empty completedAt review intervals as already open', () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const task = controller.tasks.createTask({ subject: 'Review me', owner: 'bob' });

    controller.tasks.completeTask(task.id, 'bob');
    controller.review.requestReview(task.id, { from: 'team-lead', reviewer: 'alice' });

    const taskPath = path.join(claudeDir, 'tasks', 'my-team', `${task.id}.json`);
    const rawTask = JSON.parse(fs.readFileSync(taskPath, 'utf8'));
    rawTask.reviewIntervals = [
      { reviewer: 'alice', startedAt: '2026-01-01T00:00:00.000Z', completedAt: '' },
    ];
    fs.writeFileSync(taskPath, JSON.stringify(rawTask, null, 2));

    controller.review.startReview(task.id, { from: 'alice' });
    const reloaded = controller.tasks.getTask(task.id);

    expect(reloaded.reviewIntervals).toHaveLength(2);
    expect(reloaded.reviewIntervals[0].completedAt).toBe('');
    expect(reloaded.reviewIntervals[1].reviewer).toBe('alice');
    expect(reloaded.reviewIntervals[1].completedAt).toBeUndefined();
  });

  it('records review_start after review_request and surfaces review_in_progress for the reviewer', async () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const task = controller.tasks.createTask({ subject: 'Queued for review', owner: 'bob' });

    controller.tasks.completeTask(task.id, 'bob');
    controller.review.requestReview(task.id, { from: 'team-lead', reviewer: 'alice' });
    const started = controller.review.startReview(task.id, { from: 'alice' });

    expect(started.ok).toBe(true);
    const reloaded = controller.tasks.getTask(task.id);
    const requestedEvents = reloaded.historyEvents.filter((e) => e.type === 'review_requested');
    const startedEvents = reloaded.historyEvents.filter((e) => e.type === 'review_started');
    expect(requestedEvents).toHaveLength(1);
    expect(startedEvents).toHaveLength(1);
    expect(startedEvents[0].from).toBe('review');
    expect(startedEvents[0].to).toBe('review');
    expect(startedEvents[0].actor).toBe('alice');

    const reviewerBriefing = await controller.tasks.taskBriefing('alice');
    expect(reviewerBriefing).toContain(`#${task.displayId}`);
    expect(reviewerBriefing).toContain('reason=review_in_progress');
    expect(reviewerBriefing).toContain('reviewer=alice');
  });

  it('uses the assigned reviewer when review_start omits from', async () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const task = controller.tasks.createTask({
      subject: 'Queued for implicit reviewer',
      owner: 'bob',
    });

    controller.tasks.completeTask(task.id, 'bob');
    controller.review.requestReview(task.id, { from: 'team-lead', reviewer: 'alice' });
    controller.review.startReview(task.id);

    const reloaded = controller.tasks.getTask(task.id);
    const startedEvent = reloaded.historyEvents.find((event) => event.type === 'review_started');
    expect(startedEvent.actor).toBe('alice');

    const reviewerBriefing = await controller.tasks.taskBriefing('alice');
    expect(reviewerBriefing).toContain(`#${task.displayId}`);
    expect(reviewerBriefing).toContain('reason=review_in_progress');
    expect(reviewerBriefing).toContain('reviewer=alice');
  });

  it('rejects review terminal transitions outside active completed review tasks', () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });

    const pendingTask = controller.tasks.createTask({ subject: 'Pending task', owner: 'bob' });
    expect(() => controller.review.approveReview(pendingTask.id, { from: 'alice' })).toThrow(
      'must be completed before approval'
    );

    const completedTask = controller.tasks.createTask({
      subject: 'Completed but not review',
      owner: 'bob',
    });
    controller.tasks.completeTask(completedTask.id, 'bob');
    expect(() =>
      controller.review.requestChanges(completedTask.id, { from: 'alice', comment: 'Fix it' })
    ).toThrow('must be in review before requesting changes');

    const deletedTask = controller.tasks.createTask({
      subject: 'Deleted review task',
      owner: 'bob',
    });
    controller.tasks.softDeleteTask(deletedTask.id, 'bob');
    expect(() => controller.review.approveReview(deletedTask.id, { from: 'alice' })).toThrow(
      'is deleted'
    );
    expect(() =>
      controller.review.requestChanges(deletedTask.id, { from: 'alice', comment: 'Fix it' })
    ).toThrow('is deleted');
    expect(controller.tasks.getTask(deletedTask.id).status).toBe('deleted');
  });

  it('rejects review_start outside active review and keeps owner routing intact', async () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });

    const pendingTask = controller.tasks.createTask({
      subject: 'Pending implementation',
      owner: 'bob',
    });
    expect(() => controller.review.startReview(pendingTask.id, { from: 'alice' })).toThrow(
      'must be completed before starting review'
    );
    expect(controller.tasks.getTask(pendingTask.id).reviewState).toBe('none');

    const completedTask = controller.tasks.createTask({
      subject: 'Completed without review request',
      owner: 'bob',
    });
    controller.tasks.completeTask(completedTask.id, 'bob');
    expect(() => controller.review.startReview(completedTask.id, { from: 'alice' })).toThrow(
      'must be in review before starting review'
    );

    const bobBriefing = await controller.tasks.taskBriefing('bob');
    expect(bobBriefing).toContain(`#${pendingTask.displayId}`);
    expect(bobBriefing).toContain('actionOwner=@bob');
    expect(bobBriefing).not.toContain('reason=review_in_progress');
  });

  it('rejects direct kanban lifecycle bypasses while allowing repair of matching review state', () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });

    const pendingTask = controller.tasks.createTask({
      subject: 'Kanban bypass pending',
      owner: 'bob',
    });
    expect(() => controller.kanban.setKanbanColumn(pendingTask.id, 'approved')).toThrow(
      'must be completed before moving to APPROVED column'
    );

    const completedTask = controller.tasks.createTask({
      subject: 'Kanban bypass completed',
      owner: 'bob',
    });
    controller.tasks.completeTask(completedTask.id, 'bob');
    expect(() => controller.kanban.setKanbanColumn(completedTask.id, 'review')).toThrow(
      'must be in review before moving to REVIEW column'
    );

    controller.review.requestReview(completedTask.id, { from: 'team-lead', reviewer: 'alice' });
    const kanbanPath = path.join(claudeDir, 'teams', 'my-team', 'kanban-state.json');
    const state = JSON.parse(fs.readFileSync(kanbanPath, 'utf8'));
    delete state.tasks[completedTask.id];
    fs.writeFileSync(kanbanPath, JSON.stringify(state, null, 2));

    controller.kanban.setKanbanColumn(completedTask.id, 'review');
    expect(controller.kanban.getKanbanState().tasks[completedTask.id].column).toBe('review');
  });

  it('rejects review_request for already approved tasks until work is reopened', () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const task = controller.tasks.createTask({ subject: 'Approved terminal task', owner: 'bob' });

    controller.tasks.completeTask(task.id, 'bob');
    controller.review.requestReview(task.id, { from: 'team-lead', reviewer: 'alice' });
    controller.review.startReview(task.id, { from: 'alice' });
    controller.review.approveReview(task.id, { from: 'alice' });

    expect(() =>
      controller.review.requestReview(task.id, { from: 'team-lead', reviewer: 'alice' })
    ).toThrow('is already approved');
    expect(controller.tasks.getTask(task.id).reviewState).toBe('approved');
    expect(controller.kanban.getKanbanState().tasks[task.id].column).toBe('approved');
  });

  it('repairs kanban on idempotent review transitions without duplicate history', () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const task = controller.tasks.createTask({ subject: 'Repair review column', owner: 'bob' });

    controller.tasks.completeTask(task.id, 'bob');
    controller.review.requestReview(task.id, { from: 'team-lead', reviewer: 'alice' });
    controller.review.startReview(task.id, { from: 'alice' });

    const kanbanPath = path.join(claudeDir, 'teams', 'my-team', 'kanban-state.json');
    const reviewState = JSON.parse(fs.readFileSync(kanbanPath, 'utf8'));
    delete reviewState.tasks[task.id];
    reviewState.columnOrder = { review: [] };
    fs.writeFileSync(kanbanPath, JSON.stringify(reviewState, null, 2));

    controller.review.startReview(task.id, { from: 'alice' });
    expect(controller.kanban.getKanbanState().tasks[task.id].column).toBe('review');
    expect(
      controller.tasks
        .getTask(task.id)
        .historyEvents.filter((event) => event.type === 'review_started')
    ).toHaveLength(1);

    controller.review.approveReview(task.id, { from: 'alice' });
    const approvedState = JSON.parse(fs.readFileSync(kanbanPath, 'utf8'));
    delete approvedState.tasks[task.id];
    approvedState.columnOrder = { approved: [] };
    fs.writeFileSync(kanbanPath, JSON.stringify(approvedState, null, 2));

    const approvedAgain = controller.review.approveReview(task.id, { from: 'alice' });
    expect(approvedAgain.alreadyApproved).toBe(true);
    expect(controller.kanban.getKanbanState().tasks[task.id].column).toBe('approved');
    expect(
      controller.tasks
        .getTask(task.id)
        .historyEvents.filter((event) => event.type === 'review_approved')
    ).toHaveLength(1);
  });

  it('throws when starting review on a deleted task', () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const task = controller.tasks.createTask({ subject: 'Deleted task', owner: 'bob' });
    controller.tasks.softDeleteTask(task.id, 'bob');

    expect(() => controller.review.startReview(task.id, { from: 'alice' })).toThrow('is deleted');
  });

  it('clears stale needsFix reviewState when owner restarts work', async () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const task = controller.tasks.createTask({ subject: 'Needs fix restart', owner: 'bob' });

    controller.tasks.completeTask(task.id, 'bob');
    controller.review.requestReview(task.id, { from: 'team-lead', reviewer: 'alice' });
    controller.review.requestChanges(task.id, { from: 'alice', comment: 'Please fix.' });
    const started = controller.tasks.startTask(task.id, 'bob');

    expect(started.status).toBe('in_progress');
    expect(started.reviewState).toBe('none');
    expect(controller.tasks.getTask(task.id).reviewState).toBe('none');
    expect(controller.tasks.listTaskInventory({ owner: 'bob' })[0].reviewState).toBe('none');

    const briefing = await controller.tasks.taskBriefing('bob');
    expect(briefing).toContain('reason=owner_executing');
    expect(briefing).not.toContain('reason=needs_fix');
  });

  it('persists full inbox metadata through controller messages.sendMessage', () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });

    const sent = controller.messages.sendMessage({
      to: 'bob',
      from: 'team-lead',
      text: 'Need your review',
      summary: 'Review request',
      commentId: 'comment-123',
      relayOfMessageId: 'm-original-1',
      source: 'system_notification',
      messageKind: 'member_work_sync_nudge',
      workSyncIntent: 'review_pickup',
      workSyncIntentKey: 'review-pickup:evt-1',
      workSyncReviewRequestEventIds: ['evt-1'],
      leadSessionId: 'session-42',
      attachments: [{ id: 'a1', filename: 'note.txt', mimeType: 'text/plain', size: 7 }],
    });

    expect(sent.deliveredToInbox).toBe(true);
    expect(sent.messageId).toBeTruthy();

    const inboxPath = path.join(claudeDir, 'teams', 'my-team', 'inboxes', 'bob.json');
    const rows = JSON.parse(fs.readFileSync(inboxPath, 'utf8'));
    expect(rows).toHaveLength(1);
    expect(rows[0].source).toBe('system_notification');
    expect(rows[0].messageKind).toBe('member_work_sync_nudge');
    expect(rows[0].workSyncIntent).toBe('review_pickup');
    expect(rows[0].workSyncIntentKey).toBe('review-pickup:evt-1');
    expect(rows[0].workSyncReviewRequestEventIds).toEqual(['evt-1']);
    expect(rows[0].commentId).toBe('comment-123');
    expect(rows[0].relayOfMessageId).toBe('m-original-1');
    expect(rows[0].leadSessionId).toBe('session-42');
    expect(rows[0].attachments[0].filename).toBe('note.txt');
  });

  it('persists slash command metadata through controller messages.appendSentMessage', () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });

    controller.messages.appendSentMessage({
      from: 'user',
      to: 'alice',
      text: '/compact keep only kanban context',
      messageKind: 'slash_command',
      slashCommand: {
        name: 'compact',
        command: '/compact',
        args: 'keep only kanban context',
        knownDescription: 'Compact the active context',
      },
    });

    controller.messages.appendSentMessage({
      from: 'alice',
      to: 'user',
      text: 'Compacted context.',
      messageKind: 'slash_command_result',
      commandOutput: {
        stream: 'stdout',
        commandLabel: '/compact',
      },
    });

    const sentPath = path.join(claudeDir, 'teams', 'my-team', 'sentMessages.json');
    const rows = JSON.parse(fs.readFileSync(sentPath, 'utf8'));
    expect(rows).toHaveLength(2);
    expect(rows[0].messageKind).toBe('slash_command');
    expect(rows[0].slashCommand).toMatchObject({
      name: 'compact',
      command: '/compact',
      args: 'keep only kanban context',
    });
    expect(rows[1].messageKind).toBe('slash_command_result');
    expect(rows[1].commandOutput).toEqual({
      stream: 'stdout',
      commandLabel: '/compact',
    });
  });

  it('canonicalizes local message recipients and guards user-directed sender identity', () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });

    controller.messages.sendMessage({
      to: 'team-lead',
      from: 'bob',
      text: 'Need lead input',
      summary: 'Lead input',
      actionMode: 'ask',
    });

    const leadInboxPath = path.join(claudeDir, 'teams', 'my-team', 'inboxes', 'alice.json');
    const leadRows = JSON.parse(fs.readFileSync(leadInboxPath, 'utf8'));
    expect(leadRows).toHaveLength(1);
    expect(leadRows[0].to).toBe('alice');
    expect(leadRows[0].from).toBe('bob');
    expect(leadRows[0].actionMode).toBe('ask');

    controller.messages.sendMessage({
      to: 'user',
      from: 'lead',
      text: 'Visible user reply',
      summary: 'Reply',
    });

    const userInboxPath = path.join(claudeDir, 'teams', 'my-team', 'inboxes', 'user.json');
    const userRows = JSON.parse(fs.readFileSync(userInboxPath, 'utf8'));
    expect(userRows).toHaveLength(1);
    expect(userRows[0].to).toBe('user');
    expect(userRows[0].from).toBe('alice');

    expect(() =>
      controller.messages.sendMessage({
        to: 'user',
        text: 'Missing sender',
      })
    ).toThrow('message_send to user requires from to be the responding team member name');

    expect(() =>
      controller.messages.sendMessage({
        to: 'other-team.alice',
        from: 'bob',
        text: 'Wrong transport',
      })
    ).toThrow('message_send cannot target another team. Use cross_team_send with toTeam.');

    expect(() =>
      controller.messages.sendMessage({
        to: 'cross_team_send',
        from: 'bob',
        text: 'Wrong transport',
      })
    ).toThrow('message_send cannot target cross_team_send. Use cross_team_send with toTeam.');
  });

  it('prevents agent-facing message_send from impersonating the human user', () => {
    const claudeDir = makeClaudeDir();
    const appController = createController({ teamName: 'my-team', claudeDir });
    const agentController = createController({
      teamName: 'my-team',
      claudeDir,
      allowUserMessageSender: false,
    });

    const appMessage = appController.messages.sendMessage({
      to: 'team-lead',
      from: 'user',
      text: 'Real user question.',
      summary: 'User question',
    });
    expect(appMessage.deliveredToInbox).toBe(true);

    expect(() =>
      agentController.messages.sendMessage({
        to: 'team-lead',
        from: 'user',
        text: 'Forged user message.',
        summary: 'Forged',
      })
    ).toThrow('message_send from user is reserved for the human user');

    expect(() =>
      agentController.messages.sendMessage({
        to: 'team-lead',
        text: 'Missing sender should not default to user.',
      })
    ).toThrow('message_send requires from to be your configured teammate name');

    const agentMessage = agentController.messages.sendMessage({
      to: 'team-lead',
      from: 'bob',
      text: 'Legitimate teammate message.',
      summary: 'Teammate update',
    });
    expect(agentMessage.deliveredToInbox).toBe(true);

    const leadInboxPath = path.join(claudeDir, 'teams', 'my-team', 'inboxes', 'alice.json');
    const leadRows = JSON.parse(fs.readFileSync(leadInboxPath, 'utf8'));
    expect(leadRows.map((row) => row.from)).toEqual(['user', 'bob']);
  });

  it('wakes task owner on regular comment from another member', () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const task = controller.tasks.createTask({
      subject: 'Investigate',
      owner: 'bob',
      notifyOwner: false,
    });

    const commented = controller.tasks.addTaskComment(task.id, {
      from: 'alice',
      text: 'I found the root cause.',
    });

    expect(commented.task.comments.at(-1).text).toBe('I found the root cause.');
    const inboxPath = path.join(claudeDir, 'teams', 'my-team', 'inboxes', 'bob.json');
    const rows = JSON.parse(fs.readFileSync(inboxPath, 'utf8'));
    expect(rows).toHaveLength(1);
    expect(rows[0].summary).toContain(`#${task.displayId}`);
    expect(rows[0].text).toContain('I found the root cause.');
    expect(rows[0].leadSessionId).toBe('lead-session-1');
  });

  it('normalizes task comment authors at the write boundary', () => {
    const claudeDir = makeClaudeDir();
    fs.writeFileSync(
      path.join(claudeDir, 'teams', 'my-team', 'config.json'),
      JSON.stringify(
        {
          name: 'my-team',
          leadSessionId: 'lead-session-1',
          members: [
            { name: 'team-lead', role: 'team-lead', providerId: 'codex', provider: 'codex' },
            { name: 'bob', role: 'developer' },
          ],
        },
        null,
        2
      )
    );

    const controller = createController({ teamName: 'my-team', claudeDir });
    const task = controller.tasks.createTask({ subject: 'Review result', notifyOwner: false });

    const fromProvider = controller.tasks.addTaskComment(task.id, {
      from: 'codex',
      text: 'Lead runtime finished review.',
    });
    const fromAlias = controller.tasks.addTaskComment(task.id, {
      from: 'lead',
      text: 'Lead alias finished review.',
    });
    const fromUser = controller.tasks.addTaskComment(task.id, {
      from: 'User',
      text: 'User follow-up.',
    });
    const fromSystem = controller.tasks.addTaskComment(task.id, {
      from: 'System',
      text: 'System note.',
    });

    expect(fromProvider.comment.author).toBe('team-lead');
    expect(fromAlias.comment.author).toBe('team-lead');
    expect(fromUser.comment.author).toBe('user');
    expect(fromSystem.comment.author).toBe('system');
  });

  it('rejects provider and lead aliases for agent-facing task comments', () => {
    const claudeDir = makeClaudeDir();
    fs.writeFileSync(
      path.join(claudeDir, 'teams', 'my-team', 'config.json'),
      JSON.stringify(
        {
          name: 'my-team',
          leadSessionId: 'lead-session-1',
          members: [
            { name: 'team-lead', role: 'team-lead', providerId: 'codex', provider: 'codex' },
            { name: 'bob', role: 'developer' },
          ],
        },
        null,
        2
      )
    );

    const appController = createController({ teamName: 'my-team', claudeDir });
    const agentController = createController({
      teamName: 'my-team',
      claudeDir,
      allowUserMessageSender: false,
    });
    const task = appController.tasks.createTask({
      subject: 'Reject agent aliases',
      owner: 'bob',
      notifyOwner: false,
    });

    expect(() =>
      agentController.tasks.addTaskComment(task.id, {
        from: 'codex',
        text: 'Provider alias should not be accepted from MCP.',
      })
    ).toThrow('Unknown task comment author: codex');

    expect(() =>
      agentController.tasks.addTaskComment(task.id, {
        from: 'lead',
        text: 'Lead alias should not be accepted from MCP.',
      })
    ).toThrow('Unknown task comment author: lead');

    let unknownAuthorError;
    try {
      agentController.tasks.addTaskComment(task.id, {
        from: 'Codex',
        text: 'Provider alias case should not be accepted from MCP.',
      });
    } catch (error) {
      unknownAuthorError = error;
    }
    expect(unknownAuthorError.message).toContain('Unknown task comment author: Codex');
    expect(unknownAuthorError.message).toContain('Use one of: bob, team-lead');
    expect(unknownAuthorError.message).not.toContain('user');
    expect(unknownAuthorError.message).not.toContain('system');

    expect(appController.tasks.getTask(task.id).comments || []).toEqual([]);
  });

  it('does not map a real teammate named like the lead provider id to the lead', () => {
    const claudeDir = makeClaudeDir();
    fs.writeFileSync(
      path.join(claudeDir, 'teams', 'my-team', 'config.json'),
      JSON.stringify(
        {
          name: 'my-team',
          leadSessionId: 'lead-session-1',
          members: [
            { name: 'team-lead', role: 'team-lead', providerId: 'codex', provider: 'codex' },
            { name: 'codex', role: 'developer' },
          ],
        },
        null,
        2
      )
    );

    const controller = createController({ teamName: 'my-team', claudeDir });
    const task = controller.tasks.createTask({ subject: 'Member named codex', notifyOwner: false });

    const commented = controller.tasks.addTaskComment(task.id, {
      from: 'codex',
      text: 'Real teammate comment.',
    });

    expect(commented.comment.author).toBe('codex');

    const agentController = createController({
      teamName: 'my-team',
      claudeDir,
      allowUserMessageSender: false,
    });
    const agentCommented = agentController.tasks.addTaskComment(task.id, {
      from: 'codex',
      text: 'Agent-facing real teammate comment.',
    });

    expect(agentCommented.comment.author).toBe('codex');
  });

  it('rejects task comments from unknown authors', () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const task = controller.tasks.createTask({
      subject: 'Reject unknown author',
      notifyOwner: false,
    });

    expect(() =>
      controller.tasks.addTaskComment(task.id, {
        from: 'ghost',
        text: 'This should not be persisted.',
      })
    ).toThrow('Unknown task comment author: ghost');

    expect(controller.tasks.getTask(task.id).comments || []).toEqual([]);
  });

  it('prevents agent-facing task_add_comment from impersonating app-owned authors', () => {
    const claudeDir = makeClaudeDir();
    const appController = createController({ teamName: 'my-team', claudeDir });
    const agentController = createController({
      teamName: 'my-team',
      claudeDir,
      allowUserMessageSender: false,
    });
    const task = appController.tasks.createTask({
      subject: 'Reserved comment authors',
      notifyOwner: false,
    });

    const appComment = appController.tasks.addTaskComment(task.id, {
      from: 'user',
      text: 'Real user comment.',
    });
    expect(appComment.comment.author).toBe('user');

    expect(() =>
      agentController.tasks.addTaskComment(task.id, {
        from: 'user',
        text: 'Forged user comment.',
      })
    ).toThrow('task comment author "user" is reserved for app-owned writes');

    expect(() =>
      agentController.tasks.addTaskComment(task.id, {
        text: 'Missing sender should not default to lead.',
      })
    ).toThrow('task_add_comment requires from to be your configured teammate name');

    let unknownAuthorError;
    try {
      agentController.tasks.addTaskComment(task.id, {
        from: 'ghost',
        text: 'Unknown teammate should get a useful recovery error.',
      });
    } catch (error) {
      unknownAuthorError = error;
    }
    expect(unknownAuthorError.message).toContain('Unknown task comment author: ghost');
    expect(unknownAuthorError.message).not.toContain('user');
    expect(unknownAuthorError.message).not.toContain('system');

    const agentComment = agentController.tasks.addTaskComment(task.id, {
      from: 'bob',
      text: 'Legitimate teammate comment.',
    });
    expect(agentComment.comment.author).toBe('bob');

    const comments = agentController.tasks.getTask(task.id).comments || [];
    expect(comments.map((comment) => comment.author)).toEqual(['user', 'bob']);
  });

  it('keeps internal dependency comments when agent-facing task_complete unblocks a task', () => {
    const claudeDir = makeClaudeDir();
    const appController = createController({ teamName: 'my-team', claudeDir });
    const agentController = createController({
      teamName: 'my-team',
      claudeDir,
      allowUserMessageSender: false,
    });
    const dependency = appController.tasks.createTask({
      subject: 'Prepare calculator API',
      owner: 'bob',
      notifyOwner: false,
    });
    const blocked = appController.tasks.createTask({
      subject: 'Build calculator UI',
      owner: 'bob',
      'blocked-by': dependency.displayId,
      notifyOwner: false,
    });

    expect(() => agentController.tasks.completeTask(dependency.id, 'bob')).not.toThrow();

    const comments = appController.tasks.getTask(blocked.id).comments || [];
    expect(comments).toHaveLength(1);
    expect(comments[0].author).toBe('system');
    expect(comments[0].id).toBe(`dep-resolved-${dependency.id}-${blocked.id}`);
    expect(comments[0].text).toContain('Dependency resolved');

    const inboxPath = path.join(claudeDir, 'teams', 'my-team', 'inboxes', 'bob.json');
    const rows = JSON.parse(fs.readFileSync(inboxPath, 'utf8'));
    expect(rows).toHaveLength(1);
    expect(rows[0].from).toBe('alice');
    expect(rows[0].text).toContain('Dependency resolved');
  });

  it('includes the assigned task ref in owner assignment notifications', () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });

    const task = controller.tasks.createTask({
      subject: 'Implement runtime handoff',
      owner: 'bob',
      descriptionTaskRefs: [{ taskId: 'related-task', displayId: 'rel12345', teamName: 'my-team' }],
    });

    const inboxPath = path.join(claudeDir, 'teams', 'my-team', 'inboxes', 'bob.json');
    const rows = JSON.parse(fs.readFileSync(inboxPath, 'utf8'));
    expect(rows).toHaveLength(1);
    expect(rows[0].summary).toBe(`New task #${task.displayId} assigned`);
    expect(rows[0].taskRefs).toEqual([
      { taskId: task.id, displayId: task.displayId, teamName: 'my-team' },
      { taskId: 'related-task', displayId: 'rel12345', teamName: 'my-team' },
    ]);
  });

  it('does not wake owner for self-comments and keeps user clarification sticky until explicitly cleared', () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const task = controller.tasks.createTask({
      subject: 'Need product input',
      owner: 'bob',
      needsClarification: 'user',
      notifyOwner: false,
    });

    controller.tasks.addTaskComment(task.id, {
      from: 'bob',
      text: 'Starting to investigate.',
    });

    const ownerInboxPath = path.join(claudeDir, 'teams', 'my-team', 'inboxes', 'bob.json');
    expect(fs.existsSync(ownerInboxPath)).toBe(false);

    const replied = controller.tasks.addTaskComment(task.id, {
      from: 'user',
      text: 'Please use the safer option.',
    });

    expect(replied.task.needsClarification).toBe('user');
    const reloaded = controller.tasks.getTask(task.id);
    expect(reloaded.needsClarification).toBe('user');
    const rows = JSON.parse(fs.readFileSync(ownerInboxPath, 'utf8'));
    expect(rows).toHaveLength(1);
    expect(rows[0].text).toContain('Please use the safer option.');

    const cleared = controller.tasks.setNeedsClarification(task.id, 'clear');
    expect(cleared.needsClarification).toBeUndefined();
    expect(controller.tasks.getTask(task.id).needsClarification).toBeUndefined();
  });

  it('wakes lead owner on comment from another member', () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const task = controller.tasks.createTask({
      subject: 'Lead-owned task',
      owner: 'team-lead',
      notifyOwner: false,
    });

    controller.tasks.addTaskComment(task.id, {
      from: 'bob',
      text: 'Need your decision here.',
    });

    const inboxPath = path.join(claudeDir, 'teams', 'my-team', 'inboxes', 'alice.json');
    const rows = JSON.parse(fs.readFileSync(inboxPath, 'utf8'));
    expect(rows).toHaveLength(1);
    expect(rows[0].from).toBe('bob');
    expect(rows[0].to).toBe('alice');
    expect(rows[0].text).toContain('Need your decision here.');
  });

  it('moves review back to pending+needsFix and notifies owner on requestChanges', () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const task = controller.tasks.createTask({ subject: 'Needs revision', owner: 'bob' });

    controller.tasks.completeTask(task.id, 'bob');
    controller.review.requestReview(task.id, { from: 'alice', reviewer: 'alice' });
    const updated = controller.review.requestChanges(task.id, {
      from: 'alice',
      comment: 'Please address review feedback.',
    });

    expect(updated.status).toBe('pending');
    expect(updated.reviewState).toBe('needsFix');
    expect(updated.comments.at(-1).type).toBe('review_request');

    const inboxPath = path.join(claudeDir, 'teams', 'my-team', 'inboxes', 'bob.json');
    const rows = JSON.parse(fs.readFileSync(inboxPath, 'utf8'));
    expect(rows.at(-1).source).toBe('system_notification');
    expect(rows.at(-1).summary).toContain('Fix request');
    expect(rows.at(-1).text).toContain('moved back to pending');
    expect(rows.at(-1).text).toContain('request review again');
    expect(rows.at(-1).leadSessionId).toBe('lead-session-1');
  });

  it('ignores mismatched leadSessionId placeholders on review_approve owner notifications', () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const task = controller.tasks.createTask({ subject: 'Approve me', owner: 'bob' });

    controller.kanban.addReviewer('alice');
    controller.tasks.completeTask(task.id, 'bob');
    controller.review.requestReview(task.id, { from: 'team-lead', reviewer: 'alice' });
    controller.review.approveReview(task.id, {
      from: 'team-lead',
      note: 'Looks good.',
      'notify-owner': true,
      leadSessionId: 'team-lead',
    });

    const inboxPath = path.join(claudeDir, 'teams', 'my-team', 'inboxes', 'bob.json');
    const rows = JSON.parse(fs.readFileSync(inboxPath, 'utf8'));
    expect(rows.at(-1).summary).toContain('Approved');
    expect(rows.at(-1).leadSessionId).toBe('lead-session-1');
  });

  it('ignores mismatched leadSessionId placeholders on review_request_changes owner notifications', () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const task = controller.tasks.createTask({ subject: 'Needs revision', owner: 'bob' });

    controller.kanban.addReviewer('alice');
    controller.tasks.completeTask(task.id, 'bob');
    controller.review.requestReview(task.id, { from: 'team-lead', reviewer: 'alice' });
    controller.review.requestChanges(task.id, {
      from: 'alice',
      comment: 'Please address review feedback.',
      leadSessionId: 'team-lead',
    });

    const inboxPath = path.join(claudeDir, 'teams', 'my-team', 'inboxes', 'bob.json');
    const rows = JSON.parse(fs.readFileSync(inboxPath, 'utf8'));
    expect(rows.at(-1).summary).toContain('Fix request');
    expect(rows.at(-1).leadSessionId).toBe('lead-session-1');
  });

  it('keeps approved tasks in awareness ordered by freshness', async () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });

    const approvedTasks = Array.from({ length: 12 }, (_, index) =>
      controller.tasks.createTask({
        subject: `Approved ${index + 1}`,
        owner: 'bob',
        status: 'completed',
        reviewState: 'approved',
        createdAt: `2026-01-${String(index + 1).padStart(2, '0')}T00:00:00.000Z`,
      })
    );

    const briefing = await controller.tasks.taskBriefing('bob');
    expect(briefing).toContain('Awareness:');
    expect(briefing).toContain(`#${approvedTasks[11].displayId}`);
    expect(briefing).toContain(`#${approvedTasks[2].displayId}`);
    expect(briefing).toContain(`#${approvedTasks[1].displayId}`);
    expect(briefing).toContain(`#${approvedTasks[0].displayId}`);
    expect(briefing.indexOf(`#${approvedTasks[11].displayId}`)).toBeLessThan(
      briefing.indexOf(`#${approvedTasks[0].displayId}`)
    );
  });

  it('builds derived lead briefing and filtered task inventory', async () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });

    const queuedTask = controller.tasks.createTask({
      subject: 'Queued implementation',
      owner: 'bob',
      notifyOwner: false,
    });
    const unassignedTask = controller.tasks.createTask({
      subject: 'Needs owner',
      notifyOwner: false,
    });
    const reviewTask = controller.tasks.createTask({
      subject: 'Needs review pickup',
      owner: 'bob',
      notifyOwner: false,
    });

    controller.tasks.completeTask(reviewTask.id, 'bob');
    controller.review.requestReview(reviewTask.id, { from: 'alice', reviewer: 'alice' });

    const leadBriefing = await controller.tasks.leadBriefing();
    expect(leadBriefing).toContain('Lead queue for alice on team "my-team":');
    expect(leadBriefing).toContain(
      'Primary lead queue. Sections below already represent lead-owned actions or watch-only context.'
    );
    expect(leadBriefing).toContain(
      'Use task_list only for search, filtering, and drill-down inventory lookups.'
    );
    expect(leadBriefing).toContain('Needs owner assignment:');
    expect(leadBriefing).toContain(`#${unassignedTask.displayId}`);
    expect(leadBriefing).toContain('Lead-owned follow-up:');
    expect(leadBriefing).toContain(`#${reviewTask.displayId}`);

    const reviewInventory = controller.tasks.listTaskInventory({ reviewState: 'review' });
    expect(reviewInventory).toHaveLength(1);
    expect(reviewInventory[0].id).toBe(reviewTask.id);

    const ownerPendingInventory = controller.tasks.listTaskInventory({
      owner: 'bob',
      status: 'pending',
    });
    expect(ownerPendingInventory.map((task) => task.id)).toEqual([queuedTask.id]);
  });

  it('uses legacy kanban reviewer as a migration fallback for active review tasks', async () => {
    const claudeDir = makeClaudeDir();
    const configPath = path.join(claudeDir, 'teams', 'my-team', 'config.json');
    const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
    config.members.push({ name: 'carol', role: 'reviewer' });
    fs.writeFileSync(configPath, JSON.stringify(config, null, 2));

    const controller = createController({ teamName: 'my-team', claudeDir });
    const reviewTask = controller.tasks.createTask({
      subject: 'Legacy review assignment',
      owner: 'bob',
      status: 'completed',
      reviewState: 'review',
      notifyOwner: false,
    });

    fs.writeFileSync(
      path.join(claudeDir, 'teams', 'my-team', 'kanban-state.json'),
      JSON.stringify(
        {
          teamName: 'my-team',
          reviewers: [],
          tasks: {
            [reviewTask.id]: {
              column: 'review',
              reviewer: 'carol',
              movedAt: '2026-01-01T00:00:00.000Z',
            },
          },
        },
        null,
        2
      )
    );

    const reviewerBriefing = await controller.tasks.taskBriefing('carol');
    expect(reviewerBriefing).toContain(
      'Primary queue for carol. Act only on Actionable items. Awareness items are watch-only context unless the lead reroutes the task or you become the actionOwner.'
    );
    expect(reviewerBriefing).toContain('Actionable:');
    expect(reviewerBriefing).toContain(`#${reviewTask.displayId}`);
    expect(reviewerBriefing).toContain('reviewer=carol');

    const leadBriefing = await controller.tasks.leadBriefing();
    expect(leadBriefing).toContain(
      'Use task_list only for search, filtering, and drill-down inventory lookups.'
    );
    expect(leadBriefing).toContain('Watching:');
    expect(leadBriefing).toContain(`#${reviewTask.displayId}`);
    expect(leadBriefing).not.toContain('review_reviewer_missing');
  });

  it('does not treat role names containing lead as canonical team lead', async () => {
    const claudeDir = makeClaudeDir();
    const configPath = path.join(claudeDir, 'teams', 'my-team', 'config.json');
    fs.writeFileSync(
      configPath,
      JSON.stringify(
        {
          name: 'my-team',
          leadSessionId: 'lead-session-1',
          members: [
            { name: 'team-lead', agentType: 'team-lead' },
            { name: 'alice', role: 'tech lead' },
            { name: 'bob', role: 'developer' },
          ],
        },
        null,
        2
      )
    );

    const controller = createController({ teamName: 'my-team', claudeDir });
    const task = controller.tasks.createTask({ subject: 'Alice owns this', owner: 'alice' });
    const aliceBriefing = await controller.tasks.taskBriefing('alice');
    const leadBriefing = await controller.tasks.leadBriefing();

    expect(aliceBriefing).toContain('Actionable:');
    expect(aliceBriefing).toContain(`#${task.displayId}`);
    expect(aliceBriefing).toContain('actionOwner=@alice');
    expect(leadBriefing).not.toContain(`#${task.displayId}`);
  });

  it('recognizes lead and orchestrator agent types as canonical team leads', async () => {
    const claudeDir = makeClaudeDir();
    const configPath = path.join(claudeDir, 'teams', 'my-team', 'config.json');
    fs.writeFileSync(
      configPath,
      JSON.stringify(
        {
          name: 'my-team',
          leadSessionId: 'lead-session-1',
          members: [
            { name: 'alice', role: 'developer' },
            { name: 'leadbot', agentType: 'lead' },
            { name: 'opsbot', agentType: 'orchestrator' },
          ],
        },
        null,
        2
      )
    );

    const controller = createController({ teamName: 'my-team', claudeDir });
    const aliceTask = controller.tasks.createTask({ subject: 'Alice owns this', owner: 'alice' });
    const leadTask = controller.tasks.createTask({ subject: 'Lead owns this', owner: 'leadbot' });
    const aliceBriefing = await controller.tasks.taskBriefing('alice');
    const leadBriefing = await controller.tasks.leadBriefing();

    expect(aliceBriefing).toContain(`#${aliceTask.displayId}`);
    expect(aliceBriefing).toContain('actionOwner=@alice');
    expect(aliceBriefing).not.toContain(`#${leadTask.displayId}`);
    expect(leadBriefing).toContain(`#${leadTask.displayId}`);
    expect(leadBriefing).not.toContain(`#${aliceTask.displayId}`);
  });

  it('stores canonical member names for lead aliases in owners, reviewers, and reviewer config', () => {
    const claudeDir = makeClaudeDir();
    const configPath = path.join(claudeDir, 'teams', 'my-team', 'config.json');
    fs.writeFileSync(
      configPath,
      JSON.stringify(
        {
          name: 'my-team',
          members: [
            { name: 'leadbot', agentType: 'lead' },
            { name: 'alice', role: 'reviewer' },
            { name: 'bob', role: 'developer' },
          ],
        },
        null,
        2
      )
    );

    const controller = createController({ teamName: 'my-team', claudeDir });
    const leadOwnedTask = controller.tasks.createTask({
      subject: 'Lead alias owner',
      owner: 'lead',
    });
    expect(leadOwnedTask.owner).toBe('leadbot');
    expect(fs.existsSync(path.join(claudeDir, 'teams', 'my-team', 'inboxes', 'lead.json'))).toBe(
      false
    );

    const reassignedTask = controller.tasks.createTask({
      subject: 'Reassign alias owner',
      owner: 'bob',
    });
    expect(controller.tasks.setTaskOwner(reassignedTask.id, 'team-lead').owner).toBe('leadbot');

    controller.kanban.addReviewer('lead');
    expect(controller.kanban.listReviewers()).toEqual(['leadbot']);

    const reviewTask = controller.tasks.createTask({ subject: 'Review alias', owner: 'bob' });
    controller.tasks.completeTask(reviewTask.id, 'bob');
    controller.review.requestReview(reviewTask.id, { from: 'alice', reviewer: 'lead' });

    const requested = controller.tasks
      .getTask(reviewTask.id)
      .historyEvents.filter((event) => event.type === 'review_requested')
      .at(-1);
    expect(requested.reviewer).toBe('leadbot');
    expect(fs.existsSync(path.join(claudeDir, 'teams', 'my-team', 'inboxes', 'leadbot.json'))).toBe(
      true
    );
    expect(fs.existsSync(path.join(claudeDir, 'teams', 'my-team', 'inboxes', 'lead.json'))).toBe(
      false
    );
  });

  it('rejects task_briefing for unknown members', async () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });

    await expect(controller.tasks.taskBriefing('bbo')).rejects.toThrow(
      'Member not found in team metadata or inboxes: bbo'
    );
  });

  it('warns when task_briefing member exists only because of inbox state', async () => {
    const claudeDir = makeClaudeDir();
    const inboxDir = path.join(claudeDir, 'teams', 'my-team', 'inboxes');
    fs.mkdirSync(inboxDir, { recursive: true });
    fs.writeFileSync(path.join(inboxDir, 'bbo.json'), '[]', 'utf8');
    const controller = createController({ teamName: 'my-team', claudeDir });

    const briefing = await controller.tasks.taskBriefing('bbo');

    expect(briefing).toContain('Board warnings:');
    expect(briefing).toContain(
      'Member identity warning: bbo is known only from inbox state, not team config/member metadata. Verify the member name before acting.'
    );
  });

  it('clears kanban tasks and column order when review tasks leave review', () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const task = controller.tasks.createTask({ subject: 'Column cleanup', owner: 'bob' });

    controller.tasks.completeTask(task.id, 'bob');
    controller.review.requestReview(task.id, { from: 'team-lead', reviewer: 'alice' });
    controller.kanban.updateColumnOrder('review', [task.id]);
    controller.review.requestChanges(task.id, { from: 'alice', comment: 'Needs work.' });

    let kanbanState = controller.kanban.getKanbanState();
    expect(kanbanState.tasks[task.id]).toBeUndefined();
    expect(kanbanState.columnOrder).toBeUndefined();

    controller.tasks.completeTask(task.id, 'bob');
    controller.review.requestReview(task.id, { from: 'team-lead', reviewer: 'alice' });
    controller.kanban.updateColumnOrder('review', [task.id]);
    const deleted = controller.tasks.softDeleteTask(task.id, 'bob');

    expect(deleted.status).toBe('deleted');
    expect(deleted.reviewState).toBe('none');
    kanbanState = controller.kanban.getKanbanState();
    expect(kanbanState.tasks[task.id]).toBeUndefined();
    expect(kanbanState.columnOrder).toBeUndefined();
  });

  it('clears kanban tasks and column order when task_set_status deletes a review task', () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const task = controller.tasks.createTask({
      subject: 'Generic status delete cleanup',
      owner: 'bob',
    });

    controller.tasks.completeTask(task.id, 'bob');
    controller.review.requestReview(task.id, { from: 'team-lead', reviewer: 'alice' });
    controller.kanban.updateColumnOrder('review', [task.id]);
    const deleted = controller.tasks.setTaskStatus(task.id, 'deleted', 'bob');

    expect(deleted.status).toBe('deleted');
    expect(deleted.reviewState).toBe('none');
    const kanbanState = controller.kanban.getKanbanState();
    expect(kanbanState.tasks[task.id]).toBeUndefined();
    expect(kanbanState.columnOrder).toBeUndefined();
  });

  it('surfaces unreadable task rows as board anomalies', async () => {
    const claudeDir = makeClaudeDir();
    fs.writeFileSync(path.join(claudeDir, 'tasks', 'my-team', 'broken.json'), '{ bad json', 'utf8');
    const controller = createController({ teamName: 'my-team', claudeDir });

    const leadBriefing = await controller.tasks.leadBriefing();
    expect(leadBriefing).toContain('Board anomalies:');
    expect(leadBriefing).toContain('unreadable_task (broken)');
    expect(leadBriefing).toContain('anomalies=1');
  });

  it('caps large member briefings and points agents to drill-down tools', async () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });

    for (let i = 0; i < 60; i += 1) {
      controller.tasks.createTask({
        subject: `Large queue task ${i}`,
        description: 'x'.repeat(3000),
        owner: 'bob',
        status: 'in_progress',
        comments: Array.from({ length: 8 }, (_, index) => ({
          id: `comment-${i}-${index}`,
          author: 'bob',
          text: 'y'.repeat(1000),
          createdAt: new Date(Date.UTC(2026, 0, 1, 0, i, index)).toISOString(),
        })),
        notifyOwner: false,
      });
    }

    const briefing = await controller.tasks.taskBriefing('bob');
    const renderedTaskLines = briefing.split('\n').filter((line) => line.startsWith('- #'));
    expect(renderedTaskLines.length).toBe(50);
    expect(briefing).toContain('10 more Actionable item(s) omitted');
    expect(briefing).toContain('Use task_list filters and task_get for drill-down.');
    expect(briefing.length).toBeLessThan(100_000);
  });

  it('resets approved review state when work is reopened to pending', async () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const task = controller.tasks.createTask({ subject: 'Approved then reopened', owner: 'bob' });

    controller.tasks.completeTask(task.id, 'bob');
    controller.review.requestReview(task.id, { from: 'alice', reviewer: 'alice' });
    controller.review.approveReview(task.id, { from: 'alice' });
    const reopened = controller.tasks.setTaskStatus(task.id, 'pending', 'alice');

    expect(reopened.status).toBe('pending');
    expect(reopened.reviewState).toBe('none');
    expect(controller.tasks.listTaskInventory({ reviewState: 'approved' })).toHaveLength(0);
    expect(controller.tasks.listTaskInventory({ owner: 'bob' })[0].reviewState).toBe('none');

    const bobBriefing = await controller.tasks.taskBriefing('bob');
    expect(bobBriefing).toContain(`#${task.displayId}`);
    expect(bobBriefing).toContain('reason=owner_ready');
    expect(bobBriefing).toContain('actionOwner=@bob');
  });

  it('guards direct kanban_clear against active review state while keeping no-op clears safe', () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const task = controller.tasks.createTask({
      subject: 'Do not unapprove directly',
      owner: 'bob',
    });

    controller.tasks.completeTask(task.id, 'bob');
    controller.review.requestReview(task.id, { from: 'alice', reviewer: 'alice' });
    controller.review.approveReview(task.id, { from: 'alice' });

    expect(() => controller.kanban.clearKanban(task.id)).toThrow('reviewState=approved');
    expect(controller.tasks.getTask(task.id).reviewState).toBe('approved');
    expect(controller.kanban.getKanbanState().tasks[task.id].column).toBe('approved');

    controller.tasks.setTaskStatus(task.id, 'pending', 'alice');
    const noOpState = controller.kanban.clearKanban(task.id);
    expect(noOpState.tasks[task.id]).toBeUndefined();
    expect(controller.tasks.getTask(task.id).reviewState).toBe('none');
  });

  it('does not let inbox-only names become real owners or reviewers', async () => {
    const claudeDir = makeClaudeDir();
    const inboxDir = path.join(claudeDir, 'teams', 'my-team', 'inboxes');
    fs.mkdirSync(inboxDir, { recursive: true });
    fs.writeFileSync(path.join(inboxDir, 'boob.json'), '[]', 'utf8');
    const controller = createController({ teamName: 'my-team', claudeDir });
    const task = controller.tasks.createTask({ subject: 'Typo owner guard', owner: 'bob' });

    expect(() => controller.tasks.setTaskOwner(task.id, 'boob')).toThrow(
      'Unknown task owner: boob'
    );
    controller.tasks.completeTask(task.id, 'bob');
    expect(() =>
      controller.review.requestReview(task.id, { from: 'alice', reviewer: 'boob' })
    ).toThrow('Unknown reviewer: boob');

    const taskPath = path.join(claudeDir, 'tasks', 'my-team', `${task.id}.json`);
    const rawTask = JSON.parse(fs.readFileSync(taskPath, 'utf8'));
    rawTask.owner = 'boob';
    rawTask.status = 'pending';
    rawTask.reviewState = 'none';
    fs.writeFileSync(taskPath, JSON.stringify(rawTask, null, 2));

    const leadBriefing = await controller.tasks.leadBriefing();
    expect(leadBriefing).toContain(`#${task.displayId}`);
    expect(leadBriefing).toContain('reason=owner_invalid');
    expect(leadBriefing).toContain('Needs owner assignment:');
  });

  it('prevents deleted tasks from being resurrected by normal work tools', () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const task = controller.tasks.createTask({ subject: 'Deleted work guard', owner: 'bob' });

    controller.tasks.softDeleteTask(task.id, 'bob');

    expect(() => controller.tasks.startTask(task.id, 'bob')).toThrow(
      'use task_restore before starting work'
    );
    expect(() => controller.tasks.completeTask(task.id, 'bob')).toThrow(
      'use task_restore before changing status'
    );
    expect(() => controller.tasks.setTaskStatus(task.id, 'pending', 'bob')).toThrow(
      'use task_restore before changing status'
    );

    const restored = controller.tasks.restoreTask(task.id, 'alice');
    expect(restored.status).toBe('pending');
    expect(restored.reviewState).toBe('none');
  });

  it('rejects task_restore for non-deleted tasks', () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const task = controller.tasks.createTask({
      subject: 'Approved task must stay approved',
      owner: 'bob',
    });

    controller.tasks.completeTask(task.id, 'bob');
    controller.review.requestReview(task.id, { from: 'alice', reviewer: 'alice' });
    controller.review.approveReview(task.id, { from: 'alice' });

    expect(() => controller.tasks.restoreTask(task.id, 'alice')).toThrow(
      'task_restore only restores deleted tasks'
    );
    expect(controller.tasks.getTask(task.id).status).toBe('completed');
    expect(controller.tasks.getTask(task.id).reviewState).toBe('approved');
  });

  it('uses actual kanban overlay for kanbanColumn inventory filters', () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const task = controller.tasks.createTask({ subject: 'Approved without overlay', owner: 'bob' });

    controller.tasks.completeTask(task.id, 'bob');
    controller.review.requestReview(task.id, { from: 'alice', reviewer: 'alice' });
    controller.review.approveReview(task.id, { from: 'alice' });

    const kanbanPath = path.join(claudeDir, 'teams', 'my-team', 'kanban-state.json');
    const state = JSON.parse(fs.readFileSync(kanbanPath, 'utf8'));
    delete state.tasks[task.id];
    fs.writeFileSync(kanbanPath, JSON.stringify(state, null, 2));

    expect(
      controller.tasks.listTaskInventory({ reviewState: 'approved' }).map((row) => row.id)
    ).toContain(task.id);
    expect(controller.tasks.listTaskInventory({ kanbanColumn: 'approved' })).toHaveLength(0);
  });

  it('repairs an invalid review_started actor without losing the assigned reviewer', async () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const task = controller.tasks.createTask({ subject: 'Repair reviewer actor', owner: 'bob' });

    controller.tasks.completeTask(task.id, 'bob');
    controller.review.requestReview(task.id, { from: 'alice', reviewer: 'alice' });

    const taskPath = path.join(claudeDir, 'tasks', 'my-team', `${task.id}.json`);
    const rawTask = JSON.parse(fs.readFileSync(taskPath, 'utf8'));
    rawTask.historyEvents.push({
      id: 'bad-review-start',
      timestamp: '2026-01-01T00:00:00.000Z',
      type: 'review_started',
      from: 'review',
      to: 'review',
      actor: 'alicce',
    });
    fs.writeFileSync(taskPath, JSON.stringify(rawTask, null, 2));

    controller.review.startReview(task.id, { from: 'alice' });
    const startedEvents = controller.tasks
      .getTask(task.id)
      .historyEvents.filter((event) => event.type === 'review_started');
    expect(startedEvents.at(-1).actor).toBe('alice');

    const reviewerBriefing = await controller.tasks.taskBriefing('alice');
    expect(reviewerBriefing).toContain(`#${task.displayId}`);
    expect(reviewerBriefing).toContain('reviewer=alice');
    expect(reviewerBriefing).not.toContain('review_reviewer_missing');
  });

  it('repairs a valid but mismatched review_started actor back to the assigned reviewer', async () => {
    const claudeDir = makeClaudeDir();
    const configPath = path.join(claudeDir, 'teams', 'my-team', 'config.json');
    const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
    config.members.push({ name: 'carol', role: 'reviewer' });
    fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
    const controller = createController({ teamName: 'my-team', claudeDir });
    const task = controller.tasks.createTask({
      subject: 'Repair mismatched reviewer actor',
      owner: 'bob',
    });

    controller.tasks.completeTask(task.id, 'bob');
    controller.review.requestReview(task.id, { from: 'alice', reviewer: 'alice' });

    const taskPath = path.join(claudeDir, 'tasks', 'my-team', `${task.id}.json`);
    const rawTask = JSON.parse(fs.readFileSync(taskPath, 'utf8'));
    rawTask.historyEvents.push({
      id: 'wrong-review-start',
      timestamp: '2026-01-01T00:00:00.000Z',
      type: 'review_started',
      from: 'review',
      to: 'review',
      actor: 'carol',
    });
    rawTask.reviewIntervals = [{ reviewer: 'carol', startedAt: '2026-01-01T00:00:00.000Z' }];
    fs.writeFileSync(taskPath, JSON.stringify(rawTask, null, 2));

    controller.review.startReview(task.id);
    const repairedTask = controller.tasks.getTask(task.id);
    const startedEvents = repairedTask.historyEvents.filter(
      (event) => event.type === 'review_started'
    );
    expect(startedEvents.at(-1).actor).toBe('alice');
    expect(repairedTask.reviewIntervals).toHaveLength(2);
    expect(repairedTask.reviewIntervals[0]).toMatchObject({
      reviewer: 'carol',
      completedAt: expect.any(String),
    });
    expect(repairedTask.reviewIntervals[1].reviewer).toBe('alice');
    expect(repairedTask.reviewIntervals[1].completedAt).toBeUndefined();

    const aliceBriefing = await controller.tasks.taskBriefing('alice');
    const carolBriefing = await controller.tasks.taskBriefing('carol');
    expect(aliceBriefing).toContain(`#${task.displayId}`);
    expect(aliceBriefing).toContain('reviewer=alice');
    expect(carolBriefing).not.toContain('reason=review_in_progress');
  });

  it('bounds anomaly and subject rendering on primary queue surfaces', async () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const longSubject = `Long subject ${'x'.repeat(5000)}`;
    const task = controller.tasks.createTask({
      subject: longSubject,
      owner: 'bob',
      notifyOwner: false,
    });
    const kanbanPath = path.join(claudeDir, 'teams', 'my-team', 'kanban-state.json');
    fs.writeFileSync(
      kanbanPath,
      JSON.stringify(
        {
          teamName: 'my-team',
          reviewers: [],
          tasks: {
            missing: { column: 'review', movedAt: '2026-01-01T00:00:00.000Z' },
          },
          columnOrder: { review: ['missing', task.id] },
        },
        null,
        2
      )
    );
    fs.writeFileSync(
      path.join(claudeDir, 'tasks', 'my-team', 'bad-status.json'),
      JSON.stringify({ id: 'bad-status', subject: 'Bad status', status: 'inprogress' }, null, 2),
      'utf8'
    );
    for (let index = 0; index < 30; index += 1) {
      fs.writeFileSync(
        path.join(claudeDir, 'tasks', 'my-team', `broken-${index}.json`),
        '{ bad json',
        'utf8'
      );
    }

    const briefing = await controller.tasks.leadBriefing();
    expect(briefing).toContain('Board anomalies:');
    expect(briefing).toContain('Invalid task status "inprogress"');
    expect(briefing).toContain('stale_kanban_task (missing)');
    expect(briefing).toContain('more board anomaly item(s) omitted');
    expect(briefing).not.toContain('x'.repeat(1000));

    const inventoryRow = controller.tasks.listTaskInventory({ owner: 'bob' })[0];
    expect(inventoryRow.subject).toContain('[truncated]');
    expect(inventoryRow.subject.length).toBeLessThan(300);
  });

  it('marks stale processes stopped during listing and supports unregister', () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const processesPath = path.join(claudeDir, 'teams', 'my-team', 'processes.json');

    fs.writeFileSync(
      processesPath,
      JSON.stringify(
        [
          {
            id: 'stale-entry',
            pid: 999999,
            label: 'stale',
            registeredAt: '2024-01-01T00:00:00.000Z',
          },
        ],
        null,
        2
      )
    );

    const listed = controller.processes.listProcesses();
    expect(listed).toHaveLength(1);
    expect(listed[0].alive).toBe(false);
    expect(listed[0].stoppedAt).toBeTruthy();

    const persisted = JSON.parse(fs.readFileSync(processesPath, 'utf8'));
    expect(persisted[0].stoppedAt).toBeTruthy();

    controller.processes.unregisterProcess({ id: 'stale-entry' });
    expect(controller.processes.listProcesses()).toEqual([]);
  });

  it('task_add_comment succeeds even when owner notification write fails', () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const task = controller.tasks.createTask({
      subject: 'Comment resilience',
      owner: 'bob',
      notifyOwner: false,
    });

    // Make inboxes directory read-only to force notification write failure
    const inboxDir = path.join(claudeDir, 'teams', 'my-team', 'inboxes');
    fs.mkdirSync(inboxDir, { recursive: true });
    // Write a broken file that will cause JSON parse failure on append
    fs.writeFileSync(path.join(inboxDir, 'bob.json'), 'NOT VALID JSON');

    // Comment should still succeed despite notification failure
    const commented = controller.tasks.addTaskComment(task.id, {
      from: 'alice',
      text: 'This should persist despite notification failure.',
    });

    expect(commented.commentId).toBeTruthy();
    expect(commented.task.comments).toHaveLength(1);
    expect(commented.task.comments[0].text).toBe(
      'This should persist despite notification failure.'
    );
  });

  it('launches and stops a team through the runtime control API bridge', async () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const calls = [];

    const server = await startControlServer(async ({ method, url, body }) => {
      calls.push({ method, url, body });

      if (method === 'POST' && url === '/api/teams/my-team/launch') {
        return { body: { runId: 'run-123' } };
      }
      if (method === 'GET' && url === '/api/teams/provisioning/run-123') {
        return {
          body: {
            runId: 'run-123',
            teamName: 'my-team',
            state: 'ready',
            message: 'Ready',
            startedAt: '2026-03-12T00:00:00.000Z',
            updatedAt: '2026-03-12T00:00:01.000Z',
          },
        };
      }
      if (method === 'POST' && url === '/api/teams/my-team/stop') {
        return {
          body: {
            teamName: 'my-team',
            isAlive: false,
            runId: null,
            progress: null,
          },
        };
      }
      if (method === 'GET' && url === '/api/teams/my-team/runtime') {
        return {
          body: {
            teamName: 'my-team',
            isAlive: false,
            runId: null,
            progress: null,
          },
        };
      }

      return { statusCode: 404, body: { error: `Unhandled ${method} ${url}` } };
    });

    try {
      const launched = await controller.runtime.launchTeam({
        cwd: '/tmp/project',
        controlUrl: server.baseUrl,
      });
      expect(launched.runId).toBe('run-123');
      expect(launched.isAlive).toBe(true);
      expect(launched.progress.state).toBe('ready');

      const stopped = await controller.runtime.stopTeam({
        controlUrl: server.baseUrl,
      });
      expect(stopped.isAlive).toBe(false);
      expect(stopped.runId).toBeNull();

      expect(calls).toEqual([
        {
          method: 'POST',
          url: '/api/teams/my-team/launch',
          body: { cwd: '/tmp/project' },
        },
        {
          method: 'GET',
          url: '/api/teams/provisioning/run-123',
          body: undefined,
        },
        {
          method: 'POST',
          url: '/api/teams/my-team/stop',
          body: undefined,
        },
        {
          method: 'GET',
          url: '/api/teams/my-team/runtime',
          body: undefined,
        },
      ]);
    } finally {
      await server.close();
    }
  });

  it('forwards OpenCode runtime MCP calls to the app control API', async () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const calls = [];

    const server = await startControlServer(async ({ method, url, body }) => {
      calls.push({ method, url, body });
      if (method === 'POST' && url === '/api/teams/my-team/opencode/runtime/bootstrap-checkin') {
        return { body: { ok: true, state: 'accepted' } };
      }
      if (method === 'POST' && url === '/api/teams/my-team/opencode/runtime/deliver-message') {
        return { body: { ok: true, state: 'delivered' } };
      }
      if (method === 'POST' && url === '/api/teams/my-team/opencode/runtime/task-event') {
        return { body: { ok: true, state: 'recorded' } };
      }
      if (method === 'POST' && url === '/api/teams/my-team/opencode/runtime/heartbeat') {
        return { body: { ok: true, state: 'accepted' } };
      }
      return { statusCode: 404, body: { error: `Unhandled ${method} ${url}` } };
    });

    try {
      await controller.runtime.runtimeBootstrapCheckin({
        controlUrl: server.baseUrl,
        runId: 'run-oc',
        memberName: 'bob',
        runtimeSessionId: 'ses-1',
      });
      await controller.runtime.runtimeDeliverMessage({
        controlUrl: server.baseUrl,
        idempotencyKey: 'idem-1',
        runId: 'run-oc',
        fromMemberName: 'bob',
        runtimeSessionId: 'ses-1',
        to: 'user',
        text: 'hello',
      });
      await controller.runtime.runtimeTaskEvent({
        controlUrl: server.baseUrl,
        idempotencyKey: 'idem-task-1',
        runId: 'run-oc',
        memberName: 'bob',
        runtimeSessionId: 'ses-1',
        taskId: 'task-1',
        event: 'started',
      });
      await controller.runtime.runtimeHeartbeat({
        controlUrl: server.baseUrl,
        runId: 'run-oc',
        memberName: 'bob',
        runtimeSessionId: 'ses-1',
      });

      expect(calls.map((call) => call.url)).toEqual([
        '/api/teams/my-team/opencode/runtime/bootstrap-checkin',
        '/api/teams/my-team/opencode/runtime/deliver-message',
        '/api/teams/my-team/opencode/runtime/task-event',
        '/api/teams/my-team/opencode/runtime/heartbeat',
      ]);
      expect(calls[0].body).toEqual({
        teamName: 'my-team',
        runId: 'run-oc',
        memberName: 'bob',
        runtimeSessionId: 'ses-1',
      });
    } finally {
      await server.close();
    }
  });

  it('retries OpenCode bootstrap check-in on retryable control API failures', async () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const calls = [];

    const server = await startControlServer(async ({ method, url, body }) => {
      calls.push({ method, url, body });
      if (calls.length < 3) {
        return { statusCode: 500, body: { error: 'temporary bootstrap failure' } };
      }
      return { body: { ok: true, state: 'accepted', diagnostics: [] } };
    });

    try {
      const result = await controller.runtime.runtimeBootstrapCheckin({
        controlUrl: server.baseUrl,
        runId: 'run-oc',
        memberName: 'bob',
        runtimeSessionId: 'ses-1',
      });

      expect(result).toMatchObject({
        ok: true,
        state: 'accepted',
        diagnostics: expect.arrayContaining(['opencode_bootstrap_checkin_retry']),
      });
      expect(calls).toHaveLength(3);
      expect(calls.map((call) => call.body)).toEqual([
        {
          teamName: 'my-team',
          runId: 'run-oc',
          memberName: 'bob',
          runtimeSessionId: 'ses-1',
        },
        {
          teamName: 'my-team',
          runId: 'run-oc',
          memberName: 'bob',
          runtimeSessionId: 'ses-1',
        },
        {
          teamName: 'my-team',
          runId: 'run-oc',
          memberName: 'bob',
          runtimeSessionId: 'ses-1',
        },
      ]);
    } finally {
      await server.close();
    }
  });

  it('accepts idempotent OpenCode bootstrap check-in after a timed-out committed request', async () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const calls = [];
    let committed = false;

    const server = await startControlServer(async ({ method, url, body }) => {
      calls.push({ method, url, body });
      if (!committed) {
        committed = true;
        await new Promise((resolve) => setTimeout(resolve, 1200));
        return { body: { ok: true, state: 'accepted', diagnostics: [] } };
      }
      return {
        body: {
          ok: true,
          state: 'accepted',
          diagnostics: ['opencode_bootstrap_checkin_duplicate_accepted'],
        },
      };
    });

    try {
      const result = await controller.runtime.runtimeBootstrapCheckin({
        controlUrl: server.baseUrl,
        waitTimeoutMs: 1000,
        runId: 'run-oc',
        memberName: 'bob',
        runtimeSessionId: 'ses-1',
      });

      expect(result).toMatchObject({
        ok: true,
        state: 'accepted',
        diagnostics: expect.arrayContaining([
          'opencode_bootstrap_checkin_duplicate_accepted',
          'opencode_bootstrap_checkin_retry',
        ]),
      });
      expect(calls).toHaveLength(2);
    } finally {
      await server.close();
    }
  });

  it('does not retry OpenCode bootstrap check-in on validation failures', async () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const calls = [];

    const server = await startControlServer(async ({ method, url, body }) => {
      calls.push({ method, url, body });
      return { statusCode: 400, body: { error: 'invalid bootstrap payload' } };
    });

    try {
      await expect(
        controller.runtime.runtimeBootstrapCheckin({
          controlUrl: server.baseUrl,
          runId: 'run-oc',
          memberName: 'bob',
          runtimeSessionId: 'ses-1',
        })
      ).rejects.toThrow('invalid bootstrap payload');
      expect(calls).toHaveLength(1);
    } finally {
      await server.close();
    }
  });

  it('fails OpenCode bootstrap check-in clearly after bounded timeout retries', async () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const calls = [];

    const server = await startControlServer(async ({ method, url, body }) => {
      calls.push({ method, url, body });
      await new Promise((resolve) => setTimeout(resolve, 1200));
      return { body: { ok: true, state: 'accepted' } };
    });

    try {
      await expect(
        controller.runtime.runtimeBootstrapCheckin({
          controlUrl: server.baseUrl,
          waitTimeoutMs: 1000,
          runId: 'run-oc',
          memberName: 'bob',
          runtimeSessionId: 'ses-1',
        })
      ).rejects.toThrow('Timed out calling team control API');
      expect(calls).toHaveLength(3);
    } finally {
      await server.close();
    }
  });

  it('forwards member work sync status and reports to the app validator', async () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const calls = [];

    const server = await startControlServer(async ({ method, url, body }) => {
      calls.push({ method, url, body });
      if (method === 'POST' && url === '/api/teams/my-team/member-work-sync/bob/refresh') {
        return {
          body: {
            teamName: 'my-team',
            memberName: 'bob',
            state: 'needs_sync',
            agenda: {
              teamName: 'my-team',
              memberName: 'bob',
              generatedAt: '2026-04-29T00:00:00.000Z',
              fingerprint: 'agenda:v1:abc',
              items: [],
              diagnostics: [],
            },
            reportToken: 'wrs:v1.test.token',
            reportTokenExpiresAt: '2026-04-29T00:15:00.000Z',
            evaluatedAt: '2026-04-29T00:00:00.000Z',
            diagnostics: ['no_current_report'],
          },
        };
      }
      if (method === 'POST' && url === '/api/teams/my-team/member-work-sync/report') {
        return { body: { accepted: true, code: 'accepted', status: body } };
      }
      return { statusCode: 404, body: { error: `Unhandled ${method} ${url}` } };
    });

    try {
      const status = await controller.workSync.memberWorkSyncStatus({
        controlUrl: server.baseUrl,
        from: 'bob',
      });
      const report = await controller.workSync.memberWorkSyncReport({
        controlUrl: server.baseUrl,
        memberName: 'bob',
        state: 'still_working',
        agendaFingerprint: 'agenda:v1:abc',
        reportToken: 'wrs:v1.test.token',
        taskIds: [' task-1 ', '', 'task-1'],
        note: 'Continuing work',
        leaseTtlMs: 120000,
      });

      expect(status.state).toBe('needs_sync');
      expect(report.accepted).toBe(true);
      expect(calls).toEqual([
        {
          method: 'POST',
          url: '/api/teams/my-team/member-work-sync/bob/refresh',
          body: {},
        },
        {
          method: 'POST',
          url: '/api/teams/my-team/member-work-sync/report',
          body: {
            teamName: 'my-team',
            memberName: 'bob',
            state: 'still_working',
            agendaFingerprint: 'agenda:v1:abc',
            reportToken: 'wrs:v1.test.token',
            taskIds: ['task-1'],
            note: 'Continuing work',
            leaseTtlMs: 120000,
          },
        },
      ]);
    } finally {
      await server.close();
    }
  });

  it('records member work sync report intents only when the app validator is unavailable', async () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });

    const pending = await controller.workSync.memberWorkSyncReport({
      memberName: 'bob',
      state: 'still_working',
      agendaFingerprint: 'agenda:v1:abc',
      reportToken: 'wrs:v1.test.token',
      taskIds: ['task-1'],
    });

    expect(pending.pendingValidation).toBe(true);
    expect(pending.accepted).toBe(false);

    const intentFile = path.join(
      claudeDir,
      'teams',
      'my-team',
      '.member-work-sync',
      'pending-reports.json'
    );
    const intents = JSON.parse(fs.readFileSync(intentFile, 'utf8'));
    expect(Object.values(intents.intents)).toEqual([
      expect.objectContaining({
        teamName: 'my-team',
        memberName: 'bob',
        reason: 'control_api_unavailable',
        status: 'pending',
        request: expect.objectContaining({
          memberName: 'bob',
          source: 'mcp',
          reportToken: 'wrs:v1.test.token',
        }),
      }),
    ]);
  });

  it('does not record pending work sync intents for app-side validation rejections', async () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });

    const server = await startControlServer(async () => ({
      statusCode: 400,
      body: { error: 'stale_fingerprint' },
    }));

    try {
      await expect(
        controller.workSync.memberWorkSyncReport({
          controlUrl: server.baseUrl,
          memberName: 'bob',
          state: 'still_working',
          agendaFingerprint: 'agenda:v1:stale',
          reportToken: 'wrs:v1.test.token',
        })
      ).rejects.toThrow('stale_fingerprint');

      expect(
        fs.existsSync(
          path.join(claudeDir, 'teams', 'my-team', '.member-work-sync', 'pending-reports.json')
        )
      ).toBe(false);
    } finally {
      await server.close();
    }
  });

  it('prefers the published control endpoint over a stale env URL', async () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const previousUrl = process.env.CLAUDE_TEAM_CONTROL_URL;

    const server = await startControlServer(async ({ method, url }) => {
      if (method === 'POST' && url === '/api/teams/my-team/launch') {
        return { body: { runId: 'run-fresh' } };
      }
      if (method === 'GET' && url === '/api/teams/provisioning/run-fresh') {
        return {
          body: {
            runId: 'run-fresh',
            teamName: 'my-team',
            state: 'ready',
            message: 'Ready',
            startedAt: '2026-03-12T00:00:00.000Z',
            updatedAt: '2026-03-12T00:00:01.000Z',
          },
        };
      }
      return { statusCode: 404, body: { error: `Unhandled ${method} ${url}` } };
    });

    try {
      process.env.CLAUDE_TEAM_CONTROL_URL = 'http://127.0.0.1:1';
      writeControlApiState(claudeDir, server.baseUrl);

      const launched = await controller.runtime.launchTeam({
        cwd: '/tmp/project',
      });

      expect(launched.runId).toBe('run-fresh');
      expect(launched.progress.state).toBe('ready');
    } finally {
      if (previousUrl === undefined) {
        delete process.env.CLAUDE_TEAM_CONTROL_URL;
      } else {
        process.env.CLAUDE_TEAM_CONTROL_URL = previousUrl;
      }
      await server.close();
    }
  });

  it('falls back to the env endpoint when the published control file is stale', async () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const previousUrl = process.env.CLAUDE_TEAM_CONTROL_URL;

    const server = await startControlServer(async ({ method, url }) => {
      if (method === 'POST' && url === '/api/teams/my-team/launch') {
        return { body: { runId: 'run-env' } };
      }
      if (method === 'GET' && url === '/api/teams/provisioning/run-env') {
        return {
          body: {
            runId: 'run-env',
            teamName: 'my-team',
            state: 'ready',
            message: 'Ready',
            startedAt: '2026-03-12T00:00:00.000Z',
            updatedAt: '2026-03-12T00:00:01.000Z',
          },
        };
      }
      return { statusCode: 404, body: { error: `Unhandled ${method} ${url}` } };
    });

    try {
      process.env.CLAUDE_TEAM_CONTROL_URL = server.baseUrl;
      writeControlApiState(claudeDir, 'http://127.0.0.1:1');

      const launched = await controller.runtime.launchTeam({
        cwd: '/tmp/project',
      });

      expect(launched.runId).toBe('run-env');
      expect(launched.progress.state).toBe('ready');
    } finally {
      if (previousUrl === undefined) {
        delete process.env.CLAUDE_TEAM_CONTROL_URL;
      } else {
        process.env.CLAUDE_TEAM_CONTROL_URL = previousUrl;
      }
      await server.close();
    }
  });

  it('falls back to the next control endpoint when the first one responds with 404', async () => {
    const claudeDir = makeClaudeDir();
    const controller = createController({ teamName: 'my-team', claudeDir });
    const previousUrl = process.env.CLAUDE_TEAM_CONTROL_URL;

    const staleServer = await startControlServer(async () => {
      return { statusCode: 404, body: { error: 'Not found' } };
    });
    const liveServer = await startControlServer(async ({ method, url }) => {
      if (method === 'POST' && url === '/api/teams/my-team/launch') {
        return { body: { runId: 'run-live' } };
      }
      if (method === 'GET' && url === '/api/teams/provisioning/run-live') {
        return {
          body: {
            runId: 'run-live',
            teamName: 'my-team',
            state: 'ready',
            message: 'Ready',
            startedAt: '2026-03-12T00:00:00.000Z',
            updatedAt: '2026-03-12T00:00:01.000Z',
          },
        };
      }
      return { statusCode: 404, body: { error: `Unhandled ${method} ${url}` } };
    });

    try {
      writeControlApiState(claudeDir, staleServer.baseUrl);
      process.env.CLAUDE_TEAM_CONTROL_URL = liveServer.baseUrl;

      const launched = await controller.runtime.launchTeam({
        cwd: '/tmp/project',
      });

      expect(launched.runId).toBe('run-live');
      expect(launched.progress.state).toBe('ready');
    } finally {
      if (previousUrl === undefined) {
        delete process.env.CLAUDE_TEAM_CONTROL_URL;
      } else {
        process.env.CLAUDE_TEAM_CONTROL_URL = previousUrl;
      }
      await staleServer.close();
      await liveServer.close();
    }
  });

  describe('lookupMessage', () => {
    it('finds a message by exact messageId from sentMessages', () => {
      const claudeDir = makeClaudeDir();
      const controller = createController({ teamName: 'my-team', claudeDir });

      const sent = controller.messages.appendSentMessage({
        from: 'team-lead',
        to: 'bob',
        text: 'Please check the logs',
        source: 'user_sent',
      });

      const result = controller.messages.lookupMessage(sent.messageId);

      expect(result.message.messageId).toBe(sent.messageId);
      expect(result.message.text).toBe('Please check the logs');
      expect(result.store).toBe('sent');
    });

    it('finds a message by exact messageId from inbox', () => {
      const claudeDir = makeClaudeDir();
      const controller = createController({ teamName: 'my-team', claudeDir });

      const delivered = controller.messages.sendMessage({
        to: 'bob',
        from: 'user',
        text: 'Deploy to staging',
        source: 'inbox',
      });

      const result = controller.messages.lookupMessage(delivered.messageId);

      expect(result.message.messageId).toBe(delivered.messageId);
      expect(result.message.text).toBe('Deploy to staging');
      expect(result.store).toBe('inbox:bob');
    });

    it('throws on unknown messageId', () => {
      const claudeDir = makeClaudeDir();
      const controller = createController({ teamName: 'my-team', claudeDir });

      expect(() => controller.messages.lookupMessage('nonexistent-id')).toThrow(
        'Message not found: nonexistent-id'
      );
    });

    it('throws on missing messageId', () => {
      const claudeDir = makeClaudeDir();
      const controller = createController({ teamName: 'my-team', claudeDir });

      expect(() => controller.messages.lookupMessage('')).toThrow('Missing messageId');
    });

    it('does not match by relayOfMessageId', () => {
      const claudeDir = makeClaudeDir();
      const controller = createController({ teamName: 'my-team', claudeDir });

      controller.messages.sendMessage({
        to: 'bob',
        from: 'team-lead',
        text: 'Relayed message',
        relayOfMessageId: 'original-msg-123',
        source: 'system_notification',
      });

      // The relayOfMessageId should NOT be found as a direct messageId match
      expect(() => controller.messages.lookupMessage('original-msg-123')).toThrow(
        'Message not found: original-msg-123'
      );
    });

    it('rejects ambiguous messageId found in multiple stores', () => {
      const claudeDir = makeClaudeDir();
      const controller = createController({ teamName: 'my-team', claudeDir });

      // Manually write same messageId to both sent and inbox
      const sentPath = path.join(claudeDir, 'teams', 'my-team', 'sentMessages.json');
      const inboxDir = path.join(claudeDir, 'teams', 'my-team', 'inboxes');
      fs.mkdirSync(inboxDir, { recursive: true });
      const inboxPath = path.join(inboxDir, 'bob.json');

      const dupeId = 'dupe-message-id';
      fs.writeFileSync(sentPath, JSON.stringify([{ messageId: dupeId, text: 'copy-1' }]));
      fs.writeFileSync(inboxPath, JSON.stringify([{ messageId: dupeId, text: 'copy-2' }]));

      expect(() => controller.messages.lookupMessage(dupeId)).toThrow(
        'Ambiguous messageId: dupe-message-id found in multiple stores'
      );
    });
  });
});
