Skip to content

Plan-mode review: approve/reject/revise goals & to-dos before execute#4105

Merged
senamakel merged 13 commits into
tinyhumansai:mainfrom
senamakel:feat/plan-mode-review
Jun 25, 2026
Merged

Plan-mode review: approve/reject/revise goals & to-dos before execute#4105
senamakel merged 13 commits into
tinyhumansai:mainfrom
senamakel:feat/plan-mode-review

Conversation

@senamakel

@senamakel senamakel commented Jun 25, 2026

Copy link
Copy Markdown
Member

Summary

  • Adds a Codex/Claude-style plan-mode review: when an interactive turn proposes a thread-scoped plan, the agent pauses the live turn for the user to review before executing.
  • The user reviews the plan above the composer and Approve & run (the turn resumes and executes), Reject (resumes and stops), or Send feedback (resumes, re-plans from the note, and re-parks).
  • Mechanism: a parked-future gate modelled on ApprovalGate. The orchestrator calls a new request_plan_review tool that blocks the turn on an in-memory PlanReviewGate (oneshot) until the user decides via the openhuman.plan_review_decide RPC. Non-interactive turns (cron/subconscious/CLI) auto-approve, so automation is never blocked.

Problem

OpenHuman could set goals / lay out a multi-step plan and start executing immediately — no "review once before we execute" step like Claude Code / Codex plan mode.

An initial attempt reused the task-board awaiting_approval lifecycle, but review (thanks @chatgpt-codex-connector) surfaced that this cannot work for live chat: the background dispatcher (task_dispatcher::poll_once) only sweeps the user-tasks / task-sources boards, never conversation threads. So a chat plan was never parked to awaiting_approval (the card stays todo) and an "approved" card was never executed. This PR replaces that approach.

Solution

Gate the live turn, not a board status:

  • src/openhuman/plan_review/ — in-memory PlanReviewGate parks the turn on a oneshot (fail-closed to reject on TTL/drop); request_plan_review tool blocks the interactive turn and maps the resolution to a proceed / stop / revise instruction; openhuman.plan_review_decide resolves it (approve / reject / revise + feedback).
  • Event + socketDomainEvent::PlanReviewRequested / PlanReviewDecided, bridged to a plan_review_request socket event by the existing web approval subscriber (reuses APPROVAL_CHAT_CONTEXT for routing).
  • Orchestrator — prompt calls request_plan_review after laying out the plan; allowlist gains request_plan_review (and todo, replacing the dead todowrite alias that matched no registered tool).
  • FrontendPlanReviewCard is event-driven (pendingPlanReviewByThread) and resolves via plan_review_decide; retired the task-board awaiting_approval rendering, runDecidePlanAll / runRevisePlan, threadApi.revisePlan, and the todo WebChat auto-gating.
  • Kept the orthogonal requires_plan_approval fix (explicit per-card Required overrides the global off-switch) for the dispatched boards.

