feat(WARDEN-WP-0021): T3-T5 — visibility, approve loop, runbook (scheduled worker complete)

T4 (review→send loop): conservative tick persists structured drafts to
state_dir/worker-drafts.json; `warden worker drafts` lists them, `warden worker approve
<id> [--body …]` sends the reviewed draft as the reply + marks read + drops it. Escalated
plans persist no draft. Live-verified end-to-end.

T3 (visibility): `warden worker status` (pending drafts, triage count, last digest, timer
state); best-effort notify-send nudge in the tick when drafts are pending.

T5: wiki/playbooks/scheduled-worker.md (enable/disable, the approve loop, failure modes,
conservative-only posture) + SCOPE note.

WARDEN-WP-0021 finished: the conservative worker now runs on a systemd --user timer
(enabled, every 15 min), triages new inbox messages into drafts you approve with one
command, degrades gracefully, and stops with one command. 249 tests, lint clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-30 15:24:10 +02:00
parent 9dc1db0162
commit a10bbd2162
7 changed files with 253 additions and 6 deletions

View File

@@ -206,6 +206,46 @@ def test_run_conservative_drafts_no_sends_and_dedups(tmp_path):
assert not any(c[0] == "progress" for c in hub2.calls)
# --- approve loop (WP-0021 T4) ------------------------------------------------
def test_conservative_persists_draft_and_approve_sends(tmp_path):
from warden.worker import approve_draft, list_drafts, load_drafts
hub = _FakeHub()
p = _plan([PlannedAction(kind="route_answer", summary="a", payload={"answer": "the answer"})])
run_conservative([p], hub, state_dir=tmp_path)
drafts = load_drafts(tmp_path)
assert "m1" in drafts and drafts["m1"]["body"] == "the answer"
assert "m1" in list_drafts(tmp_path)
# approve → sends the reply + marks read + drops the draft
hub2 = _FakeHub()
out = approve_draft("m1", hub2, state_dir=tmp_path)
assert any(c[0] == "reply" and c[3] == "the answer" for c in hub2.calls)
assert any(c[0] == "mark_read" for c in hub2.calls)
assert "m1" not in load_drafts(tmp_path)
assert "sent reply" in out
def test_approve_body_override(tmp_path):
from warden.worker import approve_draft, save_drafts
save_drafts(tmp_path, {"m9": {"to_agent": "bob", "subject": "Re: x", "body": "orig", "thread_id": "t"}})
hub = _FakeHub()
approve_draft("m9", hub, state_dir=tmp_path, body_override="edited")
assert any(c[0] == "reply" and c[3] == "edited" for c in hub.calls)
def test_approve_missing_draft(tmp_path):
from warden.worker import approve_draft
out = approve_draft("nope", _FakeHub(), state_dir=tmp_path)
assert "no pending draft" in out
def test_escalated_plan_persists_no_draft(tmp_path):
a = PlannedAction(kind="reply", summary="x", risk="escalate", reason="secret")
run_conservative([_plan([a])], _FakeHub(), state_dir=tmp_path)
from warden.worker import load_drafts
assert load_drafts(tmp_path) == {}
# --- executor (T3) -----------------------------------------------------------
class _FakeHub: