mirror of
https://github.com/wu736139669/hapi.git
synced 2026-11-06 01:11:37 +00:00
fix(cursor): CreatePlan Yes accepts and continues task (nested outcome + plan→execute) (#1097)
* fix(cursor): nest ACP extension outcome so plan approvals aren't cancelled
Cursor's ACP blocking extension methods (cursor/ask_question,
cursor/create_plan) expect the JSON-RPC result to nest the outcome under
an `outcome` key, e.g. `{ outcome: { outcome: "accepted" } }`. The
adapter returned it flat (`{ outcome: "accepted" }`), so Cursor read
`response.outcome.outcome` as undefined and fell back to a cancellation
— an approved plan was relayed to the agent as `User cancelled`, making
plan mode unusable over HAPI for Cursor sessions.
Wrap every ask_question / create_plan response in the nested envelope
and add regression tests asserting the exact wire shape for the
affirmative approval -> CreatePlan path (plus approved_for_session,
reject, and abort).
Fixes #79
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cursor): continue task after CreatePlan Yes (plan→execute)
Nested ACP accept alone is not a complete fix: Yes unblocked create_plan
but the prompt turn still ended with the plan "done" and no execution.
Mirror Claude ExitPlanMode: on accept, leave plan/ask for an executable
mode and queue a continue prompt so the original user task keeps going.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cursor): sync runCursor enqueue mode after CreatePlan accept
Codex Major on #1097: setPermissionMode alone left runCursor's
currentPermissionMode stale, so the next user message could re-enter
plan/ask after Yes. Notify onPermissionModeChanged from CursorSession
so the enqueue source of truth stays aligned with the session.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Debian <heavygee@oos-linux.in.lockhouse>
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
Debian
parent
311e0cef55
commit
e4cc3916c8
@@ -24,8 +24,10 @@ import {
|
||||
applyCursorAcpMode,
|
||||
applyCursorAcpModel,
|
||||
isCursorAutoReviewMode,
|
||||
resolveCursorModeAfterPlanApproval,
|
||||
wireIdForCursorSessionState
|
||||
} from './utils/cursorModeConfig';
|
||||
import { CURSOR_PLAN_CONTINUE } from './utils/cursorPlanContinue';
|
||||
import { cursorPassThroughStatusMessage, parseCursorSpecialCommand } from './cursorSpecialCommands';
|
||||
import { buildCursorModelsSeedPayload, seedCursorModelsCache } from '@/modules/common/cursorModels';
|
||||
import { readSharedCursorModelsCache } from '@/modules/common/cursorModelsSharedCache';
|
||||
@@ -34,6 +36,7 @@ import { registerAcpSessionTitleSync } from '@/agent/acpSessionTitle';
|
||||
class CursorAcpRemoteLauncher extends RemoteLauncherBase {
|
||||
private readonly session: CursorSession;
|
||||
private backend: ReturnType<typeof createCursorAcpBackend> | null = null;
|
||||
private acpSessionId: string | null = null;
|
||||
private permissionAdapter: PermissionAdapter | null = null;
|
||||
private extensionAdapter: CursorExtensionAdapter | null = null;
|
||||
private happyServer: { stop: () => void } | null = null;
|
||||
@@ -115,7 +118,8 @@ class CursorAcpRemoteLauncher extends RemoteLauncherBase {
|
||||
const extensionAdapter = new CursorExtensionAdapter(
|
||||
session.client,
|
||||
backend,
|
||||
(message) => this.handleAgentMessage(message)
|
||||
(message) => this.handleAgentMessage(message),
|
||||
() => this.handleCreatePlanAccepted()
|
||||
);
|
||||
this.extensionAdapter = extensionAdapter;
|
||||
|
||||
@@ -155,6 +159,7 @@ class CursorAcpRemoteLauncher extends RemoteLauncherBase {
|
||||
mcpServers: mcpServerList
|
||||
});
|
||||
}
|
||||
this.acpSessionId = acpSessionId;
|
||||
|
||||
if (acpSessionId !== resumeSessionId) {
|
||||
session.onSessionFoundWithProtocol(acpSessionId, 'acp');
|
||||
@@ -304,6 +309,35 @@ class CursorAcpRemoteLauncher extends RemoteLauncherBase {
|
||||
setCursorAcpModelsSnapshot(null);
|
||||
}
|
||||
|
||||
private handleCreatePlanAccepted(): void {
|
||||
const backend = this.backend;
|
||||
const acpSessionId = this.acpSessionId;
|
||||
if (!backend || !acpSessionId) {
|
||||
logger.warn('[cursor-acp] CreatePlan accepted but ACP session is not ready; skip continue handoff');
|
||||
return;
|
||||
}
|
||||
|
||||
const session = this.session;
|
||||
const executeMode = resolveCursorModeAfterPlanApproval(
|
||||
session.getPermissionMode() as PermissionMode
|
||||
) as PermissionMode;
|
||||
|
||||
// Leave plan/ask for an executable mode, then queue a continue prompt so
|
||||
// Yes means "keep going on the user task" (Claude ExitPlanMode parallel).
|
||||
session.setPermissionMode(executeMode);
|
||||
void applyCursorAcpMode(backend, acpSessionId, executeMode).then(() => {
|
||||
this.applyDisplayMode(executeMode);
|
||||
});
|
||||
|
||||
session.queue.unshiftIsolated(CURSOR_PLAN_CONTINUE, {
|
||||
permissionMode: executeMode,
|
||||
model: session.model
|
||||
});
|
||||
logger.debug('[cursor-acp] CreatePlan accepted — queued continue prompt', {
|
||||
executeMode
|
||||
});
|
||||
}
|
||||
|
||||
private handleAgentMessage(message: AgentMessage): void {
|
||||
const converted = convertAgentMessage(message);
|
||||
if (converted) {
|
||||
|
||||
Reference in New Issue
Block a user