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:
jochen
2026-10-09 09:41:52 +02:00
parent c613efe189
commit e139fb4dd9
9 changed files with 267 additions and 23 deletions
@@ -41,6 +41,26 @@ func deliver(m memConfig, v *ConfigView) {
}
}
// registeredBefore puts an item on the bus as a registration made before mesh- and node-scope text moved to the
// controller's terminal (novox/hq issue 340), for each node given (none at the mesh scope), as every node's watch
// would hand it over to this one, and renders.
func registeredBefore(t *testing.T, p Paths, w map[string]string, state memConfig, view *ConfigView, it Item, nodes ...string) {
t.Helper()
if it.Scope == ScopeMesh {
nodes = []string{""}
}
for _, n := range nodes {
item := it
if err := state.Put(it.Key(n), item); err != nil {
t.Fatal(err)
}
view.Take(it.Key(n), "put", &item)
}
if _, err := RenderNow(p, writer(w)); err != nil {
t.Fatal(err)
}
}
func one(name, content string) map[string]string { return map[string]string{name + ".md": content} }
func pluginOf(t *testing.T, w map[string]string) map[string]PluginFile {
@@ -57,10 +77,10 @@ func TestEachKindLandsInItsOnePlace(t *testing.T) {
state, view := memConfig{}, NewConfigView(p)
register := func(it Item) {
t.Helper()
answer, err := Register(p, it, nil, false, state, view, writer(w))
if err != nil || answer["registered"] == false {
t.Fatalf("%s %s: %v %v", it.Kind, it.Name, answer, err)
if problem := it.Problem(); problem != "" {
t.Fatalf("%s %s: %s", it.Kind, it.Name, problem)
}
registeredBefore(t, p, w, state, view, it)
}
register(Item{Kind: KindSkill, Name: "review", Scope: ScopeMesh,
Files: map[string]string{"SKILL.md": "---\nname: review\ndescription: d\n---\nbody", "scripts/run.sh": "#!/bin/sh\necho hi\n"}})
@@ -107,9 +127,13 @@ func TestAMeshItemReachesEveryNodeANodeItemOneAndAHomeItemOneHome(t *testing.T)
{Item{Kind: KindCommand, Name: "server-only", Scope: ScopeNode, Files: one("server-only", "x")}, []string{"server"}},
{Item{Kind: KindCommand, Name: "at-home", Scope: ScopeHome, Files: one("at-home", "x")}, []string{"laptop"}},
} {
if _, err := Register(a, r.it, r.nodes, false, state, va, writer(wa)); err != nil {
t.Fatal(err)
if r.it.Scope == ScopeHome {
if _, err := Register(a, r.it, r.nodes, false, state, va, writer(wa)); err != nil {
t.Fatal(err)
}
continue
}
registeredBefore(t, a, wa, state, va, r.it, r.nodes...)
}
deliver(state, vb)
if _, err := RenderNow(b, writer(wb)); err != nil {
@@ -289,9 +313,7 @@ func TestWhatWasRemovedWhileANodeWasAwayIsDropped(t *testing.T) {
p, w := node(t, "laptop")
state, view := memConfig{}, NewConfigView(p)
for _, name := range []string{"kept", "gone"} {
if _, err := Register(p, Item{Kind: KindCommand, Name: name, Scope: ScopeMesh, Files: one(name, "x")}, nil, false, state, view, writer(w)); err != nil {
t.Fatal(err)
}
registeredBefore(t, p, w, state, view, Item{Kind: KindCommand, Name: name, Scope: ScopeMesh, Files: one(name, "x")})
}
delete(state, "mesh.command.gone") // removed from another node while this one was off
back := NewConfigView(p) // the node starts again from what it last wrote
@@ -338,7 +360,7 @@ func TestAllIsEveryNodeAndANameThatCannotBeAKeyIsRefused(t *testing.T) {
t.Fatalf("%v %v", nodes, err)
}
p, w := node(t, "laptop")
answer, _ := Register(p, Item{Kind: KindCommand, Name: "c", Scope: ScopeNode, Files: one("c", "x")}, []string{"a.b"}, false, memConfig{}, NewConfigView(p), writer(w))
answer, _ := Register(p, Item{Kind: KindCommand, Name: "c", Scope: ScopeHome, Files: one("c", "x")}, []string{"a.b"}, false, memConfig{}, NewConfigView(p), writer(w))
if answer["registered"] != false {
t.Fatalf("a node name with a dot was used in a key: %v", answer)
}