(sessionId: string, namespace: string, opts?: { permissionMode?: PermissionMode })
| 1061 | } |
| 1062 | |
| 1063 | async resumeSession(sessionId: string, namespace: string, opts?: { permissionMode?: PermissionMode }): Promise<ResumeSessionResult> { |
| 1064 | const access = this.sessionCache.resolveSessionAccess(sessionId, namespace) |
| 1065 | if (!access.ok) { |
| 1066 | return { |
| 1067 | type: 'error', |
| 1068 | message: access.reason === 'access-denied' ? 'Session access denied' : 'Session not found', |
| 1069 | code: access.reason === 'access-denied' ? 'access_denied' : 'session_not_found' |
| 1070 | } |
| 1071 | } |
| 1072 | |
| 1073 | const initialSession = access.session |
| 1074 | if (initialSession.active) { |
| 1075 | return { type: 'success', sessionId: access.sessionId } |
| 1076 | } |
| 1077 | |
| 1078 | // tiann/hapi#824 — invisible, automatic, per-session ACP migration on |
| 1079 | // first open. If this is a legacy stream-json Cursor session and we |
| 1080 | // can safely migrate it right now (no other agent acp transport |
| 1081 | // would block the post-migration ACP launcher), run the transplant |
| 1082 | // synchronously before resuming. The user sees the regular session |
| 1083 | // loading state for ~3–5s longer; the session opens as ACP. |
| 1084 | const session = await this.maybeAutoMigrateLegacyCursorSession(initialSession, namespace) |
| 1085 | |
| 1086 | const targetResult = this.resolveLocalResumeTarget(access.sessionId, namespace) |
| 1087 | let flavor: AgentFlavor |
| 1088 | let resumeToken: string | undefined |
| 1089 | let directory: string |
| 1090 | |
| 1091 | if (targetResult.type === 'success') { |
| 1092 | flavor = targetResult.target.flavor |
| 1093 | resumeToken = targetResult.target.agentSessionId |
| 1094 | directory = targetResult.target.directory |
| 1095 | } else if ( |
| 1096 | targetResult.code === 'resume_unavailable' |
| 1097 | && this.canFreshSpawnNeverStartedSession(session, access.sessionId, namespace) |
| 1098 | ) { |
| 1099 | const metadata = session.metadata! |
| 1100 | flavor = this.resolveFlavor(session) |
| 1101 | resumeToken = undefined |
| 1102 | directory = metadata.path |
| 1103 | } else { |
| 1104 | return targetResult |
| 1105 | } |
| 1106 | |
| 1107 | const metadata = session.metadata! |
| 1108 | |
| 1109 | const onlineMachines = this.machineCache.getOnlineMachinesByNamespace(namespace) |
| 1110 | if (onlineMachines.length === 0) { |
| 1111 | return { type: 'error', message: 'No machine online', code: 'no_machine_online' } |
| 1112 | } |
| 1113 | |
| 1114 | const targetMachine = (() => { |
| 1115 | if (metadata.machineId) { |
| 1116 | const exact = onlineMachines.find((machine) => machine.id === metadata.machineId) |
| 1117 | if (exact) return exact |
| 1118 | } |
| 1119 | if (metadata.host) { |
| 1120 | const hostMatch = onlineMachines.find((machine) => machine.metadata?.host === metadata.host) |
no test coverage detected