feat: wire multi-agent config and routing

Co-authored-by: Mark Pors <1078320+pors@users.noreply.github.com>
This commit is contained in:
Peter Steinberger
2026-01-09 12:44:23 +00:00
parent 81beda0772
commit 7b81d97ec2
189 changed files with 4340 additions and 2903 deletions

View File

@@ -20,22 +20,20 @@ describe("resolveAgentRoute", () => {
test("peer binding wins over account binding", () => {
const cfg: ClawdbotConfig = {
routing: {
bindings: [
{
agentId: "a",
match: {
provider: "whatsapp",
accountId: "biz",
peer: { kind: "dm", id: "+1000" },
},
bindings: [
{
agentId: "a",
match: {
provider: "whatsapp",
accountId: "biz",
peer: { kind: "dm", id: "+1000" },
},
{
agentId: "b",
match: { provider: "whatsapp", accountId: "biz" },
},
],
},
},
{
agentId: "b",
match: { provider: "whatsapp", accountId: "biz" },
},
],
};
const route = resolveAgentRoute({
cfg,
@@ -50,26 +48,24 @@ describe("resolveAgentRoute", () => {
test("discord channel peer binding wins over guild binding", () => {
const cfg: ClawdbotConfig = {
routing: {
bindings: [
{
agentId: "chan",
match: {
provider: "discord",
accountId: "default",
peer: { kind: "channel", id: "c1" },
},
bindings: [
{
agentId: "chan",
match: {
provider: "discord",
accountId: "default",
peer: { kind: "channel", id: "c1" },
},
{
agentId: "guild",
match: {
provider: "discord",
accountId: "default",
guildId: "g1",
},
},
{
agentId: "guild",
match: {
provider: "discord",
accountId: "default",
guildId: "g1",
},
],
},
},
],
};
const route = resolveAgentRoute({
cfg,
@@ -85,22 +81,20 @@ describe("resolveAgentRoute", () => {
test("guild binding wins over account binding when peer not bound", () => {
const cfg: ClawdbotConfig = {
routing: {
bindings: [
{
agentId: "guild",
match: {
provider: "discord",
accountId: "default",
guildId: "g1",
},
bindings: [
{
agentId: "guild",
match: {
provider: "discord",
accountId: "default",
guildId: "g1",
},
{
agentId: "acct",
match: { provider: "discord", accountId: "default" },
},
],
},
},
{
agentId: "acct",
match: { provider: "discord", accountId: "default" },
},
],
};
const route = resolveAgentRoute({
cfg,
@@ -115,9 +109,7 @@ describe("resolveAgentRoute", () => {
test("missing accountId in binding matches default account only", () => {
const cfg: ClawdbotConfig = {
routing: {
bindings: [{ agentId: "defaultAcct", match: { provider: "whatsapp" } }],
},
bindings: [{ agentId: "defaultAcct", match: { provider: "whatsapp" } }],
};
const defaultRoute = resolveAgentRoute({
@@ -140,14 +132,12 @@ describe("resolveAgentRoute", () => {
test("accountId=* matches any account as a provider fallback", () => {
const cfg: ClawdbotConfig = {
routing: {
bindings: [
{
agentId: "any",
match: { provider: "whatsapp", accountId: "*" },
},
],
},
bindings: [
{
agentId: "any",
match: { provider: "whatsapp", accountId: "*" },
},
],
};
const route = resolveAgentRoute({
cfg,
@@ -161,9 +151,8 @@ describe("resolveAgentRoute", () => {
test("defaultAgentId is used when no binding matches", () => {
const cfg: ClawdbotConfig = {
routing: {
defaultAgentId: "home",
agents: { home: { workspace: "~/clawd-home" } },
agents: {
list: [{ id: "home", default: true, workspace: "~/clawd-home" }],
},
};
const route = resolveAgentRoute({

View File

@@ -1,9 +1,9 @@
import { resolveDefaultAgentId } from "../agents/agent-scope.js";
import type { ClawdbotConfig } from "../config/config.js";
import {
buildAgentMainSessionKey,
buildAgentPeerSessionKey,
DEFAULT_ACCOUNT_ID,
DEFAULT_AGENT_ID,
DEFAULT_MAIN_KEY,
normalizeAgentId,
} from "./session-key.js";
@@ -81,19 +81,13 @@ export function buildAgentSessionKey(params: {
}
function listBindings(cfg: ClawdbotConfig) {
const bindings = cfg.routing?.bindings;
const bindings = cfg.bindings;
return Array.isArray(bindings) ? bindings : [];
}
function listAgents(cfg: ClawdbotConfig) {
const agents = cfg.routing?.agents;
return agents && typeof agents === "object" ? agents : undefined;
}
function resolveDefaultAgentId(cfg: ClawdbotConfig): string {
const explicit = cfg.routing?.defaultAgentId?.trim();
if (explicit) return explicit;
return DEFAULT_AGENT_ID;
const agents = cfg.agents?.list;
return Array.isArray(agents) ? agents : [];
}
function pickFirstExistingAgentId(
@@ -102,8 +96,10 @@ function pickFirstExistingAgentId(
): string {
const normalized = normalizeAgentId(agentId);
const agents = listAgents(cfg);
if (!agents) return normalized;
if (Object.hasOwn(agents, normalized)) return normalized;
if (agents.length === 0) return normalized;
if (agents.some((agent) => normalizeAgentId(agent.id) === normalized)) {
return normalized;
}
return normalizeAgentId(resolveDefaultAgentId(cfg));
}