Files
prose-contracts/zulip-approval-fix.prose.md
T
Abiba b0c0331727 feat: infrastructure-control network-verification + IP-first doctrine, plus 3 supporting contracts
- 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.
2026-07-02 20:39:40 +00:00

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