




















@@ -61,6 +61,7 @@ export type ChatHost = ChatInputHistoryState & {
6161updateComplete?: Promise<unknown>;
6262refreshSessionsAfterChat: Set<string>;
6363pendingAbort?: { runId: string; sessionKey: string } | null;
64+chatSubmitGuards?: Map<string, Promise<void>>;
6465/** Callback for slash-command side effects that need app-level access. */
6566onSlashAction?: (action: string) => void;
6667};
@@ -225,6 +226,54 @@ async function sendChatMessageNow(
225226return ok;
226227}
227228229+function attachmentSubmitSignature(attachment: ChatAttachment): string {
230+return JSON.stringify([
231+attachment.id,
232+attachment.mimeType,
233+attachment.fileName ?? "",
234+attachment.dataUrl.length,
235+attachment.dataUrl.slice(0, 64),
236+]);
237+}
238+239+function chatSubmitKey(
240+host: ChatHost,
241+kind: "btw" | "message",
242+message: string,
243+attachments: ChatAttachment[],
244+): string {
245+return JSON.stringify([
246+kind,
247+host.sessionKey,
248+message.trim(),
249+attachments.map(attachmentSubmitSignature),
250+]);
251+}
252+253+async function withChatSubmitGuard<T>(
254+host: ChatHost,
255+key: string,
256+run: () => Promise<T>,
257+): Promise<T | undefined> {
258+const guards = (host.chatSubmitGuards ??= new Map<string, Promise<void>>());
259+if (guards.has(key)) {
260+return undefined;
261+}
262+let releaseGuard!: () => void;
263+const guard = new Promise<void>((resolve) => {
264+releaseGuard = resolve;
265+});
266+guards.set(key, guard);
267+try {
268+return await run();
269+} finally {
270+releaseGuard();
271+if (guards.get(key) === guard) {
272+guards.delete(key);
273+}
274+}
275+}
276+228277async function sendDetachedBtwMessage(
229278host: ChatHost,
230279message: string,
@@ -362,16 +411,19 @@ export async function handleSendChat(
362411}
363412364413if (isBtwCommand(message)) {
365-if (messageOverride == null) {
366-recordNonTranscriptInputHistory(host, message);
367-host.chatMessage = "";
368-host.chatAttachments = [];
369-resetChatInputHistoryNavigation(host);
370-}
371-await sendDetachedBtwMessage(host, message, {
372-previousDraft: messageOverride == null ? previousDraft : undefined,
373-attachments: hasAttachments ? attachmentsToSend : undefined,
374-previousAttachments: messageOverride == null ? attachments : undefined,
414+const submitKey = chatSubmitKey(host, "btw", message, attachmentsToSend);
415+await withChatSubmitGuard(host, submitKey, async () => {
416+if (messageOverride == null) {
417+recordNonTranscriptInputHistory(host, message);
418+host.chatMessage = "";
419+host.chatAttachments = [];
420+resetChatInputHistoryNavigation(host);
421+}
422+await sendDetachedBtwMessage(host, message, {
423+previousDraft: messageOverride == null ? previousDraft : undefined,
424+attachments: hasAttachments ? attachmentsToSend : undefined,
425+previousAttachments: messageOverride == null ? attachments : undefined,
426+});
375427});
376428return;
377429}
@@ -407,27 +459,30 @@ export async function handleSendChat(
407459}
408460409461const refreshSessions = isChatResetCommand(message);
410-if (messageOverride == null) {
411-host.chatMessage = "";
412-host.chatAttachments = [];
413-resetChatInputHistoryNavigation(host);
414-}
415-416-if (isChatBusy(host)) {
462+const submitKey = chatSubmitKey(host, "message", message, attachmentsToSend);
463+await withChatSubmitGuard(host, submitKey, async () => {
417464if (messageOverride == null) {
418-recordNonTranscriptInputHistory(host, message);
465+host.chatMessage = "";
466+host.chatAttachments = [];
467+resetChatInputHistoryNavigation(host);
468+}
469+470+if (isChatBusy(host)) {
471+if (messageOverride == null) {
472+recordNonTranscriptInputHistory(host, message);
473+}
474+enqueueChatMessage(host, message, attachmentsToSend, refreshSessions);
475+return;
419476}
420-enqueueChatMessage(host, message, attachmentsToSend, refreshSessions);
421-return;
422-}
423477424-await sendChatMessageNow(host, message, {
425-previousDraft: messageOverride == null ? previousDraft : undefined,
426-restoreDraft: Boolean(messageOverride && opts?.restoreDraft),
427-attachments: hasAttachments ? attachmentsToSend : undefined,
428-previousAttachments: messageOverride == null ? attachments : undefined,
429-restoreAttachments: Boolean(messageOverride && opts?.restoreDraft),
430- refreshSessions,
478+await sendChatMessageNow(host, message, {
479+previousDraft: messageOverride == null ? previousDraft : undefined,
480+restoreDraft: Boolean(messageOverride && opts?.restoreDraft),
481+attachments: hasAttachments ? attachmentsToSend : undefined,
482+previousAttachments: messageOverride == null ? attachments : undefined,
483+restoreAttachments: Boolean(messageOverride && opts?.restoreDraft),
484+ refreshSessions,
485+});
431486});
432487}
433488此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。