From 022c0dab38fd00e9339eb0273422385faa6f21ed Mon Sep 17 00:00:00 2001 From: Matthew Rayermann Date: Thu, 9 Apr 2026 17:03:29 -0700 Subject: [PATCH 1/3] [Node] Set requestPermission False If Using Default Permission Handler --- nodejs/src/client.ts | 3 ++- nodejs/src/extension.ts | 6 +----- nodejs/src/types.ts | 4 ++++ 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/nodejs/src/client.ts b/nodejs/src/client.ts index 6941598b8..65aea2517 100644 --- a/nodejs/src/client.ts +++ b/nodejs/src/client.ts @@ -55,6 +55,7 @@ import type { TraceContextProvider, TypedSessionLifecycleHandler, } from "./types.js"; +import { defaultJoinSessionPermissionHandler } from "./types.js"; /** * Minimum protocol version this SDK can communicate with. @@ -868,7 +869,7 @@ export class CopilotClient { })), provider: config.provider, modelCapabilities: config.modelCapabilities, - requestPermission: true, + requestPermission: config.onPermissionRequest !== defaultJoinSessionPermissionHandler, requestUserInput: !!config.onUserInputRequest, requestElicitation: !!config.onElicitationRequest, hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)), diff --git a/nodejs/src/extension.ts b/nodejs/src/extension.ts index b7c2da3a8..8688097a9 100644 --- a/nodejs/src/extension.ts +++ b/nodejs/src/extension.ts @@ -4,11 +4,7 @@ import { CopilotClient } from "./client.js"; import type { CopilotSession } from "./session.js"; -import type { PermissionHandler, PermissionRequestResult, ResumeSessionConfig } from "./types.js"; - -const defaultJoinSessionPermissionHandler: PermissionHandler = (): PermissionRequestResult => ({ - kind: "no-result", -}); +import { defaultJoinSessionPermissionHandler, type PermissionHandler, type PermissionRequestResult, type ResumeSessionConfig } from "./types.js"; export type JoinSessionConfig = Omit & { onPermissionRequest?: PermissionHandler; diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts index c2d095234..853eb0e12 100644 --- a/nodejs/src/types.ts +++ b/nodejs/src/types.ts @@ -662,6 +662,10 @@ export type PermissionHandler = ( export const approveAll: PermissionHandler = () => ({ kind: "approved" }); +export const defaultJoinSessionPermissionHandler: PermissionHandler = (): PermissionRequestResult => ({ + kind: "no-result", +}); + // ============================================================================ // User Input Request Types // ============================================================================ From 6538a7561f6b0a842640f5260ef30ed66eb1936c Mon Sep 17 00:00:00 2001 From: Matthew Rayermann Date: Thu, 9 Apr 2026 17:14:45 -0700 Subject: [PATCH 2/3] Add joinSession permission tests --- nodejs/src/extension.ts | 2 +- nodejs/test/client.test.ts | 55 +++++++++++++++++++++++++++++++++++ nodejs/test/extension.test.ts | 2 ++ 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/nodejs/src/extension.ts b/nodejs/src/extension.ts index 8688097a9..7d5d87bed 100644 --- a/nodejs/src/extension.ts +++ b/nodejs/src/extension.ts @@ -4,7 +4,7 @@ import { CopilotClient } from "./client.js"; import type { CopilotSession } from "./session.js"; -import { defaultJoinSessionPermissionHandler, type PermissionHandler, type PermissionRequestResult, type ResumeSessionConfig } from "./types.js"; +import { defaultJoinSessionPermissionHandler, type PermissionHandler, type ResumeSessionConfig } from "./types.js"; export type JoinSessionConfig = Omit & { onPermissionRequest?: PermissionHandler; diff --git a/nodejs/test/client.test.ts b/nodejs/test/client.test.ts index c3f0770cd..0c0611df8 100644 --- a/nodejs/test/client.test.ts +++ b/nodejs/test/client.test.ts @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { describe, expect, it, onTestFinished, vi } from "vitest"; import { approveAll, CopilotClient, type ModelInfo } from "../src/index.js"; +import { defaultJoinSessionPermissionHandler } from "../src/types.js"; // This file is for unit tests. Where relevant, prefer to add e2e tests in e2e/*.test.ts instead @@ -97,6 +98,60 @@ describe("CopilotClient", () => { spy.mockRestore(); }); + it("does not request permissions on session.resume when using the default joinSession handler", async () => { + const client = new CopilotClient(); + await client.start(); + onTestFinished(() => client.forceStop()); + + const session = await client.createSession({ onPermissionRequest: approveAll }); + const spy = vi + .spyOn((client as any).connection!, "sendRequest") + .mockImplementation(async (method: string, params: any) => { + if (method === "session.resume") return { sessionId: params.sessionId }; + throw new Error(`Unexpected method: ${method}`); + }); + + await client.resumeSession(session.sessionId, { + onPermissionRequest: defaultJoinSessionPermissionHandler, + }); + + expect(spy).toHaveBeenCalledWith( + "session.resume", + expect.objectContaining({ + sessionId: session.sessionId, + requestPermission: false, + }) + ); + spy.mockRestore(); + }); + + it("requests permissions on session.resume when using an explicit handler", async () => { + const client = new CopilotClient(); + await client.start(); + onTestFinished(() => client.forceStop()); + + const session = await client.createSession({ onPermissionRequest: approveAll }); + const spy = vi + .spyOn((client as any).connection!, "sendRequest") + .mockImplementation(async (method: string, params: any) => { + if (method === "session.resume") return { sessionId: params.sessionId }; + throw new Error(`Unexpected method: ${method}`); + }); + + await client.resumeSession(session.sessionId, { + onPermissionRequest: approveAll, + }); + + expect(spy).toHaveBeenCalledWith( + "session.resume", + expect.objectContaining({ + sessionId: session.sessionId, + requestPermission: true, + }) + ); + spy.mockRestore(); + }); + it("sends session.model.switchTo RPC with correct params", async () => { const client = new CopilotClient(); await client.start(); diff --git a/nodejs/test/extension.test.ts b/nodejs/test/extension.test.ts index d9fcf8dfd..1e1f11c88 100644 --- a/nodejs/test/extension.test.ts +++ b/nodejs/test/extension.test.ts @@ -2,6 +2,7 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { CopilotClient } from "../src/client.js"; import { approveAll } from "../src/index.js"; import { joinSession } from "../src/extension.js"; +import { defaultJoinSessionPermissionHandler } from "../src/types.js"; describe("joinSession", () => { const originalSessionId = process.env.SESSION_ID; @@ -25,6 +26,7 @@ describe("joinSession", () => { const [, config] = resumeSession.mock.calls[0]!; expect(config.onPermissionRequest).toBeDefined(); + expect(config.onPermissionRequest).toBe(defaultJoinSessionPermissionHandler); const result = await Promise.resolve( config.onPermissionRequest!({ kind: "write" }, { sessionId: "session-123" }) ); From 5936220f6b6c9220e3220f3e0b70c7aca8f66cab Mon Sep 17 00:00:00 2001 From: Matthew Rayermann Date: Thu, 9 Apr 2026 17:21:55 -0700 Subject: [PATCH 3/3] Format joinSession changes --- nodejs/src/client.ts | 3 ++- nodejs/src/extension.ts | 6 +++++- nodejs/src/types.ts | 7 ++++--- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/nodejs/src/client.ts b/nodejs/src/client.ts index 65aea2517..0780ba6ea 100644 --- a/nodejs/src/client.ts +++ b/nodejs/src/client.ts @@ -869,7 +869,8 @@ export class CopilotClient { })), provider: config.provider, modelCapabilities: config.modelCapabilities, - requestPermission: config.onPermissionRequest !== defaultJoinSessionPermissionHandler, + requestPermission: + config.onPermissionRequest !== defaultJoinSessionPermissionHandler, requestUserInput: !!config.onUserInputRequest, requestElicitation: !!config.onElicitationRequest, hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)), diff --git a/nodejs/src/extension.ts b/nodejs/src/extension.ts index 7d5d87bed..bd35c0997 100644 --- a/nodejs/src/extension.ts +++ b/nodejs/src/extension.ts @@ -4,7 +4,11 @@ import { CopilotClient } from "./client.js"; import type { CopilotSession } from "./session.js"; -import { defaultJoinSessionPermissionHandler, type PermissionHandler, type ResumeSessionConfig } from "./types.js"; +import { + defaultJoinSessionPermissionHandler, + type PermissionHandler, + type ResumeSessionConfig, +} from "./types.js"; export type JoinSessionConfig = Omit & { onPermissionRequest?: PermissionHandler; diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts index 853eb0e12..327686143 100644 --- a/nodejs/src/types.ts +++ b/nodejs/src/types.ts @@ -662,9 +662,10 @@ export type PermissionHandler = ( export const approveAll: PermissionHandler = () => ({ kind: "approved" }); -export const defaultJoinSessionPermissionHandler: PermissionHandler = (): PermissionRequestResult => ({ - kind: "no-result", -}); +export const defaultJoinSessionPermissionHandler: PermissionHandler = + (): PermissionRequestResult => ({ + kind: "no-result", + }); // ============================================================================ // User Input Request Types