diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index c97003469..1586f79c2 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -4,7 +4,7 @@ ### Fixed -- Fixed `debug` tool refusing every `dlv` launch on Go modules. The launch handler ran `validateLaunchProgram` before adapter selection and rejected any directory program with `launch program resolves to a directory`, while dlv's default `mode=debug` requires a Go package path (a directory or `.go` source file). Adapter resolution now precedes validation, the rejection only fires when the resolved adapter does not advertise `acceptsDirectoryProgram` (set on `dlv` in `dap/defaults.json`), and dlv's `mode` is derived from the program shape — directories and `.go` files launch as `mode=debug`, other files as `mode=exec` — so `omp` can debug both Go packages and pre-built binaries ([#2020](https://github.com/can1357/oh-my-pi/issues/2020)). +- Fixed `debug` tool refusing every `dlv` launch on Go modules. The launch handler ran `validateLaunchProgram` before adapter selection and rejected any directory program with `launch program resolves to a directory`, while dlv's default `mode=debug` requires a Go package path (a directory or `.go` source file). Adapter resolution now precedes validation, directory programs prefer adapters that advertise `acceptsDirectoryProgram` before falling back to native extensionless debuggers, the rejection only fires when the resolved adapter does not advertise that flag (set on `dlv` in `dap/defaults.json`), and dlv's `mode` is derived from the program shape — directories and `.go` files launch as `mode=debug`, other files as `mode=exec` — so `omp` can debug both Go packages and pre-built binaries ([#2020](https://github.com/can1357/oh-my-pi/issues/2020)). ## [15.10.0] - 2026-06-06 diff --git a/packages/coding-agent/src/dap/config.ts b/packages/coding-agent/src/dap/config.ts index 93d35573f..31f870b91 100644 --- a/packages/coding-agent/src/dap/config.ts +++ b/packages/coding-agent/src/dap/config.ts @@ -126,12 +126,19 @@ function sortAdaptersForLaunch(program: string, cwd: string, adapters: DapResolv return rootAware.map(entry => entry.adapter); } -export function selectLaunchAdapter(program: string, cwd: string, adapterName?: string): DapResolvedAdapter | null { +export function selectLaunchAdapter( + program: string, + cwd: string, + adapterName?: string, + programKind: LaunchProgramKind = "file", +): DapResolvedAdapter | null { if (adapterName) { return resolveAdapter(adapterName, cwd); } const matches = getMatchingAdapters(program, cwd); - const sorted = sortAdaptersForLaunch(program, cwd, matches); + const candidates = + programKind === "directory" ? matches.filter(adapter => adapter.acceptsDirectoryProgram) : matches; + const sorted = sortAdaptersForLaunch(program, cwd, candidates.length > 0 ? candidates : matches); return sorted[0] ?? null; } diff --git a/packages/coding-agent/src/tools/debug.ts b/packages/coding-agent/src/tools/debug.ts index caf54efe6..61a009d2c 100644 --- a/packages/coding-agent/src/tools/debug.ts +++ b/packages/coding-agent/src/tools/debug.ts @@ -687,7 +687,7 @@ export class DebugTool implements AgentTool { } }); + it("prefers directory-capable dlv over native adapters for extensionless Go package directories", async () => { + const dapModule = await import("../../src/dap"); + const sessionLaunchSpy = spyOn(dapModule.dapSessionManager, "launch").mockImplementation(async opts => { + throw Object.assign(new Error("captured launch"), { capturedOptions: opts }); + }); + try { + const cwd = await fs.mkdtemp(path.join(os.tmpdir(), "omp-debug-dlv-mixed-roots-")); + try { + await fs.writeFile(path.join(cwd, "go.mod"), "module hello\n\ngo 1.22\n"); + await fs.writeFile(path.join(cwd, "Makefile"), "all:\n\tgo build ./...\n"); + await fs.mkdir(path.join(cwd, "bin")); + await fs.writeFile(path.join(cwd, "bin", "dlv"), ""); + await fs.writeFile(path.join(cwd, "bin", "gdb"), ""); + await fs.mkdir(path.join(cwd, "cmd", "hello"), { recursive: true }); + const session: ToolSession = { + cwd, + hasUI: false, + getSessionFile: () => null, + getSessionSpawns: () => "*", + settings: Settings.isolated({ "debug.enabled": true }), + }; + const tool = new DebugTool(session); + + await expect(tool.execute("call", { action: "launch", program: "cmd/hello" })).rejects.toThrow( + /captured launch/, + ); + const [opts] = sessionLaunchSpy.mock.calls[0]!; + expect(opts.adapter.name).toBe("dlv"); + expect(opts.extraLaunchArguments).toEqual({ mode: "debug" }); + } finally { + await fs.rm(cwd, { recursive: true, force: true }); + } + } finally { + sessionLaunchSpy.mockRestore(); + } + }); + it("dlv launch with a compiled binary switches mode from debug to exec", async () => { const dapModule = await import("../../src/dap"); const dlvAdapter: DapResolvedAdapter = {