Files
agent-radar 2ed3415fb7
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
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

142 lines
4.2 KiB
Go

package agentextension
import (
"bytes"
"encoding/json"
"errors"
"os"
"path/filepath"
"regexp"
"strings"
"golang.org/x/mod/semver"
agentextensionbiz "github.com/tutti-os/tutti/services/tuttid/biz/agentextension"
)
const installationRelativeDir = "agent/extensions"
var installationKeyPattern = regexp.MustCompile(`^[a-z0-9](?:[a-z0-9._-]{0,62}[a-z0-9])?$`)
type FileInstallationStore struct {
stateDir string
}
func NewFileInstallationStore(stateDir string) *FileInstallationStore {
return &FileInstallationStore{stateDir: strings.TrimSpace(stateDir)}
}
func (s *FileInstallationStore) AgentDir(agentKey string) (string, error) {
root, err := s.root()
if err != nil {
return "", err
}
if !installationKeyPattern.MatchString(agentKey) {
return "", errors.New("agent extension key is invalid")
}
return filepath.Join(root, agentKey), nil
}
func (s *FileInstallationStore) PackageDir(agentKey, version string) (string, error) {
agentDir, err := s.AgentDir(agentKey)
if err != nil {
return "", err
}
if !semver.IsValid("v" + version) {
return "", errors.New("agent extension installation identity is invalid")
}
return filepath.Join(agentDir, version), nil
}
func (s *FileInstallationStore) ReadActive(agentKey string) (agentextensionbiz.Installation, error) {
agentDir, err := s.AgentDir(agentKey)
if err != nil {
return agentextensionbiz.Installation{}, err
}
return readInstallation(filepath.Join(agentDir, "active.json"))
}
func (s *FileInstallationStore) ReadInstallation(installationID string) (agentextensionbiz.Installation, error) {
agentKey, version, ok := strings.Cut(installationID, "@")
if !ok || strings.Contains(version, "@") {
return agentextensionbiz.Installation{}, errors.New("agent extension installation id is invalid")
}
packageDir, err := s.PackageDir(agentKey, version)
if err != nil {
return agentextensionbiz.Installation{}, err
}
return readInstallation(filepath.Join(packageDir, "installation.json"))
}
func (s *FileInstallationStore) PutActive(installation agentextensionbiz.Installation) error {
packageDir, err := s.PackageDir(installation.AgentKey, installation.Version)
if err != nil {
return err
}
if installation.ID != installation.AgentKey+"@"+installation.Version || filepath.Clean(installation.PackageDir) != packageDir {
return errors.New("agent extension installation record identity is invalid")
}
if err := writeInstallation(filepath.Join(packageDir, "installation.json"), installation); err != nil {
return err
}
return writeInstallation(filepath.Join(filepath.Dir(packageDir), "active.json"), installation)
}
func (s *FileInstallationStore) root() (string, error) {
if s == nil || s.stateDir == "" {
return "", errors.New("agent extension installation state directory is required")
}
return filepath.Join(s.stateDir, filepath.FromSlash(installationRelativeDir)), nil
}
func readInstallation(path string) (agentextensionbiz.Installation, error) {
data, err := os.ReadFile(path)
if err != nil {
return agentextensionbiz.Installation{}, err
}
var installation agentextensionbiz.Installation
decoder := json.NewDecoder(bytes.NewReader(data))
decoder.DisallowUnknownFields()
if err := decoder.Decode(&installation); err != nil {
return agentextensionbiz.Installation{}, err
}
// Older client-pinned Runtime builds persisted a derived preference. Keep
// accepting that exact legacy field under strict decoding, but clear it so
// any subsequent write converges to the source-pin-derived contract.
installation.LegacyPreferManagedRuntime = false
return installation, nil
}
func writeInstallation(path string, installation agentextensionbiz.Installation) error {
data, err := json.MarshalIndent(installation, "", " ")
if err != nil {
return err
}
data = append(data, '\n')
if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
return err
}
temp, err := os.CreateTemp(filepath.Dir(path), ".write-")
if err != nil {
return err
}
tempPath := temp.Name()
defer os.Remove(tempPath)
if err := temp.Chmod(0o600); err != nil {
temp.Close()
return err
}
if _, err := temp.Write(data); err != nil {
temp.Close()
return err
}
if err := temp.Sync(); err != nil {
temp.Close()
return err
}
if err := temp.Close(); err != nil {
return err
}
return os.Rename(tempPath, path)
}