diff --git a/extensions/msteams/src/sdk.test.ts b/extensions/msteams/src/sdk.test.ts index cc58e198019..1ea84925aa8 100644 --- a/extensions/msteams/src/sdk.test.ts +++ b/extensions/msteams/src/sdk.test.ts @@ -81,9 +81,8 @@ function createSdkStub(): MSTeamsTeamsSdk { describe("createMSTeamsApp", () => { it("does not crash with express 5 path-to-regexp (#55161)", async () => { // Regression test for: https://github.com/openclaw/openclaw/issues/55161 - // The default HttpPlugin in @microsoft/teams.apps uses `express().use('/api*', ...)` - // which throws in express 5 (path-to-regexp v8+). createMSTeamsApp injects a no-op - // HTTP plugin stub to prevent the SDK from creating the default HttpPlugin. + // createMSTeamsApp passes a no-op httpServerAdapter to prevent the SDK from + // creating its default HttpPlugin (which registers `/api*` — invalid in Express 5). const { App } = await import("@microsoft/teams.apps"); const { Client } = await import("@microsoft/teams.api"); const sdk: MSTeamsTeamsSdk = { App, Client }; diff --git a/extensions/msteams/src/sdk.ts b/extensions/msteams/src/sdk.ts index b7a82f343e1..a3ea98b3307 100644 --- a/extensions/msteams/src/sdk.ts +++ b/extensions/msteams/src/sdk.ts @@ -1,3 +1,4 @@ +import type { IHttpServerAdapter } from "@microsoft/teams.apps/dist/http/index.js"; import { formatUnknownError } from "./errors.js"; import type { MSTeamsAdapter } from "./messenger.js"; import type { MSTeamsCredentials } from "./token.js"; @@ -55,65 +56,22 @@ export async function loadMSTeamsSdk(): Promise { } /** - * Create a lightweight no-op HTTP plugin stub that satisfies the Teams SDK's - * plugin discovery by name ("http") but does NOT spin up an Express server. - * - * The default HttpPlugin in @microsoft/teams.apps registers an Express - * middleware with the pattern `/api*`. When the host application (OpenClaw) - * uses Express 5 — which depends on path-to-regexp v8 — that pattern is - * invalid and throws: - * - * Missing parameter name at index 5: /api* + * Create a no-op HTTP server adapter that satisfies the Teams SDK's + * IHttpServerAdapter interface without spinning up an Express server. * * OpenClaw manages its own Express server for the Teams webhook endpoint, so - * the SDK's built-in HTTP server is unnecessary. Passing this stub prevents - * the SDK from creating the default HttpPlugin and avoids the crash. + * the SDK's built-in HTTP server is unnecessary. Passing this adapter via the + * `httpServerAdapter` option prevents the SDK from creating the default + * HttpPlugin (which uses the deprecated `plugins` array and registers an + * Express middleware with the pattern `/api*` — invalid in Express 5). * * See: https://github.com/openclaw/openclaw/issues/55161 + * See: https://github.com/openclaw/openclaw/issues/60732 */ -async function createNoOpHttpPlugin(): Promise { - // Lazy-import reflect-metadata (required by the Teams SDK decorator system) - // and the decorator key constants so we can tag the stub class correctly. - // - // FRAGILE: these are internal SDK paths (not public API). If - // @microsoft/teams.apps changes its dist layout, these imports will break. - // Pin the SDK version and re-verify after any upgrade. - await import("reflect-metadata"); - const { PLUGIN_METADATA_KEY } = - await import("@microsoft/teams.apps/dist/types/plugin/decorators/plugin.js"); - const { PLUGIN_DEPENDENCIES_METADATA_KEY } = - await import("@microsoft/teams.apps/dist/types/plugin/decorators/dependency.js"); - const { PLUGIN_EVENTS_METADATA_KEY } = - await import("@microsoft/teams.apps/dist/types/plugin/decorators/event.js"); - - class NoOpHttpPlugin { - onInit() {} - async onStart() {} - onStop() {} - asServer() { - return { - onRequest: undefined as unknown, - initialize: async () => {}, - start: async () => {}, - stop: async () => {}, - } as { - onRequest: unknown; - initialize: (opts?: unknown) => Promise; - start: (port?: number | string) => Promise; - stop: () => Promise; - }; - } - } - - Reflect.defineMetadata( - PLUGIN_METADATA_KEY, - { name: "http", version: "0.0.0", description: "no-op stub (express 5 compat)" }, - NoOpHttpPlugin, - ); - Reflect.defineMetadata(PLUGIN_DEPENDENCIES_METADATA_KEY, [], NoOpHttpPlugin); - Reflect.defineMetadata(PLUGIN_EVENTS_METADATA_KEY, [], NoOpHttpPlugin); - - return new NoOpHttpPlugin(); +function createNoOpHttpServerAdapter(): IHttpServerAdapter { + return { + registerRoute() {}, + }; } /** @@ -127,15 +85,11 @@ export async function createMSTeamsApp( creds: MSTeamsCredentials, sdk: MSTeamsTeamsSdk, ): Promise { - const noOpHttp = await createNoOpHttpPlugin(); - // Use type assertion: the SDK's AppOptions generic narrows `plugins` to - // Array, but our no-op stub satisfies the runtime contract without - // matching the decorator-heavy IPlugin type at compile time. return new sdk.App({ clientId: creds.appId, clientSecret: creds.appPassword, tenantId: creds.tenantId, - plugins: [noOpHttp], + httpServerAdapter: createNoOpHttpServerAdapter(), } as ConstructorParameters[0]); }