mirror of
https://github.com/wu736139669/hapi.git
synced 2026-11-08 01:43:07 +00:00
* 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>
70 lines
2.4 KiB
TypeScript
70 lines
2.4 KiB
TypeScript
import { logger } from '@/ui/logger';
|
|
import { MessageQueue2 } from '@/utils/MessageQueue2';
|
|
import { runLocalRemoteSession } from '@/agent/loopBase';
|
|
import { CursorSession } from './session';
|
|
import { cursorLocalLauncher } from './cursorLocalLauncher';
|
|
import { cursorRemoteLauncher } from './cursorRemoteLauncher';
|
|
import type { Metadata } from '@hapi/protocol/schemas';
|
|
import { ApiClient, ApiSessionClient } from '@/lib';
|
|
import type { CursorPermissionMode } from '@hapi/protocol/types';
|
|
|
|
export type PermissionMode = CursorPermissionMode;
|
|
|
|
export interface EnhancedMode {
|
|
permissionMode: PermissionMode;
|
|
model?: string;
|
|
}
|
|
|
|
interface LoopOptions {
|
|
path: string;
|
|
startingMode?: 'local' | 'remote';
|
|
startedBy?: 'runner' | 'terminal';
|
|
onModeChange: (mode: 'local' | 'remote') => void;
|
|
messageQueue: MessageQueue2<EnhancedMode>;
|
|
session: ApiSessionClient;
|
|
api: ApiClient;
|
|
cursorArgs?: string[];
|
|
cursorWorktree?: boolean | string;
|
|
cursorAddDirs?: readonly string[];
|
|
permissionMode?: PermissionMode;
|
|
resumeSessionId?: string;
|
|
model?: string;
|
|
sessionMetadata?: Metadata | null;
|
|
onSessionReady?: (session: CursorSession) => void;
|
|
/** Keep runCursor's enqueue mode in sync when the session leaves plan/ask. */
|
|
onPermissionModeChanged?: (mode: PermissionMode) => void;
|
|
}
|
|
|
|
export async function loop(opts: LoopOptions): Promise<void> {
|
|
const logPath = logger.getLogPath();
|
|
const startedBy = opts.startedBy ?? 'terminal';
|
|
const startingMode = opts.startingMode ?? 'local';
|
|
const session = new CursorSession({
|
|
api: opts.api,
|
|
client: opts.session,
|
|
path: opts.path,
|
|
sessionId: opts.resumeSessionId ?? null,
|
|
logPath,
|
|
messageQueue: opts.messageQueue,
|
|
onModeChange: opts.onModeChange,
|
|
mode: startingMode,
|
|
startedBy,
|
|
startingMode,
|
|
cursorArgs: opts.cursorArgs,
|
|
cursorWorktree: opts.cursorWorktree,
|
|
cursorAddDirs: opts.cursorAddDirs,
|
|
model: opts.model,
|
|
permissionMode: opts.permissionMode ?? 'default',
|
|
onPermissionModeChanged: opts.onPermissionModeChanged
|
|
});
|
|
|
|
await runLocalRemoteSession({
|
|
session,
|
|
startingMode: opts.startingMode,
|
|
logTag: 'cursor-loop',
|
|
runLocal: cursorLocalLauncher,
|
|
runRemote: (session) => cursorRemoteLauncher(session, opts.sessionMetadata),
|
|
onSessionReady: opts.onSessionReady
|
|
});
|
|
}
|