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

300 lines
6.7 KiB
Go

package core
import (
"fmt"
"sort"
"strings"
)
type Status string
const (
StatusActive Status = "active"
StatusWarning Status = "warning"
StatusError Status = "error"
)
type Issue struct {
Code string
Message string
}
type State struct {
Status Status
Scope string
Active bool
Issues []Issue
}
type ScopeIssueMessages struct {
Reserved func(scope string) Issue
Conflict func(scope string, winnerAppID string) Issue
}
type ScopeSetOptions struct {
ReservedScopes map[string]struct{}
IssueMessages ScopeIssueMessages
}
type RegisteredApp struct {
AppID string
AppName string
Scope string
BaseURL string
Commands []Command
}
type CapabilityListOptions struct {
IncludeIntegration bool
}
type CommandRef struct {
AppID string
CommandID string
}
type ScopeSet struct {
reservedScopes map[string]struct{}
issueMessages ScopeIssueMessages
entries map[string]*scopeEntry
states map[string]State
commandID map[string]CommandRef
}
type scopeEntry struct {
RegisteredApp
active bool
issues []Issue
}
func NewScopeSet(options ScopeSetOptions) *ScopeSet {
reservedScopes := map[string]struct{}{}
for scope := range options.ReservedScopes {
reservedScopes[strings.TrimSpace(scope)] = struct{}{}
}
return &ScopeSet{
reservedScopes: reservedScopes,
issueMessages: options.IssueMessages,
entries: map[string]*scopeEntry{},
states: map[string]State{},
commandID: map[string]CommandRef{},
}
}
func (s *ScopeSet) Upsert(app RegisteredApp) State {
if s == nil {
return State{}
}
s.ensure()
app.AppID = strings.TrimSpace(app.AppID)
app.Scope = strings.TrimSpace(app.Scope)
if app.AppID == "" {
return State{}
}
s.entries[app.AppID] = &scopeEntry{RegisteredApp: app}
s.recompute()
return s.State(app.AppID)
}
func (s *ScopeSet) SetError(appID string, scope string, issue Issue) State {
if s == nil {
return State{}
}
s.ensure()
appID = strings.TrimSpace(appID)
if appID == "" {
return State{}
}
delete(s.entries, appID)
s.recompute()
state := State{
Status: StatusError,
Scope: strings.TrimSpace(scope),
Active: false,
Issues: []Issue{issue},
}
s.states[appID] = state
return cloneState(state)
}
func (s *ScopeSet) Remove(appID string) {
if s == nil {
return
}
s.ensure()
delete(s.entries, strings.TrimSpace(appID))
delete(s.states, strings.TrimSpace(appID))
s.recompute()
}
func (s *ScopeSet) Capabilities(options ...CapabilityListOptions) []Capability {
if s == nil || len(s.entries) == 0 {
return []Capability{}
}
includeIntegration := false
if len(options) > 0 {
includeIntegration = options[0].IncludeIntegration
}
appIDs := make([]string, 0, len(s.entries))
for appID := range s.entries {
appIDs = append(appIDs, appID)
}
sort.Strings(appIDs)
result := make([]Capability, 0)
for _, appID := range appIDs {
entry := s.entries[appID]
if entry == nil || !entry.active {
continue
}
for _, command := range entry.Commands {
if !includeIntegration && NormalizeVisibility(command.Capability.Visibility) == CommandVisibilityIntegration {
continue
}
result = append(result, command.Capability)
}
}
return result
}
func (s *ScopeSet) Command(commandID string) (RegisteredApp, Command, bool) {
if s == nil {
return RegisteredApp{}, Command{}, false
}
ref, ok := s.commandID[strings.TrimSpace(commandID)]
if !ok {
return RegisteredApp{}, Command{}, false
}
entry := s.entries[ref.AppID]
if entry == nil || !entry.active {
return RegisteredApp{}, Command{}, false
}
for _, command := range entry.Commands {
if command.Capability.ID == ref.CommandID {
return entry.RegisteredApp, command, true
}
}
return RegisteredApp{}, Command{}, false
}
func (s *ScopeSet) UpdateBaseURL(appID string, baseURL string) {
if s == nil {
return
}
if entry := s.entries[strings.TrimSpace(appID)]; entry != nil {
entry.BaseURL = strings.TrimRight(strings.TrimSpace(baseURL), "/")
}
}
func (s *ScopeSet) State(appID string) State {
if s == nil {
return State{}
}
state, ok := s.states[strings.TrimSpace(appID)]
if !ok {
return State{}
}
return cloneState(state)
}
func (s *ScopeSet) Empty() bool {
return s == nil || len(s.entries) == 0 && len(s.states) == 0
}
func (s *ScopeSet) ensure() {
if s.entries == nil {
s.entries = map[string]*scopeEntry{}
}
if s.states == nil {
s.states = map[string]State{}
}
if s.commandID == nil {
s.commandID = map[string]CommandRef{}
}
if s.reservedScopes == nil {
s.reservedScopes = map[string]struct{}{}
}
}
func (s *ScopeSet) recompute() {
s.ensure()
for commandID := range s.commandID {
delete(s.commandID, commandID)
}
for appID, state := range s.states {
if state.Status != StatusError {
delete(s.states, appID)
}
}
if len(s.entries) == 0 {
return
}
byScope := map[string][]*scopeEntry{}
for _, entry := range s.entries {
if entry == nil {
continue
}
entry.active = false
entry.issues = nil
byScope[entry.Scope] = append(byScope[entry.Scope], entry)
}
for scope, scopedEntries := range byScope {
sort.Slice(scopedEntries, func(left, right int) bool {
return scopedEntries[left].AppID < scopedEntries[right].AppID
})
if _, reserved := s.reservedScopes[scope]; reserved {
for _, entry := range scopedEntries {
entry.issues = []Issue{s.reservedIssue(scope)}
s.states[entry.AppID] = stateFromEntry(entry, StatusWarning)
}
continue
}
winner := scopedEntries[0]
winner.active = true
s.states[winner.AppID] = stateFromEntry(winner, StatusActive)
for _, command := range winner.Commands {
s.commandID[command.Capability.ID] = CommandRef{
AppID: winner.AppID,
CommandID: command.Capability.ID,
}
}
for _, loser := range scopedEntries[1:] {
loser.issues = []Issue{s.conflictIssue(scope, winner.AppID)}
s.states[loser.AppID] = stateFromEntry(loser, StatusWarning)
}
}
}
func (s *ScopeSet) reservedIssue(scope string) Issue {
if s.issueMessages.Reserved != nil {
return s.issueMessages.Reserved(scope)
}
return Issue{
Code: "app_cli_scope_reserved",
Message: fmt.Sprintf("CLI scope %q is reserved.", scope),
}
}
func (s *ScopeSet) conflictIssue(scope string, winnerAppID string) Issue {
if s.issueMessages.Conflict != nil {
return s.issueMessages.Conflict(scope, winnerAppID)
}
return Issue{
Code: "app_cli_scope_conflict",
Message: fmt.Sprintf("CLI scope %q is already provided by app %q.", scope, winnerAppID),
}
}
func stateFromEntry(entry *scopeEntry, status Status) State {
return State{
Status: status,
Scope: entry.Scope,
Active: status == StatusActive,
Issues: append([]Issue(nil), entry.issues...),
}
}
func cloneState(state State) State {
state.Issues = append([]Issue(nil), state.Issues...)
return state
}