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:
tegwick 2026-06-30 15:24:10 +02:00
parent 9dc1db0162
commit a10bbd2162
7 changed files with 253 additions and 6 deletions

View file

@ -1212,3 +1212,42 @@ def worker_run(
else:
console.print("[green]Conservative triage[/green] — drafting; nothing sent to other agents.")
console.print(run_conservative(plans, hub, topic_id=topic_id))
@worker_app.command("drafts")
def worker_drafts() -> None:
"""List the worker's pending drafted replies (from the conservative tier)."""
from warden.worker import list_drafts
console.print(list_drafts())
@worker_app.command("approve")
def worker_approve(
message_id: Annotated[str, typer.Argument(help="Message id to send the drafted reply for")],
body: Annotated[
Optional[str], typer.Option("--body", help="Override the drafted reply text before sending")
] = None,
) -> None:
"""Send a reviewed draft as the reply and mark the message read."""
from warden.worker import HubClient, approve_draft
try:
console.print(approve_draft(message_id, HubClient(), body_override=body))
except Exception as e: # noqa: BLE001 — surface transport errors cleanly
err.print(f"[red]Approve failed:[/red] {e}")
raise typer.Exit(1)
@worker_app.command("status")
def worker_status_cmd() -> None:
"""Show worker state: pending drafts, triage count, last digest, timer status."""
import subprocess
from warden.worker import worker_status
console.print(worker_status())
try:
st = subprocess.run(
["systemctl", "--user", "is-active", "ops-warden-worker.timer"],
capture_output=True, text=True, timeout=5,
).stdout.strip()
console.print(f"timer : {st or 'unknown'}")
except Exception: # noqa: BLE001 — systemd may be absent (cron/other host)
console.print("timer : (systemd not available)")