Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions wework/e2e/desktop/modules/goal-flows.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,45 @@ async function verifyActiveGoalIdleUnreadLifecycle({ composerSelector, control,
true,
'Reloading exposed a direct send path while the provider turn was still active'
)
assert.ok(
reloadedContinuationDebugSnapshot.workbench?.currentRuntimeTask,
'The reloaded Goal did not expose its runtime address for stale transcript recovery'
)
await control.command('dispatchRuntimeLifecycleEvent', 'body', {
value: JSON.stringify({
address: reloadedContinuationDebugSnapshot.workbench.currentRuntimeTask,
type: 'transcript_received',
transcript: {
taskId: goalTaskId,
messages: [],
running: false,
turns: [{ id: 'stale-running-state-turn', items: [], status: 'streaming' }],
},
}),
})
await waitForSnapshot(
control,
snapshot =>
snapshot.testIds.includes(goalTaskRowTestId) &&
snapshot.testIds.includes(goalRunningTestId) &&
snapshot.testIds.includes('pause-response-button') &&
!snapshot.testIds.includes('send-message-button') &&
!snapshot.testIds.includes(goalUnreadTestId),
'A stale coarse transcript running flag hid the active turn from the sidebar'
)
const staleTranscriptDebugSnapshot = await waitForWorkbenchDebugState(
control,
snapshot =>
snapshot.workbench?.lifecycleCurrentTaskRunning === true &&
snapshot.pane?.status?.isAssistantStreaming === true &&
snapshot.pane?.status?.isBusy === true,
'The active turn did not remain authoritative after receiving stale transcript running state'
)
assert.equal(
staleTranscriptDebugSnapshot.pane?.status?.taskExecution?.running,
true,
'The stale transcript running flag overrode the concrete active turn'
)
await assertConversationTextOccurrences(control, {
[GOAL_IDLE_INITIAL_TEXT]: 1,
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useEffect, type ReactNode } from 'react'
import type { RuntimeTaskAddress } from '@/types/api'
import type { RuntimePaneTranscript } from '@/types/workbench'
import { RuntimeTaskLifecycleContext } from './internalContext'
import type { RuntimeTaskLifecycleStore } from './RuntimeTaskLifecycleStore'

Expand All @@ -21,10 +22,13 @@ export function RuntimeTaskLifecycleProvider({
address: RuntimeTaskAddress
type: string
turnId?: string | null
transcript?: RuntimePaneTranscript
}>
).detail
if (detail?.type === 'turn_settled') {
store.turnSettled(detail.address, detail.turnId)
} else if (detail?.type === 'transcript_received' && detail.transcript) {
store.syncTranscript(detail.address, detail.transcript)
}
}
window.addEventListener(E2E_RUNTIME_LIFECYCLE_EVENT, handleLifecycleEvent)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,26 @@ describe('RuntimeTaskLifecycleStore', () => {
expect(snapshot?.derived.isTurnActive).toBe(true)
})

test('recovers a streaming turn after restart when coarse transcript running state is stale', () => {
const store = new RuntimeTaskLifecycleStore('test')

store.syncTranscript(
address,
transcript({
running: false,
turns: [{ id: 'restored-turn', items: [], status: 'streaming' }],
})
)

const snapshot = store.getTask(address)
expect(snapshot?.execution.phase).toBe('running')
expect(snapshot?.turn.phase).toBe('streaming')
expect(snapshot?.derived.shouldShowSidebarRunning).toBe(true)
expect(store.getSnapshot().runningTaskKeys).toEqual(
new Set([getRuntimeTaskLifecycleKey(address)])
)
})

test('treats an explicit executor snapshot as authoritative over a live turn', () => {
const store = new RuntimeTaskLifecycleStore('test')

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,19 +154,17 @@ export class RuntimeTaskLifecycleStore {
options.preserveActiveTurn === true &&
(this.getTask(address)?.derived.isRunning ?? false)

if (transcript.running === true) {
if (hasStreamingTurn) {
this.executorStarted(address)
} else if (transcript.running === false && !ignoreStaleIdleTranscript) {
this.executorSettled(address)
}

if (hasStreamingTurn && transcript.running !== false) {
this.dispatch(address, {
type: 'turn_recovered',
streaming: true,
turnId: streamingTurn?.id,
})
} else if (transcript.running === true) {
this.executorStarted(address)
} else if (transcript.running === false && !ignoreStaleIdleTranscript) {
this.executorSettled(address)
this.turnSettled(address)
}
}
Expand Down
Loading