fix(goals): /goal resume actually restarts work after budget exhaustion

After a standing goal auto-paused on turn-budget exhaustion, every
surface's /goal resume handler only flipped the persisted state back to
active (and reset turns_used) and rendered an acknowledgement — nothing
re-entered the conversation loop, so the goal sat idle until the user
sent another ordinary message.

Fix the whole class by scheduling the canonical
GoalManager.next_continuation_prompt() through each surface's existing
input path after a successful resume:

- Desktop/TUI (tui_gateway/methods_tools.py command.dispatch): return a
  sendable {type: "send"} dispatch with the continuation as the message,
  a "Continuing now" notice, and display "/goal resume" so the
  transcript shows the concise invocation instead of the model-facing
  scaffolding. No-goal keeps the exec response.
- Classic CLI (hermes_cli/cli_commands_mixin.py): put the continuation
  on _pending_input, same as the /goal <text> kickoff.
- Messaging gateway (gateway/slash_commands.py): enqueue a continuation
  MessageEvent through the adapter FIFO — the same path the post-turn
  judge uses — so queued real user messages preempt naturally and the
  pause/clear stale-continuation cleanup recognizes it.

Also correct the now-misleading gateway.goal.resumed copy ("Send any
message to continue…") across all 17 locale files.

Regression tests cover exact budget exhaustion → resume on the real CLI
handler, the real gateway handler (including the
_is_goal_continuation_event guard contract), and the TUI
command.dispatch boundary; verified each fails on the pre-fix code.

Fixes #75362
This commit is contained in:
Teknium
2026-08-18 16:43:42 -07:00
parent 51013e9ae0
commit bdd0a79c6a
22 changed files with 309 additions and 26 deletions
+1 -1
View File
@@ -142,7 +142,7 @@ gateway:
no_goal_set: "Geen doelwit gestel nie."
paused: "⏸ Doelwit gepouse: {goal}"
no_resume: "Geen doelwit om voort te sit nie."
resumed: "▶ Doelwit hervat: {goal}\nStuur enige boodskap om voort te gaan, of wag — ek sal die volgende stap met die volgende beurt neem."
resumed: "▶ Doelwit hervat: {goal}\nGaan nou voort — ek neem dadelik die volgende stap."
invalid: "Ongeldige doelwit: {error}"
set: "⊙ Doelwit gestel ({budget}-beurt-begroting): {goal}\nEk sal aanhou werk totdat die doelwit klaar is, jy dit pouseer/verwyder, of die begroting opgebruik is.\nBeheer: /goal status · /goal pause · /goal resume · /goal clear"
+1 -1
View File
@@ -165,7 +165,7 @@ gateway:
no_goal_set: "لم يُحدَّد هدف."
paused: "⏸ أُوقف الهدف مؤقتًا: {goal}"
no_resume: "لا يوجد هدف لاستئنافه."
resumed: "▶ استُؤنف الهدف: {goal}\nأرسل أي رسالة للمتابعة، أو انتظر — سأتخذ الخطوة التالية في الدور القادم."
resumed: "▶ استُؤنف الهدف: {goal}\nسأتابع الآن — سأتخذ الخطوة التالية فورًا."
invalid: "هدف غير صالح: {error}"
set: "⊙ حُدِّد الهدف (ميزانية {budget} دور): {goal}\nسأواصل العمل حتى يكتمل الهدف، أو توقفه/تمسحه، أو تنفد الميزانية.\nالتحكم: /goal status · /goal pause · /goal resume · /goal clear"
+1 -1
View File
@@ -142,7 +142,7 @@ gateway:
no_goal_set: "Kein Ziel gesetzt."
paused: "⏸ Ziel pausiert: {goal}"
no_resume: "Kein Ziel zum Fortsetzen."
resumed: "▶ Ziel fortgesetzt: {goal}\nSenden Sie eine Nachricht zum Fortfahren oder warten Sie — ich übernehme den nächsten Schritt im nächsten Zug."
resumed: "▶ Ziel fortgesetzt: {goal}\nEs geht sofort weiter — ich übernehme jetzt den nächsten Schritt."
invalid: "Ungültiges Ziel: {error}"
set: "⊙ Ziel gesetzt ({budget}-Zug-Budget): {goal}\nIch arbeite weiter, bis das Ziel erreicht ist, Sie es pausieren/löschen oder das Budget aufgebraucht ist.\nSteuerung: /goal status · /goal pause · /goal resume · /goal clear"
+1 -1
View File
@@ -157,7 +157,7 @@ gateway:
no_goal_set: "No goal set."
paused: "⏸ Goal paused: {goal}"
no_resume: "No goal to resume."
resumed: "▶ Goal resumed: {goal}\nSend any message to continue, or wait — I'll take the next step on the next turn."
resumed: "▶ Goal resumed: {goal}\nContinuing now — I'll take the next step right away."
invalid: "Invalid goal: {error}"
set: "⊙ Goal set ({budget}-turn budget): {goal}\nI'll keep working until the goal is done, you pause/clear it, or the budget is exhausted.\nControls: /goal status · /goal pause · /goal resume · /goal clear"
+1 -1
View File
@@ -142,7 +142,7 @@ gateway:
no_goal_set: "No hay objetivo establecido."
paused: "⏸ Objetivo pausado: {goal}"
no_resume: "No hay objetivo para reanudar."
resumed: "▶ Objetivo reanudado: {goal}\nEnvía cualquier mensaje para continuar, o espera — daré el siguiente paso en el próximo turno."
resumed: "▶ Objetivo reanudado: {goal}\nContinuando ahora — daré el siguiente paso de inmediato."
invalid: "Objetivo no válido: {error}"
set: "⊙ Objetivo establecido (presupuesto de {budget} turnos): {goal}\nSeguiré trabajando hasta que el objetivo se complete, lo pauses/elimines o se agote el presupuesto.\nControles: /goal status · /goal pause · /goal resume · /goal clear"
+1 -1
View File
@@ -142,7 +142,7 @@ gateway:
no_goal_set: "Aucun objectif défini."
paused: "⏸ Objectif en pause : {goal}"
no_resume: "Aucun objectif à reprendre."
resumed: "▶ Objectif repris : {goal}\nEnvoyez un message pour continuer, ou attendez — je passerai à l'étape suivante au prochain tour."
resumed: "▶ Objectif repris : {goal}\nReprise immédiate — je passe tout de suite à l'étape suivante."
invalid: "Objectif invalide : {error}"
set: "⊙ Objectif défini (budget de {budget} tours) : {goal}\nJe continuerai jusqu'à ce que l'objectif soit terminé, que vous le mettiez en pause/effaciez, ou que le budget soit épuisé.\nContrôles : /goal status · /goal pause · /goal resume · /goal clear"
+1 -1
View File
@@ -146,7 +146,7 @@ gateway:
no_goal_set: "Níl aon sprioc socraithe."
paused: "⏸ Sprioc curtha ar sos: {goal}"
no_resume: "Níl aon sprioc le hatosú."
resumed: "▶ Sprioc atosaithe: {goal}\nSeol teachtaireacht ar bith chun leanúint, nó fan — déanfaidh mé an chéad chéim eile sa chéad seal eile."
resumed: "▶ Sprioc atosaithe: {goal}\nAg leanúint ar aghaidh anois — déanfaidh mé an chéad chéim eile láithreach."
invalid: "Sprioc neamhbhailí: {error}"
set: "⊙ Sprioc socraithe (buiséad {budget} seal): {goal}\nLeanfaidh mé ag obair go dtí go bhfuil an sprioc críochnaithe, go gcuirfidh tú ar sos / go nglanfaidh tú í, nó go n-úsáidfear an buiséad.\nSmacht: /goal status · /goal pause · /goal resume · /goal clear"
+1 -1
View File
@@ -142,7 +142,7 @@ gateway:
no_goal_set: "Nincs cél beállítva."
paused: "⏸ Cél szüneteltetve: {goal}"
no_resume: "Nincs folytatható cél."
resumed: "▶ Cél folytatva: {goal}\nKüldj bármilyen üzenetet a folytatáshoz, vagy várj — a következő körben megteszem a következő lépést."
resumed: "▶ Cél folytatva: {goal}\nMáris folytatom — azonnal megteszem a következő lépést."
invalid: "Érvénytelen cél: {error}"
set: "⊙ Cél beállítva ({budget} körös keret): {goal}\nDolgozni fogok rajta, amíg a cél el nem készül, te nem szünetelteted/törlöd, vagy a keret ki nem merül.\nVezérlés: /goal status · /goal pause · /goal resume · /goal clear"
+1 -1
View File
@@ -142,7 +142,7 @@ gateway:
no_goal_set: "Nessun obiettivo impostato."
paused: "⏸ Obiettivo in pausa: {goal}"
no_resume: "Nessun obiettivo da riprendere."
resumed: "▶ Obiettivo ripreso: {goal}\nInvia un messaggio per continuare, oppure aspetta — farò il prossimo passo al turno successivo."
resumed: "▶ Obiettivo ripreso: {goal}\nContinuo subito — farò immediatamente il prossimo passo."
invalid: "Obiettivo non valido: {error}"
set: "⊙ Obiettivo impostato (budget di {budget} turni): {goal}\nContinuerò a lavorare finché l'obiettivo non sarà completato, lo metterai in pausa/lo cancellerai, oppure il budget sarà esaurito.\nControlli: /goal status · /goal pause · /goal resume · /goal clear"
+1 -1
View File
@@ -142,7 +142,7 @@ gateway:
no_goal_set: "目標が設定されていません。"
paused: "⏸ 目標を一時停止しました: {goal}"
no_resume: "再開する目標がありません。"
resumed: "▶ 目標を再開しました: {goal}\nメッセージを送って続行するか、お待ちください — 次のターンで続きを進めます。"
resumed: "▶ 目標を再開しました: {goal}\nすぐに続行します — 直ちに次のステップに進みます。"
invalid: "無効な目標: {error}"
set: "⊙ 目標を設定しました ({budget} ターンの予算): {goal}\n目標が完了するか、一時停止/解除されるか、予算が尽きるまで作業を続けます。\nコントロール: /goal status · /goal pause · /goal resume · /goal clear"
+1 -1
View File
@@ -142,7 +142,7 @@ gateway:
no_goal_set: "설정된 목표가 없습니다."
paused: "⏸ 목표 일시정지: {goal}"
no_resume: "재개할 목표가 없습니다."
resumed: "▶ 목표 재개: {goal}\n메시지를 보내 계속하거나 기다려 주세요 — 다음 차례에 다음 단계를 진행하겠습니다."
resumed: "▶ 목표 재개: {goal}\n바로 계속합니다 — 즉시 다음 단계를 진행하겠습니다."
invalid: "잘못된 목표: {error}"
set: "⊙ 목표 설정됨 ({budget}회 예산): {goal}\n목표가 완료되거나, 일시정지/삭제하거나, 예산이 소진될 때까지 계속 작업하겠습니다.\n제어: /goal status · /goal pause · /goal resume · /goal clear"
+1 -1
View File
@@ -142,7 +142,7 @@ gateway:
no_goal_set: "Nenhum objetivo definido."
paused: "⏸ Objetivo pausado: {goal}"
no_resume: "Nenhum objetivo para retomar."
resumed: "▶ Objetivo retomado: {goal}\nEnvia qualquer mensagem para continuar, ou aguarda — darei o próximo passo no próximo turno."
resumed: "▶ Objetivo retomado: {goal}\nContinuando agora — darei o próximo passo imediatamente."
invalid: "Objetivo inválido: {error}"
set: "⊙ Objetivo definido (orçamento de {budget} turnos): {goal}\nVou continuar a trabalhar até o objetivo estar concluído, pausares/limpares ou o orçamento esgotar.\nControlos: /goal status · /goal pause · /goal resume · /goal clear"
+1 -1
View File
@@ -142,7 +142,7 @@ gateway:
no_goal_set: "Цель не задана."
paused: "⏸ Цель приостановлена: {goal}"
no_resume: "Нет цели для возобновления."
resumed: "▶ Цель возобновлена: {goal}\nОтправьте любое сообщение, чтобы продолжить, или подождите — я сделаю следующий шаг на следующем ходу."
resumed: "▶ Цель возобновлена: {goal}\nПродолжаю прямо сейчас — немедленно сделаю следующий шаг."
invalid: "Недопустимая цель: {error}"
set: "⊙ Цель задана (бюджет {budget} ходов): {goal}\nЯ продолжу работу, пока цель не будет достигнута, вы её не приостановите/очистите, или бюджет не исчерпается.\nУправление: /goal status · /goal pause · /goal resume · /goal clear"
+1 -1
View File
@@ -142,7 +142,7 @@ gateway:
no_goal_set: "Hedef ayarlanmadı."
paused: "⏸ Hedef duraklatıldı: {goal}"
no_resume: "Devam ettirilecek hedef yok."
resumed: "▶ Hedef devam ettirildi: {goal}\nDevam etmek için herhangi bir mesaj gönderin veya bekleyin — bir sonraki turda adımı atacağım."
resumed: "▶ Hedef devam ettirildi: {goal}\nHemen devam ediyorum — bir sonraki adımı derhal atacağım."
invalid: "Geçersiz hedef: {error}"
set: "⊙ Hedef ayarlandı ({budget} turluk bütçe): {goal}\nHedef tamamlanana, siz duraklatana/temizleyene veya bütçe tükenene kadar çalışmaya devam edeceğim.\nKontroller: /goal status · /goal pause · /goal resume · /goal clear"
+1 -1
View File
@@ -142,7 +142,7 @@ gateway:
no_goal_set: "Ціль не встановлено."
paused: "⏸ Ціль призупинено: {goal}"
no_resume: "Немає цілі для продовження."
resumed: "▶ Ціль відновлено: {goal}\nНадішліть будь-яке повідомлення, щоб продовжити, або зачекайте — я зроблю наступний крок у наступному ході."
resumed: "▶ Ціль відновлено: {goal}\nПродовжую зараз — негайно зроблю наступний крок."
invalid: "Неприпустима ціль: {error}"
set: "⊙ Ціль встановлено (бюджет {budget} ходів): {goal}\nЯ продовжуватиму працювати, доки ціль не буде досягнута, ви її не призупините/очистите, або бюджет не вичерпається.\nКерування: /goal status · /goal pause · /goal resume · /goal clear"
+1 -1
View File
@@ -142,7 +142,7 @@ gateway:
no_goal_set: "未設定目標。"
paused: "⏸ 目標已暫停:{goal}"
no_resume: "沒有可恢復的目標。"
resumed: "▶ 目標已恢復:{goal}\n傳送任意訊息繼續,或等待 — 我會在下一輪繼續推進。"
resumed: "▶ 目標已恢復:{goal}\n立即繼續 — 我馬上進行下一步。"
invalid: "無效目標:{error}"
set: "⊙ 目標已設定({budget} 輪預算):{goal}\n我會持續工作直到目標完成、你暫停/清除目標,或預算耗盡。\n控制指令:/goal status · /goal pause · /goal resume · /goal clear"
+1 -1
View File
@@ -142,7 +142,7 @@ gateway:
no_goal_set: "未设置目标。"
paused: "⏸ 目标已暂停:{goal}"
no_resume: "没有可恢复的目标。"
resumed: "▶ 目标已恢复:{goal}\n发送任意消息继续,或等待 — 我会在下一轮继续推进。"
resumed: "▶ 目标已恢复:{goal}\n立即继续 — 我马上进行下一步。"
invalid: "无效目标:{error}"
set: "⊙ 目标已设置({budget} 轮预算):{goal}\n我将持续工作直到目标完成、你暂停/清除它,或预算耗尽。\n控制命令:/goal status · /goal pause · /goal resume · /goal clear"