Skip to content

Commit 473c5c1

Browse files
committed
fix(desktop): announce a background completion only for the very turn the desktop ran
Desktop activity now names each bound turn's stream, and a completion is announced only when that turn was seen running on the user's own desktop. An earlier desktop turn no longer makes a later chat-view turn in the same chat look eligible.
1 parent f255aa5 commit 473c5c1

7 files changed

Lines changed: 63 additions & 10 deletions

File tree

‎apps/sim/hooks/use-mothership-chat-events.test.ts‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ describe('reflectBackgroundChatStatus', () => {
303303
{ id: 'chat-b', name: 'Fix CI' },
304304
])
305305
queryClient.setQueryData(desktopActivityKeys.list('ws-1'), [
306-
{ chatId: 'chat-b', state: 'running', deviceName: 'Studio Mac' },
306+
{ chatId: 'chat-b', streamId: 's-1', state: 'running', deviceName: 'Studio Mac' },
307307
])
308308
}
309309

@@ -394,4 +394,17 @@ describe('reflectBackgroundChatStatus', () => {
394394

395395
expect(shown).toEqual([])
396396
})
397+
398+
it('stays silent for a later turn the chat view ran in a chat a desktop ran before', () => {
399+
showing('/workspace/ws-1/chat/chat-c')
400+
401+
reflectBackgroundChatStatus(
402+
queryClient,
403+
'ws-1',
404+
JSON.stringify({ chatId: 'chat-b', type: 'completed', streamId: 's-later' }),
405+
true
406+
)
407+
408+
expect(shown).toEqual([])
409+
})
397410
})

