fix(cursor): register cursorSessionId before ACP session/load (#837)

Pre-write resume token into session metadata before awaiting session/load
so hub and web see cursorSessionId immediately after resume spawn.

Fixes #834

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
SSU-WEI HUANG
2026-06-08 13:31:23 +08:00
committed by GitHub
co-authored by Cursor
parent 8094b500f3
commit fa363c2f6c
2 changed files with 34 additions and 2 deletions
+29 -1
View File
@@ -12,7 +12,9 @@ const harness = vi.hoisted(() => ({
backendArgs: null as { command: string; args?: string[] } | null,
setConfigOptionCalls: [] as Array<{ sessionId: string; configId: string; value: string }>,
deferSetConfigOption: null as Promise<void> | null,
releaseSetConfigOption: null as (() => void) | null
releaseSetConfigOption: null as (() => void) | null,
deferLoadSession: null as Promise<void> | null,
releaseLoadSession: null as (() => void) | null
}));
const legacyLauncher = vi.hoisted(() => vi.fn());
@@ -38,6 +40,9 @@ vi.mock('./utils/cursorAcpBackend', () => ({
supportsLoadSession: vi.fn(() => harness.supportsLoadSession),
loadSession: vi.fn(async () => {
harness.loadSessionCalled = true;
if (harness.deferLoadSession) {
await harness.deferLoadSession;
}
if (harness.loadSessionError) throw harness.loadSessionError;
return 'loaded-acp-session';
}),
@@ -174,6 +179,8 @@ describe('cursorAcpRemoteLauncher', () => {
harness.setConfigOptionCalls = [];
harness.deferSetConfigOption = null;
harness.releaseSetConfigOption = null;
harness.deferLoadSession = null;
harness.releaseLoadSession = null;
legacyLauncher.mockClear();
process.stdin.isTTY = false;
process.stdout.isTTY = false;
@@ -204,6 +211,27 @@ describe('cursorAcpRemoteLauncher', () => {
expect(harness.newSessionCalled).toBe(false);
});
it('registers cursorSessionId before session/load completes', async () => {
let releaseLoadSession!: () => void;
harness.deferLoadSession = new Promise<void>((resolve) => {
harness.releaseLoadSession = resolve;
releaseLoadSession = resolve;
});
const session = makeSession('resume-thread-1');
const launchPromise = cursorAcpRemoteLauncher(session);
await vi.waitFor(() => {
expect(session.onSessionFoundWithProtocol).toHaveBeenCalledWith('resume-thread-1', 'acp');
});
expect(harness.loadSessionCalled).toBe(true);
releaseLoadSession();
await launchPromise;
expect(session.onSessionFoundWithProtocol).toHaveBeenCalledWith('loaded-acp-session', 'acp');
});
it('throws when session/load fails instead of falling back to stream-json', async () => {
harness.loadSessionError = new Error('session not found');
const session = makeSession('old-stream-json-id');
+5 -1
View File
@@ -96,6 +96,8 @@ class CursorAcpRemoteLauncher extends RemoteLauncherBase {
let acpSessionId: string;
if (resumeSessionId && backend.supportsLoadSession()) {
// Register pending cursorSessionId before awaiting session/load (Zed PR #54431).
session.onSessionFoundWithProtocol(resumeSessionId, 'acp');
try {
acpSessionId = await backend.loadSession({
sessionId: resumeSessionId,
@@ -119,7 +121,9 @@ class CursorAcpRemoteLauncher extends RemoteLauncherBase {
});
}
session.onSessionFoundWithProtocol(acpSessionId, 'acp');
if (acpSessionId !== resumeSessionId) {
session.onSessionFoundWithProtocol(acpSessionId, 'acp');
}
syncCursorModelsFromAcp(backend, acpSessionId);