mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 09:10:45 +00:00
test: reduce agents test hotspots
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { resetFileLockStateForTest } from "../../infra/file-lock.js";
|
||||
import { captureEnv } from "../../test-utils/env.js";
|
||||
import { resolveApiKeyForProfile, resetOAuthRefreshQueuesForTest } from "./oauth.js";
|
||||
@@ -110,8 +110,13 @@ describe("OAuth credential adoption is identity-gated", () => {
|
||||
"PI_CODING_AGENT_DIR",
|
||||
]);
|
||||
let tempRoot = "";
|
||||
let caseIndex = 0;
|
||||
let mainAgentDir = "";
|
||||
|
||||
beforeAll(async () => {
|
||||
tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-oauth-adopt-identity-"));
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
resetFileLockStateForTest();
|
||||
refreshProviderOAuthCredentialWithPluginMock.mockReset();
|
||||
@@ -119,9 +124,10 @@ describe("OAuth credential adoption is identity-gated", () => {
|
||||
formatProviderAuthProfileApiKeyWithPluginMock.mockReset();
|
||||
formatProviderAuthProfileApiKeyWithPluginMock.mockReturnValue(undefined);
|
||||
clearRuntimeAuthProfileStoreSnapshots();
|
||||
tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-oauth-adopt-identity-"));
|
||||
process.env.OPENCLAW_STATE_DIR = tempRoot;
|
||||
mainAgentDir = path.join(tempRoot, "agents", "main", "agent");
|
||||
caseIndex += 1;
|
||||
const caseRoot = path.join(tempRoot, `case-${caseIndex}`);
|
||||
process.env.OPENCLAW_STATE_DIR = caseRoot;
|
||||
mainAgentDir = path.join(caseRoot, "agents", "main", "agent");
|
||||
process.env.OPENCLAW_AGENT_DIR = mainAgentDir;
|
||||
process.env.PI_CODING_AGENT_DIR = mainAgentDir;
|
||||
await fs.mkdir(mainAgentDir, { recursive: true });
|
||||
@@ -133,6 +139,9 @@ describe("OAuth credential adoption is identity-gated", () => {
|
||||
resetFileLockStateForTest();
|
||||
clearRuntimeAuthProfileStoreSnapshots();
|
||||
resetOAuthRefreshQueuesForTest();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
if (tempRoot) {
|
||||
await fs.rm(tempRoot, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { resetFileLockStateForTest } from "../../infra/file-lock.js";
|
||||
import { captureEnv } from "../../test-utils/env.js";
|
||||
import { __testing as externalAuthTesting } from "./external-auth.js";
|
||||
@@ -116,8 +116,13 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () =>
|
||||
"PI_CODING_AGENT_DIR",
|
||||
]);
|
||||
let tempRoot = "";
|
||||
let caseIndex = 0;
|
||||
let mainAgentDir = "";
|
||||
|
||||
beforeAll(async () => {
|
||||
tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-oauth-mirror-"));
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
resetFileLockStateForTest();
|
||||
refreshProviderOAuthCredentialWithPluginMock.mockReset();
|
||||
@@ -126,9 +131,10 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () =>
|
||||
formatProviderAuthProfileApiKeyWithPluginMock.mockReturnValue(undefined);
|
||||
externalAuthTesting.setResolveExternalAuthProfilesForTest(() => []);
|
||||
clearRuntimeAuthProfileStoreSnapshots();
|
||||
tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-oauth-mirror-"));
|
||||
process.env.OPENCLAW_STATE_DIR = tempRoot;
|
||||
mainAgentDir = path.join(tempRoot, "agents", "main", "agent");
|
||||
caseIndex += 1;
|
||||
const caseRoot = path.join(tempRoot, `case-${caseIndex}`);
|
||||
process.env.OPENCLAW_STATE_DIR = caseRoot;
|
||||
mainAgentDir = path.join(caseRoot, "agents", "main", "agent");
|
||||
process.env.OPENCLAW_AGENT_DIR = mainAgentDir;
|
||||
process.env.PI_CODING_AGENT_DIR = mainAgentDir;
|
||||
await fs.mkdir(mainAgentDir, { recursive: true });
|
||||
@@ -141,6 +147,9 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () =>
|
||||
externalAuthTesting.resetResolveExternalAuthProfilesForTest();
|
||||
clearRuntimeAuthProfileStoreSnapshots();
|
||||
resetOAuthRefreshQueuesForTest();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
if (tempRoot) {
|
||||
await fs.rm(tempRoot, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
@@ -460,338 +460,6 @@ describe("openclaw-tools: subagents (sessions_spawn lifecycle)", () => {
|
||||
).toBe("bot-alpha");
|
||||
});
|
||||
|
||||
it("sessions_spawn prefers peer-specific binding over channel-only binding", async () => {
|
||||
const targetRoom = "!roomA:example.org";
|
||||
expect(
|
||||
await executeBoundAccountSpawn({
|
||||
callId: "call-peer-specific",
|
||||
agentId: "bot-alpha",
|
||||
context: {
|
||||
agentSessionKey: "main",
|
||||
agentChannel: "matrix",
|
||||
agentAccountId: "bot-beta",
|
||||
agentTo: targetRoom,
|
||||
},
|
||||
bindings: [
|
||||
{
|
||||
type: "route",
|
||||
agentId: "bot-alpha",
|
||||
match: { channel: "matrix", accountId: "bot-alpha-default" },
|
||||
},
|
||||
{
|
||||
type: "route",
|
||||
agentId: "bot-alpha",
|
||||
match: {
|
||||
channel: "matrix",
|
||||
peer: { kind: "channel", id: targetRoom },
|
||||
accountId: "bot-alpha-room-a",
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
).toBe("bot-alpha-room-a");
|
||||
});
|
||||
|
||||
it("sessions_spawn falls back to channel-only binding when peer does not match", async () => {
|
||||
const otherRoom = "!roomB:example.org";
|
||||
expect(
|
||||
await executeBoundAccountSpawn({
|
||||
callId: "call-fallback",
|
||||
agentId: "bot-alpha",
|
||||
context: {
|
||||
agentSessionKey: "main",
|
||||
agentChannel: "matrix",
|
||||
agentAccountId: "bot-beta",
|
||||
agentTo: otherRoom,
|
||||
},
|
||||
bindings: [
|
||||
{
|
||||
type: "route",
|
||||
agentId: "bot-alpha",
|
||||
match: { channel: "matrix", accountId: "bot-alpha-default" },
|
||||
},
|
||||
{
|
||||
type: "route",
|
||||
agentId: "bot-alpha",
|
||||
match: {
|
||||
channel: "matrix",
|
||||
peer: { kind: "channel", id: "!roomA:example.org" },
|
||||
accountId: "bot-alpha-room-a",
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
).toBe("bot-alpha-default");
|
||||
});
|
||||
|
||||
it("sessions_spawn treats a wildcard peer binding as match-any and beats channel-only", async () => {
|
||||
const callerRoom = "!anyRoom:example.org";
|
||||
expect(
|
||||
await executeBoundAccountSpawn({
|
||||
callId: "call-wildcard-peer",
|
||||
agentId: "bot-alpha",
|
||||
context: {
|
||||
agentSessionKey: "main",
|
||||
agentChannel: "matrix",
|
||||
agentAccountId: "bot-beta",
|
||||
agentTo: callerRoom,
|
||||
},
|
||||
bindings: [
|
||||
{
|
||||
type: "route",
|
||||
agentId: "bot-alpha",
|
||||
match: { channel: "matrix", accountId: "bot-alpha-default" },
|
||||
},
|
||||
{
|
||||
type: "route",
|
||||
agentId: "bot-alpha",
|
||||
match: {
|
||||
channel: "matrix",
|
||||
peer: { kind: "channel", id: "*" },
|
||||
accountId: "bot-alpha-wildcard",
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
).toBe("bot-alpha-wildcard");
|
||||
});
|
||||
|
||||
it("sessions_spawn prefers exact peer binding over wildcard peer binding", async () => {
|
||||
const exactRoom = "!roomA:example.org";
|
||||
expect(
|
||||
await executeBoundAccountSpawn({
|
||||
callId: "call-exact-over-wildcard",
|
||||
agentId: "bot-alpha",
|
||||
context: {
|
||||
agentSessionKey: "main",
|
||||
agentChannel: "matrix",
|
||||
agentAccountId: "bot-beta",
|
||||
agentTo: exactRoom,
|
||||
},
|
||||
bindings: [
|
||||
{
|
||||
type: "route",
|
||||
agentId: "bot-alpha",
|
||||
match: {
|
||||
channel: "matrix",
|
||||
peer: { kind: "channel", id: "*" },
|
||||
accountId: "bot-alpha-wildcard",
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "route",
|
||||
agentId: "bot-alpha",
|
||||
match: {
|
||||
channel: "matrix",
|
||||
peer: { kind: "channel", id: exactRoom },
|
||||
accountId: "bot-alpha-room-a",
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
).toBe("bot-alpha-room-a");
|
||||
});
|
||||
|
||||
it("sessions_spawn uses requester roles for role-scoped target-agent accounts", async () => {
|
||||
expect(
|
||||
await executeBoundAccountSpawn({
|
||||
callId: "call-role-scoped-account",
|
||||
agentId: "bot-alpha",
|
||||
context: {
|
||||
agentSessionKey: "main",
|
||||
agentChannel: "discord",
|
||||
agentAccountId: "bot-beta",
|
||||
agentTo: "channel:ops",
|
||||
agentGroupSpace: "guild-current",
|
||||
agentMemberRoleIds: ["admin"],
|
||||
},
|
||||
bindings: [
|
||||
{
|
||||
type: "route",
|
||||
agentId: "bot-alpha",
|
||||
match: { channel: "discord", accountId: "bot-alpha-default" },
|
||||
},
|
||||
{
|
||||
type: "route",
|
||||
agentId: "bot-alpha",
|
||||
match: {
|
||||
channel: "discord",
|
||||
guildId: "guild-current",
|
||||
roles: ["admin"],
|
||||
peer: { kind: "channel", id: "channel:ops" },
|
||||
accountId: "bot-alpha-admin",
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
).toBe("bot-alpha-admin");
|
||||
});
|
||||
|
||||
it("sessions_spawn strips channel-side prefixes from agentTo before bound-account lookup", async () => {
|
||||
const rawRoomId = "!exampleRoomId:example.org";
|
||||
// agentTo arrives in delivery-target format (room:<id>), while the binding
|
||||
// stores the raw id. Without prefix normalization the exact peer match
|
||||
// would silently fail and the caller account would leak to the child.
|
||||
expect(
|
||||
await executeBoundAccountSpawn({
|
||||
callId: "call-prefixed-to",
|
||||
agentId: "bot-alpha",
|
||||
context: {
|
||||
agentSessionKey: "main",
|
||||
agentChannel: "matrix",
|
||||
agentAccountId: "bot-beta",
|
||||
agentTo: `room:${rawRoomId}`,
|
||||
},
|
||||
bindings: [
|
||||
{
|
||||
type: "route",
|
||||
agentId: "bot-alpha",
|
||||
match: {
|
||||
channel: "matrix",
|
||||
peer: { kind: "channel", id: rawRoomId },
|
||||
accountId: "bot-alpha",
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
).toBe("bot-alpha");
|
||||
});
|
||||
|
||||
it("sessions_spawn peels channel prefix then kind prefix for <channel>:<kind>:<id> targets", async () => {
|
||||
const rawGroupId = "U123example";
|
||||
// LINE emits its originatingTo as `line:group:<id>`. Without peeling the
|
||||
// channel prefix first and looping, a naive strip would leave `group:<id>`
|
||||
// (or `line:<id>`) and the exact peer-id binding would not match.
|
||||
expect(
|
||||
await executeBoundAccountSpawn({
|
||||
callId: "call-line-nested-prefix",
|
||||
agentId: "bot-alpha",
|
||||
context: {
|
||||
agentSessionKey: "main",
|
||||
agentChannel: "line",
|
||||
agentAccountId: "bot-beta",
|
||||
agentTo: `line:group:${rawGroupId}`,
|
||||
},
|
||||
bindings: [
|
||||
// Wildcard peer binding with a conflicting kind (direct) must be
|
||||
// skipped because the inferred kind is `group`.
|
||||
{
|
||||
type: "route",
|
||||
agentId: "bot-alpha",
|
||||
match: {
|
||||
channel: "line",
|
||||
peer: { kind: "direct", id: "*" },
|
||||
accountId: "bot-alpha-line-dm",
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "route",
|
||||
agentId: "bot-alpha",
|
||||
match: {
|
||||
channel: "line",
|
||||
peer: { kind: "group", id: rawGroupId },
|
||||
accountId: "bot-alpha-line",
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
).toBe("bot-alpha-line");
|
||||
});
|
||||
|
||||
it("sessions_spawn classifies Matrix room:@user targets as direct, not channel", async () => {
|
||||
const rawUserId = "@other-user:example.org";
|
||||
// Matrix thread delivery encodes per-user DM targets as `room:@user:server`.
|
||||
// The `room:` prefix must not override the embedded `@` direct-peer marker.
|
||||
expect(
|
||||
await executeBoundAccountSpawn({
|
||||
callId: "call-room-at-user",
|
||||
agentId: "bot-alpha",
|
||||
context: {
|
||||
agentSessionKey: "main",
|
||||
agentChannel: "matrix",
|
||||
agentAccountId: "bot-beta",
|
||||
agentTo: `room:${rawUserId}`,
|
||||
},
|
||||
bindings: [
|
||||
// A conflicting channel-kinded binding on the same peer id must not
|
||||
// match because the embedded `@` marker identifies a direct peer.
|
||||
{
|
||||
type: "route",
|
||||
agentId: "bot-alpha",
|
||||
match: {
|
||||
channel: "matrix",
|
||||
peer: { kind: "channel", id: rawUserId },
|
||||
accountId: "bot-alpha-wrong-kind",
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "route",
|
||||
agentId: "bot-alpha",
|
||||
match: {
|
||||
channel: "matrix",
|
||||
peer: { kind: "direct", id: rawUserId },
|
||||
accountId: "bot-alpha-dm",
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
).toBe("bot-alpha-dm");
|
||||
});
|
||||
|
||||
it("sessions_spawn strips only the Teams conversation: wrapper", async () => {
|
||||
const rawConversationId = "a:1:example-conversation@thread.v2";
|
||||
// Teams inbound context sets OriginatingTo to `conversation:<id>`. The
|
||||
// Teams id itself may start with another token-colon segment, so extraction
|
||||
// must stop after the known wrapper instead of peeling arbitrary prefixes.
|
||||
expect(
|
||||
await executeBoundAccountSpawn({
|
||||
callId: "call-teams-conversation",
|
||||
agentId: "bot-alpha",
|
||||
context: {
|
||||
agentSessionKey: "main",
|
||||
agentChannel: "msteams",
|
||||
agentAccountId: "bot-beta",
|
||||
agentTo: `conversation:${rawConversationId}`,
|
||||
},
|
||||
bindings: [
|
||||
{
|
||||
type: "route",
|
||||
agentId: "bot-alpha",
|
||||
match: {
|
||||
channel: "msteams",
|
||||
peer: { kind: "channel", id: rawConversationId },
|
||||
accountId: "bot-alpha-teams",
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
).toBe("bot-alpha-teams");
|
||||
});
|
||||
|
||||
it("sessions_spawn preserves the caller's account for same-agent subagent spawns", async () => {
|
||||
const room = "!someRoom:example.org";
|
||||
// Spawn a child of the same agent (no explicit agentId), so the caller's
|
||||
// active account must win over any configured binding for that same agent.
|
||||
expect(
|
||||
await executeBoundAccountSpawn({
|
||||
callId: "call-same-agent",
|
||||
context: {
|
||||
agentSessionKey: "agent:bot-alpha:session:main",
|
||||
agentChannel: "matrix",
|
||||
agentAccountId: "bot-alpha-adhoc",
|
||||
agentTo: room,
|
||||
},
|
||||
bindings: [
|
||||
{
|
||||
type: "route",
|
||||
agentId: "bot-alpha",
|
||||
match: { channel: "matrix", accountId: "bot-alpha-default" },
|
||||
},
|
||||
],
|
||||
}),
|
||||
).toBe("bot-alpha-adhoc");
|
||||
});
|
||||
|
||||
it("sessions_spawn announces with requester accountId", async () => {
|
||||
const ctx = setupSessionsSpawnGatewayMock({});
|
||||
|
||||
|
||||
@@ -29,9 +29,12 @@ async function createWorkspaceBundle(params: {
|
||||
}
|
||||
|
||||
describe("loadEnabledBundlePiSettingsSnapshot", () => {
|
||||
it("loads sanitized settings from enabled bundle plugins", async () => {
|
||||
it("loads sanitized settings and MCP defaults from enabled bundle plugins", async () => {
|
||||
const workspaceDir = await tempDirs.make("openclaw-workspace-");
|
||||
const pluginRoot = await createWorkspaceBundle({ workspaceDir });
|
||||
const resolvedPluginRoot = await fs.realpath(pluginRoot);
|
||||
await fs.mkdir(path.join(pluginRoot, "servers"), { recursive: true });
|
||||
const resolvedServerPath = await fs.realpath(path.join(pluginRoot, "servers"));
|
||||
await fs.writeFile(
|
||||
path.join(pluginRoot, "settings.json"),
|
||||
JSON.stringify({
|
||||
@@ -41,6 +44,22 @@ describe("loadEnabledBundlePiSettingsSnapshot", () => {
|
||||
}),
|
||||
"utf-8",
|
||||
);
|
||||
await fs.writeFile(
|
||||
path.join(pluginRoot, ".mcp.json"),
|
||||
JSON.stringify({
|
||||
mcpServers: {
|
||||
bundleProbe: {
|
||||
command: "node",
|
||||
args: ["./servers/probe.mjs"],
|
||||
},
|
||||
sharedServer: {
|
||||
command: "node",
|
||||
args: ["./servers/bundle.mjs"],
|
||||
},
|
||||
},
|
||||
}),
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
const snapshot = loadEnabledBundlePiSettingsSnapshot({
|
||||
cwd: workspaceDir,
|
||||
@@ -56,64 +75,20 @@ describe("loadEnabledBundlePiSettingsSnapshot", () => {
|
||||
expect(snapshot.hideThinkingBlock).toBe(true);
|
||||
expect(snapshot.shellPath).toBeUndefined();
|
||||
expect(snapshot.compaction?.keepRecentTokens).toBe(64_000);
|
||||
});
|
||||
|
||||
it("loads enabled bundle MCP servers into the Pi settings snapshot", async () => {
|
||||
const workspaceDir = await tempDirs.make("openclaw-workspace-");
|
||||
const pluginRoot = await createWorkspaceBundle({ workspaceDir });
|
||||
const resolvedPluginRoot = await fs.realpath(pluginRoot);
|
||||
await fs.mkdir(path.join(pluginRoot, "servers"), { recursive: true });
|
||||
const resolvedServerPath = await fs.realpath(path.join(pluginRoot, "servers"));
|
||||
await fs.writeFile(
|
||||
path.join(pluginRoot, ".mcp.json"),
|
||||
JSON.stringify({
|
||||
mcpServers: {
|
||||
bundleProbe: {
|
||||
command: "node",
|
||||
args: ["./servers/probe.mjs"],
|
||||
},
|
||||
},
|
||||
}),
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
const snapshot = loadEnabledBundlePiSettingsSnapshot({
|
||||
cwd: workspaceDir,
|
||||
cfg: {
|
||||
plugins: {
|
||||
entries: {
|
||||
"claude-bundle": { enabled: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect((snapshot as Record<string, unknown>).mcpServers).toEqual({
|
||||
bundleProbe: {
|
||||
command: "node",
|
||||
args: [path.join(resolvedServerPath, "probe.mjs")],
|
||||
cwd: resolvedPluginRoot,
|
||||
},
|
||||
sharedServer: {
|
||||
command: "node",
|
||||
args: [path.join(resolvedServerPath, "bundle.mjs")],
|
||||
cwd: resolvedPluginRoot,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("lets top-level MCP config override bundle MCP defaults", async () => {
|
||||
const workspaceDir = await tempDirs.make("openclaw-workspace-");
|
||||
const pluginRoot = await createWorkspaceBundle({ workspaceDir });
|
||||
await fs.writeFile(
|
||||
path.join(pluginRoot, ".mcp.json"),
|
||||
JSON.stringify({
|
||||
mcpServers: {
|
||||
sharedServer: {
|
||||
command: "node",
|
||||
args: ["./servers/bundle.mjs"],
|
||||
},
|
||||
},
|
||||
}),
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
const snapshot = loadEnabledBundlePiSettingsSnapshot({
|
||||
const overridden = loadEnabledBundlePiSettingsSnapshot({
|
||||
cwd: workspaceDir,
|
||||
cfg: {
|
||||
mcp: {
|
||||
@@ -131,7 +106,12 @@ describe("loadEnabledBundlePiSettingsSnapshot", () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect((snapshot as Record<string, unknown>).mcpServers).toEqual({
|
||||
expect((overridden as Record<string, unknown>).mcpServers).toEqual({
|
||||
bundleProbe: {
|
||||
command: "node",
|
||||
args: [path.join(resolvedServerPath, "probe.mjs")],
|
||||
cwd: resolvedPluginRoot,
|
||||
},
|
||||
sharedServer: {
|
||||
url: "https://example.com/mcp",
|
||||
},
|
||||
|
||||
@@ -3,6 +3,24 @@ import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { resolveRequesterOriginForChild } from "./spawn-requester-origin.js";
|
||||
|
||||
describe("resolveRequesterOriginForChild", () => {
|
||||
function resolveAccount(params: {
|
||||
cfg: OpenClawConfig;
|
||||
targetAgentId?: string;
|
||||
requesterAgentId?: string;
|
||||
requesterChannel: string;
|
||||
requesterAccountId?: string;
|
||||
requesterTo: string;
|
||||
requesterGroupSpace?: string | null;
|
||||
requesterMemberRoleIds?: string[];
|
||||
}) {
|
||||
return resolveRequesterOriginForChild({
|
||||
requesterAccountId: "bot-beta",
|
||||
...params,
|
||||
targetAgentId: params.targetAgentId ?? "bot-alpha",
|
||||
requesterAgentId: params.requesterAgentId ?? "main",
|
||||
})?.accountId;
|
||||
}
|
||||
|
||||
it.each([
|
||||
["channel:conversation-a", "channel:conversation-a", "channel"],
|
||||
["dm:conversation-a", "dm:conversation-a", "direct"],
|
||||
@@ -44,6 +62,199 @@ describe("resolveRequesterOriginForChild", () => {
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
{
|
||||
name: "prefers peer-specific binding over channel-only binding",
|
||||
requesterChannel: "matrix",
|
||||
requesterTo: "!roomA:example.org",
|
||||
expected: "bot-alpha-room-a",
|
||||
bindings: [
|
||||
{
|
||||
type: "route",
|
||||
agentId: "bot-alpha",
|
||||
match: { channel: "matrix", accountId: "bot-alpha-default" },
|
||||
},
|
||||
{
|
||||
type: "route",
|
||||
agentId: "bot-alpha",
|
||||
match: {
|
||||
channel: "matrix",
|
||||
peer: { kind: "channel", id: "!roomA:example.org" },
|
||||
accountId: "bot-alpha-room-a",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "falls back to channel-only binding when peer does not match",
|
||||
requesterChannel: "matrix",
|
||||
requesterTo: "!roomB:example.org",
|
||||
expected: "bot-alpha-default",
|
||||
bindings: [
|
||||
{
|
||||
type: "route",
|
||||
agentId: "bot-alpha",
|
||||
match: { channel: "matrix", accountId: "bot-alpha-default" },
|
||||
},
|
||||
{
|
||||
type: "route",
|
||||
agentId: "bot-alpha",
|
||||
match: {
|
||||
channel: "matrix",
|
||||
peer: { kind: "channel", id: "!roomA:example.org" },
|
||||
accountId: "bot-alpha-room-a",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "treats wildcard peer binding as match-any and beats channel-only",
|
||||
requesterChannel: "matrix",
|
||||
requesterTo: "!anyRoom:example.org",
|
||||
expected: "bot-alpha-wildcard",
|
||||
bindings: [
|
||||
{
|
||||
type: "route",
|
||||
agentId: "bot-alpha",
|
||||
match: { channel: "matrix", accountId: "bot-alpha-default" },
|
||||
},
|
||||
{
|
||||
type: "route",
|
||||
agentId: "bot-alpha",
|
||||
match: {
|
||||
channel: "matrix",
|
||||
peer: { kind: "channel", id: "*" },
|
||||
accountId: "bot-alpha-wildcard",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "prefers exact peer binding over wildcard peer binding",
|
||||
requesterChannel: "matrix",
|
||||
requesterTo: "!roomA:example.org",
|
||||
expected: "bot-alpha-room-a",
|
||||
bindings: [
|
||||
{
|
||||
type: "route",
|
||||
agentId: "bot-alpha",
|
||||
match: {
|
||||
channel: "matrix",
|
||||
peer: { kind: "channel", id: "*" },
|
||||
accountId: "bot-alpha-wildcard",
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "route",
|
||||
agentId: "bot-alpha",
|
||||
match: {
|
||||
channel: "matrix",
|
||||
peer: { kind: "channel", id: "!roomA:example.org" },
|
||||
accountId: "bot-alpha-room-a",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "uses requester roles for role-scoped target-agent accounts",
|
||||
requesterChannel: "discord",
|
||||
requesterTo: "channel:ops",
|
||||
requesterGroupSpace: "guild-current",
|
||||
requesterMemberRoleIds: ["admin"],
|
||||
expected: "bot-alpha-admin",
|
||||
bindings: [
|
||||
{
|
||||
type: "route",
|
||||
agentId: "bot-alpha",
|
||||
match: { channel: "discord", accountId: "bot-alpha-default" },
|
||||
},
|
||||
{
|
||||
type: "route",
|
||||
agentId: "bot-alpha",
|
||||
match: {
|
||||
channel: "discord",
|
||||
guildId: "guild-current",
|
||||
roles: ["admin"],
|
||||
peer: { kind: "channel", id: "channel:ops" },
|
||||
accountId: "bot-alpha-admin",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "strips channel-side prefixes before bound-account lookup",
|
||||
requesterChannel: "matrix",
|
||||
requesterTo: "room:!exampleRoomId:example.org",
|
||||
expected: "bot-alpha",
|
||||
bindings: [
|
||||
{
|
||||
type: "route",
|
||||
agentId: "bot-alpha",
|
||||
match: {
|
||||
channel: "matrix",
|
||||
peer: { kind: "channel", id: "!exampleRoomId:example.org" },
|
||||
accountId: "bot-alpha",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "classifies Matrix room:@user targets as direct, not channel",
|
||||
requesterChannel: "matrix",
|
||||
requesterTo: "room:@other-user:example.org",
|
||||
expected: "bot-alpha-dm",
|
||||
bindings: [
|
||||
{
|
||||
type: "route",
|
||||
agentId: "bot-alpha",
|
||||
match: {
|
||||
channel: "matrix",
|
||||
peer: { kind: "channel", id: "@other-user:example.org" },
|
||||
accountId: "bot-alpha-wrong-kind",
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "route",
|
||||
agentId: "bot-alpha",
|
||||
match: {
|
||||
channel: "matrix",
|
||||
peer: { kind: "direct", id: "@other-user:example.org" },
|
||||
accountId: "bot-alpha-dm",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "preserves the caller account for same-agent subagent spawns",
|
||||
requesterChannel: "matrix",
|
||||
requesterAccountId: "bot-alpha-adhoc",
|
||||
requesterAgentId: "bot-alpha",
|
||||
requesterTo: "!someRoom:example.org",
|
||||
expected: "bot-alpha-adhoc",
|
||||
bindings: [
|
||||
{
|
||||
type: "route",
|
||||
agentId: "bot-alpha",
|
||||
match: { channel: "matrix", accountId: "bot-alpha-default" },
|
||||
},
|
||||
],
|
||||
},
|
||||
] as const)("selects target account: $name", (scenario) => {
|
||||
expect(
|
||||
resolveAccount({
|
||||
cfg: { bindings: [...scenario.bindings] } as OpenClawConfig,
|
||||
requesterChannel: scenario.requesterChannel,
|
||||
requesterAccountId: scenario.requesterAccountId,
|
||||
requesterAgentId: scenario.requesterAgentId,
|
||||
requesterTo: scenario.requesterTo,
|
||||
requesterGroupSpace: scenario.requesterGroupSpace,
|
||||
requesterMemberRoleIds: scenario.requesterMemberRoleIds
|
||||
? [...scenario.requesterMemberRoleIds]
|
||||
: undefined,
|
||||
}),
|
||||
).toBe(scenario.expected);
|
||||
});
|
||||
|
||||
it("preserves canonical peer ids that start with token-colon after a known wrapper", () => {
|
||||
const to = "conversation:a:1:team-thread";
|
||||
const cfg = {
|
||||
|
||||
Reference in New Issue
Block a user