diff --git a/apps/server/src/mcp/toolkits/previewControls/handlers.test.ts b/apps/server/src/mcp/toolkits/previewControls/handlers.test.ts index 4cd06bc4233b..62840dd21e6e 100644 --- a/apps/server/src/mcp/toolkits/previewControls/handlers.test.ts +++ b/apps/server/src/mcp/toolkits/previewControls/handlers.test.ts @@ -4,8 +4,10 @@ import { expect, it } from "@effect/vitest"; import { DEFAULT_SERVER_SETTINGS, EnvironmentId, + PreviewAutomationControlInterruptedError, ProjectId, ProviderInstanceId, + SERVER_BROWSER_AUTOMATION_CLIENT_ID, ThreadId, } from "@t3tools/contracts"; import { resolveProjectSettings } from "@t3tools/shared/projectSettings"; @@ -101,3 +103,86 @@ it.effect.each([ }), ), ); + +it.effect("close reports why the server browser refused to close a tab", () => + Effect.scoped( + Effect.gen(function* () { + const threadId = ThreadId.make("preview-controls-thread"); + const scope: McpInvocationContext.McpInvocationScope = { + environmentId: EnvironmentId.make("preview-controls-environment"), + requestNamespace: "preview-controls-provider-session", + thread: { + threadId, + providerSessionId: "preview-controls-provider-session", + providerInstanceId: ProviderInstanceId.make("codex"), + }, + client: undefined, + capabilities: new Set(["preview"]), + issuedAt: 0, + }; + const manager = yield* Preview.make.pipe( + Effect.provide( + Layer.merge( + ServerConfig.layerTest(process.cwd(), { prefix: "t3-preview-controls-" }).pipe( + Layer.provide(NodeServices.layer), + ), + NodeCrypto.layer, + ), + ), + ); + const tab = yield* manager.open({ + threadId, + url: "http://localhost:3000", + runtime: "server", + }); + const layerDependencies = Layer.mergeAll( + Layer.mock(PreviewAutomationBroker.PreviewAutomationBroker)({ + invoke: () => + Effect.fail( + new PreviewAutomationControlInterruptedError({ + operation: "close", + environmentId: scope.environmentId, + threadId, + providerSessionId: "preview-controls-provider-session", + providerInstanceId: ProviderInstanceId.make("codex"), + clientId: SERVER_BROWSER_AUTOMATION_CLIENT_ID, + connectionId: "server-browser-connection", + requestId: "close-request", + tabId: tab.tabId, + timeoutMs: 1_000, + remoteTag: "PreviewAutomationControlInterruptedError", + remoteMessageLength: 0, + cause: null, + reason: "agentMismatch", + }), + ), + }), + Layer.succeed(Preview.PreviewManager, manager), + Layer.succeed(McpInvocationContext.McpInvocationContext, scope), + McpToolAccessTestkit.liveThreadsLayer, + Layer.mock(ServerSettings.ServerSettingsService)({ + getSettings: Effect.succeed({ + ...DEFAULT_SERVER_SETTINGS, + enableAgentBrowserAccess: true, + }), + }), + ); + const toolkit = yield* PreviewControlsToolkit.pipe( + Effect.provide( + McpToolAccess.HandlersLayer.layer(PreviewControlsHandlers.layer).pipe( + Layer.provide(layerDependencies), + ), + ), + ); + + const closed = yield* toolkit + .handle("t3_preview_close", { tabId: tab.tabId }) + .pipe(Stream.unwrap, Stream.runCollect, Effect.provide(layerDependencies)); + + expect(closed.at(-1)?.result).toMatchObject({ + _tag: "PreviewAutomationControlInterruptedError", + reason: "agentMismatch", + }); + }), + ), +); diff --git a/apps/server/src/mcp/toolkits/previewControls/handlers.ts b/apps/server/src/mcp/toolkits/previewControls/handlers.ts index bdb2bad2ad6e..f0b011f5c3b2 100644 --- a/apps/server/src/mcp/toolkits/previewControls/handlers.ts +++ b/apps/server/src/mcp/toolkits/previewControls/handlers.ts @@ -33,9 +33,7 @@ export const layer = McpToolAccess.toLayer(PreviewControlsToolkit, { sessions.some((session) => session.tabId === input.tabId && session.runtime === "server") ) { const broker = yield* PreviewAutomationBroker.PreviewAutomationBroker; - yield* broker - .invoke({ scope, operation: "close", input: {}, tabId: input.tabId }) - .pipe(Effect.mapError(unavailable)); + yield* broker.invoke({ scope, operation: "close", input: {}, tabId: input.tabId }); return {}; } yield* manager diff --git a/apps/server/src/mcp/toolkits/previewControls/tools.ts b/apps/server/src/mcp/toolkits/previewControls/tools.ts index 2a14ee7b5e64..8bc2ccd31c18 100644 --- a/apps/server/src/mcp/toolkits/previewControls/tools.ts +++ b/apps/server/src/mcp/toolkits/previewControls/tools.ts @@ -1,7 +1,7 @@ import { NonNegativeInt, OrchestratorMcpFailure, - PreviewAutomationUnavailableError, + PreviewAutomationError, PreviewListResult, PreviewTabId, } from "@t3tools/contracts"; @@ -13,7 +13,7 @@ import * as PreviewAutomationBroker from "../../PreviewAutomationBroker.ts"; import * as McpInvocationContext from "../../McpInvocationContext.ts"; const shared = { - failure: Schema.Union([OrchestratorMcpFailure, PreviewAutomationUnavailableError]), + failure: Schema.Union([PreviewAutomationError, OrchestratorMcpFailure]), failureMode: "return" as const, dependencies: [ McpInvocationContext.McpInvocationContext,