Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  dispatch-acp-delivery.test.ts

  Sprache: JAVA
 

Spracherkennung für: .ts vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

import { beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../../config/config.js";
import { createAcpDispatchDeliveryCoordinator } from "./dispatch-acp-delivery.js";
import type { ReplyDispatcher } from "./reply-dispatcher.js";
import { buildTestCtx } from "./test-ctx.js";
import { createAcpTestConfig } from "./test-fixtures/acp-runtime.js";

const ttsMocks = vi.hoisted(() => ({
  maybeApplyTtsToPayload: vi.fn(async (paramsUnknown: unknown) => {
    const params = paramsUnknown as { payload: unknown };
    return params.payload;
  }),
}));

const deliveryMocks = vi.hoisted(() => ({
  routeReply: vi.fn(async (_params: unknown) => ({ ok: true, messageId: "mock-message" })),
  runMessageAction: vi.fn(async (_params: unknown) => ({ ok: true as const })),
}));

const channelPluginMocks = vi.hoisted(() => ({
  shouldTreatDeliveredTextAsVisible: (({
    kind,
    text,
  }: {
    kind: "tool" | "block" | "final";
    text?: string;
  }) => kind === "block" && typeof text === "string" && text.trim().length > 0) as
    | ((params: { kind: "tool" | "block" | "final"; text?: string }) => boolean)
    | undefined,
  shouldTreatRoutedTextAsVisible: undefined as
    | ((params: { kind: "tool" | "block" | "final"; text?: string }) => boolean)
    | undefined,
  getChannelPlugin: vi.fn((channelId: string) => {
    if (channelId !== "visiblechat") {
      return undefined;
    }
    return {
      outbound: {
        shouldTreatDeliveredTextAsVisible: channelPluginMocks.shouldTreatDeliveredTextAsVisible,
        shouldTreatRoutedTextAsVisible: channelPluginMocks.shouldTreatRoutedTextAsVisible,
      },
    };
  }),
}));

vi.mock("./dispatch-acp-tts.runtime.js", () => ({
  maybeApplyTtsToPayload: (params: unknown) => ttsMocks.maybeApplyTtsToPayload(params),
}));

vi.mock("./route-reply.runtime.js", () => ({
  routeReply: (params: unknown) => deliveryMocks.routeReply(params),
}));

vi.mock("../../channels/plugins/index.js", () => ({
  getChannelPlugin: (channelId: string) => channelPluginMocks.getChannelPlugin(channelId),
  normalizeChannelId: (channelId?: string | null) => channelId?.trim().toLowerCase() || null,
}));

vi.mock("../../infra/outbound/message-action-runner.js", () => ({
  runMessageAction: (params: unknown) => deliveryMocks.runMessageAction(params),
}));

function createDispatcher(): ReplyDispatcher {
  return {
    sendToolResult: vi.fn(() => true),
    sendBlockReply: vi.fn(() => true),
    sendFinalReply: vi.fn(() => true),
    waitForIdle: vi.fn(async () => {}),
    getQueuedCounts: vi.fn(() => ({ tool: 0, block: 0, final: 0 })),
    getFailedCounts: vi.fn(() => ({ tool: 0, block: 0, final: 0 })),
    markComplete: vi.fn(),
  };
}

function createCoordinator(onReplyStart?: (...args: unknown[]) => Promise<void>) {
  return createAcpDispatchDeliveryCoordinator({
    cfg: createAcpTestConfig(),
    ctx: buildTestCtx({
      Provider: "visiblechat",
      Surface: "visiblechat",
      SessionKey: "agent:codex-acp:session-1",
    }),
    dispatcher: createDispatcher(),
    inboundAudio: false,
    shouldRouteToOriginating: false,
    ...(onReplyStart ? { onReplyStart } : {}),
  });
}

function createVisibleChatAcpCoordinator(cfg: OpenClawConfig) {
  return createAcpDispatchDeliveryCoordinator({
    cfg,
    ctx: buildTestCtx({
      Provider: "visiblechat",
      Surface: "visiblechat",
      SessionKey: "agent:codex-acp:session-1",
    }),
    dispatcher: createDispatcher(),
    inboundAudio: false,
    shouldRouteToOriginating: true,
    originatingChannel: "visiblechat",
    originatingTo: "channel:thread-1",
  });
}

async function expectVisibleChatBlockRoutesToAccount(
  cfg: OpenClawConfig,
  accountId: string | undefined,
): Promise<void> {
  const coordinator = createVisibleChatAcpCoordinator(cfg);

  await coordinator.deliver("block", { text: "hello" }, { skipTts: true });

  expect(deliveryMocks.routeReply).toHaveBeenCalledWith(
    expect.objectContaining({
      channel: "visiblechat",
      to: "channel:thread-1",
      accountId,
    }),
  );
}

describe("createAcpDispatchDeliveryCoordinator", () => {
  beforeEach(() => {
    deliveryMocks.routeReply.mockClear();
    deliveryMocks.routeReply.mockResolvedValue({ ok: true, messageId: "mock-message" });
    deliveryMocks.runMessageAction.mockClear();
    deliveryMocks.runMessageAction.mockResolvedValue({ ok: true as const });
    channelPluginMocks.getChannelPlugin.mockClear();
    channelPluginMocks.shouldTreatDeliveredTextAsVisible = ({
      kind,
      text,
    }: {
      kind: "tool" | "block" | "final";
      text?: string;
    }) => kind === "block" && typeof text === "string" && text.trim().length > 0;
    channelPluginMocks.shouldTreatRoutedTextAsVisible = undefined;
  });

  it("bypasses TTS when skipTts is requested", async () => {
    const dispatcher = createDispatcher();
    const coordinator = createAcpDispatchDeliveryCoordinator({
      cfg: createAcpTestConfig(),
      ctx: buildTestCtx({
        Provider: "visiblechat",
        Surface: "visiblechat",
        SessionKey: "agent:codex-acp:session-1",
      }),
      dispatcher,
      inboundAudio: false,
      shouldRouteToOriginating: false,
    });

    await coordinator.deliver("final", { text: "hello" }, { skipTts: true });
    await coordinator.settleVisibleText();

    expect(ttsMocks.maybeApplyTtsToPayload).not.toHaveBeenCalled();
    expect(dispatcher.sendFinalReply).toHaveBeenCalledWith({ text: "hello" });
  });

  it("tracks successful final delivery separately from routed counters", async () => {
    const coordinator = createCoordinator();

    expect(coordinator.hasDeliveredFinalReply()).toBe(false);
    expect(coordinator.hasDeliveredVisibleText()).toBe(false);
    expect(coordinator.hasFailedVisibleTextDelivery()).toBe(false);

    await coordinator.deliver("final", { text: "hello" }, { skipTts: true });
    await coordinator.settleVisibleText();

    expect(coordinator.hasDeliveredFinalReply()).toBe(true);
    expect(coordinator.hasDeliveredVisibleText()).toBe(true);
    expect(coordinator.hasFailedVisibleTextDelivery()).toBe(false);
    expect(coordinator.getRoutedCounts().final).toBe(0);
  });

  it("tracks visible direct block text for dispatcher-backed delivery", async () => {
    const coordinator = createAcpDispatchDeliveryCoordinator({
      cfg: createAcpTestConfig(),
      ctx: buildTestCtx({
        Provider: "visiblechat",
        Surface: "visiblechat",
        SessionKey: "agent:codex-acp:session-1",
      }),
      dispatcher: createDispatcher(),
      inboundAudio: false,
      shouldRouteToOriginating: false,
    });

    await coordinator.deliver("block", { text: "hello" }, { skipTts: true });
    await coordinator.settleVisibleText();

    expect(coordinator.hasDeliveredFinalReply()).toBe(false);
    expect(coordinator.hasDeliveredVisibleText()).toBe(true);
    expect(coordinator.hasFailedVisibleTextDelivery()).toBe(false);
    expect(coordinator.getRoutedCounts().block).toBe(0);
  });

  it("prefers provider over surface when detecting direct channel visibility", async () => {
    const coordinator = createAcpDispatchDeliveryCoordinator({
      cfg: createAcpTestConfig(),
      ctx: buildTestCtx({
        Provider: "visiblechat",
        Surface: "webchat",
        SessionKey: "agent:codex-acp:session-1",
      }),
      dispatcher: createDispatcher(),
      inboundAudio: false,
      shouldRouteToOriginating: false,
    });

    await coordinator.deliver("block", { text: "hello" }, { skipTts: true });
    await coordinator.settleVisibleText();

    expect(coordinator.hasDeliveredVisibleText()).toBe(true);
    expect(coordinator.hasFailedVisibleTextDelivery()).toBe(false);
  });

  it("does not treat channels without a visibility override as visible for direct block delivery", async () => {
    const coordinator = createAcpDispatchDeliveryCoordinator({
      cfg: createAcpTestConfig(),
      ctx: buildTestCtx({
        Provider: "plainchat",
        Surface: "plainchat",
        SessionKey: "agent:codex-acp:session-1",
      }),
      dispatcher: createDispatcher(),
      inboundAudio: false,
      shouldRouteToOriginating: false,
    });

    await coordinator.deliver("block", { text: "hello" }, { skipTts: true });
    await coordinator.settleVisibleText();

    expect(coordinator.hasDeliveredFinalReply()).toBe(false);
    expect(coordinator.hasDeliveredVisibleText()).toBe(false);
    expect(coordinator.hasFailedVisibleTextDelivery()).toBe(false);
    expect(coordinator.getRoutedCounts().block).toBe(0);
  });

  it("treats direct plugin-owned block text as visible", async () => {
    const coordinator = createCoordinator();

    await coordinator.deliver("block", { text: "hello" }, { skipTts: true });
    await coordinator.settleVisibleText();

    expect(coordinator.hasDeliveredVisibleText()).toBe(true);
    expect(coordinator.hasFailedVisibleTextDelivery()).toBe(false);
  });

  it("honors the legacy routed visibility hook name for plugin compatibility", async () => {
    channelPluginMocks.shouldTreatDeliveredTextAsVisible = undefined;
    channelPluginMocks.shouldTreatRoutedTextAsVisible = ({
      kind,
      text,
    }: {
      kind: "tool" | "block" | "final";
      text?: string;
    }) => kind === "block" && typeof text === "string" && text.trim().length > 0;
    const coordinator = createCoordinator();

    await coordinator.deliver("block", { text: "hello" }, { skipTts: true });
    await coordinator.settleVisibleText();

    expect(coordinator.hasDeliveredVisibleText()).toBe(true);
    expect(coordinator.hasFailedVisibleTextDelivery()).toBe(false);
  });

  it("tracks failed visible block delivery separately", async () => {
    const dispatcher: ReplyDispatcher = {
      sendToolResult: vi.fn(() => true),
      sendBlockReply: vi.fn(() => false),
      sendFinalReply: vi.fn(() => true),
      waitForIdle: vi.fn(async () => {}),
      getQueuedCounts: vi.fn(() => ({ tool: 0, block: 0, final: 0 })),
      getFailedCounts: vi.fn(() => ({ tool: 0, block: 0, final: 0 })),
      markComplete: vi.fn(),
    };
    const coordinator = createAcpDispatchDeliveryCoordinator({
      cfg: createAcpTestConfig(),
      ctx: buildTestCtx({
        Provider: "visiblechat",
        Surface: "visiblechat",
        SessionKey: "agent:codex-acp:session-1",
      }),
      dispatcher,
      inboundAudio: false,
      shouldRouteToOriginating: false,
    });

    await coordinator.deliver("block", { text: "hello" }, { skipTts: true });

    expect(coordinator.hasDeliveredVisibleText()).toBe(false);
    expect(coordinator.hasFailedVisibleTextDelivery()).toBe(true);
  });

  it("starts reply lifecycle only once when called directly and through deliver", async () => {
    const onReplyStart = vi.fn(async () => {});
    const coordinator = createCoordinator(onReplyStart);

    await coordinator.startReplyLifecycle();
    await coordinator.deliver("final", { text: "hello" });
    await coordinator.startReplyLifecycle();
    await coordinator.deliver("block", { text: "world" });

    expect(onReplyStart).toHaveBeenCalledTimes(1);
  });

  it("starts reply lifecycle once when deliver triggers first", async () => {
    const onReplyStart = vi.fn(async () => {});
    const coordinator = createCoordinator(onReplyStart);

    await coordinator.deliver("final", { text: "hello" });
    await coordinator.startReplyLifecycle();

    expect(onReplyStart).toHaveBeenCalledTimes(1);
  });

  it("does not block delivery when reply lifecycle startup hangs", async () => {
    const onReplyStart = vi.fn(
      async () =>
        await new Promise<void>(() => {
          // Intentionally never resolve to simulate a stuck typing/reaction side effect.
        }),
    );
    const coordinator = createCoordinator(onReplyStart);

    const delivered = await Promise.race([
      coordinator.deliver("final", { text: "hello" }).then(() => "delivered"),
      new Promise<string>((resolve) => {
        setTimeout(() => resolve("timed-out"), 50);
      }),
    ]);

    expect(delivered).toBe("delivered");
    expect(onReplyStart).toHaveBeenCalledTimes(1);
  });

  it("does not start reply lifecycle for empty payload delivery", async () => {
    const onReplyStart = vi.fn(async () => {});
    const coordinator = createCoordinator(onReplyStart);

    await coordinator.deliver("final", {});

    expect(onReplyStart).not.toHaveBeenCalled();
  });

  it("does not fire onReplyStart when user delivery is suppressed", async () => {
    const onReplyStart = vi.fn(async () => {});
    const dispatcher = createDispatcher();
    const coordinator = createAcpDispatchDeliveryCoordinator({
      cfg: createAcpTestConfig(),
      ctx: buildTestCtx({
        Provider: "visiblechat",
        Surface: "visiblechat",
        SessionKey: "agent:codex-acp:session-1",
      }),
      dispatcher,
      inboundAudio: false,
      suppressUserDelivery: true,
      shouldRouteToOriginating: false,
      onReplyStart,
    });

    // Directly invoking the lifecycle (e.g. from dispatch-acp.ts before the
    // first deliver call) must not fire the typing indicator when delivery is
    // suppressed by sendPolicy: "deny".
    await coordinator.startReplyLifecycle();
    const delivered = await coordinator.deliver("final", { text: "hello" });

    expect(delivered).toBe(false);
    expect(onReplyStart).not.toHaveBeenCalled();
  });

  it("keeps parent-owned background ACP child delivery silent while preserving accumulated output", async () => {
    const dispatcher = createDispatcher();
    const coordinator = createAcpDispatchDeliveryCoordinator({
      cfg: createAcpTestConfig(),
      ctx: buildTestCtx({
        Provider: "visiblechat",
        Surface: "visiblechat",
        SessionKey: "agent:codex-acp:session-1",
      }),
      dispatcher,
      inboundAudio: false,
      suppressUserDelivery: true,
      shouldRouteToOriginating: true,
      originatingChannel: "visiblechat",
      originatingTo: "visiblechat:123",
    });

    const blockDelivered = await coordinator.deliver("block", { text: "working on it" });
    const finalDelivered = await coordinator.deliver("final", { text: "done" });
    await coordinator.settleVisibleText();

    expect(blockDelivered).toBe(false);
    expect(finalDelivered).toBe(false);
    expect(dispatcher.sendBlockReply).not.toHaveBeenCalled();
    expect(dispatcher.sendFinalReply).not.toHaveBeenCalled();
    expect(coordinator.getAccumulatedBlockText()).toBe("working on it");
    expect(coordinator.hasDeliveredVisibleText()).toBe(false);
  });

  it("routes ACP replies through the configured default account when AccountId is omitted", async () => {
    await expectVisibleChatBlockRoutesToAccount(
      createAcpTestConfig({
        channels: {
          visiblechat: {
            defaultAccount: "work",
          },
        },
      }),
      "work",
    );
  });

  it("mirrors routed ACP replies into the target ACP session", async () => {
    const coordinator = createAcpDispatchDeliveryCoordinator({
      cfg: createAcpTestConfig(),
      ctx: buildTestCtx({
        Provider: "visiblechat",
        Surface: "visiblechat",
        SessionKey: "agent:main:main",
      }),
      dispatcher: createDispatcher(),
      inboundAudio: false,
      sessionKey: "agent:claude:acp:spawned",
      shouldRouteToOriginating: true,
      originatingChannel: "visiblechat",
      originatingTo: "channel:thread-1",
    });

    await coordinator.deliver("block", { text: "hello" }, { skipTts: true });

    expect(deliveryMocks.routeReply).toHaveBeenCalledWith(
      expect.objectContaining({
        sessionKey: "agent:claude:acp:spawned",
        policySessionKey: "agent:main:main",
      }),
    );
  });

  it("routes ACP replies when cfg.channels is missing", async () => {
    await expectVisibleChatBlockRoutesToAccount({} as OpenClawConfig, undefined);
  });

  it("treats routed plugin-owned block text as visible", async () => {
    const coordinator = createAcpDispatchDeliveryCoordinator({
      cfg: createAcpTestConfig(),
      ctx: buildTestCtx({
        Provider: "visiblechat",
        Surface: "visiblechat",
        SessionKey: "agent:codex-acp:session-1",
      }),
      dispatcher: createDispatcher(),
      inboundAudio: false,
      shouldRouteToOriginating: true,
      originatingChannel: "visiblechat",
      originatingTo: "channel:thread-1",
    });

    await coordinator.deliver("block", { text: "hello" }, { skipTts: true });

    expect(coordinator.hasDeliveredVisibleText()).toBe(true);
    expect(coordinator.hasFailedVisibleTextDelivery()).toBe(false);
    expect(coordinator.getRoutedCounts().block).toBe(1);
  });
});

¤ Dauer der Verarbeitung: 0.19 Sekunden  (vorverarbeitet am  2026-04-27) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge