Merged via /review-pr -> /prepare-pr -> /merge-pr. Prepared head SHA: ad35a458a55427614a35c9d0713a7386172464ad Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com> Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com> Reviewed-by: @gumadeiras
250 lines
6.7 KiB
TypeScript
250 lines
6.7 KiB
TypeScript
import os from "node:os";
|
|
import path from "node:path";
|
|
import { describe, expect, it } from "vitest";
|
|
import type { OpenClawConfig } from "../config/config.js";
|
|
import { resolveStateDir } from "../config/paths.js";
|
|
import {
|
|
applyAgentBindings,
|
|
applyAgentConfig,
|
|
buildAgentSummaries,
|
|
pruneAgentConfig,
|
|
removeAgentBindings,
|
|
} from "./agents.js";
|
|
|
|
describe("agents helpers", () => {
|
|
it("buildAgentSummaries includes default + configured agents", () => {
|
|
const cfg: OpenClawConfig = {
|
|
agents: {
|
|
defaults: {
|
|
workspace: "/main-ws",
|
|
model: { primary: "anthropic/claude" },
|
|
},
|
|
list: [
|
|
{ id: "main" },
|
|
{
|
|
id: "work",
|
|
default: true,
|
|
name: "Work",
|
|
workspace: "/work-ws",
|
|
agentDir: "/state/agents/work/agent",
|
|
model: "openai/gpt-4.1",
|
|
},
|
|
],
|
|
},
|
|
bindings: [
|
|
{
|
|
agentId: "work",
|
|
match: { channel: "whatsapp", accountId: "biz" },
|
|
},
|
|
{ agentId: "main", match: { channel: "telegram" } },
|
|
],
|
|
};
|
|
|
|
const summaries = buildAgentSummaries(cfg);
|
|
const main = summaries.find((summary) => summary.id === "main");
|
|
const work = summaries.find((summary) => summary.id === "work");
|
|
|
|
expect(main).toBeTruthy();
|
|
expect(main?.workspace).toBe(
|
|
path.join(resolveStateDir(process.env, os.homedir), "workspace-main"),
|
|
);
|
|
expect(main?.bindings).toBe(1);
|
|
expect(main?.model).toBe("anthropic/claude");
|
|
expect(main?.agentDir.endsWith(path.join("agents", "main", "agent"))).toBe(true);
|
|
|
|
expect(work).toBeTruthy();
|
|
expect(work?.name).toBe("Work");
|
|
expect(work?.workspace).toBe(path.resolve("/work-ws"));
|
|
expect(work?.agentDir).toBe(path.resolve("/state/agents/work/agent"));
|
|
expect(work?.bindings).toBe(1);
|
|
expect(work?.isDefault).toBe(true);
|
|
});
|
|
|
|
it("applyAgentConfig merges updates", () => {
|
|
const cfg: OpenClawConfig = {
|
|
agents: {
|
|
list: [{ id: "work", workspace: "/old-ws", model: "anthropic/claude" }],
|
|
},
|
|
};
|
|
|
|
const next = applyAgentConfig(cfg, {
|
|
agentId: "work",
|
|
name: "Work",
|
|
workspace: "/new-ws",
|
|
agentDir: "/state/work/agent",
|
|
});
|
|
|
|
const work = next.agents?.list?.find((agent) => agent.id === "work");
|
|
expect(work?.name).toBe("Work");
|
|
expect(work?.workspace).toBe("/new-ws");
|
|
expect(work?.agentDir).toBe("/state/work/agent");
|
|
expect(work?.model).toBe("anthropic/claude");
|
|
});
|
|
|
|
it("applyAgentBindings skips duplicates and reports conflicts", () => {
|
|
const cfg: OpenClawConfig = {
|
|
bindings: [
|
|
{
|
|
agentId: "main",
|
|
match: { channel: "whatsapp", accountId: "default" },
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = applyAgentBindings(cfg, [
|
|
{
|
|
agentId: "main",
|
|
match: { channel: "whatsapp", accountId: "default" },
|
|
},
|
|
{
|
|
agentId: "work",
|
|
match: { channel: "whatsapp", accountId: "default" },
|
|
},
|
|
{
|
|
agentId: "work",
|
|
match: { channel: "telegram" },
|
|
},
|
|
]);
|
|
|
|
expect(result.added).toHaveLength(1);
|
|
expect(result.skipped).toHaveLength(1);
|
|
expect(result.conflicts).toHaveLength(1);
|
|
expect(result.config.bindings).toHaveLength(2);
|
|
});
|
|
|
|
it("applyAgentBindings upgrades channel-only binding to account-specific binding for same agent", () => {
|
|
const cfg: OpenClawConfig = {
|
|
bindings: [
|
|
{
|
|
agentId: "main",
|
|
match: { channel: "telegram" },
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = applyAgentBindings(cfg, [
|
|
{
|
|
agentId: "main",
|
|
match: { channel: "telegram", accountId: "work" },
|
|
},
|
|
]);
|
|
|
|
expect(result.added).toHaveLength(0);
|
|
expect(result.updated).toHaveLength(1);
|
|
expect(result.conflicts).toHaveLength(0);
|
|
expect(result.config.bindings).toEqual([
|
|
{
|
|
agentId: "main",
|
|
match: { channel: "telegram", accountId: "work" },
|
|
},
|
|
]);
|
|
});
|
|
|
|
it("applyAgentBindings treats role-based bindings as distinct routes", () => {
|
|
const cfg: OpenClawConfig = {
|
|
bindings: [
|
|
{
|
|
agentId: "main",
|
|
match: {
|
|
channel: "discord",
|
|
accountId: "guild-a",
|
|
guildId: "123",
|
|
roles: ["111", "222"],
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = applyAgentBindings(cfg, [
|
|
{
|
|
agentId: "work",
|
|
match: {
|
|
channel: "discord",
|
|
accountId: "guild-a",
|
|
guildId: "123",
|
|
},
|
|
},
|
|
]);
|
|
|
|
expect(result.added).toHaveLength(1);
|
|
expect(result.conflicts).toHaveLength(0);
|
|
expect(result.config.bindings).toHaveLength(2);
|
|
});
|
|
|
|
it("removeAgentBindings does not remove role-based bindings when removing channel-level routes", () => {
|
|
const cfg: OpenClawConfig = {
|
|
bindings: [
|
|
{
|
|
agentId: "main",
|
|
match: {
|
|
channel: "discord",
|
|
accountId: "guild-a",
|
|
guildId: "123",
|
|
roles: ["111", "222"],
|
|
},
|
|
},
|
|
{
|
|
agentId: "main",
|
|
match: {
|
|
channel: "discord",
|
|
accountId: "guild-a",
|
|
guildId: "123",
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = removeAgentBindings(cfg, [
|
|
{
|
|
agentId: "main",
|
|
match: {
|
|
channel: "discord",
|
|
accountId: "guild-a",
|
|
guildId: "123",
|
|
},
|
|
},
|
|
]);
|
|
|
|
expect(result.removed).toHaveLength(1);
|
|
expect(result.conflicts).toHaveLength(0);
|
|
expect(result.config.bindings).toEqual([
|
|
{
|
|
agentId: "main",
|
|
match: {
|
|
channel: "discord",
|
|
accountId: "guild-a",
|
|
guildId: "123",
|
|
roles: ["111", "222"],
|
|
},
|
|
},
|
|
]);
|
|
});
|
|
|
|
it("pruneAgentConfig removes agent, bindings, and allowlist entries", () => {
|
|
const cfg: OpenClawConfig = {
|
|
agents: {
|
|
list: [
|
|
{ id: "work", default: true, workspace: "/work-ws" },
|
|
{ id: "home", workspace: "/home-ws" },
|
|
],
|
|
},
|
|
bindings: [
|
|
{ agentId: "work", match: { channel: "whatsapp" } },
|
|
{ agentId: "home", match: { channel: "telegram" } },
|
|
],
|
|
tools: {
|
|
agentToAgent: { enabled: true, allow: ["work", "home"] },
|
|
},
|
|
};
|
|
|
|
const result = pruneAgentConfig(cfg, "work");
|
|
expect(result.config.agents?.list?.some((agent) => agent.id === "work")).toBe(false);
|
|
expect(result.config.agents?.list?.some((agent) => agent.id === "home")).toBe(true);
|
|
expect(result.config.bindings).toHaveLength(1);
|
|
expect(result.config.bindings?.[0]?.agentId).toBe("home");
|
|
expect(result.config.tools?.agentToAgent?.allow).toEqual(["home"]);
|
|
expect(result.removedBindings).toBe(1);
|
|
expect(result.removedAllow).toBe(1);
|
|
});
|
|
});
|