Files
kandev/apps/backend/internal/agent/agents/copilot_acp_test.go
T
José Almeida 8922a9a2ee fix(backend): prefer local copilot CLI over npx at launch (#1384)
* fix(backend): prefer local copilot CLI over npx at launch

IsInstalled probed the copilot binary for the UI badge but launch always
ran `npx -y @github/copilot`, paying an npm registry round-trip every
start — minutes behind a private registry. Reuse the env-correct binary
check (standalone LookPath, SSH preflight probe) so when copilot is on
PATH the lifecycle runs it directly; containerized runtimes keep npx.

* fix(backend): make SSH native-binary probe non-fatal on transport error

The native-binary probe is an optimisation, not a requirement. A transient
SSH transport error (MaxSessions, reset) made "Verifying agent binary"
abort the launch, blaming copilot before npx was tried. Treat probe
transport errors as a miss and fall through to the required default-command
probe, which reports failures with proper context. Also call
NativeBinaryName() once.

* fix(backend): clear stale native-binary metadata before SSH probe

A pre-existing or stale MetadataKeyNativeBinary in req.Metadata could make
the command builder prefer copilot --acp on an npx-only host even when this
probe misses. Clear it up front so only a positive probe re-sets it.

* refactor(backend): use resolved RuntimeName for native-binary check

buildExecutionFromInstance re-derived the runtime from req.ExecutorType
while promoteWorkspaceExecution read execution.RuntimeName. Use the already
resolved execution.RuntimeName (set from rt.Name()) in both, so the two call
sites are consistent.

* fix(backend): reflect local copilot CLI in profile command preview

The profile setup command preview always rendered `npx -y @github/copilot`
because PreviewAgentCommand built the command without the native-binary
probe. The preview is not bound to an executor, so assume the default
standalone host and probe PATH (mirroring the standalone launch path): show
`copilot --acp` when the CLI is installed, npx otherwise.
2026-06-18 13:07:40 +01:00

90 lines
3.0 KiB
Go

package agents
import "testing"
// TestCopilotACP_PermissionSettings_Curated verifies the four curated flag
// suggestions surfaced to the profile-creation UI. `allow_all_tools` is
// enabled by default so autonomous runs don't stall on per-tool-call
// permission prompts; the other --allow-all-* and --no-ask-user toggles
// stay off as safe defaults.
func TestCopilotACP_PermissionSettings_Curated(t *testing.T) {
ag := NewCopilotACP()
settings := ag.PermissionSettings()
// Pin the expected default for each curated key. Anything not listed
// here defaults off.
wantDefault := map[string]bool{
"allow_all_tools": true,
"allow_all_paths": false,
"allow_all_urls": false,
"no_ask_user": false,
}
for k, want := range wantDefault {
s, ok := settings[k]
if !ok {
t.Fatalf("missing curated setting %q", k)
}
if !s.Supported {
t.Errorf("%q should be Supported=true", k)
}
if s.Default != want {
t.Errorf("%q Default = %v, want %v", k, s.Default, want)
}
if s.ApplyMethod != "cli_flag" {
t.Errorf("%q should apply as cli_flag, got %q", k, s.ApplyMethod)
}
if s.CLIFlag == "" {
t.Errorf("%q must specify a CLIFlag", k)
}
}
}
// TestCopilotACP_BuildCommand_NoCLIFlagSpecialCasing confirms BuildCommand
// itself is flag-agnostic: the cli_flags list travels through
// CommandBuilder.BuildCommand (in the lifecycle package) which appends the
// tokens. This test pins the bare command so any future agent drift is loud.
func TestCopilotACP_BuildCommand_NoCLIFlagSpecialCasing(t *testing.T) {
ag := NewCopilotACP()
cmd := ag.BuildCommand(CommandOptions{})
got := cmd.Args()
want := []string{"npx", "-y", "@github/copilot", "--acp"}
if len(got) != len(want) {
t.Fatalf("argv length mismatch\n got: %#v\n want: %#v", got, want)
}
for i := range got {
if got[i] != want[i] {
t.Errorf("argv[%d] mismatch: got %q, want %q", i, got[i], want[i])
}
}
}
// TestCopilotACP_BuildCommand_PreferNativeBinary verifies that when the
// lifecycle has found the standalone `copilot` CLI in the execution
// environment, BuildCommand emits it directly instead of `npx -y <pkg>` —
// skipping the npm registry round-trip that makes launches slow behind a
// private registry.
func TestCopilotACP_BuildCommand_PreferNativeBinary(t *testing.T) {
ag := NewCopilotACP()
if name := ag.NativeBinaryName(); name != "copilot" {
t.Fatalf("NativeBinaryName() = %q, want %q", name, "copilot")
}
native := ag.BuildCommand(CommandOptions{PreferNativeBinary: true}).Args()
wantNative := []string{"copilot", "--acp"}
if len(native) != len(wantNative) {
t.Fatalf("native argv mismatch\n got: %#v\n want: %#v", native, wantNative)
}
for i := range native {
if native[i] != wantNative[i] {
t.Errorf("native argv[%d] = %q, want %q", i, native[i], wantNative[i])
}
}
// Default (binary absent) still uses npx.
npx := ag.BuildCommand(CommandOptions{}).Args()
if len(npx) == 0 || npx[0] != "npx" {
t.Errorf("default command should start with npx, got %#v", npx)
}
}