Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 13 additions & 8 deletions webviews/src/agent-session/acpmux/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7985,6 +7985,7 @@ function AcpmuxPane() {
const queuedMessageActionsRef = useRef<QueuedMessageActionsValue | undefined>(undefined);
// The chat's Codex skills, for its sent prompts' `/$name` (conversation/skillWords.ts).
const skillWords = useSkillWords(snapshot.commands);
const queueRunsNext = snapshot.queue.some((entry) => !entry.heldByStop && !entry.held && !entry.awaitingOwner);
// The waiting tagged-ask card's first answer, which the composer's ⌘↩ on an empty prompt sends
// (NextStepCard.tsx, TurnActions `nextStepKey`).
const [nextStepFire, setNextStepFire] = useState<{ fire: () => void }>();
Expand Down Expand Up @@ -8060,14 +8061,17 @@ function AcpmuxPane() {
// reader's prompt and starts the next turn, or queues behind one already sent. Like Restart
// (restart.ts), never to a View Only chat, a subagent's read-only pane or where the composer
// cannot send, and only to the chat this pane still shows, never the next during a switch's hold.
...(restartChat && {
resume: (
(owner: string) => () =>
restartReaches(owner, sessionIdRef.current, directClient.current?.selectedSession)
? resumeThrough(callNative)()
: Promise.resolve()
)(restartChat),
}),
// A prompt that runs next anyway (a queued one no Stop or owner holds, such as a message acpmux
// queued again after the Stop) starts the next turn: no Continue beside it.
...(restartChat &&
!queueRunsNext && {
resume: (
(owner: string) => () =>
restartReaches(owner, sessionIdRef.current, directClient.current?.selectedSession)
? resumeThrough(callNative)()
: Promise.resolve()
)(restartChat),
}),
// A tagged ask's answer (NextStepCard.tsx) sends as Continue does, with the same reach.
...(restartChat && {
nextStepKey,
Expand Down Expand Up @@ -8216,6 +8220,7 @@ function AcpmuxPane() {
catalog,
restartChat,
snapshot.commands,
queueRunsNext,
nextStepKey,
],
);
Expand Down
4 changes: 3 additions & 1 deletion webviews/src/agent-session/acpmux/Localizable.xcstrings
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
{
"sourceLanguage": "en",
"strings": {
"turn.stoppedMark": {"comment":"Agent pane: one muted word in a stopped turn's footer (the person's Stop, or one cmux had to force); short","extractionState":"manual","localizations":{"ar":{"stringUnit":{"state":"needs_review","value":"متوقف"}},"bs":{"stringUnit":{"state":"needs_review","value":"Zaustavljeno"}},"da":{"stringUnit":{"state":"needs_review","value":"Stoppet"}},"de":{"stringUnit":{"state":"needs_review","value":"Gestoppt"}},"en":{"stringUnit":{"state":"translated","value":"Stopped"}},"es":{"stringUnit":{"state":"needs_review","value":"Detenido"}},"fr":{"stringUnit":{"state":"needs_review","value":"Arrêté"}},"it":{"stringUnit":{"state":"needs_review","value":"Interrotto"}},"ja":{"stringUnit":{"state":"needs_review","value":"停止済み"}},"km":{"stringUnit":{"state":"needs_review","value":"បានឈប់"}},"ko":{"stringUnit":{"state":"needs_review","value":"중지됨"}},"nb":{"stringUnit":{"state":"needs_review","value":"Stoppet"}},"pl":{"stringUnit":{"state":"needs_review","value":"Zatrzymano"}},"pt-BR":{"stringUnit":{"state":"needs_review","value":"Interrompido"}},"ru":{"stringUnit":{"state":"needs_review","value":"Остановлено"}},"th":{"stringUnit":{"state":"needs_review","value":"หยุดแล้ว"}},"tr":{"stringUnit":{"state":"needs_review","value":"Durduruldu"}},"uk":{"stringUnit":{"state":"needs_review","value":"Зупинено"}},"vi":{"stringUnit":{"state":"needs_review","value":"Đã dừng"}},"zh-Hans":{"stringUnit":{"state":"needs_review","value":"已停止"}},"zh-Hant":{"stringUnit":{"state":"needs_review","value":"已停止"}}}},
"turn.continueHelp": {"comment":"Agent pane: tooltip of the Continue button on a stopped turn's footer; it sends the prompt “Continue” to the agent","extractionState":"manual","localizations":{"ar":{"stringUnit":{"state":"needs_review","value":"يرسل «متابعة» ليكمل الوكيل من حيث توقف"}},"bs":{"stringUnit":{"state":"needs_review","value":"Šalje „Nastavi“ da agent nastavi tamo gdje je stao"}},"da":{"stringUnit":{"state":"needs_review","value":"Sender “Fortsæt”, så agenten fortsætter, hvor den stoppede"}},"de":{"stringUnit":{"state":"needs_review","value":"Sendet „Fortfahren“, damit der Agent dort weitermacht, wo er aufgehört hat"}},"en":{"stringUnit":{"state":"translated","value":"Sends “Continue” so the agent picks up where it stopped"}},"es":{"stringUnit":{"state":"needs_review","value":"Envía «Continuar» para que el agente siga donde se detuvo"}},"fr":{"stringUnit":{"state":"needs_review","value":"Envoie « Continuer » pour que l’agent reprenne là où il s’est arrêté"}},"it":{"stringUnit":{"state":"needs_review","value":"Invia «Continua» perché l’agente riprenda da dove si è fermato"}},"ja":{"stringUnit":{"state":"needs_review","value":"「続ける」を送信し、エージェントが停止したところから再開します"}},"km":{"stringUnit":{"state":"needs_review","value":"ផ្ញើ “បន្ត” ដើម្បីឱ្យភ្នាក់ងារបន្តពីកន្លែងដែលវាបានឈប់"}},"ko":{"stringUnit":{"state":"needs_review","value":"“계속”을 보내 에이전트가 멈춘 곳부터 이어서 하게 합니다"}},"nb":{"stringUnit":{"state":"needs_review","value":"Sender «Fortsett» slik at agenten fortsetter der den stoppet"}},"pl":{"stringUnit":{"state":"needs_review","value":"Wysyła „Kontynuuj”, aby agent wznowił pracę tam, gdzie przerwał"}},"pt-BR":{"stringUnit":{"state":"needs_review","value":"Envia “Continuar” para o agente retomar de onde parou"}},"ru":{"stringUnit":{"state":"needs_review","value":"Отправляет «Продолжить», чтобы агент продолжил с места остановки"}},"th":{"stringUnit":{"state":"needs_review","value":"ส่ง “ดำเนินการต่อ” เพื่อให้เอเจนต์ทำต่อจากจุดที่หยุด"}},"tr":{"stringUnit":{"state":"needs_review","value":"Ajanın kaldığı yerden devam etmesi için “Devam et” gönderir"}},"uk":{"stringUnit":{"state":"needs_review","value":"Надсилає «Продовжити», щоб агент продовжив з місця зупинки"}},"vi":{"stringUnit":{"state":"needs_review","value":"Gửi “Tiếp tục” để tác tử làm tiếp từ chỗ đã dừng"}},"zh-Hans":{"stringUnit":{"state":"needs_review","value":"发送“继续”,让智能体从停下的地方接着做"}},"zh-Hant":{"stringUnit":{"state":"needs_review","value":"傳送「繼續」,讓代理程式從停下的地方接著做"}}}},
"composer.heldForWake": {"comment":"Agent pane: heads the prompts sent in a Cloud chat whose machine was paused; the machine wakes and they go once the chat is back","extractionState":"manual","localizations":{"ar":{"stringUnit":{"state":"needs_review","value":"جارٍ تشغيل الجهاز…"}},"bs":{"stringUnit":{"state":"needs_review","value":"Mašina se budi…"}},"da":{"stringUnit":{"state":"needs_review","value":"Vækker maskinen…"}},"de":{"stringUnit":{"state":"needs_review","value":"Maschine wird geweckt…"}},"en":{"stringUnit":{"state":"translated","value":"Waking the machine…"}},"es":{"stringUnit":{"state":"needs_review","value":"Activando la máquina…"}},"fr":{"stringUnit":{"state":"needs_review","value":"Réveil de la machine…"}},"it":{"stringUnit":{"state":"needs_review","value":"Riattivazione della macchina…"}},"ja":{"stringUnit":{"state":"needs_review","value":"マシンを起動しています…"}},"km":{"stringUnit":{"state":"needs_review","value":"កំពុងដាស់ម៉ាស៊ីន…"}},"ko":{"stringUnit":{"state":"needs_review","value":"머신을 깨우는 중…"}},"nb":{"stringUnit":{"state":"needs_review","value":"Vekker maskinen…"}},"pl":{"stringUnit":{"state":"needs_review","value":"Wybudzanie maszyny…"}},"pt-BR":{"stringUnit":{"state":"needs_review","value":"Ativando a máquina…"}},"ru":{"stringUnit":{"state":"needs_review","value":"Пробуждение машины…"}},"th":{"stringUnit":{"state":"needs_review","value":"กำลังปลุกเครื่อง…"}},"tr":{"stringUnit":{"state":"needs_review","value":"Makine uyandırılıyor…"}},"uk":{"stringUnit":{"state":"needs_review","value":"Пробудження машини…"}},"vi":{"stringUnit":{"state":"needs_review","value":"Đang đánh thức máy…"}},"zh-Hans":{"stringUnit":{"state":"needs_review","value":"正在唤醒机器…"}},"zh-Hant":{"stringUnit":{"state":"needs_review","value":"正在喚醒機器…"}}}},
"tools.approved": {"comment":"Agent pane: at the end of a tool row whose permission ask the person approved (Allow once or Always allow); the approval reads on its row, never as a line of its own","extractionState":"manual","localizations":{"ar":{"stringUnit":{"state":"translated","value":"تمت الموافقة"}},"bs":{"stringUnit":{"state":"translated","value":"Odobreno"}},"da":{"stringUnit":{"state":"translated","value":"Godkendt"}},"de":{"stringUnit":{"state":"translated","value":"Genehmigt"}},"en":{"stringUnit":{"state":"translated","value":"Approved"}},"es":{"stringUnit":{"state":"translated","value":"Aprobado"}},"fr":{"stringUnit":{"state":"translated","value":"Approuvée"}},"it":{"stringUnit":{"state":"translated","value":"Approvata"}},"ja":{"stringUnit":{"state":"translated","value":"承認済み"}},"km":{"stringUnit":{"state":"translated","value":"បានយល់ព្រម"}},"ko":{"stringUnit":{"state":"translated","value":"승인됨"}},"nb":{"stringUnit":{"state":"translated","value":"Godkjent"}},"pl":{"stringUnit":{"state":"translated","value":"Zatwierdzono"}},"pt-BR":{"stringUnit":{"state":"translated","value":"Aprovado"}},"ru":{"stringUnit":{"state":"translated","value":"Одобрено"}},"th":{"stringUnit":{"state":"translated","value":"อนุมัติแล้ว"}},"tr":{"stringUnit":{"state":"translated","value":"Onaylandı"}},"uk":{"stringUnit":{"state":"translated","value":"Схвалено"}},"vi":{"stringUnit":{"state":"translated","value":"Đã duyệt"}},"zh-Hans":{"stringUnit":{"state":"translated","value":"已批准"}},"zh-Hant":{"stringUnit":{"state":"translated","value":"已核准"}}}},
"subagents.state.idle": {"comment":"Agent pane: a running subagent's state word when it has done nothing for minutes with no call open (it waits on background work of its own)","extractionState":"manual","localizations":{"ar":{"stringUnit":{"state":"needs_review","value":"خامل"}},"bs":{"stringUnit":{"state":"needs_review","value":"Neaktivan"}},"da":{"stringUnit":{"state":"needs_review","value":"Inaktiv"}},"de":{"stringUnit":{"state":"needs_review","value":"Untätig"}},"en":{"stringUnit":{"state":"translated","value":"Idle"}},"es":{"stringUnit":{"state":"needs_review","value":"Inactivo"}},"fr":{"stringUnit":{"state":"needs_review","value":"Inactif"}},"it":{"stringUnit":{"state":"needs_review","value":"Inattivo"}},"ja":{"stringUnit":{"state":"needs_review","value":"待機中"}},"km":{"stringUnit":{"state":"needs_review","value":"ទំនេរ"}},"ko":{"stringUnit":{"state":"needs_review","value":"유휴"}},"nb":{"stringUnit":{"state":"needs_review","value":"Inaktiv"}},"pl":{"stringUnit":{"state":"needs_review","value":"Bezczynny"}},"pt-BR":{"stringUnit":{"state":"needs_review","value":"Ocioso"}},"ru":{"stringUnit":{"state":"needs_review","value":"Простаивает"}},"th":{"stringUnit":{"state":"needs_review","value":"ว่าง"}},"tr":{"stringUnit":{"state":"needs_review","value":"Boşta"}},"uk":{"stringUnit":{"state":"needs_review","value":"Простоює"}},"vi":{"stringUnit":{"state":"needs_review","value":"Đang rảnh"}},"zh-Hans":{"stringUnit":{"state":"needs_review","value":"空闲"}},"zh-Hant":{"stringUnit":{"state":"needs_review","value":"閒置"}}}},
Expand Down Expand Up @@ -8051,7 +8053,7 @@
}
},
"stop.forced": {
"comment": "Agent pane: transcript note when the agent never answered Stop and cmux ended its turn itself",
"comment": "Agent pane: in the tooltips of a stopped turn's footer (never the transcript text) when the agent never answered Stop and cmux ended its turn itself",
"extractionState": "manual",
"localizations": {
"ar": {
Expand Down
21 changes: 18 additions & 3 deletions webviews/src/agent-session/acpmux/conversation/TurnRows.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ import { groupId, runStretches, toolGroups, type ToolGroup } from "./toolGroups"
import type { RetryNote } from "../subagents/retryState";
import { TurnActionsContext } from "./turnActions";
import { errorRecovery, signInRetry, type ErrorRecovery } from "./errorRecovery";
import { footerLabel, isEmptyFooter, workedLabel } from "./turns";
import { footerLabel, isEmptyFooter, isStopped, workedLabel } from "./turns";
import { stoppedTurnLook } from "./stoppedTurn.experiment";
import { turnCopyText } from "./turnCopy";
import { hasTokens, TimedLabel, tokensText, TurnTokens } from "./TurnTokens";
import { AlertCircle, ArrowRight, ChevronRight, Retry } from "./icons";
Expand Down Expand Up @@ -301,9 +302,20 @@ export function TurnFooter({ row }: { row: AcpmuxRow }) {
const authenticationFailed = (kind === "subscription-login" || kind === "auth") && !teamMachine;
const label = footerLabel(t, row);
// The turn's time and tokens, for the time's tooltip: "Worked for 13s · ↓ 359 out ↑ 328.8K in".
// A Stop acpmux had to force (the agent never answered it) says so here only, never in the
// transcript's text (Leo, 2026-11-03: "idk if it should be showing things like that").
const forced = row.stopForced ? t("stop.forced") : undefined;
const detail =
[label, hasTokens(row.tokens) ? tokensText(t, language, row.tokens) : undefined].filter(Boolean).join(" · ") ||
undefined;
[label, hasTokens(row.tokens) ? tokensText(t, language, row.tokens) : undefined, forced]
.filter(Boolean)
.join(" · ") || undefined;
// A stopped turn says so in one muted word, whoever ended it (stoppedTurn.experiment.ts).
const stoppedLook = isStopped(row) ? experimentArm(stoppedTurnLook) : "none";
const stoppedWord = stoppedLook !== "none" && (
<span className="cv-turn-stopped" title={forced ?? workedLabel(t, row)}>
{t("turn.stoppedMark")}
</span>
);
// The last turn, stopped and not yet followed by a prompt, offers Continue (Codex's): it sends the
// continue prompt as the composer would, and the button goes once that prompt shows (turns.ts).
const resumable = row.resumable === true && resume !== undefined;
Expand Down Expand Up @@ -444,10 +456,12 @@ export function TurnFooter({ row }: { row: AcpmuxRow }) {
</button>
)}
</span>
{stoppedLook === "word" && stoppedWord}
{resumable && (
<button
type="button"
className="cv-iconbtn cv-turn-continue"
title={t("turn.continueHelp")}
disabled={resuming}
onClick={() => {
setResuming(true);
Expand All @@ -458,6 +472,7 @@ export function TurnFooter({ row }: { row: AcpmuxRow }) {
<ArrowRight size={ROW_ICON} aria-hidden="true" />
</button>
)}
{stoppedLook === "trailing" && stoppedWord}
{!messages && <ReplyTime at={row.at} detail={detail} />}
</MessageFooterRow>
</div>
Expand Down
11 changes: 11 additions & 0 deletions webviews/src/agent-session/acpmux/conversation/conversation.css
Original file line number Diff line number Diff line change
Expand Up @@ -4200,6 +4200,17 @@ a.cv-sha:focus-visible {
margin-inline-start: -2px;
white-space: nowrap;
}
/* A stopped turn's one muted word (TurnRows.tsx `stoppedWord`), in the time's caption size and the
footer's resting ink: what ended the turn is its tooltip, never the transcript's text. */
.cv-turn-actions .cv-turn-stopped {
display: inline-flex;
align-items: center;
height: var(--cv-footer-row);
color: var(--cv-footer-ink);
font-size: var(--text-caption);
letter-spacing: var(--text-caption--letter-spacing);
white-space: nowrap;
}
.cv-iconbtn.cv-turn-continue:disabled {
opacity: 0.5;
pointer-events: none;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// l10n-allow-file: experiment descriptions are gallery (developer tool) text, never shown in the pane.
import { defineExperiment } from "../../../experiments/experiment";

/// How a stopped turn says it stopped (Leo, 2026-11-03, on "The agent didn't answer Stop, so cmux
/// ended its turn." under a Continue button: "this is an "interesting" recovery flow... idk if it
/// should be showing things like that"). A Stop acpmux had to force reads as any other Stop; only the
/// footer's tooltips say cmux ended it. Compare the arms in the gallery's `agent-pane.stopped-turn`.
export const stoppedTurnLook = defineExperiment({
id: "stopped-turn-look",
title: "Stopped turn",
description: "How a stopped turn's footer says it stopped, whoever ended it.",
arms: {
word: {
label: "Stopped, then Continue",
description: "A muted Stopped after the footer's buttons, then Continue on the last turn.",
},
trailing: {
label: "Continue, then Stopped",
description: "Continue first, the muted Stopped after it, before the time.",
},
none: {
label: "Continue only",
description: "No word: the footer's buttons and Continue, as a stop drew before; Stopped is in the tooltip.",
},
},
defaultArm: "word",
});
Loading
Loading