-
Notifications
You must be signed in to change notification settings - Fork 9
fix(app-builder): repair migrations and stale sessions #5784
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -101,8 +101,10 @@ export function createProjectManager(config: ProjectManagerConfig): ProjectManag | |
| } | ||
|
|
||
| function getActiveSession(): AppBuilderSession | undefined { | ||
| const sessions = store.getState().sessions; | ||
| return sessions[sessions.length - 1]; | ||
| if (!cloudAgentSessionId) return undefined; | ||
| return store | ||
| .getState() | ||
| .sessions.find(session => session.info.cloud_agent_session_id === cloudAgentSessionId); | ||
| } | ||
|
|
||
| function subscribeToSession(session: AppBuilderSession): void { | ||
|
|
@@ -123,12 +125,16 @@ export function createProjectManager(config: ProjectManagerConfig): ProjectManag | |
| const sessionInfos = proj.sessions; | ||
| if (sessionInfos.length === 0) return []; | ||
|
|
||
| const activeInfo = | ||
| sessionInfos.find(s => s.ended_at === null) ?? sessionInfos[sessionInfos.length - 1]; | ||
| const activeInfo = project.session_id | ||
| ? sessionInfos.find(s => s.cloud_agent_session_id === project.session_id) | ||
| : undefined; | ||
|
|
||
| const orderedSessionInfos = activeInfo | ||
| ? [...sessionInfos.filter(info => info.id !== activeInfo.id), activeInfo] | ||
| : sessionInfos; | ||
| const sessions: AppBuilderSession[] = []; | ||
|
|
||
| for (const info of sessionInfos) { | ||
| for (const info of orderedSessionInfos) { | ||
| const isActive = info.id === activeInfo?.id; | ||
|
|
||
| if (!isActive) { | ||
|
|
@@ -199,6 +205,7 @@ export function createProjectManager(config: ProjectManagerConfig): ProjectManag | |
| store.setState({ | ||
| sessions: [...currentSessions, newSession], | ||
| isStreaming: true, | ||
| isRecoveringSession: false, | ||
| }); | ||
| cloudAgentSessionId = newSessionId; | ||
|
|
||
|
|
@@ -259,15 +266,22 @@ export function createProjectManager(config: ProjectManagerConfig): ProjectManag | |
|
|
||
| // Determine if the active session needs initial streaming from the backend session info. | ||
| // `initiated` lives on ProjectSessionInfo (routing data), not on SessionDisplayInfo. | ||
| const activeProjectSessionInfo = | ||
| project.sessions.find(s => s.ended_at === null) ?? | ||
| project.sessions[project.sessions.length - 1]; | ||
| const activeProjectSessionInfo = project.session_id | ||
| ? project.sessions.find(s => s.cloud_agent_session_id === project.session_id) | ||
| : undefined; | ||
|
|
||
| if (activeProjectSessionInfo?.initiated === false) { | ||
| if (activeProjectSessionInfo?.prepared === true && activeProjectSessionInfo.initiated === false) { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This deliberately distinguishes a prepared-but-not-started session from a missing session. Only the former can be started in place; missing sessions must use the replacement-session flow. |
||
| pendingInitialStreamingStart = true; | ||
| } else if (cloudAgentSessionId) { | ||
| } else if ( | ||
| cloudAgentSessionId && | ||
| activeProjectSessionInfo && | ||
| activeProjectSessionInfo.prepared !== false | ||
| ) { | ||
| pendingReconnect = true; | ||
| } else { | ||
| if (cloudAgentSessionId) { | ||
| store.setState({ pendingNewSession: true, isRecoveringSession: true }); | ||
| } | ||
| startPreviewPollingIfNeeded(); | ||
| } | ||
|
|
||
|
|
@@ -339,6 +353,7 @@ export function createProjectManager(config: ProjectManagerConfig): ProjectManag | |
| } | ||
|
|
||
| const effectiveModel = model ?? store.getState().model; | ||
| const isRecoveringSession = store.getState().isRecoveringSession; | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Capture the mode before clearing |
||
|
|
||
| store.setState({ pendingNewSession: false, isStreaming: true }); | ||
|
|
||
|
|
@@ -370,7 +385,11 @@ export function createProjectManager(config: ProjectManagerConfig): ProjectManag | |
| .catch((err: Error) => { | ||
| if (destroyed) return; | ||
| logger.logError('Failed to start new session', err); | ||
| store.setState({ isStreaming: false }); | ||
| store.setState({ | ||
| pendingNewSession: true, | ||
| isRecoveringSession, | ||
| isStreaming: false, | ||
| }); | ||
| }); | ||
| } | ||
|
|
||
|
|
@@ -423,11 +442,12 @@ export function createProjectManager(config: ProjectManagerConfig): ProjectManag | |
| if (currentActive) { | ||
| currentActive.info.ended_at = new Date().toISOString(); | ||
| } | ||
| store.setState({ pendingNewSession: true }); | ||
| store.setState({ pendingNewSession: true, isRecoveringSession: false }); | ||
| } | ||
|
|
||
| function cancelNewSession(): void { | ||
| if (destroyed) return; | ||
| if (store.getState().isRecoveringSession) return; | ||
| const currentActive = getActiveSession(); | ||
| if (currentActive) { | ||
| currentActive.info.ended_at = null; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
project.session_idis authoritative, but existing chat and feedback consumers treat the final array entry as active. Moving the canonical session to the end keeps those consumers aligned while older/orphaned sessions remain available as history.