From cdb972161ae0773726273324af1b0950d1563645 Mon Sep 17 00:00:00 2001 From: dorianzheng Date: Fri, 20 Mar 2026 20:42:55 +0800 Subject: [PATCH] Fix Slack image upload: add files:write scope, raise WS limit, propagate errors - Add files:write to the copy-manifest scopes in settings UI - Raise WebSocket maxPayload to 16 MB so base64-encoded images are not rejected by the ws library's default 1 MB limit - Make sendImageToSlack propagate errors instead of silently swallowing them, so the RPC caller gets a real failure instead of { ok: true } --- packages/backend/src/gateway/transport.ts | 5 +++-- packages/backend/src/slack/slack-connection.ts | 15 ++++++++++++++- .../src/components/layout/settings-view.ts | 2 +- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/packages/backend/src/gateway/transport.ts b/packages/backend/src/gateway/transport.ts index 477a13b..8d054a1 100644 --- a/packages/backend/src/gateway/transport.ts +++ b/packages/backend/src/gateway/transport.ts @@ -8,6 +8,7 @@ import * as broadcast from './broadcast.js' import { openTerminal } from './terminal.js' const HEARTBEAT_INTERVAL_MS = 30_000 +const MAX_PAYLOAD_BYTES = 16 * 1024 * 1024 // 16 MB — enough for base64-encoded 10 MB images function setupHeartbeat(wss: WebSocketServer) { const heartbeat = setInterval(() => { @@ -31,7 +32,7 @@ function markAlive(ws: WebSocket) { // ── Client Gateway (/ws/client) — runs on its own port ──────────────── export function setupClientGateway(server: Server) { - const wss = new WebSocketServer({ server }) + const wss = new WebSocketServer({ server, maxPayload: MAX_PAYLOAD_BYTES }) const clientRpc = createRpcDispatcher(clientHandlers) setupHeartbeat(wss) @@ -73,7 +74,7 @@ export function setupClientGateway(server: Server) { // ── Agent Gateway (/ws/agent + terminal) — runs on its own port ─────── export function setupAgentGateway(server: Server) { - const wss = new WebSocketServer({ server }) + const wss = new WebSocketServer({ server, maxPayload: MAX_PAYLOAD_BYTES }) const agentRpc = createRpcDispatcher(agentHandlers) setupHeartbeat(wss) diff --git a/packages/backend/src/slack/slack-connection.ts b/packages/backend/src/slack/slack-connection.ts index adb6003..267557b 100644 --- a/packages/backend/src/slack/slack-connection.ts +++ b/packages/backend/src/slack/slack-connection.ts @@ -626,7 +626,20 @@ export async function sendImageToSlack( channelId: string, ): Promise { if (!webClient) throw new Error('Slack is not connected') - await uploadImageToSlack(imageUrl, alt, channelId) + + if (!imageUrl.startsWith('/media/')) { + throw new Error(`Only local /media/ URLs are supported, got: ${imageUrl}`) + } + + const filename = basename(imageUrl) + const filePath = join(MEDIA_DIR, filename) + const fileData = readFileSync(filePath) + await webClient.filesUploadV2({ + channel_id: channelId, + file: fileData, + filename, + title: alt, + }) } // ── Sync / Unsync ────────────────────────────────────────────────────── diff --git a/packages/frontend/src/components/layout/settings-view.ts b/packages/frontend/src/components/layout/settings-view.ts index eefa70a..4c3f970 100644 --- a/packages/frontend/src/components/layout/settings-view.ts +++ b/packages/frontend/src/components/layout/settings-view.ts @@ -983,7 +983,7 @@ export class SettingsView extends LitElement { }, oauth_config: { scopes: { - bot: ['channels:history', 'channels:manage', 'channels:read', 'chat:write', 'chat:write.customize', 'users:read', 'app_mentions:read'], + bot: ['channels:history', 'channels:manage', 'channels:read', 'chat:write', 'chat:write.customize', 'files:write', 'users:read', 'app_mentions:read'], }, }, settings: {