Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
5f44ff7
fix(native-chat): drop the queue-paused header and Resume button
brennanb2025 Oct 2, 2026
dc181bc
test(native-chat): type the unknown hold reason a newer host may publish
brennanb2025 Oct 2, 2026
289a345
fix(native-chat): a held card offers Send, not Steer, when no turn runs
brennanb2025 Oct 2, 2026
faaefcc
fix(native-chat): the queue sends past held cards instead of stalling…
brennanb2025 Oct 2, 2026
f91c1ae
fix(native-chat): the queue's send of a card is the person's turn, so…
brennanb2025 Oct 2, 2026
7a28cd5
Merge remote-tracking branch 'origin/main' into brennanb2025/chat-que…
brennanb2025 Oct 4, 2026
4ef532e
fix(native-chat): a queued card carries its author, so the queue's se…
brennanb2025 Oct 4, 2026
0809787
docs(native-chat): state why an unrecorded card author reads as a per…
brennanb2025 Oct 4, 2026
ccbe202
fix(native-chat): a restart holds only cards written before it, and a…
brennanb2025 Oct 4, 2026
bf82077
fix(native-chat): the host publishes which pause holds each queued card
brennanb2025 Oct 4, 2026
71d7c62
test(native-chat): Resume needs the queue capability and is disabled …
brennanb2025 Oct 4, 2026
4c3a31d
docs(native-chat): describe per-card holds in the queue contract and …
brennanb2025 Oct 4, 2026
34d5ba9
fix(native-chat): the composer goes from Resume straight to Stop, and…
brennanb2025 Oct 4, 2026
d6b5c5e
fix(native-chat): the host names the card its queue sends next, so th…
brennanb2025 Oct 4, 2026
5cf6eba
test(native-chat): the queue's next card survives the coalescer, the …
brennanb2025 Oct 4, 2026
31995e4
test(native-chat): build the snapshot that names the next card throug…
brennanb2025 Oct 4, 2026
a78ff91
feat(native-chat): a held queue keeps its header row, and a new messa…
brennanb2025 Oct 4, 2026
8ef6ea4
fix(native-chat): the paused row goes while your own message is on it…
brennanb2025 Oct 4, 2026
dfe1738
fix(native-chat): a "Send message?" choice is taken once, and a faile…
brennanb2025 Oct 4, 2026
9f8f45a
Merge remote-tracking branch 'origin/main' into brennanb2025/chat-que…
brennanb2025 Oct 4, 2026
818ec9a
fix(native-chat): Clear queue's message goes out once, and keeps text…
brennanb2025 Oct 4, 2026
14b3522
Merge remote-tracking branch 'origin/main' into brennanb2025/chat-que…
brennanb2025 Oct 5, 2026
af021db
fix(native-chat): the queue carries on after any turn, in order, and …
brennanb2025 Oct 5, 2026
8252a23
fix(native-chat): after a restart no queue pause shows, and a card wr…
brennanb2025 Oct 5, 2026
7a61051
fix(native-chat): a quit hands no queued card off, and the paused row…
brennanb2025 Oct 5, 2026
d1170f3
refactor(native-chat): the host's paused-row-while-a-turn-is-on-its-w…
brennanb2025 Oct 5, 2026
a9191b0
Merge commit 'a3bd6a6a1e7209c8a870724df3557f0813a4573c' into brennanb…
brennanb2025 Oct 5, 2026
7c561ee
refactor(native-chat): the composer's queue Resume rides the structur…
brennanb2025 Oct 5, 2026
4b50771
Merge remote-tracking branch 'origin/main' into brennanb2025/chat-que…
brennanb2025 Oct 6, 2026
439545e
fix(native-chat): the "Send message?" choice ends with the pause it a…
brennanb2025 Oct 6, 2026
ac624ba
Merge commit 'bfd1e9c579e4f442059bf99951af22b28292787a' into brennanb…
brennanb2025 Oct 6, 2026
533c535
Merge origin/main 670c59d23a1 into brennanb2025/chat-queue-continues-…
brennanb2025 Oct 6, 2026
738270b
Merge origin/main 4de9f9f85a8 into brennanb2025/chat-queue-continues-…
brennanb2025 Oct 6, 2026
7cc68c2
Merge origin/main 5b8a982f8f7 into brennanb2025/chat-queue-continues-…
brennanb2025 Oct 6, 2026
64961aa
Merge origin/main 468e4e1167b into brennanb2025/chat-queue-continues-…
brennanb2025 Oct 6, 2026
8342047
fix(native-chat): a message kept after a quit or close waits like eve…
brennanb2025 Oct 6, 2026
18441ae
fix(native-chat): mark the reopen in the one open path, and keep an i…
brennanb2025 Oct 6, 2026
e47faa9
fix(native-chat): review round 2: restore the dropped Resume and fail…
brennanb2025 Oct 6, 2026
954187a
Merge origin/main 83cf7cf5e28 into brennanb2025/chat-queue-continues-…
brennanb2025 Oct 6, 2026
70a37e3
test(mobile): a host-kept card's test stands in a Stop's pause, as th…
brennanb2025 Oct 6, 2026
ae6103c
Merge brennanb2025/chat-queue-continues-after-stop 70a37e35820 (main …
brennanb2025 Oct 6, 2026
4b769c7
refactor(native-chat): settle a restart's leftovers and mark them in …
brennanb2025 Oct 6, 2026
1c38d18
Merge origin/main c893048aebc into brennanb2025/chat-queue-continues-…
brennanb2025 Oct 6, 2026
93c1c5f
Merge origin/main 1eeb8f3f72c into brennanb2025/chat-queue-continues-…
brennanb2025 Oct 6, 2026
2d7187e
Merge origin/main 86d03ff9085 into brennanb2025/chat-queue-continues-…
brennanb2025 Oct 7, 2026
9630e8f
Merge #24586's final head 2d7187efcad into brennanb2025/chat-queue-ke…
brennanb2025 Oct 7, 2026
9da6031
Merge origin/main 0c96550ee9a into brennanb2025/chat-queue-kept-card-…
brennanb2025 Oct 7, 2026
80b9528
Merge origin/main 9fdd90ffa77 into brennanb2025/chat-queue-kept-card-…
brennanb2025 Oct 7, 2026
e0f321d
Merge origin/main 5b1bb78d14b into brennanb2025/chat-queue-kept-card-…
brennanb2025 Oct 7, 2026
cffa07b
test(native-chat): a kept card's Resume and failed-mark tests quit th…
brennanb2025 Oct 7, 2026
16f40bf
Merge origin/main 7c9c3d431a7 into brennanb2025/chat-queue-kept-card-…
brennanb2025 Oct 7, 2026
ca0fdc8
Merge origin/main acea59c6d87 into brennanb2025/chat-queue-kept-card-…
brennanb2025 Oct 7, 2026
efd9f25
Merge origin/main 25e0a297016 into brennanb2025/chat-queue-kept-card-…
brennanb2025 Oct 7, 2026
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
3 changes: 1 addition & 2 deletions mobile/src/session/MobileNativeChatQueuedMessages.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -281,8 +281,7 @@ describe('MobileNativeChatQueuedMessages', () => {

it('heads the box with why the queue is paused, for each reason', async () => {
const rows: readonly [AgentSessionQueuePause['reason'], string][] = [
['stopped', 'Queue paused because you interrupted'],
['cleared', 'Queue paused after you cleared the conversation']
['stopped', 'Queue paused because you interrupted']
]
for (const [reason, label] of rows) {
const mounted = await mountPaused({ pause: { reason } })
Expand Down
2 changes: 1 addition & 1 deletion mobile/src/session/MobileNativeChatQueuedMessages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const RESUME_KEY = '\u0000resume'
export type MobileNativeChatQueuedMessagesProps = {
cards?: MobileQueuedMessageCard[]
/** Steer for a waiting card, the paused queue's included; plain Send for a card held on its own
* (its send failed, or the host kept it unsent), or a returned one. */
* (its send failed), or a returned one. */
onSend?: (messageId: string) => Promise<boolean>
onDelete?: (messageId: string) => Promise<boolean>
/** Copy the card's text into the composer, then delete the card. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@ import { describe, expect, it } from 'vitest'
import { agentSessionFailureFact } from '../../../src/shared/agent-session-failure'
import { agentSessionFailureWords } from '../../../src/shared/agent-session-failure-words'
import { DISPATCH_REJECTED_HOST_RESTARTED } from '../../../src/shared/structured-agent-session-dispatch-rejection'
import {
QUEUED_MESSAGE_PAUSED_KEPT,
QUEUED_MESSAGE_PAUSED_SEND_FAILED
} from '../../../src/shared/agent-session-wire'
import { QUEUED_MESSAGE_PAUSED_SEND_FAILED } from '../../../src/shared/agent-session-wire'
import type { AgentSessionQueuedMessage } from '../../../src/shared/agent-session-wire'
import {
mobileQueueHasResumableCard,
Expand Down Expand Up @@ -103,35 +100,12 @@ describe('mobileQueuedMessageCards', () => {
expect(resumable([failed])).toBe(false)
expect(resumable([failed, waiting])).toBe(true)
expect(resumable([waiting, { ...returned, position: 3 }])).toBe(true)
// A kept card is held on its own, like a failed one: the drain goes past it.
const kept = draft({ messageId: 'k', paused: true, pausedReason: QUEUED_MESSAGE_PAUSED_KEPT })
expect(resumable([kept])).toBe(false)
expect(resumable([kept, waiting])).toBe(true)
})

// The host kept it unsent across a restart or a close; the cards behind it are not held by it.
it('captions a kept card as not sent yet, and leaves the cards behind it plainly queued', () => {
const cards = mobileQueuedMessageCards(
[
draft({ messageId: 'k', paused: true, pausedReason: QUEUED_MESSAGE_PAUSED_KEPT }),
draft({ messageId: 'b', position: 2 })
],
[],
{ pendingPrompt: false }
)
expect(cards.map(({ caption, needsAttention }) => ({ caption, needsAttention }))).toEqual([
{ caption: 'Not sent yet — tap Send to send it', needsAttention: false },
{ caption: null, needsAttention: false }
])
})

it('words the paused queue by reason, and one this build does not know as a plain pause', () => {
expect(mobileQueuePauseLabel({ reason: 'stopped' })).toBe(
'Queue paused because you interrupted'
)
expect(mobileQueuePauseLabel({ reason: 'cleared' })).toBe(
'Queue paused after you cleared the conversation'
)
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: a reason newer than this build's union, as a newer host would send it.
const newer = { reason: 'later_reason' } as never
expect(mobileQueuePauseLabel(newer)).toBe('Queue paused')
Expand Down
10 changes: 2 additions & 8 deletions mobile/src/session/mobile-structured-queued-message-cards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { agentSessionWriteNoticeEnglish } from '../../../src/shared/agent-sessio
import { dispatchWasWithdrawn } from '../../../src/shared/structured-agent-session-dispatch-rejection'
import { structuredAgentSessionAttemptFailureParts } from '../../../src/shared/structured-agent-session-send-disposition'
import {
QUEUED_MESSAGE_PAUSED_KEPT,
QUEUED_MESSAGE_PAUSED_SEND_FAILED,
type AgentSessionQueuedMessage,
type AgentSessionQueuePause
Expand Down Expand Up @@ -53,22 +52,17 @@ function returnedCaption(
)
}

/** One card's own hold: a failed conversion, or a send the host kept; the queue's pause is the
* list's first row. */
/** One card's own hold: a failed conversion; the queue's pause is the list's first row. */
function pausedCaption(reason: string | undefined): string {
if (reason === QUEUED_MESSAGE_PAUSED_SEND_FAILED) {
return "Couldn't send — tap Send to retry"
}
if (reason === QUEUED_MESSAGE_PAUSED_KEPT) {
return 'Not sent yet — tap Send to send it'
}
// Absent or unknown (newer host) marker: a plain pause, promising no release rule.
return 'Paused'
}

const QUEUE_PAUSE_LABELS: Readonly<Record<string, string>> = {
stopped: 'Queue paused because you interrupted',
cleared: 'Queue paused after you cleared the conversation'
stopped: 'Queue paused because you interrupted'
}

/** Whether Resume would send anything: a waiting card with no hold of its own, ahead of any
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -577,21 +577,20 @@ describe('mobile structured queued messages', () => {
listener?.(
batchEvent(
[
queuedDraft({ messageId: 'kept-1', paused: true, pausedReason: 'kept' }),
queuedDraft({ messageId: 'kept-1' }),
queuedDraft({ messageId: 'behind', position: 2 })
],
[],
// This host publishes no restart pause; a Stop's stands in for any queue-wide one.
{ reason: 'stopped' }
null
)
)
)
// Plain waiting cards: the host holds them until the chat's next turn, and shows no row.
expect(hook!.queued.cards.map(({ messageId, caption }) => ({ messageId, caption }))).toEqual([
{ messageId: 'kept-1', caption: 'Not sent yet — tap Send to send it' },
{ messageId: 'kept-1', caption: null },
{ messageId: 'behind', caption: null }
])
// The kept card is held on its own, so Resume would send the card behind it.
expect(hook!.queued.pause).toEqual({ reason: 'stopped' })
expect(hook!.queued.pause).toBeNull()
})
})

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// The queued-draft surface the structured session exposes: cards derived from
// the published list and the Send-now / Delete / Edit actions. Shown whatever
// the queue capability says: a host that does not queue sends still publishes a
// message it kept across a restart or a close, and only queueing a new send is
// gated. A host older than the queue publishes no list, so shows no cards.
// message it kept as a card across a restart or a close, and only queueing a new
// send is gated. A host older than the queue publishes no list, so shows no cards.
// Nothing here is durable: the host owns the queue, and the published list is
// the only truth a card action ever needs.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,34 +10,29 @@ import {
} from '../../../shared/structured-agent-session-dispatch-rejection'
import { rejectedDraftSettlement } from './journal-dispatch-settlement'

function settle(kind: SubmissionRejectionKind, origin?: 'client' | 'host') {
return rejectedDraftSettlement({
...agentSessionFailureWords(agentSessionFailureFact(kind), { surface: 'rejection' }),
origin
})
function settle(kind: SubmissionRejectionKind) {
return rejectedDraftSettlement(
agentSessionFailureWords(agentSessionFailureFact(kind), { surface: 'rejection' })
)
}

describe('what a rejection does to the draft it was consumed from', () => {
it("a Stop's withdrawal sends it back to waiting, under the queue's pause rather than a hold of its own", () => {
expect(settle('cancelled')).toEqual({ state: 'waiting', kept: false })
expect(settle('cancelled')).toEqual({ state: 'waiting' })
expect(rejectedDraftSettlement({ reason: DISPATCH_REJECTED_CANCELLED })).toEqual({
state: 'waiting',
kept: false
state: 'waiting'
})
expect(settle('notDelivered')).toEqual({ state: 'waiting', kept: false })
expect(settle('notDelivered')).toEqual({ state: 'waiting' })
})

// A Send the person asked for and the host never handed over waits for them; the queue's own
// hand-off, or one from a build that recorded no origin, waits under the queue's pause.
it('a restart or close before hand-over sends it back to waiting, kept only when the person sent it', () => {
// Whoever sent it, it waits under the reopen's pause like every card the chat closed with.
it('a restart or close before hand-over sends it back to waiting, with no hold of its own', () => {
for (const kind of ['hostRestarted', 'chatClosed'] as const) {
expect(settle(kind, 'client')).toEqual({ state: 'waiting', kept: true })
expect(settle(kind, 'host')).toEqual({ state: 'waiting', kept: false })
expect(settle(kind)).toEqual({ state: 'waiting', kept: false })
expect(settle(kind)).toEqual({ state: 'waiting' })
}
expect(
rejectedDraftSettlement({ reason: DISPATCH_REJECTED_HOST_RESTARTED, origin: 'client' })
).toEqual({ state: 'waiting', kept: true })
expect(rejectedDraftSettlement({ reason: DISPATCH_REJECTED_HOST_RESTARTED })).toEqual({
state: 'waiting'
})
})

it('a failure returns the card for the user to act on', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,27 +30,18 @@ export function consumedSubmissionWasRejected(
}

/** What a consumed draft becomes when its submission is rejected. */
export type RejectedDraftSettlement = { state: 'returned' } | { state: 'waiting'; kept: boolean }
export type RejectedDraftSettlement = { state: 'returned' | 'waiting' }

/**
* Where no one failed the user, the draft goes back to waiting at its own position, under whatever
* pauses the queue: a Stop's, or the restart's, derived from the host instance. A Send the person
* asked for (`origin` client) that a restart or a close cut short is kept (`kept`) until they send
* it again, as the host keeps every message a person sent and it never handed over; the queue's
* own hand-off is not theirs, so it waits as any queued card does: under the restart's pause after
* a restart, and plainly queued after a close in the same process. A returned card would block the drafts
* behind it on a failure that never happened. A failure returns the card with its refusal for the
* user to act on.
* Where no one failed the user, the draft goes back to waiting, under whatever pauses the queue: a
* Stop's, or, when a restart or a close cut it short, the reopen's, so it follows the chat's next
* turn like every card the chat closed with. A returned card would block the drafts behind it on a
* failure that never happened. A failure returns the card with its refusal for the user to act on.
*/
export function rejectedDraftSettlement(
rejected: Pick<AgentJournalSubmission, 'reason' | 'origin'> & { rejection?: unknown }
rejected: Pick<AgentJournalSubmission, 'reason'> & { rejection?: unknown }
): RejectedDraftSettlement {
const { verdict, kind } = classifyDispatchRejection(rejected)
if (verdict !== null) {
return { state: 'returned' }
}
const cutShort = kind === 'hostRestarted' || kind === 'chatClosed'
return { state: 'waiting', kept: cutShort && rejected.origin === 'client' }
return { state: classifyDispatchRejection(rejected).verdict !== null ? 'returned' : 'waiting' }
}

/** True when committing this row NEWLY settles the submission to `rejected` —
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ function replace(input: {
reason: 'legacy_import',
fence: 1,
items: input.items,
queuePause: { lifted: false, liveStop: null },
queuePause: { lifted: false, liveStop: null, reopened: false },
now,
mintEpoch: () => `epoch-${clock}`,
onPublished: input.onPublished ?? (() => undefined)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type { JournalLoad } from './journal-open'
import { applyJournalRow, createJournalReducerState } from './journal-reducer'
import { buildJournalItemRow, journalRowBase } from './journal-row-builders'
import {
buildJournalQueueReopenRow,
buildJournalQueueResumeRow,
buildJournalStopEventRow
} from './journal-stop-and-resume-rows'
Expand Down Expand Up @@ -77,7 +78,7 @@ export function replaceJournalEpoch(input: {
applyJournalRow(state, row)
rows.push(row)
}
const { lifted, liveStop } = input.queuePause
const { lifted, liveStop, reopened } = input.queuePause
const place = () => ({ state, seq: state.lastSequence + 1, fence: input.fence, ts: input.now() })
if (lifted) {
const row = buildJournalQueueResumeRow(place())
Expand All @@ -89,6 +90,11 @@ export function replaceJournalEpoch(input: {
applyJournalRow(state, row)
rows.push(row)
}
if (reopened) {
const row = buildJournalQueueReopenRow(place())
applyJournalRow(state, row)
rows.push(row)
}

const { sessionId } = input.identity
input.database.transaction((db) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
// working status, teardown, or the idle sweep.

import type Database from '../../sqlite/sync-database'
import type { AgentJournalMessageItem } from '../../../shared/agent-session-journal-types'
import type {
AgentJournalCursor,
AgentJournalMessageItem
} from '../../../shared/agent-session-journal-types'
import {
AGENT_SESSION_MAX_NEW_OPERATION_AGE_MS,
AGENT_SESSION_OPERATION_FUTURE_SKEW_MS
Expand All @@ -15,7 +18,7 @@ import type { JournalReducerState } from './journal-reducer'
import type { JournalRow } from './journal-row-schema'
import type { JournalOperationReceipt, JournalRowTransactionHook } from './journal-row-writer'
import type { JournalSubmissionConsume } from './journal-store-contracts'
import { adoptQueuedMessages, holdQueuedMessages } from './queued-message-holds'
import { holdQueuedMessages } from './queued-message-holds'
import {
deriveQueuePauses,
journalUserStopInForce,
Expand Down Expand Up @@ -55,8 +58,8 @@ export type JournalQueuedMessagesDeps = {
database: () => JournalHostDatabase
readOnly: () => boolean
state: () => JournalReducerState
/** Whether this handle found the row at `sequence` on disk when it opened. */
wroteBeforeOpen: (sequence: number) => boolean
/** Where the reopen's pause begins when this handle could not mark it (`reopenFloor`). */
reopenFloor: () => AgentJournalCursor | null
/** The journal's own commit notification. Every standalone draft-table
* transaction that changed rows fires it after COMMIT, so a draft or hold
* change publishes and wakes the drain through the same path a journal row
Expand Down Expand Up @@ -157,8 +160,21 @@ export class JournalQueuedMessages {
}

/** The queue's pauses in force, derived from the fold and the cards (`queued-message-pause.ts`). */
pauses(hostInstance: string): DerivedQueuePause[] {
return this.derivePauses(this.list(), hostInstance)
pauses(): DerivedQueuePause[] {
return this.derivePauses(this.list())
}

/** A card waits, or is mid-hand-off and may come back to waiting: a chat that stops running
* marks it (`AgentSessionJournal.markQueueReopen`). */
awaitReopenMark(): boolean {
const { submissions } = this.deps.state()
return this.list().some((row) => {
if (row.state === 'waiting') {
return true
}
const handOff = row.consumedAs ? submissions.get(row.consumedAs)?.dispatchState : undefined
return row.state === 'dispatched' && (handOff === 'pending' || handOff === 'unknown')
})
}

/** The person's Stop still pausing the queue, if any (`journalUserStopInForce`). */
Expand All @@ -167,37 +183,17 @@ export class JournalQueuedMessages {
return journalUserStopInForce(state.queuePauseMarks, state.latestAcceptedTurnSequence)
}

private derivePauses(
cards: readonly QueuedMessageRow[],
hostInstance: string
): DerivedQueuePause[] {
private derivePauses(cards: readonly QueuedMessageRow[]): DerivedQueuePause[] {
const state = this.deps.state()
return deriveQueuePauses({
epoch: state.epoch,
marks: state.queuePauseMarks,
latestAcceptedTurnSequence: state.latestAcceptedTurnSequence,
cards,
hostInstance,
restartEnded: this.restartEnded()
reopenFloor: this.deps.reopenFloor()
})
}

/** A turn started since this handle opened, which ends a restart's pause. */
restartEnded(): boolean {
const latest = this.deps.state().latestAcceptedTurnSequence
return latest > 0 && !this.deps.wroteBeforeOpen(latest)
}

/** Adopts waiting rows another host instance wrote into this one, ending a restart's pause.
* Returns whether anything changed. */
adopt(hostInstance: string): Promise<boolean> {
const { sessionId } = this.deps
return this.transact(
(db) => adoptQueuedMessages(db, { sessionId, hostInstance }),
(changed) => changed > 0
).then((changed) => changed > 0)
}

/** Inside the caller's journal-row transaction (`journal-unsent-send-hold.ts`): one kept send
* becomes a card, and the cards ahead of the queue take the positions given. False when a card
* by that id already exists, which then stands. */
Expand Down Expand Up @@ -286,7 +282,7 @@ export class JournalQueuedMessages {
// pick and this claim (both run on the session's serialized lane, held across the send), so
// this guards any pause-relevant row written off that lane from overtaking a held card.
const cards = listQueuedMessages(db, this.deps.sessionId)
const pauses = this.derivePauses(cards, input.yieldsToPause.hostInstance)
const pauses = this.derivePauses(cards)
if (nextSendableQueuedCard(pauses, cards)?.messageId !== input.messageId) {
throw new QueuedMessageNotConsumableError(input.messageId, input.expect)
}
Expand Down
Loading
Loading