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


Quelle  payloads.test.ts

  Sprache: JAVA
 

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

import { resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
import { describe, expect, it } from "vitest";
import type { ReplyPayload } from "../../auto-reply/types.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { typedCases } from "../../test-utils/typed-cases.js";
import {
  createOutboundPayloadPlan,
  formatOutboundPayloadLog,
  normalizeOutboundPayloads,
  normalizeOutboundPayloadsForJson,
  normalizeReplyPayloadsForDelivery,
  projectOutboundPayloadPlanForDelivery,
  projectOutboundPayloadPlanForJson,
  projectOutboundPayloadPlanForMirror,
  projectOutboundPayloadPlanForOutbound,
  summarizeOutboundPayloadForTransport,
} from "./payloads.js";
import { registerPendingSpawnedChildrenQuery } from "./pending-spawn-query.js";

function resolveMirrorProjection(payloads: readonly ReplyPayload[]) {
  const normalized = normalizeReplyPayloadsForDelivery(payloads);
  return {
    text: normalized
      .map((payload) => payload.text)
      .filter((text): text is string => Boolean(text))
      .join("\n"),
    mediaUrls: normalized.flatMap(
      (payload) => resolveSendableOutboundReplyParts(payload).mediaUrls,
    ),
  };
}

describe("normalizeReplyPayloadsForDelivery", () => {
  it("parses directives, merges media, and preserves reply metadata", () => {
    expect(
      normalizeReplyPayloadsForDelivery([
        {
          text: "[[reply_to: 123]] Hello [[audio_as_voice]]\nMEDIA:https://x.test/a.png",
          mediaUrl: " https://x.test/a.png ",
          mediaUrls: ["https://x.test/a.png", "https://x.test/b.png"],
          replyToTag: false,
        },
      ]),
    ).toEqual([
      {
        text: "Hello",
        mediaUrl: undefined,
        mediaUrls: ["https://x.test/a.png", "https://x.test/b.png"],
        replyToId: "123",
        replyToTag: true,
        replyToCurrent: undefined,
        audioAsVoice: true,
      },
    ]);
  });

  it("drops silent payloads without media and suppresses reasoning payloads", () => {
    expect(
      normalizeReplyPayloadsForDelivery([
        { text: "NO_REPLY" },
        { text: "Reasoning:\n_step_", isReasoning: true },
        { text: "final answer" },
      ]),
    ).toEqual([
      {
        text: "final answer",
        mediaUrls: undefined,
        mediaUrl: undefined,
        replyToId: undefined,
        replyToCurrent: undefined,
        replyToTag: false,
        audioAsVoice: false,
      },
    ]);
  });

  it("suppresses relay status placeholder payloads", () => {
    expect(
      normalizeReplyPayloadsForDelivery([
        { text: "No channel reply." },
        { text: "Replied in-thread." },
        { text: "Replied in #maintainers." },
        {
          text: "Updated [wiki/providers.md](/Users/steipete/.openclaw/workspace/wiki/providers.md:33). No channel reply.",
        },
        {
          text: "Updated [wiki/tools.md] with the rollback failure-mode nuance. No channel reply.",
        },
      ]),
    ).toEqual([]);
  });

  it("keeps normal payloads that mention wiki without matching relay placeholders", () => {
    expect(
      normalizeReplyPayloadsForDelivery([
        { text: "Please update wiki/tools.md after this ships." },
      ]),
    ).toEqual([
      {
        text: "Please update wiki/tools.md after this ships.",
        mediaUrls: undefined,
        mediaUrl: undefined,
        replyToId: undefined,
        replyToCurrent: undefined,
        replyToTag: false,
        audioAsVoice: false,
      },
    ]);
  });

  it("drops JSON NO_REPLY action payloads without media", () => {
    expect(
      normalizeReplyPayloadsForDelivery([
        { text: '{"action":"NO_REPLY"}' },
        { text: '{\n  "action": "NO_REPLY"\n}' },
      ]),
    ).toEqual([]);
  });

  it("keeps JSON NO_REPLY objects that include extra fields", () => {
    expect(
      normalizeReplyPayloadsForDelivery([{ text: '{"action":"NO_REPLY","note":"example"}' }]),
    ).toEqual([
      {
        text: '{"action":"NO_REPLY","note":"example"}',
        mediaUrls: undefined,
        mediaUrl: undefined,
        replyToId: undefined,
        replyToCurrent: undefined,
        replyToTag: false,
        audioAsVoice: false,
      },
    ]);
  });

  it("keeps mixed NO_REPLY text literal and only suppresses exact sentinel payloads", () => {
    expect(
      normalizeReplyPayloadsForDelivery([
        { text: "NO_REPLY thanks for the update" },
        { text: "NO_REPLY" },
        { text: "thanks NO_REPLY" },
      ]),
    ).toEqual([
      {
        text: "NO_REPLY thanks for the update",
        mediaUrls: undefined,
        mediaUrl: undefined,
        replyToId: undefined,
        replyToCurrent: undefined,
        replyToTag: false,
        audioAsVoice: false,
      },
      {
        text: "thanks NO_REPLY",
        mediaUrls: undefined,
        mediaUrl: undefined,
        replyToId: undefined,
        replyToCurrent: undefined,
        replyToTag: false,
        audioAsVoice: false,
      },
    ]);
  });

  it("keeps silent token payloads when media exists", () => {
    expect(
      normalizeReplyPayloadsForDelivery([
        { text: "NO_REPLY", mediaUrl: "https://x.test/one.png" },
        { text: '{"action":"NO_REPLY"}', mediaUrls: ["https://x.test/two.png"] },
      ]),
    ).toEqual([
      {
        text: "",
        mediaUrls: ["https://x.test/one.png"],
        mediaUrl: "https://x.test/one.png",
        replyToId: undefined,
        replyToCurrent: undefined,
        replyToTag: false,
        audioAsVoice: false,
      },
      {
        text: "",
        mediaUrls: ["https://x.test/two.png"],
        mediaUrl: undefined,
        replyToId: undefined,
        replyToCurrent: undefined,
        replyToTag: false,
        audioAsVoice: false,
      },
    ]);
  });

  it("rewrites bare silent replies for direct conversations where silence is disallowed", () => {
    const cfg: OpenClawConfig = {
      agents: {
        defaults: {
          silentReply: {
            direct: "disallow",
            group: "allow",
            internal: "allow",
          },
        },
      },
    };

    const sessionKey = "agent:main:telegram:direct:123";
    const projected = projectOutboundPayloadPlanForDelivery(
      createOutboundPayloadPlan([{ text: "NO_REPLY" }], {
        cfg,
        sessionKey,
        surface: "telegram",
      }),
    );
    expect(projected).toHaveLength(1);
    expect(projected[0]?.text?.trim()).toBeTruthy();
    expect(projected[0]?.text?.trim()).not.toBe("NO_REPLY");
  });

  it("drops bare silent replies for groups when policy allows silence", () => {
    const cfg: OpenClawConfig = {
      agents: {
        defaults: {
          silentReply: {
            direct: "disallow",
            group: "allow",
            internal: "allow",
          },
        },
      },
    };

    expect(
      projectOutboundPayloadPlanForDelivery(
        createOutboundPayloadPlan([{ text: "NO_REPLY" }], {
          cfg,
          sessionKey: "agent:main:telegram:group:123",
          surface: "telegram",
        }),
      ),
    ).toEqual([]);
  });

  it("does not add silent-reply chatter when visible content is already being delivered", () => {
    const cfg: OpenClawConfig = {
      agents: {
        defaults: {
          silentReply: {
            direct: "disallow",
            group: "allow",
            internal: "allow",
          },
        },
      },
    };

    expect(
      projectOutboundPayloadPlanForDelivery(
        createOutboundPayloadPlan([{ text: "NO_REPLY" }, { text: "visible reply" }], {
          cfg,
          sessionKey: "agent:main:telegram:direct:123",
          surface: "telegram",
        }),
      ),
    ).toEqual([
      expect.objectContaining({
        text: "visible reply",
      }),
    ]);
  });

  describe("pending spawned subagent children", () => {
    const cfg: OpenClawConfig = {
      agents: {
        defaults: {
          silentReply: { direct: "disallow", group: "allow", internal: "allow" },
        },
      },
    };
    const planSilent = (sessionKey: string, hasPendingSpawnedChildren?: boolean) =>
      projectOutboundPayloadPlanForDelivery(
        createOutboundPayloadPlan([{ text: "NO_REPLY" }], {
          cfg,
          sessionKey,
          surface: "telegram",
          hasPendingSpawnedChildren,
        }),
      );

    it("drops bare silent replies when the context flag is set", () => {
      expect(planSilent("agent:main:telegram:direct:123", true)).toEqual([]);
    });

    it("drops bare silent replies via the registered runtime query", () => {
      const sessionKey = "agent:main:telegram:direct:456";
      const previousQuery = registerPendingSpawnedChildrenQuery((key) => key === sessionKey);
      try {
        expect(planSilent(sessionKey)).toEqual([]);
      } finally {
        registerPendingSpawnedChildrenQuery(previousQuery);
      }
    });

    it("falls back to the visible rewrite path when the query throws", () => {
      const previousQuery = registerPendingSpawnedChildrenQuery(() => {
        throw new Error("registry unavailable");
      });
      try {
        const delivery = planSilent("agent:main:telegram:direct:789");
        expect(delivery).toHaveLength(1);
        expect(delivery[0]?.text).toBeTruthy();
        expect(delivery[0]?.text).not.toBe("NO_REPLY");
      } finally {
        registerPendingSpawnedChildrenQuery(previousQuery);
      }
    });
  });

  it("keeps bare NO_REPLY visible when silence is disallowed and rewrite is disabled", () => {
    const cfg: OpenClawConfig = {
      agents: {
        defaults: {
          silentReply: {
            direct: "disallow",
            group: "allow",
            internal: "allow",
          },
          silentReplyRewrite: {
            direct: false,
          },
        },
      },
    };

    expect(
      projectOutboundPayloadPlanForDelivery(
        createOutboundPayloadPlan([{ text: "NO_REPLY" }], {
          cfg,
          sessionKey: "agent:main:telegram:direct:123",
          surface: "telegram",
        }),
      ),
    ).toEqual([
      expect.objectContaining({
        text: "NO_REPLY",
      }),
    ]);
  });

  it("is idempotent for already-normalized delivery payloads", () => {
    const once = normalizeReplyPayloadsForDelivery([
      {
        text: "Hello",
        mediaUrls: ["https://x.test/a.png"],
        replyToId: "123",
        replyToTag: true,
        replyToCurrent: true,
        audioAsVoice: true,
      },
      {
        text: "",
        channelData: { provider: "line" },
      },
    ]);
    const twice = normalizeReplyPayloadsForDelivery(once);
    expect(twice).toEqual(once);
  });

  it("captures a tricky payload matrix snapshot", () => {
    const input: ReplyPayload[] = [
      { text: "NO_REPLY" },
      { text: "NO_REPLY with details" },
      { text: '{"action":"NO_REPLY"}' },
      { text: '{"action":"NO_REPLY","note":"keep"}' },
      { text: "NO_REPLY", mediaUrl: "https://x.test/m1.png" },
      { text: "MEDIA:https://x.test/m2.png\n[[audio_as_voice]] [[reply_to: 444]] hi" },
      { text: "headline", btw: { question: "what changed?" } },
      { text: " \n\t ", channelData: { mode: "custom" } },
      { text: "Reasoning block", isReasoning: true },
    ];
    expect(normalizeReplyPayloadsForDelivery(input)).toMatchInlineSnapshot(`
      [
        {
          "audioAsVoice": false,
          "mediaUrl": undefined,
          "mediaUrls": undefined,
          "replyToCurrent": undefined,
          "replyToId": undefined,
          "replyToTag": false,
          "text": "NO_REPLY with details",
        },
        {
          "audioAsVoice": false,
          "mediaUrl": undefined,
          "mediaUrls": undefined,
          "replyToCurrent": undefined,
          "replyToId": undefined,
          "replyToTag": false,
          "text": "{"action":"NO_REPLY","note":"keep"}",
        },
        {
          "audioAsVoice": false,
          "mediaUrl": "https://x.test/m1.png",
          "mediaUrls": [
            "https://x.test/m1.png",
          ],
          "replyToCurrent": undefined,
          "replyToId": undefined,
          "replyToTag": false,
          "text": "",
        },
        {
          "audioAsVoice": true,
          "mediaUrl": "https://x.test/m2.png",
          "mediaUrls": [
            "https://x.test/m2.png",
          ],
          "replyToCurrent": undefined,
          "replyToId": "444",
          "replyToTag": true,
          "text": "hi",
        },
        {
          "audioAsVoice": false,
          "btw": {
            "question": "what changed?",
          },
          "mediaUrl": undefined,
          "mediaUrls": undefined,
          "replyToCurrent": undefined,
          "replyToId": undefined,
          "replyToTag": false,
          "text": "BTW
      Question: what changed?

      headline",
        },
        {
          "audioAsVoice": false,
          "channelData": {
            "mode": "custom",
          },
          "mediaUrl": undefined,
          "mediaUrls": undefined,
          "replyToCurrent": undefined,
          "replyToId": undefined,
          "replyToTag": false,
          "text": "",
        },
      ]
    `);
  });

  it("keeps renderable channel-data payloads and reply-to-current markers", () => {
    expect(
      normalizeReplyPayloadsForDelivery([
        {
          text: "[[reply_to_current]]",
          channelData: { line: { flexMessage: { altText: "Card", contents: {} } } },
        },
      ]),
    ).toEqual([
      {
        text: "",
        mediaUrls: undefined,
        mediaUrl: undefined,
        replyToCurrent: true,
        replyToTag: true,
        audioAsVoice: false,
        channelData: { line: { flexMessage: { altText: "Card", contents: {} } } },
      },
    ]);
  });
});

describe("normalizeOutboundPayloadsForJson", () => {
  function cloneReplyPayloads(
    input: Parameters<typeof normalizeOutboundPayloadsForJson>[0],
  ): ReplyPayload[] {
    return input.map((payload) =>
      "mediaUrls" in payload
        ? ({
            ...payload,
            mediaUrls: payload.mediaUrls ? [...payload.mediaUrls] : undefined,
          } as ReplyPayload)
        : ({ ...payload } as ReplyPayload),
    );
  }

  it.each(
    typedCases<{
      name: string;
      input: Parameters<typeof normalizeOutboundPayloadsForJson>[0];
      expected: ReturnType<typeof normalizeOutboundPayloadsForJson>;
    }>([
      {
        name: "text + media variants",
        input: [
          { text: "hi" },
          { text: "photo", mediaUrl: "https://x.test/a.jpg", audioAsVoice: true },
          { text: "multi", mediaUrls: ["https://x.test/1.png"] },
        ],
        expected: [
          {
            text: "hi",
            mediaUrl: null,
            mediaUrls: undefined,
            audioAsVoice: undefined,
            channelData: undefined,
          },
          {
            text: "photo",
            mediaUrl: "https://x.test/a.jpg",
            mediaUrls: ["https://x.test/a.jpg"],
            audioAsVoice: true,
            channelData: undefined,
          },
          {
            text: "multi",
            mediaUrl: null,
            mediaUrls: ["https://x.test/1.png"],
            audioAsVoice: undefined,
            channelData: undefined,
          },
        ],
      },
      {
        name: "MEDIA directive extraction",
        input: [
          {
            text: "MEDIA:https://x.test/a.png\nMEDIA:https://x.test/b.png",
          },
        ],
        expected: [
          {
            text: "",
            mediaUrl: null,
            mediaUrls: ["https://x.test/a.png", "https://x.test/b.png"],
            audioAsVoice: undefined,
            channelData: undefined,
          },
        ],
      },
    ]),
  )("$name", ({ input, expected }) => {
    expect(normalizeOutboundPayloadsForJson(cloneReplyPayloads(input))).toEqual(expected);
  });

  it("suppresses reasoning payloads", () => {
    expect(
      normalizeOutboundPayloadsForJson([
        { text: "Reasoning:\n_step_", isReasoning: true },
        { text: "final answer" },
      ]),
    ).toEqual([
      { text: "final answer", mediaUrl: null, mediaUrls: undefined, audioAsVoice: undefined },
    ]);
  });
});

describe("normalizeOutboundPayloads", () => {
  it("keeps channelData-only payloads", () => {
    const channelData = { line: { flexMessage: { altText: "Card", contents: {} } } };
    expect(normalizeOutboundPayloads([{ channelData }])).toEqual([
      { text: "", mediaUrls: [], channelData },
    ]);
  });

  it("suppresses reasoning payloads", () => {
    expect(
      normalizeOutboundPayloads([
        { text: "Reasoning:\n_step_", isReasoning: true },
        { text: "final answer" },
      ]),
    ).toEqual([{ text: "final answer", mediaUrls: [] }]);
  });

  it("formats BTW replies prominently for external delivery", () => {
    expect(
      normalizeOutboundPayloads([
        {
          text: "323",
          btw: { question: "what is 17 * 19?" },
        },
      ]),
    ).toEqual([{ text: "BTW\nQuestion: what is 17 * 19?\n\n323", mediaUrls: [] }]);
  });

  it("keeps delivery and mirror projections aligned", () => {
    const payloads: ReplyPayload[] = [
      { text: "Hello" },
      { text: "MEDIA:https://x.test/a.png\nMEDIA:https://x.test/b.png" },
      { text: '{"action":"NO_REPLY"}' },
      { text: "NO_REPLY", mediaUrl: "https://x.test/c.png" },
    ];

    const deliveryProjection = normalizeOutboundPayloads(payloads);
    const mirrorProjection = resolveMirrorProjection(payloads);

    expect(mirrorProjection.text).toBe(
      deliveryProjection
        .map((payload) => payload.text)
        .filter((text) => Boolean(text))
        .join("\n"),
    );
    expect(mirrorProjection.mediaUrls).toEqual(
      deliveryProjection.flatMap((payload) => payload.mediaUrls),
    );
  });
});

describe("OutboundPayloadPlan projections", () => {
  const matrix: ReplyPayload[] = [
    { text: "hello" },
    { text: "NO_REPLY" },
    { text: "NO_REPLY", mediaUrl: "https://x.test/1.png" },
    { text: "MEDIA:https://x.test/2.png\nworld" },
    { text: '{"action":"NO_REPLY","note":"keep"}' },
    { text: "reasoning", isReasoning: true },
    { text: " \n", channelData: { mode: "flex" } },
  ];

  it("matches normalizeReplyPayloadsForDelivery", () => {
    const plan = createOutboundPayloadPlan(matrix);
    expect(projectOutboundPayloadPlanForDelivery(plan)).toEqual(
      normalizeReplyPayloadsForDelivery(matrix),
    );
  });

  it("matches normalizeOutboundPayloads", () => {
    const plan = createOutboundPayloadPlan(matrix);
    expect(projectOutboundPayloadPlanForOutbound(plan)).toEqual(normalizeOutboundPayloads(matrix));
  });

  it("matches normalizeOutboundPayloadsForJson", () => {
    const plan = createOutboundPayloadPlan(matrix);
    expect(projectOutboundPayloadPlanForJson(plan)).toEqual(
      normalizeOutboundPayloadsForJson(matrix),
    );
  });

  it("matches mirror projection behavior", () => {
    const plan = createOutboundPayloadPlan(matrix);
    expect(projectOutboundPayloadPlanForMirror(plan)).toEqual(resolveMirrorProjection(matrix));
  });
});

describe("formatOutboundPayloadLog", () => {
  it.each(
    typedCases<{
      name: string;
      input: Parameters<typeof formatOutboundPayloadLog>[0];
      expected: string;
    }>([
      {
        name: "text with media lines",
        input: {
          text: "hello  ",
          mediaUrls: ["https://x.test/a.png", "https://x.test/b.png"],
        },
        expected: "hello\nMEDIA:https://x.test/a.png\nMEDIA:https://x.test/b.png",
      },
      {
        name: "media only",
        input: {
          text: "",
          mediaUrls: ["https://x.test/a.png"],
        },
        expected: "MEDIA:https://x.test/a.png",
      },
    ]),
  )("$name", ({ input, expected }) => {
    expect(
      formatOutboundPayloadLog({
        ...input,
        mediaUrls: [...input.mediaUrls],
      }),
    ).toBe(expected);
  });
});

describe("summarizeOutboundPayloadForTransport", () => {
  it("keeps visible text as channel text and does not expose hook-only content", () => {
    const summary = summarizeOutboundPayloadForTransport({
      text: "visible",
      spokenText: "hidden transcript",
    });

    expect(summary.text).toBe("visible");
    expect(summary.hookContent).toBeUndefined();
  });

  it("surfaces spokenText only as hook content for audio-only payloads", () => {
    const summary = summarizeOutboundPayloadForTransport({
      mediaUrl: "/tmp/reply.opus",
      audioAsVoice: true,
      spokenText: "Hi Ivy, good morning.",
    });

    expect(summary.text).toBe("");
    expect(summary.hookContent).toBe("Hi Ivy, good morning.");
    expect(summary.mediaUrls).toEqual(["/tmp/reply.opus"]);
    expect(summary.audioAsVoice).toBe(true);
  });

  it("ignores blank spokenText", () => {
    const summary = summarizeOutboundPayloadForTransport({
      mediaUrl: "/tmp/reply.opus",
      spokenText: "   ",
    });

    expect(summary.text).toBe("");
    expect(summary.hookContent).toBeUndefined();
  });
});

¤ Dauer der Verarbeitung: 0.24 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