diff --git a/packages/coding-agent/src/dap/session.ts b/packages/coding-agent/src/dap/session.ts index 82f9ba3e2..f7e185b69 100644 --- a/packages/coding-agent/src/dap/session.ts +++ b/packages/coding-agent/src/dap/session.ts @@ -289,7 +289,7 @@ export class DapSessionManager { ...(options.extraLaunchArguments ?? {}), program: options.program, cwd: options.cwd, - args: options.args, + ...(options.args !== undefined ? { args: options.args } : {}), }; // Subscribe to stop events BEFORE launching so we don't miss // stopOnEntry events that arrive before we start listening. diff --git a/packages/coding-agent/test/debug/dap-launch-failures.test.ts b/packages/coding-agent/test/debug/dap-launch-failures.test.ts index 5ec7abaab..5a48eb999 100644 --- a/packages/coding-agent/test/debug/dap-launch-failures.test.ts +++ b/packages/coding-agent/test/debug/dap-launch-failures.test.ts @@ -62,6 +62,7 @@ class FakeDapClient { readonly #exited = Promise.withResolvers(); readonly #handlers = new Map>(); #alive = true; + requests: Array<{ command: string; args: unknown }> = []; constructor( readonly adapter: DapResolvedAdapter, @@ -73,6 +74,7 @@ class FakeDapClient { attachErrorDelayMs?: number; configurationDoneError?: string; rejectStopWaiters?: boolean; + stopAfterLaunch?: boolean; }, ) { this.proc = { @@ -95,7 +97,8 @@ class FakeDapClient { return { supportsConfigurationDoneRequest: true }; } - async sendRequest(command: string): Promise { + async sendRequest(command: string, args?: unknown): Promise { + this.requests.push({ command, args }); if (command === "launch" && this.options.launchError) { if (this.options.launchErrorDelayMs) await Bun.sleep(this.options.launchErrorDelayMs); throw new Error(this.options.launchError); @@ -107,6 +110,9 @@ class FakeDapClient { if (command === "configurationDone" && this.options.configurationDoneError) { throw new Error(this.options.configurationDoneError); } + if (command === "launch" && this.options.stopAfterLaunch) { + queueMicrotask(() => this.#emit("stopped", { reason: "entry", threadId: 1 })); + } return {}; } @@ -158,6 +164,21 @@ afterEach(() => { }); describe("DAP launch failure handling", () => { + it("preserves adapter launchDefaults args when launch omits args", async () => { + const adapter: DapResolvedAdapter = { + ...TEST_ADAPTER, + launchDefaults: { request: "launch", args: ["--configured"], stopOnEntry: true }, + }; + const manager = new DapSessionManager(); + const fake = new FakeDapClient(adapter, process.cwd(), { stopAfterLaunch: true }); + spyOn(DapClient, "spawn").mockResolvedValue(fake as unknown as DapClient); + + await manager.launch({ adapter, program: "/bin/echo", cwd: process.cwd() }, undefined, 10); + + const launch = fake.requests.find(request => request.command === "launch"); + expect(launch?.args).toMatchObject({ args: ["--configured"], program: "/bin/echo" }); + }); + it("surfaces the launch failure when configurationDone also fails", async () => { const manager = new DapSessionManager(); const fake = new FakeDapClient(TEST_ADAPTER, process.cwd(), {