feat: wire multi-agent config and routing
Co-authored-by: Mark Pors <1078320+pors@users.noreply.github.com>
This commit is contained in:
@@ -24,22 +24,32 @@ describe("resolveHeartbeatIntervalMs", () => {
|
||||
|
||||
it("returns null when invalid or zero", () => {
|
||||
expect(
|
||||
resolveHeartbeatIntervalMs({ agent: { heartbeat: { every: "0m" } } }),
|
||||
resolveHeartbeatIntervalMs({
|
||||
agents: { defaults: { heartbeat: { every: "0m" } } },
|
||||
}),
|
||||
).toBeNull();
|
||||
expect(
|
||||
resolveHeartbeatIntervalMs({ agent: { heartbeat: { every: "oops" } } }),
|
||||
resolveHeartbeatIntervalMs({
|
||||
agents: { defaults: { heartbeat: { every: "oops" } } },
|
||||
}),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("parses duration strings with minute defaults", () => {
|
||||
expect(
|
||||
resolveHeartbeatIntervalMs({ agent: { heartbeat: { every: "5m" } } }),
|
||||
resolveHeartbeatIntervalMs({
|
||||
agents: { defaults: { heartbeat: { every: "5m" } } },
|
||||
}),
|
||||
).toBe(5 * 60_000);
|
||||
expect(
|
||||
resolveHeartbeatIntervalMs({ agent: { heartbeat: { every: "5" } } }),
|
||||
resolveHeartbeatIntervalMs({
|
||||
agents: { defaults: { heartbeat: { every: "5" } } },
|
||||
}),
|
||||
).toBe(5 * 60_000);
|
||||
expect(
|
||||
resolveHeartbeatIntervalMs({ agent: { heartbeat: { every: "2h" } } }),
|
||||
resolveHeartbeatIntervalMs({
|
||||
agents: { defaults: { heartbeat: { every: "2h" } } },
|
||||
}),
|
||||
).toBe(2 * 60 * 60_000);
|
||||
});
|
||||
});
|
||||
@@ -51,7 +61,7 @@ describe("resolveHeartbeatPrompt", () => {
|
||||
|
||||
it("uses a trimmed override when configured", () => {
|
||||
const cfg: ClawdbotConfig = {
|
||||
agent: { heartbeat: { prompt: " ping " } },
|
||||
agents: { defaults: { heartbeat: { prompt: " ping " } } },
|
||||
};
|
||||
expect(resolveHeartbeatPrompt(cfg)).toBe("ping");
|
||||
});
|
||||
@@ -65,7 +75,7 @@ describe("resolveHeartbeatDeliveryTarget", () => {
|
||||
|
||||
it("respects target none", () => {
|
||||
const cfg: ClawdbotConfig = {
|
||||
agent: { heartbeat: { target: "none" } },
|
||||
agents: { defaults: { heartbeat: { target: "none" } } },
|
||||
};
|
||||
expect(resolveHeartbeatDeliveryTarget({ cfg, entry: baseEntry })).toEqual({
|
||||
provider: "none",
|
||||
@@ -101,7 +111,7 @@ describe("resolveHeartbeatDeliveryTarget", () => {
|
||||
|
||||
it("applies allowFrom fallback for WhatsApp targets", () => {
|
||||
const cfg: ClawdbotConfig = {
|
||||
agent: { heartbeat: { target: "whatsapp", to: "+1999" } },
|
||||
agents: { defaults: { heartbeat: { target: "whatsapp", to: "+1999" } } },
|
||||
whatsapp: { allowFrom: ["+1555", "+1666"] },
|
||||
};
|
||||
const entry = {
|
||||
@@ -118,7 +128,7 @@ describe("resolveHeartbeatDeliveryTarget", () => {
|
||||
|
||||
it("keeps explicit telegram targets", () => {
|
||||
const cfg: ClawdbotConfig = {
|
||||
agent: { heartbeat: { target: "telegram", to: "123" } },
|
||||
agents: { defaults: { heartbeat: { target: "telegram", to: "123" } } },
|
||||
};
|
||||
expect(resolveHeartbeatDeliveryTarget({ cfg, entry: baseEntry })).toEqual({
|
||||
provider: "telegram",
|
||||
@@ -150,8 +160,10 @@ describe("runHeartbeatOnce", () => {
|
||||
);
|
||||
|
||||
const cfg: ClawdbotConfig = {
|
||||
agent: {
|
||||
heartbeat: { every: "5m", target: "whatsapp", to: "+1555" },
|
||||
agents: {
|
||||
defaults: {
|
||||
heartbeat: { every: "5m", target: "whatsapp", to: "+1555" },
|
||||
},
|
||||
},
|
||||
whatsapp: { allowFrom: ["*"] },
|
||||
session: { store: storePath },
|
||||
@@ -200,8 +212,10 @@ describe("runHeartbeatOnce", () => {
|
||||
const replySpy = vi.spyOn(replyModule, "getReplyFromConfig");
|
||||
try {
|
||||
const cfg: ClawdbotConfig = {
|
||||
routing: { defaultAgentId: "work" },
|
||||
agent: { heartbeat: { every: "5m" } },
|
||||
agents: {
|
||||
defaults: { heartbeat: { every: "5m" } },
|
||||
list: [{ id: "work", default: true }],
|
||||
},
|
||||
whatsapp: { allowFrom: ["*"] },
|
||||
session: { store: storeTemplate },
|
||||
};
|
||||
@@ -277,12 +291,14 @@ describe("runHeartbeatOnce", () => {
|
||||
);
|
||||
|
||||
const cfg: ClawdbotConfig = {
|
||||
agent: {
|
||||
heartbeat: {
|
||||
every: "5m",
|
||||
target: "whatsapp",
|
||||
to: "+1555",
|
||||
ackMaxChars: 0,
|
||||
agents: {
|
||||
defaults: {
|
||||
heartbeat: {
|
||||
every: "5m",
|
||||
target: "whatsapp",
|
||||
to: "+1555",
|
||||
ackMaxChars: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
whatsapp: { allowFrom: ["*"] },
|
||||
@@ -335,8 +351,10 @@ describe("runHeartbeatOnce", () => {
|
||||
);
|
||||
|
||||
const cfg: ClawdbotConfig = {
|
||||
agent: {
|
||||
heartbeat: { every: "5m", target: "whatsapp", to: "+1555" },
|
||||
agents: {
|
||||
defaults: {
|
||||
heartbeat: { every: "5m", target: "whatsapp", to: "+1555" },
|
||||
},
|
||||
},
|
||||
whatsapp: { allowFrom: ["*"] },
|
||||
session: { store: storePath },
|
||||
@@ -392,8 +410,10 @@ describe("runHeartbeatOnce", () => {
|
||||
);
|
||||
|
||||
const cfg: ClawdbotConfig = {
|
||||
agent: {
|
||||
heartbeat: { every: "5m", target: "telegram", to: "123456" },
|
||||
agents: {
|
||||
defaults: {
|
||||
heartbeat: { every: "5m", target: "telegram", to: "123456" },
|
||||
},
|
||||
},
|
||||
telegram: { botToken: "test-bot-token-123" },
|
||||
session: { store: storePath },
|
||||
@@ -455,8 +475,10 @@ describe("runHeartbeatOnce", () => {
|
||||
);
|
||||
|
||||
const cfg: ClawdbotConfig = {
|
||||
agent: {
|
||||
heartbeat: { every: "5m", target: "telegram", to: "123456" },
|
||||
agents: {
|
||||
defaults: {
|
||||
heartbeat: { every: "5m", target: "telegram", to: "123456" },
|
||||
},
|
||||
},
|
||||
telegram: {
|
||||
accounts: {
|
||||
|
||||
@@ -53,7 +53,9 @@ export function resolveHeartbeatIntervalMs(
|
||||
overrideEvery?: string,
|
||||
) {
|
||||
const raw =
|
||||
overrideEvery ?? cfg.agent?.heartbeat?.every ?? DEFAULT_HEARTBEAT_EVERY;
|
||||
overrideEvery ??
|
||||
cfg.agents?.defaults?.heartbeat?.every ??
|
||||
DEFAULT_HEARTBEAT_EVERY;
|
||||
if (!raw) return null;
|
||||
const trimmed = String(raw).trim();
|
||||
if (!trimmed) return null;
|
||||
@@ -68,13 +70,14 @@ export function resolveHeartbeatIntervalMs(
|
||||
}
|
||||
|
||||
export function resolveHeartbeatPrompt(cfg: ClawdbotConfig) {
|
||||
return resolveHeartbeatPromptText(cfg.agent?.heartbeat?.prompt);
|
||||
return resolveHeartbeatPromptText(cfg.agents?.defaults?.heartbeat?.prompt);
|
||||
}
|
||||
|
||||
function resolveHeartbeatAckMaxChars(cfg: ClawdbotConfig) {
|
||||
return Math.max(
|
||||
0,
|
||||
cfg.agent?.heartbeat?.ackMaxChars ?? DEFAULT_HEARTBEAT_ACK_MAX_CHARS,
|
||||
cfg.agents?.defaults?.heartbeat?.ackMaxChars ??
|
||||
DEFAULT_HEARTBEAT_ACK_MAX_CHARS,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -114,7 +114,9 @@ describe("deliverOutboundPayloads", () => {
|
||||
|
||||
it("uses iMessage media maxBytes from agent fallback", async () => {
|
||||
const sendIMessage = vi.fn().mockResolvedValue({ messageId: "i1" });
|
||||
const cfg: ClawdbotConfig = { agent: { mediaMaxMb: 3 } };
|
||||
const cfg: ClawdbotConfig = {
|
||||
agents: { defaults: { mediaMaxMb: 3 } },
|
||||
};
|
||||
|
||||
await deliverOutboundPayloads({
|
||||
cfg,
|
||||
|
||||
@@ -82,7 +82,9 @@ function resolveMediaMaxBytes(
|
||||
: (cfg.imessage?.accounts?.[normalizedAccountId]?.mediaMaxMb ??
|
||||
cfg.imessage?.mediaMaxMb);
|
||||
if (providerLimit) return providerLimit * MB;
|
||||
if (cfg.agent?.mediaMaxMb) return cfg.agent.mediaMaxMb * MB;
|
||||
if (cfg.agents?.defaults?.mediaMaxMb) {
|
||||
return cfg.agents.defaults.mediaMaxMb * MB;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -130,7 +130,7 @@ export function resolveHeartbeatDeliveryTarget(params: {
|
||||
entry?: SessionEntry;
|
||||
}): OutboundTarget {
|
||||
const { cfg, entry } = params;
|
||||
const rawTarget = cfg.agent?.heartbeat?.target;
|
||||
const rawTarget = cfg.agents?.defaults?.heartbeat?.target;
|
||||
const target: HeartbeatTarget =
|
||||
rawTarget === "whatsapp" ||
|
||||
rawTarget === "telegram" ||
|
||||
@@ -148,9 +148,9 @@ export function resolveHeartbeatDeliveryTarget(params: {
|
||||
}
|
||||
|
||||
const explicitTo =
|
||||
typeof cfg.agent?.heartbeat?.to === "string" &&
|
||||
cfg.agent.heartbeat.to.trim()
|
||||
? cfg.agent.heartbeat.to.trim()
|
||||
typeof cfg.agents?.defaults?.heartbeat?.to === "string" &&
|
||||
cfg.agents.defaults.heartbeat.to.trim()
|
||||
? cfg.agents.defaults.heartbeat.to.trim()
|
||||
: undefined;
|
||||
|
||||
const lastProvider =
|
||||
|
||||
@@ -4,6 +4,7 @@ import path from "node:path";
|
||||
|
||||
import JSON5 from "json5";
|
||||
|
||||
import { resolveDefaultAgentId } from "../agents/agent-scope.js";
|
||||
import type { ClawdbotConfig } from "../config/config.js";
|
||||
import { resolveOAuthDir, resolveStateDir } from "../config/paths.js";
|
||||
import type { SessionEntry } from "../config/sessions.js";
|
||||
@@ -12,7 +13,6 @@ import { createSubsystemLogger } from "../logging.js";
|
||||
import {
|
||||
buildAgentMainSessionKey,
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
DEFAULT_AGENT_ID,
|
||||
DEFAULT_MAIN_KEY,
|
||||
normalizeAgentId,
|
||||
} from "../routing/session-key.js";
|
||||
@@ -192,9 +192,7 @@ export async function detectLegacyStateMigrations(params: {
|
||||
const stateDir = resolveStateDir(env, homedir);
|
||||
const oauthDir = resolveOAuthDir(env, stateDir);
|
||||
|
||||
const targetAgentId = normalizeAgentId(
|
||||
params.cfg.routing?.defaultAgentId ?? DEFAULT_AGENT_ID,
|
||||
);
|
||||
const targetAgentId = normalizeAgentId(resolveDefaultAgentId(params.cfg));
|
||||
const rawMainKey = params.cfg.session?.mainKey;
|
||||
const targetMainKey =
|
||||
typeof rawMainKey === "string" && rawMainKey.trim().length > 0
|
||||
|
||||
Reference in New Issue
Block a user