From 242d8b5e78db34feb0e7dca979ff563e5e7ac000 Mon Sep 17 00:00:00 2001 From: Spencer Smith Date: Sat, 4 Jul 2026 00:32:12 -0600 Subject: [PATCH 1/2] fix(providers): CursorAgent deliver prompt via @path to saved file 99 saves the full prompt to tmp/99-*-prompt before make_request. Pass a single-line --print message referencing that file with @ instead of putting the multiline XML query on argv. Closes ThePrimeagen/99#180 --- lua/99/providers.lua | 27 +++++++++++++++++++++++++-- lua/99/test/providers_spec.lua | 29 ++++++++++++++++++----------- 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/lua/99/providers.lua b/lua/99/providers.lua index 2fa2f83b..6b5d6798 100644 --- a/lua/99/providers.lua +++ b/lua/99/providers.lua @@ -231,10 +231,33 @@ end --- @class CursorAgentProvider : _99.Providers.BaseProvider local CursorAgentProvider = setmetatable({}, { __index = BaseProvider }) +--- @param context _99.Prompt +--- @return string +local function cursor_agent_prompt_file(context) + local path = vim.fn.fnamemodify(context.tmp_file .. "-prompt", ":p") + path = vim.fs.normalize(path) + if vim.fn.filereadable(path) ~= 1 then + path = vim.fs.normalize(vim.fn.fnamemodify( + vim.fs.joinpath(vim.fn.getcwd(), context.tmp_file .. "-prompt"), + ":p" + )) + end + return path +end + +--- @param context _99.Prompt +--- @return string +local function cursor_agent_print_prompt(context) + return string.format( + "Read and follow every instruction in @%s using your file tools, then complete the task exactly as specified in that file.", + cursor_agent_prompt_file(context) + ) +end + --- @param query string --- @param context _99.Prompt --- @return string[] -function CursorAgentProvider._build_command(_, query, context) +function CursorAgentProvider._build_command(_, _query, context) -- TODO: trust is sort of a hack and should probably be removed in favor of having a -- trust flag from the setup call return { @@ -244,7 +267,7 @@ function CursorAgentProvider._build_command(_, query, context) "--model", context.model, "--print", - query, + cursor_agent_print_prompt(context), } end diff --git a/lua/99/test/providers_spec.lua b/lua/99/test/providers_spec.lua index 803cd054..60636862 100644 --- a/lua/99/test/providers_spec.lua +++ b/lua/99/test/providers_spec.lua @@ -48,17 +48,24 @@ describe("providers", function() end) describe("CursorAgentProvider", function() - it("builds correct command with model", function() - local request = { model = "anthropic/claude-sonnet-4-5" } - local cmd = - Providers.CursorAgentProvider._build_command(nil, "test query", request) - eq({ - "cursor-agent", - "--model", - "anthropic/claude-sonnet-4-5", - "--print", - "test query", - }, cmd) + it("uses @ prompt file instead of XML on argv", function() + local request = { model = "sonnet-4.5", tmp_file = "tmp/99-1234" } + local cmd = Providers.CursorAgentProvider._build_command( + nil, + "ignored", + request + ) + local print_arg = cmd[#cmd] + eq("cursor-agent", cmd[1]) + eq("--trust", cmd[2]) + eq("--force", cmd[3]) + eq("--model", cmd[4]) + eq("sonnet-4.5", cmd[5]) + eq("--print", cmd[6]) + assert(print_arg:match("@")) + assert(print_arg:match("99%-1234%-prompt")) + assert(not print_arg:match("")) + assert(not print_arg:match("\n")) end) it("has correct default model", function() From 1629aec0479f7103ee0aa56ef0bb63e7fef43cf7 Mon Sep 17 00:00:00 2001 From: Spencer Smith Date: Sat, 4 Jul 2026 00:32:37 -0600 Subject: [PATCH 2/2] fix(providers): CursorAgent --workspace, cwd, and tmp path resolution Pass --workspace and set vim.system cwd to the project root. Resolve context.tmp_file under that root so agent writes and 99 reads the same TEMP_FILE path. Closes ThePrimeagen/99#181 --- lua/99/providers.lua | 83 +++++++++++++++++++++++++++++----- lua/99/test/providers_spec.lua | 12 +++-- 2 files changed, 78 insertions(+), 17 deletions(-) diff --git a/lua/99/providers.lua b/lua/99/providers.lua index 6b5d6798..f9527db7 100644 --- a/lua/99/providers.lua +++ b/lua/99/providers.lua @@ -233,24 +233,69 @@ local CursorAgentProvider = setmetatable({}, { __index = BaseProvider }) --- @param context _99.Prompt --- @return string -local function cursor_agent_prompt_file(context) - local path = vim.fn.fnamemodify(context.tmp_file .. "-prompt", ":p") - path = vim.fs.normalize(path) - if vim.fn.filereadable(path) ~= 1 then - path = vim.fs.normalize(vim.fn.fnamemodify( - vim.fs.joinpath(vim.fn.getcwd(), context.tmp_file .. "-prompt"), - ":p" - )) +local function cursor_agent_workspace(_context) + return vim.fn.getcwd() +end + +--- @param relative string +--- @param ws string +--- @return string[] +local function cursor_agent_tmp_paths(relative, ws) + local seen, out = {}, {} + + local function add(path) + path = vim.fs.normalize(vim.fn.fnamemodify(path, ":p")) + if not seen[path] then + seen[path] = true + out[#out + 1] = path + end + end + + if relative:match("^[%a]:[/\\]") or relative:match("^/") then + add(relative) + return out + end + + local rel = relative:gsub("^%.[\\/]", "") + for _, base in ipairs({ ws, vim.fn.getcwd() }) do + add(vim.fs.joinpath(base, rel)) + end + return out +end + +--- @param paths string[] +--- @return string, string|nil +local function cursor_agent_read_first(paths) + for _, path in ipairs(paths) do + local ok, lines = pcall(vim.fn.readfile, path) + if ok and #lines > 0 then + return table.concat(lines, "\n"), path + end end - return path + return "", nil end --- @param context _99.Prompt +--- @param ws string|nil --- @return string -local function cursor_agent_print_prompt(context) +local function cursor_agent_prompt_file(context, ws) + ws = ws or cursor_agent_workspace(context) + for _, path in ipairs(cursor_agent_tmp_paths(context.tmp_file .. "-prompt", ws)) do + if vim.fn.filereadable(path) == 1 then + return path + end + end + return cursor_agent_tmp_paths(context.tmp_file .. "-prompt", ws)[1] +end + +--- @param context _99.Prompt +--- @param ws string|nil +--- @return string +local function cursor_agent_print_prompt(context, ws) + local prompt_file = cursor_agent_prompt_file(context, ws) return string.format( "Read and follow every instruction in @%s using your file tools, then complete the task exactly as specified in that file.", - cursor_agent_prompt_file(context) + prompt_file ) end @@ -260,17 +305,31 @@ end function CursorAgentProvider._build_command(_, _query, context) -- TODO: trust is sort of a hack and should probably be removed in favor of having a -- trust flag from the setup call + local ws = cursor_agent_workspace(context) return { "cursor-agent", + "--workspace", + ws, "--trust", -- directories are always trusted and can be ran in "--force", -- allows for commands to run "--model", context.model, "--print", - cursor_agent_print_prompt(context), + cursor_agent_print_prompt(context, ws), } end +--- @param context _99.Prompt +--- @return boolean, string +function CursorAgentProvider:_retrieve_response(context) + local ws = cursor_agent_workspace(context) + local tmp = cursor_agent_read_first(cursor_agent_tmp_paths(context.tmp_file, ws)) + if tmp:match("%S") then + return true, tmp + end + return BaseProvider._retrieve_response(self, context) +end + --- @return string function CursorAgentProvider._get_provider_name() return "CursorAgentProvider" diff --git a/lua/99/test/providers_spec.lua b/lua/99/test/providers_spec.lua index 60636862..5022c845 100644 --- a/lua/99/test/providers_spec.lua +++ b/lua/99/test/providers_spec.lua @@ -57,11 +57,13 @@ describe("providers", function() ) local print_arg = cmd[#cmd] eq("cursor-agent", cmd[1]) - eq("--trust", cmd[2]) - eq("--force", cmd[3]) - eq("--model", cmd[4]) - eq("sonnet-4.5", cmd[5]) - eq("--print", cmd[6]) + eq("--workspace", cmd[2]) + eq(vim.fn.getcwd(), cmd[3]) + eq("--trust", cmd[4]) + eq("--force", cmd[5]) + eq("--model", cmd[6]) + eq("sonnet-4.5", cmd[7]) + eq("--print", cmd[8]) assert(print_arg:match("@")) assert(print_arg:match("99%-1234%-prompt")) assert(not print_arg:match(""))