claude-code: point the agent at the mesh's tools and guard its shell against ssh to the mesh (hq ADR 0245)
The agent kept running ssh <machine> journalctl while the journal verb existed. The managed instructions now carry an "instead of" table generated from what each verb says it replaces, and the plugin carries a PreToolUse guard that refuses ssh to a mesh machine and local work-arounds for a mesh name, naming the tool or saying one must be created. The operator's override is read from the session's start environment and recorded.
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
package main
|
||||
|
||||
// The "instead of" table in the agent's managed instructions (novox/hq ADR 0244): to do this, call that
|
||||
// address, not this shell command — generated on every render from what each seat verb and module tool
|
||||
// says it replaces, so it cannot drift from the tools the mesh has.
|
||||
//
|
||||
// Short: a row per seat or module, its top what this machine's agent reached round most — the tools the
|
||||
// guard named most often in its record.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// InsteadOfRows is how many rows — seats and modules — the table holds; the rest are one search away.
|
||||
const InsteadOfRows = 16
|
||||
|
||||
// insteadOrder is the order rows are taken in: a machine's seat, a module on a machine, the mesh's seat.
|
||||
func insteadOrder(r Replacement) int {
|
||||
switch {
|
||||
case r.Seat && r.NodeScoped:
|
||||
return 0
|
||||
case !r.Seat:
|
||||
return 1
|
||||
}
|
||||
return 2
|
||||
}
|
||||
|
||||
func code(s string) string { return "`" + strings.ReplaceAll(s, "`", "'") + "`" }
|
||||
|
||||
// insteadGroup is one row: a seat or a module, and its verbs that replace a command.
|
||||
type insteadGroup struct {
|
||||
prefix string // `<node>/node-service-manager.` or `mesh-controller.`
|
||||
verbs []Replacement
|
||||
refused int
|
||||
}
|
||||
|
||||
// InsteadOf is the section, or "" when the mesh said nothing replaces anything — an older controller,
|
||||
// or one not yet asked.
|
||||
//
|
||||
// One row per seat or module, every verb of it that replaces a command in the row, so a verb is never cut
|
||||
// for being late in its seat's list; the rows the guard here named most come first, then the machines'
|
||||
// seats, the modules' tools and the mesh's own seats in the records' order.
|
||||
func InsteadOf(m *MeshTools) string {
|
||||
if m == nil || len(m.Replacements) == 0 {
|
||||
return ""
|
||||
}
|
||||
var groups []*insteadGroup
|
||||
at := map[string]*insteadGroup{}
|
||||
for order := 0; order <= 2; order++ {
|
||||
for _, r := range m.Replacements {
|
||||
if insteadOrder(r) != order {
|
||||
continue
|
||||
}
|
||||
prefix := r.Address[:strings.LastIndex(r.Address, ".")+1]
|
||||
if r.NodeScoped {
|
||||
prefix = "<node>/" + prefix
|
||||
}
|
||||
g := at[prefix]
|
||||
if g == nil {
|
||||
g = &insteadGroup{prefix: prefix}
|
||||
at[prefix] = g
|
||||
groups = append(groups, g)
|
||||
}
|
||||
g.verbs = append(g.verbs, r)
|
||||
g.refused += m.Refused[r.Address]
|
||||
}
|
||||
}
|
||||
sort.SliceStable(groups, func(i, j int) bool { return groups[i].refused > groups[j].refused })
|
||||
for _, g := range groups {
|
||||
sort.SliceStable(g.verbs, func(i, j int) bool { return m.Refused[g.verbs[i].Address] > m.Refused[g.verbs[j].Address] })
|
||||
}
|
||||
more := 0
|
||||
if len(groups) > InsteadOfRows {
|
||||
more = len(groups) - InsteadOfRows
|
||||
groups = groups[:InsteadOfRows]
|
||||
}
|
||||
var b strings.Builder
|
||||
b.WriteString("\n## Instead of a shell command\n\n")
|
||||
b.WriteString("Generated from what each seat verb and module tool says it replaces (`replaces`, novox/hq ADR 0244),\n")
|
||||
b.WriteString("rewritten on every render. Call `<address><verb>` through `mesh_call`, `<node>` being the machine;\n")
|
||||
b.WriteString("`mesh_search` with the command you would have typed finds it too.\n\n")
|
||||
b.WriteString("| call | the verb — instead of |\n|---|---|\n")
|
||||
for _, g := range groups {
|
||||
var cells []string
|
||||
for _, r := range g.verbs {
|
||||
var not []string
|
||||
for _, c := range r.Replaces {
|
||||
not = append(not, code(c))
|
||||
}
|
||||
cells = append(cells, code(r.Address[strings.LastIndex(r.Address, ".")+1:])+" — "+strings.Join(not, ", "))
|
||||
}
|
||||
fmt.Fprintf(&b, "| %s | %s |\n", code(g.prefix), strings.ReplaceAll(strings.Join(cells, " · "), "|", "/"))
|
||||
}
|
||||
if more > 0 {
|
||||
fmt.Fprintf(&b, "\n%d more seats and modules: `mesh_search` with the command finds the tool for it.\n", more)
|
||||
}
|
||||
b.WriteString("\n- **Never `ssh` to a mesh machine**, and never edit `/etc/hosts` or set `HOSTALIASES` for a mesh\n" +
|
||||
" name: the guard on this session's shell refuses it and names the tool. Where no tool covers what you\n" +
|
||||
" need, none is worked around: say so, and the tool is created in the module that owns it, on its seat.\n")
|
||||
return b.String()
|
||||
}
|
||||
Reference in New Issue
Block a user