mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 05:16:23 -06:00
fix(message): authorize fallback broadcast provider
This commit is contained in:
@@ -404,6 +404,7 @@ function createChannelPlugin(params: {
|
||||
config?: Partial<ChannelPlugin["config"]>;
|
||||
message?: ChannelMessageAdapterShape;
|
||||
messaging?: ChannelPlugin["messaging"];
|
||||
outbound?: ChannelPlugin["outbound"];
|
||||
}): ChannelPlugin {
|
||||
return {
|
||||
id: params.id as ChannelPlugin["id"],
|
||||
@@ -423,6 +424,7 @@ function createChannelPlugin(params: {
|
||||
},
|
||||
...(params.message ? { message: params.message } : {}),
|
||||
...(params.messaging ? { messaging: params.messaging } : {}),
|
||||
...(params.outbound ? { outbound: params.outbound } : {}),
|
||||
actions: {
|
||||
describeMessageTool:
|
||||
params.describeMessageTool ??
|
||||
@@ -1771,6 +1773,44 @@ describe("message tool secret scoping", () => {
|
||||
broadcastChannel: "slack",
|
||||
broadcastTargets: ["slack:channel:one", "slack:channel:two"],
|
||||
},
|
||||
{
|
||||
name: "delegated fallback-resolved current-provider broadcast",
|
||||
channel: "googlechat",
|
||||
accountId: "alternate",
|
||||
requesterAccountId: "current",
|
||||
trusted: true,
|
||||
origin: undefined,
|
||||
rejected: true,
|
||||
broadcast: true,
|
||||
broadcastChannel: "last",
|
||||
broadcastTargets: ["googlechat:spaces/current"],
|
||||
},
|
||||
{
|
||||
name: "delegated fallback-resolved broadcast with matching current account",
|
||||
channel: "googlechat",
|
||||
accountId: "current",
|
||||
requesterAccountId: "current",
|
||||
trusted: true,
|
||||
origin: undefined,
|
||||
rejected: false,
|
||||
broadcast: true,
|
||||
broadcastChannel: "last",
|
||||
broadcastTargets: ["googlechat:spaces/current"],
|
||||
expectedRunnerChannel: "googlechat",
|
||||
},
|
||||
{
|
||||
name: "direct fallback-resolved broadcast with alternate account",
|
||||
channel: "googlechat",
|
||||
accountId: "alternate",
|
||||
requesterAccountId: undefined,
|
||||
trusted: false,
|
||||
origin: "direct-operator" as const,
|
||||
rejected: false,
|
||||
broadcast: true,
|
||||
broadcastChannel: "last",
|
||||
broadcastTargets: ["googlechat:spaces/current"],
|
||||
expectedRunnerChannel: "googlechat",
|
||||
},
|
||||
{
|
||||
name: "delegated channel-less broadcast with matching current account",
|
||||
channel: "googlechat",
|
||||
@@ -1806,6 +1846,7 @@ describe("message tool secret scoping", () => {
|
||||
listAccountIds: () => ["current", "alternate"],
|
||||
resolveAccount: () => ({ enabled: true }),
|
||||
},
|
||||
outbound: { deliveryMode: "direct", sendText: vi.fn() as never },
|
||||
});
|
||||
const slackPlugin = createChannelPlugin({
|
||||
id: "slack",
|
||||
@@ -1817,6 +1858,7 @@ describe("message tool secret scoping", () => {
|
||||
listAccountIds: () => ["alternate"],
|
||||
resolveAccount: () => ({ enabled: true }),
|
||||
},
|
||||
outbound: { deliveryMode: "direct", sendText: vi.fn() as never },
|
||||
});
|
||||
setActivePluginRegistry(
|
||||
createTestRegistry([
|
||||
@@ -1905,6 +1947,9 @@ describe("message tool secret scoping", () => {
|
||||
await expect(invocation).resolves.toBeDefined();
|
||||
expect(mocks.resolveCommandSecretRefsViaGateway).toHaveBeenCalledOnce();
|
||||
expect(mocks.runMessageAction).toHaveBeenCalledOnce();
|
||||
if ("expectedRunnerChannel" in testCase) {
|
||||
expect(firstRunMessageActionInput()?.params?.channel).toBe(testCase.expectedRunnerChannel);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ import {
|
||||
import { resolveMessageActionTurnCapability } from "../../gateway/message-action-turn-capability.js";
|
||||
import { createAbortError } from "../../infra/abort-signal.js";
|
||||
import { sha256Base64UrlPrefix } from "../../infra/crypto-digest.js";
|
||||
import { resolveMessageChannelSelection } from "../../infra/outbound/channel-selection.js";
|
||||
import {
|
||||
resolveMessageBroadcastAccountPlan,
|
||||
validateExplicitMessageAccountSelection,
|
||||
@@ -1589,12 +1590,26 @@ export function createMessageTool(options?: MessageToolOptions): AnyAgentTool {
|
||||
const gatewayOpts = readGatewayCallOptions(params);
|
||||
const rawConfig = options?.config ?? loadConfigForTool();
|
||||
const requestedAccountId = readStringParam(params, "accountId");
|
||||
const requestedScope = resolveMessageSecretScope({
|
||||
channel: params.channel,
|
||||
target: params.target,
|
||||
targets: params.targets,
|
||||
validateExplicitMessageAccountSelection({
|
||||
cfg: rawConfig,
|
||||
accountId: requestedAccountId,
|
||||
checkResolvedAccount: false,
|
||||
});
|
||||
const requestedBroadcastChannel = normalizeOptionalLowercaseString(params.channel);
|
||||
if (
|
||||
action === "broadcast" &&
|
||||
requestedBroadcastChannel &&
|
||||
requestedBroadcastChannel !== "all"
|
||||
) {
|
||||
// Authorize and execute the same canonical provider. Otherwise an unavailable
|
||||
// hint can fall back to the current provider only after account authorization.
|
||||
const selection = await resolveMessageChannelSelection({
|
||||
cfg: rawConfig,
|
||||
channel: requestedBroadcastChannel,
|
||||
fallbackChannel: effectiveCurrentChannel.currentChannelProvider,
|
||||
});
|
||||
params.channel = selection.channel;
|
||||
}
|
||||
const scope = resolveMessageSecretScope({
|
||||
channel: params.channel,
|
||||
target: params.target,
|
||||
@@ -1603,7 +1618,6 @@ export function createMessageTool(options?: MessageToolOptions): AnyAgentTool {
|
||||
accountId: requestedAccountId,
|
||||
fallbackAccountId: agentAccountId,
|
||||
});
|
||||
const requestedBroadcastChannel = normalizeOptionalLowercaseString(params.channel);
|
||||
// Broadcast execution only narrows on an explicit non-all channel. Target
|
||||
// prefixes cannot authorize fewer providers than the runner will execute.
|
||||
const unscopedExplicitBroadcast =
|
||||
@@ -1627,12 +1641,7 @@ export function createMessageTool(options?: MessageToolOptions): AnyAgentTool {
|
||||
explicitAccountId,
|
||||
selectedChannels: broadcastAccountPlan
|
||||
? broadcastAccountPlan.candidateChannels
|
||||
: [
|
||||
requestedScope.channel ??
|
||||
(action === "broadcast"
|
||||
? undefined
|
||||
: trustedTurnContext?.toolContext?.currentChannelProvider),
|
||||
],
|
||||
: [scope.channel],
|
||||
trustedCurrentChannel: trustedTurnContext?.toolContext?.currentChannelProvider,
|
||||
trustedRequesterAccountId: trustedTurnContext?.requesterAccountId,
|
||||
hasTrustedTurnContext: trustedTurnContext !== undefined,
|
||||
|
||||
Reference in New Issue
Block a user