mirror of
https://github.com/nesquena/hermes-webui.git
synced 2026-05-25 03:00:23 +00:00
fix(i18n): localize /goal runtime status strings
This commit is contained in:
+123
-4
@@ -4,6 +4,7 @@ from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import logging
|
||||
import re
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Optional
|
||||
@@ -279,6 +280,8 @@ def _payload(
|
||||
error: str | None = None,
|
||||
kickoff_prompt: str | None = None,
|
||||
decision: Dict[str, Any] | None = None,
|
||||
message_key: str | None = None,
|
||||
message_args: list[Any] | None = None,
|
||||
) -> Dict[str, Any]:
|
||||
body: Dict[str, Any] = {
|
||||
"ok": bool(ok),
|
||||
@@ -292,9 +295,98 @@ def _payload(
|
||||
body["kickoff_prompt"] = kickoff_prompt
|
||||
if decision is not None:
|
||||
body["decision"] = decision
|
||||
if message_key:
|
||||
body["message_key"] = message_key
|
||||
if message_args is not None:
|
||||
body["message_args"] = [a for a in message_args if a is not None]
|
||||
return body
|
||||
|
||||
|
||||
def _goal_status_payload(state: Any, *, default_message: str | None = None) -> Dict[str, Any]:
|
||||
"""Build localized-status style payload fields from a goal state."""
|
||||
if default_message is None:
|
||||
default_message = "No active goal. Set one with /goal <text>."
|
||||
if state is None:
|
||||
return {"message": default_message, "message_key": "goal_status_none"}
|
||||
status = str(getattr(state, "status", "") or "").strip()
|
||||
if status in ("cleared",):
|
||||
return {"message": default_message, "message_key": "goal_status_none"}
|
||||
turns_used = int(getattr(state, "turns_used", 0) or 0)
|
||||
max_turns = int(getattr(state, "max_turns", 0) or 0)
|
||||
goal = str(getattr(state, "goal", "") or "")
|
||||
if status == "active":
|
||||
return {
|
||||
"message": f"⊙ Goal (active, {turns_used}/{max_turns} turns): {goal}",
|
||||
"message_key": "goal_status_active",
|
||||
"message_args": [turns_used, max_turns, goal],
|
||||
}
|
||||
if status == "paused":
|
||||
reason = str(getattr(state, "paused_reason", "") or "")
|
||||
return {
|
||||
"message": f"⏸ Goal (paused, {turns_used}/{max_turns}{' — ' + reason if reason else ''}): {goal}",
|
||||
"message_key": "goal_status_paused",
|
||||
"message_args": [turns_used, max_turns, reason, goal],
|
||||
}
|
||||
if status == "done":
|
||||
return {
|
||||
"message": f"✓ Goal done ({turns_used}/{max_turns}): {goal}",
|
||||
"message_key": "goal_status_done",
|
||||
"message_args": [turns_used, max_turns, goal],
|
||||
}
|
||||
return {
|
||||
"message": f"Goal ({status}, {turns_used}/{max_turns}): {goal}",
|
||||
"message_args": [status, turns_used, max_turns, goal],
|
||||
}
|
||||
|
||||
|
||||
def _extract_goal_turns_from_message(message: str) -> tuple[int, int]:
|
||||
"""Best-effort extraction for continuation messages like '(1/20)'."""
|
||||
if not message:
|
||||
return 0, 0
|
||||
match = re.search(r"\((\d+)\s*/\s*(\d+)\)", message)
|
||||
if not match:
|
||||
return 0, 0
|
||||
try:
|
||||
return int(match.group(1)), int(match.group(2))
|
||||
except Exception:
|
||||
return 0, 0
|
||||
|
||||
|
||||
def _goal_decision_payload(
|
||||
decision: Dict[str, Any],
|
||||
state: Any,
|
||||
) -> Dict[str, Any]:
|
||||
"""Attach goal message i18n key/args to an evaluation decision."""
|
||||
if not isinstance(decision, dict):
|
||||
return decision
|
||||
status = str(decision.get("status") or "").strip()
|
||||
reason = str(decision.get("reason") or "").strip()
|
||||
turns_used = int(getattr(state, "turns_used", 0) or 0)
|
||||
max_turns = int(getattr(state, "max_turns", 0) or 0)
|
||||
if (turns_used, max_turns) == (0, 0):
|
||||
turns_used, max_turns = _extract_goal_turns_from_message(str(decision.get("message") or ""))
|
||||
|
||||
if status == "done":
|
||||
return {
|
||||
**decision,
|
||||
"message_key": "goal_achieved",
|
||||
"message_args": [reason],
|
||||
}
|
||||
if status == "paused":
|
||||
return {
|
||||
**decision,
|
||||
"message_key": "goal_paused_budget_exhausted",
|
||||
"message_args": [turns_used, max_turns],
|
||||
}
|
||||
if decision.get("should_continue"):
|
||||
return {
|
||||
**decision,
|
||||
"message_key": "goal_continuing",
|
||||
"message_args": [turns_used, max_turns, reason],
|
||||
}
|
||||
return decision
|
||||
|
||||
|
||||
def goal_state_snapshot(session_id: str, *, profile_home: str | Path | None = None) -> Any:
|
||||
"""Return a deep copy of current goal state for rollback before kickoff."""
|
||||
mgr = _manager(str(session_id or ""), profile_home=profile_home)
|
||||
@@ -355,24 +447,46 @@ def goal_command_payload(
|
||||
lower = text.lower()
|
||||
|
||||
if not text or lower == "status":
|
||||
return _payload(action="status", message=mgr.status_line(), state=getattr(mgr, "state", None))
|
||||
state = getattr(mgr, "state", None)
|
||||
status_payload = _goal_status_payload(state)
|
||||
return _payload(action="status", state=state, **status_payload)
|
||||
|
||||
if lower == "pause":
|
||||
state = mgr.pause(reason="user-paused")
|
||||
if state is None:
|
||||
return _payload(ok=False, action="pause", error="no_goal", message="No goal set.")
|
||||
return _payload(action="pause", message=f"⏸ Goal paused: {state.goal}", state=state)
|
||||
return _payload(
|
||||
ok=False,
|
||||
action="pause",
|
||||
error="no_goal",
|
||||
message="No goal set.",
|
||||
message_key="goal_no_goal",
|
||||
)
|
||||
return _payload(
|
||||
action="pause",
|
||||
message=f"⏸ Goal paused: {state.goal}",
|
||||
message_key="goal_paused",
|
||||
message_args=[str(state.goal)],
|
||||
state=state,
|
||||
)
|
||||
|
||||
if lower == "resume":
|
||||
state = mgr.resume()
|
||||
if state is None:
|
||||
return _payload(ok=False, action="resume", error="no_goal", message="No goal to resume.")
|
||||
return _payload(
|
||||
ok=False,
|
||||
action="resume",
|
||||
error="no_goal",
|
||||
message="No goal to resume.",
|
||||
message_key="goal_no_goal",
|
||||
)
|
||||
return _payload(
|
||||
action="resume",
|
||||
message=(
|
||||
f"▶ Goal resumed: {state.goal}\n"
|
||||
"Send a new message, or type continue, to kick it off."
|
||||
),
|
||||
message_key="goal_resumed",
|
||||
message_args=[str(state.goal)],
|
||||
state=state,
|
||||
)
|
||||
|
||||
@@ -382,6 +496,7 @@ def goal_command_payload(
|
||||
return _payload(
|
||||
action="clear",
|
||||
message="Goal cleared." if had else "No active goal.",
|
||||
message_key="goal_cleared" if had else "goal_no_goal",
|
||||
state=getattr(mgr, "state", None),
|
||||
)
|
||||
|
||||
@@ -408,6 +523,8 @@ def goal_command_payload(
|
||||
"I'll keep working until the goal is done, you pause/clear it, or the budget is exhausted.\n"
|
||||
"Controls: /goal status · /goal pause · /goal resume · /goal clear"
|
||||
),
|
||||
message_key="goal_set",
|
||||
message_args=[state.max_turns, state.goal],
|
||||
state=state,
|
||||
kickoff_prompt=state.goal,
|
||||
)
|
||||
@@ -486,4 +603,6 @@ def evaluate_goal_after_turn(
|
||||
decision.setdefault("should_continue", False)
|
||||
decision.setdefault("continuation_prompt", None)
|
||||
decision.setdefault("message", "")
|
||||
decision = dict(decision)
|
||||
decision = _goal_decision_payload(decision, getattr(mgr, "state", None))
|
||||
return decision
|
||||
|
||||
@@ -3320,6 +3320,7 @@ def _run_agent_streaming(
|
||||
'session_id': session_id,
|
||||
'state': 'evaluating',
|
||||
'message': 'Evaluating goal progress…',
|
||||
'message_key': 'goal_evaluating_progress',
|
||||
})
|
||||
_goal_decision = evaluate_goal_after_turn(
|
||||
session_id,
|
||||
@@ -3334,6 +3335,8 @@ def _run_agent_streaming(
|
||||
'session_id': session_id,
|
||||
'state': 'continuing' if decision.get('should_continue') else 'idle',
|
||||
'message': _goal_message,
|
||||
'message_key': decision.get('message_key') or ('goal_continuing' if _goal_message else ''),
|
||||
'message_args': decision.get('message_args') or [],
|
||||
'decision': decision,
|
||||
})
|
||||
if decision.get('should_continue'):
|
||||
@@ -3347,6 +3350,8 @@ def _run_agent_streaming(
|
||||
'continuation_prompt': continuation_prompt,
|
||||
'text': continuation_prompt,
|
||||
'message': _goal_message,
|
||||
'message_key': decision.get('message_key') or 'goal_continuing',
|
||||
'message_args': decision.get('message_args') or [],
|
||||
'decision': decision,
|
||||
})
|
||||
except Exception as _goal_exc:
|
||||
|
||||
+12
-2
@@ -639,7 +639,17 @@ async function cmdGoal(args){
|
||||
model_provider:S.session.model_provider||null,
|
||||
profile:S.activeProfile||S.session.profile||'default',
|
||||
})});
|
||||
const msg=String((r&&r.message)||'').trim();
|
||||
const msg = (() => {
|
||||
const raw = String((r && r.message) || '').trim();
|
||||
const key = String((r && r.message_key) || '').trim();
|
||||
const args = Array.isArray(r && r.message_args) ? r.message_args : [];
|
||||
if (raw.includes('\n')) return raw;
|
||||
if (key && typeof t === 'function') {
|
||||
const translated = String(t(key, ...args));
|
||||
if (translated && translated !== key) return translated;
|
||||
}
|
||||
return raw;
|
||||
})();
|
||||
if(msg){
|
||||
S.messages.push({role:'assistant',content:msg,_ts:Date.now()/1000,_goalStatus:true,_transient:true});
|
||||
renderMessages({preserveScroll:true});
|
||||
@@ -649,7 +659,7 @@ async function cmdGoal(args){
|
||||
S.toolCalls=[];
|
||||
if(typeof clearLiveToolCards==='function')clearLiveToolCards();
|
||||
appendThinking();setBusy(true);
|
||||
setComposerStatus('Working toward goal…');
|
||||
setComposerStatus(t('goal_working_toward'));
|
||||
S.activeStreamId=r.stream_id;
|
||||
if(S.session&&S.session.session_id===activeSid){
|
||||
S.session.active_stream_id=r.stream_id;
|
||||
|
||||
+136
-1
@@ -195,6 +195,21 @@ const LOCALES = {
|
||||
no_active_session: 'No active session',
|
||||
cmd_queue: 'Queue a message for the next turn',
|
||||
cmd_goal: 'Set or inspect a persistent goal',
|
||||
goal_evaluating_progress: 'Evaluating goal progress…',
|
||||
goal_working_toward: 'Working toward goal…',
|
||||
goal_continuing_toast: 'Continuing toward goal…',
|
||||
goal_status_none: 'No active goal. Set one with /goal <text>.',
|
||||
goal_status_active: (turns, max_turns, goal) => `⊙ Goal (active, ${turns}/${max_turns} turns): ${goal}`,
|
||||
goal_status_paused: (turns, max_turns, reason, goal) => `⏸ Goal (paused, ${turns}/${max_turns}${reason ? `, ${reason}` : ''}): ${goal}`,
|
||||
goal_status_done: (turns, max_turns, goal) => `✓ Goal done (${turns}/${max_turns}): ${goal}`,
|
||||
goal_set: (turns, goal) => `⊙ Goal set (${turns}-turn budget): ${goal}`,
|
||||
goal_paused: (goal) => `⏸ Goal paused: ${goal}`,
|
||||
goal_resumed: (goal) => `▶ Goal resumed: ${goal}`,
|
||||
goal_cleared: 'Goal cleared.',
|
||||
goal_no_goal: 'No active goal.',
|
||||
goal_achieved: (reason) => `✓ Goal achieved: ${reason}`,
|
||||
goal_paused_budget_exhausted: (turns, max_turns) => `⏸ Goal paused — ${turns}/${max_turns} turns used. Use /goal resume to keep going, or /goal clear to stop.`,
|
||||
goal_continuing: (turns, max_turns, reason) => `↻ Continuing toward goal (${turns}/${max_turns}): ${reason}`,
|
||||
cmd_interrupt: 'Cancel current turn and send a new message',
|
||||
cmd_steer: 'Inject a mid-turn correction without interrupting the agent',
|
||||
cmd_queue_no_msg: 'Usage: /queue <message>',
|
||||
@@ -1263,6 +1278,21 @@ const LOCALES = {
|
||||
no_active_session: 'アクティブなセッションがありません',
|
||||
cmd_queue: '次のターン用にメッセージをキュー',
|
||||
cmd_goal: '永続ゴールを設定または確認',
|
||||
goal_evaluating_progress: 'Evaluating goal progress…',
|
||||
goal_working_toward: 'Working toward goal…',
|
||||
goal_continuing_toast: 'Continuing toward goal…',
|
||||
goal_status_none: 'No active goal. Set one with /goal <text>.',
|
||||
goal_status_active: (turns, max_turns, goal) => `⊙ Goal (active, ${turns}/${max_turns} turns): ${goal}`,
|
||||
goal_status_paused: (turns, max_turns, reason, goal) => `⏸ Goal (paused, ${turns}/${max_turns}${reason ? `, ${reason}` : ''}): ${goal}`,
|
||||
goal_status_done: (turns, max_turns, goal) => `✓ Goal done (${turns}/${max_turns}): ${goal}`,
|
||||
goal_set: (turns, goal) => `⊙ Goal set (${turns}-turn budget): ${goal}`,
|
||||
goal_paused: (goal) => `⏸ Goal paused: ${goal}`,
|
||||
goal_resumed: (goal) => `▶ Goal resumed: ${goal}`,
|
||||
goal_cleared: 'Goal cleared.',
|
||||
goal_no_goal: 'No active goal.',
|
||||
goal_achieved: (reason) => `✓ Goal achieved: ${reason}`,
|
||||
goal_paused_budget_exhausted: (turns, max_turns) => `⏸ Goal paused — ${turns}/${max_turns} turns used. Use /goal resume to keep going, or /goal clear to stop.`,
|
||||
goal_continuing: (turns, max_turns, reason) => `↻ Continuing toward goal (${turns}/${max_turns}): ${reason}`,
|
||||
cmd_interrupt: '現在のターンをキャンセルして新規メッセージを送信',
|
||||
cmd_steer: 'エージェントを中断せずにターン中に修正を注入',
|
||||
cmd_queue_no_msg: '使い方: /queue <メッセージ>',
|
||||
@@ -2292,6 +2322,21 @@ const LOCALES = {
|
||||
no_active_session: 'Нет активной сессии',
|
||||
cmd_queue: 'Поставить сообщение в очередь на следующий оборот',
|
||||
cmd_goal: 'Задать или проверить постоянную цель',
|
||||
goal_evaluating_progress: 'Evaluating goal progress…',
|
||||
goal_working_toward: 'Working toward goal…',
|
||||
goal_continuing_toast: 'Continuing toward goal…',
|
||||
goal_status_none: 'No active goal. Set one with /goal <text>.',
|
||||
goal_status_active: (turns, max_turns, goal) => `⊙ Goal (active, ${turns}/${max_turns} turns): ${goal}`,
|
||||
goal_status_paused: (turns, max_turns, reason, goal) => `⏸ Goal (paused, ${turns}/${max_turns}${reason ? `, ${reason}` : ''}): ${goal}`,
|
||||
goal_status_done: (turns, max_turns, goal) => `✓ Goal done (${turns}/${max_turns}): ${goal}`,
|
||||
goal_set: (turns, goal) => `⊙ Goal set (${turns}-turn budget): ${goal}`,
|
||||
goal_paused: (goal) => `⏸ Goal paused: ${goal}`,
|
||||
goal_resumed: (goal) => `▶ Goal resumed: ${goal}`,
|
||||
goal_cleared: 'Goal cleared.',
|
||||
goal_no_goal: 'No active goal.',
|
||||
goal_achieved: (reason) => `✓ Goal achieved: ${reason}`,
|
||||
goal_paused_budget_exhausted: (turns, max_turns) => `⏸ Goal paused — ${turns}/${max_turns} turns used. Use /goal resume to keep going, or /goal clear to stop.`,
|
||||
goal_continuing: (turns, max_turns, reason) => `↻ Continuing toward goal (${turns}/${max_turns}): ${reason}`,
|
||||
cmd_interrupt: 'Прервать текущий оборот и отправить новое сообщение',
|
||||
cmd_steer: 'Направить агента исправлением (переходит к прерыванию)',
|
||||
cmd_queue_no_msg: 'Использование: /queue <сообщение>',
|
||||
@@ -3326,6 +3371,21 @@ const LOCALES = {
|
||||
no_active_session: 'No hay ninguna sesión activa',
|
||||
cmd_queue: 'Poner mensaje en cola para el siguiente turno',
|
||||
cmd_goal: 'Definir o consultar un objetivo persistente',
|
||||
goal_evaluating_progress: 'Evaluating goal progress…',
|
||||
goal_working_toward: 'Working toward goal…',
|
||||
goal_continuing_toast: 'Continuing toward goal…',
|
||||
goal_status_none: 'No active goal. Set one with /goal <text>.',
|
||||
goal_status_active: (turns, max_turns, goal) => `⊙ Goal (active, ${turns}/${max_turns} turns): ${goal}`,
|
||||
goal_status_paused: (turns, max_turns, reason, goal) => `⏸ Goal (paused, ${turns}/${max_turns}${reason ? `, ${reason}` : ''}): ${goal}`,
|
||||
goal_status_done: (turns, max_turns, goal) => `✓ Goal done (${turns}/${max_turns}): ${goal}`,
|
||||
goal_set: (turns, goal) => `⊙ Goal set (${turns}-turn budget): ${goal}`,
|
||||
goal_paused: (goal) => `⏸ Goal paused: ${goal}`,
|
||||
goal_resumed: (goal) => `▶ Goal resumed: ${goal}`,
|
||||
goal_cleared: 'Goal cleared.',
|
||||
goal_no_goal: 'No active goal.',
|
||||
goal_achieved: (reason) => `✓ Goal achieved: ${reason}`,
|
||||
goal_paused_budget_exhausted: (turns, max_turns) => `⏸ Goal paused — ${turns}/${max_turns} turns used. Use /goal resume to keep going, or /goal clear to stop.`,
|
||||
goal_continuing: (turns, max_turns, reason) => `↻ Continuing toward goal (${turns}/${max_turns}): ${reason}`,
|
||||
cmd_interrupt: 'Cancelar turno actual y enviar nuevo mensaje',
|
||||
cmd_steer: 'Inyectar una corrección a mitad del turno sin interrumpir al agente',
|
||||
cmd_queue_no_msg: 'Uso: /queue <mensaje>',
|
||||
@@ -4309,6 +4369,21 @@ const LOCALES = {
|
||||
model_scope_toast: 'Gilt für diesen Chat ab Ihrer nächsten Nachricht.',
|
||||
cmd_queue: 'Nachricht f\u00fcr den n\u00e4chsten Durchgang einreihen',
|
||||
cmd_goal: 'Ein dauerhaftes Ziel setzen oder prüfen',
|
||||
goal_evaluating_progress: 'Evaluating goal progress…',
|
||||
goal_working_toward: 'Working toward goal…',
|
||||
goal_continuing_toast: 'Continuing toward goal…',
|
||||
goal_status_none: 'No active goal. Set one with /goal <text>.',
|
||||
goal_status_active: (turns, max_turns, goal) => `⊙ Goal (active, ${turns}/${max_turns} turns): ${goal}`,
|
||||
goal_status_paused: (turns, max_turns, reason, goal) => `⏸ Goal (paused, ${turns}/${max_turns}${reason ? `, ${reason}` : ''}): ${goal}`,
|
||||
goal_status_done: (turns, max_turns, goal) => `✓ Goal done (${turns}/${max_turns}): ${goal}`,
|
||||
goal_set: (turns, goal) => `⊙ Goal set (${turns}-turn budget): ${goal}`,
|
||||
goal_paused: (goal) => `⏸ Goal paused: ${goal}`,
|
||||
goal_resumed: (goal) => `▶ Goal resumed: ${goal}`,
|
||||
goal_cleared: 'Goal cleared.',
|
||||
goal_no_goal: 'No active goal.',
|
||||
goal_achieved: (reason) => `✓ Goal achieved: ${reason}`,
|
||||
goal_paused_budget_exhausted: (turns, max_turns) => `⏸ Goal paused — ${turns}/${max_turns} turns used. Use /goal resume to keep going, or /goal clear to stop.`,
|
||||
goal_continuing: (turns, max_turns, reason) => `↻ Continuing toward goal (${turns}/${max_turns}): ${reason}`,
|
||||
cmd_interrupt: 'Aktuellen Durchgang abbrechen und neue Nachricht senden',
|
||||
cmd_steer: 'Korrektursignal einf\u00fcgen ohne Unterbrechung',
|
||||
cmd_queue_no_msg: 'Verwendung: /queue <Nachricht>',
|
||||
@@ -5027,7 +5102,7 @@ const LOCALES = {
|
||||
profile_gateway_stopped: 'Gateway gestoppt',
|
||||
profile_active: 'Aktiv',
|
||||
profile_no_configuration: 'Keine Konfiguration',
|
||||
profile_skill_count: '{count} Fähigkeiten',
|
||||
profile_skill_count: (count) => `${count} Fähigkeit${count === 1 ? '' : 'en'}`,
|
||||
profile_use: 'Verwenden',
|
||||
profile_switch_title: 'Profil wechseln',
|
||||
profile_delete_title: 'Profil löschen',
|
||||
@@ -5329,6 +5404,21 @@ const LOCALES = {
|
||||
no_active_session: '\u5f53\u524d\u6ca1\u6709\u6d3b\u52a8\u4f1a\u8bdd',
|
||||
cmd_queue: '\u5c06\u6d88\u606f\u52a0\u5165\u4e0b\u4e00\u8f6e\u7684\u961f\u5217',
|
||||
cmd_goal: '设置或查看持久目标',
|
||||
goal_evaluating_progress: 'Evaluating goal progress…',
|
||||
goal_working_toward: 'Working toward goal…',
|
||||
goal_continuing_toast: 'Continuing toward goal…',
|
||||
goal_status_none: 'No active goal. Set one with /goal <text>.',
|
||||
goal_status_active: (turns, max_turns, goal) => `⊙ Goal (active, ${turns}/${max_turns} turns): ${goal}`,
|
||||
goal_status_paused: (turns, max_turns, reason, goal) => `⏸ Goal (paused, ${turns}/${max_turns}${reason ? `, ${reason}` : ''}): ${goal}`,
|
||||
goal_status_done: (turns, max_turns, goal) => `✓ Goal done (${turns}/${max_turns}): ${goal}`,
|
||||
goal_set: (turns, goal) => `⊙ Goal set (${turns}-turn budget): ${goal}`,
|
||||
goal_paused: (goal) => `⏸ Goal paused: ${goal}`,
|
||||
goal_resumed: (goal) => `▶ Goal resumed: ${goal}`,
|
||||
goal_cleared: 'Goal cleared.',
|
||||
goal_no_goal: 'No active goal.',
|
||||
goal_achieved: (reason) => `✓ Goal achieved: ${reason}`,
|
||||
goal_paused_budget_exhausted: (turns, max_turns) => `⏸ Goal paused — ${turns}/${max_turns} turns used. Use /goal resume to keep going, or /goal clear to stop.`,
|
||||
goal_continuing: (turns, max_turns, reason) => `↻ Continuing toward goal (${turns}/${max_turns}): ${reason}`,
|
||||
cmd_interrupt: '\u53d6\u6d88\u5f53\u524d\u56de\u5408\u5e76\u53d1\u9001\u65b0\u6d88\u606f',
|
||||
cmd_steer: '\u7528\u7ea0\u6b63\u4fe1\u606f\u5f15\u5bfc\u4ee3\u7406\uff08\u56de\u9000\u4e3a\u4e2d\u65ad\uff09',
|
||||
cmd_queue_no_msg: '\u7528\u6cd5\uff1a/queue <\u6d88\u606f>',
|
||||
@@ -6846,6 +6936,21 @@ const LOCALES = {
|
||||
no_active_session: '\u7121\u6d3b\u8e8d\u6703\u8a71',
|
||||
cmd_queue: '\u5c07\u8a0a\u606f\u52a0\u5165\u4e0b\u4e00\u8f2a\u7684\u4f47\u5217',
|
||||
cmd_goal: '設定或查看持久目標',
|
||||
goal_evaluating_progress: 'Evaluating goal progress…',
|
||||
goal_working_toward: 'Working toward goal…',
|
||||
goal_continuing_toast: 'Continuing toward goal…',
|
||||
goal_status_none: 'No active goal. Set one with /goal <text>.',
|
||||
goal_status_active: (turns, max_turns, goal) => `⊙ Goal (active, ${turns}/${max_turns} turns): ${goal}`,
|
||||
goal_status_paused: (turns, max_turns, reason, goal) => `⏸ Goal (paused, ${turns}/${max_turns}${reason ? `, ${reason}` : ''}): ${goal}`,
|
||||
goal_status_done: (turns, max_turns, goal) => `✓ Goal done (${turns}/${max_turns}): ${goal}`,
|
||||
goal_set: (turns, goal) => `⊙ Goal set (${turns}-turn budget): ${goal}`,
|
||||
goal_paused: (goal) => `⏸ Goal paused: ${goal}`,
|
||||
goal_resumed: (goal) => `▶ Goal resumed: ${goal}`,
|
||||
goal_cleared: 'Goal cleared.',
|
||||
goal_no_goal: 'No active goal.',
|
||||
goal_achieved: (reason) => `✓ Goal achieved: ${reason}`,
|
||||
goal_paused_budget_exhausted: (turns, max_turns) => `⏸ Goal paused — ${turns}/${max_turns} turns used. Use /goal resume to keep going, or /goal clear to stop.`,
|
||||
goal_continuing: (turns, max_turns, reason) => `↻ Continuing toward goal (${turns}/${max_turns}): ${reason}`,
|
||||
cmd_interrupt: '\u53d6\u6d88\u7576\u524d\u56de\u5408\u4e26\u767c\u9001\u65b0\u8a0a\u606f',
|
||||
cmd_steer: '\u5728\u56de\u5408\u9032\u884c\u4e2d\u6ce8\u5165\u7d3a\u6b63\uff0c\u4e0d\u4e2d\u65b7\u4ee3\u7406',
|
||||
cmd_queue_no_msg: '\u7528\u6cd5\uff1a/queue <\u8a0a\u606f>',
|
||||
@@ -7371,6 +7476,21 @@ const LOCALES = {
|
||||
no_active_session: 'Nenhuma sessão ativa',
|
||||
cmd_queue: 'Enfileirar mensagem para o próximo turno',
|
||||
cmd_goal: 'Definir ou consultar uma meta persistente',
|
||||
goal_evaluating_progress: 'Evaluating goal progress…',
|
||||
goal_working_toward: 'Working toward goal…',
|
||||
goal_continuing_toast: 'Continuing toward goal…',
|
||||
goal_status_none: 'No active goal. Set one with /goal <text>.',
|
||||
goal_status_active: (turns, max_turns, goal) => `⊙ Goal (active, ${turns}/${max_turns} turns): ${goal}`,
|
||||
goal_status_paused: (turns, max_turns, reason, goal) => `⏸ Goal (paused, ${turns}/${max_turns}${reason ? `, ${reason}` : ''}): ${goal}`,
|
||||
goal_status_done: (turns, max_turns, goal) => `✓ Goal done (${turns}/${max_turns}): ${goal}`,
|
||||
goal_set: (turns, goal) => `⊙ Goal set (${turns}-turn budget): ${goal}`,
|
||||
goal_paused: (goal) => `⏸ Goal paused: ${goal}`,
|
||||
goal_resumed: (goal) => `▶ Goal resumed: ${goal}`,
|
||||
goal_cleared: 'Goal cleared.',
|
||||
goal_no_goal: 'No active goal.',
|
||||
goal_achieved: (reason) => `✓ Goal achieved: ${reason}`,
|
||||
goal_paused_budget_exhausted: (turns, max_turns) => `⏸ Goal paused — ${turns}/${max_turns} turns used. Use /goal resume to keep going, or /goal clear to stop.`,
|
||||
goal_continuing: (turns, max_turns, reason) => `↻ Continuing toward goal (${turns}/${max_turns}): ${reason}`,
|
||||
cmd_interrupt: 'Cancelar turno atual e enviar nova mensagem',
|
||||
cmd_steer: 'Injetar correção no meio do turno sem interromper',
|
||||
cmd_queue_no_msg: 'Uso: /queue <mensagem>',
|
||||
@@ -8315,6 +8435,21 @@ const LOCALES = {
|
||||
no_active_session: '활성 세션 없음',
|
||||
cmd_queue: 'Queue a message for the next turn',
|
||||
cmd_goal: '지속 목표를 설정하거나 확인',
|
||||
goal_evaluating_progress: 'Evaluating goal progress…',
|
||||
goal_working_toward: 'Working toward goal…',
|
||||
goal_continuing_toast: 'Continuing toward goal…',
|
||||
goal_status_none: 'No active goal. Set one with /goal <text>.',
|
||||
goal_status_active: (turns, max_turns, goal) => `⊙ Goal (active, ${turns}/${max_turns} turns): ${goal}`,
|
||||
goal_status_paused: (turns, max_turns, reason, goal) => `⏸ Goal (paused, ${turns}/${max_turns}${reason ? `, ${reason}` : ''}): ${goal}`,
|
||||
goal_status_done: (turns, max_turns, goal) => `✓ Goal done (${turns}/${max_turns}): ${goal}`,
|
||||
goal_set: (turns, goal) => `⊙ Goal set (${turns}-turn budget): ${goal}`,
|
||||
goal_paused: (goal) => `⏸ Goal paused: ${goal}`,
|
||||
goal_resumed: (goal) => `▶ Goal resumed: ${goal}`,
|
||||
goal_cleared: 'Goal cleared.',
|
||||
goal_no_goal: 'No active goal.',
|
||||
goal_achieved: (reason) => `✓ Goal achieved: ${reason}`,
|
||||
goal_paused_budget_exhausted: (turns, max_turns) => `⏸ Goal paused — ${turns}/${max_turns} turns used. Use /goal resume to keep going, or /goal clear to stop.`,
|
||||
goal_continuing: (turns, max_turns, reason) => `↻ Continuing toward goal (${turns}/${max_turns}): ${reason}`,
|
||||
cmd_interrupt: 'Cancel current turn and send a new message',
|
||||
cmd_steer: 'Inject a mid-turn correction without interrupting the agent',
|
||||
cmd_queue_no_msg: 'Usage: /queue <message>',
|
||||
|
||||
+18
-3
@@ -896,17 +896,30 @@ function attachLiveStream(activeSid, streamId, uploaded=[], options={}){
|
||||
}catch(_){}
|
||||
});
|
||||
|
||||
function _resolveGoalMessage(d){
|
||||
const key=String(d && d.message_key ? d.message_key : '').trim();
|
||||
const args=Array.isArray(d && d.message_args) ? d.message_args : [];
|
||||
const raw=String(d&&d.message||'').trim();
|
||||
if(key && typeof t==='function'){
|
||||
try{
|
||||
const translated=String(t(key,...args));
|
||||
if(translated && translated!==key)return translated;
|
||||
}catch(_){}
|
||||
}
|
||||
return raw;
|
||||
}
|
||||
|
||||
source.addEventListener('goal',e=>{
|
||||
try{
|
||||
const d=JSON.parse(e.data||'{}');
|
||||
if((d.session_id||activeSid)!==activeSid) return;
|
||||
const goalState=String(d.state||'').trim();
|
||||
const goalEvaluatingMessage='Evaluating goal progress…';
|
||||
const goalEvaluatingMessage=t('goal_evaluating_progress');
|
||||
if(goalState==='evaluating'){
|
||||
setComposerStatus(goalEvaluatingMessage);
|
||||
return;
|
||||
}
|
||||
const msg=String(d.message||'').trim();
|
||||
const msg=_resolveGoalMessage(d);
|
||||
if(!msg)return;
|
||||
_latestGoalStatus={message:msg,decision:d.decision||null,state:goalState||null};
|
||||
setComposerStatus(msg);
|
||||
@@ -927,7 +940,9 @@ function attachLiveStream(activeSid, streamId, uploaded=[], options={}){
|
||||
model_provider:S.session&&S.session.model_provider||null,
|
||||
profile:S.activeProfile||'default',
|
||||
};
|
||||
showToast('Continuing toward goal…',2200);
|
||||
const toast=t('goal_continuing_toast');
|
||||
const cmsg=_resolveGoalMessage(d);
|
||||
showToast((toast&&cmsg&&cmsg!==toast)?cmsg.split('\n')[0]:toast,2200);
|
||||
}catch(_){}
|
||||
});
|
||||
|
||||
|
||||
@@ -68,10 +68,18 @@ def test_goal_command_payload_matches_gateway_controls(monkeypatch):
|
||||
set_goal = webui_goals.goal_command_payload("sid-123", "ship the feature")
|
||||
|
||||
assert status["message"] == "No active goal. Set one with /goal <text>."
|
||||
assert status["message_key"] == "goal_status_none"
|
||||
assert pause["message"] == "⏸ Goal paused: ship the feature"
|
||||
assert pause["message_key"] == "goal_paused"
|
||||
assert pause["message_args"] == ["ship the feature"]
|
||||
assert resume["message"].startswith("▶ Goal resumed: ship the feature")
|
||||
assert resume["message_key"] == "goal_resumed"
|
||||
assert resume["message_args"] == ["ship the feature"]
|
||||
assert clear["message"] == "Goal cleared."
|
||||
assert clear["message_key"] == "goal_cleared"
|
||||
assert set_goal["action"] == "set"
|
||||
assert set_goal["message_key"] == "goal_set"
|
||||
assert set_goal["message_args"] == [20, "ship the feature"]
|
||||
assert set_goal["kickoff_prompt"] == "ship the feature"
|
||||
assert "⊙ Goal set (20-turn budget): ship the feature" in set_goal["message"]
|
||||
assert ("set", "ship the feature") in calls
|
||||
@@ -145,6 +153,8 @@ def test_goal_continuation_decision_emits_status_and_normal_user_prompt(monkeypa
|
||||
|
||||
decision = webui_goals.evaluate_goal_after_turn("sid-123", "not done yet", user_initiated=False)
|
||||
|
||||
assert decision["message_key"] == "goal_continuing"
|
||||
assert decision["message_args"] == [1, 20, "one step remains"]
|
||||
assert decision["message"].startswith("↻ Continuing toward goal")
|
||||
assert decision["should_continue"] is True
|
||||
assert decision["continuation_prompt"].startswith("[Continuing toward your standing goal]")
|
||||
@@ -266,7 +276,7 @@ def test_frontend_has_goal_slash_command_and_status_event_handler():
|
||||
|
||||
def test_frontend_goal_evaluating_state_uses_calm_composer_indicator():
|
||||
assert "const goalState=String(d.state||'').trim();" in MESSAGES_JS
|
||||
assert "const goalEvaluatingMessage='Evaluating goal progress…';" in MESSAGES_JS
|
||||
assert "t('goal_evaluating_progress')" in MESSAGES_JS
|
||||
assert "if(goalState==='evaluating')" in MESSAGES_JS
|
||||
assert "setComposerStatus(goalEvaluatingMessage);" in MESSAGES_JS
|
||||
assert "return;" in MESSAGES_JS
|
||||
|
||||
Reference in New Issue
Block a user