mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-19 00:15:05 +00:00
docs(hooks): user guide + example configs + changelog + Make targets
- docs/agent-hooks.md: handler reference, lifecycle events, security model - examples/hooks/: 5 runnable JSON configs (audit, lint, block-rm-rf, Discord notify, context injector) - docs/17-changelog.md: Wave 0 entry - Makefile: hooks-specific test targets - CLAUDE.md: cross-reference
This commit is contained in:
1 parent
f90f11a9f9
commit
95bdb23a36
10 files changed
+431
-1
No files matched your search
@@ -0,0 +1,45 @@
|
||||
# Example Hooks Library
|
||||
|
||||
Copy-paste-ready hook configurations for common use cases. Each JSON is a valid `hooks.create` WS payload — send it via the Web UI → **Hooks → Create** (paste into the relevant fields) or POST directly to the RPC endpoint.
|
||||
|
||||
## Safety reminders
|
||||
|
||||
- **Review regex matchers** before enabling. A too-broad matcher on a prompt hook drains token budget. Prefer anchored patterns like `^(exec|shell)$`.
|
||||
- **`command` hooks require Lite edition.** On Standard the UI greys them out and dispatch fails closed.
|
||||
- **SSRF-safe HTTP hooks**: URLs are resolved + pinned before the request. Loopback/private ranges are blocked in production.
|
||||
- **Authorization headers** get AES-256-GCM encrypted at rest. Never commit plaintext secrets to these JSON files.
|
||||
- **Test in staging first.** Use the `Test` tab in the UI — it runs with `dryRun=true` and does NOT write to `hook_executions`.
|
||||
|
||||
## Catalog
|
||||
|
||||
| File | Event | Handler | Scope | Purpose |
|
||||
|---|---|---|---|---|
|
||||
| `block-rm-rf.json` | `pre_tool_use` | command | agent | Block dangerous `rm -rf /` via local shell script. Lite only. |
|
||||
| `auto-lint-after-write.json` | `post_tool_use` | http | agent | Fire-and-forget lint request after file writes. |
|
||||
| `audit-tool-usage.json` | `post_tool_use` | http | tenant | Stream every tool invocation to an external audit sink. |
|
||||
| `session-context-injector.json` | `session_start` | http | agent | Injects project metadata into the agent context at start. |
|
||||
| `notify-discord-on-stop.json` | `stop` | http | tenant | Discord webhook notification when a session ends. |
|
||||
|
||||
## Usage
|
||||
|
||||
### Via Web UI
|
||||
|
||||
1. Open `/hooks` → click **Create hook**.
|
||||
2. Copy fields from the example JSON into the form.
|
||||
3. For `http.config.headers`, paste your secret in the Authorization field — the server encrypts it before storing.
|
||||
4. Click **Save**, then **Test** with a sample event before enabling in production.
|
||||
|
||||
### Via WS RPC
|
||||
|
||||
```bash
|
||||
wscat -c ws://localhost:18790/ws
|
||||
# After connect:
|
||||
> {"id":"1","method":"hooks.create","params": <paste JSON here> }
|
||||
```
|
||||
|
||||
## Conventions
|
||||
|
||||
- `tenant_id` omitted → current tenant from WS session. Set `scope: "global"` to apply cross-tenant (master required).
|
||||
- `agent_id` required for `scope: "agent"`; otherwise leave null.
|
||||
- `priority: 10` is the recommended default. Higher priority hooks run first; first `block` wins the chain.
|
||||
- `on_timeout: "block"` for anything security-sensitive; `"allow"` for observation-only.
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"event": "post_tool_use",
|
||||
"handler_type": "http",
|
||||
"scope": "tenant",
|
||||
"matcher": "",
|
||||
"if_expr": "",
|
||||
"config": {
|
||||
"url": "https://audit.internal.example.com/hooks/tool-usage",
|
||||
"headers": {
|
||||
"Authorization": "Bearer REPLACE_ME_ENCRYPTED",
|
||||
"X-Goclaw-Audit": "tool-usage"
|
||||
}
|
||||
},
|
||||
"timeout_ms": 2000,
|
||||
"on_timeout": "allow",
|
||||
"priority": 1,
|
||||
"enabled": true,
|
||||
"source": "seed",
|
||||
"metadata": {
|
||||
"notes": "Streams every tool invocation to an external audit sink for compliance. Tenant-wide.",
|
||||
"tags": ["observability", "compliance", "audit"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"event": "post_tool_use",
|
||||
"handler_type": "http",
|
||||
"scope": "agent",
|
||||
"matcher": "^(write_file|edit_file)$",
|
||||
"if_expr": "",
|
||||
"config": {
|
||||
"url": "https://ci.internal.example.com/hooks/lint",
|
||||
"headers": {
|
||||
"Authorization": "Bearer REPLACE_ME_ENCRYPTED",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
},
|
||||
"timeout_ms": 3000,
|
||||
"on_timeout": "allow",
|
||||
"priority": 10,
|
||||
"enabled": true,
|
||||
"source": "api",
|
||||
"metadata": {
|
||||
"notes": "Fire-and-forget lint ping after file writes. Non-blocking event — won't stall the agent loop.",
|
||||
"tags": ["observability", "quality"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"event": "pre_tool_use",
|
||||
"handler_type": "command",
|
||||
"scope": "agent",
|
||||
"matcher": "^(exec|shell)$",
|
||||
"if_expr": "",
|
||||
"config": {
|
||||
"command": "grep -qE 'rm +-[rR][fF]? +/' && exit 2 || exit 0"
|
||||
},
|
||||
"timeout_ms": 2000,
|
||||
"on_timeout": "block",
|
||||
"priority": 100,
|
||||
"enabled": true,
|
||||
"source": "api",
|
||||
"metadata": {
|
||||
"notes": "Blocks `rm -rf /` style commands before exec. Lite edition only.",
|
||||
"tags": ["safety", "command"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"event": "stop",
|
||||
"handler_type": "http",
|
||||
"scope": "tenant",
|
||||
"matcher": "",
|
||||
"if_expr": "",
|
||||
"config": {
|
||||
"url": "https://discord.com/api/webhooks/REPLACE_ME",
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
},
|
||||
"timeout_ms": 3000,
|
||||
"on_timeout": "allow",
|
||||
"priority": 1,
|
||||
"enabled": false,
|
||||
"source": "seed",
|
||||
"metadata": {
|
||||
"notes": "Discord webhook fired on every session stop. Disabled by default — enable after replacing the webhook URL. Discord expects {\"content\":\"...\"} but the hook ships event JSON; consider putting an adapter in front.",
|
||||
"tags": ["notify", "discord", "session"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"event": "session_start",
|
||||
"handler_type": "http",
|
||||
"scope": "agent",
|
||||
"matcher": "",
|
||||
"if_expr": "",
|
||||
"config": {
|
||||
"url": "https://ctx.internal.example.com/hooks/session-bootstrap",
|
||||
"headers": {
|
||||
"Authorization": "Bearer REPLACE_ME_ENCRYPTED"
|
||||
}
|
||||
},
|
||||
"timeout_ms": 4000,
|
||||
"on_timeout": "allow",
|
||||
"priority": 50,
|
||||
"enabled": true,
|
||||
"source": "api",
|
||||
"metadata": {
|
||||
"notes": "On session_start, fetches project-specific metadata and injects via additionalContext. The remote endpoint should respond with {\"decision\":\"allow\",\"additionalContext\":\"...\"}.",
|
||||
"tags": ["context", "session", "bootstrap"]
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user