diff --git a/ui/src/pages/chat/chat-outbox-drain.ts b/ui/src/pages/chat/chat-outbox-drain.ts index 5f0e510fce48..e3975ef91aa0 100644 --- a/ui/src/pages/chat/chat-outbox-drain.ts +++ b/ui/src/pages/chat/chat-outbox-drain.ts @@ -305,6 +305,10 @@ async function drainStoredChatOutbox( } syncVisibleChatQueueProjection(host); if (item.localCommandName === "reset") { + if ((item.sendAttempts ?? 0) > 0 || item.sendRequestStartedAtMs !== undefined) { + setCommandState("unconfirmed", UNCONFIRMED_CHAT_SEND_ERROR); + return "blocked"; + } const resetTarget = captureChatCommandTarget(host); if (!resetTarget) { setCommandState("failed", "The Gateway connection changed. Retry the command."); @@ -316,25 +320,12 @@ async function drainStoredChatOutbox( dependencies.setChatError(host, initialAccess.reason); return "blocked"; } - const resetText = item.localCommandArgs ? `/reset ${item.localCommandArgs}` : "/reset"; - const convertResetToMessage = (sendState?: ChatQueueItem["sendState"]) => - updateQueuedMessageForSession(host, outbox.sessionKey, item.id, (entry) => ({ - ...entry, - localCommandArgs: undefined, - localCommandName: undefined, - refreshSessions: true, - text: resetText, - ...(sendState ? { sendState } : {}), - })); const confirmation = await confirmConversationResetForCurrentSession(host, { sessionKey: outbox.sessionKey, ...(outbox.agentId ? { agentId: outbox.agentId } : {}), }); if (confirmation === "deferred") { - const approvedDuringRun = - visibleSessionMatches(host, outbox.sessionKey, outbox.agentId) && host.chatRunId; - const deferCommand = approvedDuringRun ? convertResetToMessage : setCommandState; - deferCommand("waiting-idle"); + setCommandState("waiting-idle"); return "blocked"; } if (confirmation === "cancelled") { @@ -353,7 +344,15 @@ async function drainStoredChatOutbox( ...lane.pendingOptions.get(item.id), target: resetTarget, }); - if (!convertResetToMessage()) { + const result = await dependencies.sendQueuedChatMessage( + host, + item.id, + lane.pendingOptions.get(item.id), + outbox.sessionKey, + ); + lane.outcomes.set(item.id, result); + lane.pendingOptions.delete(item.id); + if (result !== "sent") { return "blocked"; } continue; diff --git a/ui/src/pages/chat/chat-send-delivery.ts b/ui/src/pages/chat/chat-send-delivery.ts index b0a3eaf9fbc6..ddad471d9dc8 100644 --- a/ui/src/pages/chat/chat-send-delivery.ts +++ b/ui/src/pages/chat/chat-send-delivery.ts @@ -186,7 +186,8 @@ async function sendQueuedChatMessage( ): Promise { const storageMode = options?.storageMode ?? "durable"; const queued = readQueuedMessageById(host, id); - if (!queued || queued.pendingRunId || queued.localCommandName) { + const approvedReset = queued?.localCommandName === "reset" && Boolean(options?.target); + if (!queued || queued.pendingRunId || (queued.localCommandName && !approvedReset)) { return "failed"; } const queueSessionKey = queued.sessionKey ?? queuedSessionKey; @@ -248,6 +249,13 @@ async function sendQueuedChatMessage( setChatError(host, OFFLINE_QUEUE_STORAGE_ERROR); return prepared; } + if (approvedReset) { + prepared = { + ...prepared, + refreshSessions: true, + text: prepared.localCommandArgs ? `/reset ${prepared.localCommandArgs}` : "/reset", + }; + } const message = prepared.text.trim(); const attachments = prepared.attachments ?? []; if (!message && attachments.length === 0) { @@ -717,6 +725,7 @@ async function sendResetSlashCommand( previousDraft: options.previousDraft, restoreDraft: options.restoreDraft, routingSessionKey: host.sessionKey, + target: options.target, }); } diff --git a/ui/src/pages/chat/chat-send.test.ts b/ui/src/pages/chat/chat-send.test.ts index 99f460ecdce0..6c24d4ca2f27 100644 --- a/ui/src/pages/chat/chat-send.test.ts +++ b/ui/src/pages/chat/chat-send.test.ts @@ -4317,16 +4317,16 @@ describe("handleSendChat", () => { expect(approvedReset).toEqual( expect.objectContaining({ id: item.id, + localCommandName: "reset", sendState: "waiting-idle", text: "/reset", }), ); - expect(approvedReset).not.toHaveProperty("localCommandName"); host.chatRunId = null; await retryReconnectableQueuedChatSends(host); - expect(confirmConversationReset).toHaveBeenCalledOnce(); + expect(confirmConversationReset).toHaveBeenCalledTimes(2); expect(sendPayloads.map((payload) => payload.message)).toEqual(["/reset"]); expect(listStoredChatOutboxes(host)).toStrictEqual([]); });