Skip to content

Commit a392121

Browse files
committed
fix(desktop): preserve legacy inbox timestamp precision
1 parent 5c8ef1a commit a392121

2 files changed

Lines changed: 40 additions & 38 deletions

File tree

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
import { createDeferred } from '@sim/testing/helpers/deferred'
3232
import { featureFlagsMock, featureFlagsMockFns } from '@sim/testing/mocks/feature-flags.mock'
3333
import { generateId } from '@sim/utils/id'
34+
import { compareStrings } from '@sim/utils/string'
3435
import { eq, inArray, sql } from 'drizzle-orm'
3536
import { closeRedisConnection, getRedisClient } from '@/lib/core/config/redis'
3637
import type { DbTransaction } from '@/lib/db/types'
@@ -887,7 +888,7 @@ describe.runIf(Boolean(redisUrl))('desktop background executor protocol', () =>
887888
expect((await inbox(desktop)).items.map((item) => item.toolCallId)).toEqual(persisted)
888889
})
889890

890-
it('lists pending calls in persistence order even when their doorbell was never heard', async () => {
891+
it('preserves legacy sub-millisecond order even when the doorbell was never heard', async () => {
891892
const desktop = await signedInDesktop()
892893
const run = await boundRun(desktop)
893894
const first = await pendingCall(run.runId, 'browser_navigate', { url: 'https://sim.ai' })
@@ -896,13 +897,24 @@ describe.runIf(Boolean(redisUrl))('desktop background executor protocol', () =>
896897
path: 'user-local/Project--mount-1',
897898
pattern: 'TODO',
898899
})
900+
const persisted = [first, second, vfs].sort(compareStrings).reverse()
901+
const createdAt = new Date(Date.now() - 1000).toISOString()
902+
for (const [index, toolCallId] of persisted.entries()) {
903+
await db
904+
.update(copilotAsyncToolCalls)
905+
.set({
906+
persistSeq: null,
907+
createdAt: sql`${createdAt}::timestamp + ${index} * interval '100 microseconds'`,
908+
})
909+
.where(eq(copilotAsyncToolCalls.toolCallId, toolCallId))
910+
}
899911
await pendingCall(run.runId, 'run_workflow', {})
900912
/** Rung while no stream was open: nobody heard it. */
901913
ringDesktopInbox(desktop.deviceId, 'call')
902914

903915
expect(
904916
(await inbox(desktop)).items.map((item) => item.kind === 'call' && item.toolCallId)
905-
).toEqual([first, second, vfs])
917+
).toEqual(persisted)
906918
})
907919

908920
it('counts the device online after a pull or a stream open, and keeps it online when the stream closes', async () => {

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

Lines changed: 26 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -89,22 +89,12 @@ const INBOX_ROW_LIMIT = 500
8989
* can share a millisecond, so the timestamp alone cannot order them. Rows persisted before the
9090
* sequence existed have none and come first, as they are the oldest.
9191
*/
92-
const persistOrder = [
93-
sql`${copilotAsyncToolCalls.persistSeq} ASC NULLS FIRST`,
94-
asc(copilotAsyncToolCalls.createdAt),
95-
asc(copilotAsyncToolCalls.toolCallId),
96-
]
97-
98-
function comparePersistOrder(
99-
a: { persistSeq: number | null; createdAt: Date; toolCallId: string },
100-
b: { persistSeq: number | null; createdAt: Date; toolCallId: string }
101-
): number {
102-
if (a.persistSeq !== b.persistSeq) {
103-
if (a.persistSeq === null) return -1
104-
if (b.persistSeq === null) return 1
105-
return a.persistSeq - b.persistSeq
106-
}
107-
return a.createdAt.getTime() - b.createdAt.getTime() || a.toolCallId.localeCompare(b.toolCallId)
92+
function persistOrder() {
93+
return [
94+
sql`${copilotAsyncToolCalls.persistSeq} ASC NULLS FIRST`,
95+
asc(copilotAsyncToolCalls.createdAt),
96+
asc(copilotAsyncToolCalls.toolCallId),
97+
]
10898
}
10999

110100
export interface DesktopDeviceRegistration {
@@ -232,27 +222,27 @@ export async function listDesktopInboxRows(identity: Omit<DesktopDeviceIdentity,
232222
state
233223
)
234224
)
235-
.orderBy(...persistOrder)
225+
.orderBy(...persistOrder())
236226
.limit(INBOX_ROW_LIMIT)
237-
const [waiting, cancelled] = await Promise.all([
238-
rowsWhere(
239-
and(
240-
eq(copilotAsyncToolCalls.status, ASYNC_TOOL_STATUS.pending),
241-
isNull(copilotAsyncToolCalls.executionOwnerToken),
242-
or(sql`${copilotAsyncToolCalls.pickupDeadlineAt} > clock_timestamp()`, awaitingPermission),
243-
inArray(copilotRuns.status, LIVE_RUN_STATUSES),
244-
isNull(copilotRuns.toolAdmissionClosedAt)
245-
)
246-
),
247-
rowsWhere(
248-
and(
249-
isNotNull(copilotAsyncToolCalls.executionOwnerToken),
250-
isNull(copilotAsyncToolCalls.executionSettledAt),
251-
ne(copilotAsyncToolCalls.status, ASYNC_TOOL_STATUS.running)
227+
return rowsWhere(
228+
and(
229+
eq(copilotAsyncToolCalls.status, ASYNC_TOOL_STATUS.pending),
230+
isNull(copilotAsyncToolCalls.executionOwnerToken),
231+
or(sql`${copilotAsyncToolCalls.pickupDeadlineAt} > clock_timestamp()`, awaitingPermission),
232+
inArray(copilotRuns.status, LIVE_RUN_STATUSES),
233+
isNull(copilotRuns.toolAdmissionClosedAt)
234+
)
235+
)
236+
.unionAll(
237+
rowsWhere(
238+
and(
239+
isNotNull(copilotAsyncToolCalls.executionOwnerToken),
240+
isNull(copilotAsyncToolCalls.executionSettledAt),
241+
ne(copilotAsyncToolCalls.status, ASYNC_TOOL_STATUS.running)
242+
)
252243
)
253-
),
254-
])
255-
return [...waiting, ...cancelled].sort(comparePersistOrder)
244+
)
245+
.orderBy(...persistOrder())
256246
}
257247

258248
export type DesktopInboxRow = Awaited<ReturnType<typeof listDesktopInboxRows>>[number]
@@ -448,7 +438,7 @@ export async function listOverdueDesktopToolCalls(input: { slackMs: number; limi
448438
)
449439
)
450440
)
451-
.orderBy(...persistOrder)
441+
.orderBy(...persistOrder())
452442
.limit(input.limit)
453443
return rows.map((row) => row.toolCallId)
454444
}

0 commit comments

Comments
 (0)