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:
+25
-15
@@ -9,11 +9,13 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Tool — one act in the allowlist. Cmd is the fixed argv prefix run with the
|
||||
// utterance's args appended (no shell). Status 'proposed' is a scaffold that
|
||||
// drives nothing; 'enabled' is the human-flipped, runnable form.
|
||||
// Tool — one act in the allowlist. Scope namespaces tools (e.g. "homelab").
|
||||
// Cmd is the fixed argv prefix run with the utterance's args appended (no
|
||||
// shell). Status 'proposed' is a scaffold that drives nothing; 'enabled' is
|
||||
// the human-flipped, runnable form.
|
||||
type Tool struct {
|
||||
Name string
|
||||
Scope string
|
||||
Cmd []string
|
||||
Destructive bool
|
||||
Status string // proposed | enabled
|
||||
@@ -34,12 +36,16 @@ var (
|
||||
// false when a row (proposed or enabled) already existed. maven calls this when
|
||||
// she classifies an act whose verb isn't on the enabled allowlist — she drafts
|
||||
// the registration; a human enables it. Never overwrites an enabled tool.
|
||||
func (s *Store) ProposeTool(ctx context.Context, name, utterance string, ts time.Time) (bool, error) {
|
||||
// scope defaults to "homelab" when empty.
|
||||
func (s *Store) ProposeTool(ctx context.Context, name, utterance, scope string, ts time.Time) (bool, error) {
|
||||
if scope == "" {
|
||||
scope = "homelab"
|
||||
}
|
||||
res, err := s.db.ExecContext(ctx, `
|
||||
INSERT INTO tools (name, cmd, destructive, status, utterance, created_ts, updated_ts)
|
||||
VALUES (?, '[]', 0, 'proposed', ?, ?, ?)
|
||||
INSERT INTO tools (name, scope, cmd, destructive, status, utterance, created_ts, updated_ts)
|
||||
VALUES (?, ?, '[]', 0, 'proposed', ?, ?, ?)
|
||||
ON CONFLICT(name) DO NOTHING`,
|
||||
name, utterance, ts.UnixMilli(), ts.UnixMilli())
|
||||
name, scope, utterance, ts.UnixMilli(), ts.UnixMilli())
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("propose tool: %w", err)
|
||||
}
|
||||
@@ -54,10 +60,14 @@ func (s *Store) ProposeTool(ctx context.Context, name, utterance string, ts time
|
||||
// human "enable" act (the authed surface calls it); it upserts so enabling a
|
||||
// name that was never proposed still works. An empty cmd is refused — an
|
||||
// enabled tool that runs nothing is a footgun, not a tool.
|
||||
func (s *Store) EnableTool(ctx context.Context, name string, cmd []string, destructive bool, ts time.Time) error {
|
||||
// scope defaults to "homelab" when empty.
|
||||
func (s *Store) EnableTool(ctx context.Context, name string, cmd []string, destructive bool, scope string, ts time.Time) error {
|
||||
if len(cmd) == 0 {
|
||||
return ErrToolCmd
|
||||
}
|
||||
if scope == "" {
|
||||
scope = "homelab"
|
||||
}
|
||||
raw, err := json.Marshal(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("enable tool: %w", err)
|
||||
@@ -67,11 +77,11 @@ func (s *Store) EnableTool(ctx context.Context, name string, cmd []string, destr
|
||||
d = 1
|
||||
}
|
||||
_, err = s.db.ExecContext(ctx, `
|
||||
INSERT INTO tools (name, cmd, destructive, status, utterance, created_ts, updated_ts)
|
||||
VALUES (?, ?, ?, 'enabled', '', ?, ?)
|
||||
ON CONFLICT(name) DO UPDATE SET cmd=excluded.cmd, destructive=excluded.destructive,
|
||||
INSERT INTO tools (name, scope, cmd, destructive, status, utterance, created_ts, updated_ts)
|
||||
VALUES (?, ?, ?, ?, 'enabled', '', ?, ?)
|
||||
ON CONFLICT(name) DO UPDATE SET scope=excluded.scope, cmd=excluded.cmd, destructive=excluded.destructive,
|
||||
status='enabled', updated_ts=excluded.updated_ts`,
|
||||
name, string(raw), d, ts.UnixMilli(), ts.UnixMilli())
|
||||
name, scope, string(raw), d, ts.UnixMilli(), ts.UnixMilli())
|
||||
if err != nil {
|
||||
return fmt.Errorf("enable tool: %w", err)
|
||||
}
|
||||
@@ -95,7 +105,7 @@ func (s *Store) DisableTool(ctx context.Context, name string) error {
|
||||
// LookupTool returns the tool by name. ErrToolNotFound when absent.
|
||||
func (s *Store) LookupTool(ctx context.Context, name string) (Tool, error) {
|
||||
row := s.db.QueryRowContext(ctx, `
|
||||
SELECT name, cmd, destructive, status, utterance, created_ts, updated_ts
|
||||
SELECT name, scope, cmd, destructive, status, utterance, created_ts, updated_ts
|
||||
FROM tools WHERE name = ?`, name)
|
||||
t, err := scanTool(row)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
@@ -106,7 +116,7 @@ func (s *Store) LookupTool(ctx context.Context, name string) (Tool, error) {
|
||||
|
||||
// ListTools returns tools filtered by status ("" ⇒ all), name-sorted.
|
||||
func (s *Store) ListTools(ctx context.Context, status string) ([]Tool, error) {
|
||||
q := `SELECT name, cmd, destructive, status, utterance, created_ts, updated_ts FROM tools`
|
||||
q := `SELECT name, scope, cmd, destructive, status, utterance, created_ts, updated_ts FROM tools`
|
||||
var args []any
|
||||
if status != "" {
|
||||
q += ` WHERE status = ?`
|
||||
@@ -137,7 +147,7 @@ func scanTool(sc scanner) (Tool, error) {
|
||||
var cmdJSON string
|
||||
var d int
|
||||
var created, updated int64
|
||||
if err := sc.Scan(&t.Name, &cmdJSON, &d, &t.Status, &t.Utterance, &created, &updated); err != nil {
|
||||
if err := sc.Scan(&t.Name, &t.Scope, &cmdJSON, &d, &t.Status, &t.Utterance, &created, &updated); err != nil {
|
||||
return Tool{}, err
|
||||
}
|
||||
if err := json.Unmarshal([]byte(cmdJSON), &t.Cmd); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user