mirror of
https://github.com/Twigpine/zero.git
synced 2026-11-11 23:40:28 -10:00
* Add local control browser and terminal tools Add configurable local browser, desktop, and terminal control helpers with packaged helper discovery for install and release flows. Improve sandbox scope classification for local control commands and shared temporary paths. Add structured browser actions, terminal session line input/key normalization/read support, and artifact capture for local-control outputs. Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/localcontrol ./internal/tools ./internal/cli ./internal/config -run 'Test.*LocalControl|TestTerminal|TestBrowser|TestDesktop|TestCapture' Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/localcontrol ./internal/tools ./internal/cli ./internal/config ./internal/agent Tested: tuistory smoke launched bash, sent interactive input, waited for expected output, and captured a no-cursor snapshot Tested: git diff --cached --check * Fix npm wrapper helper manifest test on macOS Canonicalize expected helper paths in the npm wrapper manifest test so macOS /var and /private/var temp path aliases compare correctly. Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/npmwrapper Tested: git diff --check * Harden local control review fixes Respect resolved local-control config before tool listing and filter validation while preserving stream-json usage error ordering. Harden browser, desktop, and artifact tool validation; guard Linux-only app launch behavior; clear stale helper manifests from the npm wrapper. Make helper packaging lockfile-based with npm ci and add installer/package tests for helper staging. Tested: focused local-control/tool/CLI/release/install/sandbox suite Tested: isolated full go test ./... Tested: macOS and Windows compile-only checks for touched packages
191 lines
6.0 KiB
Go
191 lines
6.0 KiB
Go
package tools
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/Gitlawb/zero/internal/sandbox"
|
|
)
|
|
|
|
type SideEffect string
|
|
type Permission string
|
|
type Status string
|
|
type SandboxPermissionOverride string
|
|
|
|
const (
|
|
// SideEffectNone marks a control-only tool that neither reads nor mutates
|
|
// state — no read/write/shell/network/out-of-workspace effect. Examples:
|
|
// tool_search (only reports already-registered tool schemas) and
|
|
// escalate_model (only requests a loop-level model switch).
|
|
SideEffectNone SideEffect = "none"
|
|
SideEffectRead SideEffect = "read"
|
|
SideEffectWrite SideEffect = "write"
|
|
SideEffectShell SideEffect = "shell"
|
|
SideEffectNetwork SideEffect = "network"
|
|
SideEffectLocalControl SideEffect = "local_control"
|
|
SideEffectLocalBrowser SideEffect = "local_browser"
|
|
SideEffectLocalDesktop SideEffect = "local_desktop"
|
|
SideEffectLocalTerminal SideEffect = "local_terminal"
|
|
SideEffectOutOfWorkspace SideEffect = "out_of_workspace"
|
|
)
|
|
|
|
const (
|
|
PermissionAllow Permission = "allow"
|
|
PermissionPrompt Permission = "prompt"
|
|
PermissionDeny Permission = "deny"
|
|
)
|
|
|
|
const (
|
|
StatusOK Status = "ok"
|
|
StatusError Status = "error"
|
|
)
|
|
|
|
const (
|
|
SandboxPermissionsUseDefault SandboxPermissionOverride = "use_default"
|
|
SandboxPermissionsRequireEscalated SandboxPermissionOverride = "require_escalated"
|
|
SandboxPermissionsWithAdditionalPermissions SandboxPermissionOverride = "with_additional_permissions"
|
|
)
|
|
|
|
const (
|
|
SandboxLikelyDeniedMeta = "sandbox_likely_denied"
|
|
SandboxDenialKindMeta = "sandbox_denial_kind"
|
|
SandboxDenialReasonMeta = "sandbox_denial_reason"
|
|
SandboxDenialKeywordMeta = "sandbox_denial_keyword"
|
|
)
|
|
|
|
const (
|
|
SandboxDenialKindSandbox = "sandbox"
|
|
SandboxDenialKindNetwork = "network"
|
|
)
|
|
|
|
type Safety struct {
|
|
SideEffect SideEffect
|
|
Permission Permission
|
|
Reason string
|
|
// AdvertiseInAuto allows selected non-allow tools to be visible in auto mode
|
|
// while still requiring the normal permission flow before execution.
|
|
AdvertiseInAuto bool
|
|
}
|
|
|
|
type Schema struct {
|
|
Type string `json:"type"`
|
|
Properties map[string]PropertySchema `json:"properties,omitempty"`
|
|
Required []string `json:"required,omitempty"`
|
|
AdditionalProperties bool `json:"additionalProperties"`
|
|
}
|
|
|
|
type PropertySchema struct {
|
|
Type string `json:"type"`
|
|
Description string `json:"description,omitempty"`
|
|
Enum []string `json:"enum,omitempty"`
|
|
Default any `json:"default,omitempty"`
|
|
Items *PropertySchema `json:"items,omitempty"`
|
|
Minimum *int `json:"minimum,omitempty"`
|
|
Maximum *int `json:"maximum,omitempty"`
|
|
MinLength *int `json:"minLength,omitempty"`
|
|
MinItems *int `json:"minItems,omitempty"`
|
|
// Properties/Required describe nested object fields (for Type "object" or an
|
|
// object-typed Items).
|
|
Properties map[string]PropertySchema `json:"properties,omitempty"`
|
|
Required []string `json:"required,omitempty"`
|
|
}
|
|
|
|
type Result struct {
|
|
Status Status
|
|
Output string
|
|
Truncated bool
|
|
Meta map[string]string
|
|
SandboxDecision *sandbox.Decision `json:"-"`
|
|
// Redacted is set when secret scrubbing altered Output before it left the
|
|
// tool-execution boundary.
|
|
Redacted bool
|
|
// ChangedFiles lists workspace-relative paths a mutating tool wrote;
|
|
// entries under a granted extra write root are absolute, since
|
|
// workspace-relative would be ambiguous there.
|
|
ChangedFiles []string
|
|
// Display carries a short, structured summary for the TUI / stream.
|
|
Display Display
|
|
}
|
|
|
|
// Display carries a short, structured summary of a tool result for the TUI/stream.
|
|
type Display struct {
|
|
Summary string
|
|
Kind string // e.g. file, diff, search, shell
|
|
// Preview is a multi-line, card-only body (e.g. a unified diff or file head)
|
|
// for the TUI. It is NEVER sent to the model — Output stays the short summary
|
|
// the model sees — so a rich code preview costs zero model tokens.
|
|
Preview string
|
|
}
|
|
|
|
type Tool interface {
|
|
Name() string
|
|
Description() string
|
|
Parameters() Schema
|
|
Safety() Safety
|
|
Run(ctx context.Context, args map[string]any) Result
|
|
}
|
|
|
|
// ArgsPermissioner is an optional interface a Tool can implement to refine its
|
|
// permission for a SPECIFIC call based on its arguments. When a tool implements
|
|
// it, the agent loop consults PermissionForArgs(args) instead of the static
|
|
// Safety().Permission when deciding whether the call needs approval. It exists to
|
|
// safely RELAX a prompt to allow for arguments the tool can prove are harmless
|
|
// (e.g. delegating to a read-only sub-agent); a tool must return its static,
|
|
// stricter permission whenever it cannot prove the call is safe.
|
|
type ArgsPermissioner interface {
|
|
PermissionForArgs(args map[string]any) Permission
|
|
}
|
|
|
|
// PrePermissionRejecter lets a tool reject a call that cannot safely or validly
|
|
// run before any permission prompt is shown. Implementations must be purely
|
|
// local and deterministic: no filesystem, process, DNS, or network access.
|
|
type PrePermissionRejecter interface {
|
|
RejectBeforePermission(args map[string]any) (Result, bool)
|
|
}
|
|
|
|
type baseTool struct {
|
|
name string
|
|
description string
|
|
parameters Schema
|
|
safety Safety
|
|
}
|
|
|
|
func (tool baseTool) Name() string {
|
|
return tool.name
|
|
}
|
|
|
|
func (tool baseTool) Description() string {
|
|
return tool.description
|
|
}
|
|
|
|
func (tool baseTool) Parameters() Schema {
|
|
return tool.parameters
|
|
}
|
|
|
|
func (tool baseTool) Safety() Safety {
|
|
return tool.safety
|
|
}
|
|
|
|
func okResult(output string) Result {
|
|
return Result{Status: StatusOK, Output: output}
|
|
}
|
|
|
|
func errorResult(output string) Result {
|
|
return Result{Status: StatusError, Output: output}
|
|
}
|
|
|
|
func readOnlySafety(reason string) Safety {
|
|
return Safety{
|
|
SideEffect: SideEffectRead,
|
|
Permission: PermissionAllow,
|
|
Reason: reason,
|
|
}
|
|
}
|
|
|
|
func promptSafety(sideEffect SideEffect, reason string) Safety {
|
|
return Safety{
|
|
SideEffect: sideEffect,
|
|
Permission: PermissionPrompt,
|
|
Reason: reason,
|
|
}
|
|
}
|