- infrastructure-control: add Section 5.3 (Network Verification & Routing) with dual-path checks, NetBird ingress health, DNS drift detection, and routing regression alerts. Add Section 7 (Configuration Doctrine — IP-First) mandating LAN IPs for all configs/agents, URLs reserved for human browser access only. Also enrich access matrix (Mumuni, Koonimo, LiteLLM Admin, Grafana entries), reachability matrix, and CT 116 nginx routing + Prometheus targets. - build-zulip-plugin: fold Success Criteria into Maintains postconditions. - pi-approval-architecture: new reference doc — pi approval model vs Hermes, architectural limits, /approve and /deny stub commands. - zulip-approval-fix: new responsibility — documents the Zulip HTML/prefix bug that broke /approve and /deny, and the one-line fix applied.
75 lines
2.6 KiB
Markdown
75 lines
2.6 KiB
Markdown
---
|
|
kind: responsibility
|
|
name: zulip-approval-fix
|
|
description: >
|
|
Fixes broken /approve and /deny slash commands for Hermes agents in Zulip.
|
|
Zulip delivers messages as HTML (<p>/approve</p>), but the gateway's slash
|
|
command parser expects plain text. HTML tags prevent command matching.
|
|
agent: abiba
|
|
version: 2.0.0
|
|
status: deployed
|
|
---
|
|
|
|
## Root Cause
|
|
|
|
The Hermes gateway ALREADY has native `/approve` and `/deny` handling via
|
|
`slash_commands.py:_handle_approve_command()`. No custom approval interception
|
|
is needed in the platform adapter.
|
|
|
|
The bug: Zulip's API returns message content as **rendered HTML**
|
|
(`<p>/approve session</p>`), but the gateway's slash command prefix matcher
|
|
checks `text.startswith("/approve")`. The `<p>` tag prevents matching.
|
|
|
|
## Fix (Applied)
|
|
|
|
One line added to `~/.hermes/plugins/platforms/zulip/adapter.py`:
|
|
|
|
```python
|
|
content = msg.get("content", "")
|
|
content = _strip_html(content) # Strip Zulip HTML for /commands
|
|
```
|
|
|
|
Plus a small `_strip_html()` helper that strips HTML tags and decodes entities.
|
|
|
|
## How the Full Flow Works
|
|
|
|
1. Agent tries dangerous command → `check_all_command_guards()`
|
|
2. Gateway's `_approval_notify_sync` sends approval prompt via `send()`
|
|
3. User sees: "⚠️ Dangerous command requires approval — Reply /approve or /deny"
|
|
4. User types `/approve session` in Zulip
|
|
5. Zulip API returns `<p>/approve session</p>`
|
|
6. Adapter strips HTML → `/approve session`
|
|
7. Gateway's slash command handler matches `/approve`
|
|
8. `_handle_approve_command` calls `resolve_gateway_approval(session_key, "session")`
|
|
9. Agent thread unblocks, command executes
|
|
|
|
## Gateway Slash Commands Supported
|
|
|
|
| Command | Effect |
|
|
|---------|--------|
|
|
| `/approve` | Approve once |
|
|
| `/approve session` | Approve for this session |
|
|
| `/approve always` | Permanently approve this pattern |
|
|
| `/approve all` | Approve ALL pending commands |
|
|
| `/approve all session` | Approve all + remember session |
|
|
| `/deny` | Deny oldest pending |
|
|
| `/deny all` | Deny all pending |
|
|
|
|
## Files Changed
|
|
|
|
| File | Host | Change |
|
|
|------|------|--------|
|
|
| `~/.hermes/plugins/platforms/zulip/adapter.py` | .122, .123 | +_strip_html() helper + HTML stripping in message handler |
|
|
|
|
## Deployed
|
|
|
|
- ✅ Mumuni (.123) — patched + gateway restarted
|
|
- ✅ Tanko (.122) — patched (jerome user), gateway restart pending
|
|
|
|
## Verification
|
|
|
|
1. Send DM: "run rm /tmp/test" to Mumuni/Tanko in Zulip
|
|
2. Agent should respond with approval prompt: "⚠️ Dangerous command requires approval"
|
|
3. Reply `/approve` — agent should execute and respond
|
|
4. Reply `/deny` — agent should block and explain why
|