pi-agent: Implement progressive Target detection

This commit is contained in:
proxyctl coordinator
2026-07-17 21:01:35 +08:00
parent fbb81ffb40
commit ad651402fd
15 changed files with 558 additions and 31 deletions
+116 -9
View File
@@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"strings"
"sync"
"time"
"proxyctl/internal/profile"
@@ -31,15 +32,41 @@ type Intent struct {
Enabled bool
}
type TargetDetectionPhase uint8
const (
// TargetReady is the zero value so snapshots supplied by non-progressive
// Modules remain compatible with the established dashboard contract.
TargetReady TargetDetectionPhase = iota
TargetChecking
TargetDetectionFailed
)
type TargetSnapshot struct {
ID string
Description target.Description
DetectionPhase TargetDetectionPhase
DetectionErr error
Available bool
AvailabilityEvidence string
State target.State
Evidence string
}
type TargetDetectionResult struct {
TargetID string
Phase TargetDetectionPhase
Availability target.Availability
Inspection target.Inspection
Err error
}
type TargetDetectionUpdate struct {
Snapshot Snapshot
Result TargetDetectionResult
Complete bool
}
type Snapshot struct {
Profiles []profile.Profile
ActiveProfile string
@@ -75,12 +102,22 @@ type ProgressiveModule interface {
DispatchProgress(Intent) <-chan Result
}
// ProgressiveDetectionModule exposes a useful all-Checking snapshot
// synchronously, followed by one structured Target result at a time. Startup
// and explicit refresh use this same presentation-independent contract.
type ProgressiveDetectionModule interface {
BeginTargetDetection() (Snapshot, <-chan TargetDetectionUpdate)
}
type Application struct {
store profile.Store
state profile.State
targets []target.Target
availabilities map[string]target.Availability
inspections map[string]target.Inspection
detectionPhase map[string]TargetDetectionPhase
detectionErr map[string]error
detectionMu sync.RWMutex
mutationLock MutationLock
logger OperationLogger
}
@@ -112,24 +149,93 @@ func NewWithTargetsAndRuntime(store profile.Store, targets []target.Target, muta
targets: append([]target.Target(nil), targets...),
availabilities: make(map[string]target.Availability),
inspections: make(map[string]target.Inspection),
detectionPhase: make(map[string]TargetDetectionPhase),
detectionErr: make(map[string]error),
mutationLock: mutationLock,
logger: logger,
}
a.refreshTargets()
for _, adapter := range a.targets {
a.detectionPhase[adapter.ID()] = TargetChecking
}
return a, nil
}
func (a *Application) Snapshot() Snapshot {
a.detectionMu.RLock()
defer a.detectionMu.RUnlock()
return a.snapshotLocked()
}
func (a *Application) snapshotLocked() Snapshot {
profiles := append([]profile.Profile(nil), a.state.Profiles...)
snapshot := Snapshot{Profiles: profiles, ActiveProfile: a.state.ActiveProfile}
for _, adapter := range a.targets {
availability := a.availabilities[adapter.ID()]
inspection := a.inspections[adapter.ID()]
snapshot.Targets = append(snapshot.Targets, TargetSnapshot{ID: adapter.ID(), Description: adapter.Description(), Available: availability.Available, AvailabilityEvidence: availability.Evidence, State: inspection.State, Evidence: inspection.Evidence})
id := adapter.ID()
availability := a.availabilities[id]
inspection := a.inspections[id]
snapshot.Targets = append(snapshot.Targets, TargetSnapshot{
ID: id, Description: adapter.Description(), DetectionPhase: a.detectionPhase[id], DetectionErr: a.detectionErr[id],
Available: availability.Available, AvailabilityEvidence: availability.Evidence, State: inspection.State, Evidence: inspection.Evidence,
})
}
return snapshot
}
func (a *Application) BeginTargetDetection() (Snapshot, <-chan TargetDetectionUpdate) {
a.detectionMu.Lock()
for _, adapter := range a.targets {
id := adapter.ID()
a.detectionPhase[id] = TargetChecking
a.detectionErr[id] = nil
}
checking := a.snapshotLocked()
a.detectionMu.Unlock()
updates := make(chan TargetDetectionUpdate, len(a.targets)+1)
active := a.activeProfile()
outcomes := make(chan TargetDetectionResult, len(a.targets))
for _, adapter := range a.targets {
adapter := adapter
go func() {
detection := detectTarget(adapter, active)
phase := TargetReady
if detection.Err != nil {
phase = TargetDetectionFailed
}
outcomes <- TargetDetectionResult{
TargetID: adapter.ID(), Phase: phase, Availability: detection.Availability,
Inspection: detection.Inspection, Err: detection.Err,
}
}()
}
go func() {
defer close(updates)
for range a.targets {
result := <-outcomes
a.detectionMu.Lock()
a.availabilities[result.TargetID] = result.Availability
a.inspections[result.TargetID] = result.Inspection
a.detectionPhase[result.TargetID] = result.Phase
a.detectionErr[result.TargetID] = result.Err
snapshot := a.snapshotLocked()
a.detectionMu.Unlock()
updates <- TargetDetectionUpdate{Snapshot: snapshot, Result: result}
}
updates <- TargetDetectionUpdate{Snapshot: a.Snapshot(), Complete: true}
}()
return checking, updates
}
func detectTarget(adapter target.Target, active *profile.Profile) target.Detection {
if detector, ok := adapter.(target.Detector); ok {
return detector.Detect(context.Background(), active)
}
return target.Detection{
Availability: adapter.Availability(context.Background()),
Inspection: adapter.Inspect(context.Background(), active),
}
}
func (a *Application) Dispatch(intent Intent) Result {
return a.dispatch(intent, nil)
}
@@ -319,17 +425,18 @@ func (a *Application) activeProfile() *profile.Profile {
}
func (a *Application) refreshTargets() {
active := a.activeProfile()
for _, adapter := range a.targets {
a.availabilities[adapter.ID()] = adapter.Availability(context.Background())
a.inspections[adapter.ID()] = adapter.Inspect(context.Background(), active)
_, updates := a.BeginTargetDetection()
for range updates {
}
}
func (a *Application) refreshInspections() {
active := a.activeProfile()
for _, adapter := range a.targets {
a.inspections[adapter.ID()] = adapter.Inspect(context.Background(), active)
inspection := adapter.Inspect(context.Background(), active)
a.detectionMu.Lock()
a.inspections[adapter.ID()] = inspection
a.detectionMu.Unlock()
}
}