Files
multica/server/pkg/agent/pi_invocation.go
Bohan Jiang 2bda4065d0 MUL-2708: fix(agent): preserve multi-line Pi prompt on Windows by bypassing the .cmd shim (#3417)
Pi is installed on Windows via npm, which lays down `pi.cmd` → `pi.ps1`
→ `node_modules/@mariozechner/pi-coding-agent/dist/cli.js`. The daemon
spawns Pi with `exec.Command("pi", ...)`; PATHEXT resolves that to
`pi.cmd`, and cmd.exe expands `%*` in the shim by re-tokenising the
original command line, which truncates any argv containing newlines.

buildPiArgs passes the full prompt as the last positional argv, so the
multi-line system+user prompt is silently cut at the first newline
before it reaches the JS entrypoint. The session JSONL then records
only the first line ("You are running as a chat assistant for a Multica
workspace.") and Pi replies as if the user message were missing
(GitHub multica-ai/multica#3306).

Mirror the existing cursor-agent fix: when LookPath resolves Pi to a
.cmd/.bat launcher and a sibling pi.ps1 exists, invoke PowerShell with
`-File <ps1>` directly and forward each arg as a discrete token. This
keeps us on the official launch path while skipping the cmd.exe %*
re-expansion. Falls back to the original launcher when pi.ps1 or
PowerShell can't be located.

The Windows test asserts the rewrite produces the expected argv and
that the multi-line positional prompt survives unchanged.

Co-authored-by: J <j@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-05-28 12:36:16 +08:00

33 lines
1.4 KiB
Go

package agent
import "log/slog"
// choosePiInvocation selects the actual program (argv[0]) and the full
// argv to spawn a Pi run.
//
// Background:
// - On macOS/Linux, the npm-installed `pi` binstub is a shebang script
// that execs node directly with the JS entrypoint, so argv passes
// through unchanged.
// - On Windows, the npm installer ships `pi.cmd` whose body is
// "powershell ... -File pi.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
// containing newlines or other whitespace — for Pi, that's the
// multi-line positional prompt passed by buildPiArgs. Symptom: the
// Pi session JSONL records only the first line of the prompt
// (#3306). To stay on the official launch path while avoiding that
// re-tokenisation, we resolve pi.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
// pi_invocation_windows.go; on other platforms we fall through to a
// passthrough.
func choosePiInvocation(execName, lookedUp string, args []string, logger *slog.Logger) (string, []string) {
if argv0, full, ok := platformPiInvocation(lookedUp, args, logger); ok {
return argv0, full
}
return execName, args
}