8922a9a2ee
* 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.
87 lines
3.1 KiB
Go
87 lines
3.1 KiB
Go
package lifecycle
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/kandev/kandev/internal/agent/agents"
|
|
"github.com/kandev/kandev/internal/agentruntime"
|
|
)
|
|
|
|
// TestPreferNativeBinary_NonNativeAgent verifies that an agent which does not
|
|
// implement NativeBinaryAgent never prefers a native binary, regardless of
|
|
// runtime or metadata.
|
|
func TestPreferNativeBinary_NonNativeAgent(t *testing.T) {
|
|
m := &Manager{}
|
|
ag := agents.NewClaudeACP() // does not implement NativeBinaryAgent
|
|
meta := map[string]interface{}{MetadataKeyNativeBinary: "claude"}
|
|
if m.preferNativeBinary(ag, agentruntime.RuntimeStandalone, meta) {
|
|
t.Error("non-native agent should never prefer a native binary")
|
|
}
|
|
}
|
|
|
|
// TestPreferNativeBinary_SSH verifies the SSH branch reads the preflight result
|
|
// from metadata: a recorded hit (matching the agent's binary name) prefers
|
|
// native; an absent or mismatched key falls back to npx.
|
|
func TestPreferNativeBinary_SSH(t *testing.T) {
|
|
m := &Manager{}
|
|
ag := agents.NewCopilotACP()
|
|
|
|
hit := map[string]interface{}{MetadataKeyNativeBinary: "copilot"}
|
|
if !m.preferNativeBinary(ag, agentruntime.RuntimeSSH, hit) {
|
|
t.Error("ssh with recorded copilot hit should prefer native binary")
|
|
}
|
|
|
|
if m.preferNativeBinary(ag, agentruntime.RuntimeSSH, nil) {
|
|
t.Error("ssh without a recorded hit should fall back to npx")
|
|
}
|
|
|
|
mismatch := map[string]interface{}{MetadataKeyNativeBinary: "something-else"}
|
|
if m.preferNativeBinary(ag, agentruntime.RuntimeSSH, mismatch) {
|
|
t.Error("ssh with a mismatched binary name should fall back to npx")
|
|
}
|
|
}
|
|
|
|
// TestPreferNativeBinary_ContainerizedKeepsNpx verifies containerized runtimes
|
|
// keep npx even when metadata claims a hit — their controlled image pulls npx
|
|
// from the public registry, so the private-registry slowdown does not apply.
|
|
func TestPreferNativeBinary_ContainerizedKeepsNpx(t *testing.T) {
|
|
m := &Manager{}
|
|
ag := agents.NewCopilotACP()
|
|
meta := map[string]interface{}{MetadataKeyNativeBinary: "copilot"}
|
|
for _, rt := range []agentruntime.Runtime{
|
|
agentruntime.RuntimeDocker,
|
|
agentruntime.RuntimeSprites,
|
|
agentruntime.RuntimeRemoteDocker,
|
|
} {
|
|
if m.preferNativeBinary(ag, rt, meta) {
|
|
t.Errorf("runtime %q should keep npx", rt)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestPreferNativeBinary_StandaloneLooksUpPath verifies the standalone branch
|
|
// probes the backend host's PATH (which is the execution environment for
|
|
// local_pc / worktree). A binary on PATH prefers native; an empty PATH does not.
|
|
func TestPreferNativeBinary_StandaloneLooksUpPath(t *testing.T) {
|
|
m := &Manager{}
|
|
ag := agents.NewCopilotACP()
|
|
|
|
dir := t.TempDir()
|
|
bin := filepath.Join(dir, "copilot")
|
|
if err := os.WriteFile(bin, []byte("#!/bin/sh\n"), 0o755); err != nil {
|
|
t.Fatalf("write fake copilot: %v", err)
|
|
}
|
|
|
|
t.Setenv("PATH", dir)
|
|
if !m.preferNativeBinary(ag, agentruntime.RuntimeStandalone, nil) {
|
|
t.Error("standalone with copilot on PATH should prefer native binary")
|
|
}
|
|
|
|
t.Setenv("PATH", t.TempDir()) // empty dir, no copilot
|
|
if m.preferNativeBinary(ag, agentruntime.RuntimeStandalone, nil) {
|
|
t.Error("standalone without copilot on PATH should fall back to npx")
|
|
}
|
|
}
|