feat: implement plan approval gate before execution

Adds plan approval workflow per GitHub issue #111:
- Register plan_approval tool in tools index
- Add plan approval handling in message handler
  - Check for pending plans on each message
  - Detect approval/rejection messages
  - Handle plan approval flow
- Create plan approval modal TUI component
- Update system prompts to instruct agents to use plan_approval tool
  - Balanced tier: Updated to require plan_approval for complex tasks
  - Thorough tier: Updated with plan_approval workflow

Workflow:
1. Agent analyzes task complexity with plan_approval
2. Agent creates plan with steps, context, risks
3. Agent submits plan for user approval
4. User approves/rejects/provides feedback
5. Only after approval, agent proceeds with execution

Closes #111
This commit is contained in:
2026-02-05 18:51:49 -05:00
parent 3d2195f074
commit f8cde24d87
5 changed files with 405 additions and 71 deletions

View File

@@ -69,6 +69,15 @@ import {
executeDetectedCommand,
} from "@services/command-detection";
import { detectSkillCommand, executeSkill } from "@services/skill-service";
import {
getActivePlans,
isApprovalMessage,
isRejectionMessage,
approvePlan,
rejectPlan,
startPlanExecution,
formatPlanForDisplay,
} from "@services/plan-mode/plan-service";
// Track last response for feedback learning
let lastResponseContext: {
@@ -408,6 +417,56 @@ export const handleMessage = async (
// Check for feedback on previous response
await checkUserFeedback(message, callbacks);
// Check for pending plan approvals
const pendingPlans = getActivePlans().filter((p) => p.status === "pending");
if (pendingPlans.length > 0) {
const plan = pendingPlans[0];
// Check if this is an approval message
if (isApprovalMessage(message)) {
approvePlan(plan.id, message);
startPlanExecution(plan.id);
callbacks.onLog("system", `Plan "${plan.title}" approved. Proceeding with implementation.`);
addDebugLog("state", `Plan ${plan.id} approved by user`);
// Continue with agent execution - the agent will see the approved status
// and proceed with implementation
state.messages.push({
role: "user",
content: `The user approved the plan. Proceed with the implementation of plan "${plan.title}".`,
});
// Fall through to normal agent processing
} else if (isRejectionMessage(message)) {
rejectPlan(plan.id, message);
callbacks.onLog("system", `Plan "${plan.title}" rejected. Please provide feedback or a new approach.`);
addDebugLog("state", `Plan ${plan.id} rejected by user`);
// Add rejection to messages so agent can respond
state.messages.push({
role: "user",
content: `The user rejected the plan with feedback: "${message}". Please revise the approach.`,
});
// Fall through to normal agent processing to get revised plan
} else {
// Neither approval nor rejection - treat as feedback/modification request
callbacks.onLog("system", `Plan "${plan.title}" awaiting approval. Reply 'yes' to approve or 'no' to reject.`);
// Show the plan again with the feedback
const planDisplay = formatPlanForDisplay(plan);
appStore.addLog({
type: "system",
content: planDisplay,
});
// Add user's feedback to messages
state.messages.push({
role: "user",
content: `The user provided feedback on the pending plan: "${message}". Please address this feedback and update the plan.`,
});
// Fall through to normal agent processing
}
}
// Check for skill commands (e.g., /review, /commit)
const skillMatch = await detectSkillCommand(message);
if (skillMatch) {