‎apps/sim/hooks/use-mothership-chat-events.ts‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,13 +204,17 @@ export function reflectBackgroundChatStatus(
204204
const payload = parseChatStatusEventPayload(data)
205205
if (payload?.type !== 'started' && payload?.type !== 'completed') return
206206
// Read before the refresh below drops it. The events carry every member's chats in the
207-
// workspace; only one this user's own desktop was running is theirs to be told about.
207+
// workspace; only this very turn, seen running on this user's own desktop, is theirs to be told
208+
// about. Matching the turn, not just the chat, keeps an earlier desktop turn from vouching for a
209+
// later one the chat view ran.
208210
const ranOnDesktop =
209211
typeof owner === 'string' &&
210212
Boolean(
211213
queryClient
212214
.getQueryData<DesktopChatActivity[]>(desktopActivityKeys.list(owner))
213-
?.some((activity) => activity.chatId === payload.chatId)
215+
?.some(
216+
(activity) => activity.chatId === payload.chatId && activity.streamId === payload.streamId
217+
)
214218
)
215219
queryClient.invalidateQueries({ queryKey: desktopActivityKeys.lists() })
216220
if (!announceCompletions || payload.type !== 'completed' || !payload.chatId) return

‎apps/sim/lib/api/contracts/desktop-executor.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,8 @@ const desktopActivityQuerySchema = z.object({ workspaceId: workspaceIdSchema })
192192
*/
193193
const desktopChatActivitySchema = z.object({
194194
chatId: z.string().min(1),
195+
/** The turn the desktop runs, as chat status events name it. */
196+
streamId: z.string().min(1),
195197
state: z.enum(['running', 'needs_input', 'blocked']),
196198
deviceName: z.string(),
197199
})

‎apps/sim/lib/desktop/application/activity.integration.ts‎

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,18 @@ describe.runIf(Boolean(redisUrl))('background desktop activity', () => {
153153
).chats
154154
).toEqual(
155155
expect.arrayContaining([
156-
{ chatId: running.chatId, state: 'blocked', deviceName: 'Studio Mac' },
157-
{ chatId: waiting.chatId, state: 'needs_input', deviceName: 'Studio Mac' },
156+
{
157+
chatId: running.chatId,
158+
streamId: expect.any(String),
159+
state: 'blocked',
160+
deviceName: 'Studio Mac',
161+
},
162+
{
163+
chatId: waiting.chatId,
164+
streamId: expect.any(String),
165+
state: 'needs_input',
166+
deviceName: 'Studio Mac',
167+
},
158168
])
159169
)
160170

@@ -172,8 +182,18 @@ describe.runIf(Boolean(redisUrl))('background desktop activity', () => {
172182
expect(chats).toHaveLength(2)
173183
expect(chats).toEqual(
174184
expect.arrayContaining([
175-
{ chatId: running.chatId, state: 'running', deviceName: 'Studio Mac' },
176-
{ chatId: waiting.chatId, state: 'needs_input', deviceName: 'Studio Mac' },
185+
{
186+
chatId: running.chatId,
187+
streamId: expect.any(String),
188+
state: 'running',
189+
deviceName: 'Studio Mac',
190+
},
191+
{
192+
chatId: waiting.chatId,
193+
streamId: expect.any(String),
194+
state: 'needs_input',
195+
deviceName: 'Studio Mac',
196+
},
177197
])
178198
)
179199
} finally {

‎apps/sim/lib/desktop/application/activity.test.ts‎

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,13 @@ const principal = createSessionPrincipal({ userId: 'user-1', sessionId: 'session
1919
describe('desktop activity presence', () => {
2020
beforeEach(() => {
2121
rows.mockResolvedValue([
22-
{ chatId: 'chat-1', deviceId: 'device-1', deviceName: 'Studio Mac', needsInput: false },
22+
{
23+
chatId: 'chat-1',
24+
streamId: 's-1',
25+
deviceId: 'device-1',
26+
deviceName: 'Studio Mac',
27+
needsInput: false,
28+
},
2329
])
2430
})
2531

@@ -32,7 +38,9 @@ describe('desktop activity presence', () => {
3238
input: { workspaceId: 'ws-1' },
3339
})
3440

35-
expect(chats).toEqual([{ chatId: 'chat-1', state: 'running', deviceName: 'Studio Mac' }])
41+
expect(chats).toEqual([
42+
{ chatId: 'chat-1', streamId: 's-1', state: 'running', deviceName: 'Studio Mac' },
43+
])
3644
})
3745

3846
it('calls a desktop blocked when presence is tracked and it is gone', async () => {
@@ -44,6 +52,8 @@ describe('desktop activity presence', () => {
4452
input: { workspaceId: 'ws-1' },
4553
})
4654

47-
expect(chats).toEqual([{ chatId: 'chat-1', state: 'blocked', deviceName: 'Studio Mac' }])
55+
expect(chats).toEqual([
56+
{ chatId: 'chat-1', streamId: 's-1', state: 'blocked', deviceName: 'Studio Mac' },
57+
])
4858
})
4959
})

‎apps/sim/lib/desktop/application/activity.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ type DesktopChatActivityState = 'running' | 'needs_input' | 'blocked'
1818

1919
export interface DesktopChatActivityEntry {
2020
chatId: string
21+
/** The turn the desktop runs, as chat status events name it. */
22+
streamId: string
2123
state: DesktopChatActivityState
2224
deviceName: string
2325
}
@@ -66,6 +68,7 @@ export const listDesktopActivity = defineAuthorizedCredentialUserUseCase({
6668
if (chats.has(run.chatId)) continue
6769
chats.set(run.chatId, {
6870
chatId: run.chatId,
71+
streamId: run.streamId,
6972
deviceName: run.deviceName,
7073
state: run.needsInput ? 'needs_input' : online.get(run.deviceId) ? 'running' : 'blocked',
7174
})

‎apps/sim/lib/desktop/executor/repository.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,7 @@ export async function listDesktopActivityRows(input: { userId: string; workspace
464464
.select({
465465
runId: copilotRuns.id,
466466
chatId: copilotRuns.chatId,
467+
streamId: copilotRuns.streamId,
467468
deviceId: desktopDevices.id,
468469
deviceName: desktopDevices.name,
469470
})

0 commit comments

Comments
 (0)