Files
kandev/apps/backend/internal/agent/runtime/lifecycle/native_binary.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

48 lines
1.8 KiB
Go

package lifecycle
import (
"os/exec"
"github.com/kandev/kandev/internal/agent/agents"
"github.com/kandev/kandev/internal/agentruntime"
)
// MetadataKeyNativeBinary, when present in a launch's metadata, names the
// agent's standalone CLI binary that was found in the execution environment.
// It is set per launch by the remote binary probe (SSH preflight) and read by
// preferNativeBinary so the command builder emits the native binary instead of
// `npx -y <pkg>`. Not persisted across resumes — the probe re-runs each launch
// so a binary installed (or removed) between runs is always reflected.
const MetadataKeyNativeBinary = "native_binary"
// preferNativeBinary reports whether agentConfig's standalone CLI should be
// used instead of `npx -y <pkg>` for this launch. It honours the same
// "is the binary actually present in the execution environment?" check the SSH
// preflight already performs, applied per runtime:
//
// - standalone (local_pc / worktree): the agent subprocess runs directly on
// this host, so the backend's PATH is the execution environment — probe it
// with exec.LookPath.
// - ssh: the remote preflight probed the binary and recorded a hit in meta.
// - docker / sprites / remote_docker: a controlled image that pulls npx from
// the public registry — keep npx.
func (m *Manager) preferNativeBinary(agentConfig agents.Agent, runtime agentruntime.Runtime, meta map[string]interface{}) bool {
nb, ok := agentConfig.(agents.NativeBinaryAgent)
if !ok {
return false
}
name := nb.NativeBinaryName()
if name == "" {
return false
}
switch runtime {
case agentruntime.RuntimeStandalone:
_, err := exec.LookPath(name)
return err == nil
case agentruntime.RuntimeSSH:
return getMetadataString(meta, MetadataKeyNativeBinary) == name
default:
return false
}
}