import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { jsonResult } from "../../agents/tools/common.js";
import type { ChannelPlugin } from "../../channels/plugins/types.js";
import { setActivePluginRegistry } from "../../plugins/runtime.js";
import { createTestRegistry } from "../../test-utils/channel-plugins.js";
import type { GatewayRequestContext } from "./types.js";

type ResolveOutboundTarget = typeof import("../../infra/outbound/targets.js").resolveOutboundTarget;

const mocks = vi.hoisted(() => ({
  deliverOutboundPayloads: vi.fn(),
  appendAssistantMessageToSessionTranscript: vi.fn(async () => ({ ok: true, sessionFile: "x" })),
  recordSessionMetaFromInbound: vi.fn(async () => ({ ok: true })),
  resolveOutboundTarget: vi.fn<ResolveOutboundTarget>(() => ({ ok: true, to: "resolved" })),
  resolveOutboundSessionRoute: vi.fn(),
  ensureOutboundSessionEntry: vi.fn(async () => undefined),
  resolveMessageChannelSelection: vi.fn(),
  dispatchChannelMessageAction: vi.fn(),
  sendPoll: vi.fn<
    () => Promise<{
      messageId: string;
      toJid?: string;
      channelId?: string;
      conversationId?: string;
      pollId?: string;
    }>
  >(async () => ({ messageId: "poll-1" })),
  getChannelPlugin: vi.fn(),
  loadOpenClawPlugins: vi.fn(),
  applyPluginAutoEnable: vi.fn(),
  getRuntimeConfigSnapshot: vi.fn(),
  getRuntimeConfigSourceSnapshot: vi.fn(),
}));

vi.mock("../../config/config.js", async () => {
  const actual =
    await vi.importActual<typeof import("../../config/config.js")>("../../config/config.js");
  return {
    ...actual,
    getRuntimeConfig: () => ({}),
  };
});

vi.mock("../../channels/plugins/index.js", () => ({
  getLoadedChannelPlugin: mocks.getChannelPlugin,
  getChannelPlugin: mocks.getChannelPlugin,
  normalizeChannelId: (value: string) => (value === "webchat" ? null : value),
}));

vi.mock("../../channels/plugins/message-action-dispatch.js", () => ({
  dispatchChannelMessageAction: mocks.dispatchChannelMessageAction,
}));

const TEST_AGENT_WORKSPACE = "/tmp/openclaw-test-workspace";
let sendHandlers: typeof import("./send.js").sendHandlers;

function resolveAgentIdFromSessionKeyForTests(params: { sessionKey?: string }): string {
  if (typeof params.sessionKey === "string") {
    const match = params.sessionKey.match(/^agent:([^:]+)/i);
    if (match?.[1]) {
      return match[1];
    }
  }
  return "main";
}

vi.mock("../../agents/agent-scope.js", () => ({
  resolveSessionAgentId: ({
    sessionKey,
  }: {
    sessionKey?: string;
    config?: unknown;
    agentId?: string;
  }) => resolveAgentIdFromSessionKeyForTests({ sessionKey }),
  resolveDefaultAgentId: () => "main",
  resolveAgentWorkspaceDir: () => TEST_AGENT_WORKSPACE,
}));

vi.mock("../../config/plugin-auto-enable.js", () => ({
  applyPluginAutoEnable: ({ config, env }: { config: unknown; env?: unknown }) =>
    mocks.applyPluginAutoEnable({ config, env }),
}));

vi.mock("../../config/runtime-snapshot.js", async () => {
  const actual = await vi.importActual<typeof import("../../config/runtime-snapshot.js")>(
    "../../config/runtime-snapshot.js",
  );
  return {
    ...actual,
    getRuntimeConfigSnapshot: mocks.getRuntimeConfigSnapshot,
    getRuntimeConfigSourceSnapshot: mocks.getRuntimeConfigSourceSnapshot,
  };
});

vi.mock("../../plugins/loader.js", () => ({
  loadOpenClawPlugins: mocks.loadOpenClawPlugins,
  resolveRuntimePluginRegistry: vi.fn(),
}));

vi.mock("../../infra/outbound/channel-bootstrap.runtime.js", () => ({
  bootstrapOutboundChannelPlugin: vi.fn(),
  resetOutboundChannelBootstrapStateForTests: vi.fn(),
}));

vi.mock("../../infra/outbound/targets.js", () => ({
  resolveOutboundTarget: mocks.resolveOutboundTarget,
}));

vi.mock("../../infra/outbound/outbound-session.js", () => ({
  resolveOutboundSessionRoute: mocks.resolveOutboundSessionRoute,
  ensureOutboundSessionEntry: mocks.ensureOutboundSessionEntry,
}));

vi.mock("../../infra/outbound/channel-selection.js", () => ({
  resolveMessageChannelSelection: mocks.resolveMessageChannelSelection,
}));

vi.mock("../../infra/outbound/deliver.js", () => ({
  deliverOutboundPayloads: mocks.deliverOutboundPayloads,
  deliverOutboundPayloadsInternal: mocks.deliverOutboundPayloads,
}));

vi.mock("../../config/sessions.js", async () => {
  const actual = await vi.importActual<typeof import("../../config/sessions.js")>(
    "../../config/sessions.js",
  );
  return {
    ...actual,
    appendAssistantMessageToSessionTranscript: mocks.appendAssistantMessageToSessionTranscript,
    recordSessionMetaFromInbound: mocks.recordSessionMetaFromInbound,
  };
});

async function loadSendHandlersForTest() {
  ({ sendHandlers } = await import("./send.js"));
}

const makeContext = (): GatewayRequestContext =>
  ({
    dedupe: new Map(),
    getRuntimeConfig: () => ({}),
  }) as unknown as GatewayRequestContext;

async function runSend(params: Record<string, unknown>) {
  return await runSendWithClient(params);
}

async function runSendWithClient(
  params: Record<string, unknown>,
  client?: { connect?: { scopes?: string[] } } | null,
) {
  const respond = vi.fn();
  await sendHandlers.send({
    params: params as never,
    respond,
    context: makeContext(),
    req: { type: "req", id: "1", method: "send" },
    client: (client ?? null) as never,
    isWebchatConnect: () => false,
  });
  return { respond };
}

async function runPoll(params: Record<string, unknown>) {
  return await runPollWithClient(params);
}

async function runPollWithClient(
  params: Record<string, unknown>,
  client?: { connect?: { scopes?: string[] } } | null,
) {
  const respond = vi.fn();
  await sendHandlers.poll({
    params: params as never,
    respond,
    context: makeContext(),
    req: { type: "req", id: "1", method: "poll" },
    client: (client ?? null) as never,
    isWebchatConnect: () => false,
  });
  return { respond };
}

function createDeferred<T>() {
  let resolve!: (value: T | PromiseLike<T>) => void;
  let reject!: (reason?: unknown) => void;
  const promise = new Promise<T>((res, rej) => {
    resolve = res;
    reject = rej;
  });
  return { promise, resolve, reject };
}

async function runMessageActionRequest(
  params: Record<string, unknown>,
  client?: { connect?: { scopes?: string[] } } | null,
) {
  const respond = vi.fn();
  await sendHandlers["message.action"]({
    params: params as never,
    respond,
    context: makeContext(),
    req: { type: "req", id: "1", method: "message.action" },
    client: (client ?? null) as never,
    isWebchatConnect: () => false,
  });
  return { respond };
}

function deliveryCall(index = 0): Record<string, any> | undefined {
  const calls = mocks.deliverOutboundPayloads.mock.calls as unknown as Array<[Record<string, any>]>;
  return calls[index]?.[0];
}

function appendTranscriptCall(index = 0): Record<string, any> | undefined {
  const calls = mocks.appendAssistantMessageToSessionTranscript.mock.calls as unknown as Array<
    [Record<string, any>]
  >;
  return calls[index]?.[0];
}

function firstRespondCall(respond: ReturnType<typeof vi.fn>) {
  const calls = respond.mock.calls as unknown as Array<
    [
      boolean,
      Record<string, any> | undefined,
      Record<string, any> | undefined,
      Record<string, any> | undefined,
    ]
  >;
  const call = calls[0];
  if (!call) {
    throw new Error("Expected respond call");
  }
  return call;
}

function lastDispatchChannelMessageActionCall(): Record<string, any> | undefined {
  const calls = mocks.dispatchChannelMessageAction.mock.calls as unknown as Array<
    [Record<string, any>]
  >;
  return calls.at(-1)?.[0];
}

function pollCall(index = 0): Record<string, any> {
  const calls = mocks.sendPoll.mock.calls as unknown as Array<[Record<string, any>]>;
  const call = calls[index]?.[0];
  if (!call) {
    throw new Error(`Expected poll call at index ${index}`);
  }
  return call;
}

function outboundRouteCall(index = 0): Record<string, any> | undefined {
  const calls = mocks.resolveOutboundSessionRoute.mock.calls as unknown as Array<
    [Record<string, any>]
  >;
  return calls[index]?.[0];
}

function ensureSessionEntryCall(index = 0): Record<string, any> | undefined {
  const calls = mocks.ensureOutboundSessionEntry.mock.calls as unknown as Array<
    [Record<string, any>]
  >;
  return calls[index]?.[0];
}

function expectDeliverySessionMirror(params: { agentId: string; sessionKey: string }) {
  const call = deliveryCall();
  expect(call?.session?.agentId).toBe(params.agentId);
  expect(call?.session?.key).toBe(params.sessionKey);
  expect(call?.mirror?.sessionKey).toBe(params.sessionKey);
  expect(call?.mirror?.agentId).toBe(params.agentId);
}

function mockDeliverySuccess(messageId: string) {
  mocks.deliverOutboundPayloads.mockResolvedValue([{ messageId, channel: "slack" }]);
}

