claude-code: set shared agent text at the controller's terminal, for now
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.
This commit is contained in:
@@ -339,3 +339,83 @@ func TestAFileReferenceOutsideTheItemIsRefused(t *testing.T) {
|
||||
t.Errorf("a reference to its own file or a mail address was refused: %s", problem)
|
||||
}
|
||||
}
|
||||
|
||||
// Text for every node or some is set at the controller's terminal for now (novox/hq issue 340, the operator's
|
||||
// decision of 2026-10-09): through the tools, an instruction section, an output style, a skill, a command or a
|
||||
// subagent at the mesh or node scope is refused, saying where it is set, and so is an import to those scopes. The
|
||||
// home scope, one account's own directory, is still registered through them.
|
||||
func TestSharedTextIsRefusedThroughTheToolsAndHomeIsNot(t *testing.T) {
|
||||
p, _ := node(t, "laptop")
|
||||
servers, config := &bus{kept: map[string]map[string]any{}}, memConfig{}
|
||||
s := serve(t, p, servers, config)
|
||||
writeFile(t, filepath.Join(p.Home, ".claude", "x.md"), "") // the home exists
|
||||
_ = os.MkdirAll(filepath.Join(p.Home, ".claude", "commands"), 0o755)
|
||||
writeFile(t, filepath.Join(p.Home, ".claude", "commands", "mine.md"), "Ship it.")
|
||||
for _, tool := range []string{"instructions", "output_style", "skill", "command", "agent"} {
|
||||
for _, scope := range []map[string]any{{}, {"scope": "mesh"}, {"scope": "node"}, {"scope": "node", "nodes": "laptop,server"}} {
|
||||
args := map[string]any{"name": "x", "content": "---\nname: x\ndescription: d\n---\nRead the mesh's rules.\n"}
|
||||
for k, v := range scope {
|
||||
args[k] = v
|
||||
}
|
||||
text, isError := s.call(t, "claude_code_"+tool+"_register", args)
|
||||
if !isError || !strings.Contains(text, "controller's terminal") || !strings.Contains(text, "issue 340") {
|
||||
t.Errorf("%s %v was not refused, or did not say where it is set: %s", tool, scope, text)
|
||||
}
|
||||
}
|
||||
}
|
||||
if text, isError := s.call(t, "claude_code_config_import", map[string]any{"kind": "command", "name": "mine", "scope": "mesh"}); !isError ||
|
||||
!strings.Contains(text, "issue 340") {
|
||||
t.Errorf("an import to the mesh was not refused: %s", text)
|
||||
}
|
||||
if len(config) != 0 {
|
||||
t.Fatalf("a refused call kept something on the bus: %v", config)
|
||||
}
|
||||
text, isError := s.call(t, "claude_code_command_register", map[string]any{"name": "ship", "scope": "home",
|
||||
"content": "---\ndescription: ship it\n---\nShip it."})
|
||||
if isError || config["home.laptop.command.ship"] == nil {
|
||||
t.Fatalf("a command for this home was refused: %s", text)
|
||||
}
|
||||
}
|
||||
|
||||
// What the operator sets at the terminal lands where a registration did: each text a list of its lines, laid over
|
||||
// what was registered before. It is the operator's word, so what it asks of a session is not judged; an item that
|
||||
// is not well formed is left out, and said.
|
||||
func TestSharedTextSetAtTheTerminalLands(t *testing.T) {
|
||||
p, w := node(t, "laptop")
|
||||
writeFile(t, p.Settings, `{"role":"","mcp_servers":{},
|
||||
"instructions":{"conventions":["Commit in the imperative.","","Test before pushing."]},
|
||||
"output_styles":{"terse":["---","name: terse","---","Short."]},
|
||||
"skills":{"review":{"SKILL.md":["---","name: review","description: d","allowed-tools: Read","---","Review it."],"scripts/run.sh":["#!/bin/sh","echo hi"]},
|
||||
"broken":{"notes.md":"no SKILL.md"}},
|
||||
"commands":{"ship":"Ship it."},
|
||||
"agents":{"reviewer":["---","name: reviewer","---","x"],"Bad Name":"x"}}`)
|
||||
state, view := memConfig{}, NewConfigView(p)
|
||||
registeredBefore(t, p, w, state, view, Item{Kind: KindInstructions, Name: "conventions", Scope: ScopeMesh,
|
||||
Files: one("conventions", "The registration from before.")})
|
||||
out, err := RenderNow(p, writer(w))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(w["CLAUDE.md"], "## Instructions set at the controller's terminal") ||
|
||||
!strings.Contains(w["CLAUDE.md"], "### conventions\n\nCommit in the imperative.\n\nTest before pushing.") {
|
||||
t.Errorf("the instructions:\n%s", w["CLAUDE.md"])
|
||||
}
|
||||
plugin := pluginOf(t, w)
|
||||
root := "plugins/" + Plugin + "/"
|
||||
for _, want := range []string{"skills/review/SKILL.md", "skills/review/scripts/run.sh", "output-styles/terse.md",
|
||||
"commands/ship.md", "agents/reviewer.md"} {
|
||||
if _, ok := plugin[root+want]; !ok {
|
||||
t.Errorf("the plugin lacks %s; %v", want, out)
|
||||
}
|
||||
}
|
||||
if !plugin[root+"skills/review/scripts/run.sh"].Executable || !strings.Contains(plugin[root+"skills/review/SKILL.md"].Content, "allowed-tools: Read") {
|
||||
t.Error("the operator's skill was not taken as written")
|
||||
}
|
||||
if _, ok := plugin[root+"skills/broken/notes.md"]; ok {
|
||||
t.Error("a skill without its SKILL.md was placed")
|
||||
}
|
||||
said := strings.Join(out, "\n")
|
||||
if !strings.Contains(said, "skill broken") || !strings.Contains(said, "agent Bad Name") {
|
||||
t.Errorf("what was left out was not said:\n%s", said)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user