From 1f1fda7cf535b9bc7ce1d7af4fe55e573b7fcf09 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Date: Fri, 3 Jul 2026 16:42:58 -0300 Subject: [PATCH] fix(config): externalize ws for copilot-m365-web executor (#6130, closes #6062) Re-lands the #6098 ws-externalization fix onto release/v3.8.44 (it had merged to main by mistake and was reverted). Externalize ws/bufferutil/utf-8-validate so the copilot-m365-web WebSocket masking path works at runtime. Co-authored-by: diegosouzapw --- next.config.mjs | 7 +++++++ tests/unit/next-config.test.ts | 9 +++++++++ 2 files changed, 16 insertions(+) diff --git a/next.config.mjs b/next.config.mjs index 2f5c948bc8..415f5b986e 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -214,6 +214,13 @@ const nextConfig = { "tough-cookie", "@ngrok/ngrok", "@huggingface/transformers", + // copilot-m365-web.ts imports 'ws' as a client-side WebSocket. When bundled, + // ws cannot resolve its 'bufferutil' native addon (frame masking) and throws + // TypeError: b.mask is not a function on the first outgoing frame, causing + // every chat request to time out at the stream-readiness watchdog. (#6062) + "ws", + "bufferutil", + "utf-8-validate", "child_process", "fs", "path", diff --git a/tests/unit/next-config.test.ts b/tests/unit/next-config.test.ts index 11ec9ed4bb..b44e278b51 100644 --- a/tests/unit/next-config.test.ts +++ b/tests/unit/next-config.test.ts @@ -37,6 +37,15 @@ test("next config exposes standalone build settings and canonical rewrites", asy "fumadocs-ui", "fumadocs-core", ]); + // #6062: `ws` and its native masking helpers must stay external so the + // copilot-m365-web executor keeps a working WebSocket masking path at runtime + // (bundling ws breaks `bufferutil` → `TypeError: b.mask is not a function`). + for (const pkg of ["ws", "bufferutil", "utf-8-validate"]) { + assert.ok( + nextConfig.serverExternalPackages.includes(pkg), + `expected serverExternalPackages to externalize "${pkg}" (#6062)` + ); + } assert.equal(headers[0].source, "/:path*"); assert.match(securityHeaders["Content-Security-Policy"], /default-src 'self'/); assert.match(securityHeaders["Content-Security-Policy"], /frame-ancestors 'none'/);