Files
multica/server/pkg/agent/cursor_invocation.go
carmake 805071b5b1 fix(agent/cursor): route Windows launcher through PowerShell -File to preserve multi-line prompts (#1709)
On Windows the official cursor-agent installer ships cursor-agent.cmd whose
body is `powershell ... -File cursor-agent.ps1 %*`. CreateProcess for a .cmd
file goes through cmd.exe, and `%*` in a batch file is expanded by
re-tokenising the original command line, which mangles arguments containing
newlines or other whitespace - most notably a long, multi-line `-p <prompt>`.
The agent then only sees a truncated prompt and fails with "Workspace Trust
Required" or exits 1 immediately.

When LookPath resolves cursor-agent to a .cmd/.bat launcher and a sibling
cursor-agent.ps1 exists, invoke PowerShell directly with `-File <ps1>` so
Go's os/exec passes each argv as a discrete token. This is exactly what the
.cmd does internally; we just skip the cmd.exe re-tokenisation step.
PowerShell host resolution prefers pwsh.exe (PS 7) on PATH, then
powershell.exe on PATH, and finally falls back to
%SystemRoot%\System32\WindowsPowerShell\v1.0.

Platform-specific code is split via build tags
(cursor_invocation_windows.go / cursor_invocation_other.go) so non-Windows
builds carry no Windows-only dependencies. The lookup is exposed as a
package variable to make the Windows path fully unit-testable without
spawning real PowerShell. Five unit tests cover: passthrough on non-launcher
targets, successful rewrite with a multi-line prompt, .exe direct launch
(skip), missing .ps1 (skip), and missing PowerShell host (skip).

The change leaves macOS / Linux behaviour entirely untouched and stays on
the official cursor-agent launch chain - no node.exe direct invocation, no
prompt mutation, no extra flags.

Closes #1297

Made-with: Cursor
2026-04-29 14:00:15 +08:00

29 lines
1.3 KiB
Go

package agent
import "log/slog"
// chooseCursorInvocation selects the actual program (argv[0]) and the full
// argv to spawn a cursor-agent run.
//
// Background:
// - On macOS/Linux, cursor-agent is a real binary and we can pass argv
// directly via os/exec — no rewriting needed.
// - On Windows, the official installer ships cursor-agent.cmd whose body is
// "powershell ... -File cursor-agent.ps1 %*". CreateProcess for a .cmd
// file goes through cmd.exe, and %* in a .cmd batch file is expanded by
// re-tokenising the original command line, which mangles arguments that
// contain newlines or other whitespace (e.g. multi-line `-p` prompts).
// To stay on the official launch path while avoiding that re-tokenisation,
// we resolve cursor-agent.ps1 next to the .cmd and invoke PowerShell with
// `-File <ps1>` directly, letting Go pass each argv as a separate token.
//
// The Windows-specific behaviour is implemented in
// cursor_invocation_windows.go; on other platforms we fall through to a
// passthrough.
func chooseCursorInvocation(execName, lookedUp string, args []string, logger *slog.Logger) (string, []string) {
if argv0, full, ok := platformCursorInvocation(lookedUp, args, logger); ok {
return argv0, full
}
return execName, args
}