Submission Checklist

  • Tests added or updated (happy path + at least one failure / edge case) per Testing Strategy
  • Diff coverage ≥ 80% — Rust gate/tool/schema unit tests, plan_review_decide JSON-RPC E2E, and frontend card + provider/render tests cover the changed lines; gate enforced by .github/workflows/pr-ci.yml.
  • Coverage matrix updated — row 4.2.7 Plan-mode review in docs/TEST-COVERAGE-MATRIX.md reflects the parked-future gate.
  • All affected feature IDs from the matrix are listed in the PR description under ## Related
  • No new external network dependencies introduced (mock backend used per Testing Strategy)
  • N/A: Does not touch release-cut smoke surfaces (covered by unit + RPC E2E, not the manual smoke list).
  • N/A: No issue closed by this PR — the related follow-up (Make the plan-review phase extendable to any agent, not just the orchestrator #4104) intentionally stays open.

Impact

Desktop/web chat only. Interactive multi-step turns now pause for review; trivial one-step replies are unaffected. Background/cron/subconscious runs auto-approve. No migrations; one new in-memory gate + one read-only RPC + two domain events. No persistence (a parked interactive turn can't resume across a restart). i18n reuses existing keys.

Related


AI Authored PR Metadata

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

  • Branch: feat/plan-mode-review
  • Commit SHA: 0b4358a5c90c

Validation Run

  • pnpm --filter openhuman-app format:check
  • pnpm typecheck
  • Focused tests: cargo test --lib plan_review todos::ops agent::tools::todo task_dispatcher, pnpm vitest run (PlanReviewCard, taskPlanActions, Conversations.render)
  • Rust fmt/check: cargo fmt --check, cargo check --manifest-path Cargo.toml
  • N/A: Tauri shell (app/src-tauri) unchanged.

Validation Blocked

  • command: N/A
  • error: N/A
  • impact: N/A

Behavior Changes

  • Intended: interactive turns pause on a plan the user must approve before execution.
  • User-visible: a Plan review card appears above the composer; nothing executes until approved.

Parity Contract

  • Legacy preserved: background/cron/subconscious/CLI auto-approve; existing task-board + approval flows unchanged.
  • Guard/fallback: gate fails closed (reject) on TTL/dropped sender; non-WebChat origins never park.

Duplicate / Superseded PR Handling

  • Duplicate PR(s): None
  • Canonical PR: This PR
  • Resolution: N/A

Summary by CodeRabbit

  • New Features

    • Added a thread-scoped Plan Review flow that gates multi-step plan execution behind user actions: Approve & run, Reject, or Request changes with a feedback box.
    • The UI displays plan summary, ordered steps, and supports Cmd/Ctrl+Enter to submit feedback.
    • Added the experience across multiple languages and marked it as Beta.
  • Bug Fixes

    • Plan review prompts now clear reliably on completion, errors, or socket disconnect, preventing stuck prompts.
  • Tests

    • Updated/added coverage for the plan review card and decision flows.

Auto-park thread-scoped plan cards for review on interactive (WebChat) turns:
the todo tool now defaults approval_mode=Required when the turn origin is
WebChat, so the dispatcher holds the plan at awaiting_approval until the user
decides. Background origins keep the config-driven default.

Add openhuman.todos_revise_plan: rejects every awaiting card so the
orchestrator can re-plan from user feedback (sent back as a chat message).
Nudge the orchestrator prompt to lay out the plan and stop for review before
executing. Document the feature in about_app. Unit + JSON-RPC E2E coverage.
Surface a parked thread plan above the composer as a single review panel:
Approve & run (all awaiting cards -> ready), Reject (-> rejected), or send
free-text feedback (clears the plan via revisePlan, then bounces the feedback
into the thread so the agent re-plans). The todo strip stays read-only progress
while the review card owns the decision. Adds threadApi.revisePlan,
runDecidePlanAll/runRevisePlan actions, i18n keys across all locales, and
Vitest coverage.
The parked-plan Approve control moved from the todo strip to the new
PlanReviewCard; the RPC path (threadApi.decidePlan) is unchanged.
@senamakel senamakel requested a review from a team June 25, 2026 08:59
@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a2e05208-7733-4ea3-beb4-2404600f1e8b

📥 Commits

Reviewing files that changed from the base of the PR and between 78303a8 and a2cf425.

📒 Files selected for processing (19)
  • app/src/lib/i18n/ar.ts
  • app/src/lib/i18n/bn.ts
  • app/src/lib/i18n/de.ts
  • app/src/lib/i18n/en.ts
  • app/src/lib/i18n/es.ts
  • app/src/lib/i18n/fr.ts
  • app/src/lib/i18n/hi.ts
  • app/src/lib/i18n/id.ts
  • app/src/lib/i18n/it.ts
  • app/src/lib/i18n/ko.ts
  • app/src/lib/i18n/pl.ts
  • app/src/lib/i18n/pt.ts
  • app/src/lib/i18n/ru.ts
  • app/src/lib/i18n/zh-CN.ts
  • app/src/pages/Conversations.tsx
  • app/src/pages/__tests__/Conversations.render.test.tsx
  • app/src/providers/ChatRuntimeProvider.tsx
  • app/src/services/chatService.ts
  • app/src/store/chatRuntimeSlice.ts
✅ Files skipped from review due to trivial changes (7)
  • app/src/lib/i18n/ru.ts
  • app/src/lib/i18n/it.ts
  • app/src/lib/i18n/ar.ts
  • app/src/lib/i18n/fr.ts
  • app/src/lib/i18n/de.ts
  • app/src/lib/i18n/pt.ts
  • app/src/lib/i18n/id.ts
🚧 Files skipped from review as they are similar to previous changes (12)
  • app/src/lib/i18n/ko.ts
  • app/src/lib/i18n/bn.ts
  • app/src/lib/i18n/zh-CN.ts
  • app/src/lib/i18n/es.ts
  • app/src/lib/i18n/pl.ts
  • app/src/services/chatService.ts
  • app/src/lib/i18n/en.ts
  • app/src/lib/i18n/hi.ts
  • app/src/providers/ChatRuntimeProvider.tsx
  • app/src/store/chatRuntimeSlice.ts
  • app/src/pages/Conversations.tsx
  • app/src/pages/tests/Conversations.render.test.tsx

📝 Walkthrough

Walkthrough

The PR adds a plan-review gate and revise-plan RPC path, wires chat runtime events into a pending review card, and updates orchestrator policy, translations, capability metadata, and coverage docs.

Changes

Plan review flow

Layer / File(s) Summary
Revise-plan controller
src/openhuman/todos/ops.rs, src/openhuman/todos/schemas.rs, tests/json_rpc_e2e.rs
Adds the revise_plan controller, request parsing, todo mutation, and JSON-RPC coverage for rejecting awaiting cards.
Plan review gate and RPC
src/openhuman/mod.rs, src/openhuman/plan_review/*, src/core/all.rs, src/core/event_bus/events.rs, src/openhuman/channels/providers/web/event_bus.rs, src/openhuman/tools/ops.rs, src/core/jsonrpc.rs
Adds the in-memory plan-review gate, request tool, controller schema, core events, controller registration, and web-channel bridging for interactive plan decisions.
Chat runtime and conversation UI
app/src/services/chatService.ts, app/src/store/chatRuntimeSlice.ts, app/src/providers/ChatRuntimeProvider.tsx, app/src/providers/__tests__/ChatRuntimeProvider.test.tsx, app/src/pages/Conversations.tsx, app/src/pages/conversations/components/PlanReviewCard.tsx, app/src/pages/conversations/components/PlanReviewCard.test.tsx, app/src/pages/__tests__/Conversations.render.test.tsx, app/src/pages/conversations/taskPlanActions.test.tsx, app/src/lib/i18n/*.ts
Adds chat socket event parsing, pending plan-review state, provider cleanup, the plan-review card, conversation wiring, and tests and locale strings for the UI flow.
Orchestrator policy and docs
src/openhuman/about_app/catalog_data.rs, src/openhuman/agent/task_dispatcher/poller.rs, src/openhuman/agent/task_dispatcher/tests.rs, src/openhuman/agent/tools/todo.rs, src/openhuman/agent_registry/agents/orchestrator/agent.toml, src/openhuman/agent_registry/agents/orchestrator/prompt.md, docs/TEST-COVERAGE-MATRIX.md
Adds the plan-review capability entry, orchestrator gating rule, approval-mode override, tool allowlist, and coverage row.

Sequence Diagram(s)

sequenceDiagram
  participant RequestPlanReviewTool
  participant PlanReviewGate
  participant ApprovalSurfaceSubscriber
  participant ChatRuntimeProvider
  participant PlanReviewCard
  participant openhuman_plan_review_decide as openhuman.plan_review_decide

  RequestPlanReviewTool->>PlanReviewGate: request_review(summary, steps)
  PlanReviewGate->>ApprovalSurfaceSubscriber: publish PlanReviewRequested
  ApprovalSurfaceSubscriber->>ChatRuntimeProvider: plan_review_request socket event
  ChatRuntimeProvider->>PlanReviewCard: pending review state
  PlanReviewCard->>openhuman_plan_review_decide: approve / reject / revise
  openhuman_plan_review_decide->>PlanReviewGate: decide(request_id, resolution)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

Suggested labels

feature, rust-core, agent

Suggested reviewers

  • oxoxDev

Poem

I hopped through the plan with a flick of my ear,
Parked the review, then the choices grew clear.
Approve, reject, or revise with a grin,
The card and the comments all danced back in.
🐇 A carrot for every tiny win!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: a plan-review flow with approve, reject, revise, and pre-execution gating.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fd8898bab1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/openhuman/agent_registry/agents/orchestrator/prompt.md Outdated
Comment thread src/openhuman/agent/tools/todo.rs Outdated
Comment thread app/src/pages/Conversations.tsx Outdated
@coderabbitai coderabbitai Bot added agent Built-in agents, prompts, orchestration, and agent runtime in src/openhuman/agent/. feature Net-new user-facing capability or product behavior. rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. labels Jun 25, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
app/src/pages/conversations/components/PlanReviewCard.tsx (2)

85-89: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: avoid recomputing cardLabel(card).

cardLabel(card) runs up to three times per card (line 85 and twice on line 86). Hoisting it to a const label = cardLabel(card) inside the .map keeps the objective dedupe check clearer and avoids the repeated calls.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/pages/conversations/components/PlanReviewCard.tsx` around lines 85 -
89, Hoist the repeated card label computation in PlanReviewCard so it is
evaluated once per card instead of multiple times. Inside the .map for the
cards, store cardLabel(card) in a local const (for example, label) and use that
value both for rendering the title and for the objective dedupe comparison in
the objective conditional. This keeps the logic in PlanReviewCard clearer and
avoids repeated calls while preserving the existing behavior.

60-64: 🔒 Security & Privacy | 🔵 Trivial

Use a non-modal role here. alertdialog implies a modal dialog with focus management, but this panel is rendered inline above the composer and doesn’t trap or move focus. role="region" or role="group" with the existing label fits better.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/pages/conversations/components/PlanReviewCard.tsx` around lines 60 -
64, The PlanReviewCard container is using a modal-only role even though it
renders inline, so update the root element in PlanReviewCard to use a non-modal
role such as region or group while keeping the existing aria-label from
t('conversations.planReview.title'). Make the change on the main wrapper that
currently declares role="alertdialog" so the accessibility semantics match the
actual behavior.
app/src/pages/Conversations.tsx (1)

2612-2648: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Optional: guard against duplicate plan decisions; disabled prop is effectively dead here.

Two small things on the wiring:

  • This block is already gated by selectedThreadId && awaitingPlanCards.length > 0, so disabled={!selectedThreadId} is always false. It's harmless but misleading — there's no actual disabled state while a decision is in flight.
  • Because disabled never flips true, the Approve / Reject / Send-feedback buttons stay clickable while runDecidePlanAll / runRevisePlan are awaiting. A rapid double-click (or Approve-then-Reject) can fire two concurrent decidePlan batches, or send the feedback message twice, before the board refresh unmounts the card. Consider tracking a local in-flight flag and passing it as disabled so the card locks until the action settles.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/pages/Conversations.tsx` around lines 2612 - 2648, The PlanReviewCard
wiring in Conversations.tsx has no real disabled state, so
Approve/Reject/Send-feedback can be triggered multiple times while
runDecidePlanAll or runRevisePlan is still in flight. Add a local in-flight
guard around this block, set it before calling runDecidePlanAll/runRevisePlan
and clear it when the promise settles, then pass that flag into PlanReviewCard
as disabled. Use the existing selectedThreadId, awaitingPlanCards,
runDecidePlanAll, and runRevisePlan callbacks to keep the card locked until the
action finishes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@app/src/pages/Conversations.tsx`:
- Around line 2612-2648: The PlanReviewCard wiring in Conversations.tsx has no
real disabled state, so Approve/Reject/Send-feedback can be triggered multiple
times while runDecidePlanAll or runRevisePlan is still in flight. Add a local
in-flight guard around this block, set it before calling
runDecidePlanAll/runRevisePlan and clear it when the promise settles, then pass
that flag into PlanReviewCard as disabled. Use the existing selectedThreadId,
awaitingPlanCards, runDecidePlanAll, and runRevisePlan callbacks to keep the
card locked until the action finishes.

In `@app/src/pages/conversations/components/PlanReviewCard.tsx`:
- Around line 85-89: Hoist the repeated card label computation in PlanReviewCard
so it is evaluated once per card instead of multiple times. Inside the .map for
the cards, store cardLabel(card) in a local const (for example, label) and use
that value both for rendering the title and for the objective dedupe comparison
in the objective conditional. This keeps the logic in PlanReviewCard clearer and
avoids repeated calls while preserving the existing behavior.
- Around line 60-64: The PlanReviewCard container is using a modal-only role
even though it renders inline, so update the root element in PlanReviewCard to
use a non-modal role such as region or group while keeping the existing
aria-label from t('conversations.planReview.title'). Make the change on the main
wrapper that currently declares role="alertdialog" so the accessibility
semantics match the actual behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d9d8455b-951b-4b43-88a8-c4c9bc32f5ce

📥 Commits

Reviewing files that changed from the base of the PR and between febc1b5 and fd8898b.

📒 Files selected for processing (28)
  • app/src/lib/i18n/ar.ts
  • app/src/lib/i18n/bn.ts
  • app/src/lib/i18n/de.ts
  • app/src/lib/i18n/en.ts
  • app/src/lib/i18n/es.ts
  • app/src/lib/i18n/fr.ts
  • app/src/lib/i18n/hi.ts
  • app/src/lib/i18n/id.ts
  • app/src/lib/i18n/it.ts
  • app/src/lib/i18n/ko.ts
  • app/src/lib/i18n/pl.ts
  • app/src/lib/i18n/pt.ts
  • app/src/lib/i18n/ru.ts
  • app/src/lib/i18n/zh-CN.ts
  • app/src/pages/Conversations.tsx
  • app/src/pages/__tests__/Conversations.render.test.tsx
  • app/src/pages/conversations/components/PlanReviewCard.test.tsx
  • app/src/pages/conversations/components/PlanReviewCard.tsx
  • app/src/pages/conversations/taskPlanActions.test.ts
  • app/src/pages/conversations/taskPlanActions.ts
  • app/src/services/api/threadApi.ts
  • docs/TEST-COVERAGE-MATRIX.md
  • src/openhuman/about_app/catalog_data.rs
  • src/openhuman/agent/tools/todo.rs
  • src/openhuman/agent_registry/agents/orchestrator/prompt.md
  • src/openhuman/todos/ops.rs
  • src/openhuman/todos/schemas.rs
  • tests/json_rpc_e2e.rs

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 25, 2026
# Conflicts:
#	app/src/lib/i18n/ar.ts
#	app/src/lib/i18n/bn.ts
#	app/src/lib/i18n/de.ts
#	app/src/lib/i18n/en.ts
#	app/src/lib/i18n/es.ts
#	app/src/lib/i18n/fr.ts
#	app/src/lib/i18n/hi.ts
#	app/src/lib/i18n/id.ts
#	app/src/lib/i18n/it.ts
#	app/src/lib/i18n/ko.ts
#	app/src/lib/i18n/pl.ts
#	app/src/lib/i18n/pt.ts
#	app/src/lib/i18n/ru.ts
#	app/src/lib/i18n/zh-CN.ts
#	tests/json_rpc_e2e.rs

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4d77bb6a4d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/openhuman/agent/tools/todo.rs Outdated
Comment thread app/src/pages/Conversations.tsx Outdated
Three correctness fixes from PR review:
- Orchestrator allowlist listed the legacy `todowrite` alias, which resolves
  to no registered tool (named scope matches `TodoTool::name() == "todo"`
  exactly). Switch to `todo` so interactive turns can create the cards the
  plan-review gate hooks.
- `requires_plan_approval` ignored explicit per-card `Required` when the global
  task-plan-approval flag was off. Make explicit `Required` always park (and
  `NotRequired` always opt out); unset still follows the global default.
- Plan-review feedback reused handleSendMessage/handleSendFollowup, which pulled
  in the composer's pending attachments and cleared the user's draft. Add an
  `isolateComposer` option so feedback sends only its own text and preserves the
  composer state.
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 25, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 85fc17a37a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread app/src/pages/conversations/taskPlanActions.ts Outdated
The task-board approach didn't work for live chat: the dispatcher only sweeps
the user-tasks / task-sources boards, never conversation threads, so a chat
plan was never parked to awaiting_approval and an approved plan never ran.

Replace it with a parked-future gate modelled on ApprovalGate:
- New src/openhuman/plan_review/: in-memory PlanReviewGate parks the LIVE turn
  on a oneshot; the request_plan_review tool blocks the interactive turn until
  the user decides; openhuman.plan_review_decide resolves it
  (approve/reject/revise+feedback). Non-interactive origins auto-approve.
- DomainEvent::PlanReviewRequested/Decided bridged to a plan_review_request
  socket event (reuses the web approval subscriber + APPROVAL_CHAT_CONTEXT).
- Orchestrator: prompt calls request_plan_review after laying out the plan;
  allowlist gains request_plan_review (and todo, replacing the dead todowrite
  alias).
- Frontend: PlanReviewCard is now event-driven (pendingPlanReviewByThread) and
  resolves via plan_review_decide; retired the task-board awaiting_approval
  rendering, runDecidePlanAll/runRevisePlan, threadApi.revisePlan, and the
  todo WebChat auto-gating.

Keeps the requires_plan_approval fix (explicit Required overrides the global
off-switch) for the dispatched boards. Rust gate/tool/schema unit tests +
plan_review_decide JSON-RPC E2E + frontend card/provider tests.
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 25, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0b4358a5c9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/openhuman/plan_review/tool.rs
Comment thread app/src/providers/ChatRuntimeProvider.tsx
…post-approval

Address two Codex P2s on the gate:
- ChatRuntimeProvider now clears pendingPlanReviewByThread on chat_done /
  chat_error / disconnect (alongside the approval clear), so an expired/cancelled
  review card can't linger with a stale requestId.
- Orchestrator prompt: call request_plan_review with the steps BEFORE creating
  todo cards, and only create cards after approval — a rejected/revised plan no
  longer leaves todo cards pinned on the thread board.

Adds provider tests for the set + clear-on-end paths.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ac9a941dc0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/openhuman/plan_review/tool.rs
@coderabbitai coderabbitai Bot added the docs Docs-only change; used by PR automation. label Jun 25, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/openhuman/plan_review/schemas.rs (1)

90-102: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add debug/trace logging on the error branches.

handle_decide logs the success entry but the revise-without-feedback and invalid-decision branches return errors silently. Adding a tracing::debug!/warn! on these rejection paths aids diagnosing malformed RPC calls.

Proposed logging on error branches
             "revise" => {
                 let feedback = p.feedback.unwrap_or_default();
                 if feedback.trim().is_empty() {
+                    tracing::debug!(
+                        request_id = %p.request_id,
+                        "[rpc][plan_review] revise rejected: empty feedback"
+                    );
                     return Err("revise requires non-empty `feedback`".to_string());
                 }
                 PlanReviewResolution::Revise { feedback }
             }
             other => {
+                tracing::debug!(
+                    request_id = %p.request_id,
+                    decision = %other,
+                    "[rpc][plan_review] invalid decision verb"
+                );
                 return Err(format!(
                     "invalid decision '{other}' (expected approve|reject|revise)"
                 ))
             }

As per coding guidelines: "Add debug logging to entry/exit, branches, external calls, retries/timeouts, state transitions, and errors using log/tracing at debug/trace level in Rust".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/plan_review/schemas.rs` around lines 90 - 102, The
`handle_decide` error paths in `schemas.rs` return silently for `revise` with
empty `feedback` and for invalid `decision` values; add `tracing::debug!` or
`tracing::warn!` logging on both branches before returning the `Err`, including
the rejected decision/feedback context so malformed RPC calls are visible. Keep
the existing success logging intact and place the new logs near the `revise` arm
and the `other` match arm in
`PlanReviewResolution::from`/`handle_decide`-related code.

Source: Coding guidelines

src/openhuman/plan_review/gate.rs (1)

59-101: 🩺 Stability & Availability | 🔵 Trivial

Short-circuit unroutable plan reviews in src/openhuman/plan_review/gate.rs
When thread_id is None, this still registers a waiter and waits for the full TTL even though nothing can surface or call decide_by_thread. Return Reject immediately for unroutable requests instead of parking them.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/plan_review/gate.rs` around lines 59 - 101, In request_review,
handle unroutable plan reviews up front: when thread_id is None, do not create a
oneshot waiter, insert into waiters/thread_to_request, or await the TTL.
Short-circuit immediately with PlanReviewResolution::Reject before the
publish_global(DomainEvent::PlanReviewRequested) path, and keep the existing
request_id/tracing flow intact for routable requests.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/pages/Conversations.tsx`:
- Around line 2623-2625: `PlanReviewCard` is being reused across different
parked/re-parked reviews, so its local `useState` can retain stale decision and
feedback state. Update the `PlanReviewCard` usage in `Conversations.tsx` to add
a stable `key` based on the review/request id (for example, the same identifier
exposed by `pendingPlanReview`), matching the `ApprovalRequestCard` and
`IntegrationConnectCard` pattern above. This will force a remount when a new
review is shown and reset the component state correctly.

In `@src/openhuman/plan_review/tool.rs`:
- Around line 170-188: The plan-review waiter cleanup in request_review can be
skipped if the future is cancelled before rx.await completes, leaving stale
waiters and thread_to_request entries behind. Update the request_review flow in
gate.rs so cleanup happens before the await via a drop guard or similar
cancellation-safe mechanism, and ensure the existing
interactive_turn_parks_until_resolved test still passes by verifying parked
requests are removed even when the future is dropped early.

---

Nitpick comments:
In `@src/openhuman/plan_review/gate.rs`:
- Around line 59-101: In request_review, handle unroutable plan reviews up
front: when thread_id is None, do not create a oneshot waiter, insert into
waiters/thread_to_request, or await the TTL. Short-circuit immediately with
PlanReviewResolution::Reject before the
publish_global(DomainEvent::PlanReviewRequested) path, and keep the existing
request_id/tracing flow intact for routable requests.

In `@src/openhuman/plan_review/schemas.rs`:
- Around line 90-102: The `handle_decide` error paths in `schemas.rs` return
silently for `revise` with empty `feedback` and for invalid `decision` values;
add `tracing::debug!` or `tracing::warn!` logging on both branches before
returning the `Err`, including the rejected decision/feedback context so
malformed RPC calls are visible. Keep the existing success logging intact and
place the new logs near the `revise` arm and the `other` match arm in
`PlanReviewResolution::from`/`handle_decide`-related code.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7dcd43b1-cc1b-48f9-aa5e-b1f1ee72ae58

📥 Commits

Reviewing files that changed from the base of the PR and between fd8898b and ac9a941.

📒 Files selected for processing (41)
  • app/src/lib/i18n/ar.ts
  • app/src/lib/i18n/bn.ts
  • app/src/lib/i18n/de.ts
  • app/src/lib/i18n/en.ts
  • app/src/lib/i18n/es.ts
  • app/src/lib/i18n/fr.ts
  • app/src/lib/i18n/hi.ts
  • app/src/lib/i18n/id.ts
  • app/src/lib/i18n/it.ts
  • app/src/lib/i18n/ko.ts
  • app/src/lib/i18n/pl.ts
  • app/src/lib/i18n/pt.ts
  • app/src/lib/i18n/ru.ts
  • app/src/lib/i18n/zh-CN.ts
  • app/src/pages/Conversations.tsx
  • app/src/pages/__tests__/Conversations.render.test.tsx
  • app/src/pages/conversations/components/PlanReviewCard.test.tsx
  • app/src/pages/conversations/components/PlanReviewCard.tsx
  • app/src/pages/conversations/taskPlanActions.test.ts
  • app/src/providers/ChatRuntimeProvider.tsx
  • app/src/providers/__tests__/ChatRuntimeProvider.test.tsx
  • app/src/services/chatService.ts
  • app/src/store/chatRuntimeSlice.ts
  • docs/TEST-COVERAGE-MATRIX.md
  • src/core/all.rs
  • src/core/event_bus/events.rs
  • src/openhuman/about_app/catalog_data.rs
  • src/openhuman/agent/task_dispatcher/poller.rs
  • src/openhuman/agent/task_dispatcher/tests.rs
  • src/openhuman/agent/tools/todo.rs
  • src/openhuman/agent_registry/agents/orchestrator/agent.toml
  • src/openhuman/agent_registry/agents/orchestrator/prompt.md
  • src/openhuman/channels/providers/web/event_bus.rs
  • src/openhuman/mod.rs
  • src/openhuman/plan_review/gate.rs
  • src/openhuman/plan_review/mod.rs
  • src/openhuman/plan_review/schemas.rs
  • src/openhuman/plan_review/tool.rs
  • src/openhuman/plan_review/types.rs
  • src/openhuman/tools/ops.rs
  • tests/json_rpc_e2e.rs
✅ Files skipped from review due to trivial changes (10)
  • app/src/lib/i18n/de.ts
  • src/openhuman/agent/tools/todo.rs
  • app/src/lib/i18n/pt.ts
  • app/src/lib/i18n/ko.ts
  • app/src/lib/i18n/ar.ts
  • src/openhuman/plan_review/mod.rs
  • app/src/lib/i18n/pl.ts
  • app/src/lib/i18n/ru.ts
  • docs/TEST-COVERAGE-MATRIX.md
  • src/openhuman/about_app/catalog_data.rs
🚧 Files skipped from review as they are similar to previous changes (9)
  • app/src/lib/i18n/es.ts
  • src/openhuman/agent_registry/agents/orchestrator/prompt.md
  • app/src/lib/i18n/fr.ts
  • app/src/lib/i18n/en.ts
  • app/src/lib/i18n/zh-CN.ts
  • app/src/lib/i18n/hi.ts
  • app/src/lib/i18n/it.ts
  • app/src/lib/i18n/bn.ts
  • tests/json_rpc_e2e.rs

Comment thread app/src/pages/Conversations.tsx
Comment thread src/openhuman/plan_review/tool.rs
…keyed card

Address review on the gate:
- request_plan_review now returns ToolTimeout::Unbounded so the harness's
  global tool timeout (~120s) can't drop the parked future before the gate's
  own 10-min TTL — approving the card always resumes the turn.
- PlanReviewGate cleans up the waiter/thread mapping via a Drop guard, so a
  cancelled/dropped parked future can't leak a re-decidable entry.
- PlanReviewCard is keyed by requestId so a re-parked (revised) plan or a thread
  switch remounts it and resets local decision/feedback state.

Adds a gate cancellation-cleanup test.
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 25, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 961319cf3a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/openhuman/channels/providers/web/event_bus.rs
…gate

The plan-review events reuse the approval-surface subscriber, which was only
registered inside the `install_gate` branch. A standalone/CLI/Docker core run
with OPENHUMAN_APPROVAL_GATE=0 (and no listening integrations, so start_channels
is skipped) would then park a plan review and publish PlanReviewRequested with
no subscriber to bridge it — the card never shows and the turn dies at the gate
TTL. Move the subscriber registration to the always-run serve boot, independent
of the approval gate.
@coderabbitai coderabbitai Bot removed the docs Docs-only change; used by PR automation. label Jun 25, 2026
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 25, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 78303a8e84

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread app/src/providers/ChatRuntimeProvider.tsx
# Conflicts:
#	app/src/providers/ChatRuntimeProvider.tsx

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a2cf42563f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// onDone/onError would otherwise leave the card stuck for a turn that
// can't complete.
dispatch(clearPendingApprovalForThread({ threadId }));
dispatch(clearPendingPlanReviewForThread({ threadId }));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep pending plan reviews across socket reconnects

When the Socket.IO connection drops transiently while a turn is parked on request_plan_review, this clears the only stored requestId even though the core turn and PlanReviewGate are not cancelled by a socket disconnect; the socket bridge explicitly supports reconnecting to the thread room to continue receiving an in-flight stream. After reconnect the review card is gone, so the user cannot approve/revise and the parked turn waits until the 10-minute TTL rejects the plan.

Useful? React with 👍 / 👎.

@senamakel senamakel merged commit 9643712 into tinyhumansai:main Jun 25, 2026
27 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent Built-in agents, prompts, orchestration, and agent runtime in src/openhuman/agent/. feature Net-new user-facing capability or product behavior. rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant