Files
multica/server/pkg/agent/pi_invocation_test.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

37 lines
1.1 KiB
Go

package agent
import (
"io"
"log/slog"
"path/filepath"
"reflect"
"testing"
)
// TestChoosePiInvocation_PassthroughForNonLauncher verifies that when the
// resolved executable is not a Windows .cmd/.bat launcher, both argv[0] and
// the argv list are returned unchanged on every platform. This guards
// against accidental rewriting on macOS/Linux and for direct binary
// launches on Windows.
func TestChoosePiInvocation_PassthroughForNonLauncher(t *testing.T) {
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
execName := "pi"
lookedUp := filepath.Join(t.TempDir(), "pi") // no .cmd / .bat
args := []string{
"-p",
"--mode", "json",
"--session", "/tmp/pi-session.jsonl",
"You are running as a chat assistant for a Multica workspace.\n\nUser message:\n我需要创建一个issue\n",
}
gotExec, gotArgs := choosePiInvocation(execName, lookedUp, args, logger)
if gotExec != execName {
t.Errorf("argv0 changed unexpectedly: got %q want %q", gotExec, execName)
}
if !reflect.DeepEqual(gotArgs, args) {
t.Errorf("argv changed unexpectedly:\n got %#v\n want %#v", gotArgs, args)
}
}