Files
prose-contracts/zulip-approval-fix.prose.md
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

2.6 KiB

kind, name, description, agent, version, status
kind name description agent version status
responsibility zulip-approval-fix 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. abiba 2.0.0 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:

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