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

346 lines
9.0 KiB
Go

package agentstatus
import (
"context"
"os"
"path/filepath"
"strings"
"sync"
"time"
"golang.org/x/sync/singleflight"
)
type executableFingerprint struct {
info os.FileInfo
resolvedPath string
}
func readExecutableFingerprint(path string) (executableFingerprint, bool) {
path = strings.TrimSpace(path)
if path == "" {
return executableFingerprint{}, false
}
resolvedPath, err := filepath.EvalSymlinks(path)
if err != nil {
resolvedPath = filepath.Clean(path)
}
info, err := os.Stat(resolvedPath)
if err != nil {
return executableFingerprint{}, false
}
return executableFingerprint{
info: info,
resolvedPath: filepath.Clean(resolvedPath),
}, true
}
func sameExecutableFingerprint(left executableFingerprint, right executableFingerprint) bool {
return left.resolvedPath == right.resolvedPath &&
left.info.Size() == right.info.Size() &&
left.info.ModTime().Equal(right.info.ModTime()) &&
os.SameFile(left.info, right.info)
}
type cliVersionCacheEntry struct {
fingerprint executableFingerprint
output string
}
// CLIVersionCache reuses successful `--version` output until the resolved
// executable changes. A forced auth/readiness refresh does not need to restart
// an unchanged binary just to rediscover the same version.
type CLIVersionCache struct {
mu sync.RWMutex
entries map[string]cliVersionCacheEntry
group singleflight.Group
}
func NewCLIVersionCache() *CLIVersionCache {
return &CLIVersionCache{entries: make(map[string]cliVersionCacheEntry)}
}
func (c *CLIVersionCache) load(binaryPath string, loader func() string) string {
if c == nil {
return loader()
}
key := filepath.Clean(strings.TrimSpace(binaryPath))
if output, ok := c.get(key); ok {
return output
}
value, _, _ := c.group.Do(key, func() (any, error) {
if output, ok := c.get(key); ok {
return output, nil
}
output := loader()
if output != "" {
c.set(key, output)
}
return output, nil
})
return value.(string)
}
func (c *CLIVersionCache) get(binaryPath string) (string, bool) {
fingerprint, ok := readExecutableFingerprint(binaryPath)
if !ok {
return "", false
}
c.mu.RLock()
entry, found := c.entries[binaryPath]
c.mu.RUnlock()
if !found || !sameExecutableFingerprint(entry.fingerprint, fingerprint) {
return "", false
}
return entry.output, true
}
func (c *CLIVersionCache) set(binaryPath string, output string) {
fingerprint, ok := readExecutableFingerprint(binaryPath)
if !ok {
return
}
c.mu.Lock()
c.entries[binaryPath] = cliVersionCacheEntry{
fingerprint: fingerprint,
output: output,
}
c.mu.Unlock()
}
type adapterProbeCacheEntry struct {
fingerprint executableFingerprint
checkedAt time.Time
}
// AdapterProbeCache stores only successful launch probes. Failures are always
// retried, and explicit refresh/probe paths bypass this cache.
type AdapterProbeCache struct {
mu sync.RWMutex
entries map[string]adapterProbeCacheEntry
}
type bunGlobalBinCacheEntry struct {
fingerprint executableFingerprint
binDir string
}
type globalBinDiscoveryCacheEntry struct {
fingerprint executableFingerprint
binDir string
}
// BunGlobalBinCache caches only successful `bun pm bin -g` discoveries until
// the resolved Bun executable changes. Failures are retried so installing Bun
// or correcting bunfig.toml does not require a daemon restart.
type BunGlobalBinCache struct {
mu sync.RWMutex
entries map[string]bunGlobalBinCacheEntry
group singleflight.Group
}
func NewBunGlobalBinCache() *BunGlobalBinCache {
return &BunGlobalBinCache{entries: make(map[string]bunGlobalBinCacheEntry)}
}
func (c *BunGlobalBinCache) load(bunPath string, loader func() string) string {
if c == nil {
return loader()
}
key := filepath.Clean(strings.TrimSpace(bunPath))
if binDir, ok := c.get(key); ok {
return binDir
}
value, _, _ := c.group.Do(key, func() (any, error) {
if binDir, ok := c.get(key); ok {
return binDir, nil
}
binDir := loader()
if binDir != "" {
c.set(key, binDir)
}
return binDir, nil
})
return value.(string)
}
func (c *BunGlobalBinCache) get(bunPath string) (string, bool) {
fingerprint, ok := readExecutableFingerprint(bunPath)
if !ok {
return "", false
}
c.mu.RLock()
entry, found := c.entries[bunPath]
c.mu.RUnlock()
if !found || !sameExecutableFingerprint(entry.fingerprint, fingerprint) {
return "", false
}
return entry.binDir, true
}
func (c *BunGlobalBinCache) set(bunPath string, binDir string) {
fingerprint, ok := readExecutableFingerprint(bunPath)
if !ok {
return
}
c.mu.Lock()
c.entries[bunPath] = bunGlobalBinCacheEntry{fingerprint: fingerprint, binDir: binDir}
c.mu.Unlock()
}
// GlobalBinDiscoveryCache caches successful global-bin discovery commands
// such as `pnpm bin -g`, `npm prefix -g`, and `brew --prefix`. Entries are
// invalidated whenever their manager executable changes; failed queries are
// intentionally not cached so a newly-installed manager is visible without a
// daemon restart.
type GlobalBinDiscoveryCache struct {
mu sync.RWMutex
entries map[string]globalBinDiscoveryCacheEntry
group singleflight.Group
}
func NewGlobalBinDiscoveryCache() *GlobalBinDiscoveryCache {
return &GlobalBinDiscoveryCache{entries: make(map[string]globalBinDiscoveryCacheEntry)}
}
func (c *GlobalBinDiscoveryCache) load(managerPath, queryKey string, loader func() string) string {
if c == nil {
return loader()
}
key := globalBinDiscoveryCacheKey(managerPath, queryKey)
if binDir, ok := c.get(key, managerPath); ok {
return binDir
}
value, _, _ := c.group.Do(key, func() (any, error) {
if binDir, ok := c.get(key, managerPath); ok {
return binDir, nil
}
binDir := loader()
if binDir != "" {
c.set(key, managerPath, binDir)
}
return binDir, nil
})
return value.(string)
}
func (c *GlobalBinDiscoveryCache) get(key, managerPath string) (string, bool) {
fingerprint, ok := readExecutableFingerprint(managerPath)
if !ok {
return "", false
}
c.mu.RLock()
entry, found := c.entries[key]
c.mu.RUnlock()
if !found || !sameExecutableFingerprint(entry.fingerprint, fingerprint) {
return "", false
}
return entry.binDir, true
}
func (c *GlobalBinDiscoveryCache) set(key, managerPath, binDir string) {
fingerprint, ok := readExecutableFingerprint(managerPath)
if !ok {
return
}
c.mu.Lock()
c.entries[key] = globalBinDiscoveryCacheEntry{fingerprint: fingerprint, binDir: binDir}
c.mu.Unlock()
}
func globalBinDiscoveryCacheKey(managerPath, queryKey string) string {
return filepath.Clean(strings.TrimSpace(managerPath)) + "\x00" + strings.TrimSpace(queryKey)
}
func NewAdapterProbeCache() *AdapterProbeCache {
return &AdapterProbeCache{entries: make(map[string]adapterProbeCacheEntry)}
}
func (c *AdapterProbeCache) ready(key string, binaryPath string) bool {
return c.readyWithin(key, binaryPath, time.Now(), 0)
}
// readyWithin accepts only a prior successful protocol handshake whose
// executable identity and freshness window still match. A positive probe cache
// is a latency optimization for List; it is never a source of failure or repair
// evidence.
func (c *AdapterProbeCache) readyWithin(key string, binaryPath string, now time.Time, ttl time.Duration) bool {
if c == nil {
return false
}
fingerprint, ok := readExecutableFingerprint(binaryPath)
if !ok {
return false
}
c.mu.RLock()
entry, found := c.entries[key]
c.mu.RUnlock()
if !found || !sameExecutableFingerprint(entry.fingerprint, fingerprint) {
return false
}
return ttl <= 0 || !now.After(entry.checkedAt.Add(ttl))
}
func (c *AdapterProbeCache) age(key string, binaryPath string, now time.Time) (time.Duration, bool) {
if c == nil {
return 0, false
}
fingerprint, ok := readExecutableFingerprint(binaryPath)
if !ok {
return 0, false
}
c.mu.RLock()
entry, found := c.entries[key]
c.mu.RUnlock()
if !found || !sameExecutableFingerprint(entry.fingerprint, fingerprint) {
return 0, false
}
return now.Sub(entry.checkedAt), true
}
func (c *AdapterProbeCache) markReady(key string, binaryPath string) {
c.markReadyAt(key, binaryPath, time.Now())
}
func (c *AdapterProbeCache) markReadyAt(key string, binaryPath string, checkedAt time.Time) {
if c == nil {
return
}
fingerprint, ok := readExecutableFingerprint(binaryPath)
if !ok {
return
}
c.mu.Lock()
c.entries[key] = adapterProbeCacheEntry{fingerprint: fingerprint, checkedAt: checkedAt}
c.mu.Unlock()
}
// DetectionCommandLimiter bounds actual auth/version/adapter subprocesses
// across concurrent List requests. Provider-level concurrency alone does not
// cover the multiple commands started inside each provider.
type DetectionCommandLimiter struct {
slots chan struct{}
}
func NewDetectionCommandLimiter(limit int) *DetectionCommandLimiter {
if limit < 1 {
limit = 1
}
return &DetectionCommandLimiter{slots: make(chan struct{}, limit)}
}
func (l *DetectionCommandLimiter) acquire(ctx context.Context) (func(), bool) {
if l == nil {
return func() {}, true
}
if ctx == nil {
ctx = context.Background()
}
select {
case l.slots <- struct{}{}:
return func() { <-l.slots }, true
case <-ctx.Done():
return nil, false
}
}