Files
multica/server/pkg/agent/copilot_invocation_windows.go
cd9b956269 fix(agent): spawn Copilot's native binary on Windows so the prompt survives (MUL-5586) (#6236)
On Windows the daemon passes the full multi-line prompt as `-p <prompt>` but
spawns npm's `copilot.cmd`, which we already rewrite to
`powershell -File copilot.ps1`. Neither launcher can carry that argument:

- `copilot.cmd` forwards with `%*`, which cmd.exe expands by re-tokenising
  the raw command line.
- `copilot.ps1` ends in `& node.exe npm-loader.js $args`, and PowerShell
  re-serialises `$args` onto node's command line. Under Windows PowerShell
  5.1 (and pwsh <= 7.2, which default to Legacy native argument passing)
  embedded double quotes are not re-escaped, so the prompt is re-tokenised.

Copilot then sees several argv tokens where one was intended and refuses the
run with "It looks like your prompt was not quoted, so the extra words were
treated as separate arguments" — the same defect class already fixed for
cursor-agent in #5649, except Copilot has no stdin prompt channel to escape
through, so the prompt must stay on the command line and the launchers have
to go.

Copilot CLI ships a native per-platform binary and `npm-loader.js` does
nothing but `spawnSync` it with argv untouched, so resolve
`copilot-win32-{x64,arm64}\copilot.exe` out of the npm layout and spawn it
directly. That leaves exactly one hop, Go -> native binary, and Go's
syscall.EscapeArg is the exact inverse of the CRT parsing that binary uses.
This mirrors resolveOpenCodeNativeFromShim / resolveDevecoNativeFromShim.

Both the nested (current npm) and hoisted (older npm) platform-package
locations are probed; when neither resolves, we keep falling back to the
PowerShell launcher, which is still better than cmd.exe.

Co-authored-by: Bohan-J <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-07-31 16:09:36 +08:00

33 lines
1.2 KiB
Go

//go:build windows
package agent
import (
"log/slog"
"os"
)
// platformCopilotInvocation keeps the multi-line -p prompt built by
// buildCopilotArgs intact on Windows by spawning the native copilot.exe
// bundled in the npm package, so neither cmd.exe's %* expansion nor
// PowerShell's $args re-serialisation can re-tokenise it (see
// resolveCopilotNativeFromShim for why neither launcher can be made safe).
//
// When the native binary can't be located — a partial install, or a Copilot
// build old enough to predate the platform packages — fall back to
// `powershell -File copilot.ps1`, which still avoids the strictly worse
// cmd.exe layer. powerShellLookup and rewriteCmdToPS1 are defined in
// cursor_invocation_windows.go.
func platformCopilotInvocation(lookedUp string, args []string, logger *slog.Logger) (string, []string, bool) {
if native := resolveCopilotNativeFromShim(lookedUp, os.Stat); native != "" {
if logger != nil {
logger.Info("copilot: spawning the bundled native binary to keep argv intact",
"shim", lookedUp,
"native", native,
)
}
return native, args, true
}
return rewriteCmdToPS1("copilot", lookedUp, args, logger)
}