diff --git a/src/gateway/server-methods/agent.test.ts b/src/gateway/server-methods/agent.test.ts index f29a9a4c85d..411933ec0aa 100644 --- a/src/gateway/server-methods/agent.test.ts +++ b/src/gateway/server-methods/agent.test.ts @@ -435,6 +435,57 @@ describe("gateway agent handler", () => { ); }); + it("re-establishes plugin runtime gateway scope for detached agent runs", async () => { + primeMainAgentRun(); + const context = makeContext(); + const client = { + connect: { + scopes: ["operator.write"], + }, + } as AgentHandlerArgs["client"]; + const isWebchatConnect = vi.fn(() => true); + let observedScope: + | { + context?: GatewayRequestContext; + client?: AgentHandlerArgs["client"]; + isWebchatConnect?: AgentHandlerArgs["isWebchatConnect"]; + } + | undefined; + + mocks.agentCommand.mockImplementation(async () => { + const gatewayScopeModule = await import("../../plugins/runtime/gateway-request-scope.js"); + observedScope = gatewayScopeModule.getPluginRuntimeGatewayRequestScope(); + return { + payloads: [{ text: "ok" }], + meta: { durationMs: 100 }, + }; + }); + + await invokeAgent( + { + message: "test", + agentId: "main", + sessionKey: "agent:main:main", + idempotencyKey: "test-idem-plugin-scope", + }, + { + reqId: "test-idem-plugin-scope", + context, + client, + isWebchatConnect, + }, + ); + + await vi.waitFor(() => expect(observedScope).toBeDefined()); + expect(observedScope).toEqual( + expect.objectContaining({ + context, + client, + isWebchatConnect, + }), + ); + }); + it("preserves cliSessionIds from existing session entry", async () => { const existingCliSessionIds = { "claude-cli": "abc-123-def" }; const existingClaudeCliSessionId = "abc-123-def"; diff --git a/src/gateway/server-methods/agent.ts b/src/gateway/server-methods/agent.ts index bd5637fa78f..85d2223ebf1 100644 --- a/src/gateway/server-methods/agent.ts +++ b/src/gateway/server-methods/agent.ts @@ -22,6 +22,7 @@ import { resolveAgentOutboundTarget, } from "../../infra/outbound/agent-delivery.js"; import { resolveMessageChannelSelection } from "../../infra/outbound/channel-selection.js"; +import { withPluginRuntimeGatewayRequestScope } from "../../plugins/runtime/gateway-request-scope.js"; import { classifySessionKeyShape, normalizeAgentId } from "../../routing/session-key.js"; import { defaultRuntime } from "../../runtime.js"; import { normalizeInputProvenance, type InputProvenance } from "../../sessions/input-provenance.js"; @@ -144,8 +145,17 @@ function dispatchAgentRunFromGateway(params: { idempotencyKey: string; respond: GatewayRequestHandlerOptions["respond"]; context: GatewayRequestHandlerOptions["context"]; + client: GatewayRequestHandlerOptions["client"]; + isWebchatConnect: GatewayRequestHandlerOptions["isWebchatConnect"]; }) { - void agentCommandFromIngress(params.ingressOpts, defaultRuntime, params.context.deps) + void withPluginRuntimeGatewayRequestScope( + { + context: params.context, + client: params.client, + isWebchatConnect: params.isWebchatConnect, + }, + () => agentCommandFromIngress(params.ingressOpts, defaultRuntime, params.context.deps), + ) .then((result) => { const payload = { runId: params.runId, @@ -711,6 +721,8 @@ export const agentHandlers: GatewayRequestHandlers = { idempotencyKey: idem, respond, context, + client, + isWebchatConnect, }); }, "agent.identity.get": ({ params, respond }) => {