mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:40:44 +00:00
ci: split auto-reply reply routing shard
This commit is contained in:
@@ -67,16 +67,7 @@ function createAutoReplyReplySplitShards() {
|
||||
}
|
||||
}
|
||||
|
||||
const mergedGroups = {
|
||||
"auto-reply-reply-agent-runner": groups["auto-reply-reply-agent-runner"],
|
||||
"auto-reply-reply-dispatch": groups["auto-reply-reply-dispatch"],
|
||||
"auto-reply-reply-commands-state-routing": [
|
||||
...groups["auto-reply-reply-commands"],
|
||||
...groups["auto-reply-reply-state-routing"],
|
||||
],
|
||||
};
|
||||
|
||||
return Object.entries(mergedGroups)
|
||||
return Object.entries(groups)
|
||||
.map(([groupName, includePatterns]) => ({
|
||||
configs: ["test/vitest/vitest.auto-reply-reply.config.ts"],
|
||||
includePatterns,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterAll, beforeAll, describe, expect, test, vi } from "vitest";
|
||||
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import type { ChannelPlugin } from "../channels/plugins/types.js";
|
||||
import { createChannelTestPluginBase } from "../test-utils/channel-plugins.js";
|
||||
import { setRegistry } from "./server.agent.gateway-server-agent.mocks.js";
|
||||
@@ -61,9 +61,18 @@ async function setTestSessionStore(params: {
|
||||
});
|
||||
}
|
||||
|
||||
function latestAgentCall(): AgentCommandCall {
|
||||
async function waitForAgentCall(runId: string): Promise<AgentCommandCall> {
|
||||
await vi.waitFor(() =>
|
||||
expect(
|
||||
(vi.mocked(agentCommand).mock.calls as unknown as Array<[AgentCommandCall]>).some(
|
||||
([call]) => call.runId === runId,
|
||||
),
|
||||
).toBe(true),
|
||||
);
|
||||
const calls = vi.mocked(agentCommand).mock.calls as unknown as Array<[unknown]>;
|
||||
return calls.at(-1)?.[0] as AgentCommandCall;
|
||||
return calls.find(
|
||||
([call]) => (call as AgentCommandCall).runId === runId,
|
||||
)?.[0] as AgentCommandCall;
|
||||
}
|
||||
|
||||
async function runMainAgentDeliveryWithSession(params: {
|
||||
@@ -89,7 +98,7 @@ async function runMainAgentDeliveryWithSession(params: {
|
||||
...params.request,
|
||||
});
|
||||
expect(res.ok).toBe(true);
|
||||
return latestAgentCall();
|
||||
return await waitForAgentCall(String(params.request.idempotencyKey));
|
||||
} finally {
|
||||
testState.allowFrom = undefined;
|
||||
}
|
||||
@@ -160,8 +169,19 @@ const defaultRegistry = createRegistry([
|
||||
]);
|
||||
|
||||
describe("gateway server agent", () => {
|
||||
test("agent marks implicit delivery when lastTo is stale", async () => {
|
||||
beforeEach(() => {
|
||||
vi.mocked(agentCommand).mockClear();
|
||||
testState.agentsConfig = undefined;
|
||||
testState.allowFrom = undefined;
|
||||
setRegistry(defaultRegistry);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
testState.agentsConfig = undefined;
|
||||
testState.allowFrom = undefined;
|
||||
});
|
||||
|
||||
test("agent marks implicit delivery when lastTo is stale", async () => {
|
||||
testState.allowFrom = ["+436769770569"];
|
||||
await setTestSessionStore({
|
||||
entries: {
|
||||
@@ -182,7 +202,7 @@ describe("gateway server agent", () => {
|
||||
});
|
||||
expect(res.ok).toBe(true);
|
||||
|
||||
const call = latestAgentCall();
|
||||
const call = await waitForAgentCall("idem-agent-last-stale");
|
||||
expectChannels(call, "whatsapp");
|
||||
expect(call.to).toBe("+1555");
|
||||
expect(call.deliveryTargetMode).toBe("implicit");
|
||||
@@ -191,7 +211,6 @@ describe("gateway server agent", () => {
|
||||
});
|
||||
|
||||
test("agent forwards sessionKey to agentCommand", async () => {
|
||||
setRegistry(defaultRegistry);
|
||||
await setTestSessionStore({
|
||||
entries: {
|
||||
"agent:main:subagent:abc": {
|
||||
@@ -207,7 +226,7 @@ describe("gateway server agent", () => {
|
||||
});
|
||||
expect(res.ok).toBe(true);
|
||||
|
||||
const call = latestAgentCall();
|
||||
const call = await waitForAgentCall("idem-agent-subkey");
|
||||
expect(call.sessionKey).toBe("agent:main:subagent:abc");
|
||||
expect(call.sessionId).toBe("sess-sub");
|
||||
expectChannels(call, "webchat");
|
||||
@@ -216,7 +235,6 @@ describe("gateway server agent", () => {
|
||||
});
|
||||
|
||||
test("agent preserves spawnDepth on subagent sessions", async () => {
|
||||
setRegistry(defaultRegistry);
|
||||
await setTestSessionStore({
|
||||
entries: {
|
||||
"agent:main:subagent:depth": {
|
||||
@@ -245,7 +263,6 @@ describe("gateway server agent", () => {
|
||||
});
|
||||
|
||||
test("agent derives sessionKey from agentId", async () => {
|
||||
setRegistry(defaultRegistry);
|
||||
await setTestSessionStore({
|
||||
agentId: "ops",
|
||||
entries: {
|
||||
@@ -263,13 +280,12 @@ describe("gateway server agent", () => {
|
||||
});
|
||||
expect(res.ok).toBe(true);
|
||||
|
||||
const call = latestAgentCall();
|
||||
const call = await waitForAgentCall("idem-agent-id");
|
||||
expect(call.sessionKey).toBe("agent:ops:main");
|
||||
expect(call.sessionId).toBe("sess-ops");
|
||||
});
|
||||
|
||||
test("agent rejects unknown reply channel", async () => {
|
||||
setRegistry(defaultRegistry);
|
||||
const res = await rpcReq(ws, "agent", {
|
||||
message: "hi",
|
||||
replyChannel: "unknown-channel",
|
||||
@@ -283,7 +299,6 @@ describe("gateway server agent", () => {
|
||||
});
|
||||
|
||||
test("agent rejects mismatched agentId and sessionKey", async () => {
|
||||
setRegistry(defaultRegistry);
|
||||
testState.agentsConfig = { list: [{ id: "ops" }] };
|
||||
const res = await rpcReq(ws, "agent", {
|
||||
message: "hi",
|
||||
@@ -299,7 +314,6 @@ describe("gateway server agent", () => {
|
||||
});
|
||||
|
||||
test("agent rejects malformed agent-prefixed session keys", async () => {
|
||||
setRegistry(defaultRegistry);
|
||||
const res = await rpcReq(ws, "agent", {
|
||||
message: "hi",
|
||||
sessionKey: "agent:main",
|
||||
@@ -391,7 +405,6 @@ describe("gateway server agent", () => {
|
||||
});
|
||||
|
||||
test("agent forwards image attachments as images[]", async () => {
|
||||
setRegistry(defaultRegistry);
|
||||
await setTestSessionStore({
|
||||
entries: {
|
||||
main: {
|
||||
@@ -414,7 +427,7 @@ describe("gateway server agent", () => {
|
||||
});
|
||||
expect(res.ok).toBe(true);
|
||||
|
||||
const call = latestAgentCall();
|
||||
const call = await waitForAgentCall("idem-agent-attachments");
|
||||
expect(call.sessionKey).toBe("agent:main:main");
|
||||
expectChannels(call, "webchat");
|
||||
expect(typeof call.message).toBe("string");
|
||||
@@ -429,7 +442,6 @@ describe("gateway server agent", () => {
|
||||
});
|
||||
|
||||
test("agent errors when delivery requested and no last channel exists", async () => {
|
||||
setRegistry(defaultRegistry);
|
||||
testState.allowFrom = ["+1555"];
|
||||
try {
|
||||
await setTestSessionStore({
|
||||
@@ -493,7 +505,6 @@ describe("gateway server agent", () => {
|
||||
idempotencyKey: "idem-agent-last-signal",
|
||||
},
|
||||
])("agent routes main last-channel $name", async (tc) => {
|
||||
setRegistry(defaultRegistry);
|
||||
await setTestSessionStore({
|
||||
entries: {
|
||||
main: {
|
||||
@@ -513,7 +524,7 @@ describe("gateway server agent", () => {
|
||||
});
|
||||
expect(res.ok).toBe(true);
|
||||
|
||||
const call = latestAgentCall();
|
||||
const call = await waitForAgentCall(tc.idempotencyKey);
|
||||
expectChannels(call, tc.lastChannel);
|
||||
expect(call.to).toBe(tc.lastTo);
|
||||
expect(call.deliver).toBe(true);
|
||||
|
||||
@@ -111,18 +111,30 @@ function expectChannels(call: Record<string, unknown>, channel: string) {
|
||||
expect(call.messageChannel).toBe(channel);
|
||||
}
|
||||
|
||||
function readAgentCommandCall(fromEnd = 1) {
|
||||
async function readAgentCommandCall(params: { runId?: string; fromEnd?: number } = {}) {
|
||||
if (params.runId) {
|
||||
await vi.waitFor(() =>
|
||||
expect(
|
||||
(vi.mocked(agentCommand).mock.calls as unknown as Array<[Record<string, unknown>]>).some(
|
||||
([call]) => call.runId === params.runId,
|
||||
),
|
||||
).toBe(true),
|
||||
);
|
||||
const calls = vi.mocked(agentCommand).mock.calls as unknown as Array<[Record<string, unknown>]>;
|
||||
return calls.find(([call]) => call.runId === params.runId)?.[0] ?? {};
|
||||
}
|
||||
const calls = vi.mocked(agentCommand).mock.calls;
|
||||
return (calls.at(-fromEnd)?.[0] ?? {}) as Record<string, unknown>;
|
||||
return (calls.at(-(params.fromEnd ?? 1))?.[0] ?? {}) as Record<string, unknown>;
|
||||
}
|
||||
|
||||
function expectAgentRoutingCall(params: {
|
||||
async function expectAgentRoutingCall(params: {
|
||||
channel: string;
|
||||
deliver: boolean;
|
||||
to?: string;
|
||||
fromEnd?: number;
|
||||
runId?: string;
|
||||
}) {
|
||||
const call = readAgentCommandCall(params.fromEnd);
|
||||
const call = await readAgentCommandCall({ runId: params.runId, fromEnd: params.fromEnd });
|
||||
expectChannels(call, params.channel);
|
||||
if ("to" in params) {
|
||||
expect(call.to).toBe(params.to);
|
||||
@@ -186,10 +198,13 @@ async function useTempSessionStorePath() {
|
||||
|
||||
describe("gateway server agent", () => {
|
||||
beforeEach(() => {
|
||||
vi.mocked(agentCommand).mockClear();
|
||||
testState.allowFrom = undefined;
|
||||
setRegistry(defaultRegistry);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
testState.allowFrom = undefined;
|
||||
setRegistry(emptyRegistry);
|
||||
});
|
||||
|
||||
@@ -215,11 +230,11 @@ describe("gateway server agent", () => {
|
||||
idempotencyKey: "idem-agent-last-msteams",
|
||||
});
|
||||
expect(res.ok).toBe(true);
|
||||
expectAgentRoutingCall({
|
||||
await expectAgentRoutingCall({
|
||||
channel: "msteams",
|
||||
deliver: true,
|
||||
to: "conversation:teams-123",
|
||||
fromEnd: 1,
|
||||
runId: "idem-agent-last-msteams",
|
||||
});
|
||||
});
|
||||
|
||||
@@ -308,10 +323,11 @@ describe("gateway server agent", () => {
|
||||
idempotencyKey: "idem-agent-imsg",
|
||||
});
|
||||
expect(resIMessage.ok).toBe(true);
|
||||
await vi.waitFor(() => {
|
||||
expect(vi.mocked(agentCommand)).toHaveBeenCalled();
|
||||
await expectAgentRoutingCall({
|
||||
channel: "imessage",
|
||||
deliver: true,
|
||||
runId: "idem-agent-imsg",
|
||||
});
|
||||
expectAgentRoutingCall({ channel: "imessage", deliver: true, fromEnd: 1 });
|
||||
});
|
||||
|
||||
test("agent accepts plugin channel alias (teams)", async () => {
|
||||
@@ -333,11 +349,11 @@ describe("gateway server agent", () => {
|
||||
idempotencyKey: "idem-agent-teams",
|
||||
});
|
||||
expect(resTeams.ok).toBe(true);
|
||||
expectAgentRoutingCall({
|
||||
await expectAgentRoutingCall({
|
||||
channel: "msteams",
|
||||
deliver: false,
|
||||
to: "conversation:teams-abc",
|
||||
fromEnd: 1,
|
||||
runId: "idem-agent-teams",
|
||||
});
|
||||
});
|
||||
|
||||
@@ -389,7 +405,11 @@ describe("gateway server agent", () => {
|
||||
idempotencyKey: "idem-agent-webchat-best-effort",
|
||||
});
|
||||
expect(res.ok).toBe(true);
|
||||
expectAgentRoutingCall({ channel: "webchat", deliver: false });
|
||||
await expectAgentRoutingCall({
|
||||
channel: "webchat",
|
||||
deliver: false,
|
||||
runId: "idem-agent-webchat-best-effort",
|
||||
});
|
||||
});
|
||||
|
||||
test("agent downgrades to session-only when multiple channels are configured but no external target resolves", async () => {
|
||||
@@ -417,7 +437,11 @@ describe("gateway server agent", () => {
|
||||
idempotencyKey: "idem-agent-multi-configured-best-effort",
|
||||
});
|
||||
expect(res.ok).toBe(true);
|
||||
expectAgentRoutingCall({ channel: "webchat", deliver: false });
|
||||
await expectAgentRoutingCall({
|
||||
channel: "webchat",
|
||||
deliver: false,
|
||||
runId: "idem-agent-multi-configured-best-effort",
|
||||
});
|
||||
});
|
||||
|
||||
test("agent uses webchat for internal runs when last provider is webchat", async () => {
|
||||
@@ -435,7 +459,11 @@ describe("gateway server agent", () => {
|
||||
});
|
||||
expect(res.ok).toBe(true);
|
||||
|
||||
expectAgentRoutingCall({ channel: "webchat", deliver: false });
|
||||
await expectAgentRoutingCall({
|
||||
channel: "webchat",
|
||||
deliver: false,
|
||||
runId: "idem-agent-webchat-internal",
|
||||
});
|
||||
});
|
||||
|
||||
test("write-scoped callers cannot reset conversations via agent", async () => {
|
||||
|
||||
@@ -328,6 +328,12 @@ describe("scripts/lib/ci-node-test-plan.mjs", () => {
|
||||
requiresDist: false,
|
||||
shardName: "auto-reply-reply-agent-runner",
|
||||
},
|
||||
{
|
||||
checkName: "checks-node-auto-reply-reply-commands",
|
||||
configs: ["test/vitest/vitest.auto-reply-reply.config.ts"],
|
||||
requiresDist: false,
|
||||
shardName: "auto-reply-reply-commands",
|
||||
},
|
||||
{
|
||||
checkName: "checks-node-auto-reply-reply-dispatch",
|
||||
configs: ["test/vitest/vitest.auto-reply-reply.config.ts"],
|
||||
@@ -335,10 +341,10 @@ describe("scripts/lib/ci-node-test-plan.mjs", () => {
|
||||
shardName: "auto-reply-reply-dispatch",
|
||||
},
|
||||
{
|
||||
checkName: "checks-node-auto-reply-reply-commands-state-routing",
|
||||
checkName: "checks-node-auto-reply-reply-state-routing",
|
||||
configs: ["test/vitest/vitest.auto-reply-reply.config.ts"],
|
||||
requiresDist: false,
|
||||
shardName: "auto-reply-reply-commands-state-routing",
|
||||
shardName: "auto-reply-reply-state-routing",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user