mirror of
https://github.com/Twigpine/zero.git
synced 2026-11-11 23:40:28 -10:00
* feat(swarm): let swarm members build (write + sandboxed shell) Swarm subagents were effectively read-only: a member runs headless at "--auto low" (PermissionModeAuto), and ToolAdvertised strips every prompt-requiring tool from the model's list — so write_file/edit_file/ apply_patch/bash were never handed to the member. It only saw read/grep/ glob/plan tools, so it could not build and the orchestrator did all the writes itself. Add PermissionModeMemberAuto: a headless mode that ADVERTISES the in-workspace mutators (SideEffectWrite + SideEffectShell) on top of the Auto set, while behaving exactly like Auto everywhere else. The sandbox engine still decides at call time — in-workspace writes and sandbox-backed shell auto-allow; out-of-workspace writes, network, and destructive commands prompt and so are denied headless. member-auto normalizes to Auto in the sandbox layer, so no sandbox authority is widened beyond what an interactive auto agent already has. Scope: swarm members only. The launcher sets MemberAutonomy; the executor emits "--auto member" for a non-unsafe member (unsafe still -> high, plain specialists still -> low). Task-tool specialists are unchanged. Windows note: shell only auto-runs when the OS sandbox is active; without `zero sandbox setup` a member writes files but cannot run builds. * style: gofmt member_auto_test.go
52 lines
2.1 KiB
Go
52 lines
2.1 KiB
Go
package agent
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/Gitlawb/zero/internal/tools"
|
|
)
|
|
|
|
type safetyTool struct {
|
|
name string
|
|
safety tools.Safety
|
|
}
|
|
|
|
func (t safetyTool) Name() string { return t.name }
|
|
func (t safetyTool) Description() string { return "test tool" }
|
|
func (t safetyTool) Parameters() tools.Schema {
|
|
return tools.Schema{Type: "object", AdditionalProperties: false}
|
|
}
|
|
func (t safetyTool) Safety() tools.Safety { return t.safety }
|
|
func (t safetyTool) Run(context.Context, map[string]any) tools.Result {
|
|
return tools.Result{Status: tools.StatusOK}
|
|
}
|
|
|
|
// member-auto advertises the in-workspace mutators a headless member needs to
|
|
// build (write/edit + shell) that plain Auto hides, but NOT network or denied
|
|
// tools — the sandbox still gates the advertised ones at call time.
|
|
func TestToolAdvertisedMemberAuto(t *testing.T) {
|
|
write := safetyTool{name: "write_file", safety: tools.Safety{SideEffect: tools.SideEffectWrite, Permission: tools.PermissionPrompt}}
|
|
shell := safetyTool{name: "bash", safety: tools.Safety{SideEffect: tools.SideEffectShell, Permission: tools.PermissionPrompt}}
|
|
read := safetyTool{name: "read_file", safety: tools.Safety{SideEffect: tools.SideEffectRead, Permission: tools.PermissionAllow}}
|
|
network := safetyTool{name: "net_tool", safety: tools.Safety{SideEffect: tools.SideEffectNetwork, Permission: tools.PermissionPrompt}}
|
|
denied := safetyTool{name: "blocked", safety: tools.Safety{SideEffect: tools.SideEffectRead, Permission: tools.PermissionDeny}}
|
|
|
|
// Plain Auto hides prompt-requiring mutators (the read-only member problem).
|
|
if ToolAdvertised(write, PermissionModeAuto) || ToolAdvertised(shell, PermissionModeAuto) {
|
|
t.Fatal("Auto must NOT advertise write/shell prompt tools")
|
|
}
|
|
|
|
for _, tool := range []tools.Tool{write, shell, read} {
|
|
if !ToolAdvertised(tool, PermissionModeMemberAuto) {
|
|
t.Fatalf("member-auto must advertise %q", tool.Name())
|
|
}
|
|
}
|
|
if ToolAdvertised(network, PermissionModeMemberAuto) {
|
|
t.Fatal("member-auto must NOT advertise a network prompt tool")
|
|
}
|
|
if ToolAdvertised(denied, PermissionModeMemberAuto) {
|
|
t.Fatal("member-auto must NOT advertise a denied tool")
|
|
}
|
|
}
|