mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:50:43 +00:00
fix(slack): route message actions by target account
This commit is contained in:
@@ -65,6 +65,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Slack/delivery: preserve Slack Web API missing-scope details in outbound delivery errors, so queued retry state identifies the OAuth scope to add. Fixes #62391. Thanks @alexey-pelykh.
|
||||
- Slack/DMs: send text/block-only proactive DMs directly with `chat.postMessage(channel=<user id>)` while keeping conversation resolution for uploads and threaded sends. Fixes #62042. Thanks @MarkMolina.
|
||||
- Slack/routing: match route bindings written with Slack target syntax such as `channel:C...`, `user:U...`, or `<@U...>`, so bound Slack peers route to the configured agent instead of `main`. Fixes #41608. Thanks @Winnsolutionsadmin.
|
||||
- Slack/message actions: prefer the account bound to the outbound target peer before falling back to the agent's first channel account, so multi-workspace sends use the intended Slack account. Supersedes #66807. Thanks @rijhsinghani.
|
||||
- Slack/delivery: retry Slack Web API writes only when the SDK wraps a DNS request failure such as `EAI_AGAIN`, so transient resolver hiccups can recover without retrying platform errors that may duplicate messages. Fixes #68789. Thanks @sonnyb9.
|
||||
- Slack/message actions: forward agent-scoped media roots through the bundled upload-file action path, so workspace files can be attached without failing the local-media guard. Fixes #64625. Thanks @benpchandler.
|
||||
- Slack/mentions: resolve `<!subteam^...>` user-group mentions through Slack `usergroups.users.list` and treat them as explicit mentions only when the bot user is a member, so mention-gated agent channels wake for real user-group mentions without config-only allowlists. Fixes #73827. Thanks @CG-Intelligence-Agent-Jack.
|
||||
|
||||
@@ -1320,13 +1320,45 @@ describe("runMessageAction plugin dispatch", () => {
|
||||
},
|
||||
expectedAccountId: "account-b",
|
||||
},
|
||||
{
|
||||
name: "prefers the account bound to the target peer",
|
||||
args: {
|
||||
cfg: {
|
||||
bindings: [
|
||||
{
|
||||
agentId: "agent-b",
|
||||
match: {
|
||||
channel: "accountchat",
|
||||
accountId: "wrong-peer",
|
||||
peer: { kind: "channel", id: "C_OTHER" },
|
||||
},
|
||||
},
|
||||
{
|
||||
agentId: "agent-b",
|
||||
match: {
|
||||
channel: "accountchat",
|
||||
accountId: "account-peer",
|
||||
peer: { kind: "channel", id: "C_TARGET" },
|
||||
},
|
||||
},
|
||||
{
|
||||
agentId: "agent-b",
|
||||
match: { channel: "accountchat", accountId: "agent-fallback" },
|
||||
},
|
||||
],
|
||||
} as OpenClawConfig,
|
||||
agentId: "agent-b",
|
||||
target: "channel:C_TARGET",
|
||||
},
|
||||
expectedAccountId: "account-peer",
|
||||
},
|
||||
])("$name", async ({ args, expectedAccountId }) => {
|
||||
await runMessageAction({
|
||||
...args,
|
||||
action: "send",
|
||||
params: {
|
||||
channel: "accountchat",
|
||||
target: "channel:123",
|
||||
target: "target" in args ? args.target : "channel:123",
|
||||
message: "hi",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
readStringParam,
|
||||
} from "../../agents/tools/common.js";
|
||||
import { parseReplyDirectives } from "../../auto-reply/reply/reply-directives.js";
|
||||
import { normalizeChatType, type ChatType } from "../../channels/chat-type.js";
|
||||
import { getChannelPlugin } from "../../channels/plugins/index.js";
|
||||
import { dispatchChannelMessageAction } from "../../channels/plugins/message-action-dispatch.js";
|
||||
import type {
|
||||
@@ -25,8 +26,7 @@ import { getAgentScopedMediaLocalRoots } from "../../media/local-roots.js";
|
||||
import { resolveAgentScopedOutboundMediaAccess } from "../../media/read-capability.js";
|
||||
import { hasPollCreationParams } from "../../poll-params.js";
|
||||
import { resolvePollMaxSelections } from "../../polls.js";
|
||||
import { buildChannelAccountBindings } from "../../routing/bindings.js";
|
||||
import { normalizeAgentId } from "../../routing/session-key.js";
|
||||
import { resolveFirstBoundAccountId } from "../../routing/bound-account-read.js";
|
||||
import {
|
||||
normalizeOptionalLowercaseString,
|
||||
normalizeOptionalString,
|
||||
@@ -72,6 +72,7 @@ import {
|
||||
} from "./outbound-policy.js";
|
||||
import { executePollAction, executeSendAction } from "./outbound-send-service.js";
|
||||
import { ensureOutboundSessionEntry, resolveOutboundSessionRoute } from "./outbound-session.js";
|
||||
import { normalizeTargetForProvider } from "./target-normalization.js";
|
||||
import { resolveChannelTarget, type ResolvedMessagingTarget } from "./target-resolver.js";
|
||||
import { extractToolPayload } from "./tool-payload.js";
|
||||
|
||||
@@ -286,6 +287,80 @@ async function resolveChannel(
|
||||
return selection.channel;
|
||||
}
|
||||
|
||||
function addCandidateAndUnprefixedAlias(candidates: Set<string>, value?: string | null) {
|
||||
const normalized = normalizeOptionalString(value);
|
||||
if (!normalized) {
|
||||
return;
|
||||
}
|
||||
candidates.add(normalized);
|
||||
const unprefixed = normalized.replace(/^(channel|group|user):/i, "").trim();
|
||||
if (unprefixed && unprefixed !== normalized) {
|
||||
candidates.add(unprefixed);
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeTargetForAccountBinding(channel: ChannelId, target: string): string | undefined {
|
||||
try {
|
||||
return normalizeTargetForProvider(channel, target);
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function inferPeerKindForAccountBinding(channel: ChannelId, target: string): ChatType | undefined {
|
||||
const inferred = normalizeChatType(
|
||||
getChannelPlugin(channel)?.messaging?.inferTargetChatType?.({ to: target }),
|
||||
);
|
||||
if (inferred) {
|
||||
return inferred;
|
||||
}
|
||||
const normalized = normalizeTargetForAccountBinding(channel, target);
|
||||
const candidates = [target, normalized].filter((value): value is string => Boolean(value));
|
||||
if (candidates.some((value) => /^user:/i.test(value))) {
|
||||
return "direct";
|
||||
}
|
||||
if (candidates.some((value) => /^(channel|group):/i.test(value))) {
|
||||
return "channel";
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function resolveTargetBoundAccountId(params: {
|
||||
cfg: OpenClawConfig;
|
||||
channel: ChannelId;
|
||||
args: Record<string, unknown>;
|
||||
agentId?: string;
|
||||
}): string | undefined {
|
||||
if (!params.agentId) {
|
||||
return undefined;
|
||||
}
|
||||
const target =
|
||||
normalizeOptionalString(params.args.to) ?? normalizeOptionalString(params.args.channelId) ?? "";
|
||||
if (!target) {
|
||||
return resolveFirstBoundAccountId({
|
||||
cfg: params.cfg,
|
||||
channelId: params.channel,
|
||||
agentId: params.agentId,
|
||||
});
|
||||
}
|
||||
|
||||
const candidates = new Set<string>();
|
||||
addCandidateAndUnprefixedAlias(candidates, target);
|
||||
addCandidateAndUnprefixedAlias(
|
||||
candidates,
|
||||
normalizeTargetForAccountBinding(params.channel, target),
|
||||
);
|
||||
const [peerId, ...exactPeerIdAliases] = Array.from(candidates);
|
||||
return resolveFirstBoundAccountId({
|
||||
cfg: params.cfg,
|
||||
channelId: params.channel,
|
||||
agentId: params.agentId,
|
||||
peerId,
|
||||
exactPeerIdAliases,
|
||||
peerKind: inferPeerKindForAccountBinding(params.channel, target),
|
||||
});
|
||||
}
|
||||
|
||||
async function resolveActionTarget(params: {
|
||||
cfg: OpenClawConfig;
|
||||
channel: ChannelId;
|
||||
@@ -941,11 +1016,12 @@ export async function runMessageAction(
|
||||
const channel = await resolveChannel(cfg, params, input.toolContext);
|
||||
let accountId = readStringParam(params, "accountId") ?? input.defaultAccountId;
|
||||
if (!accountId && resolvedAgentId) {
|
||||
const byAgent = buildChannelAccountBindings(cfg).get(channel);
|
||||
const boundAccountIds = byAgent?.get(normalizeAgentId(resolvedAgentId));
|
||||
if (boundAccountIds && boundAccountIds.length > 0) {
|
||||
accountId = boundAccountIds[0];
|
||||
}
|
||||
accountId = resolveTargetBoundAccountId({
|
||||
cfg,
|
||||
channel,
|
||||
args: params,
|
||||
agentId: resolvedAgentId,
|
||||
});
|
||||
}
|
||||
if (accountId) {
|
||||
params.accountId = accountId;
|
||||
|
||||
Reference in New Issue
Block a user