diff --git a/src/agents/auth-profiles/oauth.adopt-identity.test.ts b/src/agents/auth-profiles/oauth.adopt-identity.test.ts index 3d89ec58db3..347bf8bdde2 100644 --- a/src/agents/auth-profiles/oauth.adopt-identity.test.ts +++ b/src/agents/auth-profiles/oauth.adopt-identity.test.ts @@ -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 }); } diff --git a/src/agents/auth-profiles/oauth.mirror-refresh.test.ts b/src/agents/auth-profiles/oauth.mirror-refresh.test.ts index a8d7e67d0cc..b89ad72582c 100644 --- a/src/agents/auth-profiles/oauth.mirror-refresh.test.ts +++ b/src/agents/auth-profiles/oauth.mirror-refresh.test.ts @@ -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 }); } diff --git a/src/agents/openclaw-tools.subagents.sessions-spawn.lifecycle.test.ts b/src/agents/openclaw-tools.subagents.sessions-spawn.lifecycle.test.ts index ea4aba0c4b8..ebb83de0a77 100644 --- a/src/agents/openclaw-tools.subagents.sessions-spawn.lifecycle.test.ts +++ b/src/agents/openclaw-tools.subagents.sessions-spawn.lifecycle.test.ts @@ -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:), 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 :: targets", async () => { - const rawGroupId = "U123example"; - // LINE emits its originatingTo as `line:group:`. Without peeling the - // channel prefix first and looping, a naive strip would leave `group:` - // (or `line:`) 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:`. 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({}); diff --git a/src/agents/pi-project-settings.bundle.test.ts b/src/agents/pi-project-settings.bundle.test.ts index d55eb34989a..a1993bf395f 100644 --- a/src/agents/pi-project-settings.bundle.test.ts +++ b/src/agents/pi-project-settings.bundle.test.ts @@ -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).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).mcpServers).toEqual({ + expect((overridden as Record).mcpServers).toEqual({ + bundleProbe: { + command: "node", + args: [path.join(resolvedServerPath, "probe.mjs")], + cwd: resolvedPluginRoot, + }, sharedServer: { url: "https://example.com/mcp", }, diff --git a/src/agents/spawn-requester-origin.test.ts b/src/agents/spawn-requester-origin.test.ts index b043d4f9a1a..38a7f73dda0 100644 --- a/src/agents/spawn-requester-origin.test.ts +++ b/src/agents/spawn-requester-origin.test.ts @@ -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 = {