Telegram /plans: review, approve, decline & revise plans from chat - #324
Draft
serxa wants to merge 1 commit into
Draft
Telegram /plans: review, approve, decline & revise plans from chat#324serxa wants to merge 1 commit into
serxa wants to merge 1 commit into
Conversation
Adds a `/plans` command to the Telegram bot so plans can be reviewed
without the WebUI — the same actions the WebUI plan page offers, driven
by inline-keyboard taps (no copy-pasted plan ids).
Flow (mirrors the /sessions patterns):
- `/plans` lists pending plans (⚙️ implementing shown after), paginated
⬅️/➡️, always with a 🔄 Refresh button.
- Tap a plan → detail card (task, status, expandable plan body) with
✅ Approve · ❌ Decline · ✍️ Revise.
- Approve/Decline get a confirm step (both are one-way). Revise uses a
native ForceReply prompt to collect feedback, then dispatches it to
the planner.
Converge the three surfaces on one implementation. The approve/decline
logic was duplicated between the HTTP route and the MCP tool handler
(revise was already shared). Extracted `approve_plan` / `decline_plan`
into `nerve/agent/plan_service.py` alongside `request_plan_revision`;
the HTTP routes, MCP tools, and the new Telegram command are now thin
adapters over it. Also registers the spawned impl task with the engine
on every surface so `/stop` can cancel a stuck implementation (the MCP
path previously didn't).
Note: routing HTTP `PATCH /api/plans/{id}` decline through the shared
helper makes it refuse a non-pending plan with 409, matching every
sibling surface (previously the route was an unconditional field
updater). No frontend change — the WebUI only declines pending plans.
Tests: tests/test_plan_actions.py (approve/decline service contract) and
tests/test_telegram_plans.py (view builders + pagination). Full suite
3361 passed; 2 pre-existing unrelated failures (config-env scalar
coercion, lockdown env flag) reproduce on a clean main.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Plans (the propose → review → approve/decline/revise loop) could only be reviewed from the WebUI. On mobile / Telegram there was no way to see what's awaiting review or to act on it — you had to open the web panel. Meanwhile the approve/decline logic was duplicated between the HTTP route and the MCP tool handler (only
revisewas already shared viaplan_service.py), so a third consumer would have meant a third copy.Change
New Telegram
/planscommand — a chat-native review queue that mirrors the WebUI, following the/sessionsconventions (PR #184 / #304):/planslists pending plans (⚙️ implementing shown after), paginated with⬅️ Prev/➡️ Moreand an always-present🔄 Refresh. Empty queue says so.callback_data.Converge the three surfaces on one implementation. Extracted
approve_plananddecline_planintonerve/agent/plan_service.py(next to the existingrequest_plan_revision). The HTTP routes, the MCPplan_approve/plan_declinetools, and the new Telegram command are now thin adapters over the shared helpers — the same rationale the module's docstring already gives for revise ("keeping this in one place prevents the surfaces from drifting apart"). The impl session is now also registered with the engine on every surface, so/stopcan cancel a stuck implementation (the MCP path previously couldn't).Pure, unit-testable view builders (
build_plans_view/build_plan_detail_view/build_plan_confirm_view) mirrorbuild_sessions_view; the router gains thinlist_plans/get_planwrappers.Behavior note (please confirm)
Routing
PATCH /api/plans/{id}decline through the shared helper makes it refuse a non-pending plan with409, matching every sibling surface (plan_declinetool,plan_approve,plan_update,POST …/approve). Previously that route was an unconditional field-updater. This is the same fix as the (policy-)closed #250; it changes no WebUI behavior (the UI only declines pending plans) and breaks no existing test. Happy to drop it back to a lenient path if you'd rather keep that route as-is.Tests
tests/test_plan_actions.py(new) — theapprove_plan/decline_plancontract: spawn + implementing + task→in_progress + engine registration; skill-prompt branch; refuses non-pending; not-found / task-not-found.tests/test_telegram_plans.py(new) — the three view builders (labels,callback_data, HTML-escape, oversized-id skip, 4096 clamp, read-only non-pending) and_plans_view_forpending-first ordering + pagination.test_config_envscalar coercion,test_lockdownenv flag) that reproduce on a cleanmain.npm run buildneeded.Draft — behavior is best reviewed by driving
/planson the bot.