mirror of
https://github.com/wu736139669/hapi.git
synced 2026-10-30 23:27:57 +00:00
fix(hub+cli): defer mergeSessions on cursor ACP reopen until session/load succeeds (closes #939) (#948)
* fix(hub+cli): defer mergeSessions on cursor ACP reopen until session/load succeeds Emit session-ready from the CLI after ACP load/newSession completes; hub resumeSession and cursor dedup wait for that signal before merging rows so a failed session/load no longer deletes the archived session the operator can retry. Refs #917. Closes #939. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(hub): gate session-ready wait on cursor ACP protocol only Legacy stream-json Cursor resumes use cursorLegacyRemoteLauncher, which does not emit session-ready; limiting the defer-merge and dedup gates to ACP avoids 60s resume_failed timeouts on those sessions. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(hub): block ACP dedup until session-ready, including on session-end Inactive ACP spawns that never emitted session-ready could still trigger deduplicateByAgentSessionId on session-end and delete the original row. Require session-ready for all ACP dedup paths and skip end-of-session dedup when load never succeeded. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(hub): restore session-end dedup for non-ACP cursor duplicates Only skip the session-end dedup retry for Cursor ACP rows that never emitted session-ready. Codex/Claude/legacy Cursor duplicates still merge when the live row ends. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -105,7 +105,7 @@ class CursorAcpRemoteLauncher extends RemoteLauncherBase {
|
||||
mcpServers: mcpServerList
|
||||
});
|
||||
} catch (error) {
|
||||
logger.warn('[cursor-acp] session/load failed', error);
|
||||
logger.warn('[cursor-acp] session/load failed', formatAcpLoadError(error));
|
||||
throw new Error(
|
||||
'Failed to resume Cursor ACP session. Legacy stream-json sessions cannot be loaded via ACP.'
|
||||
);
|
||||
@@ -125,6 +125,8 @@ class CursorAcpRemoteLauncher extends RemoteLauncherBase {
|
||||
session.onSessionFoundWithProtocol(acpSessionId, 'acp');
|
||||
}
|
||||
|
||||
session.client.emitSessionReady();
|
||||
|
||||
syncCursorModelsFromAcp(backend, acpSessionId);
|
||||
|
||||
const initialMetadata = backend.getSessionModelsMetadata(acpSessionId);
|
||||
@@ -436,6 +438,34 @@ class CursorAcpRemoteLauncher extends RemoteLauncherBase {
|
||||
}
|
||||
}
|
||||
|
||||
function formatAcpLoadError(error: unknown): Record<string, unknown> {
|
||||
if (error instanceof Error) {
|
||||
const record: Record<string, unknown> = {
|
||||
name: error.name,
|
||||
message: error.message
|
||||
};
|
||||
const code = (error as Error & { code?: unknown }).code;
|
||||
if (code !== undefined) {
|
||||
record.code = code;
|
||||
}
|
||||
const data = (error as Error & { data?: unknown }).data;
|
||||
if (data !== undefined) {
|
||||
record.data = data;
|
||||
}
|
||||
const cause = error.cause;
|
||||
if (cause !== undefined) {
|
||||
record.cause = cause instanceof Error
|
||||
? { name: cause.name, message: cause.message }
|
||||
: cause;
|
||||
}
|
||||
return record;
|
||||
}
|
||||
if (typeof error === 'object' && error !== null) {
|
||||
return { ...(error as Record<string, unknown>) };
|
||||
}
|
||||
return { message: String(error) };
|
||||
}
|
||||
|
||||
function isSpawnDefaultModel(modelId: string): boolean {
|
||||
const normalized = modelId.trim().toLowerCase();
|
||||
return normalized === 'auto' || normalized === 'default' || normalized === 'default[]';
|
||||
|
||||
Reference in New Issue
Block a user