diff --git a/cli/src/cursor/cursorRemoteLauncher.ts b/cli/src/cursor/cursorRemoteLauncher.ts index 3aaaef5c..2d1836a0 100644 --- a/cli/src/cursor/cursorRemoteLauncher.ts +++ b/cli/src/cursor/cursorRemoteLauncher.ts @@ -82,6 +82,9 @@ class CursorRemoteLauncher extends RemoteLauncherBase { }; let cursorSessionId: string | null = session.sessionId; + if (cursorSessionId) { + session.onSessionFound(cursorSessionId); + } while (!this.shouldExit) { const waitSignal = this.abortController.signal; diff --git a/hub/src/sync/sessionModel.test.ts b/hub/src/sync/sessionModel.test.ts index 1c40dd1c..7da3e020 100644 --- a/hub/src/sync/sessionModel.test.ts +++ b/hub/src/sync/sessionModel.test.ts @@ -818,7 +818,7 @@ describe('session model', () => { expect(result).toEqual({ type: 'error', - message: 'Resume session ID unavailable', + message: 'Resume session ID unavailable. Start a new session in this directory, or retry after the agent has initialized.', code: 'resume_unavailable' }) } finally { @@ -1011,7 +1011,39 @@ describe('session model', () => { expect(engine.resolveLocalResumeTarget(session.id, 'default')).toEqual({ type: 'error', - message: 'Resume session ID unavailable', + message: 'Resume session ID unavailable. Start a new session in this directory, or retry after the agent has initialized.', + code: 'resume_unavailable' + }) + } finally { + engine.stop() + } + }) + + it('returns resume_unavailable when a cursor session lacks cursorSessionId', () => { + const store = new Store(':memory:') + const engine = new SyncEngine( + store, + {} as never, + new RpcRegistry(), + { broadcast() {} } as never + ) + + try { + const session = engine.getOrCreateSession( + 'local-resume-cursor-no-id', + { + path: '/tmp/project', + host: 'localhost', + machineId: 'machine-1', + flavor: 'cursor' + }, + null, + 'default' + ) + + expect(engine.resolveLocalResumeTarget(session.id, 'default')).toEqual({ + type: 'error', + message: 'Resume session ID unavailable. Start a new session in this directory, or retry after the agent has initialized.', code: 'resume_unavailable' }) } finally { diff --git a/hub/src/sync/syncEngine.ts b/hub/src/sync/syncEngine.ts index 401f4d44..6ae12076 100644 --- a/hub/src/sync/syncEngine.ts +++ b/hub/src/sync/syncEngine.ts @@ -540,7 +540,11 @@ export class SyncEngine { const agentSessionId = this.resolveAgentResumeId(session, namespace) if (!agentSessionId) { - return { type: 'error', message: 'Resume session ID unavailable', code: 'resume_unavailable' } + return { + type: 'error', + message: 'Resume session ID unavailable. Start a new session in this directory, or retry after the agent has initialized.', + code: 'resume_unavailable' + } } return { diff --git a/hub/src/web/routes/sessions.test.ts b/hub/src/web/routes/sessions.test.ts index 459dcc78..d9887d00 100644 --- a/hub/src/web/routes/sessions.test.ts +++ b/hub/src/web/routes/sessions.test.ts @@ -569,6 +569,32 @@ describe('sessions routes', () => { expect(capturedResumeOpts).toEqual({ permissionMode: 'bypassPermissions' }) }) + it('returns 409 when resume token is unavailable', async () => { + const session = createSession({ + active: false, + metadata: { path: '/tmp/project', host: 'localhost', flavor: 'cursor' } + }) + const { app } = createApp(session, { + resumeSession: async () => ({ + type: 'error', + message: 'Resume session ID unavailable. Start a new session in this directory, or retry after the agent has initialized.', + code: 'resume_unavailable' + }) + }) + + const response = await app.request('/api/sessions/session-1/resume', { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({}) + }) + + expect(response.status).toBe(409) + expect(await response.json()).toEqual({ + error: 'Resume session ID unavailable. Start a new session in this directory, or retry after the agent has initialized.', + code: 'resume_unavailable' + }) + }) + it('falls back to metadata slash commands when RPC listing fails', async () => { const session = createSession({ metadata: { diff --git a/hub/src/web/routes/sessions.ts b/hub/src/web/routes/sessions.ts index 34c85473..a99a496c 100644 --- a/hub/src/web/routes/sessions.ts +++ b/hub/src/web/routes/sessions.ts @@ -140,7 +140,8 @@ export function createSessionsRoutes(getSyncEngine: () => SyncEngine | null): Ho const status = result.code === 'no_machine_online' ? 503 : result.code === 'access_denied' ? 403 : result.code === 'session_not_found' ? 404 - : 500 + : result.code === 'resume_unavailable' ? 409 + : 500 return c.json({ error: result.message, code: result.code }, status) }