Skip to content

Commit c3475fe

Browse files
authored
fix(mothership): decide once whether a queued send may already be on the server (#8727)
* fix(mothership): guard a Send-now restored from its stored handoff against edits A Send-now whose Stop settled has its POST out under the id its stored handoff carries. Restored after a reload it had no resumeUserMessageId, so the queue guard missed it, and an edit could run as a second turn. The guard now treats a settled handoff's id as an earlier attempt. A direct send can't reach the never-sent branch (a pending Stop queues it), so that restore now treats anything but a refusal as unknown. Docs: the 1-hour claim TTL, the claim failing closed, and the superseded admission conflict classed as busy. * fix(mothership): answer a send superseded at admission as a duplicate Admission's superseded conflict (another attempt with this id re-took the claim) went out as a bare 409, which the client reads as a busy refusal and marks the message editable. The claim's 60s in-progress TTL can run out before admission, since branch, attachment and context preparation precede it, so that attempt may admit the turn. The route now answers it as a duplicate naming the send's id, and the client keeps the message under it. * fix(mothership): decide once whether a send's id may already be on the server Whether a queued message may already be a turn on the server was inferred in several places (the store guard, a stopRequired exemption, the handoff restore, notAdmitted/neverSent), and the stopRequired exemption was wrong: a Send-now reuses a re-queued message's earlier id, which may have been sent. startSendMessage now decides it where it chooses the id (a reused id carries its entry's flag, a fresh one is unsent until its POST, a refusal clears it) and carries that one fact on the withdrawal result, the stored handoff and the queue entry. The store guard reads only the flag, filling it in only for writers with no say. * fix(mothership): align the one admission fact with the history check after rebase - one helper names the id a queued entry reuses (its Stop handoff's, else the withdrawn send's), in the order startSendMessage sends it - a conflict naming the send's own id is never read as a refusal - the stored handoff records the flag as it stands, rewritten as the POST goes out, instead of inferring it from stopRequired - the Stop-settlement test's handoff carries the flag the hook writes; a flagless legacy handoff is checked against history before it is resent * test(mothership): cover the pre-POST handoff write and the own-id conflict - a fresh Send-now reloaded with its POST unanswered restores uneditable (red without the handoff rewrite as the POST goes out) - a conflict naming the resent id itself is a duplicate, never a refusal (red without the conflictStreamId !== userMessageId check)
1 parent 055ff9c commit c3475fe

10 files changed

Lines changed: 559 additions & 49 deletions

File tree

‎apps/sim/app/workspace/[workspaceId]/home/hooks/send-handoff.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ export interface QueuedSendHandoffState extends QueuedSendHandoffSeed {
3939
assistantSearchLevel?: AssistantSearchLevel
4040
requestedAt: number
4141
resolveAttempts?: number
42+
/** Whether the server may already hold `userMessageId` (see `startSendMessage`). */
43+
admissionUnknown?: boolean
4244
}
4345

4446
interface QueuedSendHandoffClaim {
@@ -191,6 +193,9 @@ export function readQueuedSendHandoffState(): QueuedSendHandoffState | null {
191193
organizationId: parsed.organizationId,
192194
supersededStreamId,
193195
...(parsed.stopRequired === true ? { stopRequired: true } : {}),
196+
...(typeof parsed.admissionUnknown === 'boolean'
197+
? { admissionUnknown: parsed.admissionUnknown }
198+
: {}),
194199
userMessageId: parsed.userMessageId,
195200
message: parsed.message,
196201
...(Array.isArray(parsed.fileAttachments)

0 commit comments

Comments
 (0)