tools: add scope column for capability model
Add a 'scope' TEXT column (default 'homelab') to the tools table so tools can be namespaced by scope (e.g. "homelab:restart", "datacenter:reboot"). Backward-compat: bare name defaults to "homelab" scope. Changes: - Migration #1: ALTER TABLE tools ADD COLUMN scope - store.Tool: add Scope field, update all SQL and scanTool() - ipc.Tool DTO and request types: add Scope field - CoreAPI interface: pass scope in ProposeTool/EnableTool - storeAPI adapters: forward scope - cmd/mavend/voice: pass scope (empty → homelab) - cmd/mavweb/tools: show scope column in UI tables, hidden fields - All tests updated for scope field - Migration test made dynamic (startVer = len(migrations))
This commit is contained in:
+2
-2
@@ -719,7 +719,7 @@ func (h *reactiveHandler) proposeGap(ctx context.Context, dec router.Decision) s
|
||||
if name == "" {
|
||||
return "не разобрала команду — попробуй иначе."
|
||||
}
|
||||
newly, err := h.api.ProposeTool(ctx, name, dec.Utterance, h.now())
|
||||
newly, err := h.api.ProposeTool(ctx, name, dec.Utterance, "", h.now())
|
||||
if err != nil {
|
||||
log.Printf("voice: propose tool %q: %v", name, err)
|
||||
return "команды «" + name + "» нет в списке разрешённых."
|
||||
@@ -800,7 +800,7 @@ func seedTools(api ipc.CoreAPI, tools []config.ToolConfig) {
|
||||
log.Printf("voice: skipping malformed tool config %+v", tc)
|
||||
continue
|
||||
}
|
||||
if err := api.EnableTool(ctx, tc.Name, tc.Cmd, tc.Destructive, now); err != nil {
|
||||
if err := api.EnableTool(ctx, tc.Name, tc.Cmd, tc.Destructive, tc.Scope, now); err != nil {
|
||||
log.Printf("voice: seed tool %q: %v", tc.Name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ type fakeCore struct {
|
||||
historyErr error
|
||||
}
|
||||
|
||||
func (f *fakeCore) EnableTool(_ context.Context, name string, cmd []string, destructive bool, _ time.Time) error {
|
||||
func (f *fakeCore) EnableTool(_ context.Context, name string, cmd []string, destructive bool, scope string, _ time.Time) error {
|
||||
f.gotEnableName, f.gotEnableCmd, f.gotEnableDest = name, cmd, destructive
|
||||
return f.enableErr
|
||||
}
|
||||
@@ -134,8 +134,8 @@ func (f *fakeCore) RevertFact(_ context.Context, _ string) (int64, error) {
|
||||
|
||||
func TestHandleTools_GET_RendersAndEscapes(t *testing.T) {
|
||||
core := &fakeCore{
|
||||
proposed: []ipc.Tool{{Name: "<b>x", Utterance: "restart the <i>thing"}},
|
||||
enabled: []ipc.Tool{{Name: "svc", Cmd: []string{"systemctl", "restart"}, Destructive: true}},
|
||||
proposed: []ipc.Tool{{Name: "<b>x", Scope: "homelab", Utterance: "restart the <i>thing"}},
|
||||
enabled: []ipc.Tool{{Name: "svc", Scope: "", Cmd: []string{"systemctl", "restart"}, Destructive: true}},
|
||||
}
|
||||
rr := httptest.NewRecorder()
|
||||
handleTools(rr, httptest.NewRequest(http.MethodGet, "/tools", nil), core)
|
||||
|
||||
+8
-5
@@ -344,11 +344,12 @@ input[type=text]{width:22rem}code{background:#f4f4f4;padding:.1rem .3rem}
|
||||
{{if .Msg}}<div class=msg>{{.Msg}}</div>{{end}}
|
||||
<h2>proposed <small>({{len .Proposed}})</small></h2>
|
||||
{{if .Proposed}}<p>maven drafted these from acts she couldn't run. Fill the command (argv, space-separated) and enable.</p>
|
||||
<table><tr><th>name</th><th>from utterance</th><th>enable as</th></tr>
|
||||
<table><tr><th>name</th><th>scope</th><th>from utterance</th><th>enable as</th></tr>
|
||||
{{range .Proposed}}<tr>
|
||||
<td><code>{{.Name}}</code></td><td>{{.Utterance}}</td>
|
||||
<td><code>{{.Name}}</code></td><td>{{.Scope}}</td><td>{{.Utterance}}</td>
|
||||
<td><form method=post action=/tools>
|
||||
<input type=hidden name=name value="{{.Name}}">
|
||||
<input type=hidden name=scope value="{{.Scope}}">
|
||||
<input type=hidden name=action value=enable>
|
||||
<input type=text name=cmd placeholder="systemctl restart" required>
|
||||
<label><input type=checkbox name=destructive> destructive</label>
|
||||
@@ -356,11 +357,12 @@ input[type=text]{width:22rem}code{background:#f4f4f4;padding:.1rem .3rem}
|
||||
</tr>{{end}}</table>
|
||||
{{else}}<p>none pending.</p>{{end}}
|
||||
<h2>enabled <small>({{len .Enabled}})</small></h2>
|
||||
{{if .Enabled}}<table><tr><th>name</th><th>command</th><th></th><th></th></tr>
|
||||
{{range .Enabled}}<tr><td><code>{{.Name}}</code></td><td><code>{{join .Cmd " "}}</code></td>
|
||||
{{if .Enabled}}<table><tr><th>name</th><th>scope</th><th>command</th><th></th><th></th></tr>
|
||||
{{range .Enabled}}<tr><td><code>{{.Name}}</code></td><td>{{.Scope}}</td><td><code>{{join .Cmd " "}}</code></td>
|
||||
<td>{{if .Destructive}}<span class=d>destructive</span>{{end}}</td>
|
||||
<td><form method=post action=/tools style=display:inline>
|
||||
<input type=hidden name=name value="{{.Name}}">
|
||||
<input type=hidden name=scope value="{{.Scope}}">
|
||||
<input type=hidden name=action value=disable>
|
||||
<button>disable</button></form></td></tr>{{end}}</table>
|
||||
{{else}}<p>none enabled.</p>{{end}}
|
||||
@@ -435,13 +437,14 @@ func handleTools(w http.ResponseWriter, r *http.Request, core ipc.CoreAPI) {
|
||||
name := strings.TrimSpace(r.FormValue("name"))
|
||||
switch action {
|
||||
case "enable":
|
||||
scope := r.FormValue("scope")
|
||||
cmd := strings.Fields(r.FormValue("cmd"))
|
||||
destructive := r.FormValue("destructive") != ""
|
||||
if name == "" || len(cmd) == 0 {
|
||||
http.Error(w, "name and cmd required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if err := core.EnableTool(ctx, name, cmd, destructive, time.Now()); err != nil {
|
||||
if err := core.EnableTool(ctx, name, cmd, destructive, scope, time.Now()); err != nil {
|
||||
log.Printf("tools: enable %q: %v", name, err)
|
||||
http.Error(w, "enable failed: "+err.Error(), http.StatusBadGateway)
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user