diff --git a/packages/loopover-miner/lib/chat-action-dispatch.js b/packages/loopover-miner/lib/chat-action-dispatch.js index 11b44bf25..1faaca4db 100644 --- a/packages/loopover-miner/lib/chat-action-dispatch.js +++ b/packages/loopover-miner/lib/chat-action-dispatch.js @@ -71,7 +71,15 @@ export async function dispatchChatAction(request, options = {}) { return { ok: false, status: "invalid_params", action }; } - const result = await registered.handler(request); + let result; + try { + result = await registered.handler(request); + } catch (error) { + // A handler that throws fails closed with the module's typed result shape (#6989), consistent with the + // paramsValidator catch above -- a distinct "handler_error" status lets a caller tell an execution + // failure apart from a params-validation failure. + return { ok: false, status: "handler_error", action, error: error instanceof Error ? error.message : String(error) }; + } return { ok: true, status: "dispatched", action, result }; } diff --git a/test/unit/miner-chat-action-dispatch.test.ts b/test/unit/miner-chat-action-dispatch.test.ts index f8df273c1..daafef0bc 100644 --- a/test/unit/miner-chat-action-dispatch.test.ts +++ b/test/unit/miner-chat-action-dispatch.test.ts @@ -147,4 +147,21 @@ describe("dispatchChatAction (#6519)", () => { const result = await dispatchChatAction({ action: "demo", params: {} }, { env: enabledEnv, registry }); expect(result).toEqual({ ok: false, status: "invalid_params", action: "demo", error: "boom" }); }); + + it("fails closed with handler_error when the handler throws, not an unhandled rejection (#6989)", async () => { + const registry = registryWith("demo", () => true, () => { + throw new Error("network down"); + }); + const result = await dispatchChatAction({ action: "demo", params: {} }, { env: enabledEnv, registry }); + // Distinct from invalid_params so a caller can tell an execution failure from a validation failure. + expect(result).toEqual({ ok: false, status: "handler_error", action: "demo", error: "network down" }); + }); + + it("stringifies a non-Error thrown by the handler (#6989)", async () => { + const registry = registryWith("demo", () => true, () => { + throw "kaboom"; + }); + const result = await dispatchChatAction({ action: "demo", params: {} }, { env: enabledEnv, registry }); + expect(result).toEqual({ ok: false, status: "handler_error", action: "demo", error: "kaboom" }); + }); });