Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
1657 lines
65 KiB
Go
1657 lines
65 KiB
Go
package agentextension
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
agentruntime "github.com/tutti-os/tutti/packages/agent/daemon/runtime"
|
|
runtimecmd "github.com/tutti-os/tutti/packages/agent/daemon/runtimecmd"
|
|
agentextensionbiz "github.com/tutti-os/tutti/services/tuttid/biz/agentextension"
|
|
agenttargetbiz "github.com/tutti-os/tutti/services/tuttid/biz/agenttarget"
|
|
workspacebiz "github.com/tutti-os/tutti/services/tuttid/biz/workspace"
|
|
agentextensiondata "github.com/tutti-os/tutti/services/tuttid/data/agentextension"
|
|
)
|
|
|
|
func TestAgentTargetSetupInstallsGenericExtensionRuntime(t *testing.T) {
|
|
t.Setenv("PATH", t.TempDir())
|
|
t.Setenv("TUTTI_INSTALL_SECRET", "must-not-leak")
|
|
runner := &fixtureInstallRunner{binary: "generic-agent", packageName: "@example/generic-agent", version: "1.2.3"}
|
|
service, targetID := setupFixture(
|
|
t, "generic", "Generic Agent", "@example/generic-agent", "1.2.3", "generic-agent", ">=1.2.3 <2.0.0",
|
|
runner, &probeTransport{},
|
|
)
|
|
|
|
initial, err := service.GetSetup(context.Background(), InstallPlanInput{WorkspaceID: "workspace-1", AgentTargetID: targetID})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if initial.Status != SetupNotInstalled || initial.Plan == nil || initial.Plan.PackageName != "@example/generic-agent" || initial.Plan.PackageVersion != "1.2.3" {
|
|
t.Fatalf("initial setup = %#v", initial)
|
|
}
|
|
started, err := service.Install(context.Background(), InstallInput{
|
|
WorkspaceID: "workspace-1", AgentTargetID: targetID,
|
|
PlanDigest: initial.Plan.PlanDigest, ClientActionID: "desktop-double-click-1",
|
|
})
|
|
if err != nil || started.Status != SetupInstalling || started.Action == nil {
|
|
t.Fatalf("start setup = %#v, error = %v", started, err)
|
|
}
|
|
if _, err := service.Install(context.Background(), InstallInput{
|
|
WorkspaceID: "workspace-1", AgentTargetID: targetID,
|
|
PlanDigest: initial.Plan.PlanDigest, ClientActionID: "desktop-double-click-1",
|
|
}); err != nil {
|
|
t.Fatalf("idempotent install: %v", err)
|
|
}
|
|
|
|
ready := waitForSetupStatus(t, service, targetID, SetupReady)
|
|
if ready.RuntimeSource != "managed" || ready.RuntimeVersion != "1.2.3" || ready.Plan != nil {
|
|
t.Fatalf("ready setup = %#v", ready)
|
|
}
|
|
if runner.callCount() != 1 {
|
|
t.Fatalf("install calls = %d, want 1", runner.callCount())
|
|
}
|
|
userEntry := filepath.Join(service.Plans.Manager.RuntimeBinDir, "generic-agent")
|
|
resolvedEntry, err := filepath.EvalSymlinks(userEntry)
|
|
if err != nil {
|
|
t.Fatalf("resolve user executable entry %q: %v", userEntry, err)
|
|
}
|
|
wantEntry := filepath.Join(
|
|
initial.Plan.InstallRoot,
|
|
"node_modules", "@example", "generic-agent", "bin", "generic-agent",
|
|
)
|
|
resolvedWantEntry, err := filepath.EvalSymlinks(wantEntry)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if resolvedEntry != resolvedWantEntry {
|
|
t.Fatalf("user executable entry = %q, want %q", resolvedEntry, resolvedWantEntry)
|
|
}
|
|
t.Setenv("PATH", service.Plans.Manager.RuntimeBinDir)
|
|
resolvedSetup, err := service.GetSetup(context.Background(), InstallPlanInput{WorkspaceID: "workspace-1", AgentTargetID: targetID})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if resolvedSetup.RuntimeSource != "managed" {
|
|
t.Fatalf("published user executable bypassed managed integrity checks: %#v", resolvedSetup)
|
|
}
|
|
if !pathWithin(runner.cwd, service.Plans.Manager.RuntimeInstallDir) {
|
|
t.Fatalf("installer cwd = %q, want user-local Tutti runtime scratch", runner.cwd)
|
|
}
|
|
for _, value := range runner.env {
|
|
if strings.HasPrefix(value, "TUTTI_INSTALL_SECRET=") {
|
|
t.Fatalf("installer environment leaked secret: %q", value)
|
|
}
|
|
}
|
|
for _, key := range []string{"npm_config_cache=", "npm_config_userconfig=", "npm_config_globalconfig="} {
|
|
if !environmentPathWithin(runner.env, key, runner.cwd) {
|
|
t.Fatalf("installer environment %s is not isolated under %q: %v", key, runner.cwd, runner.env)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAgentTargetSetupKeepsACPReadyWhenAccountUsageInstallFails(t *testing.T) {
|
|
t.Setenv("PATH", t.TempDir())
|
|
runner := &fixtureInstallRunner{
|
|
binary: "generic-agent", packageName: "@example/generic-agent", version: "1.2.3",
|
|
failPackage: "@example/gemini-account-usage@1.0.0",
|
|
}
|
|
service, targetID := setupFixture(
|
|
t, "generic", "Generic Agent", "@example/generic-agent", "1.2.3", "generic-agent", ">=1.2.3 <2.0.0",
|
|
runner, &probeTransport{},
|
|
)
|
|
manifest := testManifest()
|
|
manifest.AgentKey = "generic"
|
|
manifest.Version = "1.0.1"
|
|
manifest.Name = "Generic Agent"
|
|
manifest.Profiles.AccountUsage = "profiles/account-usage.json"
|
|
manifest.Runtime.Install.Args = []string{"install", "--prefix", "${installRoot}", "@example/generic-agent@1.2.3"}
|
|
manifest.Runtime.Launch.Executable = "${installRoot}/node_modules/.bin/generic-agent"
|
|
discovery := `{"schemaVersion":"tutti.agent.discovery.v1","candidates":[{"binaryNames":["generic-agent"],"version":{"args":["--version"],"constraint":">=1.2.3 <2.0.0"},"launchArgs":["--acp"],"probe":{"kind":"acp-initialize","timeoutMs":5000}}]}`
|
|
installation, err := installTestPackage(
|
|
t, service.Plans.Manager, Release{AgentKey: "generic", Version: "1.0.1"},
|
|
testPackageZIPFor(t, manifest, discovery),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
launchRef, err := agenttargetbiz.CanonicalLaunchRefJSON(installation.Provider, agenttargetbiz.LaunchRef{
|
|
Type: agenttargetbiz.LaunchRefTypeAgentExtension, ExtensionInstallationID: installation.ID,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
store := service.Plans.Targets.(*targetStoreStub)
|
|
target := store.targets[targetID]
|
|
target.LaunchRefJSON = launchRef
|
|
store.targets[targetID] = target
|
|
|
|
initial, err := service.GetSetup(context.Background(), InstallPlanInput{WorkspaceID: "workspace-1", AgentTargetID: targetID})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := service.Install(context.Background(), InstallInput{
|
|
WorkspaceID: "workspace-1", AgentTargetID: targetID,
|
|
PlanDigest: initial.Plan.PlanDigest, ClientActionID: "optional-companion-failure",
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ready := waitForSetupStatus(t, service, targetID, SetupReady)
|
|
deadline := time.Now().Add(5 * time.Second)
|
|
for runner.callCount() < 2 && time.Now().Before(deadline) {
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
if ready.RuntimeSource != "managed" || runner.callCount() < 2 {
|
|
t.Fatalf("setup after companion failure = %#v, install calls = %d", ready, runner.callCount())
|
|
}
|
|
failureScope := accountUsageCompanionFailureScope(targetID, installation.ID)
|
|
var failure *agentextensionbiz.AccountUsageCompanionFailure
|
|
for failure == nil {
|
|
failure, err = service.AccountUsageFailures.Read(context.Background(), failureScope)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if time.Now().After(deadline) {
|
|
t.Fatal("account usage companion failure was not persisted")
|
|
}
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
if err := service.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if failure.ErrorCode != "install_failed" || failure.RuntimeIdentity == "" ||
|
|
failure.ConsecutiveFailures < 1 || failure.NextAttemptAtUnixMS <= failure.LastAttemptAtUnixMS {
|
|
t.Fatalf("persisted account usage failure = %#v", failure)
|
|
}
|
|
retryRunner := &fixtureInstallRunner{
|
|
binary: "generic-agent", packageName: "@example/generic-agent", version: "1.2.3",
|
|
}
|
|
restarted := NewSetupService(context.Background())
|
|
restarted.Plans = service.Plans
|
|
restarted.Transport = service.Transport
|
|
restarted.Actions = service.Actions
|
|
restarted.AccountUsageFailures = service.AccountUsageFailures
|
|
restarted.Discovery = service.Discovery
|
|
restarted.Runner = retryRunner
|
|
var retryClock atomic.Int64
|
|
retryClock.Store(failure.LastAttemptAtUnixMS)
|
|
restarted.accountUsageNow = func() time.Time { return time.UnixMilli(retryClock.Load()) }
|
|
if err := restarted.StartAccountUsageCompanionReconciler(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { _ = restarted.Close() })
|
|
companionPlan, err := restarted.Plans.GetInstallPlan(context.Background(), InstallPlanInput{
|
|
WorkspaceID: "workspace-1", AgentTargetID: targetID,
|
|
})
|
|
if err != nil || companionPlan.AccountUsage == nil {
|
|
t.Fatalf("account usage retry plan = %#v, error = %v", companionPlan.AccountUsage, err)
|
|
}
|
|
time.Sleep(100 * time.Millisecond)
|
|
if retryRunner.callCount() != 0 {
|
|
t.Fatalf("account usage retry ignored persisted backoff: calls=%d", retryRunner.callCount())
|
|
}
|
|
retryClock.Store(failure.NextAttemptAtUnixMS + 1)
|
|
restarted.WakeAccountUsageCompanionReconciler()
|
|
activationPath := filepath.Join(companionPlan.AccountUsage.InstallRoot, "activation.json")
|
|
deadline = time.Now().Add(5 * time.Second)
|
|
for {
|
|
_, statErr := os.Stat(activationPath)
|
|
if statErr == nil {
|
|
break
|
|
}
|
|
if time.Now().After(deadline) {
|
|
t.Fatalf("account usage companion was not recovered after restart: %v", statErr)
|
|
}
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
if retryRunner.callCount() != 1 {
|
|
t.Fatalf("account usage restart retry calls = %d, want 1", retryRunner.callCount())
|
|
}
|
|
deadline = time.Now().Add(5 * time.Second)
|
|
for {
|
|
persisted, readErr := restarted.AccountUsageFailures.Read(context.Background(), failureScope)
|
|
if readErr != nil {
|
|
t.Fatal(readErr)
|
|
}
|
|
if persisted == nil {
|
|
break
|
|
}
|
|
if time.Now().After(deadline) {
|
|
t.Fatalf("recovered account usage failure = %#v", persisted)
|
|
}
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
usage, err := (AccountUsageService{Manager: service.Plans.Manager, Targets: store}).Probe(context.Background(), targetID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if usage.Outcome != "error" || usage.ErrorCode != "runtime_unavailable" {
|
|
t.Fatalf("account usage after companion failure = %#v", usage)
|
|
}
|
|
}
|
|
|
|
func TestAgentTargetSetupSurfacesAccountFailureReason(t *testing.T) {
|
|
t.Setenv("PATH", t.TempDir())
|
|
transport := &probeTransport{}
|
|
service, targetID := setupFixture(
|
|
t, "generic", "Generic Agent", "@example/generic-agent", "1.2.3", "generic-agent", ">=1.2.3 <2.0.0",
|
|
&fixtureInstallRunner{binary: "generic-agent", packageName: "@example/generic-agent", version: "1.2.3"},
|
|
transport,
|
|
)
|
|
initial, err := service.GetSetup(context.Background(), InstallPlanInput{
|
|
WorkspaceID: "workspace-1", AgentTargetID: targetID,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := service.Install(context.Background(), InstallInput{
|
|
WorkspaceID: "workspace-1", AgentTargetID: targetID,
|
|
PlanDigest: initial.Plan.PlanDigest, ClientActionID: "account-failure-probe",
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
waitForSetupStatus(t, service, targetID, SetupReady)
|
|
|
|
tests := map[string]string{
|
|
`Kimi Code models endpoint rejected OAuth credentials: status code: 402, message: We're unable to verify your membership benefits. Please ensure your membership is active.`: agentruntime.FailureCodeSubscriptionRequired,
|
|
`Kimi Code request rejected OAuth credentials: status code: 403, message: You've reached your usage limit for this billing cycle.`: agentruntime.FailureCodeQuotaOrRateLimit,
|
|
`Kimi Code request rejected OAuth credentials: 402 Payment Required`: agentruntime.FailureCodeInsufficientCredits,
|
|
}
|
|
for detail, wantReason := range tests {
|
|
probeErr := fmt.Errorf("%w: %s", ErrRuntimeProbeFailed, detail)
|
|
if got := installErrorCode(probeErr); got != wantReason {
|
|
t.Fatalf("installErrorCode(%q) = %q, want %q", detail, got, wantReason)
|
|
}
|
|
transport.setSessionError(detail)
|
|
snapshot, err := service.GetSetup(context.Background(), InstallPlanInput{
|
|
WorkspaceID: "workspace-1", AgentTargetID: targetID,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if snapshot.Status != SetupFailed || snapshot.Reason != wantReason {
|
|
t.Fatalf("account failure setup = %#v, want reason %q", snapshot, wantReason)
|
|
}
|
|
}
|
|
installErr := fmt.Errorf("%w: npm registry returned 429 Too Many Requests", ErrRuntimeInstallFailed)
|
|
if got := installErrorCode(installErr); got != "install_failed" {
|
|
t.Fatalf("installErrorCode(%q) = %q, want install_failed", installErr, got)
|
|
}
|
|
}
|
|
|
|
func TestManagedBinaryVersionFixture(_ *testing.T) {
|
|
if os.Getenv("TUTTI_TEST_MANAGED_BINARY_VERSION") != "1" {
|
|
return
|
|
}
|
|
if logPath := os.Getenv("TUTTI_TEST_MANAGED_BINARY_VERSION_LOG"); logPath != "" {
|
|
file, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o600)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
if _, err := file.WriteString("probe\n"); err != nil {
|
|
_ = file.Close()
|
|
panic(err)
|
|
}
|
|
if err := file.Close(); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
fmt.Println("0.2.103")
|
|
if os.Getenv("TUTTI_TEST_MANAGED_BINARY_REPLACE") == "1" {
|
|
path, err := os.Executable()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
if err := os.WriteFile(path, []byte("replaced during version probe"), 0o700); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAgentTargetSetupReusesManagedRuntimeAcrossExtensionVersions(t *testing.T) {
|
|
t.Setenv("PATH", t.TempDir())
|
|
runner := &fixtureInstallRunner{binary: "generic-agent", packageName: "@example/generic-agent", version: "1.2.3"}
|
|
service, targetID := setupFixture(
|
|
t, "generic", "Generic Agent", "@example/generic-agent", "1.2.3", "generic-agent", ">=1.2.3 <2.0.0",
|
|
runner, &probeTransport{},
|
|
)
|
|
initial, err := service.GetSetup(context.Background(), InstallPlanInput{WorkspaceID: "workspace-1", AgentTargetID: targetID})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := service.Install(context.Background(), InstallInput{
|
|
WorkspaceID: "workspace-1", AgentTargetID: targetID,
|
|
PlanDigest: initial.Plan.PlanDigest, ClientActionID: "first-install",
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
firstReady := waitForSetupStatus(t, service, targetID, SetupReady)
|
|
if firstReady.RuntimeSource != "managed" {
|
|
t.Fatalf("first ready setup = %#v", firstReady)
|
|
}
|
|
var legacyActivation managedRuntimeActivation
|
|
if err := readJSON(filepath.Join(initial.Plan.InstallRoot, "activation.json"), &legacyActivation); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
legacyActivation.RuntimeIdentity = ""
|
|
if err := writeJSONAtomic(filepath.Join(initial.Plan.InstallRoot, "activation.json"), legacyActivation); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
legacyRoot := filepath.Join(service.Plans.Manager.RuntimeInstallDir, "generic", "1.0.0")
|
|
if err := os.Rename(initial.Plan.InstallRoot, legacyRoot); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
manifest := testManifest()
|
|
manifest.AgentKey = "generic"
|
|
manifest.Version = "1.0.1"
|
|
manifest.Name = "Generic Agent"
|
|
manifest.Runtime.Install.Args = []string{"install", "--prefix", "${installRoot}", "@example/generic-agent@1.2.3"}
|
|
manifest.Runtime.Launch.Executable = "${installRoot}/node_modules/.bin/generic-agent"
|
|
manifest.Profiles.AccountUsage = "profiles/account-usage.json"
|
|
discovery := `{"schemaVersion":"tutti.agent.discovery.v1","candidates":[{"binaryNames":["generic-agent"],"version":{"args":["--version"],"constraint":">=1.2.3 <2.0.0"},"launchArgs":["--acp"],"probe":{"kind":"acp-initialize","timeoutMs":5000}}]}`
|
|
next, err := installTestPackage(t, service.Plans.Manager, Release{AgentKey: "generic", Version: "1.0.1"}, testPackageZIPFor(t, manifest, discovery))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
launchRef, err := agenttargetbiz.CanonicalLaunchRefJSON(next.Provider, agenttargetbiz.LaunchRef{
|
|
Type: agenttargetbiz.LaunchRefTypeAgentExtension, ExtensionInstallationID: next.ID,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
store := service.Plans.Targets.(*targetStoreStub)
|
|
target := store.targets[targetID]
|
|
target.LaunchRefJSON = launchRef
|
|
store.targets[targetID] = target
|
|
|
|
snapshot, err := service.GetSetup(context.Background(), InstallPlanInput{WorkspaceID: "workspace-1", AgentTargetID: targetID})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if snapshot.Status != SetupReady || snapshot.RuntimeSource != "managed" || snapshot.RuntimeVersion != "1.2.3" {
|
|
var updatedProfile DiscoveryProfile
|
|
profileErr := readJSON(filepath.Join(next.PackageDir, next.Manifest.Profiles.Discovery), &updatedProfile)
|
|
_, resolveErr := service.Plans.Manager.resolveInstalledManagedRuntime(
|
|
context.Background(), next, updatedProfile, t.TempDir(),
|
|
)
|
|
t.Fatalf("reused runtime setup = %#v; profile error = %v; managed resolve error = %v", snapshot, profileErr, resolveErr)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(initial.Plan.InstallRoot, "activation.json")); err != nil {
|
|
t.Fatalf("adopted runtime root is unavailable: %v", err)
|
|
}
|
|
if _, err := os.Stat(legacyRoot); !errors.Is(err, os.ErrNotExist) {
|
|
t.Fatalf("legacy runtime root was not adopted away: %v", err)
|
|
}
|
|
if runner.callCount() != 1 {
|
|
t.Fatalf("status read installed or reinstalled a runtime: calls=%d", runner.callCount())
|
|
}
|
|
nextPlan, err := service.Plans.GetInstallPlan(context.Background(), InstallPlanInput{WorkspaceID: "workspace-1", AgentTargetID: targetID})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if nextPlan.InstallRoot != initial.Plan.InstallRoot || nextPlan.RuntimeIdentity != initial.Plan.RuntimeIdentity {
|
|
t.Fatalf("runtime identity changed across extension versions: first=%#v next=%#v", initial.Plan, nextPlan)
|
|
}
|
|
if nextPlan.AccountUsage == nil {
|
|
t.Fatal("account usage plan is unavailable after extension metadata update")
|
|
}
|
|
service.WakeAccountUsageCompanionReconciler()
|
|
activationPath := filepath.Join(nextPlan.AccountUsage.InstallRoot, "activation.json")
|
|
deadline := time.Now().Add(5 * time.Second)
|
|
for {
|
|
_, statErr := os.Stat(activationPath)
|
|
if statErr == nil {
|
|
break
|
|
}
|
|
if time.Now().After(deadline) {
|
|
t.Fatalf("account usage companion was not reconciled for reused runtime: %v", statErr)
|
|
}
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
if runner.callCount() != 2 {
|
|
t.Fatalf("runtime reuse calls = %d, want one ACP install and one companion install", runner.callCount())
|
|
}
|
|
usage, err := (AccountUsageService{Manager: service.Plans.Manager, Targets: store}).Probe(context.Background(), targetID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if usage.Outcome != "error" || usage.ErrorCode != "runtime_unavailable" {
|
|
t.Fatalf("migrated account usage = %#v", usage)
|
|
}
|
|
}
|
|
|
|
func TestAgentTargetSetupDoesNotOverwriteUserExecutable(t *testing.T) {
|
|
t.Setenv("PATH", t.TempDir())
|
|
service, targetID := setupFixture(
|
|
t, "generic", "Generic Agent", "@example/generic-agent", "1.2.3", "generic-agent", ">=1.2.3 <2.0.0",
|
|
&fixtureInstallRunner{binary: "generic-agent", packageName: "@example/generic-agent", version: "1.2.3"},
|
|
&probeTransport{},
|
|
)
|
|
userEntry := filepath.Join(service.Plans.Manager.RuntimeBinDir, "generic-agent")
|
|
if err := os.MkdirAll(filepath.Dir(userEntry), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(userEntry, []byte("user-owned\n"), 0o700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
initial, err := service.GetSetup(context.Background(), InstallPlanInput{WorkspaceID: "workspace-1", AgentTargetID: targetID})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := service.Install(context.Background(), InstallInput{
|
|
WorkspaceID: "workspace-1", AgentTargetID: targetID,
|
|
PlanDigest: initial.Plan.PlanDigest, ClientActionID: "user-entry-conflict",
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// A user-owned command is tolerated: activation succeeds and the user
|
|
// executable is preserved instead of failing the managed runtime.
|
|
waitForSetupStatus(t, service, targetID, SetupReady)
|
|
content, err := os.ReadFile(userEntry)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if string(content) != "user-owned\n" {
|
|
t.Fatalf("user executable was overwritten: %q", content)
|
|
}
|
|
if _, err := os.Stat(initial.Plan.InstallRoot); err != nil {
|
|
t.Fatalf("managed runtime missing after tolerated user command: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestAgentTargetSetupInstallsPinnedBinaryWithoutPublishingUserCommand(t *testing.T) {
|
|
t.Setenv("PATH", t.TempDir())
|
|
binary := managedBinaryFixtureBytes(t)
|
|
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, request *http.Request) {
|
|
if request.URL.Path != "/grok-0.2.103-test" {
|
|
http.NotFound(w, request)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "application/octet-stream")
|
|
_, _ = w.Write(binary)
|
|
}))
|
|
defer server.Close()
|
|
|
|
service, targetID := setupBinaryFixture(t, server, binary, false, &probeTransport{})
|
|
userEntry := filepath.Join(service.Plans.Manager.RuntimeBinDir, "grok")
|
|
foreignTarget := filepath.Join(t.TempDir(), "foreign-grok")
|
|
if err := os.WriteFile(foreignTarget, []byte("user-owned\n"), 0o700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.MkdirAll(filepath.Dir(userEntry), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.Symlink(foreignTarget, userEntry); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
initial, err := service.GetSetup(context.Background(), InstallPlanInput{WorkspaceID: "workspace-1", AgentTargetID: targetID})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if initial.Plan == nil || initial.Plan.Runner != "binary" || initial.Plan.PackageVersion != "0.2.103" || initial.Plan.PublishUserCommand {
|
|
t.Fatalf("binary install plan = %#v", initial.Plan)
|
|
}
|
|
if _, err := service.Install(context.Background(), InstallInput{
|
|
WorkspaceID: "workspace-1", AgentTargetID: targetID,
|
|
PlanDigest: initial.Plan.PlanDigest, ClientActionID: "binary-install",
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ready := waitForSetupStatus(t, service, targetID, SetupReady)
|
|
if ready.RuntimeSource != "managed" || ready.RuntimeVersion != "0.2.103" {
|
|
t.Fatalf("binary runtime setup = %#v", ready)
|
|
}
|
|
linkTarget, err := os.Readlink(userEntry)
|
|
if err != nil || linkTarget != foreignTarget {
|
|
t.Fatalf("foreign user entry changed: target=%q error=%v", linkTarget, err)
|
|
}
|
|
if _, err := os.Lstat(filepath.Join(service.Plans.Manager.RuntimeInstallDir, "grok", "bin", "grok")); !errors.Is(err, os.ErrNotExist) {
|
|
t.Fatalf("opt-out runtime published a stable command: %v", err)
|
|
}
|
|
binding, err := service.Plans.Manager.ResolveRuntimeForCWD(context.Background(), initial.Plan.ExtensionInstallationID, t.TempDir())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(binding.Command) == 0 || binding.Command[0] != filepath.Join(initial.Plan.InstallRoot, "grok") {
|
|
t.Fatalf("managed binary launch command = %#v", binding.Command)
|
|
}
|
|
if binding.ExecutableIdentity == nil || binding.ExecutableIdentity.SHA256 != initial.Plan.Artifact.SHA256 ||
|
|
binding.ExecutableIdentity.SizeBytes != initial.Plan.Artifact.SizeBytes {
|
|
t.Fatalf("managed binary executable identity = %#v", binding.ExecutableIdentity)
|
|
}
|
|
}
|
|
|
|
func TestAgentTargetSetupRejectsBinaryWithMismatchedSignedDigest(t *testing.T) {
|
|
t.Setenv("PATH", t.TempDir())
|
|
binary := managedBinaryFixtureBytes(t)
|
|
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
_, _ = w.Write(binary)
|
|
}))
|
|
defer server.Close()
|
|
declared := append([]byte(nil), binary...)
|
|
declared[0] ^= 0xff
|
|
service, targetID := setupBinaryFixture(t, server, declared, false, &probeTransport{})
|
|
initial, err := service.GetSetup(context.Background(), InstallPlanInput{WorkspaceID: "workspace-1", AgentTargetID: targetID})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := service.Install(context.Background(), InstallInput{
|
|
WorkspaceID: "workspace-1", AgentTargetID: targetID,
|
|
PlanDigest: initial.Plan.PlanDigest, ClientActionID: "binary-bad-digest",
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
failed := waitForSetupStatus(t, service, targetID, SetupFailed)
|
|
if failed.Action == nil || failed.Action.ErrorCode != "install_failed" ||
|
|
!strings.Contains(failed.Action.ErrorMessage, "SHA-256") {
|
|
t.Fatalf("binary digest failure = %#v", failed)
|
|
}
|
|
if _, err := os.Stat(initial.Plan.InstallRoot); !errors.Is(err, os.ErrNotExist) {
|
|
t.Fatalf("failed binary install activated a runtime: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestAgentTargetSetupRunsBinaryVersionProbeFromVerifiedSnapshot(t *testing.T) {
|
|
t.Setenv("PATH", t.TempDir())
|
|
t.Setenv("TUTTI_TEST_MANAGED_BINARY_REPLACE", "1")
|
|
binary := managedBinaryFixtureBytes(t)
|
|
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
_, _ = w.Write(binary)
|
|
}))
|
|
defer server.Close()
|
|
service, targetID := setupBinaryFixture(t, server, binary, false, &probeTransport{})
|
|
initial, err := service.GetSetup(context.Background(), InstallPlanInput{
|
|
WorkspaceID: "workspace-1", AgentTargetID: targetID,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := service.Install(context.Background(), InstallInput{
|
|
WorkspaceID: "workspace-1", AgentTargetID: targetID,
|
|
PlanDigest: initial.Plan.PlanDigest, ClientActionID: "binary-version-snapshot",
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
failed := waitForSetupStatus(t, service, targetID, SetupFailed)
|
|
if failed.Action == nil || failed.Action.ErrorCode != "version_check_failed" {
|
|
t.Fatalf("binary version snapshot failure = %#v", failed)
|
|
}
|
|
if _, err := os.Stat(initial.Plan.InstallRoot); !errors.Is(err, os.ErrNotExist) {
|
|
t.Fatalf("failed verified version probe activated a runtime: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestAgentTargetSetupPrefersCompatibleLocalCodeBuddy(t *testing.T) {
|
|
binDir := t.TempDir()
|
|
writeVersionExecutable(t, filepath.Join(binDir, "codebuddy"), "2.121.2")
|
|
t.Setenv("PATH", binDir)
|
|
runner := &fixtureInstallRunner{}
|
|
service, targetID := setupFixture(t, "codebuddy", "CodeBuddy Code", "@tencent-ai/codebuddy-code", "2.121.2", "codebuddy", ">=2.121.2 <3.0.0", runner, &probeTransport{})
|
|
snapshot, err := service.GetSetup(context.Background(), InstallPlanInput{
|
|
WorkspaceID: "workspace-1", AgentTargetID: targetID,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if snapshot.Status != SetupReady || snapshot.RuntimeSource != "local" || snapshot.RuntimeVersion != "2.121.2" || snapshot.Plan != nil {
|
|
t.Fatalf("local-first setup = %#v", snapshot)
|
|
}
|
|
if runner.callCount() != 0 {
|
|
t.Fatalf("local-first install calls = %d", runner.callCount())
|
|
}
|
|
manifest := testManifest()
|
|
manifest.AgentKey = "codebuddy"
|
|
manifest.Version = "1.0.1"
|
|
manifest.Name = "CodeBuddy Code"
|
|
manifest.Runtime.Install.Args = []string{"install", "--prefix", "${installRoot}", "@tencent-ai/codebuddy-code@2.121.2"}
|
|
manifest.Runtime.Launch.Executable = "${installRoot}/node_modules/.bin/codebuddy"
|
|
manifest.Profiles.AccountUsage = "profiles/account-usage.json"
|
|
discovery := `{"schemaVersion":"tutti.agent.discovery.v1","candidates":[{"binaryNames":["codebuddy"],"version":{"args":["--version"],"constraint":">=2.121.2 <3.0.0"},"launchArgs":["--acp"],"probe":{"kind":"acp-initialize","timeoutMs":5000}}]}`
|
|
installation, err := installTestPackage(
|
|
t,
|
|
service.Plans.Manager,
|
|
Release{AgentKey: "codebuddy", Version: "1.0.1"},
|
|
testPackageZIPFor(t, manifest, discovery),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
launchRef, err := agenttargetbiz.CanonicalLaunchRefJSON(installation.Provider, agenttargetbiz.LaunchRef{
|
|
Type: agenttargetbiz.LaunchRefTypeAgentExtension, ExtensionInstallationID: installation.ID,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
store := service.Plans.Targets.(*targetStoreStub)
|
|
target := store.targets[targetID]
|
|
target.LaunchRefJSON = launchRef
|
|
store.targets[targetID] = target
|
|
|
|
updated, err := service.GetSetup(context.Background(), InstallPlanInput{
|
|
WorkspaceID: "workspace-1", AgentTargetID: targetID,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if updated.Status != SetupReady || updated.RuntimeSource != "local" || runner.callCount() != 0 {
|
|
t.Fatalf("local runtime changed during extension update: snapshot=%#v calls=%d", updated, runner.callCount())
|
|
}
|
|
plan, err := service.Plans.GetInstallPlan(context.Background(), InstallPlanInput{
|
|
WorkspaceID: "workspace-1", AgentTargetID: targetID,
|
|
})
|
|
if err != nil || plan.AccountUsage == nil {
|
|
t.Fatalf("local account usage plan = %#v, error = %v", plan.AccountUsage, err)
|
|
}
|
|
service.WakeAccountUsageCompanionReconciler()
|
|
activationPath := filepath.Join(plan.AccountUsage.InstallRoot, "activation.json")
|
|
deadline := time.Now().Add(5 * time.Second)
|
|
for {
|
|
_, statErr := os.Stat(activationPath)
|
|
if statErr == nil {
|
|
break
|
|
}
|
|
if time.Now().After(deadline) {
|
|
t.Fatalf("account usage companion was not reconciled for local runtime: %v", statErr)
|
|
}
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
if runner.callCount() != 1 {
|
|
t.Fatalf("local runtime account usage install calls = %d, want 1", runner.callCount())
|
|
}
|
|
}
|
|
|
|
func TestManagedRuntimeReconcilerAutomaticallyInstallsClientPinnedRuntime(t *testing.T) {
|
|
binDir := t.TempDir()
|
|
writeVersionExecutable(t, filepath.Join(binDir, "codebuddy"), "2.121.2")
|
|
t.Setenv("PATH", binDir)
|
|
runner := &fixtureInstallRunner{
|
|
binary: "codebuddy",
|
|
packageName: "@tencent-ai/codebuddy-code",
|
|
version: "2.121.2",
|
|
}
|
|
service, targetID := setupFixture(
|
|
t,
|
|
"codebuddy",
|
|
"CodeBuddy Code",
|
|
"@tencent-ai/codebuddy-code",
|
|
"2.121.2",
|
|
"codebuddy",
|
|
">=2.121.2 <3.0.0",
|
|
runner,
|
|
&probeTransport{},
|
|
)
|
|
service.Plans.Manager.Sources[0].PinnedVersion = "1.0.0"
|
|
|
|
before, err := service.GetSetup(context.Background(), InstallPlanInput{
|
|
WorkspaceID: "workspace-1", AgentTargetID: targetID,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if before.RuntimeSource != "local" {
|
|
t.Fatalf("runtime before automatic reconcile = %#v, want compatible local fallback", before)
|
|
}
|
|
if err := service.StartManagedRuntimeReconciler(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
deadline := time.Now().Add(5 * time.Second)
|
|
var after SetupSnapshot
|
|
for {
|
|
after, err = service.GetSetup(context.Background(), InstallPlanInput{
|
|
WorkspaceID: "workspace-1", AgentTargetID: targetID,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if after.Status == SetupReady && after.RuntimeSource == "managed" {
|
|
break
|
|
}
|
|
if time.Now().After(deadline) {
|
|
t.Fatalf("automatic managed runtime reconcile did not converge: %#v", after)
|
|
}
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
if after.Status != SetupReady || after.RuntimeSource != "managed" || after.RuntimeVersion != "2.121.2" {
|
|
t.Fatalf("runtime after automatic reconcile = %#v", after)
|
|
}
|
|
if runner.callCount() != 1 {
|
|
t.Fatalf("automatic install calls = %d, want 1", runner.callCount())
|
|
}
|
|
if errs := service.ReconcileManagedRuntimes(context.Background()); len(errs) != 0 {
|
|
t.Fatalf("second ReconcileManagedRuntimes() errors = %v", errs)
|
|
}
|
|
if runner.callCount() != 1 {
|
|
t.Fatalf("automatic install calls after convergence = %d, want 1", runner.callCount())
|
|
}
|
|
}
|
|
|
|
func TestManagedRuntimeReconcilerIgnoresInstallationOutsideCurrentClientPin(t *testing.T) {
|
|
t.Setenv("PATH", t.TempDir())
|
|
runner := &fixtureInstallRunner{
|
|
binary: "generic-agent", packageName: "@example/generic-agent", version: "1.2.3",
|
|
}
|
|
service, _ := setupFixture(
|
|
t,
|
|
"generic",
|
|
"Generic Agent",
|
|
"@example/generic-agent",
|
|
"1.2.3",
|
|
"generic-agent",
|
|
">=1.2.3 <2.0.0",
|
|
runner,
|
|
&probeTransport{},
|
|
)
|
|
service.Plans.Manager.Sources[0].PinnedVersion = "2.0.0"
|
|
|
|
if errs := service.ReconcileManagedRuntimes(context.Background()); len(errs) != 0 {
|
|
t.Fatalf("ReconcileManagedRuntimes() errors = %v", errs)
|
|
}
|
|
if runner.callCount() != 0 {
|
|
t.Fatalf("out-of-pin automatic install calls = %d, want 0", runner.callCount())
|
|
}
|
|
}
|
|
|
|
func TestManagedRuntimeReconcilerToleratesUserCommandConflict(t *testing.T) {
|
|
t.Setenv("PATH", t.TempDir())
|
|
runner := &fixtureInstallRunner{
|
|
binary: "generic-agent", packageName: "@example/generic-agent", version: "1.2.3",
|
|
}
|
|
service, _ := setupFixture(
|
|
t,
|
|
"generic",
|
|
"Generic Agent",
|
|
"@example/generic-agent",
|
|
"1.2.3",
|
|
"generic-agent",
|
|
">=1.2.3 <2.0.0",
|
|
runner,
|
|
&probeTransport{},
|
|
)
|
|
service.Plans.Manager.Sources[0].PinnedVersion = "1.0.0"
|
|
if err := os.MkdirAll(service.Plans.Manager.RuntimeBinDir, 0o700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
commandName := "generic-agent"
|
|
if runtime.GOOS == "windows" {
|
|
commandName += ".cmd"
|
|
}
|
|
if err := os.WriteFile(
|
|
filepath.Join(service.Plans.Manager.RuntimeBinDir, commandName),
|
|
[]byte("user-owned command"),
|
|
0o700,
|
|
); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
outcome := service.reconcileManagedRuntimes(context.Background(), nil)
|
|
if len(outcome.results) != 1 || outcome.results[0].err != nil || outcome.results[0].retryable {
|
|
t.Fatalf("tolerated user command conflict outcome = %#v", outcome.results)
|
|
}
|
|
// The runtime installer still runs; only the user-command hop is skipped.
|
|
if runner.callCount() == 0 {
|
|
t.Fatalf("installer did not run after tolerated user command conflict: %d", runner.callCount())
|
|
}
|
|
retryStates := map[string]managedRuntimeRetryState{}
|
|
if delay := applyManagedRuntimeReconcileOutcome(retryStates, outcome, time.Unix(1_700_000_000, 0)); delay >= 0 {
|
|
t.Fatalf("tolerated user command conflict retry delay = %v, want no retry", delay)
|
|
}
|
|
if shouldAttemptManagedRuntime(retryStates, outcome.results[0].key, time.Unix(1_700_000_100, 0)) {
|
|
t.Fatal("tolerated user command conflict target became timer-eligible")
|
|
}
|
|
}
|
|
|
|
func TestManagedRuntimeRetryStateIsPerTarget(t *testing.T) {
|
|
now := time.Unix(1_700_000_000, 0)
|
|
outcome := managedRuntimeReconcileOutcome{
|
|
seen: map[string]struct{}{"healthy": {}, "retrying": {}},
|
|
results: []managedRuntimeReconcileResult{
|
|
{key: "healthy"},
|
|
{key: "retrying", err: errors.New("temporary install failure"), retryable: true},
|
|
},
|
|
}
|
|
retryStates := map[string]managedRuntimeRetryState{}
|
|
if delay := applyManagedRuntimeReconcileOutcome(retryStates, outcome, now); delay != managedRuntimeReconcileMinBackoff {
|
|
t.Fatalf("first retry delay = %v, want %v", delay, managedRuntimeReconcileMinBackoff)
|
|
}
|
|
if shouldAttemptManagedRuntime(retryStates, "healthy", now.Add(time.Hour)) {
|
|
t.Fatal("healthy target became eligible during another target's retry")
|
|
}
|
|
if !shouldAttemptManagedRuntime(retryStates, "retrying", now.Add(managedRuntimeReconcileMinBackoff)) {
|
|
t.Fatal("retryable target did not become eligible after its backoff")
|
|
}
|
|
}
|
|
|
|
func TestManagedRuntimeInstallFailureRetryability(t *testing.T) {
|
|
for _, test := range []struct {
|
|
name string
|
|
err error
|
|
retryable bool
|
|
}{
|
|
{name: "installer transport", err: fmt.Errorf("%w: temporary registry failure", ErrRuntimeInstallFailed), retryable: true},
|
|
{name: "ACP probe", err: fmt.Errorf("%w: temporary provider failure", ErrRuntimeProbeFailed), retryable: true},
|
|
{name: "verification", err: fmt.Errorf("%w: incompatible signed runtime", ErrRuntimeVerifyFailed)},
|
|
{name: "activation", err: fmt.Errorf("%w: command ownership changed", ErrRuntimeActivateFailed)},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
if got := managedRuntimeInstallFailureRetryable(test.err); got != test.retryable {
|
|
t.Fatalf("managedRuntimeInstallFailureRetryable() = %v, want %v", got, test.retryable)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAgentTargetSetupAuthenticatesGenericRuntimeToReady(t *testing.T) {
|
|
t.Setenv("PATH", t.TempDir())
|
|
transport := &probeTransport{authRequired: true}
|
|
service, targetID := setupFixture(
|
|
t, "generic", "Generic Agent", "@example/generic-agent", "1.2.3", "generic-agent", ">=1.2.3 <2.0.0",
|
|
&fixtureInstallRunner{binary: "generic-agent", packageName: "@example/generic-agent", version: "1.2.3"}, transport,
|
|
)
|
|
initial, err := service.GetSetup(context.Background(), InstallPlanInput{WorkspaceID: "workspace-1", AgentTargetID: targetID})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := service.Install(context.Background(), InstallInput{
|
|
WorkspaceID: "workspace-1", AgentTargetID: targetID,
|
|
PlanDigest: initial.Plan.PlanDigest, ClientActionID: "auth-install-1",
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
authRequired := waitForSetupStatus(t, service, targetID, SetupAuthRequired)
|
|
if authRequired.RuntimeSource != "managed" || authRequired.RuntimeVersion != "1.2.3" || len(authRequired.AuthMethods) != 1 {
|
|
t.Fatalf("auth-required setup = %#v", authRequired)
|
|
}
|
|
if _, err := service.Authenticate(context.Background(), AuthenticateInput{
|
|
WorkspaceID: "workspace-1", AgentTargetID: targetID,
|
|
MethodID: "attacker-method", ClientActionID: "invalid-auth-action",
|
|
}); !errors.Is(err, ErrInvalidInstallPlanRequest) {
|
|
t.Fatalf("unadvertised method error = %v", err)
|
|
}
|
|
started, err := service.Authenticate(context.Background(), AuthenticateInput{
|
|
WorkspaceID: "workspace-1", AgentTargetID: targetID,
|
|
MethodID: "oauth-personal", ClientActionID: "auth-action-1",
|
|
})
|
|
if err != nil || started.Status != SetupAuthenticating || started.Action == nil || started.Action.Kind != SetupActionAuthenticate {
|
|
t.Fatalf("authenticate start = %#v, error = %v", started, err)
|
|
}
|
|
ready := waitForSetupStatus(t, service, targetID, SetupReady)
|
|
if ready.RuntimeSource != "managed" || !transport.isAuthenticated() || ready.Account == nil ||
|
|
ready.Account.ID != "user-1" || ready.Account.DisplayName != "Rhinoc" || ready.Account.AuthMethodID != "oauth-personal" {
|
|
t.Fatalf("authenticated setup = %#v, authenticated = %v", ready, transport.isAuthenticated())
|
|
}
|
|
}
|
|
|
|
func TestAgentTargetSetupGuidesTerminalAuthMethod(t *testing.T) {
|
|
t.Setenv("PATH", t.TempDir())
|
|
transport := &probeTransport{authRequired: true, terminalAuthMethod: true}
|
|
service, targetID := setupFixture(
|
|
t, "generic", "Generic Agent", "@example/generic-agent", "1.2.3", "generic-agent", ">=1.2.3 <2.0.0",
|
|
&fixtureInstallRunner{binary: "generic-agent", packageName: "@example/generic-agent", version: "1.2.3"}, transport,
|
|
)
|
|
initial, err := service.GetSetup(context.Background(), InstallPlanInput{WorkspaceID: "workspace-1", AgentTargetID: targetID})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := service.Install(context.Background(), InstallInput{
|
|
WorkspaceID: "workspace-1", AgentTargetID: targetID,
|
|
PlanDigest: initial.Plan.PlanDigest, ClientActionID: "terminal-auth-install",
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
authRequired := waitForSetupStatus(t, service, targetID, SetupAuthRequired)
|
|
if len(authRequired.AuthMethods) != 1 {
|
|
t.Fatalf("auth-required setup = %#v", authRequired)
|
|
}
|
|
method := authRequired.AuthMethods[0]
|
|
if method.Type != "terminal" || !strings.HasSuffix(method.TerminalCommand, " login") ||
|
|
!strings.Contains(method.TerminalCommand, "generic-agent") {
|
|
t.Fatalf("terminal auth method = %#v", method)
|
|
}
|
|
if _, err := service.Authenticate(context.Background(), AuthenticateInput{
|
|
WorkspaceID: "workspace-1", AgentTargetID: targetID,
|
|
MethodID: "login", ClientActionID: "terminal-auth-action",
|
|
}); !errors.Is(err, ErrTerminalAuthMethod) {
|
|
t.Fatalf("terminal method authenticate error = %v, want ErrTerminalAuthMethod", err)
|
|
}
|
|
if transport.isAuthenticated() {
|
|
t.Fatal("terminal method must not drive ACP authenticate")
|
|
}
|
|
}
|
|
|
|
func TestAgentTargetSetupGuidesTerminalAuthMethodFromMeta(t *testing.T) {
|
|
t.Setenv("PATH", t.TempDir())
|
|
// Kimi Code declares the terminal login metadata inside the ACP _meta
|
|
// extension rather than top-level type/args fields.
|
|
transport := &probeTransport{authRequired: true, terminalAuthMeta: true}
|
|
service, targetID := setupFixture(
|
|
t, "generic", "Generic Agent", "@example/generic-agent", "1.2.3", "generic-agent", ">=1.2.3 <2.0.0",
|
|
&fixtureInstallRunner{binary: "generic-agent", packageName: "@example/generic-agent", version: "1.2.3"}, transport,
|
|
)
|
|
initial, err := service.GetSetup(context.Background(), InstallPlanInput{WorkspaceID: "workspace-1", AgentTargetID: targetID})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := service.Install(context.Background(), InstallInput{
|
|
WorkspaceID: "workspace-1", AgentTargetID: targetID,
|
|
PlanDigest: initial.Plan.PlanDigest, ClientActionID: "terminal-meta-auth-install",
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
authRequired := waitForSetupStatus(t, service, targetID, SetupAuthRequired)
|
|
if len(authRequired.AuthMethods) != 1 {
|
|
t.Fatalf("auth-required setup = %#v", authRequired)
|
|
}
|
|
method := authRequired.AuthMethods[0]
|
|
if method.Type != "terminal" || !strings.HasSuffix(method.TerminalCommand, " login") ||
|
|
!strings.Contains(method.TerminalCommand, "generic-agent") {
|
|
t.Fatalf("terminal auth method from _meta = %#v", method)
|
|
}
|
|
if _, err := service.Authenticate(context.Background(), AuthenticateInput{
|
|
WorkspaceID: "workspace-1", AgentTargetID: targetID,
|
|
MethodID: "login", ClientActionID: "terminal-meta-auth-action",
|
|
}); !errors.Is(err, ErrTerminalAuthMethod) {
|
|
t.Fatalf("terminal method authenticate error = %v, want ErrTerminalAuthMethod", err)
|
|
}
|
|
if transport.isAuthenticated() {
|
|
t.Fatal("terminal method must not drive ACP authenticate")
|
|
}
|
|
}
|
|
|
|
func TestTerminalLoginLaunch(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
method := agentruntime.StandardACPAuthMethod{ID: "login", Type: "terminal", Args: []string{"login"}}
|
|
if got := terminalLoginLaunch([]string{"/opt/agent/bin/kimi", "acp"}, method, nil); got.Command != "/opt/agent/bin/kimi login" || got.StartupAction != nil {
|
|
t.Fatalf("terminalLoginLaunch = %#v", got)
|
|
}
|
|
flagMethod := agentruntime.StandardACPAuthMethod{ID: "login", Type: "terminal", Args: []string{"--login"}}
|
|
if got := terminalLoginLaunch([]string{"/opt/agent/bin/kimi", "acp"}, flagMethod, nil); got.Command != "/opt/agent/bin/kimi acp --login" {
|
|
t.Fatalf("terminalLoginLaunch with flag args = %#v", got)
|
|
}
|
|
var declared AuthenticationMethodProfile
|
|
declared.ID = "login"
|
|
declared.Type = "terminal"
|
|
declared.Command.Strategy = "runtime-subcommand"
|
|
declared.Command.Args = []string{"login"}
|
|
if got := terminalLoginLaunch([]string{"/opt/agent/bin/kimi", "acp"}, flagMethod, &declared); got.Command != "/opt/agent/bin/kimi login" {
|
|
t.Fatalf("terminalLoginLaunch with extension declaration = %#v", got)
|
|
}
|
|
declared.Command.Strategy = "runtime-slash-command"
|
|
declared.Command.Args = []string{"login"}
|
|
declared.Command.ReadyText = "Welcome to Kimi Code!"
|
|
if got := terminalLoginLaunch([]string{"/opt/agent/bin/kimi", "acp"}, flagMethod, &declared); got.Command != "/opt/agent/bin/kimi" ||
|
|
got.StartupAction == nil || got.StartupAction.Type != "slash_command" ||
|
|
got.StartupAction.CommandName != "login" || got.StartupAction.ReadyText != "Welcome to Kimi Code!" {
|
|
t.Fatalf("terminalLoginLaunch with slash command declaration = %#v", got)
|
|
}
|
|
browserMethod := agentruntime.StandardACPAuthMethod{ID: "login", Type: "browser", Args: []string{"runtime-browser"}}
|
|
if got := terminalLoginLaunch([]string{"/opt/agent/bin/kimi", "acp"}, browserMethod, &declared); got != (terminalAuthLaunch{}) {
|
|
t.Fatalf("terminalLoginLaunch with mismatched live type = %#v", got)
|
|
}
|
|
wantPathWithSpaces := `'/opt/agent dir/bin/kimi' login`
|
|
if runtime.GOOS == "windows" {
|
|
wantPathWithSpaces = `"/opt/agent dir/bin/kimi" login`
|
|
}
|
|
if got := terminalLoginLaunch([]string{"/opt/agent dir/bin/kimi"}, method, nil); got.Command != wantPathWithSpaces {
|
|
t.Fatalf("terminalLoginLaunch with spaces = %#v", got)
|
|
}
|
|
if got := terminalLoginLaunch([]string{"/opt/agent/bin/kimi"}, agentruntime.StandardACPAuthMethod{ID: "oauth"}, nil); got != (terminalAuthLaunch{}) {
|
|
t.Fatalf("terminalLoginLaunch for non-terminal method = %#v", got)
|
|
}
|
|
if got := terminalLoginLaunch(nil, method, nil); got != (terminalAuthLaunch{}) {
|
|
t.Fatalf("terminalLoginLaunch without command = %#v", got)
|
|
}
|
|
}
|
|
|
|
func TestProbeRuntimeAppliesSignedTerminalSetupPresentation(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var declared AuthenticationMethodProfile
|
|
declared.ID = "login"
|
|
declared.Name = "Set up Example Agent"
|
|
declared.Description = "Open the runtime login flow."
|
|
declared.Type = "terminal"
|
|
declared.Command.Strategy = "runtime-slash-command"
|
|
declared.Command.Args = []string{"login"}
|
|
declared.Command.ReadyText = "Example Agent ready"
|
|
binding := RuntimeBinding{
|
|
Installation: Installation{AgentKey: "example", Provider: "acp:example"},
|
|
Command: []string{"/opt/example/bin/example", "acp"},
|
|
AuthenticationMethods: map[string]AuthenticationMethodProfile{
|
|
"login": declared,
|
|
},
|
|
}
|
|
result, err := ProbeRuntime(
|
|
context.Background(), binding, "extension:example", t.TempDir(),
|
|
&probeTransport{authRequired: true, terminalAuthMethod: true},
|
|
agentruntime.HostMetadata{},
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if result.Status != RuntimeProbeAuthRequired || len(result.AuthMethods) != 1 {
|
|
t.Fatalf("runtime probe = %#v", result)
|
|
}
|
|
method := result.AuthMethods[0]
|
|
if method.Name != declared.Name || method.Description != declared.Description ||
|
|
method.Type != "terminal" || method.TerminalCommand != "/opt/example/bin/example" ||
|
|
method.TerminalStartupAction == nil || method.TerminalStartupAction.Type != "slash_command" ||
|
|
method.TerminalStartupAction.CommandName != "login" || method.TerminalStartupAction.ReadyText != "Example Agent ready" {
|
|
t.Fatalf("projected auth method = %#v", method)
|
|
}
|
|
}
|
|
|
|
func TestRuntimeAdapterConfigProjectsModelDescriptionMetadataFormat(t *testing.T) {
|
|
binding := RuntimeBinding{
|
|
ModelDescriptionFormat: agentruntime.StandardACPModelDescriptionMetadataFormatCreditConsumptionMultiplierV1,
|
|
}
|
|
config := runtimeAdapterConfig(binding, "")
|
|
if config.ModelDescriptionFormat != agentruntime.StandardACPModelDescriptionMetadataFormatCreditConsumptionMultiplierV1 {
|
|
t.Fatalf("model description metadata format = %q", config.ModelDescriptionFormat)
|
|
}
|
|
}
|
|
|
|
func TestAgentTargetSetupFeedsRuntimeAuthFailureBackIntoDetectionAndAllowsRelogin(t *testing.T) {
|
|
binDir := t.TempDir()
|
|
writeVersionExecutable(t, filepath.Join(binDir, "gemini"), "0.50.0")
|
|
t.Setenv("PATH", binDir)
|
|
transport := &probeTransport{authRequired: true, authenticated: true}
|
|
service, targetID := setupFixture(
|
|
t, "gemini", "Gemini CLI", "@google/gemini-cli", "0.50.0", "gemini", ">=0.50.0 <1.0.0",
|
|
&fixtureInstallRunner{}, transport,
|
|
)
|
|
authState := &fixtureRuntimeAuthInvalidation{invalidated: map[string]bool{"acp:gemini": true}}
|
|
service.AuthInvalidation = authState
|
|
|
|
snapshot, err := service.GetSetup(context.Background(), InstallPlanInput{
|
|
WorkspaceID: "workspace-1", AgentTargetID: targetID,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if snapshot.Status != SetupAuthRequired || snapshot.Reason != "runtime_auth_invalidated" {
|
|
t.Fatalf("runtime auth invalidation snapshot = %#v", snapshot)
|
|
}
|
|
if _, err := service.Authenticate(context.Background(), AuthenticateInput{
|
|
WorkspaceID: "workspace-1", AgentTargetID: targetID,
|
|
MethodID: "oauth-personal", ClientActionID: "runtime-auth-retry",
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ready := waitForSetupStatus(t, service, targetID, SetupReady)
|
|
if ready.Status != SetupReady || authState.AuthInvalidated("acp:gemini") {
|
|
t.Fatalf("re-authenticated setup = %#v, invalidated = %v", ready, authState.AuthInvalidated("acp:gemini"))
|
|
}
|
|
}
|
|
|
|
func TestAgentTargetSetupPersistsProviderAuthenticationFailure(t *testing.T) {
|
|
t.Setenv("PATH", t.TempDir())
|
|
transport := &probeTransport{
|
|
authRequired: true,
|
|
authenticateError: "This account is not supported by this client",
|
|
}
|
|
service, targetID := setupFixture(
|
|
t, "gemini", "Gemini CLI", "@google/gemini-cli", "0.50.0", "gemini", ">=0.50.0 <1.0.0",
|
|
&fixtureInstallRunner{binary: "gemini", packageName: "@google/gemini-cli", version: "0.50.0"},
|
|
transport,
|
|
)
|
|
initial, err := service.GetSetup(context.Background(), InstallPlanInput{
|
|
WorkspaceID: "workspace-1", AgentTargetID: targetID,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := service.Install(context.Background(), InstallInput{
|
|
WorkspaceID: "workspace-1", AgentTargetID: targetID,
|
|
PlanDigest: initial.Plan.PlanDigest, ClientActionID: "auth-error-install",
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
authRequired := waitForSetupStatus(t, service, targetID, SetupAuthRequired)
|
|
if _, err := service.Authenticate(context.Background(), AuthenticateInput{
|
|
WorkspaceID: "workspace-1", AgentTargetID: targetID,
|
|
MethodID: "oauth-personal", ClientActionID: "auth-error-action",
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
authRequired = waitForSetupStatus(t, service, targetID, SetupAuthRequired)
|
|
if authRequired.Action == nil || authRequired.Action.Status != SetupActionFailed ||
|
|
!strings.Contains(authRequired.Action.ErrorMessage, transport.authenticateError) {
|
|
t.Fatalf("failed authentication snapshot = %#v", authRequired)
|
|
}
|
|
}
|
|
|
|
func TestAgentTargetSetupRejectsManagedRuntimeBinaryReplacement(t *testing.T) {
|
|
t.Setenv("PATH", t.TempDir())
|
|
service, targetID := setupFixture(
|
|
t, "gemini", "Gemini CLI", "@google/gemini-cli", "0.50.0", "gemini", ">=0.50.0 <1.0.0",
|
|
&fixtureInstallRunner{binary: "gemini", packageName: "@google/gemini-cli", version: "0.50.0"},
|
|
&probeTransport{},
|
|
)
|
|
initial, err := service.GetSetup(context.Background(), InstallPlanInput{WorkspaceID: "workspace-1", AgentTargetID: targetID})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := service.Install(context.Background(), InstallInput{
|
|
WorkspaceID: "workspace-1", AgentTargetID: targetID,
|
|
PlanDigest: initial.Plan.PlanDigest, ClientActionID: "fingerprint-install",
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ready := waitForSetupStatus(t, service, targetID, SetupReady)
|
|
root := initial.Plan.InstallRoot
|
|
var activation managedRuntimeActivation
|
|
if err := readJSON(filepath.Join(root, "activation.json"), &activation); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
executable := filepath.Join(root, filepath.FromSlash(activation.ExecutableRelativePath))
|
|
if err := os.WriteFile(executable, []byte("#!/bin/sh\necho 0.50.0\n# replaced\n"), 0o700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
snapshot, err := service.GetSetup(context.Background(), InstallPlanInput{WorkspaceID: "workspace-1", AgentTargetID: targetID})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if ready.RuntimeSource != "managed" || snapshot.Status != SetupNotInstalled || snapshot.Reason != "runtime_integrity_failed" || snapshot.Plan == nil {
|
|
t.Fatalf("replacement snapshot = %#v", snapshot)
|
|
}
|
|
}
|
|
|
|
func TestAgentTargetSetupToleratesRemovedUserExecutable(t *testing.T) {
|
|
t.Setenv("PATH", t.TempDir())
|
|
service, targetID := setupFixture(
|
|
t, "gemini", "Gemini CLI", "@google/gemini-cli", "0.50.0", "gemini", ">=0.50.0 <1.0.0",
|
|
&fixtureInstallRunner{binary: "gemini", packageName: "@google/gemini-cli", version: "0.50.0"},
|
|
&probeTransport{},
|
|
)
|
|
initial, err := service.GetSetup(context.Background(), InstallPlanInput{WorkspaceID: "workspace-1", AgentTargetID: targetID})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := service.Install(context.Background(), InstallInput{
|
|
WorkspaceID: "workspace-1", AgentTargetID: targetID,
|
|
PlanDigest: initial.Plan.PlanDigest, ClientActionID: "published-entry-install",
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
waitForSetupStatus(t, service, targetID, SetupReady)
|
|
if err := os.Remove(filepath.Join(service.Plans.Manager.RuntimeBinDir, "gemini")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
snapshot, err := service.GetSetup(context.Background(), InstallPlanInput{WorkspaceID: "workspace-1", AgentTargetID: targetID})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// A user-owned command published to PATH may be removed or shadowed without
|
|
// failing the managed runtime: only the runtime's own files count towards
|
|
// integrity. The agent stays ready through the internal stable hop.
|
|
if snapshot.Status != SetupReady {
|
|
t.Fatalf("removed user executable snapshot = %#v", snapshot)
|
|
}
|
|
}
|
|
|
|
func TestAgentTargetSetupRecoversRunningActionAsInterrupted(t *testing.T) {
|
|
t.Setenv("PATH", t.TempDir())
|
|
service, targetID := setupFixture(
|
|
t, "gemini", "Gemini CLI", "@google/gemini-cli", "0.50.0", "gemini", ">=0.50.0 <1.0.0",
|
|
&fixtureInstallRunner{}, &probeTransport{},
|
|
)
|
|
plan, err := service.Plans.GetInstallPlan(context.Background(), InstallPlanInput{WorkspaceID: "workspace-1", AgentTargetID: targetID})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
action := SetupAction{
|
|
ActionID: "stale-action", ClientActionID: "stale-client", WorkspaceID: "workspace-1",
|
|
Kind: SetupActionInstall,
|
|
AgentTargetID: plan.AgentTargetID, ExtensionInstallationID: plan.ExtensionInstallationID, PlanDigest: plan.PlanDigest,
|
|
Status: SetupActionRunning, Phase: SetupPhaseInstalling,
|
|
}
|
|
if err := service.writeAction(context.Background(), plan, action); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
restarted := NewSetupService(context.Background())
|
|
restarted.Plans = service.Plans
|
|
restarted.Transport = service.Transport
|
|
restarted.Actions = service.Actions
|
|
restarted.Discovery = service.Discovery
|
|
restarted.Runner = service.Runner
|
|
t.Cleanup(func() { _ = restarted.Close() })
|
|
snapshot, err := restarted.GetSetup(context.Background(), InstallPlanInput{WorkspaceID: "workspace-1", AgentTargetID: targetID})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if snapshot.Status != SetupFailed || snapshot.Action == nil || snapshot.Action.Status != SetupActionInterrupted || snapshot.Reason != "daemon_restarted" {
|
|
t.Fatalf("recovered setup = %#v", snapshot)
|
|
}
|
|
}
|
|
|
|
func setupFixture(
|
|
t *testing.T,
|
|
key, name, packageName, packageVersion, binary, constraint string,
|
|
runner InstallCommandRunner,
|
|
transport agentruntime.ProcessTransport,
|
|
) (*SetupService, string) {
|
|
t.Helper()
|
|
manifest := testManifest()
|
|
manifest.AgentKey = key
|
|
manifest.Name = name
|
|
manifest.Runtime.Install.Args = []string{"install", "--prefix", "${installRoot}", packageName + "@" + packageVersion}
|
|
manifest.Runtime.Launch.Executable = "${installRoot}/node_modules/.bin/" + binary
|
|
discovery := `{"schemaVersion":"tutti.agent.discovery.v1","candidates":[{"binaryNames":["` + binary + `"],"version":{"args":["--version"],"constraint":"` + constraint + `"},"launchArgs":["--acp"],"probe":{"kind":"acp-initialize","timeoutMs":5000}}]}`
|
|
stateDir := t.TempDir()
|
|
runtimeInstallDir := filepath.Join(testResolvedTempDir(t), ".local", "share", "tutti", "agent-runtimes")
|
|
runtimeBinDir := filepath.Join(t.TempDir(), ".local", "bin")
|
|
store := &targetStoreStub{targets: map[string]agenttargetbiz.Target{}}
|
|
manager := &Manager{
|
|
RuntimeInstallDir: runtimeInstallDir, RuntimeBinDir: runtimeBinDir, Store: store,
|
|
AccountUsageNodeSnapshotDir: filepath.Join(stateDir, "agent", "account-usage-node-snapshots"),
|
|
Installations: agentextensiondata.NewFileInstallationStore(stateDir),
|
|
Discovery: agentextensiondata.NewFileSetupDiscoveryDirectory(stateDir),
|
|
RuntimeResolver: setupFixtureRuntimeResolver(t),
|
|
}
|
|
installation, err := installTestPackage(t, manager, Release{AgentKey: key, Version: "1.0.0"}, testPackageZIPFor(t, manifest, discovery))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
launchRef, err := agenttargetbiz.CanonicalLaunchRefJSON(installation.Provider, agenttargetbiz.LaunchRef{
|
|
Type: agenttargetbiz.LaunchRefTypeAgentExtension, ExtensionInstallationID: installation.ID,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
targetID := "extension:" + key
|
|
store.targets[targetID] = agenttargetbiz.Target{
|
|
ID: targetID, Provider: installation.Provider, LaunchRefJSON: launchRef,
|
|
Name: name, Enabled: true, Source: agenttargetbiz.SourceSystem,
|
|
}
|
|
plans := InstallPlanService{
|
|
Manager: manager, Workspaces: workspaceLookupStub{workspace: workspacebiz.Summary{ID: "workspace-1"}}, Targets: store,
|
|
}
|
|
service := NewSetupService(context.Background())
|
|
service.Plans = plans
|
|
service.Transport = transport
|
|
service.Actions = agentextensiondata.NewFileSetupActionStore(stateDir)
|
|
service.AccountUsageFailures = agentextensiondata.NewFileAccountUsageCompanionFailureStore(stateDir)
|
|
service.Discovery = agentextensiondata.NewFileSetupDiscoveryDirectory(stateDir)
|
|
service.Runner = runner
|
|
if err := service.StartAccountUsageCompanionReconciler(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { _ = service.Close() })
|
|
return service, targetID
|
|
}
|
|
|
|
func setupFixtureRuntimeResolver(t *testing.T) runtimecmd.Resolver {
|
|
t.Helper()
|
|
allowedRoots := filepath.SplitList(os.Getenv("PATH"))
|
|
runtimeHome := t.TempDir()
|
|
return runtimecmd.Resolver{
|
|
HomeDir: func() (string, error) { return runtimeHome, nil },
|
|
IsExecutableFile: func(path string) bool {
|
|
allowed := false
|
|
for _, root := range allowedRoots {
|
|
if strings.TrimSpace(root) != "" && pathWithin(path, root) {
|
|
allowed = true
|
|
break
|
|
}
|
|
}
|
|
if !allowed {
|
|
return false
|
|
}
|
|
stat, err := os.Stat(path)
|
|
return err == nil && !stat.IsDir() && stat.Mode().Perm()&0o111 != 0
|
|
},
|
|
}
|
|
}
|
|
|
|
func setupBinaryFixture(
|
|
t *testing.T,
|
|
server *httptest.Server,
|
|
binary []byte,
|
|
publishUserCommand bool,
|
|
transport agentruntime.ProcessTransport,
|
|
) (*SetupService, string) {
|
|
t.Helper()
|
|
t.Setenv("TUTTI_TEST_MANAGED_BINARY_VERSION", "1")
|
|
manifest := testManifest()
|
|
manifest.AgentKey = "grok"
|
|
manifest.Name = "Grok"
|
|
manifest.Runtime.Install.Runner = "binary"
|
|
manifest.Runtime.Install.Args = nil
|
|
manifest.Runtime.Install.Artifacts = []RuntimeBinaryArtifact{{
|
|
Kind: "executable", Platform: runtime.GOOS + "-" + runtime.GOARCH, Version: "0.2.103",
|
|
URL: server.URL + "/grok-0.2.103-test", SHA256: sha256Bytes(binary), SizeBytes: int64(len(binary)),
|
|
}}
|
|
manifest.Runtime.Install.Artifacts[0].Provenance.Kind = "official-release"
|
|
manifest.Runtime.Install.Artifacts[0].Provenance.URL = server.URL + "/release/0.2.103"
|
|
manifest.Runtime.Launch.Executable = "${installRoot}/grok"
|
|
manifest.Runtime.Launch.PublishUserCommand = &publishUserCommand
|
|
manifest.Runtime.Launch.Args = []string{"--no-auto-update", "--permission-mode", "default", "agent", "stdio"}
|
|
discovery := `{"schemaVersion":"tutti.agent.discovery.v1","candidates":[{"binaryNames":["grok"],"version":{"args":["-test.run=TestManagedBinaryVersionFixture"],"constraint":">=0.2.89 <0.3.0"},"launchArgs":["--no-auto-update","--permission-mode","default","agent","stdio"],"probe":{"kind":"acp-initialize","timeoutMs":5000}}]}`
|
|
stateDir := t.TempDir()
|
|
store := &targetStoreStub{targets: map[string]agenttargetbiz.Target{}}
|
|
manager := &Manager{
|
|
RuntimeInstallDir: filepath.Join(testResolvedTempDir(t), ".local", "share", "tutti", "agent-runtimes"),
|
|
RuntimeBinDir: filepath.Join(t.TempDir(), ".local", "bin"), Store: store, Client: server.Client(),
|
|
Installations: agentextensiondata.NewFileInstallationStore(stateDir),
|
|
Discovery: agentextensiondata.NewFileSetupDiscoveryDirectory(stateDir),
|
|
}
|
|
installation, err := installTestPackage(t, manager, Release{AgentKey: "grok", Version: "1.0.0"}, testPackageZIPFor(t, manifest, discovery))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
launchRef, err := agenttargetbiz.CanonicalLaunchRefJSON(installation.Provider, agenttargetbiz.LaunchRef{
|
|
Type: agenttargetbiz.LaunchRefTypeAgentExtension, ExtensionInstallationID: installation.ID,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
targetID := "extension:grok"
|
|
store.targets[targetID] = agenttargetbiz.Target{
|
|
ID: targetID, Provider: installation.Provider, LaunchRefJSON: launchRef,
|
|
Name: "Grok", Enabled: true, Source: agenttargetbiz.SourceSystem,
|
|
}
|
|
service := NewSetupService(context.Background())
|
|
service.Plans = InstallPlanService{
|
|
Manager: manager, Workspaces: workspaceLookupStub{workspace: workspacebiz.Summary{ID: "workspace-1"}}, Targets: store,
|
|
}
|
|
service.Transport = transport
|
|
service.Actions = agentextensiondata.NewFileSetupActionStore(stateDir)
|
|
service.Discovery = agentextensiondata.NewFileSetupDiscoveryDirectory(stateDir)
|
|
t.Cleanup(func() { _ = service.Close() })
|
|
return service, targetID
|
|
}
|
|
|
|
func managedBinaryFixtureBytes(t *testing.T) []byte {
|
|
t.Helper()
|
|
path, err := os.Executable()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
value, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return value
|
|
}
|
|
|
|
func testResolvedTempDir(t *testing.T) string {
|
|
t.Helper()
|
|
path, err := filepath.EvalSymlinks(t.TempDir())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return path
|
|
}
|
|
|
|
func waitForSetupStatus(t *testing.T, service *SetupService, targetID string, status SetupStatus) SetupSnapshot {
|
|
t.Helper()
|
|
deadline := time.Now().Add(10 * time.Second)
|
|
var last SetupSnapshot
|
|
for time.Now().Before(deadline) {
|
|
snapshot, err := service.GetSetup(context.Background(), InstallPlanInput{WorkspaceID: "workspace-1", AgentTargetID: targetID})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if snapshot.Status == status {
|
|
return snapshot
|
|
}
|
|
last = snapshot
|
|
time.Sleep(25 * time.Millisecond)
|
|
}
|
|
if last.Action != nil {
|
|
t.Fatalf("setup did not reach %q; status=%s reason=%s action=%#v", status, last.Status, last.Reason, *last.Action)
|
|
}
|
|
t.Fatalf("setup did not reach %q; last snapshot = %#v", status, last)
|
|
return SetupSnapshot{}
|
|
}
|
|
|
|
type fixtureInstallRunner struct {
|
|
mu sync.Mutex
|
|
calls int
|
|
binary string
|
|
packageName string
|
|
version string
|
|
failPackage string
|
|
cwd string
|
|
env []string
|
|
}
|
|
|
|
func (r *fixtureInstallRunner) Run(_ context.Context, command []string, cwd string, env []string) error {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
r.calls++
|
|
r.cwd = cwd
|
|
r.env = append([]string(nil), env...)
|
|
for _, value := range command {
|
|
if r.failPackage != "" && value == r.failPackage {
|
|
return errors.New("fixture companion install failed")
|
|
}
|
|
}
|
|
var root string
|
|
for index, value := range command {
|
|
if value == "--prefix" && index+1 < len(command) {
|
|
root = command[index+1]
|
|
}
|
|
}
|
|
if root == "" {
|
|
return errors.New("missing install prefix")
|
|
}
|
|
installedPackage := r.packageName
|
|
for _, value := range command {
|
|
if strings.HasPrefix(value, "@") {
|
|
if versionAt := strings.LastIndex(value, "@"); versionAt > 0 {
|
|
installedPackage = value[:versionAt]
|
|
}
|
|
}
|
|
}
|
|
packageRoot := filepath.Join(root, "node_modules", filepath.FromSlash(installedPackage))
|
|
if installedPackage != r.packageName {
|
|
script := filepath.Join(packageRoot, "dist", "cli.cjs")
|
|
if err := os.MkdirAll(filepath.Dir(script), 0o700); err != nil {
|
|
return err
|
|
}
|
|
return os.WriteFile(script, []byte("process.stdout.write('{}')\n"), 0o600)
|
|
}
|
|
if runtime.GOOS == "windows" {
|
|
launcher := filepath.Join(root, "node_modules", ".bin", r.binary+".cmd")
|
|
if err := os.MkdirAll(filepath.Dir(launcher), 0o700); err != nil {
|
|
return err
|
|
}
|
|
return os.WriteFile(
|
|
launcher,
|
|
[]byte("@echo off\r\necho "+r.version+"\r\n"),
|
|
0o600,
|
|
)
|
|
}
|
|
realExecutable := filepath.Join(packageRoot, "bin", r.binary)
|
|
if err := os.MkdirAll(filepath.Dir(realExecutable), 0o700); err != nil {
|
|
return err
|
|
}
|
|
if err := os.MkdirAll(filepath.Join(root, "node_modules", ".bin"), 0o700); err != nil {
|
|
return err
|
|
}
|
|
if err := os.WriteFile(realExecutable, []byte("#!/bin/sh\necho "+r.version+"\n"), 0o700); err != nil {
|
|
return err
|
|
}
|
|
relative, err := filepath.Rel(filepath.Join(root, "node_modules", ".bin"), realExecutable)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return os.Symlink(relative, filepath.Join(root, "node_modules", ".bin", r.binary))
|
|
}
|
|
|
|
func (r *fixtureInstallRunner) callCount() int {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
return r.calls
|
|
}
|
|
|
|
func environmentPathWithin(environment []string, prefix, root string) bool {
|
|
for _, value := range environment {
|
|
if rest, ok := strings.CutPrefix(value, prefix); ok {
|
|
return pathWithin(rest, root)
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func writeVersionExecutable(t *testing.T, path, version string) {
|
|
t.Helper()
|
|
if err := os.WriteFile(path, []byte("#!/bin/sh\necho "+version+"\n"), 0o700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
type probeTransport struct {
|
|
mu sync.Mutex
|
|
authRequired bool
|
|
terminalAuthMethod bool
|
|
terminalAuthMeta bool
|
|
authenticated bool
|
|
authenticateError string
|
|
sessionError string
|
|
}
|
|
|
|
type fixtureRuntimeAuthInvalidation struct {
|
|
mu sync.Mutex
|
|
invalidated map[string]bool
|
|
}
|
|
|
|
func (s *fixtureRuntimeAuthInvalidation) AuthInvalidated(provider string) bool {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
return s.invalidated[provider]
|
|
}
|
|
|
|
func (s *fixtureRuntimeAuthInvalidation) ClearAuthInvalidated(provider string) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
delete(s.invalidated, provider)
|
|
}
|
|
|
|
func (t *probeTransport) Start(context.Context, agentruntime.ProcessSpec) (agentruntime.ProcessConnection, error) {
|
|
return &probeConnection{frames: make(chan agentruntime.ProcessFrame, 4), owner: t}, nil
|
|
}
|
|
|
|
func (t *probeTransport) isAuthenticated() bool {
|
|
t.mu.Lock()
|
|
defer t.mu.Unlock()
|
|
return t.authenticated
|
|
}
|
|
|
|
func (t *probeTransport) setSessionError(message string) {
|
|
t.mu.Lock()
|
|
defer t.mu.Unlock()
|
|
t.sessionError = message
|
|
}
|
|
|
|
func (t *probeTransport) getSessionError() string {
|
|
t.mu.Lock()
|
|
defer t.mu.Unlock()
|
|
return t.sessionError
|
|
}
|
|
|
|
type probeConnection struct {
|
|
frames chan agentruntime.ProcessFrame
|
|
once sync.Once
|
|
owner *probeTransport
|
|
}
|
|
|
|
func (c *probeConnection) Send(value []byte) error {
|
|
var request struct {
|
|
ID json.RawMessage `json:"id"`
|
|
Method string `json:"method"`
|
|
}
|
|
if err := json.Unmarshal([]byte(strings.TrimSpace(string(value))), &request); err != nil {
|
|
return err
|
|
}
|
|
result := map[string]any{}
|
|
switch request.Method {
|
|
case "initialize":
|
|
result = map[string]any{"protocolVersion": 1, "agentCapabilities": map[string]any{}, "agentInfo": map[string]any{"name": "fixture", "version": "1.0.0"}}
|
|
if c.owner.authRequired {
|
|
if c.owner.terminalAuthMethod {
|
|
result["authMethods"] = []any{map[string]any{
|
|
"id": "login", "name": "Login with Fixture account", "type": "terminal", "args": []any{"login"},
|
|
}}
|
|
} else if c.owner.terminalAuthMeta {
|
|
result["authMethods"] = []any{map[string]any{
|
|
"id": "login", "name": "Login with Fixture account",
|
|
"_meta": map[string]any{
|
|
"terminal-auth": map[string]any{"type": "terminal", "args": []any{"login"}},
|
|
},
|
|
}}
|
|
} else {
|
|
result["authMethods"] = []any{map[string]any{"id": "oauth-personal", "name": "Log in with Google"}}
|
|
}
|
|
}
|
|
case "authenticate":
|
|
if c.owner.authenticateError != "" {
|
|
response, _ := json.Marshal(map[string]any{
|
|
"jsonrpc": "2.0", "id": request.ID,
|
|
"error": map[string]any{"code": -32000, "message": c.owner.authenticateError},
|
|
})
|
|
c.frames <- agentruntime.ProcessFrame{Stdout: append(response, '\n')}
|
|
return nil
|
|
}
|
|
c.owner.mu.Lock()
|
|
c.owner.authenticated = true
|
|
c.owner.mu.Unlock()
|
|
result = map[string]any{
|
|
"_meta": map[string]any{
|
|
"codebuddy.ai/userinfo": map[string]any{
|
|
"userId": "user-1", "userName": "Ryan", "userNickname": "Rhinoc",
|
|
},
|
|
},
|
|
}
|
|
case "session/new":
|
|
if c.owner.authRequired && !c.owner.isAuthenticated() {
|
|
response, _ := json.Marshal(map[string]any{
|
|
"jsonrpc": "2.0", "id": request.ID,
|
|
"error": map[string]any{"code": -32000, "message": "authentication required"},
|
|
})
|
|
c.frames <- agentruntime.ProcessFrame{Stdout: append(response, '\n')}
|
|
return nil
|
|
}
|
|
if message := c.owner.getSessionError(); message != "" {
|
|
response, _ := json.Marshal(map[string]any{
|
|
"jsonrpc": "2.0", "id": request.ID,
|
|
"error": map[string]any{"code": -32000, "message": message},
|
|
})
|
|
c.frames <- agentruntime.ProcessFrame{Stdout: append(response, '\n')}
|
|
return nil
|
|
}
|
|
result = map[string]any{"sessionId": "fixture-session", "configOptions": []any{}}
|
|
}
|
|
response, _ := json.Marshal(map[string]any{"jsonrpc": "2.0", "id": request.ID, "result": result})
|
|
c.frames <- agentruntime.ProcessFrame{Stdout: append(response, '\n')}
|
|
return nil
|
|
}
|
|
|
|
func (c *probeConnection) Recv() (agentruntime.ProcessFrame, error) {
|
|
frame, ok := <-c.frames
|
|
if !ok {
|
|
return agentruntime.ProcessFrame{}, io.EOF
|
|
}
|
|
return frame, nil
|
|
}
|
|
|
|
func (c *probeConnection) Close() error {
|
|
c.once.Do(func() { close(c.frames) })
|
|
return nil
|
|
}
|