diff --git a/webviews/src/agent-session/acpmux/App.tsx b/webviews/src/agent-session/acpmux/App.tsx index 8bcc647fcbdd..e7b62149ec1f 100644 --- a/webviews/src/agent-session/acpmux/App.tsx +++ b/webviews/src/agent-session/acpmux/App.tsx @@ -489,6 +489,7 @@ import { AcpmuxRestartGate, hostErrorShows, hostIsReconnecting, + hostRefusedTooLarge, nextRetryDelay, offersAcpmuxRestart, } from "./hostRetry"; @@ -9581,7 +9582,9 @@ function AcpmuxPane() { if (!cancelled) { acpWire.lifecycle("handshake failed", { message: String(error) }); setSnapshot((current) => ({ ...current, connection: `connecting: ${String(error)}` })); - setHostError(errorMessage(error) || String(error)); + // A chat too large to open says so in plain words; the reply's byte count stays in the log. + const tooLarge = hostRefusedTooLarge(error); + setHostError(tooLarge ? translate("host.tooLarge") : errorMessage(error) || String(error)); failingSince ??= Date.now(); const recovering = hostIsReconnecting(error); setHostErrorShown(hostErrorShows(error, Date.now() - failingSince)); @@ -9591,7 +9594,8 @@ function AcpmuxPane() { setAcpmuxRestartOffered(offersAcpmuxRestart(error)); // Back off so a host without a daemon is not asked four times a second. A Restart acpmux // in flight asks again itself when it ends. - if (restartGate.mayAsk()) { + // A reply too large to send is the same at every ask: only the line's Retry asks again. + if (!tooLarge && restartGate.mayAsk()) { retryTimer = window.setTimeout(() => void connectHost(), retryDelay); retryDelay = nextRetryDelay(retryDelay, reconnect || recovering); } diff --git a/webviews/src/agent-session/acpmux/HostError.gallery.tsx b/webviews/src/agent-session/acpmux/HostError.gallery.tsx index e665a48599ad..80cfefc9f816 100644 --- a/webviews/src/agent-session/acpmux/HostError.gallery.tsx +++ b/webviews/src/agent-session/acpmux/HostError.gallery.tsx @@ -153,6 +153,14 @@ export default componentEntry({ }, note: "A refusal whose holder is no app bundle offers the same Restart acpmux.", }, + "too-large": { + props: { + message: "This chat is too large to open. Update acpmux, then Retry.", + initialRetrying: false, + withRetry: true, + }, + note: 'acpmux refused the chat\'s open as larger than one message (message.too_large, an acpmux before #28754): said at once in plain words, never the byte count, and the pane does not ask again on its own; Retry still asks (Leo, 2026-10-31: "this is completely hardlocked bc of message size").', + }, waiting: { props: { message: "", initialRetrying: false, withRetry: false, waitedMs: 95_000 }, play: restartService, diff --git a/webviews/src/agent-session/acpmux/Localizable.xcstrings b/webviews/src/agent-session/acpmux/Localizable.xcstrings index 123aa9aab908..a662539d2113 100644 --- a/webviews/src/agent-session/acpmux/Localizable.xcstrings +++ b/webviews/src/agent-session/acpmux/Localizable.xcstrings @@ -1,6 +1,9 @@ { "sourceLanguage": "en", "strings": { + "prompt.failure.tooLarge": {"comment":"Agent pane: under a prompt refused because it was larger than one message can carry (inline attachments); Retry is not offered, Edit is","extractionState":"manual","localizations":{"en":{"stringUnit":{"state":"translated","value":"Not sent: this message is too large to send at once. Take out an attachment with Edit, or send the attachments in separate messages."}},"ar":{"stringUnit":{"state":"needs_review","value":"لم تُرسَل: هذه الرسالة أكبر من أن تُرسَل دفعة واحدة. أزِل مرفقًا عبر «تعديل»، أو أرسل المرفقات في رسائل منفصلة."}},"bs":{"stringUnit":{"state":"needs_review","value":"Nije poslano: ova poruka je prevelika da bi se poslala odjednom. Uklonite prilog pomoću Uredi ili pošaljite priloge u zasebnim porukama."}},"da":{"stringUnit":{"state":"needs_review","value":"Ikke sendt: denne besked er for stor til at blive sendt på én gang. Fjern en vedhæftning med Rediger, eller send vedhæftningerne i separate beskeder."}},"de":{"stringUnit":{"state":"needs_review","value":"Nicht gesendet: Diese Nachricht ist zu groß, um auf einmal gesendet zu werden. Entferne mit Bearbeiten einen Anhang oder sende die Anhänge in getrennten Nachrichten."}},"es":{"stringUnit":{"state":"needs_review","value":"No enviado: este mensaje es demasiado grande para enviarlo de una vez. Quita un adjunto con Editar o envía los adjuntos en mensajes separados."}},"fr":{"stringUnit":{"state":"needs_review","value":"Non envoyé : ce message est trop volumineux pour être envoyé en une fois. Retirez une pièce jointe avec Modifier, ou envoyez les pièces jointes dans des messages séparés."}},"it":{"stringUnit":{"state":"needs_review","value":"Non inviato: questo messaggio è troppo grande per essere inviato in una volta. Rimuovi un allegato con Modifica o invia gli allegati in messaggi separati."}},"ja":{"stringUnit":{"state":"needs_review","value":"未送信:このメッセージは一度に送信するには大きすぎます。「編集」で添付ファイルを外すか、添付ファイルを別々のメッセージで送信してください。"}},"km":{"stringUnit":{"state":"needs_review","value":"មិនបានផ្ញើ៖ សារនេះធំពេកមិនអាចផ្ញើក្នុងពេលតែមួយបានទេ។ ដកឯកសារភ្ជាប់មួយចេញដោយប្រើ កែសម្រួល ឬផ្ញើឯកសារភ្ជាប់ក្នុងសារដាច់ដោយឡែក។"}},"ko":{"stringUnit":{"state":"needs_review","value":"보내지 않음: 이 메시지는 한 번에 보내기에 너무 큽니다. 편집으로 첨부 파일을 빼거나 첨부 파일을 별도의 메시지로 보내세요."}},"nb":{"stringUnit":{"state":"needs_review","value":"Ikke sendt: denne meldingen er for stor til å sendes på én gang. Fjern et vedlegg med Rediger, eller send vedleggene i separate meldinger."}},"pl":{"stringUnit":{"state":"needs_review","value":"Nie wysłano: ta wiadomość jest za duża, by wysłać ją naraz. Usuń załącznik przez Edytuj albo wyślij załączniki w osobnych wiadomościach."}},"pt-BR":{"stringUnit":{"state":"needs_review","value":"Não enviada: esta mensagem é grande demais para ser enviada de uma vez. Remova um anexo com Editar ou envie os anexos em mensagens separadas."}},"ru":{"stringUnit":{"state":"needs_review","value":"Не отправлено: это сообщение слишком велико, чтобы отправить его за раз. Уберите вложение через «Изменить» или отправьте вложения отдельными сообщениями."}},"th":{"stringUnit":{"state":"needs_review","value":"ไม่ได้ส่ง: ข้อความนี้ใหญ่เกินกว่าจะส่งในครั้งเดียว นำไฟล์แนบออกด้วย แก้ไข หรือส่งไฟล์แนบแยกเป็นหลายข้อความ"}},"tr":{"stringUnit":{"state":"needs_review","value":"Gönderilmedi: bu ileti tek seferde gönderilemeyecek kadar büyük. Düzenle ile bir eki çıkarın ya da ekleri ayrı iletilerde gönderin."}},"uk":{"stringUnit":{"state":"needs_review","value":"Не надіслано: це повідомлення завелике, щоб надіслати його за раз. Приберіть вкладення через «Редагувати» або надішліть вкладення окремими повідомленнями."}},"vi":{"stringUnit":{"state":"needs_review","value":"Chưa gửi: tin nhắn này quá lớn để gửi một lần. Gỡ một tệp đính kèm bằng Sửa, hoặc gửi các tệp đính kèm trong các tin nhắn riêng."}},"zh-Hans":{"stringUnit":{"state":"needs_review","value":"未发送:此消息太大,无法一次发送。请用“编辑”移除一个附件,或将附件分成多条消息发送。"}},"zh-Hant":{"stringUnit":{"state":"needs_review","value":"未傳送:此訊息太大,無法一次傳送。請用「編輯」移除一個附件,或將附件分成多則訊息傳送。"}}}}, + "prompt.failure.tooLargeFile": {"comment":"Agent pane: under a prompt refused because it was larger than one message can carry; {name} is the attachment that takes the most of it. Retry is not offered, Edit is","extractionState":"manual","localizations":{"en":{"stringUnit":{"state":"translated","value":"Not sent: this message is too large to send at once. Take out {name} with Edit, or send it in a message of its own."}},"ar":{"stringUnit":{"state":"needs_review","value":"لم تُرسَل: هذه الرسالة أكبر من أن تُرسَل دفعة واحدة. أزِل {name} عبر «تعديل»، أو أرسله في رسالة مستقلة."}},"bs":{"stringUnit":{"state":"needs_review","value":"Nije poslano: ova poruka je prevelika da bi se poslala odjednom. Uklonite {name} pomoću Uredi ili ga pošaljite u zasebnoj poruci."}},"da":{"stringUnit":{"state":"needs_review","value":"Ikke sendt: denne besked er for stor til at blive sendt på én gang. Fjern {name} med Rediger, eller send den i en besked for sig."}},"de":{"stringUnit":{"state":"needs_review","value":"Nicht gesendet: Diese Nachricht ist zu groß, um auf einmal gesendet zu werden. Entferne {name} mit Bearbeiten oder sende es in einer eigenen Nachricht."}},"es":{"stringUnit":{"state":"needs_review","value":"No enviado: este mensaje es demasiado grande para enviarlo de una vez. Quita {name} con Editar o envíalo en un mensaje aparte."}},"fr":{"stringUnit":{"state":"needs_review","value":"Non envoyé : ce message est trop volumineux pour être envoyé en une fois. Retirez {name} avec Modifier, ou envoyez-le dans un message à part."}},"it":{"stringUnit":{"state":"needs_review","value":"Non inviato: questo messaggio è troppo grande per essere inviato in una volta. Rimuovi {name} con Modifica o invialo in un messaggio a parte."}},"ja":{"stringUnit":{"state":"needs_review","value":"未送信:このメッセージは一度に送信するには大きすぎます。「編集」で {name} を外すか、別のメッセージで送信してください。"}},"km":{"stringUnit":{"state":"needs_review","value":"មិនបានផ្ញើ៖ សារនេះធំពេកមិនអាចផ្ញើក្នុងពេលតែមួយបានទេ។ ដក {name} ចេញដោយប្រើ កែសម្រួល ឬផ្ញើវាក្នុងសារដាច់ដោយឡែក។"}},"ko":{"stringUnit":{"state":"needs_review","value":"보내지 않음: 이 메시지는 한 번에 보내기에 너무 큽니다. 편집으로 {name}을(를) 빼거나 별도의 메시지로 보내세요."}},"nb":{"stringUnit":{"state":"needs_review","value":"Ikke sendt: denne meldingen er for stor til å sendes på én gang. Fjern {name} med Rediger, eller send den i en egen melding."}},"pl":{"stringUnit":{"state":"needs_review","value":"Nie wysłano: ta wiadomość jest za duża, by wysłać ją naraz. Usuń {name} przez Edytuj albo wyślij to w osobnej wiadomości."}},"pt-BR":{"stringUnit":{"state":"needs_review","value":"Não enviada: esta mensagem é grande demais para ser enviada de uma vez. Remova {name} com Editar ou envie em uma mensagem separada."}},"ru":{"stringUnit":{"state":"needs_review","value":"Не отправлено: это сообщение слишком велико, чтобы отправить его за раз. Уберите {name} через «Изменить» или отправьте отдельным сообщением."}},"th":{"stringUnit":{"state":"needs_review","value":"ไม่ได้ส่ง: ข้อความนี้ใหญ่เกินกว่าจะส่งในครั้งเดียว นำ {name} ออกด้วย แก้ไข หรือส่งแยกเป็นข้อความของมันเอง"}},"tr":{"stringUnit":{"state":"needs_review","value":"Gönderilmedi: bu ileti tek seferde gönderilemeyecek kadar büyük. Düzenle ile {name} ögesini çıkarın ya da ayrı bir iletide gönderin."}},"uk":{"stringUnit":{"state":"needs_review","value":"Не надіслано: це повідомлення завелике, щоб надіслати його за раз. Приберіть {name} через «Редагувати» або надішліть окремим повідомленням."}},"vi":{"stringUnit":{"state":"needs_review","value":"Chưa gửi: tin nhắn này quá lớn để gửi một lần. Gỡ {name} bằng Sửa, hoặc gửi nó trong một tin nhắn riêng."}},"zh-Hans":{"stringUnit":{"state":"needs_review","value":"未发送:此消息太大,无法一次发送。请用“编辑”移除 {name},或单独发送它。"}},"zh-Hant":{"stringUnit":{"state":"needs_review","value":"未傳送:此訊息太大,無法一次傳送。請用「編輯」移除 {name},或單獨傳送它。"}}}}, + "host.tooLarge": {"comment":"Agent pane: the line above the composer when a chat could not be opened because acpmux's reply was larger than one message; the pane no longer retries on its own, the line's Retry does","extractionState":"manual","localizations":{"en":{"stringUnit":{"state":"translated","value":"This chat is too large to open. Update acpmux, then Retry."}},"ar":{"stringUnit":{"state":"needs_review","value":"هذه المحادثة أكبر من أن تُفتح. حدّث acpmux، ثم أعد المحاولة."}},"bs":{"stringUnit":{"state":"needs_review","value":"Ovaj chat je prevelik za otvaranje. Ažurirajte acpmux, pa pokušajte ponovo."}},"da":{"stringUnit":{"state":"needs_review","value":"Denne chat er for stor til at åbne. Opdater acpmux, og prøv igen."}},"de":{"stringUnit":{"state":"needs_review","value":"Dieser Chat ist zu groß zum Öffnen. Aktualisiere acpmux und versuche es erneut."}},"es":{"stringUnit":{"state":"needs_review","value":"Este chat es demasiado grande para abrirlo. Actualiza acpmux y vuelve a intentarlo."}},"fr":{"stringUnit":{"state":"needs_review","value":"Cette discussion est trop volumineuse pour s’ouvrir. Mettez à jour acpmux, puis réessayez."}},"it":{"stringUnit":{"state":"needs_review","value":"Questa chat è troppo grande per essere aperta. Aggiorna acpmux, poi riprova."}},"ja":{"stringUnit":{"state":"needs_review","value":"このチャットは大きすぎて開けません。acpmux を更新してから再試行してください。"}},"km":{"stringUnit":{"state":"needs_review","value":"ការជជែកនេះធំពេកមិនអាចបើកបានទេ។ ធ្វើបច្ចុប្បន្នភាព acpmux រួចព្យាយាមម្ដងទៀត។"}},"ko":{"stringUnit":{"state":"needs_review","value":"이 채팅은 너무 커서 열 수 없습니다. acpmux를 업데이트한 다음 다시 시도하세요."}},"nb":{"stringUnit":{"state":"needs_review","value":"Denne chatten er for stor til å åpnes. Oppdater acpmux, og prøv igjen."}},"pl":{"stringUnit":{"state":"needs_review","value":"Ten czat jest za duży, by go otworzyć. Zaktualizuj acpmux, a potem spróbuj ponownie."}},"pt-BR":{"stringUnit":{"state":"needs_review","value":"Este chat é grande demais para abrir. Atualize o acpmux e tente novamente."}},"ru":{"stringUnit":{"state":"needs_review","value":"Этот чат слишком велик, чтобы его открыть. Обновите acpmux и повторите попытку."}},"th":{"stringUnit":{"state":"needs_review","value":"แชทนี้ใหญ่เกินกว่าจะเปิดได้ อัปเดต acpmux แล้วลองอีกครั้ง"}},"tr":{"stringUnit":{"state":"needs_review","value":"Bu sohbet açılamayacak kadar büyük. acpmux'u güncelleyip yeniden deneyin."}},"uk":{"stringUnit":{"state":"needs_review","value":"Цей чат завеликий, щоб його відкрити. Оновіть acpmux, а тоді повторіть спробу."}},"vi":{"stringUnit":{"state":"needs_review","value":"Cuộc trò chuyện này quá lớn để mở. Hãy cập nhật acpmux rồi thử lại."}},"zh-Hans":{"stringUnit":{"state":"needs_review","value":"此聊天太大,无法打开。请更新 acpmux,然后重试。"}},"zh-Hant":{"stringUnit":{"state":"needs_review","value":"此聊天太大,無法開啟。請更新 acpmux,然後重試。"}}}}, "featureTip.new": {"comment":"Agent pane feature tip: the pill beside a new feature's title","extractionState":"manual","localizations":{"en":{"stringUnit":{"state":"translated","value":"New"}},"ar":{"stringUnit":{"state":"needs_review","value":"جديد"}},"bs":{"stringUnit":{"state":"needs_review","value":"Novo"}},"da":{"stringUnit":{"state":"needs_review","value":"Ny"}},"de":{"stringUnit":{"state":"needs_review","value":"Neu"}},"es":{"stringUnit":{"state":"needs_review","value":"Nuevo"}},"fr":{"stringUnit":{"state":"needs_review","value":"Nouveau"}},"it":{"stringUnit":{"state":"needs_review","value":"Novità"}},"ja":{"stringUnit":{"state":"needs_review","value":"新機能"}},"km":{"stringUnit":{"state":"needs_review","value":"ថ្មី"}},"ko":{"stringUnit":{"state":"needs_review","value":"새 기능"}},"nb":{"stringUnit":{"state":"needs_review","value":"Ny"}},"pl":{"stringUnit":{"state":"needs_review","value":"Nowość"}},"pt-BR":{"stringUnit":{"state":"needs_review","value":"Novo"}},"ru":{"stringUnit":{"state":"needs_review","value":"Новое"}},"th":{"stringUnit":{"state":"needs_review","value":"ใหม่"}},"tr":{"stringUnit":{"state":"needs_review","value":"Yeni"}},"uk":{"stringUnit":{"state":"needs_review","value":"Нове"}},"vi":{"stringUnit":{"state":"needs_review","value":"Mới"}},"zh-Hans":{"stringUnit":{"state":"needs_review","value":"新"}},"zh-Hant":{"stringUnit":{"state":"needs_review","value":"新"}}}}, "featureTip.close": {"comment":"Agent pane feature tip: the close button's accessible name","extractionState":"manual","localizations":{"en":{"stringUnit":{"state":"translated","value":"Close tip"}},"ar":{"stringUnit":{"state":"needs_review","value":"إغلاق التلميح"}},"bs":{"stringUnit":{"state":"needs_review","value":"Zatvori savjet"}},"da":{"stringUnit":{"state":"needs_review","value":"Luk tip"}},"de":{"stringUnit":{"state":"needs_review","value":"Tipp schließen"}},"es":{"stringUnit":{"state":"needs_review","value":"Cerrar consejo"}},"fr":{"stringUnit":{"state":"needs_review","value":"Fermer l’astuce"}},"it":{"stringUnit":{"state":"needs_review","value":"Chiudi suggerimento"}},"ja":{"stringUnit":{"state":"needs_review","value":"ヒントを閉じる"}},"km":{"stringUnit":{"state":"needs_review","value":"បិទគន្លឹះ"}},"ko":{"stringUnit":{"state":"needs_review","value":"팁 닫기"}},"nb":{"stringUnit":{"state":"needs_review","value":"Lukk tips"}},"pl":{"stringUnit":{"state":"needs_review","value":"Zamknij wskazówkę"}},"pt-BR":{"stringUnit":{"state":"needs_review","value":"Fechar dica"}},"ru":{"stringUnit":{"state":"needs_review","value":"Закрыть подсказку"}},"th":{"stringUnit":{"state":"needs_review","value":"ปิดเคล็ดลับ"}},"tr":{"stringUnit":{"state":"needs_review","value":"İpucunu kapat"}},"uk":{"stringUnit":{"state":"needs_review","value":"Закрити підказку"}},"vi":{"stringUnit":{"state":"needs_review","value":"Đóng mẹo"}},"zh-Hans":{"stringUnit":{"state":"needs_review","value":"关闭提示"}},"zh-Hant":{"stringUnit":{"state":"needs_review","value":"關閉提示"}}}}, "access.addCredits": {"comment":"Agent pane: a failed turn's action that opens the cmux paid-plan Checkout (cmux credits), when the cmux model router refused the turn for want of credits or a plan","extractionState":"manual","localizations":{"en":{"stringUnit":{"state":"translated","value":"Add Credits…"}},"ar":{"stringUnit":{"state":"needs_review","value":"إضافة رصيد…"}},"bs":{"stringUnit":{"state":"needs_review","value":"Dodaj kredite…"}},"da":{"stringUnit":{"state":"needs_review","value":"Tilføj kreditter…"}},"de":{"stringUnit":{"state":"needs_review","value":"Guthaben hinzufügen…"}},"es":{"stringUnit":{"state":"needs_review","value":"Añadir créditos…"}},"fr":{"stringUnit":{"state":"needs_review","value":"Ajouter des crédits…"}},"it":{"stringUnit":{"state":"needs_review","value":"Aggiungi crediti…"}},"ja":{"stringUnit":{"state":"needs_review","value":"クレジットを追加…"}},"km":{"stringUnit":{"state":"needs_review","value":"បន្ថែមឥណទាន…"}},"ko":{"stringUnit":{"state":"needs_review","value":"크레딧 추가…"}},"nb":{"stringUnit":{"state":"needs_review","value":"Legg til kreditter…"}},"pl":{"stringUnit":{"state":"needs_review","value":"Dodaj kredyty…"}},"pt-BR":{"stringUnit":{"state":"needs_review","value":"Adicionar créditos…"}},"ru":{"stringUnit":{"state":"needs_review","value":"Добавить кредиты…"}},"th":{"stringUnit":{"state":"needs_review","value":"เพิ่มเครดิต…"}},"tr":{"stringUnit":{"state":"needs_review","value":"Kredi ekle…"}},"uk":{"stringUnit":{"state":"needs_review","value":"Додати кредити…"}},"vi":{"stringUnit":{"state":"needs_review","value":"Thêm tín dụng…"}},"zh-Hans":{"stringUnit":{"state":"needs_review","value":"添加额度…"}},"zh-Hant":{"stringUnit":{"state":"needs_review","value":"新增額度…"}}}}, diff --git a/webviews/src/agent-session/acpmux/conversation/PromptDelivery.test.tsx b/webviews/src/agent-session/acpmux/conversation/PromptDelivery.test.tsx index 6518ea0bf8d1..63156ad7cf75 100644 --- a/webviews/src/agent-session/acpmux/conversation/PromptDelivery.test.tsx +++ b/webviews/src/agent-session/acpmux/conversation/PromptDelivery.test.tsx @@ -8,8 +8,10 @@ import { act, type ReactNode } from "react"; import { createRoot, type Root } from "react-dom/client"; import { JSDOM } from "jsdom"; import { translate } from "../i18n"; -import type { AcpmuxRow, PromptDelivery } from "../model"; -import { failedRuns } from "../sendFailure"; +import type { AcpmuxRow, AcpmuxSnapshot, PromptDelivery } from "../model"; +import type { ComposerAttachment } from "../attachments"; +import { failedRuns, failedSendText } from "../sendFailure"; +import { messageMenuTarget } from "./messageMenu"; import { DeliveryFailure, FailedSendsContext, type FailedSends } from "./PromptDelivery"; let dom: JSDOM | undefined; @@ -159,3 +161,45 @@ test("Retry waits while the folder's trust question is open, and comes back once expect(button(container, "Retry")!.disabled).toBe(false); expect(button(container, "Trust folder")).toBeUndefined(); }); + +/// A prompt past one message (`message.too_large`, acpmux's `rpc::MAX_MESSAGE_BYTES`) is refused the +/// same way at every Retry (Leo, 2026-10-31: "this is completely hardlocked bc of message size"): its +/// card names the attachment to take out and offers Edit, never Retry. +test("a prompt too large to send names its largest attachment and offers Edit, not Retry", async () => { + const tooLarge = { code: -32000, reason: "message.too_large", message: "message too large to send: 16907300 bytes" }; + const attachment = (id: string, name: string, bytes: number): ComposerAttachment => ({ + id, + kind: "image", + name, + mimeType: "image/png", + size: bytes, + data: "A".repeat(bytes), + }); + const attachments = [ + attachment("a", "small.png", 10), + attachment("b", "screen-recording.png", 4_000), + attachment("c", "notes.png", 20), + ]; + const sentence = failedSendText(tooLarge, false, attachments); + expect(sentence).toBe(translate("prompt.failure.tooLargeFile", { name: "screen-recording.png" })); + expect(sentence).toContain("screen-recording.png"); + expect(sentence).not.toContain("16907300"); + // With no attachment to name (a long pasted text), it still says how to send it. + expect(failedSendText(tooLarge, false)).toBe(translate("prompt.failure.tooLarge")); + + const row: AcpmuxRow = { + ...failed("a", "look at these", { reason: "message.too_large", code: "-32000", error: tooLarge.message }), + error: sentence, + }; + const container = await render(transcript([row], {})); + const card = container.querySelector(".cv-user__status--failed")!; + expect(card.dataset.failureCause).toBe("too-large"); + expect(card.querySelector(".cv-user__failure-text")!.textContent).toBe(sentence); + expect(button(card, "Retry")).toBeUndefined(); + expect(button(card, "Edit")).toBeDefined(); + // Nor from the message's menu (the native context menu offers Retry for `retryRowId`). + const other = failed("b", "deploy", { code: "timeout", error: "no answer" }); + const snapshot = { rows: [row, other], connection: "connected" } as unknown as AcpmuxSnapshot; + expect(messageMenuTarget(snapshot, "a")?.retryRowId).toBeUndefined(); + expect(messageMenuTarget(snapshot, "b")?.retryRowId).toBe("b"); +}); diff --git a/webviews/src/agent-session/acpmux/conversation/PromptDelivery.tsx b/webviews/src/agent-session/acpmux/conversation/PromptDelivery.tsx index 2aeaa185c1b5..7736e0131527 100644 --- a/webviews/src/agent-session/acpmux/conversation/PromptDelivery.tsx +++ b/webviews/src/agent-session/acpmux/conversation/PromptDelivery.tsx @@ -7,7 +7,7 @@ import { createContext, useContext, useEffect, useState } from "react"; import type { AcpmuxRow, PromptDelivery } from "../model"; import { useT, type StringKey } from "../i18n"; -import { isTrustCause, sendCause, type FailedRun } from "../sendFailure"; +import { isTrustCause, retryHelps, sendCause, type FailedRun } from "../sendFailure"; import { copyText } from "./clipboard"; import { Clock } from "./icons"; @@ -97,7 +97,8 @@ export function DeliveryFailure({ row }: { row: AcpmuxRow }) { if (!row.failed) return null; const rowIds = sends?.runs.get(row.id)?.rowIds ?? [row.id]; if (rowIds[rowIds.length - 1] !== row.id) return null; - const trust = isTrustCause(sendCause(delivery)); + const cause = sendCause(delivery); + const trust = isTrustCause(cause); const retryWaits = trust ? sends?.trustBlock : undefined; const fix = trust ? sends?.trustFolder : undefined; const act = (name: string) => void window.cmuxAcpmuxActions?.[name]?.({ rowId: row.id, rowIds }); @@ -107,7 +108,7 @@ export function DeliveryFailure({ row }: { row: AcpmuxRow }) { return (
@@ -120,15 +121,18 @@ export function DeliveryFailure({ row }: { row: AcpmuxRow }) { {t("trust.trustFolder")} )} - + {/* A prompt too large to send is refused the same way again: Edit takes an attachment out. */} + {retryHelps(cause) && ( + + )} {delivery && ( <>