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
83 lines
2.7 KiB
Go
83 lines
2.7 KiB
Go
package implementationhost
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
|
|
market "github.com/tutti-os/tutti/packages/connector/daemon/core"
|
|
connectorruntime "github.com/tutti-os/tutti/packages/connector/runtime"
|
|
)
|
|
|
|
func (host *Host) deactivateConnector(request market.RuntimeDeactivationRequest) error {
|
|
match := func(route connectorruntime.ManagedRoute) bool {
|
|
candidate, ok := route.(*connectorRoute)
|
|
return ok && candidate.connectorKey == request.ConnectorKey
|
|
}
|
|
routeErr := host.routes.RemoveMatching(match, request.Generation, request.Deadline)
|
|
|
|
host.authorizationMu.Lock()
|
|
authorizationRoutes := make([]*connectorRoute, 0)
|
|
for key, route := range host.authorizationRoutes {
|
|
if route.connectorKey != request.ConnectorKey {
|
|
continue
|
|
}
|
|
delete(host.authorizationRoutes, key)
|
|
authorizationRoutes = append(authorizationRoutes, route)
|
|
}
|
|
host.authorizationMu.Unlock()
|
|
var authorizationErrs []error
|
|
for _, route := range authorizationRoutes {
|
|
host.authorizationProvider.cancelAuthorizationSessionByRoute(route.id)
|
|
route.Fence()
|
|
if err := route.Close(request.Deadline); err != nil {
|
|
// Keep a fenced route reachable so an idempotent uninstall retry can
|
|
// finish snapshot/process cleanup after a transient close failure.
|
|
host.authorizationMu.Lock()
|
|
if host.authorizationRoutes[route.id] == nil {
|
|
host.authorizationRoutes[route.id] = route
|
|
}
|
|
host.authorizationMu.Unlock()
|
|
authorizationErrs = append(authorizationErrs, err)
|
|
}
|
|
}
|
|
shimErr := host.removeOrphanedConnectorCLIShim(request.ConnectorKey)
|
|
host.notifyRouteChanged()
|
|
return errors.Join(routeErr, errors.Join(authorizationErrs...), shimErr)
|
|
}
|
|
|
|
func (host *Host) removeOrphanedConnectorCLIShim(connectorKey string) error {
|
|
if !hostIdentityPattern.MatchString(connectorKey) {
|
|
return errors.New("connector CLI shim removal identity is invalid")
|
|
}
|
|
if !filepath.IsAbs(host.binDir) {
|
|
return nil
|
|
}
|
|
command := "tutti-connector-" + connectorKey
|
|
if runtime.GOOS == "windows" {
|
|
command += ".cmd"
|
|
}
|
|
path := filepath.Join(host.binDir, command)
|
|
content, err := os.ReadFile(path)
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
return fmt.Errorf("read connector CLI shim before removal: %w", err)
|
|
}
|
|
managedMarker := "export TUTTI_CONNECTOR_KEY=" + shellQuote(connectorKey) + "\n"
|
|
if runtime.GOOS == "windows" {
|
|
managedMarker = "set \"TUTTI_CONNECTOR_KEY=" + connectorKey + "\"\r\n"
|
|
}
|
|
if !strings.Contains(string(content), managedMarker) {
|
|
return errors.New("connector CLI shim removal target is not Tutti-managed")
|
|
}
|
|
if err := os.Remove(path); err != nil && !errors.Is(err, os.ErrNotExist) {
|
|
return fmt.Errorf("remove connector CLI shim: %w", err)
|
|
}
|
|
return nil
|
|
}
|