agents: preserve canonical spawn peer ids

This commit is contained in:
Gustavo Madeira Santana
2026-04-17 13:04:05 -04:00
parent 61deb90e24
commit 6b1a3b02c0
4 changed files with 83 additions and 1 deletions

View File

@@ -0,0 +1,39 @@
import { describe, expect, it } from "vitest";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { resolveRequesterOriginForChild } from "./spawn-requester-origin.js";
describe("resolveRequesterOriginForChild", () => {
it("keeps canonical prefixed peer ids eligible for exact binding lookup", () => {
const cfg = {
bindings: [
{
type: "route",
agentId: "bot-alpha",
match: {
channel: "qa-channel",
peer: {
kind: "channel",
id: "channel:conversation-a",
},
accountId: "bot-alpha-qa",
},
},
],
} as OpenClawConfig;
expect(
resolveRequesterOriginForChild({
cfg,
targetAgentId: "bot-alpha",
requesterAgentId: "main",
requesterChannel: "qa-channel",
requesterAccountId: "bot-beta",
requesterTo: "channel:conversation-a",
}),
).toMatchObject({
channel: "qa-channel",
accountId: "bot-alpha-qa",
to: "channel:conversation-a",
});
});
});

View File

@@ -81,6 +81,7 @@ export function resolveRequesterOriginForChild(params: {
params.requesterChannel,
params.requesterTo,
);
const rawPeerIdAlias = params.requesterTo?.trim();
// Same-agent spawns must keep the caller's active inbound account, not
// re-resolve via bindings that may select a different account for the same
// agent/channel.
@@ -91,6 +92,8 @@ export function resolveRequesterOriginForChild(params: {
channelId: params.requesterChannel,
agentId: params.targetAgentId,
peerId: normalizedPeerId,
peerIdAliases:
rawPeerIdAlias && rawPeerIdAlias !== normalizedPeerId ? [rawPeerIdAlias] : undefined,
peerKind: inferredPeerKind,
})
: undefined;

View File

@@ -313,6 +313,39 @@ describe("resolveFirstBoundAccountId", () => {
).toBe("bot-alpha-room");
});
it("matches exact canonical peer aliases before falling back to wildcard bindings", () => {
const cfg = cfgWithBindings([
{
type: "route",
agentId: "bot-alpha",
match: {
channel: "qa-channel",
peer: { kind: "channel", id: "*" },
accountId: "bot-alpha-wildcard",
},
},
{
type: "route",
agentId: "bot-alpha",
match: {
channel: "qa-channel",
peer: { kind: "channel", id: "channel:conversation-a" },
accountId: "bot-alpha-conversation",
},
},
]);
expect(
resolveFirstBoundAccountId({
cfg,
channelId: "qa-channel",
agentId: "bot-alpha",
peerId: "conversation-a",
peerIdAliases: ["channel:conversation-a"],
peerKind: "channel",
}),
).toBe("bot-alpha-conversation");
});
it("skips peer-specific bindings whose kind does not match the caller's peerKind", () => {
const cfg = cfgWithBindings([
{

View File

@@ -65,6 +65,7 @@ export function resolveFirstBoundAccountId(params: {
channelId: string;
agentId: string;
peerId?: string;
peerIdAliases?: string[];
peerKind?: ChatType;
}): string | undefined {
const normalizedChannel = normalizeBindingChannelId(params.channelId);
@@ -73,6 +74,12 @@ export function resolveFirstBoundAccountId(params: {
}
const normalizedAgentId = normalizeAgentId(params.agentId);
const normalizedPeerId = params.peerId?.trim() || undefined;
const exactPeerIds = new Set(
[
normalizedPeerId,
...(params.peerIdAliases ?? []).map((value) => value.trim()).filter(Boolean),
].filter((value): value is string => Boolean(value)),
);
const normalizedPeerKind = normalizeChatType(params.peerKind) ?? undefined;
let wildcardPeerMatch: string | undefined;
let channelOnlyFallback: string | undefined;
@@ -121,7 +128,7 @@ export function resolveFirstBoundAccountId(params: {
) {
continue;
}
if (normalizedPeerId && resolved.peerId === normalizedPeerId) {
if (exactPeerIds.has(resolved.peerId)) {
return resolved.accountId;
}
if (!normalizedPeerId) {