@athenaintel/react 0.48.0 → 0.50.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -10
- package/dist/agent-runtime/src/call-recap.cjs +319 -0
- package/dist/agent-runtime/src/call-recap.cjs.map +1 -0
- package/dist/agent-runtime/src/call-recap.d.ts +142 -0
- package/dist/agent-runtime/src/call-recap.js +319 -0
- package/dist/agent-runtime/src/call-recap.js.map +1 -0
- package/dist/agent-runtime/src/chat-outbox-store.cjs +1 -0
- package/dist/agent-runtime/src/chat-outbox-store.cjs.map +1 -1
- package/dist/agent-runtime/src/chat-outbox-store.d.ts +7 -0
- package/dist/agent-runtime/src/chat-outbox-store.js +1 -0
- package/dist/agent-runtime/src/chat-outbox-store.js.map +1 -1
- package/dist/agent-runtime/src/chat-outbox.cjs +46 -5
- package/dist/agent-runtime/src/chat-outbox.cjs.map +1 -1
- package/dist/agent-runtime/src/chat-outbox.d.ts +29 -0
- package/dist/agent-runtime/src/chat-outbox.js +46 -5
- package/dist/agent-runtime/src/chat-outbox.js.map +1 -1
- package/dist/agent-runtime/src/conversation-index.cjs.map +1 -1
- package/dist/agent-runtime/src/conversation-index.d.ts +7 -0
- package/dist/agent-runtime/src/conversation-index.js.map +1 -1
- package/dist/agent-runtime/src/conversation-list.cjs +2 -1
- package/dist/agent-runtime/src/conversation-list.cjs.map +1 -1
- package/dist/agent-runtime/src/conversation-list.d.ts +19 -3
- package/dist/agent-runtime/src/conversation-list.js +2 -1
- package/dist/agent-runtime/src/conversation-list.js.map +1 -1
- package/dist/agent-runtime/src/index.d.ts +6 -5
- package/dist/agent-runtime/src/interrupts.cjs +28 -1
- package/dist/agent-runtime/src/interrupts.cjs.map +1 -1
- package/dist/agent-runtime/src/interrupts.d.ts +21 -1
- package/dist/agent-runtime/src/interrupts.js +28 -1
- package/dist/agent-runtime/src/interrupts.js.map +1 -1
- package/dist/agent-runtime/src/model-context.cjs +3 -2
- package/dist/agent-runtime/src/model-context.cjs.map +1 -1
- package/dist/agent-runtime/src/model-context.d.ts +6 -1
- package/dist/agent-runtime/src/model-context.js +3 -2
- package/dist/agent-runtime/src/model-context.js.map +1 -1
- package/dist/agent-runtime/src/runtime.d.ts +3 -2
- package/dist/agent-runtime/src/send-recovery.cjs +37 -3
- package/dist/agent-runtime/src/send-recovery.cjs.map +1 -1
- package/dist/agent-runtime/src/send-recovery.d.ts +19 -0
- package/dist/agent-runtime/src/send-recovery.js +37 -3
- package/dist/agent-runtime/src/send-recovery.js.map +1 -1
- package/dist/agent-runtime/src/thread-handoff-flow-machine.d.ts +8 -8
- package/dist/agent-runtime/src/thread-handoff-protocol.d.ts +113 -1
- package/dist/agent-runtime/src/thread-handoff.d.ts +1 -1
- package/dist/agent-runtime/src/use-conversation-list.cjs +3 -2
- package/dist/agent-runtime/src/use-conversation-list.cjs.map +1 -1
- package/dist/agent-runtime/src/use-conversation-list.d.ts +6 -0
- package/dist/agent-runtime/src/use-conversation-list.js +3 -2
- package/dist/agent-runtime/src/use-conversation-list.js.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-chat-outbox.cjs +21 -1
- package/dist/agent-runtime/src/use-deep-agent-chat-outbox.cjs.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-chat-outbox.d.ts +8 -2
- package/dist/agent-runtime/src/use-deep-agent-chat-outbox.js +21 -1
- package/dist/agent-runtime/src/use-deep-agent-chat-outbox.js.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-runtime.cjs +24 -6
- package/dist/agent-runtime/src/use-deep-agent-runtime.cjs.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-runtime.d.ts +17 -0
- package/dist/agent-runtime/src/use-deep-agent-runtime.js +24 -6
- package/dist/agent-runtime/src/use-deep-agent-runtime.js.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-voice.cjs +44 -9
- package/dist/agent-runtime/src/use-deep-agent-voice.cjs.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-voice.d.ts +34 -1
- package/dist/agent-runtime/src/use-deep-agent-voice.js +44 -9
- package/dist/agent-runtime/src/use-deep-agent-voice.js.map +1 -1
- package/dist/agent-runtime/src/use-thread-handoff-flow.d.ts +8 -2
- package/dist/agent-runtime/src/voice-earcon.cjs +139 -0
- package/dist/agent-runtime/src/voice-earcon.cjs.map +1 -0
- package/dist/agent-runtime/src/voice-earcon.d.ts +38 -0
- package/dist/agent-runtime/src/voice-earcon.js +139 -0
- package/dist/agent-runtime/src/voice-earcon.js.map +1 -0
- package/dist/agent-runtime/src/voice-media.cjs.map +1 -1
- package/dist/agent-runtime/src/voice-media.d.ts +14 -0
- package/dist/agent-runtime/src/voice-media.js.map +1 -1
- package/dist/agent-runtime/src/voice-presentation.cjs +2 -0
- package/dist/agent-runtime/src/voice-presentation.cjs.map +1 -1
- package/dist/agent-runtime/src/voice-presentation.js +2 -0
- package/dist/agent-runtime/src/voice-presentation.js.map +1 -1
- package/dist/assets/AthenaAssetEmbed.cjs.map +1 -1
- package/dist/assets/AthenaAssetEmbed.d.ts +11 -5
- package/dist/assets/AthenaAssetEmbed.js.map +1 -1
- package/dist/assets/use-asset-embed.cjs.map +1 -1
- package/dist/assets/use-asset-embed.d.ts +12 -5
- package/dist/assets/use-asset-embed.js.map +1 -1
- package/dist/chat/StatewireApprovalCard.cjs +41 -6
- package/dist/chat/StatewireApprovalCard.cjs.map +1 -1
- package/dist/chat/StatewireApprovalCard.d.ts +18 -1
- package/dist/chat/StatewireApprovalCard.js +42 -7
- package/dist/chat/StatewireApprovalCard.js.map +1 -1
- package/dist/chat/StatewireQueuedMessages.cjs +56 -6
- package/dist/chat/StatewireQueuedMessages.cjs.map +1 -1
- package/dist/chat/StatewireQueuedMessages.d.ts +14 -0
- package/dist/chat/StatewireQueuedMessages.js +58 -8
- package/dist/chat/StatewireQueuedMessages.js.map +1 -1
- package/dist/chat/WaitingOnYouBanner.cjs +6 -2
- package/dist/chat/WaitingOnYouBanner.cjs.map +1 -1
- package/dist/chat/WaitingOnYouBanner.js +7 -3
- package/dist/chat/WaitingOnYouBanner.js.map +1 -1
- package/dist/chat/chat-voice-controls.cjs +3 -1
- package/dist/chat/chat-voice-controls.cjs.map +1 -1
- package/dist/chat/chat-voice-controls.d.ts +6 -1
- package/dist/chat/chat-voice-controls.js +3 -1
- package/dist/chat/chat-voice-controls.js.map +1 -1
- package/dist/chat-ui/index.cjs +3 -0
- package/dist/chat-ui/index.cjs.map +1 -1
- package/dist/chat-ui/index.d.ts +1 -0
- package/dist/chat-ui/index.js +3 -0
- package/dist/chat-ui/index.js.map +1 -1
- package/dist/chat-ui/messages/from-the-call-card.cjs +124 -0
- package/dist/chat-ui/messages/from-the-call-card.cjs.map +1 -0
- package/dist/chat-ui/messages/from-the-call-card.d.ts +17 -0
- package/dist/chat-ui/messages/from-the-call-card.js +124 -0
- package/dist/chat-ui/messages/from-the-call-card.js.map +1 -0
- package/dist/chat-ui/rich-text/chat-link.cjs +1 -0
- package/dist/chat-ui/rich-text/chat-link.cjs.map +1 -1
- package/dist/chat-ui/rich-text/chat-link.d.ts +4 -0
- package/dist/chat-ui/rich-text/chat-link.js +4 -3
- package/dist/chat-ui/rich-text/chat-link.js.map +1 -1
- package/dist/chat-ui/rich-text/chat-message-body.cjs +1 -0
- package/dist/chat-ui/rich-text/chat-message-body.cjs.map +1 -1
- package/dist/chat-ui/rich-text/chat-message-body.d.ts +12 -0
- package/dist/chat-ui/rich-text/chat-message-body.js +2 -1
- package/dist/chat-ui/rich-text/chat-message-body.js.map +1 -1
- package/dist/chat-ui/thread/chat-thread-shell.cjs +2 -0
- package/dist/chat-ui/thread/chat-thread-shell.cjs.map +1 -1
- package/dist/chat-ui/thread/chat-thread-shell.js +2 -0
- package/dist/chat-ui/thread/chat-thread-shell.js.map +1 -1
- package/dist/collab/client.cjs +15 -3
- package/dist/collab/client.cjs.map +1 -1
- package/dist/collab/client.d.ts +4 -2
- package/dist/collab/client.js +16 -4
- package/dist/collab/client.js.map +1 -1
- package/dist/composer/index.d.ts +1 -1
- package/dist/composer/useReadComposerDraft.cjs +26 -7
- package/dist/composer/useReadComposerDraft.cjs.map +1 -1
- package/dist/composer/useReadComposerDraft.d.ts +23 -1
- package/dist/composer/useReadComposerDraft.js +27 -8
- package/dist/composer/useReadComposerDraft.js.map +1 -1
- package/dist/index.cjs +3 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/keryx-http/src/unconfirmed-update-attributions.cjs +190 -0
- package/dist/keryx-http/src/unconfirmed-update-attributions.cjs.map +1 -0
- package/dist/keryx-http/src/unconfirmed-update-attributions.d.ts +132 -0
- package/dist/keryx-http/src/unconfirmed-update-attributions.js +190 -0
- package/dist/keryx-http/src/unconfirmed-update-attributions.js.map +1 -0
- package/dist/keryx-http/src/update-attributions.cjs +34 -4
- package/dist/keryx-http/src/update-attributions.cjs.map +1 -1
- package/dist/keryx-http/src/update-attributions.d.ts +36 -4
- package/dist/keryx-http/src/update-attributions.js +35 -5
- package/dist/keryx-http/src/update-attributions.js.map +1 -1
- package/dist/provider/AthenaProvider.cjs +4 -0
- package/dist/provider/AthenaProvider.cjs.map +1 -1
- package/dist/provider/AthenaProvider.d.ts +11 -1
- package/dist/provider/AthenaProvider.js +4 -0
- package/dist/provider/AthenaProvider.js.map +1 -1
- package/dist/react/package.json.cjs +1 -1
- package/dist/react/package.json.js +1 -1
- package/dist/threads/api.cjs.map +1 -1
- package/dist/threads/api.d.ts +15 -0
- package/dist/threads/api.js.map +1 -1
- package/dist/threads/conversation-list-fetcher.cjs +13 -5
- package/dist/threads/conversation-list-fetcher.cjs.map +1 -1
- package/dist/threads/conversation-list-fetcher.d.ts +4 -1
- package/dist/threads/conversation-list-fetcher.js +13 -5
- package/dist/threads/conversation-list-fetcher.js.map +1 -1
- package/dist/threads/statewire-thread-list.cjs +28 -6
- package/dist/threads/statewire-thread-list.cjs.map +1 -1
- package/dist/threads/statewire-thread-list.d.ts +20 -8
- package/dist/threads/statewire-thread-list.js +28 -6
- package/dist/threads/statewire-thread-list.js.map +1 -1
- package/dist/threads/use-athena-sessions.cjs +27 -5
- package/dist/threads/use-athena-sessions.cjs.map +1 -1
- package/dist/threads/use-athena-sessions.d.ts +10 -5
- package/dist/threads/use-athena-sessions.js +28 -6
- package/dist/threads/use-athena-sessions.js.map +1 -1
- package/dist/threads/use-rest-conversation-history.d.ts +1 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-outbox.cjs","sources":["../../../../agent-runtime/src/chat-outbox.ts"],"sourcesContent":["import type { ChatOutboxAttachmentCheck, ChatOutboxAttachmentVerdict } from \"./chat-outbox-attachments\";\nimport type {\n ChatOutboxAttachment,\n ChatOutboxClaim,\n ChatOutboxEntry,\n ChatOutboxFailureReason,\n ChatOutboxFile,\n ChatOutboxRecord,\n ChatOutboxStore,\n} from \"./chat-outbox-store\";\n\n/**\n * The chat outbox: a message typed while Athena can't be reached waits on this device and is sent\n * once Athena answers again, while the app is in front, after a few seconds the person can cancel\n * (decision 5 of the offline plan, `docs/plans/2026-10-27-offline-everywhere-plan.md`).\n *\n * - **Held before the conversation's journal, never inside it.** A send made while Athena can't be\n * reached does not reach statewire at all: statewire would replay it the moment its connection\n * returns (from a hidden tab too) and has no way to take a journaled statement back. The message\n * waits here instead, and reaches statewire only when this controller hands it over, under the\n * id minted when it was typed.\n * - **Sent in front, with a countdown.** Once Athena is reachable and the app is in front (a visible\n * tab, an active app, a focused desktop window), the waiting messages count down\n * ({@link CHAT_OUTBOX_COUNTDOWN_MS}) with a Cancel that gives the text back to the composer; then\n * each is handed over once. Going to the background or losing Athena stops the countdown; it\n * starts over, in full, when both are back.\n * - **A day is the limit for sending on its own.** A message that waited more than\n * {@link CHAT_OUTBOX_ASK_AFTER_MS} asks first (Send / Edit / Discard); the messages behind it wait\n * for the answer, so nothing is sent out of order.\n * - **Once per message, never a loop.** A message is handed over at most once per return of Athena.\n * A hand-off that fails before the conversation took it (anything but the conversation going\n * away) stays as \"Couldn't send\" with Retry and Discard, kept with the message so no window and\n * no later mount sends it again on its own. Once the conversation's journal took it, statewire\n * owns it: it delivers it exactly once, and a refusal lands in the conversation's own failed\n * lane (Retry / Discard there).\n * - **One sender per message.** A hand-off claims the message in the store first, atomically\n * across windows ({@link CHAT_OUTBOX_CLAIM_LEASE_MS}), so two windows on the same conversation\n * never hand the same message twice (a second hand-off of an id whose run is in flight would be\n * read as an edit of that turn). The gate is checked again once the claim is written: a tab\n * hidden (or an Athena lost) meanwhile lets the claim go, and the message waits again.\n * - **Nothing overtakes.** Until the conversation's kept messages are read, a new send waits too\n * (it goes at once if nothing older turns up), and a message the person told to go now still\n * goes after the older ones in line.\n * - **Kept until it is safe to let go.** The waiting record is removed only once statewire has the\n * statement durably (`posted`, `applied` or settled), or once the conversation shows the message\n * (a journal adopted from an earlier page, or the message already on the transcript).\n * - **Its attachments go with it, still valid.** A message keeps the files that had finished\n * uploading before Athena went out of reach (references to files already in Athena; one picked\n * while it can't be reached is never attached). Once the message is claimed they are asked about\n * ({@link ChatOutboxOptions.checkAttachments}): one Athena refuses (deleted, or no longer shared\n * with the person) keeps the message as \"Couldn't send\" with Retry, Edit and Discard, and a check\n * Athena could not answer keeps it the same way as a send error. Nothing goes unchecked, and the\n * text is never lost.\n */\n\n/** How long the countdown before a waiting message is sent lasts. */\nexport const CHAT_OUTBOX_COUNTDOWN_MS = 5_000;\n/** A message that waited longer than this asks before it is sent. */\nexport const CHAT_OUTBOX_ASK_AFTER_MS = 24 * 60 * 60 * 1000;\n/** How long a window's claim on a message holds against another window's hand-off. */\nexport const CHAT_OUTBOX_CLAIM_LEASE_MS = 30_000;\n/** How long the check of a message's attachments may take before it counts as unanswered. */\nexport const CHAT_OUTBOX_ATTACHMENT_CHECK_TIMEOUT_MS = 15_000;\n\n/**\n * Why a sent message's Edit is not offered while Athena can't be reached (its tooltip, or the hint\n * under it where there are no tooltips). An edit rewinds the conversation and re-runs it: a\n * journaled one would replay the moment the connection returns, from a hidden window too, so an\n * edit never waits the way a new message does.\n */\nexport const CHAT_EDIT_UNREACHABLE_LABEL = \"Can't edit while Athena can't be reached\";\n\n/** What an edit submitted while Athena can't be reached says: the edit stays where it was typed. */\nexport const CHAT_EDIT_KEPT_MESSAGE =\n \"Can't reach Athena right now. Your edit stays here — submit it once Athena is back.\";\n\n/** An edit submitted while Athena can't be reached: refused, never queued (its message says so). */\nexport class ChatEditUnreachableError extends Error {\n constructor() {\n super(CHAT_EDIT_KEPT_MESSAGE);\n this.name = \"ChatEditUnreachableError\";\n }\n}\n\n/** A boolean the host reports and notifies on: Athena's reachability, the app being in front. */\nexport interface ChatOutboxSignal {\n get(): boolean;\n subscribe(listener: () => void): () => void;\n}\n\n/**\n * Whether a hand-off reaches the conversation now: `ready` (attached, a never-started chat that\n * dials on its first send, or an idle channel that reattaches on demand — in each case what the\n * conversation shows is the server's, so a message missing from it never reached it),\n * `connecting` (reattaching: what it shows may be a remembered snapshot), `refused` (the host\n * refused the conversation: stopped, or parked on an authorization denial).\n */\nexport type ChatOutboxThreadReadiness = \"ready\" | \"connecting\" | \"refused\";\n\n/** The conversation the outbox hands messages to (`chat-outbox-thread.ts` adapts a statewire thread). */\nexport interface ChatOutboxThreadPort {\n readiness(): ChatOutboxThreadReadiness;\n /**\n * Message ids the conversation shows anywhere: its transcript, its queue and steer lanes, the\n * run being dispatched, its failed lane — this page's journal included (statewire folds a\n * journaled send into them, one adopted from an earlier page too).\n */\n heldIds(): ReadonlySet<string>;\n /** Hand the message to the conversation under its own id; settles with the server's verdict. */\n send(entry: ChatOutboxEntry): Promise<unknown>;\n subscribe(listener: () => void): () => void;\n}\n\nexport type ChatOutboxRowState =\n /** Not sent yet: Athena can't be reached, or the app is not in front. */\n | { kind: \"waiting\" }\n /** Athena is back: the message is sent when the countdown ends, unless cancelled. */\n | { kind: \"countdown\"; endsAt: number }\n /** It waited more than a day: Send, Edit or Discard. */\n | { kind: \"ask\" }\n /** Being handed to the conversation (by this window or another). */\n | { kind: \"sending\" }\n /** The hand-off failed for a reason other than Athena being unreachable. */\n | { kind: \"failed\"; reason: ChatOutboxFailureReason; message: string };\n\nexport interface ChatOutboxRow {\n entry: ChatOutboxEntry;\n state: ChatOutboxRowState;\n}\n\n/** Another conversation of the same person with messages waiting (the tray offers to open it). */\nexport interface ChatOutboxElsewhere {\n threadId: string;\n /** How many messages wait there. */\n count: number;\n /** The oldest of them: its text names the conversation. */\n first: ChatOutboxEntry;\n /**\n * Every message waiting there was typed into a chat that had never started, so it opens as the\n * new chat it still is (one typed after the chat started says it has a session now).\n */\n newChat: boolean;\n}\n\nexport interface ChatOutboxSnapshot {\n rows: readonly ChatOutboxRow[];\n /** When the running countdown ends (epoch ms), or `null`. */\n countdownEndsAt: number | null;\n /** Whether every waiting message is kept on this device (false: only while the page stays open). */\n durable: boolean;\n /** Whether the thread's kept messages have been read. */\n loaded: boolean;\n /** The person's other conversations with messages waiting, oldest first. */\n elsewhere: readonly ChatOutboxElsewhere[];\n}\n\n/** What a failed row says. */\nexport function chatOutboxFailureMessage(reason: ChatOutboxFailureReason): string {\n switch (reason) {\n case \"refused\":\n return \"This conversation can't be reached right now.\";\n case \"prepare\":\n return \"Couldn't prepare this message to send.\";\n case \"attachment\":\n return \"An attachment is no longer available to you.\";\n default:\n return \"Couldn't send this message.\";\n }\n}\n\nexport interface ChatOutboxInput {\n text: string;\n composerMarkdown?: string;\n /** The draft as an HTML composer holds it (the phone's), given back exactly on Cancel. */\n composerHtml?: string;\n metadata?: Record<string, unknown>;\n files?: ChatOutboxFile[];\n /** Files already in Athena the message carries, asked about before it goes. */\n attachments?: ChatOutboxAttachment[];\n}\n\nexport type ChatOutboxEvent =\n | { type: \"held\"; threadId: string; messageId: string; durable: boolean }\n | { type: \"countdown-started\"; threadId: string; count: number }\n | { type: \"asked\"; threadId: string; messageId: string; ageMs: number }\n | { type: \"handed\"; threadId: string; messageId: string; auto: boolean; ageMs: number }\n | { type: \"delivered\"; threadId: string; messageId: string; status: string }\n | { type: \"adopted\"; threadId: string; messageId: string }\n | { type: \"failed\"; threadId: string; messageId: string; message: string; reason: ChatOutboxFailureReason }\n | { type: \"cancelled\"; threadId: string; messageId: string }\n | { type: \"discarded\"; threadId: string; messageId: string };\n\n/** The command-lifecycle update statewire reports (`onCommandChange`), structurally. */\nexport interface ChatOutboxCommandUpdate {\n status: \"queued\" | \"posted\" | \"applied\" | \"settled\";\n params?: readonly unknown[];\n failure?: { fate: string; cause?: string } | undefined;\n}\n\nexport interface ChatOutbox {\n readonly threadId: string;\n getSnapshot(): ChatOutboxSnapshot;\n subscribe(listener: () => void): () => void;\n /**\n * Whether Athena can be reached, as this outbox reads it (the host's model; in the SDK the\n * conversation's own channel too). An edit of a sent message waits on it as a send does: while it\n * says no, the Edit action is not offered and a submitted edit is refused\n * ({@link CHAT_EDIT_UNREACHABLE_LABEL}, {@link ChatEditUnreachableError}).\n */\n readonly reachability: ChatOutboxSignal;\n /**\n * Whether a send made now must wait here: Athena can't be reached, or earlier messages of this\n * conversation are still waiting (a later message never overtakes them).\n */\n shouldHold(): boolean;\n /** Keep a message typed now; it waits until it can be sent. */\n hold(input: ChatOutboxInput): ChatOutboxEntry;\n /** Cancel (or the ask's Edit): the message leaves the outbox and its text goes back to the composer. */\n cancel(id: string): void;\n /** The ask's Send, a failed row's Retry: hand it over now (one attempt; waits if Athena is out of reach). */\n send(id: string): void;\n discard(id: string): void;\n /** Feed statewire's command lifecycle (`onCommandChange`). */\n observeCommand(update: ChatOutboxCommandUpdate): void;\n /** Open another conversation the tray lists as holding waiting messages (`null`: the host cannot). */\n readonly openThread: ((target: { threadId: string; newChat: boolean }) => void) | null;\n dispose(): void;\n}\n\nexport interface ChatOutboxOptions {\n threadId: string;\n /**\n * The conversation has never started (a new chat): what waits reopens with it. Read when a\n * message is held, so a chat that started since stamps its later messages as started.\n */\n isNewChat: boolean | (() => boolean);\n /** Where waiting messages are kept for this person; `null` keeps them in memory only. */\n store: ChatOutboxStore | null;\n reachability: ChatOutboxSignal;\n foreground: ChatOutboxSignal;\n thread: ChatOutboxThreadPort;\n /** Give a cancelled message's text back to the composer. */\n restore: (entry: ChatOutboxEntry) => void;\n /**\n * Open another of the person's conversations that holds waiting messages (the tray's Open);\n * omitted where the host cannot switch conversations (an embedded chat).\n */\n openThread?: (target: { threadId: string; newChat: boolean }) => void;\n onEvent?: (event: ChatOutboxEvent) => void;\n /**\n * Asks Athena whether a waiting message's attachments can still be opened, right before it goes\n * (`createChatOutboxAttachmentCheck`). `refused` keeps the message as \"Couldn't send\" (Retry,\n * Edit, Discard); `unknown` too, as a send error. Omitted: attachments go unchecked.\n */\n checkAttachments?: ChatOutboxAttachmentCheck;\n now?: () => number;\n countdownMs?: number;\n askAfterMs?: number;\n claimLeaseMs?: number;\n /** How long the attachment check may take before it counts as unanswered. */\n attachmentCheckTimeoutMs?: number;\n newMessageId?: () => string;\n /** Timer seam (tests): schedule `task` after `ms`, returning what cancels it. */\n setTimer?: (task: () => void, ms: number) => () => void;\n}\n\ntype Phase =\n /** Waiting in line. */\n | { kind: \"idle\" }\n /**\n * The countdown ended (or the person said Send) while the conversation was reattaching, or\n * while the message's own write was still landing: it goes as soon as both are done.\n */\n | { kind: \"awaiting-thread\" }\n /** Claiming it in the store before the hand-off. */\n | { kind: \"claiming\" }\n /** Handed to the conversation; `journaled` once statewire's journal took it. */\n | { kind: \"handed\"; journaled: boolean }\n | { kind: \"failed\"; reason: ChatOutboxFailureReason };\n\n/** Fates statewire settles a send with when the conversation went away under it (not a failure). */\nconst AWAY_CAUSES: ReadonlySet<string> = new Set([\"unmount\", \"stream-gone\"]);\n\n/** The message id a run send carries, off a command update's params. */\nexport function chatOutboxCommandMessageId(params: readonly unknown[] | undefined): string | null {\n const first = params?.[0] as { message?: { id?: unknown } } | undefined;\n const id = first?.message?.id;\n return typeof id === \"string\" ? id : null;\n}\n\nfunction failureOf(error: unknown): { fate?: string; cause?: string; message: string } {\n const failure = (error as { failure?: { fate?: unknown; cause?: unknown } } | null)?.failure;\n const fate = typeof failure?.fate === \"string\" ? failure.fate : undefined;\n const cause = typeof failure?.cause === \"string\" ? failure.cause : undefined;\n const message = error instanceof Error ? error.message : String(error);\n return { fate, cause, message };\n}\n\nfunction defaultMessageId(): string {\n const host = globalThis as { crypto?: { randomUUID?: () => string } };\n if (typeof host.crypto?.randomUUID === \"function\") return host.crypto.randomUUID();\n // Hosts without randomUUID (none Athena ships today): a random v4-shaped id.\n const hex = Array.from({ length: 32 }, () => Math.floor(Math.random() * 16).toString(16)).join(\"\");\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-4${hex.slice(13, 16)}-a${hex.slice(17, 20)}-${hex.slice(20, 32)}`;\n}\n\nfunction sameChatOutboxRowState(a: ChatOutboxRowState, b: ChatOutboxRowState): boolean {\n if (a.kind !== b.kind) return false;\n switch (a.kind) {\n case \"countdown\":\n return a.endsAt === (b as typeof a).endsAt;\n case \"failed\":\n return a.reason === (b as typeof a).reason && a.message === (b as typeof a).message;\n default:\n return true;\n }\n}\n\n/** Whether two listings of a message show the same thing (another chat's preview is re-read per listing). */\nfunction sameChatOutboxEntry(a: ChatOutboxEntry, b: ChatOutboxEntry): boolean {\n return (\n a === b ||\n (a.id === b.id &&\n a.threadId === b.threadId &&\n a.text === b.text &&\n a.newChat === b.newChat &&\n a.queuedAt === b.queuedAt &&\n a.composerMarkdown === b.composerMarkdown)\n );\n}\n\n/** Whether two snapshots show the same thing (the rows' entries by identity: a listing keeps them). */\nexport function sameChatOutboxSnapshot(a: ChatOutboxSnapshot, b: ChatOutboxSnapshot): boolean {\n if (a === b) return true;\n if (a.countdownEndsAt !== b.countdownEndsAt || a.durable !== b.durable || a.loaded !== b.loaded) return false;\n if (a.rows.length !== b.rows.length || a.elsewhere.length !== b.elsewhere.length) return false;\n for (let index = 0; index < a.rows.length; index += 1) {\n const x = a.rows[index];\n const y = b.rows[index];\n if (!x || !y || x.entry !== y.entry || !sameChatOutboxRowState(x.state, y.state)) return false;\n }\n for (let index = 0; index < a.elsewhere.length; index += 1) {\n const x = a.elsewhere[index];\n const y = b.elsewhere[index];\n if (\n !x ||\n !y ||\n x.threadId !== y.threadId ||\n x.count !== y.count ||\n x.newChat !== y.newChat ||\n !sameChatOutboxEntry(x.first, y.first)\n ) {\n return false;\n }\n }\n return true;\n}\n\nexport function createChatOutbox(options: ChatOutboxOptions): ChatOutbox {\n const {\n threadId,\n isNewChat,\n store,\n reachability,\n foreground,\n thread,\n restore,\n openThread,\n onEvent,\n checkAttachments,\n now = () => Date.now(),\n countdownMs = CHAT_OUTBOX_COUNTDOWN_MS,\n askAfterMs = CHAT_OUTBOX_ASK_AFTER_MS,\n claimLeaseMs = CHAT_OUTBOX_CLAIM_LEASE_MS,\n attachmentCheckTimeoutMs = CHAT_OUTBOX_ATTACHMENT_CHECK_TIMEOUT_MS,\n newMessageId = defaultMessageId,\n setTimer = (task, ms) => {\n const timer = setTimeout(task, ms);\n return () => clearTimeout(timer);\n },\n } = options;\n\n /** This controller's name on the claims it takes. */\n const claimant = newMessageId();\n let disposed = false;\n let loaded = store === null;\n /** The thread's waiting messages, oldest first. */\n let entries: ChatOutboxEntry[] = [];\n /** The person's other conversations with messages waiting. */\n let elsewhere: readonly ChatOutboxElsewhere[] = [];\n /** Claims as the store last reported them (another window's hand-off in progress). */\n let claims = new Map<string, ChatOutboxClaim>();\n /** Messages kept on the device (absent: in the store's memory only, or no store at all). */\n const persisted = new Set<string>();\n /**\n * Messages this window wrote to the store (the message itself, or Retry's cleared failure) that\n * a listing may not show yet: how many listings had started when the write settled, or\n * `undefined` while it is pending. A listing that started before then is not believed about\n * them (it would drop a message just kept, or bring back a failure just cleared).\n */\n const unsettled = new Map<string, number | undefined>();\n let listingsStarted = 0;\n const phases = new Map<string, Phase>();\n /** Messages the person told to go now (the ask's Send, Retry): they skip the countdown. */\n const sendNow = new Set<string>();\n /** The return of Athena each message was last handed over in (one hand-off per return). */\n const handedInRegain = new Map<string, number>();\n let regain = 0;\n let wasReachable = reachability.get();\n let countdown: { endsAt: number; ids: readonly string[]; cancel: () => void } | null = null;\n let leaseTimer: (() => void) | null = null;\n /** The running attachment checks' time limits (stopped on dispose). */\n const checkTimers = new Set<() => void>();\n /**\n * Claimed messages ready to be handed over (their attachments checked, or none), each with\n * whether it goes on its own: they go in line, behind any older one still being claimed or\n * asked about ({@link releaseReady}).\n */\n const readyToGo = new Map<string, boolean>();\n let releasing = false;\n const asked = new Set<string>();\n const listeners = new Set<() => void>();\n let snapshot: ChatOutboxSnapshot = {\n rows: [],\n countdownEndsAt: null,\n durable: true,\n loaded,\n elsewhere,\n };\n\n const emit = (event: ChatOutboxEvent): void => {\n try {\n onEvent?.(event);\n } catch {\n // Telemetry never reaches the send path.\n }\n };\n\n const phaseOf = (id: string): Phase => phases.get(id) ?? { kind: \"idle\" };\n const isBusy = (phase: Phase): boolean =>\n phase.kind === \"claiming\" || phase.kind === \"handed\" || phase.kind === \"awaiting-thread\";\n /** Another window holds a live claim on it. */\n const claimedElsewhere = (id: string): boolean => {\n const claim = claims.get(id);\n return claim !== undefined && claim.by !== claimant && now() - claim.at < claimLeaseMs;\n };\n /** Whether a listing that started as listing number `listing` may speak for the message. */\n const settledFor = (id: string, listing: number): boolean => {\n if (!unsettled.has(id)) return true;\n const settledAt = unsettled.get(id);\n return settledAt !== undefined && listing > settledAt;\n };\n /** Whether this window's write of the message has not landed yet. */\n const writePending = (id: string): boolean => unsettled.has(id) && unsettled.get(id) === undefined;\n const markUnsettled = (id: string, write: Promise<unknown>): void => {\n unsettled.set(id, undefined);\n const settle = () => {\n if (!unsettled.has(id)) return;\n unsettled.set(id, listingsStarted);\n // A hand-off deferred for this write goes now.\n evaluate();\n };\n void write.then(settle, settle);\n };\n\n interface Line {\n /** The messages that would go next, in order, before any that must ask. */\n run: ChatOutboxEntry[];\n /** The head of the line when it waited more than a day. */\n askId: string | null;\n }\n\n const rowState = (entry: ChatOutboxEntry, line: Line): ChatOutboxRowState => {\n const phase = phaseOf(entry.id);\n if (phase.kind === \"failed\") {\n return { kind: \"failed\", reason: phase.reason, message: chatOutboxFailureMessage(phase.reason) };\n }\n if (isBusy(phase) || claimedElsewhere(entry.id)) return { kind: \"sending\" };\n // In line behind (or in) the running countdown: it goes when the countdown ends.\n if (countdown !== null && line.run.some((candidate) => candidate.id === entry.id)) {\n return { kind: \"countdown\", endsAt: countdown.endsAt };\n }\n if (line.askId === entry.id) return { kind: \"ask\" };\n return { kind: \"waiting\" };\n };\n\n /** The waiting messages in line to be sent, given that the gate is open. */\n const computeLine = (): Line => {\n const run: ChatOutboxEntry[] = [];\n for (const entry of entries) {\n const phase = phaseOf(entry.id);\n // A failed message is out of line until the person decides; one being handed (here or in\n // another window) is ahead of everything behind it.\n if (phase.kind === \"failed\") continue;\n if (isBusy(phase) || claimedElsewhere(entry.id)) continue;\n if (handedInRegain.get(entry.id) === regain && !sendNow.has(entry.id)) continue;\n if (!sendNow.has(entry.id) && now() - entry.queuedAt > askAfterMs) {\n return { run, askId: run.length === 0 ? entry.id : null };\n }\n run.push(entry);\n }\n return { run, askId: null };\n };\n\n const gateOpen = (): boolean => reachability.get() && foreground.get();\n\n /**\n * Re-read the snapshot; listeners hear from it only when something they would show changed.\n * The outbox re-evaluates on every notification of the thread it serves, and a host renders on\n * every snapshot it is handed (`useChatOutboxSnapshot`): a fresh object for an unchanged state\n * would send that host round again (render -> thread notification -> publish -> render).\n */\n const publish = (): void => {\n const line = gateOpen() ? computeLine() : { run: [], askId: null };\n const next: ChatOutboxSnapshot = {\n rows: entries.map((entry) => ({ entry, state: rowState(entry, line) })),\n countdownEndsAt: countdown?.endsAt ?? null,\n durable: entries.every((entry) => persisted.has(entry.id)),\n loaded,\n elsewhere,\n };\n if (sameChatOutboxSnapshot(snapshot, next)) return;\n snapshot = next;\n for (const listener of listeners) {\n try {\n listener();\n } catch {\n // A listener's fault never reaches the outbox.\n }\n }\n };\n\n const stopCountdown = (): void => {\n countdown?.cancel();\n countdown = null;\n };\n\n const dropEntry = (id: string): void => {\n entries = entries.filter((entry) => entry.id !== id);\n unsettled.delete(id);\n phases.delete(id);\n readyToGo.delete(id);\n sendNow.delete(id);\n persisted.delete(id);\n claims.delete(id);\n asked.delete(id);\n if (countdown?.ids.includes(id)) stopCountdown();\n };\n\n /** The conversation has the message now (its journal or its transcript): let the record go. */\n const letGo = (id: string, event: ChatOutboxEvent): void => {\n if (!entries.some((entry) => entry.id === id)) return;\n dropEntry(id);\n emit(event);\n if (store) void store.remove(threadId, id).catch(() => undefined);\n };\n\n /** \"Couldn't send\": kept with the message, so nothing sends it again until the person retries. */\n const fail = (id: string, reason: ChatOutboxFailureReason): void => {\n phases.set(id, { kind: \"failed\", reason });\n sendNow.delete(id);\n emit({ type: \"failed\", threadId, messageId: id, message: chatOutboxFailureMessage(reason), reason });\n if (store) {\n void store\n .markFailed({ threadId, id, claimant, failure: { reason, at: now() } })\n .catch(() => undefined);\n }\n };\n\n /** Let this window's claim go: the hand-off did not start. */\n const releaseClaim = (id: string): void => {\n if (store) void store.release({ threadId, id, claimant }).catch(() => undefined);\n };\n\n /** Statewire's journal took it, or it ended without reaching it. */\n const handOff = (entry: ChatOutboxEntry, auto: boolean): void => {\n // The conversation may have taken it since the last evaluation (an attach that just landed\n // shows a message an earlier page already delivered): never hand an id it holds.\n if (thread.heldIds().has(entry.id)) {\n letGo(entry.id, { type: \"adopted\", threadId, messageId: entry.id });\n return;\n }\n const readiness = thread.readiness();\n if (readiness === \"refused\") {\n fail(entry.id, \"refused\");\n return;\n }\n if (readiness === \"connecting\") {\n phases.set(entry.id, { kind: \"awaiting-thread\" });\n return;\n }\n // Its write (the message itself, or Retry's cleared failure) has not landed: claiming now\n // could find no record yet. It goes once the write settles (which evaluates again).\n if (writePending(entry.id)) {\n phases.set(entry.id, { kind: \"awaiting-thread\" });\n return;\n }\n sendNow.delete(entry.id);\n phases.set(entry.id, { kind: \"claiming\" });\n const claim: Promise<\"claimed\" | \"taken\" | \"gone\"> = store\n ? store.claim({ threadId, id: entry.id, claimant, now: now(), leaseMs: claimLeaseMs })\n : Promise.resolve(\"claimed\");\n void claim.then(\n (result) => {\n if (disposed || phaseOf(entry.id).kind !== \"claiming\") return;\n if (result === \"gone\") {\n // Cancelled, discarded or handed by another window meanwhile.\n dropEntry(entry.id);\n evaluate();\n return;\n }\n if (result === \"taken\") {\n phases.set(entry.id, { kind: \"idle\" });\n claims.set(entry.id, { by: \"elsewhere\", at: now() });\n evaluate();\n return;\n }\n // The claim took a moment to write: look again before anything reaches statewire.\n if (!stillGoes(entry)) return;\n checkThenDispatch(entry, auto);\n },\n () => {\n if (disposed || phaseOf(entry.id).kind !== \"claiming\") return;\n fail(entry.id, \"prepare\");\n evaluate();\n },\n );\n };\n\n /**\n * Whether a claimed hand-off goes on, looked at again once what it waited on landed (its claim,\n * its attachments' check): the conversation may show the message by now, or refuse it; the app\n * may have gone to the background or Athena out of reach (never sent from the background), or\n * the conversation be reattaching, and then the claim goes and the message waits again.\n */\n const stillGoes = (entry: ChatOutboxEntry): boolean => {\n if (thread.heldIds().has(entry.id)) {\n letGo(entry.id, { type: \"adopted\", threadId, messageId: entry.id });\n evaluate();\n return false;\n }\n const ready = thread.readiness();\n if (ready === \"refused\") {\n fail(entry.id, \"refused\");\n evaluate();\n return false;\n }\n if (!gateOpen() || ready !== \"ready\") {\n phases.set(entry.id, gateOpen() ? { kind: \"awaiting-thread\" } : { kind: \"idle\" });\n releaseClaim(entry.id);\n evaluate();\n return false;\n }\n return true;\n };\n\n /**\n * A claimed message's attachments are asked about before it is handed over: the claim holds\n * meanwhile (the row says Sending…, and no other window hands it), bounded by\n * `attachmentCheckTimeoutMs`. Only `ok` hands it over; a refusal, or a check Athena could not\n * answer, keeps it as \"Couldn't send\" until the person retries, edits or discards it.\n */\n const checkThenDispatch = (entry: ChatOutboxEntry, auto: boolean): void => {\n const attachments = entry.attachments ?? [];\n if (attachments.length === 0 || !checkAttachments) {\n readyToGo.set(entry.id, auto);\n releaseReady();\n return;\n }\n let answered = false;\n let stopTimer: () => void = () => undefined;\n const answer = (verdict: ChatOutboxAttachmentVerdict): void => {\n if (answered) return;\n answered = true;\n stopTimer();\n checkTimers.delete(stopTimer);\n if (disposed || phaseOf(entry.id).kind !== \"claiming\") return;\n if (!stillGoes(entry)) return;\n if (verdict === \"ok\") {\n readyToGo.set(entry.id, auto);\n releaseReady();\n return;\n }\n fail(entry.id, verdict === \"refused\" ? \"attachment\" : \"error\");\n evaluate();\n };\n stopTimer = setTimer(() => answer(\"unknown\"), attachmentCheckTimeoutMs);\n checkTimers.add(stopTimer);\n let checking: Promise<ChatOutboxAttachmentVerdict>;\n try {\n checking = checkAttachments(attachments);\n } catch (error) {\n checking = Promise.reject(error);\n }\n void checking.then(answer, () => answer(\"unknown\"));\n };\n\n /**\n * Hands the ready messages over in line: oldest first, and none while an older one is still\n * being claimed or asked about (a check takes a moment, and a later message must not reach the\n * conversation before it). One whose gate closed meanwhile waits again, and one that failed or\n * left lets the line move. Re-entered from `evaluate`, it runs once at a time.\n */\n function releaseReady(): void {\n if (releasing || disposed) return;\n releasing = true;\n try {\n for (const entry of [...entries]) {\n if (phaseOf(entry.id).kind !== \"claiming\") continue;\n const auto = readyToGo.get(entry.id);\n if (auto === undefined) return;\n readyToGo.delete(entry.id);\n if (stillGoes(entry)) dispatch(entry, auto);\n }\n } finally {\n releasing = false;\n }\n }\n\n const dispatch = (entry: ChatOutboxEntry, auto: boolean): void => {\n handedInRegain.set(entry.id, regain);\n phases.set(entry.id, { kind: \"handed\", journaled: false });\n emit({ type: \"handed\", threadId, messageId: entry.id, auto, ageMs: now() - entry.queuedAt });\n let sent: Promise<unknown>;\n try {\n sent = thread.send(entry);\n } catch (error) {\n sent = Promise.reject(error);\n }\n void sent.then(\n () => {\n if (disposed) return;\n letGo(entry.id, { type: \"delivered\", threadId, messageId: entry.id, status: \"accepted\" });\n evaluate();\n },\n (error: unknown) => {\n if (disposed) return;\n const phase = phaseOf(entry.id);\n // Statewire's journal took it: its own record decides what happens next (an exactly-once\n // replay, or its failed lane), and the settle update lets this record go.\n if (phase.kind === \"handed\" && phase.journaled) return;\n if (phase.kind !== \"handed\") return;\n const failure = failureOf(error);\n if (failure.cause !== undefined && AWAY_CAUSES.has(failure.cause)) {\n // The conversation went away before its journal took the message: it waits again.\n phases.set(entry.id, { kind: \"idle\" });\n handedInRegain.delete(entry.id);\n releaseClaim(entry.id);\n evaluate();\n return;\n }\n fail(entry.id, \"error\");\n evaluate();\n },\n );\n };\n\n const startCountdown = (run: readonly ChatOutboxEntry[]): void => {\n stopCountdown();\n const ids = run.map((entry) => entry.id);\n const endsAt = now() + countdownMs;\n const cancel = setTimer(() => {\n if (disposed || countdown?.endsAt !== endsAt) return;\n countdown = null;\n if (!gateOpen()) {\n evaluate();\n return;\n }\n for (const id of ids) {\n const entry = entries.find((candidate) => candidate.id === id);\n if (!entry) continue;\n const phase = phaseOf(id);\n if (phase.kind !== \"idle\" || claimedElsewhere(id)) continue;\n handOff(entry, true);\n }\n evaluate();\n }, countdownMs);\n countdown = { endsAt, ids, cancel };\n emit({ type: \"countdown-started\", threadId, count: ids.length });\n };\n\n const scheduleLeaseCheck = (): void => {\n leaseTimer?.();\n leaseTimer = null;\n let soonest = Number.POSITIVE_INFINITY;\n for (const [id, claim] of claims) {\n if (claim.by === claimant || !entries.some((entry) => entry.id === id)) continue;\n const expires = claim.at + claimLeaseMs - now();\n if (expires > 0) soonest = Math.min(soonest, expires);\n }\n if (Number.isFinite(soonest)) leaseTimer = setTimer(() => evaluate(), soonest + 1);\n };\n\n /** Re-derive everything from the inputs, then let the ready messages go in line. */\n function evaluate(): void {\n evaluateLine();\n releaseReady();\n }\n\n /** What the conversation holds, the gate, the line. */\n function evaluateLine(): void {\n if (disposed) return;\n const reachable = reachability.get();\n if (reachable && !wasReachable) regain += 1;\n wasReachable = reachable;\n\n if (loaded) {\n const held = thread.heldIds();\n for (const entry of [...entries]) {\n const phase = phaseOf(entry.id);\n if (phase.kind === \"handed\") {\n // This window's hand-off: the conversation folding it in means statewire's journal\n // took it; the record goes at `posted` (persisted before the wire) or a settle.\n if (held.has(entry.id)) phases.set(entry.id, { kind: \"handed\", journaled: true });\n continue;\n }\n if (phase.kind === \"claiming\") continue;\n // Not handed by this window, yet the conversation shows it: a journal adopted from an\n // earlier page (read back from storage, so durable), or the message already landed.\n if (held.has(entry.id)) letGo(entry.id, { type: \"adopted\", threadId, messageId: entry.id });\n }\n if (!gateOpen()) {\n // Gone to the background or out of reach before the conversation came back: those whose\n // countdown had ended go back in line, so returning starts the countdown over.\n for (const entry of entries) {\n if (phaseOf(entry.id).kind === \"awaiting-thread\") phases.set(entry.id, { kind: \"idle\" });\n }\n } else if (thread.readiness() !== \"connecting\") {\n // The conversation is back: the messages whose countdown already ended go now.\n for (const entry of entries) {\n if (phaseOf(entry.id).kind === \"awaiting-thread\") {\n phases.set(entry.id, { kind: \"idle\" });\n handOff(entry, true);\n }\n }\n }\n }\n\n if (!loaded || !gateOpen()) {\n stopCountdown();\n publish();\n return;\n }\n\n const line = computeLine();\n if (line.askId !== null && !asked.has(line.askId)) {\n asked.add(line.askId);\n const entry = entries.find((candidate) => candidate.id === line.askId);\n if (entry) emit({ type: \"asked\", threadId, messageId: entry.id, ageMs: now() - entry.queuedAt });\n }\n // Messages the person told to go now leave without a countdown, but never ahead of older\n // ones still in line: those count down first, and these follow them.\n const immediate: ChatOutboxEntry[] = [];\n for (const entry of line.run) {\n if (!sendNow.has(entry.id)) break;\n immediate.push(entry);\n }\n for (const entry of immediate) handOff(entry, false);\n const run = line.run.slice(immediate.length).filter((entry) => !sendNow.has(entry.id));\n if (run.length === 0) {\n stopCountdown();\n } else if (\n countdown === null ||\n countdown.ids.length !== run.length ||\n run.some((entry, index) => countdown?.ids[index] !== entry.id)\n ) {\n // A new countdown for a new line (a message added or cancelled starts it over).\n startCountdown(run);\n }\n scheduleLeaseCheck();\n publish();\n }\n\n const applyRecords = (records: readonly ChatOutboxRecord[], listing: number): void => {\n const mine = records.filter((record) => record.entry.threadId === threadId);\n const stored = new Map(mine.map((record) => [record.entry.id, record]));\n claims = new Map(\n mine.flatMap((record) => (record.claim ? [[record.entry.id, record.claim] as const] : [])),\n );\n for (const record of mine) {\n if (!settledFor(record.entry.id, listing)) continue;\n if (record.memoryOnly) persisted.delete(record.entry.id);\n else persisted.add(record.entry.id);\n // A hand-off that failed (in this window or another, now or before) stays failed until the\n // person retries it; this window's own failure is never cleared by a listing.\n const phase = phaseOf(record.entry.id);\n if (record.failure && phase.kind !== \"failed\" && !isBusy(phase)) {\n phases.set(record.entry.id, { kind: \"failed\", reason: record.failure.reason });\n }\n }\n // Messages the store no longer holds (sent, cancelled or discarded in another window) leave\n // this one too, unless this window is handing them or wrote them after the listing started.\n const kept = entries.filter(\n (entry) =>\n stored.has(entry.id) || !settledFor(entry.id, listing) || isBusy(phaseOf(entry.id)),\n );\n for (const entry of entries) {\n if (kept.includes(entry)) continue;\n if (countdown?.ids.includes(entry.id)) stopCountdown();\n phases.delete(entry.id);\n persisted.delete(entry.id);\n sendNow.delete(entry.id);\n }\n const known = new Set(kept.map((entry) => entry.id));\n const added = mine.filter((record) => !known.has(record.entry.id)).map((record) => record.entry);\n entries = [...kept, ...added].sort((a, b) => a.queuedAt - b.queuedAt);\n // Writes this listing has seen (it started after they settled) need no guard any more.\n for (const [id, settledAt] of unsettled) {\n if (settledAt !== undefined && listing > settledAt) unsettled.delete(id);\n }\n\n const byThread = new Map<string, { count: number; first: ChatOutboxEntry; newChat: boolean }>();\n for (const record of records) {\n if (record.entry.threadId === threadId) continue;\n const current = byThread.get(record.entry.threadId);\n if (current) {\n current.count += 1;\n current.newChat &&= record.entry.newChat;\n } else {\n byThread.set(record.entry.threadId, {\n count: 1,\n first: record.entry,\n newChat: record.entry.newChat,\n });\n }\n }\n elsewhere = [...byThread]\n .map(([otherThreadId, other]) => ({ threadId: otherThreadId, ...other }))\n .sort((a, b) => a.first.queuedAt - b.first.queuedAt);\n };\n\n let reading: Promise<void> | null = null;\n let readAgain = false;\n const reload = (): void => {\n if (!store || disposed) return;\n if (reading) {\n readAgain = true;\n return;\n }\n listingsStarted += 1;\n const listing = listingsStarted;\n reading = store\n .listAll()\n .then(\n (records) => {\n if (disposed) return;\n applyRecords(records, listing);\n loaded = true;\n },\n () => {\n loaded = true;\n },\n )\n .finally(() => {\n reading = null;\n if (disposed) return;\n evaluate();\n if (readAgain) {\n readAgain = false;\n reload();\n }\n });\n };\n\n const unsubscribers = [\n reachability.subscribe(evaluate),\n foreground.subscribe(evaluate),\n thread.subscribe(evaluate),\n ...(store ? [store.subscribe(reload)] : []),\n ];\n reload();\n publish();\n\n return {\n threadId,\n reachability,\n getSnapshot: () => snapshot,\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n shouldHold() {\n if (!reachability.get()) return true;\n // Until the conversation's kept messages are read, a new send could overtake one of them.\n if (!loaded) return true;\n // A message statewire's journal already took is ordered ahead of a new send by its seq;\n // one still waiting (or being claimed, or waiting for the conversation) is not.\n return entries.some((entry) => {\n const phase = phaseOf(entry.id);\n return phase.kind !== \"failed\" && !(phase.kind === \"handed\" && phase.journaled);\n });\n },\n hold(input) {\n const entry: ChatOutboxEntry = {\n id: newMessageId(),\n threadId,\n text: input.text,\n queuedAt: now(),\n newChat: typeof isNewChat === \"function\" ? isNewChat() : isNewChat,\n ...(input.composerMarkdown !== undefined && { composerMarkdown: input.composerMarkdown }),\n ...(input.composerHtml !== undefined && { composerHtml: input.composerHtml }),\n ...(input.metadata !== undefined && { metadata: input.metadata }),\n ...(input.files !== undefined && input.files.length > 0 && { files: input.files }),\n ...(input.attachments !== undefined &&\n input.attachments.length > 0 && { attachments: input.attachments }),\n };\n // Sent from in front while Athena answers, held only until the kept messages are read: it\n // goes at once when nothing older is waiting, after them otherwise.\n if (!loaded && gateOpen()) sendNow.add(entry.id);\n entries = [...entries, entry];\n if (store) {\n // Counted as kept from the start: a page closed before the write settles is the same\n // risk a draft has, and the row must not flash \"only while this page is open\".\n persisted.add(entry.id);\n const write = store.put(entry);\n markUnsettled(entry.id, write);\n void write.then(\n (kept) => {\n if (!kept) persisted.delete(entry.id);\n emit({ type: \"held\", threadId, messageId: entry.id, durable: kept });\n publish();\n },\n () => {\n persisted.delete(entry.id);\n emit({ type: \"held\", threadId, messageId: entry.id, durable: false });\n publish();\n },\n );\n } else {\n emit({ type: \"held\", threadId, messageId: entry.id, durable: false });\n }\n evaluate();\n return entry;\n },\n cancel(id) {\n const entry = entries.find((candidate) => candidate.id === id);\n if (!entry || isBusy(phaseOf(id))) return;\n dropEntry(id);\n emit({ type: \"cancelled\", threadId, messageId: id });\n if (store) void store.remove(threadId, id).catch(() => undefined);\n try {\n restore(entry);\n } finally {\n evaluate();\n }\n },\n send(id) {\n const entry = entries.find((candidate) => candidate.id === id);\n if (!entry || isBusy(phaseOf(id))) return;\n const wasFailed = phaseOf(id).kind === \"failed\";\n phases.set(id, { kind: \"idle\" });\n handedInRegain.delete(id);\n sendNow.add(id);\n if (wasFailed && store) {\n const write = store.clearFailure({ threadId, id });\n markUnsettled(id, write);\n void write.catch(() => undefined);\n }\n evaluate();\n },\n discard(id) {\n const entry = entries.find((candidate) => candidate.id === id);\n if (!entry || isBusy(phaseOf(id))) return;\n dropEntry(id);\n emit({ type: \"discarded\", threadId, messageId: id });\n if (store) void store.remove(threadId, id).catch(() => undefined);\n evaluate();\n },\n observeCommand(update) {\n if (disposed) return;\n const id = chatOutboxCommandMessageId(update.params);\n if (id === null) return;\n const phase = phaseOf(id);\n if (phase.kind === \"handed\") {\n if (update.status === \"queued\") {\n phases.set(id, { kind: \"handed\", journaled: true });\n return;\n }\n // Persisted before the wire (`posted`), admitted (`applied`), or settled: statewire owns\n // the message now (its exactly-once replay, or its failed lane).\n letGo(id, { type: \"delivered\", threadId, messageId: id, status: update.status });\n evaluate();\n return;\n }\n // A statement for a waiting message this window did not hand: a journal adopted from an\n // earlier page. The conversation folds it in, and the next evaluation lets the record go.\n if (entries.some((entry) => entry.id === id)) evaluate();\n },\n openThread: openThread ?? null,\n dispose() {\n if (disposed) return;\n disposed = true;\n stopCountdown();\n leaseTimer?.();\n for (const stop of checkTimers) stop();\n checkTimers.clear();\n readyToGo.clear();\n for (const unsubscribe of unsubscribers) unsubscribe();\n listeners.clear();\n // A claim taken for a hand-off that never started is released, so another window (or the\n // next mount) need not wait out the lease. One whose hand-off started stays until its lease\n // ends: statewire may still journal the statement, and the conversation then shows it.\n if (store) {\n for (const [id, phase] of phases) {\n if (phase.kind === \"claiming\") releaseClaim(id);\n }\n }\n },\n };\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"chat-outbox.cjs","sources":["../../../../agent-runtime/src/chat-outbox.ts"],"sourcesContent":["import type { ChatOutboxAttachmentCheck, ChatOutboxAttachmentVerdict } from \"./chat-outbox-attachments\";\nimport type {\n ChatOutboxAttachment,\n ChatOutboxClaim,\n ChatOutboxEntry,\n ChatOutboxFailureReason,\n ChatOutboxFile,\n ChatOutboxRecord,\n ChatOutboxStore,\n} from \"./chat-outbox-store\";\n\n/**\n * The chat outbox: a message typed while Athena can't be reached waits on this device and is sent\n * once Athena answers again, while the app is in front, after a few seconds the person can cancel\n * (decision 5 of the offline plan, `docs/plans/2026-10-27-offline-everywhere-plan.md`).\n *\n * - **Held before the conversation's journal, never inside it.** A send made while Athena can't be\n * reached does not reach statewire at all: statewire would replay it the moment its connection\n * returns (from a hidden tab too) and has no way to take a journaled statement back. The message\n * waits here instead, and reaches statewire only when this controller hands it over, under the\n * id minted when it was typed.\n * - **Sent in front, with a countdown.** Once Athena is reachable and the app is in front (a visible\n * tab, an active app, a focused desktop window), the waiting messages count down\n * ({@link CHAT_OUTBOX_COUNTDOWN_MS}) with a Cancel that gives the text back to the composer; then\n * each is handed over once. Going to the background or losing Athena stops the countdown; it\n * starts over, in full, when both are back.\n * - **A day is the limit for sending on its own.** A message that waited more than\n * {@link CHAT_OUTBOX_ASK_AFTER_MS} asks first (Send / Edit / Discard); the messages behind it wait\n * for the answer, so nothing is sent out of order.\n * - **Once per message, never a loop.** A message is handed over at most once per return of Athena.\n * A hand-off that fails before the conversation took it (anything but the conversation going\n * away) stays as \"Couldn't send\" with Retry and Discard, kept with the message so no window and\n * no later mount sends it again on its own. Once the conversation's journal took it, statewire\n * owns it: it delivers it exactly once, and a refusal lands in the conversation's own failed\n * lane (Retry / Discard there).\n * - **One sender per message.** A hand-off claims the message in the store first, atomically\n * across windows ({@link CHAT_OUTBOX_CLAIM_LEASE_MS}), so two windows on the same conversation\n * never hand the same message twice (a second hand-off of an id whose run is in flight would be\n * read as an edit of that turn). The gate is checked again once the claim is written: a tab\n * hidden (or an Athena lost) meanwhile lets the claim go, and the message waits again.\n * - **Nothing overtakes.** Until the conversation's kept messages are read, a new send waits too\n * (it goes at once if nothing older turns up), and a message the person told to go now still\n * goes after the older ones in line.\n * - **Kept until it is safe to let go.** The waiting record is removed only once statewire has the\n * statement durably (`posted`, `applied` or settled), or once the conversation shows the message\n * (a journal adopted from an earlier page, or the message already on the transcript).\n * - **Its attachments go with it, still valid.** A message keeps the files that had finished\n * uploading before Athena went out of reach (references to files already in Athena; one picked\n * while it can't be reached is never attached). Once the message is claimed they are asked about\n * ({@link ChatOutboxOptions.checkAttachments}): one Athena refuses (deleted, or no longer shared\n * with the person) keeps the message as \"Couldn't send\" with Retry, Edit and Discard, and a check\n * Athena could not answer keeps it the same way as a send error. Nothing goes unchecked, and the\n * text is never lost.\n * - **Its own send settings, never another's.** A message keeps the per-send settings it was typed\n * with ({@link ChatOutboxEntry.sendContext}), and the host stages them as it is handed over (its\n * `handed` event carries them). Every send of a conversation rides one ambient context, so a host\n * with settings that can differ between messages gives the outbox a send gate\n * ({@link ChatOutboxOptions.sendGate}): only messages whose settings are the same are handed over\n * together, and a message whose settings differ from a send the conversation has not taken yet\n * waits, in line, until it has.\n */\n\n/** How long the countdown before a waiting message is sent lasts. */\nexport const CHAT_OUTBOX_COUNTDOWN_MS = 5_000;\n/** A message that waited longer than this asks before it is sent. */\nexport const CHAT_OUTBOX_ASK_AFTER_MS = 24 * 60 * 60 * 1000;\n/** How long a window's claim on a message holds against another window's hand-off. */\nexport const CHAT_OUTBOX_CLAIM_LEASE_MS = 30_000;\n/** How long the check of a message's attachments may take before it counts as unanswered. */\nexport const CHAT_OUTBOX_ATTACHMENT_CHECK_TIMEOUT_MS = 15_000;\n\n/**\n * Why a sent message's Edit is not offered while Athena can't be reached (its tooltip, or the hint\n * under it where there are no tooltips). An edit rewinds the conversation and re-runs it: a\n * journaled one would replay the moment the connection returns, from a hidden window too, so an\n * edit never waits the way a new message does.\n */\nexport const CHAT_EDIT_UNREACHABLE_LABEL = \"Can't edit while Athena can't be reached\";\n\n/** What an edit submitted while Athena can't be reached says: the edit stays where it was typed. */\nexport const CHAT_EDIT_KEPT_MESSAGE =\n \"Can't reach Athena right now. Your edit stays here — submit it once Athena is back.\";\n\n/** An edit submitted while Athena can't be reached: refused, never queued (its message says so). */\nexport class ChatEditUnreachableError extends Error {\n constructor() {\n super(CHAT_EDIT_KEPT_MESSAGE);\n this.name = \"ChatEditUnreachableError\";\n }\n}\n\n/** A boolean the host reports and notifies on: Athena's reachability, the app being in front. */\nexport interface ChatOutboxSignal {\n get(): boolean;\n subscribe(listener: () => void): () => void;\n}\n\n/**\n * Whether a hand-off reaches the conversation now: `ready` (attached, a never-started chat that\n * dials on its first send, or an idle channel that reattaches on demand — in each case what the\n * conversation shows is the server's, so a message missing from it never reached it),\n * `connecting` (reattaching: what it shows may be a remembered snapshot), `refused` (the host\n * refused the conversation: stopped, or parked on an authorization denial).\n */\nexport type ChatOutboxThreadReadiness = \"ready\" | \"connecting\" | \"refused\";\n\n/** The conversation the outbox hands messages to (`chat-outbox-thread.ts` adapts a statewire thread). */\nexport interface ChatOutboxThreadPort {\n readiness(): ChatOutboxThreadReadiness;\n /**\n * Message ids the conversation shows anywhere: its transcript, its queue and steer lanes, the\n * run being dispatched, its failed lane — this page's journal included (statewire folds a\n * journaled send into them, one adopted from an earlier page too).\n */\n heldIds(): ReadonlySet<string>;\n /** Hand the message to the conversation under its own id; settles with the server's verdict. */\n send(entry: ChatOutboxEntry): Promise<unknown>;\n subscribe(listener: () => void): () => void;\n}\n\nexport type ChatOutboxRowState =\n /** Not sent yet: Athena can't be reached, or the app is not in front. */\n | { kind: \"waiting\" }\n /** Athena is back: the message is sent when the countdown ends, unless cancelled. */\n | { kind: \"countdown\"; endsAt: number }\n /** It waited more than a day: Send, Edit or Discard. */\n | { kind: \"ask\" }\n /** Being handed to the conversation (by this window or another). */\n | { kind: \"sending\" }\n /** The hand-off failed for a reason other than Athena being unreachable. */\n | { kind: \"failed\"; reason: ChatOutboxFailureReason; message: string };\n\nexport interface ChatOutboxRow {\n entry: ChatOutboxEntry;\n state: ChatOutboxRowState;\n}\n\n/** Another conversation of the same person with messages waiting (the tray offers to open it). */\nexport interface ChatOutboxElsewhere {\n threadId: string;\n /** How many messages wait there. */\n count: number;\n /** The oldest of them: its text names the conversation. */\n first: ChatOutboxEntry;\n /**\n * Every message waiting there was typed into a chat that had never started, so it opens as the\n * new chat it still is (one typed after the chat started says it has a session now).\n */\n newChat: boolean;\n}\n\nexport interface ChatOutboxSnapshot {\n rows: readonly ChatOutboxRow[];\n /** When the running countdown ends (epoch ms), or `null`. */\n countdownEndsAt: number | null;\n /** Whether every waiting message is kept on this device (false: only while the page stays open). */\n durable: boolean;\n /** Whether the thread's kept messages have been read. */\n loaded: boolean;\n /** The person's other conversations with messages waiting, oldest first. */\n elsewhere: readonly ChatOutboxElsewhere[];\n}\n\n/** What a failed row says. */\nexport function chatOutboxFailureMessage(reason: ChatOutboxFailureReason): string {\n switch (reason) {\n case \"refused\":\n return \"This conversation can't be reached right now.\";\n case \"prepare\":\n return \"Couldn't prepare this message to send.\";\n case \"attachment\":\n return \"An attachment is no longer available to you.\";\n default:\n return \"Couldn't send this message.\";\n }\n}\n\nexport interface ChatOutboxInput {\n text: string;\n composerMarkdown?: string;\n /** The draft as an HTML composer holds it (the phone's), given back exactly on Cancel. */\n composerHtml?: string;\n metadata?: Record<string, unknown>;\n /** The per-send settings the host stages when the message goes ({@link ChatOutboxEntry.sendContext}). */\n sendContext?: Record<string, unknown>;\n files?: ChatOutboxFile[];\n /** Files already in Athena the message carries, asked about before it goes. */\n attachments?: ChatOutboxAttachment[];\n}\n\nexport type ChatOutboxEvent =\n | { type: \"held\"; threadId: string; messageId: string; durable: boolean }\n | { type: \"countdown-started\"; threadId: string; count: number }\n | { type: \"asked\"; threadId: string; messageId: string; ageMs: number }\n | {\n type: \"handed\";\n threadId: string;\n messageId: string;\n auto: boolean;\n ageMs: number;\n /** The message's own send settings, raised just before the hand-off so the host stages them. */\n sendContext?: Record<string, unknown>;\n }\n | { type: \"delivered\"; threadId: string; messageId: string; status: string }\n | { type: \"adopted\"; threadId: string; messageId: string }\n | { type: \"failed\"; threadId: string; messageId: string; message: string; reason: ChatOutboxFailureReason }\n | { type: \"cancelled\"; threadId: string; messageId: string }\n | { type: \"discarded\"; threadId: string; messageId: string };\n\n/**\n * A host's say over which waiting messages go together, and when (Olympus: the agents a message\n * names ride the conversation's one ambient context, so a message naming other agents than a send\n * still on its way waits for it). Without one, every message goes as soon as it may.\n */\nexport interface ChatOutboxSendGate {\n /** Consecutive messages with the same key may be handed over together; a different one waits. */\n keyOf(entry: ChatOutboxEntry): string;\n /** Whether the message may be handed over now. */\n mayHandOff(entry: ChatOutboxEntry): boolean;\n /** Called whenever {@link mayHandOff} may have changed. */\n subscribe(listener: () => void): () => void;\n}\n\n/** The command-lifecycle update statewire reports (`onCommandChange`), structurally. */\nexport interface ChatOutboxCommandUpdate {\n status: \"queued\" | \"posted\" | \"applied\" | \"settled\";\n params?: readonly unknown[];\n failure?: { fate: string; cause?: string } | undefined;\n}\n\nexport interface ChatOutbox {\n readonly threadId: string;\n getSnapshot(): ChatOutboxSnapshot;\n subscribe(listener: () => void): () => void;\n /**\n * Whether Athena can be reached, as this outbox reads it (the host's model; in the SDK the\n * conversation's own channel too). An edit of a sent message waits on it as a send does: while it\n * says no, the Edit action is not offered and a submitted edit is refused\n * ({@link CHAT_EDIT_UNREACHABLE_LABEL}, {@link ChatEditUnreachableError}).\n */\n readonly reachability: ChatOutboxSignal;\n /**\n * Whether a send made now must wait here: Athena can't be reached, or earlier messages of this\n * conversation are still waiting (a later message never overtakes them).\n */\n shouldHold(): boolean;\n /** Keep a message typed now; it waits until it can be sent. */\n hold(input: ChatOutboxInput): ChatOutboxEntry;\n /** Cancel (or the ask's Edit): the message leaves the outbox and its text goes back to the composer. */\n cancel(id: string): void;\n /** The ask's Send, a failed row's Retry: hand it over now (one attempt; waits if Athena is out of reach). */\n send(id: string): void;\n discard(id: string): void;\n /** Feed statewire's command lifecycle (`onCommandChange`). */\n observeCommand(update: ChatOutboxCommandUpdate): void;\n /** Open another conversation the tray lists as holding waiting messages (`null`: the host cannot). */\n readonly openThread: ((target: { threadId: string; newChat: boolean }) => void) | null;\n dispose(): void;\n}\n\nexport interface ChatOutboxOptions {\n threadId: string;\n /**\n * The conversation has never started (a new chat): what waits reopens with it. Read when a\n * message is held, so a chat that started since stamps its later messages as started.\n */\n isNewChat: boolean | (() => boolean);\n /** Where waiting messages are kept for this person; `null` keeps them in memory only. */\n store: ChatOutboxStore | null;\n reachability: ChatOutboxSignal;\n foreground: ChatOutboxSignal;\n thread: ChatOutboxThreadPort;\n /** Give a cancelled message's text back to the composer. */\n restore: (entry: ChatOutboxEntry) => void;\n /**\n * Open another of the person's conversations that holds waiting messages (the tray's Open);\n * omitted where the host cannot switch conversations (an embedded chat).\n */\n openThread?: (target: { threadId: string; newChat: boolean }) => void;\n onEvent?: (event: ChatOutboxEvent) => void;\n /**\n * Asks Athena whether a waiting message's attachments can still be opened, right before it goes\n * (`createChatOutboxAttachmentCheck`). `refused` keeps the message as \"Couldn't send\" (Retry,\n * Edit, Discard); `unknown` too, as a send error. Omitted: attachments go unchecked.\n */\n checkAttachments?: ChatOutboxAttachmentCheck;\n /**\n * Which waiting messages go together, and when (a message whose send settings differ from a send\n * the conversation has not taken yet waits for it). Omitted: every message goes once it may.\n */\n sendGate?: ChatOutboxSendGate;\n now?: () => number;\n countdownMs?: number;\n askAfterMs?: number;\n claimLeaseMs?: number;\n /** How long the attachment check may take before it counts as unanswered. */\n attachmentCheckTimeoutMs?: number;\n newMessageId?: () => string;\n /** Timer seam (tests): schedule `task` after `ms`, returning what cancels it. */\n setTimer?: (task: () => void, ms: number) => () => void;\n}\n\ntype Phase =\n /** Waiting in line. */\n | { kind: \"idle\" }\n /**\n * The countdown ended (or the person said Send) while the conversation was reattaching, or\n * while the message's own write was still landing: it goes as soon as both are done.\n */\n | { kind: \"awaiting-thread\" }\n /** Claiming it in the store before the hand-off. */\n | { kind: \"claiming\" }\n /** Handed to the conversation; `journaled` once statewire's journal took it. */\n | { kind: \"handed\"; journaled: boolean }\n | { kind: \"failed\"; reason: ChatOutboxFailureReason };\n\n/** Fates statewire settles a send with when the conversation went away under it (not a failure). */\nconst AWAY_CAUSES: ReadonlySet<string> = new Set([\"unmount\", \"stream-gone\"]);\n\n/** The message id a run send carries, off a command update's params. */\nexport function chatOutboxCommandMessageId(params: readonly unknown[] | undefined): string | null {\n const first = params?.[0] as { message?: { id?: unknown } } | undefined;\n const id = first?.message?.id;\n return typeof id === \"string\" ? id : null;\n}\n\nfunction failureOf(error: unknown): { fate?: string; cause?: string; message: string } {\n const failure = (error as { failure?: { fate?: unknown; cause?: unknown } } | null)?.failure;\n const fate = typeof failure?.fate === \"string\" ? failure.fate : undefined;\n const cause = typeof failure?.cause === \"string\" ? failure.cause : undefined;\n const message = error instanceof Error ? error.message : String(error);\n return { fate, cause, message };\n}\n\nfunction defaultMessageId(): string {\n const host = globalThis as { crypto?: { randomUUID?: () => string } };\n if (typeof host.crypto?.randomUUID === \"function\") return host.crypto.randomUUID();\n // Hosts without randomUUID (none Athena ships today): a random v4-shaped id.\n const hex = Array.from({ length: 32 }, () => Math.floor(Math.random() * 16).toString(16)).join(\"\");\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-4${hex.slice(13, 16)}-a${hex.slice(17, 20)}-${hex.slice(20, 32)}`;\n}\n\nfunction sameChatOutboxRowState(a: ChatOutboxRowState, b: ChatOutboxRowState): boolean {\n if (a.kind !== b.kind) return false;\n switch (a.kind) {\n case \"countdown\":\n return a.endsAt === (b as typeof a).endsAt;\n case \"failed\":\n return a.reason === (b as typeof a).reason && a.message === (b as typeof a).message;\n default:\n return true;\n }\n}\n\n/** Whether two listings of a message show the same thing (another chat's preview is re-read per listing). */\nfunction sameChatOutboxEntry(a: ChatOutboxEntry, b: ChatOutboxEntry): boolean {\n return (\n a === b ||\n (a.id === b.id &&\n a.threadId === b.threadId &&\n a.text === b.text &&\n a.newChat === b.newChat &&\n a.queuedAt === b.queuedAt &&\n a.composerMarkdown === b.composerMarkdown)\n );\n}\n\n/** Whether two snapshots show the same thing (the rows' entries by identity: a listing keeps them). */\nexport function sameChatOutboxSnapshot(a: ChatOutboxSnapshot, b: ChatOutboxSnapshot): boolean {\n if (a === b) return true;\n if (a.countdownEndsAt !== b.countdownEndsAt || a.durable !== b.durable || a.loaded !== b.loaded) return false;\n if (a.rows.length !== b.rows.length || a.elsewhere.length !== b.elsewhere.length) return false;\n for (let index = 0; index < a.rows.length; index += 1) {\n const x = a.rows[index];\n const y = b.rows[index];\n if (!x || !y || x.entry !== y.entry || !sameChatOutboxRowState(x.state, y.state)) return false;\n }\n for (let index = 0; index < a.elsewhere.length; index += 1) {\n const x = a.elsewhere[index];\n const y = b.elsewhere[index];\n if (\n !x ||\n !y ||\n x.threadId !== y.threadId ||\n x.count !== y.count ||\n x.newChat !== y.newChat ||\n !sameChatOutboxEntry(x.first, y.first)\n ) {\n return false;\n }\n }\n return true;\n}\n\nexport function createChatOutbox(options: ChatOutboxOptions): ChatOutbox {\n const {\n threadId,\n isNewChat,\n store,\n reachability,\n foreground,\n thread,\n restore,\n openThread,\n onEvent,\n checkAttachments,\n sendGate,\n now = () => Date.now(),\n countdownMs = CHAT_OUTBOX_COUNTDOWN_MS,\n askAfterMs = CHAT_OUTBOX_ASK_AFTER_MS,\n claimLeaseMs = CHAT_OUTBOX_CLAIM_LEASE_MS,\n attachmentCheckTimeoutMs = CHAT_OUTBOX_ATTACHMENT_CHECK_TIMEOUT_MS,\n newMessageId = defaultMessageId,\n setTimer = (task, ms) => {\n const timer = setTimeout(task, ms);\n return () => clearTimeout(timer);\n },\n } = options;\n\n /** This controller's name on the claims it takes. */\n const claimant = newMessageId();\n let disposed = false;\n let loaded = store === null;\n /** The thread's waiting messages, oldest first. */\n let entries: ChatOutboxEntry[] = [];\n /** The person's other conversations with messages waiting. */\n let elsewhere: readonly ChatOutboxElsewhere[] = [];\n /** Claims as the store last reported them (another window's hand-off in progress). */\n let claims = new Map<string, ChatOutboxClaim>();\n /** Messages kept on the device (absent: in the store's memory only, or no store at all). */\n const persisted = new Set<string>();\n /**\n * Messages this window wrote to the store (the message itself, or Retry's cleared failure) that\n * a listing may not show yet: how many listings had started when the write settled, or\n * `undefined` while it is pending. A listing that started before then is not believed about\n * them (it would drop a message just kept, or bring back a failure just cleared).\n */\n const unsettled = new Map<string, number | undefined>();\n let listingsStarted = 0;\n const phases = new Map<string, Phase>();\n /** Messages the person told to go now (the ask's Send, Retry): they skip the countdown. */\n const sendNow = new Set<string>();\n /** The return of Athena each message was last handed over in (one hand-off per return). */\n const handedInRegain = new Map<string, number>();\n let regain = 0;\n let wasReachable = reachability.get();\n let countdown: { endsAt: number; ids: readonly string[]; cancel: () => void } | null = null;\n let leaseTimer: (() => void) | null = null;\n /** The running attachment checks' time limits (stopped on dispose). */\n const checkTimers = new Set<() => void>();\n /**\n * Claimed messages ready to be handed over (their attachments checked, or none), each with\n * whether it goes on its own: they go in line, behind any older one still being claimed or\n * asked about ({@link releaseReady}).\n */\n const readyToGo = new Map<string, boolean>();\n let releasing = false;\n const asked = new Set<string>();\n const listeners = new Set<() => void>();\n let snapshot: ChatOutboxSnapshot = {\n rows: [],\n countdownEndsAt: null,\n durable: true,\n loaded,\n elsewhere,\n };\n\n const emit = (event: ChatOutboxEvent): void => {\n try {\n onEvent?.(event);\n } catch {\n // Telemetry never reaches the send path.\n }\n };\n\n const phaseOf = (id: string): Phase => phases.get(id) ?? { kind: \"idle\" };\n const isBusy = (phase: Phase): boolean =>\n phase.kind === \"claiming\" || phase.kind === \"handed\" || phase.kind === \"awaiting-thread\";\n /** Another window holds a live claim on it. */\n const claimedElsewhere = (id: string): boolean => {\n const claim = claims.get(id);\n return claim !== undefined && claim.by !== claimant && now() - claim.at < claimLeaseMs;\n };\n /** Whether a listing that started as listing number `listing` may speak for the message. */\n const settledFor = (id: string, listing: number): boolean => {\n if (!unsettled.has(id)) return true;\n const settledAt = unsettled.get(id);\n return settledAt !== undefined && listing > settledAt;\n };\n /** Whether this window's write of the message has not landed yet. */\n const writePending = (id: string): boolean => unsettled.has(id) && unsettled.get(id) === undefined;\n const markUnsettled = (id: string, write: Promise<unknown>): void => {\n unsettled.set(id, undefined);\n const settle = () => {\n if (!unsettled.has(id)) return;\n unsettled.set(id, listingsStarted);\n // A hand-off deferred for this write goes now.\n evaluate();\n };\n void write.then(settle, settle);\n };\n\n interface Line {\n /** The messages that would go next, in order, before any that must ask. */\n run: ChatOutboxEntry[];\n /** The head of the line when it waited more than a day. */\n askId: string | null;\n }\n\n const rowState = (entry: ChatOutboxEntry, line: Line): ChatOutboxRowState => {\n const phase = phaseOf(entry.id);\n if (phase.kind === \"failed\") {\n return { kind: \"failed\", reason: phase.reason, message: chatOutboxFailureMessage(phase.reason) };\n }\n if (isBusy(phase) || claimedElsewhere(entry.id)) return { kind: \"sending\" };\n // In line behind (or in) the running countdown: it goes when the countdown ends.\n if (countdown !== null && line.run.some((candidate) => candidate.id === entry.id)) {\n return { kind: \"countdown\", endsAt: countdown.endsAt };\n }\n if (line.askId === entry.id) return { kind: \"ask\" };\n return { kind: \"waiting\" };\n };\n\n /** Messages with the same key may be handed over together (the host's send gate decides). */\n const keyOf = (entry: ChatOutboxEntry): string => sendGate?.keyOf(entry) ?? \"\";\n /** Whether the host lets the message be handed over now. */\n const mayHandOff = (entry: ChatOutboxEntry): boolean => sendGate?.mayHandOff(entry) ?? true;\n\n /** The waiting messages in line to be sent, given that the gate is open. */\n const computeLine = (): Line => {\n const run: ChatOutboxEntry[] = [];\n /** The send key of the line's head: a message with another key waits behind it. */\n let lineKey: string | null = null;\n for (const entry of entries) {\n const phase = phaseOf(entry.id);\n // A failed message is out of line until the person decides; one being handed (here or in\n // another window) is ahead of everything behind it.\n if (phase.kind === \"failed\") continue;\n const key = keyOf(entry);\n if (lineKey === null) lineKey = key;\n else if (key !== lineKey) break;\n if (isBusy(phase) || claimedElsewhere(entry.id)) continue;\n if (handedInRegain.get(entry.id) === regain && !sendNow.has(entry.id)) continue;\n if (!sendNow.has(entry.id) && now() - entry.queuedAt > askAfterMs) {\n return { run, askId: run.length === 0 ? entry.id : null };\n }\n run.push(entry);\n }\n // The line waits while the host holds its head (a send naming other settings is on its way).\n const head = run[0];\n if (head !== undefined && !mayHandOff(head)) return { run: [], askId: null };\n return { run, askId: null };\n };\n\n const gateOpen = (): boolean => reachability.get() && foreground.get();\n\n /**\n * Re-read the snapshot; listeners hear from it only when something they would show changed.\n * The outbox re-evaluates on every notification of the thread it serves, and a host renders on\n * every snapshot it is handed (`useChatOutboxSnapshot`): a fresh object for an unchanged state\n * would send that host round again (render -> thread notification -> publish -> render).\n */\n const publish = (): void => {\n const line = gateOpen() ? computeLine() : { run: [], askId: null };\n const next: ChatOutboxSnapshot = {\n rows: entries.map((entry) => ({ entry, state: rowState(entry, line) })),\n countdownEndsAt: countdown?.endsAt ?? null,\n durable: entries.every((entry) => persisted.has(entry.id)),\n loaded,\n elsewhere,\n };\n if (sameChatOutboxSnapshot(snapshot, next)) return;\n snapshot = next;\n for (const listener of listeners) {\n try {\n listener();\n } catch {\n // A listener's fault never reaches the outbox.\n }\n }\n };\n\n const stopCountdown = (): void => {\n countdown?.cancel();\n countdown = null;\n };\n\n const dropEntry = (id: string): void => {\n entries = entries.filter((entry) => entry.id !== id);\n unsettled.delete(id);\n phases.delete(id);\n readyToGo.delete(id);\n sendNow.delete(id);\n persisted.delete(id);\n claims.delete(id);\n asked.delete(id);\n if (countdown?.ids.includes(id)) stopCountdown();\n };\n\n /** The conversation has the message now (its journal or its transcript): let the record go. */\n const letGo = (id: string, event: ChatOutboxEvent): void => {\n if (!entries.some((entry) => entry.id === id)) return;\n dropEntry(id);\n emit(event);\n if (store) void store.remove(threadId, id).catch(() => undefined);\n };\n\n /** \"Couldn't send\": kept with the message, so nothing sends it again until the person retries. */\n const fail = (id: string, reason: ChatOutboxFailureReason): void => {\n phases.set(id, { kind: \"failed\", reason });\n sendNow.delete(id);\n emit({ type: \"failed\", threadId, messageId: id, message: chatOutboxFailureMessage(reason), reason });\n if (store) {\n void store\n .markFailed({ threadId, id, claimant, failure: { reason, at: now() } })\n .catch(() => undefined);\n }\n };\n\n /** Let this window's claim go: the hand-off did not start. */\n const releaseClaim = (id: string): void => {\n if (store) void store.release({ threadId, id, claimant }).catch(() => undefined);\n };\n\n /** Statewire's journal took it, or it ended without reaching it. */\n const handOff = (entry: ChatOutboxEntry, auto: boolean): void => {\n // The conversation may have taken it since the last evaluation (an attach that just landed\n // shows a message an earlier page already delivered): never hand an id it holds.\n if (thread.heldIds().has(entry.id)) {\n letGo(entry.id, { type: \"adopted\", threadId, messageId: entry.id });\n return;\n }\n const readiness = thread.readiness();\n if (readiness === \"refused\") {\n fail(entry.id, \"refused\");\n return;\n }\n if (readiness === \"connecting\") {\n phases.set(entry.id, { kind: \"awaiting-thread\" });\n return;\n }\n // Its write (the message itself, or Retry's cleared failure) has not landed: claiming now\n // could find no record yet. It goes once the write settles (which evaluates again).\n if (writePending(entry.id)) {\n phases.set(entry.id, { kind: \"awaiting-thread\" });\n return;\n }\n sendNow.delete(entry.id);\n phases.set(entry.id, { kind: \"claiming\" });\n const claim: Promise<\"claimed\" | \"taken\" | \"gone\"> = store\n ? store.claim({ threadId, id: entry.id, claimant, now: now(), leaseMs: claimLeaseMs })\n : Promise.resolve(\"claimed\");\n void claim.then(\n (result) => {\n if (disposed || phaseOf(entry.id).kind !== \"claiming\") return;\n if (result === \"gone\") {\n // Cancelled, discarded or handed by another window meanwhile.\n dropEntry(entry.id);\n evaluate();\n return;\n }\n if (result === \"taken\") {\n phases.set(entry.id, { kind: \"idle\" });\n claims.set(entry.id, { by: \"elsewhere\", at: now() });\n evaluate();\n return;\n }\n // The claim took a moment to write: look again before anything reaches statewire.\n if (!stillGoes(entry)) return;\n checkThenDispatch(entry, auto);\n },\n () => {\n if (disposed || phaseOf(entry.id).kind !== \"claiming\") return;\n fail(entry.id, \"prepare\");\n evaluate();\n },\n );\n };\n\n /**\n * Whether a claimed hand-off goes on, looked at again once what it waited on landed (its claim,\n * its attachments' check): the conversation may show the message by now, or refuse it; the app\n * may have gone to the background or Athena out of reach (never sent from the background), or\n * the conversation be reattaching, and then the claim goes and the message waits again.\n */\n const stillGoes = (entry: ChatOutboxEntry): boolean => {\n if (thread.heldIds().has(entry.id)) {\n letGo(entry.id, { type: \"adopted\", threadId, messageId: entry.id });\n evaluate();\n return false;\n }\n const ready = thread.readiness();\n if (ready === \"refused\") {\n fail(entry.id, \"refused\");\n evaluate();\n return false;\n }\n if (!gateOpen() || ready !== \"ready\") {\n phases.set(entry.id, gateOpen() ? { kind: \"awaiting-thread\" } : { kind: \"idle\" });\n releaseClaim(entry.id);\n evaluate();\n return false;\n }\n return true;\n };\n\n /**\n * A claimed message's attachments are asked about before it is handed over: the claim holds\n * meanwhile (the row says Sending…, and no other window hands it), bounded by\n * `attachmentCheckTimeoutMs`. Only `ok` hands it over; a refusal, or a check Athena could not\n * answer, keeps it as \"Couldn't send\" until the person retries, edits or discards it.\n */\n const checkThenDispatch = (entry: ChatOutboxEntry, auto: boolean): void => {\n const attachments = entry.attachments ?? [];\n if (attachments.length === 0 || !checkAttachments) {\n readyToGo.set(entry.id, auto);\n releaseReady();\n return;\n }\n let answered = false;\n let stopTimer: () => void = () => undefined;\n const answer = (verdict: ChatOutboxAttachmentVerdict): void => {\n if (answered) return;\n answered = true;\n stopTimer();\n checkTimers.delete(stopTimer);\n if (disposed || phaseOf(entry.id).kind !== \"claiming\") return;\n if (!stillGoes(entry)) return;\n if (verdict === \"ok\") {\n readyToGo.set(entry.id, auto);\n releaseReady();\n return;\n }\n fail(entry.id, verdict === \"refused\" ? \"attachment\" : \"error\");\n evaluate();\n };\n stopTimer = setTimer(() => answer(\"unknown\"), attachmentCheckTimeoutMs);\n checkTimers.add(stopTimer);\n let checking: Promise<ChatOutboxAttachmentVerdict>;\n try {\n checking = checkAttachments(attachments);\n } catch (error) {\n checking = Promise.reject(error);\n }\n void checking.then(answer, () => answer(\"unknown\"));\n };\n\n /**\n * Hands the ready messages over in line: oldest first, and none while an older one is still\n * being claimed or asked about (a check takes a moment, and a later message must not reach the\n * conversation before it). One whose gate closed meanwhile waits again, and one that failed or\n * left lets the line move. Re-entered from `evaluate`, it runs once at a time.\n */\n function releaseReady(): void {\n if (releasing || disposed) return;\n releasing = true;\n let heldBack = false;\n try {\n for (const entry of [...entries]) {\n if (phaseOf(entry.id).kind !== \"claiming\") continue;\n const auto = readyToGo.get(entry.id);\n if (auto === undefined) return;\n // The host holds it (a send with other settings went out since the line was counted down,\n // and the conversation has not taken it yet): it and everything behind it wait again.\n if (!mayHandOff(entry)) {\n holdBackFrom(entry);\n heldBack = true;\n return;\n }\n readyToGo.delete(entry.id);\n if (stillGoes(entry)) dispatch(entry, auto);\n }\n } finally {\n releasing = false;\n if (heldBack) evaluateLine();\n }\n }\n\n /**\n * `first` and every claimed message behind it go back in line unclaimed: they are counted down\n * and claimed again once the host lets the line go, so no other window waits on a claim of ours.\n */\n function holdBackFrom(first: ChatOutboxEntry): void {\n let behind = false;\n for (const entry of entries) {\n if (entry.id === first.id) behind = true;\n if (!behind || phaseOf(entry.id).kind !== \"claiming\") continue;\n readyToGo.delete(entry.id);\n phases.set(entry.id, { kind: \"idle\" });\n releaseClaim(entry.id);\n }\n }\n\n const dispatch = (entry: ChatOutboxEntry, auto: boolean): void => {\n handedInRegain.set(entry.id, regain);\n phases.set(entry.id, { kind: \"handed\", journaled: false });\n // Raised before the hand-off: the host stages the message's own send settings, which the\n // statement rides out with.\n emit({\n type: \"handed\",\n threadId,\n messageId: entry.id,\n auto,\n ageMs: now() - entry.queuedAt,\n ...(entry.sendContext !== undefined && { sendContext: entry.sendContext }),\n });\n let sent: Promise<unknown>;\n try {\n sent = thread.send(entry);\n } catch (error) {\n sent = Promise.reject(error);\n }\n void sent.then(\n () => {\n if (disposed) return;\n letGo(entry.id, { type: \"delivered\", threadId, messageId: entry.id, status: \"accepted\" });\n evaluate();\n },\n (error: unknown) => {\n if (disposed) return;\n const phase = phaseOf(entry.id);\n // Statewire's journal took it: its own record decides what happens next (an exactly-once\n // replay, or its failed lane), and the settle update lets this record go.\n if (phase.kind === \"handed\" && phase.journaled) return;\n if (phase.kind !== \"handed\") return;\n const failure = failureOf(error);\n if (failure.cause !== undefined && AWAY_CAUSES.has(failure.cause)) {\n // The conversation went away before its journal took the message: it waits again.\n phases.set(entry.id, { kind: \"idle\" });\n handedInRegain.delete(entry.id);\n releaseClaim(entry.id);\n evaluate();\n return;\n }\n fail(entry.id, \"error\");\n evaluate();\n },\n );\n };\n\n const startCountdown = (run: readonly ChatOutboxEntry[]): void => {\n stopCountdown();\n const ids = run.map((entry) => entry.id);\n const endsAt = now() + countdownMs;\n const cancel = setTimer(() => {\n if (disposed || countdown?.endsAt !== endsAt) return;\n countdown = null;\n if (!gateOpen()) {\n evaluate();\n return;\n }\n for (const id of ids) {\n const entry = entries.find((candidate) => candidate.id === id);\n if (!entry) continue;\n const phase = phaseOf(id);\n if (phase.kind !== \"idle\" || claimedElsewhere(id)) continue;\n // A send with other settings left meanwhile: the line waits for the conversation to take it.\n if (!mayHandOff(entry)) break;\n handOff(entry, true);\n }\n evaluate();\n }, countdownMs);\n countdown = { endsAt, ids, cancel };\n emit({ type: \"countdown-started\", threadId, count: ids.length });\n };\n\n const scheduleLeaseCheck = (): void => {\n leaseTimer?.();\n leaseTimer = null;\n let soonest = Number.POSITIVE_INFINITY;\n for (const [id, claim] of claims) {\n if (claim.by === claimant || !entries.some((entry) => entry.id === id)) continue;\n const expires = claim.at + claimLeaseMs - now();\n if (expires > 0) soonest = Math.min(soonest, expires);\n }\n if (Number.isFinite(soonest)) leaseTimer = setTimer(() => evaluate(), soonest + 1);\n };\n\n /** Re-derive everything from the inputs, then let the ready messages go in line. */\n function evaluate(): void {\n evaluateLine();\n releaseReady();\n }\n\n /** What the conversation holds, the gate, the line. */\n function evaluateLine(): void {\n if (disposed) return;\n const reachable = reachability.get();\n if (reachable && !wasReachable) regain += 1;\n wasReachable = reachable;\n\n if (loaded) {\n const held = thread.heldIds();\n for (const entry of [...entries]) {\n const phase = phaseOf(entry.id);\n if (phase.kind === \"handed\") {\n // This window's hand-off: the conversation folding it in means statewire's journal\n // took it; the record goes at `posted` (persisted before the wire) or a settle.\n if (held.has(entry.id)) phases.set(entry.id, { kind: \"handed\", journaled: true });\n continue;\n }\n if (phase.kind === \"claiming\") continue;\n // Not handed by this window, yet the conversation shows it: a journal adopted from an\n // earlier page (read back from storage, so durable), or the message already landed.\n if (held.has(entry.id)) letGo(entry.id, { type: \"adopted\", threadId, messageId: entry.id });\n }\n if (!gateOpen()) {\n // Gone to the background or out of reach before the conversation came back: those whose\n // countdown had ended go back in line, so returning starts the countdown over.\n for (const entry of entries) {\n if (phaseOf(entry.id).kind === \"awaiting-thread\") phases.set(entry.id, { kind: \"idle\" });\n }\n } else if (thread.readiness() !== \"connecting\") {\n // The conversation is back: the messages whose countdown already ended go now, in line, up\n // to one the host holds (it, and those behind it, are counted down again once it lets go).\n let held = false;\n let handedKey: string | null = null;\n for (const entry of entries) {\n if (phaseOf(entry.id).kind !== \"awaiting-thread\") continue;\n phases.set(entry.id, { kind: \"idle\" });\n const key = keyOf(entry);\n held ||= (handedKey !== null && key !== handedKey) || !mayHandOff(entry);\n if (held) continue;\n handedKey = key;\n handOff(entry, true);\n }\n }\n }\n\n if (!loaded || !gateOpen()) {\n stopCountdown();\n publish();\n return;\n }\n\n const line = computeLine();\n if (line.askId !== null && !asked.has(line.askId)) {\n asked.add(line.askId);\n const entry = entries.find((candidate) => candidate.id === line.askId);\n if (entry) emit({ type: \"asked\", threadId, messageId: entry.id, ageMs: now() - entry.queuedAt });\n }\n // Messages the person told to go now leave without a countdown, but never ahead of older\n // ones still in line: those count down first, and these follow them.\n const immediate: ChatOutboxEntry[] = [];\n for (const entry of line.run) {\n if (!sendNow.has(entry.id)) break;\n immediate.push(entry);\n }\n for (const entry of immediate) handOff(entry, false);\n const run = line.run.slice(immediate.length).filter((entry) => !sendNow.has(entry.id));\n if (run.length === 0) {\n stopCountdown();\n } else if (\n countdown === null ||\n countdown.ids.length !== run.length ||\n run.some((entry, index) => countdown?.ids[index] !== entry.id)\n ) {\n // A new countdown for a new line (a message added or cancelled starts it over).\n startCountdown(run);\n }\n scheduleLeaseCheck();\n publish();\n }\n\n const applyRecords = (records: readonly ChatOutboxRecord[], listing: number): void => {\n const mine = records.filter((record) => record.entry.threadId === threadId);\n const stored = new Map(mine.map((record) => [record.entry.id, record]));\n claims = new Map(\n mine.flatMap((record) => (record.claim ? [[record.entry.id, record.claim] as const] : [])),\n );\n for (const record of mine) {\n if (!settledFor(record.entry.id, listing)) continue;\n if (record.memoryOnly) persisted.delete(record.entry.id);\n else persisted.add(record.entry.id);\n // A hand-off that failed (in this window or another, now or before) stays failed until the\n // person retries it; this window's own failure is never cleared by a listing.\n const phase = phaseOf(record.entry.id);\n if (record.failure && phase.kind !== \"failed\" && !isBusy(phase)) {\n phases.set(record.entry.id, { kind: \"failed\", reason: record.failure.reason });\n }\n }\n // Messages the store no longer holds (sent, cancelled or discarded in another window) leave\n // this one too, unless this window is handing them or wrote them after the listing started.\n const kept = entries.filter(\n (entry) =>\n stored.has(entry.id) || !settledFor(entry.id, listing) || isBusy(phaseOf(entry.id)),\n );\n for (const entry of entries) {\n if (kept.includes(entry)) continue;\n if (countdown?.ids.includes(entry.id)) stopCountdown();\n phases.delete(entry.id);\n persisted.delete(entry.id);\n sendNow.delete(entry.id);\n }\n const known = new Set(kept.map((entry) => entry.id));\n const added = mine.filter((record) => !known.has(record.entry.id)).map((record) => record.entry);\n entries = [...kept, ...added].sort((a, b) => a.queuedAt - b.queuedAt);\n // Writes this listing has seen (it started after they settled) need no guard any more.\n for (const [id, settledAt] of unsettled) {\n if (settledAt !== undefined && listing > settledAt) unsettled.delete(id);\n }\n\n const byThread = new Map<string, { count: number; first: ChatOutboxEntry; newChat: boolean }>();\n for (const record of records) {\n if (record.entry.threadId === threadId) continue;\n const current = byThread.get(record.entry.threadId);\n if (current) {\n current.count += 1;\n current.newChat &&= record.entry.newChat;\n } else {\n byThread.set(record.entry.threadId, {\n count: 1,\n first: record.entry,\n newChat: record.entry.newChat,\n });\n }\n }\n elsewhere = [...byThread]\n .map(([otherThreadId, other]) => ({ threadId: otherThreadId, ...other }))\n .sort((a, b) => a.first.queuedAt - b.first.queuedAt);\n };\n\n let reading: Promise<void> | null = null;\n let readAgain = false;\n const reload = (): void => {\n if (!store || disposed) return;\n if (reading) {\n readAgain = true;\n return;\n }\n listingsStarted += 1;\n const listing = listingsStarted;\n reading = store\n .listAll()\n .then(\n (records) => {\n if (disposed) return;\n applyRecords(records, listing);\n loaded = true;\n },\n () => {\n loaded = true;\n },\n )\n .finally(() => {\n reading = null;\n if (disposed) return;\n evaluate();\n if (readAgain) {\n readAgain = false;\n reload();\n }\n });\n };\n\n const unsubscribers = [\n reachability.subscribe(evaluate),\n foreground.subscribe(evaluate),\n thread.subscribe(evaluate),\n ...(sendGate ? [sendGate.subscribe(evaluate)] : []),\n ...(store ? [store.subscribe(reload)] : []),\n ];\n reload();\n publish();\n\n return {\n threadId,\n reachability,\n getSnapshot: () => snapshot,\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n shouldHold() {\n if (!reachability.get()) return true;\n // Until the conversation's kept messages are read, a new send could overtake one of them.\n if (!loaded) return true;\n // A message statewire's journal already took is ordered ahead of a new send by its seq;\n // one still waiting (or being claimed, or waiting for the conversation) is not.\n return entries.some((entry) => {\n const phase = phaseOf(entry.id);\n return phase.kind !== \"failed\" && !(phase.kind === \"handed\" && phase.journaled);\n });\n },\n hold(input) {\n const entry: ChatOutboxEntry = {\n id: newMessageId(),\n threadId,\n text: input.text,\n queuedAt: now(),\n newChat: typeof isNewChat === \"function\" ? isNewChat() : isNewChat,\n ...(input.composerMarkdown !== undefined && { composerMarkdown: input.composerMarkdown }),\n ...(input.composerHtml !== undefined && { composerHtml: input.composerHtml }),\n ...(input.metadata !== undefined && { metadata: input.metadata }),\n ...(input.sendContext !== undefined && { sendContext: input.sendContext }),\n ...(input.files !== undefined && input.files.length > 0 && { files: input.files }),\n ...(input.attachments !== undefined &&\n input.attachments.length > 0 && { attachments: input.attachments }),\n };\n // Sent from in front while Athena answers, held only until the kept messages are read: it\n // goes at once when nothing older is waiting, after them otherwise.\n if (!loaded && gateOpen()) sendNow.add(entry.id);\n entries = [...entries, entry];\n if (store) {\n // Counted as kept from the start: a page closed before the write settles is the same\n // risk a draft has, and the row must not flash \"only while this page is open\".\n persisted.add(entry.id);\n const write = store.put(entry);\n markUnsettled(entry.id, write);\n void write.then(\n (kept) => {\n if (!kept) persisted.delete(entry.id);\n emit({ type: \"held\", threadId, messageId: entry.id, durable: kept });\n publish();\n },\n () => {\n persisted.delete(entry.id);\n emit({ type: \"held\", threadId, messageId: entry.id, durable: false });\n publish();\n },\n );\n } else {\n emit({ type: \"held\", threadId, messageId: entry.id, durable: false });\n }\n evaluate();\n return entry;\n },\n cancel(id) {\n const entry = entries.find((candidate) => candidate.id === id);\n if (!entry || isBusy(phaseOf(id))) return;\n dropEntry(id);\n emit({ type: \"cancelled\", threadId, messageId: id });\n if (store) void store.remove(threadId, id).catch(() => undefined);\n try {\n restore(entry);\n } finally {\n evaluate();\n }\n },\n send(id) {\n const entry = entries.find((candidate) => candidate.id === id);\n if (!entry || isBusy(phaseOf(id))) return;\n const wasFailed = phaseOf(id).kind === \"failed\";\n phases.set(id, { kind: \"idle\" });\n handedInRegain.delete(id);\n sendNow.add(id);\n if (wasFailed && store) {\n const write = store.clearFailure({ threadId, id });\n markUnsettled(id, write);\n void write.catch(() => undefined);\n }\n evaluate();\n },\n discard(id) {\n const entry = entries.find((candidate) => candidate.id === id);\n if (!entry || isBusy(phaseOf(id))) return;\n dropEntry(id);\n emit({ type: \"discarded\", threadId, messageId: id });\n if (store) void store.remove(threadId, id).catch(() => undefined);\n evaluate();\n },\n observeCommand(update) {\n if (disposed) return;\n const id = chatOutboxCommandMessageId(update.params);\n if (id === null) return;\n const phase = phaseOf(id);\n if (phase.kind === \"handed\") {\n if (update.status === \"queued\") {\n phases.set(id, { kind: \"handed\", journaled: true });\n return;\n }\n // Persisted before the wire (`posted`), admitted (`applied`), or settled: statewire owns\n // the message now (its exactly-once replay, or its failed lane).\n letGo(id, { type: \"delivered\", threadId, messageId: id, status: update.status });\n evaluate();\n return;\n }\n // A statement for a waiting message this window did not hand: a journal adopted from an\n // earlier page. The conversation folds it in, and the next evaluation lets the record go.\n if (entries.some((entry) => entry.id === id)) evaluate();\n },\n openThread: openThread ?? null,\n dispose() {\n if (disposed) return;\n disposed = true;\n stopCountdown();\n leaseTimer?.();\n for (const stop of checkTimers) stop();\n checkTimers.clear();\n readyToGo.clear();\n for (const unsubscribe of unsubscribers) unsubscribe();\n listeners.clear();\n // A claim taken for a hand-off that never started is released, so another window (or the\n // next mount) need not wait out the lease. One whose hand-off started stays until its lease\n // ends: statewire may still journal the statement, and the conversation then shows it.\n if (store) {\n for (const [id, phase] of phases) {\n if (phase.kind === \"claiming\") releaseClaim(id);\n }\n }\n },\n };\n}\n"],"names":["held"],"mappings":"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"}
|
|
@@ -42,6 +42,13 @@ import type { ChatOutboxAttachment, ChatOutboxEntry, ChatOutboxFailureReason, Ch
|
|
|
42
42
|
* with the person) keeps the message as "Couldn't send" with Retry, Edit and Discard, and a check
|
|
43
43
|
* Athena could not answer keeps it the same way as a send error. Nothing goes unchecked, and the
|
|
44
44
|
* text is never lost.
|
|
45
|
+
* - **Its own send settings, never another's.** A message keeps the per-send settings it was typed
|
|
46
|
+
* with ({@link ChatOutboxEntry.sendContext}), and the host stages them as it is handed over (its
|
|
47
|
+
* `handed` event carries them). Every send of a conversation rides one ambient context, so a host
|
|
48
|
+
* with settings that can differ between messages gives the outbox a send gate
|
|
49
|
+
* ({@link ChatOutboxOptions.sendGate}): only messages whose settings are the same are handed over
|
|
50
|
+
* together, and a message whose settings differ from a send the conversation has not taken yet
|
|
51
|
+
* waits, in line, until it has.
|
|
45
52
|
*/
|
|
46
53
|
/** How long the countdown before a waiting message is sent lasts. */
|
|
47
54
|
export declare const CHAT_OUTBOX_COUNTDOWN_MS = 5000;
|
|
@@ -150,6 +157,8 @@ export interface ChatOutboxInput {
|
|
|
150
157
|
/** The draft as an HTML composer holds it (the phone's), given back exactly on Cancel. */
|
|
151
158
|
composerHtml?: string;
|
|
152
159
|
metadata?: Record<string, unknown>;
|
|
160
|
+
/** The per-send settings the host stages when the message goes ({@link ChatOutboxEntry.sendContext}). */
|
|
161
|
+
sendContext?: Record<string, unknown>;
|
|
153
162
|
files?: ChatOutboxFile[];
|
|
154
163
|
/** Files already in Athena the message carries, asked about before it goes. */
|
|
155
164
|
attachments?: ChatOutboxAttachment[];
|
|
@@ -174,6 +183,8 @@ export type ChatOutboxEvent = {
|
|
|
174
183
|
messageId: string;
|
|
175
184
|
auto: boolean;
|
|
176
185
|
ageMs: number;
|
|
186
|
+
/** The message's own send settings, raised just before the hand-off so the host stages them. */
|
|
187
|
+
sendContext?: Record<string, unknown>;
|
|
177
188
|
} | {
|
|
178
189
|
type: "delivered";
|
|
179
190
|
threadId: string;
|
|
@@ -198,6 +209,19 @@ export type ChatOutboxEvent = {
|
|
|
198
209
|
threadId: string;
|
|
199
210
|
messageId: string;
|
|
200
211
|
};
|
|
212
|
+
/**
|
|
213
|
+
* A host's say over which waiting messages go together, and when (Olympus: the agents a message
|
|
214
|
+
* names ride the conversation's one ambient context, so a message naming other agents than a send
|
|
215
|
+
* still on its way waits for it). Without one, every message goes as soon as it may.
|
|
216
|
+
*/
|
|
217
|
+
export interface ChatOutboxSendGate {
|
|
218
|
+
/** Consecutive messages with the same key may be handed over together; a different one waits. */
|
|
219
|
+
keyOf(entry: ChatOutboxEntry): string;
|
|
220
|
+
/** Whether the message may be handed over now. */
|
|
221
|
+
mayHandOff(entry: ChatOutboxEntry): boolean;
|
|
222
|
+
/** Called whenever {@link mayHandOff} may have changed. */
|
|
223
|
+
subscribe(listener: () => void): () => void;
|
|
224
|
+
}
|
|
201
225
|
/** The command-lifecycle update statewire reports (`onCommandChange`), structurally. */
|
|
202
226
|
export interface ChatOutboxCommandUpdate {
|
|
203
227
|
status: "queued" | "posted" | "applied" | "settled";
|
|
@@ -268,6 +292,11 @@ export interface ChatOutboxOptions {
|
|
|
268
292
|
* Edit, Discard); `unknown` too, as a send error. Omitted: attachments go unchecked.
|
|
269
293
|
*/
|
|
270
294
|
checkAttachments?: ChatOutboxAttachmentCheck;
|
|
295
|
+
/**
|
|
296
|
+
* Which waiting messages go together, and when (a message whose send settings differ from a send
|
|
297
|
+
* the conversation has not taken yet waits for it). Omitted: every message goes once it may.
|
|
298
|
+
*/
|
|
299
|
+
sendGate?: ChatOutboxSendGate;
|
|
271
300
|
now?: () => number;
|
|
272
301
|
countdownMs?: number;
|
|
273
302
|
askAfterMs?: number;
|
|
@@ -87,6 +87,7 @@ function createChatOutbox(options) {
|
|
|
87
87
|
openThread,
|
|
88
88
|
onEvent,
|
|
89
89
|
checkAttachments,
|
|
90
|
+
sendGate,
|
|
90
91
|
now = () => Date.now(),
|
|
91
92
|
countdownMs = CHAT_OUTBOX_COUNTDOWN_MS,
|
|
92
93
|
askAfterMs = CHAT_OUTBOX_ASK_AFTER_MS,
|
|
@@ -165,11 +166,17 @@ function createChatOutbox(options) {
|
|
|
165
166
|
if (line.askId === entry.id) return { kind: "ask" };
|
|
166
167
|
return { kind: "waiting" };
|
|
167
168
|
};
|
|
169
|
+
const keyOf = (entry) => (sendGate == null ? void 0 : sendGate.keyOf(entry)) ?? "";
|
|
170
|
+
const mayHandOff = (entry) => (sendGate == null ? void 0 : sendGate.mayHandOff(entry)) ?? true;
|
|
168
171
|
const computeLine = () => {
|
|
169
172
|
const run = [];
|
|
173
|
+
let lineKey = null;
|
|
170
174
|
for (const entry of entries) {
|
|
171
175
|
const phase = phaseOf(entry.id);
|
|
172
176
|
if (phase.kind === "failed") continue;
|
|
177
|
+
const key = keyOf(entry);
|
|
178
|
+
if (lineKey === null) lineKey = key;
|
|
179
|
+
else if (key !== lineKey) break;
|
|
173
180
|
if (isBusy(phase) || claimedElsewhere(entry.id)) continue;
|
|
174
181
|
if (handedInRegain.get(entry.id) === regain && !sendNow.has(entry.id)) continue;
|
|
175
182
|
if (!sendNow.has(entry.id) && now() - entry.queuedAt > askAfterMs) {
|
|
@@ -177,6 +184,8 @@ function createChatOutbox(options) {
|
|
|
177
184
|
}
|
|
178
185
|
run.push(entry);
|
|
179
186
|
}
|
|
187
|
+
const head = run[0];
|
|
188
|
+
if (head !== void 0 && !mayHandOff(head)) return { run: [], askId: null };
|
|
180
189
|
return { run, askId: null };
|
|
181
190
|
};
|
|
182
191
|
const gateOpen = () => reachability.get() && foreground.get();
|
|
@@ -332,22 +341,46 @@ function createChatOutbox(options) {
|
|
|
332
341
|
function releaseReady() {
|
|
333
342
|
if (releasing || disposed) return;
|
|
334
343
|
releasing = true;
|
|
344
|
+
let heldBack = false;
|
|
335
345
|
try {
|
|
336
346
|
for (const entry of [...entries]) {
|
|
337
347
|
if (phaseOf(entry.id).kind !== "claiming") continue;
|
|
338
348
|
const auto = readyToGo.get(entry.id);
|
|
339
349
|
if (auto === void 0) return;
|
|
350
|
+
if (!mayHandOff(entry)) {
|
|
351
|
+
holdBackFrom(entry);
|
|
352
|
+
heldBack = true;
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
340
355
|
readyToGo.delete(entry.id);
|
|
341
356
|
if (stillGoes(entry)) dispatch(entry, auto);
|
|
342
357
|
}
|
|
343
358
|
} finally {
|
|
344
359
|
releasing = false;
|
|
360
|
+
if (heldBack) evaluateLine();
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
function holdBackFrom(first) {
|
|
364
|
+
let behind = false;
|
|
365
|
+
for (const entry of entries) {
|
|
366
|
+
if (entry.id === first.id) behind = true;
|
|
367
|
+
if (!behind || phaseOf(entry.id).kind !== "claiming") continue;
|
|
368
|
+
readyToGo.delete(entry.id);
|
|
369
|
+
phases.set(entry.id, { kind: "idle" });
|
|
370
|
+
releaseClaim(entry.id);
|
|
345
371
|
}
|
|
346
372
|
}
|
|
347
373
|
const dispatch = (entry, auto) => {
|
|
348
374
|
handedInRegain.set(entry.id, regain);
|
|
349
375
|
phases.set(entry.id, { kind: "handed", journaled: false });
|
|
350
|
-
emit({
|
|
376
|
+
emit({
|
|
377
|
+
type: "handed",
|
|
378
|
+
threadId,
|
|
379
|
+
messageId: entry.id,
|
|
380
|
+
auto,
|
|
381
|
+
ageMs: now() - entry.queuedAt,
|
|
382
|
+
...entry.sendContext !== void 0 && { sendContext: entry.sendContext }
|
|
383
|
+
});
|
|
351
384
|
let sent;
|
|
352
385
|
try {
|
|
353
386
|
sent = thread.send(entry);
|
|
@@ -394,6 +427,7 @@ function createChatOutbox(options) {
|
|
|
394
427
|
if (!entry) continue;
|
|
395
428
|
const phase = phaseOf(id);
|
|
396
429
|
if (phase.kind !== "idle" || claimedElsewhere(id)) continue;
|
|
430
|
+
if (!mayHandOff(entry)) break;
|
|
397
431
|
handOff(entry, true);
|
|
398
432
|
}
|
|
399
433
|
evaluate();
|
|
@@ -437,11 +471,16 @@ function createChatOutbox(options) {
|
|
|
437
471
|
if (phaseOf(entry.id).kind === "awaiting-thread") phases.set(entry.id, { kind: "idle" });
|
|
438
472
|
}
|
|
439
473
|
} else if (thread.readiness() !== "connecting") {
|
|
474
|
+
let held2 = false;
|
|
475
|
+
let handedKey = null;
|
|
440
476
|
for (const entry of entries) {
|
|
441
|
-
if (phaseOf(entry.id).kind
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
477
|
+
if (phaseOf(entry.id).kind !== "awaiting-thread") continue;
|
|
478
|
+
phases.set(entry.id, { kind: "idle" });
|
|
479
|
+
const key = keyOf(entry);
|
|
480
|
+
held2 || (held2 = handedKey !== null && key !== handedKey || !mayHandOff(entry));
|
|
481
|
+
if (held2) continue;
|
|
482
|
+
handedKey = key;
|
|
483
|
+
handOff(entry, true);
|
|
445
484
|
}
|
|
446
485
|
}
|
|
447
486
|
}
|
|
@@ -552,6 +591,7 @@ function createChatOutbox(options) {
|
|
|
552
591
|
reachability.subscribe(evaluate),
|
|
553
592
|
foreground.subscribe(evaluate),
|
|
554
593
|
thread.subscribe(evaluate),
|
|
594
|
+
...sendGate ? [sendGate.subscribe(evaluate)] : [],
|
|
555
595
|
...store ? [store.subscribe(reload)] : []
|
|
556
596
|
];
|
|
557
597
|
reload();
|
|
@@ -584,6 +624,7 @@ function createChatOutbox(options) {
|
|
|
584
624
|
...input.composerMarkdown !== void 0 && { composerMarkdown: input.composerMarkdown },
|
|
585
625
|
...input.composerHtml !== void 0 && { composerHtml: input.composerHtml },
|
|
586
626
|
...input.metadata !== void 0 && { metadata: input.metadata },
|
|
627
|
+
...input.sendContext !== void 0 && { sendContext: input.sendContext },
|
|
587
628
|
...input.files !== void 0 && input.files.length > 0 && { files: input.files },
|
|
588
629
|
...input.attachments !== void 0 && input.attachments.length > 0 && { attachments: input.attachments }
|
|
589
630
|
};
|