Instructions, output styles, skills, commands and subagents for every node or some reach every session as the mesh's own word, and the tools cannot tell an agent from the operator; the operator decided they are set at the terminal until a surface of their own updates them (hq issue 340). They are settings keys, each text a list of lines; the home scope stays free.
90 lines
3.1 KiB
Go
90 lines
3.1 KiB
Go
package main
|
|
|
|
// The shared text the operator sets at the controller's terminal (novox/hq issue 340, the operator's decision of
|
|
// 2026-10-09): instruction sections, output styles, skills, commands and subagents for every node or some. They are
|
|
// keys of this module's settings, which the controller takes at its terminal alone. A setting holds no line break
|
|
// (issue 339), so each text is a list of its lines; a one-line text may be a string.
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"os"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// Lines is a text as a setting holds it: a list of its lines, or one line as a string.
|
|
type Lines string
|
|
|
|
// UnmarshalJSON takes a string or a list of strings, joined by line breaks.
|
|
func (l *Lines) UnmarshalJSON(raw []byte) error {
|
|
var one string
|
|
if json.Unmarshal(raw, &one) == nil {
|
|
*l = Lines(one)
|
|
return nil
|
|
}
|
|
var many []string
|
|
if err := json.Unmarshal(raw, &many); err != nil {
|
|
return errors.New("a text is a list of its lines, or one line")
|
|
}
|
|
*l = Lines(strings.Join(many, "\n") + "\n")
|
|
return nil
|
|
}
|
|
|
|
// SharedText is the text keys of this module's settings.
|
|
type SharedText struct {
|
|
Instructions map[string]Lines `json:"instructions"`
|
|
OutputStyles map[string]Lines `json:"output_styles"`
|
|
Agents map[string]Lines `json:"agents"`
|
|
Commands map[string]Lines `json:"commands"`
|
|
Skills map[string]map[string]Lines `json:"skills"`
|
|
}
|
|
|
|
// TerminalItems are the items the operator's settings hold, as the plugin and the instruction file take them, and
|
|
// what was left out and why: an item that is not well formed (a name, a path, a skill without its SKILL.md). What
|
|
// the text asks of a session is the operator's to say there, so it is not judged.
|
|
func TerminalItems(text SharedText) ([]Item, []string) {
|
|
var out []Item
|
|
var left []string
|
|
add := func(it Item) {
|
|
it.Scope, it.By = ScopeMesh, "the controller's terminal"
|
|
if problem := it.shapeProblem(); problem != "" {
|
|
left = append(left, it.Kind+" "+it.Name+": "+problem)
|
|
return
|
|
}
|
|
out = append(out, it)
|
|
}
|
|
for kind, texts := range map[string]map[string]Lines{KindInstructions: text.Instructions,
|
|
KindOutputStyle: text.OutputStyles, KindAgent: text.Agents, KindCommand: text.Commands} {
|
|
for name, body := range texts {
|
|
add(Item{Kind: kind, Name: name, Files: map[string]string{name + ".md": string(body)}})
|
|
}
|
|
}
|
|
for name, files := range text.Skills {
|
|
it := Item{Kind: KindSkill, Name: name, Files: map[string]string{}}
|
|
for rel, body := range files {
|
|
it.Files[rel] = string(body)
|
|
}
|
|
add(it)
|
|
}
|
|
sort.Slice(out, func(i, j int) bool {
|
|
if out[i].Kind != out[j].Kind {
|
|
return out[i].Kind < out[j].Kind
|
|
}
|
|
return out[i].Name < out[j].Name
|
|
})
|
|
sort.Strings(left)
|
|
return out, left
|
|
}
|
|
|
|
// terminalText reads the shared text from this module's settings file.
|
|
func terminalText(p Paths) ([]Item, []string) {
|
|
var text SharedText
|
|
if raw, err := os.ReadFile(p.Settings); err == nil {
|
|
if err := json.Unmarshal(raw, &text); err != nil {
|
|
return nil, []string{"the shared text in this module's settings could not be read: " + err.Error()}
|
|
}
|
|
}
|
|
return TerminalItems(text)
|
|
}
|