Summary
In a Cursor-flavor session over HAPI, approving a plan-mode CreatePlan request from the web UI relays User cancelled back to the agent instead of an approval. The plan → execute handoff never fires, so plan mode is effectively unusable for Cursor sessions.
Reproduction (3/3)
- Start a Cursor session, switch to plan mode.
- Agent calls the
CreatePlan tool. HAPI renders an approval prompt: "Waiting for approval..." with Yes / Yes For Session / Abort (tool card Create Plan / CursorCreatePlan).
- Click Yes. The tool card shows a green success checkmark (hub records the request as
approved).
- The agent's
CreatePlan tool call returns Error: User cancelled.
Root cause
Cursor's ACP blocking extension methods require the JSON-RPC result to nest the outcome under an outcome key (see https://cursor.com/docs/cli/acp):
interface CursorCreatePlanResponse {
outcome:
| { outcome: "accepted"; planUri?: string }
| { outcome: "rejected"; reason?: string }
| { outcome: "cancelled" };
}
cli/src/cursor/utils/cursorExtensionAdapter.ts responded with the outcome flat, e.g. { outcome: "accepted" }. Cursor then reads response.outcome.outcome, sees undefined, and falls back to a cancellation — so an approved plan is relayed to the agent as User cancelled.
The same flat shape was used for cursor/ask_question responses, which is the identical latent bug for that method.
Note: the standard ACP session/request_permission responses already use the nested { outcome: { outcome: ... } } shape in AcpSdkBackend, so the cursor extension responses were the outlier.
Fix
Wrap all cursor/ask_question and cursor/create_plan responses in the nested { outcome: { ... } } envelope. Regression tests assert the exact nested wire shape for the affirmative approval → CreatePlan path (plus approved_for_session, reject, and abort).
Summary
In a Cursor-flavor session over HAPI, approving a plan-mode
CreatePlanrequest from the web UI relaysUser cancelledback to the agent instead of an approval. The plan → execute handoff never fires, so plan mode is effectively unusable for Cursor sessions.Reproduction (3/3)
CreatePlantool. HAPI renders an approval prompt: "Waiting for approval..." with Yes / Yes For Session / Abort (tool cardCreate Plan/CursorCreatePlan).approved).CreatePlantool call returnsError: User cancelled.Root cause
Cursor's ACP blocking extension methods require the JSON-RPC result to nest the outcome under an
outcomekey (see https://cursor.com/docs/cli/acp):cli/src/cursor/utils/cursorExtensionAdapter.tsresponded with the outcome flat, e.g.{ outcome: "accepted" }. Cursor then readsresponse.outcome.outcome, seesundefined, and falls back to a cancellation — so an approved plan is relayed to the agent asUser cancelled.The same flat shape was used for
cursor/ask_questionresponses, which is the identical latent bug for that method.Note: the standard ACP
session/request_permissionresponses already use the nested{ outcome: { outcome: ... } }shape inAcpSdkBackend, so the cursor extension responses were the outlier.Fix
Wrap all
cursor/ask_questionandcursor/create_planresponses in the nested{ outcome: { ... } }envelope. Regression tests assert the exact nested wire shape for the affirmative approval → CreatePlan path (plusapproved_for_session, reject, and abort).