Files
chenhg5-cc-connect/core/bridge_capabilities.go

145 lines
4.2 KiB
Go

package core
import (
"os"
"sort"
"strings"
)
const (
bridgeCapabilitiesSnapshotType = "capabilities_snapshot"
bridgeCapabilitiesSnapshotProto = "capabilities_snapshot_v1"
bridgeCommandArgsModeText = "text"
bridgeCommandSourceBuiltin = "builtin"
bridgeCommandSourceCustom = "custom"
)
// CurrentCommit is set by main at startup so bridge clients can inspect the
// host binary that produced a capability snapshot.
var CurrentCommit string
// CurrentBuildTime is set by main at startup so bridge clients can compare
// host snapshots without reverse-engineering git-describe version strings.
var CurrentBuildTime string
type bridgeCapabilitiesSnapshot struct {
Type string `json:"type"`
Version int `json:"v"`
Host bridgeCapabilitiesHost `json:"host"`
Projects []bridgeProjectCapabilities `json:"projects"`
}
type bridgeCapabilitiesHost struct {
ID string `json:"id"`
Hostname string `json:"hostname,omitempty"`
CCConnectVersion string `json:"cc_connect_version,omitempty"`
Commit string `json:"commit,omitempty"`
BuildTime string `json:"build_time,omitempty"`
}
type bridgeProjectCapabilities struct {
Project string `json:"project"`
Commands []bridgePublishedCommand `json:"commands"`
}
type bridgePublishedCommand struct {
Name string `json:"name"`
Description string `json:"description"`
Source string `json:"source"`
RequiresWorkspace bool `json:"requires_workspace"`
ArgsMode string `json:"args_mode"`
}
// GetBridgePublishedCommands returns the subset of commands that a bridge
// control-plane client can safely expose as slash commands. It intentionally
// excludes skills and other richer command models until the bridge protocol
// grows beyond the single free-form "args" text bucket.
func (e *Engine) GetBridgePublishedCommands() []bridgePublishedCommand {
e.userRolesMu.RLock()
disabledCmds := e.disabledCmds
e.userRolesMu.RUnlock()
seen := make(map[string]bool)
var commands []bridgePublishedCommand
for _, c := range builtinCommands {
if len(c.names) == 0 || disabledCmds[c.id] {
continue
}
if seen[c.id] {
continue
}
seen[c.id] = true
commands = append(commands, bridgePublishedCommand{
Name: c.id,
Description: e.i18n.T(MsgKey(c.id)),
Source: bridgeCommandSourceBuiltin,
RequiresWorkspace: false,
ArgsMode: bridgeCommandArgsModeText,
})
}
customCommands := e.commands.ListAll()
sort.Slice(customCommands, func(i, j int) bool {
return strings.ToLower(customCommands[i].Name) < strings.ToLower(customCommands[j].Name)
})
for _, c := range customCommands {
lowerName := strings.ToLower(strings.TrimSpace(c.Name))
if lowerName == "" || seen[lowerName] || disabledCmds[lowerName] {
continue
}
seen[lowerName] = true
desc := strings.TrimSpace(c.Description)
if desc == "" {
desc = "Custom command"
}
commands = append(commands, bridgePublishedCommand{
Name: c.Name,
Description: desc,
Source: bridgeCommandSourceCustom,
RequiresWorkspace: false,
ArgsMode: bridgeCommandArgsModeText,
})
}
return commands
}
func (bs *BridgeServer) buildCapabilitiesSnapshot() bridgeCapabilitiesSnapshot {
hostName, _ := os.Hostname()
projects := make([]bridgeProjectCapabilities, 0, len(bs.engines))
bs.enginesMu.RLock()
projectNames := make([]string, 0, len(bs.engines))
for projectName := range bs.engines {
projectNames = append(projectNames, projectName)
}
sort.Strings(projectNames)
for _, projectName := range projectNames {
ref := bs.engines[projectName]
if ref == nil || ref.engine == nil {
continue
}
projects = append(projects, bridgeProjectCapabilities{
Project: projectName,
Commands: ref.engine.GetBridgePublishedCommands(),
})
}
bs.enginesMu.RUnlock()
return bridgeCapabilitiesSnapshot{
Type: bridgeCapabilitiesSnapshotType,
Version: 1,
Host: bridgeCapabilitiesHost{
ID: hostName,
Hostname: hostName,
CCConnectVersion: CurrentVersion,
Commit: CurrentCommit,
BuildTime: CurrentBuildTime,
},
Projects: projects,
}
}