mirror of
https://github.com/patriceckhart/zot.git
synced 2026-06-27 22:06:31 +02:00
Single Go module, four top-level packages under packages/. Import
paths become github.com/patriceckhart/zot/packages/<name>; downstream
consumers can depend on individual packages without pulling the rest.
Layout:
packages/provider/ LLM clients + catalog
packages/provider/auth/ credential store + OAuth + login server
packages/core/ agent loop, sessions, cost
packages/tui/ terminal toolkit + chat view
packages/agent/ CLI wiring, system prompt
extensions/ extproto/ modes/ tools/ skills/ swarm/
sdk/ (was pkg/zotcore, package renamed zotcore -> sdk)
ext/ (was pkg/zotext, package renamed zotext -> ext)
internal/ and pkg/ removed. The internal/assets logo moved into
packages/provider/auth/assets.
Public Go SDK identifiers renamed:
pkg/zotcore (package zotcore) -> packages/agent/sdk (package sdk)
pkg/zotext (package zotext) -> packages/agent/ext (package ext)
This breaks Go-based extensions and embedders; the JSON wire protocol
for extensions and RPC is unchanged, so non-Go extensions, already-
built extension binaries, and zot rpc consumers are unaffected.
Docs, examples, and the built-in write-zot-extension skill updated
for the new paths and identifiers. Shadow-bug fixes in code samples
(ext := ext.New -> e := ext.New).
91 lines
2.8 KiB
Go
91 lines
2.8 KiB
Go
// guard — phase 3 example: event subscriptions + tool-call interception.
|
|
//
|
|
// What it does:
|
|
//
|
|
// - Subscribes to turn_start, tool_call, and turn_end events and
|
|
// writes a one-line audit entry per event to its own log file.
|
|
//
|
|
// - Intercepts every tool call. If the tool is `bash` and the
|
|
// command matches a danger pattern (rm -rf, sudo, dd of=/, etc.)
|
|
// the call is refused and the model gets a one-line refusal it
|
|
// can react to. Everything else passes through.
|
|
//
|
|
// Build it:
|
|
//
|
|
// cd examples/extensions/guard
|
|
// go build -o guard .
|
|
//
|
|
// Drop it next to its extension.json under $ZOT_HOME/extensions/guard/
|
|
// (or `zot ext install ./guard` from this directory) and the next zot
|
|
// session will load it automatically.
|
|
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/patriceckhart/zot/packages/agent/ext"
|
|
)
|
|
|
|
var dangerPatterns = []*regexp.Regexp{
|
|
regexp.MustCompile(`(?i)\brm\s+-rf\b`),
|
|
regexp.MustCompile(`(?i)\bsudo\b`),
|
|
regexp.MustCompile(`(?i)\bdd\s+of=/`),
|
|
regexp.MustCompile(`(?i)\bmkfs\b`),
|
|
regexp.MustCompile(`(?i)\b:\s*\(\s*\)\s*\{\s*:\|:`), // fork bomb
|
|
regexp.MustCompile(`(?i)\bchmod\s+-R\s+777\b`),
|
|
}
|
|
|
|
func main() {
|
|
e := ext.New("guard", "1.0.0")
|
|
|
|
auditPath := filepath.Join(os.TempDir(), "zot-guard-audit.log")
|
|
auditFile, _ := os.OpenFile(auditPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
|
|
if auditFile != nil {
|
|
defer auditFile.Close()
|
|
}
|
|
audit := func(format string, args ...any) {
|
|
line := fmt.Sprintf("%s "+format+"\n", append([]any{time.Now().Format(time.RFC3339)}, args...)...)
|
|
if auditFile != nil {
|
|
auditFile.WriteString(line)
|
|
}
|
|
e.Logf("%s", strings.TrimRight(line, "\n"))
|
|
}
|
|
|
|
// Lifecycle observers.
|
|
e.On("session_start", func(ev ext.Event) { audit("[guard] session_start") })
|
|
e.On("turn_start", func(ev ext.Event) { audit("[guard] turn_start step=%d", ev.Step) })
|
|
e.On("tool_call", func(ev ext.Event) {
|
|
audit("[guard] tool_call %s args=%s", ev.ToolName, string(ev.ToolArgs))
|
|
})
|
|
e.On("turn_end", func(ev ext.Event) { audit("[guard] turn_end stop=%s err=%s", ev.Stop, ev.Error) })
|
|
|
|
// Tool-call gate.
|
|
e.InterceptToolCall(func(toolName string, args json.RawMessage) (bool, string) {
|
|
if toolName != "bash" {
|
|
return true, "" // only guard bash
|
|
}
|
|
var in struct {
|
|
Command string `json:"command"`
|
|
}
|
|
if err := json.Unmarshal(args, &in); err != nil {
|
|
return true, ""
|
|
}
|
|
for _, pat := range dangerPatterns {
|
|
if pat.MatchString(in.Command) {
|
|
audit("[guard] BLOCKED bash: %s (matched %s)", in.Command, pat.String())
|
|
return false, fmt.Sprintf("guard refused: command matches the danger pattern %q. Ask the user before running this.", pat.String())
|
|
}
|
|
}
|
|
return true, ""
|
|
})
|
|
|
|
if err := e.Run(); err != nil {
|
|
e.Logf("fatal: %v", err)
|
|
}
|
|
}
|