mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-20 22:24:58 +00:00
fix(slack): bind download-file to channel scope
This commit is contained in:
@@ -104,6 +104,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Slack/download-file scope binding: require channel-targeted `download-file` actions and enforce channel/thread share checks before resolving Slack private download URLs, reducing cross-channel/thread attachment fetch surface.
|
||||
- Agents/Model fallback: classify additional network transport errors (`ECONNREFUSED`, `ENETUNREACH`, `EHOSTUNREACH`, `ENETRESET`, `EAI_AGAIN`) as failover-worthy so fallback chains advance when primary providers are unreachable. Landed from contributor PR #19077 by @ayanesakura. Thanks @ayanesakura.
|
||||
- Agents/Copilot token refresh: refresh GitHub Copilot runtime API tokens after auth-expiry failures and re-run with the renewed token so long-running embedded/subagent turns do not fail on mid-session 401 expiry. Landed from contributor PR #8805 by @Arthur742Ramos. Thanks @Arthur742Ramos.
|
||||
- Discord/Allowlist diagnostics: add debug logs for guild/channel allowlist drops so operators can quickly identify ignored inbound messages and required allowlist entries. Landed from contributor PR #30966 by @haosenwang1018. Thanks @haosenwang1018.
|
||||
|
||||
@@ -202,12 +202,13 @@ describe("handleSlackAction", () => {
|
||||
{
|
||||
action: "downloadFile",
|
||||
fileId: "F123",
|
||||
channelId: "C1",
|
||||
},
|
||||
slackConfig(),
|
||||
);
|
||||
expect(downloadSlackFile).toHaveBeenCalledWith(
|
||||
"F123",
|
||||
expect.objectContaining({ maxBytes: 20 * 1024 * 1024 }),
|
||||
expect.objectContaining({ channelId: "C1", maxBytes: 20 * 1024 * 1024 }),
|
||||
);
|
||||
expect(result).toEqual(
|
||||
expect.objectContaining({
|
||||
@@ -243,6 +244,18 @@ describe("handleSlackAction", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("requires a channel target for downloadFile", async () => {
|
||||
await expect(
|
||||
handleSlackAction(
|
||||
{
|
||||
action: "downloadFile",
|
||||
fileId: "F123",
|
||||
},
|
||||
slackConfig(),
|
||||
),
|
||||
).rejects.toThrow(/to/i);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
name: "JSON blocks",
|
||||
|
||||
@@ -290,8 +290,9 @@ export async function handleSlackAction(
|
||||
}
|
||||
case "downloadFile": {
|
||||
const fileId = readStringParam(params, "fileId", { required: true });
|
||||
const channelTarget = readStringParam(params, "channelId") ?? readStringParam(params, "to");
|
||||
const channelId = channelTarget ? resolveSlackChannelId(channelTarget) : undefined;
|
||||
const channelTarget =
|
||||
readStringParam(params, "channelId") ?? readStringParam(params, "to", { required: true });
|
||||
const channelId = resolveSlackChannelId(channelTarget);
|
||||
const threadId = readStringParam(params, "threadId") ?? readStringParam(params, "replyTo");
|
||||
const maxBytes = account.config?.mediaMaxMb
|
||||
? account.config.mediaMaxMb * 1024 * 1024
|
||||
|
||||
@@ -55,7 +55,7 @@ export const MESSAGE_ACTION_TARGET_MODE: Record<ChannelMessageActionName, Messag
|
||||
kick: "none",
|
||||
ban: "none",
|
||||
"set-presence": "none",
|
||||
"download-file": "none",
|
||||
"download-file": "to",
|
||||
};
|
||||
|
||||
const ACTION_TARGET_ALIASES: Partial<Record<ChannelMessageActionName, string[]>> = {
|
||||
|
||||
@@ -63,4 +63,26 @@ describe("handleSlackMessageAction", () => {
|
||||
expect.any(Object),
|
||||
);
|
||||
});
|
||||
|
||||
it("requires a target channel for download-file", async () => {
|
||||
const invoke = vi.fn(async (action: Record<string, unknown>) => ({
|
||||
ok: true,
|
||||
content: action,
|
||||
}));
|
||||
|
||||
await expect(
|
||||
handleSlackMessageAction({
|
||||
providerId: "slack",
|
||||
ctx: {
|
||||
action: "download-file",
|
||||
cfg: {},
|
||||
params: {
|
||||
fileId: "F-no-target",
|
||||
},
|
||||
} as never,
|
||||
invoke: invoke as never,
|
||||
}),
|
||||
).rejects.toThrow(/to/i);
|
||||
expect(invoke).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -178,15 +178,14 @@ export async function handleSlackMessageAction(params: {
|
||||
|
||||
if (action === "download-file") {
|
||||
const fileId = readStringParam(actionParams, "fileId", { required: true });
|
||||
const channelId =
|
||||
readStringParam(actionParams, "channelId") ?? readStringParam(actionParams, "to");
|
||||
const channelId = resolveChannelId();
|
||||
const threadId =
|
||||
readStringParam(actionParams, "threadId") ?? readStringParam(actionParams, "replyTo");
|
||||
return await invoke(
|
||||
{
|
||||
action: "downloadFile",
|
||||
fileId,
|
||||
channelId: channelId ?? undefined,
|
||||
channelId,
|
||||
threadId: threadId ?? undefined,
|
||||
accountId,
|
||||
},
|
||||
|
||||
@@ -39,6 +39,7 @@ describe("downloadSlackFile", () => {
|
||||
client,
|
||||
token: "xoxb-test",
|
||||
maxBytes: 1024,
|
||||
channelId: "C123",
|
||||
});
|
||||
|
||||
expect(result).toBeNull();
|
||||
@@ -67,6 +68,7 @@ describe("downloadSlackFile", () => {
|
||||
client,
|
||||
token: "xoxb-test",
|
||||
maxBytes: 1024,
|
||||
channelId: "C123",
|
||||
});
|
||||
|
||||
expect(client.files.info).toHaveBeenCalledWith({ file: "F123" });
|
||||
|
||||
@@ -372,12 +372,12 @@ function collectSlackThreadShares(
|
||||
|
||||
function hasSlackScopeMismatch(params: {
|
||||
file: SlackFileInfoSummary;
|
||||
channelId?: string;
|
||||
channelId: string;
|
||||
threadId?: string;
|
||||
}): boolean {
|
||||
const channelId = normalizeSlackScopeValue(params.channelId);
|
||||
if (!channelId) {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
const threadId = normalizeSlackScopeValue(params.threadId);
|
||||
|
||||
@@ -410,7 +410,7 @@ function hasSlackScopeMismatch(params: {
|
||||
*/
|
||||
export async function downloadSlackFile(
|
||||
fileId: string,
|
||||
opts: SlackActionClientOpts & { maxBytes: number; channelId?: string; threadId?: string },
|
||||
opts: SlackActionClientOpts & { maxBytes: number; channelId: string; threadId?: string },
|
||||
): Promise<SlackMediaResult | null> {
|
||||
const token = resolveToken(opts.token, opts.accountId);
|
||||
const client = await getClient(opts);
|
||||
|
||||
Reference in New Issue
Block a user