describe("gateway send mirroring", () => {
  let registrySeq = 0;

  beforeAll(async () => {
    await loadSendHandlersForTest();
  });

  beforeEach(async () => {
    vi.clearAllMocks();
    registrySeq += 1;
    setActivePluginRegistry(createTestRegistry([]), `send-test-${registrySeq}`);
    mocks.applyPluginAutoEnable.mockImplementation(({ config }) => ({
      config,
      changes: [],
      autoEnabledReasons: {},
    }));
    mocks.getRuntimeConfigSnapshot.mockReturnValue(null);
    mocks.getRuntimeConfigSourceSnapshot.mockReturnValue(null);
    mocks.resolveOutboundTarget.mockReturnValue({ ok: true, to: "resolved" });
    mocks.resolveOutboundSessionRoute.mockImplementation(
      async ({ agentId, channel }: { agentId?: string; channel?: string }) => ({
        sessionKey:
          channel === "slack"
            ? `agent:${agentId ?? "main"}:slack:channel:resolved`
            : `agent:${agentId ?? "main"}:${channel ?? "main"}:resolved`,
      }),
    );
    mocks.resolveMessageChannelSelection.mockResolvedValue({
      channel: "slack",
      configured: ["slack"],
    });
    mocks.dispatchChannelMessageAction.mockResolvedValue({
      details: { action: "handled" },
    });
    mocks.sendPoll.mockResolvedValue({ messageId: "poll-1" });
    mocks.getChannelPlugin.mockReturnValue({
      actions: { handleAction: true },
      outbound: { sendPoll: mocks.sendPoll },
    });
  });

  it("uses the resolved runtime config for message.action when the source snapshot matches", async () => {
    const sourceConfig = {
      channels: {
        discord: {
          accounts: {
            drclaw: {
              token: {
                source: "env",
                provider: "default",
                id: "DISCORD_BOT_TOKEN_DRCLAW",
              },
            },
          },
        },
      },
    };
    const runtimeConfig = {
      channels: {
        discord: {
          accounts: {
            drclaw: {
              token: "resolved-token",
            },
          },
        },
      },
    };
    mocks.applyPluginAutoEnable.mockImplementation(({ config }) => ({
      config,
      changes: [],
      autoEnabledReasons: {},
    }));
    mocks.getRuntimeConfigSnapshot.mockReturnValue(runtimeConfig);
    mocks.getRuntimeConfigSourceSnapshot.mockReturnValue(sourceConfig);

    const context = {
      ...makeContext(),
      getRuntimeConfig: () => sourceConfig,
    } as unknown as GatewayRequestContext;
    const respond = vi.fn();
    await sendHandlers["message.action"]({
      params: {
        channel: "discord",
        action: "channel-info",
        params: { channelId: "123", accountId: "drclaw" },
        idempotencyKey: "idem-action-runtime-config",
      } as never,
      respond,
      context,
      req: { type: "req", id: "1", method: "message.action" },
      client: null as never,
      isWebchatConnect: () => false,
    });

    expect(mocks.getRuntimeConfigSnapshot).toHaveBeenCalledTimes(1);
    expect(mocks.getRuntimeConfigSourceSnapshot).toHaveBeenCalledTimes(1);
    expect(lastDispatchChannelMessageActionCall()?.cfg).toBe(runtimeConfig);
    const response = firstRespondCall(respond);
    expect(response?.[0]).toBe(true);
  });

  it("matches message.action runtime config against the canonical pre-auto-enable source config", async () => {
    const sourceConfig = {
      channels: {
        discord: {
          accounts: {
            drclaw: {
              token: {
                source: "env",
                provider: "default",
                id: "DISCORD_BOT_TOKEN_DRCLAW",
              },
            },
          },
        },
      },
    };
    const autoEnabledSourceConfig = {
      channels: {
        discord: {
          enabled: true,
          accounts: {
            drclaw: {
              token: {
                source: "env",
                provider: "default",
                id: "DISCORD_BOT_TOKEN_DRCLAW",
              },
            },
          },
        },
      },
      plugins: { allow: ["discord"] },
    };
    const autoEnabledRuntimeConfig = {
      channels: {
        discord: {
          enabled: true,
          accounts: {
            drclaw: {
              token: "resolved-token",
            },
          },
        },
      },
      plugins: { allow: ["discord"] },
    };
    mocks.applyPluginAutoEnable
      .mockReturnValueOnce({
        config: autoEnabledSourceConfig,
        changes: [{ path: "channels.discord.enabled", value: true }],
        autoEnabledReasons: {},
      })
      .mockReturnValueOnce({
        config: autoEnabledRuntimeConfig,
        changes: [{ path: "channels.discord.enabled", value: true }],
        autoEnabledReasons: {},
      });
    mocks.getRuntimeConfigSnapshot.mockReturnValue(autoEnabledRuntimeConfig);
    mocks.getRuntimeConfigSourceSnapshot.mockReturnValue(sourceConfig);

    const context = {
      ...makeContext(),
      getRuntimeConfig: () => sourceConfig,
    } as unknown as GatewayRequestContext;
    const respond = vi.fn();
    await sendHandlers["message.action"]({
      params: {
        channel: "discord",
        action: "channel-info",
        params: { channelId: "123", accountId: "drclaw" },
        idempotencyKey: "idem-action-runtime-config-auto-enabled",
      } as never,
      respond,
      context,
      req: { type: "req", id: "1", method: "message.action" },
      client: null as never,
      isWebchatConnect: () => false,
    });

    expect(lastDispatchChannelMessageActionCall()?.cfg).toBe(autoEnabledRuntimeConfig);
    expect(mocks.applyPluginAutoEnable).toHaveBeenNthCalledWith(1, {
      config: sourceConfig,
      env: undefined,
    });
    expect(mocks.applyPluginAutoEnable).toHaveBeenNthCalledWith(2, {
      config: autoEnabledRuntimeConfig,
      env: undefined,
    });
    const response = firstRespondCall(respond);
    expect(response?.[0]).toBe(true);
  });

  it("keeps the post-auto-enable request config for message.action when the runtime source snapshot does not match", async () => {
    const sourceConfig = {
      channels: {
        discord: {
          accounts: {
            drclaw: {
              token: {
                source: "env",
                provider: "default",
                id: "DISCORD_BOT_TOKEN_DRCLAW",
              },
            },
          },
        },
      },
    };
    const autoEnabledRequestConfig = {
      channels: {
        discord: {
          enabled: true,
          accounts: {
            drclaw: {
              token: {
                source: "env",
                provider: "default",
                id: "DISCORD_BOT_TOKEN_DRCLAW",
              },
            },
          },
        },
      },
      plugins: { allow: ["discord"] },
    };
    mocks.applyPluginAutoEnable.mockReturnValue({
      config: autoEnabledRequestConfig,
      changes: [{ path: "channels.discord.enabled", value: true }],
      autoEnabledReasons: {},
    });
    mocks.getRuntimeConfigSnapshot.mockReturnValue({
      channels: {
        discord: {
          accounts: {
            drclaw: { token: "stale-runtime-token" },
          },
        },
      },
    });
    mocks.getRuntimeConfigSourceSnapshot.mockReturnValue({
      channels: {
        discord: {
          accounts: {
            other: { token: "different-source" },
          },
        },
      },
    });

    const context = {
      ...makeContext(),
      getRuntimeConfig: () => sourceConfig,
    } as unknown as GatewayRequestContext;
    await sendHandlers["message.action"]({
      params: {
        channel: "discord",
        action: "channel-info",
        params: { channelId: "123", accountId: "drclaw" },
        idempotencyKey: "idem-action-stale-runtime-config",
      } as never,
      respond: vi.fn(),
      context,
      req: { type: "req", id: "1", method: "message.action" },
      client: null as never,
      isWebchatConnect: () => false,
    });

    expect(lastDispatchChannelMessageActionCall()?.cfg).toBe(autoEnabledRequestConfig);
  });

  it("does not read the runtime config snapshot for send requests", async () => {
    mockDeliverySuccess("m-no-runtime-config-read");

    await runSend({
      to: "channel:C1",
      message: "hi",
      channel: "slack",
      idempotencyKey: "idem-send-no-runtime-config-read",
    });

    expect(mocks.getRuntimeConfigSnapshot).not.toHaveBeenCalled();
    expect(mocks.getRuntimeConfigSourceSnapshot).not.toHaveBeenCalled();
  });

  it("dedupes concurrent message.action requests while inflight", async () => {
    const context = makeContext();
    const firstRespond = vi.fn();
    const secondRespond = vi.fn();
    const actionDeferred = createDeferred<{ details: { action: string } }>();
    mocks.dispatchChannelMessageAction.mockReturnValueOnce(actionDeferred.promise);

    const firstRequest = sendHandlers["message.action"]({
      params: {
        channel: "slack",
        action: "poll",
        params: { question: "Q?" },
        idempotencyKey: "idem-action-concurrent",
      } as never,
      respond: firstRespond,
      context,
      req: { type: "req", id: "1", method: "message.action" },
      client: null as never,
      isWebchatConnect: () => false,
    });

    const secondRequest = sendHandlers["message.action"]({
      params: {
        channel: "slack",
        action: "poll",
        params: { question: "Q?" },
        idempotencyKey: "idem-action-concurrent",
      } as never,
      respond: secondRespond,
      context,
      req: { type: "req", id: "2", method: "message.action" },
      client: null as never,
      isWebchatConnect: () => false,
    });

    await Promise.resolve();
    expect(mocks.dispatchChannelMessageAction).toHaveBeenCalledTimes(1);

    actionDeferred.resolve({ details: { action: "handled" } });
    await Promise.all([firstRequest, secondRequest]);

    expect(mocks.dispatchChannelMessageAction).toHaveBeenCalledTimes(1);
    expect(firstRespond).toHaveBeenCalledTimes(1);
    expect(secondRespond).toHaveBeenCalledTimes(1);
    const firstCall = firstRespondCall(firstRespond);
    expect(firstCall?.[0]).toBe(true);
    expect(firstCall?.[1]).toEqual({ action: "handled" });
    expect(firstCall?.[2]).toBeUndefined();
    expect(firstCall?.[3]?.channel).toBe("slack");
    expect(firstCall?.[3]?.cached).toBeUndefined();
    const secondCall = firstRespondCall(secondRespond);
    expect(secondCall?.[0]).toBe(true);
    expect(secondCall?.[1]).toEqual({ action: "handled" });
    expect(secondCall?.[2]).toBeUndefined();
    expect(secondCall?.[3]?.channel).toBe("slack");
    expect(secondCall?.[3]?.cached).toBe(true);
  });

  it("dedupes concurrent send requests while inflight", async () => {
    const context = makeContext();
    const firstRespond = vi.fn();
    const secondRespond = vi.fn();
    const deliveryDeferred = createDeferred<Array<{ messageId: string; channel: string }>>();
    mocks.deliverOutboundPayloads.mockReturnValueOnce(deliveryDeferred.promise);

    const firstRequest = sendHandlers.send({
      params: {
        to: "channel:C1",
        message: "hi",
        channel: "slack",
        idempotencyKey: "idem-send-concurrent",
      } as never,
      respond: firstRespond,
      context,
      req: { type: "req", id: "1", method: "send" },
      client: null as never,
      isWebchatConnect: () => false,
    });

    const secondRequest = sendHandlers.send({
      params: {
        to: "channel:C1",
        message: "hi",
        channel: "slack",
        idempotencyKey: "idem-send-concurrent",
      } as never,
      respond: secondRespond,
      context,
      req: { type: "req", id: "2", method: "send" },
      client: null as never,
      isWebchatConnect: () => false,
    });

    await vi.waitFor(() => {
      expect(mocks.deliverOutboundPayloads).toHaveBeenCalledTimes(1);
    });

    deliveryDeferred.resolve([{ messageId: "m-concurrent", channel: "slack" }]);
    await Promise.all([firstRequest, secondRequest]);

    expect(mocks.deliverOutboundPayloads).toHaveBeenCalledTimes(1);
    expect(firstRespond).toHaveBeenCalledTimes(1);
    expect(secondRespond).toHaveBeenCalledTimes(1);
    const firstCall = firstRespondCall(firstRespond);
    expect(firstCall?.[0]).toBe(true);
    expect(firstCall?.[1]?.messageId).toBe("m-concurrent");
    expect(firstCall?.[1]?.runId).toBe("idem-send-concurrent");
    expect(firstCall?.[2]).toBeUndefined();
    expect(firstCall?.[3]?.channel).toBe("slack");
    expect(firstCall?.[3]?.cached).toBeUndefined();
    const secondCall = firstRespondCall(secondRespond);
    expect(secondCall?.[0]).toBe(true);
    expect(secondCall?.[1]?.messageId).toBe("m-concurrent");
    expect(secondCall?.[1]?.runId).toBe("idem-send-concurrent");
    expect(secondCall?.[2]).toBeUndefined();
    expect(secondCall?.[3]?.channel).toBe("slack");
    expect(secondCall?.[3]?.cached).toBe(true);
  });

  it("dedupes concurrent poll requests while inflight", async () => {
    const context = makeContext();
    const firstRespond = vi.fn();
    const secondRespond = vi.fn();
    const pollDeferred = createDeferred<{ messageId: string; pollId: string }>();
    mocks.sendPoll.mockReturnValueOnce(pollDeferred.promise);

    const firstRequest = sendHandlers.poll({
      params: {
        to: "channel:C1",
        question: "Q?",
        options: ["A", "B"],
        channel: "slack",
        idempotencyKey: "idem-poll-concurrent",
      } as never,
      respond: firstRespond,
      context,
      req: { type: "req", id: "1", method: "poll" },
      client: null as never,
      isWebchatConnect: () => false,
    });

    const secondRequest = sendHandlers.poll({
      params: {
        to: "channel:C1",
        question: "Q?",
        options: ["A", "B"],
        channel: "slack",
        idempotencyKey: "idem-poll-concurrent",
      } as never,
      respond: secondRespond,
      context,
      req: { type: "req", id: "2", method: "poll" },
      client: null as never,
      isWebchatConnect: () => false,
    });

    await Promise.resolve();
    expect(mocks.sendPoll).toHaveBeenCalledTimes(1);

    pollDeferred.resolve({ messageId: "poll-concurrent", pollId: "poll-1" });
    await Promise.all([firstRequest, secondRequest]);

    expect(mocks.sendPoll).toHaveBeenCalledTimes(1);
    expect(firstRespond).toHaveBeenCalledTimes(1);
    expect(secondRespond).toHaveBeenCalledTimes(1);
    const firstCall = firstRespondCall(firstRespond);
    expect(firstCall?.[0]).toBe(true);
    expect(firstCall?.[1]?.messageId).toBe("poll-concurrent");
    expect(firstCall?.[1]?.pollId).toBe("poll-1");
    expect(firstCall?.[1]?.runId).toBe("idem-poll-concurrent");
    expect(firstCall?.[2]).toBeUndefined();
    expect(firstCall?.[3]?.channel).toBe("slack");
    expect(firstCall?.[3]?.cached).toBeUndefined();
    const secondCall = firstRespondCall(secondRespond);
    expect(secondCall?.[0]).toBe(true);
    expect(secondCall?.[1]?.messageId).toBe("poll-concurrent");
    expect(secondCall?.[1]?.pollId).toBe("poll-1");
    expect(secondCall?.[1]?.runId).toBe("idem-poll-concurrent");
    expect(secondCall?.[2]).toBeUndefined();
    expect(secondCall?.[3]?.channel).toBe("slack");
    expect(secondCall?.[3]?.cached).toBe(true);
  });

  it("accepts media-only sends without message", async () => {
    mockDeliverySuccess("m-media");

    const { respond } = await runSend({
      to: "channel:C1",
      mediaUrl: "https://example.com/a.png",
      channel: "slack",
      idempotencyKey: "idem-media-only",
    });

    expect(deliveryCall()?.payloads).toEqual([
      { text: "", mediaUrl: "https://example.com/a.png", mediaUrls: undefined },
    ]);
    const response = firstRespondCall(respond);
    expect(response?.[0]).toBe(true);
    expect(response?.[1]?.messageId).toBe("m-media");
    expect(response?.[2]).toBeUndefined();
    expect(response?.[3]?.channel).toBe("slack");
  });

  it("passes outbound session context for gateway media sends", async () => {
    mockDeliverySuccess("m-whatsapp-media");

    await runSend({
      to: "+15551234567",
      message: "caption",
      mediaUrl: "file:///tmp/workspace/photo.png",
      channel: "whatsapp",
      agentId: "work",
      idempotencyKey: "idem-whatsapp-media",
    });

    expect(deliveryCall()?.channel).toBe("whatsapp");
    expect(deliveryCall()?.payloads).toEqual([
      {
        text: "caption",
        mediaUrl: "file:///tmp/workspace/photo.png",
        mediaUrls: undefined,
      },
    ]);
    expect(deliveryCall()?.session?.agentId).toBe("work");
    expect(deliveryCall()?.session?.key).toBe("agent:work:whatsapp:resolved");
  });

  it("maps gateway asVoice sends onto outbound audioAsVoice payloads", async () => {
    mockDeliverySuccess("m-voice");

    const { respond } = await runSend({
      to: "channel:C1",
      message: "voice note",
      mediaUrl: "file:///tmp/openclaw-voice.ogg",
      asVoice: true,
      channel: "slack",
      idempotencyKey: "idem-voice",
    });

    expect(deliveryCall()?.payloads?.[0]?.text).toBe("voice note");
    expect(deliveryCall()?.payloads?.[0]?.mediaUrl).toBe("file:///tmp/openclaw-voice.ogg");
    expect(deliveryCall()?.payloads?.[0]?.audioAsVoice).toBe(true);
    const response = firstRespondCall(respond);
    expect(response?.[0]).toBe(true);
    expect(response?.[1]?.messageId).toBe("m-voice");
    expect(response?.[2]).toBeUndefined();
    expect(response?.[3]?.channel).toBe("slack");
  });

  it("forwards gateway client scopes into outbound delivery", async () => {
    mockDeliverySuccess("m-scope");

    await runSendWithClient(
      {
        to: "channel:C1",
        message: "hi",
        channel: "slack",
        idempotencyKey: "idem-scope",
      },
      { connect: { scopes: ["operator.write"] } },
    );

    expect(deliveryCall()?.channel).toBe("slack");
    expect(deliveryCall()?.gatewayClientScopes).toEqual(["operator.write"]);
  });

  it("forwards an empty gateway scope array into outbound delivery", async () => {
    mockDeliverySuccess("m-empty-scope");

    await runSendWithClient(
      {
        to: "channel:C1",
        message: "hi",
        channel: "slack",
        idempotencyKey: "idem-empty-scope",
      },
      { connect: { scopes: [] } },
    );

    expect(deliveryCall()?.channel).toBe("slack");
    expect(deliveryCall()?.gatewayClientScopes).toEqual([]);
  });

  it("rejects empty sends when neither text nor media is present", async () => {
    const { respond } = await runSend({
      to: "channel:C1",
      message: "   ",
      channel: "slack",
      idempotencyKey: "idem-empty",
    });

    expect(mocks.deliverOutboundPayloads).not.toHaveBeenCalled();
    const response = firstRespondCall(respond);
    expect(response?.[0]).toBe(false);
    expect(response?.[1]).toBeUndefined();
    expect(response?.[2]?.message).toContain("text or media is required");
  });

  it("returns actionable guidance when channel is internal webchat", async () => {
    const { respond } = await runSend({
      to: "x",
      message: "hi",
      channel: "webchat",
      idempotencyKey: "idem-webchat",
    });

    expect(mocks.deliverOutboundPayloads).not.toHaveBeenCalled();
    const response = firstRespondCall(respond);
    expect(response?.[0]).toBe(false);
    expect(response?.[1]).toBeUndefined();
    expect(response?.[2]?.message).toContain("unsupported channel: webchat");
    expect(response?.[2]?.message).toContain("Use `chat.send`");
  });

  it("auto-picks the single configured channel for send", async () => {
    mockDeliverySuccess("m-single-send");

    const { respond } = await runSend({
      to: "x",
      message: "hi",
      idempotencyKey: "idem-missing-channel",
    });

    expect(mocks.resolveMessageChannelSelection).toHaveBeenCalled();
    expect(mocks.deliverOutboundPayloads).toHaveBeenCalled();
    const response = firstRespondCall(respond);
    expect(response?.[0]).toBe(true);
    expect(response?.[1]?.messageId).toBe("m-single-send");
    expect(response?.[2]).toBeUndefined();
    expect(response?.[3]?.channel).toBe("slack");
  });

  it("auto-picks the single configured channel from the auto-enabled config snapshot for send", async () => {
    const autoEnabledConfig = { channels: { slack: {} }, plugins: { allow: ["slack"] } };
    mocks.applyPluginAutoEnable.mockReturnValue({
      config: autoEnabledConfig,
      changes: [],
      autoEnabledReasons: {},
    });
    mockDeliverySuccess("m-single-send-auto");

    const { respond } = await runSend({
      to: "x",
      message: "hi",
      idempotencyKey: "idem-missing-channel-auto-enabled",
    });

    expect(mocks.applyPluginAutoEnable).toHaveBeenCalledWith({
      config: {},
    });
    expect(mocks.resolveMessageChannelSelection).toHaveBeenCalledWith({
      cfg: autoEnabledConfig,
    });
    const response = firstRespondCall(respond);
    expect(response?.[0]).toBe(true);
    expect(response?.[1]?.messageId).toBe("m-single-send-auto");
    expect(response?.[2]).toBeUndefined();
    expect(response?.[3]?.channel).toBe("slack");
  });

  it("returns invalid request when send channel selection is ambiguous", async () => {
    mocks.resolveMessageChannelSelection.mockRejectedValueOnce(
      new Error("Channel is required when multiple channels are configured: telegram, slack"),
    );

    const { respond } = await runSend({
      to: "x",
      message: "hi",
      idempotencyKey: "idem-missing-channel-ambiguous",
    });

    expect(mocks.deliverOutboundPayloads).not.toHaveBeenCalled();
    const response = firstRespondCall(respond);
    expect(response?.[0]).toBe(false);
    expect(response?.[1]).toBeUndefined();
    expect(response?.[2]?.message).toContain("Channel is required");
  });

  it("forwards gateway client scopes into outbound poll delivery", async () => {
    await runPollWithClient(
      {
        to: "channel:C1",
        question: "Q?",
        options: ["A", "B"],
        channel: "slack",
        idempotencyKey: "idem-poll-scope",
      },
      { connect: { scopes: ["operator.admin"] } },
    );

    const call = pollCall();
    if (call.cfg === undefined) {
      throw new Error("Expected poll delivery config");
    }
    expect(call.to).toBe("resolved");
    expect(call.gatewayClientScopes).toEqual(["operator.admin"]);
  });

  it("forwards an empty gateway scope array into outbound poll delivery", async () => {
    await runPollWithClient(
      {
        to: "channel:C1",
        question: "Q?",
        options: ["A", "B"],
        channel: "slack",
        idempotencyKey: "idem-poll-empty-scope",
      },
      { connect: { scopes: [] } },
    );

    const call = pollCall();
    if (call.cfg === undefined) {
      throw new Error("Expected poll delivery config");
    }
    expect(call.to).toBe("resolved");
    expect(call.gatewayClientScopes).toEqual([]);
  });

  it("includes optional poll delivery identifiers in the gateway payload", async () => {
    mocks.sendPoll.mockResolvedValue({
      messageId: "poll-rich",
      channelId: "C123",
      conversationId: "conv-1",
      toJid: "jid-1",
      pollId: "poll-meta-1",
    });

    const { respond } = await runPoll({
      to: "channel:C1",
      question: "Q?",
      options: ["A", "B"],
      channel: "slack",
      idempotencyKey: "idem-poll-rich",
    });

    const response = firstRespondCall(respond);
    expect(response?.[0]).toBe(true);
    expect(response?.[1]).toEqual({
      runId: "idem-poll-rich",
      messageId: "poll-rich",
      channel: "slack",
      channelId: "C123",
      conversationId: "conv-1",
      toJid: "jid-1",
      pollId: "poll-meta-1",
    });
    expect(response?.[2]).toBeUndefined();
    expect(response?.[3]?.channel).toBe("slack");
  });

  it("auto-picks the single configured channel for poll", async () => {
    const { respond } = await runPoll({
      to: "x",
      question: "Q?",
      options: ["A", "B"],
      idempotencyKey: "idem-poll-missing-channel",
    });

    expect(mocks.resolveMessageChannelSelection).toHaveBeenCalled();
    const response = firstRespondCall(respond);
    expect(response[0]).toBe(true);
    if (response[1] === undefined) {
      throw new Error("Expected poll missing-channel response payload");
    }
    expect(response[2]).toBeUndefined();
    expect(response[3]).toEqual({ channel: "slack" });
  });

  it("returns invalid request when poll channel selection is ambiguous", async () => {
    mocks.resolveMessageChannelSelection.mockRejectedValueOnce(
      new Error("Channel is required when multiple channels are configured: telegram, slack"),
    );

    const { respond } = await runPoll({
      to: "x",
      question: "Q?",
      options: ["A", "B"],
      idempotencyKey: "idem-poll-missing-channel-ambiguous",
    });

    const response = firstRespondCall(respond);
    expect(response?.[0]).toBe(false);
    expect(response?.[1]).toBeUndefined();
    expect(response?.[2]?.message).toContain("Channel is required");
  });

  it("does not mirror when delivery returns no results", async () => {
    mocks.deliverOutboundPayloads.mockResolvedValue([]);

    await runSend({
      to: "channel:C1",
      message: "hi",
      channel: "slack",
      idempotencyKey: "idem-1",
      sessionKey: "agent:main:main",
    });

    expect(deliveryCall()?.mirror?.sessionKey).toBe("agent:main:main");
  });

  it("mirrors media filenames when delivery succeeds", async () => {
    mockDeliverySuccess("m1");

    await runSend({
      to: "channel:C1",
      message: "caption",
      mediaUrl: "https://example.com/files/report.pdf?sig=1",
      channel: "slack",
      idempotencyKey: "idem-2",
      sessionKey: "agent:main:main",
    });

    expect(deliveryCall()?.mirror?.sessionKey).toBe("agent:main:main");
    expect(deliveryCall()?.mirror?.text).toBe("caption");
    expect(deliveryCall()?.mirror?.mediaUrls).toEqual([
      "https://example.com/files/report.pdf?sig=1",
    ]);
    expect(deliveryCall()?.mirror?.idempotencyKey).toBe("idem-2");
  });

  it("mirrors MEDIA tags as attachments", async () => {
    mockDeliverySuccess("m2");

    await runSend({
      to: "channel:C1",
      message: "Here\nMEDIA:https://example.com/image.png",
      channel: "slack",
      idempotencyKey: "idem-3",
      sessionKey: "agent:main:main",
    });

    expect(deliveryCall()?.mirror?.sessionKey).toBe("agent:main:main");
    expect(deliveryCall()?.mirror?.text).toBe("Here");
    expect(deliveryCall()?.mirror?.mediaUrls).toEqual(["https://example.com/image.png"]);
  });

  it("lowercases provided session keys for mirroring", async () => {
    mockDeliverySuccess("m-lower");

    await runSend({
      to: "channel:C1",
      message: "hi",
      channel: "slack",
      idempotencyKey: "idem-lower",
      sessionKey: "agent:main:slack:channel:C123",
    });

    expect(deliveryCall()?.mirror?.sessionKey).toBe("agent:main:slack:channel:c123");
  });

  it("derives a target session key when none is provided", async () => {
    mockDeliverySuccess("m3");

    await runSend({
      to: "channel:C1",
      message: "hello",
      channel: "slack",
      idempotencyKey: "idem-4",
    });

    expect(deliveryCall()?.mirror?.sessionKey).toBe("agent:main:slack:channel:resolved");
    expect(deliveryCall()?.mirror?.agentId).toBe("main");
  });

  it("uses explicit agentId for delivery when sessionKey is not provided", async () => {
    mockDeliverySuccess("m-agent");

    await runSend({
      to: "channel:C1",
      message: "hello",
      channel: "slack",
      agentId: "work",
      idempotencyKey: "idem-agent-explicit",
    });

    expect(deliveryCall()?.session?.agentId).toBe("work");
    expect(deliveryCall()?.session?.key).toBe("agent:work:slack:channel:resolved");
    expect(deliveryCall()?.mirror?.sessionKey).toBe("agent:work:slack:channel:resolved");
    expect(deliveryCall()?.mirror?.agentId).toBe("work");
  });

  it("uses sessionKey agentId when explicit agentId is omitted", async () => {
    mockDeliverySuccess("m-session-agent");

    await runSend({
      to: "channel:C1",
      message: "hello",
      channel: "slack",
      sessionKey: "agent:work:slack:channel:c1",
      idempotencyKey: "idem-session-agent",
    });

    expectDeliverySessionMirror({
      agentId: "work",
      sessionKey: "agent:work:slack:channel:c1",
    });
  });

  it("still resolves outbound routing metadata when a sessionKey is provided", async () => {
    mockDeliverySuccess("m-matrix-session-route");
    mocks.resolveOutboundSessionRoute.mockResolvedValueOnce({
      sessionKey: "agent:main:matrix:channel:!dm:example.org",
      baseSessionKey: "agent:main:matrix:channel:!dm:example.org",
      peer: { kind: "channel", id: "!dm:example.org" },
      chatType: "direct",
      from: "matrix:@alice:example.org",
      to: "room:!dm:example.org",
    });

    await runSend({
      to: "@alice:example.org",
      message: "hello",
      channel: "matrix",
      sessionKey: "agent:main:matrix:channel:!dm:example.org",
      idempotencyKey: "idem-matrix-session-route",
    });

    expect(outboundRouteCall()?.channel).toBe("matrix");
    expect(outboundRouteCall()?.target).toBe("resolved");
    expect(outboundRouteCall()?.currentSessionKey).toBe(
      "agent:main:matrix:channel:!dm:example.org",
    );
    expect(ensureSessionEntryCall()?.route?.sessionKey).toBe(
      "agent:main:matrix:channel:!dm:example.org",
    );
    expect(ensureSessionEntryCall()?.route?.baseSessionKey).toBe(
      "agent:main:matrix:channel:!dm:example.org",
    );
    expect(ensureSessionEntryCall()?.route?.to).toBe("room:!dm:example.org");
    expectDeliverySessionMirror({
      agentId: "main",
      sessionKey: "agent:main:matrix:channel:!dm:example.org",
    });
  });

  it("falls back to the provided sessionKey when outbound route lookup returns null", async () => {
    mockDeliverySuccess("m-session-fallback");
    mocks.resolveOutboundSessionRoute.mockResolvedValueOnce(null);

    await runSend({
      to: "channel:C1",
      message: "hello",
      channel: "slack",
      sessionKey: "agent:work:slack:channel:c1",
      idempotencyKey: "idem-session-fallback",
    });

    expect(mocks.ensureOutboundSessionEntry).not.toHaveBeenCalled();
    expect(deliveryCall()?.session?.agentId).toBe("work");
    expect(deliveryCall()?.session?.key).toBe("agent:work:slack:channel:c1");
    expect(deliveryCall()?.mirror?.sessionKey).toBe("agent:work:slack:channel:c1");
    expect(deliveryCall()?.mirror?.agentId).toBe("work");
  });

  it("prefers explicit agentId over sessionKey agent for delivery and mirror", async () => {
    mockDeliverySuccess("m-agent-precedence");

    await runSend({
      to: "channel:C1",
      message: "hello",
      channel: "slack",
      agentId: "work",
      sessionKey: "agent:main:slack:channel:c1",
      idempotencyKey: "idem-agent-precedence",
    });

    expect(deliveryCall()?.session?.agentId).toBe("work");
    expect(deliveryCall()?.session?.key).toBe("agent:main:slack:channel:c1");
    expect(deliveryCall()?.mirror?.sessionKey).toBe("agent:main:slack:channel:c1");
    expect(deliveryCall()?.mirror?.agentId).toBe("work");
  });

  it("ignores blank explicit agentId and falls back to sessionKey agent", async () => {
    mockDeliverySuccess("m-agent-blank");

    await runSend({
      to: "channel:C1",
      message: "hello",
      channel: "slack",
      agentId: "   ",
      sessionKey: "agent:work:slack:channel:c1",
      idempotencyKey: "idem-agent-blank",
    });

    expectDeliverySessionMirror({
      agentId: "work",
      sessionKey: "agent:work:slack:channel:c1",
    });
  });

  it("forwards threadId to outbound delivery when provided", async () => {
    mockDeliverySuccess("m-thread");

    await runSend({
      to: "channel:C1",
      message: "hi",
      channel: "slack",
      threadId: "1710000000.9999",
      idempotencyKey: "idem-thread",
    });

    expect(deliveryCall()?.threadId).toBe("1710000000.9999");
  });

  it("forwards gateway send delivery options to outbound delivery", async () => {
    mockDeliverySuccess("m-options");

    await runSend({
      to: "channel:C1",
      message: "<b>report</b>",
      channel: "slack",
      forceDocument: true,
      silent: true,
      parseMode: "HTML",
      idempotencyKey: "idem-send-options",
    });

    const options = mocks.deliverOutboundPayloads.mock.calls.at(0)?.[0];
    expect(options?.forceDocument).toBe(true);
    expect(options?.silent).toBe(true);
    expect(options?.formatting).toEqual({ parseMode: "HTML" });
  });

  it("updates mirror session keys and delivery thread ids when Slack routing derives a thread", async () => {
    mockDeliverySuccess("m-thread-derived");
    mocks.resolveOutboundSessionRoute.mockResolvedValueOnce({
      sessionKey: "agent:main:slack:channel:c1:thread:1710000000.9999",
      baseSessionKey: "agent:main:slack:channel:c1",
      peer: { kind: "channel", id: "c1" },
      chatType: "channel",
      from: "slack:channel:C1",
      to: "channel:C1",
      threadId: "1710000000.9999",
    });

    await runSend({
      to: "channel:C1",
      message: "threaded",
      channel: "slack",
      sessionKey: "agent:main:slack:channel:c1",
      idempotencyKey: "idem-thread-derived",
    });

    expect(ensureSessionEntryCall()?.route?.sessionKey).toBe(
      "agent:main:slack:channel:c1:thread:1710000000.9999",
    );
    expect(ensureSessionEntryCall()?.route?.baseSessionKey).toBe("agent:main:slack:channel:c1");
    expect(ensureSessionEntryCall()?.route?.threadId).toBe("1710000000.9999");
    expect(deliveryCall()?.threadId).toBe("1710000000.9999");
    expect(deliveryCall()?.mirror?.sessionKey).toBe(
      "agent:main:slack:channel:c1:thread:1710000000.9999",
    );
  });

  it("preserves the provided session when Slack derives a thread for a different base session", async () => {
    mockDeliverySuccess("m-thread-mismatch");
    mocks.resolveOutboundSessionRoute.mockResolvedValueOnce({
      sessionKey: "agent:main:slack:channel:c2:thread:1710000000.9999",
      baseSessionKey: "agent:main:slack:channel:c2",
      peer: { kind: "channel", id: "c2" },
      chatType: "channel",
      from: "slack:channel:C2",
      to: "channel:C2",
      threadId: "1710000000.9999",
    });

    await runSend({
      to: "channel:C2",
      message: "threaded",
      channel: "slack",
      sessionKey: "agent:main:slack:channel:c1",
      threadId: "1710000000.9999",
      idempotencyKey: "idem-thread-mismatch",
    });

    expect(deliveryCall()?.threadId).toBe("1710000000.9999");
    expect(deliveryCall()?.session?.key).toBe("agent:main:slack:channel:c1");
    expect(deliveryCall()?.mirror?.sessionKey).toBe("agent:main:slack:channel:c1");
  });

  it("preserves derived thread delivery for existing thread-scoped Slack session keys", async () => {
    mockDeliverySuccess("m-thread-session");
    mocks.resolveOutboundSessionRoute.mockResolvedValueOnce({
      sessionKey: "agent:main:slack:channel:c1:thread:1710000000.9999",
      baseSessionKey: "agent:main:slack:channel:c1",
      peer: { kind: "channel", id: "c1" },
      chatType: "channel",
      from: "slack:channel:C1",
      to: "channel:C1",
      threadId: "1710000000.9999",
    });

    await runSend({
      to: "channel:C1",
      message: "threaded",
      channel: "slack",
      sessionKey: "agent:main:slack:channel:c1:thread:1710000000.9999",
      idempotencyKey: "idem-thread-session",
    });

    expect(deliveryCall()?.threadId).toBe("1710000000.9999");
    expect(deliveryCall()?.session?.key).toBe("agent:main:slack:channel:c1:thread:1710000000.9999");
  });

  it("preserves numeric derived thread ids for non-Slack channels", async () => {
    mockDeliverySuccess("m-topic-derived");
    mocks.resolveOutboundSessionRoute.mockResolvedValueOnce({
      sessionKey: "agent:main:telegram:group:-100123:thread:77",
      baseSessionKey: "agent:main:telegram:group:-100123",
      peer: { kind: "group", id: "-100123" },
      chatType: "group",
      from: "telegram:group:-100123",
      to: "channel:-100123",
      threadId: 77,
    });

    await runSend({
      to: "-100123:topic:77",
      message: "topic message",
      channel: "telegram",
      idempotencyKey: "idem-topic-derived",
    });

    expect(deliveryCall()?.threadId).toBe(77);
  });

  it("returns invalid request when outbound target resolution fails", async () => {
    mocks.resolveOutboundTarget.mockReturnValue({
      ok: false,
      error: new Error("target not found"),
    });

    const { respond } = await runSend({
      to: "channel:C1",
      message: "hi",
      channel: "slack",
      idempotencyKey: "idem-target-fail",
    });

    expect(mocks.deliverOutboundPayloads).not.toHaveBeenCalled();
    const response = firstRespondCall(respond);
    expect(response?.[0]).toBe(false);
    expect(response?.[1]).toBeUndefined();
    expect(response?.[2]?.message).toContain("target not found");
    expect(response?.[3]?.channel).toBe("slack");
  });

  it("recovers cold plugin resolution for threaded sends", async () => {
    mocks.resolveOutboundTarget.mockReturnValue({ ok: true, to: "123" });
    mocks.deliverOutboundPayloads.mockResolvedValue([
      { messageId: "m-threaded", channel: "slack" },
    ]);
    const outboundPlugin = { outbound: { sendPoll: mocks.sendPoll } };
    mocks.getChannelPlugin
      .mockReturnValueOnce(undefined)
      .mockReturnValueOnce(outboundPlugin)
      .mockReturnValue(outboundPlugin);

    const { respond } = await runSend({
      to: "123",
      message: "threaded completion",
      channel: "slack",
      threadId: "1710000000.9999",
      idempotencyKey: "idem-cold-thread",
    });

    expect(deliveryCall()?.channel).toBe("slack");
    expect(deliveryCall()?.to).toBe("123");
    expect(deliveryCall()?.threadId).toBe("1710000000.9999");
    const response = firstRespondCall(respond);
    expect(response?.[0]).toBe(true);
    expect(response?.[1]?.messageId).toBe("m-threaded");
    expect(response?.[2]).toBeUndefined();
    expect(response?.[3]?.channel).toBe("slack");
  });

  it("forwards replyToId on gateway sends", async () => {
    mocks.resolveOutboundTarget.mockReturnValue({ ok: true, to: "123" });
    mocks.deliverOutboundPayloads.mockResolvedValue([{ messageId: "m-reply", channel: "slack" }]);
    const outboundPlugin = { outbound: { sendPoll: mocks.sendPoll } };
    mocks.getChannelPlugin.mockReturnValue(outboundPlugin);

    const { respond } = await runSend({
      to: "123",
      message: "threaded completion",
      channel: "slack",
      replyToId: "wamid.42",
      idempotencyKey: "idem-reply-to",
    });

    expect(deliveryCall()?.channel).toBe("slack");
    expect(deliveryCall()?.to).toBe("123");
    expect(deliveryCall()?.replyToId).toBe("wamid.42");
    expect(outboundRouteCall()?.channel).toBe("slack");
    expect(outboundRouteCall()?.target).toBe("123");
    expect(outboundRouteCall()?.replyToId).toBe("wamid.42");
    const response = firstRespondCall(respond);
    expect(response?.[0]).toBe(true);
    expect(response?.[1]?.messageId).toBe("m-reply");
    expect(response?.[2]).toBeUndefined();
    expect(response?.[3]?.channel).toBe("slack");
  });

  it("dispatches message actions through the gateway for plugin-owned channels", async () => {
    const reactPlugin: ChannelPlugin = {
      id: "whatsapp",
      meta: {
        id: "whatsapp",
        label: "WhatsApp",
        selectionLabel: "WhatsApp",
        docsPath: "/channels/whatsapp",
        blurb: "WhatsApp action dispatch test plugin.",
      },
      capabilities: { chatTypes: ["direct"], reactions: true },
      config: {
        listAccountIds: () => ["default"],
        resolveAccount: () => ({ enabled: true }),
        isConfigured: () => true,
      },
      actions: {
        describeMessageTool: () => ({ actions: ["react"] }),
        supportsAction: ({ action }) => action === "react",
        handleAction: async ({ params, requesterSenderId, toolContext }) =>
          jsonResult({
            ok: true,
            messageId: params.messageId,
            requesterSenderId,
            currentMessageId: toolContext?.currentMessageId,
            currentGraphChannelId: toolContext?.currentGraphChannelId,
            replyToMode: toolContext?.replyToMode,
            hasRepliedRef: toolContext?.hasRepliedRef?.value,
            skipCrossContextDecoration: toolContext?.skipCrossContextDecoration,
          }),
      },
    };
    mocks.getChannelPlugin.mockReturnValue(reactPlugin);
    setActivePluginRegistry(
      createTestRegistry([
        {
          pluginId: "whatsapp",
          source: "test",
          plugin: reactPlugin,
        },
      ]),
      "send-test-message-action",
    );
    mocks.dispatchChannelMessageAction.mockResolvedValueOnce(
      jsonResult({
        ok: true,
        messageId: "wamid.1",
        requesterSenderId: "trusted-user",
        currentMessageId: "wamid.1",
        currentGraphChannelId: "graph:team/chan",
        replyToMode: "first",
        hasRepliedRef: true,
        skipCrossContextDecoration: true,
      }),
    );

    const { respond } = await runMessageActionRequest({
      channel: "whatsapp",
      action: "react",
      params: {
        chatJid: "+15551234567",
        messageId: "wamid.1",
        emoji: "✅",
      },
      requesterSenderId: "trusted-user",
      inboundTurnKind: "room_event",
      toolContext: {
        currentGraphChannelId: "graph:team/chan",
        currentChannelProvider: "whatsapp",
        currentMessageId: "wamid.1",
        replyToMode: "first",
        hasRepliedRef: { value: true },
        skipCrossContextDecoration: true,
      },
      idempotencyKey: "idem-message-action",
    });

    expect(respond).toHaveBeenCalledWith(
      true,
      {
        ok: true,
        messageId: "wamid.1",
        requesterSenderId: "trusted-user",
        currentMessageId: "wamid.1",
        currentGraphChannelId: "graph:team/chan",
        replyToMode: "first",
        hasRepliedRef: true,
        skipCrossContextDecoration: true,
      },
      undefined,
      { channel: "whatsapp" },
    );
    expect(mocks.dispatchChannelMessageAction).toHaveBeenCalledWith(
      expect.objectContaining({ inboundEventKind: "room_event" }),
    );
  });

  it("mirrors successful source-conversation message.action sends into the assistant transcript", async () => {
    const telegramPlugin: ChannelPlugin = {
      id: "telegram",
      meta: {
        id: "telegram",
        label: "Telegram",
        selectionLabel: "Telegram",
        docsPath: "/channels/telegram",
        blurb: "Telegram source send transcript mirror test plugin.",
      },
      capabilities: { chatTypes: ["direct"] },
      config: {
        listAccountIds: () => ["default"],
        resolveAccount: () => ({ enabled: true }),
        isConfigured: () => true,
      },
      actions: {
        describeMessageTool: () => ({ actions: ["send"] }),
        supportsAction: ({ action }) => action === "send",
        handleAction: async () => jsonResult({ ok: true, messageId: "tg-1" }),
      },
      threading: {
        resolveCurrentChannelId: ({ to, threadId }) =>
          threadId == null ? to : `${to}:topic:${threadId}`,
      },
    };
    mocks.getChannelPlugin.mockReturnValue(telegramPlugin);
    setActivePluginRegistry(
      createTestRegistry([{ pluginId: "telegram", source: "test", plugin: telegramPlugin }]),
      "send-test-source-message-action-mirror",
    );
    mocks.dispatchChannelMessageAction.mockResolvedValueOnce(
      jsonResult({ ok: true, messageId: "tg-1" }),
    );

    const { respond } = await runMessageActionRequest({
      channel: "telegram",
      action: "send",
      params: {
        to: "chat-123",
        message: "visible source reply",
      },
      sessionKey: "agent:main:telegram:direct:chat-123",
      agentId: "main",
      toolContext: {
        currentChannelProvider: "telegram",
        currentChannelId: "chat-123",
      },
      idempotencyKey: "idem-source-message-action",
    });

    expect(firstRespondCall(respond)[0]).toBe(true);
    expect(mocks.appendAssistantMessageToSessionTranscript).toHaveBeenCalledWith({
      agentId: "main",
      sessionKey: "agent:main:telegram:direct:chat-123",
      text: "visible source reply",
      mediaUrls: undefined,
      idempotencyKey: "idem-source-message-action",
      config: {},
    });
  });

  it("mirrors accepted source send text aliases", async () => {
    mocks.dispatchChannelMessageAction.mockResolvedValueOnce(
      jsonResult({ ok: true, messageId: "tg-content-1" }),
    );

    const { respond } = await runMessageActionRequest({
      channel: "telegram",
      action: "send",
      params: {
        to: "chat-123",
        content: "visible content alias reply",
      },
      sessionKey: "agent:main:telegram:direct:chat-123",
      agentId: "main",
      toolContext: {
        currentChannelProvider: "telegram",
        currentChannelId: "chat-123",
      },
      idempotencyKey: "idem-content-source-message-action",
    });

    expect(firstRespondCall(respond)[0]).toBe(true);
    expect(mocks.appendAssistantMessageToSessionTranscript).toHaveBeenCalledWith({
      agentId: "main",
      sessionKey: "agent:main:telegram:direct:chat-123",
      text: "visible content alias reply",
      mediaUrls: undefined,
      idempotencyKey: "idem-content-source-message-action",
      config: {},
    });
  });

  it("keeps delivered source sends successful when transcript mirroring fails", async () => {
    mocks.dispatchChannelMessageAction.mockResolvedValueOnce(
      jsonResult({ ok: true, messageId: "tg-mirror-failed" }),
    );
    mocks.appendAssistantMessageToSessionTranscript.mockRejectedValueOnce(
      new Error("transcript unavailable"),
    );

    const { respond } = await runMessageActionRequest({
      channel: "telegram",
      action: "send",
      params: {
        to: "chat-123",
        message: "visible source reply",
      },
      sessionKey: "agent:main:telegram:direct:chat-123",
      agentId: "main",
      toolContext: {
        currentChannelProvider: "telegram",
        currentChannelId: "chat-123",
      },
      idempotencyKey: "idem-source-message-action-mirror-failed",
    });

    const call = firstRespondCall(respond);
    expect(call[0]).toBe(true);
    expect(call[1]).toEqual({ ok: true, messageId: "tg-mirror-failed" });
    expect(call[2]).toBeUndefined();
    expect(mocks.appendAssistantMessageToSessionTranscript).toHaveBeenCalledOnce();
  });

  it("mirrors caption-only source sends with media", async () => {
    mocks.dispatchChannelMessageAction.mockResolvedValueOnce(
      jsonResult({ ok: true, messageId: "tg-caption-1" }),
    );

    const { respond } = await runMessageActionRequest({
      channel: "telegram",
      action: "send",
      params: {
        to: "chat-123",
        mediaUrl: "https://example.com/image.png",
        caption: "visible media caption",
      },
      sessionKey: "agent:main:telegram:direct:chat-123",
      agentId: "main",
      toolContext: {
        currentChannelProvider: "telegram",
        currentChannelId: "chat-123",
      },
      idempotencyKey: "idem-caption-source-message-action",
    });

    expect(firstRespondCall(respond)[0]).toBe(true);
    expect(mocks.appendAssistantMessageToSessionTranscript).toHaveBeenCalledWith({
      agentId: "main",
      sessionKey: "agent:main:telegram:direct:chat-123",
      text: "visible media caption",
      mediaUrls: ["https://example.com/image.png"],
      idempotencyKey: "idem-caption-source-message-action",
      config: {},
    });
  });

  it("waits for source transcript mirroring before responding to message.action", async () => {
    const telegramPlugin: ChannelPlugin = {
      id: "telegram",
      meta: {
        id: "telegram",
        label: "Telegram",
        selectionLabel: "Telegram",
        docsPath: "/channels/telegram",
        blurb: "Telegram async source send transcript mirror test plugin.",
      },
      capabilities: { chatTypes: ["direct"] },
      config: {
        listAccountIds: () => ["default"],
        resolveAccount: () => ({ enabled: true }),
        isConfigured: () => true,
      },
      actions: {
        describeMessageTool: () => ({ actions: ["send"] }),
        supportsAction: ({ action }) => action === "send",
        handleAction: async () => jsonResult({ ok: true, messageId: "tg-async-1" }),
      },
    };
    const mirrorDeferred = createDeferred<{ ok: boolean; sessionFile: string }>();
    mocks.getChannelPlugin.mockReturnValue(telegramPlugin);
    setActivePluginRegistry(
      createTestRegistry([{ pluginId: "telegram", source: "test", plugin: telegramPlugin }]),
      "send-test-source-message-action-async-mirror",
    );
    mocks.dispatchChannelMessageAction.mockResolvedValueOnce(
      jsonResult({ ok: true, messageId: "tg-async-1" }),
    );
    mocks.appendAssistantMessageToSessionTranscript.mockReturnValueOnce(mirrorDeferred.promise);

    const respond = vi.fn();
    const request = sendHandlers["message.action"]({
      params: {
        channel: "telegram",
        action: "send",
        params: {
          to: "chat-123",
          message: "visible media caption",
        },
        sessionKey: "agent:main:telegram:direct:chat-123",
        agentId: "main",
        toolContext: {
          currentChannelProvider: "telegram",
          currentChannelId: "chat-123",
        },
        idempotencyKey: "idem-async-source-message-action",
      } as never,
      respond,
      context: makeContext(),
      req: { type: "req", id: "1", method: "message.action" },
      client: null,
      isWebchatConnect: () => false,
    });

    await vi.waitFor(() => {
      expect(mocks.appendAssistantMessageToSessionTranscript).toHaveBeenCalledTimes(1);
    });
    expect(respond).not.toHaveBeenCalled();

    mirrorDeferred.resolve({ ok: true, sessionFile: "x" });
    await request;

    expect(firstRespondCall(respond)[0]).toBe(true);
  });

  it("preserves source transcript mirror order before message.action responses", async () => {
    const telegramPlugin: ChannelPlugin = {
      id: "telegram",
      meta: {
        id: "telegram",
        label: "Telegram",
        selectionLabel: "Telegram",
        docsPath: "/channels/telegram",
        blurb: "Telegram ordered async source send transcript mirror test plugin.",
      },
      capabilities: { chatTypes: ["direct"] },
      config: {
        listAccountIds: () => ["default"],
        resolveAccount: () => ({ enabled: true }),
        isConfigured: () => true,
      },
      actions: {
        describeMessageTool: () => ({ actions: ["send"] }),
        supportsAction: ({ action }) => action === "send",
        handleAction: async () => jsonResult({ ok: true, messageId: "tg-ordered" }),
      },
    };
    const firstMirrorDeferred = createDeferred<{ ok: boolean; sessionFile: string }>();
    mocks.getChannelPlugin.mockReturnValue(telegramPlugin);
    setActivePluginRegistry(
      createTestRegistry([{ pluginId: "telegram", source: "test", plugin: telegramPlugin }]),
      "send-test-source-message-action-ordered-async-mirror",
    );
    mocks.dispatchChannelMessageAction.mockResolvedValue(
      jsonResult({ ok: true, messageId: "tg-ordered" }),
    );
    mocks.appendAssistantMessageToSessionTranscript
      .mockReturnValueOnce(firstMirrorDeferred.promise)
      .mockResolvedValueOnce({ ok: true, sessionFile: "x" });

    const firstRespond = vi.fn();
    const secondRespond = vi.fn();
    const first = sendHandlers["message.action"]({
      params: {
        channel: "telegram",
        action: "send",
        params: {
          to: "chat-123",
          message: "first visible reply",
        },
        sessionKey: "agent:main:telegram:direct:chat-123",
        agentId: "main",
        toolContext: {
          currentChannelProvider: "telegram",
          currentChannelId: "chat-123",
        },
        idempotencyKey: "idem-ordered-source-message-action-1",
      } as never,
      respond: firstRespond,
      context: makeContext(),
      req: { type: "req", id: "1", method: "message.action" },
      client: null,
      isWebchatConnect: () => false,
    });
    await vi.waitFor(() => {
      expect(mocks.appendAssistantMessageToSessionTranscript).toHaveBeenCalledTimes(1);
    });
    const second = sendHandlers["message.action"]({
      params: {
        channel: "telegram",
        action: "send",
        params: {
          to: "chat-123",
          message: "second visible reply",
        },
        sessionKey: "agent:main:telegram:direct:chat-123",
        agentId: "main",
        toolContext: {
          currentChannelProvider: "telegram",
          currentChannelId: "chat-123",
        },
        idempotencyKey: "idem-ordered-source-message-action-2",
      } as never,
      respond: secondRespond,
      context: makeContext(),
      req: { type: "req", id: "2", method: "message.action" },
      client: null,
      isWebchatConnect: () => false,
    });

    expect(mocks.appendAssistantMessageToSessionTranscript).toHaveBeenCalledTimes(1);
    expect(firstRespond).not.toHaveBeenCalled();
    expect(secondRespond).not.toHaveBeenCalled();
    expect(appendTranscriptCall(0)).toEqual(
      expect.objectContaining({ text: "first visible reply" }),
    );

    firstMirrorDeferred.resolve({ ok: true, sessionFile: "x" });
    await first;
    await second;

    expect(firstRespondCall(firstRespond)[0]).toBe(true);
    expect(firstRespondCall(secondRespond)[0]).toBe(true);
    expect(mocks.appendAssistantMessageToSessionTranscript).toHaveBeenCalledTimes(2);
    expect(appendTranscriptCall(1)).toEqual(
      expect.objectContaining({ text: "second visible reply" }),
    );
  });

  it("mirrors presentation-only source-conversation message.action sends", async () => {
    const telegramPlugin: ChannelPlugin = {
      id: "telegram",
      meta: {
        id: "telegram",
        label: "Telegram",
        selectionLabel: "Telegram",
        docsPath: "/channels/telegram",
        blurb: "Telegram source send rich transcript mirror test plugin.",
      },
      capabilities: { chatTypes: ["direct"] },
      config: {
        listAccountIds: () => ["default"],
        resolveAccount: () => ({ enabled: true }),
        isConfigured: () => true,
      },
      actions: {
        describeMessageTool: () => ({ actions: ["send"] }),
        supportsAction: ({ action }) => action === "send",
        handleAction: async () => jsonResult({ ok: true, messageId: "tg-rich-1" }),
      },
      threading: {
        resolveCurrentChannelId: ({ to, threadId }) =>
          threadId == null ? to : `${to}:topic:${threadId}`,
      },
    };
    mocks.getChannelPlugin.mockReturnValue(telegramPlugin);
    setActivePluginRegistry(
      createTestRegistry([{ pluginId: "telegram", source: "test", plugin: telegramPlugin }]),
      "send-test-rich-source-message-action-mirror",
    );
    mocks.dispatchChannelMessageAction.mockResolvedValueOnce(
      jsonResult({ ok: true, messageId: "tg-rich-1" }),
    );

    const { respond } = await runMessageActionRequest({
      channel: "telegram",
      action: "send",
      params: {
        to: "chat-123",
        presentation: {
          title: "Approval needed",
          blocks: [
            { type: "text", text: "Review the deployment request" },
            {
              type: "buttons",
              buttons: [
                { label: "Approve", value: "approve" },
                { label: "Reject", value: "reject" },
              ],
            },
          ],
        },
      },
      sessionKey: "agent:main:telegram:direct:chat-123",
      agentId: "main",
      toolContext: {
        currentChannelProvider: "telegram",
        currentChannelId: "chat-123",
      },
      idempotencyKey: "idem-rich-source-message-action",
    });

    expect(firstRespondCall(respond)[0]).toBe(true);
    expect(mocks.appendAssistantMessageToSessionTranscript).toHaveBeenCalledWith({
      agentId: "main",
      sessionKey: "agent:main:telegram:direct:chat-123",
      text: "Approval needed\nReview the deployment request\nApprove\nReject",
      mediaUrls: undefined,
      idempotencyKey: "idem-rich-source-message-action",
      config: {},
    });
  });

  it("mirrors title-only source-conversation presentation sends", async () => {
    const telegramPlugin: ChannelPlugin = {
      id: "telegram",
      meta: {
        id: "telegram",
        label: "Telegram",
        selectionLabel: "Telegram",
        docsPath: "/channels/telegram",
        blurb: "Telegram source send title-only transcript mirror test plugin.",
      },
      capabilities: { chatTypes: ["direct"] },
      config: {
        listAccountIds: () => ["default"],
        resolveAccount: () => ({ enabled: true }),
        isConfigured: () => true,
      },
      actions: {
        describeMessageTool: () => ({ actions: ["send"] }),
        supportsAction: ({ action }) => action === "send",
        handleAction: async () => jsonResult({ ok: true, messageId: "tg-title-1" }),
      },
    };
    mocks.getChannelPlugin.mockReturnValue(telegramPlugin);
    setActivePluginRegistry(
      createTestRegistry([{ pluginId: "telegram", source: "test", plugin: telegramPlugin }]),
      "send-test-title-only-source-message-action-mirror",
    );
    mocks.dispatchChannelMessageAction.mockResolvedValueOnce(
      jsonResult({ ok: true, messageId: "tg-title-1" }),
    );

    const { respond } = await runMessageActionRequest({
      channel: "telegram",
      action: "send",
      params: {
        to: "chat-123",
        presentation: {
          title: "Title-only approval",
        },
      },
      sessionKey: "agent:main:telegram:direct:chat-123",
      agentId: "main",
      toolContext: {
        currentChannelProvider: "telegram",
        currentChannelId: "chat-123",
      },
      idempotencyKey: "idem-title-only-source-message-action",
    });

    expect(firstRespondCall(respond)[0]).toBe(true);
    expect(mocks.appendAssistantMessageToSessionTranscript).toHaveBeenCalledWith({
      agentId: "main",
      sessionKey: "agent:main:telegram:direct:chat-123",
      text: "Title-only approval",
      mediaUrls: undefined,
      idempotencyKey: "idem-title-only-source-message-action",
      config: {},
    });
  });

  it("mirrors auto-threaded Telegram source sends into the topic transcript", async () => {
    const telegramTopicPlugin: ChannelPlugin = {
      id: "telegram",
      meta: {
        id: "telegram",
        label: "Telegram",
        selectionLabel: "Telegram",
        docsPath: "/channels/telegram",
        blurb: "Telegram topic source send transcript mirror test plugin.",
      },
      capabilities: { chatTypes: ["group"] },
      config: {
        listAccountIds: () => ["default"],
        resolveAccount: () => ({ enabled: true }),
        isConfigured: () => true,
      },
      actions: {
        describeMessageTool: () => ({ actions: ["send"] }),
        supportsAction: ({ action }) => action === "send",
        handleAction: async () => jsonResult({ ok: true, messageId: "tg-topic-1" }),
      },
      threading: {
        resolveCurrentChannelId: ({ to, threadId }) =>
          threadId == null ? to : `${to}:topic:${threadId}`,
      },
    };
    mocks.getChannelPlugin.mockReturnValue(telegramTopicPlugin);
    setActivePluginRegistry(
      createTestRegistry([{ pluginId: "telegram", source: "test", plugin: telegramTopicPlugin }]),
      "send-test-topic-source-message-action-mirror",
    );
    mocks.dispatchChannelMessageAction.mockResolvedValueOnce(
      jsonResult({ ok: true, messageId: "tg-topic-1" }),
    );

    const { respond } = await runMessageActionRequest({
      channel: "telegram",
      action: "send",
      params: {
        to: "chat-123",
        message: "visible topic source reply",
        messageThreadId: "77",
      },
      sessionKey: "agent:main:telegram:group:chat-123:topic:77",
      agentId: "main",
      toolContext: {
        currentChannelProvider: "telegram",
        currentChannelId: "chat-123:topic:77",
        currentThreadTs: "77",
      },
      idempotencyKey: "idem-topic-source-message-action",
    });

    expect(firstRespondCall(respond)[0]).toBe(true);
    expect(mocks.appendAssistantMessageToSessionTranscript).toHaveBeenCalledWith({
      agentId: "main",
      sessionKey: "agent:main:telegram:group:chat-123:topic:77",
      text: "visible topic source reply",
      mediaUrls: undefined,
      idempotencyKey: "idem-topic-source-message-action",
      config: {},
    });
  });

  it("does not mirror topic context when delivery params target the parent chat", async () => {
    const telegramTopicPlugin: ChannelPlugin = {
      id: "telegram",
      meta: {
        id: "telegram",
        label: "Telegram",
        selectionLabel: "Telegram",
        docsPath: "/channels/telegram",
        blurb: "Telegram parent send transcript mirror test plugin.",
      },
      capabilities: { chatTypes: ["group"] },
      config: {
        listAccountIds: () => ["default"],
        resolveAccount: () => ({ enabled: true }),
        isConfigured: () => true,
      },
      actions: {
        describeMessageTool: () => ({ actions: ["send"] }),
        supportsAction: ({ action }) => action === "send",
        handleAction: async () => jsonResult({ ok: true, messageId: "tg-parent-1" }),
      },
      threading: {
        resolveCurrentChannelId: ({ to, threadId }) =>
          threadId == null ? to : `${to}:topic:${threadId}`,
      },
    };
    mocks.getChannelPlugin.mockReturnValue(telegramTopicPlugin);
    setActivePluginRegistry(
      createTestRegistry([{ pluginId: "telegram", source: "test", plugin: telegramTopicPlugin }]),
      "send-test-topic-context-parent-message-action-mirror",
    );
    mocks.dispatchChannelMessageAction.mockResolvedValueOnce(
      jsonResult({ ok: true, messageId: "tg-parent-1" }),
    );

    const { respond } = await runMessageActionRequest({
      channel: "telegram",
      action: "send",
      params: {
        to: "chat-123",
        message: "visible parent source reply",
      },
      sessionKey: "agent:main:telegram:group:chat-123:topic:77",
      agentId: "main",
      toolContext: {
        currentChannelProvider: "telegram",
        currentChannelId: "chat-123:topic:77",
        currentThreadTs: "77",
      },
      idempotencyKey: "idem-topic-context-parent-message-action",
    });

    expect(firstRespondCall(respond)[0]).toBe(true);
    expect(mocks.appendAssistantMessageToSessionTranscript).not.toHaveBeenCalled();
  });

  it("does not mirror message.action sends to a different target", async () => {
    mocks.dispatchChannelMessageAction.mockResolvedValueOnce(
      jsonResult({ ok: true, messageId: "tg-external" }),
    );

    const { respond } = await runMessageActionRequest({
      channel: "telegram",
      action: "send",
      params: {
        to: "other-chat",
        message: "external visible reply",
      },
      sessionKey: "agent:main:telegram:direct:chat-123",
      agentId: "main",
      toolContext: {
        currentChannelProvider: "telegram",
        currentChannelId: "chat-123",
      },
      idempotencyKey: "idem-external-message-action",
    });

    expect(firstRespondCall(respond)[0]).toBe(true);
    expect(mocks.appendAssistantMessageToSessionTranscript).not.toHaveBeenCalled();
  });

  it("does not mirror explicitly failed message.action sends", async () => {
    mocks.dispatchChannelMessageAction.mockResolvedValueOnce(
      jsonResult({ ok: false, error: "delivery failed" }),
    );

    const { respond } = await runMessageActionRequest({
      channel: "telegram",
      action: "send",
      params: {
        to: "chat-123",
        message: "failed source reply",
      },
      sessionKey: "agent:main:telegram:direct:chat-123",
      agentId: "main",
      toolContext: {
        currentChannelProvider: "telegram",
        currentChannelId: "chat-123",
      },
      idempotencyKey: "idem-failed-message-action",
    });

    expect(firstRespondCall(respond)[0]).toBe(true);
    expect(mocks.appendAssistantMessageToSessionTranscript).not.toHaveBeenCalled();
  });

  it("passes agent-scoped media roots to gateway message actions", async () => {
    const mediaActionPlugin: ChannelPlugin = {
      id: "telegram",
      meta: {
        id: "telegram",
        label: "Telegram",
        selectionLabel: "Telegram",
        docsPath: "/channels/telegram",
        blurb: "Telegram media action dispatch test plugin.",
      },
      capabilities: { chatTypes: ["direct"] },
      config: {
        listAccountIds: () => ["default"],
        resolveAccount: () => ({ enabled: true }),
        isConfigured: () => true,
      },
      actions: {
        describeMessageTool: () => ({ actions: ["sendAttachment"] }),
        supportsAction: ({ action }) => action === "sendAttachment",
        handleAction: async () => jsonResult({ ok: true }),
      },
    };
    mocks.getChannelPlugin.mockReturnValue(mediaActionPlugin);
    setActivePluginRegistry(
      createTestRegistry([{ pluginId: "telegram", source: "test", plugin: mediaActionPlugin }]),
      "send-test-message-action-media-roots",
    );

    const { respond } = await runMessageActionRequest(
      {
        channel: "telegram",
        action: "sendAttachment",
        params: { chatId: "123", mediaUrl: `${TEST_AGENT_WORKSPACE}/render.png` },
        agentId: "work",
        idempotencyKey: "idem-message-action-media-roots",
      },
      { connect: { scopes: ["operator.write"] } },
    );

    expect(firstRespondCall(respond)[0]).toBe(true);
    const actionCall = lastDispatchChannelMessageActionCall();
    expect(actionCall?.mediaLocalRoots).toContain(TEST_AGENT_WORKSPACE);
    expect(actionCall?.gatewayClientScopes).toEqual(["operator.write"]);
  });
});
